Chapter 23: ACPI Integration

ACPI tables are the bridge between firmware and the operating system, describing platform topology, power management, and device configuration in a machine-readable format.


Table of Contents

  1. Chapter 23: ACPI Integration
    1. 23.1 What Is ACPI?
    2. 23.2 ACPI Architecture Overview
      1. 23.2.1 Table Discovery
    3. 23.3 Key ACPI Tables
      1. 23.3.1 RSDP (Root System Description Pointer)
      2. 23.3.2 XSDT (Extended System Description Table)
      3. 23.3.3 FADT (Fixed ACPI Description Table)
      4. 23.3.4 MADT (Multiple APIC Description Table)
      5. 23.3.5 MCFG (PCI Express Memory-Mapped Configuration Space)
      6. 23.3.6 DSDT and SSDT
    4. 23.4 ASL Basics
      1. 23.4.1 DefinitionBlock
      2. 23.4.2 Scopes and Devices
      3. 23.4.3 Methods
      4. 23.4.4 OperationRegion and Fields
      5. 23.4.5 Power Management (_PSx, _PRx Methods)
    5. 23.5 Installing ACPI Tables from UEFI Firmware
      1. 23.5.1 Installing a Static Table
      2. 23.5.2 Building a Dynamic MADT
      3. 23.5.3 Patching an Existing Table
    6. 23.6 SSDT Overlays
      1. 23.6.1 SSDT ASL Example
      2. 23.6.2 Loading SSDTs from Firmware
    7. 23.7 ACPI Table INF and DSC Integration
      1. 23.7.1 INF File for an ACPI Table Driver
      2. 23.7.2 ASL Compilation in the Build System
    8. 23.8 ACPI Debugging
      1. 23.8.1 acpidump
      2. 23.8.2 iasl Disassembly
      3. 23.8.3 Linux ACPI Debugging
      4. 23.8.4 Windows ACPI Debugging
      5. 23.8.5 Common ACPI Issues
    9. 23.9 Project Mu ACPI Management
      1. 23.9.1 ACPI Table Generation Patterns
      2. 23.9.2 DynamicTablesPkg
      3. 23.9.3 ACPI SDT Protocol for Runtime Table Access
    10. 23.10 Hardware-Reduced ACPI
    11. 23.11 Summary

23.1 What Is ACPI?

The Advanced Configuration and Power Interface (ACPI) is an open standard that defines how the operating system discovers and configures hardware, manages power states, and handles platform events. ACPI replaced older standards (APM, MPS, PnP BIOS) by providing a single, unified interface.

From a firmware developer’s perspective, ACPI is a set of data tables and executable bytecode (AML – ACPI Machine Language) that the firmware publishes in memory. The OS reads these tables to learn:

  • How many CPUs exist and their APIC IDs
  • Which interrupt controllers are present
  • How devices are connected (PCI topology, GPIO, I2C, SPI)
  • How to put devices and the platform into low-power states
  • How to handle thermal zones, battery status, and lid events
  • Which memory-mapped configuration regions exist

23.2 ACPI Architecture Overview

flowchart TD
    FW["UEFI Firmware"] -->|"Publishes tables\nin memory"| RSDP["RSDP\n(Root System\nDescription Pointer)"]
    RSDP --> XSDT["XSDT\n(Extended System\nDescription Table)"]
    XSDT --> FADT["FADT\n(Fixed ACPI\nDescription Table)"]
    XSDT --> MADT["MADT\n(Multiple APIC\nDescription Table)"]
    XSDT --> MCFG["MCFG\n(PCI Express\nConfig Space)"]
    XSDT --> HPET["HPET\n(High Precision\nEvent Timer)"]
    XSDT --> SSDT["SSDT(s)\n(Secondary System\nDescription Tables)"]
    XSDT --> OTHER["Other Tables\n(BGRT, SLIT,\nSRAT, DMAR, ...)"]
    FADT --> DSDT["DSDT\n(Differentiated System\nDescription Table)"]
    DSDT -->|"Contains"| AML["AML Bytecode\n(Device tree, methods,\npower management)"]

    style RSDP fill:#e74c3c,stroke:#c0392b,color:#fff
    style DSDT fill:#3498db,stroke:#2980b9,color:#fff
    style AML fill:#27ae60,stroke:#1e8449,color:#fff

