diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..59da1056 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +run_tests = "run --manifest-path tools/aml_tester/Cargo.toml --" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d20a65fb..bcce705f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,8 +40,8 @@ jobs: target: ${{ matrix.target }} components: llvm-tools-preview - - name: Build rsdp, acpi, and aml - run: cargo build -p rsdp -p acpi -p aml --target $TARGET + - name: Build crates + run: cargo build --target $TARGET test: runs-on: ubuntu-latest @@ -60,10 +60,10 @@ jobs: run: sudo apt-get install -y acpica-tools - name: Run tests - run: cargo test --all + run: cargo test - name: Run AML test suite - run: cargo run --bin aml_tester -- -p tests --reset + run: cargo run_tests -p tests clippy: runs-on: ubuntu-latest @@ -79,14 +79,8 @@ jobs: profile: minimal components: clippy - - name: Run clippy (ACPI) - run: cargo clippy -p acpi + - name: Run clippy + run: cargo clippy - - name: Run clippy (ACPI tests) - run: cargo clippy -p acpi --tests - - - name: Run clippy (AML) - run: cargo clippy -p aml - - - name: Run clippy (AML tests) - run: cargo clippy -p aml --tests + - name: Run clippy (tests) + run: cargo clippy --tests diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 0b2f174f..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -# `acpi v4.1.1` - 2022-08-01 -### Bug Fixes -- Fix a bug with how the number of comparators the HPET provides is calculated - ([#121](https://github.com/rust-osdev/acpi/pull/121)) diff --git a/Cargo.toml b/Cargo.toml index a1c9d3f4..a4d6270e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,27 @@ [workspace] -members = ["rsdp", "acpi", "aml", "acpi-dumper", "aml_tester"] +members = ["tools/aml_tester", "tools/acpi_dumper"] resolver = "2" + +[package] +name = "acpi" +version = "5.1.0" +authors = ["Isaac Woods"] +repository = "https://github.com/rust-osdev/acpi" +description = "A pure-Rust library for interacting with ACPI" +categories = ["hardware-support", "no-std"] +readme = "../README.md" +license = "MIT/Apache-2.0" +edition = "2024" + +[dependencies] +bit_field = "0.10.2" +bitflags = "2.5.0" +log = "0.4.20" +spinning_top = "0.3.0" +pci_types = { version = "0.10.0", public = true, optional = true } +byteorder = { version = "1.5.0", default-features = false } + +[features] +default = ["alloc", "aml"] +alloc = [] +aml = ["alloc", "pci_types"] diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml deleted file mode 100644 index deeb34ac..00000000 --- a/acpi/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "acpi" -version = "5.2.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" -bitflags = "2.5.0" -log = "0.4.20" - -[features] -default = ["allocator_api", "alloc"] -allocator_api = [] -alloc = ["allocator_api"] diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs deleted file mode 100644 index 0b254df1..00000000 --- a/acpi/src/lib.rs +++ /dev/null @@ -1,513 +0,0 @@ -//! 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** -//! -//! `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`] 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`] parses the HPET table and tells you how to configure the High Precision Event Timer. -//! - [`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; -pub mod spcr; - -#[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 = core::result::Result; - -/// 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`. -#[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, -} - -macro_rules! read_root_table { - ($signature_name:ident, $address:ident, $acpi_handler:ident) => {{ - #[repr(transparent)] - struct RootTable { - header: SdtHeader, - } - - unsafe impl AcpiTable for RootTable { - const SIGNATURE: Signature = Signature::$signature_name; - - fn header(&self) -> &SdtHeader { - &self.header - } - } - - // Map and validate root table - // SAFETY: Addresses from a validated RSDP are also guaranteed to be valid. - let table_mapping = unsafe { read_table::<_, RootTable>($acpi_handler.clone(), $address) }?; - - // 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::(), - table_mapping.region_length(), - table_mapping.mapped_length(), - $acpi_handler.clone(), - ) - }; - - table_mapping - }}; -} - -/// 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 { - mapping: PhysicalMapping, - revision: u8, - handler: H, -} - -impl AcpiTables -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 { - let rsdp_mapping = unsafe { handler.map_physical_region::(address, mem::size_of::()) }; - 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_on_bios`] for details. - /// details. - /// - /// ### Safety - /// - /// The caller must ensure that this function is called on BIOS platforms. - pub unsafe fn search_for_rsdp_bios(handler: H) -> AcpiResult { - 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) -> AcpiResult { - 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. - */ - let table_phys_start = rsdp_mapping.rsdt_address() as usize; - drop(rsdp_mapping); - read_root_table!(RSDT, table_phys_start, handler) - } else { - /* - * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated - * to 32 bits on x86. - */ - let table_phys_start = rsdp_mapping.xsdt_address() as usize; - drop(rsdp_mapping); - read_root_table!(XSDT, table_phys_start, handler) - }; - - Ok(Self { mapping: root_table_mapping, revision, handler }) - } - - /// Create an `AcpiTables` if you have the physical address of the RSDT/XSDT. - /// - /// ### Safety - /// - /// Caller must ensure the provided address is valid RSDT/XSDT address. - pub unsafe fn from_rsdt(handler: H, revision: u8, address: usize) -> AcpiResult { - 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, address, handler) - } 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, address, handler) - }; - - 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::() }; - let ptrs_bytes_len = self.mapping.region_length() - mem::size_of::(); - // 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::()]; - let common_ptr_size = usize::min(mem::size_of::(), 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(&self) -> AcpiResult> { - 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)) - } - - /// Iterates through all of the table headers. - pub fn headers(&self) -> SdtHeaderIterator<'_, H> { - SdtHeaderIterator { tables_phys_ptrs: self.tables_phys_ptrs(), handler: self.handler.clone() } - } - - /// Finds and returns the DSDT AML table, if it exists. - pub fn dsdt(&self) -> AcpiResult { - self.find_table::().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::(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 { - SsdtIterator { tables_phys_ptrs: self.tables_phys_ptrs(), handler: self.handler.clone() } - } - - /// Convenience method for contructing a [`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`](Self::platform_info_in), but uses the global allocator. - #[cfg(feature = "alloc")] - pub fn platform_info(&self) -> AcpiResult> { - PlatformInfo::new(self) - } - - /// Convenience method for contructing a [`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(&self, allocator: A) -> AcpiResult> - 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, 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::(), - length: length - mem::size_of::() as u32, - } - } -} - -/// ### Safety -/// -/// Caller must ensure the provided address is valid for being read as an `SdtHeader`. -unsafe fn read_table( - handler: H, - address: usize, -) -> AcpiResult> { - // 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::(address, mem::size_of::()) }; - - 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 Iterator for SsdtIterator<'_, H> -where - H: AcpiHandler, -{ - type Item = AmlTable; - - fn next(&mut self) -> Option { - #[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 - } - } - }) - } -} - -pub struct SdtHeaderIterator<'t, H> -where - H: AcpiHandler, -{ - tables_phys_ptrs: TablesPhysPtrsIter<'t>, - handler: H, -} - -impl Iterator for SdtHeaderIterator<'_, H> -where - H: AcpiHandler, -{ - type Item = SdtHeader; - - fn next(&mut self) -> Option { - loop { - let table_phys_ptr = self.tables_phys_ptrs.next()?; - // 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 { - self.handler.map_physical_region::(table_phys_ptr as usize, mem::size_of::()) - }; - let r = header_mapping.validate(header_mapping.signature); - if r.is_err() { - log::warn!("Found invalid SDT at physical address {:p}: {:?}", table_phys_ptr, r); - continue; - } - return Some(*header_mapping); - } - } -} diff --git a/acpi/src/managed_slice.rs b/acpi/src/managed_slice.rs deleted file mode 100644 index d6a8a287..00000000 --- a/acpi/src/managed_slice.rs +++ /dev/null @@ -1,83 +0,0 @@ -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 ManagedSlice<'_, 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 { - 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), - } - } -} - -#[cfg(feature = "alloc")] -impl ManagedSlice<'_, T, alloc::alloc::Global> { - pub fn new(len: usize) -> AcpiResult { - Self::new_in(len, alloc::alloc::Global) - } -} - -impl Drop for ManagedSlice<'_, T, A> -where - A: Allocator, -{ - fn drop(&mut self) { - 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 core::ops::Deref for ManagedSlice<'_, T, A> -where - A: Allocator, -{ - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.slice - } -} - -impl core::ops::DerefMut for ManagedSlice<'_, T, A> -where - A: Allocator, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.slice - } -} - -impl Clone for ManagedSlice<'_, T, A> { - fn clone(&self) -> Self { - let mut new_managed_slice = ManagedSlice::new_in(self.len(), self.allocator.clone()).unwrap(); - new_managed_slice.clone_from_slice(self); - new_managed_slice - } -} diff --git a/acpi/src/mcfg.rs b/acpi/src/mcfg.rs deleted file mode 100644 index c09d44f0..00000000 --- a/acpi/src/mcfg.rs +++ /dev/null @@ -1,149 +0,0 @@ -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(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 - A: core::alloc::Allocator, -{ - pub fn new_in(tables: &crate::AcpiTables, allocator: A) -> crate::AcpiResult> - where - H: crate::AcpiHandler, - { - let mcfg = tables.find_table::()?; - 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 { - // 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, - 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 { - 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::(); - - // 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::(); - - unsafe { - let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::()) 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/acpi/src/platform/interrupt.rs b/acpi/src/platform/interrupt.rs deleted file mode 100644 index a83fcefe..00000000 --- a/acpi/src/platform/interrupt.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::ManagedSlice; -use core::alloc::Allocator; - -#[derive(Debug, Clone, Copy)] -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, Clone, Copy)] -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, Clone, Copy)] -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, Clone, Copy)] -pub struct NmiSource { - pub global_system_interrupt: u32, - pub polarity: Polarity, - pub trigger_mode: TriggerMode, -} - -#[derive(Debug, Clone)] -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, Clone)] -#[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/aml/Cargo.toml b/aml/Cargo.toml deleted file mode 100644 index 2b79b3dc..00000000 --- a/aml/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "aml" -version = "0.16.4" -authors = ["Isaac Woods"] -repository = "https://github.com/rust-osdev/acpi" -description = "Library for parsing AML" -categories = ["hardware-support", "no-std"] -readme = "../README.md" -license = "MIT/Apache-2.0" -edition = "2021" - -[dependencies] -log = "0.4" -bit_field = "0.10" -byteorder = { version = "1", default-features = false } -bitvec = { version = "1.0.1", default-features = false, features = ["alloc", "atomic"] } -spinning_top = "0.2.4" diff --git a/aml/fuzz/.gitignore b/aml/fuzz/.gitignore deleted file mode 100644 index 572e03bd..00000000 --- a/aml/fuzz/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -target -corpus -artifacts diff --git a/aml/fuzz/Cargo.toml b/aml/fuzz/Cargo.toml deleted file mode 100644 index dadf5eea..00000000 --- a/aml/fuzz/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ - -[package] -name = "aml-fuzz" -version = "0.0.0" -authors = ["Automatically generated"] -publish = false -edition = "2018" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.3" -simplelog = "0.8" - -[dependencies.aml] -path = ".." - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - -[[bin]] -name = "fuzz_target_1" -path = "fuzz_targets/fuzz_target_1.rs" -test = false -doc = false diff --git a/aml/fuzz/fuzz_targets/fuzz_target_1.rs b/aml/fuzz/fuzz_targets/fuzz_target_1.rs deleted file mode 100644 index 280d9594..00000000 --- a/aml/fuzz/fuzz_targets/fuzz_target_1.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![no_main] -use libfuzzer_sys::fuzz_target; -extern crate aml; - -use std::sync::atomic::{AtomicBool, Ordering}; - -static INITIALIZED: AtomicBool = AtomicBool::new(false); - -fuzz_target!(|data: &[u8]| { - if let Ok(false) = INITIALIZED.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) { - simplelog::SimpleLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default()).unwrap(); - } - - let mut context = aml::AmlContext::new(Box::new(Handler), false, aml::DebugVerbosity::None); - let _ = context.parse_table(data); -}); - -struct Handler; - -impl aml::Handler for Handler { - fn read_u8(&self, _address: usize) -> u8 { - 0 - } - fn read_u16(&self, _address: usize) -> u16 { - 0 - } - fn read_u32(&self, _address: usize) -> u32 { - 0 - } - fn read_u64(&self, _address: usize) -> u64 { - 0 - } - - fn write_u8(&mut self, _address: usize, _value: u8) {} - fn write_u16(&mut self, _address: usize, _value: u16) {} - fn write_u32(&mut self, _address: usize, _value: u32) {} - fn write_u64(&mut self, _address: usize, _value: u64) {} - - fn read_io_u8(&self, _port: u16) -> u8 { - 0 - } - fn read_io_u16(&self, _port: u16) -> u16 { - 0 - } - fn read_io_u32(&self, _port: u16) -> u32 { - 0 - } - - fn write_io_u8(&self, _port: u16, _value: u8) {} - fn write_io_u16(&self, _port: u16, _value: u16) {} - fn write_io_u32(&self, _port: u16, _value: u32) {} - - fn read_pci_u8(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u8 { - 0 - } - fn read_pci_u16(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u16 { - 0 - } - fn read_pci_u32(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u32 { - 0 - } - fn write_pci_u8(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u8) {} - fn write_pci_u16(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u16) {} - fn write_pci_u32(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u32) {} -} diff --git a/aml/src/expression.rs b/aml/src/expression.rs deleted file mode 100644 index 5dfa1fb6..00000000 --- a/aml/src/expression.rs +++ /dev/null @@ -1,911 +0,0 @@ -use crate::{ - misc::debug_obj, - name_object::{name_string, simple_name, super_name, target}, - opcode::{self, opcode}, - parser::{choice, comment_scope, n_of, take, take_to_end_of_pkglength, try_with_context, Parser, Propagate}, - pkg_length::pkg_length, - term_object::{data_ref_object, def_cond_ref_of, term_arg}, - value::{AmlType, AmlValue, Args}, - AmlError, - DebugVerbosity, -}; -use alloc::{ - string::{String, ToString}, - sync::Arc, - vec, - vec::Vec, -}; -use core::{cmp::Ordering, mem, ops::Deref}; - -pub fn expression_opcode<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * ExpressionOpcode := DefAquire | DefAdd | DefAnd | DefBuffer | DefConcat | DefConcatRes | - * DefCondRefOf | DefCopyObject | DefDecrement | DefDerefOf | DefDivide | - * DefFindSetLeftBit | DefFindSetRightBit | DefFromBCD | DefIncrement | DefIndex | - * DefLAnd | DefLEqual | DefLGreater | DefLGreaterEqual | DefLLess | DefLLessEqual | - * DefMid | DefLNot | DefLNotEqual | DefLoad | DefLoadTable | DefLOr | DefMatch | DefMod | - * DefMultiply | DefNAnd | DefNOr | DefNot | DefObjectType | DefOr | DefPackage | - * DefVarPackage | DefRefOf | DefShiftLeft | DefShiftRight | DefSizeOf | DefStore | - * DefSubtract | DefTimer | DefToBCD | DefToBuffer | DefToDecimalString | - * DefToHexString | DefToInteger | DefToString | DefWait | DefXOr | MethodInvocation - */ - comment_scope( - DebugVerbosity::AllScopes, - "ExpressionOpcode", - choice!( - def_add(), - def_subtract(), - def_and(), - def_or(), - def_buffer(), - def_concat(), - def_concat_res(), - def_increment(), - def_decrement(), - def_l_equal(), - def_l_greater(), - def_l_greater_equal(), - def_l_less(), - def_l_less_equal(), - def_l_not_equal(), - def_l_and(), - def_l_or(), - def_l_not(), - def_mid(), - def_object_type(), - def_package(), - def_shift_left(), - def_shift_right(), - def_store(), - def_to_integer(), - def_cond_ref_of(), - def_size_of(), - method_invocation() // XXX: this must always appear last. See how we have to parse it to see why. - ), - ) -} - -pub fn def_add<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefAdd := 0x72 Operand Operand Target - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_ADD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefAdd", - term_arg().then(term_arg()).then(target()).map_with_context( - |((left_arg, right_arg), target), context| { - let left = try_with_context!(context, left_arg.as_integer(context)); - let right = try_with_context!(context, right_arg.as_integer(context)); - let result = AmlValue::Integer(left.wrapping_add(right)); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }, - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_subtract<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefSubtract := 0x74 Operand Operand Target - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_SUBTRACT_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefSubtract", - term_arg().then(term_arg()).then(target()).map_with_context( - |((left_arg, right_arg), target), context| { - let left = try_with_context!(context, left_arg.as_integer(context)); - let right = try_with_context!(context, right_arg.as_integer(context)); - let result = AmlValue::Integer(left.wrapping_sub(right)); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }, - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_and<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefAnd := 0x7b Operand Operand Target - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_AND_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefAnd", - term_arg().then(term_arg()).then(target()).map_with_context( - |((left_arg, right_arg), target), context| { - let left = try_with_context!(context, left_arg.as_integer(context)); - let right = try_with_context!(context, right_arg.as_integer(context)); - let result = AmlValue::Integer(left & right); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }, - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_or<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefOr := 0x7d Operand Operand Target - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_OR_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefOr", - term_arg().then(term_arg()).then(target()).map_with_context( - |((left_arg, right_arg), target), context| { - let left = try_with_context!(context, left_arg.as_integer(context)); - let right = try_with_context!(context, right_arg.as_integer(context)); - let result = AmlValue::Integer(left | right); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }, - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_buffer<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefBuffer := 0x11 PkgLength BufferSize ByteList - * BufferSize := TermArg => Integer - * - * XXX: The spec says that zero-length buffers (e.g. the PkgLength is 0) are illegal, but - * we've encountered them in QEMU-generated tables, so we return an empty buffer in these - * cases. - * - * Uninitialized elements are initialized to zero. - */ - opcode(opcode::DEF_BUFFER_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefBuffer", - pkg_length().then(term_arg()).feed(|(pkg_length, buffer_size)| { - take_to_end_of_pkglength(pkg_length).map_with_context(move |bytes, context| { - let buffer_size = try_with_context!(context, buffer_size.as_integer(context)) as usize; - - if buffer_size < bytes.len() { - return (Err(Propagate::Err(AmlError::MalformedBuffer)), context); - } - - let mut buffer = vec![0; buffer_size]; - buffer[0..bytes.len()].copy_from_slice(bytes); - (Ok(buffer), context) - }) - }), - )) - .map(|((), buffer)| Ok(AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(buffer))))) -} - -pub fn def_concat<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefConcat := 0x73 Data Data Target - * Data := TermArg => ComputationalData - */ - opcode(opcode::DEF_CONCAT_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefConcat", - term_arg().then(term_arg()).then(target()).map_with_context(|((left, right), target), context| { - let result = match left.as_concat_type() { - AmlValue::Integer(left) => { - let right = try_with_context!(context, right.as_integer(context)); - - let mut buffer = Vec::with_capacity(mem::size_of::() * 2); - buffer.extend_from_slice(&left.to_le_bytes()); - buffer.extend_from_slice(&right.to_le_bytes()); - - AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(buffer))) - } - AmlValue::Buffer(left) => { - let mut new: Vec = left.lock().deref().clone(); - new.extend(try_with_context!(context, right.as_buffer(context)).lock().iter()); - AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(new))) - } - AmlValue::String(left) => { - let right = match right.as_concat_type() { - AmlValue::String(right) => right, - AmlValue::Integer(_) => try_with_context!(context, right.as_string(context)), - AmlValue::Buffer(_) => try_with_context!(context, right.as_string(context)), - _ => panic!("Invalid type returned from `as_concat_type`"), - }; - AmlValue::String(left + &right) - } - _ => panic!("Invalid type returned from `as_concat_type`"), - }; - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_concat_res<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefConcatRes := 0x84 BufData BufData Target - * BufData := TermArg => Buffer - */ - opcode(opcode::DEF_CONCAT_RES_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefConcatRes", - term_arg().then(term_arg()).then(target()).map_with_context(|((left, right), target), context| { - let left = try_with_context!(context, left.as_buffer(context)); - let right = try_with_context!(context, right.as_buffer(context)); - - let left_len = left.lock().len(); - let right_len = right.lock().len(); - - if left_len == 1 || right_len == 1 { - return (Err(Propagate::Err(AmlError::ResourceDescriptorTooShort)), context); - } - - /* - * `left` and `right` are buffers of resource descriptors, which we're trying to concatenate into a - * new, single buffer containing all of the descriptors from the source buffers. We need to strip - * off the end tags (2 bytes from each buffer), and then add our own end tag. - * - * XXX: either buffer may be empty (contains no tags), and so our arithmetic has to be careful. - */ - let result = { - let mut result = - Vec::with_capacity(left_len.saturating_sub(2) + right_len.saturating_sub(2) + 2); - let left_contents = left.lock(); - let right_contents = right.lock(); - result.extend_from_slice(if left_len == 0 { &[] } else { &left_contents[..(left_len - 2)] }); - result.extend_from_slice(if right_len == 0 { - &[] - } else { - &right_contents[..(right_len - 2)] - }); - - /* - * Construct a new end tag, including a new checksum: - * | Bits | Field | Value | - * |-------------|-------------------|---------------------------| - * | 0-2 | Length - n bytes | 1 (for checksum) | - * | 3-6 | Small item type | 0x0f = end tag descriptor | - * | 7 | 0 = small item | 0 | - */ - result.push(0b01111001); - result.push( - result.iter().fold(0u8, |checksum, byte| checksum.wrapping_add(*byte)).wrapping_neg(), - ); - - AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(result))) - }; - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_increment<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefIncrement := 0x75 SuperName - */ - opcode(opcode::DEF_INCREMENT_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefIncrement", - super_name().map_with_context(|addend, context| { - let value = try_with_context!(context, context.read_target(&addend)).clone(); - let value = try_with_context!(context, value.as_integer(context)); - let new_value = AmlValue::Integer(value + 1); - try_with_context!(context, context.store(addend, new_value.clone())); - (Ok(new_value), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_decrement<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefDecrement := 0x76 SuperName - */ - opcode(opcode::DEF_DECREMENT_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefDecrement", - super_name().map_with_context(|minuend, context| { - let value = try_with_context!(context, context.read_target(&minuend)).clone(); - let value = try_with_context!(context, value.as_integer(context)); - let new_value = AmlValue::Integer(value - 1); - try_with_context!(context, context.store(minuend, new_value.clone())); - (Ok(new_value), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_and<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLAnd := 0x90 Operand Operand - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_L_AND_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLOr", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let left = try_with_context!(context, left_arg.as_bool(context)); - let right = try_with_context!(context, right_arg.as_bool(context)); - (Ok(AmlValue::Boolean(left && right)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_or<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLOr := 0x91 Operand Operand - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_L_OR_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLOr", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let left = try_with_context!(context, left_arg.as_bool(context)); - let right = try_with_context!(context, right_arg.as_bool(context)); - (Ok(AmlValue::Boolean(left || right)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_not<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLNot := 0x92 Operand - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_L_NOT_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLNot", - term_arg().map_with_context(|arg, context| { - let operand = try_with_context!(context, arg.as_bool(context)); - (Ok(AmlValue::Boolean(!operand)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_equal<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLEqual := 0x93 Operand Operand - * Operand := TermArg => Integer - */ - opcode(opcode::DEF_L_EQUAL_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLEqual", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord == Ordering::Equal)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_greater<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLGreater := 0x94 Operand Operand - */ - opcode(opcode::DEF_L_GREATER_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLGreater", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord == Ordering::Greater)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_greater_equal<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLGreaterEqual := LNotOp(0x92) LLessOp(0x95) Operand Operand - */ - opcode(opcode::DEF_L_NOT_OP) - .then(opcode(opcode::DEF_L_LESS_OP)) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLGreaterEqual", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord != Ordering::Less)), context) - }), - )) - .map(|(((), ()), result)| Ok(result)) -} - -fn def_l_less<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLLess := 0x95 Operand Operand - */ - opcode(opcode::DEF_L_LESS_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLLess", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord == Ordering::Less)), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -fn def_l_less_equal<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLLessEqual := LNotOp(0x92) LGreaterOp(0x94) Operand Operand - */ - opcode(opcode::DEF_L_NOT_OP) - .then(opcode(opcode::DEF_L_GREATER_OP)) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLLessEqual", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord != Ordering::Greater)), context) - }), - )) - .map(|(((), ()), result)| Ok(result)) -} - -fn def_l_not_equal<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefLNotEqual := LNotOp(0x92) LEqualOp(0x93) Operand Operand - */ - opcode(opcode::DEF_L_NOT_OP) - .then(opcode(opcode::DEF_L_EQUAL_OP)) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefLNotEqual", - term_arg().then(term_arg()).map_with_context(|(left_arg, right_arg), context| { - let ord = try_with_context!(context, left_arg.cmp(right_arg, context)); - (Ok(AmlValue::Boolean(ord != Ordering::Equal)), context) - }), - )) - .map(|(((), ()), result)| Ok(result)) -} - -fn def_mid<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefMid := 0x9e MidObj TermArg TermArg Target - * MidObj := TermArg => Buffer | String - */ - opcode(opcode::DEF_MID_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefMid", - term_arg().then(term_arg()).then(term_arg()).then(target()).map_with_context( - |(((source, index), length), target), context| { - let index = try_with_context!(context, index.as_integer(context)) as usize; - let length = try_with_context!(context, length.as_integer(context)) as usize; - - let result = try_with_context!( - context, - match source { - AmlValue::Buffer(bytes) => { - let guard = bytes.lock(); - if index >= guard.len() { - Ok(AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(vec![])))) - } else if (index + length) >= guard.len() { - Ok(AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new( - guard[index..].to_vec(), - )))) - } else { - Ok(AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new( - guard[index..(index + length)].to_vec(), - )))) - } - } - /* - * XXX: The spec conflates characters and bytes, so we effectively ignore unicode and do - * this bytewise, to hopefully match other implementations. - */ - AmlValue::String(string) => { - if index >= string.len() { - Ok(AmlValue::String(String::new())) - } else if (index + length) >= string.len() { - Ok(AmlValue::String(string[index..].to_string())) - } else { - Ok(AmlValue::String(string[index..(index + length)].to_string())) - } - } - _ => Err(AmlError::TypeCannotBeSliced(source.type_of())), - } - ); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }, - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_object_type<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefObjectType := 0x8e - * - * Returns an integer representing the type of an AML object. If executed on an object that is a reference to a - * value (e.g. produced by `Alias`, `RefOf`, or `Index`), the type of the base object is returned. For typeless - * objects, such as scopes, a type of `0 - Uninitialized` is returned. - * - * 0 = Uninitialized - * 1 = Integer - * 2 = String - * 3 = Buffer - * 4 = Package - * 5 = Field Unit - * 6 = Device - * 7 = Event - * 8 = Method - * 9 = Mutex - * 10 = Operation Region - * 11 = Power Resource - * 12 = Processor - * 13 = Thermal Zone - * 14 = Buffer Field - * 15 = Reserved - * 16 = Debug Object - * >16 = *Reserved* - */ - // TODO: this doesn't correctly handle taking the type of a namespace node (e.g. `\_SB`), but I'm not sure any - // other implementations do either? - opcode(opcode::DEF_OBJECT_TYPE_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefObjectType", - choice!( - simple_name().map_with_context(|target, context| { - let value = try_with_context!(context, context.read_target(&target)); - let typ = match value.type_of() { - AmlType::Uninitialized => 0, - AmlType::Integer => 1, - AmlType::String => 2, - AmlType::Buffer => 3, - AmlType::RawDataBuffer => 3, // TODO: not sure if this is correct - AmlType::Package => 4, - AmlType::FieldUnit => 5, - AmlType::Device => 6, - AmlType::Event => 7, - AmlType::Method => 8, - AmlType::Mutex => 9, - AmlType::OpRegion => 10, - AmlType::PowerResource => 11, - AmlType::Processor => 12, - AmlType::ThermalZone => 13, - AmlType::BufferField => 14, - AmlType::DebugObject => 16, - - // TODO: what to do with this? - AmlType::DdbHandle => 0, - AmlType::ObjReference => todo!(), - }; - - (Ok(AmlValue::Integer(typ)), context) - }), - debug_obj().map(|()| Ok(AmlValue::Integer(16))) - ), - )) - .map(|((), result)| Ok(result)) -} - -pub fn def_package<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefPackage := 0x12 PkgLength NumElements PackageElementList - * NumElements := ByteData - * PackageElementList := Nothing | - * PackageElement := DataRefObject | NameString - */ - opcode(opcode::DEF_PACKAGE_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefPackage", - pkg_length().then(take()).feed(|(pkg_length, num_elements)| { - move |mut input, mut context| { - let mut package_contents = Vec::new(); - - while pkg_length.still_parsing(input) { - let (new_input, new_context, value) = package_element().parse(input, context)?; - input = new_input; - context = new_context; - - package_contents.push(value); - } - - // ACPI6.2, §19.6.101 specifies that if NumElements is present and is greater - // than the number of entries in the PackageList, the default entry of type - // Uninitialized is used - if package_contents.len() > num_elements as usize { - return Err((input, context, Propagate::Err(AmlError::MalformedPackage))); - } - - package_contents.resize(num_elements as usize, AmlValue::Uninitialized); - - Ok((input, context, AmlValue::Package(package_contents))) - } - }), - )) - .map(|((), package)| Ok(package)) -} - -pub fn package_element<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - choice!(data_ref_object(), name_string().map(|string| Ok(AmlValue::String(string.as_string())))) -} - -fn def_shift_left<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefShiftLeft := 0x79 Operand ShiftCount Target - * Operand := TermArg => Integer - * ShiftCount := TermArg => Integer - */ - opcode(opcode::DEF_SHIFT_LEFT) - .then(comment_scope(DebugVerbosity::Scopes, "DefShiftLeft", term_arg().then(term_arg()).then(target()))) - .map_with_context(|((), ((operand, shift_count), target)), context| { - let operand = try_with_context!(context, operand.as_integer(context)); - let shift_count = try_with_context!(context, shift_count.as_integer(context)); - let shift_count = - try_with_context!(context, shift_count.try_into().map_err(|_| AmlError::InvalidShiftLeft)); - - let result = AmlValue::Integer(try_with_context!( - context, - operand.checked_shl(shift_count).ok_or(AmlError::InvalidShiftLeft) - )); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }) -} - -fn def_shift_right<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefShiftRight := 0x7a Operand ShiftCount Target - * Operand := TermArg => Integer - * ShiftCount := TermArg => Integer - */ - opcode(opcode::DEF_SHIFT_RIGHT) - .then(comment_scope(DebugVerbosity::Scopes, "DefShiftRight", term_arg().then(term_arg()).then(target()))) - .map_with_context(|((), ((operand, shift_count), target)), context| { - let operand = try_with_context!(context, operand.as_integer(context)); - let shift_count = try_with_context!(context, shift_count.as_integer(context)); - let shift_count = - try_with_context!(context, shift_count.try_into().map_err(|_| AmlError::InvalidShiftRight)); - - let result = AmlValue::Integer(try_with_context!( - context, - operand.checked_shr(shift_count).ok_or(AmlError::InvalidShiftRight) - )); - - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }) -} - -fn def_store<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefStore := 0x70 TermArg SuperName - * - * Implicit conversion is only applied when the destination target is a `Name` - not when we - * are storing into a method local or argument (these stores are semantically identical to - * CopyObject). We must also make sure to return a copy of the data that is in the destination - * after the store (as opposed to the data we think we put into it), because some stores can - * alter the data during the store. - */ - opcode(opcode::DEF_STORE_OP) - .then(comment_scope(DebugVerbosity::Scopes, "DefStore", term_arg().then(super_name()))) - .map_with_context(|((), (value, target)), context| { - (Ok(try_with_context!(context, context.store(target, value))), context) - }) -} - -fn def_to_integer<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefToInteger := 0x99 Operand Target - * Operand := TermArg - */ - opcode(opcode::DEF_TO_INTEGER_OP) - .then(comment_scope(DebugVerbosity::AllScopes, "DefToInteger", term_arg().then(target()))) - .map_with_context(|((), (operand, target)), context| { - let result = match operand { - AmlValue::Integer(value) => AmlValue::Integer(value), - AmlValue::Buffer(data) => { - AmlValue::Integer(try_with_context!(context, AmlValue::Buffer(data).as_integer(context))) - } - AmlValue::Field { .. } => { - AmlValue::Integer(try_with_context!(context, operand.as_integer(context))) - } - AmlValue::String(string) => AmlValue::Integer(try_with_context!( - context, - if string.starts_with("0x") { - u64::from_str_radix(string.trim_start_matches("0x"), 16) - } else { - string.parse::() - } - .map_err(|_| AmlError::IncompatibleValueConversion { - current: AmlType::String, - target: AmlType::Integer, - }) - )), - _ => { - return ( - Err(Propagate::Err(AmlError::IncompatibleValueConversion { - current: operand.type_of(), - target: AmlType::Integer, - })), - context, - ) - } - }; - try_with_context!(context, context.store(target, result.clone())); - (Ok(result), context) - }) -} - -fn def_size_of<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * SizeOf := 0x87 SuperName - */ - opcode(opcode::DEF_SIZE_OF_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefSizeOf", - super_name().map_with_context(|target, context| { - let value = try_with_context!(context, context.read_target(&target)); - let size_of = try_with_context!(context, value.size_of()); - (Ok(AmlValue::Integer(size_of)), context) - }), - )) - .map(|((), value)| Ok(value)) -} - -fn method_invocation<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * MethodInvocation := NameString TermArgList - * - * MethodInvocation is the worst of the AML structures, because you're meant to figure out how much you're - * meant to parse using the name of the method (by knowing from its definition how how many arguments it - * takes). However, the definition of a method can in theory appear after an invocation of that method, and - * so parsing them properly can be very difficult. - * NOTE: We don't support the case of the definition appearing after the invocation. - */ - comment_scope( - DebugVerbosity::Scopes, - "MethodInvocation", - name_string() - .map_with_context(move |name, context| { - let (full_path, handle) = - try_with_context!(context, context.namespace.search(&name, &context.current_scope)).clone(); - - /* - * `None` if the path is not a method and so doesn't have arguments, or `Some(the number of - * arguments to parse)` if it's a method. - */ - let num_args = if let AmlValue::Method { flags, .. } = - try_with_context!(context, context.namespace.get(handle)) - { - Some(flags.arg_count()) - } else { - None - }; - (Ok((full_path, num_args)), context) - }) - .feed(|(path, num_args)| { - n_of(term_arg(), num_args.unwrap_or(0) as usize).map_with_context(move |arg_list, context| { - if num_args.is_some() { - let args = try_with_context!(context, Args::from_list(arg_list)); - let result = context.invoke_method(&path, args); - (Ok(try_with_context!(context, result)), context) - } else { - (Ok(try_with_context!(context, context.namespace.get_by_path(&path)).clone()), context) - } - }) - }), - ) -} diff --git a/aml/src/lib.rs b/aml/src/lib.rs deleted file mode 100644 index 90027e3b..00000000 --- a/aml/src/lib.rs +++ /dev/null @@ -1,683 +0,0 @@ -//! `aml` is a pure-Rust AML (ACPI Machine Language) parser, used for parsing the DSDT and -//! SSDT tables from ACPI. This crate can be used by kernels to gather information about the -//! hardware, and invoke control methods to query and change the state of devices in a -//! hardware-independent way. -//! -//! ### Using the library -//! To use the library, you should create an `AmlContext` using `AmlContext::new()`, and then pass it tables -//! containing AML (probably from the `acpi` crate), which you've mapped into the virtual address space. This will -//! parse the table, populating the namespace with objects encoded by the AML. After this, you may unmap the memory -//! the table was mapped into - all the information needed will be extracted and allocated on the heap. -//! -//! You can then access specific objects by name like so: e.g. -//! ```ignore -//! let my_aml_value = aml_context.lookup(&AmlName::from_str("\\_SB.PCI0.S08._ADR").unwrap()); -//! ``` -//! -//! And invoke control methods like this: e.g. -//! ```ignore -//! let result = aml_context.invoke_method(&AmlName::from_str("\\_SB.HPET._CRS").unwrap(), value::Args::EMPTY); -//! ``` -//! -//! ### About the parser -//! The parser is written using a set of custom parser combinators - the code can be confusing on -//! first reading, but provides an extensible and type-safe way to write parsers. For an easy -//! introduction to parser combinators and the foundations used for this library, I suggest reading -//! [Bodil's fantastic blog post](https://bodil.lol/parser-combinators/). -//! -//! The actual combinators can be found in `parser.rs`. Various tricks are used to provide a nice -//! API and work around limitations in the type system, such as the concrete types like -//! `MapWithContext`. -//! -//! The actual parsers are then grouped into categories based loosely on the AML grammar sections in -//! the ACPI spec. Most are written in terms of combinators, but some have to be written in a more -//! imperitive style, either because they're clearer, or because we haven't yet found good -//! combinator patterns to express the parse. - -#![no_std] -#![feature(decl_macro)] - -extern crate alloc; - -#[cfg(test)] -extern crate std; - -#[cfg(test)] -mod test_utils; - -pub(crate) mod expression; -pub(crate) mod misc; -pub(crate) mod name_object; -pub(crate) mod namespace; -pub(crate) mod opcode; -pub mod opregion; -pub(crate) mod parser; -pub mod pci_routing; -pub(crate) mod pkg_length; -pub mod resource; -pub(crate) mod statement; -pub(crate) mod term_object; -pub mod value; - -pub use crate::{namespace::*, value::AmlValue}; - -use alloc::{ - boxed::Box, - format, - string::{String, ToString}, -}; -use core::{mem, str::FromStr}; -use log::{error, warn}; -use misc::{ArgNum, LocalNum}; -use name_object::Target; -use parser::{Parser, Propagate}; -use pkg_length::PkgLength; -use term_object::term_list; -use value::{AmlType, Args}; - -/// AML has a `RevisionOp` operator that returns the "AML interpreter revision". It's not clear -/// what this is actually used for, but this is ours. -pub const AML_INTERPRETER_REVISION: u64 = 0; - -/// Describes how much debug information the parser should emit. Set the "maximum" expected verbosity in -/// the context's `debug_verbosity` - everything will be printed that is less or equal in 'verbosity'. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum DebugVerbosity { - /// Print no debug information - None, - /// Print heads and tails when entering and leaving scopes of major objects, but not more minor ones. - Scopes, - /// Print heads and tails when entering and leaving scopes of all objects. - AllScopes, - /// Print heads and tails of all objects, and extra debug information as it's parsed. - All, -} - -#[derive(Debug)] -struct MethodContext { - /// AML local variables. These are used when we invoke a control method. A `None` value represents a null AML - /// object. - locals: [Option; 8], - /// If we're currently invoking a control method, this stores the arguments that were passed to - /// it. It's `None` if we aren't invoking a method. - args: Args, -} - -impl MethodContext { - fn new(args: Args) -> MethodContext { - // XXX: this is required because `Option` is not `Copy`, so it can't be used to initialize an - // array, but consts can :( - const NONE_BUT_CONST: Option = None; - - MethodContext { locals: [NONE_BUT_CONST; 8], args } - } -} - -pub struct AmlContext { - /// The `Handler` passed from the library user. This is stored as a boxed trait object simply to avoid having - /// to add a lifetime and type parameter to `AmlContext`, as they would massively complicate the parser types. - handler: Box, - - pub namespace: Namespace, - method_context: Option, - - /* - * These track the state of the context while it's parsing an AML table. - */ - current_scope: AmlName, - scope_indent: usize, - debug_verbosity: DebugVerbosity, -} - -impl AmlContext { - /// Creates a new `AmlContext` - the central type in managing the AML tables. Only one of these should be - /// created, and it should be passed the DSDT and all SSDTs defined by the hardware. - pub fn new(handler: Box, debug_verbosity: DebugVerbosity) -> AmlContext { - let mut context = AmlContext { - handler, - namespace: Namespace::new(), - method_context: None, - - current_scope: AmlName::root(), - scope_indent: 0, - debug_verbosity, - }; - - context.add_predefined_objects(); - context - } - - pub fn parse_table(&mut self, stream: &[u8]) -> Result<(), AmlError> { - fn stream_context(stream: &[u8], err_buf: &[u8]) -> String { - const BEFORE_LEN: usize = 4; - const ABBREV_LEN: usize = 4; - let abbreviated = if err_buf.len() >= ABBREV_LEN { &err_buf[..ABBREV_LEN] } else { err_buf }; - - if let Some(position) = (err_buf.as_ptr() as usize).checked_sub(stream.as_ptr() as usize) { - if position <= stream.len() { - let before = if position > BEFORE_LEN { - &stream[position - BEFORE_LEN..position] - } else { - &stream[..position] - }; - return format!( - "position {:#X}: preceding {:X?}, buf {:X?}", - position + 36, - before, - abbreviated - ); - } - } - format!("buf {:X?}", abbreviated) - } - - if stream.is_empty() { - return Err(AmlError::UnexpectedEndOfStream); - } - - let table_length = PkgLength::from_raw_length(stream, stream.len() as u32).unwrap(); - match term_object::term_list(table_length).parse(stream, self) { - Ok(_) => Ok(()), - Err((err_buf, _, Propagate::Err(err))) => { - error!("Failed to parse AML stream. Err = {:?}, {}", err, stream_context(stream, err_buf)); - Err(err) - } - Err((_, _, other)) => { - error!("AML table evaluated to unexpected result: {:?}", other); - Err(AmlError::MalformedStream) - } - } - } - - // TODO: docs - pub fn invoke_method(&mut self, path: &AmlName, args: Args) -> Result { - use value::MethodCode; - - match self.namespace.get_by_path(path)?.clone() { - // TODO: respect the method's flags - AmlValue::Method { flags: _, code } => { - /* - * First, set up the state we expect to enter the method with, but clearing local - * variables to "null" and setting the arguments. Save the current method state and scope, so if we're - * already executing another control method, we resume into it correctly. - */ - let old_context = mem::replace(&mut self.method_context, Some(MethodContext::new(args))); - let old_scope = mem::replace(&mut self.current_scope, path.clone()); - - /* - * Create a namespace level to store local objects created by the invocation. - */ - self.namespace.add_level(path.clone(), LevelType::MethodLocals)?; - - let return_value = match code { - MethodCode::Aml(ref code) => { - match term_list(PkgLength::from_raw_length(code, code.len() as u32).unwrap()) - .parse(code, self) - { - // If the method doesn't return a value, we implicitly return `0` - Ok(_) => Ok(AmlValue::Integer(0)), - Err((_, _, Propagate::Return(result))) => Ok(result), - Err((_, _, Propagate::Break)) => Err(AmlError::BreakInInvalidPosition), - Err((_, _, Propagate::Continue)) => Err(AmlError::ContinueInInvalidPosition), - Err((_, _, Propagate::Err(err))) => { - error!("Failed to execute control method: {:?}", err); - Err(err) - } - } - } - - MethodCode::Native(ref method) => match (method)(self) { - Ok(result) => Ok(result), - Err(err) => { - error!("Failed to execute control method: {:?}", err); - Err(err) - } - }, - }; - - /* - * Locally-created objects should be destroyed on method exit (see §5.5.2.3 of the ACPI spec). We do - * this by simply removing the method's local object layer. - */ - // TODO: this should also remove objects created by the method outside the method's scope, if they - // weren't statically created. This is harder. - self.namespace.remove_level(path.clone())?; - - /* - * Restore the old state. - */ - self.method_context = old_context; - self.current_scope = old_scope; - - return_value - } - - /* - * AML can encode methods that don't require any computation simply as the value that would otherwise be - * returned (e.g. a `_STA` object simply being an `AmlValue::Integer`, instead of a method that just - * returns an integer). - */ - value => Ok(value), - } - } - - // TODO: docs - pub fn initialize_objects(&mut self) -> Result<(), AmlError> { - use name_object::NameSeg; - use value::StatusObject; - - /* - * If `\_SB._INI` exists, we unconditionally execute it at the beginning of device initialization. - */ - match self.invoke_method(&AmlName::from_str("\\_SB._INI").unwrap(), Args::default()) { - Ok(_) => (), - Err(AmlError::ValueDoesNotExist(_)) => (), - Err(err) => return Err(err), - } - - /* - * Next, we traverse the namespace, looking for devices. - * - * XXX: we clone the namespace here, which obviously drives up heap burden quite a bit (not as much as you - * might first expect though - we're only duplicating the level data structure, not all the objects). The - * issue here is that we need to access the namespace during traversal (e.g. to invoke a method), which the - * borrow checker really doesn't like. A better solution could be a iterator-like traversal system that - * keeps track of the namespace without keeping it borrowed. This works for now. - */ - self.namespace.clone().traverse(|path, level: &NamespaceLevel| match level.typ { - LevelType::Device => { - let status = if level.values.contains_key(&NameSeg::from_str("_STA").unwrap()) { - self.invoke_method(&AmlName::from_str("_STA").unwrap().resolve(path)?, Args::default())? - .as_status()? - } else { - StatusObject::default() - }; - - /* - * If the device is present and has an `_INI` method, invoke it. - */ - if status.present && level.values.contains_key(&NameSeg::from_str("_INI").unwrap()) { - log::info!("Invoking _INI at level: {}", path); - self.invoke_method(&AmlName::from_str("_INI").unwrap().resolve(path)?, Args::default())?; - } - - /* - * We traverse the children of this device if it's present, or isn't present but is functional. - */ - Ok(status.present || status.functional) - } - - LevelType::Scope => Ok(true), - - // TODO: can any of these contain devices? - LevelType::Processor => Ok(false), - LevelType::PowerResource => Ok(false), - LevelType::ThermalZone => Ok(false), - LevelType::MethodLocals => Ok(false), - })?; - - Ok(()) - } - - pub(crate) fn read_target(&self, target: &Target) -> Result<&AmlValue, AmlError> { - match target { - Target::Null => todo!(), - Target::Name(name) => { - let (_, handle) = self.namespace.search(name, &self.current_scope)?; - self.namespace.get(handle) - } - Target::Debug => todo!(), - Target::Arg(arg) => self.current_arg(*arg), - Target::Local(local) => self.local(*local), - } - } - - /// Get the value of an argument by its argument number. Can only be executed from inside a control method. - pub(crate) fn current_arg(&self, arg: ArgNum) -> Result<&AmlValue, AmlError> { - self.method_context.as_ref().ok_or(AmlError::NotExecutingControlMethod)?.args.arg(arg) - } - - /// Get the current value of a local by its local number. Can only be executed from inside a control method. - pub(crate) fn local(&self, local: LocalNum) -> Result<&AmlValue, AmlError> { - if self.method_context.is_none() { - return Err(AmlError::NotExecutingControlMethod); - } - if local > 7 { - return Err(AmlError::InvalidLocalAccess(local)); - } - - self.method_context.as_ref().unwrap().locals[local as usize] - .as_ref() - .ok_or(AmlError::InvalidLocalAccess(local)) - } - - /// Perform a store into a `Target`, according to the rules specified by §19.3.5.8. This returns a value read - /// out of the target, if neccessary, as values can be altered during a store in some circumstances. When - /// required, this also performs required implicit conversions, otherwise stores are semantically equivalent to - /// a `CopyObject`. - pub(crate) fn store(&mut self, target: Target, value: AmlValue) -> Result { - match target { - Target::Name(ref path) => { - let (_, handle) = self.namespace.search(path, &self.current_scope)?; - - match self.namespace.get(handle).unwrap().type_of() { - AmlType::FieldUnit => { - let mut field = self.namespace.get(handle).unwrap().clone(); - field.write_field(value, self)?; - field.read_field(self) - } - AmlType::BufferField => { - let mut buffer_field = self.namespace.get(handle).unwrap().clone(); - buffer_field.write_buffer_field(value.clone(), self)?; - Ok(value) - } - typ => { - *self.namespace.get_mut(handle)? = value.as_type(typ, self)?; - Ok(self.namespace.get(handle)?.clone()) - } - } - } - - Target::Debug => { - // TODO - unimplemented!() - } - - Target::Arg(arg_num) => { - if self.method_context.is_none() { - return Err(AmlError::NotExecutingControlMethod); - } - - /* - * Stores into `Arg` objects are simply copied with no conversion applied, unless the `Arg` - * contains an Object Reference, in which case an automatic de-reference occurs and the object is - * copied to the target of the Object Reference, instead of overwriting the `Arg.` - */ - // TODO: implement behaviour for object references - self.method_context.as_mut().unwrap().args.store_arg(arg_num, value.clone())?; - Ok(value) - } - - Target::Local(local_num) => { - if self.method_context.is_none() { - return Err(AmlError::NotExecutingControlMethod); - } - - /* - * Stores into `Local` objects are always simply copied into the destination with no conversion - * applied, even if it contains an Object Reference. - */ - self.method_context.as_mut().unwrap().locals[local_num as usize] = Some(value.clone()); - Ok(value) - } - - Target::Null => Ok(value), - } - } - - fn add_predefined_objects(&mut self) { - /* - * These are the scopes predefined by the spec. Some tables will try to access them without defining them - * themselves, and so we have to pre-create them. - */ - self.namespace.add_level(AmlName::from_str("\\_GPE").unwrap(), LevelType::Scope).unwrap(); - self.namespace.add_level(AmlName::from_str("\\_SB").unwrap(), LevelType::Scope).unwrap(); - self.namespace.add_level(AmlName::from_str("\\_SI").unwrap(), LevelType::Scope).unwrap(); - self.namespace.add_level(AmlName::from_str("\\_PR").unwrap(), LevelType::Scope).unwrap(); - self.namespace.add_level(AmlName::from_str("\\_TZ").unwrap(), LevelType::Scope).unwrap(); - - /* - * In the dark ages of ACPI 1.0, before `\_OSI`, `\_OS` was used to communicate to the firmware which OS - * was running. This was predictably not very good, and so was replaced in ACPI 3.0 with `_OSI`, which - * allows support for individual capabilities to be queried. `_OS` should not be used by modern firmwares, - * but to avoid problems we follow Linux in returning `"Microsoft Windows NT"`. - * - * See https://www.kernel.org/doc/html/latest/firmware-guide/acpi/osi.html for more information. - */ - self.namespace - .add_value(AmlName::from_str("\\_OS").unwrap(), AmlValue::String("Microsoft Windows NT".to_string())) - .unwrap(); - - /* - * `\_OSI` was introduced by ACPI 3.0 to improve the situation created by `\_OS`. Unfortunately, exactly - * the same problem was immediately repeated by introducing capabilities reflecting that an ACPI - * implementation is exactly the same as a particular version of Windows' (e.g. firmwares will call - * `\_OSI("Windows 2001")`). - * - * We basically follow suit with whatever Linux does, as this will hopefully minimise breakage: - * - We always claim `Windows *` compatability - * - We answer 'yes' to `_OSI("Darwin") - * - We answer 'no' to `_OSI("Linux")`, and report that the tables are doing the wrong thing - */ - self.namespace - .add_value( - AmlName::from_str("\\_OSI").unwrap(), - AmlValue::native_method(1, false, 0, |context| { - let value = context.current_arg(0)?.clone(); - Ok( - if match value.as_string(context)?.as_str() { - "Windows 2000" => true, // 2000 - "Windows 2001" => true, // XP - "Windows 2001 SP1" => true, // XP SP1 - "Windows 2001 SP2" => true, // XP SP2 - "Windows 2001.1" => true, // Server 2003 - "Windows 2001.1 SP1" => true, // Server 2003 SP1 - "Windows 2006" => true, // Vista - "Windows 2006 SP1" => true, // Vista SP1 - "Windows 2006 SP2" => true, // Vista SP2 - "Windows 2006.1" => true, // Server 2008 - "Windows 2009" => true, // 7 and Server 2008 R2 - "Windows 2012" => true, // 8 and Server 2012 - "Windows 2013" => true, // 8.1 and Server 2012 R2 - "Windows 2015" => true, // 10 - "Windows 2016" => true, // 10 version 1607 - "Windows 2017" => true, // 10 version 1703 - "Windows 2017.2" => true, // 10 version 1709 - "Windows 2018" => true, // 10 version 1803 - "Windows 2018.2" => true, // 10 version 1809 - "Windows 2019" => true, // 10 version 1903 - - "Darwin" => true, - - "Linux" => { - // TODO: should we allow users to specify that this should be true? Linux has a - // command line option for this. - warn!("ACPI evaluated `_OSI(\"Linux\")`. This is a bug. Reporting no support."); - false - } - - "Extended Address Space Descriptor" => true, - // TODO: support module devices - "Module Device" => false, - "3.0 Thermal Model" => true, - "3.0 _SCP Extensions" => true, - // TODO: support processor aggregator devices - "Processor Aggregator Device" => false, - - _ => false, - } { - AmlValue::ones() - } else { - AmlValue::zero() - }, - ) - }), - ) - .unwrap(); - - /* - * `\_REV` evaluates to the version of the ACPI specification supported by this interpreter. Linux did this - * correctly until 2015, but firmwares misused this to detect Linux (as even modern versions of Windows - * return `2`), and so they switched to just returning `2` (as we'll also do). `_REV` should be considered - * useless and deprecated (this is mirrored in newer specs, which claim `2` means "ACPI 2 or greater"). - */ - self.namespace.add_value(AmlName::from_str("\\_REV").unwrap(), AmlValue::Integer(2)).unwrap(); - } -} - -/// Trait type used by [`AmlContext`] to handle reading and writing to various types of memory in the system. -pub trait Handler: Send + Sync { - fn read_u8(&self, address: usize) -> u8; - fn read_u16(&self, address: usize) -> u16; - fn read_u32(&self, address: usize) -> u32; - fn read_u64(&self, address: usize) -> u64; - - fn write_u8(&mut self, address: usize, value: u8); - fn write_u16(&mut self, address: usize, value: u16); - fn write_u32(&mut self, address: usize, value: u32); - fn write_u64(&mut self, address: usize, value: u64); - - fn read_io_u8(&self, port: u16) -> u8; - fn read_io_u16(&self, port: u16) -> u16; - fn read_io_u32(&self, port: u16) -> u32; - - fn write_io_u8(&self, port: u16, value: u8); - fn write_io_u16(&self, port: u16, value: u16); - fn write_io_u32(&self, port: u16, value: u32); - - fn read_pci_u8(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16) -> u8; - fn read_pci_u16(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16) -> u16; - fn read_pci_u32(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16) -> u32; - - fn write_pci_u8(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16, value: u8); - fn write_pci_u16(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16, value: u16); - fn write_pci_u32(&self, segment: u16, bus: u8, device: u8, function: u8, offset: u16, value: u32); - - /// Stall for at least the given number of **microseconds**. An implementation should not relinquish control of - /// the processor during the stall, and for this reason, firmwares should not stall for periods of more than - /// 100 microseconds. - fn stall(&self, microseconds: u64); - - /// Sleep for at least the given number of **milliseconds**. An implementation may round to the closest sleep - /// time supported, and should relinquish the processor. - fn sleep(&self, milliseconds: u64); - - fn handle_fatal_error(&self, fatal_type: u8, fatal_code: u32, fatal_arg: u64) { - panic!("Fatal error while executing AML (encountered DefFatal op). fatal_type = {:?}, fatal_code = {:?}, fatal_arg = {:?}", fatal_type, fatal_code, fatal_arg); - } -} - -/// Used when an [`AmlContext`] encounters an error. -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum AmlError { - /* - * Errors produced parsing the AML stream. - */ - UnexpectedEndOfStream, - UnexpectedByte(u8), - /// Produced when the stream evaluates to something other than nothing or an error. - MalformedStream, - InvalidNameSeg, - InvalidPkgLength, - /// Invalid PkgLength relative to an OperationRegion - InvalidRegionPkgLength { - region_bit_length: u64, - raw_length: u32, - }, - InvalidFieldFlags, - UnterminatedStringConstant, - InvalidStringConstant, - InvalidRegionSpace(u8), - /// Produced when a `DefPackage` contains a different number of elements to the package's length. - MalformedPackage, - /// Produced when a `DefBuffer` contains more bytes that its size. - MalformedBuffer, - /// Emitted by a parser when it's clear that the stream doesn't encode the object parsed by - /// that parser (e.g. the wrong opcode starts the stream). This is handled specially by some - /// parsers such as `or` and `choice!`. - WrongParser, - /// Returned when a `DefFatal` op is encountered. This is separately reported using [`Handler::handle_fatal_error`]. - FatalError, - - /* - * Errors produced manipulating AML names. - */ - EmptyNamesAreInvalid, - /// Produced when trying to normalize a path that does not point to a valid level of the - /// namespace. E.g. `\_SB.^^PCI0` goes above the root of the namespace. The contained value is the name that - /// normalization was attempted upon. - InvalidNormalizedName(AmlName), - RootHasNoParent, - - /* - * Errors produced working with the namespace. - */ - /// Produced when a sub-level or value is added to a level that has not yet been added to the namespace. The - /// `AmlName` is the name of the entire sub-level/value. - LevelDoesNotExist(AmlName), - ValueDoesNotExist(AmlName), - /// Produced when two values with the same name are added to the namespace. - NameCollision(AmlName), - TriedToRemoveRootNamespace, - - /* - * Errors produced executing control methods. - */ - /// Produced when AML tries to do something only possible in a control method (e.g. read from an argument) - /// when there's no control method executing. - NotExecutingControlMethod, - /// Produced when a method accesses an argument it does not have (e.g. a method that takes 2 - /// arguments accesses `Arg4`). The inner value is the number of the argument accessed. - InvalidArgAccess(ArgNum), - /// Produced when a method accesses a local that it has not stored into. - InvalidLocalAccess(LocalNum), - /// Tried to invoke a method with too many arguments. - TooManyArgs, - /// A `DefBreak` operation was performed outside of a `DefWhile` or `DefSwitch`. - BreakInInvalidPosition, - /// A `DefContinue` operation was performed outside of a `DefWhile`. - ContinueInInvalidPosition, - - /* - * Errors produced parsing the PCI routing tables (_PRT objects). - */ - PrtInvalidAddress, - PrtInvalidPin, - PrtInvalidSource, - PrtInvalidGsi, - /// Produced when the PRT doesn't contain an entry for the requested address + pin - PrtNoEntry, - - /* - * Errors produced parsing Resource Descriptors. - */ - ReservedResourceType, - ResourceDescriptorTooShort, - ResourceDescriptorTooLong, - UnexpectedResourceType, - - /* - * Errors produced working with AML values. - */ - IncompatibleValueConversion { - current: AmlType, - target: AmlType, - }, - InvalidStatusObject, - InvalidShiftLeft, - InvalidShiftRight, - FieldRegionIsNotOpRegion, - FieldInvalidAddress, - FieldInvalidAccessSize, - TypeCannotBeCompared(AmlType), - /// Produced when the `Mid` operator is applied to a value of a type other than `Buffer` or `String`. - TypeCannotBeSliced(AmlType), - TypeCannotBeWrittenToBufferField(AmlType), - BufferFieldIndexesOutOfBounds, - InvalidSizeOfApplication(AmlType), - - /// Unimplemented functionality - return error rather than abort - Unimplemented, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_send_sync() { - // verify that AmlContext implements Send and Sync - fn test_send_sync() {} - test_send_sync::(); - } -} diff --git a/aml/src/misc.rs b/aml/src/misc.rs deleted file mode 100644 index 210c3afe..00000000 --- a/aml/src/misc.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - opcode::{self, ext_opcode, opcode}, - parser::{choice, comment_scope, id, Parser}, - DebugVerbosity, -}; - -pub fn debug_obj<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DebugObj := ExtOpPrefix 0x31 - */ - ext_opcode(opcode::EXT_DEBUG_OP) -} - -/// Takes a value between `0` and `7`, where 0 represents `Local0` etc. -pub type LocalNum = u8; - -pub fn local_obj<'a, 'c>() -> impl Parser<'a, 'c, LocalNum> -where - 'c: 'a, -{ - /* - * LocalObj := Local0Op | Local1Op | Local2Op | Local3Op | Local4Op | Local5Op | Local6Op | Local7Op - * Local0Op := 0x60 - * Local1Op := 0x61 - * Local2Op := 0x62 - * Local3Op := 0x63 - * Local4Op := 0x64 - * Local5Op := 0x65 - * Local6Op := 0x66 - * Local7Op := 0x67 - */ - let local_parser = |i, local_opcode| { - opcode(local_opcode) - .then(comment_scope(DebugVerbosity::AllScopes, "LocalObj", id())) - .map(move |((), _)| Ok(i)) - }; - - choice!( - local_parser(0, opcode::LOCAL0_OP), - local_parser(1, opcode::LOCAL1_OP), - local_parser(2, opcode::LOCAL2_OP), - local_parser(3, opcode::LOCAL3_OP), - local_parser(4, opcode::LOCAL4_OP), - local_parser(5, opcode::LOCAL5_OP), - local_parser(6, opcode::LOCAL6_OP), - local_parser(7, opcode::LOCAL7_OP) - ) -} - -/// Takes a value between `0` and `6`, where 0 represents `Arg0` etc. -pub type ArgNum = u8; - -pub fn arg_obj<'a, 'c>() -> impl Parser<'a, 'c, ArgNum> -where - 'c: 'a, -{ - /* - * ArgObj := Arg0Op | Arg1Op | Arg2Op | Arg3Op | Arg4Op | Arg5Op | Arg6Op - * Arg0Op = 0x68 - * Arg1Op = 0x69 - * Arg2Op = 0x6a - * Arg3Op = 0x6b - * Arg4Op = 0x6c - * Arg5Op = 0x6d - * Arg6Op = 0x6e - */ - let arg_parser = |i, arg_opcode| { - opcode(arg_opcode).then(comment_scope(DebugVerbosity::AllScopes, "ArgObj", id())).map(move |((), _)| Ok(i)) - }; - - choice!( - arg_parser(0, opcode::ARG0_OP), - arg_parser(1, opcode::ARG1_OP), - arg_parser(2, opcode::ARG2_OP), - arg_parser(3, opcode::ARG3_OP), - arg_parser(4, opcode::ARG4_OP), - arg_parser(5, opcode::ARG5_OP), - arg_parser(6, opcode::ARG6_OP) - ) -} diff --git a/aml/src/name_object.rs b/aml/src/name_object.rs deleted file mode 100644 index 4c51a496..00000000 --- a/aml/src/name_object.rs +++ /dev/null @@ -1,305 +0,0 @@ -use crate::{ - misc::{arg_obj, debug_obj, local_obj, ArgNum, LocalNum}, - namespace::{AmlName, NameComponent}, - opcode::{opcode, DUAL_NAME_PREFIX, MULTI_NAME_PREFIX, NULL_NAME, PREFIX_CHAR, ROOT_CHAR}, - parser::{choice, comment_scope, consume, n_of, take, take_while, Parser, Propagate}, - AmlContext, - AmlError, - DebugVerbosity, -}; -use alloc::vec::Vec; -use core::{fmt, str}; - -/// Produced by the `Target`, `SimpleName`, and `SuperName` parsers -#[derive(Clone, Debug)] -pub enum Target { - Null, - Name(AmlName), - Debug, - Arg(ArgNum), - Local(LocalNum), -} - -pub fn target<'a, 'c>() -> impl Parser<'a, 'c, Target> -where - 'c: 'a, -{ - /* - * Target := SuperName | NullName - * NullName := 0x00 - */ - comment_scope( - DebugVerbosity::AllScopes, - "Target", - choice!(null_name().map(|_| Ok(Target::Null)), super_name()), - ) -} - -pub fn super_name<'a, 'c>() -> impl Parser<'a, 'c, Target> -where - 'c: 'a, -{ - /* - * SuperName := SimpleName | DebugObj | ReferenceTypeOpcode - * TODO: this doesn't cover ReferenceTypeOpcode yet - */ - comment_scope( - DebugVerbosity::AllScopes, - "SuperName", - choice!(debug_obj().map(|()| Ok(Target::Debug)), simple_name()), - ) -} - -pub fn simple_name<'a, 'c>() -> impl Parser<'a, 'c, Target> -where - 'c: 'a, -{ - /* - * SimpleName := NameString | ArgObj | LocalObj - */ - comment_scope( - DebugVerbosity::AllScopes, - "SimpleName", - choice!( - arg_obj().map(|arg_num| Ok(Target::Arg(arg_num))), - local_obj().map(|local_num| Ok(Target::Local(local_num))), - name_string().map(move |name| Ok(Target::Name(name))) - ), - ) -} - -pub fn name_string<'a, 'c>() -> impl Parser<'a, 'c, AmlName> -where - 'c: 'a, -{ - /* - * NameString := | - * PrefixPath := Nothing | <'^' PrefixPath> - */ - let root_name_string = opcode(ROOT_CHAR).then(name_path()).map(|((), ref name_path)| { - let mut name = alloc::vec![NameComponent::Root]; - name.extend_from_slice(name_path); - Ok(AmlName::from_components(name)) - }); - - let prefix_path = - take_while(opcode(PREFIX_CHAR)).then(name_path()).map(|(num_prefix_chars, ref name_path)| { - let mut name = alloc::vec![NameComponent::Prefix; num_prefix_chars]; - name.extend_from_slice(name_path); - Ok(AmlName::from_components(name)) - }); - - // TODO: combinator to select a parser based on a peeked byte? - comment_scope(DebugVerbosity::AllScopes, "NameString", move |input: &'a [u8], context| { - let first_char = match input.first() { - Some(&c) => c, - None => return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))), - }; - - match first_char { - ROOT_CHAR => root_name_string.parse(input, context), - PREFIX_CHAR => prefix_path.parse(input, context), - _ => name_path() - .map(|path| { - if path.is_empty() { - return Err(Propagate::Err(AmlError::EmptyNamesAreInvalid)); - } - - Ok(AmlName::from_components(path)) - }) - .parse(input, context), - } - }) -} - -pub fn name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec> -where - 'c: 'a, -{ - /* - * NamePath := NullName | DualNamePath | MultiNamePath | NameSeg - */ - choice!( - null_name(), - dual_name_path(), - multi_name_path(), - name_seg().map(|seg| Ok(alloc::vec![NameComponent::Segment(seg)])) - ) -} - -pub fn null_name<'a, 'c>() -> impl Parser<'a, 'c, Vec> -where - 'c: 'a, -{ - /* - * NullName := 0x00 - */ - opcode(NULL_NAME).map(|_| Ok(Vec::with_capacity(0))) -} - -pub fn dual_name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec> -where - 'c: 'a, -{ - /* - * DualNamePath := 0x2e NameSeg NameSeg - */ - opcode(DUAL_NAME_PREFIX).then(name_seg()).then(name_seg()).map(|(((), first), second)| { - Ok(alloc::vec![NameComponent::Segment(first), NameComponent::Segment(second)]) - }) -} - -pub fn multi_name_path<'a, 'c>() -> impl Parser<'a, 'c, Vec> -where - 'c: 'a, -{ - /* - * MultiNamePath := 0x2f ByteData{SegCount} NameSeg(SegCount) - */ - move |input, context| { - let (new_input, context, ((), seg_count)) = - opcode(MULTI_NAME_PREFIX).then(take()).parse(input, context)?; - match n_of(name_seg(), usize::from(seg_count)).parse(new_input, context) { - Ok((new_input, context, name_segs)) => { - Ok((new_input, context, name_segs.iter().map(|&seg| NameComponent::Segment(seg)).collect())) - } - // Correct returned input to the one we haven't touched - Err((_, context, err)) => Err((input, context, err)), - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct NameSeg(pub(crate) [u8; 4]); - -impl NameSeg { - pub(crate) fn from_str(string: &str) -> Result { - // Each NameSeg can only have four chars, and must have at least one - if string.is_empty() || string.len() > 4 { - return Err(AmlError::InvalidNameSeg); - } - - // We pre-fill the array with '_', so it will already be correct if the length is < 4 - let mut seg = [b'_'; 4]; - let bytes = string.as_bytes(); - - // Manually do the first one, because we have to check it's a LeadNameChar - if !is_lead_name_char(bytes[0]) { - return Err(AmlError::InvalidNameSeg); - } - seg[0] = bytes[0]; - - // Copy the rest of the chars, checking that they're NameChars - for i in 1..bytes.len() { - if !is_name_char(bytes[i]) { - return Err(AmlError::InvalidNameSeg); - } - seg[i] = bytes[i]; - } - - Ok(NameSeg(seg)) - } - - pub fn as_str(&self) -> &str { - /* - * This is safe, because we always check that all the bytes are valid ASCII, so every - * `NameSeg` will be valid UTF8. - */ - unsafe { str::from_utf8_unchecked(&self.0) } - } -} - -// A list of ASCII codes is pretty much never useful, so we always just show it as a string -impl fmt::Debug for NameSeg { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.as_str()) - } -} - -pub fn name_seg<'a, 'c>() -> impl Parser<'a, 'c, NameSeg> -where - 'c: 'a, -{ - /* - * NameSeg := - */ - // TODO: can we write this better? - move |input, context: &'c mut AmlContext| { - let (input, context, char_1) = consume(is_lead_name_char).parse(input, context)?; - let (input, context, char_2) = consume(is_name_char).parse(input, context)?; - let (input, context, char_3) = consume(is_name_char).parse(input, context)?; - let (input, context, char_4) = consume(is_name_char).parse(input, context)?; - Ok((input, context, NameSeg([char_1, char_2, char_3, char_4]))) - } -} - -fn is_lead_name_char(byte: u8) -> bool { - byte.is_ascii_uppercase() || byte == b'_' -} - -fn is_name_char(byte: u8) -> bool { - is_lead_name_char(byte) || byte.is_ascii_digit() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{parser::Parser, test_utils::*, AmlError}; - use core::str::FromStr; - - #[test] - fn test_name_seg() { - let mut context = crate::test_utils::make_test_context(); - - check_ok!( - name_seg().parse(b"AF3Z", &mut context), - NameSeg([b'A', b'F', b'3', b'Z']), - &[] - ); - check_ok!( - name_seg().parse(&[b'A', b'F', b'3', b'Z', 0xff], &mut context), - NameSeg([b'A', b'F', b'3', b'Z']), - &[0xff] - ); - check_err!( - name_seg().parse(&[0xff, b'E', b'A', b'7'], &mut context), - AmlError::UnexpectedByte(0xff), - &[0xff, b'E', b'A', b'7'] - ); - check_err!(name_seg().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - } - - #[test] - fn test_name_path() { - let mut context = crate::test_utils::make_test_context(); - - check_err!(name_path().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - check_ok!(name_path().parse(&[0x00], &mut context), alloc::vec![], &[]); - check_ok!(name_path().parse(&[0x00, 0x00], &mut context), alloc::vec![], &[0x00]); - check_err!(name_path().parse(&[0x2e, b'A'], &mut context), AmlError::UnexpectedEndOfStream, &[0x2e, b'A']); - check_ok!( - name_path().parse(&[0x2e, b'A', b'B', b'C', b'D', b'E', b'_', b'F', b'G'], &mut context), - alloc::vec![ - NameComponent::Segment(NameSeg([b'A', b'B', b'C', b'D'])), - NameComponent::Segment(NameSeg([b'E', b'_', b'F', b'G'])) - ], - &[] - ); - } - - #[test] - fn test_prefix_path() { - let mut context = crate::test_utils::make_test_context(); - - check_ok!( - name_string().parse(b"^ABCD", &mut context), - AmlName::from_str("^ABCD").unwrap(), - &[] - ); - check_ok!( - name_string().parse(b"^^^ABCD", &mut context), - AmlName::from_str("^^^ABCD").unwrap(), - &[] - ); - } -} diff --git a/aml/src/namespace.rs b/aml/src/namespace.rs deleted file mode 100644 index 8f196fcc..00000000 --- a/aml/src/namespace.rs +++ /dev/null @@ -1,797 +0,0 @@ -use crate::{name_object::NameSeg, value::AmlValue, AmlError}; -use alloc::{ - collections::BTreeMap, - string::{String, ToString}, - vec::Vec, -}; -use core::{fmt, str::FromStr}; - -/// A handle is used to refer to an AML value without actually borrowing it until you need to -/// access it (this makes borrowing situation much easier as you only have to consider who's -/// borrowing the namespace). They can also be cached to avoid expensive namespace lookups. -/// -/// Handles are never reused (the handle to a removed object will never be reused to point to a new -/// object). This ensures handles cached by the library consumer will never point to an object they -/// did not originally point to, but also means that, in theory, we can run out of handles on a -/// very-long-running system (we are yet to see if this is a problem, practically). -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct AmlHandle(u32); - -impl AmlHandle { - pub(self) fn increment(&mut self) { - self.0 += 1; - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum LevelType { - Scope, - Device, - /// A legacy `Processor` object's sub-objects are stored in a level of this type. Modern tables define - /// processors as `Device`s. - Processor, - /// A `PowerResource` object's sub-objects are stored in a level of this type. - PowerResource, - /// A `ThermalZone` object's sub-objects are stored in a level of this type. - ThermalZone, - /// A level of this type is created at the same path as the name of a method when it is invoked. It can be - /// used by the method to store local variables. - MethodLocals, -} - -#[derive(Clone, Debug)] -pub struct NamespaceLevel { - pub typ: LevelType, - pub children: BTreeMap, - pub values: BTreeMap, -} - -impl NamespaceLevel { - pub(crate) fn new(typ: LevelType) -> NamespaceLevel { - NamespaceLevel { typ, children: BTreeMap::new(), values: BTreeMap::new() } - } -} - -#[derive(Clone)] -pub struct Namespace { - /// This is a running count of ids, which are never reused. This is incremented every time we - /// add a new object to the namespace. We can then remove objects, freeing their memory, without - /// risking using the same id for two objects. - next_handle: AmlHandle, - - /// This maps handles to actual values, and is used to access the actual AML values. When removing a value - /// from the object map, care must be taken to also remove references to its handle in the level data - /// structure, as invalid handles will cause panics. - object_map: BTreeMap, - - /// Holds the first level of the namespace - containing items such as `\_SB`. Subsequent levels are held - /// recursively inside this structure. It holds handles to references, which need to be indexed into - /// `object_map` to acctually access the object. - root: NamespaceLevel, -} - -impl Default for Namespace { - fn default() -> Self { - Self::new() - } -} - -impl Namespace { - pub fn new() -> Namespace { - Namespace { - next_handle: AmlHandle(0), - object_map: BTreeMap::new(), - root: NamespaceLevel::new(LevelType::Scope), - } - } - - /// Add a new level to the namespace. A "level" is named by a single `NameSeg`, and can contain values, and - /// also other further sub-levels. Once a level has been created, AML values can be added to it with - /// `add_value`. - /// - /// ### Note - /// At first glance, you might expect `DefDevice` to add a value of type `Device`. However, because all - /// `Devices` do is hold other values, we model them as namespace levels, and so they must be created - /// accordingly. - pub fn add_level(&mut self, path: AmlName, typ: LevelType) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; - - /* - * We need to handle a special case here: if a `Scope(\) { ... }` appears in the AML, the parser will - * try and recreate the root scope. Instead of handling this specially in the parser, we just - * return nicely here. - */ - if path != AmlName::root() { - let (level, last_seg) = self.get_level_for_path_mut(&path)?; - - /* - * If the level has already been added, we don't need to add it again. The parser can try to add it - * multiple times if the ASL contains multiple blocks that add to the same scope/device. - */ - level.children.entry(last_seg).or_insert_with(|| NamespaceLevel::new(typ)); - } - - Ok(()) - } - - pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> { - assert!(path.is_absolute()); - let path = path.normalize()?; - - if path != AmlName::root() { - let (level, last_seg) = self.get_level_for_path_mut(&path)?; - - match level.children.remove(&last_seg) { - Some(_) => Ok(()), - None => Err(AmlError::LevelDoesNotExist(path)), - } - } else { - Err(AmlError::TriedToRemoveRootNamespace) - } - } - - /// Add a value to the namespace at the given path, which must be a normalized, absolute AML - /// name. If you want to add at a path relative to a given scope, use `add_at_resolved_path` - /// instead. - pub fn add_value(&mut self, path: AmlName, value: AmlValue) -> Result { - assert!(path.is_absolute()); - let path = path.normalize()?; - - let handle = self.next_handle; - self.next_handle.increment(); - self.object_map.insert(handle, value); - - let (level, last_seg) = self.get_level_for_path_mut(&path)?; - match level.values.insert(last_seg, handle) { - None => Ok(handle), - Some(_) => Err(AmlError::NameCollision(path)), - } - } - - /// Helper method for adding a value to the namespace at a path that is relative to the given - /// scope. This operation involves a lot of error handling in parts of the parser, so is - /// encapsulated here. - pub fn add_value_at_resolved_path( - &mut self, - path: AmlName, - scope: &AmlName, - value: AmlValue, - ) -> Result { - self.add_value(path.resolve(scope)?, value) - } - - /// Add an alias for an existing name. The alias will refer to the same value as the original, - /// and the fact that the alias exists is forgotten. - pub fn add_alias_at_resolved_path( - &mut self, - path: AmlName, - scope: &AmlName, - target: AmlName, - ) -> Result { - let path = path.resolve(scope)?; - let target = target.resolve(scope)?; - - let handle = self.get_handle(&target)?; - - let (level, last_seg) = self.get_level_for_path_mut(&path)?; - match level.values.insert(last_seg, handle) { - None => Ok(handle), - Some(_) => Err(AmlError::NameCollision(path)), - } - } - - pub fn get(&self, handle: AmlHandle) -> Result<&AmlValue, AmlError> { - Ok(self.object_map.get(&handle).unwrap()) - } - - pub fn get_mut(&mut self, handle: AmlHandle) -> Result<&mut AmlValue, AmlError> { - Ok(self.object_map.get_mut(&handle).unwrap()) - } - - pub fn get_handle(&self, path: &AmlName) -> Result { - let (level, last_seg) = self.get_level_for_path(path)?; - Ok(*level.values.get(&last_seg).ok_or(AmlError::ValueDoesNotExist(path.clone()))?) - } - - pub fn get_by_path(&self, path: &AmlName) -> Result<&AmlValue, AmlError> { - let handle = self.get_handle(path)?; - Ok(self.get(handle).unwrap()) - } - - pub fn get_by_path_mut(&mut self, path: &AmlName) -> Result<&mut AmlValue, AmlError> { - let handle = self.get_handle(path)?; - Ok(self.get_mut(handle).unwrap()) - } - - /// Search for an object at the given path of the namespace, applying the search rules described in §5.3 of the - /// ACPI specification, if they are applicable. Returns the resolved name, and the handle of the first valid - /// object, if found. - pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, AmlHandle), AmlError> { - if path.search_rules_apply() { - /* - * If search rules apply, we need to recursively look through the namespace. If the - * given name does not occur in the current scope, we look at the parent scope, until - * we either find the name, or reach the root of the namespace. - */ - let mut scope = starting_scope.clone(); - assert!(scope.is_absolute()); - loop { - // Search for the name at this namespace level. If we find it, we're done. - let name = path.resolve(&scope)?; - match self.get_level_for_path(&name) { - Ok((level, last_seg)) => { - if let Some(&handle) = level.values.get(&last_seg) { - return Ok((name, handle)); - } - } - - /* - * This error is caught specially to avoid a case that seems bizzare but is quite useful - when - * the passed starting scope doesn't exist. Certain methods return values that reference names - * from the point of view of the method, so it makes sense for the starting scope to be inside - * the method. However, because we have destroyed all the objects created by the method - * dynamically, the level no longer exists. - * - * To avoid erroring here, we simply continue to the parent scope. If the whole scope doesn't - * exist, this will error when we get to the root, so this seems unlikely to introduce bugs. - */ - Err(AmlError::LevelDoesNotExist(_)) => (), - Err(err) => return Err(err), - } - - // If we don't find it, go up a level in the namespace and search for it there recursively - match scope.parent() { - Ok(parent) => scope = parent, - // If we still haven't found the value and have run out of parents, return `None`. - Err(AmlError::RootHasNoParent) => return Err(AmlError::ValueDoesNotExist(path.clone())), - Err(err) => return Err(err), - } - } - } else { - // If search rules don't apply, simply resolve it against the starting scope - let name = path.resolve(starting_scope)?; - // TODO: the fuzzer crashes when path is `\` and the scope is also `\`. This means that name is `\`, - // which then trips up get_level_for_path. I don't know where to best solve this: we could check for - // specific things that crash `search`, or look for a more general solution. - let (level, last_seg) = self.get_level_for_path(&path.resolve(starting_scope)?)?; - - if let Some(&handle) = level.values.get(&last_seg) { - Ok((name, handle)) - } else { - Err(AmlError::ValueDoesNotExist(path.clone())) - } - } - } - - pub fn search_for_level(&self, level_name: &AmlName, starting_scope: &AmlName) -> Result { - if level_name.search_rules_apply() { - let mut scope = starting_scope.clone().normalize()?; - assert!(scope.is_absolute()); - - loop { - let name = level_name.resolve(&scope)?; - if let Ok((level, last_seg)) = self.get_level_for_path(&name) { - if level.children.contains_key(&last_seg) { - return Ok(name); - } - } - - // If we don't find it, move the scope up a level and search for it there recursively - match scope.parent() { - Ok(parent) => scope = parent, - Err(AmlError::RootHasNoParent) => return Err(AmlError::LevelDoesNotExist(level_name.clone())), - Err(err) => return Err(err), - } - } - } else { - Ok(level_name.clone()) - } - } - - fn get_level_for_path(&self, path: &AmlName) -> Result<(&NamespaceLevel, NameSeg), AmlError> { - assert_ne!(*path, AmlName::root()); - - let (last_seg, levels) = path.0[1..].split_last().unwrap(); - let last_seg = last_seg.as_segment().unwrap(); - - // TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. - let mut traversed_path = AmlName::root(); - - let mut current_level = &self.root; - for level in levels { - traversed_path.0.push(*level); - current_level = current_level - .children - .get(&level.as_segment().unwrap()) - .ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?; - } - - Ok((current_level, last_seg)) - } - - /// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a - /// last segment to index into that level. This must not be called on `\\`. - fn get_level_for_path_mut(&mut self, path: &AmlName) -> Result<(&mut NamespaceLevel, NameSeg), AmlError> { - assert_ne!(*path, AmlName::root()); - - let (last_seg, levels) = path.0[1..].split_last().unwrap(); - let last_seg = last_seg.as_segment().unwrap(); - - // TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. We can - // improve this by changing the `levels` interation into an `enumerate()`, and then using the index to - // create the correct path on the error path - let mut traversed_path = AmlName::root(); - - let mut current_level = &mut self.root; - for level in levels { - traversed_path.0.push(*level); - current_level = current_level - .children - .get_mut(&level.as_segment().unwrap()) - .ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?; - } - - Ok((current_level, last_seg)) - } - - /// Traverse the namespace, calling `f` on each namespace level. `f` returns a `Result` - - /// errors terminate the traversal and are propagated, and the `bool` on the successful path marks whether the - /// children of the level should also be traversed. - pub fn traverse(&mut self, mut f: F) -> Result<(), AmlError> - where - F: FnMut(&AmlName, &NamespaceLevel) -> Result, - { - fn traverse_level(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError> - where - F: FnMut(&AmlName, &NamespaceLevel) -> Result, - { - for (name, child) in level.children.iter() { - let name = AmlName::from_name_seg(*name).resolve(scope)?; - - if f(&name, child)? { - traverse_level(child, &name, f)?; - } - } - - Ok(()) - } - - if f(&AmlName::root(), &self.root)? { - traverse_level(&self.root, &AmlName::root(), &mut f)?; - } - - Ok(()) - } -} - -impl fmt::Debug for Namespace { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - const INDENT_PER_LEVEL: usize = 4; - - fn print_level( - namespace: &Namespace, - f: &mut fmt::Formatter<'_>, - level_name: &str, - level: &NamespaceLevel, - indent: usize, - ) -> fmt::Result { - writeln!(f, "{:indent$}{}:", "", level_name, indent = indent)?; - - for (name, handle) in level.values.iter() { - writeln!( - f, - "{:indent$}{}: {:?}", - "", - name.as_str(), - namespace.object_map.get(handle).unwrap(), - indent = indent + INDENT_PER_LEVEL - )?; - } - - for (name, sub_level) in level.children.iter() { - print_level(namespace, f, name.as_str(), sub_level, indent + INDENT_PER_LEVEL)?; - } - - Ok(()) - } - - print_level(self, f, "\\", &self.root, 0) - } -} - -impl FromStr for AmlName { - type Err = AmlError; - - fn from_str(mut string: &str) -> Result { - if string.is_empty() { - return Err(AmlError::EmptyNamesAreInvalid); - } - - let mut components = Vec::new(); - - // If it starts with a \, make it an absolute name - if string.starts_with('\\') { - components.push(NameComponent::Root); - string = &string[1..]; - } - - if !string.is_empty() { - // Divide the rest of it into segments, and parse those - for mut part in string.split('.') { - // Handle prefix chars - while part.starts_with('^') { - components.push(NameComponent::Prefix); - part = &part[1..]; - } - - components.push(NameComponent::Segment(NameSeg::from_str(part)?)); - } - } - - Ok(Self(components)) - } -} - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct AmlName(Vec); - -impl AmlName { - pub fn root() -> AmlName { - AmlName(alloc::vec![NameComponent::Root]) - } - - pub fn from_name_seg(seg: NameSeg) -> AmlName { - AmlName(alloc::vec![NameComponent::Segment(seg)]) - } - - pub fn from_components(components: Vec) -> AmlName { - assert!(!components.is_empty()); - AmlName(components) - } - - pub fn as_string(&self) -> String { - self.0 - .iter() - .fold(String::new(), |name, component| match component { - NameComponent::Root => name + "\\", - NameComponent::Prefix => name + "^", - NameComponent::Segment(seg) => name + seg.as_str() + ".", - }) - .trim_end_matches('.') - .to_string() - } - - /// An AML path is normal if it does not contain any prefix elements ("^" characters, when - /// expressed as a string). - pub fn is_normal(&self) -> bool { - !self.0.contains(&NameComponent::Prefix) - } - - pub fn is_absolute(&self) -> bool { - self.0.first() == Some(&NameComponent::Root) - } - - /// Special rules apply when searching for certain paths (specifically, those that are made up - /// of a single name segment). Returns `true` if those rules apply. - pub fn search_rules_apply(&self) -> bool { - if self.0.len() != 1 { - return false; - } - - matches!(self.0[0], NameComponent::Segment(_)) - } - - /// Normalize an AML path, resolving prefix chars. Returns `AmlError::InvalidNormalizedName` if the path - /// normalizes to an invalid path (e.g. `\^_FOO`) - pub fn normalize(self) -> Result { - /* - * If the path is already normal, just return it as-is. This avoids an unneccessary heap allocation and - * free. - */ - if self.is_normal() { - return Ok(self); - } - - Ok(AmlName(self.0.iter().try_fold(Vec::new(), |mut name, &component| match component { - seg @ NameComponent::Segment(_) => { - name.push(seg); - Ok(name) - } - - NameComponent::Root => { - name.push(NameComponent::Root); - Ok(name) - } - - NameComponent::Prefix => { - if let Some(NameComponent::Segment(_)) = name.iter().last() { - name.pop().unwrap(); - Ok(name) - } else { - Err(AmlError::InvalidNormalizedName(self.clone())) - } - } - })?)) - } - - /// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root - /// path has no parent, and so returns `None`. - pub fn parent(&self) -> Result { - // Firstly, normalize the path so we don't have to deal with prefix chars - let mut normalized_self = self.clone().normalize()?; - - match normalized_self.0.last() { - None | Some(NameComponent::Root) => Err(AmlError::RootHasNoParent), - Some(NameComponent::Segment(_)) => { - normalized_self.0.pop(); - Ok(normalized_self) - } - Some(NameComponent::Prefix) => unreachable!(), // Prefix chars are removed by normalization - } - } - - /// Resolve this path against a given scope, making it absolute. If the path is absolute, it is - /// returned directly. The path is also normalized. - pub fn resolve(&self, scope: &AmlName) -> Result { - assert!(scope.is_absolute()); - - if self.is_absolute() { - return Ok(self.clone()); - } - - let mut resolved_path = scope.clone(); - resolved_path.0.extend_from_slice(&(self.0)); - resolved_path.normalize() - } -} - -impl fmt::Display for AmlName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_string()) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum NameComponent { - Root, - Prefix, - Segment(NameSeg), -} - -impl NameComponent { - pub fn as_segment(self) -> Option { - match self { - NameComponent::Segment(seg) => Some(seg), - NameComponent::Root | NameComponent::Prefix => None, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::crudely_cmp_values; - - #[test] - fn test_aml_name_from_str() { - assert_eq!(AmlName::from_str(""), Err(AmlError::EmptyNamesAreInvalid)); - assert_eq!(AmlName::from_str("\\"), Ok(AmlName::root())); - assert_eq!( - AmlName::from_str("\\_SB.PCI0"), - Ok(AmlName(alloc::vec![ - NameComponent::Root, - NameComponent::Segment(NameSeg([b'_', b'S', b'B', b'_'])), - NameComponent::Segment(NameSeg([b'P', b'C', b'I', b'0'])) - ])) - ); - assert_eq!( - AmlName::from_str("\\_SB.^^^PCI0"), - Ok(AmlName(alloc::vec![ - NameComponent::Root, - NameComponent::Segment(NameSeg([b'_', b'S', b'B', b'_'])), - NameComponent::Prefix, - NameComponent::Prefix, - NameComponent::Prefix, - NameComponent::Segment(NameSeg([b'P', b'C', b'I', b'0'])) - ])) - ); - } - - #[test] - fn test_is_normal() { - assert!(AmlName::root().is_normal()); - assert!(AmlName::from_str("\\_SB.PCI0.VGA").unwrap().is_normal()); - assert!(!AmlName::from_str("\\_SB.^PCI0.VGA").unwrap().is_normal()); - assert!(!AmlName::from_str("\\^_SB.^^PCI0.VGA").unwrap().is_normal()); - assert!(!AmlName::from_str("_SB.^^PCI0.VGA").unwrap().is_normal()); - assert!(AmlName::from_str("_SB.PCI0.VGA").unwrap().is_normal()); - } - - #[test] - fn test_normalization() { - assert_eq!( - AmlName::from_str("\\_SB.PCI0").unwrap().normalize(), - Ok(AmlName::from_str("\\_SB.PCI0").unwrap()) - ); - assert_eq!( - AmlName::from_str("\\_SB.^PCI0").unwrap().normalize(), - Ok(AmlName::from_str("\\PCI0").unwrap()) - ); - assert_eq!( - AmlName::from_str("\\_SB.PCI0.^^FOO").unwrap().normalize(), - Ok(AmlName::from_str("\\FOO").unwrap()) - ); - assert_eq!( - AmlName::from_str("_SB.PCI0.^FOO.BAR").unwrap().normalize(), - Ok(AmlName::from_str("_SB.FOO.BAR").unwrap()) - ); - assert_eq!( - AmlName::from_str("\\^_SB").unwrap().normalize(), - Err(AmlError::InvalidNormalizedName(AmlName::from_str("\\^_SB").unwrap())) - ); - assert_eq!( - AmlName::from_str("\\_SB.PCI0.FOO.^^^^BAR").unwrap().normalize(), - Err(AmlError::InvalidNormalizedName(AmlName::from_str("\\_SB.PCI0.FOO.^^^^BAR").unwrap())) - ); - } - - #[test] - fn test_is_absolute() { - assert!(AmlName::root().is_absolute()); - assert!(AmlName::from_str("\\_SB.PCI0.VGA").unwrap().is_absolute()); - assert!(AmlName::from_str("\\_SB.^PCI0.VGA").unwrap().is_absolute()); - assert!(AmlName::from_str("\\^_SB.^^PCI0.VGA").unwrap().is_absolute()); - assert!(!AmlName::from_str("_SB.^^PCI0.VGA").unwrap().is_absolute()); - assert!(!AmlName::from_str("_SB.PCI0.VGA").unwrap().is_absolute()); - } - - #[test] - fn test_search_rules_apply() { - assert!(!AmlName::root().search_rules_apply()); - assert!(!AmlName::from_str("\\_SB").unwrap().search_rules_apply()); - assert!(!AmlName::from_str("^VGA").unwrap().search_rules_apply()); - assert!(!AmlName::from_str("_SB.PCI0.VGA").unwrap().search_rules_apply()); - assert!(AmlName::from_str("VGA").unwrap().search_rules_apply()); - assert!(AmlName::from_str("_SB").unwrap().search_rules_apply()); - } - - #[test] - fn test_aml_name_parent() { - assert_eq!(AmlName::from_str("\\").unwrap().parent(), Err(AmlError::RootHasNoParent)); - assert_eq!(AmlName::from_str("\\_SB").unwrap().parent(), Ok(AmlName::root())); - assert_eq!(AmlName::from_str("\\_SB.PCI0").unwrap().parent(), Ok(AmlName::from_str("\\_SB").unwrap())); - assert_eq!(AmlName::from_str("\\_SB.PCI0").unwrap().parent().unwrap().parent(), Ok(AmlName::root())); - } - - #[test] - fn test_namespace() { - let mut namespace = Namespace::new(); - - /* - * This should succeed but do nothing. - */ - assert_eq!(namespace.add_level(AmlName::from_str("\\").unwrap(), LevelType::Scope), Ok(())); - - /* - * Add `\_SB`, also testing that adding a level twice succeeds. - */ - assert_eq!(namespace.add_level(AmlName::from_str("\\_SB").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\_SB").unwrap(), LevelType::Scope), Ok(())); - - /* - * Add a device under a level that already exists. - */ - assert_eq!(namespace.add_level(AmlName::from_str("\\_SB.PCI0").unwrap(), LevelType::Device), Ok(())); - - /* - * Add some deeper scopes. - */ - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ.QUX").unwrap(), LevelType::Scope), Ok(())); - - /* - * Add some things to the scopes to query later. - */ - assert!(namespace.add_value(AmlName::from_str("\\MOO").unwrap(), AmlValue::Boolean(true)).is_ok()); - assert!(namespace.add_value(AmlName::from_str("\\FOO.BAR.A").unwrap(), AmlValue::Integer(12345)).is_ok()); - assert!(namespace.add_value(AmlName::from_str("\\FOO.BAR.B").unwrap(), AmlValue::Integer(6)).is_ok()); - assert!(namespace - .add_value(AmlName::from_str("\\FOO.BAR.C").unwrap(), AmlValue::String(String::from("hello, world!"))) - .is_ok()); - - /* - * Get objects using their absolute paths. - */ - assert!(crudely_cmp_values( - namespace.get_by_path(&AmlName::from_str("\\MOO").unwrap()).unwrap(), - &AmlValue::Boolean(true) - )); - assert!(crudely_cmp_values( - namespace.get_by_path(&AmlName::from_str("\\FOO.BAR.A").unwrap()).unwrap(), - &AmlValue::Integer(12345) - )); - assert!(crudely_cmp_values( - namespace.get_by_path(&AmlName::from_str("\\FOO.BAR.B").unwrap()).unwrap(), - &AmlValue::Integer(6) - )); - assert!(crudely_cmp_values( - namespace.get_by_path(&AmlName::from_str("\\FOO.BAR.C").unwrap()).unwrap(), - &AmlValue::String(String::from("hello, world!")) - )); - - /* - * Search for some objects that should use search rules. - */ - { - let (name, _) = namespace - .search(&AmlName::from_str("MOO").unwrap(), &AmlName::from_str("\\FOO.BAR.BAZ").unwrap()) - .unwrap(); - assert_eq!(name, AmlName::from_str("\\MOO").unwrap()); - } - { - let (name, _) = namespace - .search(&AmlName::from_str("A").unwrap(), &AmlName::from_str("\\FOO.BAR").unwrap()) - .unwrap(); - assert_eq!(name, AmlName::from_str("\\FOO.BAR.A").unwrap()); - } - { - let (name, _) = namespace - .search(&AmlName::from_str("A").unwrap(), &AmlName::from_str("\\FOO.BAR.BAZ.QUX").unwrap()) - .unwrap(); - assert_eq!(name, AmlName::from_str("\\FOO.BAR.A").unwrap()); - } - } - - #[test] - fn test_alias() { - let mut namespace = Namespace::new(); - - assert_eq!(namespace.add_level((AmlName::from_str("\\FOO")).unwrap(), LevelType::Scope), Ok(())); - - assert!(namespace - .add_value_at_resolved_path( - AmlName::from_str("BAR").unwrap(), - &AmlName::from_str("\\FOO").unwrap(), - AmlValue::Integer(100) - ) - .is_ok()); - assert!(namespace - .add_alias_at_resolved_path( - AmlName::from_str("BARA").unwrap(), - &AmlName::from_str("\\FOO").unwrap(), - AmlName::from_str("BAR").unwrap() - ) - .is_ok()); - assert!(namespace.get_by_path(&AmlName::from_str("\\FOO.BARA").unwrap()).is_ok()); - assert_eq!( - namespace.get_handle(&AmlName::from_str("\\FOO.BARA").unwrap()), - namespace.get_handle(&AmlName::from_str("\\FOO.BAR").unwrap()) - ); - } - - #[test] - fn test_get_level_for_path() { - let mut namespace = Namespace::new(); - - // Add some scopes - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ").unwrap(), LevelType::Scope), Ok(())); - assert_eq!(namespace.add_level(AmlName::from_str("\\FOO.BAR.BAZ.QUX").unwrap(), LevelType::Scope), Ok(())); - - { - let (_, last_seg) = - namespace.get_level_for_path(&AmlName::from_str("\\FOO.BAR.BAZ").unwrap()).unwrap(); - assert_eq!(last_seg, NameSeg::from_str("BAZ").unwrap()); - } - { - let (_, last_seg) = namespace.get_level_for_path(&AmlName::from_str("\\FOO").unwrap()).unwrap(); - assert_eq!(last_seg, NameSeg::from_str("FOO").unwrap()); - } - } -} diff --git a/aml/src/opcode.rs b/aml/src/opcode.rs deleted file mode 100644 index f4312c06..00000000 --- a/aml/src/opcode.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::{parser::*, AmlContext, AmlError}; - -pub const NULL_NAME: u8 = 0x00; -pub const DUAL_NAME_PREFIX: u8 = 0x2E; -pub const MULTI_NAME_PREFIX: u8 = 0x2F; -pub const ROOT_CHAR: u8 = b'\\'; -pub const PREFIX_CHAR: u8 = b'^'; - -pub const RESERVED_FIELD: u8 = 0x00; -// pub const ACCESS_FIELD: u8 = 0x01; -// pub const CONNECT_FIELD: u8 = 0x02; -// pub const EXTENDED_ACCESS_FIELD: u8 = 0x03; - -pub const ZERO_OP: u8 = 0x00; -pub const ONE_OP: u8 = 0x01; -pub const ONES_OP: u8 = 0xff; -pub const BYTE_CONST: u8 = 0x0a; -pub const WORD_CONST: u8 = 0x0b; -pub const DWORD_CONST: u8 = 0x0c; -pub const STRING_PREFIX: u8 = 0x0d; -pub const QWORD_CONST: u8 = 0x0e; - -pub const DEF_ALIAS_OP: u8 = 0x06; -pub const DEF_NAME_OP: u8 = 0x08; -pub const DEF_SCOPE_OP: u8 = 0x10; -pub const DEF_BUFFER_OP: u8 = 0x11; -pub const DEF_PACKAGE_OP: u8 = 0x12; -pub const DEF_METHOD_OP: u8 = 0x14; -pub const DEF_EXTERNAL_OP: u8 = 0x15; -pub const DEF_CREATE_DWORD_FIELD_OP: u8 = 0x8a; -pub const DEF_CREATE_WORD_FIELD_OP: u8 = 0x8b; -pub const DEF_CREATE_BYTE_FIELD_OP: u8 = 0x8c; -pub const DEF_CREATE_BIT_FIELD_OP: u8 = 0x8d; -pub const DEF_CREATE_QWORD_FIELD_OP: u8 = 0x8f; -pub const EXT_DEF_MUTEX_OP: u8 = 0x01; -pub const EXT_DEF_COND_REF_OF_OP: u8 = 0x12; -pub const EXT_DEF_CREATE_FIELD_OP: u8 = 0x13; -pub const EXT_REVISION_OP: u8 = 0x30; -pub const EXT_DEF_FATAL_OP: u8 = 0x32; -pub const EXT_DEF_OP_REGION_OP: u8 = 0x80; -pub const EXT_DEF_FIELD_OP: u8 = 0x81; -pub const EXT_DEF_DEVICE_OP: u8 = 0x82; -pub const EXT_DEF_PROCESSOR_OP: u8 = 0x83; -pub const EXT_DEF_POWER_RES_OP: u8 = 0x84; -pub const EXT_DEF_THERMAL_ZONE_OP: u8 = 0x85; - -/* - * Statement opcodes - */ -pub const DEF_CONTINUE_OP: u8 = 0x9f; -pub const DEF_IF_ELSE_OP: u8 = 0xa0; -pub const DEF_ELSE_OP: u8 = 0xa1; -pub const DEF_WHILE_OP: u8 = 0xa2; -pub const DEF_NOOP_OP: u8 = 0xa3; -pub const DEF_RETURN_OP: u8 = 0xa4; -pub const DEF_BREAK_OP: u8 = 0xa5; -pub const DEF_BREAKPOINT_OP: u8 = 0xcc; -pub const EXT_DEF_STALL_OP: u8 = 0x21; -pub const EXT_DEF_SLEEP_OP: u8 = 0x22; - -/* - * Expression opcodes - */ -pub const DEF_STORE_OP: u8 = 0x70; -pub const DEF_ADD_OP: u8 = 0x72; -pub const DEF_CONCAT_OP: u8 = 0x73; -pub const DEF_SUBTRACT_OP: u8 = 0x74; -pub const DEF_INCREMENT_OP: u8 = 0x75; -pub const DEF_DECREMENT_OP: u8 = 0x76; -pub const DEF_SHIFT_LEFT: u8 = 0x79; -pub const DEF_SHIFT_RIGHT: u8 = 0x7a; -pub const DEF_AND_OP: u8 = 0x7b; -pub const DEF_OR_OP: u8 = 0x7d; -pub const DEF_CONCAT_RES_OP: u8 = 0x84; -pub const DEF_SIZE_OF_OP: u8 = 0x87; -pub const DEF_OBJECT_TYPE_OP: u8 = 0x8e; -pub const DEF_L_AND_OP: u8 = 0x90; -pub const DEF_L_OR_OP: u8 = 0x91; -pub const DEF_L_NOT_OP: u8 = 0x92; -pub const DEF_L_EQUAL_OP: u8 = 0x93; -pub const DEF_L_GREATER_OP: u8 = 0x94; -pub const DEF_L_LESS_OP: u8 = 0x95; -pub const DEF_TO_INTEGER_OP: u8 = 0x99; -pub const DEF_MID_OP: u8 = 0x9e; - -/* - * Miscellaneous objects - */ -pub const EXT_DEBUG_OP: u8 = 0x31; -pub const LOCAL0_OP: u8 = 0x60; -pub const LOCAL1_OP: u8 = 0x61; -pub const LOCAL2_OP: u8 = 0x62; -pub const LOCAL3_OP: u8 = 0x63; -pub const LOCAL4_OP: u8 = 0x64; -pub const LOCAL5_OP: u8 = 0x65; -pub const LOCAL6_OP: u8 = 0x66; -pub const LOCAL7_OP: u8 = 0x67; -pub const ARG0_OP: u8 = 0x68; -pub const ARG1_OP: u8 = 0x69; -pub const ARG2_OP: u8 = 0x6a; -pub const ARG3_OP: u8 = 0x6b; -pub const ARG4_OP: u8 = 0x6c; -pub const ARG5_OP: u8 = 0x6d; -pub const ARG6_OP: u8 = 0x6e; - -pub const EXT_OPCODE_PREFIX: u8 = 0x5b; - -pub(crate) fn opcode<'a, 'c>(opcode: u8) -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| match input.first() { - None => Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))), - Some(&byte) if byte == opcode => Ok((&input[1..], context, ())), - Some(_) => Err((input, context, Propagate::Err(AmlError::WrongParser))), - } -} - -pub(crate) fn ext_opcode<'a, 'c>(ext_opcode: u8) -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - opcode(EXT_OPCODE_PREFIX).then(opcode(ext_opcode)).discard_result() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_utils::*, AmlError}; - - #[test] - fn empty() { - let mut context = crate::test_utils::make_test_context(); - check_err!(opcode(NULL_NAME).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - check_err!(ext_opcode(EXT_DEF_FIELD_OP).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - } - - #[test] - fn simple_opcodes() { - let mut context = crate::test_utils::make_test_context(); - check_ok!(opcode(DEF_SCOPE_OP).parse(&[DEF_SCOPE_OP], &mut context), (), &[]); - check_ok!( - opcode(DEF_NAME_OP).parse(&[DEF_NAME_OP, 0x31, 0x55, 0xf3], &mut context), - (), - &[0x31, 0x55, 0xf3] - ); - } - - #[test] - fn extended_opcodes() { - let mut context = crate::test_utils::make_test_context(); - check_err!( - ext_opcode(EXT_DEF_FIELD_OP).parse(&[EXT_DEF_FIELD_OP, EXT_DEF_FIELD_OP], &mut context), - AmlError::WrongParser, - &[EXT_DEF_FIELD_OP, EXT_DEF_FIELD_OP] - ); - check_ok!( - ext_opcode(EXT_DEF_FIELD_OP).parse(&[EXT_OPCODE_PREFIX, EXT_DEF_FIELD_OP], &mut context), - (), - &[] - ); - } -} diff --git a/aml/src/opregion.rs b/aml/src/opregion.rs deleted file mode 100644 index 12898f92..00000000 --- a/aml/src/opregion.rs +++ /dev/null @@ -1,301 +0,0 @@ -use crate::{ - value::{Args, FieldAccessType, FieldFlags, FieldUpdateRule}, - AmlContext, - AmlError, - AmlName, - AmlValue, -}; -use bit_field::BitField; -use core::str::FromStr; - -#[derive(Clone, Debug)] -pub struct OpRegion { - region: RegionSpace, - base: u64, - length: u64, - parent_device: Option, -} - -impl OpRegion { - pub fn new(region: RegionSpace, base: u64, length: u64, parent_device: Option) -> OpRegion { - OpRegion { region, base, length, parent_device } - } - - /// Get the length of this op-region, in **bits**. - pub fn length(&self) -> u64 { - self.length - } - - /// Read a field from this op-region. This has looser requirements than `read`, and will - /// perform multiple standard-sized reads and mask the result as required. - pub fn read_field( - &self, - offset: u64, - length: u64, - flags: FieldFlags, - context: &mut AmlContext, - ) -> Result { - let _max_access_size = match self.region { - RegionSpace::SystemMemory => 64, - RegionSpace::SystemIo | RegionSpace::PciConfig => 32, - _ => unimplemented!(), - }; - let minimum_access_size = match flags.access_type()? { - FieldAccessType::Any => 8, - FieldAccessType::Byte => 8, - FieldAccessType::Word => 16, - FieldAccessType::DWord => 32, - FieldAccessType::QWord => 64, - FieldAccessType::Buffer => 8, // TODO - }; - - /* - * Find the access size, as either the minimum access size allowed by the region, or the field length - * rounded up to the next power-of-2, whichever is larger. - */ - let access_size = u64::max(minimum_access_size, length.next_power_of_two()); - - /* - * TODO: we need to decide properly how to read from the region itself. Complications: - * - if the region has a minimum access size greater than the desired length, we need to read the - * minimum and mask it (reading a byte from a WordAcc region) - * - if the desired length is larger than we can read, we need to do multiple reads - */ - let value = self.read(offset, access_size, context)?.get_bits(0..(length as usize)); - Ok(AmlValue::Integer(value)) - } - - pub fn write_field( - &self, - offset: u64, - length: u64, - flags: FieldFlags, - value: AmlValue, - context: &mut AmlContext, - ) -> Result<(), AmlError> { - /* - * If the field's update rule is `Preserve`, we need to read the initial value of the field, so we can - * overwrite the correct bits. We destructure the field to do the actual write, so we read from it if - * needed here, otherwise the borrow-checker doesn't understand. - */ - let mut field_value = match flags.field_update_rule()? { - FieldUpdateRule::Preserve => self.read_field(offset, length, flags, context)?.as_integer(context)?, - FieldUpdateRule::WriteAsOnes => 0xffffffff_ffffffff, - FieldUpdateRule::WriteAsZeros => 0x0, - }; - - let _maximum_access_size = match self.region { - RegionSpace::SystemMemory => 64, - RegionSpace::SystemIo | RegionSpace::PciConfig => 32, - _ => unimplemented!(), - }; - let minimum_access_size = match flags.access_type()? { - FieldAccessType::Any => 8, - FieldAccessType::Byte => 8, - FieldAccessType::Word => 16, - FieldAccessType::DWord => 32, - FieldAccessType::QWord => 64, - FieldAccessType::Buffer => 8, // TODO - }; - - /* - * Find the access size, as either the minimum access size allowed by the region, or the field length - * rounded up to the next power-of-2, whichever is larger. - */ - let access_size = u64::max(minimum_access_size, length.next_power_of_two()); - - field_value.set_bits(0..(length as usize), value.as_integer(context)?); - self.write(offset, access_size, field_value, context) - } - - /// Perform a standard-size read from this op-region. `length` must be a supported power-of-2, - /// and `offset` correctly aligned for that `length`. `value` must be appropriately sized. - pub fn read(&self, offset: u64, length: u64, context: &mut AmlContext) -> Result { - match self.region { - RegionSpace::SystemMemory => { - let address = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - match length { - 8 => Ok(context.handler.read_u8(address) as u64), - 16 => Ok(context.handler.read_u16(address) as u64), - 32 => Ok(context.handler.read_u32(address) as u64), - 64 => Ok(context.handler.read_u64(address)), - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - RegionSpace::SystemIo => { - let port = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - match length { - 8 => Ok(context.handler.read_io_u8(port) as u64), - 16 => Ok(context.handler.read_io_u16(port) as u64), - 32 => Ok(context.handler.read_io_u32(port) as u64), - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - RegionSpace::PciConfig => { - /* - * First, we need to get some extra information out of objects in the parent object. Both - * `_SEG` and `_BBN` seem optional, with defaults that line up with legacy PCI implementations - * (e.g. systems with a single segment group and a single root, respectively). - */ - let parent_device = self.parent_device.as_ref().unwrap(); - let seg = match context.invoke_method( - &AmlName::from_str("_SEG").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - ) { - Ok(seg) => seg.as_integer(context)?.try_into().map_err(|_| AmlError::FieldInvalidAddress)?, - Err(AmlError::ValueDoesNotExist(_)) => 0, - Err(err) => return Err(err), - }; - let bbn = match context.invoke_method( - &AmlName::from_str("_BBN").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - ) { - Ok(bbn) => bbn.as_integer(context)?.try_into().map_err(|_| AmlError::FieldInvalidAddress)?, - Err(AmlError::ValueDoesNotExist(_)) => 0, - Err(err) => return Err(err), - }; - let adr = { - let adr = context.invoke_method( - &AmlName::from_str("_ADR").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - )?; - adr.as_integer(context)? - }; - - let device = adr.get_bits(16..24) as u8; - let function = adr.get_bits(0..8) as u8; - let offset = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - - match length { - 8 => Ok(context.handler.read_pci_u8(seg, bbn, device, function, offset) as u64), - 16 => Ok(context.handler.read_pci_u16(seg, bbn, device, function, offset) as u64), - 32 => Ok(context.handler.read_pci_u32(seg, bbn, device, function, offset) as u64), - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - // TODO - _ => unimplemented!(), - } - } - - /// Perform a standard-size write to this op-region. `length` must be a supported power-of-2, - /// and `offset` correctly aligned for that `length`. `value` must be appropriately sized. - pub fn write(&self, offset: u64, length: u64, value: u64, context: &mut AmlContext) -> Result<(), AmlError> { - match self.region { - RegionSpace::SystemMemory => { - let address = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - match length { - 8 => { - context.handler.write_u8(address, value as u8); - Ok(()) - } - 16 => { - context.handler.write_u16(address, value as u16); - Ok(()) - } - 32 => { - context.handler.write_u32(address, value as u32); - Ok(()) - } - 64 => { - context.handler.write_u64(address, value); - Ok(()) - } - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - RegionSpace::SystemIo => { - let port = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - match length { - 8 => { - context.handler.write_io_u8(port, value as u8); - Ok(()) - } - 16 => { - context.handler.write_io_u16(port, value as u16); - Ok(()) - } - 32 => { - context.handler.write_io_u32(port, value as u32); - Ok(()) - } - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - RegionSpace::PciConfig => { - /* - * First, we need to get some extra information out of objects in the parent object. Both - * `_SEG` and `_BBN` seem optional, with defaults that line up with legacy PCI implementations - * (e.g. systems with a single segment group and a single root, respectively). - */ - let parent_device = self.parent_device.as_ref().unwrap(); - let seg = match context.invoke_method( - &AmlName::from_str("_SEG").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - ) { - Ok(seg) => seg.as_integer(context)?.try_into().map_err(|_| AmlError::FieldInvalidAddress)?, - Err(AmlError::ValueDoesNotExist(_)) => 0, - Err(err) => return Err(err), - }; - let bbn = match context.invoke_method( - &AmlName::from_str("_BBN").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - ) { - Ok(bbn) => bbn.as_integer(context)?.try_into().map_err(|_| AmlError::FieldInvalidAddress)?, - Err(AmlError::ValueDoesNotExist(_)) => 0, - Err(err) => return Err(err), - }; - let adr = { - let adr = context.invoke_method( - &AmlName::from_str("_ADR").unwrap().resolve(parent_device).unwrap(), - Args::EMPTY, - )?; - adr.as_integer(context)? - }; - - let device = adr.get_bits(16..24) as u8; - let function = adr.get_bits(0..8) as u8; - let offset = (self.base + offset).try_into().map_err(|_| AmlError::FieldInvalidAddress)?; - - match length { - 8 => { - context.handler.write_pci_u8(seg, bbn, device, function, offset, value as u8); - Ok(()) - } - 16 => { - context.handler.write_pci_u16(seg, bbn, device, function, offset, value as u16); - Ok(()) - } - 32 => { - context.handler.write_pci_u32(seg, bbn, device, function, offset, value as u32); - Ok(()) - } - _ => Err(AmlError::FieldInvalidAccessSize), - } - } - - // TODO - _ => unimplemented!(), - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum RegionSpace { - SystemMemory, - SystemIo, - PciConfig, - EmbeddedControl, - SMBus, - SystemCmos, - PciBarTarget, - IPMI, - GeneralPurposeIo, - GenericSerialBus, - OemDefined(u8), -} diff --git a/aml/src/parser.rs b/aml/src/parser.rs deleted file mode 100644 index 1726a223..00000000 --- a/aml/src/parser.rs +++ /dev/null @@ -1,535 +0,0 @@ -use crate::{pkg_length::PkgLength, AmlContext, AmlError, AmlValue, DebugVerbosity}; -use alloc::vec::Vec; -use core::marker::PhantomData; -use log::trace; - -/// This is the number of spaces added to indent a scope when printing parser debug messages. -pub const INDENT_PER_SCOPE: usize = 2; - -impl AmlContext { - /// This is used by the parser to provide debug comments about the current object, which are indented to the - /// correct level for the current object. We most often need to print these comments from `map_with_context`s, - /// so it's most convenient to have this method on `AmlContext`. - pub(crate) fn comment(&self, verbosity: DebugVerbosity, message: &str) { - if verbosity <= self.debug_verbosity { - log::trace!("{:indent$}{}", "", message, indent = self.scope_indent); - } - } -} - -#[derive(Debug)] -pub enum Propagate { - Err(AmlError), - Return(AmlValue), - Break, - Continue, -} - -impl From for Propagate { - fn from(error: AmlError) -> Self { - Self::Err(error) - } -} - -pub type ParseResult<'a, 'c, R> = - Result<(&'a [u8], &'c mut AmlContext, R), (&'a [u8], &'c mut AmlContext, Propagate)>; - -pub trait Parser<'a, 'c, R>: Sized -where - 'c: 'a, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R>; - - fn map(self, map_fn: F) -> Map<'a, 'c, Self, F, R, A> - where - F: Fn(R) -> Result, - { - Map { parser: self, map_fn, _phantom: PhantomData } - } - - fn map_with_context(self, map_fn: F) -> MapWithContext<'a, 'c, Self, F, R, A> - where - F: Fn(R, &'c mut AmlContext) -> (Result, &'c mut AmlContext), - { - MapWithContext { parser: self, map_fn, _phantom: PhantomData } - } - - fn discard_result(self) -> DiscardResult<'a, 'c, Self, R> { - DiscardResult { parser: self, _phantom: PhantomData } - } - - /// Try parsing with `self`. If it succeeds, return its result. If it returns `AmlError::WrongParser`, try - /// parsing with `other`, returning the result of that parser in all cases. Other errors from the first - /// parser are propagated without attempting the second parser. To chain more than two parsers using - /// `or`, see the `choice!` macro. - #[allow(unused)] - fn or(self, other: OtherParser) -> Or<'a, 'c, Self, OtherParser, R> - where - OtherParser: Parser<'a, 'c, R>, - { - Or { p1: self, p2: other, _phantom: PhantomData } - } - - fn then(self, next: NextParser) -> Then<'a, 'c, Self, NextParser, R, NextR> - where - NextParser: Parser<'a, 'c, NextR>, - { - Then { p1: self, p2: next, _phantom: PhantomData } - } - - /// `feed` takes a function that takes the result of this parser (`self`) and creates another - /// parser, which is then used to parse the next part of the stream. This sounds convoluted, - /// but is useful for when the next parser's behaviour depends on a property of the result of - /// the first (e.g. the first parser might parse a length `n`, and the second parser then - /// consumes `n` bytes). - fn feed(self, producer_fn: F) -> Feed<'a, 'c, Self, P2, F, R, R2> - where - P2: Parser<'a, 'c, R2>, - F: Fn(R) -> P2, - { - Feed { parser: self, producer_fn, _phantom: PhantomData } - } -} - -impl<'a, 'c, F, R> Parser<'a, 'c, R> for F -where - 'c: 'a, - F: Fn(&'a [u8], &'c mut AmlContext) -> ParseResult<'a, 'c, R>, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R> { - self(input, context) - } -} - -/// The identity parser - returns the stream and context unchanged. Useful for producing parsers -/// that produce a result without parsing anything by doing: `id().map(|()| Ok(foo))`. -pub fn id<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| Ok((input, context, ())) -} - -pub fn take<'a, 'c>() -> impl Parser<'a, 'c, u8> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| match input.first() { - Some(&byte) => Ok((&input[1..], context, byte)), - None => Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))), - } -} - -pub fn take_u16<'a, 'c>() -> impl Parser<'a, 'c, u16> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| { - if input.len() < 2 { - return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))); - } - - Ok((&input[2..], context, u16::from_le_bytes(input[0..2].try_into().unwrap()))) - } -} - -pub fn take_u32<'a, 'c>() -> impl Parser<'a, 'c, u32> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| { - if input.len() < 4 { - return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))); - } - - Ok((&input[4..], context, u32::from_le_bytes(input[0..4].try_into().unwrap()))) - } -} - -pub fn take_u64<'a, 'c>() -> impl Parser<'a, 'c, u64> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| { - if input.len() < 8 { - return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))); - } - - Ok((&input[8..], context, u64::from_le_bytes(input[0..8].try_into().unwrap()))) - } -} - -pub fn take_n<'a, 'c>(n: u32) -> impl Parser<'a, 'c, &'a [u8]> -where - 'c: 'a, -{ - move |input: &'a [u8], context| { - if (input.len() as u32) < n { - return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))); - } - - let (result, new_input) = input.split_at(n as usize); - Ok((new_input, context, result)) - } -} - -pub fn take_to_end_of_pkglength<'a, 'c>(length: PkgLength) -> impl Parser<'a, 'c, &'a [u8]> -where - 'c: 'a, -{ - move |input: &'a [u8], context| { - /* - * TODO: fuzzing manages to find PkgLengths that correctly parse during construction, but later crash here. - * I would've thought we would pick up all invalid lengths there, so have a look at why this is needed. - */ - let bytes_to_take = match (input.len() as u32).checked_sub(length.end_offset) { - Some(bytes_to_take) => bytes_to_take, - None => return Err((input, context, Propagate::Err(AmlError::InvalidPkgLength))), - }; - take_n(bytes_to_take).parse(input, context) - } -} - -pub fn n_of<'a, 'c, P, R>(parser: P, n: usize) -> impl Parser<'a, 'c, Vec> -where - 'c: 'a, - P: Parser<'a, 'c, R>, -{ - // TODO: can we write this more nicely? - move |mut input, mut context| { - let mut results = Vec::with_capacity(n); - - for _ in 0..n { - let (new_input, new_context, result) = match parser.parse(input, context) { - Ok((input, context, result)) => (input, context, result), - Err((_, context, propagate)) => return Err((input, context, propagate)), - }; - results.push(result); - input = new_input; - context = new_context; - } - - Ok((input, context, results)) - } -} - -pub fn take_while<'a, 'c, P, R>(parser: P) -> impl Parser<'a, 'c, usize> -where - 'c: 'a, - P: Parser<'a, 'c, R>, -{ - move |mut input: &'a [u8], mut context: &'c mut AmlContext| { - let mut num_passed = 0; - loop { - match parser.parse(input, context) { - Ok((new_input, new_context, _)) => { - input = new_input; - context = new_context; - num_passed += 1; - } - Err((_, context, Propagate::Err(AmlError::WrongParser))) => { - return Ok((input, context, num_passed)) - } - Err((_, context, err)) => return Err((input, context, err)), - } - } - } -} - -pub fn consume<'a, 'c, F>(condition: F) -> impl Parser<'a, 'c, u8> -where - 'c: 'a, - F: Fn(u8) -> bool, -{ - move |input: &'a [u8], context: &'c mut AmlContext| match input.first() { - Some(&byte) if condition(byte) => Ok((&input[1..], context, byte)), - Some(&byte) => Err((input, context, Propagate::Err(AmlError::UnexpectedByte(byte)))), - None => Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))), - } -} - -pub fn comment_scope<'a, 'c, P, R>( - verbosity: DebugVerbosity, - scope_name: &'a str, - parser: P, -) -> impl Parser<'a, 'c, R> -where - 'c: 'a, - R: core::fmt::Debug, - P: Parser<'a, 'c, R>, -{ - move |input, context: &'c mut AmlContext| { - if verbosity <= context.debug_verbosity { - trace!("{:indent$}--> {}", "", scope_name, indent = context.scope_indent); - context.scope_indent += INDENT_PER_SCOPE; - } - - // Return if the parse fails, so we don't print the tail. Makes it easier to debug. - let (new_input, context, result) = parser.parse(input, context)?; - - if verbosity <= context.debug_verbosity { - context.scope_indent -= INDENT_PER_SCOPE; - trace!("{:indent$}<-- {}", "", scope_name, indent = context.scope_indent); - } - - Ok((new_input, context, result)) - } -} - -/// `extract` observes another parser consuming part of the stream, and returns the result of the parser, and the -/// section of the stream that was parsed by the parser. This is useful for re-parsing that section of the stream, -/// which allows the result of a piece of AML to be reevaluated with a new context, for example. -/// -/// Note that reparsing the stream is not idempotent - the context is changed by this parse. -pub fn extract<'a, 'c, P, R>(parser: P) -> impl Parser<'a, 'c, (R, &'a [u8])> -where - 'c: 'a, - P: Parser<'a, 'c, R>, -{ - move |input, context: &'c mut AmlContext| { - let before = input; - let (after, context, result) = parser.parse(input, context)?; - let bytes_parsed = before.len() - after.len(); - let parsed = &before[..bytes_parsed]; - - Ok((after, context, (result, parsed))) - } -} - -pub struct Or<'a, 'c, P1, P2, R> -where - 'c: 'a, - P1: Parser<'a, 'c, R>, - P2: Parser<'a, 'c, R>, -{ - p1: P1, - p2: P2, - _phantom: PhantomData<(&'a R, &'c ())>, -} - -impl<'a, 'c, P1, P2, R> Parser<'a, 'c, R> for Or<'a, 'c, P1, P2, R> -where - 'c: 'a, - P1: Parser<'a, 'c, R>, - P2: Parser<'a, 'c, R>, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R> { - match self.p1.parse(input, context) { - Ok(parse_result) => Ok(parse_result), - Err((_, context, Propagate::Err(AmlError::WrongParser))) => self.p2.parse(input, context), - Err((_, context, err)) => Err((input, context, err)), - } - } -} - -pub struct Map<'a, 'c, P, F, R, A> -where - 'c: 'a, - P: Parser<'a, 'c, R>, - F: Fn(R) -> Result, -{ - parser: P, - map_fn: F, - _phantom: PhantomData<(&'a (R, A), &'c ())>, -} - -impl<'a, 'c, P, F, R, A> Parser<'a, 'c, A> for Map<'a, 'c, P, F, R, A> -where - 'c: 'a, - P: Parser<'a, 'c, R>, - F: Fn(R) -> Result, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, A> { - match self.parser.parse(input, context) { - Ok((new_input, context, result)) => match (self.map_fn)(result) { - Ok(result_value) => Ok((new_input, context, result_value)), - Err(err) => Err((input, context, err)), - }, - Err(result) => Err(result), - } - } -} - -pub struct MapWithContext<'a, 'c, P, F, R, A> -where - 'c: 'a, - P: Parser<'a, 'c, R>, - F: Fn(R, &'c mut AmlContext) -> (Result, &'c mut AmlContext), -{ - parser: P, - map_fn: F, - _phantom: PhantomData<(&'a (R, A), &'c ())>, -} - -impl<'a, 'c, P, F, R, A> Parser<'a, 'c, A> for MapWithContext<'a, 'c, P, F, R, A> -where - 'c: 'a, - P: Parser<'a, 'c, R>, - F: Fn(R, &'c mut AmlContext) -> (Result, &'c mut AmlContext), -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, A> { - match self.parser.parse(input, context) { - Ok((new_input, context, result)) => match (self.map_fn)(result, context) { - (Ok(result_value), context) => Ok((new_input, context, result_value)), - (Err(err), context) => Err((input, context, err)), - }, - Err(result) => Err(result), - } - } -} - -pub struct DiscardResult<'a, 'c, P, R> -where - 'c: 'a, - P: Parser<'a, 'c, R>, -{ - parser: P, - _phantom: PhantomData<(&'a R, &'c ())>, -} - -impl<'a, 'c, P, R> Parser<'a, 'c, ()> for DiscardResult<'a, 'c, P, R> -where - 'c: 'a, - P: Parser<'a, 'c, R>, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, ()> { - self.parser.parse(input, context).map(|(new_input, new_context, _)| (new_input, new_context, ())) - } -} - -pub struct Then<'a, 'c, P1, P2, R1, R2> -where - 'c: 'a, - P1: Parser<'a, 'c, R1>, - P2: Parser<'a, 'c, R2>, -{ - p1: P1, - p2: P2, - _phantom: PhantomData<(&'a (R1, R2), &'c ())>, -} - -impl<'a, 'c, P1, P2, R1, R2> Parser<'a, 'c, (R1, R2)> for Then<'a, 'c, P1, P2, R1, R2> -where - 'c: 'a, - P1: Parser<'a, 'c, R1>, - P2: Parser<'a, 'c, R2>, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, (R1, R2)> { - self.p1.parse(input, context).and_then(|(next_input, context, result_a)| { - self.p2 - .parse(next_input, context) - .map(|(final_input, context, result_b)| (final_input, context, (result_a, result_b))) - }) - } -} - -pub struct Feed<'a, 'c, P1, P2, F, R1, R2> -where - 'c: 'a, - P1: Parser<'a, 'c, R1>, - P2: Parser<'a, 'c, R2>, - F: Fn(R1) -> P2, -{ - parser: P1, - producer_fn: F, - _phantom: PhantomData<(&'a (R1, R2), &'c ())>, -} - -impl<'a, 'c, P1, P2, F, R1, R2> Parser<'a, 'c, R2> for Feed<'a, 'c, P1, P2, F, R1, R2> -where - 'c: 'a, - P1: Parser<'a, 'c, R1>, - P2: Parser<'a, 'c, R2>, - F: Fn(R1) -> P2, -{ - fn parse(&self, input: &'a [u8], context: &'c mut AmlContext) -> ParseResult<'a, 'c, R2> { - let (input, context, first_result) = self.parser.parse(input, context)?; - - // We can now produce the second parser, and parse using that. - let second_parser = (self.producer_fn)(first_result); - second_parser.parse(input, context) - } -} - -/// Takes a number of parsers, and tries to apply each one to the input in order. Returns the -/// result of the first one that succeeds, or fails if all of them fail. -pub(crate) macro choice { - () => { - id().map(|()| Err(AmlError::WrongParser)) - }, - - /* - * The nice way of writing this would be something like: - * ``` - * $first_parser - * $( - * .or($other_parser) - * )* - * .or(id().map(|()| Err(AmlError::WrongParser))) - * ``` - * This problem with this is that it generates enormous types that very easily break `rustc`'s type - * limit, so writing large parsers with choice required some gymnastics, which sucks for everyone involved. - * - * Instead, we manually call each parser sequentially, checking its result to see if we should return, or try - * the next parser. This generates worse code at the macro callsite, but is much easier for the compiler to - * type-check (and so reduces the cost of pulling us in as a dependency as well as improving ergonomics). - */ - ($($parser: expr),+) => { - move |input, context| { - $( - let context = match ($parser).parse(input, context) { - Ok(parse_result) => return Ok(parse_result), - Err((_, new_context, Propagate::Err(AmlError::WrongParser))) => new_context, - Err((_, context, propagate)) => return Err((input, context, propagate)), - }; - )+ - Err((input, context, Propagate::Err(AmlError::WrongParser))) - } - } -} - -/// Helper macro for use within `map_with_context` as an alternative to "trying" an expression. -/// -/// ### Example -/// Problem: `expr?` won't work because the expected return type is `(Result, &mut AmlContext)` -/// Solution: use `try_with_context!(context, expr)` instead. -pub(crate) macro try_with_context($context: expr, $expr: expr) { - match $expr { - Ok(result) => result, - Err(err) => return (Err(Propagate::Err(err)), $context), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::*; - - #[test] - fn test_take_n() { - let mut context = make_test_context(); - check_err!(take_n(1).parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - check_err!(take_n(2).parse(&[0xf5], &mut context), AmlError::UnexpectedEndOfStream, &[0xf5]); - - check_ok!(take_n(1).parse(&[0xff], &mut context), &[0xff], &[]); - check_ok!(take_n(1).parse(&[0xff, 0xf8], &mut context), &[0xff], &[0xf8]); - check_ok!(take_n(2).parse(&[0xff, 0xf8], &mut context), &[0xff, 0xf8], &[]); - } - - #[test] - fn test_take_ux() { - let mut context = make_test_context(); - check_err!(take_u16().parse(&[0x34], &mut context), AmlError::UnexpectedEndOfStream, &[0x34]); - check_ok!(take_u16().parse(&[0x34, 0x12], &mut context), 0x1234, &[]); - - check_err!(take_u32().parse(&[0x34, 0x12], &mut context), AmlError::UnexpectedEndOfStream, &[0x34, 0x12]); - check_ok!(take_u32().parse(&[0x34, 0x12, 0xf4, 0xc3, 0x3e], &mut context), 0xc3f41234, &[0x3e]); - - check_err!(take_u64().parse(&[0x34], &mut context), AmlError::UnexpectedEndOfStream, &[0x34]); - check_ok!( - take_u64().parse(&[0x34, 0x12, 0x35, 0x76, 0xd4, 0x43, 0xa3, 0xb6, 0xff, 0x00], &mut context), - 0xb6a343d476351234, - &[0xff, 0x00] - ); - } -} diff --git a/aml/src/pkg_length.rs b/aml/src/pkg_length.rs deleted file mode 100644 index f3b03a5d..00000000 --- a/aml/src/pkg_length.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::{ - parser::{take, take_n, Parser, Propagate}, - AmlContext, - AmlError, - AmlHandle, - AmlValue, -}; -use bit_field::BitField; - -/* - * There are two types of PkgLength implemented: PkgLength and RegionPkgLength. The reason for this - * is that while both are parsed as PkgLength op, they might have different meanings in different - * contexts: - * - * - PkgLength refers to an offset within the AML input slice - * - RegionPkgLength refers to an offset within an operation region (and is used this way in parsers - * like def_field()) - * - * They both have identical fields, but the fields themselves have an entirely different meaning. - */ - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct PkgLength { - pub raw_length: u32, - /// The offset in the structure's stream to stop parsing at - the "end" of the PkgLength. We need to track this - /// instead of the actual length encoded in the PkgLength as we often need to parse some stuff between the - /// PkgLength and the explicit-length structure. - pub end_offset: u32, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct RegionPkgLength { - pub raw_length: u32, - pub end_offset: u32, -} - -impl PkgLength { - pub fn from_raw_length(stream: &[u8], raw_length: u32) -> Result { - Ok(PkgLength { - raw_length, - end_offset: (stream.len() as u32).checked_sub(raw_length).ok_or(AmlError::InvalidPkgLength)?, - }) - } - - /// Returns `true` if the given stream is still within the structure this `PkgLength` refers - /// to. - pub fn still_parsing(&self, stream: &[u8]) -> bool { - stream.len() as u32 > self.end_offset - } -} - -impl RegionPkgLength { - pub fn from_raw_length(region_bit_length: u64, raw_length: u32) -> Result { - Ok(RegionPkgLength { - raw_length, - end_offset: (region_bit_length as u32) - .checked_sub(raw_length) - .ok_or(AmlError::InvalidRegionPkgLength { region_bit_length, raw_length })?, - }) - } -} - -pub fn region_pkg_length<'a, 'c>(region_handle: AmlHandle) -> impl Parser<'a, 'c, RegionPkgLength> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| -> crate::parser::ParseResult<'a, 'c, RegionPkgLength> { - let region_value = match context.namespace.get(region_handle) { - Ok(value) => value, - Err(err) => return Err((input, context, Propagate::Err(err))), - }; - - /* - * OperationRegion length is in bytes, PkgLength is in bits, so conversion is needed - */ - let region_bit_length = match region_value { - AmlValue::OpRegion(region) => region.length() * 8, - _ => return Err((input, context, Propagate::Err(AmlError::FieldRegionIsNotOpRegion))), - }; - - let (new_input, context, raw_length) = raw_pkg_length().parse(input, context)?; - - /* - * NOTE: we use the original input here, because `raw_length` includes the length of the - * `PkgLength`. - */ - match RegionPkgLength::from_raw_length(region_bit_length, raw_length) { - Ok(pkg_length) => Ok((new_input, context, pkg_length)), - Err(err) => Err((input, context, Propagate::Err(err))), - } - } -} - -pub fn pkg_length<'a, 'c>() -> impl Parser<'a, 'c, PkgLength> -where - 'c: 'a, -{ - move |input: &'a [u8], context: &'c mut AmlContext| -> crate::parser::ParseResult<'a, 'c, PkgLength> { - let (new_input, context, raw_length) = raw_pkg_length().parse(input, context)?; - - /* - * NOTE: we use the original input here, because `raw_length` includes the length of the - * `PkgLength`. - */ - match PkgLength::from_raw_length(input, raw_length) { - Ok(pkg_length) => Ok((new_input, context, pkg_length)), - Err(err) => Err((input, context, Propagate::Err(err))), - } - } -} - -/// Parses a `PkgLength` and returns the *raw length*. If you want an instance of `PkgLength`, use -/// `pkg_length` instead. -pub fn raw_pkg_length<'a, 'c>() -> impl Parser<'a, 'c, u32> -where - 'c: 'a, -{ - /* - * PkgLength := PkgLeadByte | - * | - * | - * - * - * The length encoded by the PkgLength includes the number of bytes used to encode it. - */ - move |input: &'a [u8], context: &'c mut AmlContext| { - let (new_input, context, lead_byte) = take().parse(input, context)?; - let byte_count = lead_byte.get_bits(6..8); - - if byte_count == 0 { - let length = u32::from(lead_byte.get_bits(0..6)); - return Ok((new_input, context, length)); - } - - let (new_input, context, length): (&[u8], &mut AmlContext, u32) = match take_n(byte_count as u32) - .parse(new_input, context) - { - Ok((new_input, context, bytes)) => { - let initial_length = u32::from(lead_byte.get_bits(0..4)); - ( - new_input, - context, - bytes - .iter() - .enumerate() - .fold(initial_length, |length, (i, &byte)| length + (u32::from(byte) << (4 + i * 8))), - ) - } - - /* - * The stream was too short. We return an error, making sure to return the - * *original* stream (that we haven't consumed any of). - */ - Err((_, context, _)) => return Err((input, context, Propagate::Err(AmlError::UnexpectedEndOfStream))), - }; - - Ok((new_input, context, length)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_utils::*, AmlError}; - - fn test_correct_pkglength(stream: &[u8], expected_raw_length: u32, expected_leftover: &[u8]) { - let mut context = make_test_context(); - check_ok!( - pkg_length().parse(stream, &mut context), - PkgLength::from_raw_length(stream, expected_raw_length).unwrap(), - &expected_leftover - ); - } - - #[test] - fn test_raw_pkg_length() { - let mut context = make_test_context(); - check_ok!(raw_pkg_length().parse(&[0b01000101, 0x14], &mut context), 325, &[]); - check_ok!(raw_pkg_length().parse(&[0b01000111, 0x14, 0x46], &mut context), 327, &[0x46]); - check_ok!(raw_pkg_length().parse(&[0b10000111, 0x14, 0x46], &mut context), 287047, &[]); - } - - #[test] - fn test_pkg_length() { - let mut context = make_test_context(); - check_err!(pkg_length().parse(&[], &mut context), AmlError::UnexpectedEndOfStream, &[]); - test_correct_pkglength(&[0x00], 0, &[]); - test_correct_pkglength(&[0x05, 0xf5, 0x7f, 0x3e, 0x54, 0x03], 5, &[0xf5, 0x7f, 0x3e, 0x54, 0x03]); - - check_ok!( - pkg_length() - .feed(crate::parser::take_to_end_of_pkglength) - .parse(&[0x05, 0x01, 0x02, 0x03, 0x04, 0xff, 0xff, 0xff], &mut context), - &[0x01, 0x02, 0x03, 0x04], - &[0xff, 0xff, 0xff] - ); - } - - #[test] - fn not_enough_pkglength() { - let mut context = make_test_context(); - check_err!( - pkg_length().parse(&[0b11000000, 0xff, 0x4f], &mut context), - AmlError::UnexpectedEndOfStream, - &[0b11000000, 0xff, 0x4f] - ); - } - - #[test] - fn not_enough_stream() { - let mut context = make_test_context(); - check_err!(pkg_length().parse(&[0x05, 0xf5], &mut context), AmlError::InvalidPkgLength, &[0x05, 0xf5]); - } -} diff --git a/aml/src/statement.rs b/aml/src/statement.rs deleted file mode 100644 index 3687a686..00000000 --- a/aml/src/statement.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::{ - opcode::{self, ext_opcode, opcode}, - parser::{ - choice, - comment_scope, - extract, - id, - take, - take_to_end_of_pkglength, - take_u32, - try_with_context, - ParseResult, - Parser, - Propagate, - }, - pkg_length::{pkg_length, PkgLength}, - term_object::{term_arg, term_list}, - AmlContext, - AmlError, - DebugVerbosity, -}; - -pub fn statement_opcode<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * StatementOpcode := DefBreak | DefBreakPoint | DefContinue | DefFatal | DefIfElse | DefLoad | DefNoop | - * DefNotify | DefRelease | DefReset | DefReturn | DefSignal | DefSleep | DefStall | DefWhile - */ - comment_scope( - DebugVerbosity::AllScopes, - "StatementOpcode", - choice!( - def_break(), - def_breakpoint(), - def_continue(), - def_fatal(), - def_if_else(), - def_noop(), - def_return(), - def_sleep(), - def_stall(), - def_while() - ), - ) -} - -fn def_break<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefBreak := 0xa5 - */ - opcode(opcode::DEF_BREAK_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefBreak", - id().map(|()| -> Result<(), Propagate> { Err(Propagate::Break) }), - )) - .discard_result() -} - -fn def_breakpoint<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefBreakPoint := 0xcc - * TODO: there is no debugger, so this doesn't do anything. If there was, this should stop execution and enter - * the AML debugger. - */ - opcode(opcode::DEF_BREAKPOINT_OP) - .then(comment_scope(DebugVerbosity::AllScopes, "DefBreakPoint", id())) - .discard_result() -} - -fn def_continue<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefContinue := 0x9f - */ - opcode(opcode::DEF_CONTINUE_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefContinue", - id().map(|()| -> Result<(), Propagate> { Err(Propagate::Continue) }), - )) - .discard_result() -} - -fn def_fatal<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefFatal := ExtOpPrefix 0x32 FatalType FatalCode FatalArg - * FatalType := ByteData - * FatalCode := DWordData - * FatalArg := TermArg => Integer - */ - ext_opcode(opcode::EXT_DEF_FATAL_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefFatal", - take().then(take_u32()).then(term_arg()).map_with_context( - |((fatal_type, fatal_code), fatal_arg), context| -> (Result<(), Propagate>, &'c mut AmlContext) { - let fatal_arg = try_with_context!(context, fatal_arg.as_integer(context)); - context.handler.handle_fatal_error(fatal_type, fatal_code, fatal_arg); - (Err(Propagate::Err(AmlError::FatalError)), context) - }, - ), - )) - .discard_result() -} - -fn def_if_else<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefIfElse := 0xa0 PkgLength Predicate TermList DefElse - * Predicate := TermArg => Integer (0 = false, >0 = true) - * DefElse := Nothing | <0xa1 PkgLength TermList> - */ - - let maybe_else_opcode = |input, context| match opcode(opcode::DEF_ELSE_OP).parse(input, context) { - Err((x, y, Propagate::Err(AmlError::UnexpectedEndOfStream))) => { - Err((x, y, Propagate::Err(AmlError::WrongParser))) - } - r => r, - }; - - opcode(opcode::DEF_IF_ELSE_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefIfElse", - pkg_length() - .then(term_arg()) - .feed(|(length, predicate_arg)| { - take_to_end_of_pkglength(length).map_with_context(move |then_branch, context| { - match predicate_arg.as_bool(context) { - Ok(pred_val) => (Ok((pred_val, then_branch)), context), - Err(e) => (Err(Propagate::Err(e)), context), - } - }) - }) - .then(choice!( - maybe_else_opcode - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefElse", - pkg_length().feed(take_to_end_of_pkglength), - )) - .map(|((), else_branch): ((), &[u8])| Ok(else_branch)), - // TODO: can this be `id().map(&[])`? - |input, context| -> ParseResult<'a, 'c, &[u8]> { - /* - * This path parses an DefIfElse that doesn't have an else branch. We simply - * return an empty slice, so if the predicate is false, we don't execute - * anything. - */ - Ok((input, context, &[])) - } - )) - .map_with_context(|((predicate, then_branch), else_branch), context| { - let branch = if predicate { then_branch } else { else_branch }; - - match term_list(PkgLength::from_raw_length(branch, branch.len() as u32).unwrap()) - .parse(branch, context) - { - Ok((_, context, result)) => (Ok(result), context), - Err((_, context, err)) => (Err(err), context), - } - }), - )) - .discard_result() -} - -fn def_noop<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefNoop := 0xa3 - */ - opcode(opcode::DEF_NOOP_OP).then(comment_scope(DebugVerbosity::AllScopes, "DefNoop", id())).discard_result() -} - -fn def_return<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefReturn := 0xa4 ArgObject - * ArgObject := TermArg => DataRefObject - */ - opcode(opcode::DEF_RETURN_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefReturn", - term_arg().map(|return_arg| -> Result<(), Propagate> { - /* - * To return a value, we want to halt execution of the method and propagate the - * return value all the way up to the start of the method invocation. To do this, - * we emit a special error that is intercepted during method invocation and turned - * into a valid result. - */ - Err(Propagate::Return(return_arg)) - }), - )) - .discard_result() -} - -fn def_sleep<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefSleep := ExtOpPrefix 0x22 MSecTime - * MSecTime := TermArg => Integer - */ - ext_opcode(opcode::EXT_DEF_SLEEP_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefSleep", - term_arg().map_with_context(|milliseconds, context| { - let milliseconds = try_with_context!(context, milliseconds.as_integer(context)); - context.handler.sleep(milliseconds); - (Ok(()), context) - }), - )) - .discard_result() -} - -fn def_stall<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefStall := ExtOpPrefix 0x21 USecTime - * USecTime := TermArg => Integer - */ - ext_opcode(opcode::EXT_DEF_STALL_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefStall", - term_arg().map_with_context(|microseconds, context| { - let microseconds = try_with_context!(context, microseconds.as_integer(context)); - context.handler.stall(microseconds); - (Ok(()), context) - }), - )) - .discard_result() -} - -fn def_while<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefWhile := 0xa2 PkgLength Predicate TermList - * Predicate := TermArg => Integer (0 = false, >0 = true) - * - * Parsing this does something a little unusual - it 'extracts' the predicate when it's first parsed, which - * allows us to reevaluate it to see if we should break out of the while yet. This is required, to make sure - * we're observing changes to the context between the iterations of the loop. - */ - opcode(opcode::DEF_WHILE_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefWhile", - pkg_length() - .then(extract(term_arg())) - .feed(move |(length, (first_predicate, predicate_stream))| { - take_to_end_of_pkglength(length) - .map(move |body| Ok((first_predicate.clone(), predicate_stream, body))) - }) - .map_with_context(|(first_predicate, predicate_stream, body), mut context| { - if !try_with_context!(context, first_predicate.as_bool(context)) { - return (Ok(()), context); - } - - loop { - match term_list(PkgLength::from_raw_length(body, body.len() as u32).unwrap()) - .parse(body, context) - { - Ok((_, new_context, _result)) => { - context = new_context; - } - Err((_, new_context, Propagate::Break)) => { - context = new_context; - break; - } - Err((_, new_context, Propagate::Continue)) => { - // We don't need to do anything special here - the `Propagate::Continue` bubbles - // up, and then we can just move on to checking the predicate for the next - // iteration. - context = new_context; - } - Err((_, context, err)) => return (Err(err), context), - } - - // Reevaluate the predicate to see if we should break out of the loop yet - let predicate = - match comment_scope(DebugVerbosity::AllScopes, "WhilePredicate", term_arg()) - .parse(predicate_stream, context) - { - Ok((_, new_context, result)) => { - context = new_context; - try_with_context!(context, result.as_bool(context)) - } - Err((_, context, err)) => return (Err(err), context), - }; - - if !predicate { - break; - } - } - - (Ok(()), context) - }), - )) - .discard_result() -} diff --git a/aml/src/term_object.rs b/aml/src/term_object.rs deleted file mode 100644 index 4ddb14f0..00000000 --- a/aml/src/term_object.rs +++ /dev/null @@ -1,1094 +0,0 @@ -use crate::{ - expression::{def_buffer, def_package, expression_opcode}, - misc::{arg_obj, local_obj}, - name_object::{name_seg, name_string, target, Target}, - namespace::{AmlName, LevelType}, - opcode::{self, ext_opcode, opcode}, - opregion::{OpRegion, RegionSpace}, - parser::{ - choice, - comment_scope, - take, - take_to_end_of_pkglength, - take_u16, - take_u32, - take_u64, - try_with_context, - ParseResult, - Parser, - Propagate, - }, - pkg_length::{pkg_length, region_pkg_length, PkgLength}, - statement::statement_opcode, - value::{AmlValue, FieldFlags, MethodCode, MethodFlags}, - AmlContext, - AmlError, - AmlHandle, - DebugVerbosity, -}; -use alloc::{string::String, sync::Arc, vec::Vec}; -use core::str; - -/// `TermList`s are usually found within explicit-length objects (so they have a `PkgLength` -/// elsewhere in the structure), so this takes a number of bytes to parse. -pub fn term_list<'a, 'c>(list_length: PkgLength) -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * TermList := Nothing | - */ - // TODO: why does this use still_parsing, instead of just taking the whole thing and parsing it til it's empty? - move |mut input: &'a [u8], mut context: &'c mut AmlContext| { - while list_length.still_parsing(input) { - // TODO: currently, we ignore the value of the expression. We may need to propagate - // this. - let (new_input, new_context, _) = term_object().parse(input, context)?; - input = new_input; - context = new_context; - } - - Ok((input, context, ())) - } -} - -pub fn term_object<'a, 'c>() -> impl Parser<'a, 'c, Option> -where - 'c: 'a, -{ - /* - * TermObj := NamespaceModifierObj | NamedObj | StatementOpcode | ExpressionOpcode - */ - comment_scope( - DebugVerbosity::AllScopes, - "TermObj", - choice!( - namespace_modifier().map(|()| Ok(None)), - named_obj().map(|()| Ok(None)), - statement_opcode().map(|()| Ok(None)), - expression_opcode().map(|value| Ok(Some(value))) - ), - ) -} - -pub fn namespace_modifier<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * NamespaceModifierObj := DefAlias | DefName | DefScope - */ - choice!(def_alias(), def_name(), def_scope()) -} - -pub fn named_obj<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * NamedObj := DefBankField | DefCreateBitField | DefCreateByteField | DefCreateWordField | DefCreateDWordField | - * DefCreateQWordField | DefCreateField | DefDataRegion | DefExternal | DefOpRegion | DefPowerRes | - * DefProcessor | DefThermalZone | DefMethod | DefMutex - * - * XXX: DefMethod and DefMutex (at least) are not included in any rule in the AML grammar, - * but are defined in the NamedObj section so we assume they're part of NamedObj - */ - comment_scope( - DebugVerbosity::AllScopes, - "NamedObj", - choice!( - def_create_bit_field(), - def_create_byte_field(), - def_create_word_field(), - def_create_dword_field(), - def_create_qword_field(), - def_create_field(), - def_op_region(), - def_field(), - def_method(), - def_external(), - def_device(), - def_processor(), - def_power_res(), - def_thermal_zone(), - def_mutex() - ), - ) -} - -pub fn def_name<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefName := 0x08 NameString DataRefObject - */ - opcode(opcode::DEF_NAME_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefName", - name_string().then(data_ref_object()).map_with_context(|(name, data_ref_object), context| { - try_with_context!( - context, - context.namespace.add_value_at_resolved_path(name, &context.current_scope, data_ref_object) - ); - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_alias<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefAlias := 0x06 NameString NameString - * The second name refers to the same object as the first - */ - opcode(opcode::DEF_ALIAS_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefAlias", - name_string().then(name_string()).map_with_context(|(target, alias), context| { - try_with_context!( - context, - context.namespace.add_alias_at_resolved_path(alias, &context.current_scope, target) - ); - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_scope<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefScope := 0x10 PkgLength NameString TermList - */ - opcode(opcode::DEF_SCOPE_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefScope", - pkg_length() - .then(name_string()) - .map_with_context(|(length, name), context| { - let previous_scope = context.current_scope.clone(); - context.current_scope = try_with_context!(context, name.resolve(&context.current_scope)); - - context.comment( - DebugVerbosity::Scopes, - &(String::from("Scope name: ") + &context.current_scope.as_string()), - ); - - try_with_context!( - context, - context.namespace.add_level(context.current_scope.clone(), LevelType::Scope) - ); - - (Ok((length, previous_scope)), context) - }) - .feed(|(pkg_length, previous_scope)| { - term_list(pkg_length).map(move |_| Ok(previous_scope.clone())) - }) - .map_with_context(|previous_scope, context| { - context.current_scope = previous_scope; - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_create_bit_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateBitField := 0x8d SourceBuf BitIndex NameString - * SourceBuf := TermArg => Buffer - * BitIndex := TermArg => Integer - */ - opcode(opcode::DEF_CREATE_BIT_FIELD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefCreateBitField", - term_arg().then(term_arg()).then(name_string()).map_with_context( - |((source, index), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index, length: 1 } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_create_byte_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateByteField := 0x8c SourceBuf ByteIndex NameString - * SourceBuf := TermArg => Buffer - * ByteIndex := TermArg => Integer - */ - opcode(opcode::DEF_CREATE_BYTE_FIELD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefCreateByteField", - term_arg().then(term_arg()).then(name_string()).map_with_context( - |((source, index), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index * 8, length: 8 } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_create_word_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateWordField := 0x8b SourceBuf ByteIndex NameString - * SourceBuf := TermArg => Buffer - * ByteIndex := TermArg => Integer - */ - opcode(opcode::DEF_CREATE_WORD_FIELD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefCreateWordField", - term_arg().then(term_arg()).then(name_string()).map_with_context( - |((source, index), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index * 8, length: 16 } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_create_dword_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateDWordField := 0x8a SourceBuf ByteIndex NameString - * SourceBuf := TermArg => Buffer - * ByteIndex := TermArg => Integer - */ - opcode(opcode::DEF_CREATE_DWORD_FIELD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefCreateDWordField", - term_arg().then(term_arg()).then(name_string()).map_with_context( - |((source, index), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index * 8, length: 32 } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_create_qword_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateQWordField := 0x8f SourceBuf ByteIndex NameString - * SourceBuf := TermArg => Buffer - * ByteIndex := TermArg => Integer - */ - opcode(opcode::DEF_CREATE_QWORD_FIELD_OP) - .then(comment_scope( - DebugVerbosity::AllScopes, - "DefCreateQWordField", - term_arg().then(term_arg()).then(name_string()).map_with_context( - |((source, index), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index * 8, length: 64 } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_create_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefCreateField := ExtOpPrefix 0x13 SourceBuf BitIndex NumBits NameString - * SourceBuf := TermArg => Buffer - * BitIndex := TermArg => Integer - * NumBits := TermArg => Integer - */ - ext_opcode(opcode::EXT_DEF_CREATE_FIELD_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefCreateField", - term_arg().then(term_arg()).then(term_arg()).then(name_string()).map_with_context( - |(((source, index), num_bits), name), context| { - let source_data: Arc>> = - try_with_context!(context, source.as_buffer(context)).clone(); - let index = try_with_context!(context, index.as_integer(context)); - let num_bits = try_with_context!(context, num_bits.as_integer(context)); - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::BufferField { buffer_data: source_data, offset: index, length: num_bits } - ) - ); - - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_op_region<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefOpRegion := ExtOpPrefix 0x80 NameString RegionSpace RegionOffset RegionLen - * RegionSpace := ByteData (where 0x00 = SystemMemory - * 0x01 = SystemIO - * 0x02 = PciConfig - * 0x03 = EmbeddedControl - * 0x04 = SMBus - * 0x05 = SystemCMOS - * 0x06 = PciBarTarget - * 0x07 = IPMI - * 0x08 = GeneralPurposeIO - * 0x09 = GenericSerialBus - * 0x80-0xff = OEM Defined) - * ByteData := 0x00 - 0xff - * RegionOffset := TermArg => Integer - * RegionLen := TermArg => Integer - */ - ext_opcode(opcode::EXT_DEF_OP_REGION_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefOpRegion", - name_string().then(take()).then(term_arg()).then(term_arg()).map_with_context( - |(((name, space), offset), length), context| { - let region = match space { - 0x00 => RegionSpace::SystemMemory, - 0x01 => RegionSpace::SystemIo, - 0x02 => RegionSpace::PciConfig, - 0x03 => RegionSpace::EmbeddedControl, - 0x04 => RegionSpace::SMBus, - 0x05 => RegionSpace::SystemCmos, - 0x06 => RegionSpace::PciBarTarget, - 0x07 => RegionSpace::IPMI, - 0x08 => RegionSpace::GeneralPurposeIo, - 0x09 => RegionSpace::GenericSerialBus, - space @ 0x80..=0xff => RegionSpace::OemDefined(space), - byte => return (Err(Propagate::Err(AmlError::InvalidRegionSpace(byte))), context), - }; - let offset = match offset.as_integer(context) { - Ok(offset) => offset, - Err(err) => return (Err(Propagate::Err(err)), context), - }; - let length = match length.as_integer(context) { - Ok(length) => length, - Err(err) => return (Err(Propagate::Err(err)), context), - }; - let parent_device = match region { - RegionSpace::PciConfig | RegionSpace::IPMI | RegionSpace::GenericSerialBus => { - let resolved_path = try_with_context!(context, name.resolve(&context.current_scope)); - Some(try_with_context!(context, resolved_path.parent())) - } - _ => None, - }; - - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::OpRegion(OpRegion::new(region, offset, length, parent_device)) - ) - ); - (Ok(()), context) - }, - ), - )) - .discard_result() -} - -pub fn def_field<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefField = ExtOpPrefix 0x81 PkgLength NameString FieldFlags FieldList - * FieldFlags := ByteData - */ - let opregion_as_handle = name_string().map_with_context(|region_name, context| { - /* - * We search for the opregion that this field is referencing here as we already have the correct starting - * scope. If we leave this to later, it becomes much harder as we also need to know the field's scope. - */ - let (_, handle) = - try_with_context!(context, context.namespace.search(®ion_name, &context.current_scope)); - (Ok(handle), context) - }); - - ext_opcode(opcode::EXT_DEF_FIELD_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefField", - pkg_length().then(opregion_as_handle).then(take()).feed(|((list_length, region_handle), flags)| { - move |mut input: &'a [u8], mut context: &'c mut AmlContext| -> ParseResult<'a, 'c, ()> { - /* - * FieldList := Nothing | - */ - // TODO: can this pattern be expressed as a combinator - let mut current_offset = 0; - while list_length.still_parsing(input) { - let (new_input, new_context, field_length) = - field_element(region_handle, FieldFlags::new(flags), current_offset) - .parse(input, context)?; - input = new_input; - context = new_context; - current_offset += field_length; - } - - Ok((input, context, ())) - } - }), - )) - .discard_result() -} - -/// Parses a `FieldElement`. Takes the current offset within the field list, and returns the length -/// of the field element parsed. -pub fn field_element<'a, 'c>( - region_handle: AmlHandle, - flags: FieldFlags, - current_offset: u64, -) -> impl Parser<'a, 'c, u64> -where - 'c: 'a, -{ - /* - * FieldElement := NamedField | ReservedField | AccessField | ExtendedAccessField | - * ConnectField - * NamedField := NameSeg PkgLength - * ReservedField := 0x00 PkgLength - * AccessField := 0x01 AccessType AccessAttrib - * ConnectField := <0x02 NameString> | <0x02 BufferData> - * ExtendedAccessField := 0x03 AccessType ExtendedAccessAttrib AccessLength - * - * AccessType := ByteData - * AccessAttrib := ByteData - * - * XXX: The spec says a ConnectField can be <0x02 BufferData>, but BufferData isn't an AML - * object (it seems to be defined in ASL). We treat BufferData as if it was encoded like - * DefBuffer, and this seems to work so far. - */ - // TODO: parse ConnectField and ExtendedAccessField - - /* - * Reserved fields shouldn't actually be added to the namespace; they seem to show gaps in - * the operation region that aren't used for anything. - */ - let reserved_field = opcode(opcode::RESERVED_FIELD) - .then(region_pkg_length(region_handle)) - .map(|((), length)| Ok(length.raw_length as u64)); - - // TODO: work out what to do with an access field - // let access_field = opcode(opcode::ACCESS_FIELD) - // .then(take()) - // .then(take()) - // .map_with_context(|(((), access_type), access_attrib), context| (Ok( , context)); - - // TODO: fields' start and end offsets need to be checked against their enclosing - // OperationRegions to make sure they don't sit outside or cross the boundary. - // This might not be a problem if a sane ASL compiler is used (which should check this - // at compile-time), but it's better to be safe and validate that as well. - - let named_field = - name_seg().then(region_pkg_length(region_handle)).map_with_context(move |(name_seg, length), context| { - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - AmlName::from_name_seg(name_seg), - &context.current_scope, - AmlValue::Field { - region: region_handle, - flags, - offset: current_offset, - length: length.raw_length as u64, - }, - ) - ); - - (Ok(length.raw_length as u64), context) - }); - - choice!(reserved_field, named_field) -} - -pub fn def_method<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefMethod := 0x14 PkgLength NameString MethodFlags TermList - * MethodFlags := ByteData (where bits 0-2: ArgCount (0 to 7) - * bit 3: SerializeFlag (0 = Not Serialized, 1 = Serialized) - * bits 4-7: SyncLevel (0x00 to 0x0f)) - */ - opcode(opcode::DEF_METHOD_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefMethod", - pkg_length() - .then(name_string()) - .then(take()) - .feed(|((length, name), flags)| { - take_to_end_of_pkglength(length).map(move |code| Ok((name.clone(), flags, code))) - }) - .map_with_context(|(name, flags, code), context| { - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::Method { - flags: MethodFlags::from(flags), - code: MethodCode::Aml(code.to_vec()) - }, - ) - ); - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_external<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefExternal = 0x15 NameString ObjectType ArgumentCount - * ObjectType := ByteData - * ArgumentCount := ByteData (0 to 7) - */ - opcode(opcode::DEF_EXTERNAL_OP) - .then(comment_scope(DebugVerbosity::Scopes, "DefExternal", name_string().then(take()).then(take()))) - .discard_result() -} - -pub fn def_device<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefDevice := ExtOpPrefix 0x82 PkgLength NameString TermList - */ - ext_opcode(opcode::EXT_DEF_DEVICE_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefDevice", - pkg_length() - .then(name_string()) - .map_with_context(|(length, name), context| { - let resolved_name = try_with_context!(context, name.resolve(&context.current_scope)); - try_with_context!( - context, - context.namespace.add_value(resolved_name.clone(), AmlValue::Device) - ); - try_with_context!( - context, - context.namespace.add_level(resolved_name.clone(), LevelType::Device) - ); - - let previous_scope = context.current_scope.clone(); - context.current_scope = resolved_name; - - (Ok((length, previous_scope)), context) - }) - .feed(|(length, previous_scope)| term_list(length).map(move |_| Ok(previous_scope.clone()))) - .map_with_context(|previous_scope, context| { - context.current_scope = previous_scope; - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_processor<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefProcessor := ExtOpPrefix 0x83 PkgLength NameString ProcID PblkAddress PblkLen TermList - * ProcID := ByteData - * PblkAddress := DWordData - * PblkLen := ByteData - */ - ext_opcode(opcode::EXT_DEF_PROCESSOR_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefProcessor", - pkg_length() - .then(name_string()) - .then(take()) - .then(take_u32()) - .then(take()) - .map_with_context(|((((pkg_length, name), proc_id), pblk_address), pblk_len), context| { - /* - * Legacy `Processor` objects contain data within themselves, and can also have sub-objects, - * so we add both a level for the sub-objects, and a value for the data. - */ - let resolved_name = try_with_context!(context, name.resolve(&context.current_scope)); - try_with_context!( - context, - context.namespace.add_level(resolved_name.clone(), LevelType::Processor) - ); - try_with_context!( - context, - context.namespace.add_value( - resolved_name.clone(), - AmlValue::Processor { id: proc_id, pblk_address, pblk_len } - ) - ); - let previous_scope = context.current_scope.clone(); - context.current_scope = resolved_name; - - (Ok((previous_scope, pkg_length)), context) - }) - .feed(move |(previous_scope, pkg_length)| { - term_list(pkg_length).map(move |_| Ok(previous_scope.clone())) - }) - .map_with_context(|previous_scope, context| { - context.current_scope = previous_scope; - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_power_res<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefPowerRes := ExtOpPrefix 0x84 PkgLength NameString SystemLevel ResourceOrder TermList - * SystemLevel := ByteData - * ResourceOrder := WordData - */ - ext_opcode(opcode::EXT_DEF_POWER_RES_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefPowerRes", - pkg_length() - .then(name_string()) - .then(take()) - .then(take_u16()) - .map_with_context(|(((pkg_length, name), system_level), resource_order), context| { - /* - * `PowerResource` objects contain data within themselves, and can also have sub-objects, - * so we add both a level for the sub-objects, and a value for the data. - */ - let resolved_name = try_with_context!(context, name.resolve(&context.current_scope)); - try_with_context!( - context, - context.namespace.add_level(resolved_name.clone(), LevelType::PowerResource) - ); - try_with_context!( - context, - context.namespace.add_value( - resolved_name.clone(), - AmlValue::PowerResource { system_level, resource_order } - ) - ); - let previous_scope = context.current_scope.clone(); - context.current_scope = resolved_name; - - (Ok((previous_scope, pkg_length)), context) - }) - .feed(move |(previous_scope, pkg_length)| { - term_list(pkg_length).map(move |_| Ok(previous_scope.clone())) - }) - .map_with_context(|previous_scope, context| { - context.current_scope = previous_scope; - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_thermal_zone<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefThermalZone := ExtOpPrefix 0x85 PkgLength NameString TermList - * TODO: we use this pattern a lot (move into scope, parse a term_list, move back out). Could we simplify into - * just a `feed` by passing a scope into term_list? - */ - ext_opcode(opcode::EXT_DEF_THERMAL_ZONE_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefThermalZone", - pkg_length() - .then(name_string()) - .map_with_context(|(pkg_length, name), context| { - let resolved_name = try_with_context!(context, name.resolve(&context.current_scope)); - try_with_context!( - context, - context.namespace.add_value(resolved_name.clone(), AmlValue::ThermalZone) - ); - try_with_context!( - context, - context.namespace.add_level(resolved_name.clone(), LevelType::ThermalZone) - ); - - let previous_scope = context.current_scope.clone(); - context.current_scope = resolved_name; - - (Ok((pkg_length, previous_scope)), context) - }) - .feed(|(length, previous_scope)| term_list(length).map(move |_| Ok(previous_scope.clone()))) - .map_with_context(|previous_scope, context| { - context.current_scope = previous_scope; - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_mutex<'a, 'c>() -> impl Parser<'a, 'c, ()> -where - 'c: 'a, -{ - /* - * DefMutex := ExtOpPrefix 0x01 NameString SyncFlags - * SyncFlags := ByteData (where bits 0-3: SyncLevel - * bits 4-7: Reserved) - */ - ext_opcode(opcode::EXT_DEF_MUTEX_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefMutex", - name_string().then(take()).map_with_context(|(name, sync_level), context| { - try_with_context!( - context, - context.namespace.add_value_at_resolved_path( - name, - &context.current_scope, - AmlValue::Mutex { sync_level } - ) - ); - (Ok(()), context) - }), - )) - .discard_result() -} - -pub fn def_cond_ref_of<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DefCondRefOf := ExtOpPrefix 0x12 NameString Target => boolean - */ - ext_opcode(opcode::EXT_DEF_COND_REF_OF_OP) - .then(comment_scope( - DebugVerbosity::Scopes, - "DefCondRefOf", - name_string().then(target()).map_with_context(|(source, target), context| { - let handle = context.namespace.search(&source, &context.current_scope); - let result = AmlValue::Boolean(handle.is_ok()); - if let Ok((_name, _handle)) = handle { - match target { - Target::Null => { /* just return the result of the check */ } - _ => return (Err(Propagate::Err(AmlError::Unimplemented)), context), - } - } - (Ok(result), context) - }), - )) - .map(|((), result)| Ok(result)) -} - -pub fn term_arg<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * TermArg := ExpressionOpcode | DataObject | ArgObj | LocalObj - */ - comment_scope( - DebugVerbosity::AllScopes, - "TermArg", - choice!( - data_object(), - arg_obj().map_with_context(|arg_num, context| { - (Ok(try_with_context!(context, context.current_arg(arg_num)).clone()), context) - }), - local_obj().map_with_context(|local_num, context| { - (Ok(try_with_context!(context, context.local(local_num)).clone()), context) - }), - expression_opcode() - ), - ) -} - -pub fn data_ref_object<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DataRefObject := DataObject | ObjectReference | DDBHandle - */ - comment_scope(DebugVerbosity::AllScopes, "DataRefObject", choice!(data_object())) -} - -pub fn data_object<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * DataObject := DefPackage | DefVarPackage | ComputationalData - * - * The order of the parsers are important here, as DefPackage and DefVarPackage can be - * accidently parsed as ComputationalDatas. - */ - // TODO: this doesn't yet parse DefVarPackage - comment_scope(DebugVerbosity::AllScopes, "DataObject", choice!(def_package(), computational_data())) -} - -pub fn computational_data<'a, 'c>() -> impl Parser<'a, 'c, AmlValue> -where - 'c: 'a, -{ - /* - * ComputationalData := ByteConst | WordConst | DWordConst | QWordConst | String | - * ConstObj | RevisionOp | DefBuffer - * ByteConst := 0x0a ByteData - * WordConst := 0x0b WordData - * DWordConst := 0x0c DWordData - * QWordConst := 0x0e QWordData - * String := 0x0d AsciiCharList NullChar - * ConstObj := ZeroOp(0x00) | OneOp(0x01) | OnesOp(0xff) - * RevisionOp := ExtOpPrefix(0x5b) 0x30 - */ - let const_parser = |input: &'a [u8], context: &'c mut AmlContext| { - let string_parser = |input: &'a [u8], context| -> ParseResult<'a, 'c, AmlValue> { - /* - * Using `position` isn't very efficient here, but is probably fine because the - * strings are usually quite short. - */ - let nul_position = match input.iter().position(|&c| c == b'\0') { - Some(position) => position, - None => return Err((input, context, Propagate::Err(AmlError::UnterminatedStringConstant))), - }; - - let string = String::from(match str::from_utf8(&input[0..nul_position]) { - Ok(string) => string, - Err(_) => return Err((input, context, Propagate::Err(AmlError::InvalidStringConstant))), - }); - - Ok((&input[(nul_position + 1)..], context, AmlValue::String(string))) - }; - - let (new_input, context, op) = take().parse(input, context)?; - match op { - opcode::BYTE_CONST => { - take().map(|value| Ok(AmlValue::Integer(value as u64))).parse(new_input, context) - } - opcode::WORD_CONST => { - take_u16().map(|value| Ok(AmlValue::Integer(value as u64))).parse(new_input, context) - } - opcode::DWORD_CONST => { - take_u32().map(|value| Ok(AmlValue::Integer(value as u64))).parse(new_input, context) - } - opcode::QWORD_CONST => take_u64().map(|value| Ok(AmlValue::Integer(value))).parse(new_input, context), - opcode::STRING_PREFIX => string_parser.parse(new_input, context), - opcode::ZERO_OP => Ok((new_input, context, AmlValue::zero())), - opcode::ONE_OP => Ok((new_input, context, AmlValue::one())), - opcode::ONES_OP => Ok((new_input, context, AmlValue::ones())), - - _ => Err((input, context, Propagate::Err(AmlError::WrongParser))), - } - }; - - comment_scope( - DebugVerbosity::AllScopes, - "ComputationalData", - choice!( - ext_opcode(opcode::EXT_REVISION_OP).map(|_| Ok(AmlValue::Integer(crate::AML_INTERPRETER_REVISION))), - const_parser, - def_buffer() - ), - ) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::test_utils::*; - - #[test] - fn test_package() { - let mut context = make_test_context(); - - // The tests also check that DefPackage consumes no more input than required - // Empty DefPackage - check_ok_value!( - def_package().parse(&[0x12, 0x02, 0x00, 0x12, 0x34], &mut context), - AmlValue::Package(Vec::new()), - &[0x12, 0x34] - ); - - // DefPackage where NumElements == package_content.len() - check_ok_value!( - def_package() - .parse(&[0x12, 0x09, 0x04, 0x01, 0x0A, 0x02, 0x0A, 0x03, 0x0A, 0x04, 0x12, 0x34], &mut context), - AmlValue::Package(alloc::vec![ - AmlValue::Integer(1), - AmlValue::Integer(2), - AmlValue::Integer(3), - AmlValue::Integer(4) - ]), - &[0x12, 0x34] - ); - - // DefPackage where NumElements > package_content.len() - check_ok_value!( - def_package().parse(&[0x012, 0x05, 0x04, 0x01, 0x0A, 0x02, 0x12, 0x34], &mut context), - AmlValue::Package(alloc::vec![ - AmlValue::Integer(1), - AmlValue::Integer(2), - AmlValue::Uninitialized, - AmlValue::Uninitialized, - ]), - &[0x12, 0x34] - ); - } - - #[test] - fn test_computational_data() { - let mut context = make_test_context(); - check_ok_value!( - computational_data().parse(&[0x00, 0x34, 0x12], &mut context), - AmlValue::Integer(0), - &[0x34, 0x12] - ); - check_ok_value!( - computational_data().parse(&[0x01, 0x18, 0xf3], &mut context), - AmlValue::Integer(1), - &[0x18, 0xf3] - ); - check_ok_value!( - computational_data().parse(&[0xff, 0x98, 0xc3], &mut context), - AmlValue::Integer(u64::MAX), - &[0x98, 0xc3] - ); - check_ok_value!( - computational_data().parse(&[0x5b, 0x30], &mut context), - AmlValue::Integer(crate::AML_INTERPRETER_REVISION), - &[] - ); - check_ok_value!( - computational_data().parse(&[0x0a, 0xf3, 0x35], &mut context), - AmlValue::Integer(0xf3), - &[0x35] - ); - check_ok_value!( - computational_data().parse(&[0x0b, 0xf3, 0x35], &mut context), - AmlValue::Integer(0x35f3), - &[] - ); - check_ok_value!( - computational_data().parse(&[0x0c, 0xf3, 0x35, 0x12, 0x65, 0xff, 0x00], &mut context), - AmlValue::Integer(0x651235f3), - &[0xff, 0x00] - ); - check_ok_value!( - computational_data() - .parse(&[0x0e, 0xf3, 0x35, 0x12, 0x65, 0xff, 0x00, 0x67, 0xde, 0x28], &mut context), - AmlValue::Integer(0xde6700ff651235f3), - &[0x28] - ); - check_ok_value!( - computational_data().parse(&[0x0d, b'A', b'B', b'C', b'D', b'\0', 0xff, 0xf5], &mut context), - AmlValue::String(String::from("ABCD")), - &[0xff, 0xf5] - ); - } -} diff --git a/aml/src/test_utils.rs b/aml/src/test_utils.rs deleted file mode 100644 index 0664ca44..00000000 --- a/aml/src/test_utils.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::{parser::Propagate, AmlContext, AmlValue, Handler}; -use alloc::boxed::Box; - -struct TestHandler; - -impl Handler for TestHandler { - fn read_u8(&self, _address: usize) -> u8 { - unimplemented!() - } - fn read_u16(&self, _address: usize) -> u16 { - unimplemented!() - } - fn read_u32(&self, _address: usize) -> u32 { - unimplemented!() - } - fn read_u64(&self, _address: usize) -> u64 { - unimplemented!() - } - - fn write_u8(&mut self, _address: usize, _value: u8) { - unimplemented!() - } - fn write_u16(&mut self, _address: usize, _value: u16) { - unimplemented!() - } - fn write_u32(&mut self, _address: usize, _value: u32) { - unimplemented!() - } - fn write_u64(&mut self, _address: usize, _value: u64) { - unimplemented!() - } - - fn read_io_u8(&self, _port: u16) -> u8 { - unimplemented!() - } - fn read_io_u16(&self, _port: u16) -> u16 { - unimplemented!() - } - fn read_io_u32(&self, _port: u16) -> u32 { - unimplemented!() - } - - fn write_io_u8(&self, _port: u16, _value: u8) { - unimplemented!() - } - fn write_io_u16(&self, _port: u16, _value: u16) { - unimplemented!() - } - fn write_io_u32(&self, _port: u16, _value: u32) { - unimplemented!() - } - - fn read_pci_u8(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u8 { - unimplemented!() - } - fn read_pci_u16(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u16 { - unimplemented!() - } - fn read_pci_u32(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16) -> u32 { - unimplemented!() - } - fn write_pci_u8(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u8) { - unimplemented!() - } - fn write_pci_u16(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u16) { - unimplemented!() - } - fn write_pci_u32(&self, _segment: u16, _bus: u8, _device: u8, _function: u8, _offset: u16, _value: u32) { - unimplemented!() - } - fn stall(&self, _microseconds: u64) { - unimplemented!() - } - fn sleep(&self, _milliseconds: u64) { - unimplemented!() - } -} - -pub(crate) fn make_test_context() -> AmlContext { - AmlContext::new(Box::new(TestHandler), crate::DebugVerbosity::None) -} - -pub(crate) macro check_err($parse: expr, $error: pat, $remains: expr) { - match $parse { - Ok((remains, _, result)) => panic!("Expected Err, got {:#?}. Remaining = {:#x?}", result, remains), - Err((remains, _, Propagate::Err($error))) if *remains == *$remains => (), - Err((remains, _, Propagate::Err($error))) => { - panic!("Correct error, incorrect stream returned: {:#x?}", remains) - } - Err((_, _, err)) => panic!("Got wrong error: {:?}", err), - } -} - -pub(crate) macro check_ok($parse: expr, $expected: expr, $remains: expr) { - match $parse { - Ok((remains, _, ref result)) if remains == *$remains && result == &$expected => (), - Ok((remains, _, ref result)) if result == &$expected => { - panic!("Correct result, incorrect slice returned: {:x?}", remains) - } - Ok((_, _, ref result)) => panic!("Successfully parsed Ok, but it was wrong: {:#?}", result), - Err((_, _, err)) => panic!("Expected Ok, got {:#?}", err), - } -} - -pub(crate) macro check_ok_value($parse: expr, $expected: expr, $remains: expr) { - match $parse { - Ok((remains, _, ref result)) if remains == *$remains && crudely_cmp_values(result, &$expected) => (), - Ok((remains, _, ref result)) if crudely_cmp_values(result, &$expected) => { - panic!("Correct result, incorrect slice returned: {:x?}", remains) - } - Ok((_, _, ref result)) => panic!("Successfully parsed Ok, but it was wrong: {:#?}", result), - Err((_, _, err)) => panic!("Expected Ok, got {:#?}", err), - } -} - -/// This is a bad (but good for testing) way of comparing `AmlValue`s, which tests that they're exactly the same if -/// it can, and gives up if it can't. It's useful in tests to be able to see if you're getting the `AmlValue` that -/// you're expecting. -/// -/// NOTE: almost all of the `AmlValue` variants are `Eq`, and so for a long time, `AmlValue` implemented `Eq`. -/// However, this is a footgun as, in the real interpreter, you rarely want to directly compare values, as you need -/// to apply the AML value conversion rules to compare them correctly. This is therefore only useful for artificial -/// testing scenarios. -pub(crate) fn crudely_cmp_values(a: &AmlValue, b: &AmlValue) -> bool { - use crate::value::MethodCode; - - match a { - AmlValue::Uninitialized => matches!(b, AmlValue::Uninitialized), - AmlValue::Boolean(a) => match b { - AmlValue::Boolean(b) => a == b, - _ => false, - }, - AmlValue::Integer(a) => match b { - AmlValue::Integer(b) => a == b, - _ => false, - }, - AmlValue::String(ref a) => match b { - AmlValue::String(ref b) => a == b, - _ => false, - }, - AmlValue::OpRegion(_) => match b { - AmlValue::OpRegion(_) => panic!("Can't compare two op-regions"), - _ => false, - }, - AmlValue::Field { region, flags, offset, length } => match b { - AmlValue::Field { region: b_region, flags: b_flags, offset: b_offset, length: b_length } => { - region == b_region && flags == b_flags && offset == b_offset && length == b_length - } - _ => false, - }, - AmlValue::Device => matches!(b, AmlValue::Device), - AmlValue::Method { flags, code } => match b { - AmlValue::Method { flags: b_flags, code: b_code } => { - if flags != b_flags { - return false; - } - - match (code, b_code) { - (MethodCode::Aml(a), MethodCode::Aml(b)) => a == b, - (MethodCode::Aml(_), MethodCode::Native(_)) => false, - (MethodCode::Native(_), MethodCode::Aml(_)) => false, - (MethodCode::Native(_), MethodCode::Native(_)) => panic!("Can't compare two native methods"), - } - } - _ => false, - }, - AmlValue::Buffer(a) => match b { - AmlValue::Buffer(b) => *a.lock() == *b.lock(), - _ => false, - }, - AmlValue::BufferField { buffer_data, offset, length } => match b { - AmlValue::BufferField { buffer_data: b_buffer_data, offset: b_offset, length: b_length } => { - alloc::sync::Arc::as_ptr(buffer_data) == alloc::sync::Arc::as_ptr(b_buffer_data) - && offset == b_offset - && length == b_length - } - _ => false, - }, - AmlValue::Processor { id, pblk_address, pblk_len } => match b { - AmlValue::Processor { id: b_id, pblk_address: b_pblk_address, pblk_len: b_pblk_len } => { - id == b_id && pblk_address == b_pblk_address && pblk_len == b_pblk_len - } - _ => false, - }, - AmlValue::Mutex { sync_level } => match b { - AmlValue::Mutex { sync_level: b_sync_level } => sync_level == b_sync_level, - _ => false, - }, - AmlValue::Package(a) => match b { - AmlValue::Package(b) => { - for (a, b) in a.iter().zip(b) { - if !crudely_cmp_values(a, b) { - return false; - } - } - - true - } - _ => false, - }, - AmlValue::PowerResource { system_level, resource_order } => match b { - AmlValue::PowerResource { system_level: b_system_level, resource_order: b_resource_order } => { - system_level == b_system_level && resource_order == b_resource_order - } - _ => false, - }, - AmlValue::ThermalZone => matches!(b, AmlValue::ThermalZone), - } -} diff --git a/aml/src/value.rs b/aml/src/value.rs deleted file mode 100644 index 4da19842..00000000 --- a/aml/src/value.rs +++ /dev/null @@ -1,601 +0,0 @@ -use crate::{misc::ArgNum, opregion::OpRegion, AmlContext, AmlError, AmlHandle}; -use alloc::{ - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; -use bit_field::BitField; -use core::{cmp, fmt, fmt::Debug}; -use spinning_top::Spinlock; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum FieldAccessType { - Any, - Byte, - Word, - DWord, - QWord, - Buffer, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum FieldUpdateRule { - Preserve, - WriteAsOnes, - WriteAsZeros, -} - -// TODO: custom debug impl -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct FieldFlags(u8); - -impl FieldFlags { - pub fn new(value: u8) -> FieldFlags { - FieldFlags(value) - } - - pub fn access_type(&self) -> Result { - match self.0.get_bits(0..4) { - 0 => Ok(FieldAccessType::Any), - 1 => Ok(FieldAccessType::Byte), - 2 => Ok(FieldAccessType::Word), - 3 => Ok(FieldAccessType::DWord), - 4 => Ok(FieldAccessType::QWord), - 5 => Ok(FieldAccessType::Buffer), - _ => Err(AmlError::InvalidFieldFlags), - } - } - - pub fn lock_rule(&self) -> bool { - self.0.get_bit(4) - } - - pub fn field_update_rule(&self) -> Result { - match self.0.get_bits(5..7) { - 0 => Ok(FieldUpdateRule::Preserve), - 1 => Ok(FieldUpdateRule::WriteAsOnes), - 2 => Ok(FieldUpdateRule::WriteAsZeros), - _ => Err(AmlError::InvalidFieldFlags), - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct MethodFlags(u8); - -impl MethodFlags { - pub fn new(arg_count: u8, serialize: bool, sync_level: u8) -> MethodFlags { - assert!(arg_count <= 7); - assert!(sync_level <= 15); - - let mut value = 0; - value.set_bits(0..3, arg_count); - value.set_bit(3, serialize); - value.set_bits(4..8, sync_level); - MethodFlags(value) - } - - pub fn from(value: u8) -> MethodFlags { - MethodFlags(value) - } - - pub fn arg_count(&self) -> u8 { - self.0.get_bits(0..3) - } - - pub fn serialize(&self) -> bool { - self.0.get_bit(3) - } - - pub fn sync_level(&self) -> u8 { - self.0.get_bits(4..8) - } -} - -/// Representation of the return value of a `_STA` method, which represents the status of an object. It must be -/// evaluated, if present, before evaluating the `_INI` method for an device. -/// -/// The `Default` implementation of this type is the correct value to use if a device doesn't have a `_STA` object -/// to evaluate. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct StatusObject { - /// Whether the device is physically present. If this is `false`, `enabled` should also be `false` (i.e. a - /// device that is not present can't be enabled). However, this is not enforced here if the firmware is doing - /// something wrong. - pub present: bool, - /// Whether the device is enabled. Both `present` and `enabled` must be `true` for the device to decode its - /// hardware resources. - pub enabled: bool, - pub show_in_ui: bool, - pub functional: bool, - /// Only applicable for Control Method Battery Devices (`PNP0C0A`). For all other devices, ignore this value. - pub battery_present: bool, -} - -impl Default for StatusObject { - fn default() -> Self { - StatusObject { present: true, enabled: true, show_in_ui: true, functional: true, battery_present: true } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum AmlType { - Uninitialized, - Buffer, - BufferField, - /// Handle to a definition block handle. Returned by the `Load` operator. - DdbHandle, - DebugObject, - Event, - FieldUnit, - Device, - Integer, - Method, - Mutex, - ObjReference, - OpRegion, - Package, - PowerResource, - Processor, - RawDataBuffer, - String, - ThermalZone, -} - -type NativeMethod = Arc Result + Send + Sync>; - -#[derive(Clone)] -pub enum MethodCode { - Aml(Vec), - Native(NativeMethod), -} - -impl fmt::Debug for MethodCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MethodCode::Aml(ref code) => write!(f, "AML({:x?})", code), - MethodCode::Native(_) => write!(f, "(native method)"), - } - } -} - -#[derive(Clone, Debug)] -pub enum AmlValue { - Uninitialized, - Boolean(bool), - Integer(u64), - String(String), - /// Describes an operation region. Some regions require other objects to be declared under their parent device - /// (e.g. an `_ADR` object for a `PciConfig` region), in which case an absolute path to the object is stored in - /// `parent_device`. - OpRegion(OpRegion), - /// Describes a field unit within an operation region. - Field { - region: AmlHandle, - flags: FieldFlags, - offset: u64, - length: u64, - }, - Device, - Method { - flags: MethodFlags, - code: MethodCode, - }, - Buffer(Arc>>), - BufferField { - buffer_data: Arc>>, - /// In bits. - offset: u64, - /// In bits. - length: u64, - }, - Processor { - id: u8, - pblk_address: u32, - pblk_len: u8, - }, - Mutex { - sync_level: u8, - }, - // TODO: I think this will need to be `Arc`ed as well, as `Index` can be used on both Buffers and Packages - Package(Vec), - PowerResource { - system_level: u8, - resource_order: u16, - }, - ThermalZone, -} - -impl AmlValue { - pub fn zero() -> AmlValue { - AmlValue::Integer(0) - } - - pub fn one() -> AmlValue { - AmlValue::Integer(1) - } - - pub fn ones() -> AmlValue { - AmlValue::Integer(u64::MAX) - } - - pub fn native_method(arg_count: u8, serialize: bool, sync_level: u8, f: F) -> AmlValue - where - F: (Fn(&mut AmlContext) -> Result) + 'static + Send + Sync, - { - let flags = MethodFlags::new(arg_count, serialize, sync_level); - AmlValue::Method { flags, code: MethodCode::Native(Arc::new(f)) } - } - - pub fn type_of(&self) -> AmlType { - match self { - AmlValue::Uninitialized => AmlType::Uninitialized, - AmlValue::Boolean(_) => AmlType::Integer, - AmlValue::Integer(_) => AmlType::Integer, - AmlValue::String(_) => AmlType::String, - AmlValue::OpRegion { .. } => AmlType::OpRegion, - AmlValue::Field { .. } => AmlType::FieldUnit, - AmlValue::Device => AmlType::Device, - AmlValue::Method { .. } => AmlType::Method, - AmlValue::Buffer(_) => AmlType::Buffer, - AmlValue::BufferField { .. } => AmlType::BufferField, - AmlValue::Processor { .. } => AmlType::Processor, - AmlValue::Mutex { .. } => AmlType::Mutex, - AmlValue::Package(_) => AmlType::Package, - AmlValue::PowerResource { .. } => AmlType::PowerResource, - AmlValue::ThermalZone => AmlType::ThermalZone, - } - } - - /// Returns the `SizeOf (x)` application result as specified in ACPI 6.2 §19.6.125 - pub fn size_of(&self) -> Result { - match self { - // For a buffer, returns the size in bytes of the data - AmlValue::Buffer(value) => Ok(value.lock().len() as u64), - // For a string, returns the size in bytes (without NULL) - AmlValue::String(value) => Ok(value.len() as u64), - // For a package, returns the number of elements - AmlValue::Package(value) => Ok(value.len() as u64), - // TODO: For an Object Reference, the size of the object is returned - // Other data types cause a fatal run-time error - _ => Err(AmlError::InvalidSizeOfApplication(self.type_of())), - } - } - - pub fn as_bool(&self, context: &mut AmlContext) -> Result { - match self { - AmlValue::Boolean(value) => Ok(*value), - AmlValue::Integer(value) => Ok(*value != 0), - AmlValue::Field { .. } => Ok(self.as_integer(context)? != 0), - _ => Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::Integer }), - } - } - - pub fn as_integer(&self, context: &mut AmlContext) -> Result { - match self { - AmlValue::Integer(value) => Ok(*value), - AmlValue::Boolean(value) => Ok(if *value { u64::MAX } else { 0 }), - AmlValue::Buffer(ref bytes) => { - /* - * "The first 8 bytes of the buffer are converted to an integer, taking the first - * byte as the least significant byte of the integer. A zero-length buffer is - * illegal." - §19.6.140 - * - * XXX: Buffers with length `0` appear in real tables, so we return `0` for them. - */ - let bytes = bytes.lock(); - let bytes = if bytes.len() > 8 { &bytes[0..8] } else { &bytes[..] }; - - Ok(bytes.iter().rev().fold(0u64, |mut i, &popped| { - i <<= 8; - i += popped as u64; - i - })) - } - /* - * Read from a field or buffer field. These can return either a `Buffer` or an `Integer`, so we make sure to call - * `as_integer` on the result. - */ - AmlValue::Field { .. } => self.read_field(context)?.as_integer(context), - AmlValue::BufferField { .. } => self.read_buffer_field(context)?.as_integer(context), - - _ => Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::Integer }), - } - } - - pub fn as_buffer(&self, context: &mut AmlContext) -> Result>>, AmlError> { - match self { - AmlValue::Buffer(ref bytes) => Ok(bytes.clone()), - // TODO: implement conversion of String and Integer to Buffer - AmlValue::Field { .. } => self.read_field(context)?.as_buffer(context), - AmlValue::BufferField { .. } => self.read_buffer_field(context)?.as_buffer(context), - _ => Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::Buffer }), - } - } - - pub fn as_string(&self, context: &mut AmlContext) -> Result { - match self { - AmlValue::String(ref string) => Ok(string.clone()), - // TODO: implement conversion of Buffer to String - AmlValue::Field { .. } => self.read_field(context)?.as_string(context), - _ => Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::String }), - } - } - - /// Converts an `AmlValue` to the representation that should be used when concatenating it with other values, - /// primarily by the `DefConcat` opcode. This will always produce a `AmlValue::Integer`, `AmlValue::String`, or - /// `AmlValue::Buffer`, with other types being converted to strings containing the name of their type. - pub fn as_concat_type(&self) -> AmlValue { - match self.type_of() { - AmlType::Integer => self.clone(), - AmlType::String => self.clone(), - AmlType::Buffer => self.clone(), - - AmlType::Uninitialized => AmlValue::String("[Uninitialized]".to_string()), - AmlType::BufferField => AmlValue::String("[Buffer Field]".to_string()), - AmlType::DdbHandle => AmlValue::String("[Ddb Handle]".to_string()), - AmlType::DebugObject => AmlValue::String("[Debug Object]".to_string()), - AmlType::Event => AmlValue::String("[Event]".to_string()), - AmlType::FieldUnit => AmlValue::String("[Field]".to_string()), - AmlType::Device => AmlValue::String("[Device]".to_string()), - AmlType::Method => AmlValue::String("[Control Method]".to_string()), - AmlType::Mutex => AmlValue::String("[Mutex]".to_string()), - AmlType::ObjReference => AmlValue::String("[Obj Reference]".to_string()), - AmlType::OpRegion => AmlValue::String("[Operation Region]".to_string()), - AmlType::Package => AmlValue::String("[Package]".to_string()), - AmlType::Processor => AmlValue::String("[Processor]".to_string()), - AmlType::PowerResource => AmlValue::String("[Power Resource]".to_string()), - AmlType::RawDataBuffer => AmlValue::String("[Raw Data Buffer]".to_string()), - AmlType::ThermalZone => AmlValue::String("[Thermal Zone]".to_string()), - } - } - - /// Turns an `AmlValue` returned from a `_STA` method into a `StatusObject`. Should only be called for values - /// returned from `_STA`. If you need a `StatusObject`, but the device does not have a `_STA` method, use - /// `StatusObject::default()`. - pub fn as_status(&self) -> Result { - match self { - AmlValue::Integer(value) => { - /* - * Bits 5+ are reserved and are expected to be cleared. - */ - if value.get_bits(5..64) != 0 { - return Err(AmlError::InvalidStatusObject); - } - - Ok(StatusObject { - present: value.get_bit(0), - enabled: value.get_bit(1), - show_in_ui: value.get_bit(2), - functional: value.get_bit(3), - battery_present: value.get_bit(4), - }) - } - - _ => Err(AmlError::InvalidStatusObject), - } - } - - /// Convert this value to a value of the same data, but with the given AML type, if possible, - /// by converting the implicit conversions described in §19.3.5 of the spec. - /// - /// The implicit conversions applied are: - /// `Buffer` from: `Integer`, `String`, `Debug` - /// `BufferField` from: `Integer`, `Buffer`, `String`, `Debug` - /// `DdbHandle` from: `Integer`, `Debug` - /// `FieldUnit` from: `Integer`,`Buffer`, `String`, `Debug` - /// `Integer` from: `Buffer`, `BufferField`, `DdbHandle`, `FieldUnit`, `String`, `Debug` - /// `Package` from: `Debug` - /// `String` from: `Integer`, `Buffer`, `Debug` - pub fn as_type(&self, desired_type: AmlType, context: &mut AmlContext) -> Result { - // If the value is already of the correct type, just return it as is - if self.type_of() == desired_type { - return Ok(self.clone()); - } - - // TODO: implement all of the rules - match desired_type { - AmlType::Integer => self.as_integer(context).map(AmlValue::Integer), - AmlType::Buffer => self.as_buffer(context).map(AmlValue::Buffer), - AmlType::FieldUnit => panic!( - "Can't implicitly convert to FieldUnit. This must be special-cased by the caller for now :(" - ), - _ => Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: desired_type }), - } - } - - /// Reads from a field of an op-region, returning either a `AmlValue::Integer` or an `AmlValue::Buffer`, - /// depending on the size of the field. - pub fn read_field(&self, context: &mut AmlContext) -> Result { - if let AmlValue::Field { region, flags, offset, length } = self { - if let AmlValue::OpRegion(region) = context.namespace.get(*region)?.clone() { - region.read_field(*offset, *length, *flags, context) - } else { - Err(AmlError::FieldRegionIsNotOpRegion) - } - } else { - Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::FieldUnit }) - } - } - - /// Write to a field of an op-region, from either a `AmlValue::Integer` or `AmlValue::Buffer` - /// as necessary. - pub fn write_field(&mut self, value: AmlValue, context: &mut AmlContext) -> Result<(), AmlError> { - if let AmlValue::Field { region, flags, offset, length } = self { - if let AmlValue::OpRegion(region) = context.namespace.get(*region)?.clone() { - region.write_field(*offset, *length, *flags, value, context) - } else { - Err(AmlError::FieldRegionIsNotOpRegion) - } - } else { - Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::FieldUnit }) - } - } - - pub fn read_buffer_field(&self, _context: &AmlContext) -> Result { - use bitvec::view::BitView; - - if let AmlValue::BufferField { buffer_data, offset, length } = self { - let offset = *offset as usize; - let length = *length as usize; - let inner_data = buffer_data.lock(); - - if (offset + length) > (inner_data.len() * 8) { - return Err(AmlError::BufferFieldIndexesOutOfBounds); - } - - let bitslice = inner_data.view_bits::(); - let bits = &bitslice[offset..(offset + length)]; - - if length > 64 { - let mut bitvec = bits.to_bitvec(); - bitvec.set_uninitialized(false); - Ok(AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(bitvec.into_vec())))) - } else if length > 32 { - /* - * TODO: this is a pretty gross hack to work around a weird limitation with the `bitvec` crate on - * 32-bit platforms. For reasons beyond me right now, it can't operate on a `u64` on a 32-bit - * platform, so we manually extract two `u32`s and stick them together. In the future, we should - * definitely have a closer look at what `bitvec` is doing and see if we can fix this code, or - * replace it with a different crate. This should hold everything vaguely together until we have - * time to do that. - */ - let mut upper = 0u32; - let mut lower = 0u32; - lower.view_bits_mut::()[0..32].clone_from_bitslice(bits); - upper.view_bits_mut::()[0..(length - 32)].clone_from_bitslice(&bits[32..]); - Ok(AmlValue::Integer((upper as u64) << (32 + (lower as u64)))) - } else { - let mut value = 0u32; - value.view_bits_mut::()[0..length].clone_from_bitslice(bits); - Ok(AmlValue::Integer(value as u64)) - } - } else { - Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::BufferField }) - } - } - - pub fn write_buffer_field(&mut self, value: AmlValue, _context: &mut AmlContext) -> Result<(), AmlError> { - use bitvec::view::BitView; - - if let AmlValue::BufferField { buffer_data, offset, length } = self { - let offset = *offset as usize; - let length = *length as usize; - // TODO: check these against the size of the buffer to be written into - let mut inner_data = buffer_data.lock(); - let bitslice = inner_data.view_bits_mut::(); - - match value { - AmlValue::Integer(value) => { - /* - * When an `Integer` is written into a `BufferField`, the entire contents are overwritten. If - * it's smaller than the length of the buffer field, it's zero-extended. If it's larger, the - * upper bits are truncated. - */ - let bits_to_copy = cmp::min(length, 64); - bitslice[offset..(offset + bits_to_copy)] - .copy_from_bitslice(&value.to_le_bytes().view_bits()[..(bits_to_copy as usize)]); - // Zero extend to the end of the buffer field - bitslice[(offset + bits_to_copy)..(offset + length)].fill(false); - Ok(()) - } - AmlValue::Boolean(value) => { - bitslice.set(offset, value); - Ok(()) - } - AmlValue::Buffer(value) => { - /* - * When a `Buffer` is written into a `BufferField`, the entire contents are copied into the - * field. If the buffer is smaller than the size of the buffer field, it is zero extended. If - * the buffer is larger, the upper bits are truncated. - * XXX: this behaviour is only explicitly defined in ACPI 2.0+. While undefined in ACPI 1.0, - * we produce the same behaviour there. - */ - let value_data = value.lock(); - let bits_to_copy = cmp::min(length, value_data.len() * 8); - bitslice[offset..(offset + bits_to_copy)] - .copy_from_bitslice(&value_data.view_bits()[..(bits_to_copy as usize)]); - // Zero extend to the end of the buffer field - bitslice[(offset + bits_to_copy)..(offset + length)].fill(false); - Ok(()) - } - _ => Err(AmlError::TypeCannotBeWrittenToBufferField(value.type_of())), - } - } else { - Err(AmlError::IncompatibleValueConversion { current: self.type_of(), target: AmlType::BufferField }) - } - } - - /// Logically compare two `AmlValue`s, according to the rules that govern opcodes like `DefLEqual`, `DefLLess`, - /// etc. The type of `self` dictates the type that `other` will be converted to, and the method by which the - /// values will be compared: - /// - `Integer`s are simply compared by numeric comparison - /// - `String`s and `Buffer`s are compared lexicographically - `other` is compared byte-wise until a byte - /// is discovered that is either less or greater than the corresponding byte of `self`. If the bytes are - /// identical, the lengths are compared. Luckily, the Rust standard library implements lexicographic - /// comparison of strings and `[u8]` for us already. - pub fn cmp(&self, other: AmlValue, context: &mut AmlContext) -> Result { - let self_inner = - if self.type_of() == AmlType::FieldUnit { self.read_field(context)? } else { self.clone() }; - - match self_inner.type_of() { - AmlType::Integer => Ok(self.as_integer(context)?.cmp(&other.as_integer(context)?)), - AmlType::Buffer => Ok(self.as_buffer(context)?.lock().cmp(&other.as_buffer(context)?.lock())), - AmlType::String => Ok(self.as_string(context)?.cmp(&other.as_string(context)?)), - typ => Err(AmlError::TypeCannotBeCompared(typ)), - } - } -} - -/// A control method can take up to 7 arguments, each of which is an `AmlValue`. -#[derive(Clone, Default, Debug)] -pub struct Args(pub [Option; 7]); - -impl Args { - pub const EMPTY: Self = Self([None, None, None, None, None, None, None]); - - pub fn from_list(list: Vec) -> Result { - if list.len() > 7 { - return Err(AmlError::TooManyArgs); - } - - let mut args: Vec> = list.into_iter().map(Option::Some).collect(); - args.extend(core::iter::repeat(None).take(7 - args.len())); - Ok(Args(args.try_into().unwrap())) - } - - pub fn arg(&self, arg: ArgNum) -> Result<&AmlValue, AmlError> { - if arg > 6 { - return Err(AmlError::InvalidArgAccess(arg)); - } - - self.0[arg as usize].as_ref().ok_or(AmlError::InvalidArgAccess(arg)) - } - - pub fn store_arg(&mut self, arg: ArgNum, value: AmlValue) -> Result<(), AmlError> { - if arg > 6 { - return Err(AmlError::InvalidArgAccess(arg)); - } - - self.0[arg as usize] = Some(value); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::*; - use core::cmp::Ordering; - - #[test] - fn test_object_cmp() { - let mut context = make_test_context(); - - assert_eq!(AmlValue::Integer(76).cmp(AmlValue::Integer(89), &mut context), Ok(Ordering::Less)); - assert_eq!(AmlValue::Integer(11).cmp(AmlValue::Integer(11), &mut context), Ok(Ordering::Equal)); - assert_eq!(AmlValue::Integer(8362836690).cmp(AmlValue::Integer(1), &mut context), Ok(Ordering::Greater)); - - // TODO: test the other combinations too, as well as conversions to the correct types for the second operand - } -} diff --git a/rsdp/Cargo.toml b/rsdp/Cargo.toml deleted file mode 100644 index e51e73f6..00000000 --- a/rsdp/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "rsdp" -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"] -license = "MIT/Apache-2.0" -edition = "2021" - -[dependencies] -log = "0.4" diff --git a/rsdp/README.md b/rsdp/README.md deleted file mode 100644 index bdf379e0..00000000 --- a/rsdp/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# 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. diff --git a/rsdp/src/handler.rs b/rsdp/src/handler.rs deleted file mode 100644 index 6cc0cd05..00000000 --- a/rsdp/src/handler.rs +++ /dev/null @@ -1,128 +0,0 @@ -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/rsdp/src/lib.rs b/rsdp/src/lib.rs deleted file mode 100644 index d2dffe04..00000000 --- a/rsdp/src/lib.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! This crate provides types for representing the RSDP (the Root System Descriptor Table; the first ACPI table) -//! and methods for searching for it on BIOS systems. Importantly, this crate (unlike `acpi`, which re-exports the -//! contents of this crate) does not need `alloc`, and so can be used in environments that can't allocate. This is -//! specifically meant to be used from bootloaders for finding the RSDP, so it can be passed to the payload. If you -//! don't have this requirement, and want to do more than just find the RSDP, you can use `acpi` instead of this -//! crate. -//! -//! To use this crate, you will need to provide an implementation of `AcpiHandler`. This is the same handler type -//! used in the `acpi` crate. - -#![no_std] -#![deny(unsafe_op_in_unsafe_fn)] - -#[cfg(test)] -extern crate std; - -pub mod handler; - -use core::{mem, ops::Range, slice, str}; -use handler::{AcpiHandler, PhysicalMapping}; -use log::warn; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum RsdpError { - NoValidRsdp, - IncorrectSignature, - InvalidOemId, - InvalidChecksum, -} - -/// 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 1KB 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) -> Result, RsdpError> - 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(RsdpError::IncorrectSignature) => None, - Err(e) => { - warn!("Invalid RSDP found at {:#x}: {:?}", maybe_rsdp_phys_start, e); - - None - } - } - }) - }); - - match rsdp_address { - Some(address) => { - let rsdp_mapping = unsafe { handler.map_physical_region::(address, mem::size_of::()) }; - Ok(rsdp_mapping) - } - None => Err(RsdpError::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) -> Result<(), RsdpError> { - // Check the signature - if self.signature != RSDP_SIGNATURE { - return Err(RsdpError::IncorrectSignature); - } - - // Check the OEM id is valid UTF8 (allows use of unwrap) - if str::from_utf8(&self.oem_id).is_err() { - return Err(RsdpError::InvalidOemId); - } - - /* - * `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(RsdpError::InvalidChecksum); - } - - 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 1mb 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 1mb 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/acpi/src/address.rs b/src/address.rs similarity index 91% rename from acpi/src/address.rs rename to src/address.rs index 0d890b18..f8599b77 100644 --- a/acpi/src/address.rs +++ b/src/address.rs @@ -2,13 +2,11 @@ //! 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. +/// This is the raw form of a Generic Address Structure, and follows the layout found in the ACPI tables. #[derive(Clone, Copy, Debug)] #[repr(C, packed)] -pub(crate) struct RawGenericAddress { +pub struct RawGenericAddress { pub address_space: u8, pub bit_width: u8, pub bit_offset: u8, @@ -85,7 +83,7 @@ pub struct GenericAddress { } impl GenericAddress { - pub(crate) fn from_raw(raw: RawGenericAddress) -> crate::AcpiResult { + pub fn from_raw(raw: RawGenericAddress) -> Result { let address_space = match raw.address_space { 0x00 => AddressSpace::SystemMemory, 0x01 => AddressSpace::SystemIo, diff --git a/src/aml/mod.rs b/src/aml/mod.rs new file mode 100644 index 00000000..a45e9e11 --- /dev/null +++ b/src/aml/mod.rs @@ -0,0 +1,3016 @@ +/* + * TODO: + * - Field reads supporting custom handlers + * - Run `_REG` on supported op region handlers + * - Sort out situation with `gain_mut` omg - thinking we should have a weird mutex thingy and + * gain a 'token' to give us access to objects. Objects themselves should probs be in like an + * `UnsafeCell` or something. + * - Count operations performed and time + * - Do stores properly :( + * - Load and LoadTable + * - Entire Event subsystem and opcodes for them + * + * - Method recursion depth? + * - Loop timeouts + * - Fuzz the shit out of it I guess? + */ + +pub mod namespace; +pub mod object; +pub mod op_region; +pub mod pci_routing; +pub mod resource; + +pub use pci_types::PciAddress; + +use crate::{AcpiError, AcpiHandler, AcpiTables, AmlTable, sdt::SdtHeader}; +use alloc::{ + boxed::Box, + collections::btree_map::BTreeMap, + string::{String, ToString}, + vec, + vec::Vec, +}; +use bit_field::BitField; +use core::{mem, slice, str::FromStr}; +use log::{info, trace, warn}; +use namespace::{AmlName, Namespace, NamespaceLevelKind}; +use object::{ + DeviceStatus, + FieldFlags, + FieldUnit, + FieldUnitKind, + FieldUpdateRule, + MethodFlags, + Object, + ObjectToken, + ObjectType, + ReferenceKind, + WrappedObject, +}; +use op_region::{OpRegion, RegionHandler, RegionSpace}; +use spinning_top::Spinlock; + +pub struct Interpreter +where + H: Handler, +{ + handler: H, + pub namespace: Spinlock, + pub object_token: Spinlock, + context_stack: Spinlock>, + dsdt_revision: u8, + region_handlers: Spinlock>>, +} + +unsafe impl Send for Interpreter where H: Handler + Send {} +unsafe impl Sync for Interpreter where H: Handler + Send {} + +/// The value returned by the `Revision` opcode. +const INTERPRETER_REVISION: u64 = 1; + +impl Interpreter +where + H: Handler, +{ + /// Construct a new `Interpreter`. This does not load any tables - if you have an `AcpiTables` + /// already, use [`Interpreter::new_from_tables`] instead. + pub fn new(handler: H, dsdt_revision: u8) -> Interpreter { + info!("Initializing AML interpreter v{}", env!("CARGO_PKG_VERSION")); + Interpreter { + handler, + namespace: Spinlock::new(Namespace::new()), + object_token: Spinlock::new(unsafe { ObjectToken::create_interpreter_token() }), + context_stack: Spinlock::new(Vec::new()), + dsdt_revision, + region_handlers: Spinlock::new(BTreeMap::new()), + } + } + + /// Construct a new `Interpreter` with the given set of ACPI tables. This will automatically + /// load the DSDT and any SSDTs in the supplied [`AcpiTables`]. + // TODO: maybe merge handler types? Maybe make one a supertrait of the other? + pub fn new_from_tables( + acpi_handler: AH, + aml_handler: H, + tables: &AcpiTables, + ) -> Result, AcpiError> { + fn load_table( + interpreter: &Interpreter, + acpi_handler: &AH, + table: AmlTable, + ) -> Result<(), AcpiError> { + let mapping = unsafe { + acpi_handler.map_physical_region::(table.phys_address, table.length as usize) + }; + let stream = unsafe { + slice::from_raw_parts( + mapping.virtual_start().as_ptr().byte_add(mem::size_of::()) as *const u8, + table.length as usize - mem::size_of::(), + ) + }; + interpreter.load_table(stream).map_err(|err| AcpiError::Aml(err))?; + Ok(()) + } + + let dsdt = tables.dsdt()?; + let interpreter = Interpreter::new(aml_handler, dsdt.revision); + load_table(&interpreter, &acpi_handler, dsdt)?; + + for ssdt in tables.ssdts() { + load_table(&interpreter, &acpi_handler, ssdt)?; + } + + Ok(interpreter) + } + + /// Load the supplied byte stream as an AML table. This should be only the encoded AML stream - + /// not the header at the start of a table. If you've used [`Interpreter::new_from_tables`], + /// you'll likely not need to load any tables manually. + pub fn load_table(&self, stream: &[u8]) -> Result<(), AmlError> { + let context = unsafe { MethodContext::new_from_table(stream) }; + self.do_execute_method(context)?; + Ok(()) + } + + /// Invoke a method by its name, with the given set of arguments. If the referenced object is + /// not a method, the object will instead be returned - this is useful for objects that can + /// either be defined directly, or through a method (e.g. a `_CRS` object). + pub fn invoke_method(&self, path: AmlName, args: Vec) -> Result { + trace!("Invoking AML method: {}", path); + + let object = self.namespace.lock().get(path.clone())?.clone(); + match object.typ() { + ObjectType::Method => { + self.namespace.lock().add_level(path.clone(), NamespaceLevelKind::MethodLocals)?; + let context = MethodContext::new_from_method(object, args, path)?; + self.do_execute_method(context) + } + _ => Ok(object), + } + } + + pub fn invoke_method_if_present( + &self, + path: AmlName, + args: Vec, + ) -> Result, AmlError> { + match self.invoke_method(path.clone(), args) { + Ok(result) => Ok(Some(result)), + Err(AmlError::ObjectDoesNotExist(not_present)) => { + if path == not_present { + Ok(None) + } else { + Err(AmlError::ObjectDoesNotExist(not_present)) + } + } + Err(other) => Err(other), + } + } + + pub fn install_region_handler(&self, space: RegionSpace, handler: RH) + where + RH: RegionHandler + 'static, + { + let mut handlers = self.region_handlers.lock(); + assert!(handlers.get(&space).is_none(), "Tried to install handler for same space twice!"); + handlers.insert(space, Box::new(handler)); + } + + /// Initialize the namespace - this should be called after all tables have been loaded and + /// operation region handlers registered. Specifically, it will call relevant `_STA`, `_INI`, + /// and `_REG` methods. + pub fn initialize_namespace(&self) { + /* + * This should match the initialization order of ACPICA and uACPI. + */ + if let Err(err) = self.invoke_method_if_present(AmlName::from_str("\\_INI").unwrap(), vec![]) { + warn!("Invoking \\_INI failed: {:?}", err); + } + if let Err(err) = self.invoke_method_if_present(AmlName::from_str("\\_SB._INI").unwrap(), vec![]) { + warn!("Invoking \\_SB._INI failed: {:?}", err); + } + + // TODO: run all _REGs for globally-installed handlers (this might need more bookkeeping) + + /* + * We can now initialize each device in the namespace. For each device, we evaluate `_STA`, + * which indicates if the device is present and functional. If this method does not exist, + * we assume the device should be initialized. + * + * We then evaluate `_INI` for the device. This can dynamically populate objects such as + * `_ADR`, `_CID`, `_HID`, `_SUN`, and `_UID`, and so is necessary before further + * operation. + */ + let mut num_devices_initialized = 0; + /* + * TODO + * We clone a copy of the namespace here to traverse while executing all the `_STA` and + * `_INI` objects. Avoiding this would be good, but is not easy, as we need + * potentially-mutable access while executing all of the methods. + */ + let mut namespace = self.namespace.lock().clone(); + let init_status = namespace.traverse(|path, level| { + match level.kind { + NamespaceLevelKind::Device + | NamespaceLevelKind::Processor + | NamespaceLevelKind::ThermalZone + | NamespaceLevelKind::PowerResource => { + let should_initialize = match self + .invoke_method_if_present(AmlName::from_str("_STA").unwrap().resolve(path)?, vec![]) + { + Ok(Some(result)) => { + let Object::Integer(result) = *result else { panic!() }; + let status = DeviceStatus(result); + status.present() && status.functioning() + } + Ok(None) => true, + Err(err) => { + warn!("Failed to evaluate _STA for device {}: {:?}", path, err); + false + } + }; + + if should_initialize { + num_devices_initialized += 1; + if let Err(err) = self + .invoke_method_if_present(AmlName::from_str("_INI").unwrap().resolve(path)?, vec![]) + { + warn!("Failed to evaluate _INI for device {}: {:?}", path, err); + } + Ok(true) + } else { + /* + * If this device should not be initialized, don't initialize it's children. + */ + Ok(false) + } + } + _ => Ok(true), + } + }); + if let Err(err) = init_status { + warn!("Error while traversing namespace for devices: {:?}", err); + } + + info!("Initialized {} devices", num_devices_initialized); + } + + fn do_execute_method(&self, mut context: MethodContext) -> Result { + /* + * This is the main loop that executes operations. Every op is handled at the top-level of + * the loop to prevent pathological stack growth from nested operations. + * + * The loop has three main stages: + * 1) Check if any in-flight operations are ready to be executed (i.e. have collected all + * their arguments). An operation completing may contribute the last required argument + * of the one above, so this is repeated for as many operations as are ready to be + * retired. + * 2) Look at the next opcode in the stream. If we've run out of opcodes in the current + * block, run logic to determine where in the stream we should move to next. Special + * logic at this level handles things like moving in/out of package definitions, and + * performing control flow. + * 3) When the next opcode is determined, use it to interpret the next portion of the + * stream. If that is data, the correct number of bytes can be consumed and + * contributed to the current in-flight operation. If it's an opcode, a new in-flight + * operation is started, and we go round the loop again. + * + * This scheme is what allows the interpreter to use a loop that somewhat resembles a + * traditional fast bytecode VM, but also provides enough flexibility to handle the + * quirkier parts of the AML grammar, particularly the left-to-right encoding of operands. + */ + loop { + /* + * First, see if we've gathered enough arguments to complete some in-flight operations. + */ + while let Some(op) = context.in_flight.pop_if(|op| op.arguments.len() == op.expected_arguments) { + match op.op { + Opcode::Add + | Opcode::Subtract + | Opcode::Multiply + | Opcode::Divide + | Opcode::ShiftLeft + | Opcode::ShiftRight + | Opcode::Mod + | Opcode::Nand + | Opcode::And + | Opcode::Or + | Opcode::Nor + | Opcode::Xor => { + self.do_binary_maths(&mut context, op)?; + } + Opcode::Not | Opcode::FindSetLeftBit | Opcode::FindSetRightBit => { + self.do_unary_maths(&mut context, op)?; + } + Opcode::Increment | Opcode::Decrement => { + let [Argument::Object(operand)] = &op.arguments[..] else { panic!() }; + let token = self.object_token.lock(); + let Object::Integer(operand) = (unsafe { operand.gain_mut(&*token) }) else { + Err(AmlError::ObjectNotOfExpectedType { + expected: ObjectType::Integer, + got: operand.typ(), + })? + }; + + let new_value = match op.op { + Opcode::Increment => operand.wrapping_add(1), + Opcode::Decrement => operand.wrapping_sub(1), + _ => unreachable!(), + }; + + *operand = new_value; + context.retire_op(op); + } + Opcode::LAnd + | Opcode::LOr + | Opcode::LNot + | Opcode::LNotEqual + | Opcode::LLessEqual + | Opcode::LGreaterEqual + | Opcode::LEqual + | Opcode::LGreater + | Opcode::LLess => { + self.do_logical_op(&mut context, op)?; + } + Opcode::ToBuffer => self.do_to_buffer(&mut context, op)?, + Opcode::ToInteger => self.do_to_integer(&mut context, op)?, + Opcode::ToString => self.do_to_string(&mut context, op)?, + Opcode::ToDecimalString | Opcode::ToHexString => { + self.do_to_dec_hex_string(&mut context, op)? + } + Opcode::Mid => self.do_mid(&mut context, op)?, + Opcode::Concat => self.do_concat(&mut context, op)?, + Opcode::ConcatRes => { + let [Argument::Object(source1), Argument::Object(source2), target] = &op.arguments[..] + else { + panic!() + }; + let source1 = source1.as_buffer()?; + let source2 = source2.as_buffer()?; + let result = { + let mut buffer = Vec::from(source1); + buffer.extend_from_slice(source2); + // Add a new end-tag + buffer.push(0x78); + // Don't calculate the new real checksum - just use 0 + buffer.push(0x00); + Object::Buffer(buffer).wrap() + }; + // TODO: use potentially-updated result for return value here + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + } + Opcode::FromBCD => self.do_from_bcd(&mut context, op)?, + Opcode::ToBCD => self.do_to_bcd(&mut context, op)?, + Opcode::Name => { + let [Argument::Namestring(name), Argument::Object(object)] = &op.arguments[..] else { + panic!() + }; + + let name = name.resolve(&context.current_scope)?; + self.namespace.lock().insert(name, object.clone())?; + context.retire_op(op); + } + Opcode::Fatal => { + let [Argument::ByteData(typ), Argument::DWordData(code), Argument::Object(arg)] = + &op.arguments[..] + else { + panic!() + }; + let arg = arg.as_integer()?; + self.handler.handle_fatal_error(*typ, *code, arg); + context.retire_op(op); + } + Opcode::OpRegion => { + let [ + Argument::Namestring(name), + Argument::ByteData(region_space), + Argument::Object(region_offset), + Argument::Object(region_length), + ] = &op.arguments[..] + else { + panic!() + }; + + let region_offset = region_offset.clone().unwrap_transparent_reference(); + let region_length = region_length.clone().unwrap_transparent_reference(); + + let region = Object::OpRegion(OpRegion { + space: RegionSpace::from(*region_space), + base: region_offset.as_integer()?, + length: region_length.as_integer()?, + parent_device_path: context.current_scope.clone(), + }); + self.namespace.lock().insert(name.resolve(&context.current_scope)?, region.wrap())?; + context.retire_op(op); + } + Opcode::DataRegion => { + let [ + Argument::Namestring(name), + Argument::Object(signature), + Argument::Object(oem_id), + Argument::Object(oem_table_id), + ] = &op.arguments[..] + else { + panic!() + }; + let _signature = signature.as_string()?; + let _oem_id = oem_id.as_string()?; + let _oem_table_id = oem_table_id.as_string()?; + + // TODO: once this is integrated into the rest of the crate, load the table + log::warn!( + "DefDataRegion encountered in AML! We don't actually support these - produced region will be incorrect" + ); + + let region = Object::OpRegion(OpRegion { + space: RegionSpace::SystemMemory, + base: 0, + length: 0, + parent_device_path: context.current_scope.clone(), + }); + self.namespace.lock().insert(name.resolve(&context.current_scope)?, region.wrap())?; + context.retire_op(op); + } + Opcode::Buffer => { + let [ + Argument::TrackedPc(start_pc), + Argument::PkgLength(pkg_length), + Argument::Object(buffer_size), + ] = &op.arguments[..] + else { + panic!() + }; + let buffer_size = buffer_size.clone().unwrap_reference().as_integer()?; + + let buffer_len = pkg_length - (context.current_block.pc - start_pc); + let mut buffer = vec![0; buffer_size as usize]; + buffer[0..buffer_len].copy_from_slice( + &context.current_block.stream() + [context.current_block.pc..(context.current_block.pc + buffer_len)], + ); + context.current_block.pc += buffer_len; + + context.contribute_arg(Argument::Object(Object::Buffer(buffer).wrap())); + context.retire_op(op); + } + Opcode::Package | Opcode::VarPackage => { + let mut elements = Vec::with_capacity(op.expected_arguments); + for arg in &op.arguments { + let Argument::Object(object) = arg else { panic!() }; + elements.push(object.clone()); + } + + /* + * We can end up completing a package's in-flight op in two circumstances: + * - If the correct number of elements are supplied, we end up here + * first, and then later in the block's finishing logic. + * - If less elements are supplied, we end up in the block's finishing + * logic to add some `Uninitialized`s, then go round again to complete + * the in-flight operation. + * + * To make these consistent, we always remove the block here, making sure + * we've finished it as a sanity check. + */ + assert_eq!(context.current_block.kind, BlockKind::Package); + assert_eq!(context.peek(), Err(AmlError::RunOutOfStream)); + context.current_block = context.block_stack.pop().unwrap(); + context.contribute_arg(Argument::Object(Object::Package(elements).wrap())); + context.retire_op(op); + } + Opcode::If => { + let [ + Argument::TrackedPc(start_pc), + Argument::PkgLength(then_length), + Argument::Object(predicate), + ] = &op.arguments[..] + else { + panic!() + }; + let predicate = predicate.as_integer()?; + let remaining_then_length = then_length - (context.current_block.pc - start_pc); + + if predicate > 0 { + context.start_new_block(BlockKind::IfThenBranch, remaining_then_length); + } else { + context.current_block.pc += remaining_then_length; + + /* + * Skip over the prolog to the else branch if present. Also handle if + * there are no more bytes to peek - the `If` op could be the last op + * in a block. + */ + const DEF_ELSE_OP: u8 = 0xa1; + match context.peek() { + Ok(DEF_ELSE_OP) => { + context.next()?; + let _else_length = context.pkglength()?; + } + Ok(_) => (), + Err(AmlError::RunOutOfStream) => (), + Err(other) => Err(other)?, + } + } + context.retire_op(op); + } + opcode @ Opcode::CreateBitField + | opcode @ Opcode::CreateByteField + | opcode @ Opcode::CreateWordField + | opcode @ Opcode::CreateDWordField + | opcode @ Opcode::CreateQWordField => { + let [Argument::Object(buffer), Argument::Object(index)] = &op.arguments[..] else { + panic!() + }; + let name = context.namestring()?; + let index = index.as_integer()?; + let (offset, length) = match opcode { + Opcode::CreateBitField => (index, 1), + Opcode::CreateByteField => (index * 8, 8), + Opcode::CreateWordField => (index * 8, 16), + Opcode::CreateDWordField => (index * 8, 32), + Opcode::CreateQWordField => (index * 8, 64), + _ => unreachable!(), + }; + self.namespace.lock().insert( + name.resolve(&context.current_scope)?, + Object::BufferField { buffer: buffer.clone(), offset: offset as usize, length }.wrap(), + )?; + context.retire_op(op); + } + Opcode::CreateField => { + let [Argument::Object(buffer), Argument::Object(bit_index), Argument::Object(num_bits)] = + &op.arguments[..] + else { + panic!() + }; + let name = context.namestring()?; + let bit_index = bit_index.as_integer()?; + let num_bits = num_bits.as_integer()?; + + self.namespace.lock().insert( + name.resolve(&context.current_scope)?, + Object::BufferField { + buffer: buffer.clone(), + offset: bit_index as usize, + length: num_bits as usize, + } + .wrap(), + )?; + context.retire_op(op); + } + Opcode::Store => { + let [Argument::Object(object), target] = &op.arguments[..] else { panic!() }; + self.do_store(&target, object.clone())?; + context.retire_op(op); + } + Opcode::RefOf => { + let [Argument::Object(object)] = &op.arguments[..] else { panic!() }; + let reference = + Object::Reference { kind: ReferenceKind::RefOf, inner: object.clone() }.wrap(); + context.contribute_arg(Argument::Object(reference)); + context.retire_op(op); + } + Opcode::CondRefOf => { + let [Argument::Object(object), target] = &op.arguments[..] else { panic!() }; + let result = if let Object::Reference { kind: ReferenceKind::Unresolved, .. } = **object { + Object::Integer(0) + } else { + let reference = + Object::Reference { kind: ReferenceKind::RefOf, inner: object.clone() }.wrap(); + self.do_store(target, reference)?; + Object::Integer(u64::MAX) + }; + context.contribute_arg(Argument::Object(result.wrap())); + context.retire_op(op); + } + Opcode::DerefOf => { + let [Argument::Object(object)] = &op.arguments[..] else { panic!() }; + let result = if object.typ() == ObjectType::Reference { + object.clone().unwrap_reference() + } else if object.typ() == ObjectType::String { + let path = AmlName::from_str(&object.as_string().unwrap())? + .resolve(&context.current_scope)?; + self.namespace.lock().get(path)?.clone() + } else { + return Err(AmlError::ObjectNotOfExpectedType { + expected: ObjectType::Reference, + got: object.typ(), + }); + }; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + } + Opcode::Sleep => { + let [Argument::Object(msec)] = &op.arguments[..] else { panic!() }; + self.handler.sleep(msec.as_integer()?); + context.retire_op(op); + } + Opcode::Stall => { + let [Argument::Object(usec)] = &op.arguments[..] else { panic!() }; + self.handler.stall(usec.as_integer()?); + context.retire_op(op); + } + Opcode::Acquire => { + let [Argument::Object(mutex)] = &op.arguments[..] else { panic!() }; + let Object::Mutex { mutex, sync_level } = **mutex else { + Err(AmlError::InvalidOperationOnObject { op: Operation::Acquire, typ: mutex.typ() })? + }; + let timeout = context.next_u16()?; + + // TODO: should we do something with the sync level?? + self.handler.acquire(mutex, timeout)?; + context.retire_op(op); + } + Opcode::Release => { + let [Argument::Object(mutex)] = &op.arguments[..] else { panic!() }; + let Object::Mutex { mutex, sync_level } = **mutex else { + Err(AmlError::InvalidOperationOnObject { op: Operation::Release, typ: mutex.typ() })? + }; + // TODO: should we do something with the sync level?? + self.handler.release(mutex); + context.retire_op(op); + } + Opcode::InternalMethodCall => { + let [Argument::Object(method), Argument::Namestring(method_scope)] = &op.arguments[0..2] + else { + panic!() + }; + + let args = op.arguments[2..] + .iter() + .map(|arg| { + if let Argument::Object(arg) = arg { + arg.clone() + } else { + panic!(); + } + }) + .collect(); + + self.namespace.lock().add_level(method_scope.clone(), NamespaceLevelKind::MethodLocals)?; + + let new_context = + MethodContext::new_from_method(method.clone(), args, method_scope.clone())?; + let old_context = mem::replace(&mut context, new_context); + self.context_stack.lock().push(old_context); + context.retire_op(op); + } + Opcode::Return => { + let [Argument::Object(object)] = &op.arguments[..] else { panic!() }; + let object = object.clone().unwrap_transparent_reference(); + + if let Some(last) = self.context_stack.lock().pop() { + context = last; + context.contribute_arg(Argument::Object(object.clone())); + context.retire_op(op); + } else { + /* + * If this is the top-most context, this is a `Return` from the actual + * method. + */ + return Ok(object.clone()); + } + } + Opcode::ObjectType => { + let [Argument::Object(object)] = &op.arguments[..] else { panic!() }; + // TODO: this should technically support scopes as well - this is less easy + // (they should return `0`) + let typ = match object.typ() { + ObjectType::Uninitialized => 0, + ObjectType::Integer => 1, + ObjectType::String => 2, + ObjectType::Buffer => 3, + ObjectType::Package => 4, + ObjectType::FieldUnit => 5, + ObjectType::Device => 6, + ObjectType::Event => 7, + ObjectType::Method => 8, + ObjectType::Mutex => 9, + ObjectType::OpRegion => 10, + ObjectType::PowerResource => 11, + ObjectType::Processor => 12, + ObjectType::ThermalZone => 13, + ObjectType::BufferField => 14, + // XXX: 15 is reserved + ObjectType::Debug => 16, + ObjectType::Reference => panic!(), + ObjectType::RawDataBuffer => todo!(), + }; + + context.contribute_arg(Argument::Object(Object::Integer(typ).wrap())); + context.retire_op(op); + } + Opcode::SizeOf => self.do_size_of(&mut context, op)?, + Opcode::Index => self.do_index(&mut context, op)?, + Opcode::BankField => { + let [ + Argument::TrackedPc(start_pc), + Argument::PkgLength(pkg_length), + Argument::Namestring(region_name), + Argument::Namestring(bank_name), + Argument::Object(bank_value), + ] = &op.arguments[..] + else { + panic!() + }; + let bank_value = bank_value.as_integer()?; + let field_flags = context.next()?; + + let (region, bank) = { + let namespace = self.namespace.lock(); + let (_, region) = namespace.search(region_name, &context.current_scope)?; + let (_, bank) = namespace.search(bank_name, &context.current_scope)?; + (region, bank) + }; + + let kind = FieldUnitKind::Bank { region, bank, bank_value }; + self.parse_field_list(&mut context, kind, *start_pc, *pkg_length, field_flags)?; + context.retire_op(op); + } + Opcode::While => { + /* + * We've just evaluated the predicate for an iteration of a while loop. If + * false, skip over the rest of the loop, otherwise carry on. + */ + let [Argument::Object(predicate)] = &op.arguments[..] else { panic!() }; + let predicate = predicate.as_integer()?; + + if predicate == 0 { + // Exit from the while loop by skipping out of the current block + context.current_block = context.block_stack.pop().unwrap(); + context.retire_op(op); + } + } + _ => panic!("Unexpected operation has created in-flight op!"), + } + } + + /* + * Now that we've retired as many in-flight operations as we have arguments for, move + * forward in the AML stream. + */ + let opcode = match context.opcode() { + Ok(opcode) => opcode, + Err(AmlError::RunOutOfStream) => { + /* + * We've reached the end of the current block. What we should do about this + * depends on what type of block it was. + */ + match context.current_block.kind { + BlockKind::Table => { + break Ok(Object::Uninitialized.wrap()); + } + BlockKind::Method { method_scope } => { + self.namespace.lock().remove_level(method_scope)?; + + if let Some(prev_context) = self.context_stack.lock().pop() { + context = prev_context; + continue; + } else { + /* + * If there is no explicit `Return` op, the result is undefined. We + * just return an uninitialized object. + */ + return Ok(Object::Uninitialized.wrap()); + } + } + BlockKind::Scope { old_scope } => { + assert!(context.block_stack.len() > 0); + context.current_block = context.block_stack.pop().unwrap(); + context.current_scope = old_scope; + // Go round the loop again to get the next opcode for the new block + continue; + } + BlockKind::Package => { + /* + * We've reached the end of the package. The in-flight op may have + * already been completed in the case of the package specifying all of + * its elements, or reach the end of the block here if it does not. + * + * In the latter case, fill in the rest of the package with + * *distinct* uninitialized objects, and go round again to complete the + * in-flight op. + */ + assert!(context.block_stack.len() > 0); + + if let Some(package_op) = context.in_flight.last_mut() + && (package_op.op == Opcode::Package || package_op.op == Opcode::VarPackage) + { + let num_elements_left = match package_op.op { + Opcode::Package => package_op.expected_arguments - package_op.arguments.len(), + Opcode::VarPackage => { + let Argument::Object(total_elements) = &package_op.arguments[0] else { + panic!() + }; + let total_elements = + total_elements.clone().unwrap_reference().as_integer()? as usize; + + // Update the expected number of arguments to terminate the in-flight op + package_op.expected_arguments = total_elements; + + total_elements - package_op.arguments.len() + } + _ => panic!( + "Current in-flight op is not a `Package` or `VarPackage` when finished parsing package block" + ), + }; + + for _ in 0..num_elements_left { + package_op.arguments.push(Argument::Object(Object::Uninitialized.wrap())); + } + } + + // XXX: don't remove the package's block. Refer to completion of + // package ops for rationale here. + continue; + } + BlockKind::IfThenBranch => { + context.current_block = context.block_stack.pop().unwrap(); + + /* + * Check for an else-branch, and skip over it. We need to handle the + * case here where there isn't a next byte - that just means the `If` + * is the last op in a block. + */ + const DEF_ELSE_OP: u8 = 0xa1; + match context.peek() { + Ok(DEF_ELSE_OP) => { + context.next()?; + let start_pc = context.current_block.pc; + let else_length = context.pkglength()?; + context.current_block.pc += + else_length - (context.current_block.pc - start_pc); + } + Ok(_) => (), + Err(AmlError::RunOutOfStream) => (), + Err(other) => Err(other)?, + }; + + continue; + } + BlockKind::While { start_pc } => { + /* + * Go round again, and create a new in-flight op to have a look at the + * predicate. + */ + context.current_block.pc = start_pc; + context.start_in_flight_op(OpInFlight::new(Opcode::While, 1)); + continue; + } + } + } + Err(other_err) => return Err(other_err), + }; + match opcode { + Opcode::Zero => { + context.last_op()?.arguments.push(Argument::Object(Object::Integer(0).wrap())); + } + Opcode::One => { + context.last_op()?.arguments.push(Argument::Object(Object::Integer(1).wrap())); + } + Opcode::Alias => { + let source = context.namestring()?; + let alias = context.namestring()?; + + let mut namespace = self.namespace.lock(); + let object = namespace.get(source.resolve(&context.current_scope)?)?.clone(); + let alias = alias.resolve(&context.current_scope)?; + namespace.create_alias(alias, object)?; + } + Opcode::Name => { + let name = context.namestring()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::Name, + vec![Argument::Namestring(name)], + 1, + )); + } + Opcode::BytePrefix => { + let value = context.next()?; + context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap())); + } + Opcode::WordPrefix => { + let value = context.next_u16()?; + context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap())); + } + Opcode::DWordPrefix => { + let value = context.next_u32()?; + context.last_op()?.arguments.push(Argument::Object(Object::Integer(value as u64).wrap())); + } + Opcode::StringPrefix => { + let str_start = context.current_block.pc; + while context.next()? != b'\0' {} + // TODO: handle err + let str = String::from( + str::from_utf8(&context.current_block.stream()[str_start..(context.current_block.pc - 1)]) + .unwrap(), + ); + context.last_op()?.arguments.push(Argument::Object(Object::String(str).wrap())); + } + Opcode::QWordPrefix => { + let value = context.next_u64()?; + context.last_op()?.arguments.push(Argument::Object(Object::Integer(value).wrap())); + } + Opcode::Scope => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let name = context.namestring()?; + + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + let new_scope = name.resolve(&context.current_scope)?; + self.namespace.lock().add_level(new_scope.clone(), NamespaceLevelKind::Scope)?; + + let old_scope = mem::replace(&mut context.current_scope, new_scope); + context.start_new_block(BlockKind::Scope { old_scope }, remaining_length); + } + Opcode::Buffer => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::Buffer, + vec![Argument::TrackedPc(start_pc), Argument::PkgLength(pkg_length)], + 1, + )); + } + Opcode::Package => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let num_elements = context.next()?; + + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + /* + * We now need to interpret an arbitrary number of package elements, bounded by + * the remaining pkglength. This may be less than `num_elements` - the + * remaining elements of the package are uninitialized. We utilise a + * combination of a block to manage the pkglength, plus an in-flight op to + * store interpreted arguments. + */ + context.start_in_flight_op(OpInFlight::new(Opcode::Package, num_elements as usize)); + context.start_new_block(BlockKind::Package, remaining_length); + } + Opcode::VarPackage => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + /* + * For variable packages, we're first going to parse a `TermArg` that encodes, + * dynamically, how many elements the package will have. We then accept as many + * elements as remain in the block, and we'll sort out how many are supposed to + * be in the package later. + */ + context.start_in_flight_op(OpInFlight::new(Opcode::VarPackage, usize::MAX)); + context.start_new_block(BlockKind::Package, remaining_length); + } + Opcode::Method => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let name = context.namestring()?; + let flags = MethodFlags(context.next()?); + + let code_len = pkg_length - (context.current_block.pc - start_pc); + let code = context.current_block.stream() + [context.current_block.pc..(context.current_block.pc + code_len)] + .to_vec(); + context.current_block.pc += code_len; + + let name = name.resolve(&context.current_scope)?; + self.namespace.lock().insert(name, Object::Method { code, flags }.wrap())?; + } + Opcode::External => { + let _name = context.namestring()?; + let _object_type = context.next()?; + let _arg_count = context.next()?; + } + Opcode::Mutex => { + let name = context.namestring()?; + let sync_level = context.next()?; + + let name = name.resolve(&context.current_scope)?; + let mutex = self.handler.create_mutex(); + self.namespace.lock().insert(name, Object::Mutex { mutex, sync_level }.wrap())?; + } + Opcode::Event => { + let name = context.namestring()?; + + let name = name.resolve(&context.current_scope)?; + self.namespace.lock().insert(name, Object::Event.wrap())?; + } + Opcode::LoadTable => todo!(), + Opcode::Load => todo!(), + Opcode::Stall => context.start_in_flight_op(OpInFlight::new(Opcode::Stall, 1)), + Opcode::Sleep => context.start_in_flight_op(OpInFlight::new(Opcode::Sleep, 1)), + Opcode::Acquire => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::Release => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::Signal => todo!(), + Opcode::Wait => todo!(), + Opcode::Reset => todo!(), + Opcode::Notify => todo!(), + Opcode::FromBCD | Opcode::ToBCD => context.start_in_flight_op(OpInFlight::new(opcode, 2)), + Opcode::Revision => { + context.contribute_arg(Argument::Object(Object::Integer(INTERPRETER_REVISION).wrap())); + } + Opcode::Debug => { + context.contribute_arg(Argument::Object(Object::Debug.wrap())); + } + Opcode::Fatal => { + let typ = context.next()?; + let code = context.next_u32()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::Fatal, + vec![Argument::ByteData(typ), Argument::DWordData(code)], + 1, + )); + } + Opcode::Timer => { + // Time has to be monotonically-increasing, in 100ns units + let time = self.handler.nanos_since_boot() / 100; + context.contribute_arg(Argument::Object(Object::Integer(time).wrap())); + } + Opcode::OpRegion => { + let name = context.namestring()?; + let region_space = context.next()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::OpRegion, + vec![Argument::Namestring(name), Argument::ByteData(region_space)], + 2, + )); + } + Opcode::DataRegion => { + let name = context.namestring()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::DataRegion, + vec![Argument::Namestring(name)], + 3, + )); + } + Opcode::Field => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let region_name = context.namestring()?; + let field_flags = context.next()?; + + let region = self.namespace.lock().get(region_name.resolve(&context.current_scope)?)?.clone(); + let kind = FieldUnitKind::Normal { region }; + self.parse_field_list(&mut context, kind, start_pc, pkg_length, field_flags)?; + } + Opcode::BankField => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let region_name = context.namestring()?; + let bank_name = context.namestring()?; + + context.start_in_flight_op(OpInFlight::new_with( + Opcode::BankField, + vec![ + Argument::TrackedPc(start_pc), + Argument::PkgLength(pkg_length), + Argument::Namestring(region_name), + Argument::Namestring(bank_name), + ], + 1, + )); + } + Opcode::IndexField => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let index_name = context.namestring()?; + let data_name = context.namestring()?; + let field_flags = context.next()?; + + let (index, data) = { + let namespace = self.namespace.lock(); + let (_, index) = namespace.search(&index_name, &context.current_scope)?; + let (_, data) = namespace.search(&data_name, &context.current_scope)?; + (index, data) + }; + let kind = FieldUnitKind::Index { index, data }; + self.parse_field_list(&mut context, kind, start_pc, pkg_length, field_flags)?; + } + Opcode::Device | Opcode::ThermalZone => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let name = context.namestring()?; + + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + let new_scope = name.resolve(&context.current_scope)?; + let (kind, object) = match opcode { + Opcode::Device => (NamespaceLevelKind::Device, Object::Device), + Opcode::ThermalZone => (NamespaceLevelKind::ThermalZone, Object::ThermalZone), + _ => unreachable!(), + }; + let mut namespace = self.namespace.lock(); + namespace.add_level(new_scope.clone(), kind)?; + namespace.insert(new_scope.clone(), object.wrap())?; + + let old_scope = mem::replace(&mut context.current_scope, new_scope); + context.start_new_block(BlockKind::Scope { old_scope }, remaining_length); + } + Opcode::Processor => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let name = context.namestring()?; + let proc_id = context.next()?; + let pblk_address = context.next_u32()?; + let pblk_length = context.next()?; + + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + let new_scope = name.resolve(&context.current_scope)?; + let object = Object::Processor { proc_id, pblk_address, pblk_length }; + let mut namespace = self.namespace.lock(); + namespace.add_level(new_scope.clone(), NamespaceLevelKind::Processor)?; + namespace.insert(new_scope.clone(), object.wrap())?; + + let old_scope = mem::replace(&mut context.current_scope, new_scope); + context.start_new_block(BlockKind::Scope { old_scope }, remaining_length); + } + Opcode::PowerRes => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let name = context.namestring()?; + let system_level = context.next()?; + let resource_order = context.next_u16()?; + + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + + let new_scope = name.resolve(&context.current_scope)?; + let object = Object::PowerResource { system_level, resource_order }; + let mut namespace = self.namespace.lock(); + namespace.add_level(new_scope.clone(), NamespaceLevelKind::PowerResource)?; + namespace.insert(new_scope.clone(), object.wrap())?; + + let old_scope = mem::replace(&mut context.current_scope, new_scope); + context.start_new_block(BlockKind::Scope { old_scope }, remaining_length); + } + Opcode::Local(local) => { + let local = context.locals[local as usize].clone(); + context.last_op()?.arguments.push(Argument::Object( + Object::Reference { kind: ReferenceKind::LocalOrArg, inner: local }.wrap(), + )); + } + Opcode::Arg(arg) => { + let arg = context.args[arg as usize].clone(); + context.last_op()?.arguments.push(Argument::Object( + Object::Reference { kind: ReferenceKind::LocalOrArg, inner: arg }.wrap(), + )); + } + Opcode::Store => context.start_in_flight_op(OpInFlight::new(Opcode::Store, 2)), + Opcode::RefOf => context.start_in_flight_op(OpInFlight::new(Opcode::RefOf, 1)), + Opcode::CondRefOf => context.start_in_flight_op(OpInFlight::new(opcode, 2)), + + Opcode::DualNamePrefix + | Opcode::MultiNamePrefix + | Opcode::Digit(_) + | Opcode::NameChar(_) + | Opcode::RootChar + | Opcode::ParentPrefixChar => { + context.current_block.pc -= 1; + let name = context.namestring()?; + + /* + * The desired behaviour when we encounter a name at the top-level differs + * depending on the context we're in. There are certain places where we want to + * evaluate things like methods and field units, and others where we simply + * want to reference the name (such as inside package definitions). In the + * latter case, we also allow undefined names to be used, and will resolve them + * at the time of use. + */ + let do_not_resolve = context.current_block.kind == BlockKind::Package + || context.in_flight.last().map(|op| op.op == Opcode::CondRefOf).unwrap_or(false); + if do_not_resolve { + let object = self.namespace.lock().search(&name, &context.current_scope); + match object { + Ok((_, object)) => { + let reference = + Object::Reference { kind: ReferenceKind::RefOf, inner: object.clone() }; + context.last_op()?.arguments.push(Argument::Object(reference.wrap())); + } + Err(AmlError::ObjectDoesNotExist(_)) => { + let reference = Object::Reference { + kind: ReferenceKind::Unresolved, + inner: Object::String(name.to_string()).wrap(), + }; + context.last_op()?.arguments.push(Argument::Object(reference.wrap())); + } + Err(other) => Err(other)?, + } + } else { + let object = self.namespace.lock().search(&name, &context.current_scope); + match object { + Ok((resolved_name, object)) => { + if let Object::Method { flags, .. } = *object { + context.start_in_flight_op(OpInFlight::new_with( + Opcode::InternalMethodCall, + vec![Argument::Object(object), Argument::Namestring(resolved_name)], + flags.arg_count(), + )) + } else if let Object::FieldUnit(ref field) = *object { + let value = self.do_field_read(field)?; + context.last_op()?.arguments.push(Argument::Object(value)); + } else { + context.last_op()?.arguments.push(Argument::Object(object)); + } + } + Err(err) => Err(err)?, + } + } + } + + Opcode::Add + | Opcode::Subtract + | Opcode::Multiply + | Opcode::ShiftLeft + | Opcode::ShiftRight + | Opcode::Mod + | Opcode::Nand + | Opcode::And + | Opcode::Or + | Opcode::Nor + | Opcode::Xor + | Opcode::Concat => { + context.start_in_flight_op(OpInFlight::new(opcode, 3)); + } + + Opcode::Divide => context.start_in_flight_op(OpInFlight::new(Opcode::Divide, 4)), + Opcode::Increment | Opcode::Decrement => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::Not => context.start_in_flight_op(OpInFlight::new(Opcode::Not, 2)), + Opcode::FindSetLeftBit | Opcode::FindSetRightBit => { + context.start_in_flight_op(OpInFlight::new(opcode, 2)) + } + Opcode::DerefOf => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::ConcatRes => context.start_in_flight_op(OpInFlight::new(opcode, 3)), + Opcode::SizeOf => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::Index => context.start_in_flight_op(OpInFlight::new(opcode, 3)), + /* + * TODO + * Match is a difficult opcode to parse, as it interleaves dynamic arguments and + * random bytes that need to be extracted as you go. I think we'll need to use 1+ + * internal in-flight ops to parse the static bytedatas as we go, and then retire + * the real op at the end. + */ + Opcode::Match => todo!(), + + Opcode::CreateBitField + | Opcode::CreateByteField + | Opcode::CreateWordField + | Opcode::CreateDWordField + | Opcode::CreateQWordField => context.start_in_flight_op(OpInFlight::new(opcode, 2)), + Opcode::CreateField => context.start_in_flight_op(OpInFlight::new(Opcode::CreateField, 3)), + + Opcode::LAnd + | Opcode::LOr + | Opcode::LNot + | Opcode::LNotEqual + | Opcode::LLessEqual + | Opcode::LGreaterEqual + | Opcode::LEqual + | Opcode::LGreater + | Opcode::LLess => { + context.start_in_flight_op(OpInFlight::new(opcode, 2)); + } + + Opcode::ToBuffer | Opcode::ToDecimalString | Opcode::ToHexString | Opcode::ToInteger => { + context.start_in_flight_op(OpInFlight::new(opcode, 2)) + } + Opcode::ToString => context.start_in_flight_op(OpInFlight::new(opcode, 3)), + + Opcode::ObjectType => context.start_in_flight_op(OpInFlight::new(opcode, 1)), + Opcode::CopyObject => todo!(), + Opcode::Mid => context.start_in_flight_op(OpInFlight::new(Opcode::Mid, 4)), + Opcode::If => { + let start_pc = context.current_block.pc; + let then_length = context.pkglength()?; + context.start_in_flight_op(OpInFlight::new_with( + Opcode::If, + vec![Argument::TrackedPc(start_pc), Argument::PkgLength(then_length)], + 1, + )); + } + Opcode::Else => return Err(AmlError::ElseFoundWithoutCorrespondingIf), + Opcode::While => { + let start_pc = context.current_block.pc; + let pkg_length = context.pkglength()?; + let remaining_length = pkg_length - (context.current_block.pc - start_pc); + context.start_new_block( + BlockKind::While { start_pc: context.current_block.pc }, + remaining_length, + ); + context.start_in_flight_op(OpInFlight::new(Opcode::While, 1)); + } + Opcode::Continue => { + if let BlockKind::While { start_pc } = &context.current_block.kind { + context.current_block.pc = *start_pc; + } else { + loop { + let Some(block) = context.block_stack.pop() else { + Err(AmlError::ContinueOutsideOfWhile)? + }; + if let BlockKind::While { start_pc } = block.kind { + context.current_block.pc = start_pc; + break; + } + } + } + context.start_in_flight_op(OpInFlight::new(Opcode::While, 1)); + } + Opcode::Break => { + if let BlockKind::While { .. } = &context.current_block.kind { + context.current_block = context.block_stack.pop().unwrap(); + } else { + loop { + let Some(block) = context.block_stack.pop() else { + Err(AmlError::BreakOutsideOfWhile)? + }; + if let BlockKind::While { .. } = block.kind { + context.current_block = context.block_stack.pop().unwrap(); + break; + } + } + } + } + Opcode::Return => context.start_in_flight_op(OpInFlight::new(Opcode::Return, 1)), + Opcode::Noop => {} + Opcode::Breakpoint => { + self.handler.breakpoint(); + } + Opcode::Ones => { + context.last_op()?.arguments.push(Argument::Object(Object::Integer(u64::MAX).wrap())); + } + + Opcode::InternalMethodCall => panic!(), + } + } + } + + fn parse_field_list( + &self, + context: &mut MethodContext, + kind: FieldUnitKind, + start_pc: usize, + pkg_length: usize, + flags: u8, + ) -> Result<(), AmlError> { + const RESERVED_FIELD: u8 = 0x00; + const ACCESS_FIELD: u8 = 0x01; + const CONNECT_FIELD: u8 = 0x02; + const EXTENDED_ACCESS_FIELD: u8 = 0x03; + + let mut field_offset = 0; + + while context.current_block.pc < (start_pc + pkg_length) { + match context.next()? { + RESERVED_FIELD => { + let length = context.pkglength()?; + field_offset += length; + } + ACCESS_FIELD => { + let access_type = context.next()?; + let access_attrib = context.next()?; + todo!() + // TODO + } + CONNECT_FIELD => { + // TODO: either consume a namestring or `BufferData` (it's not + // clear what a buffer data acc is lmao) + todo!("Connect field :("); + } + EXTENDED_ACCESS_FIELD => { + todo!("Extended access field :("); + } + _ => { + context.current_block.pc -= 1; + // TODO: this should only ever be a nameseg really... + let field_name = context.namestring()?; + let field_length = context.pkglength()?; + + let field = Object::FieldUnit(FieldUnit { + kind: kind.clone(), + bit_index: field_offset, + bit_length: field_length, + flags: FieldFlags(flags), + }); + self.namespace.lock().insert(field_name.resolve(&context.current_scope)?, field.wrap())?; + + field_offset += field_length; + } + } + } + + Ok(()) + } + + fn do_binary_maths(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(left), Argument::Object(right), target] = &op.arguments[0..3] else { panic!() }; + let target2 = if op.op == Opcode::Divide { Some(&op.arguments[3]) } else { None }; + + let left = left.clone().unwrap_transparent_reference().as_integer()?; + let right = right.clone().unwrap_transparent_reference().as_integer()?; + + let result = match op.op { + Opcode::Add => left.wrapping_add(right), + Opcode::Subtract => left.wrapping_sub(right), + Opcode::Multiply => left.wrapping_mul(right), + Opcode::Divide => { + if let Some(remainder) = target2 { + self.do_store(remainder, Object::Integer(left.wrapping_rem(right)).wrap())?; + } + left.wrapping_div_euclid(right) + } + Opcode::ShiftLeft => left.wrapping_shl(right as u32), + Opcode::ShiftRight => left.wrapping_shr(right as u32), + Opcode::Mod => left.wrapping_rem(right), + Opcode::Nand => !(left & right), + Opcode::And => left & right, + Opcode::Or => left | right, + Opcode::Nor => !(left | right), + Opcode::Xor => left ^ right, + _ => panic!(), + }; + + let result = Object::Integer(result).wrap(); + // TODO: use result for arg + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + fn do_unary_maths(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(operand)] = &op.arguments[..] else { panic!() }; + let operand = operand.clone().unwrap_transparent_reference().as_integer()?; + + let result = match op.op { + Opcode::FindSetLeftBit => { + if operand == 0 { + 0 + } else { + /* + * This is a particularly important place to respect the integer width as set + * by the DSDT revision. + */ + if self.dsdt_revision >= 2 { + (operand.leading_zeros() + 1) as u64 + } else { + ((operand as u32).leading_zeros() + 1) as u64 + } + } + } + Opcode::FindSetRightBit => { + if operand == 0 { + 0 + } else { + (operand.trailing_zeros() + 1) as u64 + } + } + Opcode::Not => { + if operand == 0 { + u64::MAX + } else { + 0 + } + } + _ => panic!(), + }; + + context.contribute_arg(Argument::Object(Object::Integer(result).wrap())); + context.retire_op(op); + Ok(()) + } + + fn do_logical_op(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + if op.op == Opcode::LNot { + let [Argument::Object(operand)] = &op.arguments[..] else { panic!() }; + let operand = operand.clone().unwrap_transparent_reference().as_integer()?; + let result = if operand == 0 { u64::MAX } else { 0 }; + + context.contribute_arg(Argument::Object(Object::Integer(result).wrap())); + context.retire_op(op); + return Ok(()); + } + + let [Argument::Object(left), Argument::Object(right)] = &op.arguments[..] else { panic!() }; + + /* + * Some of these operations allow strings and buffers to be used as operands. Apparently + * NT's interpreter just takes the first 4 bytes of the string/buffer and casts them as an + * integer... + */ + let left = left.clone().unwrap_transparent_reference(); + let right = right.clone().unwrap_transparent_reference(); + let (left, right) = match *left { + Object::Integer(left) => (left, right.as_integer()?), + Object::String(ref left) => { + let left = { + let mut bytes = [0u8; 4]; + let left_bytes = left.as_bytes(); + let bytes_to_use = usize::min(4, left_bytes.len()); + (bytes[0..bytes_to_use]).copy_from_slice(&left_bytes[0..bytes_to_use]); + u32::from_le_bytes(bytes) as u64 + }; + let right = { + let mut bytes = [0u8; 4]; + let right = right.as_string()?; + let right_bytes = right.as_bytes(); + let bytes_to_use = usize::min(4, right_bytes.len()); + (bytes[0..bytes_to_use]).copy_from_slice(&right_bytes[0..bytes_to_use]); + u32::from_le_bytes(bytes) as u64 + }; + (left, right) + } + Object::Buffer(ref left) => { + let Object::Buffer(ref right) = *right else { panic!() }; + let left = { + let mut bytes = [0u8; 4]; + (bytes[0..left.len()]).copy_from_slice(left); + u32::from_le_bytes(bytes) as u64 + }; + let right = { + let mut bytes = [0u8; 4]; + (bytes[0..right.len()]).copy_from_slice(right); + u32::from_le_bytes(bytes) as u64 + }; + (left, right) + } + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::LogicalOp, typ: left.typ() })?, + }; + + let result = match op.op { + Opcode::LAnd => (left > 0) && (right > 0), + Opcode::LOr => (left > 0) || (right > 0), + Opcode::LNotEqual => left != right, + Opcode::LLessEqual => left <= right, + Opcode::LGreaterEqual => left >= right, + Opcode::LEqual => left == right, + Opcode::LGreater => left > right, + Opcode::LLess => left < right, + _ => panic!(), + }; + let result = if result { Object::Integer(u64::MAX) } else { Object::Integer(0) }; + + context.contribute_arg(Argument::Object(result.wrap())); + context.retire_op(op); + Ok(()) + } + + fn do_to_buffer(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(operand), target] = &op.arguments[..] else { panic!() }; + + let result = match **operand { + Object::Buffer(ref bytes) => Object::Buffer(bytes.clone()), + Object::Integer(value) => { + if self.dsdt_revision >= 2 { + Object::Buffer(value.to_le_bytes().to_vec()) + } else { + Object::Buffer((value as u32).to_le_bytes().to_vec()) + } + } + Object::String(ref value) => { + // XXX: an empty string is converted to an empty buffer, *without* the null-terminator + if value.is_empty() { + Object::Buffer(vec![]) + } else { + let mut bytes = value.as_bytes().to_vec(); + bytes.push(b'\0'); + Object::Buffer(bytes) + } + } + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToBuffer, typ: operand.typ() })?, + } + .wrap(); + + // TODO: use result of store + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + fn do_to_integer(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(operand), target] = &op.arguments[..] else { panic!() }; + + let result = match **operand { + Object::Integer(value) => Object::Integer(value), + Object::Buffer(ref bytes) => { + /* + * The spec says this should respect the revision of the current definition block. + * Apparently, the NT interpreter always uses the first 8 bytes of the buffer. + */ + let mut to_interpret = [0u8; 8]; + (to_interpret[0..usize::min(bytes.len(), 8)]).copy_from_slice(&bytes); + Object::Integer(u64::from_le_bytes(to_interpret)) + } + Object::String(ref value) => { + /* + * This is about the same level of effort as ACPICA puts in. The uACPI test suite + * has tests that this fails - namely because of support for octal, signs, strings + * that won't fit in a `u64` etc. We probably need to write a more robust parser + * 'real' parser to handle those cases. + */ + if let Some(value) = value.strip_prefix("0x") { + let parsed = u64::from_str_radix(value, 16).map_err(|_| { + AmlError::InvalidOperationOnObject { op: Operation::ToInteger, typ: ObjectType::String } + })?; + Object::Integer(parsed) + } else { + let parsed = u64::from_str_radix(value, 10).map_err(|_| { + AmlError::InvalidOperationOnObject { op: Operation::ToInteger, typ: ObjectType::String } + })?; + Object::Integer(parsed) + } + } + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToBuffer, typ: operand.typ() })?, + } + .wrap(); + + // TODO: use result of store + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + fn do_to_string(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(source), Argument::Object(length), target] = &op.arguments[..] else { panic!() }; + let source = source.as_buffer()?; + let length = length.as_integer()? as usize; + + let result = if source.is_empty() { + Object::String(String::new()) + } else { + let mut buffer = source.split_inclusive(|b| *b == b'\0').next().unwrap(); + if length < usize::MAX { + buffer = &buffer[0..usize::min(length, buffer.len())]; + } + let string = str::from_utf8(buffer).map_err(|_| AmlError::InvalidOperationOnObject { + op: Operation::ToString, + typ: ObjectType::Buffer, + })?; + Object::String(string.to_string()) + } + .wrap(); + + // TODO: use result of store + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + /// Perform a `ToDecimalString` or `ToHexString` operation + fn do_to_dec_hex_string(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(operand), target] = &op.arguments[..] else { panic!() }; + let operand = operand.clone().unwrap_transparent_reference(); + + let result = match *operand { + Object::String(ref value) => Object::String(value.clone()), + Object::Integer(value) => match op.op { + Opcode::ToDecimalString => Object::String(value.to_string()), + Opcode::ToHexString => Object::String(alloc::format!("{:#x}", value)), + _ => panic!(), + }, + Object::Buffer(ref bytes) => { + if bytes.is_empty() { + Object::String(String::new()) + } else { + // TODO: there has GOT to be a better way to format directly into a string... + let mut string = String::new(); + for byte in bytes { + let as_str = match op.op { + Opcode::ToDecimalString => alloc::format!("{},", byte), + Opcode::ToHexString => alloc::format!("{:#04X},", byte), + _ => panic!(), + }; + string.push_str(&as_str); + } + // Remove last comma, if present + if !string.is_empty() { + string.pop(); + } + Object::String(string) + } + } + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ToDecOrHexString, typ: operand.typ() })?, + } + .wrap(); + + // TODO: use result of store + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + fn do_mid(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(source), Argument::Object(index), Argument::Object(length), target] = + &op.arguments[..] + else { + panic!() + }; + let index = index.as_integer()? as usize; + let length = length.as_integer()? as usize; + + let result = match **source { + Object::String(ref string) => { + if index >= string.len() { + Object::String(String::new()) + } else { + let upper = usize::min(index + length, index + string.len()); + let chars = &string[index..upper]; + Object::String(String::from(chars)) + } + } + Object::Buffer(ref buffer) => { + if index >= buffer.len() { + Object::Buffer(vec![]) + } else { + let upper = usize::min(index + length, index + buffer.len()); + let bytes = &buffer[index..upper]; + Object::Buffer(bytes.to_vec()) + } + } + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::Mid, typ: source.typ() })?, + } + .wrap(); + + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + + Ok(()) + } + + fn do_concat(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(source1), Argument::Object(source2), target] = &op.arguments[..] else { panic!() }; + fn resolve_as_string(obj: &Object) -> String { + match obj { + Object::Uninitialized => "[Uninitialized Object]".to_string(), + Object::Buffer(bytes) => String::from_utf8_lossy(&bytes).into_owned(), + Object::BufferField { .. } => "[Buffer Field]".to_string(), + Object::Device => "[Device]".to_string(), + Object::Event => "[Event]".to_string(), + Object::FieldUnit(_) => "[Field]".to_string(), + Object::Integer(value) => value.to_string(), + Object::Method { .. } => "[Control Method]".to_string(), + Object::Mutex { .. } => "[Mutex]".to_string(), + Object::Reference { inner, .. } => resolve_as_string(&*(inner.clone().unwrap_reference())), + Object::OpRegion(_) => "[Operation Region]".to_string(), + Object::Package(_) => "[Package]".to_string(), + Object::PowerResource { .. } => "[Power Resource]".to_string(), + Object::Processor { .. } => "[Processor]".to_string(), + // TODO: what even is one of these?? + Object::RawDataBuffer => todo!(), + Object::String(value) => value.clone(), + Object::ThermalZone => "[Thermal Zone]".to_string(), + Object::Debug => "[Debug Object]".to_string(), + } + } + + let result = match source1.typ() { + ObjectType::Integer => { + let source1 = source1.as_integer()?; + let source2 = source2.to_integer(if self.dsdt_revision >= 2 { 8 } else { 4 })?; + let mut buffer = Vec::new(); + if self.dsdt_revision >= 2 { + buffer.extend_from_slice(&source1.to_le_bytes()); + buffer.extend_from_slice(&source2.to_le_bytes()); + } else { + buffer.extend_from_slice(&(source1 as u32).to_le_bytes()); + buffer.extend_from_slice(&(source2 as u32).to_le_bytes()); + } + Object::Buffer(buffer).wrap() + } + ObjectType::Buffer => { + let mut buffer = source1.as_buffer()?.to_vec(); + buffer.extend(source2.to_buffer(if self.dsdt_revision >= 2 { 8 } else { 4 })?); + Object::Buffer(buffer).wrap() + } + ObjectType::String | _ => { + let source1 = resolve_as_string(&source1); + let source2 = resolve_as_string(&source2); + Object::String(source1 + &source2).wrap() + } + }; + // TODO: use result of store + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + fn do_from_bcd(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(value)] = &op.arguments[..] else { panic!() }; + let mut value = value.clone().unwrap_transparent_reference().as_integer()?; + + let mut result = 0; + let mut i = 1; + while value > 0 { + result += (value & 0x0f) * i; + i *= 10; + value >>= 4; + } + + context.contribute_arg(Argument::Object(Object::Integer(result).wrap())); + context.retire_op(op); + Ok(()) + } + + fn do_to_bcd(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(value)] = &op.arguments[..] else { panic!() }; + let mut value = value.clone().unwrap_transparent_reference().as_integer()?; + + let mut result = 0; + let mut i = 0; + while value > 0 { + result |= (value % 10) << (4 * i); + value /= 10; + i += 1; + } + + context.contribute_arg(Argument::Object(Object::Integer(result).wrap())); + context.retire_op(op); + Ok(()) + } + + fn do_size_of(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(object)] = &op.arguments[..] else { panic!() }; + let object = object.clone().unwrap_reference(); + + let result = match *object { + Object::Buffer(ref buffer) => buffer.len(), + Object::String(ref str) => str.len(), + Object::Package(ref package) => package.len(), + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::SizeOf, typ: object.typ() })?, + }; + + context.contribute_arg(Argument::Object(Object::Integer(result as u64).wrap())); + context.retire_op(op); + Ok(()) + } + + fn do_index(&self, context: &mut MethodContext, op: OpInFlight) -> Result<(), AmlError> { + let [Argument::Object(object), Argument::Object(index_value), target] = &op.arguments[..] else { + panic!() + }; + let index_value = index_value.as_integer()?; + + let result = match **object { + Object::Buffer(ref buffer) => { + if index_value as usize >= buffer.len() { + Err(AmlError::IndexOutOfBounds)? + } + + Object::Reference { + kind: ReferenceKind::RefOf, + inner: Object::BufferField { + buffer: object.clone(), + offset: index_value as usize * 8, + length: 8, + } + .wrap(), + } + } + Object::String(ref string) => { + if index_value as usize >= string.len() { + Err(AmlError::IndexOutOfBounds)? + } + + Object::Reference { + kind: ReferenceKind::RefOf, + inner: Object::BufferField { + buffer: object.clone(), + offset: index_value as usize * 8, + length: 8, + } + .wrap(), + } + } + Object::Package(ref package) => { + let Some(element) = package.get(index_value as usize) else { Err(AmlError::IndexOutOfBounds)? }; + Object::Reference { kind: ReferenceKind::RefOf, inner: element.clone() } + } + _ => Err(AmlError::IndexOutOfBounds)?, + } + .wrap(); + + self.do_store(target, result.clone())?; + context.contribute_arg(Argument::Object(result)); + context.retire_op(op); + Ok(()) + } + + // TODO: this might actually do weird stuff to your data if written to a field with BufferAcc + // access. I guess we need to return something here really and use it instead of the result + // when returning?? We need to look carefully at all use-sites to make sure it actually returns + // the result of the store, not the object it passed to us. + fn do_store(&self, target: &Argument, object: WrappedObject) -> Result<(), AmlError> { + // TODO: find the destination (need to handle references, debug objects, etc.) + // TODO: convert object to be of the type of destination, in line with 19.3.5 of the spec + // TODO: write the object to the destination, including e.g. field writes that then lead to + // literally god knows what. + let object = object.unwrap_transparent_reference(); + let token = self.object_token.lock(); + + match target { + Argument::Object(target) => match unsafe { target.gain_mut(&*token) } { + Object::Integer(target) => match unsafe { object.gain_mut(&*token) } { + Object::Integer(value) => { + *target = *value; + } + Object::BufferField { .. } => { + let mut buffer = [0u8; 8]; + unsafe { object.gain_mut(&*token) }.read_buffer_field(&mut buffer)?; + let value = u64::from_le_bytes(buffer); + *target = value; + } + Object::FieldUnit(field) => { + // TODO: not sure if we should convert buffers to integers if needed here? + *target = self.do_field_read(field)?.as_integer()?; + } + _ => { + let as_integer = object.to_integer(if self.dsdt_revision >= 2 { 8 } else { 4 })?; + *target = as_integer; + } + }, + Object::BufferField { .. } => match unsafe { object.gain_mut(&*token) } { + Object::Integer(value) => { + unsafe { target.gain_mut(&*token) }.write_buffer_field(&value.to_le_bytes(), &*token)?; + } + Object::Buffer(value) => { + unsafe { target.gain_mut(&*token) }.write_buffer_field(&value.as_slice(), &*token)?; + } + _ => panic!(), + }, + Object::FieldUnit(field) => self.do_field_write(field, object)?, + Object::Reference { kind, inner } => { + match kind { + ReferenceKind::RefOf => todo!(), + ReferenceKind::LocalOrArg => { + if let Object::Reference { kind: inner_kind, inner: inner_inner } = &**inner { + // TODO: this should store into the reference, potentially doing an + // implicit cast + unsafe { + *inner_inner.gain_mut(&*token) = object.gain_mut(&*token).clone(); + } + } else { + // Overwrite the value + unsafe { + *inner.gain_mut(&*token) = object.gain_mut(&*token).clone(); + } + } + } + ReferenceKind::Unresolved => todo!(), + } + } + Object::Debug => { + self.handler.handle_debug(&*object); + } + _ => panic!("Stores to objects like {:?} are not yet supported", target), + }, + + Argument::Namestring(_) => {} + Argument::ByteData(_) | Argument::DWordData(_) | Argument::TrackedPc(_) | Argument::PkgLength(_) => { + panic!() + } + } + Ok(()) + } + + /// Do a read from a field by performing one or more well-formed accesses to the underlying + /// operation regions, and then shifting and masking the resulting value as appropriate. Will + /// return either an `Integer` or `Buffer` as appropriate, guided by the size of the field + /// and expected integer size (as per the DSDT revision). + fn do_field_read(&self, field: &FieldUnit) -> Result { + let needs_buffer = if self.dsdt_revision >= 2 { field.bit_length > 64 } else { field.bit_length > 32 }; + let access_width_bits = field.flags.access_type_bytes()? * 8; + + trace!("AML field read. Field = {:?}", field); + + // TODO: if the field needs to be locked, acquire/release a global mutex? + + enum Output { + Integer([u8; 8]), + Buffer(Vec), + } + let mut output = if needs_buffer { + Output::Buffer(vec![0; field.bit_length.next_multiple_of(8)]) + } else { + Output::Integer([0; 8]) + }; + let output_bytes = match &mut output { + Output::Buffer(bytes) => bytes.as_mut_slice(), + Output::Integer(value) => value, + }; + + let read_region = match field.kind { + FieldUnitKind::Normal { ref region } => region, + FieldUnitKind::Bank { ref region, ref bank, bank_value } => { + // TODO: put the bank_value in the bank + todo!(); + region + } + FieldUnitKind::Index { ref index, ref data } => { + // TODO: configure the correct index + todo!(); + data + } + }; + let Object::OpRegion(ref read_region) = **read_region else { panic!() }; + + /* + * TODO: it might be worth having a fast path here for reads that don't do weird + * unaligned accesses, which I'm guessing might be relatively common on real + * hardware? Eg. single native read + mask + */ + + /* + * Break the field read into native reads that respect the region's access width. + * Copy each potentially-unaligned part into the destination's bit range. + */ + let native_accesses_needed = (field.bit_length + (field.bit_index % access_width_bits)) + .next_multiple_of(access_width_bits) + / access_width_bits; + let mut read_so_far = 0; + for i in 0..native_accesses_needed { + let aligned_offset = object::align_down(field.bit_index + i * access_width_bits, access_width_bits); + let raw = self.do_native_region_read(read_region, aligned_offset / 8, access_width_bits / 8)?; + let src_index = if i == 0 { field.bit_index % access_width_bits } else { 0 }; + let remaining_length = field.bit_length - read_so_far; + let length = if i == 0 { + usize::min(remaining_length, access_width_bits - (field.bit_index % access_width_bits)) + } else { + usize::min(remaining_length, access_width_bits) + }; + + object::copy_bits(&raw.to_le_bytes(), src_index, output_bytes, read_so_far, length); + read_so_far += length; + } + + match output { + Output::Buffer(bytes) => Ok(Object::Buffer(bytes).wrap()), + Output::Integer(value) => Ok(Object::Integer(u64::from_le_bytes(value)).wrap()), + } + } + + fn do_field_write(&self, field: &FieldUnit, value: WrappedObject) -> Result<(), AmlError> { + trace!("AML field write. Field = {:?}. Value = {:?}", field, value); + + let value_bytes = match &*value { + Object::Integer(value) => &value.to_le_bytes() as &[u8], + Object::Buffer(bytes) => &bytes, + _ => Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: value.typ() })?, + }; + let access_width_bits = field.flags.access_type_bytes()? * 8; + + let write_region = match field.kind { + FieldUnitKind::Normal { ref region } => region, + FieldUnitKind::Bank { ref region, ref bank, bank_value } => { + // TODO: put the bank_value in the bank + todo!(); + region + } + FieldUnitKind::Index { ref index, ref data } => { + // TODO: configure the correct index + todo!(); + data + } + }; + let Object::OpRegion(ref write_region) = **write_region else { panic!() }; + + // TODO: if the region wants locking, do that + + // TODO: maybe also a fast path for writes + + let native_accesses_needed = (field.bit_length + (field.bit_index % access_width_bits)) + .next_multiple_of(access_width_bits) + / access_width_bits; + let mut written_so_far = 0; + + for i in 0..native_accesses_needed { + let aligned_offset = object::align_down(field.bit_index + i * access_width_bits, access_width_bits); + let dst_index = if i == 0 { field.bit_index % access_width_bits } else { 0 }; + + /* + * If we're not going to write a whole native access, respect the field's + * update rule. If we're meant to preserve the surrounding bits, we need to do + * a read first. + */ + let mut bytes = if dst_index > 0 || (field.bit_length - written_so_far) < access_width_bits { + match field.flags.update_rule() { + FieldUpdateRule::Preserve => self + .do_native_region_read(write_region, aligned_offset / 8, access_width_bits / 8)? + .to_le_bytes(), + FieldUpdateRule::WriteAsOnes => [0xff; 8], + FieldUpdateRule::WriteAsZeros => [0; 8], + } + } else { + [0; 8] + }; + + let remaining_length = field.bit_length - written_so_far; + let length = if i == 0 { + usize::min(remaining_length, access_width_bits - (field.bit_index % access_width_bits)) + } else { + usize::min(remaining_length, access_width_bits) + }; + + object::copy_bits(value_bytes, written_so_far, &mut bytes, dst_index, length); + self.do_native_region_write( + write_region, + aligned_offset / 8, + access_width_bits / 8, + u64::from_le_bytes(bytes), + )?; + written_so_far += length; + } + + Ok(()) + } + + /// Performs an actual read from an operation region. `offset` and `length` must respect the + /// access requirements of the field being read, and are supplied in **bytes**. This may call + /// AML methods if required, and may invoke user-supplied handlers. + fn do_native_region_read(&self, region: &OpRegion, offset: usize, length: usize) -> Result { + trace!("Native field read. Region = {:?}, offset = {:#x}, length={:#x}", region, offset, length); + + match region.space { + RegionSpace::SystemMemory => Ok({ + let address = region.base as usize + offset; + match length { + 1 => self.handler.read_u8(address) as u64, + 2 => self.handler.read_u16(address) as u64, + 4 => self.handler.read_u32(address) as u64, + 8 => self.handler.read_u64(address) as u64, + _ => panic!(), + } + }), + RegionSpace::SystemIO => Ok({ + let address = region.base as u16 + offset as u16; + match length { + 1 => self.handler.read_io_u8(address) as u64, + 2 => self.handler.read_io_u16(address) as u64, + 4 => self.handler.read_io_u32(address) as u64, + _ => panic!(), + } + }), + RegionSpace::PciConfig => { + let address = self.pci_address_for_device(®ion.parent_device_path)?; + match length { + 1 => Ok(self.handler.read_pci_u8(address, offset as u16) as u64), + 2 => Ok(self.handler.read_pci_u16(address, offset as u16) as u64), + 4 => Ok(self.handler.read_pci_u32(address, offset as u16) as u64), + _ => panic!(), + } + } + + RegionSpace::EmbeddedControl + | RegionSpace::SmBus + | RegionSpace::SystemCmos + | RegionSpace::PciBarTarget + | RegionSpace::Ipmi + | RegionSpace::GeneralPurposeIo + | RegionSpace::GenericSerialBus + | RegionSpace::Pcc + | RegionSpace::Oem(_) => { + if let Some(handler) = self.region_handlers.lock().get(®ion.space) { + todo!("Utilise handler"); + } else { + Err(AmlError::NoHandlerForRegionAccess(region.space)) + } + } + } + } + + /// Performs an actual write to an operation region. `offset` and `length` must respect the + /// access requirements of the field being read, and are supplied in **bytes**. This may call + /// AML methods if required, and may invoke user-supplied handlers. + fn do_native_region_write( + &self, + region: &OpRegion, + offset: usize, + length: usize, + value: u64, + ) -> Result<(), AmlError> { + trace!( + "Native field write. Region = {:?}, offset = {:#x}, length={:#x}, value={:#x}", + region, offset, length, value + ); + + match region.space { + RegionSpace::SystemMemory => Ok({ + let address = region.base as usize + offset; + match length { + 1 => self.handler.write_u8(address, value as u8), + 2 => self.handler.write_u16(address, value as u16), + 4 => self.handler.write_u32(address, value as u32), + 8 => self.handler.write_u64(address, value), + _ => panic!(), + } + }), + RegionSpace::SystemIO => Ok({ + let address = region.base as u16 + offset as u16; + match length { + 1 => self.handler.write_io_u8(address, value as u8), + 2 => self.handler.write_io_u16(address, value as u16), + 4 => self.handler.write_io_u32(address, value as u32), + _ => panic!(), + } + }), + RegionSpace::PciConfig => { + let address = self.pci_address_for_device(®ion.parent_device_path)?; + match length { + 1 => self.handler.write_pci_u8(address, offset as u16, value as u8), + 2 => self.handler.write_pci_u16(address, offset as u16, value as u16), + 4 => self.handler.write_pci_u32(address, offset as u16, value as u32), + _ => panic!(), + } + Ok(()) + } + + RegionSpace::EmbeddedControl + | RegionSpace::SmBus + | RegionSpace::SystemCmos + | RegionSpace::PciBarTarget + | RegionSpace::Ipmi + | RegionSpace::GeneralPurposeIo + | RegionSpace::GenericSerialBus + | RegionSpace::Pcc + | RegionSpace::Oem(_) => { + if let Some(handler) = self.region_handlers.lock().get(®ion.space) { + todo!("Utilise handler"); + } else { + Err(AmlError::NoHandlerForRegionAccess(region.space)) + } + } + } + } + + fn pci_address_for_device(&self, path: &AmlName) -> Result { + /* + * TODO: it's not ideal to do these reads for every native access. See if we can + * cache them somewhere? + */ + let seg = match self.invoke_method_if_present(AmlName::from_str("_SEG").unwrap().resolve(path)?, vec![])? { + Some(value) => value.as_integer()?, + None => 0, + }; + let bus = match self.invoke_method_if_present(AmlName::from_str("_BBR").unwrap().resolve(path)?, vec![])? { + Some(value) => value.as_integer()?, + None => 0, + }; + let (device, function) = { + let adr = self.invoke_method_if_present(AmlName::from_str("_ADR").unwrap().resolve(path)?, vec![])?; + let adr = match adr { + Some(adr) => adr.as_integer()?, + None => 0, + }; + (adr.get_bits(16..32), adr.get_bits(0..16)) + }; + Ok(PciAddress::new(seg as u16, bus as u8, device as u8, function as u8)) + } +} + +/// A `MethodContext` represents a piece of running AML code - either a real method, or the +/// top-level of an AML table. +/// +/// ### Safety +/// `MethodContext` does not keep the lifetime of the underlying AML stream, which for tables is +/// borrowed from the underlying physical mapping. This is because the interpreter needs to +/// preempt method contexts that execute other methods, and these contexts may have disparate +/// lifetimes. This is made safe in the case of methods by the context holding a reference to the +/// method object, but must be handled manually for AML tables. +struct MethodContext { + current_block: Block, + block_stack: Vec, + in_flight: Vec, + args: [WrappedObject; 8], + locals: [WrappedObject; 8], + current_scope: AmlName, + + _method: Option, +} + +#[derive(Debug)] +struct OpInFlight { + op: Opcode, + expected_arguments: usize, + arguments: Vec, +} + +#[derive(Debug)] +enum Argument { + Object(WrappedObject), + Namestring(AmlName), + ByteData(u8), + DWordData(u32), + TrackedPc(usize), + PkgLength(usize), +} + +struct Block { + stream: *const [u8], + pc: usize, + kind: BlockKind, +} + +impl Block { + fn stream(&self) -> &[u8] { + unsafe { &*self.stream } + } +} + +#[derive(PartialEq, Debug)] +pub enum BlockKind { + Table, + Method { + method_scope: AmlName, + }, + Scope { + old_scope: AmlName, + }, + Package, + /// Used for executing the then-branch of an `DefIfElse`. After finishing, it will check for + /// and skip over an else-branch, if present. + IfThenBranch, + While { + start_pc: usize, + }, +} + +impl OpInFlight { + pub fn new(op: Opcode, expected_arguments: usize) -> OpInFlight { + OpInFlight { op, expected_arguments, arguments: Vec::new() } + } + + pub fn new_with(op: Opcode, arguments: Vec, more: usize) -> OpInFlight { + OpInFlight { op, expected_arguments: arguments.len() + more, arguments } + } +} + +impl MethodContext { + unsafe fn new_from_table(stream: &[u8]) -> MethodContext { + let block = Block { stream: stream as *const [u8], pc: 0, kind: BlockKind::Table }; + MethodContext { + current_block: block, + block_stack: Vec::new(), + in_flight: Vec::new(), + args: core::array::from_fn(|_| Object::Uninitialized.wrap()), + locals: core::array::from_fn(|_| Object::Uninitialized.wrap()), + current_scope: AmlName::root(), + _method: None, + } + } + + fn new_from_method( + method: WrappedObject, + args: Vec, + scope: AmlName, + ) -> Result { + if let Object::Method { code, flags } = &*method { + if args.len() != flags.arg_count() { + return Err(AmlError::MethodArgCountIncorrect); + } + let block = Block { + stream: code as &[u8] as *const [u8], + pc: 0, + kind: BlockKind::Method { method_scope: scope.clone() }, + }; + let args = core::array::from_fn(|i| { + if let Some(arg) = args.get(i) { arg.clone() } else { Object::Uninitialized.wrap() } + }); + let context = MethodContext { + current_block: block, + block_stack: Vec::new(), + in_flight: Vec::new(), + args, + locals: core::array::from_fn(|_| Object::Uninitialized.wrap()), + current_scope: scope, + _method: Some(method.clone()), + }; + Ok(context) + } else { + Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Method, got: method.typ() }) + } + } + + fn last_op(&mut self) -> Result<&mut OpInFlight, AmlError> { + match self.in_flight.last_mut() { + Some(op) => Ok(op), + None => Err(AmlError::NoCurrentOp), + } + } + + fn contribute_arg(&mut self, arg: Argument) { + if let Some(in_flight) = self.in_flight.last_mut() { + if in_flight.arguments.len() < in_flight.expected_arguments { + in_flight.arguments.push(arg); + } + } + } + + fn start_in_flight_op(&mut self, op: OpInFlight) { + trace!( + "START OP: {:?}, args: {:?}, with {} more needed", + op.op, + op.arguments, + op.expected_arguments - op.arguments.len() + ); + self.in_flight.push(op); + } + + fn retire_op(&mut self, op: OpInFlight) { + trace!("RETIRE OP: {:?}, args: {:?}", op.op, op.arguments); + } + + fn start_new_block(&mut self, kind: BlockKind, length: usize) { + let block = Block { + stream: &self.current_block.stream()[..(self.current_block.pc + length)] as *const [u8], + pc: self.current_block.pc, + kind, + }; + self.current_block.pc += length; + self.block_stack.push(mem::replace(&mut self.current_block, block)); + } + + fn opcode(&mut self) -> Result { + let opcode: u16 = match self.next()? { + 0x5b => { + let ext = self.next()?; + (0x5b << 8) as u16 | ext as u16 + } + other => other as u16, + }; + + Ok(match opcode { + 0x00 => Opcode::Zero, + 0x01 => Opcode::One, + 0x06 => Opcode::Alias, + 0x08 => Opcode::Name, + 0x0a => Opcode::BytePrefix, + 0x0b => Opcode::WordPrefix, + 0x0c => Opcode::DWordPrefix, + 0x0d => Opcode::StringPrefix, + 0x0e => Opcode::QWordPrefix, + 0x10 => Opcode::Scope, + 0x11 => Opcode::Buffer, + 0x12 => Opcode::Package, + 0x13 => Opcode::VarPackage, + 0x14 => Opcode::Method, + 0x15 => Opcode::External, + 0x2e => Opcode::DualNamePrefix, + 0x2f => Opcode::MultiNamePrefix, + 0x30..=0x39 => Opcode::Digit(opcode as u8), // b'0'..=b'9' + 0x41..=0x5a => Opcode::NameChar(opcode as u8), // b'A'..=b'Z' + 0x5b01 => Opcode::Mutex, + 0x5b02 => Opcode::Event, + 0x5b12 => Opcode::CondRefOf, + 0x5b13 => Opcode::CreateField, + 0x5b1f => Opcode::LoadTable, + 0x5b20 => Opcode::Load, + 0x5b21 => Opcode::Stall, + 0x5b22 => Opcode::Sleep, + 0x5b23 => Opcode::Acquire, + 0x5b24 => Opcode::Signal, + 0x5b25 => Opcode::Wait, + 0x5b26 => Opcode::Reset, + 0x5b27 => Opcode::Release, + 0x5b28 => Opcode::FromBCD, + 0x5b29 => Opcode::ToBCD, + 0x5b30 => Opcode::Revision, + 0x5b31 => Opcode::Debug, + 0x5b32 => Opcode::Fatal, + 0x5b33 => Opcode::Timer, + 0x5b80 => Opcode::OpRegion, + 0x5b81 => Opcode::Field, + 0x5b82 => Opcode::Device, + 0x5b83 => Opcode::Processor, + 0x5b84 => Opcode::PowerRes, + 0x5b85 => Opcode::ThermalZone, + 0x5b86 => Opcode::IndexField, + 0x5b87 => Opcode::BankField, + 0x5b88 => Opcode::DataRegion, + 0x5c => Opcode::RootChar, + 0x5e => Opcode::ParentPrefixChar, + 0x5f => Opcode::NameChar(b'_'), + 0x60..=0x67 => Opcode::Local(opcode as u8 - 0x60), + 0x68..=0x6e => Opcode::Arg(opcode as u8 - 0x68), + 0x70 => Opcode::Store, + 0x71 => Opcode::RefOf, + 0x72 => Opcode::Add, + 0x73 => Opcode::Concat, + 0x74 => Opcode::Subtract, + 0x75 => Opcode::Increment, + 0x76 => Opcode::Decrement, + 0x77 => Opcode::Multiply, + 0x78 => Opcode::Divide, + 0x79 => Opcode::ShiftLeft, + 0x7a => Opcode::ShiftRight, + 0x7b => Opcode::And, + 0x7c => Opcode::Nand, + 0x7d => Opcode::Or, + 0x7e => Opcode::Nor, + 0x7f => Opcode::Xor, + 0x80 => Opcode::Not, + 0x81 => Opcode::FindSetLeftBit, + 0x82 => Opcode::FindSetRightBit, + 0x83 => Opcode::DerefOf, + 0x84 => Opcode::ConcatRes, + 0x85 => Opcode::Mod, + 0x86 => Opcode::Notify, + 0x87 => Opcode::SizeOf, + 0x88 => Opcode::Index, + 0x89 => Opcode::Match, + 0x8a => Opcode::CreateDWordField, + 0x8b => Opcode::CreateWordField, + 0x8c => Opcode::CreateByteField, + 0x8d => Opcode::CreateBitField, + 0x8e => Opcode::ObjectType, + 0x8f => Opcode::CreateQWordField, + 0x90 => Opcode::LAnd, + 0x91 => Opcode::LOr, + /* + * `0x92` is a bit strange. It can be an opcode in its own right (`LNotOp`), but when + * followed by `0x93..=0x95`, it instead serves as a negating prefix to encode + * `LNotEqualOp`, `LLessEqualOp`, and `LGreaterEqualOp`. + */ + 0x92 => match self.peek() { + Ok(0x93) => { + self.current_block.pc += 1; + Opcode::LNotEqual + } + Ok(0x94) => { + self.current_block.pc += 1; + Opcode::LLessEqual + } + Ok(0x95) => { + self.current_block.pc += 1; + Opcode::LGreaterEqual + } + _ => Opcode::LNot, + }, + 0x93 => Opcode::LEqual, + 0x94 => Opcode::LGreater, + 0x95 => Opcode::LLess, + 0x96 => Opcode::ToBuffer, + 0x97 => Opcode::ToDecimalString, + 0x98 => Opcode::ToHexString, + 0x99 => Opcode::ToInteger, + 0x9c => Opcode::ToString, + 0x9d => Opcode::CopyObject, + 0x9e => Opcode::Mid, + 0x9f => Opcode::Continue, + 0xa0 => Opcode::If, + 0xa1 => Opcode::Else, + 0xa2 => Opcode::While, + 0xa3 => Opcode::Noop, + 0xa4 => Opcode::Return, + 0xa5 => Opcode::Break, + 0xcc => Opcode::Breakpoint, + 0xff => Opcode::Ones, + + _ => Err(AmlError::IllegalOpcode(opcode))?, + }) + } + + fn pkglength(&mut self) -> Result { + let lead_byte = self.next()?; + let byte_count = lead_byte.get_bits(6..8); + assert!(byte_count < 4); + + if byte_count == 0 { + Ok(lead_byte.get_bits(0..6) as usize) + } else { + let mut length = lead_byte.get_bits(0..4) as usize; + for i in 0..byte_count { + length |= (self.next()? as usize) << (4 + i * 8); + } + Ok(length) + } + } + + fn namestring(&mut self) -> Result { + use namespace::{NameComponent, NameSeg}; + + /* + * The NameString grammar is actually a little finicky and annoying. + * + * NameString := | + * PrefixPath := Nothing | <'^' PrefixPath> + * NamePath := NameSeg | DualNamePath | MultiNamePath | NullName + * DualNamePath := DualNamePrefix NameSeg NameSeg + * MultiNamePath := MultiNamePrefix SegCount NameSeg(SegCount) + */ + const NULL_NAME: u8 = 0x00; + const DUAL_NAME_PREFIX: u8 = 0x2e; + const MULTI_NAME_PREFIX: u8 = 0x2f; + + let mut components = vec![]; + + match self.peek()? { + b'\\' => { + self.next()?; + components.push(NameComponent::Root); + } + b'^' => { + components.push(NameComponent::Prefix); + self.next()?; + while self.peek()? == b'^' { + self.next()?; + components.push(NameComponent::Prefix); + } + } + _ => (), + } + + let next = self.next()?; + match next { + NULL_NAME => {} + DUAL_NAME_PREFIX => { + for _ in 0..2 { + let name_seg = [self.next()?, self.next()?, self.next()?, self.next()?]; + components.push(NameComponent::Segment(NameSeg::from_bytes(name_seg)?)); + } + } + MULTI_NAME_PREFIX => { + let count = self.next()?; + for _ in 0..count { + let name_seg = [self.next()?, self.next()?, self.next()?, self.next()?]; + components.push(NameComponent::Segment(NameSeg::from_bytes(name_seg)?)); + } + } + first_char => { + if !namespace::is_lead_name_char(first_char) { + self.current_block.pc -= 1; + } + let name_seg = [first_char, self.next()?, self.next()?, self.next()?]; + components.push(namespace::NameComponent::Segment(namespace::NameSeg::from_bytes(name_seg)?)); + } + } + + Ok(AmlName::from_components(components)) + } + + fn next(&mut self) -> Result { + if self.current_block.pc >= self.current_block.stream.len() { + return Err(AmlError::RunOutOfStream); + } + + let byte = self.current_block.stream()[self.current_block.pc]; + self.current_block.pc += 1; + + Ok(byte) + } + + fn next_u16(&mut self) -> Result { + Ok(u16::from_le_bytes([self.next()?, self.next()?])) + } + + fn next_u32(&mut self) -> Result { + Ok(u32::from_le_bytes([self.next()?, self.next()?, self.next()?, self.next()?])) + } + + fn next_u64(&mut self) -> Result { + Ok(u64::from_le_bytes([ + self.next()?, + self.next()?, + self.next()?, + self.next()?, + self.next()?, + self.next()?, + self.next()?, + self.next()?, + ])) + } + + fn peek(&self) -> Result { + if self.current_block.pc >= self.current_block.stream.len() { + return Err(AmlError::RunOutOfStream); + } + + Ok(self.current_block.stream()[self.current_block.pc]) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Opcode { + Zero, + One, + Alias, + Name, + BytePrefix, + WordPrefix, + DWordPrefix, + StringPrefix, + QWordPrefix, + Scope, + Buffer, + Package, + VarPackage, + Method, + External, + DualNamePrefix, + MultiNamePrefix, + Digit(u8), + NameChar(u8), + Mutex, + Event, + CondRefOf, + CreateField, + LoadTable, + Load, + Stall, + Sleep, + Acquire, + Signal, + Wait, + Reset, + Release, + FromBCD, + ToBCD, + Revision, + Debug, + Fatal, + Timer, + OpRegion, + Field, + Device, + Processor, + PowerRes, + ThermalZone, + IndexField, + BankField, + DataRegion, + RootChar, + ParentPrefixChar, + Local(u8), + Arg(u8), + Store, + RefOf, + Add, + Concat, + Subtract, + Increment, + Decrement, + Multiply, + Divide, + ShiftLeft, + ShiftRight, + And, + Nand, + Or, + Nor, + Xor, + Not, + FindSetLeftBit, + FindSetRightBit, + DerefOf, + ConcatRes, + Mod, + Notify, + SizeOf, + Index, + Match, + CreateDWordField, + CreateWordField, + CreateByteField, + CreateBitField, + ObjectType, + CreateQWordField, + LAnd, + LOr, + LNot, + LNotEqual, + LLessEqual, + LGreaterEqual, + LEqual, + LGreater, + LLess, + ToBuffer, + ToDecimalString, + ToHexString, + ToInteger, + ToString, + CopyObject, + Mid, + Continue, + If, + Else, + While, + Noop, + Return, + Break, + Breakpoint, + Ones, + + /* + * Internal opcodes are not produced from the bytecode, but are used to track special in-flight + * ops etc. + */ + InternalMethodCall, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum Operation { + Mid, + SizeOf, + Acquire, + Release, + ConvertToBuffer, + + ToBuffer, + ToInteger, + ToString, + ToDecOrHexString, + + ReadBufferField, + WriteBufferField, + LogicalOp, + DecodePrt, + ParseResource, +} + +#[derive(Clone, PartialEq, Debug)] +#[non_exhaustive] +pub enum AmlError { + RunOutOfStream, + IllegalOpcode(u16), + InvalidFieldFlags, + + InvalidName(Option), + + InvalidNameSeg([u8; 4]), + InvalidNormalizedName(AmlName), + RootHasNoParent, + EmptyNamesAreInvalid, + LevelDoesNotExist(AmlName), + NameCollision(AmlName), + ObjectDoesNotExist(AmlName), + + NoCurrentOp, + ElseFoundWithoutCorrespondingIf, + ContinueOutsideOfWhile, + BreakOutsideOfWhile, + + MethodArgCountIncorrect, + + InvalidOperationOnObject { op: Operation, typ: ObjectType }, + IndexOutOfBounds, + ObjectNotOfExpectedType { expected: ObjectType, got: ObjectType }, + + InvalidResourceDescriptor, + UnexpectedResourceType, + + NoHandlerForRegionAccess(RegionSpace), + MutexAquireTimeout, + + PrtInvalidAddress, + PrtInvalidPin, + PrtInvalidGsi, + PrtInvalidSource, + PrtNoEntry, +} + +/// A `Handle` is an opaque reference to an object that is managed by the user of this library. +/// They should be returned by the `create_*` methods on `Handler`, and are then used by methods to +/// refer to a specific object. +/// +/// The library will treat the value of a handle as entirely opaque. You may manage handles +/// however you wish, and the same value can be used to refer to objects of different types, if +/// desired. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Handle(pub u32); + +/// This trait represents the interface from the `Interpreter` to the hosting kernel, and allows +/// AML to interact with the underlying hardware. +/// +/// ### Implementation notes +/// Reads and writes to PCI devices must succeed for devices that are not detected during +/// enumeration of the PCI bus / do not exist. +pub trait Handler: Send + Sync { + fn read_u8(&self, address: usize) -> u8; + fn read_u16(&self, address: usize) -> u16; + fn read_u32(&self, address: usize) -> u32; + fn read_u64(&self, address: usize) -> u64; + + fn write_u8(&self, address: usize, value: u8); + fn write_u16(&self, address: usize, value: u16); + fn write_u32(&self, address: usize, value: u32); + fn write_u64(&self, address: usize, value: u64); + + fn read_io_u8(&self, port: u16) -> u8; + fn read_io_u16(&self, port: u16) -> u16; + fn read_io_u32(&self, port: u16) -> u32; + + fn write_io_u8(&self, port: u16, value: u8); + fn write_io_u16(&self, port: u16, value: u16); + fn write_io_u32(&self, port: u16, value: u32); + + fn read_pci_u8(&self, address: PciAddress, offset: u16) -> u8; + fn read_pci_u16(&self, address: PciAddress, offset: u16) -> u16; + fn read_pci_u32(&self, address: PciAddress, offset: u16) -> u32; + + fn write_pci_u8(&self, address: PciAddress, offset: u16, value: u8); + fn write_pci_u16(&self, address: PciAddress, offset: u16, value: u16); + fn write_pci_u32(&self, address: PciAddress, offset: u16, value: u32); + + /// Returns a monotonically-increasing value of nanoseconds. + fn nanos_since_boot(&self) -> u64; + + /// Stall for at least the given number of **microseconds**. An implementation should not relinquish control of + /// the processor during the stall, and for this reason, firmwares should not stall for periods of more than + /// 100 microseconds. + fn stall(&self, microseconds: u64); + + /// Sleep for at least the given number of **milliseconds**. An implementation may round to the closest sleep + /// time supported, and should relinquish the processor. + fn sleep(&self, milliseconds: u64); + + fn create_mutex(&self) -> Handle; + + /// Acquire the mutex referred to by the given handle. `timeout` is a millisecond timeout value + /// with the following meaning: + /// - `0` - try to acquire the mutex once, in a non-blocking manner. If the mutex cannot be + /// acquired immediately, return `Err(AmlError::MutexAquireTimeout)` + /// - `1-0xfffe` - try to acquire the mutex for at least `timeout` milliseconds. + /// - `0xffff` - try to acquire the mutex indefinitely. Should not return `MutexAquireTimeout`. + /// + /// AML mutexes are **reentrant** - that is, a thread may acquire the same mutex more than once + /// without causing a deadlock. + fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError>; + fn release(&self, mutex: Handle); + + fn breakpoint(&self) {} + + fn handle_debug(&self, _object: &Object) {} + + fn handle_fatal_error(&self, fatal_type: u8, fatal_code: u32, fatal_arg: u64) { + panic!( + "Fatal error while executing AML (encountered DefFatalOp). fatal_type = {}, fatal_code = {}, fatal_arg = {}", + fatal_type, fatal_code, fatal_arg + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::str::FromStr; + + struct TestHandler; + #[rustfmt::skip] + impl Handler for TestHandler { + fn read_u8(&self, _address: usize) -> u8 {0} + fn read_u16(&self, _address: usize) -> u16 {0} + fn read_u32(&self, _address: usize) -> u32 {0} + fn read_u64(&self, _address: usize) -> u64 {0} + fn write_u8(&self, _address: usize, _value: u8) {} + fn write_u16(&self, _address: usize, _value: u16) {} + fn write_u32(&self, _address: usize, _value: u32) {} + fn write_u64(&self, _address: usize, _value: u64) {} + fn read_io_u8(&self, _port: u16) -> u8 {0} + fn read_io_u16(&self, _port: u16) -> u16 {0} + fn read_io_u32(&self, _port: u16) -> u32 {0} + fn write_io_u8(&self, _port: u16, _value: u8) {} + fn write_io_u16(&self, _port: u16, _value: u16) {} + fn write_io_u32(&self, _port: u16, _value: u32) {} + fn read_pci_u8(&self, _address: PciAddress, _offset: u16) -> u8 {0} + fn read_pci_u16(&self, _address: PciAddress, _offset: u16) -> u16 {0} + fn read_pci_u32(&self, _address: PciAddress, _offset: u16) -> u32 {0} + fn write_pci_u8(&self, _address: PciAddress, _offset: u16, _value: u8) {} + fn write_pci_u16(&self, _address: PciAddress, _offset: u16, _value: u16) {} + fn write_pci_u32(&self, _address: PciAddress, _offset: u16, _value: u32) {} + fn nanos_since_boot(&self) -> u64 {0} + fn stall(&self, _microseconds: u64) {} + fn sleep(&self, _milliseconds: u64) {} + fn create_mutex(&self) -> Handle { Handle(0) } + fn acquire(&self, _mutex: Handle, _timeout: u16) -> Result<(), AmlError> { Ok(()) } + fn release(&self, _mutex: Handle) {} + } + + #[test] + fn verify_interpreter_send_sync() { + fn test_send_sync() {} + test_send_sync::>(); + } + + #[test] + fn add_op() { + let interpreter = Interpreter::new(TestHandler, 2); + // AddOp 0x0e 0x06 => Local2 + interpreter.load_table(&[0x72, 0x0b, 0x0e, 0x00, 0x0a, 0x06, 0x62]).unwrap(); + // AddOp 0x0e (AddOp 0x01 0x03 => Local1) => Local1 + interpreter.load_table(&[0x72, 0x0a, 0x0e, 0x72, 0x0a, 0x01, 0x0a, 0x03, 0x61, 0x61]).unwrap(); + } + + #[test] + fn names() { + assert_eq!( + unsafe { MethodContext::new_from_table(b"\\\x2eABC_DEF_\0") }.namestring(), + Ok(AmlName::from_str("\\ABC.DEF").unwrap()) + ); + } +} diff --git a/src/aml/namespace.rs b/src/aml/namespace.rs new file mode 100644 index 00000000..9f3bac0b --- /dev/null +++ b/src/aml/namespace.rs @@ -0,0 +1,578 @@ +use super::object::WrappedObject; +use crate::aml::{AmlError, object::Object}; +use alloc::{ + collections::btree_map::BTreeMap, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, +}; +use bit_field::BitField; +use core::{fmt, str, str::FromStr}; +use log::trace; + +#[derive(Clone)] +pub struct Namespace { + root: NamespaceLevel, +} + +impl Namespace { + /// Create a new AML namespace, with the expected pre-defined objects. + pub fn new() -> Namespace { + let mut namespace = Namespace { root: NamespaceLevel::new(NamespaceLevelKind::Scope) }; + + namespace.add_level(AmlName::from_str("\\_GPE").unwrap(), NamespaceLevelKind::Scope).unwrap(); + namespace.add_level(AmlName::from_str("\\_SB").unwrap(), NamespaceLevelKind::Scope).unwrap(); + namespace.add_level(AmlName::from_str("\\_SI").unwrap(), NamespaceLevelKind::Scope).unwrap(); + namespace.add_level(AmlName::from_str("\\_PR").unwrap(), NamespaceLevelKind::Scope).unwrap(); + namespace.add_level(AmlName::from_str("\\_TZ").unwrap(), NamespaceLevelKind::Scope).unwrap(); + + // TODO: add pre-defined objects as well - \GL, \OSI, etc. + + namespace + } + + pub fn add_level(&mut self, path: AmlName, kind: NamespaceLevelKind) -> Result<(), AmlError> { + assert!(path.is_absolute()); + let path = path.normalize()?; + + // Don't try to recreate the root scope + if path != AmlName::root() { + let (level, last_seg) = self.get_level_for_path_mut(&path)?; + + /* + * If the level has already been added, we don't need to add it again. The parser can try to add it + * multiple times if the ASL contains multiple blocks that add to the same scope/device. + */ + level.children.entry(last_seg).or_insert_with(|| NamespaceLevel::new(kind)); + } + + Ok(()) + } + + pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> { + assert!(path.is_absolute()); + let path = path.normalize()?; + + // Don't try to remove the root scope + // TODO: we probably shouldn't be able to remove the pre-defined scopes either? + if path != AmlName::root() { + let (level, last_seg) = self.get_level_for_path_mut(&path)?; + level.children.remove(&last_seg); + } + + Ok(()) + } + + pub fn insert(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> { + assert!(path.is_absolute()); + let path = path.normalize()?; + + let (level, last_seg) = self.get_level_for_path_mut(&path)?; + match level.values.insert(last_seg, (ObjectFlags::new(false), object)) { + None => Ok(()), + Some(_) => { + /* + * Real AML often has name collisions, and so we can't afford to be too strict + * about it. We do warn the user as it does have the potential to break stuff. + */ + trace!("AML name collision: {}. Replacing object.", path); + Ok(()) + } + } + } + + pub fn create_alias(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> { + assert!(path.is_absolute()); + let path = path.normalize()?; + + let (level, last_seg) = self.get_level_for_path_mut(&path)?; + match level.values.insert(last_seg, (ObjectFlags::new(true), object)) { + None => Ok(()), + Some(_) => Err(AmlError::NameCollision(path)), + } + } + + pub fn get(&mut self, path: AmlName) -> Result { + assert!(path.is_absolute()); + let path = path.normalize()?; + + let (level, last_seg) = self.get_level_for_path_mut(&path)?; + match level.values.get(&last_seg) { + Some((_, object)) => Ok(object.clone()), + None => Err(AmlError::ObjectDoesNotExist(path.clone())), + } + } + + /// Search for an object at the given path of the namespace, applying the search rules described in §5.3 of the + /// ACPI specification, if they are applicable. Returns the resolved name, and the handle of the first valid + /// object, if found. + pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, WrappedObject), AmlError> { + if path.search_rules_apply() { + /* + * If search rules apply, we need to recursively look through the namespace. If the + * given name does not occur in the current scope, we look at the parent scope, until + * we either find the name, or reach the root of the namespace. + */ + let mut scope = starting_scope.clone(); + assert!(scope.is_absolute()); + loop { + // Search for the name at this namespace level. If we find it, we're done. + let name = path.resolve(&scope)?; + match self.get_level_for_path(&name) { + Ok((level, last_seg)) => { + if let Some((_, object)) = level.values.get(&last_seg) { + return Ok((name, object.clone())); + } + } + + Err(err) => return Err(err), + } + + // If we don't find it, go up a level in the namespace and search for it there recursively + match scope.parent() { + Ok(parent) => scope = parent, + Err(AmlError::RootHasNoParent) => return Err(AmlError::ObjectDoesNotExist(path.clone())), + Err(err) => return Err(err), + } + } + } else { + // If search rules don't apply, simply resolve it against the starting scope + let name = path.resolve(starting_scope)?; + let (level, last_seg) = self.get_level_for_path(&path.resolve(starting_scope)?)?; + + if let Some((_, object)) = level.values.get(&last_seg) { + Ok((name, object.clone())) + } else { + Err(AmlError::ObjectDoesNotExist(path.clone())) + } + } + } + + pub fn search_for_level(&self, level_name: &AmlName, starting_scope: &AmlName) -> Result { + if level_name.search_rules_apply() { + let mut scope = starting_scope.clone().normalize()?; + assert!(scope.is_absolute()); + + loop { + let name = level_name.resolve(&scope)?; + if let Ok((level, last_seg)) = self.get_level_for_path(&name) { + if level.children.contains_key(&last_seg) { + return Ok(name); + } + } + + // If we don't find it, move the scope up a level and search for it there recursively + match scope.parent() { + Ok(parent) => scope = parent, + Err(AmlError::RootHasNoParent) => return Err(AmlError::LevelDoesNotExist(level_name.clone())), + Err(err) => return Err(err), + } + } + } else { + Ok(level_name.clone()) + } + } + + /// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a + /// last segment to index into that level. This must not be called on `\\`. + fn get_level_for_path(&self, path: &AmlName) -> Result<(&NamespaceLevel, NameSeg), AmlError> { + assert_ne!(*path, AmlName::root()); + + let (last_seg, levels) = path.0[1..].split_last().unwrap(); + let NameComponent::Segment(last_seg) = last_seg else { + panic!(); + }; + + // TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. + let mut traversed_path = AmlName::root(); + + let mut current_level = &self.root; + for level in levels { + traversed_path.0.push(*level); + + let NameComponent::Segment(segment) = level else { + panic!(); + }; + current_level = + current_level.children.get(&segment).ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?; + } + + Ok((current_level, *last_seg)) + } + + /// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a + /// last segment to index into that level. This must not be called on `\\`. + fn get_level_for_path_mut(&mut self, path: &AmlName) -> Result<(&mut NamespaceLevel, NameSeg), AmlError> { + assert_ne!(*path, AmlName::root()); + + let (last_seg, levels) = path.0[1..].split_last().unwrap(); + let NameComponent::Segment(last_seg) = last_seg else { + panic!(); + }; + + // TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. We can + // improve this by changing the `levels` interation into an `enumerate()`, and then using the index to + // create the correct path on the error path + let mut traversed_path = AmlName::root(); + + let mut current_level = &mut self.root; + for level in levels { + traversed_path.0.push(*level); + + let NameComponent::Segment(segment) = level else { + panic!(); + }; + current_level = current_level + .children + .get_mut(&segment) + .ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?; + } + + Ok((current_level, *last_seg)) + } + + /// Traverse the namespace, calling `f` on each namespace level. `f` returns a `Result` - + /// errors terminate the traversal and are propagated, and the `bool` on the successful path marks whether the + /// children of the level should also be traversed. + pub fn traverse(&mut self, mut f: F) -> Result<(), AmlError> + where + F: FnMut(&AmlName, &NamespaceLevel) -> Result, + { + fn traverse_level(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError> + where + F: FnMut(&AmlName, &NamespaceLevel) -> Result, + { + for (name, child) in level.children.iter() { + let name = AmlName::from_name_seg(*name).resolve(scope)?; + + if f(&name, child)? { + traverse_level(child, &name, f)?; + } + } + + Ok(()) + } + + if f(&AmlName::root(), &self.root)? { + traverse_level(&self.root, &AmlName::root(), &mut f)?; + } + + Ok(()) + } +} + +impl fmt::Display for Namespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + const STEM: &str = "│ "; + const BRANCH: &str = "├── "; + const END: &str = "└── "; + + fn print_level( + namespace: &Namespace, + f: &mut fmt::Formatter<'_>, + level: &NamespaceLevel, + indent_stack: String, + ) -> fmt::Result { + for (i, (name, (flags, object))) in level.values.iter().enumerate() { + let end = (i == level.values.len() - 1) + && level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).count() == 0; + writeln!( + f, + "{}{}{}: {}{}", + &indent_stack, + if end { END } else { BRANCH }, + name.as_str(), + if flags.is_alias() { "[A] " } else { "" }, + **object + )?; + + // If the object has a corresponding scope, print it here + if let Some(child_level) = level.children.get(&name) { + print_level( + namespace, + f, + child_level, + if end { indent_stack.clone() + " " } else { indent_stack.clone() + STEM }, + )?; + } + } + + let remaining_scopes: Vec<_> = + level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).collect(); + for (i, (name, sub_level)) in remaining_scopes.iter().enumerate() { + let end = i == remaining_scopes.len() - 1; + writeln!(f, "{}{}{}:", &indent_stack, if end { END } else { BRANCH }, name.as_str())?; + print_level(namespace, f, sub_level, indent_stack.clone() + STEM)?; + } + + Ok(()) + } + + writeln!(f, "\n \\:")?; + print_level(self, f, &self.root, String::from(" ")) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum NamespaceLevelKind { + Scope, + Device, + Processor, + PowerResource, + ThermalZone, + MethodLocals, +} + +#[derive(Clone)] +pub struct NamespaceLevel { + pub kind: NamespaceLevelKind, + pub values: BTreeMap, + pub children: BTreeMap, +} + +#[derive(Clone, Copy, Debug)] +pub struct ObjectFlags(u8); + +impl ObjectFlags { + pub fn new(is_alias: bool) -> ObjectFlags { + let mut flags = 0; + flags.set_bit(0, is_alias); + ObjectFlags(flags) + } + + pub fn is_alias(&self) -> bool { + self.0.get_bit(0) + } +} + +impl NamespaceLevel { + pub fn new(kind: NamespaceLevelKind) -> NamespaceLevel { + NamespaceLevel { kind, values: BTreeMap::new(), children: BTreeMap::new() } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct AmlName(Vec); + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum NameComponent { + Root, + Prefix, + Segment(NameSeg), +} + +impl AmlName { + pub fn root() -> AmlName { + AmlName(vec![NameComponent::Root]) + } + + pub fn from_name_seg(seg: NameSeg) -> AmlName { + AmlName(vec![NameComponent::Segment(seg)]) + } + + pub fn from_components(components: Vec) -> AmlName { + AmlName(components) + } + + pub fn as_string(&self) -> String { + self.0 + .iter() + .fold(String::new(), |name, component| match component { + NameComponent::Root => name + "\\", + NameComponent::Prefix => name + "^", + NameComponent::Segment(seg) => name + seg.as_str() + ".", + }) + .trim_end_matches('.') + .to_string() + } + + /// An AML path is normal if it does not contain any prefix elements ("^" characters, when + /// expressed as a string). + pub fn is_normal(&self) -> bool { + !self.0.contains(&NameComponent::Prefix) + } + + pub fn is_absolute(&self) -> bool { + self.0.first() == Some(&NameComponent::Root) + } + + /// Special rules apply when searching for certain paths (specifically, those that are made up + /// of a single name segment). Returns `true` if those rules apply. + pub fn search_rules_apply(&self) -> bool { + if self.0.len() != 1 { + return false; + } + + matches!(self.0[0], NameComponent::Segment(_)) + } + + /// Normalize an AML path, resolving prefix chars. Returns `AmlError::InvalidNormalizedName` if the path + /// normalizes to an invalid path (e.g. `\^_FOO`) + pub fn normalize(self) -> Result { + /* + * If the path is already normal, just return it as-is. This avoids an unneccessary heap allocation and + * free. + */ + if self.is_normal() { + return Ok(self); + } + + Ok(AmlName(self.0.iter().try_fold(Vec::new(), |mut name, &component| match component { + seg @ NameComponent::Segment(_) => { + name.push(seg); + Ok(name) + } + + NameComponent::Root => { + name.push(NameComponent::Root); + Ok(name) + } + + NameComponent::Prefix => { + if let Some(NameComponent::Segment(_)) = name.iter().last() { + name.pop().unwrap(); + Ok(name) + } else { + Err(AmlError::InvalidNormalizedName(self.clone())) + } + } + })?)) + } + + /// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root + /// path has no parent, and so returns `None`. + pub fn parent(&self) -> Result { + // Firstly, normalize the path so we don't have to deal with prefix chars + let mut normalized_self = self.clone().normalize()?; + + match normalized_self.0.last() { + None | Some(NameComponent::Root) => Err(AmlError::RootHasNoParent), + Some(NameComponent::Segment(_)) => { + normalized_self.0.pop(); + Ok(normalized_self) + } + Some(NameComponent::Prefix) => unreachable!(), // Prefix chars are removed by normalization + } + } + + /// Resolve this path against a given scope, making it absolute. If the path is absolute, it is + /// returned directly. The path is also normalized. + pub fn resolve(&self, scope: &AmlName) -> Result { + assert!(scope.is_absolute()); + + if self.is_absolute() { + return Ok(self.clone()); + } + + let mut resolved_path = scope.clone(); + resolved_path.0.extend_from_slice(&(self.0)); + resolved_path.normalize() + } +} + +impl FromStr for AmlName { + type Err = AmlError; + + fn from_str(mut string: &str) -> Result { + if string.is_empty() { + return Err(AmlError::EmptyNamesAreInvalid); + } + + let mut components = Vec::new(); + + // If it starts with a \, make it an absolute name + if string.starts_with('\\') { + components.push(NameComponent::Root); + string = &string[1..]; + } + + if !string.is_empty() { + // Divide the rest of it into segments, and parse those + for mut part in string.split('.') { + // Handle prefix chars + while part.starts_with('^') { + components.push(NameComponent::Prefix); + part = &part[1..]; + } + + components.push(NameComponent::Segment(NameSeg::from_str(part)?)); + } + } + + Ok(Self(components)) + } +} + +impl fmt::Display for AmlName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_string()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct NameSeg(pub(crate) [u8; 4]); + +impl NameSeg { + pub fn from_str(string: &str) -> Result { + // Each NameSeg can only have four chars, and must have at least one + if string.is_empty() || string.len() > 4 { + return Err(AmlError::InvalidNameSeg([0xff, 0xff, 0xff, 0xff])); + } + + // We pre-fill the array with '_', so it will already be correct if the length is < 4 + let mut seg = [b'_'; 4]; + let bytes = string.as_bytes(); + + // Manually do the first one, because we have to check it's a LeadNameChar + if !is_lead_name_char(bytes[0]) { + return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]])); + } + seg[0] = bytes[0]; + + // Copy the rest of the chars, checking that they're NameChars + for i in 1..bytes.len() { + if !is_name_char(bytes[i]) { + return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]])); + } + seg[i] = bytes[i]; + } + + Ok(NameSeg(seg)) + } + + pub fn from_bytes(bytes: [u8; 4]) -> Result { + if !is_lead_name_char(bytes[0]) { + return Err(AmlError::InvalidNameSeg(bytes)); + } + if !is_name_char(bytes[1]) { + return Err(AmlError::InvalidNameSeg(bytes)); + } + if !is_name_char(bytes[2]) { + return Err(AmlError::InvalidNameSeg(bytes)); + } + if !is_name_char(bytes[3]) { + return Err(AmlError::InvalidNameSeg(bytes)); + } + Ok(NameSeg(bytes)) + } + + pub fn as_str(&self) -> &str { + // We should only construct valid ASCII name segments + unsafe { str::from_utf8_unchecked(&self.0) } + } +} + +pub fn is_lead_name_char(c: u8) -> bool { + c.is_ascii_uppercase() || c == b'_' +} + +pub fn is_name_char(c: u8) -> bool { + is_lead_name_char(c) || c.is_ascii_digit() +} + +impl fmt::Debug for NameSeg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.as_str()) + } +} diff --git a/src/aml/object.rs b/src/aml/object.rs new file mode 100644 index 00000000..df35619b --- /dev/null +++ b/src/aml/object.rs @@ -0,0 +1,488 @@ +use crate::aml::{AmlError, Handle, Operation, op_region::OpRegion}; +use alloc::{borrow::Cow, string::String, sync::Arc, vec::Vec}; +use bit_field::BitField; +use core::{cell::UnsafeCell, fmt, ops}; + +#[derive(Clone, Debug)] +pub enum Object { + Uninitialized, + Buffer(Vec), + BufferField { buffer: WrappedObject, offset: usize, length: usize }, + Device, + Event, + FieldUnit(FieldUnit), + Integer(u64), + Method { code: Vec, flags: MethodFlags }, + Mutex { mutex: Handle, sync_level: u8 }, + Reference { kind: ReferenceKind, inner: WrappedObject }, + OpRegion(OpRegion), + Package(Vec), + PowerResource { system_level: u8, resource_order: u16 }, + Processor { proc_id: u8, pblk_address: u32, pblk_length: u8 }, + RawDataBuffer, + String(String), + ThermalZone, + Debug, +} + +impl fmt::Display for Object { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Object::Uninitialized => write!(f, "[Uninitialized]"), + Object::Buffer(bytes) => write!(f, "Buffer({:x?})", bytes), + // TODO: include fields here + Object::BufferField { buffer, offset, length } => write!(f, "BufferField {{ .. }}"), + Object::Device => write!(f, "Device"), + Object::Event => write!(f, "Event"), + // TODO: include fields + Object::FieldUnit(_) => write!(f, "FieldUnit"), + Object::Integer(value) => write!(f, "Integer({})", value), + // TODO: decode flags here + Object::Method { code, flags } => write!(f, "Method"), + Object::Mutex { mutex, sync_level } => write!(f, "Mutex"), + Object::Reference { kind, inner } => write!(f, "Reference({:?} -> {})", kind, **inner), + Object::OpRegion(region) => write!(f, "{:?}", region), + Object::Package(elements) => { + write!(f, "Package {{ ")?; + for (i, element) in elements.iter().enumerate() { + if i == elements.len() - 1 { + write!(f, "{}", **element)?; + } else { + write!(f, "{}, ", **element)?; + } + } + write!(f, " }}")?; + Ok(()) + } + // TODO: include fields + Object::PowerResource { system_level, resource_order } => write!(f, "PowerResource"), + // TODO: include fields + Object::Processor { proc_id, pblk_address, pblk_length } => write!(f, "Processor"), + Object::RawDataBuffer => write!(f, "RawDataBuffer"), + Object::String(value) => write!(f, "String({:?})", value), + Object::ThermalZone => write!(f, "ThermalZone"), + Object::Debug => write!(f, "Debug"), + } + } +} + +/// `ObjectToken` is used to mediate mutable access to objects from a [`WrappedObject`]. It must be +/// acquired by locking the single token provided by [`super::Interpreter`]. +#[non_exhaustive] +pub struct ObjectToken { + _dont_construct_me: (), +} + +impl ObjectToken { + /// Create an [`ObjectToken`]. This should **only** be done **once** by the main interpreter, + /// as contructing your own token allows invalid mutable access to objects. + pub(super) unsafe fn create_interpreter_token() -> ObjectToken { + ObjectToken { _dont_construct_me: () } + } +} + +#[derive(Clone, Debug)] +pub struct WrappedObject(Arc>); + +impl WrappedObject { + pub fn new(object: Object) -> WrappedObject { + WrappedObject(Arc::new(UnsafeCell::new(object))) + } + + /// Gain a mutable reference to an [`Object`] from this [`WrappedObject`]. This requires an + /// [`ObjectToken`] which is protected by a lock on [`super::Interpreter`], which prevents + /// mutable access to objects from multiple contexts. It does not, however, prevent the same + /// object, referenced from multiple [`WrappedObject`]s, having multiple mutable (and therefore + /// aliasing) references being made to it, and therefore care must be taken in the interpreter + /// to prevent this. + pub unsafe fn gain_mut<'r, 'a, 't>(&'a self, _token: &'t ObjectToken) -> &'r mut Object + where + 't: 'r, + 'a: 'r, + { + unsafe { &mut *(self.0.get()) } + } + + pub fn unwrap_reference(self) -> WrappedObject { + let mut object = self; + loop { + if let Object::Reference { ref inner, .. } = *object { + object = inner.clone(); + } else { + return object.clone(); + } + } + } + + /// Unwraps 'transparent' references (e.g. locals, arguments, and internal usage of reference-type objects), but maintain 'real' + /// references deliberately created by AML. + pub fn unwrap_transparent_reference(self) -> WrappedObject { + let mut object = self; + loop { + // TODO: what should this do with unresolved namestrings? It would need namespace + // access to resolve them (and then this would probs have to move to a method on + // `Interpreter`)? + if let Object::Reference { kind, ref inner } = *object + && kind == ReferenceKind::LocalOrArg + { + object = inner.clone(); + } else { + return object.clone(); + } + } + } +} + +impl ops::Deref for WrappedObject { + type Target = Object; + + fn deref(&self) -> &Self::Target { + /* + * SAFETY: elided lifetime ensures reference cannot outlive at least one reference-counted + * instance of the object. `WrappedObject::gain_mut` is unsafe, and so it is the user's + * responsibility to ensure shared references from `Deref` do not co-exist with an + * exclusive reference. + */ + unsafe { &*self.0.get() } + } +} + +impl Object { + pub fn wrap(self) -> WrappedObject { + WrappedObject::new(self) + } + + pub fn as_integer(&self) -> Result { + if let Object::Integer(value) = self { + Ok(*value) + } else { + Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: self.typ() }) + } + } + + pub fn as_string(&self) -> Result, AmlError> { + if let Object::String(value) = self { + Ok(Cow::from(value)) + } else { + Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::String, got: self.typ() }) + } + } + + pub fn as_buffer(&self) -> Result<&[u8], AmlError> { + if let Object::Buffer(bytes) = self { + Ok(bytes) + } else { + Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Buffer, got: self.typ() }) + } + } + + pub fn to_integer(&self, allowed_bytes: usize) -> Result { + match self { + Object::Integer(value) => Ok(*value), + Object::Buffer(value) => { + let length = usize::min(value.len(), allowed_bytes); + let mut bytes = [0u8; 8]; + bytes[0..length].copy_from_slice(&value[0..length]); + Ok(u64::from_le_bytes(bytes)) + } + // TODO: how should we handle invalid inputs? What does NT do here? + Object::String(value) => Ok(value.parse::().unwrap_or(0)), + _ => Ok(0), + } + } + + pub fn to_buffer(&self, allowed_bytes: usize) -> Result, AmlError> { + match self { + Object::Buffer(bytes) => Ok(bytes.clone()), + Object::Integer(value) => match allowed_bytes { + 4 => Ok((*value as u32).to_le_bytes().to_vec()), + 8 => Ok(value.to_le_bytes().to_vec()), + _ => panic!(), + }, + Object::String(value) => Ok(value.as_bytes().to_vec()), + _ => Err(AmlError::InvalidOperationOnObject { op: Operation::ConvertToBuffer, typ: self.typ() }), + } + } + + pub fn read_buffer_field(&self, dst: &mut [u8]) -> Result<(), AmlError> { + if let Self::BufferField { buffer, offset, length } = self { + let buffer = match **buffer { + Object::Buffer(ref buffer) => buffer.as_slice(), + Object::String(ref string) => string.as_bytes(), + _ => panic!(), + }; + // TODO: bounds check the buffer first to avoid panicking + copy_bits(buffer, *offset, dst, 0, *length); + Ok(()) + } else { + Err(AmlError::InvalidOperationOnObject { op: Operation::ReadBufferField, typ: self.typ() }) + } + } + + pub fn write_buffer_field(&mut self, value: &[u8], token: &ObjectToken) -> Result<(), AmlError> { + // TODO: bounds check the buffer first to avoid panicking + if let Self::BufferField { buffer, offset, length } = self { + let buffer = match unsafe { buffer.gain_mut(token) } { + Object::Buffer(buffer) => buffer.as_mut_slice(), + // XXX: this unfortunately requires us to trust AML to keep the string as valid + // UTF8... maybe there is a better way? + Object::String(string) => unsafe { string.as_bytes_mut() }, + _ => panic!(), + }; + copy_bits(value, 0, buffer, *offset, *length); + Ok(()) + } else { + Err(AmlError::InvalidOperationOnObject { op: Operation::WriteBufferField, typ: self.typ() }) + } + } + + /// Returns the `ObjectType` of this object. Returns the type of the referenced object in the + /// case of `Object::Reference`. + pub fn typ(&self) -> ObjectType { + match self { + Object::Uninitialized => ObjectType::Uninitialized, + Object::Buffer(_) => ObjectType::Buffer, + Object::BufferField { .. } => ObjectType::BufferField, + Object::Device => ObjectType::Device, + Object::Event => ObjectType::Event, + Object::FieldUnit(_) => ObjectType::FieldUnit, + Object::Integer(_) => ObjectType::Integer, + Object::Method { .. } => ObjectType::Method, + Object::Mutex { .. } => ObjectType::Mutex, + Object::Reference { inner, .. } => inner.typ(), + Object::OpRegion(_) => ObjectType::OpRegion, + Object::Package(_) => ObjectType::Package, + Object::PowerResource { .. } => ObjectType::PowerResource, + Object::Processor { .. } => ObjectType::Processor, + Object::RawDataBuffer => ObjectType::RawDataBuffer, + Object::String(_) => ObjectType::String, + Object::ThermalZone => ObjectType::ThermalZone, + Object::Debug => ObjectType::Debug, + } + } +} + +#[derive(Clone, Debug)] +pub struct FieldUnit { + pub kind: FieldUnitKind, + pub flags: FieldFlags, + pub bit_index: usize, + pub bit_length: usize, +} + +#[derive(Clone, Debug)] +pub enum FieldUnitKind { + Normal { region: WrappedObject }, + Bank { region: WrappedObject, bank: WrappedObject, bank_value: u64 }, + Index { index: WrappedObject, data: WrappedObject }, +} + +#[derive(Clone, Copy, Debug)] +pub struct FieldFlags(pub u8); + +#[derive(Clone, Copy, Debug)] +pub enum FieldAccessType { + Any, + Byte, + Word, + DWord, + QWord, + Buffer, +} + +#[derive(Clone, Copy, Debug)] +pub enum FieldUpdateRule { + Preserve, + WriteAsOnes, + WriteAsZeros, +} + +impl FieldFlags { + pub fn access_type(&self) -> Result { + match self.0.get_bits(0..4) { + 0 => Ok(FieldAccessType::Any), + 1 => Ok(FieldAccessType::Byte), + 2 => Ok(FieldAccessType::Word), + 3 => Ok(FieldAccessType::DWord), + 4 => Ok(FieldAccessType::QWord), + 5 => Ok(FieldAccessType::Buffer), + _ => Err(AmlError::InvalidFieldFlags), + } + } + + pub fn access_type_bytes(&self) -> Result { + match self.access_type()? { + FieldAccessType::Any => { + // TODO: given more info about the field, we might be able to make a more efficient + // read, since all are valid in this case + Ok(1) + } + FieldAccessType::Byte | FieldAccessType::Buffer => Ok(1), + FieldAccessType::Word => Ok(2), + FieldAccessType::DWord => Ok(4), + FieldAccessType::QWord => Ok(8), + } + } + + pub fn lock_rule(&self) -> bool { + self.0.get_bit(4) + } + + pub fn update_rule(&self) -> FieldUpdateRule { + match self.0.get_bits(5..7) { + 0 => FieldUpdateRule::Preserve, + 1 => FieldUpdateRule::WriteAsOnes, + 2 => FieldUpdateRule::WriteAsZeros, + _ => unreachable!(), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MethodFlags(pub u8); + +impl MethodFlags { + pub fn arg_count(&self) -> usize { + self.0.get_bits(0..3) as usize + } + + pub fn serialize(&self) -> bool { + self.0.get_bit(3) + } + + pub fn sync_level(&self) -> u8 { + self.0.get_bits(4..8) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ReferenceKind { + RefOf, + LocalOrArg, + Unresolved, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ObjectType { + Uninitialized, + Buffer, + BufferField, + Device, + Event, + FieldUnit, + Integer, + Method, + Mutex, + Reference, + OpRegion, + Package, + PowerResource, + Processor, + RawDataBuffer, + String, + ThermalZone, + Debug, +} + +/// Helper type for decoding the result of `_STA` objects. +pub struct DeviceStatus(pub u64); + +impl DeviceStatus { + pub fn present(&self) -> bool { + self.0.get_bit(0) + } + + pub fn enabled(&self) -> bool { + self.0.get_bit(1) + } + + pub fn show_in_ui(&self) -> bool { + self.0.get_bit(2) + } + + pub fn functioning(&self) -> bool { + self.0.get_bit(3) + } + + /// This flag is only used for Battery devices (PNP0C0A), and indicates if the battery is + /// present. + pub fn battery_present(&self) -> bool { + self.0.get_bit(4) + } +} + +/// Copy an arbitrary bit range of `src` to an arbitrary bit range of `dst`. This is used for +/// buffer fields. Data is zero-extended if `src` does not cover `length` bits, matching the +/// expected behaviour for buffer fields. +pub(crate) fn copy_bits( + src: &[u8], + mut src_index: usize, + dst: &mut [u8], + mut dst_index: usize, + mut length: usize, +) { + while length > 0 { + let src_shift = src_index & 7; + let mut src_bits = src.get(src_index / 8).unwrap_or(&0x00) >> src_shift; + if src_shift > 0 && length > (8 - src_shift) { + src_bits |= src.get(src_index / 8 + 1).unwrap_or(&0x00) << (8 - src_shift); + } + + if length < 8 { + src_bits &= (1 << length) - 1; + } + + let dst_shift = dst_index & 7; + let mut dst_mask: u16 = if length < 8 { ((1 << length) - 1) as u16 } else { 0xff as u16 } << dst_shift; + dst[dst_index / 8] = + (dst[dst_index / 8] & !(dst_mask as u8)) | ((src_bits << dst_shift) & (dst_mask as u8)); + + if dst_shift > 0 && length > (8 - dst_shift) { + dst_mask >>= 8; + dst[dst_index / 8 + 1] &= !(dst_mask as u8); + dst[dst_index / 8 + 1] |= (src_bits >> (8 - dst_shift)) & (dst_mask as u8); + } + + if length < 8 { + length = 0; + } else { + length -= 8; + src_index += 8; + dst_index += 8; + } + } +} + +#[inline] +pub(crate) fn align_down(value: usize, align: usize) -> usize { + assert!(align == 0 || align.is_power_of_two()); + + if align == 0 { + value + } else { + /* + * Alignment must be a power of two. + * + * E.g. + * align = 0b00001000 + * align-1 = 0b00000111 + * !(align-1) = 0b11111000 + * ^^^ Masks the value to the one below it with the correct align + */ + value & !(align - 1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_copy_bits() { + let src = [0b1011_1111, 0b1111_0111, 0b1111_1111, 0b1111_1111, 0b1111_1111]; + let mut dst = [0b1110_0001, 0, 0, 0, 0]; + + copy_bits(&src, 0, &mut dst, 2, 15); + assert_eq!(dst, [0b1111_1101, 0b1101_1110, 0b0000_0001, 0b0000_0000, 0b0000_0000]); + } +} diff --git a/src/aml/op_region.rs b/src/aml/op_region.rs new file mode 100644 index 00000000..e2e73d64 --- /dev/null +++ b/src/aml/op_region.rs @@ -0,0 +1,56 @@ +use crate::aml::{AmlError, namespace::AmlName}; + +#[derive(Clone, Debug)] +pub struct OpRegion { + pub space: RegionSpace, + pub base: u64, + pub length: u64, + pub parent_device_path: AmlName, +} + +pub trait RegionHandler { + fn read_u8(&self, region: &OpRegion) -> Result; + fn read_u16(&self, region: &OpRegion) -> Result; + fn read_u32(&self, region: &OpRegion) -> Result; + fn read_u64(&self, region: &OpRegion) -> Result; + + fn write_u8(&self, region: &OpRegion, value: u8) -> Result<(), AmlError>; + fn write_u16(&self, region: &OpRegion, value: u16) -> Result<(), AmlError>; + fn write_u32(&self, region: &OpRegion, value: u32) -> Result<(), AmlError>; + fn write_u64(&self, region: &OpRegion, value: u64) -> Result<(), AmlError>; +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum RegionSpace { + SystemMemory, + SystemIO, + PciConfig, + EmbeddedControl, + SmBus, + SystemCmos, + PciBarTarget, + Ipmi, + GeneralPurposeIo, + GenericSerialBus, + Pcc, + Oem(u8), +} + +impl From for RegionSpace { + fn from(value: u8) -> Self { + match value { + 0 => RegionSpace::SystemMemory, + 1 => RegionSpace::SystemIO, + 2 => RegionSpace::PciConfig, + 3 => RegionSpace::EmbeddedControl, + 4 => RegionSpace::SmBus, + 5 => RegionSpace::SystemCmos, + 6 => RegionSpace::PciBarTarget, + 7 => RegionSpace::Ipmi, + 8 => RegionSpace::GeneralPurposeIo, + 9 => RegionSpace::GenericSerialBus, + 10 => RegionSpace::Pcc, + _ => RegionSpace::Oem(value), + } + } +} diff --git a/aml/src/pci_routing.rs b/src/aml/pci_routing.rs similarity index 74% rename from aml/src/pci_routing.rs rename to src/aml/pci_routing.rs index 7acf9264..cea969b8 100644 --- a/aml/src/pci_routing.rs +++ b/src/aml/pci_routing.rs @@ -1,17 +1,17 @@ -use crate::{ +use crate::aml::{ + AmlError, + Handler, + Interpreter, + Operation, namespace::AmlName, + object::{Object, ReferenceKind}, resource::{self, InterruptPolarity, InterruptTrigger, Resource}, - value::Args, - AmlContext, - AmlError, - AmlType, - AmlValue, }; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use bit_field::BitField; use core::str::FromStr; -pub use crate::resource::IrqDescriptor; +pub use crate::aml::resource::IrqDescriptor; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Pin { @@ -55,16 +55,20 @@ pub struct PciRoutingTable { impl PciRoutingTable { /// Construct a `PciRoutingTable` from a path to a `_PRT` object. Returns - /// `AmlError::IncompatibleValueConversion` if the value passed is not a package, or if any of the values - /// within it are not packages. Returns the various `AmlError::Prt*` errors if the internal structure of the - /// entries is invalid. - pub fn from_prt_path(prt_path: &AmlName, context: &mut AmlContext) -> Result { + /// `AmlError::InvalidOperationOnObject` if the value passed is not a package, or if any of the + /// values within it are not packages. Returns the various `AmlError::Prt*` errors if the + /// internal structure of the entries is invalid. + pub fn from_prt_path( + prt_path: AmlName, + interpreter: &Interpreter, + ) -> Result { let mut entries = Vec::new(); - let prt = context.invoke_method(prt_path, Args::default())?; - if let AmlValue::Package(ref inner_values) = prt { + let prt = interpreter.invoke_method(prt_path.clone(), vec![])?; + + if let Object::Package(ref inner_values) = *prt { for value in inner_values { - if let AmlValue::Package(ref pin_package) = value { + if let Object::Package(ref pin_package) = **value { /* * Each inner package has the following structure: * | Field | Type | Description | @@ -88,38 +92,41 @@ impl PciRoutingTable { * | | | pin is connected. | * | -----------|-----------|-----------------------------------------------------------| */ - let address = pin_package[0].as_integer(context)?; + let Object::Integer(address) = *pin_package[0] else { + return Err(AmlError::PrtInvalidAddress); + }; let device = address.get_bits(16..32).try_into().map_err(|_| AmlError::PrtInvalidAddress)?; let function = address.get_bits(0..16).try_into().map_err(|_| AmlError::PrtInvalidAddress)?; - let pin = match pin_package[1].as_integer(context)? { - 0 => Pin::IntA, - 1 => Pin::IntB, - 2 => Pin::IntC, - 3 => Pin::IntD, + let pin = match *pin_package[1] { + Object::Integer(0) => Pin::IntA, + Object::Integer(1) => Pin::IntB, + Object::Integer(2) => Pin::IntC, + Object::Integer(3) => Pin::IntD, _ => return Err(AmlError::PrtInvalidPin), }; - match pin_package[2] { - AmlValue::Integer(0) => { + match *pin_package[2] { + Object::Integer(0) => { /* * The Source Index field contains the GSI number that this interrupt is attached * to. */ + let Object::Integer(gsi) = *pin_package[3] else { + return Err(AmlError::PrtInvalidGsi); + }; entries.push(PciRoute { device, function, pin, - route_type: PciRouteType::Gsi( - pin_package[3] - .as_integer(context)? - .try_into() - .map_err(|_| AmlError::PrtInvalidGsi)?, - ), + route_type: PciRouteType::Gsi(gsi as u32), }); } - AmlValue::String(ref name) => { - let link_object_name = - context.namespace.search_for_level(&AmlName::from_str(name)?, prt_path)?; + Object::Reference { kind: ReferenceKind::Unresolved, ref inner } => { + let Object::String(ref name) = **inner else { panic!() }; + let link_object_name = interpreter + .namespace + .lock() + .search_for_level(&AmlName::from_str(&name)?, &prt_path)?; entries.push(PciRoute { device, function, @@ -130,16 +137,13 @@ impl PciRoutingTable { _ => return Err(AmlError::PrtInvalidSource), } } else { - return Err(AmlError::IncompatibleValueConversion { - current: value.type_of(), - target: AmlType::Package, - }); + return Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: value.typ() }); } } Ok(PciRoutingTable { entries }) } else { - Err(AmlError::IncompatibleValueConversion { current: prt.type_of(), target: AmlType::Package }) + return Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: prt.typ() }); } } @@ -150,7 +154,7 @@ impl PciRoutingTable { device: u16, function: u16, pin: Pin, - context: &mut AmlContext, + interpreter: &Interpreter, ) -> Result { let entry = self .entries @@ -173,9 +177,9 @@ impl PciRoutingTable { }), PciRouteType::LinkObject(ref name) => { let path = AmlName::from_str("_CRS").unwrap().resolve(name)?; - let link_crs = context.invoke_method(&path, Args::EMPTY)?; + let link_crs = interpreter.invoke_method(path, vec![])?; - let resources = resource::resource_descriptor_list(&link_crs)?; + let resources = resource::resource_descriptor_list(link_crs)?; match resources.as_slice() { [Resource::Irq(descriptor)] => Ok(descriptor.clone()), _ => Err(AmlError::UnexpectedResourceType), diff --git a/aml/src/resource.rs b/src/aml/resource.rs similarity index 94% rename from aml/src/resource.rs rename to src/aml/resource.rs index abe907ae..bb0d1289 100644 --- a/aml/src/resource.rs +++ b/src/aml/resource.rs @@ -1,12 +1,9 @@ -use core::mem; - -use crate::{ - value::{AmlType, AmlValue}, - AmlError, -}; +use super::object::WrappedObject; +use crate::aml::{AmlError, Operation, object::Object}; use alloc::vec::Vec; use bit_field::BitField; use byteorder::{ByteOrder, LittleEndian}; +use core::mem; #[derive(Debug, PartialEq, Eq)] pub enum Resource { @@ -17,13 +14,11 @@ pub enum Resource { Dma(DMADescriptor), } -/// Parse a `ResourceDescriptor` into a list of resources. Returns `AmlError::IncompatibleValueConversion` if the passed value is not a -/// `Buffer`. -pub fn resource_descriptor_list(descriptor: &AmlValue) -> Result, AmlError> { - if let AmlValue::Buffer(bytes) = descriptor { +/// Parse a `ResourceDescriptor` buffer into a list of resources. +pub fn resource_descriptor_list(descriptor: WrappedObject) -> Result, AmlError> { + if let Object::Buffer(ref bytes) = *descriptor { let mut descriptors = Vec::new(); - let buffer_data = bytes.lock(); - let mut bytes = buffer_data.as_slice(); + let mut bytes = bytes.as_slice(); while !bytes.is_empty() { let (descriptor, remaining_bytes) = resource_descriptor(bytes)?; @@ -38,12 +33,10 @@ pub fn resource_descriptor_list(descriptor: &AmlValue) -> Result, Ok(descriptors) } else { - Err(AmlError::IncompatibleValueConversion { current: descriptor.type_of(), target: AmlType::Buffer }) + Err(AmlError::InvalidOperationOnObject { op: Operation::ParseResource, typ: descriptor.typ() }) } } -/// Parse a `ResourceDescriptor`. Returns `AmlError::IncompatibleValueConversion` if the passed value is not a -/// `Buffer`. fn resource_descriptor(bytes: &[u8]) -> Result<(Option, &[u8]), AmlError> { /* * If bit 7 of Byte 0 is set, it's a large descriptor. If not, it's a small descriptor. @@ -99,7 +92,7 @@ fn resource_descriptor(bytes: &[u8]) -> Result<(Option, &[u8]), AmlErr 0x11 => unimplemented!("Pin Group Function Descriptor"), 0x12 => unimplemented!("Pin Group Configuration Descriptor"), - 0x00 | 0x13..=0x7f => Err(AmlError::ReservedResourceType), + 0x00 | 0x13..=0x7f => Err(AmlError::InvalidResourceDescriptor), 0x80..=0xff => unreachable!(), }?; @@ -131,7 +124,7 @@ fn resource_descriptor(bytes: &[u8]) -> Result<(Option, &[u8]), AmlErr let (descriptor_bytes, remaining_bytes) = bytes.split_at(length + 1); let descriptor = match descriptor_type { - 0x00..=0x03 => Err(AmlError::ReservedResourceType), + 0x00..=0x03 => Err(AmlError::InvalidResourceDescriptor), 0x04 => irq_format_descriptor(descriptor_bytes), 0x05 => dma_format_descriptor(descriptor_bytes), 0x06 => unimplemented!("Start Dependent Functions Descriptor"), @@ -139,7 +132,7 @@ fn resource_descriptor(bytes: &[u8]) -> Result<(Option, &[u8]), AmlErr 0x08 => io_port_descriptor(descriptor_bytes), 0x09 => unimplemented!("Fixed Location IO Port Descriptor"), 0x0A => unimplemented!("Fixed DMA Descriptor"), - 0x0B..=0x0D => Err(AmlError::ReservedResourceType), + 0x0B..=0x0D => Err(AmlError::InvalidResourceDescriptor), 0x0E => unimplemented!("Vendor Defined Descriptor"), 0x0F => return Ok((None, &[])), 0x10..=0xFF => unreachable!(), @@ -213,12 +206,8 @@ fn fixed_memory_descriptor(bytes: &[u8]) -> Result { * Byte 10 Range length, _LEN bits [23:16] This field contains bits [23:16] of the memory range length. The range length provides the length of the memory range in 1-byte blocks. * Byte 11 Range length, _LEN bits [31:24] This field contains bits [31:24] of the memory range length. The range length provides the length of the memory range in 1-byte blocks. */ - if bytes.len() < 12 { - return Err(AmlError::ResourceDescriptorTooShort); - } - - if bytes.len() > 12 { - return Err(AmlError::ResourceDescriptorTooLong); + if bytes.len() != 12 { + return Err(AmlError::InvalidResourceDescriptor); } let information = bytes[3]; @@ -276,14 +265,14 @@ fn address_space_descriptor(bytes: &[u8]) -> Result { let size = mem::size_of::(); if bytes.len() < 6 + size * 5 { - return Err(AmlError::ResourceDescriptorTooShort); + return Err(AmlError::InvalidResourceDescriptor); } let resource_type = match bytes[3] { 0 => AddressSpaceResourceType::MemoryRange, 1 => AddressSpaceResourceType::IORange, 2 => AddressSpaceResourceType::BusNumberRange, - 3..=191 => return Err(AmlError::ReservedResourceType), + 3..=191 => return Err(AmlError::InvalidResourceDescriptor), 192..=255 => unimplemented!(), }; @@ -344,7 +333,10 @@ fn irq_format_descriptor(bytes: &[u8]) -> Result { * Byte 3 IRQ Information. Each bit, when set, indicates this device is capable of driving a certain type of interrupt. * (Optional—if not included then assume edge sensitive, high true interrupts.) * These bits can be used both for reporting and setting IRQ resources. - * Note: This descriptor is meant for describing interrupts that are connected to PIC-compatible interrupt controllers, which can only be programmed for Active-High-Edge-Triggered or Active-Low-Level-Triggered interrupts. Any other combination is invalid. The Extended Interrupt Descriptor can be used to describe other combinations. + * Note: This descriptor is meant for describing interrupts that are connected to PIC-compatible interrupt + * controllers, which can only be programmed for Active-High-Edge-Triggered or Active-Low-Level-Triggered + * interrupts. Any other combination is invalid. The Extended Interrupt Descriptor can be used to describe + * other combinations. * Bit [7:6] Reserved (must be 0) * Bit [5] Wake Capability, _WKC * 0x0 = Not Wake Capable: This interrupt is not capable of waking the system. @@ -363,7 +355,7 @@ fn irq_format_descriptor(bytes: &[u8]) -> Result { */ match bytes.len() { - 0..=2 => Err(AmlError::ResourceDescriptorTooShort), + 0..=2 => Err(AmlError::InvalidResourceDescriptor), 3 => { // no IRQ information ("length 2" in spec) let irq = LittleEndian::read_u16(&bytes[1..=2]); @@ -404,7 +396,7 @@ fn irq_format_descriptor(bytes: &[u8]) -> Result { is_consumer: false, // assumed to be producer })) } - _ => Err(AmlError::ResourceDescriptorTooLong), + _ => Err(AmlError::InvalidResourceDescriptor), } } @@ -454,12 +446,8 @@ pub fn dma_format_descriptor(bytes: &[u8]) -> Result { * 10 16-bit only * 11 Reserved */ - if bytes.len() < 3 { - return Err(AmlError::ResourceDescriptorTooShort); - } - - if bytes.len() > 3 { - return Err(AmlError::ResourceDescriptorTooLong); + if bytes.len() != 3 { + return Err(AmlError::InvalidResourceDescriptor); } let channel_mask = bytes[1]; @@ -508,12 +496,8 @@ fn io_port_descriptor(bytes: &[u8]) -> Result { * Byte 6 Base alignment, _ALN Alignment for minimum base address, increment in 1-byte blocks. * Byte 7 Range length, _LEN The number of contiguous I/O ports requested. */ - if bytes.len() < 8 { - return Err(AmlError::ResourceDescriptorTooShort); - } - - if bytes.len() > 8 { - return Err(AmlError::ResourceDescriptorTooLong); + if bytes.len() != 8 { + return Err(AmlError::InvalidResourceDescriptor); } let information = bytes[1]; @@ -547,7 +531,7 @@ fn extended_interrupt_descriptor(bytes: &[u8]) -> Result { * NOTE: We only support the case where there is a single interrupt number. */ if bytes.len() < 9 { - return Err(AmlError::ResourceDescriptorTooShort); + return Err(AmlError::InvalidResourceDescriptor); } let number_of_interrupts = bytes[4] as usize; @@ -567,7 +551,7 @@ fn extended_interrupt_descriptor(bytes: &[u8]) -> Result { #[cfg(test)] mod tests { use super::*; - use std::sync::Arc; + use alloc::sync::Arc; #[test] fn test_parses_keyboard_crs() { @@ -601,8 +585,8 @@ mod tests { ] .to_vec(); - let value: AmlValue = AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(bytes))); - let resources = resource_descriptor_list(&value).unwrap(); + let value = Arc::new(Object::Buffer(bytes)); + let resources = resource_descriptor_list(value).unwrap(); assert_eq!( resources, @@ -711,8 +695,8 @@ mod tests { ] .to_vec(); - let value: AmlValue = AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(bytes))); - let resources = resource_descriptor_list(&value).unwrap(); + let value = Arc::new(Object::Buffer(bytes)); + let resources = resource_descriptor_list(value).unwrap(); assert_eq!( resources, @@ -812,8 +796,8 @@ mod tests { ] .to_vec(); - let value: AmlValue = AmlValue::Buffer(Arc::new(spinning_top::Spinlock::new(bytes))); - let resources = resource_descriptor_list(&value).unwrap(); + let value = Arc::new(Object::Buffer(bytes)); + let resources = resource_descriptor_list(value).unwrap(); assert_eq!( resources, diff --git a/acpi/src/handler.rs b/src/handler.rs similarity index 96% rename from acpi/src/handler.rs rename to src/handler.rs index 3a954511..745ce2b1 100644 --- a/acpi/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,9 @@ -use core::{fmt, ops::{Deref, DerefMut}, pin::Pin, ptr::NonNull}; +use core::{ + fmt, + ops::{Deref, DerefMut}, + pin::Pin, + 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::()` @@ -49,7 +54,6 @@ where /// dropped, it will be used to unmap the structure. /// /// ### Safety - /// /// The caller must ensure that the physical memory can be safely mapped. pub unsafe fn new( physical_start: usize, @@ -123,7 +127,7 @@ where /// 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.). +/// clone/move about freely (e.g. a reference, wrapper over `Arc`, 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 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..1284d577 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,259 @@ +//! `acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a +//! complex framework for power management and device discovery and configuration. ACPI is used on +//! modern x64, ARM, RISC-V, and other platforms. An operating system needs to interact with ACPI +//! to correctly set up a platform's interrupt controllers, perform power management, and fully +//! support many other platform capabilities. +//! +//! This crate provides a limited API that can be used without an allocator, for example for use +//! from a bootloader. This API will allow you to search for the RSDP, enumerate over the available +//! tables, and interact with the tables using their raw structures. All other functionality is +//! behind an `alloc` feature (enabled by default) and requires an allocator. +//! +//! With an allocator, this crate also provides higher-level interfaces to the static tables, as +//! well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT +//! tables. +//! +//! ### 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. +//! +//! Next, you'll need to get the physical address of either the RSDP, or the RSDT/XSDT. The method +//! for doing this depends on the platform you're running on and how you were booted. If you know +//! the system was booted via the BIOS, you can use [`Rsdp::search_for_rsdp_bios`]. UEFI provides a +//! separate mechanism for getting the address of the RSDP. +//! +//! 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 +//! +//! Once you have an `AcpiTables`, you can search for relevant tables, or use the higher-level +//! interfaces, such as [`PlatformInfo`], [`PciConfigRegions`], or [`HpetInfo`]. + +#![no_std] +#![feature(allocator_api, let_chains, inherent_str_constructors)] + +#[cfg_attr(test, macro_use)] +#[cfg(test)] +extern crate std; + +#[cfg(feature = "alloc")] +extern crate alloc; + +pub mod address; +#[cfg(feature = "aml")] +pub mod aml; +pub mod handler; +#[cfg(feature = "alloc")] +pub mod platform; +pub mod rsdp; +pub mod sdt; + +pub use handler::{AcpiHandler, PhysicalMapping}; +pub use sdt::{fadt::PowerProfile, hpet::HpetInfo, madt::MadtError}; + +use crate::sdt::{SdtHeader, Signature}; +use core::mem; +use log::warn; +use rsdp::Rsdp; + +/// `AcpiTables` should be constructed after finding the RSDP or RSDT/XSDT and allows enumeration +/// of the system's ACPI tables. +pub struct AcpiTables { + rsdt_mapping: PhysicalMapping, + pub rsdp_revision: u8, + handler: H, +} + +unsafe impl Send for AcpiTables where H: AcpiHandler + Send {} +unsafe impl Sync for AcpiTables where H: AcpiHandler + Send {} + +impl AcpiTables +where + H: AcpiHandler, +{ + /// Construct an `AcpiTables` from the **physical** address of the RSDP. + pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result, AcpiError> { + let rsdp_mapping = unsafe { handler.map_physical_region::(rsdp_address, mem::size_of::()) }; + + /* + * If the address given does not have a correct RSDP signature, the user has probably given + * us an invalid address, and we should not continue. We're more lenient with other errors + * as it's probably a real RSDP and the firmware developers are just lazy. + */ + match rsdp_mapping.validate() { + Ok(()) => (), + Err(AcpiError::RsdpIncorrectSignature) => return Err(AcpiError::RsdpIncorrectSignature), + Err(AcpiError::RsdpInvalidOemId) | Err(AcpiError::RsdpInvalidChecksum) => { + warn!("RSDP has invalid checksum or OEM ID. Continuing."); + } + Err(_) => (), + } + + let rsdp_revision = rsdp_mapping.revision(); + let rsdt_address = if rsdp_revision == 0 { + // We're running on ACPI Version 1.0. We should use the 32-bit RSDT address. + rsdp_mapping.rsdt_address() as usize + } else { + /* + * We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated + * to 32 bits on x86. + */ + rsdp_mapping.xsdt_address() as usize + }; + + unsafe { Self::from_rsdt(handler, rsdp_revision, rsdt_address) } + } + + /// Construct an `AcpiTables` from the **physical** address of the RSDT/XSDT, and the revision + /// found in the RSDP. + pub unsafe fn from_rsdt( + handler: H, + rsdp_revision: u8, + rsdt_address: usize, + ) -> Result, AcpiError> { + let rsdt_mapping = + unsafe { handler.map_physical_region::(rsdt_address, mem::size_of::()) }; + let rsdt_length = rsdt_mapping.length; + let rsdt_mapping = unsafe { handler.map_physical_region::(rsdt_address, rsdt_length as usize) }; + Ok(Self { rsdt_mapping, rsdp_revision, handler }) + } + + /// Iterate over the **physical** addresses of the SDTs. + pub fn table_entries(&self) -> impl Iterator { + let entry_size = if self.rsdp_revision == 0 { 4 } else { 8 }; + let mut table_entries_ptr = + unsafe { self.rsdt_mapping.virtual_start().as_ptr().byte_add(mem::size_of::()) } + .cast::(); + let mut num_entries = (self.rsdt_mapping.region_length() - mem::size_of::()) / entry_size; + + core::iter::from_fn(move || { + if num_entries > 0 { + unsafe { + let entry = if entry_size == 4 { + *table_entries_ptr.cast::() as usize + } else { + *table_entries_ptr.cast::() as usize + }; + table_entries_ptr = table_entries_ptr.byte_add(entry_size); + num_entries -= 1; + + Some(entry) + } + } else { + None + } + }) + } + + /// Iterate over the headers of each SDT, along with their **physical** addresses. + pub fn table_headers(&self) -> impl Iterator { + self.table_entries().map(|table_phys_address| { + let mapping = unsafe { + self.handler.map_physical_region::(table_phys_address, mem::size_of::()) + }; + (table_phys_address, *mapping) + }) + } + + /// Find all tables with the signature `T::SIGNATURE`. + pub fn find_tables(&self) -> impl Iterator> + where + T: AcpiTable, + { + self.table_entries().filter_map(|table_phys_address| { + let header_mapping = unsafe { + self.handler.map_physical_region::(table_phys_address, mem::size_of::()) + }; + if header_mapping.signature == T::SIGNATURE { + // Extend the mapping to the entire table + let length = header_mapping.length; + drop(header_mapping); + Some(unsafe { self.handler.map_physical_region::(table_phys_address, length as usize) }) + } else { + None + } + }) + } + + /// Find the first table with the signature `T::SIGNATURE`. + pub fn find_table(&self) -> Option> + where + T: AcpiTable, + { + self.find_tables().next() + } + + pub fn dsdt(&self) -> Result { + let Some(fadt) = self.find_table::() else { + Err(AcpiError::TableNotFound(Signature::FADT))? + }; + let phys_address = fadt.dsdt_address()?; + let header = + unsafe { self.handler.map_physical_region::(phys_address, mem::size_of::()) }; + Ok(AmlTable { phys_address, length: header.length, revision: header.revision }) + } + + pub fn ssdts(&self) -> impl Iterator { + self.table_headers().filter_map(|(phys_address, header)| { + if header.signature == Signature::SSDT { + Some(AmlTable { phys_address, length: header.length, revision: header.revision }) + } else { + None + } + }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct AmlTable { + /// The physical address of the start of the table. Add `mem::size_of::()` to this + /// to get the physical address of the start of the AML stream. + pub phys_address: usize, + /// The length of the table, including the header. + pub length: u32, + pub revision: u8, +} + +/// 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. If a table's definition may be larger than a valid +/// SDT's size, [`ExtendedField`](sdt::ExtendedField) should be used to define fields that may or +/// may not exist. +pub unsafe trait AcpiTable { + const SIGNATURE: Signature; + + fn header(&self) -> &SdtHeader; + + fn validate(&self) -> Result<(), AcpiError> { + unsafe { self.header().validate(Self::SIGNATURE) } + } +} + +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum AcpiError { + NoValidRsdp, + RsdpIncorrectSignature, + RsdpInvalidOemId, + RsdpInvalidChecksum, + + SdtInvalidSignature(Signature), + SdtInvalidOemId(Signature), + SdtInvalidTableId(Signature), + SdtInvalidChecksum(Signature), + SdtInvalidCreatorId(Signature), + + TableNotFound(Signature), + InvalidFacsAddress, + InvalidDsdtAddress, + InvalidMadt(MadtError), + InvalidGenericAddress, + + #[cfg(feature = "alloc")] + Aml(aml::AmlError), +} diff --git a/src/platform/interrupt.rs b/src/platform/interrupt.rs new file mode 100644 index 00000000..b4d50ba3 --- /dev/null +++ b/src/platform/interrupt.rs @@ -0,0 +1,361 @@ +use super::{Processor, ProcessorInfo, ProcessorState}; +use crate::{ + AcpiError, + AcpiHandler, + AcpiTables, + MadtError, + sdt::{ + Signature, + madt::{Madt, MadtEntry, parse_mps_inti_flags}, + }, +}; +use alloc::{alloc::Global, vec::Vec}; +use bit_field::BitField; +use core::{alloc::Allocator, pin::Pin}; + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum InterruptModel { + /// 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), +} + +impl InterruptModel { + pub fn new( + tables: &AcpiTables, + ) -> Result<(InterruptModel, Option>), AcpiError> { + Self::new_in(tables, Global) + } +} + +impl InterruptModel { + pub fn new_in( + tables: &AcpiTables, + allocator: A, + ) -> Result<(InterruptModel, Option>), AcpiError> { + let Some(madt) = tables.find_table::() else { Err(AcpiError::TableNotFound(Signature::MADT))? }; + + /* + * We first do a pass through the MADT to determine which interrupt model is being used. + */ + for entry in madt.get().entries() { + match entry { + MadtEntry::LocalApic(_) + | MadtEntry::LocalX2Apic(_) + | MadtEntry::IoApic(_) + | MadtEntry::InterruptSourceOverride(_) + | MadtEntry::LocalApicNmi(_) + | MadtEntry::X2ApicNmi(_) + | MadtEntry::LocalApicAddressOverride(_) => { + return Self::from_apic_model_in(madt.get(), allocator); + } + + MadtEntry::IoSapic(_) | MadtEntry::LocalSapic(_) | MadtEntry::PlatformInterruptSource(_) => { + unimplemented!(); + } + + MadtEntry::Gicc(_) + | MadtEntry::Gicd(_) + | MadtEntry::GicMsiFrame(_) + | MadtEntry::GicRedistributor(_) + | MadtEntry::GicInterruptTranslationService(_) => { + unimplemented!(); + } + + MadtEntry::NmiSource(_) => (), + MadtEntry::MultiprocessorWakeup(_) => (), + } + } + + Ok((InterruptModel::Unknown, None)) + } + + fn from_apic_model_in( + madt: Pin<&Madt>, + allocator: A, + ) -> Result<(InterruptModel, Option>), AcpiError> { + let mut local_apic_address = madt.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 madt.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::X2ApicNmi(_) => local_nmi_line_count += 1, + MadtEntry::LocalApic(_) => processor_count += 1, + MadtEntry::LocalX2Apic(_) => processor_count += 1, + _ => (), + } + } + + let mut io_apics = Vec::with_capacity_in(io_apic_count, allocator.clone()); + let mut interrupt_source_overrides = Vec::with_capacity_in(iso_count, allocator.clone()); + let mut nmi_sources = Vec::with_capacity_in(nmi_source_count, allocator.clone()); + let mut local_apic_nmi_lines = Vec::with_capacity_in(local_nmi_line_count, allocator.clone()); + let mut application_processors = Vec::with_capacity_in(processor_count.saturating_sub(1), allocator); // Subtract one for the BSP + let mut boot_processor = None; + + for entry in madt.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.push(processor); + } 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.push(processor); + } else { + boot_processor = Some(processor); + } + } + + MadtEntry::IoApic(entry) => { + io_apics.push(IoApic { + id: entry.io_apic_id, + address: entry.io_apic_address, + global_system_interrupt_base: entry.global_system_interrupt_base, + }); + } + + 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.push(InterruptSourceOverride { + isa_source: entry.irq, + global_system_interrupt: entry.global_system_interrupt, + polarity, + trigger_mode, + }); + } + + MadtEntry::NmiSource(entry) => { + let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?; + + nmi_sources.push(NmiSource { + global_system_interrupt: entry.global_system_interrupt, + polarity, + trigger_mode, + }); + } + + MadtEntry::LocalApicNmi(entry) => { + local_apic_nmi_lines.push(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)), + }, + }); + } + + MadtEntry::X2ApicNmi(entry) => { + local_apic_nmi_lines.push(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)), + }, + }); + } + + MadtEntry::LocalApicAddressOverride(entry) => { + local_apic_address = entry.local_apic_address; + } + + MadtEntry::MultiprocessorWakeup(_) => {} + + _ => { + 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, + madt.supports_8259(), + )), + Some(ProcessorInfo::new_in(boot_processor.unwrap(), application_processors)), + )) + } +} + +#[derive(Debug, Clone, Copy)] +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, Clone, Copy)] +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, Clone, Copy)] +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, Clone, Copy)] +pub struct NmiSource { + pub global_system_interrupt: u32, + pub polarity: Polarity, + pub trigger_mode: TriggerMode, +} + +#[derive(Debug, Clone)] +pub struct Apic { + pub local_apic_address: u64, + pub io_apics: Vec, + pub local_apic_nmi_lines: Vec, + pub interrupt_source_overrides: Vec, + pub nmi_sources: Vec, + + /// 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 Apic { + pub(crate) fn new( + local_apic_address: u64, + io_apics: Vec, + local_apic_nmi_lines: Vec, + interrupt_source_overrides: Vec, + nmi_sources: Vec, + 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, + } + } +} diff --git a/acpi/src/platform/mod.rs b/src/platform/mod.rs similarity index 80% rename from acpi/src/platform/mod.rs rename to src/platform/mod.rs index bf4097c3..537c95c7 100644 --- a/acpi/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,18 +1,23 @@ pub mod interrupt; +pub mod pci; + +pub use interrupt::InterruptModel; +pub use pci::PciConfigRegions; use crate::{ - address::GenericAddress, - fadt::Fadt, - madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox}, AcpiError, AcpiHandler, - AcpiResult, AcpiTables, - ManagedSlice, PowerProfile, + address::GenericAddress, + sdt::{ + Signature, + fadt::Fadt, + madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox}, + }, }; +use alloc::{alloc::Global, vec::Vec}; use core::{alloc::Allocator, mem, ptr}; -use interrupt::InterruptModel; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ProcessorState { @@ -46,20 +51,14 @@ pub struct Processor { } #[derive(Debug, Clone)] -pub struct ProcessorInfo<'a, A> -where - A: Allocator, -{ +pub struct ProcessorInfo { 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>, + pub application_processors: Vec, } -impl<'a, A> ProcessorInfo<'a, A> -where - A: Allocator, -{ - pub(crate) fn new(boot_processor: Processor, application_processors: ManagedSlice<'a, Processor, A>) -> Self { +impl ProcessorInfo { + pub(crate) fn new_in(boot_processor: Processor, application_processors: Vec) -> Self { Self { boot_processor, application_processors } } } @@ -86,24 +85,20 @@ impl PmTimer { /// 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, Clone)] -pub struct PlatformInfo<'a, A> -where - A: Allocator, -{ +pub struct PlatformInfo { pub power_profile: PowerProfile, - pub interrupt_model: InterruptModel<'a, A>, + pub interrupt_model: InterruptModel, /// 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>, + pub processor_info: Option>, pub pm_timer: Option, /* * TODO: we could provide a nice view of the hardware register blocks in the FADT here. */ } -#[cfg(feature = "alloc")] -impl PlatformInfo<'_, alloc::alloc::Global> { - pub fn new(tables: &AcpiTables) -> AcpiResult +impl PlatformInfo { + pub fn new(tables: &AcpiTables) -> Result where H: AcpiHandler, { @@ -111,22 +106,15 @@ impl PlatformInfo<'_, alloc::alloc::Global> { } } -impl PlatformInfo<'_, A> -where - A: Allocator + Clone, -{ - pub fn new_in(tables: &AcpiTables, allocator: A) -> AcpiResult +impl PlatformInfo { + pub fn new_in(tables: &AcpiTables, allocator: A) -> Result where H: AcpiHandler, { - let fadt = tables.find_table::()?; + let Some(fadt) = tables.find_table::() else { Err(AcpiError::TableNotFound(Signature::FADT))? }; let power_profile = fadt.power_profile(); - let madt = tables.find_table::(); - let (interrupt_model, processor_info) = match madt { - Ok(madt) => madt.get().parse_interrupt_model_in(allocator)?, - Err(_) => (InterruptModel::Unknown, None), - }; + let (interrupt_model, processor_info) = InterruptModel::new_in(&tables, allocator)?; let pm_timer = PmTimer::new(&fadt)?; Ok(PlatformInfo { power_profile, interrupt_model, processor_info, pm_timer }) @@ -142,7 +130,7 @@ where /// - Paging mode is enabled and physical memory for waking vector is identity mapped (virtual address equals physical address). /// - Waking vector must be contained within one physical page. /// - Selectors are set to flat and otherwise not used. -pub fn wakeup_aps( +pub unsafe fn wakeup_aps( tables: &AcpiTables, handler: H, apic_id: u32, @@ -152,7 +140,7 @@ pub fn wakeup_aps( where H: AcpiHandler, { - let madt = tables.find_table::()?; + let Some(madt) = tables.find_table::() else { Err(AcpiError::TableNotFound(Signature::MADT))? }; let mailbox_addr = madt.get().get_mpwk_mailbox_addr()?; let mut mpwk_mapping = unsafe { handler.map_physical_region::( diff --git a/src/platform/pci.rs b/src/platform/pci.rs new file mode 100644 index 00000000..7870938c --- /dev/null +++ b/src/platform/pci.rs @@ -0,0 +1,64 @@ +use crate::{ + AcpiError, + AcpiHandler, + AcpiTables, + sdt::{ + Signature, + mcfg::{Mcfg, McfgEntry}, + }, +}; +use alloc::{ + alloc::{Allocator, Global}, + vec::Vec, +}; + +/// 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, [`PciConfigRegions::physical_address`] 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). +pub struct PciConfigRegions { + pub regions: Vec, +} + +impl PciConfigRegions { + pub fn new(tables: &AcpiTables) -> Result, AcpiError> + where + H: AcpiHandler, + { + Self::new_in(tables, Global) + } +} + +impl PciConfigRegions { + pub fn new_in(tables: &AcpiTables, allocator: A) -> Result, AcpiError> + where + H: AcpiHandler, + { + let Some(mcfg) = tables.find_table::() else { Err(AcpiError::TableNotFound(Signature::MCFG))? }; + let regions = mcfg.entries().to_vec_in(allocator); + + 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 { + /* + * 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)), + ) + } +} diff --git a/acpi/src/rsdp.rs b/src/rsdp.rs similarity index 93% rename from acpi/src/rsdp.rs rename to src/rsdp.rs index 3c33e9c0..2879ee26 100644 --- a/acpi/src/rsdp.rs +++ b/src/rsdp.rs @@ -1,4 +1,4 @@ -use crate::{AcpiError, AcpiHandler, AcpiResult, PhysicalMapping}; +use crate::{AcpiError, AcpiHandler, PhysicalMapping}; use core::{mem, ops::Range, slice, str}; /// The size in bytes of the ACPI 1.0 RSDP. @@ -22,19 +22,19 @@ const RSDP_V2_EXT_LENGTH: usize = mem::size_of::() - RSDP_V1_LENGTH; #[derive(Clone, Copy, Debug)] #[repr(C, packed)] pub struct Rsdp { - signature: [u8; 8], - checksum: u8, - oem_id: [u8; 6], - revision: u8, - rsdt_address: u32, + pub signature: [u8; 8], + pub checksum: u8, + pub oem_id: [u8; 6], + pub revision: u8, + pub 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], + pub length: u32, + pub xsdt_address: u64, + pub ext_checksum: u8, + _reserved: [u8; 3], } impl Rsdp { @@ -52,7 +52,7 @@ impl Rsdp { /// - 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> + pub unsafe fn search_for_on_bios(handler: H) -> Result, AcpiError> where H: AcpiHandler, { @@ -101,13 +101,13 @@ impl Rsdp { /// 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<()> { + pub fn validate(&self) -> Result<(), AcpiError> { // Check the signature if self.signature != RSDP_SIGNATURE { return Err(AcpiError::RsdpIncorrectSignature); } - // Check the OEM id is valid UTF8 (allows use of unwrap) + // Check the OEM id is valid UTF-8 if str::from_utf8(&self.oem_id).is_err() { return Err(AcpiError::RsdpInvalidOemId); } @@ -141,8 +141,8 @@ impl Rsdp { self.checksum } - pub fn oem_id(&self) -> &str { - str::from_utf8(&self.oem_id).unwrap() + pub fn oem_id(&self) -> Result<&str, AcpiError> { + str::from_utf8(&self.oem_id).map_err(|_| AcpiError::RsdpInvalidOemId) } pub fn revision(&self) -> u8 { diff --git a/acpi/src/bgrt.rs b/src/sdt/bgrt.rs similarity index 81% rename from acpi/src/bgrt.rs rename to src/sdt/bgrt.rs index ab231511..2b82b8cd 100644 --- a/acpi/src/bgrt.rs +++ b/src/sdt/bgrt.rs @@ -1,6 +1,6 @@ use crate::{ - sdt::{SdtHeader, Signature}, AcpiTable, + sdt::{SdtHeader, Signature}, }; use bit_field::BitField; @@ -9,16 +9,15 @@ use bit_field::BitField; #[repr(C, packed)] #[derive(Debug, Clone, Copy)] pub struct Bgrt { - header: SdtHeader, + pub header: SdtHeader, pub version: u16, - status: u8, - image_type: u8, + pub status: u8, + pub image_type: u8, pub image_address: u64, - image_offset_x: u32, - image_offset_y: u32, + pub image_offset_x: u32, + pub image_offset_y: u32, } -/// ### Safety: Implementation properly represents a valid BGRT. unsafe impl AcpiTable for Bgrt { const SIGNATURE: Signature = Signature::BGRT; @@ -37,7 +36,7 @@ impl Bgrt { } /// Gets the orientation offset of the image. - /// Degrees are clockwise from the images default orientation. + /// Degrees are clockwise from the image's default orientation. pub fn orientation_offset(&self) -> u16 { let status = self.status; match status.get_bits(1..3) { @@ -45,7 +44,7 @@ impl Bgrt { 1 => 90, 2 => 180, 3 => 270, - _ => unreachable!(), // will never happen + _ => unreachable!(), } } diff --git a/acpi/src/fadt.rs b/src/sdt/fadt.rs similarity index 92% rename from acpi/src/fadt.rs rename to src/sdt/fadt.rs index f97c4e61..c2619c67 100644 --- a/acpi/src/fadt.rs +++ b/src/sdt/fadt.rs @@ -1,8 +1,8 @@ use crate::{ - address::{AccessSize, AddressSpace, GenericAddress, RawGenericAddress}, - sdt::{ExtendedField, SdtHeader, Signature}, AcpiError, AcpiTable, + address::{AccessSize, AddressSpace, GenericAddress, RawGenericAddress}, + sdt::{ExtendedField, SdtHeader, Signature}, }; use bit_field::BitField; @@ -30,15 +30,15 @@ pub enum PowerProfile { #[repr(C, packed)] #[derive(Debug, Clone, Copy)] pub struct Fadt { - header: SdtHeader, + pub header: SdtHeader, - firmware_ctrl: u32, - dsdt_address: u32, + pub firmware_ctrl: u32, + pub dsdt_address: u32, // Used in acpi 1.0; compatibility only, should be zero _reserved: u8, - preferred_pm_profile: u8, + pub 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. /// @@ -64,20 +64,20 @@ pub struct Fadt { 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 pm1a_event_block: u32, + pub pm1b_event_block: u32, + pub pm1a_control_block: u32, + pub pm1b_control_block: u32, + pub pm2_control_block: u32, + pub pm_timer_block: u32, + pub gpe0_block: u32, + pub gpe1_block: u32, + pub pm1_event_length: u8, + pub pm1_control_length: u8, + pub pm2_control_length: u8, + pub pm_timer_length: u8, + pub gpe0_block_length: u8, + pub 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 @@ -96,23 +96,23 @@ pub struct Fadt { pub iapc_boot_arch: IaPcBootArchFlags, _reserved2: u8, // must be 0 pub flags: FixedFeatureFlags, - reset_reg: RawGenericAddress, + pub reset_reg: RawGenericAddress, pub reset_value: u8, pub arm_boot_arch: ArmBootArchFlags, - fadt_minor_version: u8, - x_firmware_ctrl: ExtendedField, - x_dsdt_address: ExtendedField, - x_pm1a_event_block: ExtendedField, - x_pm1b_event_block: ExtendedField, - x_pm1a_control_block: ExtendedField, - x_pm1b_control_block: ExtendedField, - x_pm2_control_block: ExtendedField, - x_pm_timer_block: ExtendedField, - x_gpe0_block: ExtendedField, - x_gpe1_block: ExtendedField, - sleep_control_reg: ExtendedField, - sleep_status_reg: ExtendedField, - hypervisor_vendor_id: ExtendedField, + pub fadt_minor_version: u8, + pub x_firmware_ctrl: ExtendedField, + pub x_dsdt_address: ExtendedField, + pub x_pm1a_event_block: ExtendedField, + pub x_pm1b_event_block: ExtendedField, + pub x_pm1a_control_block: ExtendedField, + pub x_pm1b_control_block: ExtendedField, + pub x_pm2_control_block: ExtendedField, + pub x_pm_timer_block: ExtendedField, + pub x_gpe0_block: ExtendedField, + pub x_gpe1_block: ExtendedField, + pub sleep_control_reg: ExtendedField, + pub sleep_status_reg: ExtendedField, + pub hypervisor_vendor_id: ExtendedField, } /// ### Safety: Implementation properly represents a valid FADT. @@ -126,7 +126,7 @@ unsafe impl AcpiTable for Fadt { impl Fadt { pub fn validate(&self) -> Result<(), AcpiError> { - self.header.validate(crate::sdt::Signature::FADT) + unsafe { self.header.validate(crate::sdt::Signature::FADT) } } pub fn facs_address(&self) -> Result { diff --git a/acpi/src/hpet.rs b/src/sdt/hpet.rs similarity index 61% rename from acpi/src/hpet.rs rename to src/sdt/hpet.rs index 6360486c..a558d7fe 100644 --- a/acpi/src/hpet.rs +++ b/src/sdt/hpet.rs @@ -1,12 +1,13 @@ use crate::{ - address::RawGenericAddress, - sdt::{SdtHeader, Signature}, AcpiError, AcpiHandler, AcpiTable, AcpiTables, + address::RawGenericAddress, + sdt::{SdtHeader, Signature}, }; use bit_field::BitField; +use log::warn; #[derive(Debug)] pub enum PageProtection { @@ -21,8 +22,11 @@ pub enum PageProtection { /// 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 hardware_rev: u8, + pub num_comparators: u8, + pub main_counter_is_64bits: bool, + pub legacy_irq_capable: bool, + pub pci_vendor_id: u16, 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) @@ -35,13 +39,19 @@ impl HpetInfo { where H: AcpiHandler, { - let hpet = tables.find_table::()?; + let Some(hpet) = tables.find_table::() else { Err(AcpiError::TableNotFound(Signature::HPET))? }; - // Make sure the HPET is in system memory - assert_eq!(hpet.base_address.address_space, 0); + if hpet.base_address.address_space != 0 { + warn!("HPET reported as not in system memory; tables invalid?"); + } + let event_timer_block_id = hpet.event_timer_block_id; Ok(HpetInfo { - event_timer_block_id: hpet.event_timer_block_id, + hardware_rev: event_timer_block_id.get_bits(0..8) as u8, + num_comparators: event_timer_block_id.get_bits(8..13) as u8, + main_counter_is_64bits: event_timer_block_id.get_bit(13), + legacy_irq_capable: event_timer_block_id.get_bit(15), + pci_vendor_id: event_timer_block_id.get_bits(16..32) as u16, base_address: hpet.base_address.address as usize, hpet_number: hpet.hpet_number, clock_tick_unit: hpet.clock_tick_unit, @@ -54,42 +64,20 @@ impl HpetInfo { }, }) } - - 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, + pub header: SdtHeader, + pub event_timer_block_id: u32, + pub base_address: RawGenericAddress, + pub hpet_number: u8, + pub 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, + pub page_protection_and_oem: u8, } -/// ### Safety: Implementation properly represents a valid HPET table. unsafe impl AcpiTable for HpetTable { const SIGNATURE: Signature = Signature::HPET; diff --git a/acpi/src/madt.rs b/src/sdt/madt.rs similarity index 54% rename from acpi/src/madt.rs rename to src/sdt/madt.rs index 9f1427dd..2d69a7e1 100644 --- a/acpi/src/madt.rs +++ b/src/sdt/madt.rs @@ -1,7 +1,8 @@ use crate::{ - sdt::{ExtendedField, SdtHeader, Signature}, AcpiError, AcpiTable, + platform::interrupt::{Polarity, TriggerMode}, + sdt::{ExtendedField, SdtHeader, Signature}, }; use bit_field::BitField; use core::{ @@ -10,16 +11,7 @@ use core::{ pin::Pin, }; -#[cfg(feature = "allocator_api")] -use crate::{ - platform::{ - interrupt::{InterruptModel, Polarity, TriggerMode}, - ProcessorInfo, - }, - AcpiResult, -}; - -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum MadtError { UnexpectedEntry, InterruptOverrideEntryHasInvalidBus, @@ -40,8 +32,8 @@ pub enum MadtError { /// /// The MADT is a variable-sized structure consisting of a static header and then a variable number of entries. /// This type only contains the static portion, and then uses pointer arithmetic to parse the following entries. -/// To make this sound, this type is `!Unpin` - this prevents you from getting anything other than a `Pin<&Madt>` -/// out of a `PhysicalMapping`, thereby preventing a `Madt` from being moved before [`Madt::entries`] is called. +/// To make this sound, this type is `!Unpin` - this prevents `Madt` being moved, which would leave +/// the entries behind. #[repr(C, packed)] #[derive(Debug)] pub struct Madt { @@ -51,7 +43,6 @@ pub struct Madt { _pinned: PhantomPinned, } -/// ### Safety: Implementation properly represents a valid MADT. unsafe impl AcpiTable for Madt { const SIGNATURE: Signature = Signature::MADT; @@ -61,266 +52,6 @@ unsafe impl AcpiTable for Madt { } impl Madt { - pub fn get_mpwk_mailbox_addr(self: Pin<&Self>) -> Result { - for entry in self.entries() { - if let MadtEntry::MultiprocessorWakeup(entry) = entry { - return Ok(entry.mailbox_address); - } - } - Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry)) - } - - #[cfg(feature = "allocator_api")] - pub fn parse_interrupt_model_in<'a, A>( - self: Pin<&Self>, - allocator: A, - ) -> AcpiResult<(InterruptModel<'a, A>, Option>)> - 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: Pin<&Self>, - allocator: A, - ) -> AcpiResult<(InterruptModel<'a, A>, Option>)> - where - A: core::alloc::Allocator + Clone, - { - use crate::platform::{ - interrupt::{ - Apic, - InterruptSourceOverride, - IoApic, - LocalInterruptLine, - NmiLine, - NmiProcessor, - NmiSource, - }, - Processor, - ProcessorState, - }; - - 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::X2ApicNmi(_) => local_nmi_line_count += 1, - MadtEntry::LocalApic(_) => processor_count += 1, - MadtEntry::LocalX2Apic(_) => 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; - } - - MadtEntry::MultiprocessorWakeup(_) => {} - - _ => { - 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: Pin<&Self>) -> MadtEntryIter<'_> { let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Madt as *const u8 }; MadtEntryIter { @@ -333,6 +64,15 @@ impl Madt { pub fn supports_8259(&self) -> bool { { self.flags }.get_bit(0) } + + pub fn get_mpwk_mailbox_addr(self: Pin<&Self>) -> Result { + for entry in self.entries() { + if let MadtEntry::MultiprocessorWakeup(entry) = entry { + return Ok(entry.mailbox_address); + } + } + Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry)) + } } #[derive(Debug)] @@ -523,7 +263,7 @@ pub struct LocalSapicEntry { /// 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, + pub processor_uid_string: u8, } #[derive(Clone, Copy, Debug)] @@ -676,23 +416,22 @@ pub struct MultiprocessorWakeupMailbox { pub apic_id: u32, pub wakeup_vector: u64, pub reserved_for_os: [u64; 254], - reserved_for_firmware: [u64; 256], + pub reserved_for_firmware: [u64; 256], } -#[cfg(feature = "allocator_api")] -fn parse_mps_inti_flags(flags: u16) -> crate::AcpiResult<(Polarity, TriggerMode)> { +pub fn parse_mps_inti_flags(flags: u16) -> Result<(Polarity, TriggerMode), AcpiError> { let polarity = match flags.get_bits(0..2) { 0b00 => Polarity::SameAsBus, 0b01 => Polarity::ActiveHigh, 0b11 => Polarity::ActiveLow, - _ => return Err(crate::AcpiError::InvalidMadt(MadtError::MpsIntiInvalidPolarity)), + _ => return Err(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)), + _ => return Err(AcpiError::InvalidMadt(MadtError::MpsIntiInvalidTriggerMode)), }; Ok((polarity, trigger_mode)) diff --git a/src/sdt/mcfg.rs b/src/sdt/mcfg.rs new file mode 100644 index 00000000..d14024f4 --- /dev/null +++ b/src/sdt/mcfg.rs @@ -0,0 +1,54 @@ +use crate::{ + AcpiTable, + sdt::{SdtHeader, Signature}, +}; +use core::{fmt, mem, slice}; + +#[repr(C, packed)] +pub struct Mcfg { + pub 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::(); + + // Intentionally round down in case length isn't an exact multiple of McfgEntry size - this + // has been observed on real hardware (see rust-osdev/acpi#58) + let num_entries = length / mem::size_of::(); + + unsafe { + let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::()) as *const McfgEntry; + slice::from_raw_parts(pointer, num_entries) + } + } +} + +impl fmt::Debug for Mcfg { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> 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/acpi/src/sdt.rs b/src/sdt/mod.rs similarity index 73% rename from acpi/src/sdt.rs rename to src/sdt/mod.rs index 9d92624a..ebd69c9d 100644 --- a/acpi/src/sdt.rs +++ b/src/sdt/mod.rs @@ -1,4 +1,11 @@ -use crate::{AcpiError, AcpiHandler, AcpiResult, AcpiTable, PhysicalMapping}; +pub mod bgrt; +pub mod fadt; +pub mod hpet; +pub mod madt; +pub mod mcfg; +pub mod spcr; + +use crate::AcpiError; 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 @@ -13,11 +20,7 @@ impl ExtendedField { /// ### Safety /// If a bogus ACPI version is passed, this function may access uninitialised data. pub unsafe fn access(&self, revision: u8) -> Option { - if revision >= MIN_REVISION { - Some(unsafe { self.0.assume_init() }) - } else { - None - } + if revision >= MIN_REVISION { Some(unsafe { self.0.assume_init() }) } else { None } } } @@ -56,7 +59,7 @@ impl ExtendedField { /// * 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): +/// ACPI also 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 @@ -105,13 +108,19 @@ pub struct SdtHeader { pub oem_id: [u8; 6], pub oem_table_id: [u8; 8], pub oem_revision: u32, - pub creator_id: u32, + pub creator_id: [u8; 4], pub creator_revision: u32, } impl SdtHeader { - /// Whether values of header fields are permitted. - fn validate_header_fields(&self, signature: Signature) -> AcpiResult<()> { + /// 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. + /// + /// ### Safety + /// The entire `length` bytes of the SDT must be mapped to compute the checksum. + pub unsafe fn validate(&self, signature: Signature) -> Result<(), AcpiError> { // Check the signature if self.signature != signature || str::from_utf8(&self.signature.0).is_err() { return Err(AcpiError::SdtInvalidSignature(signature)); @@ -127,89 +136,53 @@ impl SdtHeader { 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. + // Check the checksum let table_bytes = unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::(), 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)) + if sum != 0 { + return 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( - header_mapping: PhysicalMapping, - handler: H, - ) -> AcpiResult> { - header_mapping.validate_header_fields(T::SIGNATURE)?; + #[inline] + pub fn length(&self) -> u32 { + self.length + } - // 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); + #[inline] + pub fn revision(&self) -> u8 { + self.revision + } - // SAFETY: `header_mapping` maps entire table. - unsafe { - PhysicalMapping::new( - header_mapping.physical_start(), - header_mapping.virtual_start().cast::(), - 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); + #[inline] + pub fn checksum(&self) -> u8 { + self.checksum + } - // 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) } - }; + pub fn oem_id(&self) -> Result<&str, AcpiError> { + str::from_utf8(&self.oem_id).map_err(|_| AcpiError::SdtInvalidOemId(self.signature)) + } - // This is usually redundant compared to simply calling `validate_checksum` but respects custom - // `AcpiTable::validate` implementations. - table_mapping.get().validate()?; + pub fn oem_table_id(&self) -> Result<&str, AcpiError> { + str::from_utf8(&self.oem_table_id).map_err(|_| AcpiError::SdtInvalidTableId(self.signature)) + } - Ok(table_mapping) + #[inline] + pub fn oem_revision(&self) -> u32 { + self.oem_revision } - pub fn oem_id(&self) -> &str { - // Safe to unwrap because checked in `validate` - str::from_utf8(&self.oem_id).unwrap() + #[inline] + pub fn creator_id(&self) -> Result<&str, AcpiError> { + str::from_utf8(&self.creator_id).map_err(|_| AcpiError::SdtInvalidCreatorId(self.signature)) } - pub fn oem_table_id(&self) -> &str { - // Safe to unwrap because checked in `validate` - str::from_utf8(&self.oem_table_id).unwrap() + #[inline] + pub fn creator_revision(&self) -> u32 { + self.creator_revision } } diff --git a/acpi/src/spcr.rs b/src/sdt/spcr.rs similarity index 92% rename from acpi/src/spcr.rs rename to src/sdt/spcr.rs index 5649dde4..d5645c37 100644 --- a/acpi/src/spcr.rs +++ b/src/sdt/spcr.rs @@ -1,12 +1,12 @@ use crate::{ - address::{GenericAddress, RawGenericAddress}, - AcpiResult, + AcpiError, AcpiTable, SdtHeader, Signature, + address::{GenericAddress, RawGenericAddress}, }; use core::{ - num::{NonZeroU32, NonZeroU8}, + num::{NonZeroU8, NonZeroU32}, ptr, slice, str::{self, Utf8Error}, @@ -24,33 +24,33 @@ use core::{ #[derive(Debug)] pub struct Spcr { pub header: SdtHeader, - interface_type: u8, + pub interface_type: u8, _reserved: [u8; 3], - base_address: RawGenericAddress, - interrupt_type: u8, - irq: u8, - global_system_interrupt: u32, + pub base_address: RawGenericAddress, + pub interrupt_type: u8, + pub irq: u8, + pub global_system_interrupt: u32, /// The baud rate the BIOS used for redirection. - configured_baud_rate: u8, + pub configured_baud_rate: u8, pub parity: u8, pub stop_bits: u8, - flow_control: u8, - terminal_type: u8, + pub flow_control: u8, + pub terminal_type: u8, /// Language which the BIOS was redirecting. Must be 0. pub language: u8, - pci_device_id: u16, - pci_vendor_id: u16, - pci_bus_number: u8, - pci_device_number: u8, - pci_function_number: u8, + pub pci_device_id: u16, + pub pci_vendor_id: u16, + pub pci_bus_number: u8, + pub pci_device_number: u8, + pub pci_function_number: u8, pub pci_flags: u32, /// PCI segment number. systems with fewer than 255 PCI buses, this number /// will be 0. pub pci_segment: u8, - uart_clock_freq: u32, - precise_baud_rate: u32, - namespace_string_length: u16, - namespace_string_offset: u16, + pub uart_clock_freq: u32, + pub precise_baud_rate: u32, + pub namespace_string_length: u16, + pub namespace_string_offset: u16, } unsafe impl AcpiTable for Spcr { @@ -69,7 +69,7 @@ impl Spcr { /// The base address of the Serial Port register set, if if console /// redirection is enabled. - pub fn base_address(&self) -> Option> { + pub fn base_address(&self) -> Option> { (!self.base_address.is_empty()).then(|| GenericAddress::from_raw(self.base_address)) } diff --git a/tests/buffer_fields.asl b/tests/buffer_fields.asl index 7b10daec..891b159d 100644 --- a/tests/buffer_fields.asl +++ b/tests/buffer_fields.asl @@ -1,23 +1,23 @@ DefinitionBlock("buffer_fields.aml", "DSDT", 1, "RSACPI", "BUFFLD", 1) { - Name(X, Buffer (16) { 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }) - CreateBitField(X, 3, BIT3) - CreateField(X, 0, 3, BITS) - CreateByteField(X, 1, BYTE) - CreateWordField(X, 2, WORD) - CreateDWordField(X, 4, DWRD) - CreateQWordField(X, 8, QWRD) + Name(X, Buffer (16) { 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff }) + CreateBitField(X, 3, BIT3) + CreateField(X, 0, 3, BITS) + CreateByteField(X, 1, BYTE) + CreateWordField(X, 2, WORD) + CreateDWordField(X, 4, DWRD) + CreateQWordField(X, 8, QWRD) - // `X` should end up as [0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x00, 0x00] - BIT3 = 1 - BITS = 7 - BYTE = 0x01 - WORD = 0x0302 - DWRD = 0x07060504 - // Last two bytes should be cleared because of zero-extension of this store - // We do this as a buffer store a) to test them b) because `iasl` doesn't support 64-bit integer constants... - QWRD = Buffer() { 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d } + // `X` should end up as [0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x00, 0x00] + BIT3 = 1 + BITS = 7 + BYTE = 0x01 + WORD = 0x0302 + DWRD = 0x07060504 + // Last two bytes should be cleared because of zero-extension of this store + // We do this as a buffer store a) to test them b) because `iasl` doesn't support 64-bit integer constants... + QWRD = Buffer() { 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d } - // `Y` should end up as `Integer(0x07060504)` (`Integer(117835012)` in decimal) - Name(Y, 4) - Y = DWRD + // `Y` should end up as `Integer(0x07060504)` (`Integer(117835012)` in decimal) + Name(Y, 4) + Y = DWRD } diff --git a/tests/complex_package.asl b/tests/complex_package.asl new file mode 100644 index 00000000..944b75f0 --- /dev/null +++ b/tests/complex_package.asl @@ -0,0 +1,68 @@ +DefinitionBlock ("", "DSDT", 2, "uTEST", "TESTTABL", 0xF0F0F0F0) +{ + Method(GPKG) { + Local1 = 10 + Local0 = Package (Local1) { + 0x123, + 0x321, + Package { + 0x321, + "123", + Package { + 0x321, + Package { + 0x321, + "123", + Package { + 0x321, + "123", + Package { + 0x321, + Package { + 0x321, + "123", + Package (Local1) { + 0x321, + "123", + 999, + }, + 999, + }, + "123", + 999, + }, + 999, + }, + 999, + }, + "123", + 999, + }, + 999, + }, + "Hello world", + Package { + 0x321, + "Hello", + }, + Package { + 0x321, + "World", + }, + Package { + Buffer (Local1) { 0xFF }, + 0xDEADBEEF, + }, + Buffer { 1, 2, 3 } + } + + Return (Local0) + } + + Method (MAIN) { + Local0 = GPKG() + Debug = Local0 + Local0 = 1 + Return (Local0) + } +} diff --git a/tests/external.asl b/tests/external.asl deleted file mode 100644 index 4421477a..00000000 --- a/tests/external.asl +++ /dev/null @@ -1,10 +0,0 @@ -DefinitionBlock("external.aml", "DSDT", 1, "RSACPI", "EXTRL", 1) { - Scope(_SB) { - External(FOO, MethodObj, IntObj, {IntObj}) - } - - Name(X, Zero) - Method(BAR) { - Store(\_SB.FOO(4), X) - } -} diff --git a/tests/fields.asl b/tests/fields.asl new file mode 100644 index 00000000..ac9c9e1d --- /dev/null +++ b/tests/fields.asl @@ -0,0 +1,57 @@ +DefinitionBlock("fields.aml", "DSDT", 1, "RSACPI", "BUFFLD", 1) { + OperationRegion(MEM, SystemMemory, 0x40000, 0x1000) + Field(MEM, WordAcc, NoLock, Preserve) { + , 7, + A, 15, + , 8, + B, 1, + , 1, + C, 35 + } + + OperationRegion(GIO0, SystemIO, 0x125, 0x100) + + Field(GIO0, ByteAcc, NoLock, Preserve) { + GLB1, 1, + GLB2, 1, + Offset(1), + BNK1, 4, + + Offset(2), + IDX0, 8, + DAT0, 8 + } + + BankField(GIO0, BNK1, 0, ByteAcc, NoLock, Preserve) { + Offset(0x30), + FET0, 1, + FET1, 1 + } + + BankField(GIO0, BNK1, 1, ByteAcc, NoLock, Preserve) { + Offset(0x30), + BLVL, 7, + BAC, 1 + } + + IndexField(IDX0, DAT0, ByteAcc, NoLock, Preserve) { + FET2, 1, + FET3, 1, + Offset(0x2f), + , 7, + FET4, 1 + } + + Name(RESA, 0) + Name(RESB, 0) + Name(RESC, 0) + Method(MAIN, 0, NotSerialized) { + RESA = A + RESB = B + RESC = C + + A = RESA + B = RESB + C = RESC + } +} diff --git a/tests/inc.asl b/tests/incdec.asl similarity index 52% rename from tests/inc.asl rename to tests/incdec.asl index 729231c7..4b584d51 100644 --- a/tests/inc.asl +++ b/tests/incdec.asl @@ -1,4 +1,4 @@ -DefinitionBlock("inc.aml", "DSDT", 1, "RSACPI", "INCDEC", 1) { +DefinitionBlock("incdec.aml", "DSDT", 1, "RSACPI", "INCDEC", 1) { Name(X, 0) X++ X++ diff --git a/tests/method.asl b/tests/method.asl new file mode 100644 index 00000000..35cc9a1b --- /dev/null +++ b/tests/method.asl @@ -0,0 +1,15 @@ +DefinitionBlock("method.aml", "DSDT", 1, "RSACPI", "METHOD", 1) { + Name(X, 5) + + Method(FOO, 0, NotSerialized) { + If (X > 1) { + Noop + } Else { + Return (0x55) + } + Return (0x3f) + } + + Name(Y, 0) + Y = FOO() +} diff --git a/tests/object_type.asl b/tests/object_type.asl index d4abc90e..3d064504 100644 --- a/tests/object_type.asl +++ b/tests/object_type.asl @@ -1,19 +1,19 @@ DefinitionBlock("object_type.aml", "DSDT", 1, "RSACPI", "OBJTYP", 1) { - Name(INT, 723) - Name(STR, "Hello, World!") - Name(BUFF, Buffer { 7, 2, 3, 5, 92, 6 }) - // TODO: more types + Name(INT, 723) + Name(STR, "Hello, World!") + Name(BUFF, Buffer { 7, 2, 3, 5, 92, 6 }) + // TODO: more types - Device(TYPS) { - // This is just so it compiles - Name (_HID, EisaId ("PNP0A03")) + Device(TYPS) { + // This is just so it compiles + Name (_HID, EisaId ("PNP0A03")) - Name(INT, 0) // Should be `1` - Name(STR, 0) // Should be `2` - Name(BUFF, 0) // Should be `3` + Name(INT, 0) // Should be `1` + Name(STR, 0) // Should be `2` + Name(BUFF, 0) // Should be `3` - INT = ObjectType(\INT) - STR = ObjectType(\STR) - BUFF = ObjectType(\BUFF) - } + INT = ObjectType(\INT) + STR = ObjectType(\STR) + BUFF = ObjectType(\BUFF) + } } diff --git a/tests/package.asl b/tests/package.asl new file mode 100644 index 00000000..cce92ba1 --- /dev/null +++ b/tests/package.asl @@ -0,0 +1,29 @@ +DefinitionBlock("package.aml", "DSDT", 1, "RSACPI", "PACKGE", 1) { + Name(FOO, Package (15) { + 0x01, + 0x02, + 0x03, + "Hello, World!", + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + }) + + Name(BAR, Package (5) { + Package { 0x01, 0x02, 0x03 }, + Package { 0x04, 0x05, 0x06 }, + Package { 0x07, 0x08, 0x09 }, + Package { 0x0a, 0x0b, 0x0c }, + Package { 0x0d, 0x0e, 0x0f }, + }) + + Name(LEN, 10) + Name(BAZ, Package (LEN) { + 4, + 11, + 16, + }) +} diff --git a/tests/pc-bios_acpi-dsdt.asl b/tests/pc-bios_acpi-dsdt.asl index 28e96d89..3f45079e 100644 --- a/tests/pc-bios_acpi-dsdt.asl +++ b/tests/pc-bios_acpi-dsdt.asl @@ -1896,4 +1896,4 @@ } } - \ No newline at end of file + diff --git a/tests/power_res.asl b/tests/power_res.asl index 94aab519..fd89e5e2 100644 --- a/tests/power_res.asl +++ b/tests/power_res.asl @@ -1,16 +1,16 @@ DefinitionBlock("power_res.aml", "DSDT", 1, "RSACPI", "PWRRES", 1) { - Scope(_SB) { - PowerResource(PIDE, 0, 1) { - Name(X, Zero) - Method(_STA) { - Return (X) - } - Method(_ON) { - Store(One, X) - } - Method(_OFF) { - Store(Zero, X) - } - } - } + Scope(_SB) { + PowerResource(PIDE, 0, 1) { + Name(X, Zero) + Method(_STA) { + Return (X) + } + Method(_ON) { + Store(One, X) + } + Method(_OFF) { + Store(Zero, X) + } + } + } } diff --git a/tests/scopes.asl b/tests/scopes.asl index 787ea4fd..3ae726af 100644 --- a/tests/scopes.asl +++ b/tests/scopes.asl @@ -1,12 +1,19 @@ DefinitionBlock("scopes.aml", "DSDT", 1, "RSACPI", "SCOPES", 1) { - Scope(_SB) { - Name(X, 320) - Device(PCI0) { - Name(Y, 15) - Name (_HID, EisaId ("PNP0A03")) - Scope(\) { - Name(Z, 413) - } - } - } + Scope(_SB) { + Name(X, 320) + Name(Y, Zero) + + Device(PCI0) { + Name(Y, 15) + Name (_HID, EisaId ("PNP0A03")) + Scope(\) { + Name(Z, 413) + } + } + + Device(FOO) { + Name (_HID, EisaId ("PNP0A03")) + Alias (\_SB.PCI0.Y, MY_Y) + } + } } diff --git a/tests/thermal_zone.asl b/tests/thermal_zone.asl index 0f295829..08e33f80 100644 --- a/tests/thermal_zone.asl +++ b/tests/thermal_zone.asl @@ -1,40 +1,40 @@ DefinitionBlock("thermal_zone.aml", "DSDT", 1, "RSACPI", "THERMZ", 1) { - Scope(_SB) { - Device(EC0) { - Name(_HID, EISAID("PNP0C09")) - OperationRegion(EC0, EmbeddedControl, 0, 0xFF) - Field(EC0, ByteAcc, Lock, Preserve) { - MODE, 1, // thermal policy (quiet/perform) - FAN, 1, // fan power (on/off) - , 6, // reserved - TMP, 16, // current temp - AC0, 16, // active cooling temp (fan high) - , 16, // reserved - PSV, 16, // passive cooling temp - HOT, 16, // critical S4 temp - CRT, 16 // critical temp - } - } + Scope(_SB) { + Device(EC0) { + Name(_HID, EISAID("PNP0C09")) + OperationRegion(EC0, EmbeddedControl, 0, 0xFF) + Field(EC0, ByteAcc, Lock, Preserve) { + MODE, 1, // thermal policy (quiet/perform) + FAN, 1, // fan power (on/off) + , 6, // reserved + TMP, 16, // current temp + AC0, 16, // active cooling temp (fan high) + , 16, // reserved + PSV, 16, // passive cooling temp + HOT, 16, // critical S4 temp + CRT, 16 // critical temp + } + } - Device(CPU0) { - Name(_HID, "ACPI0007") - Name(_UID, 1) // unique number for this processor - } + Device(CPU0) { + Name(_HID, "ACPI0007") + Name(_UID, 1) // unique number for this processor + } - ThermalZone(TZ0) { - Method(_TMP) { Return (\_SB.EC0.TMP )} // get current temp - Method(_AC0) { Return (\_SB.EC0.AC0) } // fan high temp - Name(_AL0, Package(){\_SB.EC0.FAN}) // fan is act cool dev - Method(_PSV) { Return (\_SB.EC0.PSV) } // passive cooling temp - Name(_PSL, Package (){\_SB.CPU0}) // passive cooling devices - Method(_HOT) { Return (\_SB.EC0.HOT) } // get critical S4 temp - Method(_CRT) { Return (\_SB.EC0.CRT) } // get critical temp - Method(_SCP, 1) { Store (Arg0, \_SB.EC0.MODE) } // set cooling mode - Name(_TC1, 4) // bogus example constant - Name(_TC2, 3) // bogus example constant - Name(_TSP, 150) // passive sampling = 15 sec - Name(_TZP, 0) // polling not required - Name (_STR, Unicode ("System thermal zone")) - } - } + ThermalZone(TZ0) { + Method(_TMP) { Return (\_SB.EC0.TMP )} // get current temp + Method(_AC0) { Return (\_SB.EC0.AC0) } // fan high temp + Name(_AL0, Package(){\_SB.EC0.FAN}) // fan is act cool dev + Method(_PSV) { Return (\_SB.EC0.PSV) } // passive cooling temp + Name(_PSL, Package (){\_SB.CPU0}) // passive cooling devices + Method(_HOT) { Return (\_SB.EC0.HOT) } // get critical S4 temp + Method(_CRT) { Return (\_SB.EC0.CRT) } // get critical temp + Method(_SCP, 1) { Store (Arg0, \_SB.EC0.MODE) } // set cooling mode + Name(_TC1, 4) // bogus example constant + Name(_TC2, 3) // bogus example constant + Name(_TSP, 150) // passive sampling = 15 sec + Name(_TZP, 0) // polling not required + Name (_STR, Unicode ("System thermal zone")) + } + } } diff --git a/tests/to_integer.asl b/tests/to_integer.asl new file mode 100644 index 00000000..8de15d7c --- /dev/null +++ b/tests/to_integer.asl @@ -0,0 +1,109 @@ +DefinitionBlock ("", "SSDT", 2, "uTEST", "TESTTABL", 0xF0F0F0F0) +{ + Name(FCNT, 0) + + Method (CHEK, 3) + { + If (Arg0 != Arg1) { + FCNT++ + Printf("On line %o: invalid number %o, expected %o", ToDecimalString(Arg2), ToHexString(Arg0), ToHexString(Arg1)) + } + } + + Method (MAIN, 0, NotSerialized) + { + Local0 = ToInteger(123) + Local1 = 123 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("123") + Local1 = 123 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger(" \t\t\t\v 123") + Local1 = 123 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("123abcd") + Local1 = 123 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0x123abcd") + Local1 = 0x123abcd + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("") + Local1 = 0 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0X") + Local1 = 0 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0x") + Local1 = 0 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0") + Local1 = 0 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0xDeAdBeeF") + Local1 = 0xDEADBEEF + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0XDeAdBeeFCafeBabeHelloWorld") + Local1 = 0xDEADBEEFCAFEBABE + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger(Buffer { 0xDE, 0xAD, 0xBE, 0xEF }) + Local1 = 0xEFBEADDE + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger(Buffer { 1 }) + Local1 = 1 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger(Buffer { 0 }) + Local1 = 0 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger(Buffer { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE }) + Local1 = 0xBEBAFECAEFBEADDE + CHEK(Local0, Local1, __LINE__) + + // These are incompatible with ACPICA, skip if it's detected + // TODO: these currently fail on our interpreter too. Can fix later. + /*If (ToHexString(0xF) == "0xF") { + Local0 = ToInteger("99999999999999999999999999999999999999999999999") + Local1 = 0xFFFFFFFFFFFFFFFF + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0xDEADBEEFCAFEBABE1") + Local1 = 0xFFFFFFFFFFFFFFFF + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("+123") + Local1 = 123 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("-123") + Local1 = 0xFFFFFFFFFFFFFF85 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("-0xDEADBEF HELLOWORLD") + Local1 = 0xFFFFFFFFF2152411 + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("+0XC0D\t123") + Local1 = 0xC0D + CHEK(Local0, Local1, __LINE__) + + Local0 = ToInteger("0123") + Local1 = 83 + CHEK(Local0, Local1, __LINE__) + }*/ + + Return (FCNT) + } +} diff --git a/tests/to_x.asl b/tests/to_x.asl new file mode 100644 index 00000000..be110816 --- /dev/null +++ b/tests/to_x.asl @@ -0,0 +1,96 @@ +DefinitionBlock ("", "SSDT", 2, "uTEST", "TESTTABL", 0xF0F0F0F0) +{ + Name(FCNT, 0) + + Method (CHEK, 2) + { + If (ObjectType(Arg0) == 3) { + Arg0 = ToHexString(Arg0) + } + + If (ObjectType(Arg1) == 3) { + Arg1 = ToHexString(Arg1) + } + + If (Arg0 != Arg1) { + FCNT++ + Printf("Invalid string %o, expected %o", Arg0, Arg1) + } + } + + Method (MAIN, 0, NotSerialized) + { + // Dec string + Local0 = ToDecimalString(123) + Local1 = "123" + CHEK(Local0, Local1) + + Local0 = ToDecimalString(Buffer { 1, 2, 222, 33, 45, 192, 3, 255 }) + Local1 = "1,2,222,33,45,192,3,255" + CHEK(Local0, Local1) + + Local0 = ToDecimalString("") + Local1 = "" + CHEK(Local0, Local1) + + Local0 = ToDecimalString("123") + Local1 = "123" + CHEK(Local0, Local1) + + Local0 = ToDecimalString(0xFFFFFFFFFFFFFFFF) + Local1 = "18446744073709551615" + CHEK(Local0, Local1) + + // Hex string + Local0 = ToHexString(123) + Local1 = "0x7B" + CHEK(Local0, Local1) + + Local0 = ToHexString(Buffer { 1, 2, 222, 33, 45, 192, 3, 255 }) + Local1 = "0x01,0x02,0xDE,0x21,0x2D,0xC0,0x03,0xFF" + CHEK(Local0, Local1) + + Local0 = ToHexString("") + Local1 = "" + CHEK(Local0, Local1) + + Local0 = ToHexString("123") + Local1 = "123" + CHEK(Local0, Local1) + + Local0 = ToHexString(0xF) + Local1 = "0xF" + CHEK(Local0, Local1) + + Local0 = ToHexString(0xFF) + Local1 = "0xFF" + CHEK(Local0, Local1) + + Local0 = ToHexString(0xFFF) + Local1 = "0xFFF" + CHEK(Local0, Local1) + + Local0 = ToHexString(0xFFFFF) + Local1 = "0xFFFFF" + CHEK(Local0, Local1) + + Local0 = ToHexString(0xFFFFFFFFFFFFFFFF) + Local1 = "0xFFFFFFFFFFFFFFFF" + CHEK(Local0, Local1) + + // Buffer + Local0 = ToBuffer(Buffer { 1, 2, 3 }) + Local1 = Buffer { 1, 2, 3 } + CHEK(Local0, Local1) + + Local0 = ToBuffer("Hello") + Local1 = Buffer { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00 } + CHEK(Local0, Local1) + + Local0 = ToBuffer(0xDEADBEEFCAFEBABE) + Local1 = Buffer { 0xBE, 0xBA, 0xFE, 0xCA, 0xEF, 0xBE, 0xAD, 0xDE } + CHEK(Local0, Local1) + + Return (FCNT) + } +} diff --git a/tests/while.asl b/tests/while.asl index 517af2bc..d58d45d2 100644 --- a/tests/while.asl +++ b/tests/while.asl @@ -1,25 +1,25 @@ DefinitionBlock("while.aml", "DSDT", 1, "RSACPI", "WHILE", 1) { - Name(X, 0) - While (X < 5) { - X++ - } + Name(X, 0) + While (X < 5) { + X++ + } - // Test `DefBreak` - Y should only make it to 5 - Name(Y, 0) - While (Y < 10) { - If (Y >= 5) { - Break - } + // Test `DefBreak` - Y should only make it to 5 + Name(Y, 0) + While (Y < 10) { + If (Y >= 5) { + Break + } - Y++ - } + Y++ + } - // Test `DefContinue` - Z should remain at zero - Name(CNT, 0) - Name(Z, 0) - While (CNT < 5) { - CNT++ - Continue - Z++ - } + // Test `DefContinue` - Z should remain at zero + Name(CNT, 0) + Name(Z, 0) + While (CNT < 5) { + CNT++ + Continue + Z++ + } } diff --git a/acpi-dumper/Cargo.toml b/tools/acpi_dumper/Cargo.toml similarity index 93% rename from acpi-dumper/Cargo.toml rename to tools/acpi_dumper/Cargo.toml index f6b260bb..dd4ce277 100644 --- a/acpi-dumper/Cargo.toml +++ b/tools/acpi_dumper/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "acpi-dumper" +name = "acpi_dumper" version = "0.0.0" publish = false authors = ["Isaac Woods", "Matt Taylor"] diff --git a/acpi-dumper/src/main.rs b/tools/acpi_dumper/src/main.rs similarity index 100% rename from acpi-dumper/src/main.rs rename to tools/acpi_dumper/src/main.rs diff --git a/aml_tester/Cargo.toml b/tools/aml_tester/Cargo.toml similarity index 76% rename from aml_tester/Cargo.toml rename to tools/aml_tester/Cargo.toml index a5a3fb17..14dcf618 100644 --- a/aml_tester/Cargo.toml +++ b/tools/aml_tester/Cargo.toml @@ -5,7 +5,8 @@ authors = ["Isaac Woods"] edition = "2018" [dependencies] +acpi = { path = "../.." } clap = "4" termion = "1" log = "0.4" -aml = { path = "../aml" } +pci_types = "0.10" diff --git a/aml_tester/src/main.rs b/tools/aml_tester/src/main.rs similarity index 56% rename from aml_tester/src/main.rs rename to tools/aml_tester/src/main.rs index 50fab073..02af7bab 100644 --- a/aml_tester/src/main.rs +++ b/tools/aml_tester/src/main.rs @@ -9,16 +9,18 @@ * - For failing tests, print out a nice summary of the errors for each file */ -use aml::{AmlContext, DebugVerbosity}; +use acpi::aml::{namespace::AmlName, AmlError, Handle, Interpreter}; use clap::{Arg, ArgAction, ArgGroup}; +use log::info; +use pci_types::PciAddress; use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, + collections::HashSet, ffi::OsStr, fs::{self, File}, io::{Read, Write}, path::{Path, PathBuf}, process::Command, + str::FromStr, }; enum CompilationOutcome { @@ -35,8 +37,13 @@ fn main() -> std::io::Result<()> { .version("v0.1.0") .author("Isaac Woods") .about("Compiles and tests ASL files") - .arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue).help("Don't compile asl to aml")) - .arg(Arg::new("reset").long("reset").action(ArgAction::SetTrue).help("Clear namespace after each file")) + .arg(Arg::new("no_compile").long("no-compile").action(ArgAction::SetTrue).help("Don't compile ASL to AML")) + .arg( + Arg::new("combined") + .long("combined") + .action(ArgAction::SetTrue) + .help("Don't clear the namespace between tests"), + ) .arg(Arg::new("path").short('p').long("path").required(false).action(ArgAction::Set).value_name("DIR")) .arg(Arg::new("files").action(ArgAction::Append).value_name("FILE.{asl,aml}")) .group(ArgGroup::new("files_list").args(["path", "files"]).required(true)); @@ -49,34 +56,6 @@ fn main() -> std::io::Result<()> { let matches = cmd.get_matches(); - // Get an initial list of files - may not work correctly on non-UTF8 OsString - let files: Vec = if matches.contains_id("path") { - let dir_path = Path::new(matches.get_one::("path").unwrap()); - println!("Running tests in directory: {:?}", dir_path); - fs::read_dir(dir_path)? - .filter_map(|entry| { - if entry.is_ok() { - Some(entry.unwrap().path().to_string_lossy().to_string()) - } else { - None - } - }) - .collect() - } else { - matches.get_many::("files").unwrap_or_default().map(|name| name.to_string()).collect() - }; - - // Make sure all files exist, propagate error if it occurs - files.iter().fold(Ok(()), |result: std::io::Result<()>, file| { - let path = Path::new(file); - if !path.is_file() { - println!("Not a regular file: {}", file); - // Get the io error if there is one - path.metadata()?; - } - result - })?; - // Make sure we have the ability to compile ASL -> AML, if user wants it let user_wants_compile = !matches.get_flag("no_compile"); let can_compile = user_wants_compile && @@ -89,8 +68,9 @@ fn main() -> std::io::Result<()> { Err(_) => false, }; + let tests = find_tests(&matches)?; let compiled_files: Vec = - files.iter().map(|name| resolve_and_compile(name, can_compile).unwrap()).collect(); + tests.iter().map(|name| resolve_and_compile(name, can_compile).unwrap()).collect(); // Check if compilation should have happened but did not if user_wants_compile @@ -108,14 +88,35 @@ fn main() -> std::io::Result<()> { _ => (passed, failed), }); if passed + failed > 0 { - println!("Compiled {} ASL files: {} passed, {} failed.", passed + failed, passed, failed); + println!( + "Compiled {} ASL files: {}{} passed{}, {}{} failed{}", + passed + failed, + termion::color::Fg(termion::color::Green), + passed, + termion::style::Reset, + termion::color::Fg(termion::color::Red), + failed, + termion::style::Reset + ); println!(); } } + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + enum TestResult { + /// The test passed. + Pass, + /// The test ASL failed compilation by `iasl`. + CompileFail, + /// Our interpreter failed to parse the resulting AML. + ParseFail, + // TODO: should we do this?? + NotCompiled, + } + // Make a list of the files we have processed, and skip them if we see them again let mut dedup_list: HashSet = HashSet::new(); - let summaries: RefCell> = RefCell::new(HashSet::new()); + let mut summaries: HashSet<(PathBuf, TestResult)> = HashSet::new(); // Filter down to the final list of AML files let aml_files = compiled_files .iter() @@ -124,11 +125,11 @@ fn main() -> std::io::Result<()> { CompilationOutcome::Newer(path) => Some(path.clone()), CompilationOutcome::Succeeded(path) => Some(path.clone()), CompilationOutcome::Failed(path) => { - summaries.borrow_mut().insert((path.clone(), "COMPILE FAILED")); + summaries.insert((path.clone(), TestResult::CompileFail)); None } CompilationOutcome::NotCompiled(path) => { - summaries.borrow_mut().insert((path.clone(), "NotCompiled")); + summaries.insert((path.clone(), TestResult::NotCompiled)); None } CompilationOutcome::Ignored => None, @@ -140,37 +141,43 @@ fn main() -> std::io::Result<()> { dedup_list.insert(path.clone()); true } - }); + }) + .collect::>(); + + let combined_test = matches.get_flag("combined"); - let user_wants_reset = matches.get_flag("reset"); - let mut context = AmlContext::new(Box::new(Handler), DebugVerbosity::None); + let mut interpreter = Interpreter::new(Handler, 2); - let (passed, failed) = aml_files.fold((0, 0), |(passed, failed), file_entry| { + let (passed, failed) = aml_files.into_iter().fold((0, 0), |(passed, failed), file_entry| { print!("Testing AML file: {:?}... ", file_entry); std::io::stdout().flush().unwrap(); - let mut file = File::open(&file_entry).unwrap(); + let Ok(mut file) = File::open(&file_entry) else { + summaries.insert((file_entry, TestResult::CompileFail)); + return (passed, failed + 1); + }; let mut contents = Vec::new(); file.read_to_end(&mut contents).unwrap(); - const AML_TABLE_HEADER_LENGTH: usize = 36; - - if user_wants_reset { - context = AmlContext::new(Box::new(Handler), DebugVerbosity::None); + if !combined_test { + interpreter = Interpreter::new(Handler, 2); } - match context.parse_table(&contents[AML_TABLE_HEADER_LENGTH..]) { + const AML_TABLE_HEADER_LENGTH: usize = 36; + let stream = &contents[AML_TABLE_HEADER_LENGTH..]; + + match run_test(stream, &mut interpreter) { Ok(()) => { println!("{}OK{}", termion::color::Fg(termion::color::Green), termion::style::Reset); - println!("Namespace: {:#?}", context.namespace); - summaries.borrow_mut().insert((file_entry, "PASS")); + println!("Namespace: {}", interpreter.namespace.lock()); + summaries.insert((file_entry, TestResult::Pass)); (passed + 1, failed) } Err(err) => { println!("{}Failed ({:?}){}", termion::color::Fg(termion::color::Red), err, termion::style::Reset); - println!("Namespace: {:#?}", context.namespace); - summaries.borrow_mut().insert((file_entry, "PARSE FAIL")); + println!("Namespace: {}", interpreter.namespace.lock()); + summaries.insert((file_entry, TestResult::ParseFail)); (passed, failed + 1) } } @@ -178,23 +185,89 @@ fn main() -> std::io::Result<()> { // Print summaries println!("Summary:"); - for (file, status) in summaries.borrow().iter() { - println!("{:<50}: {}", file.to_str().unwrap(), status); - } - println!("\nTest results:\n\tpassed:{}\n\tfailed:{}", passed, failed); + for (file, status) in summaries.iter() { + let status = match status { + TestResult::Pass => { + format!("{}OK{}", termion::color::Fg(termion::color::Green), termion::style::Reset) + } + TestResult::CompileFail => { + format!("{}COMPILE FAIL{}", termion::color::Fg(termion::color::Red), termion::style::Reset) + } + TestResult::ParseFail => { + format!("{}PARSE FAIL{}", termion::color::Fg(termion::color::Red), termion::style::Reset) + } + TestResult::NotCompiled => { + format!("{}NOT COMPILED{}", termion::color::Fg(termion::color::Red), termion::style::Reset) + } + }; + println!("\t{:<50}: {}", file.to_str().unwrap(), status); + } + println!( + "\nTest results: {}{} passed{}, {}{} failed{}", + termion::color::Fg(termion::color::Green), + passed, + termion::style::Reset, + termion::color::Fg(termion::color::Red), + failed, + termion::style::Reset + ); Ok(()) } +fn run_test(stream: &[u8], interpreter: &mut Interpreter) -> Result<(), AmlError> { + interpreter.load_table(stream)?; + + match interpreter.invoke_method(AmlName::from_str("\\MAIN").unwrap(), vec![]) { + Ok(_) => Ok(()), + Err(AmlError::ObjectDoesNotExist(name)) => { + if name == AmlName::from_str("\\MAIN").unwrap() { + Ok(()) + } else { + Err(AmlError::ObjectDoesNotExist(name)) + } + } + Err(other) => Err(other), + } +} + +fn find_tests(matches: &clap::ArgMatches) -> std::io::Result> { + // Get an initial list of files - may not work correctly on non-UTF8 OsString + let files: Vec = if matches.contains_id("path") { + let dir_path = Path::new(matches.get_one::("path").unwrap()); + + if std::fs::metadata(&dir_path).unwrap().is_dir() { + println!("Running tests in directory: {:?}", dir_path); + fs::read_dir(dir_path)? + .filter_map(|entry| if entry.is_ok() { Some(entry.unwrap().path().to_path_buf()) } else { None }) + .collect() + } else { + println!("Running single test: {:?}", dir_path); + vec![dir_path.to_path_buf()] + } + } else { + matches.get_many::("files").unwrap_or_default().cloned().collect() + }; + + // Make sure all files exist, propagate error if it occurs + files.iter().fold(Ok(()), |result: std::io::Result<()>, path| { + if !path.is_file() { + println!("Not a regular file: {}", path.display()); + path.metadata()?; + } + result + })?; + + Ok(files) +} + /// Determine what to do with this file - ignore, compile and parse, or just parse. /// If ".aml" does not exist, or if ".asl" is newer, compiles the file. /// If the ".aml" file is newer, indicate it is ready to parse. -fn resolve_and_compile(name: &str, can_compile: bool) -> std::io::Result { - let path = PathBuf::from(name); - +fn resolve_and_compile(path: &PathBuf, can_compile: bool) -> std::io::Result { // If this file is aml and it exists, it's ready for parsing // metadata() will error if the file does not exist if path.extension() == Some(OsStr::new("aml")) && path.metadata()?.is_file() { - return Ok(CompilationOutcome::IsAml(path)); + return Ok(CompilationOutcome::IsAml(path.clone())); } // If this file is not asl, it's not interesting. Error if the file does not exist. @@ -215,20 +288,20 @@ fn resolve_and_compile(name: &str, can_compile: bool) -> std::io::Result u8 { println!("read_u8 {address:#x}"); 0 @@ -270,16 +343,16 @@ impl aml::Handler for Handler { 0 } - fn write_u8(&mut self, address: usize, value: u8) { + fn write_u8(&self, address: usize, value: u8) { println!("write_u8 {address:#x}<-{value:#x}"); } - fn write_u16(&mut self, address: usize, value: u16) { + fn write_u16(&self, address: usize, value: u16) { println!("write_u16 {address:#x}<-{value:#x}"); } - fn write_u32(&mut self, address: usize, value: u32) { + fn write_u32(&self, address: usize, value: u32) { println!("write_u32 {address:#x}<-{value:#x}"); } - fn write_u64(&mut self, address: usize, value: u64) { + fn write_u64(&self, address: usize, value: u64) { println!("write_u64 {address:#x}<-{value:#x}"); } @@ -306,27 +379,35 @@ impl aml::Handler for Handler { println!("write_io_u32 {port:#x}<-{value:#x}"); } - fn read_pci_u8(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16) -> u8 { - println!("read_pci_u8 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})"); + fn read_pci_u8(&self, address: PciAddress, _offset: u16) -> u8 { + println!("read_pci_u8 ({address})"); 0 } - fn read_pci_u16(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16) -> u16 { - println!("read_pci_u16 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})"); + fn read_pci_u16(&self, address: PciAddress, _offset: u16) -> u16 { + println!("read_pci_u16 ({address})"); 0 } - fn read_pci_u32(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16) -> u32 { - println!("read_pci_32 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})"); + fn read_pci_u32(&self, address: PciAddress, _offset: u16) -> u32 { + println!("read_pci_u32 ({address})"); 0 } - fn write_pci_u8(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16, value: u8) { - println!("write_pci_u8 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})<-{value:#x}"); + fn write_pci_u8(&self, address: PciAddress, _offset: u16, value: u8) { + println!("write_pci_u8 ({address})<-{value}"); + } + fn write_pci_u16(&self, address: PciAddress, _offset: u16, value: u16) { + println!("write_pci_u16 ({address})<-{value}"); } - fn write_pci_u16(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16, value: u16) { - println!("write_pci_u16 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})<-{value:#x}"); + fn write_pci_u32(&self, address: PciAddress, _offset: u16, value: u32) { + println!("write_pci_u32 ({address})<-{value}"); } - fn write_pci_u32(&self, segment: u16, bus: u8, device: u8, function: u8, _offset: u16, value: u32) { - println!("write_pci_u32 ({segment:#x}, {bus:#x}, {device:#x}, {function:#x})<-{value:#x}"); + + fn handle_debug(&self, object: &acpi::aml::object::Object) { + info!("Debug store: {:?}", object); + } + + fn nanos_since_boot(&self) -> u64 { + 0 } fn stall(&self, microseconds: u64) { @@ -335,4 +416,12 @@ impl aml::Handler for Handler { fn sleep(&self, milliseconds: u64) { println!("Sleeping for {}ms", milliseconds); } + + fn create_mutex(&self) -> Handle { + Handle(0) + } + fn acquire(&self, _mutex: Handle, _timeout: u16) -> Result<(), AmlError> { + Ok(()) + } + fn release(&self, _mutex: Handle) {} }