virtio-9p (modern PCI 9P transport)
This is a provenance map for the in-tree virtio-9p driver: it cites the specs,
summarizes only the wire-format subset the code actually implements, and points
into the implementation. It is not a re-spec. The driver reuses the modern
split-ring transport seam introduced for virtio-net
(virtio-net) and the single-request-queue bring-up shape of
virtio-blk; this page covers only the 9p-specific parts.
Status: QEMU fixture that currently grants no userspace authority. The
driver, its PCI discovery, and its DMA ledger are gated behind the qemu cargo
feature (diagnose_qemu_virtio_9p in kernel/src/pci.rs; kernel/src/virtio.rs
is replaced by kernel/src/virtio_stub.rs in non-qemu builds). What is
implemented today is transport bring-up plus the 9P2000.L session handshake,
observable only as kernel serial diagnostics. No Directory or File
capability is served over 9p and no 9p kernel grant source exists, so no
userspace process can reach the host share through any manifest. The intended
use is dev-loop payload injection and artifact export, per
the host-directory proposal,
whose “Driver Boundary Decision” section records why this is a kernel-side
fixture (Option A) rather than a userspace driver process.
The driver lives in the virtio-9p section of kernel/src/virtio.rs
(Virtio9pDriver), and the protocol encoding/decoding is the transport-free
client codec capos_lib::ninep (capos-lib/src/ninep.rs).
1. Spec basis
- Device: virtio 9P transport device, modern (virtio 1.x) PCI transport.
PCI vendor
0x1af4; device0x1049(modern) /0x1009(transitional). IDs atkernel/src/pci.rs(VIRTIO_VENDOR_ID,VIRTIO_9P_MODERN_DEVICE_ID,VIRTIO_9P_TRANSITIONAL_DEVICE_ID; matched byPciDevice::is_virtio_9p). Exactly one function is bound, into the singleVIRTIO_9P_DRIVERslot; a second discovered function is refused rather than silently rebinding. - Authoritative specs:
- Virtual I/O Device (VIRTIO) Version 1.2, OASIS Committee Specification 01 (2022-07-01). Source: https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html. Relevant sections: 4.1 (virtio over PCI bus), 2.7 (split virtqueues), 5.7 (9P transport device).
- 9P2000.L protocol, the Linux-flavoured 9P dialect.
Source: https://github.com/chaos/diod/blob/master/protocol.md.
The version string the driver negotiates is exactly
9P2000.L(capos_lib::ninep::VERSION).
- Reference: cross-checked against the QEMU 9p server (
hw/9pfs/9p.c) for the session-establishment behavior, and the Linux9pnet_virtiotransport for the descriptor-chain shape.
2. Wire format (implemented subset)
- Transport discovery and negotiation: unchanged from the shared modern-PCI
seam – vendor-specific capability walk, common/notify/ISR/device-config
region selection, and the reset ->
ACKNOWLEDGE->DRIVER-> feature-negotiation ->FEATURES_OK->DRIVER_OKhandshake. Seevirtio-net§2 for the seam itself (kernel/src/virtio_transport.rs). - Features:
VIRTIO_F_VERSION_1andVIRTIO_9P_F_MOUNT_TAGare both required and both selected; nothing else is accepted. A device that does not offer both fails bring-up closed (Ninep9pInitError::MissingRequiredFeatures). - Device configuration:
struct virtio_9p_config– au16tag length followed by that many mount-tag bytes. The driver maps exactly the length the transport capability advertises and validates the tag length against bothVIRTIO_9P_MAX_TAG_LENand the bytes actually present before reading the tag (Ninep9pInitError::MountTagOutOfRange). - Queues: one request virtqueue (index 0), a split ring built by the shared
Virtqueue::initialize. Size is clamped to the largest power of two not exceedingVIRTIO_9P_REQUEST_QUEUE_SIZEand the device maximum. Completion is polled (QueueInterruptPlan::polled); no MSI-X vector is programmed. - Descriptor chain: each 9p exchange is a two-descriptor chain – a
device-readable request segment followed by a device-writable reply segment,
each backed by its own dedicated DMA page so the two cannot alias
(
Virtio9pDriver::exchange). - Messages: only session establishment is implemented.
Tversion/Rversion– negotiatesmsizeand the9P2000.Lversion string. The requestedmsizeisVIRTIO_9P_MSIZE(4096); QEMU’s server rejects anything below itsP9_MIN_MSIZEof 4096 withRlerror(EMSGSIZE). The server may shrinkmsize, and every later message is bounded by what it actually granted.Tattach/Rattach– establishes the share’s root fid (VIRTIO_9P_ROOT_FID) and yields its qid.Rlerroris accepted in place of either success reply and surfaces asVirtio9pHandshakeError::ServerError.
- Not implemented: walk, open, read, readdir, getattr, and clunk are encoded
and decoded by
capos_lib::ninepbut are not yet driven by the driver; the entire write path (Tlcreate/Twrite/Tfsync/Trename/Tunlinkat) is not implemented at all. Multiple concurrent tags,Tflush, andTauthare out of scope: the fixture keeps one exchange in flight at a time.
3. capOS mapping
- Authority gate: the device is enumerated by the qemu-gated
diagnose_qemu_virtio_9p(kernel/src/pci.rs), which claims the PCI function through the device-manager ownership ledger asDeviceOwner::Virtio9pand attaches a DMAPool authority record. Manifests without a-device virtio-9p-pciline take thepci: virtio-9p device not foundpath, so this is a diagnostic and never a boot dependency. DeviceMmio: the common, ISR, notify, and device-config regions are mapped throughpci::map_bar_regionwith the same bounds validation the other virtio drivers use; the device-config region is mapped to exactly the advertised capability length because the 9p config is variable-length. NoDeviceMmiocapability is granted to userspace.Interrupt: none. The fixture polls its used ring, so it deliberately claims no MSI-X route –interrupt_owner_for_device_ownermapsDeviceOwner::Virtio9ptoNone, and the bring-up diagnostic recordsinterrupt=none completion=polled-used-ring. NoInterruptcapability exists for this device.DMAPool: the driver owns a dedicated single-queue DMA ledger (device_dma.rs,Virtio9pPoolConfig), a distinct type from the virtio-blk and virtio-net pools, so a page handle minted against one never validates against another. It holds five pages: the three split-ring pages plus one request and one reply bounce page. The ledger enforces generation-checked page handles and scrub-before-frame-free ordering. Host physical addresses stay kernel-owned; none is exposed to userspace, and there is no userspace consumer to expose them to.- Fail-closed / validation rules: bring-up fails closed and leaves the
driver unpublished on any of – missing required features, rejected
FEATURES_OK, an undersized or oversized device-config/mount-tag, an absent or too-small request queue, or a failed handshake. Every reply is bounded before decoding: the device-reported written length must be at least a 9p header and no larger than the bring-up scratch buffer, and the reply’s size prefix is cleared before each exchange so stale page contents cannot be decoded as a fresh reply. Thecapos_lib::ninepcodec then independently validates the frame against the negotiatedmsize, the declared size, the expected tag, and the expected reply type before exposing a field. The host-supplied mount tag is rendered as printable ASCII only, so a hostile tag cannot forge or split a diagnostic line. - QEMU-emulable vs hardware-only: entirely QEMU-emulable. virtio-9p is a
paravirtual device with no hardware counterpart, so there is no hardware-only
residue. The end-to-end proof is
make test-virtio-9p-bringup(tools/qemu-virtio-9p-smoke.sh,manifests/system-virtio-9p.cue), which asserts the negotiatedmsizeand version, the mount tag read back from device config, and the attach qid’s directory type bit.
Related
- Implementation:
kernel/src/virtio.rs(Virtio9pDriver,Virtio9pDma,diagnose_virtio_9p_transport),kernel/src/pci.rs(diagnose_qemu_virtio_9p,PciDevice::is_virtio_9p),kernel/src/device_dma.rs(Virtio9pPoolConfig,SingleQueueDmaLedger). - Protocol codec:
capos-lib/src/ninep.rs; fuzz targetfuzz/fuzz_targets/ninep_reply_decode.rs. - Design: Virtio-9p Host Directory Passthrough.
- Shared transport seam:
virtio-net,virtio-blk.