Arve Hjønnevåg | 8dac36a | 2024-02-26 14:19:16 -0800 | [diff] [blame] | 1 | //! A library for parsing ACPI tables. This crate can be used by bootloaders and kernels for architectures that |
| 2 | //! support ACPI. This crate is not feature-complete, but can parse lots of the more common tables. Parsing the |
| 3 | //! ACPI tables is required for correctly setting up the APICs, HPET, and provides useful information about power |
| 4 | //! management and many other platform capabilities. |
| 5 | //! |
| 6 | //! This crate is designed to find and parse the static tables ACPI provides. It should be used in conjunction with |
| 7 | //! the `aml` crate, which is the (much less complete) AML parser used to parse the DSDT and SSDTs. These crates |
| 8 | //! are separate because some kernels may want to detect the static tables, but delay AML parsing to a later stage. |
| 9 | //! |
| 10 | //! This crate can be used in three configurations, depending on the environment it's being used from: |
| 11 | //! - **Without allocator support** - this can be achieved by disabling the `allocator_api` and `alloc` |
| 12 | //! features. The core parts of the library will still be usable, but with generally reduced functionality |
| 13 | //! and ease-of-use. |
| 14 | //! - **With a custom allocator** - by disabling just the `alloc` feature, you can use the `new_in` functions to |
| 15 | //! access increased functionality with your own allocator. This allows `acpi` to be integrated more closely |
| 16 | //! with environments that already provide a custom allocator, for example to gracefully handle allocation |
| 17 | //! errors. |
| 18 | //! - **With the globally-set allocator** - the `alloc` feature provides `new` functions that simply use the |
| 19 | //! global allocator. This is the easiest option, and the one the majority of users will want. It is the |
| 20 | //! default configuration of the crate. |
| 21 | //! |
| 22 | //! ### Usage |
| 23 | //! To use the library, you will need to provide an implementation of the `AcpiHandler` trait, which allows the |
| 24 | //! library to make requests such as mapping a particular region of physical memory into the virtual address space. |
| 25 | //! |
| 26 | //! You then need to construct an instance of `AcpiTables`, which can be done in a few ways depending on how much |
| 27 | //! information you have: |
| 28 | //! * Use `AcpiTables::from_rsdp` if you have the physical address of the RSDP |
| 29 | //! * Use `AcpiTables::from_rsdt` if you have the physical address of the RSDT/XSDT |
| 30 | //! * Use `AcpiTables::search_for_rsdp_bios` if you don't have the address of either, but **you know you are |
| 31 | //! running on BIOS, not UEFI** |
| 32 | //! * Use `AcpiTables::from_tables_direct` if you are using the library in an unusual setting, such as in usermode, |
| 33 | //! and have a custom method to enumerate and access the tables. |
| 34 | //! |
| 35 | //! `AcpiTables` stores the addresses of all of the tables detected on a platform. The SDTs are parsed by this |
| 36 | //! library, or can be accessed directly with `from_sdt`, while the `DSDT` and any `SSDTs` should be parsed with |
| 37 | //! `aml`. |
| 38 | //! |
| 39 | //! To gather information out of the static tables, a few of the types you should take a look at are: |
| 40 | //! - [`PlatformInfo`](crate::platform::PlatformInfo) parses the FADT and MADT to create a nice view of the |
| 41 | //! processor topology and interrupt controllers on `x86_64`, and the interrupt controllers on other platforms. |
| 42 | //! `AcpiTables::platform_info` is a convenience method for constructing a `PlatformInfo`. |
| 43 | //! - [`HpetInfo`](crate::hpet::HpetInfo) parses the HPET table and tells you how to configure the High |
| 44 | //! Precision Event Timer. |
| 45 | //! - [`PciConfigRegions`](crate::mcfg::PciConfigRegions) parses the MCFG and tells you how PCIe configuration |
| 46 | //! space is mapped into physical memory. |
| 47 | |
| 48 | /* |
| 49 | * Contributing notes (you may find these useful if you're new to contributing to the library): |
| 50 | * - Accessing packed fields without UB: Lots of the structures defined by ACPI are defined with `repr(packed)` |
| 51 | * to prevent padding being introduced, which would make the structure's layout incorrect. In Rust, this |
| 52 | * creates a problem as references to these fields could be unaligned, which is undefined behaviour. For the |
| 53 | * majority of these fields, this problem can be easily avoided by telling the compiler to make a copy of the |
| 54 | * field's contents: this is the perhaps unfamiliar pattern of e.g. `!{ entry.flags }.get_bit(0)` we use |
| 55 | * around the codebase. |
| 56 | */ |
| 57 | |
| 58 | #![no_std] |
| 59 | #![deny(unsafe_op_in_unsafe_fn)] |
| 60 | #![cfg_attr(feature = "allocator_api", feature(allocator_api))] |
| 61 | |
| 62 | #[cfg_attr(test, macro_use)] |
| 63 | #[cfg(test)] |
| 64 | extern crate std; |
| 65 | |
| 66 | #[cfg(feature = "alloc")] |
| 67 | extern crate alloc; |
| 68 | |
| 69 | pub mod address; |
| 70 | pub mod bgrt; |
| 71 | pub mod fadt; |
| 72 | pub mod handler; |
| 73 | pub mod hpet; |
| 74 | pub mod madt; |
| 75 | pub mod mcfg; |
| 76 | pub mod rsdp; |
| 77 | pub mod sdt; |
| 78 | |
| 79 | #[cfg(feature = "allocator_api")] |
| 80 | mod managed_slice; |
| 81 | #[cfg(feature = "allocator_api")] |
| 82 | pub use managed_slice::*; |
| 83 | |
| 84 | #[cfg(feature = "allocator_api")] |
| 85 | pub mod platform; |
| 86 | #[cfg(feature = "allocator_api")] |
| 87 | pub use crate::platform::{interrupt::InterruptModel, PlatformInfo}; |
| 88 | |
| 89 | #[cfg(feature = "allocator_api")] |
| 90 | pub use crate::mcfg::PciConfigRegions; |
| 91 | |
| 92 | pub use fadt::PowerProfile; |
| 93 | pub use handler::{AcpiHandler, PhysicalMapping}; |
| 94 | pub use hpet::HpetInfo; |
| 95 | pub use madt::MadtError; |
| 96 | |
| 97 | use crate::sdt::{SdtHeader, Signature}; |
| 98 | use core::mem; |
| 99 | use rsdp::Rsdp; |
| 100 | |
| 101 | /// Result type used by error-returning functions. |
| 102 | pub type AcpiResult<T> = core::result::Result<T, AcpiError>; |
| 103 | |
| 104 | /// All types representing ACPI tables should implement this trait. |
| 105 | /// |
| 106 | /// ### Safety |
| 107 | /// |
| 108 | /// The table's memory is naively interpreted, so you must be careful in providing a type that |
| 109 | /// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will |
| 110 | /// be the size specified in the SDT's header. Providing a table impl that is larger than this, *may* lead to |
| 111 | /// page-faults, aliasing references, or derefencing uninitialized memory (the latter two being UB). |
| 112 | /// This isn't forbidden, however, because some tables rely on the impl being larger than a provided SDT in some |
| 113 | /// versions of ACPI (the [`ExtendedField`](crate::sdt::ExtendedField) type will be useful if you need to do |
| 114 | /// this. See our [`Fadt`](crate::fadt::Fadt) type for an example of this). |
| 115 | pub unsafe trait AcpiTable { |
| 116 | const SIGNATURE: Signature; |
| 117 | |
| 118 | fn header(&self) -> &sdt::SdtHeader; |
| 119 | |
| 120 | fn validate(&self) -> AcpiResult<()> { |
| 121 | self.header().validate(Self::SIGNATURE) |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Error type used by functions that return an `AcpiResult<T>`. |
| 126 | #[derive(Debug)] |
| 127 | pub enum AcpiError { |
| 128 | NoValidRsdp, |
| 129 | RsdpIncorrectSignature, |
| 130 | RsdpInvalidOemId, |
| 131 | RsdpInvalidChecksum, |
| 132 | |
| 133 | SdtInvalidSignature(Signature), |
| 134 | SdtInvalidOemId(Signature), |
| 135 | SdtInvalidTableId(Signature), |
| 136 | SdtInvalidChecksum(Signature), |
| 137 | |
| 138 | TableMissing(Signature), |
| 139 | InvalidFacsAddress, |
| 140 | InvalidDsdtAddress, |
| 141 | InvalidMadt(MadtError), |
| 142 | InvalidGenericAddress, |
| 143 | |
| 144 | AllocError, |
| 145 | } |
| 146 | |
| 147 | /// Type capable of enumerating the existing ACPI tables on the system. |
| 148 | /// |
| 149 | /// |
| 150 | /// ### Implementation Note |
| 151 | /// |
| 152 | /// When using the `allocator_api`±`alloc` features, [`PlatformInfo::new()`] or [`PlatformInfo::new_in()`] provide |
| 153 | /// a much cleaner API for enumerating ACPI structures once an `AcpiTables` has been constructed. |
| 154 | #[derive(Debug)] |
| 155 | pub struct AcpiTables<H: AcpiHandler> { |
| 156 | mapping: PhysicalMapping<H, SdtHeader>, |
| 157 | revision: u8, |
| 158 | handler: H, |
| 159 | } |
| 160 | |
| 161 | impl<H> AcpiTables<H> |
| 162 | where |
| 163 | H: AcpiHandler, |
| 164 | { |
| 165 | /// Create an `AcpiTables` if you have the physical address of the RSDP. |
| 166 | /// |
| 167 | /// ### Safety: Caller must ensure the provided address is valid to read as an RSDP. |
| 168 | pub unsafe fn from_rsdp(handler: H, address: usize) -> AcpiResult<Self> { |
| 169 | let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) }; |
| 170 | rsdp_mapping.validate()?; |
| 171 | |
| 172 | // Safety: RSDP has been validated. |
| 173 | unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) } |
| 174 | } |
| 175 | |
| 176 | /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not |
| 177 | /// work on UEFI platforms. See [Rsdp::search_for_rsdp_bios](rsdp_search::Rsdp::search_for_rsdp_bios) for |
| 178 | /// details. |
| 179 | pub unsafe fn search_for_rsdp_bios(handler: H) -> AcpiResult<Self> { |
| 180 | let rsdp_mapping = unsafe { Rsdp::search_for_on_bios(handler.clone())? }; |
| 181 | // Safety: RSDP has been validated from `Rsdp::search_for_on_bios` |
| 182 | unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) } |
| 183 | } |
| 184 | |
| 185 | /// Create an `AcpiTables` if you have a `PhysicalMapping` of the RSDP that you know is correct. This is called |
| 186 | /// from `from_rsdp` after validation, but can also be used if you've searched for the RSDP manually on a BIOS |
| 187 | /// system. |
| 188 | /// |
| 189 | /// ### Safety: Caller must ensure that the provided mapping is a fully validated RSDP. |
| 190 | pub unsafe fn from_validated_rsdp(handler: H, rsdp_mapping: PhysicalMapping<H, Rsdp>) -> AcpiResult<Self> { |
| 191 | macro_rules! read_root_table { |
| 192 | ($signature_name:ident, $address_getter:ident) => {{ |
| 193 | #[repr(transparent)] |
| 194 | struct RootTable { |
| 195 | header: SdtHeader, |
| 196 | } |
| 197 | |
| 198 | unsafe impl AcpiTable for RootTable { |
| 199 | const SIGNATURE: Signature = Signature::$signature_name; |
| 200 | |
| 201 | fn header(&self) -> &SdtHeader { |
| 202 | &self.header |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | // Unmap RSDP as soon as possible |
| 207 | let table_phys_start = rsdp_mapping.$address_getter() as usize; |
| 208 | drop(rsdp_mapping); |
| 209 | |
| 210 | // Map and validate root table |
| 211 | // SAFETY: Addresses from a validated RSDP are also guaranteed to be valid. |
| 212 | let table_mapping = unsafe { read_table::<_, RootTable>(handler.clone(), table_phys_start) }?; |
| 213 | |
| 214 | // Convert `table_mapping` to header mapping for storage |
| 215 | // Avoid requesting table unmap twice (from both original and converted `table_mapping`s) |
| 216 | let table_mapping = mem::ManuallyDrop::new(table_mapping); |
| 217 | // SAFETY: `SdtHeader` is equivalent to `Sdt` memory-wise |
| 218 | let table_mapping = unsafe { |
| 219 | PhysicalMapping::new( |
| 220 | table_mapping.physical_start(), |
| 221 | table_mapping.virtual_start().cast::<SdtHeader>(), |
| 222 | table_mapping.region_length(), |
| 223 | table_mapping.mapped_length(), |
| 224 | handler.clone(), |
| 225 | ) |
| 226 | }; |
| 227 | |
| 228 | table_mapping |
| 229 | }}; |
| 230 | } |
| 231 | |
| 232 | let revision = rsdp_mapping.revision(); |
| 233 | let root_table_mapping = if revision == 0 { |
| 234 | /* |
| 235 | * We're running on ACPI Version 1.0. We should use the 32-bit RSDT address. |
| 236 | */ |
| 237 | |
| 238 | read_root_table!(RSDT, rsdt_address) |
| 239 | } else { |
| 240 | /* |
| 241 | * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated |
| 242 | * to 32 bits on x86. |
| 243 | */ |
| 244 | |
| 245 | read_root_table!(XSDT, xsdt_address) |
| 246 | }; |
| 247 | |
| 248 | Ok(Self { mapping: root_table_mapping, revision, handler }) |
| 249 | } |
| 250 | |
| 251 | /// The ACPI revision of the tables enumerated by this structure. |
| 252 | #[inline] |
| 253 | pub const fn revision(&self) -> u8 { |
| 254 | self.revision |
| 255 | } |
| 256 | |
| 257 | /// Constructs a [`TablesPhysPtrsIter`] over this table. |
| 258 | fn tables_phys_ptrs(&self) -> TablesPhysPtrsIter<'_> { |
| 259 | // SAFETY: The virtual address of the array of pointers follows the virtual address of the table in memory. |
| 260 | let ptrs_virt_start = unsafe { self.mapping.virtual_start().as_ptr().add(1).cast::<u8>() }; |
| 261 | let ptrs_bytes_len = self.mapping.region_length() - mem::size_of::<SdtHeader>(); |
| 262 | // SAFETY: `ptrs_virt_start` points to an array of `ptrs_bytes_len` bytes that lives as long as `self`. |
| 263 | let ptrs_bytes = unsafe { core::slice::from_raw_parts(ptrs_virt_start, ptrs_bytes_len) }; |
| 264 | let ptr_size = if self.revision == 0 { |
| 265 | 4 // RSDT entry size |
| 266 | } else { |
| 267 | 8 // XSDT entry size |
| 268 | }; |
| 269 | |
| 270 | ptrs_bytes.chunks(ptr_size).map(|ptr_bytes_src| { |
| 271 | // Construct a native pointer using as many bytes as required from `ptr_bytes_src` (note that ACPI is |
| 272 | // little-endian) |
| 273 | |
| 274 | let mut ptr_bytes_dst = [0; mem::size_of::<usize>()]; |
| 275 | let common_ptr_size = usize::min(mem::size_of::<usize>(), ptr_bytes_src.len()); |
| 276 | ptr_bytes_dst[..common_ptr_size].copy_from_slice(&ptr_bytes_src[..common_ptr_size]); |
| 277 | |
| 278 | usize::from_le_bytes(ptr_bytes_dst) as *const SdtHeader |
| 279 | }) |
| 280 | } |
| 281 | |
| 282 | /// Searches through the ACPI table headers and attempts to locate the table with a matching `T::SIGNATURE`. |
| 283 | pub fn find_table<T: AcpiTable>(&self) -> AcpiResult<PhysicalMapping<H, T>> { |
| 284 | self.tables_phys_ptrs() |
| 285 | .find_map(|table_phys_ptr| { |
| 286 | // SAFETY: Table guarantees its contained addresses to be valid. |
| 287 | match unsafe { read_table(self.handler.clone(), table_phys_ptr as usize) } { |
| 288 | Ok(table_mapping) => Some(table_mapping), |
| 289 | Err(AcpiError::SdtInvalidSignature(_)) => None, |
| 290 | Err(e) => { |
| 291 | log::warn!( |
| 292 | "Found invalid {} table at physical address {:p}: {:?}", |
| 293 | T::SIGNATURE, |
| 294 | table_phys_ptr, |
| 295 | e |
| 296 | ); |
| 297 | |
| 298 | None |
| 299 | } |
| 300 | } |
| 301 | }) |
| 302 | .ok_or(AcpiError::TableMissing(T::SIGNATURE)) |
| 303 | } |
| 304 | |
| 305 | /// Finds and returns the DSDT AML table, if it exists. |
| 306 | pub fn dsdt(&self) -> AcpiResult<AmlTable> { |
| 307 | self.find_table::<fadt::Fadt>().and_then(|fadt| { |
| 308 | #[repr(transparent)] |
| 309 | struct Dsdt { |
| 310 | header: SdtHeader, |
| 311 | } |
| 312 | |
| 313 | // Safety: Implementation properly represents a valid DSDT. |
| 314 | unsafe impl AcpiTable for Dsdt { |
| 315 | const SIGNATURE: Signature = Signature::DSDT; |
| 316 | |
| 317 | fn header(&self) -> &SdtHeader { |
| 318 | &self.header |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | let dsdt_address = fadt.dsdt_address()?; |
| 323 | let dsdt = unsafe { read_table::<H, Dsdt>(self.handler.clone(), dsdt_address)? }; |
| 324 | |
| 325 | Ok(AmlTable::new(dsdt_address, dsdt.header().length)) |
| 326 | }) |
| 327 | } |
| 328 | |
| 329 | /// Iterates through all of the SSDT tables. |
| 330 | pub fn ssdts(&self) -> SsdtIterator<H> { |
| 331 | SsdtIterator { tables_phys_ptrs: self.tables_phys_ptrs(), handler: self.handler.clone() } |
| 332 | } |
| 333 | |
| 334 | /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the |
| 335 | /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about |
| 336 | /// the platform from the ACPI tables. |
| 337 | /// |
| 338 | /// Like `platform_info_in`, but uses the global allocator. |
| 339 | #[cfg(feature = "alloc")] |
| 340 | pub fn platform_info(&self) -> AcpiResult<PlatformInfo<alloc::alloc::Global>> { |
| 341 | PlatformInfo::new(self) |
| 342 | } |
| 343 | |
| 344 | /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the |
| 345 | /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about |
| 346 | /// the platform from the ACPI tables. |
| 347 | #[cfg(feature = "allocator_api")] |
| 348 | pub fn platform_info_in<A>(&self, allocator: A) -> AcpiResult<PlatformInfo<A>> |
| 349 | where |
| 350 | A: core::alloc::Allocator + Clone, |
| 351 | { |
| 352 | PlatformInfo::new_in(self, allocator) |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | #[derive(Debug)] |
| 357 | pub struct Sdt { |
| 358 | /// Physical address of the start of the SDT, including the header. |
| 359 | pub physical_address: usize, |
| 360 | /// Length of the table in bytes. |
| 361 | pub length: u32, |
| 362 | /// Whether this SDT has been validated. This is set to `true` the first time it is mapped and validated. |
| 363 | pub validated: bool, |
| 364 | } |
| 365 | |
| 366 | /// An iterator over the physical table addresses in an RSDT or XSDT. |
| 367 | type TablesPhysPtrsIter<'t> = core::iter::Map<core::slice::Chunks<'t, u8>, fn(&[u8]) -> *const SdtHeader>; |
| 368 | |
| 369 | #[derive(Debug)] |
| 370 | pub struct AmlTable { |
| 371 | /// Physical address of the start of the AML stream (excluding the table header). |
| 372 | pub address: usize, |
| 373 | /// Length (in bytes) of the AML stream. |
| 374 | pub length: u32, |
| 375 | } |
| 376 | |
| 377 | impl AmlTable { |
| 378 | /// Create an `AmlTable` from the address and length of the table **including the SDT header**. |
| 379 | pub(crate) fn new(address: usize, length: u32) -> AmlTable { |
| 380 | AmlTable { |
| 381 | address: address + mem::size_of::<SdtHeader>(), |
| 382 | length: length - mem::size_of::<SdtHeader>() as u32, |
| 383 | } |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | /// ### Safety: Caller must ensure the provided address is valid for being read as an `SdtHeader`. |
| 388 | unsafe fn read_table<H: AcpiHandler, T: AcpiTable>( |
| 389 | handler: H, |
| 390 | address: usize, |
| 391 | ) -> AcpiResult<PhysicalMapping<H, T>> { |
| 392 | // Attempt to peek at the SDT header to correctly enumerate the entire table. |
| 393 | |
| 394 | // SAFETY: `address` needs to be valid for the size of `SdtHeader`, or the ACPI tables are malformed (not a |
| 395 | // software issue). |
| 396 | let header_mapping = unsafe { handler.map_physical_region::<SdtHeader>(address, mem::size_of::<SdtHeader>()) }; |
| 397 | |
| 398 | SdtHeader::validate_lazy(header_mapping, handler) |
| 399 | } |
| 400 | |
| 401 | /// Iterator that steps through all of the tables, and returns only the SSDTs as `AmlTable`s. |
| 402 | pub struct SsdtIterator<'t, H> |
| 403 | where |
| 404 | H: AcpiHandler, |
| 405 | { |
| 406 | tables_phys_ptrs: TablesPhysPtrsIter<'t>, |
| 407 | handler: H, |
| 408 | } |
| 409 | |
| 410 | impl<'t, H> Iterator for SsdtIterator<'t, H> |
| 411 | where |
| 412 | H: AcpiHandler, |
| 413 | { |
| 414 | type Item = AmlTable; |
| 415 | |
| 416 | fn next(&mut self) -> Option<Self::Item> { |
| 417 | #[repr(transparent)] |
| 418 | struct Ssdt { |
| 419 | header: SdtHeader, |
| 420 | } |
| 421 | |
| 422 | // SAFETY: Implementation properly represents a valid SSDT. |
| 423 | unsafe impl AcpiTable for Ssdt { |
| 424 | const SIGNATURE: Signature = Signature::SSDT; |
| 425 | |
| 426 | fn header(&self) -> &SdtHeader { |
| 427 | &self.header |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | // Borrow single field for closure to avoid immutable reference to `self` that inhibits `find_map` |
| 432 | let handler = &self.handler; |
| 433 | |
| 434 | // Consume iterator until next valid SSDT and return the latter |
| 435 | self.tables_phys_ptrs.find_map(|table_phys_ptr| { |
| 436 | // SAFETY: Table guarantees its contained addresses to be valid. |
| 437 | match unsafe { read_table::<_, Ssdt>(handler.clone(), table_phys_ptr as usize) } { |
| 438 | Ok(ssdt_mapping) => Some(AmlTable::new(ssdt_mapping.physical_start(), ssdt_mapping.header.length)), |
| 439 | Err(AcpiError::SdtInvalidSignature(_)) => None, |
| 440 | Err(e) => { |
| 441 | log::warn!("Found invalid SSDT at physical address {:p}: {:?}", table_phys_ptr, e); |
| 442 | |
| 443 | None |
| 444 | } |
| 445 | } |
| 446 | }) |
| 447 | } |
| 448 | } |