Documentation
¶
Overview ¶
Package net is a pure-Go virtio-net (network device) driver. It drives a modern (Virtio 1.0+) PCI virtio-net device through the transport interfaces defined in github.com/go-virtio/common; the same code drives a UEFI-backed device, a bare-metal device, or a virtio-mmio device depending on which `common.Transport` implementation the caller supplies.
Scope:
- Modern transport (VIRTIO_F_VERSION_1 mandatory). Legacy / I/O-port transitional devices are NOT supported — the modern transport init sequence rejects them via the underlying common package.
- Split-virtqueue layout. The packed-ring variant (VIRTIO_F_RING_PACKED) is NOT supported; the driver negotiates it OUT.
- One queue pair (rxq + txq). Multi-queue (VIRTIO_NET_F_MQ) is NOT negotiated.
- No GSO, no checksum offload (driver always emits an all-zero virtio_net_hdr; on RX we ignore the device-set flags).
References:
- Virtio 1.1 §5.1 "Network Device" — device-type 1 binding.
- Virtio 1.1 §5.1.3 "Feature bits" — VIRTIO_NET_F_*.
- Virtio 1.1 §5.1.4 "Device configuration layout" — MAC, status, max_virtqueue_pairs, MTU.
- Virtio 1.1 §5.1.6 "Device Operation" — the per-frame struct virtio_net_hdr.
- Virtio 1.1 §3.1.1 "Driver Requirements: Device Initialization" — the status-bit choreography in OpenVirtioNet.
- Linux drivers/net/virtio_net.c — canonical Go-translatable reference for the init sequence and rxq pre-post pattern.
Index ¶
Constants ¶
const ( HdrFNeedsCsum uint8 = 0x1 HdrFDataValid uint8 = 0x2 GSONone uint8 = 0 GSOTCPv4 uint8 = 1 GSOUDP uint8 = 3 GSOTCPv6 uint8 = 4 GSOECN uint8 = 0x80 )
VIRTIO_NET_HDR_F_* and VIRTIO_NET_HDR_GSO_* values (Virtio 1.1 §5.1.6.2). The driver emits all-zero headers (no GSO, no checksum offload).
const ( // FeatureCSUM (bit 0): driver checksum-offload support. // Informational; not accepted. FeatureCSUM uint64 = 1 << 0 // FeatureGuestCSUM (bit 1): device may set "needs checksum" on // RX. Informational; not accepted. FeatureGuestCSUM uint64 = 1 << 1 // FeatureMTU (bit 3): device-provided MTU. // // Required by Apple VZ — without ack'ing it, VZ clears FEATURES_OK // and the init aborts. Informational/no-op on QEMU+EDK2 (the bit is // always offered, no semantic change in the driver path). // // We don't read the device's `mtu` field; we use the default-MTU // 1518-byte rxq buffer per VirtioNetMaxFrameSize. FeatureMTU uint64 = 1 << 3 // FeatureMAC (bit 5): device-provided MAC at DeviceCfg offset 0. // REQUIRED by this driver — the probe needs the device-published // MAC for the source field of outbound frames. FeatureMAC uint64 = 1 << 5 // FeatureGuestTSO4 (bit 7), FeatureGuestTSO6 (bit 8), // FeatureHostTSO4 (bit 11), FeatureHostTSO6 (bit 12): TCP // segmentation offload. Informational; not accepted (the driver // emits all-zero headers). FeatureGuestTSO4 uint64 = 1 << 7 FeatureGuestTSO6 uint64 = 1 << 8 FeatureHostTSO4 uint64 = 1 << 11 FeatureHostTSO6 uint64 = 1 << 12 // FeatureMrgRxbuf (bit 15): merged-receive-buffer mode. // NOT ACCEPTED — the driver assumes one packet per buffer (no // chained descriptors on RX). FeatureMrgRxbuf uint64 = 1 << 15 // FeatureStatus (bit 16): device publishes link-up bit in // DeviceCfg.status. Informational; accepted so the device // publishes the field. FeatureStatus uint64 = 1 << 16 // FeatureMQ (bit 22): multi-queue. NOT ACCEPTED — driver uses one // queue pair. FeatureMQ uint64 = 1 << 22 )
VIRTIO_NET_F_* feature bits (Virtio 1.1 §5.1.3). Only the ones we either accept or explicitly reject are listed; the full set lives in the spec.
const ( CfgOffsetMAC uint32 = 0 CfgOffsetStatus uint32 = 6 CfgOffsetMaxVirtqueuePairs uint32 = 8 CfgOffsetMTU uint32 = 10 )
VirtioNetCfgOffset* — the device-specific config field offsets for virtio-net (Virtio 1.1 §5.1.4):
struct virtio_net_config {
u8 mac[6]; // offset 0
le16 status; // offset 6
le16 max_virtqueue_pairs; // offset 8
le16 mtu; // offset 10
// ... 1.1 additions
};
const ( RxQueueIdx uint16 = 0 TxQueueIdx uint16 = 1 )
RxQueueIdx / TxQueueIdx are the canonical queue indices for the single virtio-net queue pair (Virtio 1.1 §5.1.2 — "queue 0 = receive queue, queue 1 = transmit queue").
const AcceptedFeatures uint64 = FeatureMTU | FeatureMAC | FeatureStatus | common.FeatureVersion1
AcceptedFeatures is the feature mask the driver negotiates ON:
VIRTIO_NET_F_MTU (3) device-provided MTU (REQUIRED by Apple VZ;
informational/no-op on QEMU+EDK2)
VIRTIO_NET_F_MAC (5) device MAC published in DeviceCfg
VIRTIO_NET_F_STATUS (16) link-up bit (informational)
VIRTIO_F_VERSION_1 (32) modern transport (non-negotiable)
Apple VZ live empirical narrow (2026-06-07) established that VZ clears FEATURES_OK and aborts the init unless VIRTIO_NET_F_MTU is acknowledged — ack'ing the bit costs us nothing on the driver side (we don't read the device-published `mtu` field) and unblocks the VZ cell. QEMU+EDK2 always offers this bit too, so accepting it is a no-op there.
All other bits are masked OUT. If the device REQUIRES a bit we didn't ack, FEATURES_OK will fail to stick after we write it; the init sequence catches that and surfaces ErrFeaturesNotOK.
NOTE: VIRTIO_NET_F_MRG_RXBUF (15) is NOT accepted. Without it the device places one packet per buffer (no chained descriptors on receive), which simplifies the RX path significantly.
const MACLen = 6
MACLen is the byte length of the virtio-net MAC field (Virtio 1.1 §5.1.4 — 6 bytes, IEEE 802.3 EUI-48).
const MaxFrameSize = 1518
MaxFrameSize is the Ethernet MTU (1500) + Ethernet header (14) + 4 bytes for VLAN headroom. We pre-post 1518-byte buffers on the rxq; on the txq the same size is enough for ARP (42 bytes) plus the virtio header.
const RxRingSize uint16 = 16
RxRingSize is the number of buffers pre-posted on the rxq. Sized for "a few simultaneous in-flight frames". MUST be a power of two.
const TxPollIterations = 200000
TxPollIterations is the default polling budget for TransmitFrame. Empirically large enough on QEMU+EDK2 and Apple VZ; far from the firmware's actual round-trip but a sensible upper bound for the busy-poll model the driver uses.
const TxRingSize uint16 = 8
TxRingSize is the txq depth. The driver issues one frame at a time so 8 is plenty.
const VirtioNetHdrSize = 12
VirtioNetHdrSize is the on-the-wire byte length of `struct virtio_net_hdr` (Virtio 1.1 §5.1.6.1). With VIRTIO_F_VERSION_1 negotiated, the header is ALWAYS 12 bytes regardless of VIRTIO_NET_F_MRG_RXBUF — the `num_buffers` field is unconditional on modern devices. We negotiate VERSION_1 ON and MRG_RXBUF OFF, so 12 bytes is what every TX/RX buffer must reserve.
Field offsets (little-endian within the 12-byte block):
0 u8 flags 1 u8 gso_type 2..3 le16 hdr_len 4..5 le16 gso_size 6..7 le16 csum_start 8..9 le16 csum_offset 10..11 le16 num_buffers (driver writes 0 on TX; device fills on RX)
Variables ¶
var ( ErrFrameTooShort = commonNetError("go-virtio/net: RX buffer shorter than virtio_net_hdr (12 bytes)") ErrNotModernDevice = commonNetError("go-virtio/net: device doesn't offer VIRTIO_F_VERSION_1 (legacy-only)") ErrNoMACFeature = commonNetError("go-virtio/net: device doesn't offer VIRTIO_NET_F_MAC") ErrFeaturesNotOK = commonNetError("go-virtio/net: FEATURES_OK status bit didn't stick after DriverFeature write") ErrMACReadFailed = commonNetError("go-virtio/net: MAC read returned all-zero (bounds-check failure or unsupported device)") ErrInitWrongDeviceID = commonNetError("go-virtio/net: PCI device ID is not 0x1041 (modern net device)") ErrQueueNotAvailable = commonNetError("go-virtio/net: device reports QueueSize=0 for required queue") ErrTransmitTimeout = commonNetError("go-virtio/net: TX poll timeout (device did not return descriptor)") ErrReceiveTimeout = commonNetError("go-virtio/net: RX poll timeout (no frame received within budget)") ErrBufferTooSmall = commonNetError("go-virtio/net: PageAllocator returned a chunk smaller than VirtioNetHdrSize + MaxFrameSize") )
Sentinel errors for the virtio-net path. All exported so callers can branch + format them.
Functions ¶
func AcceptFeatures ¶
AcceptFeatures returns the negotiated feature mask: the intersection of what the device offers and what we accept. The caller writes this back via DriverFeature.
We require VIRTIO_F_VERSION_1 — if the device doesn't offer it, we return ErrNotModernDevice and the init aborts. We require VIRTIO_NET_F_MAC because the probe needs the device-published MAC.
func PrependVirtioNetHdr ¶
PrependVirtioNetHdr builds a 12-byte all-zero `virtio_net_hdr` followed by the Ethernet frame in `frame`. Returns the full per-descriptor buffer the driver passes to AddBuffer.
func StripVirtioNetHdr ¶
StripVirtioNetHdr returns the Ethernet frame embedded in a device-RX buffer of the given length. Returns nil + ErrFrameTooShort if the buffer is shorter than the header (a malformed RX).
Types ¶
type MAC6 ¶
type MAC6 [6]byte
MAC6 is the 6-byte EUI-48 MAC address of one virtio-net device.
type VirtioNet ¶
type VirtioNet struct {
// Cfg is the modern-transport handle (BARs + offsets + the
// BARMemoryAccessor used for every register access).
Cfg *common.ModernConfig
// MAC is the device-published MAC address (Virtio 1.1 §5.1.4).
// Read after FEATURES_OK at OpenVirtioNet completion.
MAC MAC6
// NegotiatedFeatures records what the driver-feature handshake
// settled on. Exposed for diagnostic prints.
NegotiatedFeatures uint64
// contains filtered or unexported fields
}
VirtioNet wraps one initialised virtio-net device. The caller holds this for the lifetime of the probe; the underlying virtqueue pages live as long as the supplied PageAllocator's lifetime contract.
func OpenVirtioNet ¶
OpenVirtioNet drives the full bring-up of one virtio-net device:
- Verify the PCI VID:DID is 1AF4:1041 (modern net).
- InitModernConfig walks PCI caps + populates the BAR locators.
- Reset → ACK → DRIVER status progression.
- Read DeviceFeature, mask, write DriverFeature.
- Set FEATURES_OK, verify it stuck.
- Allocate + publish rxq (queue 0) + txq (queue 1).
- DRIVER_OK status.
- Read MAC from DeviceCfg.
- Pre-post RxRingSize receive buffers + notify the device.
On success the device is in DRIVER_OK state, the rxq is pre-posted with RxRingSize buffers, the txq is empty + ready, and the device MAC is set.
func OpenVirtioNetWithFeatures ¶
OpenVirtioNetWithFeatures is the parameterised variant that takes a caller-supplied accepted-features mask. The override is applied AFTER the device's offered bitmap is read, so the negotiated mask is `deviceFeats & overrideAcceptedFeatures` (with FeatureVersion1 and FeatureMAC still enforced).
Used by diagnostic narrows to test widening the accepted set (e.g. acknowledging Apple's private bits 28/29) without committing that to the production mask.
func (*VirtioNet) ReceiveFrame ¶
ReceiveFrame polls the rxq for one new frame. Returns the Ethernet payload (header stripped) on success, or ErrReceiveTimeout if no frame arrives within `pollIterations` busy-spin cycles.
The returned slice is a fresh copy of the descriptor's DMA buffer — safe to retain after this call returns (and after the descriptor is reclaimed and refilled).
func (*VirtioNet) RxQueue ¶
RxQueue / TxQueue expose the per-direction *common.Virtqueue handles. Read-only accessors so callers can inspect ring state for diagnostic dumps; the fields themselves stay unexported.
func (*VirtioNet) TransmitFrame ¶
TransmitFrame copies a virtio_net_hdr + the Ethernet `frame` into a fresh DMA-visible buffer, enqueues it on the txq, notifies the device, and polls the used ring for completion.
Polls for up to `TxPollIterations` iterations. On QEMU the round-trip is usually < 1 ms; on VZ similar. Returns ErrTransmitTimeout if the budget exhausts.
type VirtioNetHdr ¶
type VirtioNetHdr struct {
Flags uint8
GSOType uint8
HdrLen uint16
GSOSize uint16
CsumStart uint16
CsumOffset uint16
NumBuffers uint16
}
VirtioNetHdr is the Go view of `struct virtio_net_hdr` (Virtio 1.1 §5.1.6.1). The driver always emits an all-zero header and ignores the received one.