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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! The console every module logs to.
//!
//! One sink for the whole image, installed once during bring-up and
//! reached from anywhere by [`logln!`](crate::logln) — so a driver five
//! modules down can report a retry without being handed a UART it has no
//! other use for.
//!
//! ```ignore
//! static UART: StaticCell<Uart> = StaticCell::new();
//!
//! console::init(
//! UART.init(Uart::init(&peripherals.GPIO, peripherals.UART0)),
//! || embassy_time::Instant::now().as_micros(),
//! );
//! logln!("board: starting");
//! ```
//!
//! # Deliberately blocking
//!
//! [`logln!`](crate::logln) busies until the last byte has left the FIFO.
//! At 115200 baud a full line costs a few milliseconds, which is not free
//! once there are tasks with deadlines: a line logged from a task holds up
//! every other task on that executor for as long as it takes to shift out.
//!
//! It is accepted because the alternative — an interrupt-driven transmit
//! ring — is several times the code for a benefit that only matters once
//! logging is chatty in steady state. Bring-up logging happens before the
//! executor starts and costs nothing at all. What follows from it is a
//! rule about *where* to log rather than a reason not to: log decoded
//! events, never individual edges, because a line in the wrong place can
//! span a symbol period.
//!
//! # Why the sink is a trait object
//!
//! [`init`](crate::console::init) takes a `&'static mut (dyn Write +
//! Send)` rather than a
//! `rpi_hal::uart::Uart`, which costs the application a `StaticCell` and
//! buys two things.
//!
//! The first is that this module needs nothing from `rpi-hal` — and so
//! nothing from a chip PAC. `Uart::init` takes PAC singletons, so naming
//! that type here would mean this crate selecting a chip, with all of
//! `rpi-hal`'s precedence order (`bcm2711` > `bcm2837` > `bcm2835`) to
//! forward, for a module whose whole job is to call `write_str`.
//!
//! The second is that the console is genuinely not always UART0. A board
//! that has given the PL011 to a Bluetooth controller logs to
//! `rpi_hal::mini_uart::MiniUart` instead, and both implement
//! [`core::fmt::Write`]; so does a RAM ring buffer read back after a
//! fault, which is the arrangement a board with no serial cable attached
//! wants.
//!
//! # Why the clock is passed in
//!
//! Every line is stamped with the uptime, and
//! [`init`](crate::console::init) takes the function
//! that reads it rather than choosing one. A free-running microsecond
//! counter is available three different ways here — `embassy-time`'s
//! `Instant`, `rpi-hal`'s `Timer`, the ARM generic timer — and picking one
//! would make the most basic module in this crate impose the heaviest
//! dependency in it. A board on Embassy passes
//! `|| Instant::now().as_micros()`; one without passes a `fn` that reads
//! `rpi_hal::timer::Timer`.
//!
//! The stamp is the uptime and not the wall clock deliberately. A board
//! spends its first seconds establishing a date it does not yet have, so
//! the interval that most needs measuring is the interval during which a
//! wall clock would print nothing useful. What the stamp is for is reading
//! a boot log after the fact and seeing where the time went — which step
//! blocked, for how long, and whether a retry waited out a timeout or
//! failed immediately.
use RefCell;
use ;
use Mutex;
/// The installed console: where lines go, how to read the clock, and what
/// to measure it from.
///
/// One struct in one slot rather than a static apiece, so there is no
/// state for [`init`] to leave half-written — a sink installed with the
/// previous clock's origin would stamp every line with an uptime measured
/// from the wrong zero.
/// The console, once [`init`] has installed it.
///
/// `None` before then: [`write_line`] silently discards anything logged
/// that early rather than panicking, since a panic there would have
/// nowhere to report itself.
///
/// Behind a `critical-section` mutex rather than in a `static mut`, so a
/// log from an interrupt handler cannot interleave with one from a task
/// and produce shredded output. The implementation of that critical
/// section is the application's to supply — `rpi-hal`'s `rt` feature has
/// one.
static CONSOLE: = new;
/// Installs `sink` as the console, stamping each line with `uptime`.
///
/// `uptime` returns microseconds from any fixed origin; only differences
/// are used, so it need not start at zero. It is read once here to fix
/// that origin and then once per line.
///
/// Call this before anything logs. Anything logged earlier is discarded —
/// see [`write_line`] — and a second call replaces the console, which
/// restarts the stamp from the new origin.
/// Writes one stamped line, or discards it if [`init`] has not run yet.
///
/// The backing function for [`logln!`](crate::logln); there is no reason
/// to call it directly.
///
/// The clock read, the stamp and the line all go out inside a single
/// `critical_section::with`. Reading the clock outside it would leave a
/// window in which an interrupt-time log lands between a timestamp and
/// the text it belongs to — the interleaving the mutex is here to
/// prevent, and worse than no stamp at all, since the resulting line
/// attributes one module's message to another module's clock reading.
/// Logs a line to the console, stamped with the uptime and with a
/// trailing CRLF.
///
/// Takes the same arguments as [`format_args!`]. Discards the line if
/// [`console::init`](crate::console::init) has not run yet, so it is safe
/// to call from anywhere, including before the console exists.
///
/// ```ignore
/// logln!("dhcp: bound {ip} in {ms}ms");
/// ```