Skip to content

feat(firecracker): hot-plug behind PCIe root ports and wait for device removal - #290

Merged
CMGS merged 4 commits into
masterfrom
fix/fc-pcie-root-ports
Oct 24, 2026
Merged

CMGS merged 4 commits into
masterfrom
fix/fc-pcie-root-ports

Conversation

@CMGS

@CMGS CMGS commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Upstream Firecracker changes how PCI hot-plug works, and the current cocoon Firecracker backend breaks on it:

  • Every post-boot PCI device now goes behind a free PCIe root port. Root ports come from machine-config pcie_hotplug_ports, which defaults to 0, so every hot-plug fails with NoFreeRootPort. This hits vm disk attach, NIC resize, and clone-time --data-disk/--nics on --pci VMs.
  • A DELETE of a device behind a root port is now graceful and non-blocking. It returns 204 at once, and the device stays until the guest releases it. Cocoon treated the 204 as "removed".

This PR:

  • gives each --pci VM 8 root ports. MMIO VMs do not get the field, because Firecracker rejects root ports without PCI.
  • makes RemoveNIC and DiskDetach poll /vm/config after the DELETE until the device is gone, with the same 30s bound as the Cloud Hypervisor eject wait. A NIC the guest never releases returns ErrEjectPending, as on Cloud Hypervisor.
  • updates docs/devices.md and docs/firecracker.md, and makes the printed guest hints say they apply only when Firecracker plugs devices onto the root bus.

The guest kernel needs PCIe hot-plug (CONFIG_HOTPLUG_PCI_PCIE) on a root-port build. Without it the guest never releases a device, so every detach waits 30s and fails.

Compatibility with the Firecracker build that cocoon-check --upgrade installs today (fork dev 74a2b360b): it already accepts pcie_hotplug_ports and removes devices synchronously, so the first poll returns there. It rejects any DELETE body, so {"force": true} is not an option.

Order: merge this before the Firecracker fork dev moves to the new upstream. Snapshots keep the root port count of their VM, so bases made before this change have none. On the new Firecracker, clones of those bases cannot hot-plug until the base is created again.

Tested:

  • GOWORK=off make lint and make fmt-check (linux and darwin), asl -forwarder=false ./... (linux and darwin), make vet, go test -race on hypervisor/..., extend/..., cmd/vm/...
  • new tests: TestRemoveNICWaitsForGuestRelease, TestRemoveNICReportsAnUnreleasedDevice, TestMachineConfigFor. The first two fail against the old extend.go.

Not tested:

  • no run on real KVM yet, with either Firecracker build
  • the boot-time cost of 8 root ports is not measured
  • the 8 ports share the 32 root-bus slots with the boot devices (one drive per image layer, NICs, vsock, balloon, entropy). A --pci VM with a very large layer count could run out of slots.

CMGS added 4 commits October 24, 2026 21:09
…e removal

Upstream Firecracker now places every post-boot PCI device behind a free
PCIe root port and fails the hot-plug when machine-config sets none, which
is the default. A DELETE of a device behind a root port is now graceful:
it returns at once, and the device stays until the guest releases it.

Give each --pci VM 8 root ports. The field stays off the wire for MMIO VMs,
because Firecracker rejects root ports there. After a DELETE, poll
/vm/config until the device is gone, with the same 30s bound as the Cloud
Hypervisor eject wait. A NIC the guest never releases returns
ErrEjectPending, as on Cloud Hypervisor. The released Firecracker build
removes the device at once, so the first poll returns there. A DELETE
body ({"force": true}) is not an option: that build rejects any body.
…onfig, method-aware test route

Trim waitDeviceRemoved and pcieHotplugPorts comments to the fact the code cannot carry, drop two comments that restate type names, capitalize own-line comments in start.go, rename machineConfigFor to buildMachineConfig, stop the removal closures shadowing cfg, and register the stub route as GET /vm/config.
…ot-bus builds only

The removal wait completes only when the guest kernel runs pciehp, and the printed guest steps are needed only when Firecracker plugs devices onto the root bus.
- createSnapshotFC and loadSnapshotFC call sendJSONOnce on their long-timeout
  socket client instead of hand-rolling marshal plus DoAPIOnce. Same client,
  no retry, same 204; only the marshal-error text changes.
- putEntropy calls putJSON with struct{}{}: same retry, PUT, 204 and {} body.
- LiveNICs drops the idx reset after Sscanf: a failed Sscanf never writes idx
  (checked for EOF, non-integer, mismatch and overflow inputs).
- Drop the goroutine that closed the PTY master after a failed console relay.
  It only mattered in a long-lived process that launches VMs; the daemon
  launches none and no sibling repo imports cocoon/hypervisor, so the CLI
  exit closes the fd.

Existing tests cover LiveNICs; the other three rest on the helper source.
The relay 5s timeout stays: it bounds a hang under the ops lock.
@CMGS
CMGS merged commit c91df9e into master Oct 24, 2026
4 checks passed
@CMGS
CMGS deleted the fix/fc-pcie-root-ports branch October 24, 2026 17:13
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.

1 participant