umsh_ulcp_runtime/log.rs
1//! The runtime's debug-log seam.
2//!
3//! Shared code needs to emit the same diagnostic lines the firmwares
4//! already print, but each board owns its own sink: the nRF images
5//! multiplex a USB-serial debug channel, the ESP32 image writes to its
6//! UART. Rather than thread a logger through every signature — most of
7//! which are `static`-backed tasks with no place to put one — a board
8//! installs its sink once at boot and the shared modules call
9//! [`debug_log`] freely.
10//!
11//! Before [`set_debug_log`] runs, logging is a no-op. That is the
12//! correct behavior for the earliest boot code, which runs before the
13//! transport that would carry a log line exists.
14
15use core::fmt::Arguments;
16use core::sync::atomic::{AtomicPtr, Ordering};
17
18type Sink = fn(Arguments);
19
20/// The installed sink, held as a raw code pointer.
21///
22/// Deliberately not a critical-section mutex. [`debug_log`] is called from
23/// the device node's per-packet receive tap, on the same boards whose BLE
24/// controller is the entire reason for the `node-thread-mode-mutex`
25/// feature — taking a critical section once per received packet is exactly
26/// the cost that feature exists to keep out of the radio path, and it
27/// would be paid even when the board's sink discards the line.
28///
29/// A plain pointer slot is sufficient here: it is written once at boot and
30/// only read afterwards, and there is no data behind the pointer to
31/// publish, so relaxed ordering has nothing weaker to expose.
32static SINK: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
33
34/// Install the board's debug-log sink. Call once, as early in boot as
35/// the sink itself is usable; a later call replaces the previous sink.
36pub fn set_debug_log(sink: Sink) {
37 SINK.store(sink as *mut (), Ordering::Relaxed);
38}
39
40/// Emit one debug line through the board's sink, or drop it if no sink
41/// is installed yet.
42pub fn debug_log(args: Arguments) {
43 let sink = SINK.load(Ordering::Relaxed);
44 if sink.is_null() {
45 return;
46 }
47 // SAFETY: the slot is null or a `Sink` stored by `set_debug_log`, and
48 // function pointers carry no lifetime to outlive.
49 let sink: Sink = unsafe { core::mem::transmute::<*mut (), Sink>(sink) };
50 sink(args);
51}