瀏覽代碼

Merge pull request #75 from IsaacWoods/new

Lay out new architecture for acpi, allowing separation of table discovery and parsing
Isaac Woods 4 年之前
父節點
當前提交
a885b2139a
共有 17 個文件被更改,包括 993 次插入850 次删除
  1. 1 1
      Cargo.toml
  2. 5 3
      README.md
  3. 2 1
      acpi/Cargo.toml
  4. 35 40
      acpi/src/fadt.rs
  5. 0 35
      acpi/src/handler.rs
  6. 35 23
      acpi/src/hpet.rs
  7. 0 90
      acpi/src/interrupt.rs
  8. 245 180
      acpi/src/lib.rs
  9. 199 209
      acpi/src/madt.rs
  10. 19 9
      acpi/src/mcfg.rs
  11. 161 0
      acpi/src/platform.rs
  12. 0 90
      acpi/src/rsdp.rs
  13. 0 104
      acpi/src/rsdp_search.rs
  14. 8 65
      acpi/src/sdt.rs
  15. 13 0
      rsdp/Cargo.toml
  16. 51 0
      rsdp/src/handler.rs
  17. 219 0
      rsdp/src/lib.rs

+ 1 - 1
Cargo.toml

@@ -1,2 +1,2 @@
 [workspace]
-members = ["acpi", "aml", "acpi-dumper", "aml_tester"]
+members = ["rsdp", "acpi", "aml", "acpi-dumper", "aml_tester"]

+ 5 - 3
README.md

@@ -2,15 +2,17 @@
 [![Build Status](https://travis-ci.org/rust-osdev/acpi.svg?branch=master)](https://travis-ci.org/rust-osdev/acpi)
 [![Version](https://img.shields.io/crates/v/acpi.svg?style=rounded-square)](https://crates.io/crates/acpi/)
 
+### [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 two crates:
+A library to parse ACPI tables and AML, written in pure Rust. Designed to be easy to use from Rust bootloaders and kernels. The library is split into three crates:
+- `rsdp` parses the RSDP and can locate it on BIOS platforms. It does not depend on `alloc`, so is suitable to use from bootloaders without heap alloctors. All of its
+  functionality is reexported by `acpi`.
 - `acpi` parses the static tables (useful but not feature-complete)
 - `aml` parses the AML tables (can be useful, far from feature-complete)
 
-There is also the `acpi-dumper` utility to easily dump a platform's ACPI tables (this currently only works on
-Linux).
+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:

+ 2 - 1
acpi/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "acpi"
-version = "1.1.0"
+version = "2.0.0"
 authors = ["Isaac Woods"]
 repository = "https://github.com/rust-osdev/acpi"
 description = "Library for parsing ACPI tables"
@@ -12,3 +12,4 @@ edition = "2018"
 [dependencies]
 log = "0.4"
 bit_field = "0.10"
+rsdp = "1"

+ 35 - 40
acpi/src/fadt.rs

@@ -1,11 +1,8 @@
 use crate::{
     sdt::{ExtendedField, SdtHeader, ACPI_VERSION_2_0},
-    Acpi,
     AcpiError,
-    AcpiHandler,
-    AmlTable,
+    AcpiTable,
     GenericAddress,
-    PhysicalMapping,
 };
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -93,43 +90,41 @@ pub struct Fadt {
     hypervisor_vendor_id: ExtendedField<u64, ACPI_VERSION_2_0>,
 }
 
-pub(crate) fn parse_fadt<H>(
-    acpi: &mut Acpi,
-    handler: &mut H,
-    mapping: &PhysicalMapping<Fadt>,
-) -> Result<(), AcpiError>
-where
-    H: AcpiHandler,
-{
-    let fadt = &*mapping;
-    fadt.header.validate(crate::sdt::Signature::FADT)?;
-
-    acpi.power_profile = match fadt.preferred_pm_profile {
-        0 => PowerProfile::Unspecified,
-        1 => PowerProfile::Desktop,
-        2 => PowerProfile::Mobile,
-        3 => PowerProfile::Workstation,
-        4 => PowerProfile::EnterpriseServer,
-        5 => PowerProfile::SohoServer,
-        6 => PowerProfile::AppliancePc,
-        7 => PowerProfile::PerformanceServer,
-        8 => PowerProfile::Tablet,
-        other => PowerProfile::Reserved(other),
-    };
+impl AcpiTable for Fadt {
+    fn header(&self) -> &SdtHeader {
+        &self.header
+    }
+}
 
-    let dsdt_address = unsafe {
-        fadt.x_dsdt_address
-            .access(fadt.header.revision)
-            .filter(|&p| p != 0)
-            .or(Some(fadt.dsdt_address as u64))
-            .filter(|p| *p != 0)
-            .map(|p| p as usize)
-    };
+impl Fadt {
+    pub fn validate(&self) -> Result<(), AcpiError> {
+        self.header.validate(crate::sdt::Signature::FADT)
+    }
 
-    acpi.dsdt = dsdt_address.map(|address| {
-        let dsdt_header = crate::sdt::peek_at_sdt_header(handler, address);
-        AmlTable::new(address, dsdt_header.length)
-    });
+    pub fn dsdt_address(&self) -> Result<usize, AcpiError> {
+        unsafe {
+            self.x_dsdt_address
+                .access(self.header.revision)
+                .filter(|&p| p != 0)
+                .or(Some(self.dsdt_address as u64))
+                .filter(|p| *p != 0)
+                .map(|p| p as usize)
+                .ok_or(AcpiError::InvalidDsdtAddress)
+        }
+    }
 
-    Ok(())
+    pub fn power_profile(&self) -> PowerProfile {
+        match self.preferred_pm_profile {
+            0 => PowerProfile::Unspecified,
+            1 => PowerProfile::Desktop,
+            2 => PowerProfile::Mobile,
+            3 => PowerProfile::Workstation,
+            4 => PowerProfile::EnterpriseServer,
+            5 => PowerProfile::SohoServer,
+            6 => PowerProfile::AppliancePc,
+            7 => PowerProfile::PerformanceServer,
+            8 => PowerProfile::Tablet,
+            other => PowerProfile::Reserved(other),
+        }
+    }
 }

+ 0 - 35
acpi/src/handler.rs

@@ -1,35 +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.
-pub struct PhysicalMapping<T> {
-    pub physical_start: usize,
-    pub virtual_start: NonNull<T>,
-    pub region_length: usize, // Can be equal or larger than size_of::<T>()
-    pub mapped_length: usize, // Differs from `region_length` if padding is added for alignment
-}
-
-impl<T> Deref for PhysicalMapping<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        unsafe { self.virtual_start.as_ref() }
-    }
-}
-
-/// 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.
-pub trait AcpiHandler {
-    /// Given a starting physical address and a size, map a region of physical memory that contains
-    /// a `T` (but may be bigger than `size_of::<T>()`). The address doesn't have to be
-    /// page-aligned, so the implementation may have to add padding to either end. The given
-    /// size must be greater or equal to the size of a `T`. The virtual address the memory is
-    /// mapped to does not matter, as long as it is accessible from `acpi`.
-    unsafe fn map_physical_region<T>(&mut self, physical_address: usize, size: usize) -> PhysicalMapping<T>;
-
-    /// Unmap the given physical mapping. Safe because we consume the mapping, and so it can't be
-    /// used after being passed to this function.
-    fn unmap_physical_region<T>(&mut self, region: PhysicalMapping<T>);
-}

+ 35 - 23
acpi/src/hpet.rs

@@ -1,4 +1,4 @@
-use crate::{sdt::SdtHeader, Acpi, AcpiError, GenericAddress, PhysicalMapping};
+use crate::{sdt::SdtHeader, AcpiError, AcpiHandler, AcpiTable, AcpiTables, GenericAddress};
 use bit_field::BitField;
 
 #[derive(Debug)]
@@ -22,6 +22,36 @@ pub struct HpetInfo {
     pub page_protection: PageProtection,
 }
 
+impl HpetInfo {
+    pub fn new<H>(tables: &AcpiTables<H>) -> Result<HpetInfo, AcpiError>
+    where
+        H: AcpiHandler,
+    {
+        let hpet = unsafe {
+            tables
+                .get_sdt::<HpetTable>(crate::sdt::Signature::HPET)?
+                .ok_or(AcpiError::TableMissing(crate::sdt::Signature::HPET))?
+        };
+
+        // Make sure the HPET's in system memory
+        assert_eq!(hpet.base_address.address_space, 0);
+
+        Ok(HpetInfo {
+            event_timer_block_id: hpet.event_timer_block_id,
+            base_address: hpet.base_address.address as usize,
+            hpet_number: hpet.hpet_number,
+            clock_tick_unit: hpet.clock_tick_unit,
+            page_protection: match hpet.page_protection_oem.get_bits(0..5) {
+                0 => PageProtection::None,
+                1 => PageProtection::Protected4K,
+                2 => PageProtection::Protected64K,
+                3..=15 => PageProtection::Other,
+                _ => unreachable!(),
+            },
+        })
+    }
+}
+
 #[repr(C, packed)]
 pub(crate) struct HpetTable {
     header: SdtHeader,
@@ -32,26 +62,8 @@ pub(crate) struct HpetTable {
     page_protection_oem: u8,
 }
 
-pub(crate) fn parse_hpet(acpi: &mut Acpi, mapping: &PhysicalMapping<HpetTable>) -> Result<(), AcpiError> {
-    (*mapping).header.validate(crate::sdt::Signature::HPET)?;
-    let hpet = &*mapping;
-
-    // Make sure the HPET's in system memory
-    assert_eq!(hpet.base_address.address_space, 0);
-
-    acpi.hpet = Some(HpetInfo {
-        event_timer_block_id: hpet.event_timer_block_id,
-        base_address: hpet.base_address.address as usize,
-        hpet_number: hpet.hpet_number,
-        clock_tick_unit: hpet.clock_tick_unit,
-        page_protection: match hpet.page_protection_oem.get_bits(0..5) {
-            0 => PageProtection::None,
-            1 => PageProtection::Protected4K,
-            2 => PageProtection::Protected64K,
-            3..=15 => PageProtection::Other,
-            _ => unreachable!(),
-        },
-    });
-
-    Ok(())
+impl AcpiTable for HpetTable {
+    fn header(&self) -> &SdtHeader {
+        &self.header
+    }
 }

+ 0 - 90
acpi/src/interrupt.rs

@@ -1,90 +0,0 @@
-use alloc::vec::Vec;
-
-#[derive(Debug)]
-pub struct IoApic {
-    pub id: u8,
-    pub address: u32,
-    pub global_system_interrupt_base: u32,
-}
-
-#[derive(Debug)]
-pub struct NmiLine {
-    pub processor: NmiProcessor,
-    pub line: LocalInterruptLine,
-}
-
-#[derive(Debug)]
-pub enum LocalInterruptLine {
-    Lint0,
-    Lint1,
-}
-
-#[derive(Debug)]
-pub enum NmiProcessor {
-    All,
-    /// Refers to a processor with the given UID. This is stored as a `u32`, but should be casted to `u8` when the
-    /// DSDT uses the deprecated `DefProcessor` operator to define processor UIDs.
-    ProcessorUid(u32),
-}
-
-#[derive(Debug)]
-pub enum Polarity {
-    SameAsBus,
-    ActiveHigh,
-    ActiveLow,
-}
-
-#[derive(Debug)]
-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)]
-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)]
-pub struct NmiSource {
-    pub global_system_interrupt: u32,
-    pub polarity: Polarity,
-    pub trigger_mode: TriggerMode,
-}
-
-#[derive(Debug)]
-pub struct Apic {
-    pub local_apic_address: u64,
-    pub io_apics: Vec<IoApic>,
-    pub local_apic_nmi_lines: Vec<NmiLine>,
-    pub interrupt_source_overrides: Vec<InterruptSourceOverride>,
-    pub nmi_sources: Vec<NmiSource>,
-
-    /// 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,
-}
-
-#[derive(Debug)]
-#[non_exhaustive]
-pub enum InterruptModel {
-    /// This model is only chosen when a newer one can not be found and the system supports the
-    /// legacy dual-8259 PIC.
-    Pic,
-
-    /// Describes an interrupt controller based around the Advanced Programmable Interrupt
-    /// Controllers. 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),
-}

