Skip to main content

virtio_accel_guest/
types.rs

1use bitflags::bitflags;
2use core::num::{NonZeroU32, NonZeroU64};
3
4use virtio_accel_proto::{HARD_MAX_BINDINGS, StatusCode, WireDeviceInfo};
5use virtio_accel_transport::QueueEpoch;
6
7const CAPABILITY_HOST_VISIBLE_MEMORY: u64 = 1 << 0;
8const CAPABILITY_DEVICE_LOCAL_MEMORY: u64 = 1 << 1;
9const CAPABILITY_EVENT_CANCELLATION: u64 = 1 << 2;
10const CAPABILITY_RESERVED_EXTERNAL_MEMORY: u64 = 1 << 3;
11const CAPABILITY_RESERVED_SECURE_CONTEXTS: u64 = 1 << 4;
12const CAPABILITY_SHARED_MEMORY: u64 = 1 << 5;
13const RESERVED_CAPABILITIES: u64 =
14    CAPABILITY_RESERVED_EXTERNAL_MEMORY | CAPABILITY_RESERVED_SECURE_CONTEXTS;
15const MEMORY_DOMAIN_CAPABILITIES: u64 =
16    CAPABILITY_HOST_VISIBLE_MEMORY | CAPABILITY_DEVICE_LOCAL_MEMORY | CAPABILITY_SHARED_MEMORY;
17
18bitflags! {
19    /// Protocol 1.0 buffer usage bits.
20    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
21    pub struct BufferUsage: u32 {
22        const TRANSFER_SOURCE = 1 << 0;
23        const TRANSFER_DESTINATION = 1 << 1;
24        const PROGRAM_INPUT = 1 << 2;
25        const PROGRAM_OUTPUT = 1 << 3;
26        const MUTABLE_STATE = 1 << 4;
27    }
28}
29
30/// Requested provider-owned memory placement.
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32#[repr(u8)]
33pub enum MemoryDomain {
34    /// Host-preferred memory.
35    Host = 1,
36    /// Accelerator-local memory.
37    Device = 2,
38    /// Provider-owned host-visible, directly bindable memory.
39    Shared = 3,
40}
41
42/// Program binding access mode.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44#[repr(u8)]
45pub enum AccessMode {
46    /// Program reads the buffer.
47    Read = 1,
48    /// Program writes the buffer.
49    Write = 2,
50    /// Program reads and writes the buffer.
51    ReadWrite = 3,
52}
53
54/// Locally invalid typed request value.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum ValueError {
57    /// A required scalar is zero.
58    Zero,
59    /// A byte range overflows.
60    Overflow,
61    /// Alignment is not a nonzero power of two.
62    Alignment,
63    /// Buffer usage is empty or contains an unknown bit.
64    BufferUsage,
65}
66
67/// Guest-side ownership meaning of a well-formed non-success response.
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum FailureDisposition {
70    /// The device rejected the operation before consuming semantic ownership.
71    Retryable,
72    /// A referenced object is stale and must not be retried as live.
73    Invalidated,
74    /// Device loss leaves semantic ownership uncertain and requires reset.
75    Indeterminate,
76    /// An unknown status has no protocol 1.0 ownership meaning.
77    Unknown,
78}
79
80/// Requested buffer properties retained by the typed guest handle.
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub struct BufferDesc {
83    /// Logical buffer bytes.
84    pub(crate) bytes: u64,
85    /// Required power-of-two alignment.
86    pub(crate) alignment: u64,
87    /// Requested memory placement.
88    pub(crate) memory_domain: MemoryDomain,
89    /// Declared operation usage.
90    pub(crate) usage: BufferUsage,
91}
92
93impl BufferDesc {
94    /// Construct a locally valid buffer request.
95    pub fn new(
96        bytes: u64,
97        alignment: u64,
98        memory_domain: MemoryDomain,
99        usage: BufferUsage,
100    ) -> Result<Self, ValueError> {
101        if bytes == 0 {
102            return Err(ValueError::Zero);
103        }
104        if !alignment.is_power_of_two() {
105            return Err(ValueError::Alignment);
106        }
107        if usage.is_empty() || BufferUsage::from_bits(usage.bits()).is_none() {
108            return Err(ValueError::BufferUsage);
109        }
110        Ok(Self {
111            bytes,
112            alignment,
113            memory_domain,
114            usage,
115        })
116    }
117
118    /// Logical buffer bytes.
119    pub const fn bytes(self) -> u64 {
120        self.bytes
121    }
122
123    /// Required power-of-two alignment.
124    pub const fn alignment(self) -> u64 {
125        self.alignment
126    }
127
128    /// Requested memory placement.
129    pub const fn memory_domain(self) -> MemoryDomain {
130        self.memory_domain
131    }
132
133    /// Declared operation usage.
134    pub const fn usage(self) -> BufferUsage {
135        self.usage
136    }
137}
138
139/// Nonempty checked buffer byte range.
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub struct BufferRange {
142    /// Starting byte offset.
143    pub(crate) offset: u64,
144    /// Number of bytes.
145    pub(crate) bytes: u64,
146}
147
148impl BufferRange {
149    /// Construct a nonempty, non-overflowing range.
150    pub fn new(offset: u64, bytes: u64) -> Result<Self, ValueError> {
151        if bytes == 0 {
152            return Err(ValueError::Zero);
153        }
154        offset.checked_add(bytes).ok_or(ValueError::Overflow)?;
155        Ok(Self { offset, bytes })
156    }
157
158    /// Starting byte offset.
159    pub const fn offset(self) -> u64 {
160        self.offset
161    }
162
163    /// Number of bytes.
164    pub const fn bytes(self) -> u64 {
165        self.bytes
166    }
167
168    pub(crate) fn fits(self, limit: u64) -> bool {
169        self.offset
170            .checked_add(self.bytes)
171            .is_some_and(|end| end <= limit)
172    }
173}
174
175/// Opaque program envelope retained until program creation completes.
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct ProgramDesc {
178    /// Provider-owned nonzero artifact format.
179    pub format: NonZeroU32,
180    /// Opaque provider-owned target words.
181    pub target: [u32; 12],
182    /// Declared nonzero resident-memory charge.
183    pub resident_bytes: NonZeroU64,
184}
185
186impl ProgramDesc {
187    /// Construct a program descriptor.
188    pub const fn new(format: NonZeroU32, target: [u32; 12], resident_bytes: NonZeroU64) -> Self {
189        Self {
190            format,
191            target,
192            resident_bytes,
193        }
194    }
195}
196
197#[derive(Debug, PartialEq, Eq)]
198pub(crate) struct Handle {
199    id: NonZeroU64,
200    epoch: QueueEpoch,
201}
202
203impl Handle {
204    pub(crate) const fn new(id: NonZeroU64, epoch: QueueEpoch) -> Self {
205        Self { id, epoch }
206    }
207
208    pub(crate) const fn id(&self) -> NonZeroU64 {
209        self.id
210    }
211
212    pub(crate) const fn epoch(&self) -> QueueEpoch {
213        self.epoch
214    }
215}
216
217macro_rules! simple_handle {
218    ($name:ident, $doc:literal) => {
219        #[doc = $doc]
220        #[derive(Debug, PartialEq, Eq)]
221        pub struct $name {
222            pub(crate) handle: Handle,
223            pub(crate) context: Option<NonZeroU64>,
224        }
225
226        impl $name {
227            /// Raw opaque wire object ID.
228            pub const fn raw(&self) -> u64 {
229                self.handle.id().get()
230            }
231
232            /// Reset epoch in which this handle was created.
233            pub const fn epoch(&self) -> QueueEpoch {
234                self.handle.epoch()
235            }
236        }
237    };
238}
239
240simple_handle!(Context, "Guest-owned context handle.");
241simple_handle!(Program, "Guest-owned resident program handle.");
242simple_handle!(
243    ExecutionQueue,
244    "Guest-owned accelerator execution-queue handle."
245);
246simple_handle!(Event, "Guest-owned submission event handle.");
247
248/// Guest-owned buffer handle and retained bounds needed for local validation.
249#[derive(Debug, PartialEq, Eq)]
250pub struct Buffer {
251    pub(crate) handle: Handle,
252    pub(crate) context: NonZeroU64,
253    pub(crate) desc: BufferDesc,
254}
255
256impl Buffer {
257    /// Raw opaque wire object ID.
258    pub const fn raw(&self) -> u64 {
259        self.handle.id().get()
260    }
261
262    /// Reset epoch in which this handle was created.
263    pub const fn epoch(&self) -> QueueEpoch {
264        self.handle.epoch()
265    }
266
267    /// Buffer properties accepted by the device.
268    pub const fn desc(&self) -> BufferDesc {
269        self.desc
270    }
271}
272
273/// One borrowed binding encoded directly into a submission chain.
274#[derive(Clone, Copy, Debug)]
275pub struct Binding<'a> {
276    /// Buffer retained by the event on successful or indeterminate admission.
277    pub buffer: &'a Buffer,
278    /// Bound nonempty range.
279    pub range: BufferRange,
280    /// Program-defined slot number.
281    pub slot: u32,
282    /// Program access mode.
283    pub access: AccessMode,
284}
285
286/// Validated device discovery result.
287#[derive(Clone, Copy, Debug, PartialEq, Eq)]
288pub struct DeviceInfo {
289    /// Stable device UUID.
290    pub uuid: [u8; 16],
291    /// Extensible raw accelerator class.
292    pub class: u16,
293    /// Provider vendor identifier.
294    pub vendor_id: u32,
295    /// Provider device identifier.
296    pub device_id: u32,
297    /// Raw semantic capabilities, including unknown diagnostic bits.
298    pub capabilities: u64,
299    /// Device-wide live context limit.
300    pub max_contexts: u32,
301    /// Per-context live buffer limit.
302    pub max_buffers_per_context: u32,
303    /// Per-context live program limit.
304    pub max_programs_per_context: u32,
305    /// Per-context live execution-queue limit.
306    pub max_queues_per_context: u32,
307    /// Per-context live event limit.
308    pub max_events_per_context: u32,
309    /// Maximum bindings accepted by one submission.
310    pub max_bindings_per_submission: u32,
311    /// Maximum logical buffer allocation.
312    pub max_buffer_bytes: u64,
313    /// Maximum program artifact tail.
314    pub max_artifact_bytes: u64,
315}
316
317impl DeviceInfo {
318    pub(crate) fn from_wire(
319        wire: WireDeviceInfo,
320        max_request_bytes: u32,
321    ) -> Result<Self, DeviceInfoError> {
322        if wire.reserved.get() != 0 {
323            return Err(DeviceInfoError::Reserved);
324        }
325        let capabilities = wire.capabilities.get();
326        if capabilities & RESERVED_CAPABILITIES != 0 {
327            return Err(DeviceInfoError::ReservedCapabilities);
328        }
329        if capabilities & MEMORY_DOMAIN_CAPABILITIES == 0 {
330            return Err(DeviceInfoError::MissingMemoryDomain);
331        }
332        let limits = [
333            wire.max_contexts.get(),
334            wire.max_buffers_per_context.get(),
335            wire.max_programs_per_context.get(),
336            wire.max_queues_per_context.get(),
337            wire.max_events_per_context.get(),
338        ];
339        if limits.contains(&0) {
340            return Err(DeviceInfoError::ZeroLimit);
341        }
342        let max_bindings = wire.max_bindings_per_submission.get();
343        if !(1..=HARD_MAX_BINDINGS).contains(&max_bindings) {
344            return Err(DeviceInfoError::BindingLimit);
345        }
346        if wire.max_buffer_bytes.get() == 0 || wire.max_artifact_bytes.get() == 0 {
347            return Err(DeviceInfoError::ZeroLimit);
348        }
349        let artifact_frame = 16_u64
350            .checked_add(80)
351            .and_then(|bytes| bytes.checked_add(wire.max_artifact_bytes.get()))
352            .ok_or(DeviceInfoError::ArtifactLimit)?;
353        if artifact_frame > u64::from(max_request_bytes) {
354            return Err(DeviceInfoError::ArtifactLimit);
355        }
356        Ok(Self {
357            uuid: wire.uuid,
358            class: wire.class.get(),
359            vendor_id: wire.vendor_id.get(),
360            device_id: wire.device_id.get(),
361            capabilities,
362            max_contexts: wire.max_contexts.get(),
363            max_buffers_per_context: wire.max_buffers_per_context.get(),
364            max_programs_per_context: wire.max_programs_per_context.get(),
365            max_queues_per_context: wire.max_queues_per_context.get(),
366            max_events_per_context: wire.max_events_per_context.get(),
367            max_bindings_per_submission: max_bindings,
368            max_buffer_bytes: wire.max_buffer_bytes.get(),
369            max_artifact_bytes: wire.max_artifact_bytes.get(),
370        })
371    }
372
373    /// Whether event cancellation is semantically available.
374    pub const fn supports_event_cancellation(self) -> bool {
375        self.capabilities & CAPABILITY_EVENT_CANCELLATION != 0
376    }
377
378    pub(crate) const fn supports_domain(self, domain: MemoryDomain) -> bool {
379        let bit = match domain {
380            MemoryDomain::Host => CAPABILITY_HOST_VISIBLE_MEMORY,
381            MemoryDomain::Device => CAPABILITY_DEVICE_LOCAL_MEMORY,
382            MemoryDomain::Shared => CAPABILITY_SHARED_MEMORY,
383        };
384        self.capabilities & bit != 0
385    }
386}
387
388/// Invalid device-information payload.
389#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390pub enum DeviceInfoError {
391    /// Reserved structure bytes are nonzero.
392    Reserved,
393    /// A reserved semantic capability was advertised.
394    ReservedCapabilities,
395    /// No protocol 1.0 provider-owned memory domain is usable.
396    MissingMemoryDomain,
397    /// A mandatory advertised limit is zero.
398    ZeroLimit,
399    /// Binding limit is outside protocol bounds.
400    BindingLimit,
401    /// Artifact limit does not fit the configured request frame.
402    ArtifactLimit,
403}
404
405/// Validated accelerator execution state.
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub enum EventState {
408    /// Execution remains pending.
409    Pending,
410    /// Execution completed successfully.
411    Complete,
412    /// Execution failed with an opaque protocol status.
413    Failed(StatusCode),
414    /// Execution was cancelled.
415    Cancelled,
416}
417
418/// Successful or ownership-indeterminate submission admission.
419#[derive(Debug, PartialEq, Eq)]
420pub enum SubmissionOutcome {
421    /// Backend admission was accepted.
422    Accepted(Event),
423    /// Admission could not be determined; the event retains all operation resources.
424    Indeterminate {
425        /// Mapped non-success admission status.
426        status: StatusCode,
427        /// Event that owns the possibly accepted operation.
428        event: Event,
429    },
430}
431
432/// Metadata for a successful `READ_BUFFER` response retained in its returned chain.
433#[derive(Clone, Copy, Debug, PartialEq, Eq)]
434pub struct ReadBufferOutput {
435    /// Payload bytes beginning at writable offset 16.
436    pub bytes: u64,
437}