diff --git a/Cargo.toml b/Cargo.toml index f9e7a481f..c5f0d0e0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,12 @@ members = [ ] [workspace.dependencies.sel4-capdl-initializer] -git = "https://github.com/seL4/rust-sel4" -rev = "cf43f5d0a97854e9916cc999fdd97ff3b5fbea7b" +git = "https://github.com/au-ts/rust-sel4" +branch = "cheng/iommu_support" [workspace.dependencies.sel4-capdl-initializer-types] -git = "https://github.com/seL4/rust-sel4" -rev = "cf43f5d0a97854e9916cc999fdd97ff3b5fbea7b" +git = "https://github.com/au-ts/rust-sel4" +branch = "cheng/iommu_support" [profile.release.package.microkit-tool] strip = true diff --git a/build_sdk.py b/build_sdk.py index 5ff324b1f..1f6050a3b 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -68,7 +68,7 @@ "KernelPlatform": "pc99", "KernelX86MicroArch": "generic", # See https://github.com/seL4/microkit/issues/418 for details. - "KernelIOMMU": False, + "KernelIOMMU": True, } | DEFAULT_KERNEL_OPTIONS diff --git a/tool/microkit/src/capdl/builder.rs b/tool/microkit/src/capdl/builder.rs index 44754e8e6..a801ee847 100644 --- a/tool/microkit/src/capdl/builder.rs +++ b/tool/microkit/src/capdl/builder.rs @@ -11,12 +11,13 @@ use std::{ }; use sel4_capdl_initializer_types::{ - object, CapTableEntry, Fill, FillEntry, FillEntryContent, NamedObject, Object, ObjectId, Spec, - Word, + object::{self}, + CapTableEntry, Fill, FillEntry, FillEntryContent, NamedObject, Object, ObjectId, Spec, Word, }; use crate::{ capdl::{ + iomem::{create_iospace, map_io_page}, irq::create_irq_handler_cap, memory::{create_vspace, create_vspace_ept, map_page}, spec::{capdl_obj_physical_size_bits, BytesContent, ElfContent, FillContent}, @@ -24,7 +25,7 @@ use crate::{ }, elf::ElfFile, sdf::{ - CpuCore, SysMap, SysMapPerms, SystemDescription, BUDGET_DEFAULT, MONITOR_PD_NAME, + CpuCore, IOMemMap, SysMap, SysMapPerms, SystemDescription, BUDGET_DEFAULT, MONITOR_PD_NAME, MONITOR_PRIORITY, }, sel4::{Arch, Config, PageSize}, @@ -381,6 +382,36 @@ fn map_memory_region( } } +/// Map frames to the IOSpace +fn map_io_memory_region( + spec_container: &mut CapDLSpecContainer, + sel4_config: &Config, + pd_name: &str, + map: &IOMemMap, + target_iospace: ObjectId, + frames: &[ObjectId], +) { + let mut cur_vaddr = map.ioaddr; + let read = map.perms & SysMapPerms::Read as u8 != 0; + let write = map.perms & SysMapPerms::Write as u8 != 0; + + for frame_obj_id in frames.iter() { + // Make a cap for this frame. + let frame_cap = capdl_util_make_frame_cap(*frame_obj_id, read, write, false, false); + // Map it into this PD IOSpace. + map_io_page( + spec_container, + sel4_config, + pd_name, + target_iospace, + frame_cap, + cur_vaddr, + ) + .unwrap(); + cur_vaddr += PageSize::Small as u64; + } +} + /// Build a CapDL Spec according to the System Description File. pub fn build_capdl_spec( kernel_config: &Config, @@ -673,7 +704,46 @@ pub fn build_capdl_spec( cur_stack_vaddr += PageSize::Small as u64; } - // Step 3-4 Create Scheduling Context + // Step 3-4 Create and Map IO Page Table Structure + if kernel_config.iommu { + let mut pci_to_iospace: HashMap<(u8, u8, u8), ObjectId> = HashMap::new(); + for iomap in &pd.iomaps { + let iospace_id = + match pci_to_iospace.get(&(iomap.pci_bus, iomap.pci_dev, iomap.dev_func)) { + Some(iospace_id) => *iospace_id, + None => { + let iospace_id = create_iospace( + &mut spec_container, + kernel_config, + &pd.name, + iomap.pci_bus, + iomap.pci_dev, + iomap.dev_func, + pd_global_idx, + ); + + pci_to_iospace + .insert((iomap.pci_bus, iomap.pci_dev, iomap.dev_func), iospace_id); + iospace_id + } + }; + + let frames = mr_name_to_frames.get(&iomap.name).unwrap(); + + map_io_memory_region( + &mut spec_container, + kernel_config, + &pd.name, + iomap, + iospace_id, + frames, + ); + } + } else if !pd.iomaps.is_empty() { + return Err("ERROR: System description file contains iomap elements despite IOMMU is not supported!".to_string()); + } + + // Step 3-5 Create Scheduling Context let pd_sc_obj_id = capdl_util_make_sc_obj( &mut spec_container, &pd.name, @@ -688,7 +758,7 @@ pub fn build_capdl_spec( pd_sc_cap, )); - // Step 3-5 Create fault Endpoint cap to parent/monitor + // Step 3-6 Create fault Endpoint cap to parent/monitor let pd_fault_ep_cap = if let Some(pd_parent) = pd.parent { assert!(pd_global_idx > pd_parent); let badge: u64 = FAULT_BADGE | pd.id.unwrap(); @@ -720,7 +790,7 @@ pub fn build_capdl_spec( pd_fault_ep_cap.clone(), )); - // Step 3-6 Create cap to Monitor's endpoint for passive PDs. + // Step 3-7 Create cap to Monitor's endpoint for passive PDs. if pd.passive { let pd_monitor_ep_cap = capdl_util_make_endpoint_cap( mon_fault_ep_obj_id, @@ -735,7 +805,7 @@ pub fn build_capdl_spec( )); } - // Step 3-7 Create endpoint object for the PD if it has children or can receive PPCs, else it will be a notification + // Step 3-8 Create endpoint object for the PD if it has children or can receive PPCs, else it will be a notification let pd_ntfn_obj_id = capdl_util_make_ntfn_obj(&mut spec_container, &pd.name); let pd_ntfn_cap = capdl_util_make_ntfn_cap(pd_ntfn_obj_id, true, true, 0); let mut pd_ep_obj_id: Option = None; @@ -763,13 +833,13 @@ pub fn build_capdl_spec( pd_ntfn_cap, )); - // Step 3-8 Create Reply obj + cap and insert into CSpace + // Step 3-9 Create Reply obj + cap and insert into CSpace let pd_reply_obj_id = capdl_util_make_reply_obj(&mut spec_container, &pd.name); let pd_reply_cap = capdl_util_make_reply_cap(pd_reply_obj_id); caps_to_insert_to_pd_cspace .push(capdl_util_make_cte(PD_REPLY_CAP_IDX as u32, pd_reply_cap)); - // Step 3-9 Create spec and caps to IRQs + // Step 3-10 Create spec and caps to IRQs for irq in pd.irqs.iter() { // Create a IRQ handler cap and insert into the requested CSpace's slot. let irq_handle_cap = create_irq_handler_cap( @@ -785,7 +855,7 @@ pub fn build_capdl_spec( .push(capdl_util_make_cte(irq_cap_idx as u32, irq_handle_cap)); } - // Step 3-10 Create I/O port objects on x86 platform. + // Step 3-11 Create I/O port objects on x86 platform. for ioport in pd.ioports.iter() { let ioport_obj_id = capdl_util_make_ioport_obj(&mut spec_container, &pd.name, ioport.addr, ioport.size); @@ -796,7 +866,7 @@ pub fn build_capdl_spec( )); } - // Step 3-11 Create VM Spec. + // Step 3-12 Create VM Spec. if let Some(virtual_machine) = &pd.virtual_machine { // A VM really is just a collection of special threads, it has its own TCBs, Scheduling Contexts, etc... // The difference is that it have a vCPU for each TCB to store the virtual CPUs' states. @@ -974,7 +1044,7 @@ pub fn build_capdl_spec( } } - // Step 3-12 Create ARM SMC cap if requested. + // Step 3-13 Create ARM SMC cap if requested. if pd.smc { caps_to_insert_to_pd_cspace.push(capdl_util_make_cte( PD_ARM_SMC_CAP_IDX as u32, @@ -982,7 +1052,7 @@ pub fn build_capdl_spec( )); } - // Step 3-13 Create CSpace and add all caps that the PD code and libmicrokit need to access. + // Step 3-14 Create CSpace and add all caps that the PD code and libmicrokit need to access. let pd_cnode_obj_id = capdl_util_make_cnode_obj( &mut spec_container, &pd.name, @@ -997,7 +1067,7 @@ pub fn build_capdl_spec( )); pd_id_to_cspace_id.insert(pd_global_idx, pd_cnode_obj_id); - // Step 3-14 Set the TCB parameters and all the various caps that we need to bind to this TCB. + // Step 3-15 Set the TCB parameters and all the various caps that we need to bind to this TCB. if let Object::Tcb(pd_tcb) = &mut spec_container .get_root_object_mut(pd_tcb_obj_id) .unwrap() @@ -1016,7 +1086,7 @@ pub fn build_capdl_spec( unreachable!("internal bug: build_capdl_spec() got a non TCB object ID when trying to set TCB parameters for the monitor."); } - // Step 3-15 bind this PD's TCB to the monitor, this accomplish two purposes: + // Step 3-16 bind this PD's TCB to the monitor, this accomplish two purposes: // 1. Allow PDs' TCBs to be named to their proper name in SDF in debug config. // 2. Allow passive PDs. capdl_util_insert_cap_into_cspace( diff --git a/tool/microkit/src/capdl/initialiser.rs b/tool/microkit/src/capdl/initialiser.rs index 250203b55..4be001d52 100644 --- a/tool/microkit/src/capdl/initialiser.rs +++ b/tool/microkit/src/capdl/initialiser.rs @@ -44,7 +44,7 @@ impl CapDLInitialiser { elf, phys_base: None, spec_metadata: None, - log_level: LogLevel::Info, + log_level: LogLevel::Debug, } } diff --git a/tool/microkit/src/capdl/iomem.rs b/tool/microkit/src/capdl/iomem.rs new file mode 100644 index 000000000..4ce89616d --- /dev/null +++ b/tool/microkit/src/capdl/iomem.rs @@ -0,0 +1,282 @@ +use std::ops::Range; + +use sel4_capdl_initializer_types::{cap, object, Cap, Object, ObjectId}; + +use crate::{ + capdl::{util::capdl_util_make_cte, CapDLNamedObject, CapDLSpecContainer}, + sel4::Config, +}; + +// VTD Page table level is defaulted to the three-level structure even if the hardware supports four level. +// Sel4 will only attempt to use the four-level structure if the hardware does not supports three level. +// https://github.com/seL4/seL4/blob/c406015c389decc4559fd44cb69604ddd24a0ddb/src/plat/pc99/machine/intel-vtd.c#L498 +const VTD_PML4_LEVEL: u8 = 0; +const VTD_PAGE_TABLE_LEVEL: u8 = 4; +const VTD_SEL4_DEFAULT_PT_LEVEL: u8 = 3; +const VTD_BITS_PER_LEVEL: u8 = 9; +const VTD_ENTRY_BITS: u8 = 12; +pub(crate) const VTD_MAX_ADDR: u64 = + (1 << (VTD_BITS_PER_LEVEL * VTD_SEL4_DEFAULT_PT_LEVEL + VTD_ENTRY_BITS)) - 1; + +pub fn create_iospace( + spec_container: &mut CapDLSpecContainer, + sel4_config: &Config, + pd_name: &str, + pci_bus: u8, + pci_device: u8, + dev_func: u8, + pd_id: usize, +) -> ObjectId { + let id = spec_container.add_root_object(CapDLNamedObject { + name: format!( + "{}_{}", + get_iopt_level_name(sel4_config, VTD_PML4_LEVEL), + pd_name, + ) + .into(), + object: Object::IOPageTable(object::IOPageTable { + is_root: true, + level: Some(VTD_PML4_LEVEL), + slots: [].to_vec(), + }), + }); + + const PD_TO_DOMAIN_ID_OFFSET: u16 = 1; + + spec_container.add_root_object(CapDLNamedObject { + name: format!("IOSpace_{}", pd_name,).into(), + object: Object::IOSpace(object::IOSpace { + pci_bus, + pci_device, + dev_func, + domain_id: u16::try_from(pd_id) + .unwrap_or_else(|_| panic!("The pd id {} is too large!", pd_id)) + + PD_TO_DOMAIN_ID_OFFSET, + slots: vec![capdl_util_make_cte( + 0, + Cap::IOPageTable(cap::IOPageTable { object: id }), + )], + }), + }); + + id +} + +pub fn map_io_page( + spec_container: &mut CapDLSpecContainer, + sel4_config: &Config, + pd_name: &str, + iospace_obj_id: ObjectId, + frame_cap: Cap, + ioaddr: u64, +) -> Result<(), String> { + map_recursive( + spec_container, + sel4_config, + pd_name, + iospace_obj_id, + VTD_PML4_LEVEL, + frame_cap, + ioaddr, + ) +} + +fn get_iopt_level_name(sel4_config: &Config, level: u8) -> &str { + match sel4_config.arch { + crate::sel4::Arch::X86_64 => match level { + 0 => "VTD_pml4", + 1 => "VTD_pdpt", + 2 => "VTD_pd", + 3 => "VTD_pt", + _ => unreachable!(), + }, + _ => unreachable!("get_iopt_level_name(): Internal bug: Only x86 support iommu!"), + } +} + +#[allow(clippy::too_many_arguments)] +fn map_intermediary_level_helper( + spec_container: &mut CapDLSpecContainer, + sel4_config: &Config, + pd_name: &str, + next_level_name_prefix: &str, + cur_level_obj_id: ObjectId, + cur_level: u8, + cur_level_slot: usize, + ioaddr: u64, +) -> Result { + let page_table_level_obj_wrapper = spec_container.get_root_object(cur_level_obj_id).unwrap(); + if let Object::IOPageTable(page_table_object) = &page_table_level_obj_wrapper.object { + match page_table_object + .slots + .iter() + .find(|cte| usize::from(cte.slot) == cur_level_slot) + { + Some(cte_unwrapped) => { + // Next level object already created, nothing to do here + return Ok(cte_unwrapped.cap.obj()); + } + None => { + // We need to create the next level paging structure, get out of this scope for now + // so we don't get a double mutable borrow of spec when we need to insert the next level object + } + } + } else { + return Err(format!("map_intermediary_level_helper(): internal bug: received a non-Page Table object id {} with name '{}', for mapping at level {}, to pd {}.", + usize::from(cur_level_obj_id), spec_container.get_root_object(cur_level_obj_id).unwrap().name.as_ref().unwrap(), cur_level, pd_name)); + } + + // get_pt_level_coverage works the same for io memory as well + let next_level_coverage = get_io_pt_level_coverage(sel4_config, cur_level + 1, ioaddr); + let next_level_inner_obj = object::IOPageTable { + is_root: false, // IOSpace is always created seperately + level: Some(cur_level + 1), + slots: [].to_vec(), + }; + // We create name with this PT level coverage so that every object names are unique + let next_level_object = CapDLNamedObject { + name: format!( + "{}_{}_ioaddr_0x{:x}", + next_level_name_prefix, pd_name, next_level_coverage.start + ) + .into(), + object: Object::IOPageTable(next_level_inner_obj), + }; + let next_level_obj_id = spec_container.add_root_object(next_level_object); + let next_level_cap = Cap::IOPageTable(cap::IOPageTable { + object: next_level_obj_id, + }); + + // Then insert into the correct slot at the current level, return and continue mapping + match insert_cap_into_io_page_table_level( + spec_container, + cur_level_obj_id, + cur_level, + cur_level_slot, + next_level_cap, + ) { + Ok(_) => Ok(next_level_obj_id), + Err(err_reason) => Err(err_reason), + } +} + +fn insert_cap_into_io_page_table_level( + spec_container: &mut CapDLSpecContainer, + cur_level_obj_id: ObjectId, + cur_level: u8, + cur_level_slot: usize, + cap: Cap, +) -> Result<(), String> { + let page_table_level_obj_wrapper = spec_container + .get_root_object_mut(cur_level_obj_id) + .unwrap(); + if let Object::IOPageTable(page_table_object) = &mut page_table_level_obj_wrapper.object { + // Sanity check that this slot is free + match page_table_object + .slots + .iter() + .find(|cte| usize::from(cte.slot) == cur_level_slot) + { + Some(_) => Err(format!( + "insert_cap_into_io_page_table_level(): internal bug: slot {} at PT level {} with name '{}' already filled", + cur_level_slot, cur_level, spec_container.get_root_object(cur_level_obj_id).unwrap().name.as_ref().unwrap() + )), + None => { + page_table_object.slots.push(capdl_util_make_cte(cur_level_slot as u32, cap)); + Ok(()) + } + } + } else { + Err(format!( + "insert_cap_into_io_page_table_level(): internal bug: received a non-Page Table object id {} with name '{}'", + usize::from(cur_level_obj_id), spec_container.get_root_object(cur_level_obj_id).unwrap().name.as_ref().unwrap() + )) + } +} + +#[allow(clippy::too_many_arguments)] +fn map_recursive( + spec_container: &mut CapDLSpecContainer, + sel4_config: &Config, + pd_name: &str, + pt_obj_id: ObjectId, + cur_level: u8, + frame_cap: Cap, + ioaddr: u64, +) -> Result<(), String> { + if cur_level >= VTD_PAGE_TABLE_LEVEL { + unreachable!("internal bug: we should have never recursed further!"); + } + + let this_level_index = get_io_pt_level_index(sel4_config, cur_level, ioaddr); + + if cur_level == VTD_PAGE_TABLE_LEVEL - 1 { + // Base case: we got to the target level to insert the frame cap. + insert_cap_into_io_page_table_level( + spec_container, + pt_obj_id, + cur_level, + this_level_index, + frame_cap, + ) + } else { + // Recursive case: we have not gotten to the correct level, create the next level and recurse down. + let next_level_name_prefix = get_iopt_level_name(sel4_config, cur_level + 1); + match map_intermediary_level_helper( + spec_container, + sel4_config, + pd_name, + next_level_name_prefix, + pt_obj_id, + cur_level, + this_level_index, + ioaddr, + ) { + Ok(next_level_pt_obj_id) => map_recursive( + spec_container, + sel4_config, + pd_name, + next_level_pt_obj_id, + cur_level + 1, + frame_cap, + ioaddr, + ), + Err(err_reason) => Err(err_reason), + } + } +} + +fn get_io_pt_level_index(sel4_config: &Config, level: u8, ioaddr: u64) -> usize { + match sel4_config.arch { + crate::sel4::Arch::X86_64 => { + assert!(level < VTD_PAGE_TABLE_LEVEL); + + let shift = VTD_BITS_PER_LEVEL * (VTD_PAGE_TABLE_LEVEL - level) - VTD_BITS_PER_LEVEL + + VTD_ENTRY_BITS; + + ((ioaddr >> shift) & ((1u64 << VTD_BITS_PER_LEVEL) - 1)) as usize + } + crate::sel4::Arch::Aarch64 => { + unreachable!("Internal bug: Aarch64 is not supported for IOMMU") + } + crate::sel4::Arch::Riscv64 => { + unreachable!("Internal bug: Riscv64 is not supported for IOMMU") + } + } +} + +fn get_io_pt_level_coverage(sel4_config: &Config, level: u8, ioaddr: u64) -> Range { + match sel4_config.arch { + crate::sel4::Arch::X86_64 => { + let bits_from_higher_lvls: u64 = + (VTD_PAGE_TABLE_LEVEL as u64 - (level as u64)) * VTD_BITS_PER_LEVEL as u64; + let coverage_bits = VTD_BITS_PER_LEVEL as u64 + bits_from_higher_lvls; + let low = (ioaddr >> coverage_bits) << coverage_bits; + let high = ioaddr | ((1 << coverage_bits) - 1); + low..high + } + _ => unreachable!( + "get_io_pt_level_coverage(): Internal bug: IOMMU is only supported for x86!" + ), + } +} diff --git a/tool/microkit/src/capdl/memory.rs b/tool/microkit/src/capdl/memory.rs index d8b0c7bd1..6985dcb0f 100644 --- a/tool/microkit/src/capdl/memory.rs +++ b/tool/microkit/src/capdl/memory.rs @@ -45,7 +45,7 @@ fn get_pt_level_name(sel4_config: &Config, level: usize) -> &str { } } -fn get_pt_level_index(sel4_config: &Config, level: usize, vaddr: u64) -> usize { +pub(crate) fn get_pt_level_index(sel4_config: &Config, level: usize, vaddr: u64) -> usize { let levels = sel4_config.num_page_table_levels(); assert!(level < levels); @@ -73,7 +73,7 @@ fn get_pt_level_index(sel4_config: &Config, level: usize, vaddr: u64) -> usize { ((vaddr >> shift) & mask) as usize } -fn get_pt_level_coverage(sel4_config: &Config, level: usize, vaddr: u64) -> Range { +pub(crate) fn get_pt_level_coverage(sel4_config: &Config, level: usize, vaddr: u64) -> Range { let levels = sel4_config.num_page_table_levels() as u64; let page_bits = 12; let bits_from_higher_lvls: u64 = (levels - (level as u64)) * 9; @@ -86,7 +86,7 @@ fn get_pt_level_coverage(sel4_config: &Config, level: usize, vaddr: u64) -> Rang low..high } -fn get_pt_level_to_insert(sel4_config: &Config, page_size_bytes: u64) -> usize { +pub(crate) fn get_pt_level_to_insert(sel4_config: &Config, page_size_bytes: u64) -> usize { const SMALL_PAGE_BYTES: u64 = PageSize::Small as u64; const LARGE_PAGE_BYTES: u64 = PageSize::Large as u64; match page_size_bytes { diff --git a/tool/microkit/src/capdl/mod.rs b/tool/microkit/src/capdl/mod.rs index 88a8d7339..72fafaf84 100644 --- a/tool/microkit/src/capdl/mod.rs +++ b/tool/microkit/src/capdl/mod.rs @@ -7,6 +7,7 @@ pub mod allocation; pub mod builder; pub mod initialiser; +pub mod iomem; mod irq; mod memory; pub mod packaging; diff --git a/tool/microkit/src/capdl/spec.rs b/tool/microkit/src/capdl/spec.rs index 2486c837a..03335541e 100644 --- a/tool/microkit/src/capdl/spec.rs +++ b/tool/microkit/src/capdl/spec.rs @@ -53,6 +53,9 @@ pub fn capdl_obj_physical_size_bits(obj: &Object, sel4_config: &Confi Object::AsidPool(_) => ObjectType::AsidPool.fixed_size_bits(sel4_config).unwrap(), Object::SchedContext(sched_context) => sched_context.size_bits as u64, Object::Reply => ObjectType::Reply.fixed_size_bits(sel4_config).unwrap(), + Object::IOPageTable(_) => ObjectType::IOPageTable + .fixed_size_bits(sel4_config) + .unwrap(), _ => 0, } } @@ -74,6 +77,8 @@ pub fn capdl_obj_human_name(obj: &Object, sel4_config: &Config) -> &' } } Object::PageTable(_) => "PageTable", + Object::IOPageTable(_) => "IOPageTable", + Object::IOSpace(_) => "IOSpace", Object::AsidPool(_) => "AsidPool", Object::ArmIrq(_) => "ARM IRQ", Object::IrqMsi(_) => "x86 MSI IRQ", diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 600087272..5e0616b7b 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -487,6 +487,12 @@ fn main() -> Result<(), String> { _ => false, }; + // Add check to make sure IOMMU is enabled and the platform is x86 in kernel_config_json. + let iommu = match arch { + Arch::X86_64 => json_str_as_bool(&kernel_config_json, "IOMMU")?, + _ => false, + }; + let arm_pa_size_bits = match arch { Arch::Aarch64 => { if json_str_as_bool(&kernel_config_json, "ARM_PA_SIZE_BITS_40")? { @@ -525,6 +531,7 @@ fn main() -> Result<(), String> { "MAX_NUM_BOOTINFO_UNTYPED_CAPS", )?, hypervisor, + iommu, benchmark: args.config == "benchmark", num_cores: if json_str_as_bool(&kernel_config_json, "ENABLE_SMP_SUPPORT")? { json_str_as_u64(&kernel_config_json, "MAX_NUM_NODES")? diff --git a/tool/microkit/src/sdf.rs b/tool/microkit/src/sdf.rs index ceb582886..f27e21192 100644 --- a/tool/microkit/src/sdf.rs +++ b/tool/microkit/src/sdf.rs @@ -156,6 +156,17 @@ impl SysMemoryRegion { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct IOMemMap { + pub name: String, + pub pci_bus: u8, + pub pci_dev: u8, + pub dev_func: u8, + pub ioaddr: u64, + pub perms: u8, + pub text_pos: Option, +} + #[derive(Debug, PartialEq, Eq)] pub enum SysIrqKind { Conventional { @@ -215,6 +226,7 @@ pub enum SysSetVarKind { Id { id: u64 }, X86IoPortAddr { address: u64 }, PrefillSize { mr: String }, + IoAddr { address: u64 }, } #[derive(Debug, PartialEq, Eq)] @@ -264,6 +276,7 @@ pub struct ProtectionDomain { pub maps: Vec, pub irqs: Vec, pub ioports: Vec, + pub iomaps: Vec, pub setvars: Vec, pub virtual_machine: Option, /// Only used when parsing child PDs. All elements will be removed @@ -416,6 +429,122 @@ impl SysMap { } } +// Things to check, make sure that there are no overlapping mappings per PCI device +impl IOMemMap { + fn from_xml( + config: &Config, + xml_sdf: &XmlSystemDescription, + node: &roxmltree::Node, + allow_setvar: bool, + ) -> Result { + const PCI_DEV_MAX: u8 = 0x1F; + const DEV_FUNC_MAX: u8 = 0b111; + if config.arch != Arch::X86_64 { + return Err(value_error( + xml_sdf, + node, + "IOMMU isn't supported on ARM and RISC-V".to_string(), + )); + } + + let mut attrs = vec!["mr", "pcidev", "addr"]; + if allow_setvar { + attrs.push("setvar_vaddr"); + attrs.push("setvar_size"); + } + + check_attributes(xml_sdf, node, &attrs)?; + + let mr = checked_lookup(xml_sdf, node, "mr")?.to_string(); + let pcidev_str = checked_lookup(xml_sdf, node, "pcidev")?.to_string(); + let ioaddr = sdf_parse_number(checked_lookup(xml_sdf, node, "addr")?, node)?; + + let perms = if let Some(xml_perms) = node.attribute("perms") { + match SysMapPerms::from_str(xml_perms) { + Ok(parsed_perms) => { + if parsed_perms != SysMapPerms::Read as u8 | SysMapPerms::Write as u8 + || parsed_perms != SysMapPerms::Read as u8 + { + return Err(value_error( + xml_sdf, + node, + "perms for io mapped memory can only be 'r' or 'rw'".to_string(), + )); + } + parsed_perms + } + Err(()) => { + return Err(value_error( + xml_sdf, + node, + "perms for io mapped memory can only be 'r' or 'rw'".to_string(), + )) + } + } + } else { + // Default to read-write + SysMapPerms::Read as u8 | SysMapPerms::Write as u8 + }; + + let pci_parts: Vec = pcidev_str + .split([':', '.']) + .map(str::trim) + .map(|x| { + i64::from_str_radix(x, 16) + .expect("Error: Failed to parse parts of the PCI device address") + }) + .collect(); + + if pci_parts.len() != 3 { + return Err(format!( + "Error: failed to parse PCI address '{}' on element '{}'", + pcidev_str, + node.tag_name().name() + )); + } + + let pci_bus: u8 = pci_parts[0] as u8; + + if pci_parts[0] != pci_bus as i64 { + return Err(value_error( + xml_sdf, + node, + "Invalid value for PCI bus".to_string(), + )); + } + + let pci_dev: u8 = pci_parts[1] as u8; + + if pci_parts[1] != pci_dev as i64 || pci_dev > PCI_DEV_MAX { + return Err(value_error( + xml_sdf, + node, + "Invalid value for PCI Device".to_string(), + )); + } + + let dev_func: u8 = pci_parts[2] as u8; + + if pci_parts[2] != dev_func as i64 || dev_func > DEV_FUNC_MAX { + return Err(value_error( + xml_sdf, + node, + "Invalid value for PCI Device Function".to_string(), + )); + } + + Ok(IOMemMap { + name: mr, + pci_bus: u8::try_from(pci_parts[0]).unwrap(), + pci_dev: u8::try_from(pci_parts[1]).unwrap(), + dev_func: u8::try_from(pci_parts[2]).unwrap(), + ioaddr, + perms, + text_pos: Some(xml_sdf.doc.text_pos_at(node.range().start)), + }) + } +} + impl ProtectionDomain { pub fn needs_ep(&self, self_id: usize, channels: &[Channel]) -> bool { self.has_children @@ -591,6 +720,7 @@ impl ProtectionDomain { let mut maps = Vec::new(); let mut irqs = Vec::new(); let mut ioports = Vec::new(); + let mut iomaps = Vec::new(); let mut setvars: Vec = Vec::new(); let mut child_pds = Vec::new(); @@ -993,6 +1123,31 @@ impl ProtectionDomain { )); } } + "iomap" => { + let iomap = IOMemMap::from_xml(config, xml_sdf, &child, true)?; + + if let Some(setvar_vaddr) = child.attribute("setvar_vaddr") { + let setvar = SysSetVar { + symbol: setvar_vaddr.to_string(), + kind: SysSetVarKind::IoAddr { + address: iomap.ioaddr, + }, + }; + checked_add_setvar(&mut setvars, setvar, xml_sdf, &child)?; + } + + if let Some(setvar_size) = child.attribute("setvar_size") { + let setvar = SysSetVar { + symbol: setvar_size.to_string(), + kind: SysSetVarKind::Size { + mr: iomap.name.clone(), + }, + }; + checked_add_setvar(&mut setvars, setvar, xml_sdf, &child)?; + } + + iomaps.push(iomap); + } "setvar" => { check_attributes(xml_sdf, &child, &["symbol", "region_paddr"])?; let symbol = checked_lookup(xml_sdf, &child, "symbol")?.to_string(); @@ -1084,6 +1239,7 @@ impl ProtectionDomain { maps, irqs, ioports, + iomaps, setvars, child_pds, virtual_machine, @@ -1499,6 +1655,82 @@ pub struct SystemDescription { pub channels: Vec, } +fn io_check_maps( + xml_sdf: &XmlSystemDescription, + mrs: &[SysMemoryRegion], + e: &dyn ExecutionContext, + io_maps: &[IOMemMap], +) -> Result<(), String> { + let mut checked_maps = Vec::with_capacity(io_maps.len()); + for io_map in io_maps { + let maybe_mr = mrs.iter().find(|mr| mr.name == io_map.name); + let pos = io_map.text_pos.unwrap(); + match maybe_mr { + Some(mr) => { + // Page size check + if mr.page_size_bytes() != PageSize::Small as u64 { + return Err(format!( + "Error: invalid page size specified on 'iomap' @ {}, only 4 KB page size is supported for x86 IOMMU", + loc_string(xml_sdf, pos) + )); + } + // Alignment check + if !io_map.ioaddr.is_multiple_of(mr.page_size_bytes()) { + return Err(format!( + "Error: invalid ioaddr alignment on 'iomap' @ {}", + loc_string(xml_sdf, pos) + )); + } + + // Addr and Overlap check + let map_start = io_map.ioaddr; + let map_end = map_start + mr.size; + if map_end > crate::capdl::iomem::VTD_MAX_ADDR { + return Err( + format!( + "Error: iomap for '{}' has address 0x{:x} which exceeds the upper limits of {} in {} '{}' @ {}", + io_map.name, + map_end, + crate::capdl::iomem::VTD_MAX_ADDR, + e.kind(), + e.name(), + loc_string(xml_sdf, pos) + ) + ); + } + for (name, start, end) in &checked_maps { + if !(map_start >= *end || map_end <= *start) { + return Err( + format!( + "Error: map for '{}' has io address range [0x{:x}..0x{:x}) which overlaps with map for '{}' [0x{:x}..0x{:x}) in {} '{}' @ {}", + io_map.name, + map_start, + map_end, + name, + start, + end, + e.kind(), + e.name(), + loc_string(xml_sdf, pos) + ) + ); + } + } + checked_maps.push((&io_map.name, map_start, map_end)); + } + None => { + return Err(format!( + "Error: invalid memory region name '{}' on 'map' @ {}", + io_map.name, + loc_string(xml_sdf, pos) + )) + } + } + } + + Ok(()) +} + fn check_maps( xml_sdf: &XmlSystemDescription, mrs: &[SysMemoryRegion], @@ -1990,6 +2222,9 @@ pub fn parse( // Ensure that all maps are correct for pd in &pds { check_maps(&xml_sdf, &mrs, pd, &pd.maps)?; + if !pd.iomaps.is_empty() { + io_check_maps(&xml_sdf, &mrs, pd, &pd.iomaps)? + } if let Some(vm) = &pd.virtual_machine { check_maps(&xml_sdf, &mrs, vm, &vm.maps)?; } @@ -2026,8 +2261,10 @@ pub fn parse( // Check that all MRs are used let mut all_maps = vec![]; + let mut all_iomaps = vec![]; for pd in &pds { all_maps.extend(&pd.maps); + all_iomaps.extend(&pd.iomaps); if let Some(vm) = &pd.virtual_machine { all_maps.extend(&vm.maps); } @@ -2040,7 +2277,12 @@ pub fn parse( break; } } - + for iomap in &all_iomaps { + if iomap.name == mr.name { + found = true; + break; + } + } if !found { println!("WARNING: unused memory region '{}'", mr.name); } @@ -2059,6 +2301,13 @@ pub fn parse( continue; } + // Check if this MR is mapped as io memory + for iomap in &all_iomaps { + if iomap.name == mr.name { + continue; + } + } + // Get all the addresses that this MR will be mapped into let mut addrs: Vec<_> = all_maps .iter() diff --git a/tool/microkit/src/sel4.rs b/tool/microkit/src/sel4.rs index cca595fd0..16f969c2b 100644 --- a/tool/microkit/src/sel4.rs +++ b/tool/microkit/src/sel4.rs @@ -290,6 +290,7 @@ pub struct Config { pub fan_out_limit: u64, pub max_num_bootinfo_untypeds: u64, pub hypervisor: bool, + pub iommu: bool, pub benchmark: bool, pub num_cores: u8, pub fpu: bool, @@ -442,6 +443,7 @@ pub enum ObjectType { SmallPage, LargePage, PageTable, + IOPageTable, Vcpu, AsidPool, } @@ -467,6 +469,8 @@ impl ObjectType { _ => panic!("Unexpected architecture asking for vCPU size bits"), }, ObjectType::AsidPool => Some(object_sizes.asid_pool), + ObjectType::IOPageTable => Some(12), + // It would be best to avoid such catch all case so people might forget to add the size of new object type here. _ => None, } } diff --git a/tool/microkit/src/symbols.rs b/tool/microkit/src/symbols.rs index 477ef5c7b..fb5c09627 100644 --- a/tool/microkit/src/symbols.rs +++ b/tool/microkit/src/symbols.rs @@ -159,6 +159,7 @@ pub fn patch_symbols( sdf::SysSetVarKind::PrefillSize { mr } => { mr_name_to_desc[mr].prefill_bytes.as_ref().unwrap().len() as u64 } + sdf::SysSetVarKind::IoAddr { address } => *address, }; symbols_to_write.push((&setvar.symbol, data)); } diff --git a/tool/microkit/tests/sdf/iommu_out_of_bound.system b/tool/microkit/tests/sdf/iommu_out_of_bound.system new file mode 100644 index 000000000..1d65146aa --- /dev/null +++ b/tool/microkit/tests/sdf/iommu_out_of_bound.system @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/tool/microkit/tests/test.rs b/tool/microkit/tests/test.rs index d2becea69..dcdf700c7 100644 --- a/tool/microkit/tests/test.rs +++ b/tool/microkit/tests/test.rs @@ -21,6 +21,7 @@ const DEFAULT_AARCH64_KERNEL_CONFIG: sel4::Config = sel4::Config { max_num_bootinfo_untypeds: 230, fan_out_limit: 256, hypervisor: true, + iommu: false, benchmark: false, num_cores: 1, fpu: true, @@ -45,6 +46,7 @@ const DEFAULT_X86_64_KERNEL_CONFIG: sel4::Config = sel4::Config { max_num_bootinfo_untypeds: 230, fan_out_limit: 256, hypervisor: true, + iommu: true, benchmark: false, num_cores: 1, fpu: true, @@ -623,6 +625,15 @@ mod protection_domain { "Error: cpu core must be less than 1, got 10 on element 'protection_domain':", ) } + + #[test] + fn test_iommu_valid_on_x86() { + check_error( + &DEFAULT_X86_64_KERNEL_CONFIG, + "iommu_out_of_bound.system", + "Error: iomap for 'region' has address 0x8000001000 which exceeds the upper limits of 549755813887 in protection domain 'test' @ iommu_out_of_bound.system:13:9", + ) + } } #[cfg(test)]