Skip to main content

riscv_aia/
lib.rs

1//! Rust support for RISC-V Advanced Interrupt Architecture (AIA).
2//!
3//! This crate follows _The RISC-V Advanced Interrupt Architecture_ specification, Version 1.0, Revised 20250312.  
4
5#![no_std]
6
7pub mod geilen;
8pub mod peripheral;
9pub mod register;
10
11use core::num::NonZeroU16;
12
13use riscv::InterruptNumber;
14
15/// RISC-V AIA Interrupt Identity (IID).
16///
17/// An IID is the encoded identity used by AIA/IMSIC to refer to an interrupt.
18/// Value `0` is reserved/invalid. Valid identities are in the range `1..=N`.
19/// The specification allows a platform-chosen `N` drawn from {63, 127, ..., 2047}
20/// (i.e., one less than a multiple of 64). This implementation conservatively
21/// assumes `N == 2047` unless a smaller limit is enforced elsewhere.
22///
23/// # Examples
24///
25/// ```
26/// # use riscv_aia::Iid;
27/// assert!(Iid::new(1).is_some());
28/// assert!(Iid::new(2047).is_some());
29/// assert!(Iid::new(0).is_none());
30/// assert!(Iid::new(3000).is_none());
31/// ```
32///
33/// ```
34/// # use riscv_aia::Iid;
35/// # mod mtopi {
36/// # use riscv_aia::Iid;
37/// # pub struct Mtopi;
38/// # impl Mtopi { pub fn iid(&self) -> Option<Iid> { None } }
39/// # pub fn read() -> Mtopi { Mtopi }
40/// # }
41/// // Read AIA interrupt ID from `mtopi` register.
42/// // On each read, `mtopi` returns the next interrupt we should process.
43/// while let Some(iid) = mtopi::read().iid() {
44///     if iid == Iid::MTIMER {
45///         // Handle machine timer interrupt in SBI implementation.
46///     } else if iid == Iid::MSOFT {
47///         // Handle machine software interrupt in SBI implementation.
48///     } else if iid == Iid::MEXT {
49///         // Handle machine external interrupt in SBI implementation.
50///     } else {
51///         // Default interrupt handler.
52///     }
53/// }
54/// ```
55///
56/// `Iid` can be converted into, or can be tried to convert from the `Interrupt` enum
57/// in the `riscv` crate:
58///
59/// ```
60/// # use riscv_aia::Iid;
61/// use riscv::interrupt::Interrupt;
62///
63/// let interrupt = Interrupt::MachineSoft;
64/// assert_eq!(Iid::MSOFT, interrupt.into());
65///
66/// let iid = Iid::MEXT;
67/// assert_eq!(Ok(Interrupt::MachineExternal), iid.try_into());
68/// ```
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
70#[repr(transparent)]
71pub struct Iid {
72    number: NonZeroU16,
73}
74
75impl Iid {
76    /// `Iid` for Supervisor software interrupt in standard RISC-V.
77    pub const SSOFT: Iid = Iid::new(1).unwrap();
78
79    /// `Iid` for Machine software interrupt in standard RISC-V.
80    pub const MSOFT: Iid = Iid::new(3).unwrap();
81
82    /// `Iid` for Supervisor timer interrupt in standard RISC-V.
83    pub const STIMER: Iid = Iid::new(5).unwrap();
84
85    /// `Iid` for Machine timer interrupt in standard RISC-V.
86    pub const MTIMER: Iid = Iid::new(7).unwrap();
87
88    /// `Iid` for Supervisor external interrupt in standard RISC-V.
89    pub const SEXT: Iid = Iid::new(9).unwrap();
90
91    /// `Iid` for Machine external interrupt in standard RISC-V.
92    pub const MEXT: Iid = Iid::new(11).unwrap();
93
94    /// Attempts to construct an [`Iid`] from `number`.
95    ///
96    /// Returns `Some(Iid)` when `1 <= number <= 2047`; returns `None` if
97    /// `number` is `0` or exceeds the assumed maximum.
98    #[inline]
99    pub const fn new(number: u16) -> Option<Iid> {
100        // Note: 2047 chosen for default software cap; platform may choose a smaller N
101        const IID_MAX: u16 = 2047;
102        // TODO: use Option filter-map once stablized in Rust's std.
103        match number {
104            1..=IID_MAX => match NonZeroU16::new(number) {
105                Some(nz) => Some(Iid { number: nz }),
106                None => None, // only hits when number == 0; kept to avoid unwraps in const
107            },
108            _ => None,
109        }
110    }
111
112    /// Returns the underlying interrupt identity number as `u16`.
113    #[inline]
114    pub const fn number(self) -> u16 {
115        self.number.get()
116    }
117}
118
119impl From<riscv::interrupt::Interrupt> for Iid {
120    #[inline]
121    fn from(value: riscv::interrupt::Interrupt) -> Self {
122        assert!(value.number() <= u16::MAX as usize && value.number() != 0);
123        Iid::new(value.number() as u16).unwrap()
124    }
125}
126
127impl TryFrom<Iid> for riscv::interrupt::Interrupt {
128    type Error = ();
129
130    #[inline]
131    fn try_from(value: Iid) -> Result<Self, Self::Error> {
132        use riscv::interrupt::Interrupt;
133        match value {
134            Iid::SSOFT => Ok(Interrupt::SupervisorSoft),
135            Iid::MSOFT => Ok(Interrupt::MachineSoft),
136            Iid::STIMER => Ok(Interrupt::SupervisorTimer),
137            Iid::MTIMER => Ok(Interrupt::MachineTimer),
138            Iid::SEXT => Ok(Interrupt::SupervisorExternal),
139            Iid::MEXT => Ok(Interrupt::MachineExternal),
140            _ => Err(()),
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn iid_new_bounds() {
151        assert!(Iid::new(0).is_none());
152        assert!(Iid::new(1).is_some());
153        assert!(Iid::new(2047).is_some());
154        assert!(Iid::new(2048).is_none());
155    }
156
157    #[test]
158    fn iid_consts() {
159        assert_eq!(Iid::SSOFT.number(), 1);
160        assert_eq!(Iid::MSOFT.number(), 3);
161        assert_eq!(Iid::STIMER.number(), 5);
162        assert_eq!(Iid::MTIMER.number(), 7);
163        assert_eq!(Iid::SEXT.number(), 9);
164        assert_eq!(Iid::MEXT.number(), 11);
165    }
166
167    #[test]
168    fn iid_usage_match_if() {
169        let iid = Iid::MEXT;
170        if iid == Iid::MTIMER {
171            unreachable!()
172        } else if iid == Iid::MSOFT {
173            unreachable!()
174        } else if iid == Iid::MEXT {
175            assert!(true)
176        } else {
177            unreachable!()
178        }
179
180        match iid {
181            Iid::MTIMER => unreachable!(),
182            Iid::MSOFT => unreachable!(),
183            Iid::MEXT => assert!(true),
184            _ => unreachable!(),
185        }
186
187        // Mock `mtopi::read` result where `iid: Option<Iid>`.
188        let iid = Some(Iid::MSOFT);
189        match iid {
190            Some(Iid::MTIMER) => unreachable!(),
191            Some(Iid::MSOFT) => assert!(true),
192            Some(Iid::MEXT) => unreachable!(),
193            // Redundant, can be `_ => unreachable!()` in real use.
194            Some(_) | None => unreachable!(),
195        }
196    }
197
198    #[test]
199    fn iid_convert_riscv_crate() {
200        use riscv::interrupt::Interrupt;
201        let irqs = [
202            (Interrupt::SupervisorExternal, Iid::SEXT),
203            (Interrupt::MachineExternal, Iid::MEXT),
204            (Interrupt::SupervisorSoft, Iid::SSOFT),
205            (Interrupt::MachineSoft, Iid::MSOFT),
206            (Interrupt::SupervisorTimer, Iid::STIMER),
207            (Interrupt::MachineTimer, Iid::MTIMER),
208        ];
209        for (riscv_irq, aia_iid) in irqs {
210            assert_eq!(aia_iid, Iid::from(riscv_irq));
211            assert_eq!(aia_iid, riscv_irq.into());
212            assert_eq!(aia_iid.try_into(), Ok(riscv_irq));
213            assert_eq!(Interrupt::try_from(aia_iid), Ok(riscv_irq));
214        }
215    }
216}