common

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

go-virtio/common

Transport-agnostic infrastructure for the go-virtio family of pure-Go virtio drivers.

This package hosts the shared building blocks that every virtio device-class driver needs and that do not themselves depend on a particular host transport (UEFI, bare-metal MMIO, virtio-mmio, vhost-user, …):

  • PCI capability walker (pci.go) — parses the standard struct virtio_pci_cap chain published by every modern virtio device (Virtio 1.1 §4.1.4). Driven through a PCIConfigReader interface so the same walker covers any host.
  • Modern transport layout (modern.go) — the ModernConfig handle that pins the four required + one optional PCI capabilities (COMMON_CFG / NOTIFY_CFG / ISR_CFG / DEVICE_CFG / PCI_CFG) and the typed register accessors that route through a BARMemoryAccessor. Covers the full Virtio 1.1 §4.1.5 register table.
  • Split-virtqueue layout + driver-side state machine (virtqueue.go) — descriptor table, available ring, used ring, plus the AddBuffer / PostAvail / PollUsed / Reclaim bookkeeping. Backing pages come from a PageAllocator.
  • Transport interfaces (transport.go) — PCIConfigReader, BARMemoryAccessor, PageAllocator, Transport.

Mirrors the Linux kernel's <linux/virtio.h> shared-infrastructure header pattern: per-device-class drivers (virtio-net, virtio-blk, …) import this package for the transport-independent pieces and provide their own spec-level driver on top.

Sibling packages

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package common holds the transport-agnostic infrastructure shared by every device-class virtio driver in the go-virtio family (virtio-net, virtio-blk, …). The high-level shape mirrors Linux's <linux/virtio.h> + virtio_ring.h split: this package owns the PCI capability walker, the modern transport layout + register accessors, and the split-virtqueue layout + driver-side state machine. The per-device-class drivers (go-virtio/net, go-virtio/blk) sit on top and import this package for everything below the wire format.

Transports plug in through three small interfaces:

  • PCIConfigReader exposes PCI configuration-space reads for the capability walker. Implementations bridge to whatever the host offers: EFI_PCI_IO_PROTOCOL.Pci.Read on UEFI, direct MMIO config reads on bare metal, ioread* on Linux user-space, etc.

  • BARMemoryAccessor exposes BAR-window memory reads + writes for the modern transport's register accesses. Implementations bridge to EFI_PCI_IO_PROTOCOL.Mem.Read/Write, raw MMIO, or whatever the host exposes.

  • PageAllocator returns DMA-capable, physically-contiguous, page-aligned memory for virtqueue backing storage. Implementations bridge to gBS->AllocatePages on UEFI, sysfs `dma_alloc_coherent`- equivalents on Linux, etc.

Bundling all three behind a single `Transport` typedef is a convenience — most callers want to pass one value to the driver-level `Open(transport)` constructors and let the package extract whichever sub-interface it needs.

References (cited at every layout/register decision in the package):

  • Virtio 1.1 (committee specification 01, 2019-04-11):
  • §4.1.2.1 "PCI Device Discovery" — vendor/device IDs.
  • §4.1.4 "Virtio Structure PCI Capabilities" — cap layout.
  • §4.1.5 "PCI-specific Initialization And Device Operation" — COMMON_CFG / NOTIFY_CFG / ISR_CFG / DEVICE_CFG / PCI_CFG register layouts.
  • §2.6 "Virtqueues" — split-ring layout (this package implements split-ring; packed-ring is not supported).
  • §3.1.1 "Driver Requirements: Device Initialization" — the status-bit choreography device-class drivers follow.
  • Linux drivers/virtio/virtio_pci_modern.c — canonical Go-translatable reference for the COMMON_CFG handshake.
  • Linux drivers/virtio/virtio_ring.c — canonical reference for the descriptor + ring helpers.

Index

Constants

View Source
const (
	CfgDeviceFeatureSelect uint64 = 0x00
	CfgDeviceFeature       uint64 = 0x04
	CfgDriverFeatureSelect uint64 = 0x08
	CfgDriverFeature       uint64 = 0x0c
	CfgMsixConfig          uint64 = 0x10
	CfgNumQueues           uint64 = 0x12
	CfgDeviceStatus        uint64 = 0x14
	CfgConfigGeneration    uint64 = 0x15
	CfgQueueSelect         uint64 = 0x16
	CfgQueueSize           uint64 = 0x18
	CfgQueueMsixVector     uint64 = 0x1a
	CfgQueueEnable         uint64 = 0x1c
	CfgQueueNotifyOff      uint64 = 0x1e
	CfgQueueDesc           uint64 = 0x20
	CfgQueueDriver         uint64 = 0x28
	CfgQueueDevice         uint64 = 0x30
)

COMMON_CFG register offsets (Virtio 1.1 §4.1.5.1, table 4.1.5.1.1):

0x00  le32  device_feature_select
0x04  le32  device_feature        (read)
0x08  le32  driver_feature_select
0x0c  le32  driver_feature        (write)
0x10  le16  msix_config
0x12  le16  num_queues            (read)
0x14  u8    device_status
0x15  u8    config_generation     (read)
0x16  le16  queue_select
0x18  le16  queue_size
0x1a  le16  queue_msix_vector
0x1c  le16  queue_enable
0x1e  le16  queue_notify_off      (read)
0x20  le64  queue_desc
0x28  le64  queue_driver
0x30  le64  queue_device

Total 56 bytes.

View Source
const (
	StatusAcknowledge uint8 = 0x01
	StatusDriver      uint8 = 0x02
	StatusDriverOK    uint8 = 0x04
	StatusFeaturesOK  uint8 = 0x08
	StatusNeedsReset  uint8 = 0x40
	StatusFailed      uint8 = 0x80
)

DeviceStatus bits (Virtio 1.1 §2.1). The init sequence drives these through SetDeviceStatus:

  1. Write 0 to DeviceStatus (full reset).
  2. Set ACKNOWLEDGE.
  3. Set DRIVER.
  4. Read DeviceFeature, mask down, write DriverFeature.
  5. Set FEATURES_OK, re-read DeviceStatus, confirm FEATURES_OK.
  6. Set up per-class queues.
  7. Set DRIVER_OK.

