Skip to content

fix(qemu-acpi): emit ACPI PCI hotplug AML for root-port buses - #1203

Closed
kvinwang wants to merge 5 commits into
nextfrom
fix/qemu-acpi-root-port-hotplug
Closed

kvinwang wants to merge 5 commits into
nextfrom
fix/qemu-acpi-root-port-hotplug

Conversation

@kvinwang

@kvinwang kvinwang commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A TDX CVM with GPUs behind pcie-root-ports and ACPI PCI hotplug on (the default, hotplug_off = false) fails lite ACPI verification. A captured 8×B200 CVM (QEMU 10.2.1, dstack-0.6.0-rc3) fails verifier-v0.6.0-rc3 with:

TDX lite acpi-loader digest mismatch:
  expected adae98e24401f6c8d6f3f2c24a78023ebc3c82ed951a92dde7958e5eae1fe1b4352ad1075aec822b687469075027b82c
  reported 01f02cba34d8f7213872ce341a587c636016a6268d0b9c2d2198058a8a709fe38cde80f67c808067444cb8fa11cdd27c

Once a root port's secondary bus is hotplug capable, QEMU gives it a BSEL and adds hotplug AML to the DSDT (per-port BSEL/S00_ scope/DVNT, a \_SB.PCI0.PCNT tree, and a non-empty _GPE._E01). The generator emitted none of it.

Fix

  • notify.rs: with hotplug on, emit that AML for each pcie.0 root port plus the PCNT scope. BSEL runs in reverse slot order (QEMU inserts child buses at the list head and numbers depth-first). The helper documents the VMM device order this relies on.
  • gpe.rs: _E01 acquires BLCK, calls PCNT, releases BLCK when a PCNT exists; otherwise stays empty.
  • PXB-attached root ports get no AML (QEMU does not enable hotplug on expander bridges).
  • Differential oracle: dump the tables after machine reset. BSEL is assigned in the reset path, so the pre-reset dump had no hotplug AML and could not catch this. Bumps the QEMU compat fork and adds GPU-bearing fixed cases.

The involved QEMU functions are unchanged (modulo moves/renames) from v8.2.0 to v11.1.0, so no compatibility-profile gating is needed.

Verification

  • The captured 8×B200 attestation passes quote, event log, OS image hash and ACPI checks with this change, given the pci_hole64_size the guest actually saw (see gap below). tdx_lite_acpi_matches_captured_eight_gpu_qemu_10_2_vm pins all three RTMR0 ACPI digests from that CVM's event log.
  • cargo test -p qemu-acpi -p dstack-verifier, fmt, clippy clean; differential job green.

Known gaps (follow-up)

  • With the default pci_hole64_size = 0, QEMU sizes the DSDT _CRS 64-bit window from the firmware-assigned GPU BARs (~3.75 TiB for 8×B200), which vm_config cannot express, so the tables digest still mismatches on such hosts. Options: GPU hosts set an explicit hole size, the VMM sizes it, or the verifier rejects num_gpus > 0 && pci_hole64_size == 0.
  • Multi-PXB (hugepages across several NUMA nodes) is not modelled by qemu-acpi at all; this PR does not change that.

Co-authored with @Leechael, who wrote the qemu-acpi change.

Copilot AI lite review requested due to automatic review settings September 11, 2026 16:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved multi-PXB mapping and production BAR-window issues can cause ACPI measurement divergence.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds QEMU-compatible ACPI PCI hotplug AML for root-port buses to fix TDX lite ACPI measurements.

Changes:

  • Generates root-port hotplug AML, BSEL mappings, and PCNT notifications.
  • Updates _GPE._E01 handling.
  • Adds golden and eight-GPU regression tests.
File summaries
File Summary Final review comments
dstack/verifier/src/verification.rs Adds captured eight-GPU ACPI regression coverage. Moderate, 1 vote: Test supplies a realized BAR-window size and masks the production default failure.
dstack/crates/qemu-acpi/src/golden_tests.rs Updates hotplug-related golden tests and hashes. No final comments.
dstack/crates/qemu-acpi/src/dsdt/notify.rs Generates root-port hotplug and PCNT AML. Critical, 1 vote: Multi-PXB topologies can map PCNT scopes to incorrect devfns. Moderate, 2 votes: PXB handling lacks QEMU-generated coverage.
dstack/crates/qemu-acpi/src/dsdt/mod.rs Integrates hotplug AML and PCNT generation. No final comments.
dstack/crates/qemu-acpi/src/dsdt/gpe.rs Adds conditional PCI hotplug scanning through _E01. No final comments.
Review details

