Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

virtio-accel protocol 1.0 wire ABI

This document defines the protocol 1.0 candidate byte contract used by the command virtqueue. It is normative together with specification.md and virtqueue.md. Structure names refer to the Rust implementation for convenience; implementations in other languages depend only on the byte layouts and rules below.

All multibyte integers are unsigned little-endian values unless a field explicitly says otherwise. Every structure has byte alignment one and contains no implicit padding. Offsets and sizes are listed in the checked-in layout manifest.

1. Global limits

ConstantValueRequirement
Protocol version1.0Configuration MUST report major 1 and minor 0
Baseline command queues1Only command virtqueue index 0 exists
Hard maximum chain descriptors256The advertised maximum MUST be 2 through 256
Hard maximum request frame16 MiBIncludes the 16-byte request header
Hard maximum response frame16 MiBIncludes the 16-byte response header
Hard maximum submission bindings4096The semantic advertised limit may be smaller

The device-specific configuration limits are additional negotiated bounds. A device MUST NOT advertise a limit above a hard maximum. A driver MUST reject invalid configuration rather than allocating from it.

Every count-to-byte conversion MUST use checked multiplication and addition. A value that overflows the implementation address space or the fixed-width containing field is invalid.

2. Device-specific configuration

WireConfig is 16 bytes:

OffsetBytesFieldProtocol 1.0 rule
02protocol_majorMUST be 1
22protocol_minorA conforming 1.0 device reports 0; a 1.0 driver MUST accept a higher minor and use only 1.0 behavior
42command_queue_countMUST be 1
62max_chain_descriptorsMUST be 2 through 256 and no greater than the configured queue size
84max_request_bytesMUST be 97 through 16 MiB
124max_response_bytesMUST be 92 through 16 MiB

The minimum request limit admits a one-byte program artifact. The minimum response limit admits the complete device-information response.

The 16 bytes above are the protocol 1.0 configuration prefix. A future minor version may append configuration only when a 1.0 driver can safely ignore it and the extension does not alter baseline behavior without feature negotiation. A 1.0 driver reads and validates the prefix and does not require the entire transport-specific configuration region to be exactly 16 bytes.

The baseline device-specific feature set is empty. Feature-bit positions 0 through 4 are reserved for multi-queue, event-queue, external-memory, timeline-fence, and secure-context proposals. A protocol 1.0 device MUST NOT advertise them and a protocol 1.0 driver MUST NOT accept them.

3. Request and response frames

3.1 Request header

Every request begins with the 16-byte RequestHeader:

OffsetBytesFieldRule
02opcodeRaw opcode from section 5
22flagsMUST be zero
44payload_bytesExact number of readable bytes after this header
88request_idNonzero and unique among requests outstanding on this device instance

Bit zero of flags, formerly drafted as NO_WAIT, is reserved and has no 1.0 semantics.

The readable byte count MUST equal 16 + payload_bytes exactly. There is no tolerated trailing extension area. An unknown opcode remains a raw integer long enough to produce UNSUPPORTED; it MUST NOT be materialized as an invalid language enum.

A request ID may be reused only after the corresponding descriptor chain has been returned used or after the driver has completed a device reset. Request IDs correlate command completion; they do not identify accelerator events.

3.2 Response header

Every written response begins with the 16-byte ResponseHeader:

OffsetBytesFieldRule
02statusRaw status from section 6
22flagsMUST be zero
44payload_bytesExact number of bytes written after this header
88request_idExact request ID from the corresponding valid request header

The driver MUST validate that the response request ID matches the request associated with the used descriptor head. An unknown status is an opaque failure, never success.

Except for an indeterminate SUBMIT, every non-OK response has an empty payload. An indeterminate SUBMIT has the original mapped failure status and an eight-byte SubmitResponse; possession of that event ID prevents premature resource release.

4. Common value namespaces

4.1 Object IDs

An object ID is an opaque nonzero u64. Zero is invalid. Its encoding is device-private and no bit has driver-visible meaning.

4.2 Memory domains

ValueMeaning
1Host-preferred memory
2Device-preferred memory
3Shared/coherent memory class

All other values produce INVALID_ARGUMENT. These values express placement intent only and do not create a host mapping or external-memory handle.

4.3 Buffer usage bits

BitValueMeaning
00x00000001Transfer source
10x00000002Transfer destination
20x00000004Program input
30x00000008Program output
40x00000010Mutable program state

At least one usage bit MUST be set. Unknown bits produce UNSUPPORTED.

4.4 Binding access

ValueMeaning
1Read
2Write
3Read and write

All other values produce INVALID_ARGUMENT.

Read requires PROGRAM_INPUT or MUTABLE_STATE, Write requires PROGRAM_OUTPUT or MUTABLE_STATE, and Read and write requires MUTABLE_STATE. A mismatch produces PERMISSION_DENIED before backend admission.