Failure mode: FAILED bit set (firmware/device rejected our config).

View Source
const (
	// FeatureVersion1 = bit 32. Non-negotiable for this package — the
	// entire modern transport layout depends on it.
	FeatureVersion1 uint64 = 1 << 32

	// FeatureRingPacked = bit 34. The driver-side state machine in
	// virtqueue.go implements split-ring only, so this package's
	// drivers MUST NOT acknowledge this bit. Constant exposed for
	// diagnostic narrow probes that want to verify a device offers it.
	FeatureRingPacked uint64 = 1 << 34
)

Transport-level Virtio reserved feature bits (Virtio 1.1 §6). The device-class drivers (go-virtio/net, …) own the per-class bits; the transport-level bits live here.

View Source
const (
	PCIDeviceIDLegacyMin uint16 = 0x1000
	PCIDeviceIDLegacyMax uint16 = 0x103F
	PCIDeviceIDModernMin uint16 = 0x1040
	PCIDeviceIDModernMax uint16 = 0x107F
)

Virtio PCI device-ID ranges (Virtio 1.1 §4.1.2.1):

0x1000..0x103F  legacy / transitional devices (one DID per type).
0x1040..0x107F  modern devices; DID = 0x1040 + device_type.
View Source
const (
	DeviceTypeNet     uint16 = 1
	DeviceTypeBlock   uint16 = 2
	DeviceTypeConsole uint16 = 3
	DeviceTypeEntropy uint16 = 4
)

Per-device-type encodings (Virtio 1.1 §5 device chapters). The integer is the `T` in `0x1040 + T` for the modern range.

View Source
const (
	PCIDeviceIDLegacyNet     uint16 = 0x1000
	PCIDeviceIDModernNet     uint16 = 0x1041
	PCIDeviceIDLegacyBlock   uint16 = 0x1001
	PCIDeviceIDModernBlock   uint16 = 0x1042
	PCIDeviceIDLegacyEntropy uint16 = 0x1005
	PCIDeviceIDModernEntropy uint16 = 0x1044
)

Convenience constants for the common device-class IDs.

View Source
const (
	PCICapCommonCfg    uint8 = 1 // common configuration
	PCICapNotifyCfg    uint8 = 2 // notifications
	PCICapISRCfg       uint8 = 3 // ISR access
	PCICapDeviceCfg    uint8 = 4 // device-specific config
	PCICapPCICfg       uint8 = 5 // PCI configuration access (alternate window)
	PCICapSharedMemCfg uint8 = 8 // 1.1 addition: shared memory
	PCICapVendorCfg    uint8 = 9 // 1.1 addition: vendor-specific
)

VIRTIO_PCI_CAP_* values — the `cfg_type` byte at offset +3 of a virtio PCI capability (Virtio 1.1 §4.1.4):

struct virtio_pci_cap {
    u8 cap_vndr;     // PCI cap ID, always 0x09 (vendor-specific)
    u8 cap_next;     // next-pointer (PCI cap-list link)
    u8 cap_len;      // sizeof(struct virtio_pci_cap), >= 16
    u8 cfg_type;     // VIRTIO_PCI_CAP_* (one of the constants below)
    u8 bar;          // BAR index containing this structure
    u8 id;           // multiple-capability disambiguator
    u8 padding[2];
    le32 offset;     // offset within `bar`
    le32 length;     // length of the structure
};
View Source
const (
	PCICfgVendorID        uint8 = 0x00
	PCICfgDeviceID        uint8 = 0x02
	PCICfgCommand         uint8 = 0x04
	PCICfgStatus          uint8 = 0x06
	PCICfgCapabilitiesPtr uint8 = 0x34
)

PCI configuration-space header offsets used by the modern transport's initialization path. The standard PCI Type 0 header is documented in PCI Local Bus Specification 3.0 §6.1.

View Source
const (
	VirtqDescFNext     uint16 = 0x1 // descriptor chain continues at .next
	VirtqDescFWrite    uint16 = 0x2 // buffer is device-write-only (RX)
	VirtqDescFIndirect uint16 = 0x4 // descriptor refers to an indirect table (Virtio 1.1 §2.6.5.3)
)

VIRTQ_DESC_F_* flags (Virtio 1.1 §2.6.5).

View Source
const (
	VirtqAvailHeaderSize    = 4 // flags + idx (2 + 2)
	VirtqAvailRingEntrySize = 2 // ring[i] is le16
	VirtqAvailUsedEventSize = 2 // trailing `used_event` field
)

VirtqAvail* — components of the available ring (Virtio 1.1 §2.6.6).

View Source
const (
	VirtqUsedHeaderSize     = 4 // flags + idx (2 + 2)
	VirtqUsedRingEntrySize  = 8 // ring[i] is { id (le32), len (le32) }
	VirtqUsedAvailEventSize = 2 // trailing `avail_event` field
)

VirtqUsed* — components of the used ring (Virtio 1.1 §2.6.8).

View Source
const CommonCfgSize uint32 = 0x38

CommonCfgSize is the minimum byte-length of a valid VIRTIO_PCI_CAP_COMMON_CFG region (56 bytes = 0x38). QEMU+EDK2 and Apple VZ both publish this value or larger; anything smaller is a firmware bug.

View Source
const MaxCapsToWalk = 64

MaxCapsToWalk caps the cap-list walk so a malformed (cyclic or self-referential) cap chain doesn't hang the probe. The PCI spec allows at most 48 capabilities in the 192-byte device-specific config area; 64 is generous.

View Source
const PCICapHeaderSize = 16

PCICapHeaderSize is the minimum cap_len. Capabilities of every cfg_type fit in 16 bytes for the common/notify/ISR/device/PCI-cfg kinds; SharedMem and VendorCfg can be larger but this package's walker doesn't dive into their bodies.

View Source
const PCICapIDVendorSpecific uint8 = 0x09

PCICapIDVendorSpecific = 0x09 (PCI Local Bus Specification 3.0 Appendix H). All Virtio PCI capabilities use this ID.

View Source
const PCICapNotifyExtraSize = 4

