1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//! 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 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.
//!
//! 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.
//!
//! `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`](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.

/*
 * 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)]

extern crate alloc;
#[cfg_attr(test, macro_use)]
#[cfg(test)]
extern crate std;

pub mod fadt;
pub mod hpet;
pub mod madt;
pub mod mcfg;
pub mod platform;
pub mod sdt;

pub use crate::{
    fadt::PowerProfile,
    hpet::HpetInfo,
    madt::MadtError,
    mcfg::PciConfigRegions,
    platform::{interrupt::InterruptModel, PlatformInfo},
};
pub use rsdp::{
    handler::{AcpiHandler, PhysicalMapping},
    RsdpError,
};

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 {
    Rsdp(RsdpError),

    SdtInvalidSignature(Signature),
    SdtInvalidOemId(Signature),
    SdtInvalidTableId(Signature),
    SdtInvalidChecksum(Signature),

    TableMissing(Signature),
    InvalidFacsAddress,
    InvalidDsdtAddress,
    InvalidMadt(MadtError),
    InvalidGenericAddress,
}

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,
}

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(AcpiError::Rsdp)?;

        Self::from_validated_rsdp(handler, rsdp_mapping)
    }

    /// Search for the RSDP on a BIOS platform. This accesses BIOS-specific memory locations and will probably not
    /// work on UEFI platforms. See [Rsdp::search_for_rsdp_bios](rsdp_search::Rsdp::search_for_rsdp_bios) for
    /// details.
    pub unsafe fn search_for_rsdp_bios(handler: H) -> Result<AcpiTables<H>, AcpiError> {
        let rsdp_mapping = unsafe { Rsdp::search_for_on_bios(handler.clone()) }.map_err(AcpiError::Rsdp)?;
        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) }
        }
    }

    /// 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.add(i) 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.add(i) 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 crate::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)
    }
}

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,
}

/// All types representing ACPI tables should implement this trait.
pub trait AcpiTable {
    fn header(&self) -> &sdt::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,
        }
    }
}