diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index 7acf844917..5afbd1050b 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -1003,6 +1003,25 @@ impl InitializedVm { #[cfg(not(guest_arch = "aarch64"))] let smmu_count = 0; + // On aarch64 Linux direct boot, start RAM at 1 GiB to avoid the low GPA + // region (128 MiB–129 MiB) that iommufd reserves for the host MSI + // doorbell in IOVA space. Without this gap, iommufd identity-mapped DMA + // for passthrough devices fails because it cannot allocate IOVAs in + // that range. + // + // FUTURE: this needs to be present for UEFI as well, but UEFI cannot + // only boot from low memory. Either: + // 1. Fix Linux to allow configuring the reserved IOVA range. + // 2. Fix UEFI to allow booting from >0. + // 3. Install a little bit of low memory, enough for UEFI to get to DXE + // (which can run anywhere.) + let ram_start_address = + if cfg!(guest_arch = "aarch64") && matches!(cfg.load_mode, LoadMode::Linux { .. }) { + 1024 * 1024 * 1024 // 1 GiB + } else { + 0 + }; + let resolved_layout = resolve_memory_layout(MemoryLayoutInput { mem_size: cfg.memory.mem_size, numa_mem_sizes: cfg.memory.numa_mem_sizes.as_deref(), @@ -1011,6 +1030,7 @@ impl InitializedVm { virtio_mmio_count, smmu_count, vtl2_layout, + ram_start_address, physical_address_size, }) .context("invalid memory configuration")?; diff --git a/openvmm/openvmm_core/src/worker/memory_layout.rs b/openvmm/openvmm_core/src/worker/memory_layout.rs index 0ecd36c445..5cd7b924e7 100644 --- a/openvmm/openvmm_core/src/worker/memory_layout.rs +++ b/openvmm/openvmm_core/src/worker/memory_layout.rs @@ -102,6 +102,11 @@ pub(super) struct MemoryLayoutInput<'a> { /// Optional IGVM VTL2 private-memory request. This is allocated after all /// VTL0-visible RAM and MMIO and is carried separately from ordinary RAM. pub vtl2_layout: Option, + /// Minimum guest physical address for ordinary RAM. When nonzero, the + /// range `0..ram_start_address` is reserved so RAM is placed above it. + /// This is used on aarch64 Linux direct boot to avoid the low GPA region + /// that conflicts with iommufd IOVA reservations. + pub ram_start_address: u64, /// Host-supported physical address width used only after allocation. The /// allocator computes the smallest layout it can; host fit is validation. pub physical_address_size: u8, @@ -138,6 +143,13 @@ pub(super) fn resolve_memory_layout( // least the architectural reserved zone (LAPIC, IOAPIC, TPM, ...) so // guests can arbitrate fixed-address children like TPM2 against this // window; the caller-requested size may extend it lower. + // Reserve low addresses so RAM starts above `ram_start_address`. This is + // used on aarch64 Linux direct boot to skip the 128 MiB–129 MiB IOVA + // region that iommufd reserves for the host MSI doorbell. + if input.ram_start_address > 0 { + builder.reserve("low-ram-gap", MemoryRange::new(0..input.ram_start_address)); + } + let arch_reserved = if cfg!(guest_arch = "x86_64") { ARCH_RESERVED_X86_64 } else { @@ -542,6 +554,7 @@ mod tests { virtio_mmio_count: 0, smmu_count: 0, vtl2_layout, + ram_start_address: 0, physical_address_size: 46, } }