defmt_persist/
lib.rs

1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5use core::mem::{align_of, size_of};
6use core::sync::atomic::{AtomicBool, Ordering};
7use ring_buffer::RingBuffer;
8#[cfg(feature = "qemu-test")]
9pub use ring_buffer::offsets;
10pub use ring_buffer::{Consumer, GrantR};
11
12#[cfg(feature = "async-await")]
13pub(crate) mod atomic_waker;
14pub(crate) mod logger;
15mod ring_buffer;
16
17/// Error returned by [`init`] when initialization fails.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
19pub enum InitError {
20    /// [`init`] has already been called.
21    AlreadyInitialized,
22    /// Memory region is not properly aligned for the ring buffer header.
23    BadAlignment,
24    /// Memory region is too small to hold the ring buffer header plus data.
25    TooSmall,
26    /// Buffer size would overflow pointer arithmetic.
27    TooLarge,
28}
29
30/// Holds the log reader and some additional information from initialization.
31pub struct ConsumerAndMetadata<'a> {
32    /// Reads logs from the buffer.
33    pub consumer: Consumer<'a>,
34    /// Number of bytes that were not from the current run.
35    ///
36    /// If the recovered logs were produced by a different firmware,
37    /// different decoders need to be used. This field helps identify the
38    /// data that was definitely produced by the current firmware.
39    pub recovered_logs_len: usize,
40}
41
42/// Initialize the logger.
43///
44/// This reads the buffer region from the linker symbols `__defmt_persist_start` and
45/// `__defmt_persist_end`. Define these in your linker script to reserve memory for
46/// the persist buffer.
47///
48/// # Errors
49///
50/// Returns an error if:
51/// - [`InitError::AlreadyInitialized`]: Called more than once
52/// - [`InitError::BadAlignment`]: Memory region is not properly aligned
53/// - [`InitError::TooSmall`]: Memory region is too small for the header plus data
54/// - [`InitError::TooLarge`]: Buffer size would overflow pointer arithmetic
55///
56/// # Safety considerations
57///
58/// The linker symbols must define a valid memory region that is not used for any
59/// other purpose. It is safe for both a bootloader and application to call this,
60/// provided the bootloader terminates before the application starts.
61///
62/// Corrupt memory may be accepted as valid. While index bounds are validated,
63/// the data content is not. Treat recovered logs as untrusted external input.
64pub fn init() -> Result<ConsumerAndMetadata<'static>, InitError> {
65    // SAFETY: These symbols are provided by the linker script and point to a reserved memory region.
66    unsafe extern "C" {
67        static __defmt_persist_start: u8;
68        static __defmt_persist_end: u8;
69    }
70
71    static INITIALIZED: AtomicBool = AtomicBool::new(false);
72
73    if INITIALIZED.swap(true, Ordering::SeqCst) {
74        return Err(InitError::AlreadyInitialized);
75    }
76
77    let start = (&raw const __defmt_persist_start).expose_provenance();
78    let end = (&raw const __defmt_persist_end).expose_provenance();
79    let memory = start..end;
80
81    if !memory.start.is_multiple_of(align_of::<RingBuffer>()) {
82        return Err(InitError::BadAlignment);
83    }
84    if memory.len() <= size_of::<RingBuffer>() {
85        return Err(InitError::TooSmall);
86    }
87    let buf_len = memory.len() - size_of::<RingBuffer>();
88    if buf_len >= i32::MAX as usize / 4 {
89        return Err(InitError::TooLarge);
90    }
91
92    // SAFETY:
93    // - Linker symbols provide the memory region.
94    // - The atomic swap above guarantees this code runs exactly once, ensuring exclusive ownership.
95    // - Alignment and size are validated above.
96    let (p, mut c) = unsafe { RingBuffer::recover_or_reinitialize(memory) };
97
98    // SAFETY: The atomic swap guarantees this is called only once.
99    unsafe { logger::LOGGER_STATE.initialize(p) };
100
101    let recovered_logs_len = {
102        let grant = c.read();
103        let (buf1, buf2) = grant.bufs();
104        buf1.len() + buf2.len()
105    };
106
107    Ok(ConsumerAndMetadata {
108        consumer: c,
109        recovered_logs_len,
110    })
111}