consortium_dbg/lib.rs
1#![cfg_attr(not(feature = "host"), no_std)]
2
3//! `defmt` logging over shared memory: the `CDBG` ring, its firmware-side
4//! [`global_logger`], its host-side reader/decoder, and an optional panic handler.
5//!
6//! [`global_logger`]: https://docs.rs/defmt/latest/defmt/attr.global_logger.html
7//!
8//! # The problem
9//!
10//! A coprocessor on a Consortium SoC usually has no UART of its own to log
11//! through, or has one already spoken for. But it does share memory with a
12//! Linux-capable core that has a filesystem, a clock, and somewhere to put logs.
13//! So the debug path runs the same way as everything else here: firmware writes
14//! into a shared window, Linux reads it.
15//!
16//! `defmt` is the right encoder for the firmware end. Format strings and type
17//! metadata stay in the ELF and never reach the wire, so a log line costs a few
18//! bytes and no formatting code — which is what makes logging affordable on a
19//! Cortex-M at all. The cost is that the bytes are meaningless without the ELF
20//! they came from, so the host side needs a decoder.
21//!
22//! # Architecture
23//!
24//! ```text
25//! firmware (no_std) Linux / host
26//! ──────────────────── ─────────────────────
27//! defmt::info!(..)
28//! └─ logger::ConsortiumLogger host::HostRing
29//! (global_logger; rzcobs-encodes (validate CDBG, drain frames)
30//! one frame, wraps it in a └─ host::DefmtSession
31//! packet, appends to the ring) (decode against the ELF)
32//! │ │
33//! └──────── CDBG shared window ────────┘
34//! descriptor + circular buffer
35//! panic::(handler) ─ latches Flags::Panic in the descriptor
36//! ```
37//!
38//! | Module | Role |
39//! | --------------- | --------------------------------------------------------------- |
40//! | `config` | The ring base/length `ConsortiumLogger::init` records. |
41//! | [`descriptor`] | The `CDBG` descriptor and single-writer/single-reader ring rules. |
42//! | [`packet`] | Per-frame envelope inside the ring. |
43//! | [`ring`] | [`RingWriter`](ring::RingWriter) / [`RingReader`](ring::RingReader): the ring as a non-waiting transport. |
44//! | `logger` | Firmware `#[global_logger]`; encodes and appends. |
45//! | `host` | Host-side validation, draining, and `defmt` decoding. |
46//! | `panic` | Optional `#[panic_handler]` that records a crash in the ring. |
47//!
48//! # The ring as a transport
49//!
50//! The ring is a memory-backed link with no doorbell, so it implements
51//! `consortium-core`'s non-waiting pair, the same base the IPC shared-memory
52//! transport and the SCMI agent build on. [`ring::RingWriter`] is a
53//! `TrySendTransport`: appending a frame never waits, and a full ring is
54//! `TryError::WouldBlock` with `Overflow` latched, which is what lets the
55//! `#[global_logger]` and the panic handler use it. [`ring::RingReader`] is a
56//! `TryRecvTransport` that copies into a caller buffer without allocating; the
57//! host's `HostRing` is built on it. Both are generic over any coherent
58//! `consortium_core::Region` and refuse a cached one: the `CDBG` layout shares
59//! cache lines between writer- and reader-owned fields, so flushes would clobber
60//! each other. The logger maps its ring as an `UncachedRegion`, as it always has.
61//!
62//! # Descriptor discipline
63//!
64//! The ring has exactly one writer (firmware) and one reader (host), and there is
65//! no lock. What keeps it correct is that **each side only mutates the fields it
66//! owns**: the writer owns `write_ptr`, `capacity`, and `flags`; the reader owns
67//! `read_ptr`; `write_ptr == read_ptr` means empty. Preserve that split when
68//! changing anything here — it is the whole synchronization story.
69//!
70//! # Configuration
71//!
72//! Layout comes from the manifest, not from the firmware source. `csti build`
73//! lowers `[dbg.<core>]` in `Consortium.toml` into the generated `init()`, which
74//! calls `ConsortiumLogger::init(base, len)` first thing. Until that call, the
75//! logger and the panic handler are **no-ops**, so a firmware crate built without
76//! generated code still links and runs; it just logs nothing.
77//!
78//! # Features
79//!
80//! Defaults are `host` and `logger`.
81//!
82//! | Feature | Effect |
83//! | --------------- | -------------------------------------------------------------- |
84//! | `logger` | Firmware `#[global_logger]` writing into the ring. |
85//! | `host` | Host-side `HostRing` reader; implies `std`. |
86//! | `decode` | Adds `defmt-decoder`/`defmt-parser` for `host::DefmtSession`; implies `host`. |
87//! | `panic-handler` | Installs `#[panic_handler]`; implies `logger`. |
88//! | `arch-cortex-m` | Interrupt-disable and halt for Cortex-M. Required by `panic-handler`. |
89//! | `arch-riscv` | Interrupt-disable and halt for RISC-V. Required by `panic-handler`. |
90//!
91//! `panic-handler` needs **exactly one** arch feature, and the two arch features
92//! are mutually exclusive; both conditions are `compile_error!`s below rather than
93//! link-time surprises.
94//!
95//! # Testing
96//!
97//! ```bash
98//! cargo test -p consortium-dbg # descriptor, ring, packet
99//! cargo test -p consortium-dbg --features decode
100//! just test dbg host
101//! ```
102//!
103//! Host-side tests cover descriptor initialization, ring read/write behaviour,
104//! decoding fixtures, and the overflow/error flags — no target hardware needed.
105
106#[cfg(feature = "logger")]
107mod config;
108pub mod descriptor;
109#[cfg(feature = "host")]
110pub mod host;
111
112/// Re-export of the defmt stream decoder, so host consumers (e.g.
113/// `consortium-runtime-app`) can name `host::DefmtSession` callback types
114/// (`Frame`) without a separate `defmt-decoder` dependency.
115#[cfg(feature = "decode")]
116pub use defmt_decoder;
117/// Re-export of `defmt-parser`, whose [`Level`](defmt_parser::Level) is the type
118/// returned by `Frame::level()` (defmt-decoder does not re-export it publicly).
119#[cfg(feature = "decode")]
120pub use defmt_parser;
121#[cfg(feature = "logger")]
122pub mod logger;
123pub mod packet;
124#[cfg(feature = "panic-handler")]
125pub mod panic;
126pub mod ring;
127pub mod utils;
128
129#[cfg(all(
130 feature = "panic-handler",
131 not(any(feature = "arch-cortex-m", feature = "arch-riscv"))
132))]
133compile_error!(
134 "`panic-handler` requires exactly one arch feature: enable `arch-cortex-m` or `arch-riscv`"
135);
136
137#[cfg(all(feature = "arch-cortex-m", feature = "arch-riscv"))]
138compile_error!("`arch-cortex-m` and `arch-riscv` are mutually exclusive");