Skip to main content

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}