23.2.1 Table Discovery

The OS locates ACPI tables through this chain:

  1. RSDP: Found via the UEFI Configuration Table (or EBDA in legacy BIOS). Contains the physical address of the XSDT.
  2. XSDT: An array of 64-bit physical addresses pointing to all other ACPI tables.
  3. Individual tables: Each table has a standard header with signature, length, revision, and checksum.

23.3 Key ACPI Tables

23.3.1 RSDP (Root System Description Pointer)

The RSDP is not a full table but a fixed-size structure that bootstraps ACPI table discovery:

typedef struct {
  CHAR8   Signature[8];      // "RSD PTR "
  UINT8   Checksum;          // Checksum of first 20 bytes
  CHAR8   OemId[6];          // OEM identification
  UINT8   Revision;          // 2 for ACPI 2.0+
  UINT32  RsdtAddress;       // Physical address of RSDT (32-bit)
  UINT32  Length;             // Length of this structure
  UINT64  XsdtAddress;       // Physical address of XSDT (64-bit)
  UINT8   ExtendedChecksum;  // Checksum of entire structure
  UINT8   Reserved[3];
} EFI_ACPI_RSDP;

In UEFI firmware, the RSDP is published in the EFI System Table’s Configuration Table array under gEfiAcpi20TableGuid.

23.3.2 XSDT (Extended System Description Table)

The XSDT contains an array of 64-bit physical addresses, each pointing to another ACPI table:

typedef struct {
  EFI_ACPI_DESCRIPTION_HEADER  Header;    // Signature = "XSDT"
  UINT64                       Entry[];   // Array of table pointers
} EFI_ACPI_XSDT;

23.3.3 FADT (Fixed ACPI Description Table)

The FADT describes fixed hardware features and contains the address of the DSDT:

Field Description
FIRMWARE_CTRL Physical address of FACS (Firmware ACPI Control Structure)
DSDT Physical address of the DSDT
PM1a_EVT_BLK Address of PM1a Event Register Block
PM1a_CNT_BLK Address of PM1a Control Register Block
PM_TMR_BLK Address of PM Timer Block
SCI_INT System Control Interrupt vector
SMI_CMD I/O port address for SMI command
ACPI_ENABLE Value to write to SMI_CMD to enable ACPI
FLAGS Feature flags (HW_REDUCED_ACPI, etc.)

23.3.4 MADT (Multiple APIC Description Table)

The MADT describes the interrupt controller topology:

//
// MADT entry types
//
// Type 0: Processor Local APIC
// Type 1: I/O APIC
// Type 2: Interrupt Source Override
// Type 9: Processor Local x2APIC
//
typedef struct {
  UINT8   Type;               // 0 = Local APIC
  UINT8   Length;              // 8
  UINT8   AcpiProcessorId;    // ACPI processor UID
  UINT8   ApicId;             // Local APIC ID
  UINT32  Flags;              // Bit 0: Enabled
} EFI_ACPI_MADT_LOCAL_APIC;

typedef struct {
  UINT8   Type;               // 1 = I/O APIC
  UINT8   Length;              // 12
  UINT8   IoApicId;           // I/O APIC ID
  UINT8   Reserved;
  UINT32  IoApicAddress;      // Physical address
  UINT32  GlobalSystemInterruptBase;
} EFI_ACPI_MADT_IO_APIC;

23.3.5 MCFG (PCI Express Memory-Mapped Configuration Space)

The MCFG table tells the OS where the PCI Express Enhanced Configuration Access Mechanism (ECAM) memory region is located:

typedef struct {
  UINT64  BaseAddress;       // Base address of ECAM
  UINT16  PciSegmentGroup;   // PCI segment group number
  UINT8   StartBusNumber;    // Start PCI bus number
  UINT8   EndBusNumber;      // End PCI bus number
  UINT32  Reserved;
} EFI_ACPI_MCFG_CONFIG_SPACE;

