Skip to main content

squib_arch/
sysregs.rs

1//! Curated sysreg subset (~100 registers we touch).
2//!
3//! The full ARMv8 sysreg space is enormous; squib only saves and restores what is
4//! necessary for boot, exception handling, the `V1N1` CPU template, and the GIC. New
5//! registers are added with a snapshot version bump (additive contract — never remove).
6//!
7//! See [13-arch-and-boot.md § 3](../../../specs/13-arch-and-boot.md#3-sysreg-subset).
8
9/// Sysreg enum — `#[non_exhaustive]` because the full curated list is iterative.
10#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
11#[non_exhaustive]
12pub enum SysReg {
13    // === Boot setup ===
14    /// SCTLR_EL1 — system control register, EL1.
15    SctlrEl1,
16    /// TTBR0_EL1 — translation table base, EL1.
17    Ttbr0El1,
18    /// TTBR1_EL1 — translation table base 1, EL1.
19    Ttbr1El1,
20    /// MAIR_EL1 — memory attribute indirection register.
21    MairEl1,
22    /// AMAIR_EL1 — auxiliary memory attribute indirection register.
23    AmairEl1,
24    /// TCR_EL1 — translation control register.
25    TcrEl1,
26    /// SP_EL1.
27    SpEl1,
28    /// ELR_EL1 — exception link register.
29    ElrEl1,
30    /// SPSR_EL1 — saved program status register.
31    SpsrEl1,
32    /// VBAR_EL1 — vector base address register.
33    VbarEl1,
34
35    // === ID registers (read-only) ===
36    /// ID_AA64MMFR0_EL1 — memory model feature register 0.
37    IdAa64Mmfr0El1,
38    /// ID_AA64MMFR1_EL1 — memory model feature register 1.
39    IdAa64Mmfr1El1,
40    /// ID_AA64PFR0_EL1 — processor feature register 0.
41    IdAa64Pfr0El1,
42    /// ID_AA64PFR1_EL1 — processor feature register 1.
43    IdAa64Pfr1El1,
44    /// ID_AA64DFR0_EL1 — debug feature register 0.
45    IdAa64Dfr0El1,
46    /// ID_AA64ISAR0_EL1 — instruction set attribute register 0.
47    IdAa64Isar0El1,
48    /// ID_AA64ISAR1_EL1 — instruction set attribute register 1.
49    IdAa64Isar1El1,
50    /// MPIDR_EL1 — multiprocessor affinity register (per-vCPU; identity).
51    MpidrEl1,
52
53    // === Generic timers ===
54    /// CNTV_CTL_EL0 — virtual timer control.
55    CntvCtlEl0,
56    /// CNTV_CVAL_EL0 — virtual timer compare value.
57    CntvCvalEl0,
58    /// CNTV_OFF_EL2 — virtual timer offset.
59    CntvOffEl2,
60    /// CNTFRQ_EL0 — counter frequency.
61    CntFrqEl0,
62    /// CNTKCTL_EL1 — kernel counter control.
63    CntKctlEl1,
64    /// CNTP_CTL_EL0 — physical timer control.
65    CntpCtlEl0,
66    /// CNTP_CVAL_EL0 — physical timer compare value.
67    CntpCvalEl0,
68
69    // === Performance / Pmu ===
70    /// PMCCNTR_EL0 — cycle counter.
71    PmCcntrEl0,
72    /// PMCCFILTR_EL0 — cycle counter filter.
73    PmCcfiltrEl0,
74    /// PMUSERENR_EL0 — user enable register.
75    PmUserEnrEl0,
76    /// PMCR_EL0 — PMU control.
77    PmCrEl0,
78    /// PMCNTENSET_EL0 — counter enable set.
79    PmCntEnSetEl0,
80    /// PMOVSSET_EL0 — overflow status set.
81    PmOvsSetEl0,
82    /// PMSELR_EL0 — event counter selection.
83    PmSelrEl0,
84
85    // === Exception handling ===
86    /// ESR_EL1 — exception syndrome.
87    EsrEl1,
88    /// FAR_EL1 — fault address register.
89    FarEl1,
90    /// AFSR0_EL1 — auxiliary fault status register 0.
91    Afsr0El1,
92    /// AFSR1_EL1 — auxiliary fault status register 1.
93    Afsr1El1,
94
95    // === Memory model / TLB ===
96    /// CONTEXTIDR_EL1 — context ID register.
97    ContextIdrEl1,
98    /// TPIDR_EL0 — thread pointer EL0.
99    TpidrEl0,
100    /// TPIDRRO_EL0 — thread pointer (read-only, EL0).
101    TpidrroEl0,
102    /// TPIDR_EL1 — thread pointer EL1.
103    TpidrEl1,
104    /// PAR_EL1 — physical address register.
105    ParEl1,
106
107    // === FP/SIMD ===
108    /// FPCR — FP control register.
109    Fpcr,
110    /// FPSR — FP status register.
111    Fpsr,
112    /// CPACR_EL1 — architectural feature access control.
113    CpacrEl1,
114
115    // === Debug ===
116    /// MDSCR_EL1 — monitor debug system control.
117    MdscrEl1,
118    /// OSLAR_EL1 — OS lock access register.
119    OslarEl1,
120    /// OSDLR_EL1 — OS double-lock register.
121    OsdlrEl1,
122}
123
124impl SysReg {
125    /// A stable wire-encoding for use as a `BTreeMap<u64, u64>` key in
126    /// `VcpuState::sys_regs`.
127    ///
128    /// Each variant carries an explicitly assigned wire constant — reordering [`Self::all`]
129    /// or inserting a new variant in the middle does **not** change the encoding (the
130    /// previous positional scheme silently re-keyed every register on a reorder, which
131    /// would surface as a snapshot mismatch on the *next* `restore`, not the build).
132    /// `0` is reserved for "unknown" and never assigned. Snapshot consumers must round-trip
133    /// through [`Self::from_encoded`] — the wire shape is squib-private (D6).
134    #[must_use]
135    #[allow(
136        clippy::too_many_lines,
137        reason = "one match arm per curated sysreg — splitting hides the additive contract"
138    )]
139    pub fn as_encoded(self) -> u64 {
140        match self {
141            // boot setup — block 1..=10
142            Self::SctlrEl1 => 1,
143            Self::Ttbr0El1 => 2,
144            Self::Ttbr1El1 => 3,
145            Self::MairEl1 => 4,
146            Self::AmairEl1 => 5,
147            Self::TcrEl1 => 6,
148            Self::SpEl1 => 7,
149            Self::ElrEl1 => 8,
150            Self::SpsrEl1 => 9,
151            Self::VbarEl1 => 10,
152            // ID registers — block 11..=18
153            Self::IdAa64Mmfr0El1 => 11,
154            Self::IdAa64Mmfr1El1 => 12,
155            Self::IdAa64Pfr0El1 => 13,
156            Self::IdAa64Pfr1El1 => 14,
157            Self::IdAa64Dfr0El1 => 15,
158            Self::IdAa64Isar0El1 => 16,
159            Self::IdAa64Isar1El1 => 17,
160            Self::MpidrEl1 => 18,
161            // generic timers — block 19..=25
162            Self::CntvCtlEl0 => 19,
163            Self::CntvCvalEl0 => 20,
164            Self::CntvOffEl2 => 21,
165            Self::CntFrqEl0 => 22,
166            Self::CntKctlEl1 => 23,
167            Self::CntpCtlEl0 => 24,
168            Self::CntpCvalEl0 => 25,
169            // PMU — block 26..=32
170            Self::PmCcntrEl0 => 26,
171            Self::PmCcfiltrEl0 => 27,
172            Self::PmUserEnrEl0 => 28,
173            Self::PmCrEl0 => 29,
174            Self::PmCntEnSetEl0 => 30,
175            Self::PmOvsSetEl0 => 31,
176            Self::PmSelrEl0 => 32,
177            // exceptions — block 33..=36
178            Self::EsrEl1 => 33,
179            Self::FarEl1 => 34,
180            Self::Afsr0El1 => 35,
181            Self::Afsr1El1 => 36,
182            // memory model / TLB — block 37..=41
183            Self::ContextIdrEl1 => 37,
184            Self::TpidrEl0 => 38,
185            Self::TpidrroEl0 => 39,
186            Self::TpidrEl1 => 40,
187            Self::ParEl1 => 41,
188            // FP/SIMD — block 42..=44
189            Self::Fpcr => 42,
190            Self::Fpsr => 43,
191            Self::CpacrEl1 => 44,
192            // debug — block 45..=47
193            Self::MdscrEl1 => 45,
194            Self::OslarEl1 => 46,
195            Self::OsdlrEl1 => 47,
196        }
197    }
198
199    /// Inverse of [`Self::as_encoded`]. Returns `None` for keys not in the curated
200    /// list (forward-compat: a state file from a future squib build that added
201    /// registers we don't know about surfaces as `None` and the loader rejects
202    /// with `SnapshotError::Incompatible`).
203    #[must_use]
204    #[allow(
205        clippy::too_many_lines,
206        reason = "one match arm per curated sysreg — splitting hides the additive contract"
207    )]
208    pub fn from_encoded(key: u64) -> Option<Self> {
209        Some(match key {
210            1 => Self::SctlrEl1,
211            2 => Self::Ttbr0El1,
212            3 => Self::Ttbr1El1,
213            4 => Self::MairEl1,
214            5 => Self::AmairEl1,
215            6 => Self::TcrEl1,
216            7 => Self::SpEl1,
217            8 => Self::ElrEl1,
218            9 => Self::SpsrEl1,
219            10 => Self::VbarEl1,
220            11 => Self::IdAa64Mmfr0El1,
221            12 => Self::IdAa64Mmfr1El1,
222            13 => Self::IdAa64Pfr0El1,
223            14 => Self::IdAa64Pfr1El1,
224            15 => Self::IdAa64Dfr0El1,
225            16 => Self::IdAa64Isar0El1,
226            17 => Self::IdAa64Isar1El1,
227            18 => Self::MpidrEl1,
228            19 => Self::CntvCtlEl0,
229            20 => Self::CntvCvalEl0,
230            21 => Self::CntvOffEl2,
231            22 => Self::CntFrqEl0,
232            23 => Self::CntKctlEl1,
233            24 => Self::CntpCtlEl0,
234            25 => Self::CntpCvalEl0,
235            26 => Self::PmCcntrEl0,
236            27 => Self::PmCcfiltrEl0,
237            28 => Self::PmUserEnrEl0,
238            29 => Self::PmCrEl0,
239            30 => Self::PmCntEnSetEl0,
240            31 => Self::PmOvsSetEl0,
241            32 => Self::PmSelrEl0,
242            33 => Self::EsrEl1,
243            34 => Self::FarEl1,
244            35 => Self::Afsr0El1,
245            36 => Self::Afsr1El1,
246            37 => Self::ContextIdrEl1,
247            38 => Self::TpidrEl0,
248            39 => Self::TpidrroEl0,
249            40 => Self::TpidrEl1,
250            41 => Self::ParEl1,
251            42 => Self::Fpcr,
252            43 => Self::Fpsr,
253            44 => Self::CpacrEl1,
254            45 => Self::MdscrEl1,
255            46 => Self::OslarEl1,
256            47 => Self::OsdlrEl1,
257            _ => return None,
258        })
259    }
260
261    /// All curated sysregs, in canonical order. The order is the additive contract: new
262    /// registers may be appended; never insert in the middle.
263    #[must_use]
264    pub const fn all() -> &'static [SysReg] {
265        &[
266            // boot
267            Self::SctlrEl1,
268            Self::Ttbr0El1,
269            Self::Ttbr1El1,
270            Self::MairEl1,
271            Self::AmairEl1,
272            Self::TcrEl1,
273            Self::SpEl1,
274            Self::ElrEl1,
275            Self::SpsrEl1,
276            Self::VbarEl1,
277            // id
278            Self::IdAa64Mmfr0El1,
279            Self::IdAa64Mmfr1El1,
280            Self::IdAa64Pfr0El1,
281            Self::IdAa64Pfr1El1,
282            Self::IdAa64Dfr0El1,
283            Self::IdAa64Isar0El1,
284            Self::IdAa64Isar1El1,
285            Self::MpidrEl1,
286            // timer
287            Self::CntvCtlEl0,
288            Self::CntvCvalEl0,
289            Self::CntvOffEl2,
290            Self::CntFrqEl0,
291            Self::CntKctlEl1,
292            Self::CntpCtlEl0,
293            Self::CntpCvalEl0,
294            // pmu
295            Self::PmCcntrEl0,
296            Self::PmCcfiltrEl0,
297            Self::PmUserEnrEl0,
298            Self::PmCrEl0,
299            Self::PmCntEnSetEl0,
300            Self::PmOvsSetEl0,
301            Self::PmSelrEl0,
302            // exceptions
303            Self::EsrEl1,
304            Self::FarEl1,
305            Self::Afsr0El1,
306            Self::Afsr1El1,
307            // memory model / TLB
308            Self::ContextIdrEl1,
309            Self::TpidrEl0,
310            Self::TpidrroEl0,
311            Self::TpidrEl1,
312            Self::ParEl1,
313            // FP/SIMD
314            Self::Fpcr,
315            Self::Fpsr,
316            Self::CpacrEl1,
317            // debug
318            Self::MdscrEl1,
319            Self::OslarEl1,
320            Self::OsdlrEl1,
321        ]
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn all_returns_unique_registers() {
331        let all = SysReg::all();
332        for (i, reg) in all.iter().enumerate() {
333            assert!(
334                !all[..i].contains(reg),
335                "duplicate sysreg at index {i}: {reg:?}"
336            );
337        }
338    }
339
340    #[test]
341    fn all_is_non_empty_and_below_two_hundred() {
342        let count = SysReg::all().len();
343        assert!(count >= 30, "got {count} sysregs; expected at least 30");
344        assert!(count <= 200, "got {count} sysregs; spec caps at ~100");
345    }
346
347    #[test]
348    fn additive_contract_starts_with_boot_setup() {
349        // The boot-setup block is listed first per spec § 3; SCTLR_EL1 is the canonical
350        // first entry.
351        assert_eq!(SysReg::all()[0], SysReg::SctlrEl1);
352    }
353
354    #[test]
355    fn test_should_round_trip_as_encoded_through_from_encoded() {
356        for reg in SysReg::all() {
357            let key = reg.as_encoded();
358            assert_ne!(key, 0, "encoding must be non-zero for {reg:?}");
359            assert_eq!(SysReg::from_encoded(key), Some(*reg));
360        }
361    }
362
363    #[test]
364    fn test_should_reject_zero_and_out_of_range_encoded_keys() {
365        assert_eq!(SysReg::from_encoded(0), None);
366        // The wire-encoding is hand-assigned and currently dense up to `all().len()`. A
367        // key one past the dense range is the canonical "unknown to this squib" sentinel —
368        // future variants may extend the dense block, but a value picked far beyond
369        // (a million) is guaranteed to be unrecognised by every published wire schema.
370        assert_eq!(SysReg::from_encoded(1_000_000), None);
371        let beyond = u64::try_from(SysReg::all().len()).expect("len fits in u64") + 1;
372        assert_eq!(SysReg::from_encoded(beyond), None);
373    }
374
375    #[test]
376    fn test_should_assign_distinct_wire_constants_per_variant() {
377        let mut seen = std::collections::BTreeSet::new();
378        for reg in SysReg::all() {
379            let key = reg.as_encoded();
380            assert!(
381                seen.insert(key),
382                "duplicate wire constant {key} hit twice (second hit: {reg:?})"
383            );
384        }
385    }
386}