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
- Variables
- func NotifyOffMultiplierCfgOffset(notifyCapCfgSpaceOffset uint8) uint32
- func PCIDeviceIDIsBlock(deviceID uint16) bool
- func PCIDeviceIDIsEntropy(deviceID uint16) bool
- func PCIDeviceIDIsLegacy(deviceID uint16) bool
- func PCIDeviceIDIsModern(deviceID uint16) bool
- func PCIDeviceIDIsNet(deviceID uint16) bool
- type BARMemoryAccessor
- type ModernConfig
- func (c *ModernConfig) ConfigGeneration() (uint8, error)
- func (c *ModernConfig) DeviceCfgRead8(offset uint32) (uint8, error)
- func (c *ModernConfig) DeviceCfgRead16(offset uint32) (uint16, error)
- func (c *ModernConfig) DeviceCfgRead32(offset uint32) (uint32, error)
- func (c *ModernConfig) DeviceCfgRead64(offset uint32) (uint64, error)
- func (c *ModernConfig) DeviceFeatures64() (uint64, error)
- func (c *ModernConfig) DeviceStatus() (uint8, error)
- func (c *ModernConfig) HasDeviceCfg() bool
- func (c *ModernConfig) HasNotifyCfg() bool
- func (c *ModernConfig) NotifyQueue(queueIdx uint16, queueNotifyOff uint16) error
- func (c *ModernConfig) NumQueues() (uint16, error)
- func (c *ModernConfig) PerQueueNotifyOffset(queueNotifyOff uint16) uint64
- func (c *ModernConfig) QueueDesc() (uint64, error)
- func (c *ModernConfig) QueueDevice() (uint64, error)
- func (c *ModernConfig) QueueDriver() (uint64, error)
- func (c *ModernConfig) QueueEnable() (uint16, error)
- func (c *ModernConfig) QueueNotifyOff() (uint16, error)
- func (c *ModernConfig) QueueSize() (uint16, error)
- func (c *ModernConfig) ReadDeviceFeature() (uint32, error)
- func (c *ModernConfig) ReadDeviceFeatureSelect() (uint32, error)
- func (c *ModernConfig) SelectQueue(idx uint16) error
- func (c *ModernConfig) SetDeviceStatus(v uint8) error
- func (c *ModernConfig) SetDriverFeatures64(v uint64) error
- func (c *ModernConfig) SetQueueDesc(addr uint64) error
- func (c *ModernConfig) SetQueueDevice(addr uint64) error
- func (c *ModernConfig) SetQueueDriver(addr uint64) error
- func (c *ModernConfig) SetQueueEnable(v uint16) error
- func (c *ModernConfig) SetQueueSize(v uint16) error
- func (c *ModernConfig) WriteDeviceFeatureSelect(v uint32) error
- func (c *ModernConfig) WriteDriverFeature(v uint32) error
- func (c *ModernConfig) WriteDriverFeatureSelect(v uint32) error
- type PCICap
- type PCIConfigReader
- type PageAllocator
- type Transport
- type Virtqueue
- func (q *Virtqueue) AddBuffer(addr uintptr, phys uint64, length uint32, writable bool) (uint16, error)
- func (q *Virtqueue) AvailFlags() uint16
- func (q *Virtqueue) AvailHeaderBytes() []byte
- func (q *Virtqueue) AvailIdx() uint16
- func (q *Virtqueue) DescBytes(idx uint16) []byte
- func (q *Virtqueue) LastSeenUsedIdx() uint16
- func (q *Virtqueue) Mem() []byte
- func (q *Virtqueue) NextAvailIdx() uint16
- func (q *Virtqueue) PollUsed() (uint16, uint32, bool)
- func (q *Virtqueue) PostAvail(descIdx uint16) error
- func (q *Virtqueue) Reclaim(descIdx uint16) error
- func (q *Virtqueue) UsedHeaderBytes() []byte
- func (q *Virtqueue) UsedIdx() uint16
- func (q *Virtqueue) UsedIdxRaw() uint16
- func (q *Virtqueue) UsedRingAt(usedIdx uint16) (id uint32, length uint32)
- type VirtqueueBuffer
- type VirtqueueLayout
Constants ¶
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.
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:
- Write 0 to DeviceStatus (full reset).
- Set ACKNOWLEDGE.
- Set DRIVER.
- Read DeviceFeature, mask down, write DriverFeature.
- Set FEATURES_OK, re-read DeviceStatus, confirm FEATURES_OK.
- Set up per-class queues.
- Set DRIVER_OK.
Failure mode: FAILED bit set (firmware/device rejected our config).
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.
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.
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.
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.
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) 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
};
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.
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).
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).
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).
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.
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.
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.
const PCICapIDVendorSpecific uint8 = 0x09
PCICapIDVendorSpecific = 0x09 (PCI Local Bus Specification 3.0 Appendix H). All Virtio PCI capabilities use this ID.
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`.
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).
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).
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.
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 ¶
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.
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.
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 ¶
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 ¶
PCIDeviceIDIsBlock reports whether a DeviceID identifies a virtio-blk device.
func PCIDeviceIDIsEntropy ¶ added in v0.1.1
PCIDeviceIDIsEntropy reports whether a DeviceID identifies a virtio-rng (virtio-entropy) device (legacy 0x1005 OR modern 0x1044).
func PCIDeviceIDIsLegacy ¶
PCIDeviceIDIsLegacy reports whether a DeviceID is in the legacy (0.9 / transitional) range. Exposed so callers can branch early.
func PCIDeviceIDIsModern ¶
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 ¶
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`:
- Read PCI Status and confirm bit 4 (CapabilityList) is set.
- Read the CapabilitiesPtr at config offset 0x34.
- Walk the cap list via `WalkPCICaps` against the transport.
- Pass the resulting `[]PCICap` to `ParseCaps` along with the transport's BAR accessor.
- 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 ¶
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 ¶
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 ¶
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 ¶
AvailFlags returns the current `flags` field of the available ring (Virtio 1.1 §2.6.6 — VIRTQ_AVAIL_F_NO_INTERRUPT).
func (*Virtqueue) AvailHeaderBytes ¶
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 ¶
AvailIdx returns the current `idx` field — the running count of available-ring publications.
func (*Virtqueue) DescBytes ¶
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 ¶
LastSeenUsedIdx returns the driver's view of the used-ring index.
func (*Virtqueue) Mem ¶
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 ¶
NextAvailIdx returns the driver's view of the available-ring index.
func (*Virtqueue) PollUsed ¶
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 ¶
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 ¶
Reclaim marks descriptor `descIdx` as free (caller has consumed the device's used-ring report and copied the data out). Idempotent.
func (*Virtqueue) UsedHeaderBytes ¶
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 ¶
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 ¶
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.
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.