23.3.6 DSDT and SSDT

The DSDT (Differentiated System Description Table) and SSDTs (Secondary System Description Tables) contain AML bytecode that defines the device hierarchy, power management methods, and platform-specific logic. The DSDT is required; SSDTs are optional and additive.

23.4 ASL Basics

ACPI Source Language (ASL) is the high-level language used to write ACPI tables that contain executable logic. The ASL compiler (iasl) compiles ASL into AML (ACPI Machine Language) bytecode.

23.4.1 DefinitionBlock

Every ASL file starts with a DefinitionBlock:

DefinitionBlock ("dsdt.aml", "DSDT", 2, "MYOEM", "MYBOARD", 0x00000001)
{
    // Table contents go here
}

Parameters: output filename, table signature, compliance revision, OEM ID, OEM Table ID, OEM revision.

23.4.2 Scopes and Devices

ASL organizes hardware into a hierarchical namespace:

DefinitionBlock ("dsdt.aml", "DSDT", 2, "PROJMU", "BOARD01", 1)
{
    //
    // Root scope - the ACPI namespace root
    //
    Scope (\_SB)  // System Bus
    {
        //
        // CPU definitions
        //
        Device (PR00)  // Processor 0
        {
            Name (_HID, "ACPI0007")   // Processor Device HID
            Name (_UID, 0)
        }

        //
        // PCI Root Bridge
        //
        Device (PCI0)
        {
            Name (_HID, EisaId ("PNP0A08"))  // PCI Express Root Bridge
            Name (_CID, EisaId ("PNP0A03"))  // PCI Bus (compatible)
            Name (_SEG, 0)                    // PCI Segment Group
            Name (_BBN, 0)                    // Base Bus Number
            Name (_UID, 0)

            //
            // PCI device under the root bridge
            //
            Device (GFX0)  // Integrated Graphics
            {
                Name (_ADR, 0x00020000)  // PCI Device 2, Function 0
            }

            //
            // Another PCI device
            //
            Device (SATA)  // SATA Controller
            {
                Name (_ADR, 0x00170000)  // PCI Device 23, Function 0
            }
        }

        //
        // Power Button
        //
        Device (PWRB)
        {
            Name (_HID, EisaId ("PNP0C0C"))  // Power Button Device
        }
    }
}

23.4.3 Methods

Methods are executable ASL procedures that the OS calls:

Device (LID0)
{
    Name (_HID, EisaId ("PNP0C0D"))  // Lid Device

    //
    // _LID method returns lid state (0 = closed, 1 = open)
    //
    Method (_LID, 0, Serialized)
    {
        //
        // Read GPIO pin to determine lid state
        //
        If (\_SB.PCI0.LPCB.EC0.LIDS)
        {
            Return (1)  // Lid is open
        }
        Else
        {
            Return (0)  // Lid is closed
        }
    }
}

23.4.4 OperationRegion and Fields

OperationRegions define how ASL accesses hardware registers:

//
// Access an MMIO region at address 0xFED00000
//
OperationRegion (HPTM, SystemMemory, 0xFED00000, 0x400)
Field (HPTM, DWordAcc, Lock, Preserve)
{
    GCID, 32,    // General Capabilities and ID Register
    GPER, 32,    // General Period Register
    Offset (0xF0),
    MCFG, 64,    // Main Counter Value
}

//
// Access PCI configuration space for Device 31, Function 0
//
OperationRegion (LPCR, PCI_Config, 0x00, 0x100)
Field (LPCR, AnyAcc, NoLock, Preserve)
{
    Offset (0x60),
    PIRA, 8,     // PIRQ[A] Routing Control
    PIRB, 8,     // PIRQ[B] Routing Control
    PIRC, 8,     // PIRQ[C] Routing Control
    PIRD, 8,     // PIRQ[D] Routing Control
}

//
// Access I/O ports
//
OperationRegion (SYSI, SystemIO, 0x0072, 0x02)
Field (SYSI, ByteAcc, NoLock, Preserve)
{
    INDX, 8,     // CMOS Index Register
    DATA, 8,     // CMOS Data Register
}

