diff options
author | Arve Hjønnevåg <arve@android.com> | 2024-02-26 14:19:16 -0800 |
---|---|---|
committer | Arve Hjønnevåg <arve@android.com> | 2024-02-27 11:01:06 -0800 |
commit | 8dac36a3052b2a93743d04d57ce077c598b7530e (patch) | |
tree | a2abe66d0ef992e89bdab5b08acf973beb523e57 | |
parent | 1444dc8fbfd1c24b36dc68f490fc58e9d95acc5d (diff) | |
download | acpi-upstream.tar.gz |
Import 'acpi' crateupstream
Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
For Build Team: go/ab-third-party-imports
Bug: http://b/326112506
Test: m <Crate Library>
Change-Id: I6125fb3f61e971693086e0846d745743cc8d087f
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Cargo.toml | 38 | ||||
-rw-r--r-- | Cargo.toml.orig | 19 | ||||
-rw-r--r-- | LICENSE | 222 | ||||
-rw-r--r-- | METADATA | 19 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | README.md | 39 | ||||
-rw-r--r-- | src/address.rs | 105 | ||||
-rw-r--r-- | src/bgrt.rs | 69 | ||||
-rw-r--r-- | src/fadt.rs | 521 | ||||
-rw-r--r-- | src/handler.rs | 128 | ||||
-rw-r--r-- | src/hpet.rs | 99 | ||||
-rw-r--r-- | src/lib.rs | 448 | ||||
-rw-r--r-- | src/madt.rs | 646 | ||||
-rw-r--r-- | src/managed_slice.rs | 75 | ||||
-rw-r--r-- | src/mcfg.rs | 149 | ||||
-rw-r--r-- | src/platform/interrupt.rs | 133 | ||||
-rw-r--r-- | src/platform/mod.rs | 134 | ||||
-rw-r--r-- | src/rsdp.rs | 214 | ||||
-rw-r--r-- | src/sdt.rs | 302 |
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"] @@ -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 @@ -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()) + } +} |