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/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" 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 64ba08315a..29960be389 100644 --- a/kernel/src/arch/x86_64/init/pvh/mod.rs +++ b/kernel/src/arch/x86_64/init/pvh/mod.rs @@ -21,6 +21,8 @@ use crate::{ mod param; +use super::rsdp::{cache_bios_rsdp_result, cached_bios_rsdp}; + static START_INFO: Lazy = Lazy::new(); struct PvhBootCallback; @@ -31,12 +33,29 @@ impl BootCallbacks for PvhBootCallback { } 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)); } + + // 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)); + } + + log::warn!("pvh: RSDP not found"); + Ok(BootloaderAcpiArg::NotProvided) } fn init_kernel_cmdline(&self) -> Result<(), SystemError> { @@ -86,7 +105,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) @@ -121,6 +140,16 @@ 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. + if start_info.rsdp_paddr == 0 { + cache_bios_rsdp_result(); + } + Ok(()) } 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/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/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 285b4cacf7..4fc7d92b7b 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(), } @@ -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); }