PCICapNotifyExtraSize is the byte length of the extended `virtio_pci_notify_cap` body beyond the 16-byte standard header (Virtio 1.1 §4.1.4.4). The 4 bytes at +16 hold the `notify_off_multiplier`.

View Source
const PCIStatusCapabilityList uint16 = 0x0010

PCIStatusCapabilityList is bit 4 of the PCI Status register; when set, the CapabilitiesPtr at 0x34 is valid (PCI Local Bus Specification 3.0 §6.2.3).

View Source
const PCIVendorID uint16 = 0x1AF4

PCIVendorID is the Red Hat / Qumranet vendor ID assigned to every transitional and modern virtio device (Virtio 1.1 §4.1.2.1).

View Source
const PageSize uintptr = 4096

PageSize is the UEFI / common page size — 4 KiB on every UEFI arch (UEFI 2.10 §2.3) and the natural unit virtio backends expect for virtqueue backing. Exposed so transport adapters can match.

View Source
const VirtqDescriptorSize = 16

VirtqDescriptorSize is the on-the-wire size of one descriptor (Virtio 1.1 §2.6.5). Sixteen bytes:

0..7   addr   (le64) — guest-physical address of the buffer
8..11  len    (le32) — buffer length
12..13 flags  (le16) — VIRTQ_DESC_F_*
14..15 next   (le16) — descriptor index for chains

Variables

View Source
var (
	ErrNoCommonCfg          = commonError("go-virtio/common: no VIRTIO_PCI_CAP_COMMON_CFG capability found")
	ErrCommonCfgTooShort    = commonError("go-virtio/common: COMMON_CFG length < 56 (firmware malformed)")
	ErrNoNotifyCfg          = commonError("go-virtio/common: no VIRTIO_PCI_CAP_NOTIFY_CFG capability found")
	ErrCapListBitUnset      = commonError("go-virtio/common: PCI Status[CapList] bit unset (legacy-only device)")
	ErrNoDeviceCfg          = commonError("go-virtio/common: device has no VIRTIO_PCI_CAP_DEVICE_CFG")
	ErrDeviceCfgOutOfBounds = commonError("go-virtio/common: device-cfg read past published length")
)

Sentinel errors for the modern transport.

View Source
var (
	ErrCapChainTooLong = commonError("go-virtio/common: PCI cap-list walk exceeded MaxCapsToWalk (likely cyclic)")
	ErrCapChainBadPtr  = commonError("go-virtio/common: PCI cap-list pointer < 0x40 (outside standard config-space)")
)

Sentinel errors for the cap walker.

View Source
var (
	ErrQueueFull         = commonError("go-virtio/common: virtqueue: descriptor table full")
	ErrInvalidIdx        = commonError("go-virtio/common: virtqueue: descriptor index out of range")
	ErrInvalidQueueSize  = commonError("go-virtio/common: virtqueue: queue size must be a non-zero power of two")
	ErrAllocReturnedZero = commonError("go-virtio/common: virtqueue: PageAllocator returned addr=0 with success")
)

Sentinel errors for the virtqueue path.

Functions

func NotifyOffMultiplierCfgOffset

func NotifyOffMultiplierCfgOffset(notifyCapCfgSpaceOffset uint8) uint32

NotifyOffMultiplierCfgOffset returns the PCI config-space byte offset of the `notify_off_multiplier` field for a NOTIFY_CFG cap that sits at `notifyCapCfgSpaceOffset` in cfg-space. Per Virtio 1.1 §4.1.4.4, the extended notify variant adds 4 bytes past the 16-byte header.

func PCIDeviceIDIsBlock

func PCIDeviceIDIsBlock(deviceID uint16) bool

PCIDeviceIDIsBlock reports whether a DeviceID identifies a virtio-blk device.

func PCIDeviceIDIsEntropy added in v0.1.1

func PCIDeviceIDIsEntropy(deviceID uint16) bool

PCIDeviceIDIsEntropy reports whether a DeviceID identifies a virtio-rng (virtio-entropy) device (legacy 0x1005 OR modern 0x1044).

func PCIDeviceIDIsLegacy

func PCIDeviceIDIsLegacy(deviceID uint16) bool

PCIDeviceIDIsLegacy reports whether a DeviceID is in the legacy (0.9 / transitional) range. Exposed so callers can branch early.

func PCIDeviceIDIsModern

func PCIDeviceIDIsModern(deviceID uint16) bool

PCIDeviceIDIsModern reports whether a DeviceID is in the modern (1.0+) range (0x1040..0x107F). Legacy devices use 0x1000..0x103F and have a different PCI capability shape; this package's modern transport rejects them.

func PCIDeviceIDIsNet

func PCIDeviceIDIsNet(deviceID uint16) bool

PCIDeviceIDIsNet reports whether a DeviceID identifies a virtio-net device (legacy 0x1000 OR modern 0x1041). Other legacy DIDs (0x1001 block, 0x1002 console, …) are not net devices even though they share vendor 0x1AF4.

Types

type BARMemoryAccessor

type BARMemoryAccessor interface {
	Read8(bar uint8, offset uint64) (uint8, error)
	Read16(bar uint8, offset uint64) (uint16, error)
	Read32(bar uint8, offset uint64) (uint32, error)
	Read64(bar uint8, offset uint64) (uint64, error)
	Write8(bar uint8, offset uint64, val uint8) error
	Write16(bar uint8, offset uint64, val uint16) error
	Write32(bar uint8, offset uint64, val uint32) error
	Write64(bar uint8, offset uint64, val uint64) error
}

BARMemoryAccessor exposes typed reads + writes against a virtio modern device's BAR-window registers (Virtio 1.1 §4.1.5). All accesses are little-endian; on every Go-supported arch that's the native byte-order so callers don't need to swap.

`bar` is the PCI BAR index (0..5) the virtio capability published. `offset` is the byte offset within that BAR. Implementations route (bar, offset) into the host's BAR-access primitive:

  • UEFI: EFI_PCI_IO_PROTOCOL.Mem.Read/Write with Width=Uint{8,16,32,64}.
  • Bare metal: direct MMIO at (BAR-physical-base + offset).