//
// Access Embedded Controller
//
OperationRegion (ECOR, EmbeddedControl, 0x00, 0xFF)
Field (ECOR, ByteAcc, Lock, Preserve)
{
    Offset (0x10),
    BTMP, 16,    // Battery Temperature
    BCUR, 16,    // Battery Current
    BVOL, 16,    // Battery Voltage
    BCAP, 16,    // Battery Remaining Capacity
}

23.4.5 Power Management (_PSx, _PRx Methods)

Device (WIFI)
{
    Name (_ADR, 0x00140003)  // PCI Device 20, Function 3

    //
    // Power state methods
    //
    Method (_PS0, 0, Serialized)  // Enter D0 (fully on)
    {
        // Enable power to WiFi module
        Store (1, \_SB.PCI0.LPCB.GPIO.WFP0)
        Sleep (10)  // Wait 10ms for power stabilization
    }

    Method (_PS3, 0, Serialized)  // Enter D3 (off)
    {
        // Cut power to WiFi module
        Store (0, \_SB.PCI0.LPCB.GPIO.WFP0)
    }

    //
    // Power resource requirements
    //
    Name (_PR0, Package () { \_SB.PCI0.WFPW })  // D0 power resources
    Name (_PR3, Package () { \_SB.PCI0.WFPW })  // D3 power resources
}

//
// Power Resource definition
//
PowerResource (WFPW, 0, 0)
{
    Method (_STA, 0)
    {
        Return (\_SB.PCI0.LPCB.GPIO.WFP0)
    }

    Method (_ON, 0)
    {
        Store (1, \_SB.PCI0.LPCB.GPIO.WFP0)
    }

    Method (_OFF, 0)
    {
        Store (0, \_SB.PCI0.LPCB.GPIO.WFP0)
    }
}

23.5 Installing ACPI Tables from UEFI Firmware

UEFI firmware installs ACPI tables using the EFI_ACPI_TABLE_PROTOCOL. This protocol manages the RSDP, XSDT, and individual tables.

23.5.1 Installing a Static Table

Static ACPI tables are typically compiled from ASL sources and linked into the firmware as binary data:

#include <Protocol/AcpiTable.h>
#include <IndustryStandard/Acpi.h>

//
// The compiled AML table is linked as an external array
//
extern UINT8 DsdtTable[];

EFI_STATUS
InstallDsdtTable (
  VOID
  )
{
  EFI_STATUS               Status;
  EFI_ACPI_TABLE_PROTOCOL  *AcpiTable;
  UINTN                    TableKey;

  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID **)&AcpiTable
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Install the DSDT
  //
  Status = AcpiTable->InstallAcpiTable (
                        AcpiTable,
                        DsdtTable,
                        ((EFI_ACPI_DESCRIPTION_HEADER *)DsdtTable)->Length,
                        &TableKey
                        );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed to install DSDT: %r\n", Status));
  }

  return Status;
}

23.5.2 Building a Dynamic MADT

Some tables must be built dynamically because their content depends on the actual hardware configuration discovered at boot time. The MADT is a common example – the number of CPUs varies by platform:

#include <IndustryStandard/Acpi.h>
#include <Protocol/AcpiTable.h>
#include <Protocol/MpService.h>

