Skip to main content

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}