The 64-bit accessors exist for the QueueDesc / QueueDriver / QueueDevice registers (Virtio 1.1 §4.1.5.1). Some firmware tolerates only two consecutive 32-bit halves; if a host's MMIO primitive lacks a 64-bit access, the implementation MAY decompose Read64/Write64 into two Read32/Write32 internally.

type ModernConfig

type ModernConfig struct {
	// BAR is the BARMemoryAccessor that backs every COMMON_CFG /
	// NOTIFY_CFG / ISR_CFG / DEVICE_CFG read/write. Held by value as
	// an interface; the concrete implementation lives in the host
	// transport adapter.
	BAR BARMemoryAccessor

	// CommonCfg is the BAR + offset of the VIRTIO_PCI_CAP_COMMON_CFG
	// region. Length is guaranteed >= 56 by the spec; we don't store
	// it since every COMMON_CFG access is at a fixed offset below 56.
	CommonCfgBAR    uint8
	CommonCfgOffset uint64

	// NotifyCfg is the BAR + offset of the VIRTIO_PCI_CAP_NOTIFY_CFG
	// region. NotifyOffMultiplier is the per-device multiplier read
	// from the extended cap (Virtio 1.1 §4.1.4.4).
	NotifyCfgBAR        uint8
	NotifyCfgOffset     uint64
	NotifyCfgLength     uint32
	NotifyOffMultiplier uint32

	// ISRCfg is the BAR + offset of the VIRTIO_PCI_CAP_ISR_CFG region.
	// 1 byte, holding the interrupt-status bits the device publishes
	// for polling (Virtio 1.1 §4.1.5.3).
	ISRCfgBAR    uint8
	ISRCfgOffset uint64

	// DeviceCfg is the BAR + offset of the VIRTIO_PCI_CAP_DEVICE_CFG
	// region. Length is stored because some firmware (notably Apple VZ
	// on virtio-net) publishes shorter device-cfg regions than QEMU+EDK2;
	// the read accessors enforce a bounds-check against this value.
	DeviceCfgBAR    uint8
	DeviceCfgOffset uint64
	DeviceCfgLength uint32

	// PCICfg is the BAR + offset of the VIRTIO_PCI_CAP_PCI_CFG region
	// (alternate cfg-access window). Optional; on devices that don't
	// publish it, this field is zero.
	PCICfgBAR    uint8
	PCICfgOffset uint64
}

ModernConfig is the parsed, pre-located handle for one virtio modern device. It pins the (BAR, offset, length) tuples for each capability so the per-register accessors don't have to re-walk the cap list, and it carries a `BARMemoryAccessor` for the actual MMIO routing.

`NotifyOffMultiplier` is read from the NOTIFY_CFG capability's trailing 4-byte field (Virtio 1.1 §4.1.4.4). The per-queue notification address is

notify_cap.offset + queue_notify_off * notify_off_multiplier

where `queue_notify_off` comes from COMMON_CFG.QueueNotifyOff after QueueSelect.

func InitModernConfig

func InitModernConfig(t Transport) (*ModernConfig, error)

InitModernConfig drives the full modern-transport setup for one virtio device using a `Transport`:

  1. Read PCI Status and confirm bit 4 (CapabilityList) is set.
  2. Read the CapabilitiesPtr at config offset 0x34.
  3. Walk the cap list via `WalkPCICaps` against the transport.
  4. Pass the resulting `[]PCICap` to `ParseCaps` along with the transport's BAR accessor.
  5. If NOTIFY_CFG was found, read the 4-byte `notify_off_multiplier` from cfg-space at NotifyCfg's CfgSpaceOffset + 16.

Returns the populated `*ModernConfig` or a wrapped error.

Errors propagated:

  • any error from the transport's ReadConfig* / Read* calls.
  • the cap-walker's `ErrCapChainTooLong` / `ErrCapChainBadPtr`.
  • `ErrNoCommonCfg`, `ErrNoNotifyCfg`, `ErrCommonCfgTooShort`.
  • `ErrCapListBitUnset` — the device's PCI Status[CapList] bit is 0, indicating a pre-1.0 / I/O-port-only legacy device that the modern transport cannot drive.

func ParseCaps

func ParseCaps(caps []PCICap, bar BARMemoryAccessor) (*ModernConfig, error)

ParseCaps converts the raw capability list (output of WalkPCICaps) into a populated `*ModernConfig` by locating each required capability and pinning its BAR + offset. The `BAR` accessor is caller-supplied (typically the host transport adapter).

`NotifyOffMultiplier` is NOT filled here — that requires reading 4 bytes from PCI config space at notify_cap.CfgSpaceOffset+16, which is a `PCIConfigReader.ReadConfig32` call; use `InitModernConfig(transport)` for the full bring-up.

Errors:

  • ErrNoCommonCfg: required COMMON_CFG capability missing.
  • ErrCommonCfgTooShort: COMMON_CFG length < 56.
  • ErrNoNotifyCfg: required NOTIFY_CFG capability missing.

Optional caps (ISRCfg, DeviceCfg, PCICfg) leave the corresponding fields zero if absent; callers check HasDeviceCfg() before reading device-specific config.

func (*ModernConfig) ConfigGeneration

func (c *ModernConfig) ConfigGeneration() (uint8, error)

ConfigGeneration returns the device's configuration-generation counter (Virtio 1.1 §2.4.1). Used to detect device-cfg races.

func (*ModernConfig) DeviceCfgRead8

func (c *ModernConfig) DeviceCfgRead8(offset uint32) (uint8, error)

DeviceCfgRead8 reads one byte from the device-specific config region with a bounds-check against `DeviceCfgLength`. Returns 0 + a sentinel error if the offset is outside the region the device published.

Device-class drivers (virtio-net's MAC reader, virtio-blk's capacity reader, …) route their DeviceCfg accesses through this entry so the bounds-check is applied consistently.

func (*ModernConfig) DeviceCfgRead16

func (c *ModernConfig) DeviceCfgRead16(offset uint32) (uint16, error)

DeviceCfgRead16 / DeviceCfgRead32 / DeviceCfgRead64 are typed device-cfg readers with the same bounds-check. The width is the access width; the bounds-check guards offset + sizeof(width).

