virtio-accel portable protocol foundations
Status: frozen portable protocol 1.0 candidate. The final freeze audit is recorded in
conformance/v1.0/freeze-audit.md.
This document defines the portable semantic foundation for virtio-accel. It does not assign a
Virtio device ID and is not an OASIS Virtio specification. The transport model is intended to remain
compatible with the general facilities defined by the
Virtio specification.
Sections 1 through 11, wire-abi.md, and virtqueue.md are normative for the portable protocol 1.0 candidate. Appendix A maps the current Rust surface to the terms in this document and is non-normative implementation guidance.
1. Normative language
The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, MAY, and OPTIONAL are to be interpreted as described by RFC 2119 and RFC 8174 when, and only when, they appear in bold capitals.
Explanatory paragraphs beginning with “Rationale” are non-normative.
2. Scope
The portable v1 contract defines:
- device discovery and protocol compatibility;
- contexts and context-scoped resource ownership;
- buffers and bounded byte transfers;
- opaque program artifacts;
- accelerator execution queues;
- submissions, bindings, relative timeouts, and asynchronous events;
- cancellation when supported;
- explicit destruction, reset, and failure recovery; and
- a single baseline command virtqueue carrying request and response frames.
The portable v1 contract deliberately does not define:
- a standardized accelerator graph, compiler IR, or executable format;
- an OASIS device ID or standards submission;
- PCI, MMIO, vhost-user, QEMU, kernel, DRM, VFIO, or hypervisor integration;
- operating-system or vendor accelerator APIs;
- DMA-BUF, platform shared handles, zero-copy external memory, cache-coherency protocols, or timeline fences;
- packed virtqueues; or
- process, thread, executor, or async-runtime policy.
Adding an integration listed above MUST NOT change the portable object lifecycle by convention. If it changes driver/device-visible behavior, it requires a negotiated feature or a new protocol version.
3. Roles and boundaries
3.1 Driver
The driver is the request initiator. It discovers the device, validates protocol compatibility, negotiates Virtio features, constructs request frames, publishes descriptor chains, validates responses, and owns guest-side request tracking.
The driver MUST treat all device-written bytes as untrusted.
3.2 Device
The device owns the guest-visible protocol state. It validates descriptor direction and frame bytes, maps opaque object IDs to live resources, enforces limits and context isolation, invokes an accelerator backend, writes responses, and performs reset.
The device MUST treat all driver-written bytes, lengths, counts, object IDs, flags, and timing as untrusted.
3.3 Transport adapter
A transport adapter maps a concrete Virtio implementation to portable readable/writable regions, queue notification, used-length reporting, and reset operations.
A transport adapter MUST NOT expose guest addresses, descriptor objects, host mappings, or transport-specific synchronization types to the accelerator backend.
3.4 Command engine
The command engine is the transport-neutral device state machine. It decodes one validated request, performs object and quota checks, invokes the backend, updates ownership state, and produces one response.
The command engine MUST NOT depend on an operating system, VMM, kernel, guest-memory crate, vendor API, or compiler API.
3.5 Accelerator backend
The accelerator backend implements device-local execution semantics using provider-owned native
handles. It is represented by virtio_accel_core::Accelerator.
A backend MUST NOT receive wire structures, object IDs, guest addresses, or virtqueue descriptors. It receives validated semantic values and provider-owned handles.
4. Terminology and object model
4.1 Device instance
A device instance is one initialized protocol endpoint and its backend. It owns:
- one device identity and set of semantic capabilities;
- advertised limits;
- one object-ID namespace;
- zero or more contexts; and
- one reset epoch.
Object IDs are meaningful only within the device instance and reset epoch that created them.
4.2 Command virtqueue
The command virtqueue is the Virtio transport queue that carries request and response frames. Queue index zero is the only command virtqueue in the mandatory baseline.
The command virtqueue is not an accelerator execution queue.
4.3 Context
A context is the isolation and ownership parent for buffers, programs, accelerator execution queues, and the events created by its submissions.
A device MUST reject an operation that combines objects from different contexts before invoking the backend. A context MUST NOT be destroyed while any child object or in-flight reference remains live.
4.4 Buffer
A buffer is a bounded backend allocation with:
- a nonzero byte length;
- a nonzero power-of-two alignment;
- a memory domain; and
- declared usage flags.
The buffer’s guest-visible object ID is not an address. Every transfer and binding range MUST fit within the buffer without integer overflow.
Memory domains are strict allocation requirements:
Hostrequests provider memory optimized for host transfers;Devicerequests the provider’s accelerator-local placement class; andSharedrequests one provider-owned allocation that is both host visible and directly bindable by the accelerator.
Shared does not mean guest-memory import, cross-process export, a platform shared handle, or
implicit cache coherence. Those operations require the reserved external-memory feature and have no
protocol 1.0 semantics.
An allocation result MUST report the requested descriptor, actual retained allocation bytes, actual guaranteed alignment, and honest backing properties. Actual bytes MUST NOT be smaller than the logical buffer and actual alignment MUST NOT be weaker than requested. Device resource accounting uses actual retained bytes rather than assuming logical bytes include provider padding. A provider MUST NOT return an allocation whose reported placement can be achieved only by copying through another full-size allocation during submission.
A buffer whose usage includes program input, program output, or mutable state MUST report direct
binding. A compatible submission MUST bind the exact provider allocation without copying the
bound range into or out of another allocation. If the provider cannot satisfy that invariant, it
MUST reject allocation. If a particular program is incompatible with an otherwise valid buffer,
submission MUST be rejected as INCOMPATIBLE; the provider MUST NOT silently stage it.
WRITE_BUFFER and READ_BUFFER are the only baseline operations that explicitly transfer buffer
contents. A write requires TRANSFER_DESTINATION; a read requires TRANSFER_SOURCE. Device-local
memory may use bounded provider staging during these explicit transfers, but no transfer may retain
the request or response byte region after the backend call returns.
The semantic transfer API accepts bounded byte sources and sinks that may be segmented. This avoids requiring the command engine to allocate a contiguous copy of a valid transfer payload or response before calling the backend. A contiguous region remains available to providers as an optional fast path.
4.5 Program
A program is a resident backend object created from an opaque artifact format, opaque target identity, payload, and caller-authorized upper bound on retained resident bytes.
The transport MUST NOT interpret vendor artifact contents. Artifact-format adapters own their validation beyond the portable envelope fields.
The semantic artifact payload is a bounded byte source rather than a required contiguous slice. Providers may inspect an available contiguous view or read segmented payload bytes directly into final resident storage.
4.6 Accelerator execution queue
An accelerator execution queue is a context-scoped backend object used to submit programs for
execution. It is created by the protocol CreateQueue command and represented by
Accelerator::Queue.
It is unrelated to a Virtio queue index. Documentation and APIs SHOULD use “execution queue” whenever omitting “accelerator” would make the distinction unclear.
4.7 Binding
A binding associates one program slot with a nonempty buffer range and an intended access mode for one submission.
Binding slot numbers MUST be unique within a submission. A binding does not transfer ownership of its buffer, but the device MUST retain the buffer until the resulting event is safely reclaimed.
Read access requires a buffer declared for program input or mutable state. Write access requires a buffer declared for program output or mutable state. Read-write access requires mutable state. Binding-array order has no semantic meaning; the slot number identifies the program argument.
4.8 Submission
A submission is an attempt to admit one program, execution queue, binding list, and relative timeout to the backend.
Admission has an explicit acceptance boundary:
- rejected means the backend guarantees that execution was not accepted and no operation resources were retained;
- accepted returns an event; and
- indeterminate means acceptance cannot be established and therefore also returns an event that owns the retained resources.
The command engine MUST NOT convert an indeterminate submission into an ordinary error.
4.9 Event
An event is the ownership and completion token for one accepted or indeterminate submission. Its state is pending, complete, failed, or cancelled.
An event MUST retain its execution queue, program, buffers, and per-invocation backend state until it is terminal and successfully destroyed. A pending event MUST NOT be destroyed.
Terminal event states are stable. Cancellation and completion select exactly one terminal result:
if cancellation wins, the cancellation command succeeds and polling reports cancelled; if
completion wins, cancellation returns BUSY and polling reports the completed or failed result.
4.10 Request and response
A request is one driver-readable command frame identified by a request ID. A response is the corresponding device-written frame with the same request ID.
Request IDs correlate command completion only. They are not object IDs or execution event IDs.
4.11 Reset
A reset stops admission, disposes or quarantines in-flight state according to its known ownership, invalidates every guest-visible object ID, advances the device epoch, resets command queues, and returns the device to its initial negotiation state.
The transport MUST stop fetching command chains and publishing completions before portable object teardown begins. Teardown MUST be bounded and child-before-parent: events precede their execution queues, programs, and buffers, and all context children precede the context.
The device MAY reuse a backend instance only when every known resource is released successfully. An unresolved pending event, a rejected reset-time release, an indeterminate release, backend device loss, or an accounting contradiction requires discarding the complete backend instance. Once discard is required, repeated reset attempts MUST NOT invoke that backend again.
Successful reinitialization MUST use a fresh nonzero object namespace. No object ID created before reset may resolve after reset.
4.12 Host resource policy and hostile input
Every guest-controlled byte length, object count, array count, descriptor count, queue occupancy,
and in-flight reference count MUST be checked against an authoritative nonzero bound before it
can drive allocation, iteration, or provider invocation. Checked arithmetic MUST reject a value
whose derived size is not representable. Wire framing bounds belong to WireConfig, provider
capability and per-object bounds belong to DeviceLimits, and aggregate provider-retained storage
bounds belong to a host-supplied ResourcePolicy; an implementation MUST NOT use conflicting
duplicate limits for the same boundary.
A device integration MUST provide nonzero aggregate limits for actual buffer backing bytes and
declared program resident bytes. The command engine MUST charge the actual
BufferInfo::allocation_bytes returned by the provider and the ArtifactRef::resident_bytes
authorized before program loading. A provider MUST NOT retain program storage attributable to a
returned program handle beyond that program’s resident-byte charge.
A retained-byte charge begins before the associated object ID is published and MUST remain accounted while the provider handle is live or releasing. Rejected release MUST restore the charge with the same live object. Indeterminate release MUST transfer the charge to quarantine and require discard of the complete backend instance.
If actual buffer backing would exceed aggregate policy, the device MUST NOT expose the new object ID. It MUST attempt release through the provider ownership boundary; unless successful release is proven, the device MUST stop admission and require backend discard rather than report ordinary resource exhaustion.
Portable parsing, lookup, queue, polling, cancellation, and reset work MUST terminate after a validated finite amount of local work and MUST NOT busy-wait for guest or provider progress. Malformed guest input and configured resource exhaustion MUST NOT cause a panic or expose uninitialized response bytes. Repeated-command rate limits, guest-memory pinning budgets, and provider-hang watchdogs belong to the transport or host integration because the portable layer has no clock, scheduler, tenant, or safe way to preempt arbitrary provider code.
5. Baseline capabilities and feature policy
5.1 Mandatory baseline
The portable v1 baseline MUST provide:
- command virtqueue zero;
- fixed-width little-endian request and response headers;
- device information;
- context creation and destruction;
- buffer allocation, destruction, and bounded read/write transfer;
- opaque program loading and destruction;
- accelerator execution queue creation and destruction;
- submission with bounded bindings and relative timeout;
- event polling and destruction;
- stale-object and cross-context rejection; and
- whole-device reset.
The command opcode for cancellation is part of the baseline namespace. If the backend does not
advertise semantic event-cancellation capability, the device MUST return UNSUPPORTED without
changing the event.
5.2 Transport feature bits
Transport feature bits alter driver/device protocol behavior. The mandatory baseline has no
device-specific transport features: BASELINE_FEATURES is empty.
The currently reserved bits MULTI_QUEUE, EVENT_QUEUE, EXTERNAL_MEMORY,
TIMELINE_FENCES, and SECURE_CONTEXTS reserve numeric positions only. A baseline device MUST NOT
advertise them and a baseline driver MUST NOT accept them. Protocol 1.0 assigns no semantics to
these positions.
Unknown device-specific feature bits MUST NOT be accepted by a driver. A device MUST NOT offer a feature it cannot honor if accepted.
5.3 Semantic backend capabilities
Backend Capabilities report whether a semantic operation or resource class is supported. They do
not independently change wire framing.
Protocol 1.0 assigns these semantic capability bits:
| Bit | Name | Meaning |
|---|---|---|
| 0 | HOST_VISIBLE_MEMORY | MemoryDomain::Host allocation is supported |
| 1 | DEVICE_LOCAL_MEMORY | MemoryDomain::Device allocation is supported |
| 2 | EVENT_CANCELLATION | CANCEL_EVENT is implemented for pending events |
| 5 | SHARED_MEMORY | Provider-owned MemoryDomain::Shared allocation is supported |
Semantic bits 3 (EXTERNAL_MEMORY) and 4 (SECURE_CONTEXTS) are reserved until their transport,
ownership, synchronization, and isolation rules are specified. A protocol 1.0 device MUST NOT
advertise either bit.
The device MUST reject an allocation for an unsupported memory domain before invoking the backend. Advertising a memory-domain capability commits the backend to the corresponding allocation properties and direct-binding rules from section 4.4; capability reporting is not permission to substitute a staged implementation.
A baseline backend MUST advertise at least one assigned memory-domain capability. Every
resource-count, binding-count, and byte limit in DeviceInfo MUST be nonzero. Identity,
capabilities, and limits MUST remain stable for the lifetime of one backend instance so the
command engine can validate and cache them once before constructing object state.
If EVENT_CANCELLATION is absent, the command engine MUST reject cancellation before backend
invocation. If it is present, the backend MUST implement cancellation and MUST NOT return
UNSUPPORTED for a valid pending event.
If enabling a backend capability would require different descriptor direction, additional queues, new synchronization, or changed lifetime rules, the device MUST also negotiate an appropriate transport feature.
ContextFlags::SECURE and QueueFlags::IN_ORDER reserve semantic API positions only. Protocol 1.0
defines neither behavior and accepts only empty context and execution-queue flags. The command engine
MUST reject nonempty values before backend invocation; a direct caller of the backend trait
receives UNSUPPORTED without a retained resource. Enabling either reservation in a later version
requires explicit ownership, ordering, synchronization, and negotiation rules.
5.4 Request and object flags
All request-header flags are zero in protocol 1.0. The former draft NO_WAIT position, bit zero, is
reserved and MUST be zero.
Unknown request, context, execution-queue, submission, buffer-usage, or binding-access flag bits MUST be rejected before backend invocation. Reserved fields MUST be zero.
6. Versioning and compatibility
6.1 Candidate version
This specification assigns version 1.0 to the candidate exercised by the conformance artifacts.
Candidate implementations MUST expose major 1 and minor 0 in device-specific configuration
and MUST NOT claim candidate protocol 1.0 conformance unless they satisfy the normative wire,
queue, lifecycle, and compatibility requirements.
Protocol 1.0 compatibility is frozen by the final audit recorded in
conformance/v1.0/freeze-audit.md. A future erratum,
extension, or incompatible change MUST follow the coordinated procedure in
wire-abi.md and the classification rules in release-policy.md.
6.2 Stable major versions
For protocol major one and later:
- a driver MUST reject a device with an unsupported major version;
- a device minor version is backward compatible with all earlier minor versions of the same major;
- a driver MUST NOT use behavior newer than the device minor version it observed; and
- feature negotiation remains required even when both endpoints know a feature’s numeric bit.
A major version changes when compatibility cannot be preserved through a new opcode, a new negotiated feature, or a previously reserved value.
6.3 Extending existing frames
The config space does not communicate the driver’s supported minor version back to the device. Therefore a minor version alone MUST NOT append fields to an existing response that an older driver would receive.
Within a stable major version, an existing request or response payload length is immutable unless a negotiated feature explicitly selects a different layout. Extensions SHOULD use a new opcode or feature-gated payload.
Without such a feature, a receiver MUST require the exact payload length for the opcode and MUST reject trailing bytes. It MUST NOT silently reinterpret or ignore an unknown tail.
6.4 Unknown numeric values
- An unknown opcode produces
UNSUPPORTEDand no semantic state change. - An unknown request or object flag bit produces
UNSUPPORTEDand no backend call. - An unknown object ID or zero object ID produces the specified invalid/stale-object failure.
- An unknown response status is an opaque failure. A driver MUST NOT treat it as success or construct an invalid Rust enum value.
- Unknown accelerator classes and provider-owned artifact formats remain representable as opaque
numeric values, but using them may produce
UNSUPPORTEDorINCOMPATIBLE.
7. Ownership and destruction
The device owns the mapping from opaque object IDs to backend handles. The mapping MUST reject:
- zero IDs;
- stale generations;
- wrong resource kinds;
- IDs from another context; and
- IDs invalidated by reset.
Object-ID encoding is implementation-private. Drivers MUST NOT infer slot, kind, generation, or address information from an ID.
Destructive backend calls consume handles and have an explicit release boundary:
- a rejected release returns the still-live handle and the device MUST restore it for retry;
- a successful release removes the object permanently; and
- an indeterminate release invalidates the guest ID and requires recovery. The device MUST NOT reuse or free the handle based on an assumption.
Drop may provide defensive cleanup inside an implementation but MUST NOT be used as
guest-visible protocol state.
A creation method that returns an ordinary error MUST NOT retain a newly created resource. A failed explicit write may have modified part of its requested buffer range; that complete range is then unspecified, the buffer remains live, and a later successful full-range write reestablishes its contents. A failed explicit read may have partially initialized its destination but does not modify the buffer; the command engine MUST NOT publish that destination as a successful payload.
8. Time and progress
Wire timeouts are relative nanosecond durations measured from backend admission. Zero means infinite. Absolute guest timestamps MUST NOT be compared with a host clock because their monotonic epochs are unrelated.
Polling an event MUST be nonblocking and bounded. The portable contract creates no background thread and selects no async executor.
Discovery, context, allocation, transfer, program, execution-queue, and release methods may perform synchronous provider work. Submission performs validation and admission only and MUST NOT wait for execution to become terminal. Cancellation MUST also be nonblocking and bounded. No portable method requires a provider handle to contain a lock or atomic value; cross-thread calls are available only when the concrete backend and handle types opt into the corresponding Rust auto traits.
A timeout before admission is rejected. A timeout or communication failure after admission that cannot prove rejection is indeterminate and MUST retain an event.
9. Errors
The portable status namespace distinguishes unsupported behavior, incompatibility, invalid arguments, out-of-bounds access, busy resources, host allocation failure, configured resource-limit exhaustion, deadline expiration, device loss, permission failure, stale objects, and internal errors.
A device MUST map errors deterministically and MUST NOT expose uninitialized response bytes.
Provider-specific External domains and codes are not part of the baseline wire ABI; absent a
future negotiated diagnostic extension, they map to INTERNAL_ERROR.
An error response MUST NOT imply that a backend operation was rejected when acceptance was indeterminate.
10. Portability requirements
Portable crates MUST NOT select an operating system, VMM, kernel, vendor API, or global runtime.
virtio-accel-proto,virtio-accel-transport, andvirtio-accel-corerequire neitherstdnoralloc.virtio-accel-deviceand the future reference guest/queue layers may requireallocbut notstd.- reference host tooling and mock backends may require
std.
Platform adapters depend inward on the portable layers. A portable layer MUST NOT conditionally expose different semantics by host OS.
11. Release governance and audit trail
This document, wire-abi.md, and virtqueue.md define the frozen portable driver/device protocol 1.0 baseline. Post-freeze change classification, Cargo semver, feature, MSRV, target, unsafe-code, dependency, license, and publish-metadata policy are documented in release-policy.md.
The versioned audit result is conformance/v1.0/freeze-audit.md and the release note is docs/releases/v1.0.md. Future releases MUST preserve that audit trail by linking the versioned checklist, release note, and conformance directory for the protocol version they claim.
The portable security assumptions, finite attacker-controlled dimensions, resource-policy owners, and explicit exclusions are documented in threat-model.md.
No implementation MAY advertise a reserved optional feature merely because a Rust constant records its numeric position.
Appendix A: Rust surface mapping
This appendix is non-normative. It ensures every current public concept has an explicit place in the model or is identified as an implementation helper.
| Rust concept | Protocol term or classification |
|---|---|
BackendError | Backend failure taxonomy translated by the command engine into portable status |
AcceleratorClass | Extensible device class in device identity |
Capabilities | Semantic backend capabilities; not Virtio feature bits |
DeviceIdentity | Device instance identity |
DeviceLimits | Context, buffer, program, execution-queue, event, binding, and byte limits enforced before backend invocation |
DeviceInfo | Device identity, semantic capabilities, and limits |
DeviceInfoError | Construction-time rejection of reserved capabilities, absent baseline memory domains, or zero limits |
ContextFlags, ContextDesc | Context creation intent; protocol 1.0 accepts only empty context flags |
MemoryDomain | Strict provider-owned buffer placement requirement |
BufferUsage, BufferDesc, BufferRange | Buffer allocation intent and checked byte range |
BufferProperties, BufferInfo, AllocatedBuffer | Verified backing properties kept out of the backend hot path |
AccessMode | Binding read/write intent |
ByteSource, ByteSink | Bounded contiguous-or-segmented bulk byte ports |
ArtifactFormat, TargetIdentity, ArtifactRef | Opaque program artifact description over a byte source |
QueueFlags, QueueDesc | Accelerator execution-queue creation intent, not a Virtio queue |
Timeout | Relative admission timeout; zero on wire means infinite |
BindingRef | Borrowed program binding view. Hosts must reject AccessMode incompatible with BufferUsage before backend admission |
EventState | Event completion state |
SubmitFailure | Rejected versus indeterminate submission acceptance boundary |
ReleaseFailure | Rejected versus indeterminate resource-release boundary |
Accelerator | Accelerator backend contract |
DeviceHealth | Running, known-state reset required, or backend-discard-required processor state |
ResourcePolicy | Mandatory host-private aggregate limits for provider-retained buffer and program storage |
RetainedBytes | Exact live and quarantined provider-retained bulk-storage charges |
ResourceCounts, ResetDisposition, ResetReport, ResetError | Reset object and retained-byte accounting, reuse decision, and namespace validation |
Le16, Le32, Le64 | Wire implementation aliases; not semantic API |
PROTOCOL_MAJOR, PROTOCOL_MINOR | Candidate protocol version 1.0 |
COMMAND_QUEUE | Baseline command virtqueue index |
BASELINE_COMMAND_QUEUES, HARD_MAX_*, MIN_MAX_* | Candidate queue, frame, binding, and configuration bounds |
KNOWN_*_BITS, RESERVED_REQUEST_FLAG_NO_WAIT | Assigned and reserved-zero flag namespaces |
FeatureBits | Device-specific Virtio transport features |
BASELINE_FEATURES | Mandatory feature set, currently empty |
RESERVED_FEATURES | Reserved namespace that must not be advertised |
RequestFlags | Per-request wire flags; empty in protocol 1.0 |
KnownOpcode, UnknownOpcode | Validated command namespace and opaque unknown opcode |
StatusCode | Extensible response status namespace |
WireConfig, ConfigError | Device-specific configuration and executable validity rules |
RequestHeader, ResponseHeader | Request and response frame headers |
WireDeviceInfo | Device-information response payload |
CreateContextRequest | Context-create request payload |
ObjectPayload | One opaque object ID in a request or response |
AllocateBufferRequest | Buffer-allocation request payload |
TransferBufferRequest | Bounded buffer-transfer request prefix |
LoadProgramRequest | Program-load request prefix |
CreateQueueRequest | Accelerator execution-queue-create payload |
SubmitRequest, WireBinding | Submission prefix and binding array entry |
SubmitResponse | Event ownership payload for accepted or indeterminate submission |
WireEventState, KnownEventState, UnknownEventState | Event-poll response payload and extensible raw state handling |
DecodeError, read_exact, checked_array_bytes | Protocol decoding helpers; implementation-only |
QueueSize, QueueState, QueueEpoch, QueueControl | Validated Virtio queue configuration and reset lifecycle |
ChainId, ChainRegion, ChainLayout, ChainIo, DeviceChain | Reset-scoped descriptor-chain identity, flattened metadata, and mapped byte-port presentation |
DriverQueue, PublishedChain, UsedChain, ReclaimedChain, PublishError | Ownership-preserving driver publication, completion, reset reclamation, and retryable backpressure |
DeviceQueue, UsedLength, NotificationHint, NotificationRecheck | Consuming device completion, exact used length, and lost-wakeup-safe notification decisions |
SegmentedSource, SegmentedSink | Device test/reference byte ports over segmented storage |
FrameDecoder, DecodedRequest, DecodedRequestBody, FramePreflight | Complete untrusted request validation before semantic dispatch |
ResponseWriter, ResponsePayload | Bounded response framing and exact direct payload destination |
ObjectNamespace, ObjectKind, ObjectId | Device-private namespaced typed object identity |
ObjectTable, ObjectTableError | Device implementation state; encoding is not wire ABI |
DeviceState, typed record types, ChildCounts | Context-scoped ownership graph, quotas, and in-flight reference accounting |
ReleaseState, CreateError, RestoreError | Device-state admission and release rollback helpers |
status_from_backend_error, status_from_object_table_error, status_from_device_state_error | Device implementation mapping helpers |
MockAccelerator, FaultAccelerator, and their handle types | Reference/test implementation, not normative protocol |
virtio-accel-conformance cases, target fixtures, hooks, and reports | Reusable provider-test implementation, not normative protocol |