1#![no_std]
8#![forbid(unsafe_code)]
9
10use bitflags::bitflags;
11use zerocopy::byteorder::{LE, U16, U32, U64};
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
13
14pub type Le16 = U16<LE>;
15pub type Le32 = U32<LE>;
16pub type Le64 = U64<LE>;
17
18pub const PROTOCOL_MAJOR: u16 = 1;
20pub const PROTOCOL_MINOR: u16 = 0;
22pub const COMMAND_QUEUE: u16 = 0;
27pub const BASELINE_COMMAND_QUEUES: u16 = 1;
29pub const HARD_MAX_CHAIN_DESCRIPTORS: u16 = 256;
31pub const HARD_MAX_REQUEST_BYTES: u32 = 16 * 1024 * 1024;
33pub const HARD_MAX_RESPONSE_BYTES: u32 = 16 * 1024 * 1024;
35pub const HARD_MAX_BINDINGS: u32 = 4_096;
37pub const MIN_MAX_REQUEST_BYTES: u32 = 97;
39pub const MIN_MAX_RESPONSE_BYTES: u32 = 92;
41
42pub const KNOWN_BUFFER_USAGE_BITS: u32 = 0x1f;
44pub const KNOWN_REQUEST_FLAG_BITS: u16 = 0;
46pub const KNOWN_CONTEXT_FLAG_BITS: u32 = 0;
48pub const KNOWN_PROGRAM_FLAG_BITS: u32 = 0;
50pub const KNOWN_QUEUE_FLAG_BITS: u32 = 0;
52pub const KNOWN_SUBMIT_FLAG_BITS: u32 = 0;
54pub const RESERVED_REQUEST_FLAG_NO_WAIT: u16 = 1 << 0;
56
57bitflags! {
58 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
60 pub struct FeatureBits: u64 {
61 const MULTI_QUEUE = 1 << 0;
62 const EVENT_QUEUE = 1 << 1;
63 const EXTERNAL_MEMORY = 1 << 2;
64 const TIMELINE_FENCES = 1 << 3;
65 const SECURE_CONTEXTS = 1 << 4;
66 }
67}
68
69pub const BASELINE_FEATURES: FeatureBits = FeatureBits::empty();
75
76pub const RESERVED_FEATURES: FeatureBits = FeatureBits::from_bits_retain(
81 FeatureBits::MULTI_QUEUE.bits()
82 | FeatureBits::EVENT_QUEUE.bits()
83 | FeatureBits::EXTERNAL_MEMORY.bits()
84 | FeatureBits::TIMELINE_FENCES.bits()
85 | FeatureBits::SECURE_CONTEXTS.bits(),
86);
87
88bitflags! {
89 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
93 pub struct RequestFlags: u16 {}
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97#[repr(u16)]
98pub enum KnownOpcode {
99 GetDeviceInfo = 0x0001,
100 CreateContext = 0x0100,
101 DestroyContext = 0x0101,
102 AllocateBuffer = 0x0200,
103 FreeBuffer = 0x0201,
104 WriteBuffer = 0x0202,
105 ReadBuffer = 0x0203,
106 LoadProgram = 0x0300,
107 UnloadProgram = 0x0301,
108 CreateQueue = 0x0400,
109 DestroyQueue = 0x0401,
110 Submit = 0x0500,
111 PollEvent = 0x0501,
112 CancelEvent = 0x0502,
113 DestroyEvent = 0x0503,
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub struct UnknownOpcode(pub u16);
118
119impl TryFrom<u16> for KnownOpcode {
120 type Error = UnknownOpcode;
121
122 fn try_from(value: u16) -> Result<Self, Self::Error> {
123 match value {
124 0x0001 => Ok(Self::GetDeviceInfo),
125 0x0100 => Ok(Self::CreateContext),
126 0x0101 => Ok(Self::DestroyContext),
127 0x0200 => Ok(Self::AllocateBuffer),
128 0x0201 => Ok(Self::FreeBuffer),
129 0x0202 => Ok(Self::WriteBuffer),
130 0x0203 => Ok(Self::ReadBuffer),
131 0x0300 => Ok(Self::LoadProgram),
132 0x0301 => Ok(Self::UnloadProgram),
133 0x0400 => Ok(Self::CreateQueue),
134 0x0401 => Ok(Self::DestroyQueue),
135 0x0500 => Ok(Self::Submit),
136 0x0501 => Ok(Self::PollEvent),
137 0x0502 => Ok(Self::CancelEvent),
138 0x0503 => Ok(Self::DestroyEvent),
139 _ => Err(UnknownOpcode(value)),
140 }
141 }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145#[repr(transparent)]
146pub struct StatusCode(pub u16);
147
148impl StatusCode {
149 pub const OK: Self = Self(0);
150 pub const UNSUPPORTED: Self = Self(1);
151 pub const INCOMPATIBLE: Self = Self(2);
152 pub const INVALID_ARGUMENT: Self = Self(3);
153 pub const OUT_OF_BOUNDS: Self = Self(4);
154 pub const BUSY: Self = Self(5);
155 pub const OUT_OF_MEMORY: Self = Self(6);
156 pub const RESOURCE_LIMIT: Self = Self(7);
157 pub const DEADLINE_EXPIRED: Self = Self(8);
158 pub const DEVICE_LOST: Self = Self(9);
159 pub const PERMISSION_DENIED: Self = Self(10);
160 pub const STALE_OBJECT: Self = Self(11);
161 pub const INTERNAL_ERROR: Self = Self(0xffff);
162
163 pub const fn is_known(self) -> bool {
165 matches!(self.0, 0..=11 | 0xffff)
166 }
167
168 pub const fn is_success(self) -> bool {
169 self.0 == Self::OK.0
170 }
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174#[repr(u16)]
175pub enum KnownEventState {
176 Pending = 0,
177 Complete = 1,
178 Failed = 2,
179 Cancelled = 3,
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub struct UnknownEventState(pub u16);
184
185impl TryFrom<u16> for KnownEventState {
186 type Error = UnknownEventState;
187
188 fn try_from(value: u16) -> Result<Self, Self::Error> {
189 match value {
190 0 => Ok(Self::Pending),
191 1 => Ok(Self::Complete),
192 2 => Ok(Self::Failed),
193 3 => Ok(Self::Cancelled),
194 _ => Err(UnknownEventState(value)),
195 }
196 }
197}
198
199#[derive(
200 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
201)]
202#[repr(C)]
203pub struct WireConfig {
204 pub protocol_major: Le16,
205 pub protocol_minor: Le16,
206 pub command_queue_count: Le16,
207 pub max_chain_descriptors: Le16,
208 pub max_request_bytes: Le32,
209 pub max_response_bytes: Le32,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub enum ConfigError {
214 Version,
215 CommandQueueCount,
216 ChainDescriptorLimit,
217 RequestByteLimit,
218 ResponseByteLimit,
219}
220
221impl WireConfig {
222 pub fn validate(&self) -> Result<(), ConfigError> {
227 if self.protocol_major.get() != PROTOCOL_MAJOR {
228 return Err(ConfigError::Version);
229 }
230 if self.command_queue_count.get() != BASELINE_COMMAND_QUEUES {
231 return Err(ConfigError::CommandQueueCount);
232 }
233 if !(2..=HARD_MAX_CHAIN_DESCRIPTORS).contains(&self.max_chain_descriptors.get()) {
234 return Err(ConfigError::ChainDescriptorLimit);
235 }
236 if !(MIN_MAX_REQUEST_BYTES..=HARD_MAX_REQUEST_BYTES).contains(&self.max_request_bytes.get())
237 {
238 return Err(ConfigError::RequestByteLimit);
239 }
240 if !(MIN_MAX_RESPONSE_BYTES..=HARD_MAX_RESPONSE_BYTES)
241 .contains(&self.max_response_bytes.get())
242 {
243 return Err(ConfigError::ResponseByteLimit);
244 }
245 Ok(())
246 }
247}
248
249#[derive(
250 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
251)]
252#[repr(C)]
253pub struct RequestHeader {
254 pub opcode: Le16,
255 pub flags: Le16,
256 pub payload_bytes: Le32,
257 pub request_id: Le64,
258}
259
260impl RequestHeader {
261 pub fn new(
262 opcode: KnownOpcode,
263 flags: RequestFlags,
264 payload_bytes: u32,
265 request_id: u64,
266 ) -> Self {
267 Self {
268 opcode: Le16::new(opcode as u16),
269 flags: Le16::new(flags.bits()),
270 payload_bytes: Le32::new(payload_bytes),
271 request_id: Le64::new(request_id),
272 }
273 }
274
275 pub fn known_opcode(&self) -> Result<KnownOpcode, UnknownOpcode> {
276 self.opcode.get().try_into()
277 }
278}
279
280#[derive(
281 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
282)]
283#[repr(C)]
284pub struct ResponseHeader {
285 pub status: Le16,
286 pub flags: Le16,
287 pub payload_bytes: Le32,
288 pub request_id: Le64,
289}
290
291impl ResponseHeader {
292 pub fn new(status: StatusCode, payload_bytes: u32, request_id: u64) -> Self {
293 Self {
294 status: Le16::new(status.0),
295 flags: Le16::new(0),
296 payload_bytes: Le32::new(payload_bytes),
297 request_id: Le64::new(request_id),
298 }
299 }
300}
301
302#[derive(
303 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
304)]
305#[repr(C)]
306pub struct WireDeviceInfo {
307 pub uuid: [u8; 16],
308 pub class: Le16,
309 pub reserved: Le16,
310 pub vendor_id: Le32,
311 pub device_id: Le32,
312 pub capabilities: Le64,
313 pub max_contexts: Le32,
314 pub max_buffers_per_context: Le32,
315 pub max_programs_per_context: Le32,
316 pub max_queues_per_context: Le32,
317 pub max_events_per_context: Le32,
318 pub max_bindings_per_submission: Le32,
319 pub max_buffer_bytes: Le64,
320 pub max_artifact_bytes: Le64,
321}
322
323#[derive(
324 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
325)]
326#[repr(C)]
327pub struct CreateContextRequest {
328 pub flags: Le32,
329 pub reserved: Le32,
330}
331
332#[derive(
333 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
334)]
335#[repr(C)]
336pub struct ObjectPayload {
337 pub object_id: Le64,
338}
339
340#[derive(
341 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
342)]
343#[repr(C)]
344pub struct AllocateBufferRequest {
345 pub context_id: Le64,
346 pub bytes: Le64,
347 pub alignment: Le64,
348 pub memory_domain: u8,
349 pub reserved0: [u8; 7],
350 pub usage: Le32,
351 pub reserved1: Le32,
352}
353
354#[derive(
355 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
356)]
357#[repr(C)]
358pub struct TransferBufferRequest {
359 pub buffer_id: Le64,
360 pub offset: Le64,
361 pub bytes: Le64,
362}
363
364#[derive(
365 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
366)]
367#[repr(C)]
368pub struct LoadProgramRequest {
369 pub context_id: Le64,
370 pub format: Le32,
371 pub flags: Le32,
372 pub target: [Le32; 12],
373 pub payload_bytes: Le64,
374 pub resident_bytes: Le64,
375}
376
377#[derive(
378 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
379)]
380#[repr(C)]
381pub struct CreateQueueRequest {
382 pub context_id: Le64,
383 pub flags: Le32,
384 pub reserved: Le32,
385}
386
387#[derive(
388 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
389)]
390#[repr(C)]
391pub struct SubmitRequest {
392 pub queue_id: Le64,
393 pub program_id: Le64,
394 pub binding_count: Le32,
395 pub flags: Le32,
396 pub timeout_ns: Le64,
398}
399
400#[derive(
401 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
402)]
403#[repr(C)]
404pub struct WireBinding {
405 pub buffer_id: Le64,
406 pub offset: Le64,
407 pub bytes: Le64,
408 pub slot: Le32,
409 pub access: u8,
410 pub reserved: [u8; 3],
411}
412
413#[derive(
415 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
416)]
417#[repr(C)]
418pub struct SubmitResponse {
419 pub event_id: Le64,
420}
421
422#[derive(
423 Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
424)]
425#[repr(C)]
426pub struct WireEventState {
427 pub state: Le16,
428 pub error: Le16,
429 pub reserved: Le32,
430}
431
432impl WireEventState {
433 pub fn known_state(&self) -> Result<KnownEventState, UnknownEventState> {
434 self.state.get().try_into()
435 }
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq)]
439pub enum DecodeError {
440 Size,
441 CountOverflow,
442}
443
444pub fn read_exact<T: FromBytes>(bytes: &[u8]) -> Result<T, DecodeError> {
445 T::read_from_bytes(bytes).map_err(|_| DecodeError::Size)
446}
447
448pub fn checked_array_bytes<T>(count: u32) -> Result<usize, DecodeError> {
449 core::mem::size_of::<T>()
450 .checked_mul(count as usize)
451 .ok_or(DecodeError::CountOverflow)
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457 use core::mem::size_of;
458 use zerocopy::IntoBytes;
459
460 #[test]
461 fn headers_are_fixed_little_endian_frames() {
462 let header = RequestHeader::new(KnownOpcode::Submit, RequestFlags::empty(), 32, 7);
463 assert_eq!(size_of::<RequestHeader>(), 16);
464 assert_eq!(header.as_bytes()[..2], 0x0500_u16.to_le_bytes());
465 let decoded = read_exact::<RequestHeader>(header.as_bytes()).unwrap();
466 assert_eq!(decoded.known_opcode(), Ok(KnownOpcode::Submit));
467 assert_eq!(decoded.request_id.get(), 7);
468 }
469
470 #[test]
471 fn reserved_features_are_not_baseline_requirements() {
472 assert!(BASELINE_FEATURES.is_empty());
473 assert!(!RESERVED_FEATURES.is_empty());
474 assert!(!BASELINE_FEATURES.intersects(RESERVED_FEATURES));
475 }
476}