Suppressed comments (1)

dstack/verifier/src/verification.rs:2482

  • This regression test supplies the realized GPU BAR-window length instead of the production default (pci_hole64_size = 0/None). With the default, crs::build still emits the fixed 32 GiB window while QEMU derives _CRS from the firmware-assigned GPU BARs, so the real eight-B200 configuration continues to fail the tables digest; this test masks that failure. Please wire the realized window into measurement or reject GPU configurations without an explicit size before treating the verifier path as fixed.
            "pci_hole64_size": 0x3c0_0601_4000u64,
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +121 to +125
pub(crate) fn pcnt(
slot_count: u32,
root_port_count: u32,
pxb_devfn: Option<u8>,
) -> Option<Vec<u8>> {
Comment on lines +164 to +168
fn root_ports(slot_count: u32, count: u32, pxb_devfn: Option<u8>) -> Vec<(u8, u32)> {
let mut ports = Vec::with_capacity(count as usize);
let mut slot = slot_count;
for index in 0..count {
if pxb_devfn == Some((slot * 8) as u8) {
@kvinwang
kvinwang marked this pull request as draft September 11, 2026 17:02
@kvinwang
kvinwang force-pushed the fix/qemu-acpi-root-port-hotplug branch from fb366e2 to b6535e4 Compare September 12, 2026 06:43
kvinwang and others added 5 commits September 24, 2026 02:03
Co-authored-by: Leechael Yim <yanleech@gmail.com>
The oracle dumped the ACPI tables built inside acpi_setup(), a
machine_init_done notifier. QEMU assigns the ACPI hotplug BSEL property to
PCI bridge secondary buses in acpi_set_pci_info(), reached from the ICH9 PM
reset handler through acpi_pcihp_reset(), and machine reset runs after that
notifier. QEMU then rebuilds the tables via acpi_build_update(), so the
blob a guest reads -- and measures into RTMR0 -- is the rebuilt one.

Sampling before reset produced a DSDT in which no bus had a BSEL, and so
none of the root-port hotplug AML this branch adds. The oracle agreed with
any generator that omitted those terms: 17 of its 61 cases could not fail,
namely every case with bridge hotplug enabled and a root port on pcie.0.
That is why this branch's differential job reported the generator as wrong
when it was the reference that was wrong.

The fix lives in the QEMU compatibility fork, which now performs the one
reset side effect that changes the generated AML before dumping, and folds
the directory output mode in so both consumers sample at the same point.
Bump to that revision, drop the patch it replaces, and repin the image.

Also cross the axes that had only ever run GPU-less. Every per-version and
large-CPU fixed case ran with no GPUs, so no version tier and no CPU-count
boundary was pinned against a topology with root ports. Add the host
sharing mode as an explicit axis too: dstack-mr accepts "9p", "vvfat" and
"vhd" and models none of them, on the theory that each contributes exactly
one PCI slot and the DSDT records only _ADR; QEMU confirms all three are
byte-identical. Fixed cases go from 29 to 40.

Document the two measured inputs the oracle structurally cannot reach, so a
green run is not read as covering them: the 64-bit PCI window when
pci_hole64_size is 0, which QEMU derives from BARs the guest firmware has
assigned and no dump-and-exit oracle can observe, and multi-PXB topologies,
which MachineConfig cannot express.
@kvinwang
kvinwang force-pushed the fix/qemu-acpi-root-port-hotplug branch from b6535e4 to 79296be Compare September 24, 2026 09:04
@kvinwang

Copy link
Copy Markdown
Collaborator Author

Superseded by #1387: instead of modeling ACPI hotplug AML for root ports, hotplug is now required off for GPU passthrough and off by default.

@kvinwang kvinwang closed this Sep 24, 2026
source-c pushed a commit to silicon-foundation/dstack that referenced this pull request Oct 4, 2026
…passthrough

QEMU emits ACPI hotplug AML for pcie-root-port buses when PCI hotplug is on,
and qemu-acpi does not model it, so a TDX CVM with GPUs or NVSwitches on root
ports failed lite verification with an ACPI digest mismatch.

Rather than modeling that AML (Dstack-TEE#1203), require hotplug off for such
topologies: MachineConfig::validate rejects them with a named error, and the
VMM config, dstackup template, tutorial and e2e config default
qemu_hotplug_off to true. The GPU goldens move to hotplug off, with hashes
checked against the pinned QEMU reference image.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants