Skip to content
Merged
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[workspace]
members = ["rsdp", "acpi", "aml", "acpi-dumper", "aml_tester"]
resolver = "2"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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)

Expand Down
14 changes: 7 additions & 7 deletions acpi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
[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"
edition = "2018"
edition = "2021"

[dependencies]
bit_field = "0.10"
log = "0.4"
rsdp = { version = "2", path = "../rsdp" }
bit_field = "0.10.2"
log = "0.4.20"

[features]
default = ["allocator_api"]
default = ["allocator_api", "alloc"]
allocator_api = []
alloc = ["allocator_api"]
128 changes: 128 additions & 0 deletions acpi/src/handler.rs
Original file line number Diff line number Diff line change
@@ -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>>();
}
}
}
51 changes: 37 additions & 14 deletions acpi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +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.
//!
//! 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
Expand Down Expand Up @@ -55,12 +63,17 @@
#[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")]
Expand All @@ -77,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;
Expand Down Expand Up @@ -115,7 +125,10 @@ pub unsafe trait AcpiTable {
/// Error type used by functions that return an `AcpiResult<T>`.
#[derive(Debug)]
pub enum AcpiError {
Rsdp(RsdpError),
NoValidRsdp,
RsdpIncorrectSignature,
RsdpInvalidOemId,
RsdpInvalidChecksum,

SdtInvalidSignature(Signature),
SdtInvalidOemId(Signature),
Expand All @@ -136,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<H: AcpiHandler> {
mapping: PhysicalMapping<H, SdtHeader>,
Expand All @@ -154,17 +167,17 @@ 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<Self> {
let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
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) }
}

/// 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()) }.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) }
}
Expand Down Expand Up @@ -195,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
Expand Down Expand Up @@ -318,6 +331,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<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.
Expand Down
58 changes: 33 additions & 25 deletions acpi/src/managed_slice.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
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,
}

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<Self> {
// Safety: Type automatically deallocated memory on `Drop` and;
// Constructed slice is from valid, aligned, allocated memory.
unsafe {
allocator
.allocate(alloc::Layout::array::<T>(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<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: 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::<u8>()) };
// 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::<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: alloc::Allocator,
A: Allocator,
{
type Target = [T];

Expand All @@ -59,7 +67,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
Expand Down
Loading