From 054f13e32f0ca77915d472885841298edea91a3c Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Sun, 30 Jul 2023 22:25:26 +0800 Subject: [PATCH] Support IOMMU page fault reporting --- .../src/arch/x86/iommu/context_table.rs | 2 +- .../jinux-frame/src/arch/x86/iommu/fault.rs | 206 ++++++++++++++++++ .../jinux-frame/src/arch/x86/iommu/mod.rs | 96 +------- .../src/arch/x86/iommu/remapping.rs | 184 ++++++++++++++++ framework/jinux-frame/src/arch/x86/mod.rs | 2 +- 5 files changed, 399 insertions(+), 91 deletions(-) create mode 100644 framework/jinux-frame/src/arch/x86/iommu/fault.rs create mode 100644 framework/jinux-frame/src/arch/x86/iommu/remapping.rs diff --git a/framework/jinux-frame/src/arch/x86/iommu/context_table.rs b/framework/jinux-frame/src/arch/x86/iommu/context_table.rs index 59fd859e4..ce215f025 100644 --- a/framework/jinux-frame/src/arch/x86/iommu/context_table.rs +++ b/framework/jinux-frame/src/arch/x86/iommu/context_table.rs @@ -137,7 +137,7 @@ impl RootTable { } let address = page_table.root_paddr(); context_table.page_tables.insert(address, page_table); - let entry = ContextEntry(address as u128 | 3 | 0x1_0000_0000_0000_0000); + let entry = ContextEntry(address as u128 | 1 | 0x1_0000_0000_0000_0000); context_table .entries_frame .write_val::( diff --git a/framework/jinux-frame/src/arch/x86/iommu/fault.rs b/framework/jinux-frame/src/arch/x86/iommu/fault.rs new file mode 100644 index 000000000..3f55fe6fe --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/iommu/fault.rs @@ -0,0 +1,206 @@ +use core::fmt::Debug; + +use alloc::vec::Vec; +use bitflags::bitflags; +use log::info; +use spin::Once; +use trapframe::TrapFrame; +use volatile::{access::ReadWrite, Volatile}; + +use crate::{trap::IrqAllocateHandle, vm::Vaddr}; + +use super::remapping::Capability; + +#[derive(Debug)] +pub struct FaultEventRegisters { + status: Volatile<&'static mut u32, ReadWrite>, + /// bit31: Interrupt Mask; bit30: Interrupt Pending. + control: Volatile<&'static mut u32, ReadWrite>, + data: Volatile<&'static mut u32, ReadWrite>, + address: Volatile<&'static mut u32, ReadWrite>, + upper_address: Volatile<&'static mut u32, ReadWrite>, + recordings: Vec>, + + fault_irq: IrqAllocateHandle, +} + +impl FaultEventRegisters { + pub fn status(&self) -> FaultStatus { + FaultStatus::from_bits_truncate(self.status.read()) + } + + /// # Safety + /// + /// User must ensure the base_register_vaddr is read from DRHD + unsafe fn new(base_register_vaddr: Vaddr) -> Self { + let capability = Volatile::new_read_only(&*((base_register_vaddr + 0x08) as *const u64)); + let length = ((capability.read() & Capability::NFR.bits()) >> 40) + 1; + let mut recordings = Vec::with_capacity(length as usize); + let offset = (capability.read() & 0x3_ff00_0000) >> 24; + for i in 0..length { + recordings.push(Volatile::new( + &mut *((base_register_vaddr + 16 * (offset + i) as usize) as *mut u128), + )) + } + let status = Volatile::new(&mut *((base_register_vaddr + 0x34) as *mut u32)); + let mut control = Volatile::new(&mut *((base_register_vaddr + 0x38) as *mut u32)); + let mut data = Volatile::new(&mut *((base_register_vaddr + 0x3c) as *mut u32)); + let mut address = Volatile::new(&mut *((base_register_vaddr + 0x40) as *mut u32)); + let upper_address = Volatile::new(&mut *((base_register_vaddr + 0x44) as *mut u32)); + let mut fault_irq = crate::trap::allocate_irq().unwrap(); + + // Set page fault interrupt vector and address + data.write(fault_irq.num() as u32); + address.write(0xFEE0_0000); + control.write(0); + fault_irq.on_active(iommu_page_fault_handler); + FaultEventRegisters { + status, + control, + data, + address, + upper_address, + recordings, + fault_irq, + } + } +} + +pub struct FaultRecording(u128); + +impl FaultRecording { + pub fn fault(&self) -> bool { + self.0 & (1 << 127) != 0 + } + + pub fn request_type(&self) -> FaultRequestType { + // bit 126 and bit 92 + let t1 = ((self.0 & (1 << 126)) >> 125) as u8; + let t2 = ((self.0 & (1 << 92)) >> 92) as u8; + let typ = t1 + t2; + match typ { + 0 => FaultRequestType::Write, + 1 => FaultRequestType::Page, + 2 => FaultRequestType::Read, + 3 => FaultRequestType::AtomicOp, + _ => unreachable!(), + } + } + + pub fn address_type(&self) -> FaultAddressType { + match self.0 & (3 << 124) { + 0 => FaultAddressType::UntranslatedRequest, + 1 => FaultAddressType::TranslationRequest, + 2 => FaultAddressType::TranslatedRequest, + _ => unreachable!(), + } + } + + pub fn source_identifier(&self) -> u16 { + // bit 79:64 + ((self.0 & 0xFFFF_0000_0000_0000_0000) >> 64) as u16 + } + + /// If fault reason is one of the address translation fault conditions, this field contains bits 63:12 + /// of the page address in the faulted request. + /// + /// If fault reason is interrupt-remapping fault conditions other than fault reash 0x25, bits 63:48 + /// indicate the interrupt index computed for the faulted interrupt request, and bits 47:12 are cleared. + /// + /// If fault reason is interrupt-remapping fault conditions of blocked compatibility mode interrupt (fault reason 0x25), + /// this field is undefined. + pub fn fault_info(&self) -> u64 { + // bit 63:12 + ((self.0 & 0xFFFF_FFFF_FFFF_F000) >> 12) as u64 + } + + pub fn pasid_value(&self) -> u32 { + // bit 123:104 + ((self.0 & 0xF_FFFF0_0000_0000_0000_0000_0000_0000) >> 104) as u32 + } + + pub fn fault_reason(&self) -> u8 { + // bit 103:96 + ((self.0 & 0xF_0000_0000_0000_0000_0000_0000) >> 96) as u8 + } + + pub fn pasid_present(&self) -> bool { + // bit 95 + (self.0 & 0x8000_0000_0000_0000_0000_0000) != 0 + } + + pub fn execute_permission_request(&self) -> bool { + // bit 94 + (self.0 & 0x4000_0000_0000_0000_0000_0000) != 0 + } + + pub fn privilege_mode_request(&self) -> bool { + // bit 93 + (self.0 & 0x2000_0000_0000_0000_0000_0000) != 0 + } +} + +impl Debug for FaultRecording { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("FaultRecording") + .field("Fault", &self.fault()) + .field("Request type", &self.request_type()) + .field("Address type", &self.address_type()) + .field("Source identifier", &self.source_identifier()) + .field("Fault Reson", &self.fault_reason()) + .field("Fault info", &self.fault_info()) + .field("Raw", &self.0) + .finish() + } +} + +#[derive(Debug)] +#[repr(u8)] +pub enum FaultRequestType { + Write = 0, + Page = 1, + Read = 2, + AtomicOp = 3, +} + +#[derive(Debug)] +#[repr(u8)] +pub enum FaultAddressType { + UntranslatedRequest = 0, + TranslationRequest = 1, + TranslatedRequest = 2, +} + +bitflags! { + pub struct FaultStatus : u32{ + /// Primary Fault Overflow, indicates overflow of the fault recording registers. + const PFO = 1 << 0; + /// Primary Pending Fault, indicates there are one or more pending faults logged in the fault recording registers. + const PPF = 1 << 1; + /// Invalidation Queue Error. + const IQE = 1 << 4; + /// Invalidation Completion Error. Hardware received an unexpected or invalid Device-TLB invalidation completion. + const ICE = 1 << 5; + /// Invalidation Time-out Error. Hardware detected a Device-TLB invalidation completion time-out. + const ITE = 1 << 6; + /// Fault Record Index, valid only when PPF field is set. This field indicates the index (from base) of the fault recording register + /// to which the first pending fault was recorded when the PPF field was Set by hardware. + const FRI = (0xFF) << 8; + } +} + +pub(super) static FAULT_EVENT_REGS: Once = Once::new(); + +/// # Safety +/// +/// User must ensure the base_register_vaddr is read from DRHD +pub(super) unsafe fn init(base_register_vaddr: Vaddr) { + FAULT_EVENT_REGS.call_once(|| FaultEventRegisters::new(base_register_vaddr)); +} + +fn iommu_page_fault_handler(frame: &TrapFrame) { + let fault_event = FAULT_EVENT_REGS.get().unwrap(); + let index = (fault_event.status().bits & FaultStatus::FRI.bits) >> 8; + let recording = FaultRecording(*(&fault_event.recordings[index as usize].read())); + info!("Catch iommu page fault, recording:{:x?}", recording) +} diff --git a/framework/jinux-frame/src/arch/x86/iommu/mod.rs b/framework/jinux-frame/src/arch/x86/iommu/mod.rs index e1441a046..ecba35330 100644 --- a/framework/jinux-frame/src/arch/x86/iommu/mod.rs +++ b/framework/jinux-frame/src/arch/x86/iommu/mod.rs @@ -1,30 +1,20 @@ mod context_table; +mod fault; +mod remapping; mod second_stage; -use log::debug; +use log::info; use spin::{Mutex, Once}; use crate::{ - arch::{ - iommu::{context_table::RootTable, second_stage::PageTableEntry}, - x86::kernel::acpi::{ - dmar::{Dmar, Remapping}, - ACPI_TABLES, - }, - }, + arch::iommu::{context_table::RootTable, second_stage::PageTableEntry}, bus::pci::PciDeviceLocation, vm::{ - paddr_to_vaddr, page_table::{PageTableConfig, PageTableError}, Paddr, PageTable, Vaddr, }, }; -use volatile::{ - access::{ReadOnly, ReadWrite, WriteOnly}, - Volatile, -}; - #[derive(Debug)] pub enum IommuError { NoIommu, @@ -83,11 +73,8 @@ pub(crate) fn unmap(vaddr: Vaddr) -> Result<(), IommuError> { }) } -pub fn init() -> Result<(), IommuError> { - let mut remapping_reg = RemappingRegisters::new().ok_or(IommuError::NoIommu)?; - +pub(crate) fn init() -> Result<(), IommuError> { let mut root_table = RootTable::new(); - // For all PCI Device, use the same page table. let page_table: PageTable = PageTable::new(PageTableConfig { address_width: crate::vm::page_table::AddressWidth::Level3PageTable, @@ -95,79 +82,10 @@ pub fn init() -> Result<(), IommuError> { for table in PciDeviceLocation::all() { root_table.specify_device_page_table(table, page_table.clone()) } - - let paddr = root_table.paddr(); - - // write remapping register - remapping_reg.root_table_address.write(paddr as u64); - // start writing - remapping_reg.global_command.write(0x4000_0000); - // wait until complete - while remapping_reg.global_status.read() & 0x4000_0000 == 0 {} - - // enable iommu - remapping_reg.global_command.write(0x8000_0000); - - debug!("IOMMU registers:{:#x?}", remapping_reg); - + remapping::init(&root_table)?; PAGE_TABLE.call_once(|| Mutex::new(root_table)); + info!("IOMMU enabled"); Ok(()) } -#[derive(Debug)] -#[repr(C)] -struct RemappingRegisters { - version: Volatile<&'static u32, ReadOnly>, - capability: Volatile<&'static u64, ReadOnly>, - extended_capability: Volatile<&'static u64, ReadOnly>, - global_command: Volatile<&'static mut u32, WriteOnly>, - global_status: Volatile<&'static u32, ReadOnly>, - root_table_address: Volatile<&'static mut u64, ReadWrite>, - context_command: Volatile<&'static mut u64, ReadWrite>, -} - -impl RemappingRegisters { - /// Create a instance from base address - fn new() -> Option { - let dmar = Dmar::new()?; - let acpi_table_lock = ACPI_TABLES.get().unwrap().lock(); - - debug!("DMAR:{:#x?}", dmar); - let base_address = { - let mut addr = 0; - for remapping in dmar.remapping_iter() { - match remapping { - Remapping::Drhd(drhd) => addr = drhd.register_base_addr(), - _ => {} - } - } - if addr == 0 { - panic!("There should be a DRHD structure in the DMAR table"); - } - addr - }; - - let vaddr = paddr_to_vaddr(base_address as usize); - // Safety: All offsets and sizes are strictly adhered to in the manual, and the base address is obtained from Drhd. - unsafe { - let version = Volatile::new_read_only(&*(vaddr as *const u32)); - let capability = Volatile::new_read_only(&*((vaddr + 0x08) as *const u64)); - let extended_capability = Volatile::new_read_only(&*((vaddr + 0x10) as *const u64)); - let global_command = Volatile::new_write_only(&mut *((vaddr + 0x18) as *mut u32)); - let global_status = Volatile::new_read_only(&*((vaddr + 0x1C) as *const u32)); - let root_table_address = Volatile::new(&mut *((vaddr + 0x20) as *mut u64)); - let context_command = Volatile::new(&mut *((vaddr + 0x28) as *mut u64)); - Some(Self { - version, - capability, - extended_capability, - global_command, - global_status, - root_table_address, - context_command, - }) - } - } -} - static PAGE_TABLE: Once> = Once::new(); diff --git a/framework/jinux-frame/src/arch/x86/iommu/remapping.rs b/framework/jinux-frame/src/arch/x86/iommu/remapping.rs new file mode 100644 index 000000000..ca2a2165c --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/iommu/remapping.rs @@ -0,0 +1,184 @@ +use bitflags::bitflags; +use log::debug; +use spin::Once; +use volatile::{ + access::{ReadOnly, ReadWrite, WriteOnly}, + Volatile, +}; + +use crate::{ + arch::{ + iommu::fault, + x86::kernel::acpi::{ + dmar::{Dmar, Remapping}, + ACPI_TABLES, + }, + }, + vm::paddr_to_vaddr, +}; + +use super::{context_table::RootTable, IommuError}; + +#[derive(Debug)] +pub struct RemappingRegisters { + version: Volatile<&'static u32, ReadOnly>, + capability: Volatile<&'static u64, ReadOnly>, + extended_capability: Volatile<&'static u64, ReadOnly>, + global_command: Volatile<&'static mut u32, WriteOnly>, + global_status: Volatile<&'static u32, ReadOnly>, + root_table_address: Volatile<&'static mut u64, ReadWrite>, + context_command: Volatile<&'static mut u64, ReadWrite>, +} + +impl RemappingRegisters { + pub fn capability(&self) -> Capability { + Capability::from_bits_truncate(self.capability.read()) + } + + /// Create a instance from base address + fn new(root_table: &RootTable) -> Option { + let dmar = Dmar::new()?; + let acpi_table_lock = ACPI_TABLES.get().unwrap().lock(); + + debug!("DMAR:{:#x?}", dmar); + let base_address = { + let mut addr = 0; + for remapping in dmar.remapping_iter() { + match remapping { + Remapping::Drhd(drhd) => addr = drhd.register_base_addr(), + _ => {} + } + } + if addr == 0 { + panic!("There should be a DRHD structure in the DMAR table"); + } + addr + }; + + let vaddr: usize = paddr_to_vaddr(base_address as usize); + // Safety: All offsets and sizes are strictly adhered to in the manual, and the base address is obtained from Drhd. + let mut remapping_reg = unsafe { + fault::init(vaddr); + let version = Volatile::new_read_only(&*(vaddr as *const u32)); + let capability = Volatile::new_read_only(&*((vaddr + 0x08) as *const u64)); + let extended_capability: Volatile<&u64, ReadOnly> = + Volatile::new_read_only(&*((vaddr + 0x10) as *const u64)); + let global_command = Volatile::new_write_only(&mut *((vaddr + 0x18) as *mut u32)); + let global_status = Volatile::new_read_only(&*((vaddr + 0x1C) as *const u32)); + let root_table_address = Volatile::new(&mut *((vaddr + 0x20) as *mut u64)); + let context_command = Volatile::new(&mut *((vaddr + 0x28) as *mut u64)); + Self { + version, + capability, + extended_capability, + global_command, + global_status, + root_table_address, + context_command, + } + }; + + // write remapping register + remapping_reg + .root_table_address + .write(root_table.paddr() as u64); + // start writing + remapping_reg.global_command.write(0x4000_0000); + // wait until complete + while remapping_reg.global_status.read() & 0x4000_0000 == 0 {} + + // enable iommu + remapping_reg.global_command.write(0x8000_0000); + + debug!("IOMMU registers:{:#x?}", remapping_reg); + + Some(remapping_reg) + } +} + +bitflags! { + pub struct Capability : u64{ + /// Number of domain support. + /// + /// ```norun + /// 0 => 4-bit domain-ids with support for up to 16 domains. + /// 1 => 6-bit domain-ids with support for up to 64 domains. + /// 2 => 8-bit domain-ids with support for up to 256 domains. + /// 3 => 10-bit domain-ids with support for up to 1024 domains. + /// 4 => 12-bit domain-ids with support for up to 4K domains. + /// 5 => 14-bit domain-ids with support for up to 16K domains. + /// 6 => 16-bit domain-ids with support for up to 64K domains. + /// 7 => Reserved. + /// ``` + const ND = 0x7 << 0; + /// Required Write-Buffer Flushing. + const RWBF = 1 << 4; + /// Protected Low-Memory Region + const PLMR = 1 << 5; + /// Protected High-Memory Region + const PHMR = 1 << 6; + /// Caching Mode + const CM = 1 << 7; + /// Supported Adjusted Guest Address Widths. + /// ```norun + /// 0/4 => Reserved + /// 1 => 39-bit AGAW (3-level page-table) + /// 2 => 48-bit AGAW (4-level page-table) + /// 3 => 57-bit AGAW (5-level page-table) + /// ``` + const SAGAW = 0x1F << 8; + /// Maximum Guest Address Width. + /// The maximum guest physical address width supported by second-stage translation in remapping hardware. + /// MGAW is computed as (N+1), where N is the valued reported in this field. + const MGAW = 0x3F << 16; + /// Zero Length Read. Whether the remapping hardware unit supports zero length + /// DMA read requests to write-only pages. + const ZLR = 1 << 22; + /// Fault-recording Register offset, specifies the offset of the first fault recording register + /// relative to the register base address of this remapping hardware unit. + /// + /// If the register base address is X, and the value reported in this field + /// is Y, the address for the first fault recording register is calculated as X+(16*Y). + const FRO = 0x3FF << 24; + /// Second Stage Large Page Support. + /// ```norun + /// 2/3 => Reserved + /// 0 => 21-bit offset to page frame(2MB) + /// 1 => 30-bit offset to page frame(1GB) + /// ``` + const SSLPS = 0xF << 34; + /// Page Selective Invalidation. Whether hardware supports page-selective invalidation for IOTLB. + const PSI = 1 << 39; + /// Number of Fault-recording Registers. Number of fault recording registers is computed as N+1. + const NFR = 0xFF << 40; + /// Maximum Address Mask Value, indicates the maximum supported value for the + /// Address Mask (AM) field in the Invalidation Address register + /// (IVA_REG), and IOTLB Invalidation Descriptor (iotlb_inv_dsc) used + /// for invalidations of second-stage translation. + const MAMV = 0x3F << 48; + /// Write Draining. + const DWD = 1 << 54; + /// Read Draining. + const DRD = 1 << 55; + /// First Stage 1-GByte Page Support. + const FS1GP = 1 << 56; + /// Posted Interrupts Support. + const PI = 1 << 59; + /// First Stage 5-level Paging Support. + const FS5LP = 1 << 60; + /// Enhanced Command Support. + const ECMDS = 1 << 61; + /// Enhanced Set Interrupt Remap Table Pointer Support. + const ESIRTPS = 1 << 62; + /// Enhanced Set Root Table Pointer Support. + const ESRTPS = 1 << 63; + } +} + +pub static REMAPPING_REGS: Once = Once::new(); + +pub(super) fn init(root_table: &RootTable) -> Result<(), IommuError> { + let remapping_regs = RemappingRegisters::new(root_table).ok_or(IommuError::NoIommu)?; + REMAPPING_REGS.call_once(|| remapping_regs); + Ok(()) +} diff --git a/framework/jinux-frame/src/arch/x86/mod.rs b/framework/jinux-frame/src/arch/x86/mod.rs index 217c173b3..4b7dd298a 100644 --- a/framework/jinux-frame/src/arch/x86/mod.rs +++ b/framework/jinux-frame/src/arch/x86/mod.rs @@ -33,11 +33,11 @@ pub(crate) fn after_all_init() { kernel::pic::enable(); } } + timer::init(); match iommu::init() { Ok(_) => {} Err(err) => warn!("IOMMU initialization error:{:?}", err), } - timer::init(); // Some driver like serial may use PIC kernel::pic::init(); }