use thiserror::Error;
pub const SPI_INTID_MAX: u32 = 1019;
pub const PPI_INTID_MIN: u32 = 16;
pub const PPI_INTID_MAX: u32 = 31;
pub const SPI_INTID_MIN: u32 = 32;
pub const FDT_CELL_TYPE_SPI: u32 = 0;
pub const FDT_CELL_TYPE_PPI: u32 = 1;
pub const FDT_CELL_FLAGS_EDGE_RISING: u32 = 1;
pub const FDT_CELL_FLAGS_LEVEL_HIGH: u32 = 4;
pub const FDT_CELL_FLAGS_LEVEL_LOW: u32 = 8;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Trigger {
EdgeRising,
LevelHigh,
LevelLow,
}
impl Trigger {
#[must_use]
pub const fn fdt_flags(self) -> u32 {
match self {
Self::EdgeRising => FDT_CELL_FLAGS_EDGE_RISING,
Self::LevelHigh => FDT_CELL_FLAGS_LEVEL_HIGH,
Self::LevelLow => FDT_CELL_FLAGS_LEVEL_LOW,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Error)]
pub enum IntIdError {
#[error("invalid SPI intid {0}: must be in range 32..={SPI_INTID_MAX}")]
SpiOutOfRange(u32),
#[error("invalid PPI intid {0}: must be in range 16..=31")]
PpiOutOfRange(u32),
#[error("invalid SPI cell offset {0}: would map to an INTID > {SPI_INTID_MAX}")]
SpiCellOffsetOutOfRange(u32),
#[error("invalid PPI cell offset {0}: must be in range 0..=15 (cells map to INTIDs 16..=31)")]
PpiCellOffsetOutOfRange(u32),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct IntId(u32);
impl IntId {
pub const fn from_spi_intid(intid: u32) -> Result<Self, IntIdError> {
if intid < SPI_INTID_MIN || intid > SPI_INTID_MAX {
return Err(IntIdError::SpiOutOfRange(intid));
}
Ok(Self(intid))
}
pub const fn from_ppi_intid(intid: u32) -> Result<Self, IntIdError> {
if intid < PPI_INTID_MIN || intid > PPI_INTID_MAX {
return Err(IntIdError::PpiOutOfRange(intid));
}
Ok(Self(intid))
}
pub const fn from_spi_cell(cell_offset: u32) -> Result<Self, IntIdError> {
let Some(intid) = SPI_INTID_MIN.checked_add(cell_offset) else {
return Err(IntIdError::SpiCellOffsetOutOfRange(cell_offset));
};
if intid > SPI_INTID_MAX {
return Err(IntIdError::SpiCellOffsetOutOfRange(cell_offset));
}
Ok(Self(intid))
}
pub const fn from_ppi_cell(cell_offset: u32) -> Result<Self, IntIdError> {
if cell_offset > 15 {
return Err(IntIdError::PpiCellOffsetOutOfRange(cell_offset));
}
Ok(Self(PPI_INTID_MIN + cell_offset))
}
#[must_use]
pub const fn as_raw(self) -> u32 {
self.0
}
#[must_use]
pub const fn is_spi(self) -> bool {
self.0 >= SPI_INTID_MIN && self.0 <= SPI_INTID_MAX
}
#[must_use]
pub const fn is_ppi(self) -> bool {
self.0 >= PPI_INTID_MIN && self.0 <= PPI_INTID_MAX
}
#[must_use]
pub const fn fdt_cell_type(self) -> u32 {
if self.is_ppi() {
FDT_CELL_TYPE_PPI
} else {
FDT_CELL_TYPE_SPI
}
}
#[must_use]
pub const fn fdt_cell_offset(self) -> u32 {
if self.is_ppi() {
self.0 - PPI_INTID_MIN
} else {
self.0 - SPI_INTID_MIN
}
}
}
pub mod fixed {
use super::{IntId, PPI_INTID_MIN, SPI_INTID_MIN};
pub const PL011: IntId = IntId(SPI_INTID_MIN + 1);
pub const VIRTIO_MMIO_SLOT0: IntId = IntId(SPI_INTID_MIN + 16);
pub const CNTV: IntId = IntId(PPI_INTID_MIN + 11);
pub const CNTHP: IntId = IntId(PPI_INTID_MIN + 10);
pub const CNTP: IntId = IntId(PPI_INTID_MIN + 14);
pub const CNTPS: IntId = IntId(PPI_INTID_MIN + 13);
const _: () = {
assert!(PL011.as_raw() == 33);
assert!(VIRTIO_MMIO_SLOT0.as_raw() == 48);
assert!(CNTV.as_raw() == 27);
assert!(CNTHP.as_raw() == 26);
assert!(CNTP.as_raw() == 30);
assert!(CNTPS.as_raw() == 29);
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pl011_constants_match_spec() {
let pl011 = IntId::from_spi_cell(1).unwrap();
assert_eq!(pl011.as_raw(), 33);
assert_eq!(pl011.fdt_cell_type(), FDT_CELL_TYPE_SPI);
assert_eq!(pl011.fdt_cell_offset(), 1);
assert_eq!(fixed::PL011, pl011);
}
#[test]
fn virtio_slot_n_maps_to_intid_48_plus_n() {
for slot in 0..32u32 {
let id = IntId::from_spi_cell(16 + slot).unwrap();
assert_eq!(id.as_raw(), 48 + slot, "slot {slot}");
assert_eq!(id.fdt_cell_offset(), 16 + slot, "slot {slot}");
}
}
#[test]
fn timer_ppi_constants_match_spec() {
assert_eq!(fixed::CNTV.as_raw(), 27);
assert_eq!(fixed::CNTHP.as_raw(), 26);
assert_eq!(fixed::CNTP.as_raw(), 30);
assert_eq!(fixed::CNTPS.as_raw(), 29);
}
#[test]
fn ppi_round_trips_through_cell_offset() {
for offset in 0..=15u32 {
let id = IntId::from_ppi_cell(offset).unwrap();
assert!(id.is_ppi());
assert_eq!(id.fdt_cell_offset(), offset);
assert_eq!(id.fdt_cell_type(), FDT_CELL_TYPE_PPI);
}
}
#[test]
fn spi_round_trips_through_cell_offset() {
for offset in [0, 1, 16, 47, 100, 987] {
let id = IntId::from_spi_cell(offset).unwrap();
assert!(id.is_spi());
assert_eq!(id.fdt_cell_offset(), offset);
assert_eq!(id.fdt_cell_type(), FDT_CELL_TYPE_SPI);
}
}
#[test]
fn ppi_cell_offset_above_15_rejected() {
assert!(matches!(
IntId::from_ppi_cell(16),
Err(IntIdError::PpiCellOffsetOutOfRange(16))
));
}
#[test]
fn spi_cell_offset_overflow_rejected() {
assert!(matches!(
IntId::from_spi_cell(988),
Err(IntIdError::SpiCellOffsetOutOfRange(988))
));
assert!(matches!(
IntId::from_spi_cell(u32::MAX),
Err(IntIdError::SpiCellOffsetOutOfRange(_))
));
}
#[test]
fn raw_intid_constructors_validate_ranges() {
assert!(IntId::from_spi_intid(31).is_err()); assert!(IntId::from_spi_intid(32).is_ok());
assert!(IntId::from_spi_intid(SPI_INTID_MAX).is_ok());
assert!(IntId::from_spi_intid(SPI_INTID_MAX + 1).is_err());
assert!(IntId::from_ppi_intid(15).is_err());
assert!(IntId::from_ppi_intid(16).is_ok());
assert!(IntId::from_ppi_intid(31).is_ok());
assert!(IntId::from_ppi_intid(32).is_err()); }
#[test]
fn trigger_fdt_flags_match_spec() {
assert_eq!(Trigger::EdgeRising.fdt_flags(), 1);
assert_eq!(Trigger::LevelHigh.fdt_flags(), 4);
assert_eq!(Trigger::LevelLow.fdt_flags(), 8);
}
}