summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorInna Palant <ipalant@google.com>2024-03-13 00:55:45 +0000
committerInna Palant <ipalant@google.com>2024-03-13 00:55:45 +0000
commit6754be5f3585ea9b50a835127fb086ddb4652a8e (patch)
treea2abe66d0ef992e89bdab5b08acf973beb523e57
parent1444dc8fbfd1c24b36dc68f490fc58e9d95acc5d (diff)
parent8dac36a3052b2a93743d04d57ce077c598b7530e (diff)
downloadacpi-6754be5f3585ea9b50a835127fb086ddb4652a8e.tar.gz
Merge remote-tracking branch 'origin/upstream'
Import b/326112506
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Cargo.toml38
-rw-r--r--Cargo.toml.orig19
-rw-r--r--LICENSE222
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md39
-rw-r--r--src/address.rs105
-rw-r--r--src/bgrt.rs69
-rw-r--r--src/fadt.rs521
-rw-r--r--src/handler.rs128
-rw-r--r--src/hpet.rs99
-rw-r--r--src/lib.rs448
-rw-r--r--src/madt.rs646
-rw-r--r--src/managed_slice.rs75
-rw-r--r--src/mcfg.rs149
-rw-r--r--src/platform/interrupt.rs133
-rw-r--r--src/platform/mod.rs134
-rw-r--r--src/rsdp.rs214
-rw-r--r--src/sdt.rs302
21 files changed, 3367 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..ce5dbab
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "6b38e25e27c5e3762ac85d76e6ae588691e91178"
+ },
+ "path_in_vcs": "acpi"
+} \ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..61033d3
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,38 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "acpi"
+version = "5.0.0"
+authors = ["Isaac Woods"]
+description = "A pure-Rust library for parsing ACPI tables"
+readme = "README.md"
+categories = [
+ "hardware-support",
+ "no-std",
+]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/rust-osdev/acpi"
+
+[dependencies.bit_field]
+version = "0.10.2"
+
+[dependencies.log]
+version = "0.4.20"
+
+[features]
+alloc = ["allocator_api"]
+allocator_api = []
+default = [
+ "allocator_api",
+ "alloc",
+]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..e80b462
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,19 @@
+[package]
+name = "acpi"
+version = "5.0.0"
+authors = ["Isaac Woods"]
+repository = "https://github.com/rust-osdev/acpi"
+description = "A pure-Rust library for parsing ACPI tables"
+categories = ["hardware-support", "no-std"]
+readme = "../README.md"
+license = "MIT/Apache-2.0"
+edition = "2021"
+
+[dependencies]
+bit_field = "0.10.2"
+log = "0.4.20"
+
+[features]
+default = ["allocator_api", "alloc"]
+allocator_api = []
+alloc = ["allocator_api"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3129885
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,222 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-----
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..df3c062
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "acpi"
+description: "A pure-Rust library for parsing ACPI tables"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "https://crates.io/crates/acpi"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/acpi/acpi-5.0.0.crate"
+ }
+ version: "5.0.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 2
+ day: 26
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..5a2b844
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8b14560
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# Acpi
+![Build Status](https://github.com/rust-osdev/acpi/actions/workflows/build.yml/badge.svg)
+[![Version](https://img.shields.io/crates/v/rsdp.svg?style=rounded-square)](https://crates.io/crates/rsdp/)
+[![Version](https://img.shields.io/crates/v/acpi.svg?style=rounded-square)](https://crates.io/crates/acpi/)
+[![Version](https://img.shields.io/crates/v/aml.svg?style=rounded-square)](https://crates.io/crates/aml/)
+
+### [Documentation (`rsdp`)](https://docs.rs/rsdp)
+### [Documentation (`acpi`)](https://docs.rs/acpi)
+### [Documentation (`aml`)](https://docs.rs/aml)
+
+A library to parse ACPI tables and AML, written in pure Rust. Designed to be easy to use from Rust bootloaders and kernels. The library is split into three crates:
+- `rsdp` parses the RSDP and can locate it on BIOS platforms. It does not depend on `alloc`, so is suitable to use from bootloaders without heap alloctors. All of its
+ functionality is reexported by `acpi`.
+- `acpi` parses the static tables (useful but not feature-complete). It can be used from environments that have allocators, and ones that don't (but with reduced functionality).
+- `aml` parses the AML tables (can be useful, far from feature-complete).
+
+There is also the `acpi-dumper` utility to easily dump a platform's ACPI tables (this currently only works on Linux).
+
+## Contributing
+Contributions are more than welcome! You can:
+- Write code - the ACPI spec is huge and there are bound to be things we don't support yet!
+- Improve our documentation!
+- Use the crates within your kernel and file bug reports and feature requests!
+
+Useful resources for contributing are:
+- [The ACPI specification](https://uefi.org/specifications)
+- [OSDev Wiki](https://wiki.osdev.org/ACPI)
+
+You can run the AML test suite with `cargo run --bin aml_tester -- -p tests`.
+You can run fuzz the AML parser with `cd aml && cargo fuzz run fuzz_target_1` (you may need to `cargo install cargo-fuzz`).
+
+## Licence
+This project is dual-licenced under:
+- Apache Licence, Version 2.0 ([LICENCE-APACHE](LICENCE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+- MIT license ([LICENCE-MIT](LICENCE-MIT) or http://opensource.org/licenses/MIT)
+
+Unless you explicitly state otherwise, any contribution submitted for inclusion in this work by you,
+as defined in the Apache-2.0 licence, shall be dual licenced as above, without additional terms or
+conditions.
diff --git a/src/address.rs b/src/address.rs
new file mode 100644
index 0000000..6681736
--- /dev/null
+++ b/src/address.rs
@@ -0,0 +1,105 @@
+//! ACPI defines a Generic Address Structure (GAS), which provides a versatile way to describe register locations
+//! in a wide range of address spaces.
+
+use crate::AcpiError;
+use core::convert::TryFrom;
+
+/// This is the raw form of a Generic Address Structure, and follows the layout found in the ACPI tables. It does
+/// not form part of the public API, and should be turned into a `GenericAddress` for most use-cases.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub(crate) struct RawGenericAddress {
+ pub address_space: u8,
+ pub bit_width: u8,
+ pub bit_offset: u8,
+ pub access_size: u8,
+ pub address: u64,
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub enum AddressSpace {
+ SystemMemory,
+ SystemIo,
+ /// Describes a register in the configuration space of a PCI device in segment `0`, on bus `0`. The `address`
+ /// field is of the format:
+ /// ```ignore
+ /// 64 48 32 16 0
+ /// +---------------+---------------+---------------+---------------+
+ /// | reserved (0) | device | function | offset |
+ /// +---------------+---------------+---------------+---------------+
+ /// ```
+ PciConfigSpace,
+ EmbeddedController,
+ SMBus,
+ SystemCmos,
+ PciBarTarget,
+ Ipmi,
+ GeneralIo,
+ GenericSerialBus,
+ PlatformCommunicationsChannel,
+ FunctionalFixedHardware,
+ OemDefined(u8),
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub enum AccessSize {
+ Undefined,
+ ByteAccess,
+ WordAccess,
+ DWordAccess,
+ QWordAccess,
+}
+
+impl TryFrom<u8> for AccessSize {
+ type Error = AcpiError;
+
+ fn try_from(size: u8) -> Result<Self, Self::Error> {
+ match size {
+ 0 => Ok(AccessSize::Undefined),
+ 1 => Ok(AccessSize::ByteAccess),
+ 2 => Ok(AccessSize::WordAccess),
+ 3 => Ok(AccessSize::DWordAccess),
+ 4 => Ok(AccessSize::QWordAccess),
+ _ => Err(AcpiError::InvalidGenericAddress),
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct GenericAddress {
+ pub address_space: AddressSpace,
+ pub bit_width: u8,
+ pub bit_offset: u8,
+ pub access_size: AccessSize,
+ pub address: u64,
+}
+
+impl GenericAddress {
+ pub(crate) fn from_raw(raw: RawGenericAddress) -> crate::AcpiResult<GenericAddress> {
+ let address_space = match raw.address_space {
+ 0x00 => AddressSpace::SystemMemory,
+ 0x01 => AddressSpace::SystemIo,
+ 0x02 => AddressSpace::PciConfigSpace,
+ 0x03 => AddressSpace::EmbeddedController,
+ 0x04 => AddressSpace::SMBus,
+ 0x05 => AddressSpace::SystemCmos,
+ 0x06 => AddressSpace::PciBarTarget,
+ 0x07 => AddressSpace::Ipmi,
+ 0x08 => AddressSpace::GeneralIo,
+ 0x09 => AddressSpace::GenericSerialBus,
+ 0x0a => AddressSpace::PlatformCommunicationsChannel,
+ 0x0b..=0x7e => return Err(AcpiError::InvalidGenericAddress),
+ 0x7f => AddressSpace::FunctionalFixedHardware,
+ 0x80..=0xbf => return Err(AcpiError::InvalidGenericAddress),
+ 0xc0..=0xff => AddressSpace::OemDefined(raw.address_space),
+ };
+
+ Ok(GenericAddress {
+ address_space,
+ bit_width: raw.bit_width,
+ bit_offset: raw.bit_offset,
+ access_size: AccessSize::try_from(raw.access_size)?,
+ address: raw.address,
+ })
+ }
+}
diff --git a/src/bgrt.rs b/src/bgrt.rs
new file mode 100644
index 0000000..ab23151
--- /dev/null
+++ b/src/bgrt.rs
@@ -0,0 +1,69 @@
+use crate::{
+ sdt::{SdtHeader, Signature},
+ AcpiTable,
+};
+use bit_field::BitField;
+
+/// The BGRT table contains information about a boot graphic that was displayed
+/// by firmware.
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy)]
+pub struct Bgrt {
+ header: SdtHeader,
+ pub version: u16,
+ status: u8,
+ image_type: u8,
+ pub image_address: u64,
+ image_offset_x: u32,
+ image_offset_y: u32,
+}
+
+/// ### Safety: Implementation properly represents a valid BGRT.
+unsafe impl AcpiTable for Bgrt {
+ const SIGNATURE: Signature = Signature::BGRT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
+
+impl Bgrt {
+ pub fn image_type(&self) -> ImageType {
+ let img_type = self.image_type;
+ match img_type {
+ 0 => ImageType::Bitmap,
+ _ => ImageType::Reserved,
+ }
+ }
+
+ /// Gets the orientation offset of the image.
+ /// Degrees are clockwise from the images default orientation.
+ pub fn orientation_offset(&self) -> u16 {
+ let status = self.status;
+ match status.get_bits(1..3) {
+ 0 => 0,
+ 1 => 90,
+ 2 => 180,
+ 3 => 270,
+ _ => unreachable!(), // will never happen
+ }
+ }
+
+ pub fn was_displayed(&self) -> bool {
+ let status = self.status;
+ status.get_bit(0)
+ }
+
+ pub fn image_offset(&self) -> (u32, u32) {
+ let x = self.image_offset_x;
+ let y = self.image_offset_y;
+ (x, y)
+ }
+}
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum ImageType {
+ Bitmap,
+ Reserved,
+}
diff --git a/src/fadt.rs b/src/fadt.rs
new file mode 100644
index 0000000..f97c4e6
--- /dev/null
+++ b/src/fadt.rs
@@ -0,0 +1,521 @@
+use crate::{
+ address::{AccessSize, AddressSpace, GenericAddress, RawGenericAddress},
+ sdt::{ExtendedField, SdtHeader, Signature},
+ AcpiError,
+ AcpiTable,
+};
+use bit_field::BitField;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PowerProfile {
+ Unspecified,
+ Desktop,
+ Mobile,
+ Workstation,
+ EnterpriseServer,
+ SohoServer,
+ AppliancePc,
+ PerformanceServer,
+ Tablet,
+ Reserved(u8),
+}
+
+/// Represents the Fixed ACPI Description Table (FADT). This table contains various fixed hardware
+/// details, such as the addresses of the hardware register blocks. It also contains a pointer to
+/// the Differentiated Definition Block (DSDT).
+///
+/// In cases where the FADT contains both a 32-bit and 64-bit field for the same address, we should
+/// always prefer the 64-bit one. Only if it's zero or the CPU will not allow us to access that
+/// address should the 32-bit one be used.
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy)]
+pub struct Fadt {
+ header: SdtHeader,
+
+ firmware_ctrl: u32,
+ dsdt_address: u32,
+
+ // Used in acpi 1.0; compatibility only, should be zero
+ _reserved: u8,
+
+ preferred_pm_profile: u8,
+ /// On systems with an i8259 PIC, this is the vector the System Control Interrupt (SCI) is wired to. On other systems, this is
+ /// the Global System Interrupt (GSI) number of the SCI.
+ ///
+ /// The SCI should be treated as a sharable, level, active-low interrupt.
+ pub sci_interrupt: u16,
+ /// The system port address of the SMI Command Port. This port should only be accessed from the boot processor.
+ /// A value of `0` indicates that System Management Mode is not supported.
+ ///
+ /// - Writing the value in `acpi_enable` to this port will transfer control of the ACPI hardware registers
+ /// from the firmware to the OS. You must synchronously wait for the transfer to complete, indicated by the
+ /// setting of `SCI_EN`.
+ /// - Writing the value in `acpi_disable` will relinquish ownership of the hardware registers to the
+ /// firmware. This should only be done if you've previously acquired ownership. Before writing this value,
+ /// the OS should mask all SCI interrupts and clear the `SCI_EN` bit.
+ /// - Writing the value in `s4bios_req` requests that the firmware enter the S4 state through the S4BIOS
+ /// feature. This is only supported if the `S4BIOS_F` flag in the FACS is set.
+ /// - Writing the value in `pstate_control` yields control of the processor performance state to the OS.
+ /// If this field is `0`, this feature is not supported.
+ /// - Writing the value in `c_state_control` tells the firmware that the OS supports `_CST` AML objects and
+ /// notifications of C State changes.
+ pub smi_cmd_port: u32,
+ pub acpi_enable: u8,
+ pub acpi_disable: u8,
+ pub s4bios_req: u8,
+ pub pstate_control: u8,
+ pm1a_event_block: u32,
+ pm1b_event_block: u32,
+ pm1a_control_block: u32,
+ pm1b_control_block: u32,
+ pm2_control_block: u32,
+ pm_timer_block: u32,
+ gpe0_block: u32,
+ gpe1_block: u32,
+ pm1_event_length: u8,
+ pm1_control_length: u8,
+ pm2_control_length: u8,
+ pm_timer_length: u8,
+ gpe0_block_length: u8,
+ gpe1_block_length: u8,
+ pub gpe1_base: u8,
+ pub c_state_control: u8,
+ /// The worst-case latency to enter and exit the C2 state, in microseconds. A value `>100` indicates that the
+ /// system does not support the C2 state.
+ pub worst_c2_latency: u16,
+ /// The worst-case latency to enter and exit the C3 state, in microseconds. A value `>1000` indicates that the
+ /// system does not support the C3 state.
+ pub worst_c3_latency: u16,
+ pub flush_size: u16,
+ pub flush_stride: u16,
+ pub duty_offset: u8,
+ pub duty_width: u8,
+ pub day_alarm: u8,
+ pub month_alarm: u8,
+ pub century: u8,
+ pub iapc_boot_arch: IaPcBootArchFlags,
+ _reserved2: u8, // must be 0
+ pub flags: FixedFeatureFlags,
+ reset_reg: RawGenericAddress,
+ pub reset_value: u8,
+ pub arm_boot_arch: ArmBootArchFlags,
+ fadt_minor_version: u8,
+ x_firmware_ctrl: ExtendedField<u64, 2>,
+ x_dsdt_address: ExtendedField<u64, 2>,
+ x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
+ x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
+ x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
+ x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
+ x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
+ x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
+ x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
+ x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
+ sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
+ sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
+ hypervisor_vendor_id: ExtendedField<u64, 2>,
+}
+
+/// ### Safety: Implementation properly represents a valid FADT.
+unsafe impl AcpiTable for Fadt {
+ const SIGNATURE: Signature = Signature::FADT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
+
+impl Fadt {
+ pub fn validate(&self) -> Result<(), AcpiError> {
+ self.header.validate(crate::sdt::Signature::FADT)
+ }
+
+ pub fn facs_address(&self) -> Result<usize, AcpiError> {
+ unsafe {
+ { self.x_firmware_ctrl }
+ .access(self.header.revision)
+ .filter(|&p| p != 0)
+ .or(Some(self.firmware_ctrl as u64))
+ .filter(|&p| p != 0)
+ .map(|p| p as usize)
+ .ok_or(AcpiError::InvalidFacsAddress)
+ }
+ }
+
+ pub fn dsdt_address(&self) -> Result<usize, AcpiError> {
+ unsafe {
+ { self.x_dsdt_address }
+ .access(self.header.revision)
+ .filter(|&p| p != 0)
+ .or(Some(self.dsdt_address as u64))
+ .filter(|&p| p != 0)
+ .map(|p| p as usize)
+ .ok_or(AcpiError::InvalidDsdtAddress)
+ }
+ }
+
+ pub fn power_profile(&self) -> PowerProfile {
+ match self.preferred_pm_profile {
+ 0 => PowerProfile::Unspecified,
+ 1 => PowerProfile::Desktop,
+ 2 => PowerProfile::Mobile,
+ 3 => PowerProfile::Workstation,
+ 4 => PowerProfile::EnterpriseServer,
+ 5 => PowerProfile::SohoServer,
+ 6 => PowerProfile::AppliancePc,
+ 7 => PowerProfile::PerformanceServer,
+ 8 => PowerProfile::Tablet,
+ other => PowerProfile::Reserved(other),
+ }
+ }
+
+ pub fn pm1a_event_block(&self) -> Result<GenericAddress, AcpiError> {
+ if let Some(raw) = unsafe { self.x_pm1a_event_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return GenericAddress::from_raw(raw);
+ }
+ }
+
+ Ok(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.pm1_event_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm1a_event_block.into(),
+ })
+ }
+
+ pub fn pm1b_event_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.x_pm1b_event_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.pm1b_event_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.pm1_event_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm1b_event_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn pm1a_control_block(&self) -> Result<GenericAddress, AcpiError> {
+ if let Some(raw) = unsafe { self.x_pm1a_control_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return GenericAddress::from_raw(raw);
+ }
+ }
+
+ Ok(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.pm1_control_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm1a_control_block.into(),
+ })
+ }
+
+ pub fn pm1b_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.x_pm1b_control_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.pm1b_control_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.pm1_control_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm1b_control_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn pm2_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.x_pm2_control_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.pm2_control_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.pm2_control_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm2_control_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Attempts to parse the FADT's PWM timer blocks, first returning the extended block, and falling back to
+ /// parsing the legacy block into a `GenericAddress`.
+ pub fn pm_timer_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ // ACPI spec indicates `PM_TMR_LEN` should be 4, or otherwise the PM_TMR is not supported.
+ if self.pm_timer_length != 4 {
+ return Ok(None);
+ }
+
+ if let Some(raw) = unsafe { self.x_pm_timer_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.pm_timer_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: 32,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.pm_timer_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn gpe0_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.x_gpe0_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.gpe0_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.gpe0_block_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.gpe0_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn gpe1_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.x_gpe1_block.access(self.header().revision) } {
+ if raw.address != 0x0 {
+ return Ok(Some(GenericAddress::from_raw(raw)?));
+ }
+ }
+
+ if self.gpe1_block != 0 {
+ Ok(Some(GenericAddress {
+ address_space: AddressSpace::SystemIo,
+ bit_width: self.gpe1_block_length * 8,
+ bit_offset: 0,
+ access_size: AccessSize::Undefined,
+ address: self.gpe1_block.into(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn reset_register(&self) -> Result<GenericAddress, AcpiError> {
+ GenericAddress::from_raw(self.reset_reg)
+ }
+
+ pub fn sleep_control_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.sleep_control_reg.access(self.header().revision) } {
+ Ok(Some(GenericAddress::from_raw(raw)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn sleep_status_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
+ if let Some(raw) = unsafe { self.sleep_status_reg.access(self.header().revision) } {
+ Ok(Some(GenericAddress::from_raw(raw)?))
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct FixedFeatureFlags(u32);
+
+impl FixedFeatureFlags {
+ /// If true, an equivalent to the x86 [WBINVD](https://www.felixcloutier.com/x86/wbinvd) instruction is supported.
+ /// All caches will be flushed and invalidated upon completion of this instruction,
+ /// and memory coherency is properly maintained. The cache *SHALL* only contain what OSPM references or allows to be cached.
+ pub fn supports_equivalent_to_wbinvd(&self) -> bool {
+ self.0.get_bit(0)
+ }
+
+ /// If true, [WBINVD](https://www.felixcloutier.com/x86/wbinvd) properly flushes all caches and memory coherency is maintained, but caches may not be invalidated.
+ pub fn wbinvd_flushes_all_caches(&self) -> bool {
+ self.0.get_bit(1)
+ }
+
+ /// If true, all processors implement the C1 power state.
+ pub fn all_procs_support_c1_power_state(&self) -> bool {
+ self.0.get_bit(2)
+ }
+
+ /// If true, the C2 power state is configured to work on a uniprocessor and multiprocessor system.
+ pub fn c2_configured_for_mp_system(&self) -> bool {
+ self.0.get_bit(3)
+ }
+
+ /// If true, the power button is handled as a control method device.
+ /// If false, the power button is handled as a fixed-feature programming model.
+ pub fn power_button_is_control_method(&self) -> bool {
+ self.0.get_bit(4)
+ }
+
+ /// If true, the sleep button is handled as a control method device.
+ /// If false, the sleep button is handled as a fixed-feature programming model.
+ pub fn sleep_button_is_control_method(&self) -> bool {
+ self.0.get_bit(5)
+ }
+
+ /// If true, the RTC wake status is not supported in fixed register space.
+ pub fn no_rtc_wake_in_fixed_register_space(&self) -> bool {
+ self.0.get_bit(6)
+ }
+
+ /// If true, the RTC alarm function can wake the system from an S4 sleep state.
+ pub fn rtc_wakes_system_from_s4(&self) -> bool {
+ self.0.get_bit(7)
+ }
+
+ /// If true, indicates that the PM timer is a 32-bit value.
+ /// If false, the PM timer is a 24-bit value and the remaining 8 bits are clear.
+ pub fn pm_timer_is_32_bit(&self) -> bool {
+ self.0.get_bit(8)
+ }
+
+ /// If true, the system supports docking.
+ pub fn supports_docking(&self) -> bool {
+ self.0.get_bit(9)
+ }
+
+ /// If true, the system supports system reset via the reset_reg field of the FADT.
+ pub fn supports_system_reset_via_fadt(&self) -> bool {
+ self.0.get_bit(10)
+ }
+
+ /// If true, the system supports no expansion capabilities and the case is sealed.
+ pub fn case_is_sealed(&self) -> bool {
+ self.0.get_bit(11)
+ }
+
+ /// If true, the system cannot detect the monitor or keyboard/mouse devices.
+ pub fn system_is_headless(&self) -> bool {
+ self.0.get_bit(12)
+ }
+
+ /// If true, OSPM must use a processor instruction after writing to the SLP_TYPx register.
+ pub fn use_instr_after_write_to_slp_typx(&self) -> bool {
+ self.0.get_bit(13)
+ }
+
+ /// If set, the platform supports the `PCIEXP_WAKE_STS` and `PCIEXP_WAKE_EN` bits in the PM1 status and enable registers.
+ pub fn supports_pciexp_wake_in_pm1(&self) -> bool {
+ self.0.get_bit(14)
+ }
+
+ /// If true, OSPM should use the ACPI power management timer or HPET for monotonically-decreasing timers.
+ pub fn use_pm_or_hpet_for_monotonically_decreasing_timers(&self) -> bool {
+ self.0.get_bit(15)
+ }
+
+ /// If true, the contents of the `RTC_STS` register are valid after wakeup from S4.
+ pub fn rtc_sts_is_valid_after_wakeup_from_s4(&self) -> bool {
+ self.0.get_bit(16)
+ }
+
+ /// If true, the platform supports OSPM leaving GPE wake events armed prior to an S5 transition.
+ pub fn ospm_may_leave_gpe_wake_events_armed_before_s5(&self) -> bool {
+ self.0.get_bit(17)
+ }
+
+ /// If true, all LAPICs must be configured using the cluster destination model when delivering interrupts in logical mode.
+ pub fn lapics_must_use_cluster_model_for_logical_mode(&self) -> bool {
+ self.0.get_bit(18)
+ }
+
+ /// If true, all LXAPICs must be configured using physical destination mode.
+ pub fn local_xapics_must_use_physical_destination_mode(&self) -> bool {
+ self.0.get_bit(19)
+ }
+
+ /// If true, this system is a hardware-reduced ACPI platform, and software methods are used for fixed-feature functions defined in chapter 4 of the ACPI specification.
+ pub fn system_is_hw_reduced_acpi(&self) -> bool {
+ self.0.get_bit(20)
+ }
+
+ /// If true, the system can achieve equal or better power savings in an S0 power state, making an S3 transition useless.
+ pub fn no_benefit_to_s3(&self) -> bool {
+ self.0.get_bit(21)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct IaPcBootArchFlags(u16);
+
+impl IaPcBootArchFlags {
+ /// If true, legacy user-accessible devices are available on the LPC and/or ISA buses.
+ pub fn legacy_devices_are_accessible(&self) -> bool {
+ self.0.get_bit(0)
+ }
+
+ /// If true, the motherboard exposes an IO port 60/64 keyboard controller, typically implemented as an 8042 microcontroller.
+ pub fn motherboard_implements_8042(&self) -> bool {
+ self.0.get_bit(1)
+ }
+
+ /// If true, OSPM *must not* blindly probe VGA hardware.
+ /// VGA hardware is at MMIO addresses A0000h-BFFFFh and IO ports 3B0h-3BBh and 3C0h-3DFh.
+ pub fn dont_probe_vga(&self) -> bool {
+ self.0.get_bit(2)
+ }
+
+ /// If true, OSPM *must not* enable message-signaled interrupts.
+ pub fn dont_enable_msi(&self) -> bool {
+ self.0.get_bit(3)
+ }
+
+ /// If true, OSPM *must not* enable PCIe ASPM control.
+ pub fn dont_enable_pcie_aspm(&self) -> bool {
+ self.0.get_bit(4)
+ }
+
+ /// If true, OSPM *must not* use the RTC via its IO ports, either because it isn't implemented or is at other addresses;
+ /// instead, OSPM *MUST* use the time and alarm namespace device control method.
+ pub fn use_time_and_alarm_namespace_for_rtc(&self) -> bool {
+ self.0.get_bit(5)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct ArmBootArchFlags(u16);
+
+impl ArmBootArchFlags {
+ /// If true, the system implements PSCI.
+ pub fn implements_psci(&self) -> bool {
+ self.0.get_bit(0)
+ }
+
+ /// If true, OSPM must use HVC instead of SMC as the PSCI conduit.
+ pub fn use_hvc_as_psci_conduit(&self) -> bool {
+ self.0.get_bit(1)
+ }
+}
diff --git a/src/handler.rs b/src/handler.rs
new file mode 100644
index 0000000..6cc0cd0
--- /dev/null
+++ b/src/handler.rs
@@ -0,0 +1,128 @@
+use core::{ops::Deref, ptr::NonNull};
+
+/// Describes a physical mapping created by `AcpiHandler::map_physical_region` and unmapped by
+/// `AcpiHandler::unmap_physical_region`. The region mapped must be at least `size_of::<T>()`
+/// bytes, but may be bigger.
+///
+/// See `PhysicalMapping::new` for the meaning of each field.
+#[derive(Debug)]
+pub struct PhysicalMapping<H, T>
+where
+ H: AcpiHandler,
+{
+ physical_start: usize,
+ virtual_start: NonNull<T>,
+ region_length: usize, // Can be equal or larger than size_of::<T>()
+ mapped_length: usize, // Differs from `region_length` if padding is added for alignment
+ handler: H,
+}
+
+impl<H, T> PhysicalMapping<H, T>
+where
+ H: AcpiHandler,
+{
+ /// Construct a new `PhysicalMapping`.
+ ///
+ /// - `physical_start` should be the physical address of the structure to be mapped.
+ /// - `virtual_start` should be the corresponding virtual address of that structure. It may differ from the
+ /// start of the region mapped due to requirements of the paging system. It must be a valid, non-null
+ /// pointer.
+ /// - `region_length` should be the number of bytes requested to be mapped. It must be equal to or larger than
+ /// `size_of::<T>()`.
+ /// - `mapped_length` should be the number of bytes mapped to fulfill the request. It may be equal to or larger
+ /// than `region_length`, due to requirements of the paging system or other reasoning.
+ /// - `handler` should be the same `AcpiHandler` that created the mapping. When the `PhysicalMapping` is
+ /// dropped, it will be used to unmap the structure.
+ pub unsafe fn new(
+ physical_start: usize,
+ virtual_start: NonNull<T>,
+ region_length: usize,
+ mapped_length: usize,
+ handler: H,
+ ) -> Self {
+ Self { physical_start, virtual_start, region_length, mapped_length, handler }
+ }
+
+ pub fn physical_start(&self) -> usize {
+ self.physical_start
+ }
+
+ pub fn virtual_start(&self) -> NonNull<T> {
+ self.virtual_start
+ }
+
+ pub fn region_length(&self) -> usize {
+ self.region_length
+ }
+
+ pub fn mapped_length(&self) -> usize {
+ self.mapped_length
+ }
+
+ pub fn handler(&self) -> &H {
+ &self.handler
+ }
+}
+
+unsafe impl<H: AcpiHandler + Send, T: Send> Send for PhysicalMapping<H, T> {}
+
+impl<H, T> Deref for PhysicalMapping<H, T>
+where
+ H: AcpiHandler,
+{
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ unsafe { self.virtual_start.as_ref() }
+ }
+}
+
+impl<H, T> Drop for PhysicalMapping<H, T>
+where
+ H: AcpiHandler,
+{
+ fn drop(&mut self) {
+ H::unmap_physical_region(self)
+ }
+}
+
+/// An implementation of this trait must be provided to allow `acpi` to access platform-specific
+/// functionality, such as mapping regions of physical memory. You are free to implement these
+/// however you please, as long as they conform to the documentation of each function. The handler is stored in
+/// every `PhysicalMapping` so it's able to unmap itself when dropped, so this type needs to be something you can
+/// clone/move about freely (e.g. a reference, wrapper over `Rc`, marker struct, etc.).
+pub trait AcpiHandler: Clone {
+ /// Given a physical address and a size, map a region of physical memory that contains `T` (note: the passed
+ /// size may be larger than `size_of::<T>()`). The address is not neccessarily page-aligned, so the
+ /// implementation may need to map more than `size` bytes. The virtual address the region is mapped to does not
+ /// matter, as long as it is accessible to `acpi`.
+ ///
+ /// See the documentation on `PhysicalMapping::new` for an explanation of each field on the `PhysicalMapping`
+ /// return type.
+ ///
+ /// ## Safety
+ ///
+ /// - `physical_address` must point to a valid `T` in physical memory.
+ /// - `size` must be at least `size_of::<T>()`.
+ unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T>;
+
+ /// Unmap the given physical mapping. This is called when a `PhysicalMapping` is dropped, you should **not** manually call this.
+ ///
+ /// Note: A reference to the handler used to construct `region` can be acquired by calling [`PhysicalMapping::handler`].
+ fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[allow(dead_code)]
+ fn test_send_sync() {
+ // verify that PhysicalMapping implements Send and Sync
+ fn test_send_sync<T: Send>() {}
+ fn caller<H: AcpiHandler + Send, T: Send>() {
+ test_send_sync::<PhysicalMapping<H, T>>();
+ }
+ }
+}
diff --git a/src/hpet.rs b/src/hpet.rs
new file mode 100644
index 0000000..6360486
--- /dev/null
+++ b/src/hpet.rs
@@ -0,0 +1,99 @@
+use crate::{
+ address::RawGenericAddress,
+ sdt::{SdtHeader, Signature},
+ AcpiError,
+ AcpiHandler,
+ AcpiTable,
+ AcpiTables,
+};
+use bit_field::BitField;
+
+#[derive(Debug)]
+pub enum PageProtection {
+ None,
+ /// Access to the rest of the 4KiB, relative to the base address, will not generate a fault.
+ Protected4K,
+ /// Access to the rest of the 64KiB, relative to the base address, will not generate a fault.
+ Protected64K,
+ Other,
+}
+
+/// Information about the High Precision Event Timer (HPET)
+#[derive(Debug)]
+pub struct HpetInfo {
+ // TODO(3.0.0): unpack these fields directly, and get rid of methods
+ pub event_timer_block_id: u32,
+ pub base_address: usize,
+ pub hpet_number: u8,
+ /// The minimum number of clock ticks that can be set without losing interrupts (for timers in Periodic Mode)
+ pub clock_tick_unit: u16,
+ pub page_protection: PageProtection,
+}
+
+impl HpetInfo {
+ pub fn new<H>(tables: &AcpiTables<H>) -> Result<HpetInfo, AcpiError>
+ where
+ H: AcpiHandler,
+ {
+ let hpet = tables.find_table::<HpetTable>()?;
+
+ // Make sure the HPET is in system memory
+ assert_eq!(hpet.base_address.address_space, 0);
+
+ Ok(HpetInfo {
+ event_timer_block_id: hpet.event_timer_block_id,
+ base_address: hpet.base_address.address as usize,
+ hpet_number: hpet.hpet_number,
+ clock_tick_unit: hpet.clock_tick_unit,
+ page_protection: match hpet.page_protection_and_oem.get_bits(0..4) {
+ 0 => PageProtection::None,
+ 1 => PageProtection::Protected4K,
+ 2 => PageProtection::Protected64K,
+ 3..=15 => PageProtection::Other,
+ _ => unreachable!(),
+ },
+ })
+ }
+
+ pub fn hardware_rev(&self) -> u8 {
+ self.event_timer_block_id.get_bits(0..8) as u8
+ }
+
+ pub fn num_comparators(&self) -> u8 {
+ self.event_timer_block_id.get_bits(8..13) as u8 + 1
+ }
+
+ pub fn main_counter_is_64bits(&self) -> bool {
+ self.event_timer_block_id.get_bit(13)
+ }
+
+ pub fn legacy_irq_capable(&self) -> bool {
+ self.event_timer_block_id.get_bit(15)
+ }
+
+ pub fn pci_vendor_id(&self) -> u16 {
+ self.event_timer_block_id.get_bits(16..32) as u16
+ }
+}
+
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy)]
+pub struct HpetTable {
+ /// The contents of the HPET's 'General Capabilities and ID register'
+ header: SdtHeader,
+ event_timer_block_id: u32,
+ base_address: RawGenericAddress,
+ hpet_number: u8,
+ clock_tick_unit: u16,
+ /// Bits `0..4` specify the page protection guarantee. Bits `4..8` are reserved for OEM attributes.
+ page_protection_and_oem: u8,
+}
+
+/// ### Safety: Implementation properly represents a valid HPET table.
+unsafe impl AcpiTable for HpetTable {
+ const SIGNATURE: Signature = Signature::HPET;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..83facc5
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,448 @@
+//! A library for parsing ACPI tables. This crate can be used by bootloaders and kernels for architectures that
+//! support ACPI. This crate is not feature-complete, but can parse lots of the more common tables. Parsing the
+//! ACPI tables is required for correctly setting up the APICs, HPET, and provides useful information about power
+//! management and many other platform capabilities.
+//!
+//! This crate is designed to find and parse the static tables ACPI provides. It should be used in conjunction with
+//! the `aml` crate, which is the (much less complete) AML parser used to parse the DSDT and SSDTs. These crates
+//! are separate because some kernels may want to detect the static tables, but delay AML parsing to a later stage.
+//!
+//! This crate can be used in three configurations, depending on the environment it's being used from:
+//! - **Without allocator support** - this can be achieved by disabling the `allocator_api` and `alloc`
+//! features. The core parts of the library will still be usable, but with generally reduced functionality
+//! and ease-of-use.
+//! - **With a custom allocator** - by disabling just the `alloc` feature, you can use the `new_in` functions to
+//! access increased functionality with your own allocator. This allows `acpi` to be integrated more closely
+//! with environments that already provide a custom allocator, for example to gracefully handle allocation
+//! errors.
+//! - **With the globally-set allocator** - the `alloc` feature provides `new` functions that simply use the
+//! global allocator. This is the easiest option, and the one the majority of users will want. It is the
+//! default configuration of the crate.
+//!
+//! ### Usage
+//! To use the library, you will need to provide an implementation of the `AcpiHandler` trait, which allows the
+//! library to make requests such as mapping a particular region of physical memory into the virtual address space.
+//!
+//! You then need to construct an instance of `AcpiTables`, which can be done in a few ways depending on how much
+//! information you have:
+//! * Use `AcpiTables::from_rsdp` if you have the physical address of the RSDP
+//! * Use `AcpiTables::from_rsdt` if you have the physical address of the RSDT/XSDT
+//! * Use `AcpiTables::search_for_rsdp_bios` if you don't have the address of either, but **you know you are
+//! running on BIOS, not UEFI**
+//! * Use `AcpiTables::from_tables_direct` if you are using the library in an unusual setting, such as in usermode,
+//! and have a custom method to enumerate and access the tables.
+//!
+//! `AcpiTables` stores the addresses of all of the tables detected on a platform. The SDTs are parsed by this
+//! library, or can be accessed directly with `from_sdt`, while the `DSDT` and any `SSDTs` should be parsed with
+//! `aml`.
+//!
+//! To gather information out of the static tables, a few of the types you should take a look at are:
+//! - [`PlatformInfo`](crate::platform::PlatformInfo) parses the FADT and MADT to create a nice view of the
+//! processor topology and interrupt controllers on `x86_64`, and the interrupt controllers on other platforms.
+//! `AcpiTables::platform_info` is a convenience method for constructing a `PlatformInfo`.
+//! - [`HpetInfo`](crate::hpet::HpetInfo) parses the HPET table and tells you how to configure the High
+//! Precision Event Timer.
+//! - [`PciConfigRegions`](crate::mcfg::PciConfigRegions) parses the MCFG and tells you how PCIe configuration
+//! space is mapped into physical memory.
+
+/*
+ * Contributing notes (you may find these useful if you're new to contributing to the library):
+ * - Accessing packed fields without UB: Lots of the structures defined by ACPI are defined with `repr(packed)`
+ * to prevent padding being introduced, which would make the structure's layout incorrect. In Rust, this
+ * creates a problem as references to these fields could be unaligned, which is undefined behaviour. For the
+ * majority of these fields, this problem can be easily avoided by telling the compiler to make a copy of the
+ * field's contents: this is the perhaps unfamiliar pattern of e.g. `!{ entry.flags }.get_bit(0)` we use
+ * around the codebase.
+ */
+
+#![no_std]
+#![deny(unsafe_op_in_unsafe_fn)]
+#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
+
+#[cfg_attr(test, macro_use)]
+#[cfg(test)]
+extern crate std;
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+pub mod address;
+pub mod bgrt;
+pub mod fadt;
+pub mod handler;
+pub mod hpet;
+pub mod madt;
+pub mod mcfg;
+pub mod rsdp;
+pub mod sdt;
+
+#[cfg(feature = "allocator_api")]
+mod managed_slice;
+#[cfg(feature = "allocator_api")]
+pub use managed_slice::*;
+
+#[cfg(feature = "allocator_api")]
+pub mod platform;
+#[cfg(feature = "allocator_api")]
+pub use crate::platform::{interrupt::InterruptModel, PlatformInfo};
+
+#[cfg(feature = "allocator_api")]
+pub use crate::mcfg::PciConfigRegions;
+
+pub use fadt::PowerProfile;
+pub use handler::{AcpiHandler, PhysicalMapping};
+pub use hpet::HpetInfo;
+pub use madt::MadtError;
+
+use crate::sdt::{SdtHeader, Signature};
+use core::mem;
+use rsdp::Rsdp;
+
+/// Result type used by error-returning functions.
+pub type AcpiResult<T> = core::result::Result<T, AcpiError>;
+
+/// All types representing ACPI tables should implement this trait.
+///
+/// ### Safety
+///
+/// The table's memory is naively interpreted, so you must be careful in providing a type that
+/// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
+/// be the size specified in the SDT's header. Providing a table impl that is larger than this, *may* lead to
+/// page-faults, aliasing references, or derefencing uninitialized memory (the latter two being UB).
+/// This isn't forbidden, however, because some tables rely on the impl being larger than a provided SDT in some
+/// versions of ACPI (the [`ExtendedField`](crate::sdt::ExtendedField) type will be useful if you need to do
+/// this. See our [`Fadt`](crate::fadt::Fadt) type for an example of this).
+pub unsafe trait AcpiTable {
+ const SIGNATURE: Signature;
+
+ fn header(&self) -> &sdt::SdtHeader;
+
+ fn validate(&self) -> AcpiResult<()> {
+ self.header().validate(Self::SIGNATURE)
+ }
+}
+
+/// Error type used by functions that return an `AcpiResult<T>`.
+#[derive(Debug)]
+pub enum AcpiError {
+ NoValidRsdp,
+ RsdpIncorrectSignature,
+ RsdpInvalidOemId,
+ RsdpInvalidChecksum,
+
+ SdtInvalidSignature(Signature),
+ SdtInvalidOemId(Signature),
+ SdtInvalidTableId(Signature),
+ SdtInvalidChecksum(Signature),
+
+ TableMissing(Signature),
+ InvalidFacsAddress,
+ InvalidDsdtAddress,
+ InvalidMadt(MadtError),
+ InvalidGenericAddress,
+
+ AllocError,
+}
+
+/// Type capable of enumerating the existing ACPI tables on the system.
+///
+///
+/// ### Implementation Note
+///
+/// When using the `allocator_api`±`alloc` features, [`PlatformInfo::new()`] or [`PlatformInfo::new_in()`] provide
+/// a much cleaner API for enumerating ACPI structures once an `AcpiTables` has been constructed.
+#[derive(Debug)]
+pub struct AcpiTables<H: AcpiHandler> {
+ mapping: PhysicalMapping<H, SdtHeader>,
+ revision: u8,
+ handler: H,
+}
+
+impl<H> AcpiTables<H>
+where
+ H: AcpiHandler,
+{
+ /// Create an `AcpiTables` if you have the physical address of the RSDP.
+ ///
+ /// ### Safety: Caller must ensure the provided address is valid to read as an RSDP.
+ pub unsafe fn from_rsdp(handler: H, address: usize) -> AcpiResult<Self> {
+ let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
+ rsdp_mapping.validate()?;
+
+ // Safety: RSDP has been validated.
+ unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) }
+ }
+
+ /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not
+ /// work on UEFI platforms. See [Rsdp::search_for_rsdp_bios](rsdp_search::Rsdp::search_for_rsdp_bios) for
+ /// details.
+ pub unsafe fn search_for_rsdp_bios(handler: H) -> AcpiResult<Self> {
+ let rsdp_mapping = unsafe { Rsdp::search_for_on_bios(handler.clone())? };
+ // Safety: RSDP has been validated from `Rsdp::search_for_on_bios`
+ unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) }
+ }
+
+ /// Create an `AcpiTables` if you have a `PhysicalMapping` of the RSDP that you know is correct. This is called
+ /// from `from_rsdp` after validation, but can also be used if you've searched for the RSDP manually on a BIOS
+ /// system.
+ ///
+ /// ### Safety: Caller must ensure that the provided mapping is a fully validated RSDP.
+ pub unsafe fn from_validated_rsdp(handler: H, rsdp_mapping: PhysicalMapping<H, Rsdp>) -> AcpiResult<Self> {
+ macro_rules! read_root_table {
+ ($signature_name:ident, $address_getter:ident) => {{
+ #[repr(transparent)]
+ struct RootTable {
+ header: SdtHeader,
+ }
+
+ unsafe impl AcpiTable for RootTable {
+ const SIGNATURE: Signature = Signature::$signature_name;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+ }
+
+ // Unmap RSDP as soon as possible
+ let table_phys_start = rsdp_mapping.$address_getter() as usize;
+ drop(rsdp_mapping);
+
+ // Map and validate root table
+ // SAFETY: Addresses from a validated RSDP are also guaranteed to be valid.
+ let table_mapping = unsafe { read_table::<_, RootTable>(handler.clone(), table_phys_start) }?;
+
+ // Convert `table_mapping` to header mapping for storage
+ // Avoid requesting table unmap twice (from both original and converted `table_mapping`s)
+ let table_mapping = mem::ManuallyDrop::new(table_mapping);
+ // SAFETY: `SdtHeader` is equivalent to `Sdt` memory-wise
+ let table_mapping = unsafe {
+ PhysicalMapping::new(
+ table_mapping.physical_start(),
+ table_mapping.virtual_start().cast::<SdtHeader>(),
+ table_mapping.region_length(),
+ table_mapping.mapped_length(),
+ handler.clone(),
+ )
+ };
+
+ table_mapping
+ }};
+ }
+
+ let revision = rsdp_mapping.revision();
+ let root_table_mapping = if revision == 0 {
+ /*
+ * We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
+ */
+
+ read_root_table!(RSDT, rsdt_address)
+ } else {
+ /*
+ * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated
+ * to 32 bits on x86.
+ */
+
+ read_root_table!(XSDT, xsdt_address)
+ };
+
+ Ok(Self { mapping: root_table_mapping, revision, handler })
+ }
+
+ /// The ACPI revision of the tables enumerated by this structure.
+ #[inline]
+ pub const fn revision(&self) -> u8 {
+ self.revision
+ }
+
+ /// Constructs a [`TablesPhysPtrsIter`] over this table.
+ fn tables_phys_ptrs(&self) -> TablesPhysPtrsIter<'_> {
+ // SAFETY: The virtual address of the array of pointers follows the virtual address of the table in memory.
+ let ptrs_virt_start = unsafe { self.mapping.virtual_start().as_ptr().add(1).cast::<u8>() };
+ let ptrs_bytes_len = self.mapping.region_length() - mem::size_of::<SdtHeader>();
+ // SAFETY: `ptrs_virt_start` points to an array of `ptrs_bytes_len` bytes that lives as long as `self`.
+ let ptrs_bytes = unsafe { core::slice::from_raw_parts(ptrs_virt_start, ptrs_bytes_len) };
+ let ptr_size = if self.revision == 0 {
+ 4 // RSDT entry size
+ } else {
+ 8 // XSDT entry size
+ };
+
+ ptrs_bytes.chunks(ptr_size).map(|ptr_bytes_src| {
+ // Construct a native pointer using as many bytes as required from `ptr_bytes_src` (note that ACPI is
+ // little-endian)
+
+ let mut ptr_bytes_dst = [0; mem::size_of::<usize>()];
+ let common_ptr_size = usize::min(mem::size_of::<usize>(), ptr_bytes_src.len());
+ ptr_bytes_dst[..common_ptr_size].copy_from_slice(&ptr_bytes_src[..common_ptr_size]);
+
+ usize::from_le_bytes(ptr_bytes_dst) as *const SdtHeader
+ })
+ }
+
+ /// Searches through the ACPI table headers and attempts to locate the table with a matching `T::SIGNATURE`.
+ pub fn find_table<T: AcpiTable>(&self) -> AcpiResult<PhysicalMapping<H, T>> {
+ self.tables_phys_ptrs()
+ .find_map(|table_phys_ptr| {
+ // SAFETY: Table guarantees its contained addresses to be valid.
+ match unsafe { read_table(self.handler.clone(), table_phys_ptr as usize) } {
+ Ok(table_mapping) => Some(table_mapping),
+ Err(AcpiError::SdtInvalidSignature(_)) => None,
+ Err(e) => {
+ log::warn!(
+ "Found invalid {} table at physical address {:p}: {:?}",
+ T::SIGNATURE,
+ table_phys_ptr,
+ e
+ );
+
+ None
+ }
+ }
+ })
+ .ok_or(AcpiError::TableMissing(T::SIGNATURE))
+ }
+
+ /// Finds and returns the DSDT AML table, if it exists.
+ pub fn dsdt(&self) -> AcpiResult<AmlTable> {
+ self.find_table::<fadt::Fadt>().and_then(|fadt| {
+ #[repr(transparent)]
+ struct Dsdt {
+ header: SdtHeader,
+ }
+
+ // Safety: Implementation properly represents a valid DSDT.
+ unsafe impl AcpiTable for Dsdt {
+ const SIGNATURE: Signature = Signature::DSDT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+ }
+
+ let dsdt_address = fadt.dsdt_address()?;
+ let dsdt = unsafe { read_table::<H, Dsdt>(self.handler.clone(), dsdt_address)? };
+
+ Ok(AmlTable::new(dsdt_address, dsdt.header().length))
+ })
+ }
+
+ /// Iterates through all of the SSDT tables.
+ pub fn ssdts(&self) -> SsdtIterator<H> {
+ SsdtIterator { tables_phys_ptrs: self.tables_phys_ptrs(), handler: self.handler.clone() }
+ }
+
+ /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the
+ /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about
+ /// the platform from the ACPI tables.
+ ///
+ /// Like `platform_info_in`, but uses the global allocator.
+ #[cfg(feature = "alloc")]
+ pub fn platform_info(&self) -> AcpiResult<PlatformInfo<alloc::alloc::Global>> {
+ PlatformInfo::new(self)
+ }
+
+ /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the
+ /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about
+ /// the platform from the ACPI tables.
+ #[cfg(feature = "allocator_api")]
+ pub fn platform_info_in<A>(&self, allocator: A) -> AcpiResult<PlatformInfo<A>>
+ where
+ A: core::alloc::Allocator + Clone,
+ {
+ PlatformInfo::new_in(self, allocator)
+ }
+}
+
+#[derive(Debug)]
+pub struct Sdt {
+ /// Physical address of the start of the SDT, including the header.
+ pub physical_address: usize,
+ /// Length of the table in bytes.
+ pub length: u32,
+ /// Whether this SDT has been validated. This is set to `true` the first time it is mapped and validated.
+ pub validated: bool,
+}
+
+/// An iterator over the physical table addresses in an RSDT or XSDT.
+type TablesPhysPtrsIter<'t> = core::iter::Map<core::slice::Chunks<'t, u8>, fn(&[u8]) -> *const SdtHeader>;
+
+#[derive(Debug)]
+pub struct AmlTable {
+ /// Physical address of the start of the AML stream (excluding the table header).
+ pub address: usize,
+ /// Length (in bytes) of the AML stream.
+ pub length: u32,
+}
+
+impl AmlTable {
+ /// Create an `AmlTable` from the address and length of the table **including the SDT header**.
+ pub(crate) fn new(address: usize, length: u32) -> AmlTable {
+ AmlTable {
+ address: address + mem::size_of::<SdtHeader>(),
+ length: length - mem::size_of::<SdtHeader>() as u32,
+ }
+ }
+}
+
+/// ### Safety: Caller must ensure the provided address is valid for being read as an `SdtHeader`.
+unsafe fn read_table<H: AcpiHandler, T: AcpiTable>(
+ handler: H,
+ address: usize,
+) -> AcpiResult<PhysicalMapping<H, T>> {
+ // Attempt to peek at the SDT header to correctly enumerate the entire table.
+
+ // SAFETY: `address` needs to be valid for the size of `SdtHeader`, or the ACPI tables are malformed (not a
+ // software issue).
+ let header_mapping = unsafe { handler.map_physical_region::<SdtHeader>(address, mem::size_of::<SdtHeader>()) };
+
+ SdtHeader::validate_lazy(header_mapping, handler)
+}
+
+/// Iterator that steps through all of the tables, and returns only the SSDTs as `AmlTable`s.
+pub struct SsdtIterator<'t, H>
+where
+ H: AcpiHandler,
+{
+ tables_phys_ptrs: TablesPhysPtrsIter<'t>,
+ handler: H,
+}
+
+impl<'t, H> Iterator for SsdtIterator<'t, H>
+where
+ H: AcpiHandler,
+{
+ type Item = AmlTable;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ #[repr(transparent)]
+ struct Ssdt {
+ header: SdtHeader,
+ }
+
+ // SAFETY: Implementation properly represents a valid SSDT.
+ unsafe impl AcpiTable for Ssdt {
+ const SIGNATURE: Signature = Signature::SSDT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+ }
+
+ // Borrow single field for closure to avoid immutable reference to `self` that inhibits `find_map`
+ let handler = &self.handler;
+
+ // Consume iterator until next valid SSDT and return the latter
+ self.tables_phys_ptrs.find_map(|table_phys_ptr| {
+ // SAFETY: Table guarantees its contained addresses to be valid.
+ match unsafe { read_table::<_, Ssdt>(handler.clone(), table_phys_ptr as usize) } {
+ Ok(ssdt_mapping) => Some(AmlTable::new(ssdt_mapping.physical_start(), ssdt_mapping.header.length)),
+ Err(AcpiError::SdtInvalidSignature(_)) => None,
+ Err(e) => {
+ log::warn!("Found invalid SSDT at physical address {:p}: {:?}", table_phys_ptr, e);
+
+ None
+ }
+ }
+ })
+ }
+}
diff --git a/src/madt.rs b/src/madt.rs
new file mode 100644
index 0000000..7debcf6
--- /dev/null
+++ b/src/madt.rs
@@ -0,0 +1,646 @@
+use crate::{
+ sdt::{ExtendedField, SdtHeader, Signature},
+ AcpiTable,
+};
+use bit_field::BitField;
+use core::{marker::PhantomData, mem};
+
+#[cfg(feature = "allocator_api")]
+use crate::{
+ platform::{
+ interrupt::{InterruptModel, Polarity, TriggerMode},
+ ProcessorInfo,
+ },
+ AcpiResult,
+};
+
+#[derive(Debug)]
+pub enum MadtError {
+ UnexpectedEntry,
+ InterruptOverrideEntryHasInvalidBus,
+ InvalidLocalNmiLine,
+ MpsIntiInvalidPolarity,
+ MpsIntiInvalidTriggerMode,
+}
+
+/// Represents the MADT - this contains the MADT header fields. You can then iterate over a `Madt`
+/// to read each entry from it.
+///
+/// In modern versions of ACPI, the MADT can detail one of four interrupt models:
+/// * The ancient dual-i8259 legacy PIC model
+/// * The Advanced Programmable Interrupt Controller (APIC) model
+/// * The Streamlined Advanced Programmable Interrupt Controller (SAPIC) model (for Itanium systems)
+/// * The Generic Interrupt Controller (GIC) model (for ARM systems)
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy)]
+pub struct Madt {
+ pub header: SdtHeader,
+ pub local_apic_address: u32,
+ pub flags: u32,
+}
+
+/// ### Safety: Implementation properly represents a valid MADT.
+unsafe impl AcpiTable for Madt {
+ const SIGNATURE: Signature = Signature::MADT;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
+
+impl Madt {
+ #[cfg(feature = "allocator_api")]
+ pub fn parse_interrupt_model_in<'a, A>(
+ &self,
+ allocator: A,
+ ) -> AcpiResult<(InterruptModel<'a, A>, Option<ProcessorInfo<'a, A>>)>
+ where
+ A: core::alloc::Allocator + Clone,
+ {
+ /*
+ * We first do a pass through the MADT to determine which interrupt model is being used.
+ */
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::LocalApic(_) |
+ MadtEntry::LocalX2Apic(_) |
+ MadtEntry::IoApic(_) |
+ MadtEntry::InterruptSourceOverride(_) |
+ MadtEntry::NmiSource(_) | // TODO: is this one used by more than one model?
+ MadtEntry::LocalApicNmi(_) |
+ MadtEntry::X2ApicNmi(_) |
+ MadtEntry::LocalApicAddressOverride(_) => {
+ return self.parse_apic_model_in(allocator);
+ }
+
+ MadtEntry::IoSapic(_) |
+ MadtEntry::LocalSapic(_) |
+ MadtEntry::PlatformInterruptSource(_) => {
+ unimplemented!();
+ }
+
+ MadtEntry::Gicc(_) |
+ MadtEntry::Gicd(_) |
+ MadtEntry::GicMsiFrame(_) |
+ MadtEntry::GicRedistributor(_) |
+ MadtEntry::GicInterruptTranslationService(_) => {
+ unimplemented!();
+ }
+
+ MadtEntry::MultiprocessorWakeup(_) => ()
+ }
+ }
+
+ Ok((InterruptModel::Unknown, None))
+ }
+
+ #[cfg(feature = "allocator_api")]
+ fn parse_apic_model_in<'a, A>(
+ &self,
+ allocator: A,
+ ) -> AcpiResult<(InterruptModel<'a, A>, Option<ProcessorInfo<'a, A>>)>
+ where
+ A: core::alloc::Allocator + Clone,
+ {
+ use crate::{
+ platform::{
+ interrupt::{
+ Apic,
+ InterruptSourceOverride,
+ IoApic,
+ LocalInterruptLine,
+ NmiLine,
+ NmiProcessor,
+ NmiSource,
+ },
+ Processor,
+ ProcessorState,
+ },
+ AcpiError,
+ };
+
+ let mut local_apic_address = self.local_apic_address as u64;
+ let mut io_apic_count = 0;
+ let mut iso_count = 0;
+ let mut nmi_source_count = 0;
+ let mut local_nmi_line_count = 0;
+ let mut processor_count = 0usize;
+
+ // Do a pass over the entries so we know how much space we should reserve in the vectors
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::IoApic(_) => io_apic_count += 1,
+ MadtEntry::InterruptSourceOverride(_) => iso_count += 1,
+ MadtEntry::NmiSource(_) => nmi_source_count += 1,
+ MadtEntry::LocalApicNmi(_) => local_nmi_line_count += 1,
+ MadtEntry::LocalApic(_) => processor_count += 1,
+ _ => (),
+ }
+ }
+
+ let mut io_apics = crate::ManagedSlice::new_in(io_apic_count, allocator.clone())?;
+ let mut interrupt_source_overrides = crate::ManagedSlice::new_in(iso_count, allocator.clone())?;
+ let mut nmi_sources = crate::ManagedSlice::new_in(nmi_source_count, allocator.clone())?;
+ let mut local_apic_nmi_lines = crate::ManagedSlice::new_in(local_nmi_line_count, allocator.clone())?;
+ let mut application_processors =
+ crate::ManagedSlice::new_in(processor_count.saturating_sub(1), allocator)?; // Subtract one for the BSP
+ let mut boot_processor = None;
+
+ io_apic_count = 0;
+ iso_count = 0;
+ nmi_source_count = 0;
+ local_nmi_line_count = 0;
+ processor_count = 0;
+
+ for entry in self.entries() {
+ match entry {
+ MadtEntry::LocalApic(entry) => {
+ /*
+ * The first processor is the BSP. Subsequent ones are APs. If we haven't found
+ * the BSP yet, this must be it.
+ */
+ let is_ap = boot_processor.is_some();
+ let is_disabled = !{ entry.flags }.get_bit(0);
+
+ let state = match (is_ap, is_disabled) {
+ (_, true) => ProcessorState::Disabled,
+ (true, false) => ProcessorState::WaitingForSipi,
+ (false, false) => ProcessorState::Running,
+ };
+
+ let processor = Processor {
+ processor_uid: entry.processor_id as u32,
+ local_apic_id: entry.apic_id as u32,
+ state,
+ is_ap,
+ };
+
+ if is_ap {
+ application_processors[processor_count] = processor;
+ processor_count += 1;
+ } else {
+ boot_processor = Some(processor);
+ }
+ }
+
+ MadtEntry::LocalX2Apic(entry) => {
+ let is_ap = boot_processor.is_some();
+ let is_disabled = !{ entry.flags }.get_bit(0);
+
+ let state = match (is_ap, is_disabled) {
+ (_, true) => ProcessorState::Disabled,
+ (true, false) => ProcessorState::WaitingForSipi,
+ (false, false) => ProcessorState::Running,
+ };
+
+ let processor = Processor {
+ processor_uid: entry.processor_uid,
+ local_apic_id: entry.x2apic_id,
+ state,
+ is_ap,
+ };
+
+ if is_ap {
+ application_processors[processor_count] = processor;
+ processor_count += 1;
+ } else {
+ boot_processor = Some(processor);
+ }
+ }
+
+ MadtEntry::IoApic(entry) => {
+ io_apics[io_apic_count] = IoApic {
+ id: entry.io_apic_id,
+ address: entry.io_apic_address,
+ global_system_interrupt_base: entry.global_system_interrupt_base,
+ };
+ io_apic_count += 1;
+ }
+
+ MadtEntry::InterruptSourceOverride(entry) => {
+ if entry.bus != 0 {
+ return Err(AcpiError::InvalidMadt(MadtError::InterruptOverrideEntryHasInvalidBus));
+ }
+
+ let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
+
+ interrupt_source_overrides[iso_count] = InterruptSourceOverride {
+ isa_source: entry.irq,
+ global_system_interrupt: entry.global_system_interrupt,
+ polarity,
+ trigger_mode,
+ };
+ iso_count += 1;
+ }
+
+ MadtEntry::NmiSource(entry) => {
+ let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
+
+ nmi_sources[nmi_source_count] = NmiSource {
+ global_system_interrupt: entry.global_system_interrupt,
+ polarity,
+ trigger_mode,
+ };
+ nmi_source_count += 1;
+ }
+
+ MadtEntry::LocalApicNmi(entry) => {
+ local_apic_nmi_lines[local_nmi_line_count] = NmiLine {
+ processor: if entry.processor_id == 0xff {
+ NmiProcessor::All
+ } else {
+ NmiProcessor::ProcessorUid(entry.processor_id as u32)
+ },
+ line: match entry.nmi_line {
+ 0 => LocalInterruptLine::Lint0,
+ 1 => LocalInterruptLine::Lint1,
+ _ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
+ },
+ };
+ local_nmi_line_count += 1;
+ }
+
+ MadtEntry::X2ApicNmi(entry) => {
+ local_apic_nmi_lines[local_nmi_line_count] = NmiLine {
+ processor: if entry.processor_uid == 0xffffffff {
+ NmiProcessor::All
+ } else {
+ NmiProcessor::ProcessorUid(entry.processor_uid)
+ },
+ line: match entry.nmi_line {
+ 0 => LocalInterruptLine::Lint0,
+ 1 => LocalInterruptLine::Lint1,
+ _ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
+ },
+ };
+ local_nmi_line_count += 1;
+ }
+
+ MadtEntry::LocalApicAddressOverride(entry) => {
+ local_apic_address = entry.local_apic_address;
+ }
+
+ _ => {
+ return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
+ }
+ }
+ }
+
+ Ok((
+ InterruptModel::Apic(Apic::new(
+ local_apic_address,
+ io_apics,
+ local_apic_nmi_lines,
+ interrupt_source_overrides,
+ nmi_sources,
+ self.supports_8259(),
+ )),
+ Some(ProcessorInfo::new(boot_processor.unwrap(), application_processors)),
+ ))
+ }
+
+ pub fn entries(&self) -> MadtEntryIter {
+ MadtEntryIter {
+ pointer: unsafe { (self as *const Madt as *const u8).add(mem::size_of::<Madt>()) },
+ remaining_length: self.header.length - mem::size_of::<Madt>() as u32,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn supports_8259(&self) -> bool {
+ { self.flags }.get_bit(0)
+ }
+}
+
+#[derive(Debug)]
+pub struct MadtEntryIter<'a> {
+ pointer: *const u8,
+ /*
+ * The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
+ * whole SDT can only be at most `u32::MAX`.
+ */
+ remaining_length: u32,
+ _phantom: PhantomData<&'a ()>,
+}
+
+#[derive(Debug)]
+pub enum MadtEntry<'a> {
+ LocalApic(&'a LocalApicEntry),
+ IoApic(&'a IoApicEntry),
+ InterruptSourceOverride(&'a InterruptSourceOverrideEntry),
+ NmiSource(&'a NmiSourceEntry),
+ LocalApicNmi(&'a LocalApicNmiEntry),
+ LocalApicAddressOverride(&'a LocalApicAddressOverrideEntry),
+ IoSapic(&'a IoSapicEntry),
+ LocalSapic(&'a LocalSapicEntry),
+ PlatformInterruptSource(&'a PlatformInterruptSourceEntry),
+ LocalX2Apic(&'a LocalX2ApicEntry),
+ X2ApicNmi(&'a X2ApicNmiEntry),
+ Gicc(&'a GiccEntry),
+ Gicd(&'a GicdEntry),
+ GicMsiFrame(&'a GicMsiFrameEntry),
+ GicRedistributor(&'a GicRedistributorEntry),
+ GicInterruptTranslationService(&'a GicInterruptTranslationServiceEntry),
+ MultiprocessorWakeup(&'a MultiprocessorWakeupEntry),
+}
+
+impl<'a> Iterator for MadtEntryIter<'a> {
+ type Item = MadtEntry<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while self.remaining_length > 0 {
+ let entry_pointer = self.pointer;
+ let header = unsafe { *(self.pointer as *const EntryHeader) };
+
+ self.pointer = unsafe { self.pointer.offset(header.length as isize) };
+ self.remaining_length -= header.length as u32;
+
+ macro_rules! construct_entry {
+ ($entry_type:expr,
+ $entry_pointer:expr,
+ $(($value:expr => $variant:path as $type:ty)),*
+ ) => {
+ match $entry_type {
+ $(
+ $value => {
+ return Some($variant(unsafe {
+ &*($entry_pointer as *const $type)
+ }))
+ }
+ )*
+
+ /*
+ * These entry types are reserved by the ACPI standard. We should skip them
+ * if they appear in a real MADT.
+ */
+ 0x11..=0x7f => {}
+
+ /*
+ * These entry types are reserved for OEM use. Atm, we just skip them too.
+ * TODO: work out if we should ever do anything else here
+ */
+ 0x80..=0xff => {}
+ }
+ }
+ }
+
+ #[rustfmt::skip]
+ construct_entry!(
+ header.entry_type,
+ entry_pointer,
+ (0x0 => MadtEntry::LocalApic as LocalApicEntry),
+ (0x1 => MadtEntry::IoApic as IoApicEntry),
+ (0x2 => MadtEntry::InterruptSourceOverride as InterruptSourceOverrideEntry),
+ (0x3 => MadtEntry::NmiSource as NmiSourceEntry),
+ (0x4 => MadtEntry::LocalApicNmi as LocalApicNmiEntry),
+ (0x5 => MadtEntry::LocalApicAddressOverride as LocalApicAddressOverrideEntry),
+ (0x6 => MadtEntry::IoSapic as IoSapicEntry),
+ (0x7 => MadtEntry::LocalSapic as LocalSapicEntry),
+ (0x8 => MadtEntry::PlatformInterruptSource as PlatformInterruptSourceEntry),
+ (0x9 => MadtEntry::LocalX2Apic as LocalX2ApicEntry),
+ (0xa => MadtEntry::X2ApicNmi as X2ApicNmiEntry),
+ (0xb => MadtEntry::Gicc as GiccEntry),
+ (0xc => MadtEntry::Gicd as GicdEntry),
+ (0xd => MadtEntry::GicMsiFrame as GicMsiFrameEntry),
+ (0xe => MadtEntry::GicRedistributor as GicRedistributorEntry),
+ (0xf => MadtEntry::GicInterruptTranslationService as GicInterruptTranslationServiceEntry),
+ (0x10 => MadtEntry::MultiprocessorWakeup as MultiprocessorWakeupEntry)
+ );
+ }
+
+ None
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct EntryHeader {
+ pub entry_type: u8,
+ pub length: u8,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub apic_id: u8,
+ pub flags: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct IoApicEntry {
+ pub header: EntryHeader,
+ pub io_apic_id: u8,
+ _reserved: u8,
+ pub io_apic_address: u32,
+ pub global_system_interrupt_base: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct InterruptSourceOverrideEntry {
+ pub header: EntryHeader,
+ pub bus: u8, // 0 - ISA bus
+ pub irq: u8, // This is bus-relative
+ pub global_system_interrupt: u32,
+ pub flags: u16,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct NmiSourceEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub global_system_interrupt: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicNmiEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub flags: u16,
+ pub nmi_line: u8, // Describes which LINTn is the NMI connected to
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalApicAddressOverrideEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub local_apic_address: u64,
+}
+
+/// If this entry is present, the system has an I/O SAPIC, which must be used instead of the I/O
+/// APIC.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct IoSapicEntry {
+ pub header: EntryHeader,
+ pub io_apic_id: u8,
+ _reserved: u8,
+ pub global_system_interrupt_base: u32,
+ pub io_sapic_address: u64,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalSapicEntry {
+ pub header: EntryHeader,
+ pub processor_id: u8,
+ pub local_sapic_id: u8,
+ pub local_sapic_eid: u8,
+ _reserved: [u8; 3],
+ pub flags: u32,
+ pub processor_uid: u32,
+
+ /// This string can be used to associate this local SAPIC to a processor defined in the
+ /// namespace when the `_UID` object is a string. It is a null-terminated ASCII string, and so
+ /// this field will be `'\0'` if the string is not present, otherwise it extends from the
+ /// address of this field.
+ processor_uid_string: u8,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct PlatformInterruptSourceEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub interrupt_type: u8,
+ pub processor_id: u8,
+ pub processor_eid: u8,
+ pub io_sapic_vector: u8,
+ pub global_system_interrupt: u32,
+ pub platform_interrupt_source_flags: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct LocalX2ApicEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub x2apic_id: u32,
+ pub flags: u32,
+ pub processor_uid: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct X2ApicNmiEntry {
+ pub header: EntryHeader,
+ pub flags: u16,
+ pub processor_uid: u32,
+ pub nmi_line: u8,
+ _reserved: [u8; 3],
+}
+
+/// This field will appear for ARM processors that support ACPI and use the Generic Interrupt
+/// Controller. In the GICC interrupt model, each logical process has a Processor Device object in
+/// the namespace, and uses this structure to convey its GIC information.
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GiccEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub cpu_interface_number: u32,
+ pub processor_uid: u32,
+ pub flags: u32,
+ pub parking_protocol_version: u32,
+ pub performance_interrupt_gsiv: u32,
+ pub parked_address: u64,
+ pub gic_registers_address: u64,
+ pub gic_virtual_registers_address: u64,
+ pub gic_hypervisor_registers_address: u64,
+ pub vgic_maintenance_interrupt: u32,
+ pub gicr_base_address: u64,
+ pub mpidr: u64,
+ pub processor_power_efficiency_class: u8,
+ _reserved2: u8,
+ /// SPE overflow Interrupt.
+ ///
+ /// ACPI 6.3 defined this field. It is zero in prior versions or
+ /// if this processor does not support SPE.
+ pub spe_overflow_interrupt: u16,
+ pub trbe_interrupt: ExtendedField<u16, 6>,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicdEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub gic_id: u32,
+ pub physical_base_address: u64,
+ pub system_vector_base: u32,
+
+ /// The GIC version
+ /// 0x00: Fall back to hardware discovery
+ /// 0x01: GICv1
+ /// 0x02: GICv2
+ /// 0x03: GICv3
+ /// 0x04: GICv4
+ /// 0x05-0xff: Reserved for future use
+ pub gic_version: u8,
+ _reserved2: [u8; 3],
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicMsiFrameEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub frame_id: u32,
+ pub physical_base_address: u64,
+ pub flags: u32,
+ pub spi_count: u16,
+ pub spi_base: u16,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicRedistributorEntry {
+ pub header: EntryHeader,
+ _reserved: u16,
+ pub discovery_range_base_address: u64,
+ pub discovery_range_length: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct GicInterruptTranslationServiceEntry {
+ pub header: EntryHeader,
+ _reserved1: u16,
+ pub id: u32,
+ pub physical_base_address: u64,
+ _reserved2: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MultiprocessorWakeupEntry {
+ pub header: EntryHeader,
+ pub mailbox_version: u16,
+ _reserved: u32,
+ pub mailbox_address: u64,
+}
+
+#[cfg(feature = "allocator_api")]
+fn parse_mps_inti_flags(flags: u16) -> crate::AcpiResult<(Polarity, TriggerMode)> {
+ let polarity = match flags.get_bits(0..2) {
+ 0b00 => Polarity::SameAsBus,
+ 0b01 => Polarity::ActiveHigh,
+ 0b11 => Polarity::ActiveLow,
+ _ => return Err(crate::AcpiError::InvalidMadt(MadtError::MpsIntiInvalidPolarity)),
+ };
+
+ let trigger_mode = match flags.get_bits(2..4) {
+ 0b00 => TriggerMode::SameAsBus,
+ 0b01 => TriggerMode::Edge,
+ 0b11 => TriggerMode::Level,
+ _ => return Err(crate::AcpiError::InvalidMadt(MadtError::MpsIntiInvalidTriggerMode)),
+ };
+
+ Ok((polarity, trigger_mode))
+}
diff --git a/src/managed_slice.rs b/src/managed_slice.rs
new file mode 100644
index 0000000..e00c9c8
--- /dev/null
+++ b/src/managed_slice.rs
@@ -0,0 +1,75 @@
+use crate::{AcpiError, AcpiResult};
+use core::{
+ alloc::{Allocator, Layout},
+ mem,
+ ptr::NonNull,
+};
+
+/// Thin wrapper around a regular slice, taking a reference to an allocator for automatic
+/// deallocation when the slice is dropped out of scope.
+#[derive(Debug)]
+pub struct ManagedSlice<'a, T, A>
+where
+ A: Allocator,
+{
+ slice: &'a mut [T],
+ allocator: A,
+}
+
+impl<'a, T, A> ManagedSlice<'a, T, A>
+where
+ A: Allocator,
+{
+ /// Attempt to allocate a new `ManagedSlice` that holds `len` `T`s.
+ pub fn new_in(len: usize, allocator: A) -> AcpiResult<Self> {
+ let layout = Layout::array::<T>(len).map_err(|_| AcpiError::AllocError)?;
+ match allocator.allocate(layout) {
+ Ok(mut ptr) => {
+ let slice = unsafe { core::slice::from_raw_parts_mut(ptr.as_mut().as_mut_ptr().cast(), len) };
+ Ok(ManagedSlice { slice, allocator })
+ }
+ Err(_) => Err(AcpiError::AllocError),
+ }
+ }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a, T> ManagedSlice<'a, T, alloc::alloc::Global> {
+ pub fn new(len: usize) -> AcpiResult<Self> {
+ Self::new_in(len, alloc::alloc::Global)
+ }
+}
+
+impl<'a, T, A> Drop for ManagedSlice<'a, T, A>
+where
+ A: Allocator,
+{
+ fn drop(&mut self) {
+ unsafe {
+ let slice_ptr = NonNull::new_unchecked(self.slice.as_ptr().cast_mut().cast::<u8>());
+ let slice_layout =
+ Layout::from_size_align_unchecked(mem::size_of_val(self.slice), mem::align_of_val(self.slice));
+ self.allocator.deallocate(slice_ptr, slice_layout);
+ }
+ }
+}
+
+impl<'a, T, A> core::ops::Deref for ManagedSlice<'a, T, A>
+where
+ A: Allocator,
+{
+ type Target = [T];
+
+ fn deref(&self) -> &Self::Target {
+ self.slice
+ }
+}
+
+impl<'a, T, A> core::ops::DerefMut for ManagedSlice<'a, T, A>
+where
+ A: Allocator,
+{
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.slice
+ }
+}
diff --git a/src/mcfg.rs b/src/mcfg.rs
new file mode 100644
index 0000000..c09d44f
--- /dev/null
+++ b/src/mcfg.rs
@@ -0,0 +1,149 @@
+use crate::{
+ sdt::{SdtHeader, Signature},
+ AcpiTable,
+};
+use core::{mem, slice};
+
+/// Describes a set of regions of physical memory used to access the PCIe configuration space. A
+/// region is created for each entry in the MCFG. Given the segment group, bus, device number, and
+/// function of a PCIe device, the `physical_address` method on this will give you the physical
+/// address of the start of that device function's configuration space (each function has 4096
+/// bytes of configuration space in PCIe).
+#[cfg(feature = "allocator_api")]
+pub struct PciConfigRegions<'a, A>
+where
+ A: core::alloc::Allocator,
+{
+ regions: crate::ManagedSlice<'a, McfgEntry, A>,
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> PciConfigRegions<'a, alloc::alloc::Global> {
+ pub fn new<H>(tables: &crate::AcpiTables<H>) -> crate::AcpiResult<PciConfigRegions<'a, alloc::alloc::Global>>
+ where
+ H: crate::AcpiHandler,
+ {
+ Self::new_in(tables, alloc::alloc::Global)
+ }
+}
+
+#[cfg(feature = "allocator_api")]
+impl<'a, A> PciConfigRegions<'a, A>
+where
+ A: core::alloc::Allocator,
+{
+ pub fn new_in<H>(tables: &crate::AcpiTables<H>, allocator: A) -> crate::AcpiResult<PciConfigRegions<'a, A>>
+ where
+ H: crate::AcpiHandler,
+ {
+ let mcfg = tables.find_table::<Mcfg>()?;
+ let mcfg_entries = mcfg.entries();
+
+ let mut regions = crate::ManagedSlice::new_in(mcfg_entries.len(), allocator)?;
+ regions.copy_from_slice(mcfg_entries);
+
+ Ok(Self { regions })
+ }
+
+ /// Get the physical address of the start of the configuration space for a given PCIe device
+ /// function. Returns `None` if there isn't an entry in the MCFG that manages that device.
+ pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> {
+ // First, find the memory region that handles this segment and bus. This method is fine
+ // because there should only be one region that handles each segment group + bus
+ // combination.
+ let region = self.regions.iter().find(|region| {
+ region.pci_segment_group == segment_group_no
+ && (region.bus_number_start..=region.bus_number_end).contains(&bus)
+ })?;
+
+ Some(
+ region.base_address
+ + ((u64::from(bus - region.bus_number_start) << 20)
+ | (u64::from(device) << 15)
+ | (u64::from(function) << 12)),
+ )
+ }
+
+ /// Returns an iterator providing information about the system's present PCI busses.
+ /// This is roughly equivalent to manually iterating the system's MCFG table.
+ pub fn iter(&self) -> PciConfigEntryIterator {
+ PciConfigEntryIterator { entries: &self.regions, index: 0 }
+ }
+}
+
+/// Configuration entry describing a valid bus range for the given PCI segment group.
+pub struct PciConfigEntry {
+ pub segment_group: u16,
+ pub bus_range: core::ops::RangeInclusive<u8>,
+ pub physical_address: usize,
+}
+
+/// Iterator providing a [`PciConfigEntry`] for all of the valid bus ranges on the system.
+pub struct PciConfigEntryIterator<'a> {
+ entries: &'a [McfgEntry],
+ index: usize,
+}
+
+impl Iterator for PciConfigEntryIterator<'_> {
+ type Item = PciConfigEntry;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let entry = self.entries.get(self.index)?;
+ self.index += 1;
+
+ Some(PciConfigEntry {
+ segment_group: entry.pci_segment_group,
+ bus_range: entry.bus_number_start..=entry.bus_number_end,
+ physical_address: entry.base_address as usize,
+ })
+ }
+}
+
+#[repr(C, packed)]
+pub struct Mcfg {
+ header: SdtHeader,
+ _reserved: u64,
+ // Followed by `n` entries with format `McfgEntry`
+}
+
+/// ### Safety: Implementation properly represents a valid MCFG.
+unsafe impl AcpiTable for Mcfg {
+ const SIGNATURE: Signature = Signature::MCFG;
+
+ fn header(&self) -> &SdtHeader {
+ &self.header
+ }
+}
+
+impl Mcfg {
+ /// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should
+ /// be enumerated instead.
+ pub fn entries(&self) -> &[McfgEntry] {
+ let length = self.header.length as usize - mem::size_of::<Mcfg>();
+
+ // Intentionally round down in case length isn't an exact multiple of McfgEntry size
+ // (see rust-osdev/acpi#58)
+ let num_entries = length / mem::size_of::<McfgEntry>();
+
+ unsafe {
+ let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
+ slice::from_raw_parts(pointer, num_entries)
+ }
+ }
+}
+
+impl core::fmt::Debug for Mcfg {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish()
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct McfgEntry {
+ pub base_address: u64,
+ pub pci_segment_group: u16,
+ pub bus_number_start: u8,
+ pub bus_number_end: u8,
+ _reserved: u32,
+}
diff --git a/src/platform/interrupt.rs b/src/platform/interrupt.rs
new file mode 100644
index 0000000..75d2eff
--- /dev/null
+++ b/src/platform/interrupt.rs
@@ -0,0 +1,133 @@
+use crate::ManagedSlice;
+use core::alloc::Allocator;
+
+#[derive(Debug)]
+pub struct IoApic {
+ pub id: u8,
+ /// The physical address at which to access this I/O APIC.
+ pub address: u32,
+ /// The global system interrupt number where this I/O APIC's inputs start.
+ pub global_system_interrupt_base: u32,
+}
+
+#[derive(Debug)]
+pub struct NmiLine {
+ pub processor: NmiProcessor,
+ pub line: LocalInterruptLine,
+}
+
+/// Indicates which local interrupt line will be utilized by an external interrupt. Specifically,
+/// these lines directly correspond to their requisite LVT entries in a processor's APIC.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum LocalInterruptLine {
+ Lint0,
+ Lint1,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum NmiProcessor {
+ All,
+ ProcessorUid(u32),
+}
+
+/// Polarity indicates what signal mode the interrupt line needs to be in to be considered 'active'.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Polarity {
+ SameAsBus,
+ ActiveHigh,
+ ActiveLow,
+}
+
+/// Trigger mode of an interrupt, describing how the interrupt is triggered.
+///
+/// When an interrupt is `Edge` triggered, it is triggered exactly once, when the interrupt
+/// signal goes from its opposite polarity to its active polarity.
+///
+/// For `Level` triggered interrupts, a continuous signal is emitted so long as the interrupt
+/// is in its active polarity.
+///
+/// `SameAsBus`-triggered interrupts will utilize the same interrupt triggering as the system bus
+/// they communicate across.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TriggerMode {
+ SameAsBus,
+ Edge,
+ Level,
+}
+
+/// Describes a difference in the mapping of an ISA interrupt to how it's mapped in other interrupt
+/// models. For example, if a device is connected to ISA IRQ 0 and IOAPIC input 2, an override will
+/// appear mapping source 0 to GSI 2. Currently these will only be created for ISA interrupt
+/// sources.
+#[derive(Debug)]
+pub struct InterruptSourceOverride {
+ pub isa_source: u8,
+ pub global_system_interrupt: u32,
+ pub polarity: Polarity,
+ pub trigger_mode: TriggerMode,
+}
+
+/// Describes a Global System Interrupt that should be enabled as non-maskable. Any source that is
+/// non-maskable can not be used by devices.
+#[derive(Debug)]
+pub struct NmiSource {
+ pub global_system_interrupt: u32,
+ pub polarity: Polarity,
+ pub trigger_mode: TriggerMode,
+}
+
+#[derive(Debug)]
+pub struct Apic<'a, A>
+where
+ A: Allocator,
+{
+ pub local_apic_address: u64,
+ pub io_apics: ManagedSlice<'a, IoApic, A>,
+ pub local_apic_nmi_lines: ManagedSlice<'a, NmiLine, A>,
+ pub interrupt_source_overrides: ManagedSlice<'a, InterruptSourceOverride, A>,
+ pub nmi_sources: ManagedSlice<'a, NmiSource, A>,
+
+ /// If this field is set, you must remap and mask all the lines of the legacy PIC, even if
+ /// you choose to use the APIC. It's recommended that you do this even if ACPI does not
+ /// require you to.
+ pub also_has_legacy_pics: bool,
+}
+
+impl<'a, A> Apic<'a, A>
+where
+ A: Allocator,
+{
+ pub(crate) fn new(
+ local_apic_address: u64,
+ io_apics: ManagedSlice<'a, IoApic, A>,
+ local_apic_nmi_lines: ManagedSlice<'a, NmiLine, A>,
+ interrupt_source_overrides: ManagedSlice<'a, InterruptSourceOverride, A>,
+ nmi_sources: ManagedSlice<'a, NmiSource, A>,
+ also_has_legacy_pics: bool,
+ ) -> Self {
+ Self {
+ local_apic_address,
+ io_apics,
+ local_apic_nmi_lines,
+ interrupt_source_overrides,
+ nmi_sources,
+ also_has_legacy_pics,
+ }
+ }
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum InterruptModel<'a, A>
+where
+ A: Allocator,
+{
+ /// This model is only chosen when the MADT does not describe another interrupt model. On `x86_64` platforms,
+ /// this probably means only the legacy i8259 PIC is present.
+ Unknown,
+
+ /// Describes an interrupt controller based around the Advanced Programmable Interrupt Controller (any of APIC,
+ /// XAPIC, or X2APIC). These are likely to be found on x86 and x86_64 systems and are made up of a Local APIC
+ /// for each core and one or more I/O APICs to handle external interrupts.
+ Apic(Apic<'a, A>),
+}
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
new file mode 100644
index 0000000..c8f2221
--- /dev/null
+++ b/src/platform/mod.rs
@@ -0,0 +1,134 @@
+pub mod interrupt;
+
+use crate::{
+ address::GenericAddress,
+ fadt::Fadt,
+ madt::Madt,
+ AcpiError,
+ AcpiHandler,
+ AcpiResult,
+ AcpiTables,
+ ManagedSlice,
+ PowerProfile,
+};
+use core::alloc::Allocator;
+use interrupt::InterruptModel;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ProcessorState {
+ /// A processor in this state is unusable, and you must not attempt to bring it up.
+ Disabled,
+
+ /// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
+ /// but may be brought up.
+ WaitingForSipi,
+
+ /// A Running processor is currently brought up and running code.
+ Running,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Processor {
+ /// Corresponds to the `_UID` object of the processor's `Device`, or the `ProcessorId` field of the `Processor`
+ /// object, in AML.
+ pub processor_uid: u32,
+ /// The ID of the local APIC of the processor. Will be less than `256` if the APIC is being used, but can be
+ /// greater than this if the X2APIC is being used.
+ pub local_apic_id: u32,
+
+ /// The state of this processor. Check that the processor is not `Disabled` before attempting to bring it up!
+ pub state: ProcessorState,
+
+ /// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
+ /// When the bootloader is entered, the BSP is the only processor running code. To run code on
+ /// more than one processor, you need to "bring up" the APs.
+ pub is_ap: bool,
+}
+
+#[derive(Debug)]
+pub struct ProcessorInfo<'a, A>
+where
+ A: Allocator,
+{
+ pub boot_processor: Processor,
+ /// Application processors should be brought up in the order they're defined in this list.
+ pub application_processors: ManagedSlice<'a, Processor, A>,
+}
+
+impl<'a, A> ProcessorInfo<'a, A>
+where
+ A: Allocator,
+{
+ pub(crate) fn new(boot_processor: Processor, application_processors: ManagedSlice<'a, Processor, A>) -> Self {
+ Self { boot_processor, application_processors }
+ }
+}
+
+/// Information about the ACPI Power Management Timer (ACPI PM Timer).
+#[derive(Debug)]
+pub struct PmTimer {
+ /// A generic address to the register block of ACPI PM Timer.
+ pub base: GenericAddress,
+ /// This field is `true` if the hardware supports 32-bit timer, and `false` if the hardware supports 24-bit timer.
+ pub supports_32bit: bool,
+}
+
+impl PmTimer {
+ pub fn new(fadt: &Fadt) -> Result<Option<PmTimer>, AcpiError> {
+ match fadt.pm_timer_block()? {
+ Some(base) => Ok(Some(PmTimer { base, supports_32bit: { fadt.flags }.pm_timer_is_32_bit() })),
+ None => Ok(None),
+ }
+ }
+}
+
+/// `PlatformInfo` allows the collection of some basic information about the platform from some of the fixed-size
+/// tables in a nice way. It requires access to the `FADT` and `MADT`. It is the easiest way to get information
+/// about the processors and interrupt controllers on a platform.
+#[derive(Debug)]
+pub struct PlatformInfo<'a, A>
+where
+ A: Allocator,
+{
+ pub power_profile: PowerProfile,
+ pub interrupt_model: InterruptModel<'a, A>,
+ /// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
+ /// interrupt model. That information is stored here, if present.
+ pub processor_info: Option<ProcessorInfo<'a, A>>,
+ pub pm_timer: Option<PmTimer>,
+ /*
+ * TODO: we could provide a nice view of the hardware register blocks in the FADT here.
+ */
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> PlatformInfo<'a, alloc::alloc::Global> {
+ pub fn new<H>(tables: &AcpiTables<H>) -> AcpiResult<Self>
+ where
+ H: AcpiHandler,
+ {
+ Self::new_in(tables, alloc::alloc::Global)
+ }
+}
+
+impl<'a, A> PlatformInfo<'a, A>
+where
+ A: Allocator + Clone,
+{
+ pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> AcpiResult<Self>
+ where
+ H: AcpiHandler,
+ {
+ let fadt = tables.find_table::<Fadt>()?;
+ let power_profile = fadt.power_profile();
+
+ let madt = tables.find_table::<Madt>();
+ let (interrupt_model, processor_info) = match madt {
+ Ok(madt) => madt.parse_interrupt_model_in(allocator)?,
+ Err(_) => (InterruptModel::Unknown, None),
+ };
+ let pm_timer = PmTimer::new(&fadt)?;
+
+ Ok(PlatformInfo { power_profile, interrupt_model, processor_info, pm_timer })
+ }
+}
diff --git a/src/rsdp.rs b/src/rsdp.rs
new file mode 100644
index 0000000..3c33e9c
--- /dev/null
+++ b/src/rsdp.rs
@@ -0,0 +1,214 @@
+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 ";
diff --git a/src/sdt.rs b/src/sdt.rs
new file mode 100644
index 0000000..9ea72f3
--- /dev/null
+++ b/src/sdt.rs
@@ -0,0 +1,302 @@
+use crate::{AcpiError, AcpiHandler, AcpiResult, AcpiTable, PhysicalMapping};
+use core::{fmt, mem::MaybeUninit, str};
+
+/// Represents a field which may or may not be present within an ACPI structure, depending on the version of ACPI
+/// that a system supports. If the field is not present, it is not safe to treat the data as initialised.
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub struct ExtendedField<T: Copy, const MIN_REVISION: u8>(MaybeUninit<T>);
+
+impl<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
+ /// Access the field if it's present for the given revision of the table.
+ ///
+ /// ### Safety
+ /// If a bogus ACPI version is passed, this function may access uninitialised data.
+ pub unsafe fn access(&self, revision: u8) -> Option<T> {
+ if revision >= MIN_REVISION {
+ Some(unsafe { self.0.assume_init() })
+ } else {
+ None
+ }
+ }
+}
+
+/// All SDTs share the same header, and are `length` bytes long. The signature tells us which SDT
+/// this is.
+///
+/// The ACPI Spec (Version 6.4) defines the following SDT signatures:
+///
+/// * APIC - Multiple APIC Description Table (MADT)
+/// * BERT - Boot Error Record Table
+/// * BGRT - Boot Graphics Resource Table
+/// * CPEP - Corrected Platform Error Polling Table
+/// * DSDT - Differentiated System Description Table (DSDT)
+/// * ECDT - Embedded Controller Boot Resources Table
+/// * EINJ - Error Injection Table
+/// * ERST - Error Record Serialization Table
+/// * FACP - Fixed ACPI Description Table (FADT)
+/// * FACS - Firmware ACPI Control Structure
+/// * FPDT - Firmware Performance Data Table
+/// * GTDT - Generic Timer Description Table
+/// * HEST - Hardware Error Source Table
+/// * MSCT - Maximum System Characteristics Table
+/// * MPST - Memory Power StateTable
+/// * NFIT - NVDIMM Firmware Interface Table
+/// * OEMx - OEM Specific Information Tables
+/// * PCCT - Platform Communications Channel Table
+/// * PHAT - Platform Health Assessment Table
+/// * PMTT - Platform Memory Topology Table
+/// * PSDT - Persistent System Description Table
+/// * RASF - ACPI RAS Feature Table
+/// * RSDT - Root System Description Table
+/// * SBST - Smart Battery Specification Table
+/// * SDEV - Secure DEVices Table
+/// * SLIT - System Locality Distance Information Table
+/// * SRAT - System Resource Affinity Table
+/// * SSDT - Secondary System Description Table
+/// * XSDT - Extended System Description Table
+///
+/// Acpi reserves the following signatures and the specifications for them can be found [here](https://uefi.org/acpi):
+///
+/// * AEST - ARM Error Source Table
+/// * BDAT - BIOS Data ACPI Table
+/// * CDIT - Component Distance Information Table
+/// * CEDT - CXL Early Discovery Table
+/// * CRAT - Component Resource Attribute Table
+/// * CSRT - Core System Resource Table
+/// * DBGP - Debug Port Table
+/// * DBG2 - Debug Port Table 2 (note: ACPI 6.4 defines this as "DBPG2" but this is incorrect)
+/// * DMAR - DMA Remapping Table
+/// * DRTM -Dynamic Root of Trust for Measurement Table
+/// * ETDT - Event Timer Description Table (obsolete, superseeded by HPET)
+/// * HPET - IA-PC High Precision Event Timer Table
+/// * IBFT - iSCSI Boot Firmware Table
+/// * IORT - I/O Remapping Table
+/// * IVRS - I/O Virtualization Reporting Structure
+/// * LPIT - Low Power Idle Table
+/// * MCFG - PCI Express Memory-mapped Configuration Space base address description table
+/// * MCHI - Management Controller Host Interface table
+/// * MPAM - ARM Memory Partitioning And Monitoring table
+/// * MSDM - Microsoft Data Management Table
+/// * PRMT - Platform Runtime Mechanism Table
+/// * RGRT - Regulatory Graphics Resource Table
+/// * SDEI - Software Delegated Exceptions Interface table
+/// * SLIC - Microsoft Software Licensing table
+/// * SPCR - Microsoft Serial Port Console Redirection table
+/// * SPMI - Server Platform Management Interface table
+/// * STAO - _STA Override table
+/// * SVKL - Storage Volume Key Data table (Intel TDX only)
+/// * TCPA - Trusted Computing Platform Alliance Capabilities Table
+/// * TPM2 - Trusted Platform Module 2 Table
+/// * UEFI - Unified Extensible Firmware Interface Specification table
+/// * WAET - Windows ACPI Emulated Devices Table
+/// * WDAT - Watch Dog Action Table
+/// * WDRT - Watchdog Resource Table
+/// * WPBT - Windows Platform Binary Table
+/// * WSMT - Windows Security Mitigations Table
+/// * XENV - Xen Project
+#[derive(Debug, Clone, Copy)]
+#[repr(C, packed)]
+pub struct SdtHeader {
+ pub signature: Signature,
+ pub length: u32,
+ pub revision: u8,
+ pub checksum: u8,
+ pub oem_id: [u8; 6],
+ pub oem_table_id: [u8; 8],
+ pub oem_revision: u32,
+ pub creator_id: u32,
+ pub creator_revision: u32,
+}
+
+impl SdtHeader {
+ /// Whether values of header fields are permitted.
+ fn validate_header_fields(&self, signature: Signature) -> AcpiResult<()> {
+ // Check the signature
+ if self.signature != signature || str::from_utf8(&self.signature.0).is_err() {
+ return Err(AcpiError::SdtInvalidSignature(signature));
+ }
+
+ // Check the OEM id
+ if str::from_utf8(&self.oem_id).is_err() {
+ return Err(AcpiError::SdtInvalidOemId(signature));
+ }
+
+ // Check the OEM table id
+ if str::from_utf8(&self.oem_table_id).is_err() {
+ return Err(AcpiError::SdtInvalidTableId(signature));
+ }
+
+ Ok(())
+ }
+
+ /// Whether table is valid according to checksum.
+ fn validate_checksum(&self, signature: Signature) -> AcpiResult<()> {
+ // SAFETY: Entire table is mapped.
+ let table_bytes =
+ unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::<u8>(), self.length as usize) };
+ let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
+
+ if sum == 0 {
+ Ok(())
+ } else {
+ Err(AcpiError::SdtInvalidChecksum(signature))
+ }
+ }
+
+ /// Checks that:
+ ///
+ /// 1. The signature matches the one given.
+ /// 2. The values of various fields in the header are allowed.
+ /// 3. The checksum of the SDT is valid.
+ ///
+ /// This assumes that the whole SDT is mapped.
+ pub fn validate(&self, signature: Signature) -> AcpiResult<()> {
+ self.validate_header_fields(signature)?;
+ self.validate_checksum(signature)?;
+
+ Ok(())
+ }
+
+ /// Validates header, proceeding with checking entire table and returning a [`PhysicalMapping`] to it if
+ /// successful.
+ ///
+ /// The same checks are performed as [`SdtHeader::validate`], but `header_mapping` does not have to map the
+ /// entire table when calling. This is useful to avoid completely mapping a table that will be immediately
+ /// unmapped if it does not have a particular signature or has an invalid header.
+ pub(crate) fn validate_lazy<H: AcpiHandler, T: AcpiTable>(
+ header_mapping: PhysicalMapping<H, Self>,
+ handler: H,
+ ) -> AcpiResult<PhysicalMapping<H, T>> {
+ header_mapping.validate_header_fields(T::SIGNATURE)?;
+
+ // Reuse `header_mapping` to access the rest of the table if the latter is already mapped entirely
+ let table_length = header_mapping.length as usize;
+ let table_mapping = if header_mapping.mapped_length() >= table_length {
+ // Avoid requesting table unmap twice (from both `header_mapping` and `table_mapping`)
+ let header_mapping = core::mem::ManuallyDrop::new(header_mapping);
+
+ // SAFETY: `header_mapping` maps entire table.
+ unsafe {
+ PhysicalMapping::new(
+ header_mapping.physical_start(),
+ header_mapping.virtual_start().cast::<T>(),
+ table_length,
+ header_mapping.mapped_length(),
+ handler,
+ )
+ }
+ } else {
+ // Unmap header as soon as possible
+ let table_phys_start = header_mapping.physical_start();
+ drop(header_mapping);
+
+ // SAFETY: `table_phys_start` is the physical address of the header and the rest of the table.
+ unsafe { handler.map_physical_region(table_phys_start, table_length) }
+ };
+
+ // This is usually redundant compared to simply calling `validate_checksum` but respects custom
+ // `AcpiTable::validate` implementations.
+ table_mapping.validate()?;
+
+ Ok(table_mapping)
+ }
+
+ pub fn oem_id(&self) -> &str {
+ // Safe to unwrap because checked in `validate`
+ str::from_utf8(&self.oem_id).unwrap()
+ }
+
+ pub fn oem_table_id(&self) -> &str {
+ // Safe to unwrap because checked in `validate`
+ str::from_utf8(&self.oem_table_id).unwrap()
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(transparent)]
+pub struct Signature([u8; 4]);
+
+impl Signature {
+ pub const RSDT: Signature = Signature(*b"RSDT");
+ pub const XSDT: Signature = Signature(*b"XSDT");
+ pub const FADT: Signature = Signature(*b"FACP");
+ pub const HPET: Signature = Signature(*b"HPET");
+ pub const MADT: Signature = Signature(*b"APIC");
+ pub const MCFG: Signature = Signature(*b"MCFG");
+ pub const SSDT: Signature = Signature(*b"SSDT");
+ pub const BERT: Signature = Signature(*b"BERT");
+ pub const BGRT: Signature = Signature(*b"BGRT");
+ pub const CPEP: Signature = Signature(*b"CPEP");
+ pub const DSDT: Signature = Signature(*b"DSDT");
+ pub const ECDT: Signature = Signature(*b"ECDT");
+ pub const EINJ: Signature = Signature(*b"EINJ");
+ pub const ERST: Signature = Signature(*b"ERST");
+ pub const FACS: Signature = Signature(*b"FACS");
+ pub const FPDT: Signature = Signature(*b"FPDT");
+ pub const GTDT: Signature = Signature(*b"GTDT");
+ pub const HEST: Signature = Signature(*b"HEST");
+ pub const MSCT: Signature = Signature(*b"MSCT");
+ pub const MPST: Signature = Signature(*b"MPST");
+ pub const NFIT: Signature = Signature(*b"NFIT");
+ pub const PCCT: Signature = Signature(*b"PCCT");
+ pub const PHAT: Signature = Signature(*b"PHAT");
+ pub const PMTT: Signature = Signature(*b"PMTT");
+ pub const PSDT: Signature = Signature(*b"PSDT");
+ pub const RASF: Signature = Signature(*b"RASF");
+ pub const SBST: Signature = Signature(*b"SBST");
+ pub const SDEV: Signature = Signature(*b"SDEV");
+ pub const SLIT: Signature = Signature(*b"SLIT");
+ pub const SRAT: Signature = Signature(*b"SRAT");
+ pub const AEST: Signature = Signature(*b"AEST");
+ pub const BDAT: Signature = Signature(*b"BDAT");
+ pub const CDIT: Signature = Signature(*b"CDIT");
+ pub const CEDT: Signature = Signature(*b"CEDT");
+ pub const CRAT: Signature = Signature(*b"CRAT");
+ pub const CSRT: Signature = Signature(*b"CSRT");
+ pub const DBGP: Signature = Signature(*b"DBGP");
+ pub const DBG2: Signature = Signature(*b"DBG2");
+ pub const DMAR: Signature = Signature(*b"DMAR");
+ pub const DRTM: Signature = Signature(*b"DRTM");
+ pub const ETDT: Signature = Signature(*b"ETDT");
+ pub const IBFT: Signature = Signature(*b"IBFT");
+ pub const IORT: Signature = Signature(*b"IORT");
+ pub const IVRS: Signature = Signature(*b"IVRS");
+ pub const LPIT: Signature = Signature(*b"LPIT");
+ pub const MCHI: Signature = Signature(*b"MCHI");
+ pub const MPAM: Signature = Signature(*b"MPAM");
+ pub const MSDM: Signature = Signature(*b"MSDM");
+ pub const PRMT: Signature = Signature(*b"PRMT");
+ pub const RGRT: Signature = Signature(*b"RGRT");
+ pub const SDEI: Signature = Signature(*b"SDEI");
+ pub const SLIC: Signature = Signature(*b"SLIC");
+ pub const SPCR: Signature = Signature(*b"SPCR");
+ pub const SPMI: Signature = Signature(*b"SPMI");
+ pub const STAO: Signature = Signature(*b"STAO");
+ pub const SVKL: Signature = Signature(*b"SVKL");
+ pub const TCPA: Signature = Signature(*b"TCPA");
+ pub const TPM2: Signature = Signature(*b"TPM2");
+ pub const UEFI: Signature = Signature(*b"UEFI");
+ pub const WAET: Signature = Signature(*b"WAET");
+ pub const WDAT: Signature = Signature(*b"WDAT");
+ pub const WDRT: Signature = Signature(*b"WDRT");
+ pub const WPBT: Signature = Signature(*b"WPBT");
+ pub const WSMT: Signature = Signature(*b"WSMT");
+ pub const XENV: Signature = Signature(*b"XENV");
+
+ pub fn as_str(&self) -> &str {
+ str::from_utf8(&self.0).unwrap()
+ }
+}
+
+impl fmt::Display for Signature {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
+impl fmt::Debug for Signature {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "\"{}\"", self.as_str())
+ }
+}