← Back · part of NextBSD Research · sibling of the virtio-gpu DRM plan · decision record: arm64 KMS options · console policy: quiet console plan

NextBSD under Apple Virtualization.framework — boot, console, and the fbdev gap

Apple’s VM stack is the one arm64 target where NextBSD has never booted, and the reason turned out not to be ACPI, GIC, or graphics. It was USB. This page records what was measured on real hardware, what the remaining blockers are, who else is already working on them, and the order to fix them in.

Investigated 2026-08-18, updated 2026-08-19 on an Apple Silicon Mac, macOS 26.5.2. Every claim tagged measured was observed directly on that machine against a NextBSD arm64 install (kernel 20260818-174943) and the NextBSD-arm64-20260817-065847 ISO. Upstream claims carry source links. Claims from binary analysis of Apple’s framework are tagged as such and are the least durable thing here.

Root-caused 2026-08-18 The “Internal Virtualization error” is xHCI. Apple’s virtual xHCI controller aborts the entire VMM — Security Assertion Hit__os_crashbrk #0x1 on the vCPU thread — when a guest writes a register it does not model, and FreeBSD’s xhci(4) does exactly that. Any VZUSBKeyboardConfiguration, VZUSBScreenCoordinatePointingDeviceConfiguration, or VZUSBMassStorageDevice instantiates that controller. Remove them and the crash goes away: 6–8 s crash → 121 s and still running, guest idle at 0.1 % CPU.

Still blocked Booting is not fixed, only unblocked. With USB removed the guest reaches the kernel and then sits idle without networking. It is mute — no GOP, no UART, no SPCR — so the next blocker cannot be identified without first giving the guest a console. That, not ACPI, is the critical path.

