From 1178ebf6048dfb369229a401899c3908c4c3fc45 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 21:46:39 +0100 Subject: [PATCH 01/10] acpi: make `ManagedSlice` a little more readable Rearranging the imported names was necessary to add the `alloc` feature anyways, and I thought this looked a little neater while I was here. --- acpi/src/managed_slice.rs | 51 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/acpi/src/managed_slice.rs b/acpi/src/managed_slice.rs index e60b996e..cbd7992e 100644 --- a/acpi/src/managed_slice.rs +++ b/acpi/src/managed_slice.rs @@ -1,11 +1,16 @@ -use core::{alloc, mem}; +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: alloc::Allocator, + A: Allocator, { slice: &'a mut [T], allocator: A, @@ -13,42 +18,38 @@ where impl<'a, T, A> ManagedSlice<'a, T, A> where - A: alloc::Allocator, + A: Allocator, { - /// Attempts to allocate a new `&mut [T]` in the given allocator. - pub fn new_in(len: usize, allocator: A) -> crate::AcpiResult { - // Safety: Type automatically deallocated memory on `Drop` and; - // Constructed slice is from valid, aligned, allocated memory. - unsafe { - allocator - .allocate(alloc::Layout::array::(len).map_err(|_| crate::AcpiError::AllocError)?) - .map(|mut ptr| core::slice::from_raw_parts_mut(ptr.as_mut().as_mut_ptr().cast(), len)) - .map(|slice| Self { slice, allocator }) - .map_err(|_| crate::AcpiError::AllocError) + /// Attempt to allocate a new `ManagedSlice` that holds `len` `T`s. + pub fn new_in(len: usize, allocator: A) -> AcpiResult { + let layout = Layout::array::(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), } } } impl<'a, T, A> Drop for ManagedSlice<'a, T, A> where - A: alloc::Allocator, + A: Allocator, { fn drop(&mut self) { - // Safety: Slice is required by function to point to non-null memory. - let slice_ptr = unsafe { core::ptr::NonNull::new_unchecked(self.slice.as_ptr().cast_mut().cast::()) }; - // Safety: Slice is constructed from a valid layout. - let slice_layout = unsafe { - alloc::Layout::from_size_align_unchecked(mem::size_of_val(self.slice), mem::align_of_val(self.slice)) - }; - - // Safety: Caller is required to provide a slice allocated with the provided allocator. - unsafe { self.allocator.deallocate(slice_ptr, slice_layout) }; + unsafe { + let slice_ptr = NonNull::new_unchecked(self.slice.as_ptr().cast_mut().cast::()); + 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: alloc::Allocator, + A: Allocator, { type Target = [T]; @@ -59,7 +60,7 @@ where impl<'a, T, A> core::ops::DerefMut for ManagedSlice<'a, T, A> where - A: alloc::Allocator, + A: Allocator, { fn deref_mut(&mut self) -> &mut Self::Target { self.slice From 5fd54a19d51374cbf9030b9fc36358fc8d4433f9 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 21:49:05 +0100 Subject: [PATCH 02/10] acpi: add `alloc` feature for easy use of the global allocator Most people won't care much about the "new" `allocator_api` work, so add `new` methods that just use the globally-set allocator too. --- acpi/Cargo.toml | 3 ++- acpi/src/lib.rs | 14 ++++++++++++++ acpi/src/managed_slice.rs | 7 +++++++ acpi/src/mcfg.rs | 10 ++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index 08e251dd..51e9d3c6 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -15,5 +15,6 @@ log = "0.4" rsdp = { version = "2", path = "../rsdp" } [features] -default = ["allocator_api"] +default = ["allocator_api", "alloc"] allocator_api = [] +alloc = ["allocator_api"] diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index 77f7001e..bd5d1a45 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -7,6 +7,7 @@ //! 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. //! +//! TODO: make this correct re alloc features //! This crate requires `alloc` to make heap allocations. If you are trying to find the RSDP in an environment that //! does not have a heap (e.g. a bootloader), you can use the `rsdp` crate. The types from that crate are //! compatible with `acpi`. @@ -55,6 +56,9 @@ #[cfg(test)] extern crate std; +#[cfg(feature = "alloc")] +extern crate alloc; + pub mod address; pub mod bgrt; pub mod fadt; @@ -318,6 +322,16 @@ where 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::new_in(self, alloc::alloc::Global) + } + /// 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. diff --git a/acpi/src/managed_slice.rs b/acpi/src/managed_slice.rs index cbd7992e..e00c9c80 100644 --- a/acpi/src/managed_slice.rs +++ b/acpi/src/managed_slice.rs @@ -33,6 +33,13 @@ where } } +#[cfg(feature = "alloc")] +impl<'a, T> ManagedSlice<'a, T, alloc::alloc::Global> { + pub fn new(len: usize) -> AcpiResult { + Self::new_in(len, alloc::alloc::Global) + } +} + impl<'a, T, A> Drop for ManagedSlice<'a, T, A> where A: Allocator, diff --git a/acpi/src/mcfg.rs b/acpi/src/mcfg.rs index 47276af2..c09d44f0 100644 --- a/acpi/src/mcfg.rs +++ b/acpi/src/mcfg.rs @@ -17,6 +17,16 @@ where regions: crate::ManagedSlice<'a, McfgEntry, A>, } +#[cfg(feature = "alloc")] +impl<'a> PciConfigRegions<'a, alloc::alloc::Global> { + pub fn new(tables: &crate::AcpiTables) -> crate::AcpiResult> + where + H: crate::AcpiHandler, + { + Self::new_in(tables, alloc::alloc::Global) + } +} + #[cfg(feature = "allocator_api")] impl<'a, A> PciConfigRegions<'a, A> where From 404d3bc4df663846b8ea26fbcc428e6b3695f982 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 22:10:58 +0100 Subject: [PATCH 03/10] acpi: specify exact minimum dependency versions This is apparently better practice. --- acpi/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index 51e9d3c6..0538c077 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -10,8 +10,8 @@ license = "MIT/Apache-2.0" edition = "2018" [dependencies] -bit_field = "0.10" -log = "0.4" +bit_field = "0.10.2" +log = "0.4.20" rsdp = { version = "2", path = "../rsdp" } [features] From 4fa1663b34a3e513cd43d95d6dc2bf17120d0309 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 22:11:20 +0100 Subject: [PATCH 04/10] acpi: add top-level documentation about `allocator_api,alloc` features --- acpi/src/lib.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index bd5d1a45..84531f29 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -7,10 +7,17 @@ //! 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. //! -//! TODO: make this correct re alloc features -//! This crate requires `alloc` to make heap allocations. If you are trying to find the RSDP in an environment that -//! does not have a heap (e.g. a bootloader), you can use the `rsdp` crate. The types from that crate are -//! compatible with `acpi`. +//! 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 From c0698bd39f9466f44ab4625ff6e2c81bd2dd8879 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 22:13:50 +0100 Subject: [PATCH 05/10] acpi: provide `new` function directly on `PlatformInfo` too --- acpi/src/lib.rs | 2 +- acpi/src/platform/mod.rs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index 84531f29..e7edcccf 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -336,7 +336,7 @@ where /// Like `platform_info_in`, but uses the global allocator. #[cfg(feature = "alloc")] pub fn platform_info(&self) -> AcpiResult> { - PlatformInfo::new_in(self, alloc::alloc::Global) + PlatformInfo::new(self) } /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the diff --git a/acpi/src/platform/mod.rs b/acpi/src/platform/mod.rs index da77a55a..c8f22219 100644 --- a/acpi/src/platform/mod.rs +++ b/acpi/src/platform/mod.rs @@ -6,6 +6,7 @@ use crate::{ madt::Madt, AcpiError, AcpiHandler, + AcpiResult, AcpiTables, ManagedSlice, PowerProfile, @@ -100,11 +101,21 @@ where */ } +#[cfg(feature = "alloc")] +impl<'a> PlatformInfo<'a, alloc::alloc::Global> { + pub fn new(tables: &AcpiTables) -> AcpiResult + where + H: AcpiHandler, + { + Self::new_in(tables, alloc::alloc::Global) + } +} + impl<'a, A> PlatformInfo<'a, A> where A: Allocator + Clone, { - pub fn new_in(tables: &AcpiTables, allocator: A) -> crate::AcpiResult + pub fn new_in(tables: &AcpiTables, allocator: A) -> AcpiResult where H: AcpiHandler, { From 77e9ab860d553ee966c5aac006082707a961559a Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 22:19:18 +0100 Subject: [PATCH 06/10] rsdp,acpi: update crates to 2021 edition `aml` should also be moved to the 2021 edition at some point --- Cargo.toml | 1 + acpi/Cargo.toml | 2 +- rsdp/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48cb5b2f..a1c9d3f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] members = ["rsdp", "acpi", "aml", "acpi-dumper", "aml_tester"] +resolver = "2" diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index 0538c077..7d6854eb 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -7,7 +7,7 @@ description = "Library for parsing ACPI tables" categories = ["hardware-support", "no-std"] readme = "../README.md" license = "MIT/Apache-2.0" -edition = "2018" +edition = "2021" [dependencies] bit_field = "0.10.2" diff --git a/rsdp/Cargo.toml b/rsdp/Cargo.toml index 616b4ad2..7529422f 100644 --- a/rsdp/Cargo.toml +++ b/rsdp/Cargo.toml @@ -7,7 +7,7 @@ description = "Zero-allocation library for locating and parsing the RSDP, the fi categories = ["hardware-support", "no-std"] readme = "../README.md" license = "MIT/Apache-2.0" -edition = "2018" +edition = "2021" [dependencies] log = "0.4" From 419bd95b93320de4ea960ed8f6f2184653302900 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Sat, 2 Sep 2023 22:28:06 +0100 Subject: [PATCH 07/10] Make some tweaks to `README` re allocation in `acpi` --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b658db49..8b14560f 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ 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) -- `aml` parses the AML tables (can be useful, far from feature-complete) +- `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). @@ -30,7 +30,7 @@ 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 -Acpi is dual-licenced under: +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) From 0f98b1bcefba63ca1373ebee496a397b2a821ef1 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Tue, 5 Sep 2023 01:32:12 +0100 Subject: [PATCH 08/10] Move contents of `rsdp` into `acpi` Now `acpi` can be used from an allocator-less environment, the original motivation of the `rsdp` crate has been removed. We can therefore move towards deprecating it, and maintaining one less crate! --- acpi/Cargo.toml | 1 - acpi/src/handler.rs | 128 ++++++++++++++++++++++++++ acpi/src/lib.rs | 24 ++--- acpi/src/rsdp.rs | 214 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 355 insertions(+), 12 deletions(-) create mode 100644 acpi/src/handler.rs create mode 100644 acpi/src/rsdp.rs diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index 7d6854eb..6fa3423a 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -12,7 +12,6 @@ edition = "2021" [dependencies] bit_field = "0.10.2" log = "0.4.20" -rsdp = { version = "2", path = "../rsdp" } [features] default = ["allocator_api", "alloc"] diff --git a/acpi/src/handler.rs b/acpi/src/handler.rs new file mode 100644 index 00000000..6cc0cd05 --- /dev/null +++ b/acpi/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::()` +/// bytes, but may be bigger. +/// +/// See `PhysicalMapping::new` for the meaning of each field. +#[derive(Debug)] +pub struct PhysicalMapping +where + H: AcpiHandler, +{ + physical_start: usize, + virtual_start: NonNull, + region_length: usize, // Can be equal or larger than size_of::() + mapped_length: usize, // Differs from `region_length` if padding is added for alignment + handler: H, +} + +impl PhysicalMapping +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::()`. + /// - `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, + 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 { + 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 Send for PhysicalMapping {} + +impl Deref for PhysicalMapping +where + H: AcpiHandler, +{ + type Target = T; + + fn deref(&self) -> &T { + unsafe { self.virtual_start.as_ref() } + } +} + +impl Drop for PhysicalMapping +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::()`). 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::()`. + unsafe fn map_physical_region(&self, physical_address: usize, size: usize) -> PhysicalMapping; + + /// 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(region: &PhysicalMapping); +} + +#[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() {} + fn caller() { + test_send_sync::>(); + } + } +} diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index e7edcccf..83facc5b 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -69,9 +69,11 @@ 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")] @@ -88,12 +90,9 @@ pub use crate::platform::{interrupt::InterruptModel, PlatformInfo}; pub use crate::mcfg::PciConfigRegions; pub use fadt::PowerProfile; +pub use handler::{AcpiHandler, PhysicalMapping}; pub use hpet::HpetInfo; pub use madt::MadtError; -pub use rsdp::{ - handler::{AcpiHandler, PhysicalMapping}, - RsdpError, -}; use crate::sdt::{SdtHeader, Signature}; use core::mem; @@ -126,7 +125,10 @@ pub unsafe trait AcpiTable { /// Error type used by functions that return an `AcpiResult`. #[derive(Debug)] pub enum AcpiError { - Rsdp(RsdpError), + NoValidRsdp, + RsdpIncorrectSignature, + RsdpInvalidOemId, + RsdpInvalidChecksum, SdtInvalidSignature(Signature), SdtInvalidOemId(Signature), @@ -147,8 +149,8 @@ pub enum AcpiError { /// /// ### Implementation Note /// -/// When using the `allocator_api` feature, [`PlatformInfo::new()`] provides a much cleaner -/// API for enumerating ACPI structures once an `AcpiTables` has been constructed. +/// 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 { mapping: PhysicalMapping, @@ -165,9 +167,9 @@ where /// ### Safety: Caller must ensure the provided address is valid to read as an RSDP. pub unsafe fn from_rsdp(handler: H, address: usize) -> AcpiResult { let rsdp_mapping = unsafe { handler.map_physical_region::(address, mem::size_of::()) }; - rsdp_mapping.validate().map_err(AcpiError::Rsdp)?; + rsdp_mapping.validate()?; - // Safety: `RSDP` has been validated. + // Safety: RSDP has been validated. unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) } } @@ -175,7 +177,7 @@ where /// 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 { - let rsdp_mapping = unsafe { Rsdp::search_for_on_bios(handler.clone()) }.map_err(AcpiError::Rsdp)?; + 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) } } @@ -206,7 +208,7 @@ where drop(rsdp_mapping); // Map and validate root table - // SAFETY: Addresses from a validated `RSDP` are also guaranteed to be valid. + // 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 diff --git a/acpi/src/rsdp.rs b/acpi/src/rsdp.rs new file mode 100644 index 00000000..19d6d6f0 --- /dev/null +++ b/acpi/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_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(handler: H) -> AcpiResult> + 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::(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::()).step_by(16).find_map(|maybe_rsdp_bytes_slice| { + let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::(); + 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::(address, mem::size_of::()) }; + 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. +pub fn find_search_areas(handler: H) -> [Range; 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::(EBDA_START_SEGMENT_PTR, mem::size_of::()) }; + 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 "; From 6b38e25e27c5e3762ac85d76e6ae588691e91178 Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Wed, 13 Sep 2023 01:29:54 +0100 Subject: [PATCH 09/10] Publish `apci v5.0.0` --- acpi/Cargo.toml | 4 ++-- acpi/src/rsdp.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index 6fa3423a..e80b462d 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "acpi" -version = "4.1.1" +version = "5.0.0" authors = ["Isaac Woods"] repository = "https://github.com/rust-osdev/acpi" -description = "Library for parsing ACPI tables" +description = "A pure-Rust library for parsing ACPI tables" categories = ["hardware-support", "no-std"] readme = "../README.md" license = "MIT/Apache-2.0" diff --git a/acpi/src/rsdp.rs b/acpi/src/rsdp.rs index 19d6d6f0..3c33e9c0 100644 --- a/acpi/src/rsdp.rs +++ b/acpi/src/rsdp.rs @@ -170,7 +170,7 @@ impl Rsdp { } /// Find the areas we should search for the RSDP in. -pub fn find_search_areas(handler: H) -> [Range; 2] +fn find_search_areas(handler: H) -> [Range; 2] where H: AcpiHandler, { From 649cce3286cca95e1f4fd7c2534aa1b35758c41b Mon Sep 17 00:00:00 2001 From: Isaac Woods Date: Wed, 13 Sep 2023 02:01:34 +0100 Subject: [PATCH 10/10] Add deprecation notice to `rsdp` and publish as `rsdp v2.0.1` --- rsdp/Cargo.toml | 3 +-- rsdp/README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 rsdp/README.md diff --git a/rsdp/Cargo.toml b/rsdp/Cargo.toml index 7529422f..e51e73f6 100644 --- a/rsdp/Cargo.toml +++ b/rsdp/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "rsdp" -version = "2.0.0" +version = "2.0.1" authors = ["Isaac Woods", "Restioson"] repository = "https://github.com/rust-osdev/acpi" description = "Zero-allocation library for locating and parsing the RSDP, the first ACPI table" categories = ["hardware-support", "no-std"] -readme = "../README.md" license = "MIT/Apache-2.0" edition = "2021" diff --git a/rsdp/README.md b/rsdp/README.md new file mode 100644 index 00000000..bdf379e0 --- /dev/null +++ b/rsdp/README.md @@ -0,0 +1,43 @@ +# Acpi +⚠️**WARNING: The `rsdp` crate was previously a component of the `acpi` ecosystem, but has been deprecated. Its +functionality is now entirely supported by the `acpi` crate, including a subset of functionality that will work in +an environment that does not have an allocator. This crate will likely not receive further updates**⚠️ + +![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.