consortium_dbg/descriptor.rs
1//! Shared memory defmt descriptor definitions.
2//!
3//! This module defines the `CDBG` descriptor: a fixed-size header placed at the
4//! start of a shared-memory region, immediately followed by a circular buffer of
5//! defmt log frames. The MCU (firmware) side is the *writer* and the application
6//! (Linux/host) side is the *reader*; the two communicate purely through the
7//! descriptor fields and the ring data, with no locks.
8//!
9//! Ownership of each field is split so that, in steady state, each side only
10//! mutates fields it owns: the writer owns `write_ptr`, `capacity`, and `flags`;
11//! the reader owns `read_ptr`. `write_ptr == read_ptr` means the ring is empty.
12//!
13//! ```text
14//! Offset Size Owner Field
15//! 0x00 6 N/A header (refer to `DescriptorHeader`)
16//! 0x06 2 mcu write_ptr
17//! 0x08 2 app read_ptr
18//! 0x0A 2 mcu capacity
19//! 0x0C 2 mcu flags (error flags, etc.)
20//! 0x0E 2 n/a reserved
21//! 0x10 vary mcu data (circular buffer of log data)
22//! ```
23
24use bitflags::bitflags;
25use consortium_core::descriptor::{DescriptorHeader, DescriptorMagic};
26
27use crate::packet::{PACKET_HEADER_SIZE, Packet};
28use crate::utils::{rv_u16, wv_u16};
29
30/// In-memory mirror of the shared `CDBG` descriptor.
31///
32/// `#[repr(C)]` keeps the field order matching the on-wire layout documented in
33/// the module header, but reads/writes still go through the explicit offset
34/// constants below (via volatile helpers) rather than relying on struct layout.
35#[repr(C)]
36pub struct Descriptor {
37 /// Magic + version stamp shared by all Consortium descriptors.
38 pub header: DescriptorHeader,
39 /// Writer-owned cursor: byte offset where the next frame will be written.
40 pub write_ptr: u16,
41 /// Reader-owned cursor: byte offset of the next frame to be read.
42 /// `read_ptr == write_ptr` means the ring is empty.
43 pub read_ptr: u16,
44 /// Total byte length of the shared region (header + data). Acts as the
45 /// wrap-around bound for the circular buffer; usable data is
46 /// `[DATA_START, capacity)`.
47 pub capacity: u16,
48 /// Writer-owned status flags (e.g. overflow).
49 pub flags: Flags,
50 _reserved1: [u8; 2],
51}
52
53bitflags! {
54 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55 pub struct Flags: u16 {
56 /// Writer is mid-write; readers should not consume the in-flight frame.
57 const Writing = 0b0000_0000_0000_0001;
58 /// A non-recoverable error occurred on the writer side.
59 const Error = 0b0000_0000_0001_0000;
60 /// The last frame was dropped because it did not fit without
61 /// overwriting unread data.
62 const Overflow = 0b0000_0000_0010_0000;
63 /// The writer panicked; the stream ended abnormally and the frames up to
64 /// this point are the pre-panic log, followed by the panic message.
65 const Panic = 0b0000_0000_0100_0000;
66 }
67}
68
69/// Identifies a Consortium defmt debug descriptor.
70pub const MAGIC: DescriptorMagic = DescriptorMagic::new(b"CDBG");
71/// On-wire layout version; bump when the descriptor/packet format changes.
72pub const VERSION: u8 = 0;
73
74// Byte offsets of each field within the shared region. All field access goes
75// through these constants so the volatile reads/writes stay layout-agnostic.
76pub const WRITE_PTR_OFFSET: usize = 0x06;
77pub const READ_PTR_OFFSET: usize = 0x08;
78pub const CAPACITY_OFFSET: usize = 0x0A;
79pub const FLAGS_OFFSET: usize = 0x0C;
80
81/// Size of the descriptor header in bytes.
82pub const DESCRIPTOR_SIZE: usize = core::mem::size_of::<Descriptor>();
83/// First byte usable for ring data, i.e. just past the descriptor header.
84/// Both cursors wrap back to this offset rather than to 0.
85const DATA_START: u16 = DESCRIPTOR_SIZE as u16;
86/// Largest payload one frame carries: the packet `len` field is a `u8`.
87pub const MAX_FRAME_LEN: usize = u8::MAX as usize;
88
89/// What [`Descriptor::try_read_frame`] found.
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub enum ReadOutcome {
92 /// Nothing unread in the ring.
93 Empty,
94 /// One frame of this many payload bytes was copied out and consumed.
95 Frame(usize),
96 /// The next frame's payload is `needed` bytes, more than the buffer holds; it
97 /// was left unread.
98 TooSmall {
99 /// The frame's payload length.
100 needed: usize,
101 },
102}
103
104impl Descriptor {
105 /// Builds a fresh descriptor for a region of `capacity` total bytes, with
106 /// both cursors parked at `DATA_START` (empty ring) and no flags set.
107 pub const fn new(capacity: u16) -> Self {
108 Self {
109 header: DescriptorHeader::new(MAGIC, VERSION),
110 write_ptr: DATA_START,
111 read_ptr: DATA_START,
112 capacity,
113 flags: Flags::empty(),
114 _reserved1: [0; 2],
115 }
116 }
117
118 /// Returns `true` if the descriptor header has the expected magic and version.
119 pub fn check(&self) -> bool {
120 self.header.check(MAGIC, VERSION).is_ok()
121 }
122
123 /// Reads a `Descriptor` from shared memory at `ptr`.
124 ///
125 /// # Safety
126 /// `ptr` must point to a valid, readable shared-memory region of at least
127 /// `DESCRIPTOR_SIZE` bytes with consistent volatile layout.
128 pub unsafe fn from_raw_ptr(ptr: *const u8) -> Self {
129 // SAFETY: caller guarantees the descriptor header is present at `ptr`.
130 let header = unsafe { DescriptorHeader::from_raw_ptr(ptr) };
131 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
132 let write_ptr = unsafe { rv_u16(ptr, WRITE_PTR_OFFSET) };
133 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
134 let read_ptr = unsafe { rv_u16(ptr, READ_PTR_OFFSET) };
135 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
136 let capacity = unsafe { rv_u16(ptr, CAPACITY_OFFSET) };
137 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
138 let flags = Flags::from_bits_retain(unsafe { rv_u16(ptr, FLAGS_OFFSET) });
139 Self {
140 header,
141 write_ptr,
142 read_ptr,
143 capacity,
144 flags,
145 _reserved1: [0; 2],
146 }
147 }
148
149 /// Writes this descriptor to shared memory at `ptr`.
150 ///
151 /// # Safety
152 /// `ptr` must point to a valid, writable shared-memory region of at least
153 /// `DESCRIPTOR_SIZE` bytes.
154 pub unsafe fn write_to_raw_ptr(&self, ptr: *mut u8) {
155 // SAFETY: caller guarantees `ptr` covers the descriptor header.
156 unsafe { DescriptorHeader::new(MAGIC, VERSION).write_to_raw_ptr(ptr) };
157 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
158 unsafe { wv_u16(ptr, WRITE_PTR_OFFSET, self.write_ptr) };
159 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
160 unsafe { wv_u16(ptr, READ_PTR_OFFSET, self.read_ptr) };
161 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
162 unsafe { wv_u16(ptr, CAPACITY_OFFSET, self.capacity) };
163 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
164 unsafe { wv_u16(ptr, FLAGS_OFFSET, self.flags.bits()) };
165 }
166
167 /// Copies the next available frame's payload into `buf` and consumes it.
168 ///
169 /// The one read path: [`read_one_frame`](Self::read_one_frame), the host reader,
170 /// and [`RingReader`](crate::ring::RingReader) all go through it. It allocates
171 /// nothing, so it serves `no_std` readers too.
172 ///
173 /// A frame whose payload is longer than `buf` is left in the ring, unconsumed,
174 /// and reported as [`ReadOutcome::TooSmall`] so the caller can retry with a
175 /// larger buffer; one of [`MAX_FRAME_LEN`] bytes always fits.
176 ///
177 /// # Safety
178 /// `base` must point to the start of the shared-memory region owning this descriptor,
179 /// valid for at least `self.capacity` bytes.
180 pub unsafe fn try_read_frame(&mut self, base: *mut u8, buf: &mut [u8]) -> ReadOutcome {
181 // The writer wraps early when a frame does not fit in the tail before
182 // `capacity`, jumping `write_ptr` back to `DATA_START` and leaving a gap
183 // the reader cannot predict from packet lengths alone. When the reader's
184 // linear advance lands in that gap it finds no fresh packet; it must then
185 // follow the writer's wrap to `DATA_START` rather than stall there. At
186 // most one wrap can occur per call, so a single retry suffices.
187 for _ in 0..2 {
188 if self.read_ptr == self.write_ptr {
189 return ReadOutcome::Empty;
190 }
191 // SAFETY: caller guarantees the ring buffer covers `self.read_ptr`.
192 let packet_ptr = unsafe { base.add(self.read_ptr as usize) };
193 // SAFETY: `packet_ptr` points into the shared ring buffer.
194 let frame_header = unsafe { Packet::try_from_raw_ptr(packet_ptr) };
195
196 // A packet that would run past `capacity` was never written by a writer
197 // (it wraps first), so it is stale or corrupt: treat it like a bad header
198 // rather than read past the ring.
199 let in_ring = |header: &Packet| {
200 self.read_ptr as usize + PACKET_HEADER_SIZE + header.len as usize
201 <= self.capacity as usize
202 };
203 let mut frame_header = match frame_header {
204 Some(header) if header.is_valid() && in_ring(&header) => header,
205 // No fresh frame here. If we are already at `DATA_START` the ring
206 // is genuinely drained (a partially written or stale head); stop.
207 // Otherwise the writer wrapped early — follow it and retry once.
208 _ => {
209 if self.read_ptr == DATA_START {
210 return ReadOutcome::Empty;
211 }
212 self.read_ptr = DATA_START;
213 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
214 unsafe { wv_u16(base, READ_PTR_OFFSET, self.read_ptr) };
215 continue;
216 }
217 };
218
219 let len = frame_header.len as usize;
220 let Some(payload) = buf.get_mut(..len) else {
221 return ReadOutcome::TooSmall { needed: len };
222 };
223 // SAFETY: `packet_ptr` points to the valid frame described by `frame_header`.
224 unsafe { frame_header.read_payload(packet_ptr, payload) };
225 // SAFETY: `packet_ptr` points to the valid frame described by `frame_header`.
226 unsafe { frame_header.mark_as_consumed(packet_ptr) };
227 // SAFETY: `base` points to the descriptor-owned ring buffer.
228 self.read_ptr = unsafe { self.next_read_ptr(base) };
229 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
230 unsafe { wv_u16(base, READ_PTR_OFFSET, self.read_ptr) };
231 return ReadOutcome::Frame(len);
232 }
233 ReadOutcome::Empty
234 }
235
236 /// Reads the next available defmt frame from the circular buffer, returning its payload.
237 ///
238 /// Returns `None` if no frame is available or if the current frame header is invalid.
239 ///
240 /// # Safety
241 /// `base` must point to the start of the shared-memory region owning this descriptor,
242 /// valid for at least `self.capacity` bytes.
243 #[cfg(feature = "host")]
244 pub unsafe fn read_one_frame(&mut self, base: *mut u8) -> Option<Vec<u8>> {
245 let mut buf = [0u8; MAX_FRAME_LEN];
246 // SAFETY: forwarded to the caller.
247 match unsafe { self.try_read_frame(base, &mut buf) } {
248 ReadOutcome::Frame(len) => Some(buf[..len].to_vec()),
249 // A `MAX_FRAME_LEN` buffer holds every frame, so `TooSmall` cannot occur.
250 ReadOutcome::Empty | ReadOutcome::TooSmall { .. } => None,
251 }
252 }
253
254 /// Drains all available defmt frames from the circular buffer, returning them as a `Vec`.
255 ///
256 /// # Safety
257 /// Same requirements as [`read_one_frame`]: `base` must be valid for at least
258 /// `self.capacity` bytes.
259 ///
260 /// [`read_one_frame`]: Self::read_one_frame
261 #[cfg(feature = "host")]
262 pub unsafe fn read_bulk_frames(&mut self, base: *mut u8) -> Vec<Vec<u8>> {
263 let mut frames = Vec::new();
264 // SAFETY: caller guarantees `base` points to this descriptor`s ring buffer.
265 while let Some(frame) = unsafe { self.read_one_frame(base) } {
266 frames.push(frame);
267 }
268 frames
269 }
270
271 /// Writes a single defmt frame into the circular buffer.
272 ///
273 /// Returns `true` on success; returns `false` and sets the `Overflow` flag if the
274 /// frame does not fit without overwriting unread data.
275 ///
276 /// # Safety
277 /// `base` must point to the start of the shared-memory region owning this descriptor,
278 /// valid for at least `self.capacity` bytes.
279 pub unsafe fn write_one_frame(&mut self, base: *mut u8, frame: &[u8]) -> bool {
280 // SAFETY: caller guarantees `base` points to this descriptor`s ring buffer.
281 match unsafe { self.allocate_write_ptr(base, frame.len()) } {
282 Some(p) => {
283 // SAFETY: `p` was allocated within the ring buffer.
284 let packet_ptr = unsafe { base.add(p as usize) };
285 // SAFETY: `packet_ptr` points to enough space for the full packet.
286 unsafe { Packet::write_full_packet_to_raw_ptr(packet_ptr, frame) };
287 self.flags.remove(Flags::Overflow);
288 self.write_ptr = self.advance_ptr(p, frame.len());
289 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
290 unsafe { wv_u16(base, WRITE_PTR_OFFSET, self.write_ptr) };
291 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
292 unsafe { wv_u16(base, FLAGS_OFFSET, self.flags.bits()) };
293 true
294 }
295 None => {
296 self.flags.insert(Flags::Overflow);
297 // SAFETY: caller guarantees the descriptor fields are valid and coherent.
298 unsafe { wv_u16(base, FLAGS_OFFSET, self.flags.bits()) };
299 false
300 }
301 }
302 }
303
304 /// Computes the `read_ptr` value after consuming the frame at the current
305 /// `read_ptr`, wrapping back to `DATA_START` once it reaches `capacity`.
306 ///
307 /// If the current slot does not hold a valid packet header, the cursor is
308 /// reset to the start of the data area.
309 ///
310 /// # Safety
311 /// `base` must point to this descriptor's ring buffer, valid for at least
312 /// `self.capacity` bytes.
313 // Read-path helper shared by every reader (`try_read_frame`) and the logger's
314 // own drain path.
315 pub(crate) unsafe fn next_read_ptr(&mut self, base: *const u8) -> u16 {
316 // SAFETY: caller guarantees the ring buffer covers `self.read_ptr`.
317 let cursor = unsafe { base.add(self.read_ptr as usize) };
318 // SAFETY: `cursor` points into the shared ring buffer.
319 let Some(frame_header) = (unsafe { Packet::try_from_raw_ptr(cursor) }) else {
320 return DESCRIPTOR_SIZE as u16;
321 };
322 let next_padding = frame_header.len as usize + PACKET_HEADER_SIZE;
323 let next_ptr = self.read_ptr.wrapping_add(next_padding as u16);
324 if next_ptr >= self.capacity {
325 DATA_START
326 } else {
327 next_ptr
328 }
329 }
330
331 /// Reserves a write position for a packet of `frame_len` payload bytes.
332 ///
333 /// Returns the offset to write at, or `None` if the frame is rejected:
334 /// the payload exceeds the per-frame limit (`u8::MAX`), the packet cannot
335 /// fit in the data area at all, or wrapping would overwrite an unread frame
336 /// (back-pressure). When the remaining tail is too small, the write wraps to
337 /// `DATA_START`.
338 ///
339 /// # Safety
340 /// `base` must point to this descriptor's ring buffer, valid for at least
341 /// `self.capacity` bytes.
342 pub(crate) unsafe fn allocate_write_ptr(
343 &mut self,
344 base: *mut u8,
345 frame_len: usize,
346 ) -> Option<u16> {
347 // Packet length field is a single byte, so payloads larger than u8::MAX
348 // cannot be represented.
349 if frame_len > u8::MAX as usize {
350 return None;
351 }
352
353 let packet_len = PACKET_HEADER_SIZE + frame_len;
354 // Reject frames that can never fit, even in an empty ring.
355 if packet_len > self.capacity as usize - DATA_START as usize {
356 return None;
357 }
358
359 let mut ptr = self.write_ptr;
360 // Not enough room before the end of the region: wrap to the data start.
361 if ptr as usize + packet_len > self.capacity as usize {
362 ptr = DATA_START;
363 }
364
365 // SAFETY: `ptr` has been wrapped into the ring buffer range.
366 let cursor = unsafe { base.add(ptr as usize) };
367 // SAFETY: `cursor` points into the shared ring buffer.
368 // A still-valid packet at the target slot means the reader has not yet
369 // consumed it; refuse to clobber it.
370 if unsafe { Packet::try_from_raw_ptr(cursor) }.is_some_and(|packet| packet.is_valid()) {
371 None
372 } else {
373 Some(ptr)
374 }
375 }
376
377 /// Returns the `write_ptr` value after a packet of `frame_len` payload bytes
378 /// is written at `ptr`, wrapping to `DATA_START` at the end of the region.
379 fn advance_ptr(&self, ptr: u16, frame_len: usize) -> u16 {
380 let next_ptr = ptr.wrapping_add((PACKET_HEADER_SIZE + frame_len) as u16);
381 if next_ptr >= self.capacity {
382 DATA_START
383 } else {
384 next_ptr
385 }
386 }
387
388 /// Block until host has read all pending data. (defmt's Logger trait)
389 ///
390 /// Spins on each pending frame until the reader marks it consumed
391 /// (invalidates its header), advancing `read_ptr` until the ring drains.
392 ///
393 /// # Safety
394 /// `base` must point to this descriptor's ring buffer, valid for at least
395 /// `self.capacity` bytes.
396 #[cfg(feature = "logger")]
397 pub(crate) unsafe fn flush(&mut self, base: *mut u8) {
398 // SAFETY: caller guarantees the ring buffer covers `self.read_ptr`.
399 let cursor = unsafe { base.add(self.read_ptr as usize) };
400 // SAFETY: `cursor` points into the shared ring buffer.
401 let Some(mut frame_header) = (unsafe { Packet::try_from_raw_ptr(cursor) }) else {
402 return;
403 };
404 while self.read_ptr != self.write_ptr && self.read_ptr != DESCRIPTOR_SIZE as u16 {
405 while frame_header.is_valid() {
406 core::hint::spin_loop();
407 }
408 // SAFETY: `base` points to the descriptor-owned ring buffer.
409 self.read_ptr = unsafe { self.next_read_ptr(base) };
410 // SAFETY: caller guarantees the ring buffer covers `self.read_ptr`.
411 let cursor = unsafe { base.add(self.read_ptr as usize) };
412 // SAFETY: `cursor` points into the shared ring buffer.
413 match unsafe { Packet::try_from_raw_ptr(cursor) } {
414 Some(header) => frame_header = header,
415 None => return,
416 }
417 }
418 }
419}