Updated 2026-08-19 Two of the three blockers on this page are now closed in tree, and neither of them was the boot hang. The vtgpu attach spin (§4 #4) is fixed by kernel patch 0011, which bounds the control-queue wait and calls virtio_stop() on timeout instead of spinning a vCPU forever. The fbdev gap (§6) is closed outright: drm_fbdev_shmem is built and shipping, and a stock arm64 boot now reaches /dev/dri/card0 with a drmfb console unattended. Neither depends on a firmware framebuffer, so both carry over to VZ, where there is no GOP at all.

What is left for VZ is exactly one thing: the early hang. With USB removed the guest reaches the kernel and goes idle before the virtio bus is probed — earlier than any console this project can register, which is why the consdev (§8 Phase 1) is resident, correct, and silent. The next move is the PSCI tracer (§8 Phase 2): it needs no console at all. Everything else on this page is either measured-and-eliminated or now shipped.

Contents

  1. What was measured
  2. The xHCI fatality
  3. Why the guest is mute — three routes to a console
  4. Remaining boot blockers, ranked
  5. Prior art — we are not first
  6. The fbdev gap — smaller than we thought
  7. Reconciling with V7 and Option D
  8. Plan
  9. Open calls

1. What was measured

A NextBSD arm64 install was copied out of its UTM qcow2 to a sparse raw image and booted under Virtualization.framework through a ~90-line Swift harness, ad-hoc signed with com.apple.security.virtualization. Everything below is first-hand.

QuestionResultHow it was established
Does Apple’s EFI read a FreeBSD disk?Yeslsdev at the loader prompt: disk0: ISO9660 / GPT+ESP raw image; kernel and mfsroot load normally
Device tree?NoneLoader: No valid device tree blob found! — a warning, not a failure; bootinfo.c prints it whenever acpi.revision is unset
ACPI?Yesshow acpi.rsdp0x00000000efbf0018; show acpi.oemAPPLE
GOP / framebuffer?None at allgop listgop: Graphics Output Protocol not present. Not Blt-only — absent
Kernel output after ExitBootServices?Zero, everScreen freezes mid-word on the loader’s last line; nothing follows on any configuration
With USB devices attachedVMM aborts in 6–8 sVZErrorDomain Code=1; EXC_BREAKPOINT/SIGTRAP on com.apple.virtualization.thread.cpu-0
With no USB devicesSurvives; idle121 s+ alive, VMM at 0.1 % CPU, no DHCP lease
Stock kernel (with virtio_gpu)Hangs, spinningVM alive but pegged ~199 % across 2 vCPUs — a different failure from the idle case
Does virtio_gpu(4) drive Apple’s virtio-gpu?No evidence it doesA kernel containing it produced no console under VZ
Linux under VZEFIBootLoader (GRUB on the guest ESP)Boots, console worksACPI path confirmed (rtc-efi, not pl031). Tables present: APIC DSDT FACP GTDT MCFGno SPCR
MADT GIC Distributor versionGicVersion = 3Not 0. The carried GIC-version fallback patch is inert here and was dropped. GICD base 0x10000000, GICv2m MSI frame 0x1fff0000
GTDT / FADTWell-formedVirtualTimer INTID 27 (what Linux uses), NonSecureEL1 30, EL2 26; FADT rev 6 with HW_REDUCED_ACPI, PSCI_COMPLIANT and PSCI_USE_HVC all set
Linux via VZLinuxBootLoader (direct kernel)Boots, DT pathVZ’s own DTB captured: arm,gic-v3, psci method hvc, RAM at 0x70000000, PL031 0x20050000, PL061 0x20060000, no serial node
NextBSD + VZ graphics device199 % spinEvery kernel containing base virtio_gpu(4) spins; the one built nodevice virtio_gpu does not
NextBSD, no graphics deviceIdle 0.1 %No console output even with the consdev patch resident — so it stops before virtio device probe. Cause unknown
Stock FreeBSD 16.0-CURRENT VM image (-ufs.raw, 2026-08-04 snapshot)Same 199 % spin, no leaseLoader menu renders normally under Apple’s EFI, Loading kernel…, then two vCPUs pegged for 151 s+ with no DHCP. Not a NextBSD bug
Stock FreeBSD disc1.iso as VZ mediaNot bootableNo loader output, then a clean power-off. macOS also refuses that ESP (mount -t msdos → error 71) while NextBSD’s mounts as FAT12 EFISYS. Two independent readers failing on the same image points at the media, not the kernel

Media caveat Test FreeBSD releases with a VM image or memstick, never disc1.iso. An install ISO that Apple’s EFI cannot read produces exactly the same outward signature as a kernel that fails to boot — black screen, no output — so it is an easy way to draw a wrong conclusion. This may be what vfkit #446 actually is, reported there as “missing device tree”.

The crash/hang split is diagnostic, not cosmetic: a crash means the guest touched a device model Apple refuses; an idle guest means the kernel stopped scheduling; a spin means it is looping. The triggered thread name in ~/Library/Logs/DiagnosticReports/com.apple.Virtualization.VirtualMachine-*.ips discriminates further — thread.cpu-N is a guest-triggered device assertion, virtual-machine-service is a configuration the VMM refused at start.

2. The xHCI fatality

The crash report’s faulting function, disassembled, references only xHCI MMIO-decode strings:

"Security Assertion Hit: "
"Guest attempted to write into RsvdP in the Host Controller Runtime Registers's
 Interrupter Register Set \"%u\". "
"Guest attempted to write into a Host Controller Operational Registers' port
 register at offset \"0x%x\". The offset is past MaxPorts of this device."
"Unhandled endpoint type %d"   "Non-zero doorbell target %u is unexpected"

The call site is adrp x1, "Report an error"; bl …, and that callee ends in adrp x0, "assertion failed"; bl __os_crash; brk #0x1. This is a deliberate design choice: an unmodelled guest write kills the VMM rather than being ignored.

It also matches the only known-good FreeBSD-on-VZ writeup independently, whose blocker #4 reads verbatim “USB/XHCI is fatal — any USB device makes VZ present an XHCI controller and FreeBSD’s USB stack dies”.

Two independent belts. Host side: never configure keyboards, pointing devices, USB mass storage, or an explicit VZXHCIControllerConfiguration. Guest side: nodevice xhci in KERNCONF, or hint.xhci.0.disabled="1". The guest-side belt is the one that matters, because it protects users running third-party VZ front-ends we do not control — and every published VZ tool configures a keyboard by default.

Sources: local crash reports 2026-08-18; networkextension/FreeBSD-vz.

3. Why the guest is mute — three routes to a console

VZ gives an arm64 guest no console path that FreeBSD can currently use. This is a platform fact, not a bug in our configuration:

Route A — port the virtio-console consdev

Recommended ~250 LOC, already written by someone else, no entitlements, ships to users.

The FreeBSD-vz fork carries a low-level console for virtio_console(4) (cf50f191e, +252 lines). This is the only console VZ hands an unentitled harness, it needs no private API, and it is the piece that makes NextBSD debuggable on this platform for good. It also benefits std.virt generally — the one-line device virtio_console is worth adding regardless of what else lands.

Reconcile with driver delivery §3: console (vt) sits in the compiled-in tier. A console-capable virtio_console must join it there, or take an explicit documented exception.

Route B — Apple’s private PL011

Debug harness only Zero kernel LOC — but entitlement-gated, and unusable by end users.

Binary analysis of Virtualization.framework shows a private _VZPL011SerialPortConfiguration (serial port type 1) beside the public virtio console (type 2), plus a device table giving four slots at 0x20010000 + n*0x10000, GSIV 32+n. QEMU’s clean-room vmapple machine model places a PL011 at exactly 0x20010000, which corroborates it. FreeBSD would then need only:

hw.uart.console="mm:0x20010000,br:115200,rs:2,rw:4,xo:0"

Measured caveat: the class passes validate() but start() fails with Code=1 under an ad-hoc signature, trapping on virtual-machine-service — the com.apple.private.virtualization gate. Unlocking it means weakening SIP/AMFI. Fine for a lab, never for a shipped tool. Treat as a debugging accelerator, not a solution.

Route C — the virtio-gpu KMS console

Downstream The eventual product, not a debugging tool.

Since VZ has no GOP, the only way a VZ guest ever gets a graphical console is a virtio-gpu driver that publishes one — which is the virtio-gpu plan plus the fbdev work in §6. It cannot help diagnose early boot (it needs a fully running kernel), so it is sequenced last.

4. Remaining boot blockers, ranked

Settled 2026-08-18 Four of these six are now eliminated by measurement (§1): the MADT reports GicVersion = 3, there is no SPCR to be malformed, the GTDT virtual timer is INTID 27, and the FADT sets both PSCI flags. VZ’s firmware description is well formed — the blocker is not in the tables.

Two failures, not one #4 was real and is now fixed. Base virtio_gpu(4) attaching to Apple’s virtio-gpu pegged both vCPUs, because vtgpu_attach() waited on the control queue with an unbounded virtqueue_poll(9). Kernel patch 0011 replaces that with a 5 s bounded dequeue loop that reports and calls virtio_stop() rather than spinning. The spin is gone without removing the graphics device from the VM.

What remains underneath is the second, earlier failure, and it is now the only one: the guest goes idle at 0.1 % with no console output at all, which places it before virtio device probe — early enough that no device-attach-time console, the consdev included, can ever report it.

With xHCI removed, the guest reaches the kernel and goes idle. These are the candidates, ranked by how well they fit that signature, each with the cheapest test. Status reflects what has since been measured.

#HypothesisFitCheapest test / known fix
1MADT GICD Version == 0gic_v3_acpi_identify() bails → panic: No usable event timer foundHigh — explains an idle guest with no outputDump APIC from a Linux VZ guest and read the version byte. Fix exists: read GICD_PIDR2 as Linux does — aabce0c83 on networkextension:apple-vz-gic-version-none, not upstream
2Zeroed/bad SPCR geometry (rs:0,rw:1 vs rs:2,rw:4)HighFreeBSD bug 282936 is the same signature; fixed upstream by dea3eef94caf (D47946), not in releng/14.3. Kernel-side SerialPort.Address == 0 guard analogous to d82698ac68c2 is still unfiled
3virtio feature negotiation — VZ is strict/all-or-nothingHighProbe with hw.vtnet.{csum,tso,lro,mq}_disable="1"; then cherry-pick f9a4dd60b0cf, 8bdc312dac55 (VIRTIO_F_RING_RESET), d4ddf8eaee93, 5481c2d3ac1f from mainnone are MFC’d to stable/15
4vtgpu hangs at attach fixedWas medium-high — it was the 199 % spinFixed in tree by patch 0011: bounded control-queue wait (5 s, 10 µs interval) then device_printf + virtio_stop() + ETIMEDOUT. No nodevice needed, and the fix helps qemu guests too
5GTDT virtual-timer interrupt wrong — an EL1 guest uses GT_VIRT only; vmapple wires only INTID 27MediumSame ACPI dump, iasl -d GTDT
6PSCI conduit resolved to SMCpsci_acpi_get_callfn() reads FADT ArmBootFlags with no probe or fallback; an SMC faults on a guest with no EL3Low — VZ’s DT says hvciasl -d FACP, check bits 0/1

Do this first One Linux guest settles four hypotheses at once. Boot arm64 Linux under VZEFIBootLoader on the same host and copy out /sys/firmware/acpi/tables/{APIC,GTDT,FACP,SPCR,MCFG,DSDT}, then iasl -d them. That answers #1, #2, #5 and #6 with no NextBSD changes at all. A second, direct-kernel boot (VZLinuxBootLoader) yields VZ’s own DTB via dtc -I fs -O dts /sys/firmware/devicetree/base — which could be handed to loader.efi in /boot/dtb/ and selected with kern.cfg.order=fdt, bypassing the ACPI path entirely as a bring-up shortcut.

Note also that bus_probe() in sys/arm64/arm64/machdep.c has no health check and no fallback — the presence of an RSDP alone wins, even if the tables that follow are unusable. And FreeBSD detects hypervisors only via SMBIOS, so under VZ vm_guest is unset and no guest quirks apply (bug 292082 / D54595 proposes boot_el-based detection).

Measured 2026-08-18 Upstream main does not boot under VZ either. The stock FreeBSD 16.0-CURRENT arm64 VM image — which already carries the four merged virtio fixes and ACPI-by-default — loads its kernel and then spins two vCPUs indefinitely with no networking, exactly as NextBSD’s stock kernel does. This removes NextBSD from the suspect list entirely and makes the out-of-tree patch set in §5 a hard dependency, not an optimisation. It also means a VZ boot cannot be fixed by rebasing.

5. Prior art — we are not first

WhoStatusWhat it gives us
networkextension/FreeBSD-vzFreeBSD 16.0-CURRENT boots to multiuser under VZ, headless, 2026-07-06The proof it is possible, a VZ KERNCONF, the GIC-version patch, and the virtio-console consdev port. Their dmesg shows acpi0: <APPLE Apple Vz>, gic0 … v3.0, vtnet0/vtcon0/vtblk0/vtrnd0
tjfontaine/freebsd-apple-virtualizationPublishes dated evidence runsRaw ACPI blobs — potentially skips our §4 dump entirely
Faraz Vahedi, freebsd-src PRs 2319 / 2322Merged to main, 2026-07The four virtio commits VZ needs. Not MFC’d — if NextBSD tracks releng/15.0 we must carry them
OpenBSD 7.9, 2026-05-19“Made OpenBSD work on Apple Virtualization.”Every blocker they hit was virtio feature negotiation, not boot: VIRTIO_NET_F_MTU (VZ refuses to work without it), TSO-without-MRG_RXBUF, and a viogpu mmap returning a KVA where a physical address was required. Also the architectural idea worth stealing: their bootloader synthesizes an FDT from ACPI (efiacpi.c) so the kernel only ever sees FDT
Apple, containerizationReference guest configThe authoritative device list: GICv3, PSCI, arch timer, PCI-generic, virtio-PCI, PL011, PL061, PL031, EFI stub. Console is hvc0; shutdown is PL061 GPIO → gpio-keys, not ACPI PWRBTN
UTM / vfkitExplicitly unsupportedUTM documents macOS and Linux only; vfkit issue 446 has FreeBSD 15.0 failing with “missing device tree”. Nobody ships this — being early is the point

The platform, as measured by others

Worth recording because it differs from QEMU’s virt in ways that break assumptions: RAM is at 0x7000_0000, not 0x4000_0000 (PCIe ECAM sits there instead); GICv3 only, no ITS and no MSI/MSI-X — PCIe is legacy INTx on SPIs; PSCI 0.2 with HVC conduit and an EL1-only guest, so SMC faults; virtio-PCI modern only, no virtio-mmio; 4K or 16K granule, never 64K. Our own acpi.rsdp = 0xefbf0018 is consistent with the 0x70000000 RAM base and inconsistent with a QEMU-virt layout — a useful independent confirmation.

6. The fbdev gap — closed

Shipped 2026-08-19 This section is now history rather than plan. drm_fbdev_shmem.c is vendored into graphics/drivers/gpu/drm/ with four #ifdef __linux__ guards, built by drm_shmem_helpers, and called by default from the virtio-gpu kext (hw.virtio_gpu_drm.fbdev=0 opts out) — kernel-modules#38. The prediction below held: no deferred I/O was required. One further fix was needed that this section did not anticipate — lkpinew_pci_dev() created a pci_dev with no DMA tags, so the first dma_map_sgtable() page-faulted; kernel patch 0012 gives it tags via linux_pdev_dma_init(). With both in place: card0 + renderD128, VT: Replacing driver "virtio_gpu" with new "drmfb", and zero alignment warnings or [drm] *ERROR* lines.

This closes the open question left dangling in the graphics plan §10 (“Did drm-kmod’s core build with CONFIG_DRM_FBDEV_EMULATION?”) and corrects the rationale currently written into graphics/drm_shmem_helpers/Makefile.

Enabling drm_fbdev_shmem.c in CI produced exactly 8 errors, no missing headers, no incomplete types: fb_deferred_io_init/_cleanup undeclared, fb_WARN_ON_ONCE undeclared, and fbdefio missing from struct drm_fb_helper and struct linux_fb_info.

Corrected Deferred I/O is not needed at all. The Makefile’s note that “wiring deferred I/O into drm-kmod’s fb_helper is its own piece of work” is wrong. drm_fbdev_ttm.c — which drm-kmod does build, and which is why BochsGraphics has a console — makes the identical calls and simply wraps them in #ifdef __linux__. Everything in Linux’s fb_defio.c exists to service a userspace mmap() of /dev/fbN, and drm-kmod creates no such cdev.

The console’s damage path is separate and already implemented and exported: vt_drmfb draws only through info->fbops->fb_imageblitdrm_fb_helper_damage_area()fb_dirty → our drm_atomic_helper_dirtyfbvirtio_gpu_cmd_transfer_to_host_2d() + resource_flush. vt(4) already batches at 25 Hz (VT_TIMERFREQ) — the same order as Linux’s HZ/20 defio delay, with exact damage clips rather than full-screen blasts.

So the fix is ~20–40 LOC of stubs, with upstream precedent: drm-kmod commit 2ac98d2787 (wulf@, 2026-06-14, on master but not yet 6.12-lts) adds the empty fb_deferred_io_cleanup() and drops the __linux__ guard; Phabricator D57488 moves this code into base keeping the stubs. Implementing real deferred I/O would fight the direction of travel and needs VM plumbing FreeBSD lacks (vm_page has no lru).

Three runtime traps the compiler will not catch: drm_fb_helper_deferred_io is declared but __linux__-guarded out (undefined symbol at load — tools/check-kext-symbols.py catches it); fb_deferred_io_mmap() is a live panic(); and shmem’s fix.smem_start == 0 means vt_fb_mmap() would hand out pages nothing ever flushes, so set FB_FLAG_NOMMAP.

Scope note: this unlocks shmem-backed fbdev only — i.e. virtio-gpu, plus the other shmem consumers (ast, mgag200, cirrus, hyperv) our drm_shmem_helpers module already pays for. Bochs and VBox are unaffected: they are TTM-backed and already work.

OpenBSD’s viogpu.c is the counter-example worth knowing: it transfers and flushes the whole screen every 10 ms with no damage tracking, from a non-DRM wsdisplay driver. It proves a crude periodic flush is viable, and simultaneously that FreeBSD’s vt_drmfb path is the better of the two.

7. Reconciling with V7 and Option D

The virtio-gpu plan carries standing constraint V7: never add device virtio_gpu to the NEXTBSD kernel config, and arm64 KMS options §Option D recommends nodevice virtio_gpu, because base’s driver registers at VD_PRIORITY_GENERIC+10 against efifb’s +1 and displaces the console that works.

Both remain correct, with one qualification neither page states: they assume a firmware framebuffer exists. That holds when QemuRamfbDxe produces a GOP with a real linear framebuffer. It does not hold on a VM whose only display device is the virtio-gpu — there OVMF’s GOP is Blt-only, and under Apple’s EFI there is no GOP at all. On such a machine nodevice virtio_gpu leaves VT: init without driver and a black screen from kernel start until a kext loads. Measured on a UTM guest configured -vga none -device virtio-gpu-pci.

Settled 2026-08-19 — the other way V7 and Option D are withdrawn for virtio-gpu. Base virtio_gpu(4) stays compiled in, and the kext takes the device from it. The reasoning above is why: on a machine whose only display is the virtio-gpu — which is every VZ guest — base is not a nuisance competing with efifb, it is the only console there is. nodevice buys KMS at the price of a blind boot from the first kernel message until userland loads a kext, panics included. That is the wrong trade on the one platform we cannot currently debug.

Restated constraint: “never rely on base virtio_gpu(4) as the final console, but do keep it as the first one, and hand the device over explicitly once the KMS kext is resident.”

The handover mechanism, since newbus will not do it for us. It remains true that there is no re-bidbus_generic_driver_added() re-probes only DS_NOTPRESENT children, and releng/15.1 has no DF_REBID — so an attached vtgpu0 is never displaced by a better driver arriving later. Two pieces close that:

Measured 2026-08-19 Atomicity is the whole design, not a detail. Detaching whichever driver currently owns the console panics the machine seconds later, when the next console write reaches a driver that is gone — vtgpu_detach() calls vt_deallocate() and nothing repaints until a replacement registers. Both devctl detach vtgpu0 and devctl clear driver -f virtio_gpu_drm0 did exactly that, the latter twice; clear driver is just as fatal because it detaches first and re-attaches only if device_probe() succeeds. The atomic form, devctl set driver -f vtgpu0 virtio_gpu_drm, hands over cleanly. Any in-kernel trigger must do the same and never a bare detach.

Result, on a stock arm64 boot with nothing typed: base paints the console from VT: initialize with new VT driver "virtio_gpu", kextd loads the bundle the scan asked for, the kext takes the device, and vt switches to drmfb — console the whole way through, plus card0. This is the shape VZ will inherit for free once it boots at all.

8. Plan

Phase 0 — stop killing the VM done 2026-08-18

Remove USB from the harness; add nodevice xhci / hint.xhci.0.disabled="1" to the guest side so third-party front-ends cannot trip it. Exit criterion met: guest survives >120 s.

Phase 1 — give the guest a voice merged

Done: the consdev landed as patch 0010 in nextbsd-kernel (arm64 GENERIC already includes std.virt, so no config change was needed), and VZ’s ACPI tables were dumped from a Linux guest booted on the same host. A carried GIC-version patch was landed alongside it and then dropped once the MADT proved it inert.

Exit criterion not yet met. The consdev is resident and correct (cn_pri = CN_NORMAL, cnadd(9) at attach) but produces nothing, because the guest stops before the virtio bus is probed. A console that registers at device-attach time cannot report a pre-attach failure — a limitation worth recording, since it is the whole premise of Route A.

Phase 2 — make it boot

the only thing left The table-driven theories are spent and the vtgpu spin is fixed (patch 0011), so this is now the whole remaining task. Since the guest is mute before device probe, the one technique that needs no console is a PSCI tracer: patch an HVC SYSTEM_OFF (0x84000008) in at a chosen early point and watch for a clean power-off on the host — guestDidStop is observable from the harness — then binary-search the boot path. Roughly log₂(N) CI builds, and it reports through the hypervisor rather than through any guest device, which is precisely why it survives a failure this early.

Note what this no longer needs: no console work, no graphics work, no ACPI work. Those were the plausible-looking prerequisites, and every one has since been either measured away (§1) or shipped (§6, §7). Exit criterion: multiuser, DHCP lease, ssh.

Phase 3 — the console people actually see done on qemu 2026-08-19

Landed as kernel-modules#37 (probe priority), #38 (drm_fbdev_shmem), #39 (atomic takeover) and kernel#76 (present-scan look-through), with kernel patch 0012 supplying the DMA tags the fbdev path needs. Exit criterion met on arm64 qemu, unattended, on a stock boot:

vtgpu0: <VirtIO GPU> on virtio_pci1
VT: initialize with new VT driver "virtio_gpu".
vtgpu0: detached
virtio_gpu_drm0: <VirtIO GPU (DRM/KMS)> on virtio_pci1
[drm] Initialized virtio_gpu 0.1.0 for virtio_pci1 on minor 0
VT: Replacing driver "virtio_gpu" with new "drmfb".
/dev/dri/card0  /dev/dri/renderD128

The VZ half remains untested, for the obvious reason — it needs Phase 2 first. Nothing in this path depends on a GOP or a firmware framebuffer, so it should carry over unchanged; that expectation is reasoned, not measured, and is flagged as such deliberately.

Phase 4 — upstream and CI

Offer the virtio-console consdev and the GIC-version fallback to FreeBSD; add a VZ boot lane once a headless boot is reproducible. Note the boot-test guard in kernel-modules greps kldstat, which cannot see a compiled-in driver — it should read kern.conftxt.

9. Open calls

  1. Do we carry the four virtio commits locally, or wait for an MFC to stable/15? They are merged to main only.
  2. Does virtio_console join the compiled-in tier in driver delivery §3, or take a documented exception?
  3. Contact networkextension and tjfontaine before writing code — two parties are already on this platform, and one has it booting. Given that stock main spins (§5), their patch set is the dependency; the question is whether we carry it, or help push it upstream.
  4. Is the OpenBSD trick (synthesize FDT from ACPI in the loader) worth adopting as a bring-up shortcut, given bus_probe() has no fallback when ACPI is present but unusable?
  5. Does the harness belong in swift-vm-cli (which already does the VZ + virtio-console half) rather than living as a scratch tool?
  6. Restate V7 / Option D per §7, or leave them and add the qualification only here? Answered 2026-08-19: both are withdrawn for virtio-gpu — base stays compiled in and the kext takes the device (§7). The peer pages still need editing to match.

Sub-plan of the NextBSD graphics plan where it touches the console; a peer of the virtio-gpu DRM plan and the arm64 KMS decision record, which it amends in §7. Everything tagged measured was observed on 2026-08-18 (VZ, macOS 26.5.2) or 2026-08-19 (arm64 qemu guest, kernel 20260819-184420) against NextBSD arm64; upstream claims are linked to source. Claims derived from disassembling Apple’s framework are marked and are the least durable material here — they describe private, undocumented API and may change without notice.