Skip to main content

ws63_hal/
safety.rs

1//! Compile-time safety checks for ws63-hal.
2//!
3//! Provides:
4//! 1. Const assertions that key peripheral MMIO addresses are within range and
5//!    that timer-tick arithmetic cannot overflow at the timer clock (24 MHz TCXO)
6//! 2. Newtype helpers (`PeripheralIndex`, `GpioPinIndex`) for bounds-checked indices
7//!
8//! Tautological `const X == <literal>` count assertions were removed: pinning a
9//! `soc` constant to a duplicated magic number verifies nothing about the code
10//! that indexes with it.
11
12// ── Compile-time assertions ──────────────────────────────────────
13
14/// Compile-time assertion. If `$cond` is false, compilation fails with `$msg`.
15macro_rules! const_assert {
16    ($cond:expr, $msg:expr) => {
17        const _: () = {
18            #[allow(dead_code, clippy::unit_arg)]
19            const ASSERT: () = if !$cond {
20                panic!($msg)
21            };
22        };
23    };
24}
25
26/// Assert that a pointer is 4-byte aligned (RISC-V MMIO requirement).
27#[allow(unused_macros)]
28macro_rules! ptr_aligned {
29    ($ptr:expr) => {
30        const_assert!($ptr as usize % 4 == 0, "MMIO pointer must be 4-byte aligned");
31    };
32}
33
34// ── Verify PAC peripheral addresses ──────────────────────────────
35
36/// All PAC peripherals must be within the WS63 MMIO address range.
37const MMIO_LOW: usize = 0x4000_0000;
38const MMIO_HIGH: usize = 0x5704_0000; // ULP_GPIO at 0x5703_0000
39
40// Verify key peripheral addresses are within range
41const_assert!(
42    0x4401_0000 >= MMIO_LOW && 0x4401_2000 <= MMIO_HIGH,
43    "UART region (UART0@0x4401_0000..UART2@0x4401_2000) out of MMIO range"
44);
45const_assert!(0x4000_6000 >= MMIO_LOW && 0x4000_6100 <= MMIO_HIGH, "WDT region (0x4000_6000) out of MMIO range");
46const_assert!(0x4800_0000 >= MMIO_LOW && 0x4800_0100 <= MMIO_HIGH, "SFC base out of MMIO range");
47const_assert!(0x4A00_0000 >= MMIO_LOW && 0x4A00_0000 <= MMIO_HIGH, "DMA base out of MMIO range");
48const_assert!(0x4410_0000 >= MMIO_LOW && 0x4411_4000 <= MMIO_HIGH, "Crypto base out of MMIO range");
49
50// PERIPHERAL_COUNT (clock.rs) bounds the PeripheralIndex newtype below.
51use crate::clock::PERIPHERAL_COUNT;
52
53// ── Verify timer tick arithmetic doesn't overflow at compile time ─
54
55const_assert!(crate::soc::ws63::SYSTEM_CLOCK_HZ == 240_000_000, "SYSTEM_CLOCK_HZ must be 240MHz (CPU/PLL clock)");
56// The Timer/WDT count at the TCXO crystal (TIMER_CLOCK_HZ), not the CPU clock.
57const_assert!(
58    crate::soc::ws63::TIMER_CLOCK_HZ.is_multiple_of(1_000_000) && crate::soc::ws63::TIMER_CLOCK_HZ >= 1_000_000,
59    "TIMER_CLOCK_HZ must be a whole number of MHz so us->ticks is exact"
60);
61// Verify the maximum safe us value for the timer is computable at the timer clock.
62const MAX_SAFE_TIMER_US: u64 = u32::MAX as u64 / (crate::soc::ws63::TIMER_CLOCK_HZ as u64 / 1_000_000);
63const_assert!(MAX_SAFE_TIMER_US > 17_000_000, "Timer max safe period must cover at least 17 seconds");
64
65// ── Type-level safety invariant helpers ──────────────────────────
66
67/// Newtype proving a value is a valid peripheral index (0-16).
68/// Can be constructed via `PeripheralIndex::try_from(peripheral as usize)`.
69#[derive(Debug, Clone, Copy)]
70pub struct PeripheralIndex(u8);
71
72impl PeripheralIndex {
73    /// SAFETY: `idx` must be < PERIPHERAL_COUNT (17).
74    #[allow(clippy::missing_safety_doc)]
75    pub const unsafe fn new_unchecked(idx: u8) -> Self {
76        PeripheralIndex(idx)
77    }
78
79    pub const fn get(&self) -> usize {
80        self.0 as usize
81    }
82}
83
84impl TryFrom<usize> for PeripheralIndex {
85    type Error = ();
86    fn try_from(idx: usize) -> Result<Self, ()> {
87        if idx < PERIPHERAL_COUNT { Ok(PeripheralIndex(idx as u8)) } else { Err(()) }
88    }
89}
90
91/// Newtype proving a value is a valid GPIO pin number (0-18).
92#[derive(Debug, Clone, Copy)]
93pub struct GpioPinIndex(#[allow(dead_code)] u8);
94
95impl GpioPinIndex {
96    pub const fn new(pin: u8) -> Option<Self> {
97        if pin < crate::soc::ws63::GPIO_COUNT as u8 { Some(GpioPinIndex(pin)) } else { None }
98    }
99}
100
101// ── Tests ────────────────────────────────────────────────────────
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_peripheral_index_bounds() {
109        assert!(PeripheralIndex::try_from(0).is_ok());
110        assert!(PeripheralIndex::try_from(16).is_ok());
111        assert!(PeripheralIndex::try_from(17).is_err());
112    }
113
114    #[test]
115    fn test_gpio_pin_bounds() {
116        assert!(GpioPinIndex::new(0).is_some());
117        assert!(GpioPinIndex::new(18).is_some());
118        assert!(GpioPinIndex::new(19).is_none());
119    }
120
121    #[test]
122    fn test_soc_constants_consistency() {
123        use crate::soc::ws63;
124        assert_eq!(ws63::SYSTEM_CLOCK_HZ, 240_000_000);
125        assert_eq!(ws63::TIMER_COUNT, 3);
126        assert_eq!(ws63::PWM_CHANNEL_COUNT, 8);
127        assert_eq!(ws63::DMA_CHANNEL_COUNT, 4);
128        assert_eq!(ws63::SPI_COUNT, 2);
129        assert_eq!(ws63::UART_COUNT, 3);
130        assert_eq!(ws63::I2C_COUNT, 2);
131        assert_eq!(ws63::GPIO_COUNT, 19);
132        assert_eq!(ws63::ULP_GPIO_COUNT, 8);
133        assert_eq!(ws63::LSADC_CHANNEL_COUNT, 6);
134    }
135
136    #[test]
137    fn test_max_safe_timer_us() {
138        // The timer counts at the TCXO crystal (TIMER_CLOCK_HZ, 24 MHz), so the
139        // max safe one-shot period without u32 overflow is u32::MAX / ticks_per_us.
140        let ticks_per_us = crate::soc::ws63::TIMER_CLOCK_HZ as u64 / 1_000_000;
141        let max_safe: u64 = u32::MAX as u64 / ticks_per_us;
142        assert!(max_safe > 17_000_000); // at least 17 seconds
143        let overflow: u64 = crate::soc::ws63::TIMER_CLOCK_HZ as u64 * (max_safe + 1) / 1_000_000;
144        assert!(overflow > u32::MAX as u64); // beyond safe range overflows
145    }
146
147    #[test]
148    fn test_pwm_channel_count_fits_u8() {
149        // 8 PWM channels max, channel index 0-7
150        assert!(crate::soc::ws63::PWM_CHANNEL_COUNT <= 8);
151    }
152
153    #[test]
154    fn test_dma_channel_bound_check() {
155        // DMA channels 0-3 are valid, 4+ is out of bounds
156        assert!(crate::soc::ws63::DMA_CHANNEL_COUNT == 4);
157        for ch in 0u8..4 {
158            assert!(ch < crate::soc::ws63::DMA_CHANNEL_COUNT as u8);
159        }
160        assert!(4u8 >= crate::soc::ws63::DMA_CHANNEL_COUNT as u8);
161    }
162}