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<T> = core::result::Result<T, AcpiError>;
-
-/// All types representing ACPI tables should implement this trait.
-///
-/// ### Safety
-///
-/// The table's memory is naively interpreted, so you must be careful in providing a type that
-/// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
-/// be the size specified in the SDT's header. Providing a table impl that is larger than this, *may* lead to
-/// page-faults, aliasing references, or derefencing uninitialized memory (the latter two being UB).
-/// This isn't forbidden, however, because some tables rely on the impl being larger than a provided SDT in some
-/// versions of ACPI (the [`ExtendedField`](crate::sdt::ExtendedField) type will be useful if you need to do
-/// this. See our [`Fadt`](crate::fadt::Fadt) type for an example of this).
-pub unsafe trait AcpiTable {
-    const SIGNATURE: Signature;
-
-    fn header(&self) -> &sdt::SdtHeader;
-
-    fn validate(&self) -> AcpiResult<()> {
-        self.header().validate(Self::SIGNATURE)
-    }
-}
-
-/// Error type used by functions that return an `AcpiResult<T>`.
-#[derive(Debug)]
-pub enum AcpiError {
-    NoValidRsdp,
-    RsdpIncorrectSignature,
-    RsdpInvalidOemId,
-    RsdpInvalidChecksum,
-
-    SdtInvalidSignature(Signature),
-    SdtInvalidOemId(Signature),
-    SdtInvalidTableId(Signature),
-    SdtInvalidChecksum(Signature),
-
-    TableMissing(Signature),
-    InvalidFacsAddress,
-    InvalidDsdtAddress,
-    InvalidMadt(MadtError),
-    InvalidGenericAddress,
-
-    AllocError,
-}
-
-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::<SdtHeader>(),
-                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<H: AcpiHandler> {
-    mapping: PhysicalMapping<H, SdtHeader>,
-    revision: u8,
-    handler: H,
-}
-
-impl<H> AcpiTables<H>
-where
-    H: AcpiHandler,
-{
-    /// Create an `AcpiTables` if you have the physical address of the RSDP.
-    ///
-    /// ### Safety
-    ///
-    /// Caller must ensure the provided address is valid to read as an RSDP.
-    pub unsafe fn from_rsdp(handler: H, address: usize) -> AcpiResult<Self> {
-        let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
-        rsdp_mapping.validate()?;
-
-        // Safety: RSDP has been validated.
-        unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) }
-    }
-
-    /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not
-    /// work on UEFI platforms. See [`Rsdp::search_for_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<Self> {
-        let rsdp_mapping = unsafe { Rsdp::search_for_on_bios(handler.clone())? };
-        // Safety: RSDP has been validated from `Rsdp::search_for_on_bios`
-        unsafe { Self::from_validated_rsdp(handler, rsdp_mapping) }
-    }
-
-    /// Create an `AcpiTables` if you have a `PhysicalMapping` of the RSDP that you know is correct. This is called
-    /// from `from_rsdp` after validation, but can also be used if you've searched for the RSDP manually on a BIOS
-    /// system.
-    ///
-    /// ### Safety
-    ///
-    /// Caller must ensure that the provided mapping is a fully validated RSDP.
-    pub unsafe fn from_validated_rsdp(handler: H, rsdp_mapping: PhysicalMapping<H, Rsdp>) -> AcpiResult<Self> {
-        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<Self> {
-        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::<u8>() };
-        let ptrs_bytes_len = self.mapping.region_length() - mem::size_of::<SdtHeader>();
-        // SAFETY: `ptrs_virt_start` points to an array of `ptrs_bytes_len` bytes that lives as long as `self`.
-        let ptrs_bytes = unsafe { core::slice::from_raw_parts(ptrs_virt_start, ptrs_bytes_len) };
-        let ptr_size = if self.revision == 0 {
-            4 // RSDT entry size
-        } else {
-            8 // XSDT entry size
-        };
-
-        ptrs_bytes.chunks(ptr_size).map(|ptr_bytes_src| {
-            // Construct a native pointer using as many bytes as required from `ptr_bytes_src` (note that ACPI is
-            // little-endian)
-
-            let mut ptr_bytes_dst = [0; mem::size_of::<usize>()];
-            let common_ptr_size = usize::min(mem::size_of::<usize>(), ptr_bytes_src.len());
-            ptr_bytes_dst[..common_ptr_size].copy_from_slice(&ptr_bytes_src[..common_ptr_size]);
-
-            usize::from_le_bytes(ptr_bytes_dst) as *const SdtHeader
-        })
-    }
-
-    /// Searches through the ACPI table headers and attempts to locate the table with a matching `T::SIGNATURE`.
-    pub fn find_table<T: AcpiTable>(&self) -> AcpiResult<PhysicalMapping<H, T>> {
-        self.tables_phys_ptrs()
-            .find_map(|table_phys_ptr| {
-                // SAFETY: Table guarantees its contained addresses to be valid.
-                match unsafe { read_table(self.handler.clone(), table_phys_ptr as usize) } {
-                    Ok(table_mapping) => Some(table_mapping),
-                    Err(AcpiError::SdtInvalidSignature(_)) => None,
-                    Err(e) => {
-                        log::warn!(
-                            "Found invalid {} table at physical address {:p}: {:?}",
-                            T::SIGNATURE,
-                            table_phys_ptr,
-                            e
-                        );
-
-                        None
-                    }
-                }
-            })
-            .ok_or(AcpiError::TableMissing(T::SIGNATURE))
-    }
-
-    /// 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<AmlTable> {
-        self.find_table::<fadt::Fadt>().and_then(|fadt| {
-            #[repr(transparent)]
-            struct Dsdt {
-                header: SdtHeader,
-            }
-
-            // Safety: Implementation properly represents a valid DSDT.
-            unsafe impl AcpiTable for Dsdt {
-                const SIGNATURE: Signature = Signature::DSDT;
-
-                fn header(&self) -> &SdtHeader {
-                    &self.header
-                }
-            }
-
-            let dsdt_address = fadt.dsdt_address()?;
-            let dsdt = unsafe { read_table::<H, Dsdt>(self.handler.clone(), dsdt_address)? };
-
-            Ok(AmlTable::new(dsdt_address, dsdt.header().length))
-        })
-    }
-
-    /// Iterates through all of the SSDT tables.
-    pub fn ssdts(&self) -> SsdtIterator<H> {
-        SsdtIterator { tables_phys_ptrs: self.tables_phys_ptrs(), handler: self.handler.clone() }
-    }
-
-    /// Convenience method for contructing a [`PlatformInfo`]. 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<alloc::alloc::Global>> {
-        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<A>(&self, allocator: A) -> AcpiResult<PlatformInfo<A>>
-    where
-        A: core::alloc::Allocator + Clone,
-    {
-        PlatformInfo::new_in(self, allocator)
-    }
-}
-
-#[derive(Debug)]
-pub struct Sdt {
-    /// Physical address of the start of the SDT, including the header.
-    pub physical_address: usize,
-    /// Length of the table in bytes.
-    pub length: u32,
-    /// Whether this SDT has been validated. This is set to `true` the first time it is mapped and validated.
-    pub validated: bool,
-}
-
-/// An iterator over the physical table addresses in an RSDT or XSDT.
-type TablesPhysPtrsIter<'t> = core::iter::Map<core::slice::Chunks<'t, u8>, fn(&[u8]) -> *const SdtHeader>;
-
-#[derive(Debug)]
-pub struct AmlTable {
-    /// Physical address of the start of the AML stream (excluding the table header).
-    pub address: usize,
-    /// Length (in bytes) of the AML stream.
-    pub length: u32,
-}
-
-impl AmlTable {
-    /// Create an `AmlTable` from the address and length of the table **including the SDT header**.
-    pub(crate) fn new(address: usize, length: u32) -> AmlTable {
-        AmlTable {
-            address: address + mem::size_of::<SdtHeader>(),
-            length: length - mem::size_of::<SdtHeader>() as u32,
-        }
-    }
-}
-
-/// ### Safety
-///
-/// Caller must ensure the provided address is valid for being read as an `SdtHeader`.
-unsafe fn read_table<H: AcpiHandler, T: AcpiTable>(
-    handler: H,
-    address: usize,
-) -> AcpiResult<PhysicalMapping<H, T>> {
-    // Attempt to peek at the SDT header to correctly enumerate the entire table.
-
-    // SAFETY: `address` needs to be valid for the size of `SdtHeader`, or the ACPI tables are malformed (not a
-    // software issue).
-    let header_mapping = unsafe { handler.map_physical_region::<SdtHeader>(address, mem::size_of::<SdtHeader>()) };
-
-    SdtHeader::validate_lazy(header_mapping, handler)
-}
-
-/// Iterator that steps through all of the tables, and returns only the SSDTs as `AmlTable`s.
-pub struct SsdtIterator<'t, H>
-where
-    H: AcpiHandler,
-{
-    tables_phys_ptrs: TablesPhysPtrsIter<'t>,
-    handler: H,
-}
-
-impl<H> Iterator for SsdtIterator<'_, H>
-where
-    H: AcpiHandler,
-{
-    type Item = AmlTable;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        #[repr(transparent)]
-        struct Ssdt {
-            header: SdtHeader,
-        }
-
-        // SAFETY: Implementation properly represents a valid SSDT.
-        unsafe impl AcpiTable for Ssdt {
-            const SIGNATURE: Signature = Signature::SSDT;
-
-            fn header(&self) -> &SdtHeader {
-                &self.header
-            }
-        }
-
-        // Borrow single field for closure to avoid immutable reference to `self` that inhibits `find_map`
-        let handler = &self.handler;
-
-        // Consume iterator until next valid SSDT and return the latter
-        self.tables_phys_ptrs.find_map(|table_phys_ptr| {
-            // SAFETY: Table guarantees its contained addresses to be valid.
-            match unsafe { read_table::<_, Ssdt>(handler.clone(), table_phys_ptr as usize) } {
-                Ok(ssdt_mapping) => Some(AmlTable::new(ssdt_mapping.physical_start(), ssdt_mapping.header.length)),
-                Err(AcpiError::SdtInvalidSignature(_)) => None,
-                Err(e) => {
-                    log::warn!("Found invalid SSDT at physical address {:p}: {:?}", table_phys_ptr, e);
-
-                    None
-                }
-            }
-        })
-    }
-}
-
-pub struct SdtHeaderIterator<'t, H>
-where
-    H: AcpiHandler,
-{
-    tables_phys_ptrs: TablesPhysPtrsIter<'t>,
-    handler: H,
-}
-
-impl<H> Iterator for SdtHeaderIterator<'_, H>
-where
-    H: AcpiHandler,
-{
-    type Item = SdtHeader;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        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::<SdtHeader>(table_phys_ptr as usize, mem::size_of::<SdtHeader>())
-            };
-            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<T, A> 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<Self> {
-        let layout = Layout::array::<T>(len).map_err(|_| AcpiError::AllocError)?;
-        match allocator.allocate(layout) {
-            Ok(mut ptr) => {
-                let slice = unsafe { core::slice::from_raw_parts_mut(ptr.as_mut().as_mut_ptr().cast(), len) };
-                Ok(ManagedSlice { slice, allocator })
-            }
-            Err(_) => Err(AcpiError::AllocError),
-        }
-    }
-}
-
-#[cfg(feature = "alloc")]
-impl<T> ManagedSlice<'_, T, alloc::alloc::Global> {
-    pub fn new(len: usize) -> AcpiResult<Self> {
-        Self::new_in(len, alloc::alloc::Global)
-    }
-}
-
-impl<T, A> 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::<u8>());
-            let slice_layout =
-                Layout::from_size_align_unchecked(mem::size_of_val(self.slice), mem::align_of_val(self.slice));
-            self.allocator.deallocate(slice_ptr, slice_layout);
-        }
-    }
-}
-
-impl<T, A> core::ops::Deref for ManagedSlice<'_, T, A>
-where
-    A: Allocator,
-{
-    type Target = [T];
-
-    fn deref(&self) -> &Self::Target {
-        self.slice
-    }
-}
-
-impl<T, A> core::ops::DerefMut for ManagedSlice<'_, T, A>
-where
-    A: Allocator,
-{
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.slice
-    }
-}
-
-impl<T: Clone, A: Allocator + Clone> 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<H>(tables: &crate::AcpiTables<H>) -> crate::AcpiResult<PciConfigRegions<'a, alloc::alloc::Global>>
-    where
-        H: crate::AcpiHandler,
-    {
-        Self::new_in(tables, alloc::alloc::Global)
-    }
-}
-
-#[cfg(feature = "allocator_api")]
-impl<'a, A> PciConfigRegions<'a, A>
-where
-    A: core::alloc::Allocator,
-{
-    pub fn new_in<H>(tables: &crate::AcpiTables<H>, allocator: A) -> crate::AcpiResult<PciConfigRegions<'a, A>>
-    where
-        H: crate::AcpiHandler,
-    {
-        let mcfg = tables.find_table::<Mcfg>()?;
-        let mcfg_entries = mcfg.entries();
-
-        let mut regions = crate::ManagedSlice::new_in(mcfg_entries.len(), allocator)?;
-        regions.copy_from_slice(mcfg_entries);
-
-        Ok(Self { regions })
-    }
-
-    /// Get the physical address of the start of the configuration space for a given PCIe device
-    /// function. Returns `None` if there isn't an entry in the MCFG that manages that device.
-    pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> {
-        // First, find the memory region that handles this segment and bus. This method is fine
-        // because there should only be one region that handles each segment group + bus
-        // combination.
-        let region = self.regions.iter().find(|region| {
-            region.pci_segment_group == segment_group_no
-                && (region.bus_number_start..=region.bus_number_end).contains(&bus)
-        })?;
-
-        Some(
-            region.base_address
-                + ((u64::from(bus - region.bus_number_start) << 20)
-                    | (u64::from(device) << 15)
-                    | (u64::from(function) << 12)),
-        )
-    }
-
-    /// Returns an iterator providing information about the system's present PCI busses.
-    /// This is roughly equivalent to manually iterating the system's MCFG table.
-    pub fn iter(&self) -> PciConfigEntryIterator {
-        PciConfigEntryIterator { entries: &self.regions, index: 0 }
-    }
-}
-
-/// Configuration entry describing a valid bus range for the given PCI segment group.
-pub struct PciConfigEntry {
-    pub segment_group: u16,
-    pub bus_range: core::ops::RangeInclusive<u8>,
-    pub physical_address: usize,
-}
-
-/// Iterator providing a [`PciConfigEntry`] for all of the valid bus ranges on the system.
-pub struct PciConfigEntryIterator<'a> {
-    entries: &'a [McfgEntry],
-    index: usize,
-}
-
-impl Iterator for PciConfigEntryIterator<'_> {
-    type Item = PciConfigEntry;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let entry = self.entries.get(self.index)?;
-        self.index += 1;
-
-        Some(PciConfigEntry {
-            segment_group: entry.pci_segment_group,
-            bus_range: entry.bus_number_start..=entry.bus_number_end,
-            physical_address: entry.base_address as usize,
-        })
-    }
-}
-
-#[repr(C, packed)]
-pub struct Mcfg {
-    header: SdtHeader,
-    _reserved: u64,
-    // Followed by `n` entries with format `McfgEntry`
-}
-
-/// ### Safety: Implementation properly represents a valid MCFG.
-unsafe impl AcpiTable for Mcfg {
-    const SIGNATURE: Signature = Signature::MCFG;
-
-    fn header(&self) -> &SdtHeader {
-        &self.header
-    }
-}
-
-impl Mcfg {
-    /// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should
-    /// be enumerated instead.
-    pub fn entries(&self) -> &[McfgEntry] {
-        let length = self.header.length as usize - mem::size_of::<Mcfg>();
-
-        // Intentionally round down in case length isn't an exact multiple of McfgEntry size
-        // (see rust-osdev/acpi#58)
-        let num_entries = length / mem::size_of::<McfgEntry>();
-
-        unsafe {
-            let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
-            slice::from_raw_parts(pointer, num_entries)
-        }
-    }
-}
-
-impl core::fmt::Debug for Mcfg {
-    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish()
-    }
-}
-
-#[derive(Clone, Copy, Debug)]
-#[repr(C, packed)]
-pub struct McfgEntry {
-    pub base_address: u64,
-    pub pci_segment_group: u16,
-    pub bus_number_start: u8,
-    pub bus_number_end: u8,
-    _reserved: u32,
-}
diff --git a/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::<u64>() * 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<u8> = 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 <SimpleName | DebugObj | DefRefOf | DefDerefOf | DefIndex>
-     *
-     * 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 PackageElementList>
-     * 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::<u64>()
-                    }
-                    .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<AmlValue>; 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<AmlValue>` is not `Copy`, so it can't be used to initialize an
-        // array, but consts can :(
-        const NONE_BUT_CONST: Option<AmlValue> = 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<dyn Handler>,
-
-    pub namespace: Namespace,
-    method_context: Option<MethodContext>,
-
-    /*
-     * 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<dyn Handler>, 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<AmlValue, AmlError> {
-        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<AmlValue, AmlError> {
-        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<T: Send + Sync>() {}
-        test_send_sync::<AmlContext>();
-    }
-}
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 := <RootChar('\') NamePath> | <PrefixPath NamePath>
-     * 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<NameComponent>>
-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<NameComponent>>
-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<NameComponent>>
-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<NameComponent>>
-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<NameSeg, AmlError> {
-        // 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 := <LeadNameChar NameChar NameChar NameChar>
-     */
-    // 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<NameSeg, NamespaceLevel>,
-    pub values: BTreeMap<NameSeg, AmlHandle>,
-}
-
-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<AmlHandle, AmlValue>,
-
-    /// 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<AmlHandle, AmlError> {
-        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<AmlHandle, AmlError> {
-        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<AmlHandle, AmlError> {
-        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<AmlHandle, AmlError> {
-        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<AmlName, AmlError> {
-        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<bool, AmlError>` -
-    /// 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<F>(&mut self, mut f: F) -> Result<(), AmlError>
-    where
-        F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
-    {
-        fn traverse_level<F>(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError>
-        where
-            F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
-        {
-            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<Self, Self::Err> {
-        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<NameComponent>);
-
-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<NameComponent>) -> 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<AmlName, AmlError> {
-        /*
-         * 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<AmlName, AmlError> {
-        // 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<AmlName, AmlError> {
-        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<NameSeg> {
-        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<AmlName>,
-}
-
-impl OpRegion {
-    pub fn new(region: RegionSpace, base: u64, length: u64, parent_device: Option<AmlName>) -> 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<AmlValue, AmlError> {
-        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<u64, AmlError> {
-        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<AmlError> 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<F, A>(self, map_fn: F) -> Map<'a, 'c, Self, F, R, A>
-    where
-        F: Fn(R) -> Result<A, Propagate>,
-    {
-        Map { parser: self, map_fn, _phantom: PhantomData }
-    }
-
-    fn map_with_context<F, A>(self, map_fn: F) -> MapWithContext<'a, 'c, Self, F, R, A>
-    where
-        F: Fn(R, &'c mut AmlContext) -> (Result<A, Propagate>, &'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<OtherParser>(self, other: OtherParser) -> Or<'a, 'c, Self, OtherParser, R>
-    where
-        OtherParser: Parser<'a, 'c, R>,
-    {
-        Or { p1: self, p2: other, _phantom: PhantomData }
-    }
-
-    fn then<NextParser, NextR>(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<F, P2, R2>(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<R>>
-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<A, Propagate>,
-{
-    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<A, Propagate>,
-{
-    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<A, Propagate>, &'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<A, Propagate>, &'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<R, AmlError>, &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<PkgLength, AmlError> {
-        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<RegionPkgLength, AmlError> {
-        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 |
-     * <PkgLeadByte ByteData> |
-     * <PkgLeadByte ByteData ByteData> |
-     * <PkgLeadByte ByteData ByteData ByteData>
-     *
-     * 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 | <TermObj TermList>
-     */
-    // 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<AmlValue>>
-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<spinning_top::Spinlock<Vec<u8>>> =
-                        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<spinning_top::Spinlock<Vec<u8>>> =
-                        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<spinning_top::Spinlock<Vec<u8>>> =
-                        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<spinning_top::Spinlock<Vec<u8>>> =
-                        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<spinning_top::Spinlock<Vec<u8>>> =
-                        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<spinning_top::Spinlock<Vec<u8>>> =
-                        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(&region_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 | <FieldElement FieldList>
-                     */
-                    // 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<FieldAccessType, AmlError> {
-        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<FieldUpdateRule, AmlError> {
-        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<dyn Fn(&mut AmlContext) -> Result<AmlValue, AmlError> + Send + Sync>;
-
-#[derive(Clone)]
-pub enum MethodCode {
-    Aml(Vec<u8>),
-    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<Spinlock<Vec<u8>>>),
-    BufferField {
-        buffer_data: Arc<Spinlock<Vec<u8>>>,
-        /// 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<AmlValue>),
-    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<F>(arg_count: u8, serialize: bool, sync_level: u8, f: F) -> AmlValue
-    where
-        F: (Fn(&mut AmlContext) -> Result<AmlValue, AmlError>) + '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<u64, AmlError> {
-        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<bool, AmlError> {
-        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<u64, AmlError> {
-        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<Arc<Spinlock<Vec<u8>>>, 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<String, AmlError> {
-        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<StatusObject, AmlError> {
-        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<AmlValue, AmlError> {
-        // 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<AmlValue, AmlError> {
-        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<AmlValue, AmlError> {
-        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::<bitvec::order::Lsb0>();
-            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::<bitvec::order::Lsb0>()[0..32].clone_from_bitslice(bits);
-                upper.view_bits_mut::<bitvec::order::Lsb0>()[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::<bitvec::order::Lsb0>()[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::<bitvec::order::Lsb0>();
-
-            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<cmp::Ordering, AmlError> {
-        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<AmlValue>; 7]);
-
-impl Args {
-    pub const EMPTY: Self = Self([None, None, None, None, None, None, None]);
-
-    pub fn from_list(list: Vec<AmlValue>) -> Result<Args, AmlError> {
-        if list.len() > 7 {
-            return Err(AmlError::TooManyArgs);
-        }
-
-        let mut args: Vec<Option<AmlValue>> = 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::<T>()`
-/// bytes, but may be bigger.
-///
-/// See `PhysicalMapping::new` for the meaning of each field.
-#[derive(Debug)]
-pub struct PhysicalMapping<H, T>
-where
-    H: AcpiHandler,
-{
-    physical_start: usize,
-    virtual_start: NonNull<T>,
-    region_length: usize, // Can be equal or larger than size_of::<T>()
-    mapped_length: usize, // Differs from `region_length` if padding is added for alignment
-    handler: H,
-}
-
-impl<H, T> PhysicalMapping<H, T>
-where
-    H: AcpiHandler,
-{
-    /// Construct a new `PhysicalMapping`.
-    ///
-    /// - `physical_start` should be the physical address of the structure to be mapped.
-    /// - `virtual_start` should be the corresponding virtual address of that structure. It may differ from the
-    ///   start of the region mapped due to requirements of the paging system. It must be a valid, non-null
-    ///   pointer.
-    /// - `region_length` should be the number of bytes requested to be mapped. It must be equal to or larger than
-    ///   `size_of::<T>()`.
-    /// - `mapped_length` should be the number of bytes mapped to fulfill the request. It may be equal to or larger
-    ///   than `region_length`, due to requirements of the paging system or other reasoning.
-    /// - `handler` should be the same `AcpiHandler` that created the mapping. When the `PhysicalMapping` is
-    ///   dropped, it will be used to unmap the structure.
-    pub unsafe fn new(
-        physical_start: usize,
-        virtual_start: NonNull<T>,
-        region_length: usize,
-        mapped_length: usize,
-        handler: H,
-    ) -> Self {
-        Self { physical_start, virtual_start, region_length, mapped_length, handler }
-    }
-
-    pub fn physical_start(&self) -> usize {
-        self.physical_start
-    }
-
-    pub fn virtual_start(&self) -> NonNull<T> {
-        self.virtual_start
-    }
-
-    pub fn region_length(&self) -> usize {
-        self.region_length
-    }
-
-    pub fn mapped_length(&self) -> usize {
-        self.mapped_length
-    }
-
-    pub fn handler(&self) -> &H {
-        &self.handler
-    }
-}
-
-unsafe impl<H: AcpiHandler + Send, T: Send> Send for PhysicalMapping<H, T> {}
-
-impl<H, T> Deref for PhysicalMapping<H, T>
-where
-    H: AcpiHandler,
-{
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        unsafe { self.virtual_start.as_ref() }
-    }
-}
-
-impl<H, T> Drop for PhysicalMapping<H, T>
-where
-    H: AcpiHandler,
-{
-    fn drop(&mut self) {
-        H::unmap_physical_region(self)
-    }
-}
-
-/// An implementation of this trait must be provided to allow `acpi` to access platform-specific
-/// functionality, such as mapping regions of physical memory. You are free to implement these
-/// however you please, as long as they conform to the documentation of each function. The handler is stored in
-/// every `PhysicalMapping` so it's able to unmap itself when dropped, so this type needs to be something you can
-/// clone/move about freely (e.g. a reference, wrapper over `Rc`, marker struct, etc.).
-pub trait AcpiHandler: Clone {
-    /// Given a physical address and a size, map a region of physical memory that contains `T` (note: the passed
-    /// size may be larger than `size_of::<T>()`). The address is not neccessarily page-aligned, so the
-    /// implementation may need to map more than `size` bytes. The virtual address the region is mapped to does not
-    /// matter, as long as it is accessible to `acpi`.
-    ///
-    /// See the documentation on `PhysicalMapping::new` for an explanation of each field on the `PhysicalMapping`
-    /// return type.
-    ///
-    /// ## Safety
-    ///
-    /// - `physical_address` must point to a valid `T` in physical memory.
-    /// - `size` must be at least `size_of::<T>()`.
-    unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T>;
-
-    /// Unmap the given physical mapping. This is called when a `PhysicalMapping` is dropped, you should **not** manually call this.
-    ///
-    /// Note: A reference to the handler used to construct `region` can be acquired by calling [`PhysicalMapping::handler`].
-    fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>);
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[allow(dead_code)]
-    fn test_send_sync() {
-        // verify that PhysicalMapping implements Send and Sync
-        fn test_send_sync<T: Send>() {}
-        fn caller<H: AcpiHandler + Send, T: Send>() {
-            test_send_sync::<PhysicalMapping<H, T>>();
-        }
-    }
-}
diff --git a/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>() - 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<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, 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::<u8>(area.start, area.end - area.start + RSDP_V2_EXT_LENGTH)
-            };
-
-            let extended_area_bytes =
-                unsafe { slice::from_raw_parts(mapping.virtual_start().as_ptr(), mapping.region_length()) };
-
-            // Search `Rsdp`-sized windows at 16-byte boundaries relative to the base of the area (which is also
-            // aligned to 16 bytes due to the implementation of `find_search_areas`)
-            extended_area_bytes.windows(mem::size_of::<Rsdp>()).step_by(16).find_map(|maybe_rsdp_bytes_slice| {
-                let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::<Rsdp>();
-                let maybe_rsdp_phys_start = maybe_rsdp_virt_ptr as usize
-                    - mapping.virtual_start().as_ptr() as usize
-                    + mapping.physical_start();
-                // SAFETY: `maybe_rsdp_virt_ptr` points to an aligned, readable `Rsdp`-sized value, and the `Rsdp`
-                // struct's fields are always initialized.
-                let maybe_rsdp = unsafe { &*maybe_rsdp_virt_ptr };
-
-                match maybe_rsdp.validate() {
-                    Ok(()) => Some(maybe_rsdp_phys_start),
-                    Err(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::<Rsdp>(address, mem::size_of::<Rsdp>()) };
-                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<H>(handler: H) -> [Range<usize>; 2]
-where
-    H: AcpiHandler,
-{
-    /*
-     * Read the base address of the EBDA from its location in the BDA (BIOS Data Area). Not all BIOSs fill this out
-     * unfortunately, so we might not get a sensible result. We shift it left 4, as it's a segment address.
-     */
-    let ebda_start_mapping =
-        unsafe { handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>()) };
-    let ebda_start = (*ebda_start_mapping as usize) << 4;
-
-    [
-        /*
-         * The main BIOS area below 1MiB. In practice, from my [Restioson's] testing, the RSDP is more often here
-         * than the EBDA. We also don't want to search the entire possibele EBDA range, if we've failed to find it
-         * from the BDA.
-         */
-        RSDP_BIOS_AREA_START..(RSDP_BIOS_AREA_END + 1),
-        // Check if base segment ptr is in valid range for EBDA base
-        if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
-            // First KiB of EBDA
-            ebda_start..ebda_start + 1024
-        } else {
-            // We don't know where the EBDA starts, so just search the largest possible EBDA
-            EBDA_EARLIEST_START..(EBDA_END + 1)
-        },
-    ]
-}
-
-/// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4
-const EBDA_START_SEGMENT_PTR: usize = 0x40e;
-/// The earliest (lowest) memory address an EBDA (Extended Bios Data Area) can start
-const EBDA_EARLIEST_START: usize = 0x80000;
-/// The end of the EBDA (Extended Bios Data Area)
-const EBDA_END: usize = 0x9ffff;
-/// The start of the main BIOS area below 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<GenericAddress> {
+    pub fn from_raw(raw: RawGenericAddress) -> Result<GenericAddress, AcpiError> {
         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<H>
+where
+    H: Handler,
+{
+    handler: H,
+    pub namespace: Spinlock<Namespace>,
+    pub object_token: Spinlock<ObjectToken>,
+    context_stack: Spinlock<Vec<MethodContext>>,
+    dsdt_revision: u8,
+    region_handlers: Spinlock<BTreeMap<RegionSpace, Box<dyn RegionHandler>>>,
+}
+
+unsafe impl<H> Send for Interpreter<H> where H: Handler + Send {}
+unsafe impl<H> Sync for Interpreter<H> where H: Handler + Send {}
+
+/// The value returned by the `Revision` opcode.
+const INTERPRETER_REVISION: u64 = 1;
+
+impl<H> Interpreter<H>
+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<H> {
+        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<AH: AcpiHandler>(
+        acpi_handler: AH,
+        aml_handler: H,
+        tables: &AcpiTables<AH>,
+    ) -> Result<Interpreter<H>, AcpiError> {
+        fn load_table<H: Handler, AH: AcpiHandler>(
+            interpreter: &Interpreter<H>,
+            acpi_handler: &AH,
+            table: AmlTable,
+        ) -> Result<(), AcpiError> {
+            let mapping = unsafe {
+                acpi_handler.map_physical_region::<SdtHeader>(table.phys_address, table.length as usize)
+            };
+            let stream = unsafe {
+                slice::from_raw_parts(
+                    mapping.virtual_start().as_ptr().byte_add(mem::size_of::<SdtHeader>()) as *const u8,
+                    table.length as usize - mem::size_of::<SdtHeader>(),
+                )
+            };
+            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<WrappedObject>) -> Result<WrappedObject, AmlError> {
+        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<WrappedObject>,
+    ) -> Result<Option<WrappedObject>, 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<RH>(&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<WrappedObject, AmlError> {
+        /*
+         * 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<WrappedObject, AmlError> {
+        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<u8>),
+        }
+        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<u64, AmlError> {
+        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(&region.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(&region.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(&region.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(&region.space) {
+                    todo!("Utilise handler");
+                } else {
+                    Err(AmlError::NoHandlerForRegionAccess(region.space))
+                }
+            }
+        }
+    }
+
+    fn pci_address_for_device(&self, path: &AmlName) -> Result<PciAddress, AmlError> {
+        /*
+         * 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<Block>,
+    in_flight: Vec<OpInFlight>,
+    args: [WrappedObject; 8],
+    locals: [WrappedObject; 8],
+    current_scope: AmlName,
+
+    _method: Option<WrappedObject>,
+}
+
+#[derive(Debug)]
+struct OpInFlight {
+    op: Opcode,
+    expected_arguments: usize,
+    arguments: Vec<Argument>,
+}
+
+#[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<Argument>, 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<WrappedObject>,
+        scope: AmlName,
+    ) -> Result<MethodContext, AmlError> {
+        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<Opcode, AmlError> {
+        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<usize, AmlError> {
+        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<AmlName, AmlError> {
+        use namespace::{NameComponent, NameSeg};
+
+        /*
+         * The NameString grammar is actually a little finicky and annoying.
+         *
+         * NameString := <RootChar NamePath> | <PrefixPath NamePath>
+         * 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<u8, AmlError> {
+        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<u16, AmlError> {
+        Ok(u16::from_le_bytes([self.next()?, self.next()?]))
+    }
+
+    fn next_u32(&mut self) -> Result<u32, AmlError> {
+        Ok(u32::from_le_bytes([self.next()?, self.next()?, self.next()?, self.next()?]))
+    }
+
+    fn next_u64(&mut self) -> Result<u64, AmlError> {
+        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<u8, AmlError> {
+        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<AmlName>),
+
+    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<T: Send + Sync>() {}
+        test_send_sync::<Interpreter<TestHandler>>();
+    }
+
+    #[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<WrappedObject, AmlError> {
+        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<AmlName, AmlError> {
+        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<bool, AmlError>` -
+    /// 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<F>(&mut self, mut f: F) -> Result<(), AmlError>
+    where
+        F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
+    {
+        fn traverse_level<F>(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError>
+        where
+            F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
+        {
+            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<NameSeg, (ObjectFlags, WrappedObject)>,
+    pub children: BTreeMap<NameSeg, NamespaceLevel>,
+}
+
+#[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<NameComponent>);
+
+#[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<NameComponent>) -> 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<AmlName, AmlError> {
+        /*
+         * 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<AmlName, AmlError> {
+        // 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<AmlName, AmlError> {
+        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<Self, Self::Err> {
+        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<NameSeg, AmlError> {
+        // 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<NameSeg, AmlError> {
+        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<u8>),
+    BufferField { buffer: WrappedObject, offset: usize, length: usize },
+    Device,
+    Event,
+    FieldUnit(FieldUnit),
+    Integer(u64),
+    Method { code: Vec<u8>, flags: MethodFlags },
+    Mutex { mutex: Handle, sync_level: u8 },
+    Reference { kind: ReferenceKind, inner: WrappedObject },
+    OpRegion(OpRegion),
+    Package(Vec<WrappedObject>),
+    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<UnsafeCell<Object>>);
+
+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<u64, AmlError> {
+        if let Object::Integer(value) = self {
+            Ok(*value)
+        } else {
+            Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: self.typ() })
+        }
+    }
+
+    pub fn as_string(&self) -> Result<Cow<str>, 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<u64, AmlError> {
+        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::<u64>().unwrap_or(0)),
+            _ => Ok(0),
+        }
+    }
+
+    pub fn to_buffer(&self, allowed_bytes: usize) -> Result<Vec<u8>, 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<FieldAccessType, AmlError> {
+        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<usize, AmlError> {
+        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<u8, AmlError>;
+    fn read_u16(&self, region: &OpRegion) -> Result<u16, AmlError>;
+    fn read_u32(&self, region: &OpRegion) -> Result<u32, AmlError>;
+    fn read_u64(&self, region: &OpRegion) -> Result<u64, AmlError>;
+
+    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<u8> 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<PciRoutingTable, AmlError> {
+    /// `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<impl Handler>,
+    ) -> Result<PciRoutingTable, AmlError> {
         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<impl Handler>,
     ) -> Result<IrqDescriptor, AmlError> {
         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<Vec<Resource>, AmlError> {
-    if let AmlValue::Buffer(bytes) = descriptor {
+/// Parse a `ResourceDescriptor` buffer into a list of resources.
+pub fn resource_descriptor_list(descriptor: WrappedObject) -> Result<Vec<Resource>, 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<Vec<Resource>,
 
         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<Resource>, &[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<Resource>, &[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<Resource>, &[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<Resource>, &[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<Resource, AmlError> {
      * 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<T>(bytes: &[u8]) -> Result<Resource, AmlError> {
     let size = mem::size_of::<T>();
 
     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<Resource, AmlError> {
      * 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<Resource, AmlError> {
      */
 
     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<Resource, AmlError> {
                 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<Resource, AmlError> {
      *           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<Resource, AmlError> {
      * 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<Resource, AmlError> {
      * 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<Resource, AmlError> {
 #[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::<T>()`
@@ -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::<T>()`). 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<H: AcpiHandler> {
+    rsdt_mapping: PhysicalMapping<H, SdtHeader>,
+    pub rsdp_revision: u8,
+    handler: H,
+}
+
+unsafe impl<H> Send for AcpiTables<H> where H: AcpiHandler + Send {}
+unsafe impl<H> Sync for AcpiTables<H> where H: AcpiHandler + Send {}
+
+impl<H> AcpiTables<H>
+where
+    H: AcpiHandler,
+{
+    /// Construct an `AcpiTables` from the **physical** address of the RSDP.
+    pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result<AcpiTables<H>, AcpiError> {
+        let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
+
+        /*
+         * 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<AcpiTables<H>, AcpiError> {
+        let rsdt_mapping =
+            unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, mem::size_of::<SdtHeader>()) };
+        let rsdt_length = rsdt_mapping.length;
+        let rsdt_mapping = unsafe { handler.map_physical_region::<SdtHeader>(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<Item = usize> {
+        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::<SdtHeader>()) }
+                .cast::<u8>();
+        let mut num_entries = (self.rsdt_mapping.region_length() - mem::size_of::<SdtHeader>()) / entry_size;
+
+        core::iter::from_fn(move || {
+            if num_entries > 0 {
+                unsafe {
+                    let entry = if entry_size == 4 {
+                        *table_entries_ptr.cast::<u32>() as usize
+                    } else {
+                        *table_entries_ptr.cast::<u64>() 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<Item = (usize, SdtHeader)> {
+        self.table_entries().map(|table_phys_address| {
+            let mapping = unsafe {
+                self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
+            };
+            (table_phys_address, *mapping)
+        })
+    }
+
+    /// Find all tables with the signature `T::SIGNATURE`.
+    pub fn find_tables<T>(&self) -> impl Iterator<Item = PhysicalMapping<H, T>>
+    where
+        T: AcpiTable,
+    {
+        self.table_entries().filter_map(|table_phys_address| {
+            let header_mapping = unsafe {
+                self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
+            };
+            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::<T>(table_phys_address, length as usize) })
+            } else {
+                None
+            }
+        })
+    }
+
+    /// Find the first table with the signature `T::SIGNATURE`.
+    pub fn find_table<T>(&self) -> Option<PhysicalMapping<H, T>>
+    where
+        T: AcpiTable,
+    {
+        self.find_tables().next()
+    }
+
+    pub fn dsdt(&self) -> Result<AmlTable, AcpiError> {
+        let Some(fadt) = self.find_table::<sdt::fadt::Fadt>() else {
+            Err(AcpiError::TableNotFound(Signature::FADT))?
+        };
+        let phys_address = fadt.dsdt_address()?;
+        let header =
+            unsafe { self.handler.map_physical_region::<SdtHeader>(phys_address, mem::size_of::<SdtHeader>()) };
+        Ok(AmlTable { phys_address, length: header.length, revision: header.revision })
+    }
+
+    pub fn ssdts(&self) -> impl Iterator<Item = AmlTable> {
+        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::<SdtHeader>()` 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<A: Allocator = Global> {
+    /// 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>),
+}
+
+impl InterruptModel<Global> {
+    pub fn new<H: AcpiHandler>(
+        tables: &AcpiTables<H>,
+    ) -> Result<(InterruptModel<Global>, Option<ProcessorInfo<Global>>), AcpiError> {
+        Self::new_in(tables, Global)
+    }
+}
+
+impl<A: Allocator + Clone> InterruptModel<A> {
+    pub fn new_in<H: AcpiHandler>(
+        tables: &AcpiTables<H>,
+        allocator: A,
+    ) -> Result<(InterruptModel<A>, Option<ProcessorInfo<A>>), AcpiError> {
+        let Some(madt) = tables.find_table::<Madt>() 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<A>, Option<ProcessorInfo<A>>), 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<A: Allocator = Global> {
+    pub local_apic_address: u64,
+    pub io_apics: Vec<IoApic, A>,
+    pub local_apic_nmi_lines: Vec<NmiLine, A>,
+    pub interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
+    pub nmi_sources: Vec<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: Allocator> Apic<A> {
+    pub(crate) fn new(
+        local_apic_address: u64,
+        io_apics: Vec<IoApic, A>,
+        local_apic_nmi_lines: Vec<NmiLine, A>,
+        interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
+        nmi_sources: Vec<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,
+        }
+    }
+}
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<A: Allocator = Global> {
     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<Processor, A>,
 }
 
-impl<'a, A> ProcessorInfo<'a, A>
-where
-    A: Allocator,
-{
-    pub(crate) fn new(boot_processor: Processor, application_processors: ManagedSlice<'a, Processor, A>) -> Self {
+impl<A: Allocator> ProcessorInfo<A> {
+    pub(crate) fn new_in(boot_processor: Processor, application_processors: Vec<Processor, A>) -> 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<A: Allocator = Global> {
     pub power_profile: PowerProfile,
-    pub interrupt_model: InterruptModel<'a, A>,
+    pub interrupt_model: InterruptModel<A>,
     /// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
     /// interrupt model. That information is stored here, if present.
-    pub processor_info: Option<ProcessorInfo<'a, A>>,
+    pub processor_info: Option<ProcessorInfo<A>>,
     pub pm_timer: Option<PmTimer>,
     /*
      * TODO: we could provide a nice view of the hardware register blocks in the FADT here.
      */
 }
 
-#[cfg(feature = "alloc")]
-impl PlatformInfo<'_, alloc::alloc::Global> {
-    pub fn new<H>(tables: &AcpiTables<H>) -> AcpiResult<Self>
+impl PlatformInfo<Global> {
+    pub fn new<H>(tables: &AcpiTables<H>) -> Result<Self, AcpiError>
     where
         H: AcpiHandler,
     {
@@ -111,22 +106,15 @@ impl PlatformInfo<'_, alloc::alloc::Global> {
     }
 }
 
-impl<A> PlatformInfo<'_, A>
-where
-    A: Allocator + Clone,
-{
-    pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> AcpiResult<Self>
+impl<A: Allocator + Clone> PlatformInfo<A> {
+    pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> Result<Self, AcpiError>
     where
         H: AcpiHandler,
     {
-        let fadt = tables.find_table::<Fadt>()?;
+        let Some(fadt) = tables.find_table::<Fadt>() else { Err(AcpiError::TableNotFound(Signature::FADT))? };
         let power_profile = fadt.power_profile();
 
-        let madt = tables.find_table::<Madt>();
-        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<H>(
+pub unsafe fn wakeup_aps<H>(
     tables: &AcpiTables<H>,
     handler: H,
     apic_id: u32,
@@ -152,7 +140,7 @@ pub fn wakeup_aps<H>(
 where
     H: AcpiHandler,
 {
-    let madt = tables.find_table::<Madt>()?;
+    let Some(madt) = tables.find_table::<Madt>() else { Err(AcpiError::TableNotFound(Signature::MADT))? };
     let mailbox_addr = madt.get().get_mpwk_mailbox_addr()?;
     let mut mpwk_mapping = unsafe {
         handler.map_physical_region::<MultiprocessorWakeupMailbox>(
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<A: Allocator> {
+    pub regions: Vec<McfgEntry, A>,
+}
+
+impl PciConfigRegions<Global> {
+    pub fn new<H>(tables: &AcpiTables<H>) -> Result<PciConfigRegions<Global>, AcpiError>
+    where
+        H: AcpiHandler,
+    {
+        Self::new_in(tables, Global)
+    }
+}
+
+impl<A: Allocator> PciConfigRegions<A> {
+    pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> Result<PciConfigRegions<A>, AcpiError>
+    where
+        H: AcpiHandler,
+    {
+        let Some(mcfg) = tables.find_table::<Mcfg>() 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<u64> {
+        /*
+         * First, find the memory region that handles this segment and bus. This method is fine
+         * because there should only be one region that handles each segment group + bus
+         * combination.
+         */
+        let region = self.regions.iter().find(|region| {
+            region.pci_segment_group == segment_group_no
+                && (region.bus_number_start..=region.bus_number_end).contains(&bus)
+        })?;
+
+        Some(
+            region.base_address
+                + ((u64::from(bus - region.bus_number_start) << 20)
+                    | (u64::from(device) << 15)
+                    | (u64::from(function) << 12)),
+        )
+    }
+}
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>() - 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<H>(handler: H) -> AcpiResult<PhysicalMapping<H, Rsdp>>
+    pub unsafe fn search_for_on_bios<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, 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<u64, 2>,
-    x_dsdt_address: ExtendedField<u64, 2>,
-    x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
-    x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
-    x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
-    x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
-    x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
-    x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
-    x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
-    x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
-    sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
-    sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
-    hypervisor_vendor_id: ExtendedField<u64, 2>,
+    pub fadt_minor_version: u8,
+    pub x_firmware_ctrl: ExtendedField<u64, 2>,
+    pub x_dsdt_address: ExtendedField<u64, 2>,
+    pub x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
+    pub x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
+    pub sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
+    pub sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
+    pub hypervisor_vendor_id: ExtendedField<u64, 2>,
 }
 
 /// ### 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<usize, AcpiError> {
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::<HpetTable>()?;
+        let Some(hpet) = tables.find_table::<HpetTable>() 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<u64, AcpiError> {
-        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<ProcessorInfo<'a, A>>)>
-    where
-        A: core::alloc::Allocator + Clone,
-    {
-        /*
-         * We first do a pass through the MADT to determine which interrupt model is being used.
-         */
-        for entry in self.entries() {
-            match entry {
-                MadtEntry::LocalApic(_) |
-                MadtEntry::LocalX2Apic(_) |
-                MadtEntry::IoApic(_) |
-                MadtEntry::InterruptSourceOverride(_) |
-                MadtEntry::NmiSource(_) |   // TODO: is this one used by more than one model?
-                MadtEntry::LocalApicNmi(_) |
-                MadtEntry::X2ApicNmi(_) |
-                MadtEntry::LocalApicAddressOverride(_) => {
-                    return self.parse_apic_model_in(allocator);
-                }
-
-                MadtEntry::IoSapic(_) |
-                MadtEntry::LocalSapic(_) |
-                MadtEntry::PlatformInterruptSource(_) => {
-                    unimplemented!();
-                }
-
-                MadtEntry::Gicc(_) |
-                MadtEntry::Gicd(_) |
-                MadtEntry::GicMsiFrame(_) |
-                MadtEntry::GicRedistributor(_) |
-                MadtEntry::GicInterruptTranslationService(_) => {
-                    unimplemented!();
-                }
-
-                MadtEntry::MultiprocessorWakeup(_) => ()
-            }
-        }
-
-        Ok((InterruptModel::Unknown, None))
-    }
-
-    #[cfg(feature = "allocator_api")]
-    fn parse_apic_model_in<'a, A>(
-        self: Pin<&Self>,
-        allocator: A,
-    ) -> AcpiResult<(InterruptModel<'a, A>, Option<ProcessorInfo<'a, A>>)>
-    where
-        A: core::alloc::Allocator + Clone,
-    {
-        use crate::platform::{
-            interrupt::{
-                Apic,
-                InterruptSourceOverride,
-                IoApic,
-                LocalInterruptLine,
-                NmiLine,
-                NmiProcessor,
-                NmiSource,
-            },
-            Processor,
-            ProcessorState,
-        };
-
-        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<u64, AcpiError> {
+        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::<Mcfg>();
+
+        // 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::<McfgEntry>();
+
+        unsafe {
+            let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
+            slice::from_raw_parts(pointer, num_entries)
+        }
+    }
+}
+
+impl 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<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
     /// ### Safety
     /// If a bogus ACPI version is passed, this function may access uninitialised data.
     pub unsafe fn access(&self, revision: u8) -> Option<T> {
-        if revision >= MIN_REVISION {
-            Some(unsafe { self.0.assume_init() })
-        } else {
-            None
-        }
+        if revision >= MIN_REVISION { Some(unsafe { self.0.assume_init() }) } else { None }
     }
 }
 
@@ -56,7 +59,7 @@ impl<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
 /// * 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::<u8>(), self.length as usize) };
         let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
-
-        if sum == 0 {
-            Ok(())
-        } else {
-            Err(AcpiError::SdtInvalidChecksum(signature))
+        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<H: AcpiHandler, T: AcpiTable>(
-        header_mapping: PhysicalMapping<H, Self>,
-        handler: H,
-    ) -> AcpiResult<PhysicalMapping<H, T>> {
-        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::<T>(),
-                    table_length,
-                    header_mapping.mapped_length(),
-                    handler,
-                )
-            }
-        } else {
-            // Unmap header as soon as possible
-            let table_phys_start = header_mapping.physical_start();
-            drop(header_mapping);
+    #[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<AcpiResult<GenericAddress>> {
+    pub fn base_address(&self) -> Option<Result<GenericAddress, AcpiError>> {
         (!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<String> = if matches.contains_id("path") {
-        let dir_path = Path::new(matches.get_one::<String>("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::<String>("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<CompilationOutcome> =
-        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<PathBuf> = HashSet::new();
-    let summaries: RefCell<HashSet<(PathBuf, &str)>> = 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::<Vec<_>>();
+
+    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<Handler>) -> 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<Vec<PathBuf>> {
+    // Get an initial list of files - may not work correctly on non-UTF8 OsString
+    let files: Vec<PathBuf> = if matches.contains_id("path") {
+        let dir_path = Path::new(matches.get_one::<String>("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::<PathBuf>("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<CompilationOutcome> {
-    let path = PathBuf::from(name);
-
+fn resolve_and_compile(path: &PathBuf, can_compile: bool) -> std::io::Result<CompilationOutcome> {
     // 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<Compila
     }
 
     if !can_compile {
-        return Ok(CompilationOutcome::NotCompiled(path));
+        return Ok(CompilationOutcome::NotCompiled(path.clone()));
     }
 
     // Compile the ASL file using `iasl`
-    println!("Compiling file: {}", name);
-    let output = Command::new("iasl").arg(name).output()?;
+    println!("Compiling file: {}", path.display());
+    let output = Command::new("iasl").arg(path).output()?;
 
     if !output.status.success() {
         println!(
             "Failed to compile ASL file: {}. Output from iasl:\n {}",
-            name,
+            path.display(),
             String::from_utf8_lossy(&output.stderr)
         );
-        Ok(CompilationOutcome::Failed(path))
+        Ok(CompilationOutcome::Failed(path.clone()))
     } else {
         Ok(CompilationOutcome::Succeeded(aml_path))
     }
@@ -252,7 +325,7 @@ impl log::Log for Logger {
 
 struct Handler;
 
-impl aml::Handler for Handler {
+impl acpi::aml::Handler for Handler {
     fn read_u8(&self, address: usize) -> 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) {}
 }