1use crate::queue::{AQL_PACKET_BYTES, PACKET_TYPE_INVALID, PACKET_TYPE_KERNEL_DISPATCH};
8
9pub const PACKET_TYPE_BARRIER_AND: u16 = 3;
11pub const PACKET_TYPE_AGENT_DISPATCH: u16 = 4;
13pub const PACKET_TYPE_BARRIER_OR: u16 = 5;
15
16pub const DIAGNOSTIC_COMPLETE_NO_EXECUTION: &str = "diagnostic_complete_no_execution";
18pub const DIAGNOSTIC_REJECTED: &str = "diagnostic_rejected";
19pub const KERNEL_SUCCESS: &str = "softgpu_kernel_success";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PacketType {
24 Invalid,
25 KernelDispatch,
26 BarrierAnd,
27 AgentDispatch,
28 BarrierOr,
29 VendorOrUnknown(u16),
30}
31
32impl PacketType {
33 pub fn from_header_type(ty: u16) -> Self {
34 match ty & 0xff {
35 1 => Self::Invalid,
36 2 => Self::KernelDispatch,
37 3 => Self::BarrierAnd,
38 4 => Self::AgentDispatch,
39 5 => Self::BarrierOr,
40 other => Self::VendorOrUnknown(other),
41 }
42 }
43
44 pub fn as_u16(self) -> u16 {
45 match self {
46 Self::Invalid => PACKET_TYPE_INVALID,
47 Self::KernelDispatch => PACKET_TYPE_KERNEL_DISPATCH,
48 Self::BarrierAnd => PACKET_TYPE_BARRIER_AND,
49 Self::AgentDispatch => PACKET_TYPE_AGENT_DISPATCH,
50 Self::BarrierOr => PACKET_TYPE_BARRIER_OR,
51 Self::VendorOrUnknown(v) => v,
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum KernargClass {
59 Null,
60 SoftGpu {
62 alloc_id: Option<u64>,
63 addr: u64,
64 },
65 ForeignOpaque {
67 addr: u64,
68 },
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct DispatchDescriptor {
74 pub packet_index: u64,
75 pub packet_type: PacketType,
76 pub header: u16,
77 pub setup: u16,
78 pub dimensions: u16,
79 pub workgroup_size: [u16; 3],
80 pub grid_size: [u32; 3],
81 pub private_segment_size: u32,
82 pub group_segment_size: u32,
83 pub kernel_object: u64,
84 pub kernarg: KernargClass,
85 pub completion_signal: u64,
86 pub captured_bytes: [u8; AQL_PACKET_BYTES],
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum AqlParseError {
92 WrongLength { got: usize },
93 StillInvalid,
94 UnsupportedType { packet_type: u16 },
95 ZeroWorkgroup { dim: usize },
96 GridSmallerThanWorkgroup { dim: usize },
97 BadDimensions { dimensions: u16 },
98 DimConstraint { detail: String },
99}
100
101impl std::fmt::Display for AqlParseError {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 write!(f, "{self:?}")
104 }
105}
106
107fn read_u16(bytes: &[u8], off: usize) -> u16 {
108 u16::from_le_bytes([bytes[off], bytes[off + 1]])
109}
110
111fn read_u32(bytes: &[u8], off: usize) -> u32 {
112 u32::from_le_bytes([bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]])
113}
114
115fn read_u64(bytes: &[u8], off: usize) -> u64 {
116 u64::from_le_bytes([
117 bytes[off],
118 bytes[off + 1],
119 bytes[off + 2],
120 bytes[off + 3],
121 bytes[off + 4],
122 bytes[off + 5],
123 bytes[off + 6],
124 bytes[off + 7],
125 ])
126}
127
128pub fn parse_supported_packet(
130 bytes: &[u8],
131 packet_index: u64,
132 classify_kernarg: impl FnOnce(u64) -> KernargClass,
133) -> Result<DispatchDescriptor, AqlParseError> {
134 if bytes.len() != AQL_PACKET_BYTES {
135 return Err(AqlParseError::WrongLength { got: bytes.len() });
136 }
137 let header = read_u16(bytes, 0);
138 let ty = header & 0xff;
139 match PacketType::from_header_type(ty) {
140 PacketType::Invalid => Err(AqlParseError::StillInvalid),
141 PacketType::KernelDispatch => parse_kernel_dispatch(bytes, packet_index, classify_kernarg),
142 PacketType::BarrierAnd | PacketType::BarrierOr => {
143 parse_barrier_minimal(bytes, packet_index)
144 }
145 other => Err(AqlParseError::UnsupportedType {
146 packet_type: other.as_u16(),
147 }),
148 }
149}
150
151pub fn parse_barrier_minimal(
153 bytes: &[u8],
154 packet_index: u64,
155) -> Result<DispatchDescriptor, AqlParseError> {
156 if bytes.len() != AQL_PACKET_BYTES {
157 return Err(AqlParseError::WrongLength { got: bytes.len() });
158 }
159 let header = read_u16(bytes, 0);
160 let ty = header & 0xff;
161 let packet_type = PacketType::from_header_type(ty);
162 match packet_type {
163 PacketType::BarrierAnd | PacketType::BarrierOr => {}
164 PacketType::Invalid => return Err(AqlParseError::StillInvalid),
165 other => {
166 return Err(AqlParseError::UnsupportedType {
167 packet_type: other.as_u16(),
168 });
169 }
170 }
171 let mut captured = [0u8; AQL_PACKET_BYTES];
172 captured.copy_from_slice(bytes);
173 Ok(DispatchDescriptor {
174 packet_index,
175 packet_type,
176 header,
177 setup: 0,
178 dimensions: 0,
179 workgroup_size: [0, 0, 0],
180 grid_size: [0, 0, 0],
181 private_segment_size: 0,
182 group_segment_size: 0,
183 kernel_object: 0,
184 kernarg: KernargClass::Null,
185 completion_signal: read_u64(bytes, 56),
186 captured_bytes: captured,
187 })
188}
189
190pub fn parse_kernel_dispatch(
192 bytes: &[u8],
193 packet_index: u64,
194 classify_kernarg: impl FnOnce(u64) -> KernargClass,
195) -> Result<DispatchDescriptor, AqlParseError> {
196 if bytes.len() != AQL_PACKET_BYTES {
197 return Err(AqlParseError::WrongLength { got: bytes.len() });
198 }
199 let header = read_u16(bytes, 0);
200 let ty = header & 0xff;
201 let packet_type = PacketType::from_header_type(ty);
202 match packet_type {
203 PacketType::Invalid => return Err(AqlParseError::StillInvalid),
204 PacketType::KernelDispatch => {}
205 other => {
206 return Err(AqlParseError::UnsupportedType {
207 packet_type: other.as_u16(),
208 });
209 }
210 }
211
212 let setup = read_u16(bytes, 2);
213 let dimensions = setup & 0b11;
215 if !(1..=3).contains(&dimensions) {
216 return Err(AqlParseError::BadDimensions { dimensions });
217 }
218
219 let workgroup_size = [read_u16(bytes, 4), read_u16(bytes, 6), read_u16(bytes, 8)];
220 let grid_size = [
221 read_u32(bytes, 12),
222 read_u32(bytes, 16),
223 read_u32(bytes, 20),
224 ];
225 let private_segment_size = read_u32(bytes, 24);
226 let group_segment_size = read_u32(bytes, 28);
227 let kernel_object = read_u64(bytes, 32);
228 let kernarg_addr = read_u64(bytes, 40);
229 let completion_signal = read_u64(bytes, 56);
230
231 for d in 0..3 {
232 if workgroup_size[d] == 0 {
233 return Err(AqlParseError::ZeroWorkgroup { dim: d });
234 }
235 if grid_size[d] < u32::from(workgroup_size[d]) {
236 return Err(AqlParseError::GridSmallerThanWorkgroup { dim: d });
237 }
238 }
239 if dimensions == 1 && (workgroup_size[1] != 1 || workgroup_size[2] != 1) {
240 return Err(AqlParseError::DimConstraint {
241 detail: "dims=1 requires workgroup y=z=1".into(),
242 });
243 }
244 if dimensions == 1 && (grid_size[1] != 1 || grid_size[2] != 1) {
245 return Err(AqlParseError::DimConstraint {
246 detail: "dims=1 requires grid y=z=1".into(),
247 });
248 }
249 if dimensions == 2 && (workgroup_size[2] != 1 || grid_size[2] != 1) {
250 return Err(AqlParseError::DimConstraint {
251 detail: "dims=2 requires workgroup/grid z=1".into(),
252 });
253 }
254
255 let mut captured = [0u8; AQL_PACKET_BYTES];
256 captured.copy_from_slice(bytes);
257
258 Ok(DispatchDescriptor {
259 packet_index,
260 packet_type,
261 header,
262 setup,
263 dimensions,
264 workgroup_size,
265 grid_size,
266 private_segment_size,
267 group_segment_size,
268 kernel_object,
269 kernarg: classify_kernarg(kernarg_addr),
270 completion_signal,
271 captured_bytes: captured,
272 })
273}
274
275pub fn replay_dispatch(
277 bytes: &[u8],
278 packet_index: u64,
279) -> Result<DispatchDescriptor, AqlParseError> {
280 parse_supported_packet(bytes, packet_index, |addr| {
281 if addr == 0 {
282 KernargClass::Null
283 } else {
284 KernargClass::ForeignOpaque { addr }
286 }
287 })
288}
289
290pub fn golden_kernel_dispatch_1d(
292 workgroup_x: u16,
293 grid_x: u32,
294 kernel_object: u64,
295 kernarg: u64,
296 completion_signal: u64,
297) -> [u8; AQL_PACKET_BYTES] {
298 let mut b = [0u8; AQL_PACKET_BYTES];
299 b[0] = PACKET_TYPE_KERNEL_DISPATCH as u8;
301 b[2] = 1;
303 b[4..6].copy_from_slice(&workgroup_x.to_le_bytes());
304 b[6..8].copy_from_slice(&1u16.to_le_bytes());
305 b[8..10].copy_from_slice(&1u16.to_le_bytes());
306 b[12..16].copy_from_slice(&grid_x.to_le_bytes());
307 b[16..20].copy_from_slice(&1u32.to_le_bytes());
308 b[20..24].copy_from_slice(&1u32.to_le_bytes());
309 b[32..40].copy_from_slice(&kernel_object.to_le_bytes());
310 b[40..48].copy_from_slice(&kernarg.to_le_bytes());
311 b[56..64].copy_from_slice(&completion_signal.to_le_bytes());
312 b
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn golden_parses_and_replays() {
321 let bytes = golden_kernel_dispatch_1d(64, 256, 0xABCDu64, 0, 0x1111);
322 let d = parse_kernel_dispatch(&bytes, 0, |_| KernargClass::Null).unwrap();
323 assert_eq!(d.dimensions, 1);
324 assert_eq!(d.workgroup_size, [64, 1, 1]);
325 assert_eq!(d.grid_size, [256, 1, 1]);
326 assert_eq!(d.kernel_object, 0xABCD);
327 assert_eq!(d.completion_signal, 0x1111);
328 let replayed = replay_dispatch(&d.captured_bytes, 0).unwrap();
329 assert_eq!(replayed.grid_size, d.grid_size);
330 assert_eq!(replayed.kernel_object, d.kernel_object);
331 }
332
333 #[test]
334 fn rejects_zero_workgroup() {
335 let mut bytes = golden_kernel_dispatch_1d(0, 256, 1, 0, 0);
336 let err = parse_kernel_dispatch(&bytes, 0, |_| KernargClass::Null).unwrap_err();
338 assert!(matches!(err, AqlParseError::ZeroWorkgroup { dim: 0 }));
339 bytes = golden_kernel_dispatch_1d(64, 32, 1, 0, 0); let err = parse_kernel_dispatch(&bytes, 0, |_| KernargClass::Null).unwrap_err();
341 assert!(matches!(
342 err,
343 AqlParseError::GridSmallerThanWorkgroup { dim: 0 }
344 ));
345 }
346
347 #[test]
348 fn rejects_invalid_and_unsupported() {
349 let mut bytes = [0u8; AQL_PACKET_BYTES];
350 bytes[0] = PACKET_TYPE_INVALID as u8;
351 assert!(matches!(
352 parse_kernel_dispatch(&bytes, 0, |_| KernargClass::Null),
353 Err(AqlParseError::StillInvalid)
354 ));
355 bytes[0] = PACKET_TYPE_AGENT_DISPATCH as u8;
356 assert!(matches!(
357 parse_kernel_dispatch(&bytes, 0, |_| KernargClass::Null),
358 Err(AqlParseError::UnsupportedType { .. })
359 ));
360 }
361}