4.5 Event states

ValueStateerror field
0PendingOK
1CompleteOK
2FailedNon-OK status explaining execution failure
3CancelledOK

Unknown event states make the response invalid to a 1.0 driver. A driver MUST retain the event and request recovery rather than guessing that the event is terminal.

5. Opcodes and payloads

The request payload length is exact. A fixed prefix followed by variable bytes has no alignment padding between the prefix and tail.

OpcodeValueRequest payloadOK response payloadMaximum required writable capacity
GET_DEVICE_INFO0x0001EmptyWireDeviceInfo92
CREATE_CONTEXT0x0100CreateContextRequestObjectPayload context ID24
DESTROY_CONTEXT0x0101ObjectPayload context IDEmpty16
ALLOCATE_BUFFER0x0200AllocateBufferRequestObjectPayload buffer ID24
FREE_BUFFER0x0201ObjectPayload buffer IDEmpty16
WRITE_BUFFER0x0202TransferBufferRequest + dataEmpty16
READ_BUFFER0x0203TransferBufferRequestExactly bytes data16 + bytes
LOAD_PROGRAM0x0300LoadProgramRequest + artifactObjectPayload program ID24
UNLOAD_PROGRAM0x0301ObjectPayload program IDEmpty16
CREATE_QUEUE0x0400CreateQueueRequestObjectPayload execution-queue ID24
DESTROY_QUEUE0x0401ObjectPayload execution-queue IDEmpty16
SUBMIT0x0500SubmitRequest + WireBinding[]SubmitResponse event ID24
POLL_EVENT0x0501ObjectPayload event IDWireEventState24
CANCEL_EVENT0x0502ObjectPayload event IDEmpty16
DESTROY_EVENT0x0503ObjectPayload event IDEmpty16

The maximum required writable capacity is validated before any semantic mutation or backend call. Protocol 1.0 has no object-list payload: every destruction or event operation names exactly one object ID. The binding array is the only variable-count structured array in a baseline request.

5.1 WireDeviceInfo

WireDeviceInfo is 76 bytes:

FieldRule
uuid[16]Stable identity for this accelerator device
classExtensible raw class; 0 other, 1 NPU, 2 GPU, 3 DSP
reservedZero
vendor_id, device_idProvider identity; zero means unspecified
capabilitiesAssigned semantic capability bits only
max_contextsNonzero device-wide live-context limit
max_buffers_per_contextNonzero live-buffer limit
max_programs_per_contextNonzero live-program limit
max_queues_per_contextNonzero live execution-queue limit
max_events_per_contextNonzero live-event/in-flight-submission limit
max_bindings_per_submission1 through 4096
max_buffer_bytesNonzero maximum allocation size
max_artifact_bytesNonzero maximum artifact tail size, additionally bounded by the request-frame limit

Capability bits are semantic reports, not Virtio feature bits. A capability MUST NOT alter wire framing without a separately negotiated feature. Unknown capability bits are ignored for operation selection and preserved by diagnostic tooling.

Assigned protocol 1.0 semantic capability bits are:

BitName
0HOST_VISIBLE_MEMORY
1DEVICE_LOCAL_MEMORY
2EVENT_CANCELLATION
5SHARED_MEMORY

Bits 3 (EXTERNAL_MEMORY) and 4 (SECURE_CONTEXTS) are reserved and MUST NOT be advertised by a protocol 1.0 device.

5.2 Context

CreateContextRequest is eight bytes: flags: u32 followed by reserved: u32. Both fields MUST be zero in protocol 1.0.

Context destruction uses an eight-byte ObjectPayload.

5.3 Buffers

AllocateBufferRequest is 40 bytes:

FieldRule
context_idLive context
bytesNonzero and no greater than max_buffer_bytes
alignmentNonzero power of two
memory_domainAssigned value from section 4.2
reserved0[7]All zero
usageNonempty subset of assigned usage bits
reserved1Zero

The device MUST reject a memory domain whose corresponding semantic capability is absent before backend invocation. Host, Device, and provider-owned Shared allocations use capability bits 0, 1, and 5 respectively. Successful allocation commits the backend to the placement and direct-binding rules in specification.md; it may not silently substitute a staged submission path.

TransferBufferRequest is 24 bytes containing buffer_id, offset, and bytes. bytes MUST be nonzero. offset + bytes MUST NOT overflow and MUST fit in the buffer.

For WRITE_BUFFER, the request payload length MUST be 24 + bytes, and bytes following the prefix are copied to the buffer. For READ_BUFFER, the request payload is exactly 24 bytes and the success response payload contains exactly bytes bytes. Transfers must also fit the configured request or response frame maximum.

WRITE_BUFFER requires buffer usage TRANSFER_DESTINATION; READ_BUFFER requires TRANSFER_SOURCE. These commands are explicit copy boundaries. Their existence does not permit allocation or submission to copy program bindings through hidden bounce buffers.

