Skip to main content

ws63_hal/
interrupt.rs

1//! WS63 interrupt controller — riscv31 *custom local interrupts* (NOT a PLIC).
2//!
3//! WS63's application core is a HiSilicon "riscv31" hart with **no PLIC**. There
4//! are two interrupt tiers, mirroring fbb_ws63
5//! `drivers/chips/ws63/arch/riscv/riscv31/interrupt.c`:
6//!
7//! * **IRQ 26..=31** — standard machine *local* interrupts, enabled by the
8//!   matching bit of the standard `mie` CSR (bit index == IRQ number).
9//! * **IRQ >= 32** — HiSilicon custom local interrupts, gated by the custom
10//!   CSRs `LOCIEN0..2` (`0xBE0..2`, enable, 32 IRQs/reg from base 32),
11//!   `LOCIPD0..` (`0xBE8..`, pending) and cleared by writing the IRQ number to
12//!   `LOCIPCLR` (`0xBF0`). Each local IRQ (from base **26**) also has a 4-bit
13//!   priority in `LOCIPRI0..15` (`0xBC0..`, 8 IRQs/reg); a global threshold
14//!   lives in `PRITHD` (`0xBFE`). An IRQ is delivered only when it is enabled
15//!   **and** its priority is strictly greater than the threshold (ties broken
16//!   by the lowest IRQ number). Reset default priority is 1, threshold 0.
17//!
18//! This module owns only the interrupt *controller* (mask / priority /
19//! threshold / pending / clear). The trap vector table is the runtime's concern
20//! (`ws63-rt`); the `timer_irq` (mie path) and `gpio_irq` (LOCIEN + LOCIPCLR
21//! path) examples drive this API end-to-end and are exercised on ws63-qemu.
22
23// On non-riscv (host) builds the CSR access compiles to no-op stubs, leaving the
24// surrounding `unsafe` blocks empty — silence the resulting lints there.
25#![cfg_attr(not(target_arch = "riscv32"), allow(unused_unsafe, unused_variables, unused_mut))]
26
27pub use crate::soc::ws63::Interrupt;
28
29// --- model constants (fbb_ws63 vectors.h / riscv_interrupt.h) ---------------
30/// First IRQ with a configurable `LOCIPRI` priority (IRQs 0..26 are system vectors).
31const SYS_VECTOR_CNT: u16 = 26;
32/// IRQs below this use `mie` bits; IRQs at/above it use the `LOCIEN*` custom CSRs.
33const LOCAL_IRQ_VECTOR_CNT: u16 = 32;
34/// IRQs per `LOCIEN`/`LOCIPD` register.
35const LOCIEN_IRQ_NUM: u16 = 32;
36/// IRQs per `LOCIPRI` register (each a [`LOCIPRI_IRQ_BITS`]-bit field).
37const LOCIPRI_IRQ_NUM: u16 = 8;
38const LOCIPRI_IRQ_BITS: u16 = 4;
39const LOCIPRI_FIELD_MASK: u32 = 0xF;
40/// Number of `LOCIPRI` registers (0..15) → highest priority-configurable IRQ.
41const PRIORITY_IRQ_END: u16 = SYS_VECTOR_CNT + 16 * LOCIPRI_IRQ_NUM;
42/// Default priority word written to every `LOCIPRI` register (priority 1 per IRQ).
43const LOCIPRI_DEFAULT_VAL: u32 = 0x1111_1111;
44
45// --- raw CSR helpers --------------------------------------------------------
46// Custom CSR addresses must be assembler immediates, so each register needs its
47// own instruction; the `match` arms below mirror the C SDK's per-register switch.
48// Pseudo-ops (`csrs`/`csrc`/`csrw`/`csrr`) with a numeric CSR are accepted by the
49// assembler (same forms the examples already use).
50
51// Two macro sets: real CSR asm on riscv32, and no-op/zero stubs elsewhere so the
52// crate (and its host unit tests) build on x86. The interrupt-controller functions
53// are not exercised by host tests; the stubs only need to compile.
54#[cfg(target_arch = "riscv32")]
55macro_rules! csr_set {
56    ($csr:literal, $v:expr) => {
57        core::arch::asm!(concat!("csrs ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
58    };
59}
60#[cfg(target_arch = "riscv32")]
61macro_rules! csr_clear {
62    ($csr:literal, $v:expr) => {
63        core::arch::asm!(concat!("csrc ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
64    };
65}
66#[cfg(target_arch = "riscv32")]
67macro_rules! csr_write {
68    ($csr:literal, $v:expr) => {
69        core::arch::asm!(concat!("csrw ", $csr, ", {0}"), in(reg) $v, options(nomem, nostack))
70    };
71}
72#[cfg(target_arch = "riscv32")]
73macro_rules! csr_read {
74    ($csr:literal) => {{
75        let v: u32;
76        core::arch::asm!(concat!("csrr {0}, ", $csr), out(reg) v, options(nomem, nostack));
77        v
78    }};
79}
80
81#[cfg(not(target_arch = "riscv32"))]
82macro_rules! csr_set {
83    ($csr:literal, $v:expr) => {{
84        let _ = $v;
85    }};
86}
87#[cfg(not(target_arch = "riscv32"))]
88macro_rules! csr_clear {
89    ($csr:literal, $v:expr) => {{
90        let _ = $v;
91    }};
92}
93#[cfg(not(target_arch = "riscv32"))]
94macro_rules! csr_write {
95    ($csr:literal, $v:expr) => {{
96        let _ = $v;
97    }};
98}
99#[cfg(not(target_arch = "riscv32"))]
100macro_rules! csr_read {
101    ($csr:literal) => {{ 0u32 }};
102}
103
104/// Set/clear a bit-mask in `LOCIEN{idx}` (idx 0..=2 cover IRQ 32..=127).
105#[inline]
106unsafe fn locien_write(idx: u16, mask: u32, set: bool) {
107    unsafe {
108        match (idx, set) {
109            (0, true) => csr_set!("0xbe0", mask),
110            (0, false) => csr_clear!("0xbe0", mask),
111            (1, true) => csr_set!("0xbe1", mask),
112            (1, false) => csr_clear!("0xbe1", mask),
113            (2, true) => csr_set!("0xbe2", mask),
114            (2, false) => csr_clear!("0xbe2", mask),
115            _ => {}
116        }
117    }
118}
119
120#[inline]
121fn locien_read(idx: u16) -> u32 {
122    unsafe {
123        match idx {
124            0 => csr_read!("0xbe0"),
125            1 => csr_read!("0xbe1"),
126            2 => csr_read!("0xbe2"),
127            _ => 0,
128        }
129    }
130}
131
132#[inline]
133fn locipd_read(idx: u16) -> u32 {
134    unsafe {
135        match idx {
136            0 => csr_read!("0xbe8"),
137            1 => csr_read!("0xbe9"),
138            2 => csr_read!("0xbea"),
139            _ => 0,
140        }
141    }
142}
143
144/// Read-modify-write a 4-bit priority field in `LOCIPRI{idx}` (idx 0..=15).
145/// A bare `csrs` (as the C SDK uses) cannot lower a field below its current
146/// value, so we clear the nibble first — strictly more correct than the SDK.
147#[inline]
148fn locipri_set_field(idx: u16, shift: u16, value: u32) {
149    let clear = LOCIPRI_FIELD_MASK << shift;
150    let set = (value & LOCIPRI_FIELD_MASK) << shift;
151    unsafe {
152        macro_rules! rmw {
153            ($csr:literal) => {{
154                csr_clear!($csr, clear);
155                csr_set!($csr, set);
156            }};
157        }
158        match idx {
159            0 => rmw!("0xbc0"),
160            1 => rmw!("0xbc1"),
161            2 => rmw!("0xbc2"),
162            3 => rmw!("0xbc3"),
163            4 => rmw!("0xbc4"),
164            5 => rmw!("0xbc5"),
165            6 => rmw!("0xbc6"),
166            7 => rmw!("0xbc7"),
167            8 => rmw!("0xbc8"),
168            9 => rmw!("0xbc9"),
169            10 => rmw!("0xbca"),
170            11 => rmw!("0xbcb"),
171            12 => rmw!("0xbcc"),
172            13 => rmw!("0xbcd"),
173            14 => rmw!("0xbce"),
174            15 => rmw!("0xbcf"),
175            _ => {}
176        }
177    }
178}
179
180#[inline]
181fn locipri_read(idx: u16) -> u32 {
182    unsafe {
183        match idx {
184            0 => csr_read!("0xbc0"),
185            1 => csr_read!("0xbc1"),
186            2 => csr_read!("0xbc2"),
187            3 => csr_read!("0xbc3"),
188            4 => csr_read!("0xbc4"),
189            5 => csr_read!("0xbc5"),
190            6 => csr_read!("0xbc6"),
191            7 => csr_read!("0xbc7"),
192            8 => csr_read!("0xbc8"),
193            9 => csr_read!("0xbc9"),
194            10 => csr_read!("0xbca"),
195            11 => csr_read!("0xbcb"),
196            12 => csr_read!("0xbcc"),
197            13 => csr_read!("0xbcd"),
198            14 => csr_read!("0xbce"),
199            15 => csr_read!("0xbcf"),
200            _ => 0,
201        }
202    }
203}
204
205// --- public types -----------------------------------------------------------
206
207/// Local-interrupt priority. Valid range **1 (lowest) ..= 7 (highest)**; the
208/// reset default is 1. An interrupt is delivered only when its priority is
209/// strictly greater than the global threshold (see [`set_threshold`]).
210#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
211pub struct Priority(u8);
212
213impl Priority {
214    pub const P1: Self = Priority(1);
215    pub const P2: Self = Priority(2);
216    pub const P3: Self = Priority(3);
217    pub const P4: Self = Priority(4);
218    pub const P5: Self = Priority(5);
219    pub const P6: Self = Priority(6);
220    pub const P7: Self = Priority(7);
221
222    /// Lowest deliverable priority (1).
223    pub const LOWEST: Self = Priority(1);
224    /// Highest priority (7).
225    pub const HIGHEST: Self = Priority(7);
226
227    /// Create a priority, clamping `level` into the valid `1..=7` range.
228    pub const fn new(level: u8) -> Self {
229        let l = if level < 1 {
230            1
231        } else if level > 7 {
232            7
233        } else {
234            level
235        };
236        Priority(l)
237    }
238
239    /// The numeric priority level (1..=7).
240    pub const fn level(self) -> u8 {
241        self.0
242    }
243}
244
245#[inline]
246fn irq_num(irq: Interrupt) -> u16 {
247    irq as u16
248}
249
250// --- enable / disable -------------------------------------------------------
251
252/// Enable an interrupt source at the controller.
253///
254/// IRQ 26..=31 set the matching `mie` bit; IRQ >= 32 set the matching `LOCIEN`
255/// bit. The global interrupt enable ([`enable_global`]) must also be set.
256///
257/// # Safety
258/// Enabling an interrupt can cause its handler to run, which may break
259/// assumptions held outside a critical section (reentrancy, data races with
260/// data the handler touches). The caller must ensure the source's handler and
261/// shared state are ready.
262pub unsafe fn enable(irq: Interrupt) {
263    let n = irq_num(irq);
264    if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
265        unsafe { csr_set!("mie", 1u32 << n) };
266    } else if n >= LOCAL_IRQ_VECTOR_CNT {
267        let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
268        let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
269        unsafe { locien_write(reg, 1u32 << bit, true) };
270    }
271}
272
273/// Disable an interrupt source at the controller and clear any latched pending
274/// state (mirrors the C SDK's `int_disable_irq`).
275///
276/// # Safety
277/// See [`enable`]; toggling interrupt masks outside a critical section can race
278/// with a handler that is mid-flight.
279pub unsafe fn disable(irq: Interrupt) {
280    let n = irq_num(irq);
281    if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
282        unsafe { csr_clear!("mie", 1u32 << n) };
283    } else if n >= LOCAL_IRQ_VECTOR_CNT {
284        let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
285        let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
286        unsafe { locien_write(reg, 1u32 << bit, false) };
287    }
288    clear_pending(irq);
289}
290
291/// Whether the given interrupt source is currently enabled at the controller.
292pub fn is_enabled(irq: Interrupt) -> bool {
293    let n = irq_num(irq);
294    if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
295        let mie: u32 = unsafe { csr_read!("mie") };
296        (mie & (1u32 << n)) != 0
297    } else if n >= LOCAL_IRQ_VECTOR_CNT {
298        let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
299        let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
300        (locien_read(reg) & (1u32 << bit)) != 0
301    } else {
302        false
303    }
304}
305
306// --- priority / threshold ---------------------------------------------------
307
308/// Set a local interrupt's 4-bit priority (`LOCIPRI`). Applies to IRQ >= 26;
309/// a no-op for the system vectors below that.
310pub fn set_priority(irq: Interrupt, priority: Priority) {
311    let n = irq_num(irq);
312    if (SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n) {
313        let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
314        let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
315        locipri_set_field(reg, order * LOCIPRI_IRQ_BITS, priority.0 as u32);
316    }
317}
318
319/// Read back a local interrupt's configured priority (`LOCIPRI`).
320pub fn priority(irq: Interrupt) -> Priority {
321    let n = irq_num(irq);
322    if (SYS_VECTOR_CNT..PRIORITY_IRQ_END).contains(&n) {
323        let order = (n - SYS_VECTOR_CNT) % LOCIPRI_IRQ_NUM;
324        let reg = (n - SYS_VECTOR_CNT) / LOCIPRI_IRQ_NUM;
325        let shift = order * LOCIPRI_IRQ_BITS;
326        let field = (locipri_read(reg) >> shift) & LOCIPRI_FIELD_MASK;
327        Priority::new(field as u8)
328    } else {
329        Priority::LOWEST
330    }
331}
332
333/// Set the global priority threshold (`PRITHD`, 0..=7). Only interrupts with a
334/// priority **strictly greater** than this are delivered; 0 admits all
335/// priorities >= 1.
336pub fn set_threshold(level: u8) {
337    unsafe { csr_write!("0xbfe", (level & 0x7) as u32) };
338}
339
340/// Read the current global priority threshold (`PRITHD`).
341pub fn threshold() -> u8 {
342    (unsafe { csr_read!("0xbfe") } & 0x7) as u8
343}
344
345// --- pending ----------------------------------------------------------------
346
347/// Clear a pending local interrupt by writing its number to `LOCIPCLR`. Safe to
348/// call from within the interrupt's own handler (the usual place).
349pub fn clear_pending(irq: Interrupt) {
350    let n = irq_num(irq) as u32;
351    unsafe {
352        #[cfg(target_arch = "riscv32")]
353        core::arch::asm!("fence", options(nostack));
354        csr_write!("0xbf0", n);
355        #[cfg(target_arch = "riscv32")]
356        core::arch::asm!("fence", options(nostack));
357    }
358}
359
360/// Whether the given interrupt is currently pending (`LOCIPD` for IRQ >= 32,
361/// `mip` for IRQ 26..=31).
362pub fn is_pending(irq: Interrupt) -> bool {
363    let n = irq_num(irq);
364    if (SYS_VECTOR_CNT..LOCAL_IRQ_VECTOR_CNT).contains(&n) {
365        let mip: u32 = unsafe { csr_read!("mip") };
366        (mip & (1u32 << n)) != 0
367    } else if n >= LOCAL_IRQ_VECTOR_CNT {
368        let bit = (n - LOCAL_IRQ_VECTOR_CNT) % LOCIEN_IRQ_NUM;
369        let reg = (n - LOCAL_IRQ_VECTOR_CNT) / LOCIEN_IRQ_NUM;
370        (locipd_read(reg) & (1u32 << bit)) != 0
371    } else {
372        false
373    }
374}
375
376// --- global enable / critical section / init --------------------------------
377
378/// Set the default priority of every local interrupt to 1 (`LOCIPRI = 0x1111_1111`
379/// for all registers), mirroring the C SDK's `int_setup`. Without this, an IRQ
380/// whose priority resets to 0 would never clear the default threshold of 0.
381pub fn init() {
382    unsafe {
383        csr_write!("0xbc0", LOCIPRI_DEFAULT_VAL);
384        csr_write!("0xbc1", LOCIPRI_DEFAULT_VAL);
385        csr_write!("0xbc2", LOCIPRI_DEFAULT_VAL);
386        csr_write!("0xbc3", LOCIPRI_DEFAULT_VAL);
387        csr_write!("0xbc4", LOCIPRI_DEFAULT_VAL);
388        csr_write!("0xbc5", LOCIPRI_DEFAULT_VAL);
389        csr_write!("0xbc6", LOCIPRI_DEFAULT_VAL);
390        csr_write!("0xbc7", LOCIPRI_DEFAULT_VAL);
391        csr_write!("0xbc8", LOCIPRI_DEFAULT_VAL);
392        csr_write!("0xbc9", LOCIPRI_DEFAULT_VAL);
393        csr_write!("0xbca", LOCIPRI_DEFAULT_VAL);
394        csr_write!("0xbcb", LOCIPRI_DEFAULT_VAL);
395        csr_write!("0xbcc", LOCIPRI_DEFAULT_VAL);
396        csr_write!("0xbcd", LOCIPRI_DEFAULT_VAL);
397        csr_write!("0xbce", LOCIPRI_DEFAULT_VAL);
398        csr_write!("0xbcf", LOCIPRI_DEFAULT_VAL);
399    }
400}
401
402/// Set the global machine-interrupt enable (`mstatus.MIE`).
403///
404/// # Safety
405/// After this, any enabled-and-ready source can preempt. Configure sources and
406/// their handlers first.
407pub unsafe fn enable_global() {
408    #[cfg(target_arch = "riscv32")]
409    unsafe {
410        core::arch::asm!("csrsi mstatus, 0x8", options(nomem, nostack))
411    };
412}
413
414/// Clear the global machine-interrupt enable (`mstatus.MIE`).
415pub fn disable_global() {
416    #[cfg(target_arch = "riscv32")]
417    unsafe {
418        core::arch::asm!("csrci mstatus, 0x8", options(nomem, nostack))
419    };
420}
421
422/// Run `f` with machine interrupts globally masked, restoring the previous
423/// `mstatus.MIE` afterwards (nesting-safe — only re-enables if it was enabled).
424pub fn free<F, R>(f: F) -> R
425where
426    F: FnOnce() -> R,
427{
428    #[cfg(target_arch = "riscv32")]
429    let prev: usize;
430    #[cfg(target_arch = "riscv32")]
431    unsafe {
432        core::arch::asm!("csrrci {0}, mstatus, 0x8", out(reg) prev, options(nomem, nostack))
433    };
434    let r = f();
435    #[cfg(target_arch = "riscv32")]
436    if prev & 0x8 != 0 {
437        unsafe { core::arch::asm!("csrsi mstatus, 0x8", options(nomem, nostack)) };
438    }
439    r
440}