acpi/lib.rs
1//! `acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a
2//! complex framework for power management and device discovery and configuration. ACPI is used on
3//! modern x64, as well as some ARM and RISC-V platforms. An operating system needs to interact with
4//! ACPI to correctly set up a platform's interrupt controllers, perform power management, and fully
5//! support many other platform capabilities.
6//!
7//! This crate provides a limited API that can be used without an allocator, for example for use
8//! from a bootloader. This API will allow you to search for the RSDP, enumerate over the available
9//! tables, and interact with the tables using their raw structures. All other functionality is
10//! behind an `alloc` feature (enabled by default) and requires an allocator.
11//!
12//! With an allocator, this crate also provides higher-level interfaces to the static tables, as
13//! well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT
14//! tables.
15//!
16//! ### Usage
17//! To use the library, you will need to provide an implementation of the [`Handler`] trait,
18//! which allows the library to make requests such as mapping a particular region of physical
19//! memory into the virtual address space.
20//!
21//! Next, you'll need to get the physical address of either the RSDP, or the RSDT/XSDT. The method
22//! for doing this depends on the platform you're running on and how you were booted. If you know
23//! the system was booted via the BIOS, you can use [`rsdp::Rsdp::search_for_on_bios`]. UEFI provides a
24//! separate mechanism for getting the address of the RSDP.
25//!
26//! You then need to construct an instance of [`AcpiTables`], which can be done in a few ways
27//! depending on how much 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//!
31//! Once you have an [`AcpiTables`], you can search for relevant tables, or use the higher-level
32//! interfaces, such as [`PlatformInfo`], [`PciConfigRegions`], or [`HpetInfo`].
33
34#![no_std]
35#![feature(allocator_api)]
36
37#[cfg_attr(test, macro_use)]
38#[cfg(test)]
39extern crate std;
40
41#[cfg(feature = "alloc")]
42extern crate alloc;
43
44pub mod address;
45#[cfg(feature = "aml")]
46pub mod aml;
47#[cfg(feature = "alloc")]
48pub mod platform;
49pub mod registers;
50pub mod rsdp;
51pub mod sdt;
52
53pub use pci_types::PciAddress;
54pub use sdt::{fadt::PowerProfile, hpet::HpetInfo, madt::MadtError};
55
56use crate::sdt::{SdtHeader, Signature};
57use core::{
58 fmt,
59 mem,
60 ops::{Deref, DerefMut},
61 pin::Pin,
62 ptr::NonNull,
63};
64use log::warn;
65use rsdp::Rsdp;
66
67/// `AcpiTables` should be constructed after finding the RSDP or RSDT/XSDT and allows enumeration
68/// of the system's ACPI tables.
69pub struct AcpiTables<H: Handler> {
70 rsdt_mapping: PhysicalMapping<H, SdtHeader>,
71 pub rsdp_revision: u8,
72 handler: H,
73}
74
75unsafe impl<H> Send for AcpiTables<H> where H: Handler + Send {}
76unsafe impl<H> Sync for AcpiTables<H> where H: Handler + Send {}
77
78impl<H> AcpiTables<H>
79where
80 H: Handler,
81{
82 /// Construct an `AcpiTables` from the **physical** address of the RSDP.
83 ///
84 /// # Safety
85 /// The address of the RSDP must be valid.
86 pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result<AcpiTables<H>, AcpiError> {
87 let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
88
89 /*
90 * If the address given does not have a correct RSDP signature, the user has probably given
91 * us an invalid address, and we should not continue. We're more lenient with other errors
92 * as it's probably a real RSDP and the firmware developers are just lazy.
93 */
94 match rsdp_mapping.validate() {
95 Ok(()) => (),
96 Err(AcpiError::RsdpIncorrectSignature) => return Err(AcpiError::RsdpIncorrectSignature),
97 Err(AcpiError::RsdpInvalidOemId) | Err(AcpiError::RsdpInvalidChecksum) => {
98 warn!("RSDP has invalid checksum or OEM ID. Continuing.");
99 }
100 Err(_) => (),
101 }
102
103 let rsdp_revision = rsdp_mapping.revision();
104 let rsdt_address = if rsdp_revision == 0 {
105 // We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
106 rsdp_mapping.rsdt_address() as usize
107 } else {
108 /*
109 * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated
110 * to 32 bits on x86.
111 */
112 rsdp_mapping.xsdt_address() as usize
113 };
114
115 unsafe { Self::from_rsdt(handler, rsdp_revision, rsdt_address) }
116 }
117
118 /// Construct an `AcpiTables` from the **physical** address of the RSDT/XSDT, and the revision
119 /// found in the RSDP.
120 ///
121 /// # Safety
122 /// The address of the RSDT must be valid.
123 pub unsafe fn from_rsdt(
124 handler: H,
125 rsdp_revision: u8,
126 rsdt_address: usize,
127 ) -> Result<AcpiTables<H>, AcpiError> {
128 let rsdt_mapping =
129 unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, mem::size_of::<SdtHeader>()) };
130 let rsdt_length = rsdt_mapping.length;
131 let rsdt_mapping = unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, rsdt_length as usize) };
132 Ok(Self { rsdt_mapping, rsdp_revision, handler })
133 }
134
135 /// Iterate over the **physical** addresses of the SDTs.
136 pub fn table_entries(&self) -> impl Iterator<Item = usize> {
137 let entry_size = if self.rsdp_revision == 0 { 4 } else { 8 };
138 let mut table_entries_ptr =
139 unsafe { self.rsdt_mapping.virtual_start.as_ptr().byte_add(mem::size_of::<SdtHeader>()) }.cast::<u8>();
140 let mut num_entries = (self.rsdt_mapping.region_length - mem::size_of::<SdtHeader>()) / entry_size;
141
142 core::iter::from_fn(move || {
143 if num_entries > 0 {
144 unsafe {
145 let entry = if entry_size == 4 {
146 *table_entries_ptr.cast::<u32>() as usize
147 } else {
148 *table_entries_ptr.cast::<u64>() as usize
149 };
150 table_entries_ptr = table_entries_ptr.byte_add(entry_size);
151 num_entries -= 1;
152
153 Some(entry)
154 }
155 } else {
156 None
157 }
158 })
159 }
160
161 /// Iterate over the headers of each SDT, along with their **physical** addresses.
162 pub fn table_headers(&self) -> impl Iterator<Item = (usize, SdtHeader)> {
163 self.table_entries().map(|table_phys_address| {
164 let mapping = unsafe {
165 self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
166 };
167 (table_phys_address, *mapping)
168 })
169 }
170
171 /// Find all tables with the signature `T::SIGNATURE`.
172 pub fn find_tables<T>(&self) -> impl Iterator<Item = PhysicalMapping<H, T>>
173 where
174 T: AcpiTable,
175 {
176 self.table_entries().filter_map(|table_phys_address| {
177 let header_mapping = unsafe {
178 self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
179 };
180 if header_mapping.signature == T::SIGNATURE {
181 // Extend the mapping to the entire table
182 let length = header_mapping.length;
183 drop(header_mapping);
184 Some(unsafe { self.handler.map_physical_region::<T>(table_phys_address, length as usize) })
185 } else {
186 None
187 }
188 })
189 }
190
191 /// Find the first table with the signature `T::SIGNATURE`.
192 pub fn find_table<T>(&self) -> Option<PhysicalMapping<H, T>>
193 where
194 T: AcpiTable,
195 {
196 self.find_tables().next()
197 }
198
199 pub fn dsdt(&self) -> Result<AmlTable, AcpiError> {
200 let Some(fadt) = self.find_table::<sdt::fadt::Fadt>() else {
201 Err(AcpiError::TableNotFound(Signature::FADT))?
202 };
203 let phys_address = fadt.dsdt_address()?;
204 let header =
205 unsafe { self.handler.map_physical_region::<SdtHeader>(phys_address, mem::size_of::<SdtHeader>()) };
206 Ok(AmlTable { phys_address, length: header.length, revision: header.revision })
207 }
208
209 pub fn ssdts(&self) -> impl Iterator<Item = AmlTable> {
210 self.table_headers().filter_map(|(phys_address, header)| {
211 if header.signature == Signature::SSDT {
212 Some(AmlTable { phys_address, length: header.length, revision: header.revision })
213 } else {
214 None
215 }
216 })
217 }
218}
219
220#[derive(Clone, Copy, Debug)]
221pub struct AmlTable {
222 /// The physical address of the start of the table. Add `mem::size_of::<SdtHeader>()` to this
223 /// to get the physical address of the start of the AML stream.
224 pub phys_address: usize,
225 /// The length of the table, including the header.
226 pub length: u32,
227 pub revision: u8,
228}
229
230/// All types representing ACPI tables should implement this trait.
231///
232/// ### Safety
233/// The table's memory is naively interpreted, so you must be careful in providing a type that
234/// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
235/// be the size specified in the SDT's header. If a table's definition may be larger than a valid
236/// SDT's size, [`ExtendedField`](sdt::ExtendedField) should be used to define fields that may or
237/// may not exist.
238pub unsafe trait AcpiTable {
239 const SIGNATURE: Signature;
240
241 fn header(&self) -> &SdtHeader;
242
243 fn validate(&self) -> Result<(), AcpiError> {
244 unsafe { self.header().validate(Self::SIGNATURE) }
245 }
246}
247
248#[derive(Clone, Debug)]
249#[non_exhaustive]
250pub enum AcpiError {
251 NoValidRsdp,
252 RsdpIncorrectSignature,
253 RsdpInvalidOemId,
254 RsdpInvalidChecksum,
255
256 SdtInvalidSignature(Signature),
257 SdtInvalidOemId(Signature),
258 SdtInvalidTableId(Signature),
259 SdtInvalidChecksum(Signature),
260 SdtInvalidCreatorId(Signature),
261
262 TableNotFound(Signature),
263 InvalidFacsAddress,
264 InvalidDsdtAddress,
265 InvalidMadt(MadtError),
266 InvalidGenericAddress,
267
268 Timeout,
269
270 #[cfg(feature = "alloc")]
271 Aml(aml::AmlError),
272
273 /// This is emitted to signal that the library does not support the requested behaviour. This
274 /// should eventually never be emitted.
275 LibUnimplemented,
276
277 /// This can be returned by the host (user of the library) to signal that required behaviour
278 /// has not been implemented. This will cause the error to be propagated back to the host if an
279 /// operation that requires that behaviour is performed.
280 HostUnimplemented,
281}
282
283/// Describes a physical mapping created by [`Handler::map_physical_region`] and unmapped by
284/// [`Handler::unmap_physical_region`]. The region mapped must be at least `size_of::<T>()`
285/// bytes, but may be bigger.
286pub struct PhysicalMapping<H, T>
287where
288 H: Handler,
289{
290 /// The physical address of the mapped structure. The actual mapping may start at a lower address
291 /// if the requested physical address is not well-aligned.
292 pub physical_start: usize,
293 /// The virtual address of the mapped structure. It must be a valid, non-null pointer to the
294 /// start of the requested structure. The actual virtual mapping may start at a lower address
295 /// if the requested address is not well-aligned.
296 pub virtual_start: NonNull<T>,
297 /// The size of the requested region, in bytes. Can be equal or larger to `size_of::<T>()`. If a
298 /// larger region has been mapped, this should still be the requested size.
299 pub region_length: usize,
300 /// The total size of the produced mapping. This may be the same as `region_length`, or larger to
301 /// meet requirements of the mapping implementation.
302 pub mapped_length: usize,
303 /// The [`Handler`] that was used to produce the mapping. When this mapping is dropped, this
304 /// handler will be used to unmap the region.
305 pub handler: H,
306}
307
308impl<H, T> PhysicalMapping<H, T>
309where
310 H: Handler,
311{
312 /// Get a pinned reference to the inner `T`. This is generally only useful if `T` is `!Unpin`,
313 /// otherwise the mapping can simply be dereferenced to access the inner type.
314 pub fn get(&self) -> Pin<&T> {
315 unsafe { Pin::new_unchecked(self.virtual_start.as_ref()) }
316 }
317}
318
319impl<H, T> fmt::Debug for PhysicalMapping<H, T>
320where
321 H: Handler,
322{
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.debug_struct("PhysicalMapping")
325 .field("physical_start", &self.physical_start)
326 .field("virtual_start", &self.virtual_start)
327 .field("region_length", &self.region_length)
328 .field("mapped_length", &self.mapped_length)
329 .field("handler", &())
330 .finish()
331 }
332}
333
334unsafe impl<H: Handler + Send, T: Send> Send for PhysicalMapping<H, T> {}
335
336impl<H, T> Deref for PhysicalMapping<H, T>
337where
338 T: Unpin,
339 H: Handler,
340{
341 type Target = T;
342
343 fn deref(&self) -> &T {
344 unsafe { self.virtual_start.as_ref() }
345 }
346}
347
348impl<H, T> DerefMut for PhysicalMapping<H, T>
349where
350 T: Unpin,
351 H: Handler,
352{
353 fn deref_mut(&mut self) -> &mut T {
354 unsafe { self.virtual_start.as_mut() }
355 }
356}
357
358impl<H, T> Drop for PhysicalMapping<H, T>
359where
360 H: Handler,
361{
362 fn drop(&mut self) {
363 H::unmap_physical_region(self)
364 }
365}
366
367/// A `Handle` is an opaque reference to an object that is managed by the host on behalf of this
368/// library.
369///
370/// The library will treat the value of a handle as entirely opaque. You may manage handles
371/// however you wish, and the same value can be used to refer to objects of different types, if
372/// desired.
373#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
374pub struct Handle(pub u32);
375
376/// An implementation of this trait must be provided to allow `acpi` to perform operations that
377/// interface with the underlying hardware and other systems in your host implementation. This
378/// interface is designed to be flexible to allow usage of the library from a variety of settings.
379///
380/// Depending on your usage of this library, not all functionality may be required. If you do not
381/// provide certain functionality, you should return [`AcpiError::HostUnimplemented`]. The library
382/// will attempt to propagate this error back to the host if an operation cannot be performed
383/// without that functionality.
384///
385/// The `Handler` must be cheaply clonable (e.g. a reference, `Arc`, marker struct, etc.) as a copy
386/// of the handler is stored in various structures, such as in each [`PhysicalMapping`] to
387/// facilitate unmapping.
388pub trait Handler: Clone {
389 /// Given a physical address and a size, map a region of physical memory that contains `T` (note: the passed
390 /// size may be larger than `size_of::<T>()`). The address is not neccessarily page-aligned, so the
391 /// implementation may need to map more than `size` bytes. The virtual address the region is mapped to does not
392 /// matter, as long as it is accessible to `acpi`. Refer to the fields on [`PhysicalMapping`] to understand how
393 /// to produce one properly.
394 ///
395 /// ## Safety
396 ///
397 /// - `physical_address` must point to a valid `T` in physical memory.
398 /// - `size` must be at least `size_of::<T>()`.
399 unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T>;
400
401 /// Unmap the given physical mapping. This is called when a `PhysicalMapping` is dropped, you should **not** manually call this.
402 ///
403 /// Note: A reference to the `Handler` used to construct `region` can be acquired by calling [`PhysicalMapping::mapper`].
404 fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>);
405
406 // TODO: maybe we should map stuff ourselves in the AML interpreter and do this internally?
407 // Maybe provide a hook for tracing the IO / emit trace events ourselves if we do do that?
408 fn read_u8(&self, address: usize) -> u8;
409 fn read_u16(&self, address: usize) -> u16;
410 fn read_u32(&self, address: usize) -> u32;
411 fn read_u64(&self, address: usize) -> u64;
412
413 fn write_u8(&self, address: usize, value: u8);
414 fn write_u16(&self, address: usize, value: u16);
415 fn write_u32(&self, address: usize, value: u32);
416 fn write_u64(&self, address: usize, value: u64);
417
418 // TODO: would be nice to provide defaults that just do the actual port IO on x86?
419 fn read_io_u8(&self, port: u16) -> u8;
420 fn read_io_u16(&self, port: u16) -> u16;
421 fn read_io_u32(&self, port: u16) -> u32;
422
423 fn write_io_u8(&self, port: u16, value: u8);
424 fn write_io_u16(&self, port: u16, value: u16);
425 fn write_io_u32(&self, port: u16, value: u32);
426
427 fn read_pci_u8(&self, address: PciAddress, offset: u16) -> u8;
428 fn read_pci_u16(&self, address: PciAddress, offset: u16) -> u16;
429 fn read_pci_u32(&self, address: PciAddress, offset: u16) -> u32;
430
431 fn write_pci_u8(&self, address: PciAddress, offset: u16, value: u8);
432 fn write_pci_u16(&self, address: PciAddress, offset: u16, value: u16);
433 fn write_pci_u32(&self, address: PciAddress, offset: u16, value: u32);
434
435 /// Returns a monotonically-increasing value of nanoseconds.
436 fn nanos_since_boot(&self) -> u64;
437
438 /// Stall for at least the given number of **microseconds**. An implementation should not relinquish control of
439 /// the processor during the stall, and for this reason, firmwares should not stall for periods of more than
440 /// 100 microseconds.
441 fn stall(&self, microseconds: u64);
442
443 /// Sleep for at least the given number of **milliseconds**. An implementation may round to the closest sleep
444 /// time supported, and should relinquish the processor.
445 fn sleep(&self, milliseconds: u64);
446
447 #[cfg(feature = "aml")]
448 fn create_mutex(&self) -> Handle;
449
450 /// Acquire the mutex referred to by the given handle. `timeout` is a millisecond timeout value
451 /// with the following meaning:
452 /// - `0` - try to acquire the mutex once, in a non-blocking manner. If the mutex cannot be
453 /// acquired immediately, return `Err(AmlError::MutexAcquireTimeout)`
454 /// - `1-0xfffe` - try to acquire the mutex for at least `timeout` milliseconds.
455 /// - `0xffff` - try to acquire the mutex indefinitely. Should not return `MutexAcquireTimeout`.
456 ///
457 /// AML mutexes are **reentrant** - that is, a thread may acquire the same mutex more than once
458 /// without causing a deadlock.
459 #[cfg(feature = "aml")]
460 fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), aml::AmlError>;
461 #[cfg(feature = "aml")]
462 fn release(&self, mutex: Handle);
463
464 #[cfg(feature = "aml")]
465 fn breakpoint(&self) {}
466
467 #[cfg(feature = "aml")]
468 fn handle_debug(&self, _object: &aml::object::Object) {}
469
470 #[cfg(feature = "aml")]
471 fn handle_fatal_error(&self, fatal_type: u8, fatal_code: u32, fatal_arg: u64) {
472 panic!(
473 "Fatal error while executing AML (encountered DefFatalOp). fatal_type = {}, fatal_code = {}, fatal_arg = {}",
474 fatal_type, fatal_code, fatal_arg
475 );
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 #[allow(dead_code)]
485 fn test_physical_mapping_send_sync() {
486 fn test_send_sync<T: Send>() {}
487 fn caller<H: Handler + Send, T: Send>() {
488 test_send_sync::<PhysicalMapping<H, T>>();
489 }
490 }
491}