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}