func (*ModernConfig) DeviceCfgRead32

func (c *ModernConfig) DeviceCfgRead32(offset uint32) (uint32, error)

DeviceCfgRead32 reads a 32-bit field from device-cfg with bounds-check.

func (*ModernConfig) DeviceCfgRead64

func (c *ModernConfig) DeviceCfgRead64(offset uint32) (uint64, error)

DeviceCfgRead64 reads a 64-bit field from device-cfg with bounds-check.

func (*ModernConfig) DeviceFeatures64

func (c *ModernConfig) DeviceFeatures64() (uint64, error)

DeviceFeatures64 reads the full 64-bit feature bitmap in two 32-bit halves, hiding the select+read dance.

func (*ModernConfig) DeviceStatus

func (c *ModernConfig) DeviceStatus() (uint8, error)

DeviceStatus reads the DeviceStatus byte. The init sequence reads it after writing FEATURES_OK to confirm the device accepted the subset.

func (*ModernConfig) HasDeviceCfg

func (c *ModernConfig) HasDeviceCfg() bool

HasDeviceCfg reports whether DEVICE_CFG was located.

func (*ModernConfig) HasNotifyCfg

func (c *ModernConfig) HasNotifyCfg() bool

HasNotifyCfg reports whether NOTIFY_CFG was located (length > 0). An init sequence MUST have it; ParseCaps surfaces the absence early.

func (*ModernConfig) NotifyQueue

func (c *ModernConfig) NotifyQueue(queueIdx uint16, queueNotifyOff uint16) error

NotifyQueue writes the queue index to the per-queue notification address (Virtio 1.1 §4.1.4.4).

**Write-width selection.** The spec ("The driver writes the 16-bit virtqueue index ...") prescribes the VALUE width, not the MMIO WIDTH. Empirically, virtio backends differ on the MMIO width they accept:

  • QEMU + EDK2 accepts any width as long as it lands on the per-queue slot — the canonical reference driver (Linux drivers/virtio/virtio_pci_modern.c::vp_notify) issues `iowrite16(vq->index, addr)` everywhere.
  • Apple VZ's virtio-net backend (vfkit 0.6.3 / arm64) appears to dispatch notifications based on the per-queue stride implied by `notify_off_multiplier`: with `multiplier=4` and `length=8` (two queues, stride 4 each), VZ honors a 32-bit MMIO write at the slot's base offset but silently drops a 16-bit write at the same offset.

So we widen the doorbell write to match the per-queue stride: when the device publishes `notify_off_multiplier >= 4` we issue a uint32 MMIO write (queue index zero-extended); when the multiplier is 0, 1, or 2 we keep the spec-default uint16. The value written is the queue index in either case, exactly as the spec mandates.

On QEMU+EDK2 with `notify_off_multiplier=4` (the standard modern transport), this is a no-op for correctness — a uint32 write with the queue index in the low 16 bits and zero in the high 16 bits hits the same per-queue dispatch path; the upper 16 bits are "reserved, write zero" per spec.

func (*ModernConfig) NumQueues

func (c *ModernConfig) NumQueues() (uint16, error)

NumQueues returns the device's maximum supported queue count (Virtio 1.1 §4.1.5.1). For virtio-net this is at least 2 (rxq+txq); for virtio-blk usually 1.

func (*ModernConfig) PerQueueNotifyOffset

func (c *ModernConfig) PerQueueNotifyOffset(queueNotifyOff uint16) uint64

PerQueueNotifyOffset returns the BAR-relative offset the virtqueue's `notify` write must hit, given the device's `queue_notify_off` value (read from COMMON_CFG after QueueSelect). Per Virtio 1.1 §4.1.4.4:

addr = notify_cap.offset + queue_notify_off * notify_off_multiplier

Returned offset is BAR-relative; the caller turns it into an MMIO write via BAR.Write16/Write32 at (NotifyCfgBAR, returned offset).

func (*ModernConfig) QueueDesc

func (c *ModernConfig) QueueDesc() (uint64, error)

QueueDesc / QueueDriver / QueueDevice / QueueEnable read back the per-queue address registers. Exposed for diagnostic readback after SetQueue* — useful to confirm the host actually stored the writes (some firmware silently drops 64-bit MMIO writes).

func (*ModernConfig) QueueDevice

func (c *ModernConfig) QueueDevice() (uint64, error)

QueueDevice reads the per-queue used-ring address.

func (*ModernConfig) QueueDriver

func (c *ModernConfig) QueueDriver() (uint64, error)

QueueDriver reads the per-queue avail-ring address.

func (*ModernConfig) QueueEnable

func (c *ModernConfig) QueueEnable() (uint16, error)

QueueEnable reads the per-queue enable bit.

func (*ModernConfig) QueueNotifyOff

func (c *ModernConfig) QueueNotifyOff() (uint16, error)

QueueNotifyOff returns the per-queue notification offset (Virtio 1.1 §4.1.4.4).

func (*ModernConfig) QueueSize

func (c *ModernConfig) QueueSize() (uint16, error)

QueueSize returns the device's current size for the selected queue (the device's maximum capability; the driver MAY write a smaller power-of-two value).

func (*ModernConfig) ReadDeviceFeature

func (c *ModernConfig) ReadDeviceFeature() (uint32, error)

ReadDeviceFeature reads the 32-bit feature half corresponding to the last DeviceFeatureSelect write.

func (*ModernConfig) ReadDeviceFeatureSelect

func (c *ModernConfig) ReadDeviceFeatureSelect() (uint32, error)

ReadDeviceFeatureSelect / WriteDeviceFeatureSelect drive the feature negotiation iterator. The driver writes a select value (0 or 1) and then reads DeviceFeature for the corresponding 32-bit half (low or high) of the 64-bit feature bitmap.

func (*ModernConfig) SelectQueue

func (c *ModernConfig) SelectQueue(idx uint16) error

SelectQueue selects which virtqueue subsequent register accesses target (Virtio 1.1 §4.1.5.1.3). MUST be called before any QueueSize / QueueDesc / QueueDriver / QueueDevice / QueueEnable read or write.