EFI_STATUS
BuildAndInstallMadt (
  VOID
  )
{
  EFI_STATUS                          Status;
  EFI_MP_SERVICES_PROTOCOL            *MpServices;
  UINTN                               NumberOfProcessors;
  UINTN                               NumberOfEnabledProcessors;
  EFI_ACPI_TABLE_PROTOCOL             *AcpiTable;
  UINTN                               TableKey;
  UINT8                               *MadtBuffer;
  UINTN                               MadtSize;
  EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER  *MadtHeader;
  EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC_STRUCTURE           *LocalApic;
  EFI_ACPI_6_5_IO_APIC_STRUCTURE                        *IoApic;
  UINTN                               Index;
  UINTN                               Offset;

  //
  // Get processor count
  //
  Status = gBS->LocateProtocol (
                  &gEfiMpServiceProtocolGuid,
                  NULL,
                  (VOID **)&MpServices
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = MpServices->GetNumberOfProcessors (
                          MpServices,
                          &NumberOfProcessors,
                          &NumberOfEnabledProcessors
                          );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Calculate MADT size
  //
  MadtSize = sizeof (EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER) +
             (NumberOfProcessors *
              sizeof (EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC_STRUCTURE)) +
             sizeof (EFI_ACPI_6_5_IO_APIC_STRUCTURE);

  MadtBuffer = AllocateZeroPool (MadtSize);
  if (MadtBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Fill MADT header
  //
  MadtHeader = (EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER *)MadtBuffer;
  MadtHeader->Header.Signature = EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_SIGNATURE;
  MadtHeader->Header.Length    = (UINT32)MadtSize;
  MadtHeader->Header.Revision  = EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_REVISION;
  CopyMem (MadtHeader->Header.OemId, "PROJMU", 6);
  MadtHeader->Header.OemTableId     = SIGNATURE_64 ('B','O','A','R','D','0','0','1');
  MadtHeader->Header.OemRevision    = 1;
  MadtHeader->Header.CreatorId      = SIGNATURE_32 ('P','M','U',' ');
  MadtHeader->Header.CreatorRevision = 1;
  MadtHeader->LocalApicAddress      = 0xFEE00000;  // Standard Local APIC address
  MadtHeader->Flags                 = EFI_ACPI_6_5_PCAT_COMPAT;

  Offset = sizeof (EFI_ACPI_6_5_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER);

  //
  // Add Local APIC entries for each processor
  //
  for (Index = 0; Index < NumberOfProcessors; Index++) {
    LocalApic = (EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC_STRUCTURE *)(MadtBuffer + Offset);
    LocalApic->Type            = EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC;
    LocalApic->Length           = sizeof (EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC_STRUCTURE);
    LocalApic->AcpiProcessorUid = (UINT8)Index;
    LocalApic->ApicId           = (UINT8)Index;  // Simplified; real code queries MpServices
    LocalApic->Flags            = EFI_ACPI_6_5_LOCAL_APIC_ENABLED;
    Offset += sizeof (EFI_ACPI_6_5_PROCESSOR_LOCAL_APIC_STRUCTURE);
  }

  //
  // Add I/O APIC entry
  //
  IoApic = (EFI_ACPI_6_5_IO_APIC_STRUCTURE *)(MadtBuffer + Offset);
  IoApic->Type                    = EFI_ACPI_6_5_IO_APIC;
  IoApic->Length                  = sizeof (EFI_ACPI_6_5_IO_APIC_STRUCTURE);
  IoApic->IoApicId                = 2;
  IoApic->IoApicAddress           = 0xFEC00000;
  IoApic->GlobalSystemInterruptBase = 0;

  //
  // Install the MADT
  //
  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID **)&AcpiTable
                  );
  if (EFI_ERROR (Status)) {
    FreePool (MadtBuffer);
    return Status;
  }

  Status = AcpiTable->InstallAcpiTable (
                        AcpiTable,
                        MadtBuffer,
                        MadtSize,
                        &TableKey
                        );

  FreePool (MadtBuffer);
  return Status;
}

23.5.3 Patching an Existing Table

Sometimes a static DSDT needs runtime patching to reflect actual hardware:

