https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=296662
Bug ID: 296662
Summary: bhyve: passthru caches PCI config header while device
is still recovering from FLR; physical BARs left
zeroed
Product: Base System
Version: 15.1-RELEASE
Hardware: amd64
OS: Any
Status: New
Severity: Affects Some People
Priority: ---
Component: bhyve
Assignee: [email protected]
Reporter: [email protected]
Created attachment 272687
--> https://bugs.freebsd.org/bugzilla/attachment.cgi?id=272687&action=edit
Proof of concept, not for commit: readiness wait + BAR restore in cfginit()
### Environment
- Host: FreeBSD 15.1-RELEASE (releng/15.1-n283562-96841ea08dcf) GENERIC
amd64
- CPU: AMD Ryzen (Cezanne), AMD-Vi IOMMU (ivhd0), hw.vmm.amdvi.enable=1
- Device: Intel Arc A380 (DG2), 8086:56a5 rev 05, subvendor 1849:6006
(ASRock), plus its HDA audio function 8086:4f92; both attached to
ppt(4) via pptdevs="3/0/0 4/0/0"
- Guest: Ubuntu 24.04.4, kernel 6.17.0-35-generic, booted via grub-bhyve
(vm-bhyve 1.7.3), devices: -s 0:6:0,passthru,3/0/0
-s 0:7:0,passthru,4/0/0
- IOMMU initializes fine (hw.vmm.iommu.initialized: 1); the EN-25:20.vmm
fix is included in 15.1.
### Overview
Intel DG2 GPUs do not complete FLR cleanly: the Transactions Pending bit
never clears, and after the FLR the device does not respond to config
space accesses (all reads return 0xff) for up to ~1 second. Measured on
this card with `devctl reset pci0:3:0:0`: reads return 0xffffffff
immediately after the reset and recover within ~1 s.
When such a device is assigned to a VM, two things go wrong in sequence:
1. ppt(4) resets the device (ppt_pci_reset() -> pcie_flr(dev, ...,
force); "pci0:3:0:0: Transactions pending after FLR!" is logged on
every start). pcie_flr() waits a fixed delay and then
pci_cfg_restore() writes the saved config into a device that is not
yet responding, so the restore is lost: afterwards the physical BARs
read back as zero (only the type flag bits remain, e.g. 0x00000004 /
0x0000000c) and PCIM_CMD_MEMEN / PCIM_CMD_BUSMASTEREN are clear in
the command register.
2. Immediately afterwards, bhyve's passthru_init() -> cfginit() copies
the physical PCI header into the emulated config space:
for (int i = 0; i <= PCIR_MAXLAT; i += 4)
pci_set_cfgdata32(pi, i, passthru_read_config(...));
Because the device is still unresponsive, vendor/device are cached
as 0xffff. Since the standard header range is served to the guest
from this cache ("Emulate most PCI header register",
set_pcir_handler(sc, 0, PCIR_MAXLAT + 1, passthru_cfgread_emulate,
...)), the guest permanently sees an empty slot. A PCI rescan in the
guest does not help even after the device has recovered, because the
poisoned cache is never refreshed.
Fast-resetting functions are unaffected — the audio function 4f92 of
the same card resets instantly and is always visible in the guest —
which is what makes the failure look so puzzling: one function of the
card appears, the other silently does not, with no error from bhyve.
Even when the header is read correctly (see PoC below), the guest
driver then fails with "Device is non-operational; MMIO access returns
0xFFFFFFFF!" because of the zeroed physical BARs from step 1.
### Steps to Reproduce
1. Attach an Intel Arc GPU function to ppt(4) via pptdevs.
2. Start any bhyve VM with `-s N,passthru,B/S/F` for that function.
3. Observe "Transactions pending after FLR!" on the host console.
4. In the guest: `lspci` — the slot is empty.
`echo 1 > /sys/bus/pci/rescan` does not help.
5. On the host, `pciconf -r pciB:S:F 0x10` etc. show zeroed BARs and a
cleared command register, while `pciconf -r ... 0x00` returns the
correct vendor/device ID (the card itself has recovered).
### Actual Results
Guest sees no device; bhyve reports no error. After manually restoring
the BAR values and command register with pciconf(8) on the host and
re-scanning the PCI bus in the guest, the i915 driver initializes the
GPU completely (GuC/HuC load and authenticate, /dev/dri/renderD128
works, VA-API transcoding functional) — confirming that nothing else is
missing.
### Expected Results
The passthru device is visible and functional in the guest, or at
minimum bhyve reports a diagnostic that the device did not respond.
### Root cause confirmation (proof of concept)
To confirm the analysis, a proof-of-concept change to cfginit() was
tested on the affected system (see attachment):
- poll PCIR_VENDOR (50 ms x 40, i.e. up to 2 s) until the device
responds before caching the config space header;
- then compare each BAR register of the physical device against the
kernel's cached value (PCIOCGETBAR, which survives the reset),
rewrite it if it was lost, and re-enable memory decoding and bus
mastering.
With the PoC applied, a plain `vm stop` / `vm start` cycle produces a
fully working GPU in the guest with no manual intervention; bhyve logs
"PCI 3/0/0: restoring BAR0 after reset" / "...BAR2..." on each start.
This confirms both halves of the analysis (poisoned header cache; lost
config restore). The PoC is provided for reproduction and as a
mitigation for affected users — not as a ready-to-commit fix.
Suggested directions for a proper fix (left to the maintainers):
- Kernel: pcie_flr() in sys/dev/pci/pci.c could poll for device
readiness (vendor ID != 0xffff, as Linux does in pci_dev_wait())
after issuing the FLR and before pci_cfg_restore(). That would fix
the lost-restore problem for all consumers of pcie_flr(), not just
bhyve, and is probably the right layer for it.
- bhyve: cfginit() should not cache a config header read from a device
that is still returning 0xff, and could verify/restore the physical
BARs against the kernel's cached values before use.
### Observed behaviour with and without the PoC
- Without the PoC: on every VM start, "Transactions pending after FLR!"
on the host; guest sees an empty slot; physical BARs read back zeroed
(0x00000004 / 0x0000000c) with MEMEN/BUSMASTEREN cleared; manually
rewriting the BARs and command register with pciconf(8) plus a PCI
rescan in the guest brings the device fully up — confirming the
mechanism.
- With the PoC: plain `vm stop` / `vm start` cycles produce a fully
functional GPU in the guest with no manual intervention. In the
Ubuntu 24.04 guest (kernel 6.17), i915 initializes completely (DMC,
GuC 70.36.0 loaded, HuC authenticated), /dev/dri/renderD128 is
created, and VA-API reports full decode/encode profiles
(H.264/HEVC/VP9/AV1); hardware transcoding is functional.
- The audio function (8086:4f92) of the same card, which resets
quickly, behaves identically before and after the PoC — the readiness
wait is effectively a no-op for devices that recover promptly, and
the BAR restore writes nothing when registers already match the
kernel's cached values.
usr.sbin/bhyve/pci_passthru.c is currently identical in main and
releng/15.1, so the PoC applies unmodified to both; all testing was
done on 15.1-RELEASE.
### Provenance disclosure
The root-cause analysis was performed interactively on the affected
hardware; all measurements, register dumps and logs in this report are
from that system. The analysis and the attached proof-of-concept diff
were developed with the assistance of an LLM (Anthropic Claude). I am
not a C developer and have not performed a full code review of the
diff, so I am explicitly NOT proposing it for commit; it is attached
only to document how the root cause was confirmed and as a temporary
mitigation for affected users. Please treat it as a reproduction aid —
the root cause description should be sufficient for a committer to
implement a proper fix independently.
What I can vouch for personally is the empirical side: the reproduction
steps, the register dumps, the before/after behaviour, and the fact
that the described mechanism (manual BAR restore via pciconf(8) + guest
PCI rescan) brings the device fully up without any code changes. I am
happy to test any proposed fix on the affected hardware and can provide
any additional diagnostics on request.
A related but independent bug (bhyve's gvt-d probe claiming discrete
Intel GPUs and failing VM startup) is filed separately.
--
You are receiving this mail because:
You are the assignee for the bug.