lib.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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 requires `alloc` to make heap allocations. If you are trying to find the RSDP in an environment that
  11. //! does not have a heap (e.g. a bootloader), you can use the `rsdp` crate. The types from that crate are
  12. //! compatible with `acpi`.
  13. //!
  14. //! ### Usage
  15. //! To use the library, you will need to provide an implementation of the `AcpiHandler` trait, which allows the
  16. //! library to make requests such as mapping a particular region of physical memory into the virtual address space.
  17. //!
  18. //! You then need to construct an instance of `AcpiTables`, which can be done in a few ways depending on how much
  19. //! information you have:
  20. //! * Use `AcpiTables::from_rsdp` if you have the physical address of the RSDP
  21. //! * Use `AcpiTables::from_rsdt` if you have the physical address of the RSDT/XSDT
  22. //! * Use `AcpiTables::search_for_rsdp_bios` if you don't have the address of either, but **you know you are
  23. //! running on BIOS, not UEFI**
  24. //! * Use `AcpiTables::from_tables_direct` if you are using the library in an unusual setting, such as in usermode,
  25. //! and have a custom method to enumerate and access the tables.
  26. //!
  27. //! `AcpiTables` stores the addresses of all of the tables detected on a platform. The SDTs are parsed by this
  28. //! library, or can be accessed directly with `from_sdt`, while the `DSDT` and any `SSDTs` should be parsed with
  29. //! `aml`.
  30. //!
  31. //! To gather information out of the static tables, a few of the types you should take a look at are:
  32. //! - [`PlatformInfo`](crate::platform::PlatformInfo) parses the FADT and MADT to create a nice view of the
  33. //! processor topology and interrupt controllers on `x86_64`, and the interrupt controllers on other platforms.
  34. //! `AcpiTables::platform_info` is a convenience method for constructing a `PlatformInfo`.
  35. //! - [`HpetInfo`](crate::hpet::HpetInfo) parses the HPET table and tells you how to configure the High
  36. //! Precision Event Timer.
  37. //! - [`PciConfigRegions`](crate::mcfg::PciConfigRegions) parses the MCFG and tells you how PCIe configuration
  38. //! space is mapped into physical memory.
  39. #![no_std]
  40. #![feature(const_generics, unsafe_block_in_unsafe_fn)]
  41. #![deny(unsafe_op_in_unsafe_fn)]
  42. extern crate alloc;
  43. #[cfg_attr(test, macro_use)]
  44. #[cfg(test)]
  45. extern crate std;
  46. pub mod fadt;
  47. pub mod hpet;
  48. mod madt;
  49. mod mcfg;
  50. pub mod platform;
  51. pub mod sdt;
  52. pub use crate::{
  53. fadt::PowerProfile,
  54. hpet::HpetInfo,
  55. madt::MadtError,
  56. mcfg::PciConfigRegions,
  57. platform::{InterruptModel, PlatformInfo},
  58. };
  59. pub use rsdp::{
  60. handler::{AcpiHandler, PhysicalMapping},
  61. RsdpError,
  62. };
  63. use crate::sdt::{SdtHeader, Signature};
  64. use alloc::{collections::BTreeMap, vec::Vec};
  65. use core::mem;
  66. use log::trace;
  67. use rsdp::Rsdp;
  68. #[derive(Debug)]
  69. pub enum AcpiError {
  70. Rsdp(RsdpError),
  71. SdtInvalidSignature(Signature),
  72. SdtInvalidOemId(Signature),
  73. SdtInvalidTableId(Signature),
  74. SdtInvalidChecksum(Signature),
  75. TableMissing(Signature),
  76. InvalidFacsAddress,
  77. InvalidDsdtAddress,
  78. InvalidMadt(MadtError),
  79. InvalidGenericAddress,
  80. }
  81. pub struct AcpiTables<H>
  82. where
  83. H: AcpiHandler,
  84. {
  85. /// The revision of ACPI that the system uses, as inferred from the revision of the RSDT/XSDT.
  86. pub revision: u8,
  87. pub sdts: BTreeMap<sdt::Signature, Sdt>,
  88. pub dsdt: Option<AmlTable>,
  89. pub ssdts: Vec<AmlTable>,
  90. handler: H,
  91. }
  92. impl<H> AcpiTables<H>
  93. where
  94. H: AcpiHandler,
  95. {
  96. /// Create an `AcpiTables` if you have the physical address of the RSDP.
  97. pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result<AcpiTables<H>, AcpiError> {
  98. let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
  99. rsdp_mapping.validate().map_err(|err| AcpiError::Rsdp(err))?;
  100. Self::from_validated_rsdp(handler, rsdp_mapping)
  101. }
  102. /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not
  103. /// work on UEFI platforms. See [Rsdp::search_for_rsdp_bios](rsdp_search::Rsdp::search_for_rsdp_bios) for
  104. /// details.
  105. pub unsafe fn search_for_rsdp_bios(handler: H) -> Result<AcpiTables<H>, AcpiError> {
  106. let rsdp_mapping =
  107. unsafe { Rsdp::search_for_on_bios(handler.clone()) }.map_err(|err| AcpiError::Rsdp(err))?;
  108. Self::from_validated_rsdp(handler, rsdp_mapping)
  109. }
  110. /// Create an `AcpiTables` if you have a `PhysicalMapping` of the RSDP that you know is correct. This is called
  111. /// from `from_rsdp` after validation, but can also be used if you've searched for the RSDP manually on a BIOS
  112. /// system.
  113. pub fn from_validated_rsdp(
  114. handler: H,
  115. rsdp_mapping: PhysicalMapping<H, Rsdp>,
  116. ) -> Result<AcpiTables<H>, AcpiError> {
  117. let revision = rsdp_mapping.revision();
  118. if revision == 0 {
  119. /*
  120. * We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
  121. */
  122. let rsdt_address = rsdp_mapping.rsdt_address();
  123. unsafe { Self::from_rsdt(handler, revision, rsdt_address as usize) }
  124. } else {
  125. /*
  126. * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated
  127. * to 32 bits on x86.
  128. */
  129. let xsdt_address = rsdp_mapping.xsdt_address();
  130. unsafe { Self::from_rsdt(handler, revision, xsdt_address as usize) }
  131. }
  132. }
  133. /// Create an `AcpiTables` if you have the physical address of the RSDT. This is useful, for example, if your chosen
  134. /// bootloader reads the RSDP and passes you the address of the RSDT. You also need to supply the correct ACPI
  135. /// revision - if `0`, a RSDT is expected, while a `XSDT` is expected for greater revisions.
  136. pub unsafe fn from_rsdt(handler: H, revision: u8, rsdt_address: usize) -> Result<AcpiTables<H>, AcpiError> {
  137. let mut result = AcpiTables { revision, sdts: BTreeMap::new(), dsdt: None, ssdts: Vec::new(), handler };
  138. let header = sdt::peek_at_sdt_header(&result.handler, rsdt_address);
  139. let mapping =
  140. unsafe { result.handler.map_physical_region::<SdtHeader>(rsdt_address, header.length as usize) };
  141. if revision == 0 {
  142. /*
  143. * ACPI Version 1.0. It's a RSDT!
  144. */
  145. mapping.validate(sdt::Signature::RSDT)?;
  146. let num_tables = (mapping.length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u32>();
  147. let tables_base =
  148. ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u32;
  149. for i in 0..num_tables {
  150. result.process_sdt(unsafe { *tables_base.offset(i as isize) as usize })?;
  151. }
  152. } else {
  153. /*
  154. * ACPI Version 2.0+. It's a XSDT!
  155. */
  156. mapping.validate(sdt::Signature::XSDT)?;
  157. let num_tables = (mapping.length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u64>();
  158. let tables_base =
  159. ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u64;
  160. for i in 0..num_tables {
  161. result.process_sdt(unsafe { *tables_base.offset(i as isize) as usize })?;
  162. }
  163. }
  164. Ok(result)
  165. }
  166. /// Construct an `AcpiTables` from a custom set of "discovered" tables. This is provided to allow the library
  167. /// to be used from unconventional settings (e.g. in userspace), for example with a `AcpiHandler` that detects
  168. /// accesses to specific physical addresses, and provides the correct data.
  169. pub fn from_tables_direct(
  170. handler: H,
  171. revision: u8,
  172. sdts: BTreeMap<sdt::Signature, Sdt>,
  173. dsdt: Option<AmlTable>,
  174. ssdts: Vec<AmlTable>,
  175. ) -> AcpiTables<H> {
  176. AcpiTables { revision, sdts, dsdt, ssdts, handler }
  177. }
  178. fn process_sdt(&mut self, physical_address: usize) -> Result<(), AcpiError> {
  179. let header = sdt::peek_at_sdt_header(&self.handler, physical_address);
  180. trace!("Found ACPI table with signature {:?} and length {:?}", header.signature, header.length);
  181. match header.signature {
  182. Signature::FADT => {
  183. use fadt::Fadt;
  184. /*
  185. * For whatever reason, they chose to put the DSDT inside the FADT, instead of just listing it
  186. * as another SDT. We extract it here to provide a nicer public API.
  187. */
  188. let fadt_mapping =
  189. unsafe { self.handler.map_physical_region::<Fadt>(physical_address, mem::size_of::<Fadt>()) };
  190. fadt_mapping.validate()?;
  191. let dsdt_address = fadt_mapping.dsdt_address()?;
  192. let dsdt_header = sdt::peek_at_sdt_header(&self.handler, dsdt_address);
  193. self.dsdt = Some(AmlTable::new(dsdt_address, dsdt_header.length));
  194. /*
  195. * We've already validated the FADT to get the DSDT out, so it doesn't need to be done again.
  196. */
  197. self.sdts
  198. .insert(Signature::FADT, Sdt { physical_address, length: header.length, validated: true });
  199. }
  200. Signature::SSDT => {
  201. self.ssdts.push(AmlTable::new(physical_address, header.length));
  202. }
  203. signature => {
  204. self.sdts.insert(signature, Sdt { physical_address, length: header.length, validated: false });
  205. }
  206. }
  207. Ok(())
  208. }
  209. /// Create a mapping to a SDT, given its signature. This validates the SDT if it has not already been
  210. /// validated.
  211. ///
  212. /// ### Safety
  213. /// The table's memory is naively interpreted as a `T`, and so you must be careful in providing a type that
  214. /// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
  215. /// be the size specified in the SDT's header. Providing a `T` that is larger than this, *may* lead to
  216. /// page-faults, aliasing references, or derefencing uninitialized memory (the latter two of which are UB).
  217. /// This isn't forbidden, however, because some tables rely on `T` being larger than a provided SDT in some
  218. /// versions of ACPI (the [`ExtendedField`](crate::sdt::ExtendedField) type will be useful if you need to do
  219. /// this. See our [`Fadt`](crate::fadt::Fadt) type for an example of this).
  220. pub unsafe fn get_sdt<T>(&self, signature: sdt::Signature) -> Result<Option<PhysicalMapping<H, T>>, AcpiError>
  221. where
  222. T: AcpiTable,
  223. {
  224. let sdt = match self.sdts.get(&signature) {
  225. Some(sdt) => sdt,
  226. None => return Ok(None),
  227. };
  228. let mapping = unsafe { self.handler.map_physical_region::<T>(sdt.physical_address, sdt.length as usize) };
  229. if !sdt.validated {
  230. mapping.header().validate(signature)?;
  231. }
  232. Ok(Some(mapping))
  233. }
  234. /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the
  235. /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about
  236. /// the platform from the ACPI tables.
  237. pub fn platform_info(&self) -> Result<PlatformInfo, AcpiError> {
  238. PlatformInfo::new(self)
  239. }
  240. }
  241. pub struct Sdt {
  242. /// Physical address of the start of the SDT, including the header.
  243. pub physical_address: usize,
  244. /// Length of the table in bytes.
  245. pub length: u32,
  246. /// Whether this SDT has been validated. This is set to `true` the first time it is mapped and validated.
  247. pub validated: bool,
  248. }
  249. /// All types representing ACPI tables should implement this trait.
  250. pub trait AcpiTable {
  251. fn header(&self) -> &sdt::SdtHeader;
  252. }
  253. #[derive(Debug)]
  254. pub struct AmlTable {
  255. /// Physical address of the start of the AML stream (excluding the table header).
  256. pub address: usize,
  257. /// Length (in bytes) of the AML stream.
  258. pub length: u32,
  259. }
  260. impl AmlTable {
  261. /// Create an `AmlTable` from the address and length of the table **including the SDT header**.
  262. pub(crate) fn new(address: usize, length: u32) -> AmlTable {
  263. AmlTable {
  264. address: address + mem::size_of::<SdtHeader>(),
  265. length: length - mem::size_of::<SdtHeader>() as u32,
  266. }
  267. }
  268. }