1use thiserror::Error;
13
14pub const SPI_INTID_MAX: u32 = 1019;
17
18pub const PPI_INTID_MIN: u32 = 16;
20pub const PPI_INTID_MAX: u32 = 31;
22
23pub const SPI_INTID_MIN: u32 = 32;
25
26pub const FDT_CELL_TYPE_SPI: u32 = 0;
28pub const FDT_CELL_TYPE_PPI: u32 = 1;
30
31pub const FDT_CELL_FLAGS_EDGE_RISING: u32 = 1;
33pub const FDT_CELL_FLAGS_LEVEL_HIGH: u32 = 4;
35pub const FDT_CELL_FLAGS_LEVEL_LOW: u32 = 8;
37
38#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
40pub enum Trigger {
41 EdgeRising,
43 LevelHigh,
45 LevelLow,
47}
48
49impl Trigger {
50 #[must_use]
52 pub const fn fdt_flags(self) -> u32 {
53 match self {
54 Self::EdgeRising => FDT_CELL_FLAGS_EDGE_RISING,
55 Self::LevelHigh => FDT_CELL_FLAGS_LEVEL_HIGH,
56 Self::LevelLow => FDT_CELL_FLAGS_LEVEL_LOW,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Eq, PartialEq, Error)]
63pub enum IntIdError {
64 #[error("invalid SPI intid {0}: must be in range 32..={SPI_INTID_MAX}")]
66 SpiOutOfRange(u32),
67 #[error("invalid PPI intid {0}: must be in range 16..=31")]
69 PpiOutOfRange(u32),
70 #[error("invalid SPI cell offset {0}: would map to an INTID > {SPI_INTID_MAX}")]
72 SpiCellOffsetOutOfRange(u32),
73 #[error("invalid PPI cell offset {0}: must be in range 0..=15 (cells map to INTIDs 16..=31)")]
75 PpiCellOffsetOutOfRange(u32),
76}
77
78#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
84pub struct IntId(u32);
85
86impl IntId {
87 pub const fn from_spi_intid(intid: u32) -> Result<Self, IntIdError> {
93 if intid < SPI_INTID_MIN || intid > SPI_INTID_MAX {
94 return Err(IntIdError::SpiOutOfRange(intid));
95 }
96 Ok(Self(intid))
97 }
98
99 pub const fn from_ppi_intid(intid: u32) -> Result<Self, IntIdError> {
104 if intid < PPI_INTID_MIN || intid > PPI_INTID_MAX {
105 return Err(IntIdError::PpiOutOfRange(intid));
106 }
107 Ok(Self(intid))
108 }
109
110 pub const fn from_spi_cell(cell_offset: u32) -> Result<Self, IntIdError> {
118 let Some(intid) = SPI_INTID_MIN.checked_add(cell_offset) else {
119 return Err(IntIdError::SpiCellOffsetOutOfRange(cell_offset));
120 };
121 if intid > SPI_INTID_MAX {
122 return Err(IntIdError::SpiCellOffsetOutOfRange(cell_offset));
123 }
124 Ok(Self(intid))
125 }
126
127 pub const fn from_ppi_cell(cell_offset: u32) -> Result<Self, IntIdError> {
132 if cell_offset > 15 {
133 return Err(IntIdError::PpiCellOffsetOutOfRange(cell_offset));
134 }
135 Ok(Self(PPI_INTID_MIN + cell_offset))
136 }
137
138 #[must_use]
140 pub const fn as_raw(self) -> u32 {
141 self.0
142 }
143
144 #[must_use]
146 pub const fn is_spi(self) -> bool {
147 self.0 >= SPI_INTID_MIN && self.0 <= SPI_INTID_MAX
148 }
149
150 #[must_use]
152 pub const fn is_ppi(self) -> bool {
153 self.0 >= PPI_INTID_MIN && self.0 <= PPI_INTID_MAX
154 }
155
156 #[must_use]
163 pub const fn fdt_cell_type(self) -> u32 {
164 if self.is_ppi() {
165 FDT_CELL_TYPE_PPI
166 } else {
167 FDT_CELL_TYPE_SPI
168 }
169 }
170
171 #[must_use]
173 pub const fn fdt_cell_offset(self) -> u32 {
174 if self.is_ppi() {
175 self.0 - PPI_INTID_MIN
176 } else {
177 self.0 - SPI_INTID_MIN
178 }
179 }
180}
181
182pub mod fixed {
188 use super::{IntId, PPI_INTID_MIN, SPI_INTID_MIN};
189
190 pub const PL011: IntId = IntId(SPI_INTID_MIN + 1);
192 pub const VIRTIO_MMIO_SLOT0: IntId = IntId(SPI_INTID_MIN + 16);
194 pub const CNTV: IntId = IntId(PPI_INTID_MIN + 11);
196 pub const CNTHP: IntId = IntId(PPI_INTID_MIN + 10);
198 pub const CNTP: IntId = IntId(PPI_INTID_MIN + 14);
200 pub const CNTPS: IntId = IntId(PPI_INTID_MIN + 13);
202
203 const _: () = {
206 assert!(PL011.as_raw() == 33);
207 assert!(VIRTIO_MMIO_SLOT0.as_raw() == 48);
208 assert!(CNTV.as_raw() == 27);
209 assert!(CNTHP.as_raw() == 26);
210 assert!(CNTP.as_raw() == 30);
211 assert!(CNTPS.as_raw() == 29);
212 };
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn pl011_constants_match_spec() {
221 let pl011 = IntId::from_spi_cell(1).unwrap();
222 assert_eq!(pl011.as_raw(), 33);
223 assert_eq!(pl011.fdt_cell_type(), FDT_CELL_TYPE_SPI);
224 assert_eq!(pl011.fdt_cell_offset(), 1);
225 assert_eq!(fixed::PL011, pl011);
226 }
227
228 #[test]
229 fn virtio_slot_n_maps_to_intid_48_plus_n() {
230 for slot in 0..32u32 {
231 let id = IntId::from_spi_cell(16 + slot).unwrap();
232 assert_eq!(id.as_raw(), 48 + slot, "slot {slot}");
233 assert_eq!(id.fdt_cell_offset(), 16 + slot, "slot {slot}");
234 }
235 }
236
237 #[test]
238 fn timer_ppi_constants_match_spec() {
239 assert_eq!(fixed::CNTV.as_raw(), 27);
240 assert_eq!(fixed::CNTHP.as_raw(), 26);
241 assert_eq!(fixed::CNTP.as_raw(), 30);
242 assert_eq!(fixed::CNTPS.as_raw(), 29);
243 }
244
245 #[test]
246 fn ppi_round_trips_through_cell_offset() {
247 for offset in 0..=15u32 {
248 let id = IntId::from_ppi_cell(offset).unwrap();
249 assert!(id.is_ppi());
250 assert_eq!(id.fdt_cell_offset(), offset);
251 assert_eq!(id.fdt_cell_type(), FDT_CELL_TYPE_PPI);
252 }
253 }
254
255 #[test]
256 fn spi_round_trips_through_cell_offset() {
257 for offset in [0, 1, 16, 47, 100, 987] {
258 let id = IntId::from_spi_cell(offset).unwrap();
259 assert!(id.is_spi());
260 assert_eq!(id.fdt_cell_offset(), offset);
261 assert_eq!(id.fdt_cell_type(), FDT_CELL_TYPE_SPI);
262 }
263 }
264
265 #[test]
266 fn ppi_cell_offset_above_15_rejected() {
267 assert!(matches!(
268 IntId::from_ppi_cell(16),
269 Err(IntIdError::PpiCellOffsetOutOfRange(16))
270 ));
271 }
272
273 #[test]
274 fn spi_cell_offset_overflow_rejected() {
275 assert!(matches!(
277 IntId::from_spi_cell(988),
278 Err(IntIdError::SpiCellOffsetOutOfRange(988))
279 ));
280 assert!(matches!(
282 IntId::from_spi_cell(u32::MAX),
283 Err(IntIdError::SpiCellOffsetOutOfRange(_))
284 ));
285 }
286
287 #[test]
288 fn raw_intid_constructors_validate_ranges() {
289 assert!(IntId::from_spi_intid(31).is_err()); assert!(IntId::from_spi_intid(32).is_ok());
291 assert!(IntId::from_spi_intid(SPI_INTID_MAX).is_ok());
292 assert!(IntId::from_spi_intid(SPI_INTID_MAX + 1).is_err());
293
294 assert!(IntId::from_ppi_intid(15).is_err());
295 assert!(IntId::from_ppi_intid(16).is_ok());
296 assert!(IntId::from_ppi_intid(31).is_ok());
297 assert!(IntId::from_ppi_intid(32).is_err()); }
299
300 #[test]
301 fn trigger_fdt_flags_match_spec() {
302 assert_eq!(Trigger::EdgeRising.fdt_flags(), 1);
303 assert_eq!(Trigger::LevelHigh.fdt_flags(), 4);
304 assert_eq!(Trigger::LevelLow.fdt_flags(), 8);
305 }
306}