console

package module
v0.1.0 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: 2 Imported by: 0

README

go-virtio/console

Pure-Go virtio-console driver targeting the go-virtio/common transport interfaces. Implements the modern-transport (Virtio 1.0+) init sequence and the raw byte-stream RX / TX path for the standard PCI-bound virtio-console device (VID 0x1AF4, DID 0x1043).

This package targets the single-port baseline (Virtio 1.1 §5.3): 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). VIRTIO_CONSOLE_F_SIZE is also masked out, so the driver performs no device-config reads.

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. Every transport-level operation is routed through go-virtio/common's Transport interface, so any implementation of that interface (UEFI's EFI_PCI_IO_PROTOCOL, bare-metal MMIO, virtio-mmio adapter) drives the same driver code.

Quick start

import (
    virtioconsole "github.com/go-virtio/console"
)

// transport is any value that implements go-virtio/common.Transport.
vc, err := virtioconsole.OpenVirtioConsole(transport)
if err != nil {
    return err
}

// Write sends raw bytes to the console output, chunked one page at a time.
if _, err := vc.Write([]byte("hello from the guest\n")); err != nil {
    return err
}

// Read polls the receiveq for one buffer of console input. The argument
// is a busy-poll budget; ErrReceiveTimeout is returned if it is exhausted.
in, err := vc.Read(10000)

OpenVirtioConsole leaves the device in DRIVER_OK state with the receiveq pre-posted with one-page buffers and the transmitq empty + ready.

Sibling packages

License

BSD-3-Clause. See LICENSE.

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

View Source
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).

View Source
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.

View Source
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.

View Source
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

View Source
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

func AcceptFeatures(deviceFeatures uint64) (uint64, error)

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:

  1. Verify the PCI device ID is 0x1043 (modern console).
  2. InitModernConfig walks PCI caps + populates the BAR locators.
  3. Reset → ACK → DRIVER status progression.
  4. Read DeviceFeature, require VERSION_1, mask, write DriverFeature.
  5. Set FEATURES_OK, verify it stuck.
  6. Allocate + publish receiveq (queue 0) + transmitq (queue 1).
  7. DRIVER_OK status.
  8. 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).

Jump to

Keyboard shortcuts

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