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:
- 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. - 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.
- 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
BlockDevicecaps. The writable 9pDirectorynow 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
qemuand for 9p undervirtio_9p_host_fixture.kernel/src/virtio.rscarries a generic virtqueue implementation (Virtqueue,submit_request_chain,notify,poll_used,poll_used_within_ns) over the device-agnostic modern-PCI surfacekernel/src/virtio_transport.rs.bring_up_blk_deviceuses it to drive the virtio-blk fixture that backsreadonly_fs,fat_fs,persistent_store, andwritable_fs; those block-device and other full Device Driver Foundation (DDF) consumers remainqemu-only. - Userspace MMIO driver authority, available only under
not(qemu). Thecloud_virtio_net_userspace_*_proofandcloud_nvme_controller_reset_prooffeatures admit narrow, selected-write userspaceDeviceMmio.write32access. Each is documented inkernel/Cargo.tomlas mutually exclusive withqemu, 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 avirtio_9p_*selected-write policy for the common-config handshake and queue-address registers andqueue_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 underqemu.) 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 landedcloud_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 addsTlcreate/Twrite/Tfsync/Trename/Tunlinkat. Message codec is pureno_stdlogic incapos-libstyle: 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/Fileinterfaces over the 9p client, the wayreadonly_fsandfat_fsserve them overBlockDevice. Consumers (shellls/cat,wasm-host, the task coordinator) need no new interface. Read-only exports serve aDirectorywhose mutating methods fail closed. - Manifest gating: the grant sources require either
qemuor the explicitvirtio_9p_host_fixtureproof 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.
Directoryspawn-grants are landed; theBlockDevicespawn-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:
- virtio-9p kernel fixture bring-up —
Landed: kernel-side modern-PCI probe, feature negotiation
(
VIRTIO_F_VERSION_1+VIRTIO_9P_F_MOUNT_TAG), and polled request-virtqueue setup overvirtio_transportinkernel/src/virtio.rs, followingbring_up_blk_device;Tversion/Tattachhandshake driven through the landedcapos_lib::ninepcodec.make test-virtio-9p-bringupasserts the negotiatedmsize/version, the mount tag read back from device config, and the attach qid’s directory type bit from a kernel-side serial diagnostic, following thediagnose_virtio_blk_transportprecedent. That slice granted no userspace authority on its own: theDirectory/Fileexport and its grant source arrived in slice 3 below. No userspaceDeviceMmiowrite authority is involved. Provenance map: virtio-9p. - 9P2000.L client core — Landed: host-tested
no_stdread-subset message codec, bounded fail-closed reply decode, and theninep_reply_decodefuzz target. - Directory/File caps over 9p —
Landed: read-only
Directory/Fileserved bykernel/src/cap/virtio_9p_fs.rsover the driver’s bounded read subset (Twalk/Tlopen/Tgetattr/Treaddir/Tread/Tclunk), alongsidereadonly_fs/fat_fs, behind the fixture-gatedvirtio_9p_rootgrant source.make test-virtio-9p-fshas 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 neitherqemunorvirtio_9p_host_fixturecompiles no 9p cap module and resolves the source tokernel source `virtio_9p_root` requires the qemu feature (or the composable virtio_9p_host_fixture feature). - Write support — Landed: 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-gatedvirtio_9p_root_writablegrant 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 writableFileresults are nontransferable, while read-only results retain same-session copy delegation.make test-virtio-9p-writeruns four stages: a read/write share whose bytes are verified on the host, areadonly=onshare where the server refuses write intent withEROFSwhile reads keep working, the single-writer child-grant proof, and a two-boot forced-QEMU-poweroffTfsyncstage with a pre-sync negative control. The first stage covers multi-chunkTwriteresubmission, but server-reported short writes and zero-countWriteStalledhandling remain unproven. The device provenance map is authoritative for the fourth stage’s bounded outcome and for the remainingTfsyncand non-atomicTrenameresiduals: virtio-9p. - Task-coordinator 9p persistence —
Landed: 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-9pproves 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 fencedCAPOSRS1WAL and is not production storage durability. - WASI payload hot-reload — Landed:
wasm-hostreads a bounded, single-component payload through an explicitly granted read-onlyDirectory, while manifests without that grant retain the embedded-payload path.make test-wasi-9p-reloadbuilds 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 9pFilecapabilities 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 ofmake test-virtio-9p-fs. The completed implementation is commit1eb399e2(2026-07-20 05:28 UTC). Preview 1proc_exitnow 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 keepswasm-hostalive 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 mapproc_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. - Composable fixture feature —
Implemented (this document, Composable Fixture Feature). Extracts the 9p
fixture from the monolithic
qemuaxis into avirtio_9p_host_fixturefeature that also builds in anot(qemu)cloud kernel, so the writable-9p persistence path and the Phase C userspace network stack co-boot for the host-accessible task backend. Thenot(qemu)fixture build routes device claim through the minimaldevice_manager::virtio_9p_fixture_claimsurface (the qemu-only DDF ledger, net/blk/rng drivers,prove_qemu_*harness, and IOMMU diagnostic stay#[cfg(feature = "qemu")]), and PCI enumeration takes the dedicatedpci::diagnose_virtio_9p_host_fixturestartup route. Co-residency is proven bymake test-virtio-9p-net-coboot: onenot(qemu)boot serving both the Phase C userspaceNicpath and a writable virtio-9pDirectory, 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::Virtio9pRootWritable→cap::virtio_9p_fs, the realmod virtioinkernel/src/main.rs). - The Phase C userspace network stack builds under the
not(qemu)cloud_virtio_net_userspace_sustained_receive_pool_proofchain, whose transitivecloud_virtio_net_userspace_features_ok_prooftrips an explicitcompile_error!againstqemu(kernel/src/cap/mod.rs): theqemuDDF 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 tonot(qemu):pci::DmaRemappingRequesterBindingand its builderpci::dma_remapping_requester_binding_for_device— threaded throughvirtio::diagnose_virtio_9p_transport; built from the always-availablecapos_lib::device_authoritytypes plusacpi::DmarDiscovery. The 9p driver carries this binding value; it never calls intoiommu.rs.device_manager::claim_pci_functionanddevice_manager::attach_dmapool_record_with_remapping— today defined only indevice_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 (plusDeviceOwner::Virtio9p) to exist in thenot(qemu)surface — extracted into the always-builtdevice_managersurface / a shared submodule, or provided by thestubbackend, not the wholeqemu_fullDDF 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-privatediagnose_qemu_virtio_9pinpci.rs.pci::enumeratedoes not call this helper today. The call is nested at the end ofpci::diagnose_qemu_virtio_net, which is itself reached only from the#[cfg(feature = "qemu")]diagnostics block inmain.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-builtdevice_manager/handles.rs); thedevice_dmasingle-queue 9p pool (begin_virtio_9p_pool,register_virtio_9p_queue,allocate_virtio_9p_page,free_virtio_9p_page, and theVIRTIO_9P_*budgets, in always-builtdevice_dma.rs); the generic split-ring machinery (Virtqueue, theVirtqueueDmatrait,DmaPage,VirtqueueDescriptorTracker,submit_request_chain,poll_used,discover_modern_transport); the always-builtvirtio_transportmodern-PCI surface;pci::is_virtio_9pand theVIRTIO_9P_*_DEVICE_IDconstants; and thecapos_lib::ninepcodec.
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*ProofErrortypes; - the userspace-provider grant-source modules
cap::{devicemmio,interrupt,dmapool}_grant_source; crate::iommu— reached only via the virtio-rngIommuRngDmaVehicleproof (iommu.rsitself 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 functionsattach_devicemmio_record,attach_interrupt_source, andselect_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:qemucontinues to imply the whole fixture, so every existing--features qemupath is unchanged.virtio_9p_host_fixturealone brings the 9p subset into anot(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::Virtio9pRootWritablebinds only when eitherqemuorvirtio_9p_host_fixtureis compiled in, and namingvirtio_9p_root_writablein the manifest remains the sole write opt-in (no runtime upgrade of a read-only cap). In a build with neither feature, bothvirtio_9p_rootandvirtio_9p_root_writableresolve to akernel source ... requires ...refusal — the fixture is never a production authority.capos_config::virtio_9p_writable_source_refusalkeeps rejecting the writable source in non-fixture manifests. The current refusal string names only theqemufeature; the implementation slice broadens the wording (and the refusal helper’s build-condition input) to nameqemuorvirtio_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
| Build | virtio-9p read-only | virtio-9p writable | Phase C userspace net | Full DDF (qemu_full) |
|---|---|---|---|---|
| default (neither) | fail closed | fail closed | no | no (stub) |
qemu | yes (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 closed | fail closed | yes | no (stub) |
fixture only (virtio_9p_host_fixture) | yes | yes (opt-in manifest) | no | no (stub) |
net + fixture (…sustained_receive_pool_proof, virtio_9p_host_fixture) | yes | yes (opt-in manifest) | yes | no (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_ID0x1049/ transitional0x1009), selected bypci::is_virtio_9p, and claimed forDeviceOwner::Virtio9pviadevice_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_dmaVIRTIO_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.rsstaysqemu-only and unreferenced by 9p). TheDmaRemappingRequesterBindingthreaded into the driver is descriptive requester identity, not a remapping-table owner. - Teardown. Unchanged: the driver’s reset-before-free
Dropquiesces 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, andscripts/check-md-links.pycover 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-qemupath. 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 typedNicpath and a writable 9pDirectoryboth serve in one boot. Task-coordinator persistence and the HTTP/JSON task API remain the dependenttask-backend-9p-api-persistencetask’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-cobootis the Makefile smoke target for that cell, anddocs/devices/virtio-9p.mdrecords 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.