ACPI and the MPTable

Before a guest kernel can use the vCPUs and the interrupt controller the VMM created, it has to be told they exist — how many CPUs there are, where the IOAPIC lives, which interrupts are routed where. On a real PC this information comes from firmware tables the BIOS leaves in memory. Firecracker has no BIOS, so the VMM writes those tables itself, into guest memory, before the kernel starts. Historically it wrote a legacy MPTable (the Intel MultiProcessor Specification format); more recently it builds proper ACPI tables (RSDP/MADT and friends) and is deprecating MPTable.

This chapter covers the legacy MPTable vs the newer ACPI tables, the acpi-tables crate, why ACPI was added and MPTable is going away, how vCPUs and the IOAPIC are described to the guest, and how this all relates to the PCI/PVH future of Firecracker. This is x86_64 territory — aarch64 uses an FDT/DTB instead (see the-boot-sequence.md).

Note: This is a topology-description problem, not a device-emulation one. The tables are static data structures the VMM lays into guest RAM at boot; the guest kernel parses them once, early, to discover its own hardware. Get a checksum or a field wrong and the guest either ignores a CPU, mis-routes interrupts, or refuses to boot — failures that look mysterious until you remember the table is the contract.


The two formats

rg -n "mptable|MpTable|MP_SPEC|mpf_intel|mpc_table" src/vmm/src/arch/x86_64/
find src/vmm/src/arch/x86_64 -name "mptable*"
find src/acpi-tables -name "*.rs"
FormatWhat it isStatus in Firecracker
MPTableIntel MultiProcessor Specification — a pre-ACPI table describing CPUs, the local/IO APIC, and interrupt routinglegacy; being deprecated (verify default on your branch)
ACPIAdvanced Configuration and Power Interface — the modern standard; Firecracker builds RSDP → RSDT/XSDT → MADT (+ others)the direction; built by the in-tree acpi-tables crate

MPTable lives in src/vmm/src/arch/x86_64/mptable.rs (find it with the find above). The ACPI table builders live in the dedicated acpi-tables crate (src/acpi-tables/), consumed by the x86_64 arch code. Both produce the same information — CPU count, APIC layout, interrupt overrides — in two different on-memory encodings the kernel knows how to read.


What the tables describe

rg -n "MADT|Madt|Rsdp|Xsdt|local_apic|io_apic|IOAPIC|LocalApic|InterruptSourceOverride" src/acpi-tables/src/ src/vmm/src/arch/x86_64/

The essential content, regardless of format:

ItemDescribed byWhy the guest needs it
vCPU count + their Local APIC IDsMADT Local APIC entries / MPTable CPU entriesthe kernel must know how many CPUs to bring online and their APIC IDs
The IOAPICMADT I/O APIC entry / MPTable IOAPIC entrywhere external-interrupt routing is programmed
Interrupt source overridesMADT Interrupt Source Override entriescorrects the default ISA IRQ→GSI mapping
The Local APIC addressMADT header / MPTablewhere each CPU's local APIC MMIO lives

For ACPI the kernel finds everything by first locating the RSDP (Root System Description Pointer), which points to the RSDT/XSDT, which lists the other tables, of which the MADT (Multiple APIC Description Table) is the one that enumerates CPUs and APICs.

flowchart TD
    RSDP["RSDP (Root System Description Pointer)"] --> XSDT["XSDT / RSDT (table directory)"]
    XSDT --> MADT["MADT — Local APIC × N, IO APIC, Interrupt Source Overrides"]
    XSDT --> Other["(FADT/DSDT/... as needed)"]
    MADT --> CPUs["guest brings N CPUs online"]
    MADT --> IOAPIC["guest programs interrupt routing"]

Every ACPI table carries a checksum field; the sum of all its bytes must be zero. A wrong checksum makes the kernel reject the table. This is the single most common ACPI-table bug, and a good reason the building is centralized in acpi-tables where the checksum logic is written once.


Why ACPI was added and MPTable is deprecated

rg -n "acpi|enable_pci|pvh|ACPI" docs/ CHANGELOG.md
sed -n '1,40p' CHANGELOG.md

MPTable is genuinely legacy — the MultiProcessor Specification predates ACPI by years, modern Linux treats it as a fallback, and it cannot express things ACPI can (power management, richer device description, PCI host bridges, memory hotplug). As Firecracker grows features that need richer firmware-style description — notably the optional PCI transport (--enable-pci) and PVH boot, plus memory hotplug — MPTable simply isn't enough. ACPI is the standard the rest of the ecosystem (Cloud Hypervisor, QEMU) already speaks, and it's what these newer features assume. So Firecracker added ACPI (the acpi-tables crate) and is steering MPTable toward removal.

CapabilityMPTableACPI
Enumerate CPUs + APICsyesyes
Power management (sleep/shutdown semantics)noyes
PCI host bridge descriptionnoyes
Memory/device hotplugnoyes
What the modern ecosystem usesrarelyyes

Tip: Check your branch's behavior empirically. rg/CHANGELOG for the MPTable deprecation and whether ACPI is the default for a plain (non-PCI) boot, because this is exactly the kind of fact that changes release to release. Treat any claim about "the default" as "(verify on your branch)."


The acpi-tables crate

find src/acpi-tables -name "*.rs" | sort
rg -n "struct Rsdp|struct Madt|struct .*Header|fn checksum|trait Aml|fn write|SdtHeader" src/acpi-tables/src/