+ 245 - 180
acpi/src/lib.rs

@@ -1,28 +1,45 @@
-//! A library for parsing ACPI tables. This crate can be used by bootloaders and kernels for
-//! architectures that support ACPI. The crate is far from feature-complete, but can still be used
-//! for finding and parsing the static tables, which is enough to set up hardware such as the APIC
-//! and HPET on x86_64.
+//! 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.
 //!
-//! The crate is designed for use 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 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 requires `alloc` to make heap allocations. If you are trying to find the RSDP in an environment that
+//! does not have a heap (e.g. a bootloader), you can use the `rsdp` crate. The types from that crate are
+//! compatible with `acpi`.
 //!
 //! ### 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.
+//! 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**
+//! * Use `AcpiTables::from_tables_direct` if you are using the library in an unusual setting, such as in usermode,
+//!   and have a custom method to enumerate and access the tables.
 //!
-//! You should then call one of the entry points, based on how much information you have:
-//! * Call `parse_rsdp` if you have the physical address of the RSDP
-//! * Call `parse_rsdt` if you have the physical address of the RSDT / XSDT
-//! * Call `search_for_rsdp_bios` if you don't have the address of either structure, but **you know
-//! you're 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`.
 //!
-//! All of these methods return an instance of `Acpi`. This struct contains all the information
-//! gathered from the static tables, and can be queried to set up hardware etc.
+//! To gather information out of the static tables, a few of the types you should take a look at are:
+//!    - [`PlatformInfo`](crate::platform::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`](crate::hpet::HpetInfo) parses the HPET table and tells you how to configure the High
+//!      Precision Event Timer.
+//!    - [`PciConfigRegions`](crate::mcfg::PciConfigRegions) parses the MCFG and tells you how PCIe configuration
+//!      space is mapped into physical memory.
 
 #![no_std]
 #![feature(const_generics, unsafe_block_in_unsafe_fn)]
+#![deny(unsafe_op_in_unsafe_fn)]
 
 extern crate alloc;
 #[cfg_attr(test, macro_use)]
@@ -30,83 +47,241 @@ extern crate alloc;
 extern crate std;
 
 mod fadt;
-pub mod handler;
 mod hpet;
-pub mod interrupt;
 mod madt;
 mod mcfg;
-mod rsdp;
-mod rsdp_search;
+pub mod platform;
 mod sdt;
 
 pub use crate::{
     fadt::PowerProfile,
-    handler::{AcpiHandler, PhysicalMapping},
     hpet::HpetInfo,
-    interrupt::InterruptModel,
     madt::MadtError,
     mcfg::PciConfigRegions,
-    rsdp_search::search_for_rsdp_bios,
+    platform::{InterruptModel, PlatformInfo},
 };