func (*ModernConfig) SetDeviceStatus

func (c *ModernConfig) SetDeviceStatus(v uint8) error

SetDeviceStatus writes a new DeviceStatus byte. The init sequence drives this through the {0, ACK, ACK|DRIVER, ACK|DRIVER|FEATURES_OK, ACK|DRIVER|FEATURES_OK|DRIVER_OK} progression.

func (*ModernConfig) SetDriverFeatures64

func (c *ModernConfig) SetDriverFeatures64(v uint64) error

SetDriverFeatures64 writes the full 64-bit driver-feature bitmap in two 32-bit halves.

func (*ModernConfig) SetQueueDesc

func (c *ModernConfig) SetQueueDesc(addr uint64) error

SetQueueDesc / SetQueueDriver / SetQueueDevice publish the per-queue physical addresses to the device (Virtio 1.1 §4.1.5.1). All three are 64-bit; we write them with one BAR.Write64 per spec.

func (*ModernConfig) SetQueueDevice

func (c *ModernConfig) SetQueueDevice(addr uint64) error

SetQueueDevice publishes the used-ring's physical address.

func (*ModernConfig) SetQueueDriver

func (c *ModernConfig) SetQueueDriver(addr uint64) error

SetQueueDriver publishes the avail-ring's physical address.

func (*ModernConfig) SetQueueEnable

func (c *ModernConfig) SetQueueEnable(v uint16) error

SetQueueEnable writes 1 to QueueEnable (Virtio 1.1 §4.1.5.1.3) — the device starts servicing the queue.

func (*ModernConfig) SetQueueSize

func (c *ModernConfig) SetQueueSize(v uint16) error

SetQueueSize writes the driver's chosen queue size. MUST be a power of two and <= the device's reported max.

func (*ModernConfig) WriteDeviceFeatureSelect

func (c *ModernConfig) WriteDeviceFeatureSelect(v uint32) error

WriteDeviceFeatureSelect writes the feature-select index.

func (*ModernConfig) WriteDriverFeature

func (c *ModernConfig) WriteDriverFeature(v uint32) error

WriteDriverFeature writes the 32-bit driver-feature half corresponding to the last DriverFeatureSelect write.

func (*ModernConfig) WriteDriverFeatureSelect

func (c *ModernConfig) WriteDriverFeatureSelect(v uint32) error

WriteDriverFeatureSelect writes the driver-feature-select index.

type PCICap

type PCICap struct {
	// CapID is always 0x09 (vendor-specific). Stored only so a caller
	// can sanity-check.
	CapID uint8

	// Next is the PCI cap-list link byte (config-space offset of the
	// next capability, or 0 to terminate). Stored for debugging.
	Next uint8

	// Len is the cap_len byte; values < 16 are spec-violating.
	Len uint8

	// CfgType is one of the PCICap* constants above. The walker
	// returns all of them; callers filter by type.
	CfgType uint8

	// BAR is the PCI BAR index containing this structure (0..5).
	BAR uint8

	// ID disambiguates multiple capabilities of the same CfgType
	// (e.g. two device-cfg structures on one transitional device).
	ID uint8

	// Offset is the byte offset within BAR of this structure.
	Offset uint32

	// Length is the byte length of the structure inside BAR.
	Length uint32

	// CfgSpaceOffset is the config-space offset where this capability
	// header sits (not part of the spec'd virtio_pci_cap struct — the
	// walker fills it in for diagnostic prints and for reading the
	// extended NOTIFY_CFG body).
	CfgSpaceOffset uint8
}

PCICap is the parsed Go view of `struct virtio_pci_cap` from the device's PCI cap-list. Stored as direct fields (not a pointer into the host's config-space view) so callers — including host tests — can hand-build instances.

func PCICapsByType

func PCICapsByType(caps []PCICap, cfgType uint8) *PCICap

PCICapsByType returns the first capability in `caps` whose CfgType matches, or nil. Callers use this to locate (for example) the VIRTIO_PCI_CAP_DEVICE_CFG capability before reading device-specific config like a virtio-net MAC.

func WalkPCICaps

func WalkPCICaps(r PCIConfigReader, firstCapOffset uint8) ([]PCICap, error)

WalkPCICaps walks the PCI capability linked list starting at `firstCapOffset`, returning every entry whose CapID is 0x09 (vendor-specific — i.e. a virtio capability). Non-vendor capabilities (e.g. MSI-X) are skipped — the walker follows their next pointer without emitting them.

Per Virtio 1.1 §4.1.4, each vendor cap reads:

+0  CapID    (u8) = 0x09
+1  Next     (u8)
+2  cap_len  (u8) >= 16
+3  cfg_type (u8) = one of PCICap*
+4  bar      (u8)
+5  id       (u8)
+6  padding  (u8 x 2)
+8  offset   (LE u32)
+12 length   (LE u32)

Errors:

  • ErrCapChainBadPtr — a Next pointer landed below 0x40 (outside the standard cap area). Returned with the partial result.
  • ErrCapChainTooLong — the walker hit MaxCapsToWalk iterations without seeing a 0-terminator. Returned with whatever caps were collected.
  • any error returned by the PCIConfigReader — propagated as-is.

On a Read* error mid-walk, the partial result is returned alongside the error so the caller can still inspect whatever caps were successfully enumerated before the failure.

type PCIConfigReader

type PCIConfigReader interface {
	ReadConfig8(offset uint8) (uint8, error)
	ReadConfig16(offset uint8) (uint16, error)
	ReadConfig32(offset uint8) (uint32, error)
}

PCIConfigReader exposes PCI configuration-space reads for the virtio capability walker (Virtio 1.1 §4.1.4). The walker only reads from the standard PCI cap-list area (0x40..0xFF) and only ever needs 8-bit and 32-bit accesses; 16-bit is exposed for completeness because some transports (Linux, EFI) have a native 16-bit read primitive that's cheaper than two byte reads.

Implementations:

  • UEFI: EFI_PCI_IO_PROTOCOL.Pci.Read with Width=Uint8/Uint16/Uint32.
  • Bare metal: direct MMIO config-space access (post-EBS on platforms where the firmware exposes a flat config-space window).
  • virtio-mmio: this interface is unused (mmio devices don't have a PCI cap chain — the equivalent metadata lives at MMIO offsets 0x00..0xFF on the device's register window).

