Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Proposal: Virtio-9p Host Directory Passthrough

A QEMU-hosted fixture driver mounts a host directory into a capOS guest as Directory/File capabilities over the virtio-9p device, using the 9P2000.L protocol. Read-only and structurally separate writable grant sources are implemented under either the full qemu feature or the composable virtio_9p_host_fixture feature. Builds with neither feature refuse both grant sources: this is development infrastructure, never a production path.

Motivation

Three concrete needs, in priority order:

  1. Dev-loop payload injection. Today every guest-visible input — WASI payloads, Lua scripts, test corpora, fixture files — must be baked into the boot ISO or a disk image (tools/mkstore-image) before boot. Changing one test input costs a full ISO rebuild. A host-shared directory removes that rebuild from the inner loop.
  2. Artifact export from proofs (write slice). QEMU proofs assert by grepping serial-log markers. With guest→host file export, a proof can write structured results (reports, dumps, benchmark tables) that host harnesses read directly — richer assertions than ordered-marker matching, and a natural output channel for a future declarative proof runner.
  3. Task-backend persistence shortcut. The Self-Hosted Task Backend track aims to replace the host-side PostgreSQL task board with a capOS-served coordinator. Its durable serve loop is currently blocked on a spawn-grant authority gap for BlockDevice caps. The writable 9p Directory now has a single-writer child spawn-grant path, so it can give the coordinator restart-persistent, host-inspectable state files through an authority path that exists today.

What Exists Today

Directory/File/Store/Namespace are established Cap’n Proto interfaces with several backers: the RAM-backed kernel fixtures (kernel/src/cap/directory.rs, kernel/src/cap/file.rs, kernel/src/cap/store.rs, and kernel/src/cap/namespace.rs), the block-device filesystems (readonly_fs, fat_fs, persistent_store, writable_fs), the boot-ISO reader (installable_image), and the QEMU-hosted virtio-9p host-share fixture described here. Disk-backed paths require an image prepared host-side; virtio-9p instead exposes one dedicated host directory at QEMU launch.

The pure protocol layer in capos-lib/src/ninep.rs implements the bounded read and write subsets. It encodes requests and decodes replies with negotiated msize, frame-size, tag, reply-type, counted-payload, directory-entry, and attribute-mask validation. Host tests cover the supported request/reply shapes and hostile frames; fuzz/fuzz_targets/ninep_reply_decode.rs exercises structured reply decoding. This codec has no transport I/O of its own.

The kernel-side fixture driver that carries it over the wire is implemented (Virtio9pDriver in kernel/src/virtio.rs): it probes the modern-PCI device, negotiates features, brings up a polled request virtqueue, and completes the Tversion/Tattach session handshake against a QEMU -fsdev local share, proven by make test-virtio-9p-bringup. Above it, kernel/src/cap/virtio_9p_fs.rs serves the share as read-only Directory/File capabilities through the fixture-gated virtio_9p_root grant source, proven by make test-virtio-9p-fs. The separate virtio_9p_root_writable source serves the bounded write subset, proven by make test-virtio-9p-write; a read-only grant cannot be upgraded at runtime. WASI payload reload consumes the read-only source today. Host-inspectable task-backend persistence on the writable source is the active follow-on.

Two distinct transport surfaces remain mutually exclusive at the virtio-net ownership boundary. The explicit virtio_9p_host_fixture feature is the narrow exception for a different PCI function: it carries only the kernel-side 9p subset into the userspace-network build without enabling the full qemu surface or weakening the Nic guard.

  • Kernel-side split-ring machinery, available under qemu and for 9p under virtio_9p_host_fixture. kernel/src/virtio.rs carries a generic virtqueue implementation (Virtqueue, submit_request_chain, notify, poll_used, poll_used_within_ns) over the device-agnostic modern-PCI surface kernel/src/virtio_transport.rs. bring_up_blk_device uses it to drive the virtio-blk fixture that backs readonly_fs, fat_fs, persistent_store, and writable_fs; those block-device and other full Device Driver Foundation (DDF) consumers remain qemu-only.
  • Userspace MMIO driver authority, available only under not(qemu). The cloud_virtio_net_userspace_*_proof and cloud_nvme_controller_reset_proof features admit narrow, selected-write userspace DeviceMmio.write32 access. Each is documented in kernel/Cargo.toml as mutually exclusive with qemu, and they are staged from real cloud PCI enumeration, not from QEMU.

