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 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32#[repr(u8)]
33pub enum MemoryDomain {
34 Host = 1,
36 Device = 2,
38 Shared = 3,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44#[repr(u8)]
45pub enum AccessMode {
46 Read = 1,
48 Write = 2,
50 ReadWrite = 3,
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum ValueError {
57 Zero,
59 Overflow,
61 Alignment,
63 BufferUsage,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum FailureDisposition {
70 Retryable,
72 Invalidated,
74 Indeterminate,
76 Unknown,
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub struct BufferDesc {
83 pub(crate) bytes: u64,
85 pub(crate) alignment: u64,
87 pub(crate) memory_domain: MemoryDomain,
89 pub(crate) usage: BufferUsage,
91}
92
93impl BufferDesc {
94 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 pub const fn bytes(self) -> u64 {
120 self.bytes
121 }
122
123 pub const fn alignment(self) -> u64 {
125 self.alignment
126 }
127
128 pub const fn memory_domain(self) -> MemoryDomain {
130 self.memory_domain
131 }
132
133 pub const fn usage(self) -> BufferUsage {
135 self.usage
136 }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub struct BufferRange {
142 pub(crate) offset: u64,
144 pub(crate) bytes: u64,
146}
147
148impl BufferRange {
149 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 pub const fn offset(self) -> u64 {
160 self.offset
161 }
162
163 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct ProgramDesc {
178 pub format: NonZeroU32,
180 pub target: [u32; 12],
182 pub resident_bytes: NonZeroU64,
184}
185
186impl ProgramDesc {
187 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 pub const fn raw(&self) -> u64 {
229 self.handle.id().get()
230 }
231
232 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#[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 pub const fn raw(&self) -> u64 {
259 self.handle.id().get()
260 }
261
262 pub const fn epoch(&self) -> QueueEpoch {
264 self.handle.epoch()
265 }
266
267 pub const fn desc(&self) -> BufferDesc {
269 self.desc
270 }
271}
272
273#[derive(Clone, Copy, Debug)]
275pub struct Binding<'a> {
276 pub buffer: &'a Buffer,
278 pub range: BufferRange,
280 pub slot: u32,
282 pub access: AccessMode,
284}
285
286#[derive(Clone, Copy, Debug, PartialEq, Eq)]
288pub struct DeviceInfo {
289 pub uuid: [u8; 16],
291 pub class: u16,
293 pub vendor_id: u32,
295 pub device_id: u32,
297 pub capabilities: u64,
299 pub max_contexts: u32,
301 pub max_buffers_per_context: u32,
303 pub max_programs_per_context: u32,
305 pub max_queues_per_context: u32,
307 pub max_events_per_context: u32,
309 pub max_bindings_per_submission: u32,
311 pub max_buffer_bytes: u64,
313 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390pub enum DeviceInfoError {
391 Reserved,
393 ReservedCapabilities,
395 MissingMemoryDomain,
397 ZeroLimit,
399 BindingLimit,
401 ArtifactLimit,
403}
404
405#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub enum EventState {
408 Pending,
410 Complete,
412 Failed(StatusCode),
414 Cancelled,
416}
417
418#[derive(Debug, PartialEq, Eq)]
420pub enum SubmissionOutcome {
421 Accepted(Event),
423 Indeterminate {
425 status: StatusCode,
427 event: Event,
429 },
430}
431
432#[derive(Clone, Copy, Debug, PartialEq, Eq)]
434pub struct ReadBufferOutput {
435 pub bytes: u64,
437}