Errors returned MUST be non-nil whenever the read failed and the returned scalar is meaningless; the walker short-circuits on the first non-nil error and surfaces it to the caller along with any caps collected so far.

type PageAllocator

type PageAllocator interface {
	AllocatePages(count int) (physAddr uint64, mem []byte, err error)
}

PageAllocator returns physically-contiguous, page-aligned, DMA-capable pages suitable for virtqueue backing storage. Virtio 1.1 §2.6 requires the descriptor / available-ring / used-ring regions to be naturally aligned (16 / 2 / 4 bytes respectively); allocating a single 4 KiB page for the lot trivially satisfies all three.

Implementations:

  • UEFI: gBS->AllocatePages(EfiBootServicesData, count) — identity- mapped during Boot Services so the physical address returned is also a usable Go-side pointer.
  • Bare-metal Linux user-space: hugepage backing + an IOMMU mapping.

`count` is the number of 4 KiB pages to allocate. The returned `physAddr` is the physical address the device will see (published via QueueDesc / QueueDriver / QueueDevice MMIO registers); `mem` is a host-side Go-byte view of the same memory that the driver writes descriptors + available-ring entries into. On UEFI-style identity- mapped hosts these may correspond to the same address; on hosts with separate physical / virtual address spaces, the implementation provides the translation.

Implementations MUST return zero-initialised memory — Virtio 1.1 doesn't strictly require it, but a stale used-ring `idx` would make the driver's first PollUsed() spuriously see a "completed" entry.

type Transport

type Transport interface {
	PCIConfigReader
	BARMemoryAccessor
	PageAllocator
}

Transport bundles the three transport-level interfaces a virtio device-class driver needs. Most drivers want to pass a single value to their `Open(transport)` constructor and let the package extract whichever sub-interface it needs; the alternative is to pass three separate values, which gets noisy.

Implementations are free to satisfy `Transport` via a single struct that embeds all three methods, or via three separate struct fields — either shape works as long as the methods are present.

type Virtqueue

type Virtqueue struct {
	// Index is the queue's index inside the device (e.g. 0 = rxq, 1 =
	// txq on virtio-net per Virtio 1.1 §5.1.2).
	Index uint16

	// Layout is the byte-offset map for this queue's allocation.
	Layout VirtqueueLayout

	// BasePhys is the physical-address view of `mem[0]` — what the
	// device sees. Published via SetQueueDesc / SetQueueDriver /
	// SetQueueDevice.
	BasePhys uint64

	// NotifyOff is the device-published `queue_notify_off` (read from
	// COMMON_CFG after QueueSelect). Used to compute the per-queue
	// notification BAR offset.
	NotifyOff uint16

	// Buffers holds the driver-side bookkeeping for each descriptor.
	Buffers []VirtqueueBuffer
	// contains filtered or unexported fields
}

Virtqueue is the driver-side handle for one split virtqueue.

`mem` is the host-side Go-byte view of the queue allocation (the PageAllocator returned this). The driver writes descriptors and available-ring entries through this slice. `BasePhys` is the same region's physical-address view — what the device sees through DMA; it's published via SetQueueDesc / SetQueueDriver / SetQueueDevice.

`Buffers` is the per-descriptor driver-side bookkeeping (the host-visible pointer + the physical address + the length + an InUse marker).

func NewVirtqueue

func NewVirtqueue(a PageAllocator, size uint16, queueIdx uint16, notifyOff uint16) (*Virtqueue, error)

NewVirtqueue allocates the backing memory for a split virtqueue of the given size via the supplied PageAllocator, zero-initializes it, and returns a driver-side handle ready for descriptor publication.

The caller next calls cfg.SetQueueDesc/Driver/Device(q.BasePhys + offset) to publish the per-region physical addresses to the device, then cfg.SetQueueEnable(1).

`size` MUST be a non-zero power of two; otherwise ErrInvalidQueueSize.

func NewVirtqueueFromAlloc

func NewVirtqueueFromAlloc(phys uint64, mem []byte, size uint16, index uint16) *Virtqueue

NewVirtqueueFromAlloc constructs a Virtqueue from a pre-zeroed memory allocation. `phys` is the page's physical base; `mem` is the host-side byte view (slice into the same region); `size` is the queue size (power of two); `index` is the queue index inside the device.

The allocator MUST have zeroed `mem` — Virtio 1.1 §2.6 doesn't strictly require it but a stale used-ring `idx` would make PollUsed think the device already published frames.

`mem` MUST be at least `ComputeVirtqueueLayout(size).TotalSize` bytes long; this is the caller's responsibility (no runtime length check — passing too-short backing would only manifest as out-of-bounds slice access in the typed accessors, which is preferable to silent corruption).

func (*Virtqueue) AddBuffer

func (q *Virtqueue) AddBuffer(addr uintptr, phys uint64, length uint32, writable bool) (uint16, error)

AddBuffer finds the first free descriptor slot, fills it with the given buffer's (phys, len, flags), bookkeeps it, publishes it in the available ring, and returns the descriptor index. `writable` drives the VIRTQ_DESC_F_WRITE flag (true = device-write-only, i.e. RX; false = device-read-only, i.e. TX).

func (*Virtqueue) AvailFlags

func (q *Virtqueue) AvailFlags() uint16

AvailFlags returns the current `flags` field of the available ring (Virtio 1.1 §2.6.6 — VIRTQ_AVAIL_F_NO_INTERRUPT).

func (*Virtqueue) AvailHeaderBytes

func (q *Virtqueue) AvailHeaderBytes() []byte

AvailHeaderBytes returns a copy of the first 8 bytes of the avail-ring region (flags, idx, ring[0..1]). Exposed for diagnostic dumps.

func (*Virtqueue) AvailIdx

func (q *Virtqueue) AvailIdx() uint16

AvailIdx returns the current `idx` field — the running count of available-ring publications.

func (*Virtqueue) DescBytes

func (q *Virtqueue) DescBytes(idx uint16) []byte