Under --features qemu, userspace MMIO write authority exists but is confined to narrow per-register claims staged by the grant source. device_manager/qemu_full.rs’s validate_devicemmio_write32_claim admits only three shapes: an exact pinned (offset, value) pair for the virtio claims (the virtio-net TX notify doorbell, the virtio-rng MSI-X vector-control write); a value-flexible NVMe CC write that is accepted only with CC.EN clear, i.e. a controller reset; and brokered NVMe doorbells whose address-bearing fields the kernel materializes from its own ledger. Any other offset is unclaimed and fails closed.

None of those shapes let userspace program the virtio common-config queue-address registers, set queue_enable, or drive the feature-negotiation handshake — those offset constants are compiled only under the not(qemu) cloud_virtio_net_userspace_ownable_vring_proof and cloud_virtio_net_userspace_queue_enable_driver_ok_proof features, which took a multi-slice ladder to land.

Separately, and on both axes, no capability exists for userspace-authored descriptor chains or used-ring polling on a live queue. Even in the not(qemu) NIC ladder the userspace driver handles only opaque deviceIova bounce tokens that the kernel resolves; descriptor authoring and used-ring completion stay kernel-mediated behind a typed Nic capability.

An earlier revision of this document claimed the userspace driver track had already landed the groundwork this proposal needs. That was an overstatement: it read not(qemu) production-path proofs as if they were available to a qemu fixture. The Driver Boundary Decision below supersedes it.

Driver Boundary Decision

Decided: a kernel-side QEMU-hosted fixture driver (Option A). virtio-9p is implemented in kernel/src/virtio.rs reusing the existing generic split-ring machinery exactly as bring_up_blk_device does for virtio-blk. The Directory/File capabilities are served by a kernel cap module backed by that driver, mirroring how readonly_fs and fat_fs back the same interfaces over BlockDevice. Userspace consumers — shell ls/cat, wasm-host, the task coordinator — see only the existing typed Directory/File interfaces and need no new interface, which was the point of the userspace-driver framing in the first place.

The original full fixture path remains inside --features qemu; the later composable extraction exposes the same 9p subset through the explicit virtio_9p_host_fixture proof feature. Both routes fail closed when neither feature is selected, introduce no new authority class, and reuse the same Virtqueue/VirtqueueDma seam governed by the settled DMA isolation design (docs/dma-isolation-design.md), so they add no new DMA backend.

Two costs are real and belong to slice 1 rather than to the decision: submit_request_chain is device-agnostic, but Virtqueue still carries virtio-net-specific fields whose factor-out the code defers to “the first block-device caller”, and a 9p driver needs its own VirtqueueDma implementation.

Rejected alternatives:

  • Option B — a new userspace MMIO authority class under qemu. Would preserve the userspace-driver shape, but requires a virtio_9p_* selected-write policy for the common-config handshake and queue-address registers and queue_enable — none of which the qemu axis admits today — plus a capability for userspace-authored descriptor chains and used-ring polling on a live queue that exists nowhere in the tree. (The notify doorbell alone is not the gap: a pinned-claim virtio doorbell write already exists under qemu.) That re-implements the NIC-driver ladder on a third feature axis and extends past anything landed, for a development fixture. Cost is not justified by the benefit; if a userspace virtio driver authority class is wanted, it should be motivated by a production device, not by a dev-loop convenience, and decomposed as its own ladder.
  • Option C — relocate the proof to the not(qemu) production axis. Would reuse the landed cloud_virtio_net_userspace_* authority chain, but it contradicts this proposal’s core premise: virtio-9p passthrough is development infrastructure that must never be a production path. It would also stage a dev-loop fixture behind real cloud PCI enumeration, which defeats the dev-loop motivation entirely.

The accepted cost of Option A is that the driver is kernel-side rather than a userspace driver process. That is the same trade already accepted for the virtio-blk fixture, and it remains bounded because the code is selected only by the full qemu feature or the explicit virtio_9p_host_fixture proof feature. Neither is a default or implied cloud feature, and builds with neither fail closed, so this does not grow the production kernel’s device surface.

