| use crate::{AcpiError, AcpiHandler, AcpiResult, PhysicalMapping}; |
| use core::{mem, ops::Range, slice, str}; |
| |
| /// The size in bytes of the ACPI 1.0 RSDP. |
| const RSDP_V1_LENGTH: usize = 20; |
| /// The total size in bytes of the RSDP fields introduced in ACPI 2.0. |
| const RSDP_V2_EXT_LENGTH: usize = mem::size_of::<Rsdp>() - RSDP_V1_LENGTH; |
| |
| /// The first structure found in ACPI. It just tells us where the RSDT is. |
| /// |
| /// On BIOS systems, it is either found in the first 1KiB of the Extended Bios Data Area, or between `0x000e0000` |
| /// and `0x000fffff`. The signature is always on a 16 byte boundary. On (U)EFI, it may not be located in these |
| /// locations, and so an address should be found in the EFI configuration table instead. |
| /// |
| /// The recommended way of locating the RSDP is to let the bootloader do it - Multiboot2 can pass a |
| /// tag with the physical address of it. If this is not possible, a manual scan can be done. |
| /// |
| /// If `revision > 0`, (the hardware ACPI version is Version 2.0 or greater), the RSDP contains |
| /// some new fields. For ACPI Version 1.0, these fields are not valid and should not be accessed. |
| /// For ACPI Version 2.0+, `xsdt_address` should be used (truncated to `u32` on x86) instead of |
| /// `rsdt_address`. |
| #[derive(Clone, Copy, Debug)] |
| #[repr(C, packed)] |
| pub struct Rsdp { |
| signature: [u8; 8], |
| checksum: u8, |
| oem_id: [u8; 6], |
| revision: u8, |
| rsdt_address: u32, |
| |
| /* |
| * These fields are only valid for ACPI Version 2.0 and greater |
| */ |
| length: u32, |
| xsdt_address: u64, |
| ext_checksum: u8, |
| reserved: [u8; 3], |
| } |
| |
| impl Rsdp { |
| /// This searches for a RSDP on BIOS systems. |
| /// |
| /// ### Safety |
| /// This function probes memory in three locations: |
| /// - It reads a word from `40:0e` to locate the EBDA. |
| /// - The first 1KiB of the EBDA (Extended BIOS Data Area). |
| /// - The BIOS memory area at `0xe0000..=0xfffff`. |
| /// |
| /// This should be fine on all BIOS systems. However, UEFI platforms are free to put the RSDP wherever they |
| /// please, so this won't always find the RSDP. Further, prodding these memory locations may have unintended |
| /// side-effects. On UEFI systems, the RSDP should be found in the Configuration Table, using two GUIDs: |
| /// - ACPI v1.0 structures use `eb9d2d30-2d88-11d3-9a16-0090273fc14d`. |
| /// - ACPI v2.0 or later structures use `8868e871-e4f1-11d3-bc22-0080c73c8881`. |
| /// You should search the entire table for the v2.0 GUID before searching for the v1.0 one. |
| pub unsafe fn search_for_on_bios<H>(handler: H) -> AcpiResult<PhysicalMapping<H, Rsdp>> |
| where |
| H: AcpiHandler, |
| { |
| let rsdp_address = find_search_areas(handler.clone()).iter().find_map(|area| { |
| // Map the search area for the RSDP followed by `RSDP_V2_EXT_LENGTH` bytes so an ACPI 1.0 RSDP at the |
| // end of the area can be read as an `Rsdp` (which always has the size of an ACPI 2.0 RSDP) |
| let mapping = unsafe { |
| handler.map_physical_region::<u8>(area.start, area.end - area.start + RSDP_V2_EXT_LENGTH) |
| }; |
| |
| let extended_area_bytes = |
| unsafe { slice::from_raw_parts(mapping.virtual_start().as_ptr(), mapping.region_length()) }; |
| |
| // Search `Rsdp`-sized windows at 16-byte boundaries relative to the base of the area (which is also |
| // aligned to 16 bytes due to the implementation of `find_search_areas`) |
| extended_area_bytes.windows(mem::size_of::<Rsdp>()).step_by(16).find_map(|maybe_rsdp_bytes_slice| { |
| let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::<Rsdp>(); |
| let maybe_rsdp_phys_start = maybe_rsdp_virt_ptr as usize |
| - mapping.virtual_start().as_ptr() as usize |
| + mapping.physical_start(); |
| // SAFETY: `maybe_rsdp_virt_ptr` points to an aligned, readable `Rsdp`-sized value, and the `Rsdp` |
| // struct's fields are always initialized. |
| let maybe_rsdp = unsafe { &*maybe_rsdp_virt_ptr }; |
| |
| match maybe_rsdp.validate() { |
| Ok(()) => Some(maybe_rsdp_phys_start), |
| Err(AcpiError::RsdpIncorrectSignature) => None, |
| Err(err) => { |
| log::warn!("Invalid RSDP found at {:#x}: {:?}", maybe_rsdp_phys_start, err); |
| None |
| } |
| } |
| }) |
| }); |
| |
| match rsdp_address { |
| Some(address) => { |
| let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) }; |
| Ok(rsdp_mapping) |
| } |
| None => Err(AcpiError::NoValidRsdp), |
| } |
| } |
| |
| /// Checks that: |
| /// 1) The signature is correct |
| /// 2) The checksum is correct |
| /// 3) For Version 2.0+, that the extension checksum is correct |
| pub fn validate(&self) -> AcpiResult<()> { |
| // Check the signature |
| if self.signature != RSDP_SIGNATURE { |
| return Err(AcpiError::RsdpIncorrectSignature); |
| } |
| |
| // Check the OEM id is valid UTF8 (allows use of unwrap) |
| if str::from_utf8(&self.oem_id).is_err() { |
| return Err(AcpiError::RsdpInvalidOemId); |
| } |
| |
| /* |
| * `self.length` doesn't exist on ACPI version 1.0, so we mustn't rely on it. Instead, |
| * check for version 1.0 and use a hard-coded length instead. |
| */ |
| let length = if self.revision > 0 { |
| // For Version 2.0+, include the number of bytes specified by `length` |
| self.length as usize |
| } else { |
| RSDP_V1_LENGTH |
| }; |
| |
| let bytes = unsafe { slice::from_raw_parts(self as *const Rsdp as *const u8, length) }; |
| let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte)); |
| |
| if sum != 0 { |
| return Err(AcpiError::RsdpInvalidChecksum); |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn signature(&self) -> [u8; 8] { |
| self.signature |
| } |
| |
| pub fn checksum(&self) -> u8 { |
| self.checksum |
| } |
| |
| pub fn oem_id(&self) -> &str { |
| str::from_utf8(&self.oem_id).unwrap() |
| } |
| |
| pub fn revision(&self) -> u8 { |
| self.revision |
| } |
| |
| pub fn rsdt_address(&self) -> u32 { |
| self.rsdt_address |
| } |
| |
| pub fn length(&self) -> u32 { |
| assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0"); |
| self.length |
| } |
| |
| pub fn xsdt_address(&self) -> u64 { |
| assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0"); |
| self.xsdt_address |
| } |
| |
| pub fn ext_checksum(&self) -> u8 { |
| assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0"); |
| self.ext_checksum |
| } |
| } |
| |
| /// Find the areas we should search for the RSDP in. |
| fn find_search_areas<H>(handler: H) -> [Range<usize>; 2] |
| where |
| H: AcpiHandler, |
| { |
| /* |
| * Read the base address of the EBDA from its location in the BDA (BIOS Data Area). Not all BIOSs fill this out |
| * unfortunately, so we might not get a sensible result. We shift it left 4, as it's a segment address. |
| */ |
| let ebda_start_mapping = |
| unsafe { handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>()) }; |
| let ebda_start = (*ebda_start_mapping as usize) << 4; |
| |
| [ |
| /* |
| * The main BIOS area below 1MiB. In practice, from my [Restioson's] testing, the RSDP is more often here |
| * than the EBDA. We also don't want to search the entire possibele EBDA range, if we've failed to find it |
| * from the BDA. |
| */ |
| RSDP_BIOS_AREA_START..(RSDP_BIOS_AREA_END + 1), |
| // Check if base segment ptr is in valid range for EBDA base |
| if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) { |
| // First KiB of EBDA |
| ebda_start..ebda_start + 1024 |
| } else { |
| // We don't know where the EBDA starts, so just search the largest possible EBDA |
| EBDA_EARLIEST_START..(EBDA_END + 1) |
| }, |
| ] |
| } |
| |
| /// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4 |
| const EBDA_START_SEGMENT_PTR: usize = 0x40e; |
| /// The earliest (lowest) memory address an EBDA (Extended Bios Data Area) can start |
| const EBDA_EARLIEST_START: usize = 0x80000; |
| /// The end of the EBDA (Extended Bios Data Area) |
| const EBDA_END: usize = 0x9ffff; |
| /// The start of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer) |
| const RSDP_BIOS_AREA_START: usize = 0xe0000; |
| /// The end of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer) |
| const RSDP_BIOS_AREA_END: usize = 0xfffff; |
| /// The RSDP (Root System Description Pointer)'s signature, "RSD PTR " (note trailing space) |
| const RSDP_SIGNATURE: [u8; 8] = *b"RSD PTR "; |