5.4 Programs

LoadProgramRequest is an 80-byte prefix:

FieldRule
context_idLive context
formatNonzero provider-owned format ID
flagsZero
target[12]Opaque format-owned target words
payload_bytesNonzero, equals the exact artifact tail length
resident_bytesNonzero upper bound on provider storage retained for the returned program

80 + payload_bytes MUST fit the request payload and configured frame limit.

5.5 Execution queues

CreateQueueRequest is 16 bytes: context_id: u64, flags: u32, and reserved: u32. Both flag and reserved words MUST be zero in protocol 1.0.

5.6 Submission and events

SubmitRequest is a 32-byte prefix containing queue_id, program_id, binding_count, flags, and timeout_ns.

  • binding_count MUST be 1 through both advertised max_bindings_per_submission and 4096.
  • flags MUST be zero.
  • timeout_ns is relative to backend admission; zero means infinite.
  • The payload length MUST be 32 + binding_count * 32.

Each 32-byte WireBinding contains buffer_id, offset, bytes, slot, access, and three reserved-zero bytes. Buffer ranges are nonempty and checked for overflow. Slots are unique within the submission. Every object belongs to the same context.

SubmitResponse is an eight-byte event ID. It is returned with OK after accepted admission and with the mapped non-OK status when admission is indeterminate. A rejected submission has an empty error payload.

WireEventState is eight bytes: state: u16, error: u16, and reserved: u32. Reserved bytes are zero and the state/error combinations are exactly those in section 4.5. Polling is nonblocking. Once a terminal state is observed, later successful polls return the same terminal state.

6. Status namespace

ValueNameMeaning
0OKCommand completed successfully
1UNSUPPORTEDOpcode, flag, feature, capability, or operation is not supported
2INCOMPATIBLEKnown artifact, target, object, or capability combination is incompatible
3INVALID_ARGUMENTMalformed value, reserved field, length mismatch, zero required value, or duplicate slot
4OUT_OF_BOUNDSChecked byte range does not fit its object
5BUSYObject is live, referenced, pending, or otherwise retryable
6OUT_OF_MEMORYHost/provider allocation failed
7RESOURCE_LIMITConfigured count or byte limit would be exceeded
8DEADLINE_EXPIREDOperation expired according to the relative timeout contract
9DEVICE_LOSTBackend/device state cannot continue normally
10PERMISSION_DENIEDIsolation or provider policy denied the operation
11STALE_OBJECTNonzero object ID is stale, wrong-kind, reset-invalidated, or not valid in this context
65535INTERNAL_ERRORUnclassified implementation/provider failure

Unknown status values are opaque non-success failures. Provider-specific error domains do not cross the 1.0 wire boundary; absent a future diagnostic feature, they map to INTERNAL_ERROR.

Malformed input is classified before backend invocation:

  • unknown opcode or nonzero/unknown flags: UNSUPPORTED;
  • fixed-length mismatch, trailing bytes, reserved nonzero, invalid scalar, zero required value, or arithmetic overflow: INVALID_ARGUMENT;
  • frame, binding, object, or configured quota exceeded: RESOURCE_LIMIT;
  • valid object ID with wrong generation, kind, reset epoch, or context: STALE_OBJECT; and
  • valid byte range outside the selected object: OUT_OF_BOUNDS.

7. Response atomicity

Before backend invocation, the device MUST validate the complete readable frame and enough writable capacity for every possible response shape of that command. It MUST initialize every response byte it reports used.

Ordinary protocol errors have no semantic state change. If an unexpected transport write failure occurs after semantic mutation, or a release becomes indeterminate, the device MUST enter recovery and expose the Virtio DEVICE_NEEDS_RESET condition. It MUST NOT report an ordinary rejected response that would let the driver free resources whose ownership is uncertain.

8. Versioned compatibility artifacts

Protocol constants, layouts, and canonical bytes are checked in under conformance/v1.0. They are review inputs, not test-generated output. The final freeze audit in ../conformance/v1.0/freeze-audit.md makes these files the frozen 1.0 inputs. Post-freeze changes are classified by section 9 and release-policy.md.

9. Candidate and post-freeze change procedure

A proposed wire change MUST be classified before code is merged:

  1. An erratum that changes no accepted or emitted bytes may clarify the 1.0 documents and tests.
  2. A compatible extension uses a previously reserved number plus explicit feature or new-opcode negotiation, preserves every 1.0 frame, and receives a new minor-version conformance directory.
  3. Any changed assigned number, existing payload length, field meaning, required response, or ownership interpretation requires a new protocol major version and a new conformance directory.

The same reviewed change MUST update the normative documents, Rust constants/layout assertions, machine layout manifest, canonical vectors, and compatibility tests. Protocol version directories are never regenerated opportunistically from current Rust types.