acpi/rsdp.rs
1use crate::{AcpiError, Handler, PhysicalMapping};
2use core::{mem, ops::Range, slice, str};
3
4/// The size in bytes of the ACPI 1.0 RSDP.
5const RSDP_V1_LENGTH: usize = 20;
6/// The total size in bytes of the RSDP fields introduced in ACPI 2.0.
7const RSDP_V2_EXT_LENGTH: usize = mem::size_of::<Rsdp>() - RSDP_V1_LENGTH;
8
9/// The first structure found in ACPI. It just tells us where the RSDT is.
10///
11/// On BIOS systems, it is either found in the first 1KiB of the Extended Bios Data Area, or between `0x000e0000`
12/// and `0x000fffff`. The signature is always on a 16 byte boundary. On (U)EFI, it may not be located in these
13/// locations, and so an address should be found in the EFI configuration table instead.
14///
15/// The recommended way of locating the RSDP is to let the bootloader do it - Multiboot2 can pass a
16/// tag with the physical address of it. If this is not possible, a manual scan can be done.
17///
18/// If `revision > 0`, (the hardware ACPI version is Version 2.0 or greater), the RSDP contains
19/// some new fields. For ACPI Version 1.0, these fields are not valid and should not be accessed.
20/// For ACPI Version 2.0+, `xsdt_address` should be used (truncated to `u32` on x86) instead of
21/// `rsdt_address`.
22#[derive(Clone, Copy, Debug)]
23#[repr(C, packed)]
24pub struct Rsdp {
25 pub signature: [u8; 8],
26 pub checksum: u8,
27 pub oem_id: [u8; 6],
28 pub revision: u8,
29 pub rsdt_address: u32,
30
31 /*
32 * These fields are only valid for ACPI Version 2.0 and greater
33 */
34 pub length: u32,
35 pub xsdt_address: u64,
36 pub ext_checksum: u8,
37 _reserved: [u8; 3],
38}
39
40impl Rsdp {
41 /// This searches for a RSDP on BIOS systems.
42 ///
43 /// ### Safety
44 /// This function probes memory in three locations:
45 /// - It reads a word from `40:0e` to locate the EBDA.
46 /// - The first 1KiB of the EBDA (Extended BIOS Data Area).
47 /// - The BIOS memory area at `0xe0000..=0xfffff`.
48 ///
49 /// This should be fine on all BIOS systems. However, UEFI platforms are free to put the RSDP wherever they
50 /// please, so this won't always find the RSDP. Further, prodding these memory locations may have unintended
51 /// side-effects. On UEFI systems, the RSDP should be found in the Configuration Table, using two GUIDs:
52 /// - ACPI v1.0 structures use `eb9d2d30-2d88-11d3-9a16-0090273fc14d`.
53 /// - ACPI v2.0 or later structures use `8868e871-e4f1-11d3-bc22-0080c73c8881`.
54 /// You should search the entire table for the v2.0 GUID before searching for the v1.0 one.
55 pub unsafe fn search_for_on_bios<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, AcpiError>
56 where
57 H: Handler,
58 {
59 let rsdp_address = find_search_areas(handler.clone()).iter().find_map(|area| {
60 // Map the search area for the RSDP followed by `RSDP_V2_EXT_LENGTH` bytes so an ACPI 1.0 RSDP at the
61 // end of the area can be read as an `Rsdp` (which always has the size of an ACPI 2.0 RSDP)
62 let mapping = unsafe {
63 handler.map_physical_region::<u8>(area.start, area.end - area.start + RSDP_V2_EXT_LENGTH)
64 };
65
66 let extended_area_bytes =
67 unsafe { slice::from_raw_parts(mapping.virtual_start.as_ptr(), mapping.region_length) };
68
69 // Search `Rsdp`-sized windows at 16-byte boundaries relative to the base of the area (which is also
70 // aligned to 16 bytes due to the implementation of `find_search_areas`)
71 extended_area_bytes.windows(mem::size_of::<Rsdp>()).step_by(16).find_map(|maybe_rsdp_bytes_slice| {
72 let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::<Rsdp>();
73 let maybe_rsdp_phys_start = maybe_rsdp_virt_ptr as usize - mapping.virtual_start.as_ptr() as usize
74 + mapping.physical_start;
75 // SAFETY: `maybe_rsdp_virt_ptr` points to an aligned, readable `Rsdp`-sized value, and the `Rsdp`
76 // struct's fields are always initialized.
77 let maybe_rsdp = unsafe { &*maybe_rsdp_virt_ptr };
78
79 match maybe_rsdp.validate() {
80 Ok(()) => Some(maybe_rsdp_phys_start),
81 Err(AcpiError::RsdpIncorrectSignature) => None,
82 Err(err) => {
83 log::warn!("Invalid RSDP found at {:#x}: {:?}", maybe_rsdp_phys_start, err);
84 None
85 }
86 }
87 })
88 });
89
90 match rsdp_address {
91 Some(address) => {
92 let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
93 Ok(rsdp_mapping)
94 }
95 None => Err(AcpiError::NoValidRsdp),
96 }
97 }
98
99 /// Checks that:
100 /// 1) The signature is correct
101 /// 2) The checksum is correct
102 /// 3) For Version 2.0+, that the extension checksum is correct
103 pub fn validate(&self) -> Result<(), AcpiError> {
104 // Check the signature
105 if self.signature != RSDP_SIGNATURE {
106 return Err(AcpiError::RsdpIncorrectSignature);
107 }
108
109 // Check the OEM id is valid UTF-8
110 if str::from_utf8(&self.oem_id).is_err() {
111 return Err(AcpiError::RsdpInvalidOemId);
112 }
113
114 /*
115 * `self.length` doesn't exist on ACPI version 1.0, so we mustn't rely on it. Instead,
116 * check for version 1.0 and use a hard-coded length instead.
117 */
118 let length = if self.revision > 0 {
119 // For Version 2.0+, include the number of bytes specified by `length`
120 self.length as usize
121 } else {
122 RSDP_V1_LENGTH
123 };
124
125 let bytes = unsafe { slice::from_raw_parts(self as *const Rsdp as *const u8, length) };
126 let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
127
128 if sum != 0 {
129 return Err(AcpiError::RsdpInvalidChecksum);
130 }
131
132 Ok(())
133 }
134
135 pub fn signature(&self) -> [u8; 8] {
136 self.signature
137 }
138
139 pub fn checksum(&self) -> u8 {
140 self.checksum
141 }
142
143 pub fn oem_id(&self) -> Result<&str, AcpiError> {
144 str::from_utf8(&self.oem_id).map_err(|_| AcpiError::RsdpInvalidOemId)
145 }
146
147 pub fn revision(&self) -> u8 {
148 self.revision
149 }
150
151 pub fn rsdt_address(&self) -> u32 {
152 self.rsdt_address
153 }
154
155 pub fn length(&self) -> u32 {
156 assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
157 self.length
158 }
159
160 pub fn xsdt_address(&self) -> u64 {
161 assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
162 self.xsdt_address
163 }
164
165 pub fn ext_checksum(&self) -> u8 {
166 assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
167 self.ext_checksum
168 }
169}
170
171/// Find the areas we should search for the RSDP in.
172fn find_search_areas<H>(handler: H) -> [Range<usize>; 2]
173where
174 H: Handler,
175{
176 /*
177 * Read the base address of the EBDA from its location in the BDA (BIOS Data Area). Not all BIOSs fill this out
178 * unfortunately, so we might not get a sensible result. We shift it left 4, as it's a segment address.
179 */
180 let ebda_start_mapping =
181 unsafe { handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>()) };
182 let ebda_start = (*ebda_start_mapping as usize) << 4;
183
184 [
185 /*
186 * The main BIOS area below 1MiB. In practice, from my [Restioson's] testing, the RSDP is more often here
187 * than the EBDA. We also don't want to search the entire possibele EBDA range, if we've failed to find it
188 * from the BDA.
189 */
190 RSDP_BIOS_AREA_START..(RSDP_BIOS_AREA_END + 1),
191 // Check if base segment ptr is in valid range for EBDA base
192 if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
193 // First KiB of EBDA
194 ebda_start..ebda_start + 1024
195 } else {
196 // We don't know where the EBDA starts, so just search the largest possible EBDA
197 EBDA_EARLIEST_START..(EBDA_END + 1)
198 },
199 ]
200}
201
202/// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4
203const EBDA_START_SEGMENT_PTR: usize = 0x40e;
204/// The earliest (lowest) memory address an EBDA (Extended Bios Data Area) can start
205const EBDA_EARLIEST_START: usize = 0x80000;
206/// The end of the EBDA (Extended Bios Data Area)
207const EBDA_END: usize = 0x9ffff;
208/// The start of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
209const RSDP_BIOS_AREA_START: usize = 0xe0000;
210/// The end of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
211const RSDP_BIOS_AREA_END: usize = 0xfffff;
212/// The RSDP (Root System Description Pointer)'s signature, "RSD PTR " (note trailing space)
213const RSDP_SIGNATURE: [u8; 8] = *b"RSD PTR ";