1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
//! [`defmt`](https://github.com/knurling-rs/defmt) global logger over RTT.
//!
//! NOTE when using this crate it's not possible to use (link to) the `rtt-target` crate
//!
//! To use this crate, link to it by importing it somewhere in your project.
//!
//! ```
//! // src/main.rs or src/bin/my-app.rs
//! use defmt_rtt as _;
//! ```
//!
//! # Blocking/Non-blocking
//!
//! `probe-run` puts RTT into blocking-mode, to avoid losing data.
//!
//! As an effect this implementation may block forever if `probe-run` disconnects on runtime. This
//! is because the RTT buffer will fill up and writing will eventually halt the program execution.
//!
//! `defmt::flush` would also block forever in that case.
//!
//! # Critical section implementation
//!
//! This crate uses [`critical-section`](https://github.com/rust-embedded/critical-section) to ensure only one thread
//! is writing to the buffer at a time. You must import a crate that provides a `critical-section` implementation
//! suitable for the current target. See the `critical-section` README for details.
//!
//! For example, for single-core privileged-mode Cortex-M targets, you can add the following to your Cargo.toml.
//!
//! ```toml
//! [dependencies]
//! cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
//! ```
#![no_std]
mod channel;
mod consts;
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use crate::{channel::Channel, consts::BUF_SIZE};
#[defmt::global_logger]
struct Logger;
/// Global logger lock.
static TAKEN: AtomicBool = AtomicBool::new(false);
static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
unsafe impl defmt::Logger for Logger {
fn acquire() {
// safety: Must be paired with corresponding call to release(), see below
let restore = unsafe { critical_section::acquire() };
// safety: accessing the `static mut` is OK because we have acquired a critical section.
if TAKEN.load(Ordering::Relaxed) {
panic!("defmt logger taken reentrantly")
}
// safety: accessing the `static mut` is OK because we have acquired a critical section.
TAKEN.store(true, Ordering::Relaxed);
// safety: accessing the `static mut` is OK because we have acquired a critical section.
unsafe { CS_RESTORE = restore };
// safety: accessing the `static mut` is OK because we have acquired a critical section.
unsafe { ENCODER.start_frame(do_write) }
}
unsafe fn flush() {
// safety: accessing the `&'static _` is OK because we have acquired a critical section.
handle().flush();
}
unsafe fn release() {
// safety: accessing the `static mut` is OK because we have acquired a critical section.
ENCODER.end_frame(do_write);
// safety: accessing the `static mut` is OK because we have acquired a critical section.
TAKEN.store(false, Ordering::Relaxed);
// safety: accessing the `static mut` is OK because we have acquired a critical section.
let restore = CS_RESTORE;
// safety: Must be paired with corresponding call to acquire(), see above
critical_section::release(restore);
}
unsafe fn write(bytes: &[u8]) {
// safety: accessing the `static mut` is OK because we have acquired a critical section.
ENCODER.write(bytes, do_write);
}
}
fn do_write(bytes: &[u8]) {
unsafe { handle().write_all(bytes) }
}
#[repr(C)]
struct Header {
id: [u8; 16],
max_up_channels: usize,
max_down_channels: usize,
up_channel: Channel,
}
const MODE_MASK: usize = 0b11;
/// Block the application if the RTT buffer is full, wait for the host to read data.
const MODE_BLOCK_IF_FULL: usize = 2;
/// Don't block if the RTT buffer is full. Truncate data to output as much as fits.
const MODE_NON_BLOCKING_TRIM: usize = 1;
// make sure we only get shared references to the header/channel (avoid UB)
/// # Safety
/// `Channel` API is not re-entrant; this handle should not be held from different execution
/// contexts (e.g. thread-mode, interrupt context)
unsafe fn handle() -> &'static Channel {
// NOTE the `rtt-target` API is too permissive. It allows writing arbitrary data to any
// channel (`set_print_channel` + `rprint*`) and that can corrupt defmt log frames.
// So we declare the RTT control block here and make it impossible to use `rtt-target` together
// with this crate.
#[no_mangle]
static mut _SEGGER_RTT: Header = Header {
id: *b"SEGGER RTT\0\0\0\0\0\0",
max_up_channels: 1,
max_down_channels: 0,
up_channel: Channel {
name: &NAME as *const _ as *const u8,
#[allow(static_mut_refs)]
buffer: unsafe { &mut BUFFER as *mut _ as *mut u8 },
size: BUF_SIZE,
write: AtomicUsize::new(0),
read: AtomicUsize::new(0),
flags: AtomicUsize::new(MODE_NON_BLOCKING_TRIM),
},
};
#[cfg_attr(target_os = "macos", link_section = ".uninit,defmt-rtt.BUFFER")]
#[cfg_attr(not(target_os = "macos"), link_section = ".uninit.defmt-rtt.BUFFER")]
static mut BUFFER: [u8; BUF_SIZE] = [0; BUF_SIZE];
// Place NAME in data section, so the whole RTT header can be read from RAM.
// This is useful if flash access gets disabled by the firmware at runtime.
#[link_section = ".data"]
static NAME: [u8; 6] = *b"defmt\0";
&_SEGGER_RTT.up_channel
}