Documentation
¶
Overview ¶
go-virtio/console — driver core: feature negotiation + init sequence + raw byte-stream RX / TX path for the modern virtio-console device (Virtio 1.1 §5.3).
This driver targets the single-port baseline: it negotiates only VIRTIO_F_VERSION_1, so VIRTIO_CONSOLE_F_MULTIPORT is NOT acknowledged and the device exposes exactly two virtqueues — a receiveq (port 0 input) and a transmitq (port 0 output). There are no control queues and no per-message header: the console is a raw bidirectional byte stream, which makes the data path simpler than virtio-net (no virtio_net_hdr to prepend or strip).
The driver pre-posts one-page device-writable buffers on the receiveq at bring-up so the device has somewhere to land guest input, and posts device-readable buffers on demand in Write.
Index ¶
Constants ¶
const ( ReceiveQueueIdx uint16 = 0 TransmitQueueIdx uint16 = 1 )
ReceiveQueueIdx / TransmitQueueIdx are the two virtqueue indices for the single-port baseline (Virtio 1.1 §5.3.2: port 0 receiveq = 0, transmitq = 1; the control queues only exist when F_MULTIPORT is negotiated, which this driver does not do).
const ( ReceiveQueueSize uint16 = 16 TransmitQueueSize uint16 = 16 )
ReceiveQueueSize / TransmitQueueSize are the desired ring sizes for the two queues. Clamped down to the device's advertised maximum (and rounded to a power of two) during setup.
const AcceptedFeatures uint64 = common.FeatureVersion1
AcceptedFeatures is the feature mask the driver negotiates ON. For the single-port baseline the only bit we ever accept is the non-negotiable VIRTIO_F_VERSION_1 (modern transport). VIRTIO_CONSOLE_F_SIZE (0) and VIRTIO_CONSOLE_F_MULTIPORT (1) are deliberately masked OUT — the former would require device-config reads we skip, the latter would add control queues we do not drive.
const TxPollIterations = 200000
TxPollIterations is the default busy-poll budget Write spends waiting for the device to return a transmitted buffer. The console round-trip is sub-millisecond on every backend; this is a generous upper bound for the busy-poll model the driver uses.
Variables ¶
var ( ErrNotModernDevice = commonConsoleError("go-virtio/console: device doesn't offer VIRTIO_F_VERSION_1 (legacy-only)") ErrFeaturesNotOK = commonConsoleError("go-virtio/console: FEATURES_OK status bit didn't stick after DriverFeature write") ErrInitWrongDeviceID = commonConsoleError("go-virtio/console: PCI device ID is not 0x1043 (modern console device)") ErrQueueNotAvailable = commonConsoleError("go-virtio/console: device reports QueueSize=0 for a required queue") ErrTransmitTimeout = commonConsoleError("go-virtio/console: TX poll timeout (device did not return descriptor)") ErrReceiveTimeout = commonConsoleError("go-virtio/console: RX poll timeout (no input received within budget)") ErrBufferTooSmall = commonConsoleError("go-virtio/console: PageAllocator returned a chunk smaller than one page") )
Sentinel errors for the virtio-console 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, the device is legacy-only and we return ErrNotModernDevice.
Types ¶
type VirtioConsole ¶
type VirtioConsole struct {
// Cfg is the modern-transport handle (BARs + offsets + the
// BARMemoryAccessor used for every register access).
Cfg *common.ModernConfig
// NegotiatedFeatures records what the driver-feature handshake
// settled on. Exposed for diagnostic prints.
NegotiatedFeatures uint64
// contains filtered or unexported fields
}
VirtioConsole wraps one initialised virtio-console device. The caller holds this for the lifetime of the console; the underlying virtqueue pages live as long as the supplied PageAllocator's lifetime contract.
func OpenVirtioConsole ¶
func OpenVirtioConsole(t common.Transport) (*VirtioConsole, error)
OpenVirtioConsole drives the full bring-up of one virtio-console device:
- Verify the PCI device ID is 0x1043 (modern console).
- InitModernConfig walks PCI caps + populates the BAR locators.
- Reset → ACK → DRIVER status progression.
- Read DeviceFeature, require VERSION_1, mask, write DriverFeature.
- Set FEATURES_OK, verify it stuck.
- Allocate + publish receiveq (queue 0) + transmitq (queue 1).
- DRIVER_OK status.
- Pre-post receiveq buffers + notify the device.
On success the device is in DRIVER_OK state, the receiveq is pre-posted with one-page buffers, and the transmitq is empty + ready. Unlike virtio-net there is no device-config region to read (F_SIZE is not negotiated) and no per-message header.
func (*VirtioConsole) Read ¶
func (v *VirtioConsole) Read(pollIterations int) ([]byte, error)
Read polls the receiveq for one buffer of console input, busy-polling up to pollIterations iterations. On success it copies the device's bytes out, reclaims + re-posts the same buffer (so the device has somewhere to land the next input), notifies the device, and returns the raw bytes. Returns ErrReceiveTimeout if no input arrives within the budget.
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 re-posted).
func (*VirtioConsole) ReceiveQueue ¶
func (v *VirtioConsole) ReceiveQueue() *common.Virtqueue
ReceiveQueue / TransmitQueue 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 (*VirtioConsole) TransmitQueue ¶
func (v *VirtioConsole) TransmitQueue() *common.Virtqueue
TransmitQueue returns the transmit virtqueue handle.
func (*VirtioConsole) Write ¶
func (v *VirtioConsole) Write(p []byte) (int, error)
Write sends the raw bytes in p to the console output, chunked one page at a time. Each chunk is copied into a fresh device-readable DMA buffer, enqueued on the transmitq, notified, and busy-polled for completion up to TxPollIterations iterations. Returns the total number of bytes written, or ErrTransmitTimeout if the device stalls.
Write(nil) / Write([]byte{}) is a no-op returning (0, nil).