acpi Rust Library

repository·main·Indexed 42 days ago

https://github.com/rust-osdev/acpi

A pure-Rust library for interacting with the Advanced Configuration and Power Interface (ACPI) version 6.1.1. It supports both no-alloc environments (e.g., bootloaders) and full OS environments. Key features include RSDP searching, ACPI table enumeration, a dynamic interpreter for ACPI Machine Language (AML) bytecode in DSDT and SSDT tables, and support for x64, ARM, and RISC-V platforms.

Tokens
19.8K
Snippets
57
Records
93
Agent score
52%

What's inside acpi

  1. Overview of the acpi crate

    main

    The acpi crate is a Rust library designed for interacting with the Advanced Configuration and Power Interface (ACPI). It enables operating systems or bootloaders to perform power management, device discovery, and configuration on x64, ARM, and RISC-V platforms.

    Key capabilities include:

    • Searching for the Root System Description Pointer (RSDP).
    • Enumerating available ACPI tables.
    • Interacting with tables via raw structures (no-alloc).
    • High-level interfaces for static tables (requires alloc).
    • A dynamic interpreter for ACPI Machine Language (AML) bytecode found in DSDT and SSDT tables (requires alloc).
  2. Choose between no-alloc and alloc APIs

    main

    The acpi crate provides two distinct modes of operation depending on your environment:

    No-alloc Mode

    If you are working in an environment without a memory allocator (such as a bootloader), you can use the limited API. This allows you to:

    • Search for the RSDP.
    • Enumerate available tables.
    • Interact with tables using their raw structures.

    Alloc Mode

    By enabling the alloc feature (which is enabled by default), you gain access to a richer set of tools that require a memory allocator:

    • Higher-level interfaces for interacting with static tables.
    • A dynamic interpreter for AML (ACPI Machine Language) bytecode used in DSDT and SSDT tables.
  3. How AML object storage and copying work

    main

    The interpreter provides two primary mechanisms for moving data between objects:

    do_store (DefStore)

    Stores an object into a target. The behavior depends on the target's reference kind:

    • Locals/Args: Overwritten, unless they are references, in which case the referenced object is overwritten (with implicit casting).
    • Named Objects: Stored into with implicit casting.
    • Index References: Behave like locals.
    • Invalid Types: Storing to RefOf or Unresolved references returns AmlError::StoreToInvalidReferenceType.

    do_copy_object (DefCopyObject)

    Copies an object into a target. The target must be a reference:

    • Named/Local References: The underlying object is overwritten.
    • Arg References: If the argument is a reference to another reference, the inner object is overwritten; otherwise, the argument itself is overwritten.
    • Invalid Types: Storing to RefOf or Unresolved references returns AmlError::StoreToInvalidReferenceType.
  4. How AML field reads and writes work

    main

    The interpreter handles FieldUnit access by performing native reads/writes to underlying OpRegions.

    Field Read Logic

    When reading a field, the interpreter:

    1. Determines if the field requires a Buffer or an Integer based on bit length.
    2. Identifies the FieldUnitKind (Normal, Bank, or Index).
    3. For Index fields, it updates the associated Index register before reading.
    4. Breaks the read into multiple native accesses that respect the region's access width.
    5. Uses object::copy_bits to assemble the bits into the final result.

    Field Write Logic

    When writing to a field, the interpreter:

    1. Determines the FieldUpdateRule to decide how to handle surrounding bits:
      • Preserve: Performs a read of the existing bits first to ensure only the target bits are changed.
      • WriteAsOnes: Fills the access width with 0xff.
      • WriteAsZeros: Fills the access width with 0x00.
    2. For Index fields, it updates the Index register before writing.
    3. Performs native writes to the underlying OpRegion.
  5. Understand the Rsdp structure and versioning

    main

    The Rsdp (Root System Description Pointer) is the entry point for ACPI. It points to the RSDT (Root System Description Table) or XSDT (Extended System Description Table).

    Versioning and Fields

    • ACPI 1.0: Only the rsdt_address field is valid. Accessing extended fields will cause an assertion failure.
    • ACPI 2.0+: The revision field will be greater than 0. You should use xsdt_address (the 64-bit pointer) instead of rsdt_address (the 32-bit pointer) for better compatibility on 64-bit systems.

    Validation

    You can call validate() on an Rsdp instance to ensure:

    1. The signature matches RSD PTR .
    2. The oem_id is valid UTF-8.
    3. The checksum (standard or extended, depending on revision) is correct.
  6. PCI Route Types

    main

    The PciRouteType enum defines how a PCI interrupt is connected to the interrupt controller:

    • Gsi(u32): The interrupt is hard-coded to a specific Global System Interrupt (GSI) number.
    • LinkObject(AmlName): The interrupt is linked to a link object. This object contains _PRS, _CRS fields and a _SRS method used to allocate the interrupt. Note that some platforms (like QEMU's q35 chipset) use link objects but do not support changing the interrupt via _SRS.
  7. Handle test results with `RunTestResult`

    main

    The run_test family of functions returns a RunTestResult<T>. This enum distinguishes between successful tests, logical failures, and interpreter panics:

    • Pass(Interpreter<T>): The test passed, and the interpreter is still valid for further use.
    • Failed(Interpreter<T>, TestFailureReason): The test failed (e.g., due to parsing or execution errors), but the interpreter is still valid.
    • Panicked: The test caused a panic in the interpreter. In this case, the interpreter is no longer valid and must be replaced.
  8. Understand ProcessorInfo and Processor states

    main

    The ProcessorInfo struct provides a view of the system's processor topology, distinguishing between the boot_processor (BSP) and a list of application_processors (APs).

    Each Processor contains:

    • processor_uid: The unique ID from AML.
    • local_apic_id: The APIC ID.
    • is_ap: Boolean indicating if it is an Application Processor.
    • state: The current ProcessorState.

    ProcessorState

    • Disabled: The processor is unusable and must not be brought up.
    • WaitingForSipi: The processor is currently inactive but can be brought up.
    • Running: The processor is actively executing code.
  9. How PhysicalMapping works

    main

    A PhysicalMapping<H, T> represents a region of physical memory that has been mapped into the virtual address space.

    Key Features:

    • Automatic Unmapping: When the PhysicalMapping is dropped, it automatically calls handler.unmap_physical_region to clean up the mapping.
    • Access: You can access the underlying data T by dereferencing the mapping (if T is Unpin) or by using .get() to obtain a Pin<&T>.
    • Safety: The mapping is created via unsafe { PhysicalMapping::new(...) }. The user must ensure the physical address is valid and the size is sufficient for T.
    let mapping = unsafe { PhysicalMapping::<_, MyTable>::new(phys_addr, size, handler) };
    let table_ref = &*mapping; // Deref access
  10. How AML Field Creation works

    main

    The interpreter provides several opcodes to create field mappings within buffers or regions. These allow the AML to access specific bits or bytes within a larger data structure.

    • Fixed-size Fields: CreateByteField, CreateWordField, CreateDWordField, and CreateQWordField create fields of 8, 16, 32, or 64 bits respectively, based on an index provided in the opcode arguments.
    • Bit-level Fields: CreateBitField allows for arbitrary bit-width fields.
    • Custom Fields: CreateField and Field allow defining fields with specific bit offsets and lengths, often used in conjunction with regions.
    • Bank Fields: BankField is used to access fields within a specific bank of a region.
  11. How AML name resolution works

    main

    In ACPI Machine Language (AML), names (represented as NamePaths) are resolved against a namespace. The resolve_name_path mechanism follows these rules:

    • Indirection: A name can resolve to another name. To prevent infinite loops in circular firmware definitions, the interpreter follows a bounded number of indirections (MAX_NAME_PATH_INDIRECTIONS).
    • Resolution Behaviour: Different operations use different resolution strategies via ResolveBehaviour:
      • TermArg: Standard resolution for most operands.
      • SimpleName: Resolves to the object itself (used when an operation needs the object, not its value).
      • SuperName: Similar to SimpleName, but can also resolve to Debug objects.
      • SuperNameIfExists: Resolves to an unresolved reference if the name does not exist.
      • Target: Used for destination objects in store operations.
      • AsPackageElements: Used for DefPackage and DefVarPackage. Names are kept with their scope and resolved only when the reference is used, allowing for out-of-order table loading.
  12. Iterate over MADT entries

    main

    The Madt (Multiple Interrupt Controller) structure is a variable-sized table containing a static header followed by multiple entries. Because the table size is dynamic, the Madt type is !Unpin to prevent it from being moved in memory, which would invalidate the pointers used to access its entries.

    To access the entries, you must wrap the Madt in a Pin<&Madt> and call .entries(), which returns a MadtEntryIter. This iterator yields MadtEntry enums representing the different interrupt controller models supported (e.g., APIC, SAPIC, GIC, etc.).

    // Assuming `madt_ptr` is a Pin<&Madt>
    for entry in madt_ptr.entries() {
        match entry {
            MadtEntry::LocalApic(local_apic) => {
                // Handle Local APIC entry
            }
            MadtEntry::Gicc(gicc) => {
                // Handle ARM GICC entry
            }
            // ... handle other variants
            _ => {}
        }
    }