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 240 MHz
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!(
56    crate::soc::ws63::SYSTEM_CLOCK_HZ == 240_000_000,
57    "SYSTEM_CLOCK_HZ must be 240MHz — timer tick calculations assume this"
58);
59// Verify that the maximum safe us value for timer is computable at 240MHz
60const MAX_SAFE_TIMER_US: u64 = u32::MAX as u64 / 240;
61const_assert!(MAX_SAFE_TIMER_US > 17_000_000, "Timer max safe period must cover at least 17 seconds");
62
63// ── Type-level safety invariant helpers ──────────────────────────
64
65/// Newtype proving a value is a valid peripheral index (0-16).
66/// Can be constructed via `PeripheralIndex::try_from(peripheral as usize)`.
67#[derive(Debug, Clone, Copy)]
68pub struct PeripheralIndex(u8);
69
70impl PeripheralIndex {
71    /// SAFETY: `idx` must be < PERIPHERAL_COUNT (17).
72    #[allow(clippy::missing_safety_doc)]
73    pub const unsafe fn new_unchecked(idx: u8) -> Self {
74        PeripheralIndex(idx)
75    }
76
77    pub const fn get(&self) -> usize {
78        self.0 as usize
79    }
80}
81
82impl TryFrom<usize> for PeripheralIndex {
83    type Error = ();
84    fn try_from(idx: usize) -> Result<Self, ()> {
85        if idx < PERIPHERAL_COUNT { Ok(PeripheralIndex(idx as u8)) } else { Err(()) }
86    }
87}
88
89/// Newtype proving a value is a valid GPIO pin number (0-18).
90#[derive(Debug, Clone, Copy)]
91pub struct GpioPinIndex(#[allow(dead_code)] u8);
92
93impl GpioPinIndex {
94    pub const fn new(pin: u8) -> Option<Self> {
95        if pin < crate::soc::ws63::GPIO_COUNT as u8 { Some(GpioPinIndex(pin)) } else { None }
96    }
97}
98
99// ── Tests ────────────────────────────────────────────────────────
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_peripheral_index_bounds() {
107        assert!(PeripheralIndex::try_from(0).is_ok());
108        assert!(PeripheralIndex::try_from(16).is_ok());
109        assert!(PeripheralIndex::try_from(17).is_err());
110    }
111
112    #[test]
113    fn test_gpio_pin_bounds() {
114        assert!(GpioPinIndex::new(0).is_some());
115        assert!(GpioPinIndex::new(18).is_some());
116        assert!(GpioPinIndex::new(19).is_none());
117    }
118
119    #[test]
120    fn test_soc_constants_consistency() {
121        use crate::soc::ws63;
122        assert_eq!(ws63::SYSTEM_CLOCK_HZ, 240_000_000);
123        assert_eq!(ws63::TIMER_COUNT, 3);
124        assert_eq!(ws63::PWM_CHANNEL_COUNT, 8);
125        assert_eq!(ws63::DMA_CHANNEL_COUNT, 4);
126        assert_eq!(ws63::SPI_COUNT, 2);
127        assert_eq!(ws63::UART_COUNT, 3);
128        assert_eq!(ws63::I2C_COUNT, 2);
129        assert_eq!(ws63::GPIO_COUNT, 19);
130        assert_eq!(ws63::ULP_GPIO_COUNT, 8);
131        assert_eq!(ws63::LSADC_CHANNEL_COUNT, 6);
132    }
133
134    #[test]
135    fn test_max_safe_timer_us() {
136        // At 240MHz, max safe period without overflow: u32::MAX / 240
137        let max_safe: u64 = u32::MAX as u64 / 240;
138        assert!(max_safe > 17_000_000); // at least 17 seconds
139        let overflow: u64 = 240_000_000u64 * (max_safe as u64 + 1) / 1_000_000;
140        assert!(overflow > u32::MAX as u64); // beyond safe range overflows
141    }
142
143    #[test]
144    fn test_pwm_channel_count_fits_u8() {
145        // 8 PWM channels max, channel index 0-7
146        assert!(crate::soc::ws63::PWM_CHANNEL_COUNT <= 8);
147    }
148
149    #[test]
150    fn test_dma_channel_bound_check() {
151        // DMA channels 0-3 are valid, 4+ is out of bounds
152        assert!(crate::soc::ws63::DMA_CHANNEL_COUNT == 4);
153        for ch in 0u8..4 {
154            assert!(ch < crate::soc::ws63::DMA_CHANNEL_COUNT as u8);
155        }
156        assert!(4u8 >= crate::soc::ws63::DMA_CHANNEL_COUNT as u8);
157    }
158}