-
-use crate::{
-    rsdp::Rsdp,
-    sdt::{SdtHeader, Signature},
+pub use rsdp::{
+    handler::{AcpiHandler, PhysicalMapping},
+    RsdpError,
 };
-use alloc::vec::Vec;
+
+use crate::sdt::{SdtHeader, Signature};
+use alloc::{collections::BTreeMap, vec::Vec};
 use core::mem;
+use log::trace;
+use rsdp::Rsdp;
 
 #[derive(Debug)]
 pub enum AcpiError {
-    RsdpIncorrectSignature,
-    RsdpInvalidOemId,
-    RsdpInvalidChecksum,
-    NoValidRsdp,
+    Rsdp(RsdpError),
 
     SdtInvalidSignature(Signature),
     SdtInvalidOemId(Signature),
     SdtInvalidTableId(Signature),
     SdtInvalidChecksum(Signature),
 
+    TableMissing(Signature),
+    InvalidDsdtAddress,
     InvalidMadt(MadtError),
 }
 
-#[derive(Clone, Copy, Debug)]
-#[repr(C, packed)]
-pub(crate) struct GenericAddress {
-    address_space: u8,
-    bit_width: u8,
-    bit_offset: u8,
-    access_size: u8,
-    address: u64,
+pub struct AcpiTables<H>
+where
+    H: AcpiHandler,
+{
+    /// The revision of ACPI that the system uses, as inferred from the revision of the RSDT/XSDT.
+    pub revision: u8,
+    pub sdts: BTreeMap<sdt::Signature, Sdt>,
+    pub dsdt: Option<AmlTable>,
+    pub ssdts: Vec<AmlTable>,
+    handler: H,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum ProcessorState {
-    /// A processor in this state is unusable, and you must not attempt to bring it up.
-    Disabled,
+impl<H> AcpiTables<H>
+where
+    H: AcpiHandler,
+{
+    /// Create an `AcpiTables` if you have 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>()) };
+        rsdp_mapping.validate().map_err(|err| AcpiError::Rsdp(err))?;
 
-    /// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
-    /// but may be brought up.
-    WaitingForSipi,
+        Self::from_validated_rsdp(handler, rsdp_mapping)
+    }
 
-    /// A Running processor is currently brought up and running code.
-    Running,
-}
+    /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not
+    /// work on UEFI platforms. See [Rsdp::search_for_rsdp_bios](rsdp_search::Rsdp::search_for_rsdp_bios) for
+    /// details.
+    pub unsafe fn search_for_rsdp_bios(handler: H) -> Result<AcpiTables<H>, AcpiError> {
+        let rsdp_mapping =
+            unsafe { Rsdp::search_for_on_bios(handler.clone()) }.map_err(|err| AcpiError::Rsdp(err))?;
+        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.
+    pub fn from_validated_rsdp(
+        handler: H,
+        rsdp_mapping: PhysicalMapping<H, Rsdp>,
+    ) -> Result<AcpiTables<H>, AcpiError> {
+        let revision = rsdp_mapping.revision();
+
+        if revision == 0 {
+            /*
+             * We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
+             */
+            let rsdt_address = rsdp_mapping.rsdt_address();
+            unsafe { Self::from_rsdt(handler, revision, 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.
+             */
+            let xsdt_address = rsdp_mapping.xsdt_address();
+            unsafe { Self::from_rsdt(handler, revision, xsdt_address as usize) }
+        }
+    }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct Processor {
-    pub processor_uid: u8,
-    pub local_apic_id: u8,
+    /// Create an `AcpiTables` if you have the physical address of the RSDT. This is useful, for example, if your chosen
+    /// bootloader reads the RSDP and passes you the address of the RSDT. You also need to supply the correct ACPI
+    /// revision - if `0`, a RSDT is expected, while a `XSDT` is expected for greater revisions.
+    pub unsafe fn from_rsdt(handler: H, revision: u8, rsdt_address: usize) -> Result<AcpiTables<H>, AcpiError> {
+        let mut result = AcpiTables { revision, sdts: BTreeMap::new(), dsdt: None, ssdts: Vec::new(), handler };
+
+        let header = sdt::peek_at_sdt_header(&result.handler, rsdt_address);
+        let mapping =
+            unsafe { result.handler.map_physical_region::<SdtHeader>(rsdt_address, header.length as usize) };
+
+        if revision == 0 {
+            /*
+             * ACPI Version 1.0. It's a RSDT!
+             */
+            mapping.validate(sdt::Signature::RSDT)?;
+
+            let num_tables = (mapping.length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u32>();
+            let tables_base =
+                ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u32;
+
+            for i in 0..num_tables {
+                result.process_sdt(unsafe { *tables_base.offset(i as isize) as usize })?;
+            }
+        } else {
+            /*
+             * ACPI Version 2.0+. It's a XSDT!
+             */
+            mapping.validate(sdt::Signature::XSDT)?;
+
+            let num_tables = (mapping.length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u64>();
+            let tables_base =
+                ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u64;
+
+            for i in 0..num_tables {
+                result.process_sdt(unsafe { *tables_base.offset(i as isize) as usize })?;
+            }
+        }
+
+        Ok(result)
+    }
+
+    /// Construct an `AcpiTables` from a custom set of "discovered" tables. This is provided to allow the library
+    /// to be used from unconventional settings (e.g. in userspace), for example with a `AcpiHandler` that detects
+    /// accesses to specific physical addresses, and provides the correct data.
+    pub fn from_tables_direct(
+        handler: H,
+        revision: u8,
+        sdts: BTreeMap<sdt::Signature, Sdt>,
+        dsdt: Option<AmlTable>,
+        ssdts: Vec<AmlTable>,
+    ) -> AcpiTables<H> {
+        AcpiTables { revision, sdts, dsdt, ssdts, handler }
+    }
+
+    fn process_sdt(&mut self, physical_address: usize) -> Result<(), AcpiError> {
+        let header = sdt::peek_at_sdt_header(&self.handler, physical_address);
+        trace!("Found ACPI table with signature {:?} and length {:?}", header.signature, header.length);
+
+        match header.signature {
+            Signature::FADT => {
+                use fadt::Fadt;
+
+                /*
+                 * For whatever reason, they chose to put the DSDT inside the FADT, instead of just listing it
+                 * as another SDT. We extract it here to provide a nicer public API.
+                 */
+                let fadt_mapping =
+                    unsafe { self.handler.map_physical_region::<Fadt>(physical_address, mem::size_of::<Fadt>()) };
+                fadt_mapping.validate()?;
+
+                let dsdt_address = fadt_mapping.dsdt_address()?;
+                let dsdt_header = sdt::peek_at_sdt_header(&self.handler, dsdt_address);
+                self.dsdt = Some(AmlTable::new(dsdt_address, dsdt_header.length));
+
+                /*
+                 * We've already validated the FADT to get the DSDT out, so it doesn't need to be done again.
+                 */
+                self.sdts
+                    .insert(Signature::FADT, Sdt { physical_address, length: header.length, validated: true });
+            }
+            Signature::SSDT => {
+                self.ssdts.push(AmlTable::new(physical_address, header.length));
+            }
+            signature => {
+                self.sdts.insert(signature, Sdt { physical_address, length: header.length, validated: false });
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Create a mapping to a SDT, given its signature. This validates the SDT if it has not already been
+    /// validated.
+    ///
+    /// ### Safety
+    /// The table's memory is naively interpreted as a `T`, and 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 `T` that is larger than this, *may* lead to
+    /// page-faults, aliasing references, or derefencing uninitialized memory (the latter two of which are UB).
+    /// This isn't forbidden, however, because some tables rely on `T` 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 fn get_sdt<T>(&self, signature: sdt::Signature) -> Result<Option<PhysicalMapping<H, T>>, AcpiError>
+    where
+        T: AcpiTable,
+    {
+        let sdt = match self.sdts.get(&signature) {
+            Some(sdt) => sdt,
+            None => return Ok(None),
+        };
+        let mapping = unsafe { self.handler.map_physical_region::<T>(sdt.physical_address, sdt.length as usize) };
+
+        if !sdt.validated {
+            mapping.header().validate(signature)?;
+        }
+
+        Ok(Some(mapping))
+    }
+
+    /// Convenience method for contructing a [`PlatformInfo`](crate::platform::PlatformInfo). This is one of the
+    /// first things you should usually do with an `AcpiTables`, and allows to collect helpful information about
+    /// the platform from the ACPI tables.
+    pub fn platform_info(&self) -> Result<PlatformInfo, AcpiError> {
+        PlatformInfo::new(self)
+    }
+}
 
-    /// The state of this processor. Always check that the processor is not `Disabled` before
-    /// attempting to bring it up!
-    pub state: ProcessorState,
+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,
+}
 
-    /// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
-    /// When the bootloader is entered, the BSP is the only processor running code. To run code on
-    /// more than one processor, you need to "bring up" the APs.
-    pub is_ap: bool,
+/// All types representing ACPI tables should implement this trait.
+pub trait AcpiTable {
+    fn header(&self) -> &sdt::SdtHeader;
 }
 
 #[derive(Debug)]
@@ -127,122 +302,12 @@ impl AmlTable {
     }
 }
 
-#[derive(Debug)]
-pub struct Acpi {
-    pub acpi_revision: u8,
-
-    /// The boot processor. Until you bring up any APs, this is the only processor running code.
-    pub boot_processor: Option<Processor>,
-
-    /// Application processes. These are not brought up until you do so, and must be brought up in
-    /// the order they appear in this list.
-    pub application_processors: Vec<Processor>,
-
-    /// ACPI theoretically allows for more than one interrupt model to be supported by the same
-    /// hardware. For simplicity and because hardware practically will only support one model, we
-    /// just error in cases that the tables detail more than one.
-    pub interrupt_model: Option<InterruptModel>,
-    pub hpet: Option<HpetInfo>,
-    pub power_profile: PowerProfile,
-
-    /// Info about the DSDT, if we find it.
-    pub dsdt: Option<AmlTable>,
-
-    /// Info about any SSDTs, if there are any.
-    pub ssdts: Vec<AmlTable>,
-
-    /// Info about the PCI-E configuration memory regions, collected from the MCFG.
-    pub pci_config_regions: Option<PciConfigRegions>,
-}
-
-/// This is the entry point of `acpi` if you have the **physical** address of the RSDP. It maps
-/// the RSDP, works out what version of ACPI the hardware supports, and passes the physical
-/// address of the RSDT/XSDT to `parse_rsdt`.
-pub unsafe fn parse_rsdp<H>(handler: &mut H, rsdp_address: usize) -> Result<Acpi, AcpiError>
-where
-    H: AcpiHandler,
-{
-    let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
-    (*rsdp_mapping).validate()?;
-
-    parse_validated_rsdp(handler, rsdp_mapping)
-}
-
-fn parse_validated_rsdp<H>(handler: &mut H, rsdp_mapping: PhysicalMapping<Rsdp>) -> Result<Acpi, AcpiError>
-where
-    H: AcpiHandler,
-{
-    let revision = (*rsdp_mapping).revision();
-
-    if revision == 0 {
-        /*
-         * We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
-         */
-        let rsdt_address = (*rsdp_mapping).rsdt_address();
-        handler.unmap_physical_region(rsdp_mapping);
-        unsafe { parse_rsdt(handler, revision, 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.
-         */
-        let xsdt_address = (*rsdp_mapping).xsdt_address();
-        handler.unmap_physical_region(rsdp_mapping);
-        unsafe { parse_rsdt(handler, revision, xsdt_address as usize) }
-    }
-}
-
-/// This is the entry point of `acpi` if you already have the **physical** address of the
-/// RSDT/XSDT; it parses all the SDTs in the RSDT/XSDT, calling the relevant handlers in the
-/// implementation's `AcpiHandler`.
-///
-/// If the given revision is 0, an address to the RSDT is expected. Otherwise, an address to
-/// the XSDT is expected.
-pub unsafe fn parse_rsdt<H>(handler: &mut H, revision: u8, physical_address: usize) -> Result<Acpi, AcpiError>
-where
-    H: AcpiHandler,
-{
-    let mut acpi = Acpi {
-        acpi_revision: revision,
-        boot_processor: None,
-        application_processors: Vec::new(),
-        interrupt_model: None,
-        hpet: None,
-        power_profile: PowerProfile::Unspecified,
-        dsdt: None,
-        ssdts: Vec::new(),
-        pci_config_regions: None,
-    };
-
-    let header = sdt::peek_at_sdt_header(handler, physical_address);
-    let mapping = unsafe { handler.map_physical_region::<SdtHeader>(physical_address, header.length as usize) };
-
-    if revision == 0 {
-        /*
-         * ACPI Version 1.0. It's a RSDT!
-         */
-        (*mapping).validate(sdt::Signature::RSDT)?;
-
-        let num_tables = ((*mapping).length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u32>();
-        let tables_base = ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u32;
-
-        for i in 0..num_tables {
-            sdt::dispatch_sdt(&mut acpi, handler, unsafe { *tables_base.offset(i as isize) } as usize)?;
-        }
-    } else {
-        /*
-         * ACPI Version 2.0+. It's a XSDT!
-         */
-        (*mapping).validate(sdt::Signature::XSDT)?;
-
-        let num_tables = ((*mapping).length as usize - mem::size_of::<SdtHeader>()) / mem::size_of::<u64>();
-        let tables_base = ((mapping.virtual_start.as_ptr() as usize) + mem::size_of::<SdtHeader>()) as *const u64;
-
-        for i in 0..num_tables {
-            sdt::dispatch_sdt(&mut acpi, handler, unsafe { *tables_base.offset(i as isize) } as usize)?;
-        }
-    }
-
-    handler.unmap_physical_region(mapping);
-    Ok(acpi)
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub(crate) struct GenericAddress {
+    address_space: u8,
+    bit_width: u8,
+    bit_offset: u8,
+    access_size: u8,
+    address: u64,
 }

+ 199 - 209
acpi/src/madt.rs

@@ -1,22 +1,22 @@
 use crate::{
-    interrupt::{
+    platform::{
         Apic,
         InterruptModel,
         InterruptSourceOverride,
         IoApic,
+        LocalInterruptLine,
         NmiLine,
         NmiProcessor,
         NmiSource,
         Polarity,
+        Processor,
+        ProcessorInfo,
+        ProcessorState,
         TriggerMode,
     },
     sdt::SdtHeader,
-    Acpi,
     AcpiError,
-    AcpiHandler,
-    PhysicalMapping,
-    Processor,
-    ProcessorState,
+    AcpiTable,
 };
 use alloc::vec::Vec;
 use bit_field::BitField;
@@ -40,14 +40,185 @@ pub enum MadtError {
 ///     * The Streamlined Advanced Programmable Interrupt Controller (SAPIC) model
 ///     * The Generic Interrupt Controller (GIC) model (ARM systems only)
 #[repr(C, packed)]
-pub(crate) struct Madt {
+pub struct Madt {
     header: SdtHeader,
     local_apic_address: u32,
     flags: u32,
 }
 
+impl AcpiTable for Madt {
+    fn header(&self) -> &SdtHeader {
+        &self.header
+    }
+}
+
 impl Madt {
-    fn entries(&self) -> MadtEntryIter {
+    pub fn parse_interrupt_model(&self) -> Result<(InterruptModel, Option<ProcessorInfo>), AcpiError> {
+        /*
+         * 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::IoApic(_) |
+            MadtEntry::InterruptSourceOverride(_) |
+            MadtEntry::NmiSource(_) |   // TODO: is this one used by more than one model?
+            MadtEntry::LocalApicNmi(_) |
+            MadtEntry::LocalApicAddressOverride(_) => {
+                return self.parse_apic_model();
+            }
+
+            MadtEntry::IoSapic(_) |
+            MadtEntry::LocalSapic(_) |
+            MadtEntry::PlatformInterruptSource(_) => {
+                unimplemented!();
+            }
+
+            MadtEntry::LocalX2Apic(_) |
+            MadtEntry::X2ApicNmi(_) => {
+                unimplemented!();
+            }
+
+            MadtEntry::Gicc(_) |
+            MadtEntry::Gicd(_) |
+            MadtEntry::GicMsiFrame(_) |
+            MadtEntry::GicRedistributor(_) |
+            MadtEntry::GicInterruptTranslationService(_) => {
+                unimplemented!();
+            }
+        }
+        }
+
+        Ok((InterruptModel::Unknown, None))
+    }
+
+    fn parse_apic_model(&self) -> Result<(InterruptModel, Option<ProcessorInfo>), AcpiError> {
+        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::LocalApic(_) => processor_count += 1,
+                _ => (),
+            }
+        }
+
+        let mut io_apics = Vec::with_capacity(io_apic_count);
+        let mut interrupt_source_overrides = Vec::with_capacity(iso_count);
+        let mut nmi_sources = Vec::with_capacity(nmi_source_count);
+        let mut local_apic_nmi_lines = Vec::with_capacity(local_nmi_line_count);
+        let mut boot_processor = None;
+        let mut application_processors = Vec::with_capacity(processor_count.saturating_sub(1)); // Subtract one for the BSP
+
+        for entry in self.entries() {
+            match entry {
+                MadtEntry::LocalApic(ref 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 = !unsafe { 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,
+                        local_apic_id: entry.apic_id,
+                        state,
+                        is_ap,
+                    };
+
+                    if is_ap {
+                        application_processors.push(processor);
+                    } else {
+                        boot_processor = Some(processor);
+                    }
+                }
+
+                MadtEntry::IoApic(ref 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(ref 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(ref 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(ref 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::LocalApicAddressOverride(ref entry) => {
+                    local_apic_address = entry.local_apic_address;
+                }
+
+                _ => {
+                    return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
+                }
+            }
+        }
+
+        Ok((
+            InterruptModel::Apic(Apic {
+                local_apic_address,
+                io_apics,
+                local_apic_nmi_lines,
+                interrupt_source_overrides,
+                nmi_sources,
+                also_has_legacy_pics: self.supports_8259(),
+            }),
+            Some(ProcessorInfo { boot_processor: boot_processor.unwrap(), application_processors }),
+        ))
+    }
+
+    pub fn entries(&self) -> MadtEntryIter {
         MadtEntryIter {
             pointer: unsafe { (self as *const Madt as *const u8).offset(mem::size_of::<Madt>() as isize) },
             remaining_length: self.header.length - mem::size_of::<Madt>() as u32,
@@ -55,12 +226,12 @@ impl Madt {
         }
     }
 
-    fn supports_8259(&self) -> bool {
+    pub fn supports_8259(&self) -> bool {
         unsafe { self.flags.get_bit(0) }
     }
 }
 
-struct MadtEntryIter<'a> {
+pub struct MadtEntryIter<'a> {
     pointer: *const u8,
     /*
      * The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
@@ -70,7 +241,7 @@ struct MadtEntryIter<'a> {
     _phantom: PhantomData<&'a ()>,
 }
 
-enum MadtEntry<'a> {
+pub enum MadtEntry<'a> {
     LocalApic(&'a LocalApicEntry),
     IoApic(&'a IoApicEntry),
     InterruptSourceOverride(&'a InterruptSourceOverrideEntry),
@@ -158,13 +329,13 @@ impl<'a> Iterator for MadtEntryIter<'a> {
 
 #[derive(Clone, Copy)]
 #[repr(C, packed)]
-struct EntryHeader {
+pub struct EntryHeader {
     entry_type: u8,
     length: u8,
 }
 
 #[repr(C, packed)]
-struct LocalApicEntry {
+pub struct LocalApicEntry {
     header: EntryHeader,
     processor_id: u8,
     apic_id: u8,
@@ -172,7 +343,7 @@ struct LocalApicEntry {
 }
 
 #[repr(C, packed)]
-struct IoApicEntry {
+pub struct IoApicEntry {
     header: EntryHeader,
     io_apic_id: u8,
     _reserved: u8,
@@ -181,7 +352,7 @@ struct IoApicEntry {
 }
 
 #[repr(C, packed)]
-struct InterruptSourceOverrideEntry {
+pub struct InterruptSourceOverrideEntry {
     header: EntryHeader,
     bus: u8, // 0 - ISA bus
     irq: u8, // This is bus-relative
@@ -190,14 +361,14 @@ struct InterruptSourceOverrideEntry {
 }
 
 #[repr(C, packed)]
-struct NmiSourceEntry {
+pub struct NmiSourceEntry {
     header: EntryHeader,
     flags: u16,
     global_system_interrupt: u32,
 }
 
 #[repr(C, packed)]
-struct LocalApicNmiEntry {
+pub struct LocalApicNmiEntry {
     header: EntryHeader,
     processor_id: u8,
     flags: u16,
@@ -205,7 +376,7 @@ struct LocalApicNmiEntry {
 }
 
 #[repr(C, packed)]
-struct LocalApicAddressOverrideEntry {
+pub struct LocalApicAddressOverrideEntry {
     header: EntryHeader,
     _reserved: u16,
     local_apic_address: u64,
@@ -214,7 +385,7 @@ struct LocalApicAddressOverrideEntry {
 /// If this entry is present, the system has an I/O SAPIC, which must be used instead of the I/O
 /// APIC.
 #[repr(C, packed)]
-struct IoSapicEntry {
+pub struct IoSapicEntry {
     header: EntryHeader,
     io_apic_id: u8,
     _reserved: u8,
@@ -223,7 +394,7 @@ struct IoSapicEntry {
 }
 
 #[repr(C, packed)]
-struct LocalSapicEntry {
+pub struct LocalSapicEntry {
     header: EntryHeader,
     processor_id: u8,
     local_sapic_id: u8,
@@ -240,7 +411,7 @@ struct LocalSapicEntry {
 }
 
 #[repr(C, packed)]
-struct PlatformInterruptSourceEntry {
+pub struct PlatformInterruptSourceEntry {
     header: EntryHeader,
     flags: u16,
     interrupt_type: u8,
@@ -252,7 +423,7 @@ struct PlatformInterruptSourceEntry {
 }
 
 #[repr(C, packed)]
-struct LocalX2ApicEntry {
+pub struct LocalX2ApicEntry {
     header: EntryHeader,
     _reserved: u16,
     x2apic_id: u32,
@@ -261,7 +432,7 @@ struct LocalX2ApicEntry {
 }
 
 #[repr(C, packed)]
-struct X2ApicNmiEntry {
+pub struct X2ApicNmiEntry {
     header: EntryHeader,
     flags: u16,
     processor_uid: u32,
@@ -273,7 +444,7 @@ struct X2ApicNmiEntry {
 /// Controller. In the GICC interrupt model, each logical process has a Processor Device object in
 /// the namespace, and uses this structure to convey its GIC information.
 #[repr(C, packed)]
-struct GiccEntry {
+pub struct GiccEntry {
     header: EntryHeader,
     _reserved1: u16,
     cpu_interface_number: u32,
@@ -292,7 +463,7 @@ struct GiccEntry {
 }
 
 #[repr(C, packed)]
-struct GicdEntry {
+pub struct GicdEntry {
     header: EntryHeader,
     _reserved1: u16,
     gic_id: u32,
@@ -311,7 +482,7 @@ struct GicdEntry {
 }
 
 #[repr(C, packed)]
-struct GicMsiFrameEntry {
+pub struct GicMsiFrameEntry {
     header: EntryHeader,
     _reserved: u16,
     frame_id: u32,
@@ -322,7 +493,7 @@ struct GicMsiFrameEntry {
 }
 
 #[repr(C, packed)]
-struct GicRedistributorEntry {
+pub struct GicRedistributorEntry {
     header: EntryHeader,
     _reserved: u16,
     discovery_range_base_address: u64,
@@ -330,7 +501,7 @@ struct GicRedistributorEntry {
 }
 
 #[repr(C, packed)]
-struct GicInterruptTranslationServiceEntry {
+pub struct GicInterruptTranslationServiceEntry {
     header: EntryHeader,
     _reserved1: u16,
     id: u32,
@@ -338,187 +509,6 @@ struct GicInterruptTranslationServiceEntry {
     _reserved2: u32,
 }
 
-pub(crate) fn parse_madt<H>(
-    acpi: &mut Acpi,
-    _handler: &mut H,
-    mapping: &PhysicalMapping<Madt>,
-) -> Result<(), AcpiError>
-where
-    H: AcpiHandler,
-{
-    (*mapping).header.validate(crate::sdt::Signature::MADT)?;
-
-    /*
-     * If the MADT doesn't contain another supported interrupt model (either APIC, SAPIC, X2APIC
-     * or GIC), and the system supports the legacy i8259 PIC, recommend that.
-     * TODO: It's not clear how trustworthy this field is - should we be relying on it in any
-     * way?
-     */
-    if (*mapping).supports_8259() {
-        acpi.interrupt_model = Some(InterruptModel::Pic);
-    }
-
-    /*
-     * We first do a pass through the MADT to determine which interrupt model is being used.
-     */
-    for entry in (*mapping).entries() {
-        match entry {
-            MadtEntry::LocalApic(_) |
-            MadtEntry::IoApic(_) |
-            MadtEntry::InterruptSourceOverride(_) |
-            MadtEntry::NmiSource(_) |   // TODO: is this one used by more than one model?
-            MadtEntry::LocalApicNmi(_) |
-            MadtEntry::LocalApicAddressOverride(_) => {
-                acpi.interrupt_model = Some(parse_apic_model(acpi, mapping)?);
-                break;
-            }
-
-            MadtEntry::IoSapic(_) |
-            MadtEntry::LocalSapic(_) |
-            MadtEntry::PlatformInterruptSource(_) => {
-                unimplemented!();
-            }
-
-            MadtEntry::LocalX2Apic(_) |
-            MadtEntry::X2ApicNmi(_) => {
-                unimplemented!();
-            }
-
-            MadtEntry::Gicc(_) |
-            MadtEntry::Gicd(_) |
-            MadtEntry::GicMsiFrame(_) |
-            MadtEntry::GicRedistributor(_) |
-            MadtEntry::GicInterruptTranslationService(_) => {
-                unimplemented!();
-            }
-        }
-    }
-
-    Ok(())
-}
-
-/// This parses the MADT and gathers information about a APIC interrupt model. We error if we
-/// encounter an entry that doesn't configure the APIC.
-fn parse_apic_model(acpi: &mut Acpi, mapping: &PhysicalMapping<Madt>) -> Result<InterruptModel, AcpiError> {
-    use crate::interrupt::LocalInterruptLine;
-
-    let mut local_apic_address = (*mapping).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 (*mapping).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::LocalApic(_) => processor_count += 1,
-            _ => (),
-        }
-    }
-
-    let mut io_apics = Vec::with_capacity(io_apic_count);
-    let mut interrupt_source_overrides = Vec::with_capacity(iso_count);
-    let mut nmi_sources = Vec::with_capacity(nmi_source_count);
-    let mut local_apic_nmi_lines = Vec::with_capacity(local_nmi_line_count);
-    acpi.application_processors = Vec::with_capacity(processor_count.saturating_sub(1)); // Subtract one for the BSP
-
-    for entry in (*mapping).entries() {
-        match entry {
-            MadtEntry::LocalApic(ref 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 = acpi.boot_processor.is_some();
-                let is_disabled = !unsafe { 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, local_apic_id: entry.apic_id, state, is_ap };
-
-                if is_ap {
-                    acpi.application_processors.push(processor);
-                } else {
-                    acpi.boot_processor = Some(processor);
-                }
-            }
-
-            MadtEntry::IoApic(ref 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(ref 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(ref 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(ref 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::LocalApicAddressOverride(ref entry) => {
-                local_apic_address = entry.local_apic_address;
-            }
-
-            _ => {
-                return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
-            }
-        }
-    }
-
-    Ok(InterruptModel::Apic(Apic {
-        local_apic_address,
-        io_apics,
-        local_apic_nmi_lines,
-        interrupt_source_overrides,
-        nmi_sources,
-        also_has_legacy_pics: (*mapping).supports_8259(),
-    }))
-}
-
 fn parse_mps_inti_flags(flags: u16) -> Result<(Polarity, TriggerMode), AcpiError> {
     let polarity = match flags.get_bits(0..2) {
         0b00 => Polarity::SameAsBus,

+ 19 - 9
acpi/src/mcfg.rs

@@ -1,4 +1,4 @@
-use crate::{handler::PhysicalMapping, sdt::SdtHeader, Acpi, AcpiError};
+use crate::{sdt::SdtHeader, AcpiError, AcpiHandler, AcpiTable, AcpiTables};
 use alloc::vec::Vec;
 use core::{mem, slice};
 
@@ -13,6 +13,18 @@ pub struct PciConfigRegions {
 }
 
 impl PciConfigRegions {
+    pub fn new<H>(tables: &AcpiTables<H>) -> Result<PciConfigRegions, AcpiError>
+    where
+        H: AcpiHandler,
+    {
+        let mcfg = unsafe {
+            tables
+                .get_sdt::<Mcfg>(crate::sdt::Signature::MCFG)?
+                .ok_or(AcpiError::TableMissing(crate::sdt::Signature::MCFG))?
+        };
+        Ok(PciConfigRegions { regions: mcfg.entries().iter().map(|&entry| entry).collect() })
+    }
+
     /// 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> {
@@ -40,6 +52,12 @@ pub(crate) struct Mcfg {
     // Followed by `n` entries with format `McfgEntry`
 }
 
+impl AcpiTable for Mcfg {
+    fn header(&self) -> &SdtHeader {
+        &self.header
+    }
+}
+
 impl Mcfg {
     fn entries(&self) -> &[McfgEntry] {
         let length = self.header.length as usize - mem::size_of::<Mcfg>();
@@ -64,11 +82,3 @@ struct McfgEntry {
     bus_number_end: u8,
     _reserved: u32,
 }
-
-pub(crate) fn parse_mcfg(acpi: &mut Acpi, mapping: &PhysicalMapping<Mcfg>) -> Result<(), AcpiError> {
-    (*mapping).header.validate(crate::sdt::Signature::MCFG)?;
-
-    acpi.pci_config_regions =
-        Some(PciConfigRegions { regions: mapping.entries().iter().map(|&entry| entry).collect() });
-    Ok(())
-}

+ 161 - 0
acpi/src/platform.rs

@@ -0,0 +1,161 @@
+use crate::{fadt::Fadt, madt::Madt, AcpiError, AcpiHandler, AcpiTables, PowerProfile};
+use alloc::vec::Vec;
+
+#[derive(Debug)]
+pub struct IoApic {
+    pub id: u8,
+    pub address: u32,
+    pub global_system_interrupt_base: u32,
+}
+
+#[derive(Debug)]
+pub struct NmiLine {
+    pub processor: NmiProcessor,
+    pub line: LocalInterruptLine,
+}
+
+#[derive(Debug)]
+pub enum LocalInterruptLine {
+    Lint0,
+    Lint1,
+}
+
+#[derive(Debug)]
+pub enum NmiProcessor {
+    All,
+    /// Refers to a processor with the given UID. This is stored as a `u32`, but should be casted to `u8` when the
+    /// DSDT uses the deprecated `DefProcessor` operator to define processor UIDs.
+    ProcessorUid(u32),
+}
+
+#[derive(Debug)]
+pub enum Polarity {
+    SameAsBus,
+    ActiveHigh,
+    ActiveLow,
+}
+
+#[derive(Debug)]
+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)]
+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)]
+pub struct NmiSource {
+    pub global_system_interrupt: u32,
+    pub polarity: Polarity,
+    pub trigger_mode: TriggerMode,
+}
+
+#[derive(Debug)]
+pub struct Apic {
+    pub local_apic_address: u64,
+    pub io_apics: Vec<IoApic>,
+    pub local_apic_nmi_lines: Vec<NmiLine>,
+    pub interrupt_source_overrides: Vec<InterruptSourceOverride>,
+    pub nmi_sources: Vec<NmiSource>,
+
+    /// 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,
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum InterruptModel {
+    /// This model is only chosen when the MADT does not describe another interrupt model. On `x86_64` platforms,
+    /// this probably means only the legacy i8259 PIC is present.
+    Unknown,
+
+    /// Describes an interrupt controller based around the Advanced Programmable Interrupt
+    /// Controllers. 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),
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ProcessorState {
+    /// A processor in this state is unusable, and you must not attempt to bring it up.
+    Disabled,
+
+    /// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
+    /// but may be brought up.
+    WaitingForSipi,
+
+    /// A Running processor is currently brought up and running code.
+    Running,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Processor {
+    pub processor_uid: u8,
+    pub local_apic_id: u8,
+
+    /// The state of this processor. Always check that the processor is not `Disabled` before
+    /// attempting to bring it up!
+    pub state: ProcessorState,
+
+    /// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
+    /// When the bootloader is entered, the BSP is the only processor running code. To run code on
+    /// more than one processor, you need to "bring up" the APs.
+    pub is_ap: bool,
+}
+
+pub struct ProcessorInfo {
+    pub boot_processor: Processor,
+    /// Application processors should be brought up in the order they're defined in this list.
+    pub application_processors: Vec<Processor>,
+}
+
+/// `PlatformInfo` allows the collection of some basic information about the platform from some of the fixed-size
+/// 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.
+pub struct PlatformInfo {
+    pub power_profile: PowerProfile,
+    pub interrupt_model: InterruptModel,
+    /// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
+    /// interrupt model. That information is stored here, if present.
+    pub processor_info: Option<ProcessorInfo>,
+    /*
+     * TODO: we could provide a nice view of the hardware register blocks in the FADT here.
+     */
+}
+
+impl PlatformInfo {
+    pub fn new<H>(tables: &AcpiTables<H>) -> Result<PlatformInfo, AcpiError>
+    where
+        H: AcpiHandler,
+    {
+        let fadt = unsafe {
+            tables
+                .get_sdt::<Fadt>(crate::sdt::Signature::FADT)?
+                .ok_or(AcpiError::TableMissing(crate::sdt::Signature::FADT))?
+        };
+        let power_profile = fadt.power_profile();
+
+        let madt = unsafe { tables.get_sdt::<Madt>(crate::sdt::Signature::MADT)? };
+        let (interrupt_model, processor_info) = match madt {
+            Some(madt) => madt.parse_interrupt_model()?,
+            None => (InterruptModel::Unknown, None),
+        };
+
+        Ok(PlatformInfo { power_profile, interrupt_model, processor_info })
+    }
+}

+ 0 - 90
acpi/src/rsdp.rs

@@ -1,90 +0,0 @@
-use super::AcpiError;
-use core::{slice, str};
-
-/// 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_SYSTEM_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`.
-#[repr(C, packed)]
-pub(crate) 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 {
-    /// Checks that:
-    ///     1) The signature is correct
-    ///     2) The checksum is correct
-    ///     3) For Version 2.0+, that the extension checksum is correct
-    pub(crate) fn validate(&self) -> Result<(), AcpiError> {
-        const RSDP_V1_LENGTH: usize = 20;
-
-        // Check the signature
-        if &self.signature != b"RSD PTR " {
-            return Err(AcpiError::RsdpIncorrectSignature);
-        }
-
-        // Check the OEM id is valid UTF8 (allows use of unwrap)
-        if str::from_utf8(&self.oem_id).is_err() {
-            return Err(AcpiError::RsdpInvalidOemId);
-        }
-
-        /*
-         * `self.length` doesn't exist on ACPI version 1.0, so we mustn't rely on it. Instead,
-         * check for version 1.0 and use a hard-coded length instead.
-         */
-        let length = if self.revision > 0 {
-            // For Version 2.0+, include the number of bytes specified by `length`
-            self.length as usize
-        } else {
-            RSDP_V1_LENGTH
-        };
-
-        let bytes = unsafe { slice::from_raw_parts(self as *const Rsdp as *const u8, length) };
-        let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
-
-        if sum != 0 {
-            return Err(AcpiError::RsdpInvalidChecksum);
-        }
-
-        Ok(())
-    }
-
-    pub(crate) fn oem_id<'a>(&'a self) -> &'a str {
-        str::from_utf8(&self.oem_id).unwrap()
-    }
-
-    pub(crate) fn revision(&self) -> u8 {
-        self.revision
-    }
-
-    pub(crate) fn rsdt_address(&self) -> u32 {
-        self.rsdt_address
-    }
-
-    pub(crate) fn xsdt_address(&self) -> u64 {
-        assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
-        self.xsdt_address
-    }
-}

+ 0 - 104
acpi/src/rsdp_search.rs

@@ -1,104 +0,0 @@
-use crate::{parse_validated_rsdp, rsdp::Rsdp, Acpi, AcpiError, AcpiHandler};
-use core::{mem, ops::RangeInclusive};
-use log::warn;
-
-/// The pointer to the EBDA (Extended Bios Data Area) start segment pointer
-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: &'static [u8; 8] = b"RSD PTR ";
-
-/// Find the begining of the EBDA (Extended Bios Data Area) and return `None` if the ptr at
-/// `0x40e` is invalid.
-pub fn find_search_areas<H>(handler: &mut H) -> [RangeInclusive<usize>; 2]
-where
-    H: AcpiHandler,
-{
-    // Read base segment from BIOS area. This is not always given by the bios, so it needs to be
-    // checked. We left shift 4 because it is a segment ptr.
-    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;
-    handler.unmap_physical_region(ebda_start_mapping);
-
-    [
-        // Main bios area below 1 mb
-        // In practice (from my [Restioson's] testing, at least), the RSDP is more often here than
-        // the in EBDA. Also, if we cannot find the EBDA, then we don't want to search the largest
-        // possible EBDA first.
-        RSDP_BIOS_AREA_START..=RSDP_BIOS_AREA_END,
-        // Check if base segment ptr is in valid range for EBDA base
-        if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
-            // First kb 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
-        },
-    ]
-}
-
-/// This is the entry point of `acpi` if you have no information except that the machine is running
-/// BIOS and not UEFI. It maps the RSDP, works out what version of ACPI the hardware supports, and
-/// passes the physical address of the RSDT/XSDT to `parse_rsdt`.
-///
-/// # Unsafety
-///
-/// This function is unsafe because it may read from protected memory if the computer is using UEFI.
-/// Only use this function if you are sure the computer is using BIOS.
-pub unsafe fn search_for_rsdp_bios<H>(handler: &mut H) -> Result<Acpi, AcpiError>
-where
-    H: AcpiHandler,
-{
-    // The areas that will be searched for the RSDP
-    let areas = find_search_areas(handler);
-
-    // On x86 it is more efficient to map 4096 bytes at a time because of how paging works
-    let mut area_mapping = handler.map_physical_region::<[[u8; 8]; 0x1000 / 8]>(
-        areas[0].clone().next().unwrap() & !0xfff, // Get frame addr
-        0x1000,
-    );
-
-    // Signature is always on a 16 byte boundary so only search there
-    for address in areas.iter().flat_map(|i| i.clone()).step_by(16) {
-        let mut mapping_start = area_mapping.physical_start as usize;
-        if !(mapping_start..mapping_start + 0x1000).contains(&address) {
-            handler.unmap_physical_region(area_mapping);
-            area_mapping = handler.map_physical_region::<[[u8; 8]; 0x1000 / 8]>(
-                address & !0xfff, // Get frame addr
-                0x1000,
-            );
-
-            // Update if mapping remapped
-            mapping_start = area_mapping.physical_start as usize;
-        }
-
-        let index = (address - mapping_start) / 8;
-        let signature = (*area_mapping)[index];
-
-        if signature != *RSDP_SIGNATURE {
-            continue;
-        }
-
-        let rsdp_mapping = handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>());
-
-        if let Err(e) = (*rsdp_mapping).validate() {
-            warn!("Invalid RSDP found at 0x{:x}: {:?}", address, e);
-            continue;
-        }
-
-        handler.unmap_physical_region(area_mapping);
-        return parse_validated_rsdp(handler, rsdp_mapping);
-    }
-
-    Err(AcpiError::NoValidRsdp)
-}

+ 8 - 65
acpi/src/sdt.rs

@@ -1,6 +1,5 @@
-use crate::{fadt::Fadt, hpet::HpetTable, madt::Madt, mcfg::Mcfg, Acpi, AcpiError, AcpiHandler, AmlTable};
+use crate::{AcpiError, AcpiHandler};
 use core::{fmt, mem, mem::MaybeUninit, str};
-use log::{trace, warn};
 
 pub const ACPI_VERSION_2_0: u8 = 20;
 
@@ -17,7 +16,7 @@ impl<T: Copy, const MIN_VERSION: u8> ExtendedField<T, MIN_VERSION> {
     /// If a bogus ACPI version is passed, this function may access uninitialised data, which is unsafe.
     pub unsafe fn access(&self, version: u8) -> Option<T> {
         if version >= MIN_VERSION {
-            Some(self.0.assume_init())
+            Some(unsafe { self.0.assume_init() })
         } else {
             None
         }
@@ -57,6 +56,9 @@ impl<T: Copy, const MIN_VERSION: u8> ExtendedField<T, MIN_VERSION> {
 ///     "SRAT" - System Resource Affinity Table
 ///     "SSDT" - Secondary System Description Table
 ///     "XSDT" - eXtended System Descriptor Table
+///
+/// We've come across some more ACPI tables in the wild:
+///     "WAET" - Windows ACPI Emulated device Table
 #[derive(Clone, Copy)]
 #[repr(C, packed)]
 pub struct SdtHeader {
@@ -118,7 +120,7 @@ impl SdtHeader {
     }
 }
 
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 #[repr(transparent)]
 pub struct Signature([u8; 4]);
 
@@ -150,70 +152,11 @@ impl fmt::Debug for Signature {
 
 /// Takes the physical address of an SDT, and maps, clones and unmaps its header. Useful for
 /// finding out how big it is to map it correctly later.
-pub(crate) fn peek_at_sdt_header<H>(handler: &mut H, physical_address: usize) -> SdtHeader
+pub(crate) fn peek_at_sdt_header<H>(handler: &H, physical_address: usize) -> SdtHeader
 where
     H: AcpiHandler,
 {
     let mapping =
         unsafe { handler.map_physical_region::<SdtHeader>(physical_address, mem::size_of::<SdtHeader>()) };
-    let header = (*mapping).clone();
-    handler.unmap_physical_region(mapping);
-
-    header
-}
-
-/// This takes the physical address of an SDT, maps it correctly and dispatches it to whatever
-/// function parses that table.
-pub(crate) fn dispatch_sdt<H>(acpi: &mut Acpi, handler: &mut H, physical_address: usize) -> Result<(), AcpiError>
-where
-    H: AcpiHandler,
-{
-    let header = peek_at_sdt_header(handler, physical_address);
-    trace!("Found ACPI table with signature {:?} and length {:?}", header.signature, header.length);
-
-    /*
-     * For a recognised signature, a new physical mapping should be created with the correct type
-     * and length, and then the dispatched to the correct function to actually parse the table.
-     */
-    match header.signature {
-        Signature::FADT => {
-            let fadt_mapping =
-                unsafe { handler.map_physical_region::<Fadt>(physical_address, mem::size_of::<Fadt>()) };
-            crate::fadt::parse_fadt(acpi, handler, &fadt_mapping)?;
-            handler.unmap_physical_region(fadt_mapping);
-        }
-
-        Signature::HPET => {
-            let hpet_mapping =
-                unsafe { handler.map_physical_region::<HpetTable>(physical_address, mem::size_of::<HpetTable>()) };
-            crate::hpet::parse_hpet(acpi, &hpet_mapping)?;
-            handler.unmap_physical_region(hpet_mapping);
-        }
-
-        Signature::MADT => {
-            let madt_mapping =
-                unsafe { handler.map_physical_region::<Madt>(physical_address, header.length as usize) };
-            crate::madt::parse_madt(acpi, handler, &madt_mapping)?;
-            handler.unmap_physical_region(madt_mapping);
-        }
-
-        Signature::MCFG => {
-            let mcfg_mapping =
-                unsafe { handler.map_physical_region::<Mcfg>(physical_address, header.length as usize) };
-            crate::mcfg::parse_mcfg(acpi, &mcfg_mapping)?;
-            handler.unmap_physical_region(mcfg_mapping);
-        }
-
-        Signature::SSDT => acpi.ssdts.push(AmlTable::new(physical_address, header.length)),
-
-        signature => {
-            /*
-             * We don't recognise this signature. Early on, this probably just means we don't
-             * have support yet, but later on maybe this should become an actual error
-             */
-            warn!("Unsupported SDT signature: {}. Skipping.", signature);
-        }
-    }
-
-    Ok(())
+    (*mapping).clone()
 }

+ 13 - 0
rsdp/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "rsdp"
+version = "1.0.0"
+authors = ["Isaac Woods", "Restioson"]
+repository = "https://github.com/rust-osdev/acpi"
+description = "Zero-allocation library for locating and parsing the RSDP, the first ACPI table"
+categories = ["hardware-support", "no-std"]
+readme = "../README.md"
+license = "MIT/Apache-2.0"
+edition = "2018"
+
+[dependencies]
+log = "0.4"

+ 51 - 0
rsdp/src/handler.rs

@@ -0,0 +1,51 @@
+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.
+pub struct PhysicalMapping<H, T>
+where
+    H: AcpiHandler,
+{
+    pub physical_start: usize,
+    pub virtual_start: NonNull<T>,
+    pub region_length: usize, // Can be equal or larger than size_of::<T>()
+    pub mapped_length: usize, // Differs from `region_length` if padding is added for alignment
+    pub handler: H,
+}
+
+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) {
+        self.handler.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 + Sized {
+    /// 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`.
+    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.
+    fn unmap_physical_region<T>(&self, region: &PhysicalMapping<Self, T>);
+}

+ 219 - 0
rsdp/src/lib.rs

@@ -0,0 +1,219 @@
+//! 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.
+//!
+//! 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]
+
+pub mod handler;
+
+use core::{mem, ops::RangeInclusive, slice, str};
+use handler::{AcpiHandler, PhysicalMapping};
+use log::warn;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum RsdpError {
+    NoValidRsdp,
+    IncorrectSignature,
+    InvalidOemId,
+    InvalidChecksum,
+}
+
+/// 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_SYSTEM_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`.
+#[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`.
+    pub unsafe fn search_for_on_bios<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, RsdpError>
+    where
+        H: AcpiHandler,
+    {
+        let areas = find_search_areas(handler.clone());
+
+        /*
+         * We map a page at a time, as mapping the entire areas puts a lot of burden on a naive paging
+         * implementation.
+         */
+        let mut area_mapping = handler.map_physical_region::<[[u8; 8]; 0x1000 / 8]>(
+            areas[0].clone().next().unwrap() & !0xfff, // Get frame addr
+            0x1000,
+        );
+
+        // Signature is always on a 16 byte boundary so only search there
+        for address in areas.iter().flat_map(|i| i.clone()).step_by(16) {
+            let mut mapping_start = area_mapping.physical_start as usize;
+            if !(mapping_start..mapping_start + 0x1000).contains(&address) {
+                /*
+                 * This replaces the current mapping, unmapping the last one.
+                 */
+                area_mapping = handler.map_physical_region::<[[u8; 8]; 0x1000 / 8]>(
+                    address & !0xfff, // Get frame addr
+                    0x1000,
+                );
+
+                // Update if mapping remapped
+                mapping_start = area_mapping.physical_start as usize;
+            }
+
+            let index = (address - mapping_start) / 8;
+            let signature = (*area_mapping)[index];
+
+            if signature != *RSDP_SIGNATURE {
+                continue;
+            }
+
+            let rsdp_mapping = handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>());
+
+            if let Err(e) = (*rsdp_mapping).validate() {
+                warn!("Invalid RSDP found at 0x{:x}: {:?}", address, e);
+                continue;
+            }
+
+            return Ok(rsdp_mapping);
+        }
+
+        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> {
+        const RSDP_V1_LENGTH: usize = 20;
+
+        // Check the signature
+        if &self.signature != b"RSD PTR " {
+            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 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 xsdt_address(&self) -> u64 {
+        assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
+        self.xsdt_address
+    }
+}
+
+/// Find the areas we should search for the RSDP in.
+pub fn find_search_areas<H>(handler: H) -> [RangeInclusive<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 1mb. 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,
+        // Check if base segment ptr is in valid range for EBDA base
+        if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
+            // First kb 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
+        },
+    ]
+}
+
+/// 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: &'static [u8; 8] = b"RSD PTR ";