From 1ddbf7fe02b998aed5abb78803b318a624aeee53 Mon Sep 17 00:00:00 2001 From: Donjuanplatinum Date: Wed, 15 Jul 2026 21:39:02 +0800 Subject: [PATCH 1/7] acpi: add RSDP discovery from BIOS memory regions. impl ACPI RSDP discovery for legacy BIOS platforms by scanning the standard locations defined by the ACPI specification. The impl follow the linux kernel drivers/acpi/acpica/tbxfroot.c checking: - EBDA region - BIOS reserved memory range 0xE0000-0xFFFFF Signed-off-by: DonjuanPlatinum --- kernel/src/arch/x86_64/init/pvh/mod.rs | 31 ++++-- kernel/src/arch/x86_64/init/pvh/param.rs | 2 +- kernel/src/driver/acpi/mod.rs | 1 + kernel/src/driver/acpi/rsdp.rs | 128 +++++++++++++++++++++++ 4 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 kernel/src/driver/acpi/rsdp.rs diff --git a/kernel/src/arch/x86_64/init/pvh/mod.rs b/kernel/src/arch/x86_64/init/pvh/mod.rs index 64ba08315a..7747f34cd2 100644 --- a/kernel/src/arch/x86_64/init/pvh/mod.rs +++ b/kernel/src/arch/x86_64/init/pvh/mod.rs @@ -9,7 +9,8 @@ use system_error::SystemError; use crate::{ arch::MMArch, driver::{ - serial::serial8250::send_to_default_serial8250_port, video::fbdev::base::BootTimeScreenInfo, + acpi::rsdp::find_rsdp_in_bios, serial::serial8250::send_to_default_serial8250_port, + video::fbdev::base::BootTimeScreenInfo, }, init::{ boot::{register_boot_callbacks, BootCallbacks, BootloaderAcpiArg}, @@ -29,14 +30,30 @@ impl BootCallbacks for PvhBootCallback { fn init_bootloader_name(&self) -> Result, SystemError> { return Ok(Some("x86 PVH".to_string())); } - + fn init_acpi_args(&self) -> Result { - let rsdp_paddr = PhysAddr::new(START_INFO.get().rsdp_paddr as usize); + let si = START_INFO.get(); + log::info!( + "pvh: HvmStartInfo: magic={:#010x} version={} flags={:#x} nr_modules={} rsdp_paddr={:#x} memmap_paddr={:#x} memmap_entries={}", + si.magic, si.version, si.flags, si.nr_modules, + si.rsdp_paddr, si.memmap_paddr, si.memmap_entries + ); + + let rsdp_paddr = PhysAddr::new(si.rsdp_paddr as usize); + + // if RSDP has been provided if rsdp_paddr.data() != 0 { - Ok(BootloaderAcpiArg::Rsdp(rsdp_paddr)) - } else { - Ok(BootloaderAcpiArg::NotProvided) + return Ok(BootloaderAcpiArg::Rsdp(rsdp_paddr)); } + + log::info!("pvh: rsdp_paddr not provided, scanning BIOS area for RSDP"); + if let Some(paddr) = find_rsdp_in_bios() { + log::info!("pvh: found RSDP at {:#x}", paddr.data()); + return Ok(BootloaderAcpiArg::Rsdp(paddr)); + } + + log::warn!("pvh: RSDP not found"); + Ok(BootloaderAcpiArg::NotProvided) } fn init_kernel_cmdline(&self) -> Result<(), SystemError> { @@ -86,7 +103,7 @@ impl BootCallbacks for PvhBootCallback { total_mem_size += size; match typ { - param::E820Type::Ram => { + E820Type::Ram => { usable_mem_size += size; mem_block_manager() .add_block(start, size) diff --git a/kernel/src/arch/x86_64/init/pvh/param.rs b/kernel/src/arch/x86_64/init/pvh/param.rs index c6226d9266..d8eb1f03e5 100644 --- a/kernel/src/arch/x86_64/init/pvh/param.rs +++ b/kernel/src/arch/x86_64/init/pvh/param.rs @@ -160,7 +160,7 @@ pub struct HvmMemmapTableEntry { } /// The E820 types known to the kernel. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum E820Type { Ram = 1, diff --git a/kernel/src/driver/acpi/mod.rs b/kernel/src/driver/acpi/mod.rs index fe4e3bf319..0d98a93245 100644 --- a/kernel/src/driver/acpi/mod.rs +++ b/kernel/src/driver/acpi/mod.rs @@ -23,6 +23,7 @@ pub mod bus; pub mod glue; pub mod pmtmr; pub mod reboot; +pub mod rsdp; mod sysfs; static mut __ACPI_TABLE: Option> = None; diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs new file mode 100644 index 0000000000..135cac4be0 --- /dev/null +++ b/kernel/src/driver/acpi/rsdp.rs @@ -0,0 +1,128 @@ +//! RSDP (Root System Description Pointer) discovery. +//! +//! When the bootloader does not hand us an RSDP physical address, we fall back +//! to scanning the well-known BIOS locations, following the ACPI specification. +//! +//! Reference: Linux `acpi_find_root_pointer()` in drivers/acpi/acpica/tbxfroot.c +//! Also see: https://wiki.osdev.org/RSDP + +use crate::mm::{PhysAddr, early_ioremap::EarlyIoRemap}; + +// On IA-PC systems, the RSDP is either located within the first 1 KiB of +// the EBDA or in the memory region from 0x000E0000 to 0x000FFFFF. +// To find the table, the operating system has to find the RSD PTR signature (notice the last space character) in one of the two areas. +// The signature always starts on a 16 byte boundary. - wiki.osdev.org + +/// RSDP signature: An 8-byte magic number "RSD PTR" +const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR "; +/// Physical address of the 2-byte EBDA segment pointer in the BIOS data area. +const EBDA_PTR_ADDR: usize = 0x40E; +/// Start of the BIOS read-only memory region. +const BIOS_AREA_START: usize = 0xE0000; +/// Size of the BIOS read-only memory region (0xE0000 - 0xFFFFF). +const BIOS_AREA_SIZE: usize = 0x20000; + +/// Validate an RSDP structure at the given virtual address. +/// +/// Checks the 8-byte signature, v1 checksum (first 20 bytes), and if +/// revision >= 2 also verifies the extended checksum over the full length. +/// ported from linux kernel: `acpi_tb_validate_rsdp()` +fn rsdp_valid(ptr: *const u8, available_len: usize) -> bool { + // SAFETY: every check will check available_len fist. + if available_len < 20 { + return false; + } + // check the RSDP_SIGNATURE + let sig = unsafe { core::slice::from_raw_parts(ptr, 8) }; + if sig != RSDP_SIGNATURE { + return false; + } + // ACPI v1 checksum covers the first 20 bytes + let v1 = unsafe { core::slice::from_raw_parts(ptr, 20) }; + if v1.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { + return false; + } + // ACPI 2.0+ if revision >= 2, also verify the extended checksum over the full struct + let revision = unsafe { *ptr.add(15) }; + if revision >= 2 { + if available_len < 36 { + return false; + } + let length = + unsafe { u32::from_le_bytes([*ptr.add(20), *ptr.add(21), *ptr.add(22), *ptr.add(23)]) } + as usize; + if length >= 36 && length <= available_len { + let full = unsafe { core::slice::from_raw_parts(ptr, length) }; + if full.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { + return false; + } + } + } + true +} + +/// Scan a virtually-mapped buffer for a valid RSDP, stepping 16 bytes at a time. +/// Returns the physical address of the RSDP if found. +fn scan_rsdp_in_buf(virt_base: usize, phys_base: usize, size: usize) -> Option { + let mut offset = 0usize; + while offset + 20 <= size { + let ptr = (virt_base + offset) as *const u8; + // scan rsdp + if rsdp_valid(ptr, size - offset) { + return Some(PhysAddr::new(phys_base + offset)); + } + offset += 16; + } + None +} + +/// Discover the RSDP by scanning the standard BIOS locations. +/// +/// Searches the EBDA (first 1 KiB) and the BIOS read-only area +/// (0xE0000 - 0xFFFFF), using [`EarlyIoRemap`] for temporary physical access. +/// +// SAFETY: Safe to call after `mm_init()`; [`EarlyIoRemap`] uses the current CR3 and +// allocates page-table pages from the static BSS pool. +pub fn find_rsdp_in_bios() -> Option { + // First search the EBDA area + // Read the 2-byte segment pointer at 0x40E, shift left 4 for its physical address. + if let Ok(ptr_virt) = EarlyIoRemap::map_not_aligned(PhysAddr::new(EBDA_PTR_ADDR), 2, true) { + let ebda_segment = unsafe { + let ptr = ptr_virt.data() as *const u8; + + u16::from_le_bytes([ + core::ptr::read_volatile(ptr), + core::ptr::read_volatile(ptr.add(1)), + ]) + } as usize; + let _ = EarlyIoRemap::unmap(ptr_virt); + + let ebda_phys = ebda_segment << 4; + if ebda_phys > 0x400 && ebda_phys < 0xA0000 { + if let Ok(ebda_virt) = + // first 1kb of EBDA + EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), 1024, true) + { + // scan rsdp + let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, 1024); + let _ = EarlyIoRemap::unmap(ebda_virt); + if result.is_some() { + return result; + } + } + } + } + + // Second search the BIOS area + if let Ok(bios_virt) = + EarlyIoRemap::map_not_aligned(PhysAddr::new(BIOS_AREA_START), BIOS_AREA_SIZE, true) + { + let result = scan_rsdp_in_buf(bios_virt.data(), BIOS_AREA_START, BIOS_AREA_SIZE); + let _ = EarlyIoRemap::unmap(bios_virt); + if result.is_some() { + return result; + } + } + + None +} From 8e4f5770863257fa0ad08acb0a4ebd25002f4c1f Mon Sep 17 00:00:00 2001 From: Donjuanplatinum Date: Wed, 15 Jul 2026 22:33:03 +0800 Subject: [PATCH 2/7] 1. change RSDP scan before mm_init into early_init to follow the linux impl. 2. when len < 36 || len > available_len, rsdp_valid return false --- kernel/src/arch/x86_64/init/pvh/mod.rs | 26 ++++++++++++++++++++++++-- kernel/src/driver/acpi/rsdp.rs | 13 ++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/kernel/src/arch/x86_64/init/pvh/mod.rs b/kernel/src/arch/x86_64/init/pvh/mod.rs index 7747f34cd2..71a0687153 100644 --- a/kernel/src/arch/x86_64/init/pvh/mod.rs +++ b/kernel/src/arch/x86_64/init/pvh/mod.rs @@ -24,6 +24,14 @@ mod param; static START_INFO: Lazy = Lazy::new(); +/// RSDP physical address discovered during early init (before mm_init). +/// +/// Like Linux, the BIOS-area RSDP scan runs in the early boot stage while the +/// bootstrap page table is still active and `EarlyIoRemap` may be used. The +/// result is cached here and consumed later by `init_acpi_args`, which runs +/// after mm_init and must not touch `EarlyIoRemap`. +static EARLY_RSDP_PADDR: Lazy> = Lazy::new(); + struct PvhBootCallback; impl BootCallbacks for PvhBootCallback { @@ -46,8 +54,9 @@ impl BootCallbacks for PvhBootCallback { return Ok(BootloaderAcpiArg::Rsdp(rsdp_paddr)); } - log::info!("pvh: rsdp_paddr not provided, scanning BIOS area for RSDP"); - if let Some(paddr) = find_rsdp_in_bios() { + // rsdp_paddr not provided by the bootloader: use the address discovered + // during the early BIOS-area scan (see EARLY_RSDP_PADDR / early_init_memory_blocks). + if let Some(paddr) = EARLY_RSDP_PADDR.try_get().copied().flatten() { log::info!("pvh: found RSDP at {:#x}", paddr.data()); return Ok(BootloaderAcpiArg::Rsdp(paddr)); } @@ -138,6 +147,19 @@ impl BootCallbacks for PvhBootCallback { total_mem_size, usable_mem_size ); + + // Discover the RSDP here, in the early boot stage: the bootstrap page + // table is still active and mm_init has not run yet, so EarlyIoRemap may + // be used safely (this mirrors Linux, which scans the RSDP in setup_arch + // via early_memremap). If the bootloader already provided rsdp_paddr we + // skip the scan. The result is cached for init_acpi_args. + let early_rsdp = if start_info.rsdp_paddr != 0 { + None + } else { + find_rsdp_in_bios() + }; + EARLY_RSDP_PADDR.init(early_rsdp); + Ok(()) } diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs index 135cac4be0..31d770606e 100644 --- a/kernel/src/driver/acpi/rsdp.rs +++ b/kernel/src/driver/acpi/rsdp.rs @@ -51,11 +51,14 @@ fn rsdp_valid(ptr: *const u8, available_len: usize) -> bool { let length = unsafe { u32::from_le_bytes([*ptr.add(20), *ptr.add(21), *ptr.add(22), *ptr.add(23)]) } as usize; - if length >= 36 && length <= available_len { - let full = unsafe { core::slice::from_raw_parts(ptr, length) }; - if full.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { - return false; - } + // reject candidates whose extended checksum cannot be fully validated: + // a corrupted/out-of-range length must not pass as a valid RSDP. + if length < 36 || length > available_len { + return false; + } + let full = unsafe { core::slice::from_raw_parts(ptr, length) }; + if full.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { + return false; } } true From 1bc222838f90b5914f6a47218251c0e1e05a36e3 Mon Sep 17 00:00:00 2001 From: Donjuanplatinum Date: Wed, 15 Jul 2026 22:49:23 +0800 Subject: [PATCH 3/7] fmt --- kernel/src/arch/x86_64/init/pvh/mod.rs | 6 +++--- kernel/src/driver/acpi/rsdp.rs | 2 +- kernel/src/mm/no_init.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/src/arch/x86_64/init/pvh/mod.rs b/kernel/src/arch/x86_64/init/pvh/mod.rs index 71a0687153..21b83732b0 100644 --- a/kernel/src/arch/x86_64/init/pvh/mod.rs +++ b/kernel/src/arch/x86_64/init/pvh/mod.rs @@ -38,7 +38,7 @@ impl BootCallbacks for PvhBootCallback { fn init_bootloader_name(&self) -> Result, SystemError> { return Ok(Some("x86 PVH".to_string())); } - + fn init_acpi_args(&self) -> Result { let si = START_INFO.get(); log::info!( @@ -46,10 +46,10 @@ impl BootCallbacks for PvhBootCallback { si.magic, si.version, si.flags, si.nr_modules, si.rsdp_paddr, si.memmap_paddr, si.memmap_entries ); - + let rsdp_paddr = PhysAddr::new(si.rsdp_paddr as usize); - // if RSDP has been provided + // if RSDP has been provided if rsdp_paddr.data() != 0 { return Ok(BootloaderAcpiArg::Rsdp(rsdp_paddr)); } diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs index 31d770606e..341c3e97b9 100644 --- a/kernel/src/driver/acpi/rsdp.rs +++ b/kernel/src/driver/acpi/rsdp.rs @@ -6,7 +6,7 @@ //! Reference: Linux `acpi_find_root_pointer()` in drivers/acpi/acpica/tbxfroot.c //! Also see: https://wiki.osdev.org/RSDP -use crate::mm::{PhysAddr, early_ioremap::EarlyIoRemap}; +use crate::mm::{early_ioremap::EarlyIoRemap, PhysAddr}; // On IA-PC systems, the RSDP is either located within the first 1 KiB of // the EBDA or in the memory region from 0x000E0000 to 0x000FFFFF. diff --git a/kernel/src/mm/no_init.rs b/kernel/src/mm/no_init.rs index 285b4cacf7..55dcdbfb85 100644 --- a/kernel/src/mm/no_init.rs +++ b/kernel/src/mm/no_init.rs @@ -209,7 +209,7 @@ pub unsafe fn pseudo_unmap_phys(vaddr: VirtAddr, count: PageFrameCount) { for i in 0..count.data() { let vaddr = vaddr + i * MMArch::PAGE_SIZE; - if let Some((_, _, flusher)) = mapper.unmap_phys(vaddr, true) { + if let Some((_, _, flusher)) = mapper.unmap_phys(vaddr, false) { flusher.ignore(); }; } From 40ffa4958ded974e9b71f1b7322ee9bf24c0f921 Mon Sep 17 00:00:00 2001 From: Donjuanplatinum Date: Wed, 15 Jul 2026 23:58:22 +0800 Subject: [PATCH 4/7] change the scan window into min(1KiB, 0xA0000 - ebda_phys) Signed-off-by: Donjuanplatinum --- kernel/src/driver/acpi/rsdp.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs index 341c3e97b9..92e19f6ede 100644 --- a/kernel/src/driver/acpi/rsdp.rs +++ b/kernel/src/driver/acpi/rsdp.rs @@ -21,6 +21,10 @@ const EBDA_PTR_ADDR: usize = 0x40E; const BIOS_AREA_START: usize = 0xE0000; /// Size of the BIOS read-only memory region (0xE0000 - 0xFFFFF). const BIOS_AREA_SIZE: usize = 0x20000; +/// Default EBDA scan window: the first 1 KiB of the EBDA. +const EBDA_WINDOW_SIZE: usize = 1024; +/// End of conventional low memory; the VGA/ISA reserved region begins here. +const LOW_MEMORY_END: usize = 0xA0000; /// Validate an RSDP structure at the given virtual address. /// @@ -101,13 +105,16 @@ pub fn find_rsdp_in_bios() -> Option { let _ = EarlyIoRemap::unmap(ptr_virt); let ebda_phys = ebda_segment << 4; - if ebda_phys > 0x400 && ebda_phys < 0xA0000 { + if ebda_phys > 0x400 && ebda_phys < LOW_MEMORY_END { + // The EBDA is not guaranteed to be 1 KiB large. If it starts less + // than 1 KiB below LOW_MEMORY_END, clamp the window so the scan does + // not cross into the VGA/ISA reserved region (ACPICA does the same). + let window = core::cmp::min(EBDA_WINDOW_SIZE, LOW_MEMORY_END - ebda_phys); if let Ok(ebda_virt) = - // first 1kb of EBDA - EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), 1024, true) + EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), window, true) { // scan rsdp - let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, 1024); + let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, window); let _ = EarlyIoRemap::unmap(ebda_virt); if result.is_some() { return result; From f93ecce229567b3499667efd7ef4bcfc19841ba4 Mon Sep 17 00:00:00 2001 From: Donjuanplatinum Date: Fri, 17 Jul 2026 03:48:56 +0800 Subject: [PATCH 5/7] fix EarlyRemapPage. Signed-off-by: Donjuanplatinum --- kernel/src/driver/acpi/rsdp.rs | 82 ++++++++++++++++++---------------- kernel/src/mm/no_init.rs | 6 +-- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs index 92e19f6ede..930b1cc90c 100644 --- a/kernel/src/driver/acpi/rsdp.rs +++ b/kernel/src/driver/acpi/rsdp.rs @@ -15,6 +15,10 @@ use crate::mm::{early_ioremap::EarlyIoRemap, PhysAddr}; /// RSDP signature: An 8-byte magic number "RSD PTR" const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR "; +/// ACPI 1.0 checksum length (Linux: ACPI_RSDP_CHECKSUM_LENGTH) +const RSDP_CHECKSUM_LENGTH: usize = 20; +/// ACPI 2.0+ extended checksum length (Linux: ACPI_RSDP_XCHECKSUM_LENGTH) +const RSDP_XCHECKSUM_LENGTH: usize = 36; /// Physical address of the 2-byte EBDA segment pointer in the BIOS data area. const EBDA_PTR_ADDR: usize = 0x40E; /// Start of the BIOS read-only memory region. @@ -25,42 +29,36 @@ const BIOS_AREA_SIZE: usize = 0x20000; const EBDA_WINDOW_SIZE: usize = 1024; /// End of conventional low memory; the VGA/ISA reserved region begins here. const LOW_MEMORY_END: usize = 0xA0000; +/// Scan step: 16-byte boundary alignment (Linux: ACPI_RSDP_SCAN_STEP) +const RSDP_SCAN_STEP: usize = 16; /// Validate an RSDP structure at the given virtual address. /// /// Checks the 8-byte signature, v1 checksum (first 20 bytes), and if -/// revision >= 2 also verifies the extended checksum over the full length. -/// ported from linux kernel: `acpi_tb_validate_rsdp()` +/// revision >= 2 also verifies the extended checksum (fixed 36 bytes). +/// +/// Follows Linux `acpi_tb_validate_rsdp()` in drivers/acpi/acpica/tbxfroot.c: +/// - Never uses the firmware-provided `length` field for checksum computation. +/// - revision 0/1: only v1 checksum (20 bytes). +/// - revision >= 2: v1 checksum (20 bytes) + extended checksum (36 bytes). fn rsdp_valid(ptr: *const u8, available_len: usize) -> bool { - // SAFETY: every check will check available_len fist. - if available_len < 20 { + if available_len < RSDP_CHECKSUM_LENGTH { return false; } - // check the RSDP_SIGNATURE let sig = unsafe { core::slice::from_raw_parts(ptr, 8) }; if sig != RSDP_SIGNATURE { return false; } - // ACPI v1 checksum covers the first 20 bytes - let v1 = unsafe { core::slice::from_raw_parts(ptr, 20) }; + let v1 = unsafe { core::slice::from_raw_parts(ptr, RSDP_CHECKSUM_LENGTH) }; if v1.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { return false; } - // ACPI 2.0+ if revision >= 2, also verify the extended checksum over the full struct let revision = unsafe { *ptr.add(15) }; if revision >= 2 { - if available_len < 36 { - return false; - } - let length = - unsafe { u32::from_le_bytes([*ptr.add(20), *ptr.add(21), *ptr.add(22), *ptr.add(23)]) } - as usize; - // reject candidates whose extended checksum cannot be fully validated: - // a corrupted/out-of-range length must not pass as a valid RSDP. - if length < 36 || length > available_len { + if available_len < RSDP_XCHECKSUM_LENGTH { return false; } - let full = unsafe { core::slice::from_raw_parts(ptr, length) }; + let full = unsafe { core::slice::from_raw_parts(ptr, RSDP_XCHECKSUM_LENGTH) }; if full.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { return false; } @@ -69,16 +67,26 @@ fn rsdp_valid(ptr: *const u8, available_len: usize) -> bool { } /// Scan a virtually-mapped buffer for a valid RSDP, stepping 16 bytes at a time. -/// Returns the physical address of the RSDP if found. -fn scan_rsdp_in_buf(virt_base: usize, phys_base: usize, size: usize) -> Option { +/// +/// `scan_len` is the candidate search range (candidate start must be < scan_len). +/// `mapped_len` is the total readable bytes from virt_base (must be >= scan_len + 35 +/// to guarantee the last candidate's full 36 bytes are readable). +/// +/// Follows Linux `acpi_tb_scan_memory_for_rsdp()`: the loop condition only requires +/// the candidate start to be within the scan range. +fn scan_rsdp_in_buf( + virt_base: usize, + phys_base: usize, + scan_len: usize, + mapped_len: usize, +) -> Option { let mut offset = 0usize; - while offset + 20 <= size { + while offset < scan_len { let ptr = (virt_base + offset) as *const u8; - // scan rsdp - if rsdp_valid(ptr, size - offset) { + if rsdp_valid(ptr, mapped_len - offset) { return Some(PhysAddr::new(phys_base + offset)); } - offset += 16; + offset += RSDP_SCAN_STEP; } None } @@ -88,15 +96,13 @@ fn scan_rsdp_in_buf(virt_base: usize, phys_base: usize, size: usize) -> Option

Option { - // First search the EBDA area - // Read the 2-byte segment pointer at 0x40E, shift left 4 for its physical address. if let Ok(ptr_virt) = EarlyIoRemap::map_not_aligned(PhysAddr::new(EBDA_PTR_ADDR), 2, true) { let ebda_segment = unsafe { let ptr = ptr_virt.data() as *const u8; - u16::from_le_bytes([ core::ptr::read_volatile(ptr), core::ptr::read_volatile(ptr.add(1)), @@ -106,15 +112,12 @@ pub fn find_rsdp_in_bios() -> Option { let ebda_phys = ebda_segment << 4; if ebda_phys > 0x400 && ebda_phys < LOW_MEMORY_END { - // The EBDA is not guaranteed to be 1 KiB large. If it starts less - // than 1 KiB below LOW_MEMORY_END, clamp the window so the scan does - // not cross into the VGA/ISA reserved region (ACPICA does the same). - let window = core::cmp::min(EBDA_WINDOW_SIZE, LOW_MEMORY_END - ebda_phys); + let scan_len = core::cmp::min(EBDA_WINDOW_SIZE, LOW_MEMORY_END - ebda_phys); + let map_len = scan_len + RSDP_XCHECKSUM_LENGTH - 1; if let Ok(ebda_virt) = - EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), window, true) + EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), map_len, true) { - // scan rsdp - let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, window); + let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, scan_len, map_len); let _ = EarlyIoRemap::unmap(ebda_virt); if result.is_some() { return result; @@ -123,11 +126,12 @@ pub fn find_rsdp_in_bios() -> Option { } } - // Second search the BIOS area + let scan_len = BIOS_AREA_SIZE; + let map_len = scan_len + RSDP_XCHECKSUM_LENGTH - 1; if let Ok(bios_virt) = - EarlyIoRemap::map_not_aligned(PhysAddr::new(BIOS_AREA_START), BIOS_AREA_SIZE, true) + EarlyIoRemap::map_not_aligned(PhysAddr::new(BIOS_AREA_START), map_len, true) { - let result = scan_rsdp_in_buf(bios_virt.data(), BIOS_AREA_START, BIOS_AREA_SIZE); + let result = scan_rsdp_in_buf(bios_virt.data(), BIOS_AREA_START, scan_len, map_len); let _ = EarlyIoRemap::unmap(bios_virt); if result.is_some() { return result; diff --git a/kernel/src/mm/no_init.rs b/kernel/src/mm/no_init.rs index 55dcdbfb85..377cbdec48 100644 --- a/kernel/src/mm/no_init.rs +++ b/kernel/src/mm/no_init.rs @@ -32,7 +32,7 @@ pub static EARLY_IOREMAP_PAGES: SpinLock = #[repr(align(4096))] #[derive(Clone, Copy)] struct EarlyRemapPage { - data: [u64; MMArch::PAGE_SIZE], + data: [u64; MMArch::PAGE_SIZE / core::mem::size_of::()], } impl EarlyRemapPage { @@ -60,7 +60,7 @@ impl EarlyIoRemapPages { const fn new() -> Self { Self { pages: [EarlyRemapPage { - data: [0; MMArch::PAGE_SIZE], + data: [0; MMArch::PAGE_SIZE / core::mem::size_of::()], }; Self::EARLY_REMAP_PAGES_NUM], bmp: StaticBitmap::new(), } @@ -209,7 +209,7 @@ pub unsafe fn pseudo_unmap_phys(vaddr: VirtAddr, count: PageFrameCount) { for i in 0..count.data() { let vaddr = vaddr + i * MMArch::PAGE_SIZE; - if let Some((_, _, flusher)) = mapper.unmap_phys(vaddr, false) { + if let Some((_, _, flusher)) = mapper.unmap_phys(vaddr, true) { flusher.ignore(); }; } From 9023d620badbf336a908f3176308f44eb1ae1f5f Mon Sep 17 00:00:00 2001 From: DonjuanPlatinum <113148619+donjuanplatinum@users.noreply.github.com> Date: Sat, 18 Jul 2026 16:50:01 +0800 Subject: [PATCH 6/7] Update acpi and aml dependencies Updated acpi and aml dependencies to a new commit hash. --- kernel/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index d3ae5bfeac..05bd8d84e6 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -36,8 +36,8 @@ fifo_demo = [] # 运行时依赖项 [dependencies] -acpi = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git", rev = "282df2af7b" } -aml = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git", rev = "282df2af7b" } +acpi = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git", rev = "1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1" } +aml = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git", rev = "1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1" } asm_macros = { path = "crates/asm_macros" } atomic_enum = "=0.2.0" bit_field = "=0.10" From 17713ed2a8ac1e154c4f770c2d8b00790be3982e Mon Sep 17 00:00:00 2001 From: longjin Date: Sat, 18 Jul 2026 17:31:15 +0000 Subject: [PATCH 7/7] fix(acpi): add legacy BIOS RSDP fallback Signed-off-by: longjin --- kernel/Cargo.lock | 4 +- kernel/src/arch/x86_64/init/mod.rs | 1 + kernel/src/arch/x86_64/init/multiboot2.rs | 45 +++- kernel/src/arch/x86_64/init/pvh/mod.rs | 28 +-- kernel/src/arch/x86_64/init/rsdp.rs | 250 ++++++++++++++++++++++ kernel/src/driver/acpi/mod.rs | 1 - kernel/src/driver/acpi/rsdp.rs | 142 ------------ kernel/src/init/boot.rs | 16 +- kernel/src/init/init.rs | 2 +- kernel/src/mm/no_init.rs | 5 +- 10 files changed, 310 insertions(+), 184 deletions(-) create mode 100644 kernel/src/arch/x86_64/init/rsdp.rs delete mode 100644 kernel/src/driver/acpi/rsdp.rs diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index a9945c7a31..ce2af70870 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -5,7 +5,7 @@ version = 4 [[package]] name = "acpi" version = "5.0.0" -source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git?rev=282df2af7b#282df2af7b9edee629af391005c2a6b89e73f88c" +source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git?rev=1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1#1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1" dependencies = [ "bit_field", "bitflags 2.9.1", @@ -42,7 +42,7 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "aml" version = "0.16.4" -source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git?rev=282df2af7b#282df2af7b9edee629af391005c2a6b89e73f88c" +source = "git+https://git.mirrors.dragonos.org.cn/DragonOS-Community/acpi-rs.git?rev=1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1#1bbdc9767ac72725fa7bdbc53260b5b6baa2bce1" dependencies = [ "bit_field", "bitvec", diff --git a/kernel/src/arch/x86_64/init/mod.rs b/kernel/src/arch/x86_64/init/mod.rs index be1c994dbd..9b6c8b7299 100644 --- a/kernel/src/arch/x86_64/init/mod.rs +++ b/kernel/src/arch/x86_64/init/mod.rs @@ -27,6 +27,7 @@ use super::{ mod boot; mod multiboot2; mod pvh; +mod rsdp; mod boot_params; diff --git a/kernel/src/arch/x86_64/init/multiboot2.rs b/kernel/src/arch/x86_64/init/multiboot2.rs index 5d0856a8fc..c4ae14c1b5 100644 --- a/kernel/src/arch/x86_64/init/multiboot2.rs +++ b/kernel/src/arch/x86_64/init/multiboot2.rs @@ -22,12 +22,23 @@ use crate::{ mm::{memblock::mem_block_manager, PhysAddr}, }; +use super::rsdp::{cache_bios_rsdp_result, cached_bios_rsdp}; + pub(super) const MULTIBOOT2_ENTRY_MAGIC: u32 = multiboot2::MAGIC; static MB2_INFO: Lazy = Lazy::new(); const MB2_RAW_INFO_MAX_SIZE: usize = 4096; static mut MB2_RAW_INFO: [u8; MB2_RAW_INFO_MAX_SIZE] = [0u8; MB2_RAW_INFO_MAX_SIZE]; +fn has_efi_context(boot_info: &BootInformation) -> bool { + boot_info.efi_sdt32_tag().is_some() + || boot_info.efi_sdt64_tag().is_some() + || boot_info.efi_memory_map_tag().is_some() + || boot_info.efi_bs_not_exited_tag().is_some() + || boot_info.efi_ih32_tag().is_some() + || boot_info.efi_ih64_tag().is_some() +} + fn mb2_rsdp_v1_tag_to_rsdp_struct(tag: &RsdpV1Tag) -> Rsdp { Rsdp { signature: tag.signature, @@ -70,17 +81,23 @@ impl BootCallbacks for Mb2Callback { } fn init_acpi_args(&self) -> Result { - if let Some(v1_tag) = MB2_INFO.get().rsdp_v1_tag() { - Ok(BootloaderAcpiArg::Rsdt(mb2_rsdp_v1_tag_to_rsdp_struct( - v1_tag, - ))) - } else if let Some(v2_tag) = MB2_INFO.get().rsdp_v2_tag() { - Ok(BootloaderAcpiArg::Xsdt(mb2_rsdp_v2_tag_to_rsdp_struct( + let boot_info = MB2_INFO.get(); + if let Some(v2_tag) = boot_info.rsdp_v2_tag() { + return Ok(BootloaderAcpiArg::Xsdt(mb2_rsdp_v2_tag_to_rsdp_struct( v2_tag, - ))) - } else { - Ok(BootloaderAcpiArg::NotProvided) + ))); + } + if let Some(v1_tag) = boot_info.rsdp_v1_tag() { + return Ok(BootloaderAcpiArg::Rsdt(mb2_rsdp_v1_tag_to_rsdp_struct( + v1_tag, + ))); + } + if let Some(paddr) = cached_bios_rsdp()? { + log::info!("multiboot2: found RSDP at {:#x}", paddr.data()); + return Ok(BootloaderAcpiArg::Rsdp(paddr)); } + + Ok(BootloaderAcpiArg::NotProvided) } fn init_kernel_cmdline(&self) -> Result<(), SystemError> { @@ -209,6 +226,16 @@ impl BootCallbacks for Mb2Callback { // setup kernel load base self.setup_kernel_load_base(); + // Legacy BIOS fallback is valid only when no ACPI tag and no EFI + // context were supplied. UEFI systems must use their configuration + // table and must not probe the fixed Legacy BIOS regions. + if mb2_info.rsdp_v2_tag().is_none() + && mb2_info.rsdp_v1_tag().is_none() + && !has_efi_context(mb2_info) + { + cache_bios_rsdp_result(); + } + Ok(()) } diff --git a/kernel/src/arch/x86_64/init/pvh/mod.rs b/kernel/src/arch/x86_64/init/pvh/mod.rs index 21b83732b0..29960be389 100644 --- a/kernel/src/arch/x86_64/init/pvh/mod.rs +++ b/kernel/src/arch/x86_64/init/pvh/mod.rs @@ -9,8 +9,7 @@ use system_error::SystemError; use crate::{ arch::MMArch, driver::{ - acpi::rsdp::find_rsdp_in_bios, serial::serial8250::send_to_default_serial8250_port, - video::fbdev::base::BootTimeScreenInfo, + serial::serial8250::send_to_default_serial8250_port, video::fbdev::base::BootTimeScreenInfo, }, init::{ boot::{register_boot_callbacks, BootCallbacks, BootloaderAcpiArg}, @@ -22,15 +21,9 @@ use crate::{ mod param; -static START_INFO: Lazy = Lazy::new(); +use super::rsdp::{cache_bios_rsdp_result, cached_bios_rsdp}; -/// RSDP physical address discovered during early init (before mm_init). -/// -/// Like Linux, the BIOS-area RSDP scan runs in the early boot stage while the -/// bootstrap page table is still active and `EarlyIoRemap` may be used. The -/// result is cached here and consumed later by `init_acpi_args`, which runs -/// after mm_init and must not touch `EarlyIoRemap`. -static EARLY_RSDP_PADDR: Lazy> = Lazy::new(); +static START_INFO: Lazy = Lazy::new(); struct PvhBootCallback; @@ -54,9 +47,9 @@ impl BootCallbacks for PvhBootCallback { return Ok(BootloaderAcpiArg::Rsdp(rsdp_paddr)); } - // rsdp_paddr not provided by the bootloader: use the address discovered - // during the early BIOS-area scan (see EARLY_RSDP_PADDR / early_init_memory_blocks). - if let Some(paddr) = EARLY_RSDP_PADDR.try_get().copied().flatten() { + // The bootloader did not provide an RSDP. Consume the shared x86 + // early-discovery result without touching EarlyIoRemap after mm_init. + if let Some(paddr) = cached_bios_rsdp()? { log::info!("pvh: found RSDP at {:#x}", paddr.data()); return Ok(BootloaderAcpiArg::Rsdp(paddr)); } @@ -153,12 +146,9 @@ impl BootCallbacks for PvhBootCallback { // be used safely (this mirrors Linux, which scans the RSDP in setup_arch // via early_memremap). If the bootloader already provided rsdp_paddr we // skip the scan. The result is cached for init_acpi_args. - let early_rsdp = if start_info.rsdp_paddr != 0 { - None - } else { - find_rsdp_in_bios() - }; - EARLY_RSDP_PADDR.init(early_rsdp); + if start_info.rsdp_paddr == 0 { + cache_bios_rsdp_result(); + } Ok(()) } diff --git a/kernel/src/arch/x86_64/init/rsdp.rs b/kernel/src/arch/x86_64/init/rsdp.rs new file mode 100644 index 0000000000..6479a263b7 --- /dev/null +++ b/kernel/src/arch/x86_64/init/rsdp.rs @@ -0,0 +1,250 @@ +//! Early RSDP discovery for x86 Legacy BIOS boot. +//! +//! This module runs before `mm_init()`, while [`EarlyIoRemap`] is available, +//! and caches only the physical RSDP address (or the discovery error) for the +//! later boot-parameter callback. + +use system_error::SystemError; + +use crate::{ + arch::MMArch, + libs::lazy_init::Lazy, + mm::{early_ioremap::EarlyIoRemap, MemoryManagementArch, PhysAddr}, +}; + +const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR "; +const RSDP_CHECKSUM_LENGTH: usize = 20; +const RSDP_XCHECKSUM_LENGTH: usize = 36; +const EBDA_PTR_ADDR: usize = 0x40e; +const BIOS_AREA_START: usize = 0xe0000; +const BIOS_AREA_SIZE: usize = 0x20000; +const EBDA_WINDOW_SIZE: usize = 1024; +const LOW_MEMORY_END: usize = 0xa0000; +const RSDP_SCAN_STEP: usize = 16; +const RSDP_MAX_TRAILING_BYTES: usize = RSDP_XCHECKSUM_LENGTH - 1; + +/// Result of the single Legacy BIOS discovery attempt performed by the BSP. +/// +/// The value is initialized before `mm_init()` and read after `mm_init()`, but +/// always before SMP startup. Bootloader-provided and UEFI paths leave it +/// uninitialized because they must not probe the Legacy BIOS regions. +static BIOS_RSDP_RESULT: Lazy, SystemError>> = Lazy::new(); + +/// Cache one Legacy BIOS discovery result for the later ACPI boot callback. +/// +/// This is an early-boot, BSP-only, single-shot operation. The initialized +/// check protects against an accidental repeated call on the same boot path; +/// it is not a general concurrent initialization mechanism. +pub(super) fn cache_bios_rsdp_result() { + if BIOS_RSDP_RESULT.initialized() { + return; + } + BIOS_RSDP_RESULT.init(find_rsdp_in_bios()); +} + +/// Return the cached Legacy BIOS discovery result. +/// +/// An uninitialized cache means the selected boot path did not request Legacy +/// BIOS discovery (for example, the bootloader supplied an RSDP or MB2 reported +/// an EFI context). +pub(super) fn cached_bios_rsdp() -> Result, SystemError> { + BIOS_RSDP_RESULT.try_get().cloned().unwrap_or(Ok(None)) +} + +/// Validate an RSDP candidate using the fixed checksum lengths used by ACPICA. +fn rsdp_valid(candidate: &[u8]) -> bool { + let Some(v1) = candidate.get(..RSDP_CHECKSUM_LENGTH) else { + return false; + }; + + if &v1[..RSDP_SIGNATURE.len()] != RSDP_SIGNATURE { + return false; + } + if v1.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)) != 0 { + return false; + } + + let revision = v1[15]; + if revision >= 2 { + let Some(full) = candidate.get(..RSDP_XCHECKSUM_LENGTH) else { + return false; + }; + if full.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)) != 0 { + return false; + } + } + + true +} + +/// Scan candidates whose start offsets are in `[0, scan_len)`. +fn scan_rsdp_in_buf( + mapped: &[u8], + phys_base: usize, + scan_len: usize, +) -> Result, SystemError> { + let required_len = scan_len + .checked_add(RSDP_MAX_TRAILING_BYTES) + .ok_or(SystemError::EINVAL)?; + if mapped.len() < required_len { + return Err(SystemError::EINVAL); + } + + let mut offset = 0usize; + while offset < scan_len { + if rsdp_valid(&mapped[offset..]) { + let candidate_phys = phys_base.checked_add(offset).ok_or(SystemError::EINVAL)?; + return Ok(Some(PhysAddr::new(candidate_phys))); + } + offset += RSDP_SCAN_STEP; + } + + Ok(None) +} + +/// Map and scan one physical search window, then unmap it exactly once. +fn scan_physical_window( + phys_base: usize, + scan_len: usize, +) -> Result, SystemError> { + let map_len = scan_len + .checked_add(RSDP_MAX_TRAILING_BYTES) + .ok_or(SystemError::EINVAL)?; + phys_base.checked_add(map_len).ok_or(SystemError::EINVAL)?; + map_len + .checked_add(phys_base % MMArch::PAGE_SIZE) + .ok_or(SystemError::EINVAL)?; + + let virt = EarlyIoRemap::map_not_aligned(PhysAddr::new(phys_base), map_len, true)?; + // SAFETY: `virt` is the virtual address corresponding to `phys_base`, and + // `map_not_aligned` successfully mapped at least the requested `map_len` + // bytes. The slice is used only until the matching unmap below. + let mapped = unsafe { core::slice::from_raw_parts(virt.data() as *const u8, map_len) }; + let scan_result = scan_rsdp_in_buf(mapped, phys_base, scan_len); + let unmap_result = EarlyIoRemap::unmap(virt); + + // A cleanup failure indicates a broken early-mapping invariant and takes + // precedence over a found/not-found scan result. + unmap_result?; + scan_result +} + +fn read_ebda_base() -> Result { + let virt = EarlyIoRemap::map_not_aligned(PhysAddr::new(EBDA_PTR_ADDR), 2, true)?; + // SAFETY: the successful mapping covers both bytes at EBDA_PTR_ADDR. BIOS + // data is volatile and may not be optimized into ordinary memory reads. + let segment = unsafe { + let ptr = virt.data() as *const u8; + u16::from_le_bytes([ + core::ptr::read_volatile(ptr), + core::ptr::read_volatile(ptr.add(1)), + ]) + } as usize; + EarlyIoRemap::unmap(virt)?; + + Ok(segment << 4) +} + +/// Find the RSDP in the ACPI Legacy BIOS search regions. +fn find_rsdp_in_bios() -> Result, SystemError> { + let ebda_phys = read_ebda_base()?; + if ebda_phys > 0x400 && ebda_phys < LOW_MEMORY_END { + let scan_len = core::cmp::min(EBDA_WINDOW_SIZE, LOW_MEMORY_END - ebda_phys); + if let Some(rsdp) = scan_physical_window(ebda_phys, scan_len)? { + return Ok(Some(rsdp)); + } + } + + scan_physical_window(BIOS_AREA_START, BIOS_AREA_SIZE) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::vec; + + fn make_rsdp(revision: u8) -> [u8; RSDP_XCHECKSUM_LENGTH] { + let mut rsdp = [0u8; RSDP_XCHECKSUM_LENGTH]; + rsdp[..8].copy_from_slice(RSDP_SIGNATURE); + rsdp[9..15].copy_from_slice(b"DRAGON"); + rsdp[15] = revision; + rsdp[20..24].copy_from_slice(&(RSDP_XCHECKSUM_LENGTH as u32).to_le_bytes()); + + let v1_sum = rsdp[..RSDP_CHECKSUM_LENGTH] + .iter() + .fold(0u8, |sum, byte| sum.wrapping_add(*byte)); + rsdp[8] = rsdp[8].wrapping_sub(v1_sum); + + let full_sum = rsdp.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)); + rsdp[32] = rsdp[32].wrapping_sub(full_sum); + rsdp + } + + #[test] + fn validates_revision_specific_checksums() { + for revision in [0, 1, 2, 6] { + assert!(rsdp_valid(&make_rsdp(revision)), "revision {revision}"); + } + + let mut revision_one = make_rsdp(1); + revision_one[35] ^= 1; + assert!(rsdp_valid(&revision_one)); + + let mut revision_two = make_rsdp(2); + revision_two[35] ^= 1; + assert!(!rsdp_valid(&revision_two)); + } + + #[test] + fn rejects_bad_signature_and_v1_checksum() { + let mut bad_signature = make_rsdp(2); + bad_signature[0] ^= 1; + assert!(!rsdp_valid(&bad_signature)); + + let mut bad_checksum = make_rsdp(2); + bad_checksum[10] ^= 1; + assert!(!rsdp_valid(&bad_checksum)); + } + + #[test] + fn scans_first_and_last_candidate_starts() { + let scan_len = 32usize; + let mut first = vec![0u8; scan_len + RSDP_MAX_TRAILING_BYTES]; + first[..RSDP_XCHECKSUM_LENGTH].copy_from_slice(&make_rsdp(2)); + assert_eq!( + scan_rsdp_in_buf(&first, 0x1000, scan_len), + Ok(Some(PhysAddr::new(0x1000))) + ); + + let mut last = vec![0u8; scan_len + RSDP_MAX_TRAILING_BYTES]; + last[16..16 + RSDP_XCHECKSUM_LENGTH].copy_from_slice(&make_rsdp(2)); + assert_eq!( + scan_rsdp_in_buf(&last, 0x1000, scan_len), + Ok(Some(PhysAddr::new(0x1010))) + ); + } + + #[test] + fn does_not_scan_candidate_starting_at_scan_len() { + let scan_len = 16usize; + let mut mapped = vec![0u8; scan_len + RSDP_XCHECKSUM_LENGTH]; + mapped[scan_len..scan_len + RSDP_XCHECKSUM_LENGTH].copy_from_slice(&make_rsdp(2)); + assert_eq!(scan_rsdp_in_buf(&mapped, 0x1000, scan_len), Ok(None)); + } + + #[test] + fn rejects_short_mapping_and_physical_address_overflow() { + assert_eq!( + scan_rsdp_in_buf(&[0u8; RSDP_XCHECKSUM_LENGTH], 0x1000, 2), + Err(SystemError::EINVAL) + ); + + let scan_len = 17usize; + let mut mapped = vec![0u8; scan_len + RSDP_MAX_TRAILING_BYTES]; + mapped[16..16 + RSDP_XCHECKSUM_LENGTH].copy_from_slice(&make_rsdp(2)); + assert_eq!( + scan_rsdp_in_buf(&mapped, usize::MAX - 15, scan_len), + Err(SystemError::EINVAL) + ); + } +} diff --git a/kernel/src/driver/acpi/mod.rs b/kernel/src/driver/acpi/mod.rs index 0d98a93245..fe4e3bf319 100644 --- a/kernel/src/driver/acpi/mod.rs +++ b/kernel/src/driver/acpi/mod.rs @@ -23,7 +23,6 @@ pub mod bus; pub mod glue; pub mod pmtmr; pub mod reboot; -pub mod rsdp; mod sysfs; static mut __ACPI_TABLE: Option> = None; diff --git a/kernel/src/driver/acpi/rsdp.rs b/kernel/src/driver/acpi/rsdp.rs deleted file mode 100644 index 930b1cc90c..0000000000 --- a/kernel/src/driver/acpi/rsdp.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! RSDP (Root System Description Pointer) discovery. -//! -//! When the bootloader does not hand us an RSDP physical address, we fall back -//! to scanning the well-known BIOS locations, following the ACPI specification. -//! -//! Reference: Linux `acpi_find_root_pointer()` in drivers/acpi/acpica/tbxfroot.c -//! Also see: https://wiki.osdev.org/RSDP - -use crate::mm::{early_ioremap::EarlyIoRemap, PhysAddr}; - -// On IA-PC systems, the RSDP is either located within the first 1 KiB of -// the EBDA or in the memory region from 0x000E0000 to 0x000FFFFF. -// To find the table, the operating system has to find the RSD PTR signature (notice the last space character) in one of the two areas. -// The signature always starts on a 16 byte boundary. - wiki.osdev.org - -/// RSDP signature: An 8-byte magic number "RSD PTR" -const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR "; -/// ACPI 1.0 checksum length (Linux: ACPI_RSDP_CHECKSUM_LENGTH) -const RSDP_CHECKSUM_LENGTH: usize = 20; -/// ACPI 2.0+ extended checksum length (Linux: ACPI_RSDP_XCHECKSUM_LENGTH) -const RSDP_XCHECKSUM_LENGTH: usize = 36; -/// Physical address of the 2-byte EBDA segment pointer in the BIOS data area. -const EBDA_PTR_ADDR: usize = 0x40E; -/// Start of the BIOS read-only memory region. -const BIOS_AREA_START: usize = 0xE0000; -/// Size of the BIOS read-only memory region (0xE0000 - 0xFFFFF). -const BIOS_AREA_SIZE: usize = 0x20000; -/// Default EBDA scan window: the first 1 KiB of the EBDA. -const EBDA_WINDOW_SIZE: usize = 1024; -/// End of conventional low memory; the VGA/ISA reserved region begins here. -const LOW_MEMORY_END: usize = 0xA0000; -/// Scan step: 16-byte boundary alignment (Linux: ACPI_RSDP_SCAN_STEP) -const RSDP_SCAN_STEP: usize = 16; - -/// Validate an RSDP structure at the given virtual address. -/// -/// Checks the 8-byte signature, v1 checksum (first 20 bytes), and if -/// revision >= 2 also verifies the extended checksum (fixed 36 bytes). -/// -/// Follows Linux `acpi_tb_validate_rsdp()` in drivers/acpi/acpica/tbxfroot.c: -/// - Never uses the firmware-provided `length` field for checksum computation. -/// - revision 0/1: only v1 checksum (20 bytes). -/// - revision >= 2: v1 checksum (20 bytes) + extended checksum (36 bytes). -fn rsdp_valid(ptr: *const u8, available_len: usize) -> bool { - if available_len < RSDP_CHECKSUM_LENGTH { - return false; - } - let sig = unsafe { core::slice::from_raw_parts(ptr, 8) }; - if sig != RSDP_SIGNATURE { - return false; - } - let v1 = unsafe { core::slice::from_raw_parts(ptr, RSDP_CHECKSUM_LENGTH) }; - if v1.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { - return false; - } - let revision = unsafe { *ptr.add(15) }; - if revision >= 2 { - if available_len < RSDP_XCHECKSUM_LENGTH { - return false; - } - let full = unsafe { core::slice::from_raw_parts(ptr, RSDP_XCHECKSUM_LENGTH) }; - if full.iter().fold(0u8, |acc, &b| acc.wrapping_add(b)) != 0 { - return false; - } - } - true -} - -/// Scan a virtually-mapped buffer for a valid RSDP, stepping 16 bytes at a time. -/// -/// `scan_len` is the candidate search range (candidate start must be < scan_len). -/// `mapped_len` is the total readable bytes from virt_base (must be >= scan_len + 35 -/// to guarantee the last candidate's full 36 bytes are readable). -/// -/// Follows Linux `acpi_tb_scan_memory_for_rsdp()`: the loop condition only requires -/// the candidate start to be within the scan range. -fn scan_rsdp_in_buf( - virt_base: usize, - phys_base: usize, - scan_len: usize, - mapped_len: usize, -) -> Option { - let mut offset = 0usize; - while offset < scan_len { - let ptr = (virt_base + offset) as *const u8; - if rsdp_valid(ptr, mapped_len - offset) { - return Some(PhysAddr::new(phys_base + offset)); - } - offset += RSDP_SCAN_STEP; - } - None -} - -/// Discover the RSDP by scanning the standard BIOS locations. -/// -/// Searches the EBDA (first 1 KiB) and the BIOS read-only area -/// (0xE0000 - 0xFFFFF), using [`EarlyIoRemap`] for temporary physical access. -/// -/// Must be called before `mm_init()` while the bootstrap page table is active. -/// [`EarlyIoRemap`] uses the current CR3 and allocates page-table pages from -/// the static BSS pool (`PseudoAllocator`), which must not be used after mm_init. -pub fn find_rsdp_in_bios() -> Option { - if let Ok(ptr_virt) = EarlyIoRemap::map_not_aligned(PhysAddr::new(EBDA_PTR_ADDR), 2, true) { - let ebda_segment = unsafe { - let ptr = ptr_virt.data() as *const u8; - u16::from_le_bytes([ - core::ptr::read_volatile(ptr), - core::ptr::read_volatile(ptr.add(1)), - ]) - } as usize; - let _ = EarlyIoRemap::unmap(ptr_virt); - - let ebda_phys = ebda_segment << 4; - if ebda_phys > 0x400 && ebda_phys < LOW_MEMORY_END { - let scan_len = core::cmp::min(EBDA_WINDOW_SIZE, LOW_MEMORY_END - ebda_phys); - let map_len = scan_len + RSDP_XCHECKSUM_LENGTH - 1; - if let Ok(ebda_virt) = - EarlyIoRemap::map_not_aligned(PhysAddr::new(ebda_phys), map_len, true) - { - let result = scan_rsdp_in_buf(ebda_virt.data(), ebda_phys, scan_len, map_len); - let _ = EarlyIoRemap::unmap(ebda_virt); - if result.is_some() { - return result; - } - } - } - } - - let scan_len = BIOS_AREA_SIZE; - let map_len = scan_len + RSDP_XCHECKSUM_LENGTH - 1; - if let Ok(bios_virt) = - EarlyIoRemap::map_not_aligned(PhysAddr::new(BIOS_AREA_START), map_len, true) - { - let result = scan_rsdp_in_buf(bios_virt.data(), BIOS_AREA_START, scan_len, map_len); - let _ = EarlyIoRemap::unmap(bios_virt); - if result.is_some() { - return result; - } - } - - None -} diff --git a/kernel/src/init/boot.rs b/kernel/src/init/boot.rs index 69584d8bcb..78401bf2fb 100644 --- a/kernel/src/init/boot.rs +++ b/kernel/src/init/boot.rs @@ -193,20 +193,22 @@ pub fn boot_callbacks() -> &'static dyn BootCallbacks { *p } -pub(super) fn boot_callback_except_early() { - let mut boot_params = boot_params().write(); - boot_params.bootloader_name = boot_callbacks() +pub(super) fn boot_callback_except_early() -> Result<(), SystemError> { + let bootloader_name = boot_callbacks() .init_bootloader_name() .expect("Failed to init bootloader name"); - boot_params.acpi = boot_callbacks() - .init_acpi_args() - .unwrap_or(BootloaderAcpiArg::NotProvided); + let acpi = boot_callbacks().init_acpi_args()?; + + let mut boot_params = boot_params().write(); + boot_params.bootloader_name = bootloader_name; + boot_params.acpi = acpi; + Ok(()) } /// ACPI information from the bootloader. #[derive(Copy, Clone, Debug)] pub enum BootloaderAcpiArg { - /// The bootloader does not provide one, a manual search is needed. + /// No ACPI root-table argument is available to the ACPI manager. NotProvided, /// Physical address of the RSDP. #[allow(dead_code)] diff --git a/kernel/src/init/init.rs b/kernel/src/init/init.rs index e742e80c9a..ce994e5698 100644 --- a/kernel/src/init/init.rs +++ b/kernel/src/init/init.rs @@ -71,7 +71,7 @@ fn do_start_kernel() { // 初始化内核命令行参数 kenrel_cmdline_param_manager().init(); - boot_callback_except_early(); + boot_callback_except_early().expect("failed to initialize boot parameters"); init_intertrait(); diff --git a/kernel/src/mm/no_init.rs b/kernel/src/mm/no_init.rs index 377cbdec48..4fc7d92b7b 100644 --- a/kernel/src/mm/no_init.rs +++ b/kernel/src/mm/no_init.rs @@ -152,11 +152,10 @@ pub unsafe fn pseudo_map_phys(vaddr: VirtAddr, paddr: PhysAddr, count: PageFrame pseudo_map_phys_with_flags(vaddr, paddr, count, flags); } -/// Use pseudo mapper to map physical memory to virtual memory -/// with READ_ONLY and EXECUTE flags. +/// Use pseudo mapper to map read-only data with execute permission disabled. #[inline(never)] pub unsafe fn pseudo_map_phys_ro(vaddr: VirtAddr, paddr: PhysAddr, count: PageFrameCount) { - let flags: EntryFlags = EntryFlags::new().set_write(false).set_execute(true); + let flags: EntryFlags = EntryFlags::new().set_write(false).set_execute(false); pseudo_map_phys_with_flags(vaddr, paddr, count, flags); }