defmt_ringbuf/lib.rs
1//! defmt-ringbuf is a [`defmt`](https://github.com/knurling-rs/defmt) global logger that logs into
2//! a persistent ring buffer.
3//!
4//! The ring buffer is not cleared at startup, i.e. if placed in a static global variable log messages
5//! will still be available after a reset.
6//!
7//! To use this crate, link to it by importing it somewhere in your project.
8//!
9//! ```
10//! use core::mem::MaybeUninit;
11//! use defmt_ringbuf as _;
12//!
13//! static mut LOG: MaybeUninit<defmt_ringbuf::RingBuffer<1024>> = MaybeUninit::uninit();
14//!
15//! #[entry]
16//! fn main() -> ! {
17//! unsafe {
18//! defmt_ringbuf::init(&mut LOG, || ());
19//! }
20//!
21//! // ...
22//! }
23//! ```
24//!
25//! Call [init] to initialize logging and [read] to read buffered log data.
26//!
27//! # Critical section implementation
28//!
29//! This crate uses [`critical-section`](https://github.com/rust-embedded/critical-section) to ensure only one thread
30//! is writing to the buffer at a time. You must import a crate that provides a `critical-section` implementation
31//! suitable for the current target. See the `critical-section` README for details.
32//!
33//! For example, for single-core privileged-mode Cortex-M targets, you can add the following to your Cargo.toml.
34//!
35//! ```toml
36//! [dependencies]
37//! cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
38//! ```
39
40#![no_std]
41
42use core::{
43 mem::MaybeUninit,
44 sync::atomic::{AtomicBool, Ordering},
45};
46
47mod ring_buffer;
48
49pub use ring_buffer::{RingBuf, RingBuffer};
50
51#[defmt::global_logger]
52struct Logger;
53
54/// Global logger lock.
55static TAKEN: AtomicBool = AtomicBool::new(false);
56
57/// Crticial section.
58static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
59
60/// Encoder.
61static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
62
63/// Ring buffer.
64static mut RING_BUFFER: Option<&'static mut dyn RingBuf> = None;
65
66/// Callback when new log data is available.
67static mut LOG_AVAILABLE: fn() = || ();
68
69unsafe impl defmt::Logger for Logger {
70 fn acquire() {
71 let restore = unsafe { critical_section::acquire() };
72 if TAKEN.load(Ordering::Relaxed) {
73 panic!("defmt logger taken reentrantly")
74 }
75 TAKEN.store(true, Ordering::Relaxed);
76 unsafe { CS_RESTORE = restore };
77
78 unsafe { ENCODER.start_frame(do_write) }
79 }
80
81 unsafe fn flush() {
82 // Flush is a no-op.
83 }
84
85 unsafe fn release() {
86 ENCODER.end_frame(do_write);
87
88 TAKEN.store(false, Ordering::Relaxed);
89 let restore = CS_RESTORE;
90 critical_section::release(restore);
91 }
92
93 unsafe fn write(bytes: &[u8]) {
94 ENCODER.write(bytes, do_write);
95 }
96}
97
98fn do_write(data: &[u8]) {
99 unsafe {
100 if let Some(buffer) = RING_BUFFER.as_mut() {
101 buffer.write(data);
102 LOG_AVAILABLE();
103 }
104 }
105}
106
107/// Initializes logging to a ring buffer.
108///
109/// `ring_buffer` specifies the location of the log buffer.
110/// It is not cleared if it contains vailid data from the previous boot.
111///
112/// `log_available` is called when new log messages are available.
113///
114/// This must be called exactly once.
115/// Log messages received before initiailization are discarded.
116pub unsafe fn init<const SIZE: usize>(
117 ring_buffer: &'static mut MaybeUninit<RingBuffer<SIZE>>, log_available: fn(),
118) {
119 defmt::assert!(RING_BUFFER.is_none());
120
121 let ring_buffer = RingBuffer::init(ring_buffer);
122 RING_BUFFER = Some(ring_buffer as &mut dyn RingBuf);
123 LOG_AVAILABLE = log_available;
124}
125
126/// Reads and removes data from the log buffer.
127///
128/// Returns the number of bytes read and whether data was lost.
129pub fn read(data: &mut [u8]) -> (usize, bool) {
130 unsafe {
131 critical_section::with(|_cs| {
132 if let Some(buffer) = RING_BUFFER.as_mut() {
133 buffer.read(data)
134 } else {
135 (0, false)
136 }
137 })
138 }
139}