Design

  • Device: virtio-9p (-fsdev local,path=<hostdir>,security_model=none, readonly=on -device virtio-9p-pci,mount_tag=...). Fully emulated inside QEMU; no external daemon.
  • Protocol: 9P2000.L, minimal client subset. Read path: Tversion/Tattach/Twalk/Tlopen/Tread/Treaddir/Tgetattr/ Tclunk. Write slice adds Tlcreate/Twrite/Tfsync/Trename/ Tunlinkat. Message codec is pure no_std logic in capos-lib style: host-tested, fuzzable, bounded, fail-closed on malformed replies.
  • Driver shape: a kernel-side QEMU-hosted fixture driver over the generic split-ring machinery in kernel/src/virtio.rs (see the Driver Boundary Decision above), polled — no MSI-X dependency, matching the virtio-blk fixture. A dev fixture has no latency requirement that justifies interrupt plumbing.
  • Capability surface: a kernel cap module serves the existing Directory/File interfaces over the 9p client, the way readonly_fs and fat_fs serve them over BlockDevice. Consumers (shell ls/cat, wasm-host, the task coordinator) need no new interface. Read-only exports serve a Directory whose mutating methods fail closed.
  • Manifest gating: the grant sources require either qemu or the explicit virtio_9p_host_fixture proof feature and fail closed with neither. Write-enabled exports additionally require a distinct manifest source so a read-only share cannot be silently upgraded.

Why 9p and not virtiofs

virtiofs needs an external virtiofsd daemon, vhost-user shared-memory plumbing, and a guest FUSE client — an order of magnitude more machinery whose payoff is performance, which is irrelevant for a development fixture. virtio-9p is built into QEMU, its transport is plain virtqueues over the already-landed modern-PCI surface, and the 9P2000.L request/reply messages map nearly 1:1 onto the Directory/File capability methods. Other rejected alternatives: fw_cfg (single small blobs, no directory semantics) and keeping the disk-image-only workflow (remains correct for production-shaped storage proofs, but leaves the dev loop and the spawn-grant shortcut on the table).

Task-Backend Shortcut: Honest Trade-offs

The CAPOSRS1 WAL record store over BlockDevice already provides durable, crash-recovering coordinator persistence (make test-task-coordinator-persist) — 9p adds no new durability capability. What it adds:

  • A grant path that works now. Directory spawn-grants are landed; the BlockDevice spawn-grant gap blocks the durable Endpoint serve loop.
  • Host-inspectable state. Record-per-file layout (one file per task, current/temp/backup publication) makes the board state a plain host directory: greppable, diffable, backed up or versioned with ordinary tools, no image mounting. Task keys use canonical lowercase-hex UTF-8 filenames, so arbitrary valid keys remain reversible without becoming paths.
  • Operational simplicity for self-hosting. A capOS-in-QEMU task backend whose state survives VM restarts in a host directory is a realistic dogfooding deployment shape for the multi-agent workflow.

Limits to state plainly: durability depends on the host filesystem and QEMU’s Tfsync handling, weaker than the fenced CAPOSRS1 frame guarantees — the WAL-over-BlockDevice path remains the reference persistence design, and the 9p path is a pragmatic parallel track, not a replacement. Single writer only. QEMU-hosted by construction; a production deployment still needs the NVMe/ writable_fs path or the future userspace storage service. The implementation accepts at most 20 tasks and 60 directory entries, rejects malformed, stray, duplicate, oversized, or ambiguous recovery state, and keeps the previously published record for every pre-publication crash window. A boot validates the complete restored graph before serving. It preserves fencing generations but invalidates restored leases because boot-relative monotonic expiry instants are not comparable across restarts.

Decomposition