EFI_STATUS
PatchDsdtTable (
  IN EFI_ACPI_DESCRIPTION_HEADER  *Dsdt
  )
{
  UINT8  *Ptr;
  UINT8  *End;

  Ptr = (UINT8 *)Dsdt + sizeof (EFI_ACPI_DESCRIPTION_HEADER);
  End = (UINT8 *)Dsdt + Dsdt->Length;

  //
  // Scan for a Name object "MEM0" and patch its value
  // This is a simplified example; production code uses
  // proper AML parsing.
  //
  while (Ptr < End - 8) {
    //
    // Look for NameOp (0x08) followed by "MEM0"
    //
    if (Ptr[0] == 0x08 &&  // NameOp
        Ptr[1] == 'M' &&
        Ptr[2] == 'E' &&
        Ptr[3] == 'M' &&
        Ptr[4] == '0')
    {
      //
      // Patch the DWord value that follows
      // (assuming it's a DWordConst: opcode 0x0C + 4 bytes)
      //
      if (Ptr[5] == 0x0C) {
        UINT32 *Value = (UINT32 *)&Ptr[6];
        *Value = GetActualMemorySize ();
        DEBUG ((DEBUG_INFO, "Patched MEM0 to 0x%08X\n", *Value));
      }
      break;
    }
    Ptr++;
  }

  //
  // Recalculate table checksum
  //
  Dsdt->Checksum = 0;
  Dsdt->Checksum = CalculateCheckSum8 ((UINT8 *)Dsdt, Dsdt->Length);

  return EFI_SUCCESS;
}

23.6 SSDT Overlays

SSDTs (Secondary System Description Tables) allow firmware to add device definitions without modifying the DSDT. This is useful for modular platform designs where different boards share a common DSDT but need board-specific additions.

23.6.1 SSDT ASL Example

//
// SSDT for a board-specific I2C sensor
//
DefinitionBlock ("sensor.aml", "SSDT", 2, "PROJMU", "SENSOR01", 1)
{
    External (\_SB.PCI0.I2C0, DeviceObj)

    Scope (\_SB.PCI0.I2C0)
    {
        Device (SENS)
        {
            Name (_HID, "BME0280")   // Bosch BME280 sensor
            Name (_UID, 0)

            Name (_CRS, ResourceTemplate ()
            {
                I2cSerialBusV2 (0x76, ControllerInitiated,
                    400000, AddressingMode7Bit,
                    "\\_SB.PCI0.I2C0", 0x00)
            })

            Method (_STA, 0)
            {
                //
                // Return 0x0F if sensor is present, 0x00 otherwise
                //
                If (\_SB.PCI0.I2C0.SENS.PRES)
                {
                    Return (0x0F)
                }
                Return (0x00)
            }
        }
    }
}

23.6.2 Loading SSDTs from Firmware

//
// Install a compiled SSDT from a firmware volume file
//
EFI_STATUS
InstallSsdtFromFv (
  IN EFI_GUID  *SsdtFileGuid
  )
{
  EFI_STATUS                Status;
  EFI_ACPI_TABLE_PROTOCOL   *AcpiTable;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv;
  UINT8                     *SsdtBuffer;
  UINTN                     SsdtSize;
  UINT32                    AuthenticationStatus;
  UINTN                     TableKey;

  //
  // Locate the Firmware Volume containing our SSDT
  //
  Status = LocateFvWithFile (SsdtFileGuid, &Fv);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read the SSDT section from the FV file
  //
  SsdtBuffer = NULL;
  SsdtSize   = 0;
  Status = Fv->ReadSection (
                 Fv,
                 SsdtFileGuid,
                 EFI_SECTION_RAW,
                 0,
                 (VOID **)&SsdtBuffer,
                 &SsdtSize,
                 &AuthenticationStatus
                 );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Install the SSDT
  //
  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID **)&AcpiTable
                  );
  if (!EFI_ERROR (Status)) {
    Status = AcpiTable->InstallAcpiTable (
                          AcpiTable,
                          SsdtBuffer,
                          SsdtSize,
                          &TableKey
                          );
  }

  FreePool (SsdtBuffer);
  return Status;
}

23.7 ACPI Table INF and DSC Integration

23.7.1 INF File for an ACPI Table Driver

[Defines]
  INF_VERSION    = 0x00010017
  BASE_NAME      = PlatformAcpiTables
  FILE_GUID      = 7E374E25-8E01-4FEE-87F2-390C23C606CD
  MODULE_TYPE    = DXE_DRIVER
  VERSION_STRING = 1.0
  ENTRY_POINT    = AcpiTableEntryPoint

[Sources]
  AcpiTableDxe.c
  Dsdt.asl         # Compiled by build system
  Madt.c
  Fadt.c

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiDriverEntryPoint
  UefiBootServicesTableLib
  DebugLib
  BaseMemoryLib
  MemoryAllocationLib

