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}