Tracked as loopyard tasks; each behavior slice carries its QEMU proof and the driver slices maintain a docs/devices/virtio-9p.md provenance map as part of the same change:

  1. virtio-9p kernel fixture bring-upLanded: kernel-side modern-PCI probe, feature negotiation (VIRTIO_F_VERSION_1 + VIRTIO_9P_F_MOUNT_TAG), and polled request-virtqueue setup over virtio_transport in kernel/src/virtio.rs, following bring_up_blk_device; Tversion/Tattach handshake driven through the landed capos_lib::ninep codec. make test-virtio-9p-bringup asserts the negotiated msize/version, the mount tag read back from device config, and the attach qid’s directory type bit from a kernel-side serial diagnostic, following the diagnose_virtio_blk_transport precedent. That slice granted no userspace authority on its own: the Directory/File export and its grant source arrived in slice 3 below. No userspace DeviceMmio write authority is involved. Provenance map: virtio-9p.
  2. 9P2000.L client coreLanded: host-tested no_std read-subset message codec, bounded fail-closed reply decode, and the ninep_reply_decode fuzz target.
  3. Directory/File caps over 9pLanded: read-only Directory/File served by kernel/src/cap/virtio_9p_fs.rs over the driver’s bounded read subset (Twalk/Tlopen/Tgetattr/Treaddir/Tread/Tclunk), alongside readonly_fs/fat_fs, behind the fixture-gated virtio_9p_root grant source. make test-virtio-9p-fs has a guest consumer list the host share, read exact host bytes whole and at an offset, and observe every mutating and traversing method fail closed. A kernel built with neither qemu nor virtio_9p_host_fixture compiles no 9p cap module and resolves the source to kernel source `virtio_9p_root` requires the qemu feature (or the composable virtio_9p_host_fixture feature).
  4. Write supportLanded: the write-path messages (Tlcreate/Twrite/Tfsync/Trename/Tunlinkat) in the codec and a separate driver write façade, served by a second, structurally attenuated cap-type pair (Virtio9pWritableDirectoryCap / Virtio9pWritableFileCap) behind the fixture-gated virtio_9p_root_writable grant source. Naming that source in the manifest is the explicit write flag: there is no rights flag and no method that upgrades a read-only cap, so the choice is fixed at spawn. One fixture-gated raw spawn grant may mint the writable root for a child; the root and writable File results are nontransferable, while read-only results retain same-session copy delegation. make test-virtio-9p-write runs four stages: a read/write share whose bytes are verified on the host, a readonly=on share where the server refuses write intent with EROFS while reads keep working, the single-writer child-grant proof, and a two-boot forced-QEMU-poweroff Tfsync stage with a pre-sync negative control. The first stage covers multi-chunk Twrite resubmission, but server-reported short writes and zero-count WriteStalled handling remain unproven. The device provenance map is authoritative for the fourth stage’s bounded outcome and for the remaining Tfsync and non-atomic Trename residuals: virtio-9p.
  5. Task-coordinator 9p persistenceLanded: the Endpoint-serving coordinator optionally consumes the spawn-granted writable 9p Directory, writes one canonical record per task, and recovers the bounded current/temp/backup publication windows before serving. make test-task-coordinator-9p proves state reload across two VM boots, host-visible clean state files, preserved lease generations with restored leases invalidated, and a post-restart mutation. This path remains weaker than the fenced CAPOSRS1 WAL and is not production storage durability.
  6. WASI payload hot-reloadLanded: wasm-host reads a bounded, single-component payload through an explicitly granted read-only Directory, while manifests without that grant retain the embedded-payload path. make test-wasi-9p-reload builds one payload-free ISO, rejects missing, oversized, malformed, and truncated host-share payloads, and executes two distinct host-swapped payloads during one success boot without rebuilding the ISO. Read-only 9p File capabilities bind reads to the qid and metadata captured at open, so an atomic host replacement cannot substitute bytes under a stale size snapshot; the synchronized replacement proof is part of make test-virtio-9p-fs. The completed implementation is commit 1eb399e2 (2026-07-20 05:28 UTC). Preview 1 proc_exit now terminates only the current wasm instance and returns its status to the host loop. The focused proof runs two distinct exit-zero payloads across host replacements, records each exit code, and keeps wasm-host alive between instances. Every fixture payload opens and retains a File result capability and attenuates its borrowed preopen rights before terminating; the same QEMU transcript proves teardown releases one File, clears its fd slot and argv/environment buffers, and restores the preopen rights before a successor starts. The same boot distinguishes a non-zero exit from a runtime trap and does not label either outcome as a successful reload. Single-shot payloads still map proc_exit(code) back to the wasm-host process status. This remains a bounded four-instance QEMU development proof, not a general restart-policy or long-running module supervisor: wasmi 1.0.9 exposes no instance-removal API, so completed instances and linear memories remain allocated in the shared Store until wasm-host exits.
  7. Composable fixture featureImplemented (this document, Composable Fixture Feature). Extracts the 9p fixture from the monolithic qemu axis into a virtio_9p_host_fixture feature that also builds in a not(qemu) cloud kernel, so the writable-9p persistence path and the Phase C userspace network stack co-boot for the host-accessible task backend. The not(qemu) fixture build routes device claim through the minimal device_manager::virtio_9p_fixture_claim surface (the qemu-only DDF ledger, net/blk/rng drivers, prove_qemu_* harness, and IOMMU diagnostic stay #[cfg(feature = "qemu")]), and PCI enumeration takes the dedicated pci::diagnose_virtio_9p_host_fixture startup route. Co-residency is proven by make test-virtio-9p-net-coboot: one not(qemu) boot serving both the Phase C userspace Nic path and a writable virtio-9p Directory, with the writable-9p write proof verified on the host side of the share.

Security Considerations

The guest→host boundary is the new surface. Mitigations: the share is scoped to one dedicated host directory; QEMU-level readonly=on mirrors the capability-level read-only export (defense in depth); security_model=none maps all access to the QEMU process’s own uid — the guest can never act as another host principal; write-enabled shares are opt-in per manifest and should point only at dedicated scratch/state directories. The 9p client treats the QEMU server as untrusted input: all replies are bounded and fail closed, and reply parsing is host-fuzzed like the other mount/wire parsers.

Composable Fixture Feature (virtio_9p_host_fixture)

Why the qemu axis is not enough

The Host-Accessible Persistent Task Backend milestone needs one boot that serves both the writable-9p persistence path (host-inspectable task state) and the Phase C userspace HTTP/JSON network topology (the host-API surface, task-backend-9p-api-persistence). Those two capabilities sit on disjoint feature axes today:

  • Writable 9p is reachable only under --features qemu (KernelCapSource::Virtio9pRootWritablecap::virtio_9p_fs, the real mod virtio in kernel/src/main.rs).
  • The Phase C userspace network stack builds under the not(qemu) cloud_virtio_net_userspace_sustained_receive_pool_proof chain, whose transitive cloud_virtio_net_userspace_features_ok_proof trips an explicit compile_error! against qemu (kernel/src/cap/mod.rs): the qemu DDF backend and the proof-only selected-write virtio-net common-config grant would race the same virtio-net BDF.

Directly composing the two feature worlds is therefore rejected by construction, and it should stay rejected: virtio-net ownership must not be shared between the kernel DDF fixture and the userspace driver. The chosen direction keeps userspace networking unchanged and instead makes virtio-9p — a different PCI device on a different BDF, with no virtio-net contention — composable into the not(qemu) build behind its own feature. This does not weaken the Nic × qemu guard, does not restore kernel TCP/socket ownership, and adds no networking surface.

Dependency cut (empirically derived)

A compile probe re-gated the minimal 9p surface — mod virtio (real) and mod iommu in main.rs, mod virtio_9p_fs plus the Virtio9pRoot / Virtio9pRootWritable grant-source arms in cap/mod.rs, and the Writable9pSpawnReservation spawn arm in cap/process_spawner.rs — onto any(feature = "qemu", feature = "virtio_9p_host_fixture") and built the kernel --no-default-features --features virtio_9p_host_fixture --target x86_64-unknown-none. (The kernel declares no default feature, so --no-default-features is a no-op; it is spelled out only for reproducibility.)

The build stopped with 67 distinct unresolved-symbol errors (46× E0425, 21× E0433), all located in kernel/src/virtio.rs and zero reported from cap/virtio_9p_fs.rs, device_dma.rs, virtio_transport.rs, or capos_lib::ninep. No compile_error! fired. This is partial dependency-cut evidence, not a successful build: unresolved names can prevent later items from being type-checked and can hide additional diagnostics.

Adding the network chain (virtio_9p_host_fixture, cloud_virtio_net_userspace_sustained_receive_pool_proof) produced the byte-identical 67-error set (delta 0). That shows only that the incomplete probe exposed no additional diagnostic before the same failure point. It makes the feature composition plausible because the existing mutual-exclusion targets qemu, but it neither proves that the two features coexist successfully nor rules out errors hidden behind the unresolved names. Successful builds of all five matrix cells and the co-boot proof below are the buildability evidence.

Bucket A — required 9p infrastructure (must be available in not(qemu)).

  • Surfaced by the probe as unresolved edges inside the 9p bring-up, currently qemu-gated, mechanically re-gatable to not(qemu):
    • pci::DmaRemappingRequesterBinding and its builder pci::dma_remapping_requester_binding_for_device — threaded through virtio::diagnose_virtio_9p_transport; built from the always-available capos_lib::device_authority types plus acpi::DmarDiscovery. The 9p driver carries this binding value; it never calls into iommu.rs.
    • device_manager::claim_pci_function and device_manager::attach_dmapool_record_with_remapping — today defined only in device_manager/qemu_full.rs. These are the same device-ownership + DMA-pool-attach functions the virtio-blk fixture uses, i.e. genuine shared single-queue-fixture infrastructure, not 9p-bespoke. Making 9p composable requires these two (plus DeviceOwner::Virtio9p) to exist in the not(qemu) surface — extracted into the always-built device_manager surface / a shared submodule, or provided by the stub backend, not the whole qemu_full DDF backend.
  • Also qemu-gated and required for a live cloud wire-up, but not surfaced as an error because its caller is itself still gated: the module-private diagnose_qemu_virtio_9p in pci.rs. pci::enumerate does not call this helper today. The call is nested at the end of pci::diagnose_qemu_virtio_net, which is itself reached only from the #[cfg(feature = "qemu")] diagnostics block in main.rs. The composable path therefore needs the independent startup route defined below; merely re-gating the helper cannot bind a fixture-only device.
  • Already resolved in not(qemu) — no change needed, listed so the cut is complete: DeviceOwner::Virtio9p (always-built device_manager/handles.rs); the device_dma single-queue 9p pool (begin_virtio_9p_pool, register_virtio_9p_queue, allocate_virtio_9p_page, free_virtio_9p_page, and the VIRTIO_9P_* budgets, in always-built device_dma.rs); the generic split-ring machinery (Virtqueue, the VirtqueueDma trait, DmaPage, VirtqueueDescriptorTracker, submit_request_chain, poll_used, discover_modern_transport); the always-built virtio_transport modern-PCI surface; pci::is_virtio_9p and the VIRTIO_9P_*_DEVICE_ID constants; and the capos_lib::ninep codec.

Bucket B — unrelated qemu/DDF surface, stays qemu-only (must NOT be pulled into the fixture build). The remaining ~54 edges are the net/blk/rng drivers and the DDF proof harness sharing one virtio.rs and one device_manager/qemu_full.rs:

  • the prove_qemu_* DDF hostile-smoke family (ownership, teardown, and the devicemmio/dmapool/dmabuffer/interrupt cap-release, driver-crash, reset-disable, and stale-DMA-completion hooks) and its *ProofError types;
  • the userspace-provider grant-source modules cap::{devicemmio,interrupt,dmapool}_grant_source;
  • crate::iommu — reached only via the virtio-rng IommuRngDmaVehicle proof (iommu.rs itself carries an inner #![cfg(feature = "qemu")]), so VT-d IOMMU is confirmed not a 9p dependency;
  • sched::current_cpu_lease_nohz_active, the net-provider helpers (provider_notify_doorbell_write_count, provider_route_device_handle_for_bdf_owner), and the net/rng bring-up device-manager functions attach_devicemmio_record, attach_interrupt_source, and select_first_decoded_memory_bar_region.

The polled 9p driver claims no MSI-X route (device_manager/qemu_full.rs already returns None for DeviceOwner::Virtio9p), so none of the interrupt/MSI-X surface in Bucket B is needed.

Independent 9p startup route

The not(qemu) fixture path must not enter the QEMU net diagnostic wrapper. The implementation extracts a dedicated PCI entry point such as pci::diagnose_virtio_9p_host_fixture(dmar), gated for virtio_9p_host_fixture, and calls it from main.rs after ACPI discovery and pci::record_mcfg_probe under all(feature = "virtio_9p_host_fixture", not(feature = "qemu")). That entry point enumerates PCI once for its path and passes the resulting device list to the existing 9p-only bind helper. It selects only pci::is_virtio_9p, claims only DeviceOwner::Virtio9p, and never invokes the virtio-net/blk/rng binders, the IOMMU diagnostic, or DDF proof entry points.

The full-qemu path keeps its current single enumeration and 9p-helper call inside pci::diagnose_qemu_virtio_net; main.rs must not call the new fixture entry point in that build, which avoids a duplicate 9p claim. An absent 9p function still takes the existing not-found path, leaving both grant sources unable to mount.

The virtio.rs split

The crux is that virtio.rs is monolithic but structurally separable. The failed probe exposed no diagnostics outside virtio.rs, and its visible unresolved edges distinguish the shared ring/9p dependencies from the net/blk/rng drivers and prove_qemu_* harness. It did not compile either subset successfully, so the matrix builds remain the check on this classification.

The implementation gates virtio.rs at item granularity so a virtio_9p_host_fixture build compiles the generic scaffold and 9p driver (Virtio9pDriver, its VirtqueueDma implementation, bring_up_9p_device, diagnose_virtio_9p_transport, and the VIRTIO_9P_* constants) while excluding the real QEMU net/blk/rng drivers and DDF proof items. The qemu implementation items stay #[cfg(feature = "qemu")]; the shared scaffold and 9p driver use any(feature = "qemu", feature = "virtio_9p_host_fixture").

Selecting that real 9p module cannot discard the current non-QEMU typed-negative network façade. cap::network is always compiled and consumes the handle, error, configuration, TCP, UDP, and polling symbols currently provided by virtio_stub.rs. The split therefore moves that façade into a non-QEMU submodule (or equivalently includes and re-exports it) whenever not(feature = "qemu"), including fixture-only and net-plus-fixture builds. Those functions keep returning the same fail-closed results; they are compatibility types, not a kernel networking implementation and not Bucket B authority.

Splitting virtio.rs into a virtio/ module directory (scaffold, blk, net, network_stub, rng, ninep, proofs) is the recommended shape, but an equivalent façade/re-export layout is acceptable if all three configurations remain type-correct: full qemu, fixture with the real 9p subset plus the negative network façade, and neither feature with the negative façade alone. The virtio.rs scaffold factor-out already flagged in the Driver Boundary Decision (the virtio-net-specific fields on Virtqueue) is adjacent to this split and should land with it.

Feature definition and fail-closed cfg rules

# kernel/Cargo.toml
virtio_9p_host_fixture = []   # no implied features; does NOT imply qemu
  • The feature is additive and orthogonal to qemu: qemu continues to imply the whole fixture, so every existing --features qemu path is unchanged. virtio_9p_host_fixture alone brings the 9p subset into a not(qemu) build.
  • Every 9p code gate becomes any(feature = "qemu", feature = "virtio_9p_host_fixture"). Bucket B stays #[cfg(feature = "qemu")].
  • Writable 9p stays opt-in and fail-closed. The manifest gate is unchanged: KernelCapSource::Virtio9pRootWritable binds only when either qemu or virtio_9p_host_fixture is compiled in, and naming virtio_9p_root_writable in the manifest remains the sole write opt-in (no runtime upgrade of a read-only cap). In a build with neither feature, both virtio_9p_root and virtio_9p_root_writable resolve to a kernel source ... requires ... refusal — the fixture is never a production authority. capos_config::virtio_9p_writable_source_refusal keeps rejecting the writable source in non-fixture manifests. The current refusal string names only the qemu feature; the implementation slice broadens the wording (and the refusal helper’s build-condition input) to name qemu or virtio_9p_host_fixture, so the diagnostic stays accurate in the new build shape.
  • The feature must not be a default and must not be implied by any cloud/production feature; it is dev/proof infrastructure, selected explicitly by the co-boot proof target only.

Feature matrix

Buildvirtio-9p read-onlyvirtio-9p writablePhase C userspace netFull DDF (qemu_full)
default (neither)fail closedfail closednono (stub)
qemuyes (kernel fixture)yes (opt-in manifest)no (compile_error! on the NIC chain)yes
Phase C net only (cloud_virtio_net_userspace_sustained_receive_pool_proof)fail closedfail closedyesno (stub)
fixture only (virtio_9p_host_fixture)yesyes (opt-in manifest)nono (stub)
net + fixture (…sustained_receive_pool_proof, virtio_9p_host_fixture)yesyes (opt-in manifest)yesno (stub)

The last row is the target milestone cell. The implementation builds all five matrix cells and make test-virtio-9p-net-coboot boots the last row, proving the Phase C Nic path and a writable virtio-9p Directory both serve in one not(qemu) boot (host-verified). The qemu row still cannot host the NIC chain — that guard is intentional and untouched.

Invariants (device identity, DMA, teardown, single-owner)

  • Device identity. virtio-9p is a distinct PCI device (VIRTIO_9P_MODERN_DEVICE_ID 0x1049 / transitional 0x1009), selected by pci::is_virtio_9p, and claimed for DeviceOwner::Virtio9p via device_manager::claim_pci_function. Because it is a different BDF from virtio-net, no ownership arbitration against the userspace-NIC chain is needed; the fixture must still refuse to bind if the 9p function is absent (pci: virtio-9p device not found → grant source fails closed).
  • DMA. Unchanged from the landed fixture: kernel-owned, single-request-queue bounce accounting through the device_dma VIRTIO_9P_DMA_POOL (generation-checked page handles, scrub-before-free, five-page budget). This is the settled DMA-isolation backend (../dma-isolation-design.md); the composable feature introduces no new DMA backend and does not touch VT-d (iommu.rs stays qemu-only and unreferenced by 9p). The DmaRemappingRequesterBinding threaded into the driver is descriptive requester identity, not a remapping-table owner.
  • Teardown. Unchanged: the driver’s reset-before-free Drop quiesces the device before its DMA frames are freed. The composable build compiles the same driver, so the same teardown path applies.
  • Single owner. The writable root remains single-writer, non-transferable, minted at most once per boot through the qemu/fixture-gated raw spawn grant; read-only results retain same-session copy delegation. The composable feature changes only when the code compiles, never the grant/ownership semantics.

Verification gates

  • Design record: make workflow-check, make docs, and scripts/check-md-links.py cover the dependency-cut documentation.
  • Implemented extraction: the existing 9p proofs (make test-virtio-9p-bringup, make test-virtio-9p-fs, make test-virtio-9p-write) cover the unchanged full-qemu path. All five feature-matrix rows build, including the negative default and Phase C-only configurations. The co-boot proof builds --features cloud_virtio_net_userspace_sustained_receive_pool_proof, virtio_9p_host_fixture, boots with both a virtio-9p device and the userspace network stack, and asserts the typed Nic path and a writable 9p Directory both serve in one boot. Task-coordinator persistence and the HTTP/JSON task API remain the dependent task-backend-9p-api-persistence task’s scope. The feature/cfg audit and proof also establish that this path neither compiles nor invokes the real QEMU net/blk/rng drivers, IOMMU diagnostic, or DDF proof entry points, while the kernel network façade continues to return its non-QEMU typed-negative results. make test-virtio-9p-net-coboot is the Makefile smoke target for that cell, and docs/devices/virtio-9p.md records the composable build path.

Implemented extraction

The bounded implementation (virtio-9p-composable-fixture-implementation) adds (1) the virtio_9p_host_fixture feature; (2) the independent non-qemu 9p PCI startup route; (3) re-gate the Bucket A edges to any(qemu, virtio_9p_host_fixture) and extract claim_pci_function + attach_dmapool_record_with_remapping into the not(qemu)-reachable device_manager surface; (4) split/gate virtio.rs so the fixture build keeps the non-QEMU typed-negative network façade while excluding the real net/blk/rng drivers and the prove_qemu_* harness; (5) confirm Bucket B stays qemu-only; (6) build all five matrix rows and add the co-boot proof plus its Makefile smoke entry; and (7) refresh docs/devices/virtio-9p.md.