[Protocols]
  gEfiAcpiTableProtocolGuid
  gEfiMpServiceProtocolGuid

[Depex]
  gEfiAcpiTableProtocolGuid

23.7.2 ASL Compilation in the Build System

The EDK II build system automatically compiles .asl files listed in [Sources] using the iasl compiler. The output AML is packaged as a raw section in the firmware volume. Build configuration in the DSC:

[BuildOptions]
  # IASL flags
  *_*_*_ASL_FLAGS = -oi -so

23.8 ACPI Debugging

23.8.1 acpidump

acpidump extracts raw ACPI tables from a running system:

# Dump all ACPI tables to binary files
sudo acpidump -b

# This creates files like dsdt.dat, apic.dat, facp.dat, etc.

# Disassemble the DSDT back to ASL
iasl -d dsdt.dat
# Produces dsdt.dsl (human-readable ASL)

23.8.2 iasl Disassembly

The Intel ASL compiler can disassemble AML back to ASL for inspection:

# Disassemble with external reference resolution
iasl -d -e ssdt*.dat dsdt.dat

# Compile ASL and check for errors/warnings
iasl -tc dsdt.dsl

# Common flags:
#   -d    Disassemble AML to ASL
#   -e    Include external table references
#   -tc   Compile ASL and create C header output
#   -so   Override table signature validation

23.8.3 Linux ACPI Debugging

# View installed ACPI tables
ls /sys/firmware/acpi/tables/

# Read a specific table
cat /sys/firmware/acpi/tables/MADT | xxd | head

# View the ACPI namespace
cat /sys/firmware/acpi/tables/DSDT > /tmp/dsdt.dat
iasl -d /tmp/dsdt.dat

# Enable ACPI debug output in kernel
echo "method" > /sys/module/acpi/parameters/debug_layer
echo "0x1F" > /sys/module/acpi/parameters/debug_level

# View ACPI errors
dmesg | grep -i acpi

23.8.4 Windows ACPI Debugging

# Dump ACPI tables (requires administrator)
# Use the Windows ADK or RWEverything tool

# Check ACPI errors in Event Viewer:
# Applications and Services Logs -> Microsoft -> Windows -> Kernel-ACPI

# Use ACPI Test tool (part of Windows HLK)

23.8.5 Common ACPI Issues

Issue Symptom Solution
Bad checksum OS rejects table Recalculate checksum after patching
Missing _STA method Device not enumerated Add _STA returning 0x0F
Wrong _ADR Device not matched Verify PCI device/function address
AML error in method OS BSOD / panic Test with iasl -d and fix ASL
Missing interrupt override IRQ conflicts Add proper MADT interrupt source overrides
Wrong ECAM base in MCFG PCI devices not found Verify PCIe config space base address

23.9 Project Mu ACPI Management

23.9.1 ACPI Table Generation Patterns

Project Mu platforms typically use a combination of:

  1. Static ASL tables: DSDT and board-specific SSDTs compiled from ASL source
  2. Dynamic C-generated tables: MADT, SRAT, SLIT built at runtime based on hardware topology
  3. Silicon-provided SSDTs: CPU power management tables from silicon vendor packages
  4. SSDT overlays: Board-specific device additions

23.9.2 DynamicTablesPkg

Project Mu leverages EDK II’s DynamicTablesPkg for ARM and increasingly for x86 platforms. This package provides a framework for generating ACPI tables from a platform description:

flowchart LR
    A["Configuration\nManager"] -->|"Provides platform\nobjects"| B["Table\nGenerators"]
    B -->|"Generate"| C["ACPI Tables"]
    C -->|"Install via"| D["AcpiTableProtocol"]
    D --> E["OS receives\ntables"]

    style A fill:#3498db,stroke:#2980b9,color:#fff
    style C fill:#27ae60,stroke:#1e8449,color:#fff

The Configuration Manager is a platform-specific module that answers queries about the hardware (e.g., “How many CPUs?”, “What is the I/O APIC address?”). Table Generators are reusable components that consume these answers and produce standard ACPI tables.