acpi-tables is a focused crate: it knows how to construct well-formed ACPI tables — the shared SdtHeader (signature, length, revision, checksum, OEM id), the RSDP, the MADT with its entry types, and the AML/structure helpers — and to compute the mandatory checksums. The x86_64 arch code calls into it during build_microvm_for_boot to lay the tables into guest memory at the right addresses, then the boot configuration tells the kernel where to find the RSDP. Read the crate's table types and note how every table flows through one checksum routine — that centralization is the whole point of a dedicated crate.

# Where the arch code invokes the table builders during boot.
rg -n "acpi|Madt|Rsdp|create_acpi_tables|write.*acpi" src/vmm/src/arch/x86_64/ src/vmm/src/builder.rs

Relation to PCI and the PVH future

rg -n "enable_pci|pci|pvh|Pvh|PVH" src/vmm/src/ docs/

Firecracker's default transport is virtio-MMIO, which needs no PCI enumeration — the guest is told each device's MMIO location on the kernel command line, so a plain microVM can boot with very little firmware description. But the optional virtio-PCI transport (--enable-pci, verify) and PVH boot change that: PCI requires the guest to enumerate a PCI host bridge, which is described through ACPI; PVH is an ACPI-aware boot path. So ACPI is not just a nicer MPTable — it is the enabling substrate for the PCI/hotplug direction. (The PCI transport is also where CVE-2026-5747 was found and fixed in 1.14.4/1.15.1 — a reminder that new surface is new risk; see virtio-transport-mmio.md and the minimal-device-model philosophy.)

plain microVM (default):   virtio-MMIO  → device location via cmdline → minimal firmware tables
                                          (CPUs/APIC via MPTable or ACPI MADT)
--enable-pci / PVH:        virtio-PCI    → guest enumerates PCI host bridge
                                          → REQUIRES ACPI (MADT + more)  → MPTable insufficient

This is why the MPTable→ACPI migration and the MMIO→PCI option are entangled: you cannot have the richer transport without the richer firmware description, and ACPI is that description.


Reading exercise

# 1. The legacy MPTable builder.
find src/vmm/src/arch/x86_64 -name "mptable*"
rg -n "MpTable|mpc_table|mpf_intel|checksum" src/vmm/src/arch/x86_64/

# 2. The acpi-tables crate: RSDP, MADT, the shared header + checksum.
find src/acpi-tables -name "*.rs"
rg -n "Rsdp|Madt|SdtHeader|fn checksum|local_apic|io_apic" src/acpi-tables/src/

# 3. Where the arch/boot code writes the tables into guest memory.
rg -n "acpi|Madt|Rsdp|mptable|create_.*tables" src/vmm/src/arch/x86_64/ src/vmm/src/builder.rs

# 4. The deprecation + PCI/PVH context.
rg -n -i "mptable|acpi|enable_pci|pvh" CHANGELOG.md docs/

# 5. Boot a microVM and from the guest inspect what it parsed:
#    cat /proc/cpuinfo | grep -c processor   (CPU count the tables advertised)
#    dmesg | grep -i "ACPI\|APIC\|MP-table"  (what the kernel found at boot)

# 6. Contrast with aarch64, which uses an FDT instead.
rg -n "FdtWriter|fdt|dtb|DRAM_MEM_START" src/vmm/src/arch/aarch64/

Answer:

  1. Why does Firecracker have to write these tables at all? What provides them on a real PC?
  2. Name the two formats and their status. Which crate builds the ACPI tables?
  3. Walk the ACPI table chain RSDP → XSDT → MADT and say what the MADT enumerates.
  4. What does an ACPI table checksum guarantee, and why is centralizing it in one crate valuable?
  5. Give three capabilities ACPI has that MPTable lacks, and tie each to a Firecracker feature direction.
  6. Explain why the MMIO→PCI transport change requires the MPTable→ACPI change.

Common bugs and symptoms

SymptomRoot causeWhere to look
Guest sees fewer CPUs than configuredMADT/MPTable Local APIC entries wrong or missingthe per-CPU entry generation; vCPU count plumbing
Guest boots but interrupts misbehaveIOAPIC entry or interrupt source override wrongMADT IO APIC / override entries
Kernel rejects a table / ignores ACPIbad checksum or malformed headerSdtHeader/checksum in acpi-tables
--enable-pci guest can't find devicesPCI host bridge not described (ACPI incomplete)ACPI/PCI description; transport code
Works on x86_64, breaks on aarch64aarch64 uses FDT, not ACPI/MPTablesrc/vmm/src/arch/aarch64/ FDT code
"Default changed" between branchesMPTable deprecation / ACPI-default flipCHANGELOG; verify on your branch

Validation: prove you understand this

  1. Explain why a BIOS-less VMM must construct firmware tables, and what information they carry.
  2. Compare MPTable and ACPI: encoding, capability, and Firecracker's deprecation direction.
  3. Draw the RSDP→XSDT→MADT chain and label what the guest derives from the MADT.
  4. Explain the ACPI checksum rule and why the acpi-tables crate centralizes it.
  5. Justify why ACPI was added by tying three of its capabilities to PCI/PVH/hotplug.
  6. State how the topology-description story differs on aarch64 and why.

This is the final deep dive in the security/lifecycle/topology arc. Return to the deep-dives index to pick your next thread, or step back to the-boot-sequence.md to see where these tables are written into guest memory during boot.