Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare new version of acpi #197

Merged
merged 10 commits into from
Sep 13, 2023
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
Loading