23.9.3 ACPI SDT Protocol for Runtime Table Access

The EFI_ACPI_SDT_PROTOCOL allows DXE drivers to programmatically traverse and modify installed ACPI tables:

#include <Protocol/AcpiSystemDescriptionTable.h>

EFI_STATUS
FindAndModifyAcpiTable (
  IN UINT32  TableSignature
  )
{
  EFI_STATUS                     Status;
  EFI_ACPI_SDT_PROTOCOL          *AcpiSdt;
  EFI_ACPI_DESCRIPTION_HEADER    *Table;
  UINTN                          Index;
  UINTN                          TableKey;
  EFI_ACPI_TABLE_VERSION         Version;

  Status = gBS->LocateProtocol (
                  &gEfiAcpiSdtProtocolGuid,
                  NULL,
                  (VOID **)&AcpiSdt
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Iterate through installed tables to find our target
  //
  for (Index = 0; ; Index++) {
    Status = AcpiSdt->GetAcpiTable (
                        Index,
                        &Table,
                        &Version,
                        &TableKey
                        );
    if (EFI_ERROR (Status)) {
      break;  // No more tables
    }

    if (Table->Signature == TableSignature) {
      DEBUG ((
        DEBUG_INFO,
        "Found table '%.4a' at 0x%p, length %u\n",
        (CHAR8 *)&Table->Signature,
        Table,
        Table->Length
        ));

      //
      // Modify the table as needed
      //
      // ...

      break;
    }
  }

  return Status;
}

23.10 Hardware-Reduced ACPI

Modern platforms, especially those targeting Windows on ARM or simplified x86 designs, use Hardware-Reduced ACPI (HW_REDUCED_ACPI flag in FADT). This mode removes the requirement for legacy hardware like the PM timer, fixed event registers, and SMI command port.

In Hardware-Reduced ACPI:

  • All events are delivered via GPIO Signaled ACPI Events (GED)
  • No SMI command port is required
  • Sleep states use the HW-reduced sleep mechanism
  • Platform reset uses the ACPI Reset Register or a GED
//
// Generic Event Device (GED) for Hardware-Reduced ACPI
//
Device (\_SB.GED0)
{
    Name (_HID, "ACPI0013")  // Generic Event Device

    Name (_CRS, ResourceTemplate ()
    {
        GpioInt (Edge, ActiveHigh, ExclusiveAndWake, PullNone, 0,
                 "\\_SB.GPIO", 0, ResourceConsumer, , ) { 10 }
        GpioInt (Edge, ActiveHigh, Exclusive, PullNone, 0,
                 "\\_SB.GPIO", 0, ResourceConsumer, , ) { 15 }
    })

    Method (_EVT, 1, Serialized)
    {
        Switch (ToInteger (Arg0))
        {
            Case (10)
            {
                // Power button event
                Notify (\_SB.PWRB, 0x80)
            }
            Case (15)
            {
                // Thermal event
                Notify (\_TZ.TZ00, 0x80)
            }
        }
    }
}

23.11 Summary

ACPI tables are the primary mechanism through which UEFI firmware describes the platform to the operating system. Building correct ACPI tables is essential for hardware to function properly under the OS.

Key takeaways:

  • RSDP -> XSDT -> individual tables forms the ACPI table discovery chain.
  • DSDT and SSDTs contain AML bytecode describing the device hierarchy, power management methods, and hardware access regions.
  • The ACPI Table Protocol is the standard UEFI interface for installing tables.
  • Dynamic table generation is necessary for tables like MADT where content depends on runtime hardware discovery.
  • ASL is the source language for ACPI bytecode; key constructs include Device, Method, Name, OperationRegion, and Field.
  • Debugging tools (acpidump, iasl -d, kernel ACPI logging) are essential for diagnosing table issues.
  • Project Mu uses a combination of static ASL, dynamic C-generated tables, and the DynamicTablesPkg framework for ACPI management.

This concludes Part 5: Advanced Topics. You now have a deep understanding of the UEFI boot phases (PEI and DXE), system management mode, the security infrastructure, and how firmware communicates hardware topology to the OS through ACPI.