Expand description
defmt logging over shared memory: the CDBG ring, its firmware-side
global_logger, its host-side reader/decoder, and an optional panic handler.
§The problem
A coprocessor on a Consortium SoC usually has no UART of its own to log through, or has one already spoken for. But it does share memory with a Linux-capable core that has a filesystem, a clock, and somewhere to put logs. So the debug path runs the same way as everything else here: firmware writes into a shared window, Linux reads it.
defmt is the right encoder for the firmware end. Format strings and type
metadata stay in the ELF and never reach the wire, so a log line costs a few
bytes and no formatting code — which is what makes logging affordable on a
Cortex-M at all. The cost is that the bytes are meaningless without the ELF
they came from, so the host side needs a decoder.
§Architecture
firmware (no_std) Linux / host
──────────────────── ─────────────────────
defmt::info!(..)
└─ logger::ConsortiumLogger host::HostRing
(global_logger; rzcobs-encodes (validate CDBG, drain frames)
one frame, wraps it in a └─ host::DefmtSession
packet, appends to the ring) (decode against the ELF)
│ │
└──────── CDBG shared window ────────┘
descriptor + circular buffer
panic::(handler) ─ latches Flags::Panic in the descriptor| Module | Role |
|---|---|
config | The ring base/length ConsortiumLogger::init records. |
descriptor | The CDBG descriptor and single-writer/single-reader ring rules. |
packet | Per-frame envelope inside the ring. |
ring | RingWriter / RingReader: the ring as a non-waiting transport. |
logger | Firmware #[global_logger]; encodes and appends. |
host | Host-side validation, draining, and defmt decoding. |
panic | Optional #[panic_handler] that records a crash in the ring. |
§The ring as a transport
The ring is a memory-backed link with no doorbell, so it implements
consortium-core’s non-waiting pair, the same base the IPC shared-memory
transport and the SCMI agent build on. ring::RingWriter is a
TrySendTransport: appending a frame never waits, and a full ring is
TryError::WouldBlock with Overflow latched, which is what lets the
#[global_logger] and the panic handler use it. ring::RingReader is a
TryRecvTransport that copies into a caller buffer without allocating; the
host’s HostRing is built on it. Both are generic over any coherent
consortium_core::Region and refuse a cached one: the CDBG layout shares
cache lines between writer- and reader-owned fields, so flushes would clobber
each other. The logger maps its ring as an UncachedRegion, as it always has.
§Descriptor discipline
The ring has exactly one writer (firmware) and one reader (host), and there is
no lock. What keeps it correct is that each side only mutates the fields it
owns: the writer owns write_ptr, capacity, and flags; the reader owns
read_ptr; write_ptr == read_ptr means empty. Preserve that split when
changing anything here — it is the whole synchronization story.
§Configuration
Layout comes from the manifest, not from the firmware source. csti build
lowers [dbg.<core>] in Consortium.toml into the generated init(), which
calls ConsortiumLogger::init(base, len) first thing. Until that call, the
logger and the panic handler are no-ops, so a firmware crate built without
generated code still links and runs; it just logs nothing.
§Features
Defaults are host and logger.
| Feature | Effect |
|---|---|
logger | Firmware #[global_logger] writing into the ring. |
host | Host-side HostRing reader; implies std. |
decode | Adds defmt-decoder/defmt-parser for host::DefmtSession; implies host. |
panic-handler | Installs #[panic_handler]; implies logger. |
arch-cortex-m | Interrupt-disable and halt for Cortex-M. Required by panic-handler. |
arch-riscv | Interrupt-disable and halt for RISC-V. Required by panic-handler. |
panic-handler needs exactly one arch feature, and the two arch features
are mutually exclusive; both conditions are compile_error!s below rather than
link-time surprises.
§Testing
cargo test -p consortium-dbg # descriptor, ring, packet
cargo test -p consortium-dbg --features decode
just test dbg hostHost-side tests cover descriptor initialization, ring read/write behaviour, decoding fixtures, and the overflow/error flags — no target hardware needed.
Modules§
- descriptor
- Shared memory defmt descriptor definitions.
- host
- A-core shared-memory reader for the CDBG debug ring.
- logger
- Firmware-side
defmtsink: the#[global_logger]that appends encoded frames to theCDBGring. - packet
- Per-frame envelope inside the
CDBGring. - ring
- The
CDBGring as a Consortium transport. - utils
- Byte-at-a-time volatile accessors for the shared debug region.