From a764c7b46112bf591a6305a4f81aa022241be3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 6 Aug 2026 20:45:41 +0200 Subject: [PATCH 1/6] fix(x86_64/apic): identity-map SMP boot code regardless of UEFI --- src/arch/x86_64/kernel/apic.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/arch/x86_64/kernel/apic.rs b/src/arch/x86_64/kernel/apic.rs index 7c33db2720..20cf88563e 100644 --- a/src/arch/x86_64/kernel/apic.rs +++ b/src/arch/x86_64/kernel/apic.rs @@ -751,8 +751,6 @@ pub fn init_next_processor_variables() { /// This is partly confirmed by #[cfg(all(target_os = "none", feature = "smp"))] pub fn boot_application_processors() { - use x86_64::structures::paging::Translate; - use crate::arch::start::smp; let smp_boot_code = include_bytes!(concat!(core::env!("OUT_DIR"), "/boot.bin")); @@ -764,24 +762,19 @@ pub fn boot_application_processors() { ); debug!("SMP boot code is {} bytes long", smp_boot_code.len()); - if env::is_uefi() { - // Since UEFI already provides identity-mapped pagetables, we only have to sanity-check the identity mapping - let pt = unsafe { paging::identity_mapped_page_table() }; - let virt_addr = SMP_BOOT_CODE_ADDRESS; - let phys_addr = pt.translate_addr(virt_addr.into()).unwrap(); - assert_eq!(phys_addr.as_u64(), virt_addr.as_u64()); - } else { - // Identity-map the boot code page and copy over the code. - debug!("Mapping SMP boot code to physical and virtual address {SMP_BOOT_CODE_ADDRESS:p}"); - let mut flags = PageTableEntryFlags::empty(); - flags.normal().writable(); + // Ensure identity mapping + // Does not use `paging::identity_map()` since this mapping must not be `NO_EXECUTE`. + let phys_addr = paging::virtual_to_physical(SMP_BOOT_CODE_ADDRESS); + let expected_phys_addr = PhysAddr::new(SMP_BOOT_CODE_ADDRESS.as_u64()); + if phys_addr != Some(expected_phys_addr) { paging::map::( SMP_BOOT_CODE_ADDRESS, - PhysAddr::new(SMP_BOOT_CODE_ADDRESS.as_u64()), + expected_phys_addr, 1, - flags, + PageTableEntryFlags::WRITABLE, ); } + unsafe { // FIXME: do bounds checking. Better yet: do the copy via slices SMP_BOOT_CODE_ADDRESS From 893a5f918c5fa88913a73c2ce2a624fa49026a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sun, 9 Aug 2026 14:17:13 +0200 Subject: [PATCH 2/6] fix(x86_64/paging): make level 4 page table writable regardless of UEFI --- src/arch/x86_64/mm/paging.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/arch/x86_64/mm/paging.rs b/src/arch/x86_64/mm/paging.rs index b99cdf854d..27cb479fa2 100644 --- a/src/arch/x86_64/mm/paging.rs +++ b/src/arch/x86_64/mm/paging.rs @@ -17,7 +17,7 @@ use x86_64::structures::paging::{ use crate::arch::kernel::processor; use crate::arch::mm::{PhysAddr, VirtAddr}; use crate::mm::{FrameAlloc, PageRangeAllocator}; -use crate::{env, scheduler}; +use crate::scheduler; unsafe impl FrameAllocator for FrameAlloc { fn allocate_frame(&mut self) -> Option> { @@ -313,14 +313,13 @@ pub fn init() { log_page_tables(); } - if env::is_uefi() { - make_p4_writable(); - } + ensure_p4_writable(); } -fn make_p4_writable() { - debug!("Making P4 table writable"); - +/// Makes the level 4 page table writable. +/// +/// This is useful when reusing UEFI's page tables which might not be writable. +fn ensure_p4_writable() { let mut pt = unsafe { identity_mapped_page_table() }; let p4_page = { @@ -333,6 +332,12 @@ fn make_p4_writable() { unreachable!() }; + if flags.contains(PageTableEntryFlags::WRITABLE) { + return; + } + + debug!("Making P4 table writable..."); + let make_writable = || unsafe { let flags = flags | PageTableEntryFlags::WRITABLE; match frame { From 20b684e1db74a474c8950e2507fe67b1d5987ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 6 Aug 2026 20:47:03 +0200 Subject: [PATCH 3/6] fix(x86_64/ioapic): identity-map I/O APIC regardless of UEFI --- src/arch/x86_64/kernel/apic.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/arch/x86_64/kernel/apic.rs b/src/arch/x86_64/kernel/apic.rs index 20cf88563e..a02c037a0c 100644 --- a/src/arch/x86_64/kernel/apic.rs +++ b/src/arch/x86_64/kernel/apic.rs @@ -306,22 +306,11 @@ pub fn local_apic_id_count() -> u32 { } fn init_ioapic_address(phys_addr: PhysAddr) { - if env::is_uefi() { - // UEFI systems have already id mapped everything, so we can just set the physical address as the virtual one - IOAPIC_ADDRESS - .set(VirtAddr::new(phys_addr.as_u64())) - .unwrap(); - } else { - let layout = PageLayout::from_size(BasePageSize::SIZE as usize).unwrap(); - let page_range = PageAlloc::allocate(layout).unwrap(); - let ioapic_address = VirtAddr::from(page_range.start()); - IOAPIC_ADDRESS.set(ioapic_address).unwrap(); - debug!("Mapping IOAPIC at {phys_addr:p} to virtual address {ioapic_address:p}"); + paging::identity_map::(phys_addr); - let mut flags = PageTableEntryFlags::empty(); - flags.device().writable().execute_disable(); - paging::map::(ioapic_address, phys_addr, 1, flags); - } + IOAPIC_ADDRESS + .set(VirtAddr::new(phys_addr.as_u64())) + .unwrap(); } #[cfg(not(feature = "acpi"))] From a46e9923e448caacad10d5d1a877f71be64d8620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 6 Aug 2026 20:47:50 +0200 Subject: [PATCH 4/6] fix(x86_64/xapic): identity-map xAPIC regardless of UEFI --- src/arch/x86_64/kernel/apic.rs | 25 ++++++------------------- src/arch/x86_64/mm/paging.rs | 1 + 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/arch/x86_64/kernel/apic.rs b/src/arch/x86_64/kernel/apic.rs index a02c037a0c..daf2546880 100644 --- a/src/arch/x86_64/kernel/apic.rs +++ b/src/arch/x86_64/kernel/apic.rs @@ -25,9 +25,9 @@ use crate::arch::mm::paging::{ BasePageSize, PageSize, PageTableEntryFlags, PageTableEntryFlagsExt, }; use crate::arch::swapgs; -use crate::mm::{PageAlloc, PageBox, PageRangeAllocator}; +use crate::mm::PageBox; use crate::scheduler::CoreId; -use crate::{arch, env, scheduler}; +use crate::{arch, scheduler}; /// APIC Location and Status (R/W) See Table 35-2. See Section 10.4.4, Local APIC Status and Location. const IA32_APIC_BASE: Msr = Msr::new(0x1b); @@ -497,7 +497,7 @@ fn default_apic() -> PhysAddr { fn apic_addr() -> PhysAddr { #[cfg(feature = "uhyve")] - if env::is_uhyve() { + if crate::env::is_uhyve() { return default_apic(); } @@ -517,25 +517,12 @@ pub fn init() { // Initialize x2APIC or xAPIC, depending on what's available. if processor::supports_x2apic() { init_x2apic(); - } else if env::is_uefi() { - // already id mapped in UEFI systems, just use the physical address as virtual one + } else { + paging::identity_map::(local_apic_physical_address); + LOCAL_APIC_ADDRESS .set(VirtAddr::new(local_apic_physical_address.as_u64())) .unwrap(); - } else { - // We use the traditional xAPIC mode available on all x86-64 CPUs. - // It uses a mapped page for communication. - let layout = PageLayout::from_size(BasePageSize::SIZE as usize).unwrap(); - let page_range = PageAlloc::allocate(layout).unwrap(); - let local_apic_address = VirtAddr::from(page_range.start()); - LOCAL_APIC_ADDRESS.set(local_apic_address).unwrap(); - debug!( - "Mapping Local APIC at {local_apic_physical_address:p} to virtual address {local_apic_address:p}" - ); - - let mut flags = PageTableEntryFlags::empty(); - flags.device().writable().execute_disable(); - paging::map::(local_apic_address, local_apic_physical_address, 1, flags); } // Set gates to ISRs for the APIC interrupts we are going to enable. diff --git a/src/arch/x86_64/mm/paging.rs b/src/arch/x86_64/mm/paging.rs index 27cb479fa2..6b41f4dce6 100644 --- a/src/arch/x86_64/mm/paging.rs +++ b/src/arch/x86_64/mm/paging.rs @@ -32,6 +32,7 @@ unsafe impl FrameAllocator for FrameAlloc { } pub trait PageTableEntryFlagsExt { + #[cfg_attr(not(any(feature = "pci", feature = "vga")), expect(dead_code))] fn device(&mut self) -> &mut Self; fn normal(&mut self) -> &mut Self; From 9e34ef827aa3b759c42cff54fdaa417a70086071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 6 Aug 2026 20:52:27 +0200 Subject: [PATCH 5/6] fix(x86_64/acpi): identity-map ACPI table regardless of UEFI --- src/arch/x86_64/kernel/acpi.rs | 105 ++++++++------------------------- src/arch/x86_64/mm/paging.rs | 3 +- 2 files changed, 25 insertions(+), 83 deletions(-) diff --git a/src/arch/x86_64/kernel/acpi.rs b/src/arch/x86_64/kernel/acpi.rs index a01cd21fad..4c93a375d0 100644 --- a/src/arch/x86_64/kernel/acpi.rs +++ b/src/arch/x86_64/kernel/acpi.rs @@ -1,18 +1,14 @@ use core::{ptr, slice, str}; use align_address::Align; -use free_list::{PageLayout, PageRange}; use hermit_sync::OnceCell; use memory_addresses::{PhysAddr, VirtAddr}; use x86_64::instructions::port::Port; -use x86_64::structures::paging::PhysFrame; +use x86_64::structures::paging::{PageTableFlags, PhysFrame}; use crate::arch::mm::paging; -use crate::arch::mm::paging::{ - BasePageSize, PageSize, PageTableEntryFlags, PageTableEntryFlagsExt, -}; +use crate::arch::mm::paging::{BasePageSize, LargePageSize, PageSize}; use crate::env; -use crate::mm::{PageAlloc, PageRangeAllocator}; /// Memory at this physical address is supposed to contain a pointer to the Extended BIOS Data Area (EBDA). const EBDA_PTR_LOCATION: PhysAddr = PhysAddr::new(0x0000_040e); @@ -101,74 +97,36 @@ impl AcpiSdtHeader { #[derive(Debug)] pub struct AcpiTable<'a> { header: &'a AcpiSdtHeader, - allocated_virtual_address: VirtAddr, - allocated_length: usize, } impl AcpiTable<'_> { - fn map(physical_address: PhysAddr) -> Self { - if env::is_uefi() { - // For UEFI Systems, the tables are already mapped so we only need to return a proper reference to the table - let allocated_virtual_address = VirtAddr::new(physical_address.as_u64()); - let header = unsafe { - allocated_virtual_address - .as_ptr::() - .as_ref() - .unwrap() - }; - let allocated_length = usize::try_from(header.length).unwrap(); - - return Self { - header, - allocated_virtual_address, - allocated_length, - }; - } - - let mut flags = PageTableEntryFlags::empty(); - flags.normal().read_only().execute_disable(); - - // Allocate two 4 KiB pages for the table and map it. - // This guarantees that we can access at least the "length" field of the table header when its physical address - // crosses a page boundary. - let mut allocated_length = 2 * BasePageSize::SIZE as usize; - let mut count = allocated_length / BasePageSize::SIZE as usize; - - let physical_map_address = physical_address.align_down(BasePageSize::SIZE); - let offset = (physical_address - physical_map_address) as usize; - let layout = PageLayout::from_size(allocated_length).unwrap(); - let page_range = PageAlloc::allocate(layout).unwrap(); - let mut virtual_address = VirtAddr::from(page_range.start()); - paging::map::(virtual_address, physical_map_address, count, flags); - - // Get a pointer to the header and query the table length. - let mut header_ptr: *const AcpiSdtHeader = (virtual_address + offset).as_ptr(); - let table_length = unsafe { (*header_ptr).length } as usize; - - // Remap if the length exceeds what we've allocated. - if table_length > allocated_length - offset { - let range = - PageRange::from_start_len(virtual_address.as_usize(), allocated_length).unwrap(); - unsafe { - PageAlloc::deallocate(range); + fn map(phys_addr: PhysAddr) -> Self { + // Allocate at least two consecutive pages to ensure the `length` field is always readable, even when it is on the next page. + let page_count = 2; + let frame_start_addr = phys_addr.align_down(LargePageSize::SIZE); + + for i in 0..page_count { + let virt_addr = VirtAddr::new(frame_start_addr.as_u64()) + i * LargePageSize::SIZE; + let phys_addr = paging::virtual_to_physical(virt_addr); + let expected_phys_addr = PhysAddr::new(virt_addr.as_u64()); + + // Does not use `paging::identity_map()` since this mapping should not be `WRITABLE` and be `NO_EXECUTE`. + if phys_addr != Some(expected_phys_addr) { + paging::map::( + virt_addr, + expected_phys_addr, + 1, + PageTableFlags::NO_EXECUTE, + ); } - - allocated_length = (table_length + offset).align_up(BasePageSize::SIZE as usize); - count = allocated_length / BasePageSize::SIZE as usize; - - let layout = PageLayout::from_size(allocated_length).unwrap(); - let page_range = PageAlloc::allocate(layout).unwrap(); - virtual_address = VirtAddr::from(page_range.start()); - paging::map::(virtual_address, physical_map_address, count, flags); - - header_ptr = (virtual_address + offset).as_ptr(); } - // Return the table. + let header_ptr = ptr::with_exposed_provenance::(phys_addr.as_usize()); + let table_length = u64::from(unsafe { (*header_ptr).length }); + assert!(phys_addr + table_length <= frame_start_addr + page_count * LargePageSize::SIZE); + Self { header: unsafe { &*header_ptr }, - allocated_virtual_address: virtual_address, - allocated_length, } } @@ -189,21 +147,6 @@ impl AcpiTable<'_> { } } -impl Drop for AcpiTable<'_> { - fn drop(&mut self) { - if !env::is_uefi() { - let range = PageRange::from_start_len( - self.allocated_virtual_address.as_usize(), - self.allocated_length, - ) - .unwrap(); - unsafe { - PageAlloc::deallocate(range); - } - } - } -} - /// The ACPI Generic Address Structure (GAS). /// Described in ACPI Specification 6.2 A, 5.2.3.2 Generic Address Structure. #[repr(C, packed)] diff --git a/src/arch/x86_64/mm/paging.rs b/src/arch/x86_64/mm/paging.rs index 6b41f4dce6..3a51128e8a 100644 --- a/src/arch/x86_64/mm/paging.rs +++ b/src/arch/x86_64/mm/paging.rs @@ -37,7 +37,7 @@ pub trait PageTableEntryFlagsExt { fn normal(&mut self) -> &mut Self; - #[cfg(feature = "acpi")] + #[expect(dead_code)] fn read_only(&mut self) -> &mut Self; fn writable(&mut self) -> &mut Self; @@ -66,7 +66,6 @@ impl PageTableEntryFlagsExt for PageTableEntryFlags { self } - #[cfg(feature = "acpi")] fn read_only(&mut self) -> &mut Self { self.remove(PageTableEntryFlags::WRITABLE); self From 93a79dc98cae47a1becad0b439e92620a3332ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sun, 9 Aug 2026 16:59:56 +0200 Subject: [PATCH 6/6] fix(mm/phys): initialize physical memory regardless of UEFI --- src/arch/x86_64/mm/paging.rs | 18 +++++++++++++++- src/env.rs | 4 ---- src/mm/physicalmem.rs | 41 ++++++++++++++++++------------------ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/arch/x86_64/mm/paging.rs b/src/arch/x86_64/mm/paging.rs index 3a51128e8a..cff4210b28 100644 --- a/src/arch/x86_64/mm/paging.rs +++ b/src/arch/x86_64/mm/paging.rs @@ -11,7 +11,8 @@ use x86_64::structures::paging::frame::PhysFrameRange; use x86_64::structures::paging::mapper::{MapToError, MappedFrame, TranslateResult, UnmapError}; use x86_64::structures::paging::page::PageRange; use x86_64::structures::paging::{ - FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB, Translate, + FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableIndex, PhysFrame, Size4KiB, + Translate, }; use crate::arch::kernel::processor; @@ -115,6 +116,21 @@ pub unsafe fn identity_mapped_page_table() -> OffsetPageTable<'static> { } } +/// Returns true if the level 4 page table has a recursive entry. +/// +/// This is useful for compatibility with the Hermit loader version 0.5.6. +// FIXME: Remove once we drop support for loader 0.5.6 +pub fn is_recursive() -> bool { + let identity_mapped_page_table = unsafe { identity_mapped_page_table() }; + let level_4_table = identity_mapped_page_table.level_4_table(); + + let recursive_index = PageTableIndex::new(511); + let level_4_table_virt_addr = ptr::from_ref(level_4_table).addr(); + let recursive_index_phys_addr = level_4_table[recursive_index].addr().as_u64() as usize; + + level_4_table_virt_addr == recursive_index_phys_addr +} + /// Translate a virtual memory address to a physical one. pub fn virtual_to_physical(virtual_address: VirtAddr) -> Option { let addr = x86_64::VirtAddr::from(virtual_address); diff --git a/src/env.rs b/src/env.rs index 38d6f83056..1864ae2631 100644 --- a/src/env.rs +++ b/src/env.rs @@ -78,10 +78,6 @@ pub fn uhyve_num_cpus() -> Option> { } } -pub fn is_uefi() -> bool { - fdt().is_some_and(|fdt| fdt.root().compatible().first() == "hermit,uefi") -} - pub fn fdt_addr() -> Option> { boot_info() .hardware_info diff --git a/src/mm/physicalmem.rs b/src/mm/physicalmem.rs index 758871230c..3c50f71142 100644 --- a/src/mm/physicalmem.rs +++ b/src/mm/physicalmem.rs @@ -117,22 +117,29 @@ unsafe fn detect_from_fdt() -> Result<(), ()> { .map(|m| m.reg().unwrap().next().unwrap()); for m in all_regions { - let start_address = m.starting_address.expose_provenance() as u64; - let size = m.size.unwrap() as u64; - let end_address = start_address + size; + let mut start_addr = m.starting_address.expose_provenance(); + let mut end_addr = start_addr + m.size.unwrap(); - if end_address <= super::kernel_end_address().as_u64() && !env::is_uefi() { - continue; + // Don't use the zero page. + start_addr = start_addr.max(0x1000); + + #[cfg(target_arch = "x86_64")] + if paging::is_recursive() { + start_addr = start_addr.max(super::kernel_end_address().as_usize()); + } + + if cfg!(target_arch = "aarch64") || cfg!(target_arch = "riscv64") { + start_addr = start_addr.max(super::kernel_end_address().as_usize()); } - let start_address = - if start_address <= super::kernel_start_address().as_u64() && !env::is_uefi() { - super::kernel_end_address() - } else { - VirtAddr::new(start_address) - }; + start_addr = start_addr.align_up(0x1000); + end_addr = end_addr.align_down(0x1000); - let range = PageRange::new(start_address.as_usize(), end_address as usize).unwrap(); + if start_addr > end_addr { + continue; + } + + let range = PageRange::new(start_addr, end_addr).unwrap(); unsafe { FrameAlloc::deallocate(range); map_frame_range(range); @@ -160,13 +167,7 @@ unsafe fn detect_from_fdt() -> Result<(), ()> { reserve(reservation); } - let kernel_start = if env::is_uefi() { - super::kernel_start_address().as_usize() - } else { - // FIXME: Memory before the kernel causes trouble on non-uefi systems. - // It is unclear, which exact regions cause problems. - 0 - }; + let kernel_start = super::kernel_start_address().as_usize(); let kernel_end = super::kernel_end_address().as_usize(); let kernel_region = PageRange::new(kernel_start, kernel_end).unwrap(); reserve(kernel_region); @@ -201,7 +202,7 @@ impl PageRangeExt for PageRange { } unsafe fn init() { - if env::is_uefi() && DeviceAlloc.phys_offset() != VirtAddr::zero() { + if cfg!(target_arch = "x86_64") && DeviceAlloc.phys_offset() != VirtAddr::zero() { let start = DeviceAlloc.phys_offset(); let count = DeviceAlloc.phys_offset().as_u64() / HugePageSize::SIZE; let count = usize::try_from(count).unwrap();