rlvgl_platform/board_runtime/telemetry.rs
1//! DPR-02 telemetry slot layout — byte offsets inside the DPR-00 §5.3
2//! reserved `0x3800_0500..0x3800_0600` Board Runtime range.
3//!
4//! Per [DPR-02 §5.3][dpr02]: every Board Runtime telemetry surface
5//! claims a fixed byte range inside SRAM4's reserved Board Runtime
6//! window. Slots MUST NOT overlap; the layout is the source of truth.
7//!
8//! ## Frozen layout
9//!
10//! | Range | Slot | Owner |
11//! |---|---|---|
12//! | `0x3800_0500..0x3800_0510` | `boot_sentinels` | `boot_sentinel::write(stage)` |
13//! | `0x3800_0510..0x3800_0520` | `safe_stop_report` | `SafeStop::run()` |
14//! | `0x3800_0520..0x3800_0530` | `service_set_active` | `BoardRuntime::init` |
15//! | `0x3800_0530..0x3800_0600` | reserved | (unallocated) |
16//!
17//! ## Registration policy
18//!
19//! Adding a new slot inside the reserved range is **Expert Review**
20//! and MUST §15-amend the DPR-02 §5.3 table. Moving an existing slot
21//! is **Standards Action**.
22//!
23//! ## Scaffold status
24//!
25//! This module exports the byte offsets as `const u32` items inside
26//! [`slot`], plus the [`TelemetrySlot`] enum that names the three
27//! allocated slots for typed lookup. The MMIO-side typed handles that
28//! `SafeStop::run` and `boot_sentinel::write` will use to persist into
29//! SRAM4 land alongside the consumer wiring in DPR-02a / DPR-02b —
30//! per the DPR-00 §6 INV-DPR-3 and the "Register-Mashing Discipline"
31//! section of `CLAUDE.md`, those handles live under
32//! [`crate::hwcore::regs`] and not in this module.
33//!
34//! [dpr02]: https://github.com/softoboros/rlvgl/blob/main/docs/concepts/DPR-02-CONCEPTS.md
35
36/// Byte-offset constants for the §5.3 Board Runtime telemetry layout.
37///
38/// All offsets are absolute SRAM4 addresses (SRAM4 base is `0x3800_0000`
39/// on the STM32H747). The reserved Board Runtime window is
40/// `0x3800_0500..0x3800_0600` (256 bytes total) per DPR-00 §5.3.
41pub mod slot {
42 /// Absolute address of the `boot_sentinels` slot. 4 × u32; slot 0
43 /// holds the most-recent sentinel value per DPR-02 §5.3. Slots
44 /// 1..3 are reserved for a possible "ring buffer of last-N stages"
45 /// (DPR-02b decides whether to use them; default = slot 0 only).
46 pub const BOOT_SENTINELS_BASE: u32 = 0x3800_0500;
47 /// Size of the `boot_sentinels` slot in bytes (4 × u32).
48 pub const BOOT_SENTINELS_LEN: u32 = 16;
49 /// One-past-the-end of the `boot_sentinels` slot.
50 pub const BOOT_SENTINELS_END: u32 = BOOT_SENTINELS_BASE + BOOT_SENTINELS_LEN;
51
52 /// Absolute address of the `safe_stop_report` slot. 4 × u32 per
53 /// DPR-02 §5.3: `[entry sentinel, exit | timeout mask, elapsed_us,
54 /// reserved]`. The in-RAM Rust payload (the
55 /// [`super::super::safe_stop::SafeStopReport`] struct) is the
56 /// `timeouts` + `elapsed_us` pair; the entry/exit sentinels are
57 /// supplied at persist time.
58 pub const SAFE_STOP_REPORT_BASE: u32 = 0x3800_0510;
59 /// Size of the `safe_stop_report` slot in bytes (4 × u32).
60 pub const SAFE_STOP_REPORT_LEN: u32 = 16;
61 /// One-past-the-end of the `safe_stop_report` slot.
62 pub const SAFE_STOP_REPORT_END: u32 = SAFE_STOP_REPORT_BASE + SAFE_STOP_REPORT_LEN;
63
64 /// Absolute address of the `service_set_active` slot. 4 × u32 per
65 /// DPR-02 §5.3: `[bitmask of activated services, ..reserved for
66 /// activation timestamps under DPR-02b]`. Bit positions follow
67 /// DPR-01 §5.1 `ServiceSet`.
68 pub const SERVICE_SET_ACTIVE_BASE: u32 = 0x3800_0520;
69 /// Size of the `service_set_active` slot in bytes (4 × u32).
70 pub const SERVICE_SET_ACTIVE_LEN: u32 = 16;
71 /// One-past-the-end of the `service_set_active` slot.
72 pub const SERVICE_SET_ACTIVE_END: u32 = SERVICE_SET_ACTIVE_BASE + SERVICE_SET_ACTIVE_LEN;
73
74 /// Start of the unallocated "reserved" range inside the Board
75 /// Runtime window. Claiming a slot here is Expert Review per
76 /// DPR-02 §5.3 and MUST §15-amend the DPR-02 layout table.
77 pub const RESERVED_BASE: u32 = 0x3800_0530;
78 /// One-past-the-end of the unallocated reserved range. Coincides
79 /// with the end of the DPR-00 §5.3 Board Runtime window
80 /// (`0x3800_0600`).
81 pub const RESERVED_END: u32 = 0x3800_0600;
82
83 /// Start of the Board Runtime window per DPR-00 §5.3. Equal to
84 /// [`BOOT_SENTINELS_BASE`].
85 pub const BOARD_RUNTIME_WINDOW_BASE: u32 = 0x3800_0500;
86 /// One-past-the-end of the Board Runtime window per DPR-00 §5.3.
87 /// Coincides with [`RESERVED_END`].
88 pub const BOARD_RUNTIME_WINDOW_END: u32 = 0x3800_0600;
89
90 // ── Compile-time invariants ────────────────────────────────────
91
92 // Slots are contiguous and non-overlapping.
93 const _CONTIG_SENTINELS_TO_REPORT: () = {
94 assert!(BOOT_SENTINELS_END == SAFE_STOP_REPORT_BASE);
95 };
96 const _CONTIG_REPORT_TO_SERVICES: () = {
97 assert!(SAFE_STOP_REPORT_END == SERVICE_SET_ACTIVE_BASE);
98 };
99 const _CONTIG_SERVICES_TO_RESERVED: () = {
100 assert!(SERVICE_SET_ACTIVE_END == RESERVED_BASE);
101 };
102 // Window covers exactly the four sub-slots.
103 const _WINDOW_START: () = {
104 assert!(BOARD_RUNTIME_WINDOW_BASE == BOOT_SENTINELS_BASE);
105 };
106 const _WINDOW_END: () = {
107 assert!(BOARD_RUNTIME_WINDOW_END == RESERVED_END);
108 };
109 // Window is the DPR-00 §5.3 reserved 256-byte Board Runtime range.
110 const _WINDOW_SIZE: () = {
111 assert!(BOARD_RUNTIME_WINDOW_END - BOARD_RUNTIME_WINDOW_BASE == 0x100);
112 };
113 // Window MUST end before 0x3800_1000 — sanity check that we are not
114 // running off SRAM4's 64 KiB span on the H747.
115 const _WINDOW_FITS_SRAM4_LOW: () = {
116 assert!(BOARD_RUNTIME_WINDOW_END <= 0x3800_1000);
117 };
118}
119
120/// Typed name for one of the three allocated DPR-02 §5.3 telemetry
121/// slots.
122///
123/// Provides typed [`base`][TelemetrySlot::base] /
124/// [`len`][TelemetrySlot::len] / [`end`][TelemetrySlot::end] accessors
125/// for the byte ranges declared in [`slot`]. The MMIO-typed handles
126/// that perform the SRAM4 writes land in [`crate::hwcore::regs`] under
127/// DPR-02a / DPR-02b per the platform's "Register-Mashing Discipline".
128///
129/// `non_exhaustive` so future Expert Review additions to DPR-02 §5.3
130/// can extend the enum without breaking downstream `match` arms.
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132#[non_exhaustive]
133pub enum TelemetrySlot {
134 /// 4 × u32 ring of boot sentinels per DPR-02 §5.3.
135 BootSentinels,
136 /// 4 × u32 [`super::safe_stop::SafeStopReport`] persistence slot
137 /// per DPR-02 §5.3.
138 SafeStopReport,
139 /// 4 × u32 active-ServiceSet bitmask + reserved activation
140 /// timestamps per DPR-02 §5.3.
141 ServiceSetActive,
142}
143
144impl TelemetrySlot {
145 /// Slot base address (absolute SRAM4) per DPR-02 §5.3.
146 #[inline]
147 pub const fn base(self) -> u32 {
148 match self {
149 TelemetrySlot::BootSentinels => slot::BOOT_SENTINELS_BASE,
150 TelemetrySlot::SafeStopReport => slot::SAFE_STOP_REPORT_BASE,
151 TelemetrySlot::ServiceSetActive => slot::SERVICE_SET_ACTIVE_BASE,
152 }
153 }
154
155 /// Slot length in bytes per DPR-02 §5.3.
156 #[inline]
157 pub const fn len(self) -> u32 {
158 match self {
159 TelemetrySlot::BootSentinels => slot::BOOT_SENTINELS_LEN,
160 TelemetrySlot::SafeStopReport => slot::SAFE_STOP_REPORT_LEN,
161 TelemetrySlot::ServiceSetActive => slot::SERVICE_SET_ACTIVE_LEN,
162 }
163 }
164
165 /// Whether the slot has zero bytes allocated.
166 #[inline]
167 pub const fn is_empty(self) -> bool {
168 self.len() == 0
169 }
170
171 /// One-past-the-end absolute SRAM4 address.
172 #[inline]
173 pub const fn end(self) -> u32 {
174 self.base() + self.len()
175 }
176}
177
178// ── Tests ───────────────────────────────────────────────────────────────
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn slot_addresses_match_dpr02_section_5_3() {
186 // DPR-02 §5.3 frozen layout — any change here is a
187 // Standards-Action amendment for moving existing slots.
188 assert_eq!(slot::BOOT_SENTINELS_BASE, 0x3800_0500);
189 assert_eq!(slot::SAFE_STOP_REPORT_BASE, 0x3800_0510);
190 assert_eq!(slot::SERVICE_SET_ACTIVE_BASE, 0x3800_0520);
191 assert_eq!(slot::RESERVED_BASE, 0x3800_0530);
192 assert_eq!(slot::RESERVED_END, 0x3800_0600);
193 }
194
195 #[test]
196 fn slot_sizes_are_four_u32() {
197 assert_eq!(slot::BOOT_SENTINELS_LEN, 16);
198 assert_eq!(slot::SAFE_STOP_REPORT_LEN, 16);
199 assert_eq!(slot::SERVICE_SET_ACTIVE_LEN, 16);
200 }
201
202 #[test]
203 fn slots_are_contiguous_and_non_overlapping() {
204 // Compile-time assertions in `slot` already cover contiguity;
205 // duplicate them here so a test-runner failure points the reader
206 // at the right invariant.
207 assert_eq!(slot::BOOT_SENTINELS_END, slot::SAFE_STOP_REPORT_BASE);
208 assert_eq!(slot::SAFE_STOP_REPORT_END, slot::SERVICE_SET_ACTIVE_BASE);
209 assert_eq!(slot::SERVICE_SET_ACTIVE_END, slot::RESERVED_BASE);
210 }
211
212 #[test]
213 fn slots_fit_inside_board_runtime_window() {
214 assert!(slot::BOOT_SENTINELS_BASE >= slot::BOARD_RUNTIME_WINDOW_BASE);
215 assert!(slot::RESERVED_END <= slot::BOARD_RUNTIME_WINDOW_END);
216 assert_eq!(
217 slot::BOARD_RUNTIME_WINDOW_END - slot::BOARD_RUNTIME_WINDOW_BASE,
218 0x100,
219 "DPR-00 §5.3 reserves exactly 256 bytes for Board Runtime telemetry"
220 );
221 }
222
223 #[test]
224 fn window_ends_before_sram4_one_kib() {
225 // SRAM4 is 64 KiB on the H747; the Board Runtime window sits
226 // well inside the low 4 KiB of SRAM4 with plenty of margin.
227 // The `< 0x3800_1000` upper bound is the conservative DPR-00
228 // §5.3 sanity check.
229 assert!(slot::BOARD_RUNTIME_WINDOW_END < 0x3800_1000);
230 }
231
232 #[test]
233 fn typed_slot_addresses_agree_with_module_constants() {
234 assert_eq!(
235 TelemetrySlot::BootSentinels.base(),
236 slot::BOOT_SENTINELS_BASE
237 );
238 assert_eq!(TelemetrySlot::BootSentinels.len(), slot::BOOT_SENTINELS_LEN);
239 assert_eq!(TelemetrySlot::BootSentinels.end(), slot::BOOT_SENTINELS_END);
240
241 assert_eq!(
242 TelemetrySlot::SafeStopReport.base(),
243 slot::SAFE_STOP_REPORT_BASE
244 );
245 assert_eq!(
246 TelemetrySlot::SafeStopReport.len(),
247 slot::SAFE_STOP_REPORT_LEN
248 );
249 assert_eq!(
250 TelemetrySlot::SafeStopReport.end(),
251 slot::SAFE_STOP_REPORT_END
252 );
253
254 assert_eq!(
255 TelemetrySlot::ServiceSetActive.base(),
256 slot::SERVICE_SET_ACTIVE_BASE
257 );
258 assert_eq!(
259 TelemetrySlot::ServiceSetActive.len(),
260 slot::SERVICE_SET_ACTIVE_LEN
261 );
262 assert_eq!(
263 TelemetrySlot::ServiceSetActive.end(),
264 slot::SERVICE_SET_ACTIVE_END
265 );
266 }
267}