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#![cfg_attr(not(feature = "logger"), allow(dead_code))]
41#![no_std]
42
43use core::{
44 mem::MaybeUninit,
45 sync::atomic::{AtomicBool, Ordering},
46};
47
48mod ring_buffer;
49
50pub use ring_buffer::{RingBuf, RingBuffer};
51
52#[cfg_attr(feature = "logger", defmt::global_logger)]
53struct Logger;
54
55/// Global logger lock.
56static TAKEN: AtomicBool = AtomicBool::new(false);
57
58/// Crticial section.
59static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
60
61/// Encoder.
62static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
63
64/// Ring buffer.
65static mut RING_BUFFER: Option<&'static mut dyn RingBuf> = None;
66
67/// Callback when new log data is available.
68static mut LOG_AVAILABLE: fn() = || ();
69
70unsafe impl defmt::Logger for Logger {
71 fn acquire() {
72 let restore = unsafe { critical_section::acquire() };
73 if TAKEN.load(Ordering::Relaxed) {
74 panic!("defmt logger taken reentrantly")
75 }
76 TAKEN.store(true, Ordering::Relaxed);
77 unsafe { CS_RESTORE = restore };
78
79 unsafe {
80 #[allow(static_mut_refs)]
81 ENCODER.start_frame(do_write)
82 }
83 }
84
85 unsafe fn flush() {
86 // Flush is a no-op.
87 }
88
89 unsafe fn release() {
90 #[allow(static_mut_refs)]
91 ENCODER.end_frame(do_write);
92
93 TAKEN.store(false, Ordering::Relaxed);
94 let restore = CS_RESTORE;
95 critical_section::release(restore);
96 }
97
98 unsafe fn write(bytes: &[u8]) {
99 #[allow(static_mut_refs)]
100 ENCODER.write(bytes, do_write);
101 }
102}
103
104fn do_write(data: &[u8]) {
105 unsafe {
106 #![allow(static_mut_refs)]
107 if let Some(buffer) = RING_BUFFER.as_mut() {
108 buffer.write(data);
109 LOG_AVAILABLE();
110 }
111 }
112}
113
114/// Initializes logging to a ring buffer.
115///
116/// `ring_buffer` specifies the location of the log buffer.
117/// It is not cleared if it contains vailid data from the previous boot.
118///
119/// `log_available` is called when new log messages are available.
120///
121/// Log messages received before initiailization are discarded.
122///
123/// # Safety
124/// This must be called exactly once.
125pub unsafe fn init<const SIZE: usize>(
126 ring_buffer: &'static mut MaybeUninit<RingBuffer<SIZE>>, log_available: fn(),
127) {
128 defmt::assert!({
129 #[allow(static_mut_refs)]
130 RING_BUFFER.is_none()
131 });
132
133 let ring_buffer = RingBuffer::init(ring_buffer);
134 RING_BUFFER = Some(ring_buffer as &mut dyn RingBuf);
135 LOG_AVAILABLE = log_available;
136}
137
138/// Reads and removes data from the log buffer.
139///
140/// Returns the number of bytes read and whether data was lost.
141pub fn read(data: &mut [u8]) -> (usize, bool) {
142 unsafe {
143 critical_section::with(|_cs| {
144 #[allow(static_mut_refs)]
145 if let Some(buffer) = RING_BUFFER.as_mut() {
146 buffer.read(data)
147 } else {
148 (0, false)
149 }
150 })
151 }
152}