Skip to main content

Module console

Module console 

Source
Expand description

Async buffered serial console for both targets — byte-level ring buffer with overwrite-on-full semantics. The producer (log!() / send_line) is O(1) and NEVER blocks the caller: never spins, never awaits, never creates back-pressure. On full, the oldest bytes are overwritten so the lines fired just before a failure survive (those are the informative ones).

A single drain_task pulls contiguous slices from the ring and writes them via the target’s async TX sink (write_all().await parks on the TX-done IRQ when the FIFO is full — no busy-spin). Producers signal the drain after every write; the drain awaits the signal when the ring is empty.

Per-target seam (hardware): the sink types + setup (build + split the peripheral) + [imp::boot_panic_write]. fire27 = UART0 @ 1 Mbaud; cores3 = USB-Serial-JTAG CDC. setup does NOT make the fire27 TX async — into_async() binds the IRQ to the calling core, so the binary does it from main (PRO).

The firmware’s alternator_regulator::logger::cat_line calls send_line for the :cat dump. Unlike log!(), send_line is back-pressuring: it awaits ring space before writing, so a fast read-back self-paces to the TX drain rate and is lossless (a plain overwrite-on-full write dropped lines and made log_interval’s read-back show false gaps). alternator-regulator depends on this crate ONLY for that — optional + esp-hal-gated, so host builds never pull it.

Modules§

markers
Stable, greppable log markers that the HIL detect_crash contract keys on. Treat these as part of the public contract — do not reword without updating the test harness.

Structs§

Config
Console install configuration. serial: None is the production backstop — the log backend is registered (so log! is a cheap no-op into the ring) but no transport is brought up and no drain task runs, leaving zero serial surface. Some(_) brings up the chip’s native transport (UART0 on Fire27, USB-Serial-JTAG CDC on CoreS3) and spawns the drain.
Console
What install hands back: the console RX half, when a transport was brought up, to be handed to serial_cmd (bidirectional on one port, R7). In a serial-free build (console-serial off, R9) it carries no transport.
PanicCrumb
A panic breadcrumb recovered from the previous run: location is the panic file, reason a 32-bit digest of the panic message.
SerialResources
The peripheral bundle super::install needs: just the USB-Serial-JTAG device (the CoreS3 console is the native CDC port — no probe needed, R7).

Functions§

drain_task
The single console writer: pulls contiguous slices from the ring and writes them via the target’s async TX sink. write_all().await parks on the TX-done IRQ when the FIFO is full — no spin, no interrupts-off, no cross-core contention. When the ring is empty, awaits [DRAIN_SIGNAL] (woken by every producer write). Spawn once from the binary’s main (fire27: pass tx.into_async(); cores3: the split TX is already async).
enable_async
Compatibility no-op. The prior design switched producers from a blocking writer to an async queue here; with the ring buffer the producer path is the same in every phase (boot and steady-state both write to the ring), so there’s nothing to switch. Kept so binaries don’t have to change in this commit — can be removed once both binaries stop calling it.
init
Register the console as the global log backend. Call once, early. The ring is statically initialised, so producers can write immediately — pre-drain_task writes simply accumulate in the ring and are flushed once the drain runs.
install
One-call console bring-up: register the log backend at cfg.level and, when cfg.serial is Some, build the chip’s transport + spawn the single drain_task. Replaces the hand-wired init() + setup() + drain-spawn sequence in every binary. Call once from main (the core that owns the drain; Fire27’s TX into_async() binds the IRQ to the calling core).
on_panic
Shared message-only panic handler for both targets. Pushes the panic info into the ring (alongside the pre-panic context that’s already there), then synchronously drains the ring via the raw FIFO poker — the async drain task is gone (or never started) so it can’t service the ring for us. After the drain, halts so the fault stays visible. No stack walk: message-only on both targets (deliberate, symmetric), which is why neither esp-backtrace nor esp-println is pulled in. The binary’s #[panic_handler] is a one-line wrapper around this.
send_line
Back-pressuring line sink — the R10 injection point. A control crate must not depend on this BSP directly; instead it defines its own LineSink-style trait and the binary injects a ~5-line newtype whose send_line forwards here (the trait stays consumer-side, so neither BSP nor control crate depends on the other). This is also the bulk-dump path for the HIL :cat CSV.
setup
Build the USB-Serial-JTAG console and split. into_async() here binds the IRQ to whatever core calls this — the binary calls it from main.
take_panic_breadcrumb
Read and clear the previous run’s panic breadcrumb, if any. Call once at boot, before install, and log the result (the markers::PREV_PANIC line) — that read-back is the cross-transport fault contract (R8), identical on both targets. Returns None on a clean boot or once the crumb is taken.

Type Aliases§

ConsoleRx
RX half → serial_cmd (async poller); TX half → the drain task.
ConsoleTx
ConsoleTxAsync
The drain task’s sink — the split TX is already async on cores3.