DescBytes returns a copy of the 16 bytes of descriptor[idx]. Exposed for diagnostic / observability dumps. Returns nil if `idx` is out-of-range — callers always get either a full copy or nil, never a partial / aliased view.

func (*Virtqueue) LastSeenUsedIdx

func (q *Virtqueue) LastSeenUsedIdx() uint16

LastSeenUsedIdx returns the driver's view of the used-ring index.

func (*Virtqueue) Mem

func (q *Virtqueue) Mem() []byte

Mem returns the underlying byte view of the virtqueue's backing memory. Exposed for advanced diagnostic / test scenarios that need to manipulate the bytes directly (e.g. simulating device-side writes in host tests).

func (*Virtqueue) NextAvailIdx

func (q *Virtqueue) NextAvailIdx() uint16

NextAvailIdx returns the driver's view of the available-ring index.

func (*Virtqueue) PollUsed

func (q *Virtqueue) PollUsed() (uint16, uint32, bool)

PollUsed checks whether the device has added a new used-ring entry since the last call. Returns (descIdx, length, ok=true) if so; (0, 0, false) otherwise. Mutates `lastSeenUsedIdx` only on a successful poll, so retrying after a false result is cheap.

func (*Virtqueue) PostAvail

func (q *Virtqueue) PostAvail(descIdx uint16) error

PostAvail publishes descriptor[descIdx] in the available ring at position `nextAvailIdx % Size` and bumps the published `idx` counter. Per Virtio 1.1 §2.6.13 ("Drivers MUST suppress device interrupts before checking the available ring"), the device MUST observe `ring[]` before `idx`.

Ordering: the available ring's header is two adjacent uint16 fields — `flags` at offset 0, `idx` at offset 2 — together forming a 4-byte naturally-aligned word at the start of the region. We publish `idx` via an `atomic.StoreUint32` on that word, preserving the current `flags` value in the low 16 bits and writing the new idx into the high 16 bits. This is a release-store on every Go-supported architecture, so the device's subsequent read of `idx` happens-after our write to `ring[]`.

Single-driver invariant: only this PostAvail writes to the available-ring header word.

func (*Virtqueue) Reclaim

func (q *Virtqueue) Reclaim(descIdx uint16) error

Reclaim marks descriptor `descIdx` as free (caller has consumed the device's used-ring report and copied the data out). Idempotent.

func (*Virtqueue) UsedHeaderBytes

func (q *Virtqueue) UsedHeaderBytes() []byte

UsedHeaderBytes returns a copy of the first 16 bytes of the used-ring region (flags, idx, ring[0]). Exposed for diagnostic dumps.

func (*Virtqueue) UsedIdx

func (q *Virtqueue) UsedIdx() uint16

UsedIdx returns the device-side `idx` field of the used ring. Reads with acquire semantics so the subsequent ring[] read sees a committed entry.

Layout mirror of the available ring: used-ring header is two adjacent uint16 fields at offset 0 (`flags` at +0, `idx` at +2), together forming a 4-byte naturally-aligned word. We atomic.LoadUint32 the word and extract `idx` from the high 16 bits. The load is an acquire on every Go-supported arch.

func (*Virtqueue) UsedIdxRaw

func (q *Virtqueue) UsedIdxRaw() uint16

UsedIdxRaw returns the raw bytes of the used-ring idx field (offset 2..4 — two bytes) without going through the atomic.LoadUint32 path. Useful for cross-checking the atomic load against the raw memory view in case of cache-coherency questions on weakly-ordered arches.

func (*Virtqueue) UsedRingAt

func (q *Virtqueue) UsedRingAt(usedIdx uint16) (id uint32, length uint32)

UsedRingAt returns the device's `(id, len)` tuple at slot `usedIdx % Size`. Caller computes usedIdx as a counter; this function reads the ring entry at the wrapping position.

type VirtqueueBuffer

type VirtqueueBuffer struct {
	Addr  uintptr // host-virtual address of the data buffer
	Phys  uint64  // physical address (what the device sees)
	Len   uint32  // buffer length in bytes
	InUse bool    // true between AddBuffer and Reclaim
}

VirtqueueBuffer is the driver's per-descriptor bookkeeping. Not part of the on-the-wire layout; lives in normal Go heap.

type VirtqueueLayout

type VirtqueueLayout struct {
	// Size is the queue size (power-of-two count of descriptors).
	Size uint16

	// DescTableOffset is the byte offset of the descriptor table from
	// the allocation base. Always 0.
	DescTableOffset uint32

	// AvailRingOffset is the byte offset of the available ring's
	// `flags` field from the allocation base. = Size * 16.
	AvailRingOffset uint32

	// AvailUsedEventOffset is the byte offset of the `used_event`
	// field (Virtio 1.1 §2.6.7) — appended after the available
	// ring's `ring[]`.
	AvailUsedEventOffset uint32

	// UsedRingOffset is the byte offset of the used ring's `flags`
	// field from the allocation base. 4-byte aligned.
	UsedRingOffset uint32

	// UsedAvailEventOffset is the byte offset of the `avail_event`
	// field — appended after the used ring's `ring[]`.
	UsedAvailEventOffset uint32

	// TotalSize is the total byte size of the allocation needed to
	// hold all three regions.
	TotalSize uint32
}

VirtqueueLayout describes the byte-offset layout of one split virtqueue inside a single contiguous allocation.

Per Virtio 1.1 §2.6, the three regions are independently aligned:

descriptor table : 16-byte aligned
available ring   : 2-byte aligned
used ring        : 4-byte aligned

We allocate ALL THREE on a single 4 KiB page so every alignment constraint is trivially met. There's no in-page padding between the descriptor table and the available ring; the used ring is placed at the next 4-byte boundary after the available ring's `used_event`.

func ComputeVirtqueueLayout

func ComputeVirtqueueLayout(size uint16) VirtqueueLayout

ComputeVirtqueueLayout returns the byte-offset layout for a split virtqueue of the given size. Size MUST be a power of two between 1 and 32768 (Virtio 1.1 §2.6 — `queue_size` is le16 with the power-of-two constraint). Callers validate `size` before calling; this routine accepts any value and returns the resulting layout.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL