Skip to main content

esp_hal/interrupt/
riscv.rs

1//! Interrupt handling
2//!
3//! Peripheral interrupts go through the interrupt matrix, which routes them to the appropriate CPU
4//! interrupt line. The interrupt matrix is largely the same across devices, but CPU interrupts are
5//! device-specific before CLIC.
6//!
7//! Peripheral interrupts can be bound directly to CPU interrupts for better performance, but
8//! due to the limited number of CPU interrupts, the preferred mechanism is to use the vectored
9//! interrupts. The vectored interrupt handlers will call the appropriate interrupt handlers.
10//! The configuration of vectored interrupt handlers cannot be changed in runtime.
11
12#[cfg(feature = "rt")]
13#[instability::unstable]
14pub use esp_riscv_rt::TrapFrame;
15
16#[cfg_attr(interrupt_controller = "riscv_basic", path = "riscv/basic.rs")]
17#[cfg_attr(interrupt_controller = "plic", path = "riscv/plic.rs")]
18#[cfg_attr(interrupt_controller = "clic", path = "riscv/clic.rs")]
19mod cpu_int;
20
21// The software-interrupt driver is the only caller on this architecture, and that driver is
22// unstable.
23#[cfg(feature = "unstable")]
24pub(crate) use riscv::interrupt::free;
25
26use crate::{
27    interrupt::{PriorityError, RunLevel},
28    peripherals::Interrupt,
29    system::Cpu,
30};
31
32/// Interrupt kind
33#[cfg_attr(feature = "defmt", derive(defmt::Format))]
34#[instability::unstable]
35pub enum InterruptKind {
36    /// Level interrupt
37    Level,
38    /// Edge interrupt
39    Edge,
40}
41
42for_each_interrupt!(
43    (all $( ([$class:ident $idx_in_class:literal] $n:literal) ),*) => {
44        paste::paste! {
45            /// Enumeration of available CPU interrupts.
46            #[repr(u32)]
47            #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
48            #[cfg_attr(feature = "defmt", derive(defmt::Format))]
49            #[instability::unstable]
50            pub enum CpuInterrupt {
51                $(
52                    #[doc = concat!(" Interrupt number ", stringify!($n), ".")]
53                    [<Interrupt $n>] = $n,
54                )*
55            }
56
57            impl CpuInterrupt {
58                #[inline]
59                pub(crate) fn from_u32(n: u32) -> Option<Self> {
60                    match n {
61                        $(n if n == $n && n != DISABLED_CPU_INTERRUPT => Some(Self:: [<Interrupt $n>]),)*
62                        _ => None
63                    }
64                }
65            }
66        }
67    };
68);
69
70for_each_classified_interrupt!(
71    (direct_bindable $( ([$class:ident $idx_in_class:literal] $n:literal) ),*) => {
72        paste::paste! {
73            /// Enumeration of CPU interrupts available for direct binding.
74            #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75            #[cfg_attr(feature = "defmt", derive(defmt::Format))]
76            #[instability::unstable]
77            pub enum DirectBindableCpuInterrupt {
78                $(
79                    #[doc = concat!(" Direct bindable CPU interrupt number ", stringify!($idx_in_class), ".")]
80                    #[doc = " "]
81                    #[doc = concat!(" Corresponds to CPU interrupt ", stringify!($n), ".")]
82                    [<Interrupt $idx_in_class>] = $n,
83                )*
84            }
85
86            impl From<DirectBindableCpuInterrupt> for CpuInterrupt {
87                fn from(bindable: DirectBindableCpuInterrupt) -> CpuInterrupt {
88                    match bindable {
89                        $(
90                            DirectBindableCpuInterrupt::[<Interrupt $idx_in_class>] => CpuInterrupt::[<Interrupt $n>],
91                        )*
92                    }
93                }
94            }
95        }
96    };
97);
98
99impl CpuInterrupt {
100    #[inline]
101    #[cfg(feature = "rt")]
102    pub(crate) fn is_vectored(self) -> bool {
103        // Assumes contiguous interrupt allocation.
104        const VECTORED_CPU_INTERRUPT_RANGE: core::ops::RangeInclusive<u32> = PRIORITY_TO_INTERRUPT
105            [0] as u32
106            ..=PRIORITY_TO_INTERRUPT[PRIORITY_TO_INTERRUPT.len() - 1] as u32;
107        VECTORED_CPU_INTERRUPT_RANGE.contains(&(self as u32))
108    }
109
110    /// Enables the CPU interrupt.
111    #[inline]
112    #[instability::unstable]
113    pub fn enable(self) {
114        cpu_int::enable_cpu_interrupt_raw(self as u32);
115    }
116
117    /// Clears the CPU interrupt status bit.
118    #[inline]
119    #[instability::unstable]
120    pub fn clear(self) {
121        cpu_int::clear_raw(self as u32);
122    }
123
124    /// Sets the interrupt kind (i.e. level or edge) of an CPU interrupt.
125    ///
126    /// This is safe to call when the `vectored` feature is enabled. The
127    /// vectored interrupt handler will take care of clearing edge interrupt
128    /// bits.
129    #[inline]
130    #[instability::unstable]
131    pub fn set_kind(self, kind: InterruptKind) {
132        cpu_int::set_kind_raw(self as u32, kind);
133    }
134
135    /// Sets the priority level of a CPU interrupt.
136    #[inline]
137    #[instability::unstable]
138    pub fn set_priority(self, priority: Priority) {
139        cpu_int::set_priority_raw(self as u32, priority);
140    }
141
142    /// Returns the interrupt priority for the CPU.
143    #[inline]
144    #[instability::unstable]
145    pub fn priority(self) -> Priority {
146        unwrap!(Priority::try_from_u32(self.level()))
147    }
148
149    #[inline]
150    pub(crate) fn level(self) -> u32 {
151        cpu_int::cpu_interrupt_priority_raw(self as u32) as u32
152    }
153}
154
155for_each_interrupt_priority!(
156    (all $( ($idx:literal, $n:literal, $ident:ident, $level:ident) ),*) => {
157        /// Interrupt priority levels.
158        ///
159        /// A higher numeric value means higher priority. Interrupt requests at higher priority
160        /// levels will be able to preempt code running at a lower [`RunLevel`][super::RunLevel].
161        #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
162        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
163        #[repr(u8)]
164        pub enum Priority {
165            $(
166                #[doc = concat!(" Priority level ", stringify!($n), ".")]
167                $ident = $n,
168            )*
169        }
170
171        impl Priority {
172            fn iter() -> impl Iterator<Item = Priority> {
173                [$(Priority::$ident,)*].into_iter()
174            }
175        }
176
177        /// Interrupt run levels.
178        #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
179        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
180        #[repr(u8)]
181        pub enum ElevatedRunLevel {
182            $(
183                #[doc = concat!("Run level ", stringify!($n), ".")]
184                $level = $n,
185            )*
186        }
187
188        impl ElevatedRunLevel {
189            /// Converts a [`Priority`] into an [`ElevatedRunLevel`].
190            pub const fn from_priority(priority: Priority) -> Self {
191                match priority {
192                    $(Priority::$ident => Self::$level,)*
193                }
194            }
195        }
196    };
197);
198
199impl Priority {
200    /// Maximum interrupt priority
201    #[allow(unused_assignments)]
202    #[instability::unstable]
203    pub const fn max() -> Priority {
204        const {
205            let mut last = Self::min();
206            for_each_interrupt_priority!(
207                ($_idx:literal, $_n:literal, $ident:ident, $_level:ident) => {
208                    last = Self::$ident;
209                };
210            );
211            last
212        }
213    }
214
215    /// Minimum interrupt priority
216    pub const fn min() -> Priority {
217        Priority::Priority1
218    }
219
220    pub(crate) fn try_from_u32(priority: u32) -> Result<Self, PriorityError> {
221        let result;
222        for_each_interrupt_priority!(
223            (all $( ($idx:literal, $n:literal, $ident:ident, $_level:ident) ),*) => {
224                result = match priority {
225                    $($n => Ok(Priority::$ident),)*
226                    _ => Err(PriorityError::InvalidInterruptPriority),
227                }
228            };
229        );
230        result
231    }
232}
233
234impl ElevatedRunLevel {
235    /// Returns the highest run level.
236    #[instability::unstable]
237    pub const fn max() -> ElevatedRunLevel {
238        Self::from_priority(Priority::max())
239    }
240
241    /// Minimum elevated run level
242    pub const fn min() -> ElevatedRunLevel {
243        Self::from_priority(Priority::min())
244    }
245
246    pub(crate) fn try_from_u32(level: u32) -> Result<Self, PriorityError> {
247        Priority::try_from_u32(level).map(Self::from_priority)
248    }
249}
250
251#[instability::unstable]
252impl TryFrom<u32> for ElevatedRunLevel {
253    type Error = PriorityError;
254
255    fn try_from(value: u32) -> Result<Self, Self::Error> {
256        Self::try_from_u32(value)
257    }
258}
259
260impl From<Priority> for ElevatedRunLevel {
261    fn from(priority: Priority) -> Self {
262        Self::from_priority(priority)
263    }
264}
265
266#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
267pub(super) static DISABLED_CPU_INTERRUPT: u32 = property!("interrupts.disabled_interrupt");
268
269/// The number of vectored interrupts / The number of priority levels.
270const VECTOR_COUNT: usize = const {
271    let mut count = 0;
272    for_each_interrupt!(([vector $n:tt] $_:literal) => { count += 1; };);
273
274    core::assert!(count == Priority::max() as usize);
275
276    count
277};
278
279/// Maps priority levels to their corresponding interrupt vectors.
280#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
281pub(super) static PRIORITY_TO_INTERRUPT: [CpuInterrupt; VECTOR_COUNT] = const {
282    let mut counter = 0;
283    let mut vector = [CpuInterrupt::Interrupt0; VECTOR_COUNT];
284
285    for_each_interrupt!(
286        ([vector $_n:tt] $interrupt:literal) => {
287            vector[counter] = paste::paste! { CpuInterrupt::[<Interrupt $interrupt>] };
288            counter += 1;
289        };
290    );
291    vector
292};
293
294/// Enables an interrupt by directly binding it to an available CPU interrupt.
295///
296/// ⚠️ This installs a *raw trap handler*: the provided `handler` is written
297/// directly into the CPU interrupt vector table. That means:
298///
299/// - The provided handler is used as an actual trap handler.
300/// - The caller must:
301///   - Save and restore all used registers.
302///   - Clear the interrupt source if necessary.
303///   - Return using the `mret` instruction.
304/// - The handler must be declared as a naked function. The compiler does not insert a function
305///   prologue or epilogue; a normal Rust `fn` results in an error
306///
307/// Unless low-level control is required for the lowest possible latency,
308/// [`enable`][crate::interrupt::enable] is usually preferable
309#[instability::unstable]
310pub fn enable_direct(
311    interrupt: Interrupt,
312    level: Priority,
313    cpu_interrupt: DirectBindableCpuInterrupt,
314    handler: unsafe extern "C" fn(),
315) {
316    cfg_select! {
317        interrupt_controller = "clic" => {
318            let clic = unsafe { crate::soc::pac::CLIC::steal() };
319
320            // Enable hardware vectoring
321            clic.int_attr(cpu_interrupt as usize).modify(|_, w| {
322                w.shv().hardware();
323                w.trig().positive_level()
324            });
325
326            let mtvt_table: *mut [u32; 48];
327            unsafe { core::arch::asm!("csrr {0}, 0x307", out(reg) mtvt_table) };
328
329            let int_slot = mtvt_table
330                .cast::<u32>()
331                .wrapping_add(cpu_interrupt as usize);
332
333            let instr = handler as usize as u32;
334        }
335        _ => {
336            use riscv::register::mtvec;
337            let mt = mtvec::read();
338
339            assert_eq!(
340                mt.trap_mode().into_usize(),
341                mtvec::TrapMode::Vectored.into_usize()
342            );
343
344            let base_addr = mt.address() as usize;
345
346            let int_slot = base_addr.wrapping_add((cpu_interrupt as usize) * 4) as *mut u32;
347
348            let instr = encode_jal_x0(handler as usize, int_slot as usize);
349        }
350    }
351
352    if crate::debugger::debugger_connected() {
353        unsafe { core::ptr::write_volatile(int_slot, instr) };
354    } else {
355        crate::debugger::DEBUGGER_LOCK.lock(|| unsafe {
356            let wp = crate::debugger::clear_watchpoint(1);
357            core::ptr::write_volatile(int_slot, instr);
358            crate::debugger::restore_watchpoint(1, wp);
359        });
360    }
361    unsafe {
362        core::arch::asm!("fence.i");
363    }
364
365    #[cfg(esp32p4)]
366    unsafe {
367        // Write back the cache to make sure the new interrupt handler is visible to the CPU.
368        crate::soc::cache_writeback_addr(mtvt_table as u32, 48 * 4);
369        // Invalidate the cache to make sure the CPU does not read from a stale instruction cache.
370        crate::soc::cache_invalidate_icache_addr(mtvt_table as u32, 48 * 4);
371    }
372
373    super::map_raw(Cpu::current(), interrupt, cpu_interrupt as u32);
374    cpu_int::set_priority_raw(cpu_interrupt as u32, level);
375    cpu_int::set_kind_raw(cpu_interrupt as u32, InterruptKind::Level);
376    cpu_int::enable_cpu_interrupt_raw(cpu_interrupt as u32);
377}
378
379// helper: returns correctly encoded RISC-V `jal` instruction
380#[cfg(not(interrupt_controller = "clic"))]
381fn encode_jal_x0(target: usize, pc: usize) -> u32 {
382    let offset = (target as isize) - (pc as isize);
383
384    const MIN: isize = -(1isize << 20);
385    const MAX: isize = (1isize << 20) - 1;
386
387    assert!(offset % 2 == 0 && (MIN..=MAX).contains(&offset));
388
389    let imm = offset as u32;
390    let imm20 = (imm >> 20) & 0x1;
391    let imm10_1 = (imm >> 1) & 0x3ff;
392    let imm11 = (imm >> 11) & 0x1;
393    let imm19_12 = (imm >> 12) & 0xff;
394
395    (imm20 << 31)
396        | (imm19_12 << 12)
397        | (imm11 << 20)
398        | (imm10_1 << 21)
399        // https://lhtin.github.io/01world/app/riscv-isa/?xlen=32&insn_name=jal
400        | 0b1101111u32
401}
402
403// Runlevel APIs
404
405/// Returns the current run level (the level below which interrupts are masked).
406pub(crate) fn current_raw_runlevel() -> u32 {
407    cpu_int::current_runlevel() as u32
408}
409
410/// Changes the current run level (the level below which interrupts are
411/// masked), and returns the previous run level.
412///
413/// # Safety
414///
415/// Must only be used to raise the runlevel and to restore it to a previous
416/// value. Must not be used to arbitrarily lower the runlevel.
417pub(crate) unsafe fn change_current_runlevel(level: RunLevel) -> RunLevel {
418    let previous = cpu_int::change_current_runlevel(level);
419    unwrap!(RunLevel::try_from_u32(previous as u32))
420}
421
422fn cpu_wait_mode_on() -> bool {
423    cfg_select! {
424        soc_has_pcr => crate::peripherals::PCR::regs()
425            .cpu_waiti_conf()
426            .read()
427            .cpu_wait_mode_force_on()
428            .bit_is_set(),
429        soc_has_hp_sys => crate::peripherals::HP_SYS::regs()
430            .cpu_waiti_conf()
431            .read()
432            .cpu_wait_mode_force_on()
433            .bit_is_set(),
434        _ => crate::peripherals::SYSTEM::regs()
435            .cpu_per_conf()
436            .read()
437            .cpu_wait_mode_force_on()
438            .bit_is_set(),
439    }
440}
441
442/// Waits for an interrupt to occur.
443///
444/// Causes the current CPU core to execute its Wait For Interrupt (WFI or
445/// equivalent) instruction. After this call, the CPU core stops execution until
446/// an interrupt occurs.
447///
448/// Returns immediately when a debugger is attached; intended to be called in a
449/// loop.
450#[inline(always)]
451#[instability::unstable]
452pub fn wait_for_interrupt() {
453    if crate::debugger::debugger_connected() && !cpu_wait_mode_on() {
454        // when SYSTEM_CPU_WAIT_MODE_FORCE_ON is disabled in WFI mode SBA access to memory does not
455        // work for debugger, so do not enter that mode when debugger is connected.
456        // https://github.com/espressif/esp-idf/blob/b9a308a47ca4128d018495662b009a7c461b6780/components/esp_hw_support/cpu.c#L57-L60
457        return;
458    }
459    unsafe { core::arch::asm!("wfi") };
460}
461
462pub(crate) fn priority_to_cpu_interrupt(_interrupt: Interrupt, level: Priority) -> CpuInterrupt {
463    PRIORITY_TO_INTERRUPT[(level as usize) - 1]
464}
465
466/// Sets up interrupts ready for vectoring.
467///
468/// # Safety
469///
470/// Must be called only during core startup.
471#[cfg(any(feature = "rt", all(feature = "unstable", multi_core)))]
472pub(crate) unsafe fn init_vectoring() {
473    use riscv::register::mtvec;
474
475    unsafe extern "C" {
476        static _vector_table: u32;
477    }
478
479    unsafe {
480        let vec_table = (&raw const _vector_table).addr();
481
482        #[cfg(not(interrupt_controller = "clic"))]
483        {
484            mtvec::write({
485                let mut mtvec = mtvec::Mtvec::from_bits(0);
486                mtvec.set_trap_mode(mtvec::TrapMode::Vectored);
487                mtvec.set_address(vec_table);
488                mtvec
489            });
490        }
491
492        #[cfg(interrupt_controller = "clic")]
493        {
494            mtvec::write({
495                let mut mtvec = mtvec::Mtvec::from_bits(0x03); // MODE = CLIC
496                mtvec.set_address(vec_table);
497                mtvec
498            });
499
500            // set mtvt (hardware vector base)
501            let mtvt_table = match Cpu::current() {
502                Cpu::ProCpu => {
503                    unsafe extern "C" {
504                        static _mtvt_table: u32;
505                    }
506                    (&raw const _mtvt_table).addr()
507                }
508                #[cfg(multi_core)]
509                Cpu::AppCpu => {
510                    unsafe extern "C" {
511                        static _mtvt_table2: u32;
512                    }
513                    (&raw const _mtvt_table2).addr()
514                }
515            };
516            core::arch::asm!("csrw 0x307, {0}", in(reg) mtvt_table);
517        }
518    };
519
520    // Configure CLIC for hardware-vectored mode (shv=1) and set nlbits.
521    // Must run on each core since CLIC control registers are per-core.
522    #[cfg(feature = "rt")]
523    cpu_int::init();
524
525    // Configure and enable vectored interrupts
526    for (int, prio) in PRIORITY_TO_INTERRUPT.iter().copied().zip(Priority::iter()) {
527        let num = int as u32;
528        cpu_int::set_kind_raw(num, InterruptKind::Level);
529        cpu_int::set_priority_raw(num, prio);
530        cpu_int::enable_cpu_interrupt_raw(num);
531    }
532}
533
534#[cfg(feature = "rt")]
535pub(crate) mod rt {
536    use esp_riscv_rt::TrapFrame;
537    use riscv::register::mcause;
538
539    use super::*;
540    use crate::interrupt::InterruptStatus;
541
542    /// The total number of interrupts.
543    #[cfg(not(interrupt_controller = "clic"))]
544    const INTERRUPT_COUNT: usize = const {
545        let mut count = 0;
546        for_each_interrupt!(([$_class:tt $n:tt] $_:literal) => { count += 1; };);
547        count
548    };
549
550    /// Maps interrupt numbers to their vector priority levels.
551    #[cfg(not(interrupt_controller = "clic"))]
552    #[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
553    pub(super) static INTERRUPT_TO_PRIORITY: [Option<Priority>; INTERRUPT_COUNT] = const {
554        let mut priorities = [None; INTERRUPT_COUNT];
555
556        for_each_interrupt!(
557            ([vector $n:tt] $int:literal) => {
558                for_each_interrupt_priority!(($n, $__:tt, $ident:ident, $_level:ident) => { priorities[$int] = Some(Priority::$ident); };);
559            };
560        );
561
562        priorities
563    };
564
565    /// # Safety
566    ///
567    /// Called from an assembly trap handler.
568    #[doc(hidden)]
569    #[unsafe(link_section = ".trap.rust")]
570    #[unsafe(export_name = "_start_trap_rust_hal")]
571    unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
572        assert!(
573            mcause::read().is_exception(),
574            "Arrived into _start_trap_rust_hal but mcause is not an exception!"
575        );
576        unsafe extern "C" {
577            fn ExceptionHandler(tf: *mut TrapFrame);
578        }
579        unsafe {
580            ExceptionHandler(trap_frame);
581        }
582    }
583
584    #[doc(hidden)]
585    #[unsafe(no_mangle)]
586    #[unsafe(link_section = ".init")]
587    unsafe fn _setup_interrupts() {
588        crate::soc::riscv_preinit();
589        crate::interrupt::setup_interrupts();
590
591        #[cfg(interrupt_controller = "plic")]
592        unsafe {
593            core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX);
594        }
595    }
596
597    #[unsafe(no_mangle)]
598    #[crate::ram]
599    unsafe fn handle_interrupts(cpu_intr: CpuInterrupt) {
600        let status = InterruptStatus::current();
601
602        // this has no effect on level interrupts, but the interrupt may be an edge one
603        // so we clear it anyway
604        cpu_intr.clear();
605
606        cfg_select! {
607            interrupt_controller = "clic" => {
608                let prio = cpu_int::current_runlevel();
609                let mcause = riscv::register::mcause::read();
610            }
611            _ => {
612                // Change the current runlevel so that interrupt handlers can access the correct
613                // runlevel.
614                let prio = unwrap!(INTERRUPT_TO_PRIORITY[cpu_intr as usize]);
615                let level = unsafe {
616                    change_current_runlevel(RunLevel::Interrupt(ElevatedRunLevel::from(prio)))
617                };
618                let prio = prio as u8;
619            }
620        }
621
622        let handle_interrupts = || unsafe {
623            for interrupt_nr in status.iterator().filter(|&interrupt_nr| {
624                crate::interrupt::should_handle(Cpu::current(), interrupt_nr as u32, prio as u32)
625            }) {
626                let handler =
627                    crate::soc::pac::__EXTERNAL_INTERRUPTS[interrupt_nr as usize]._handler;
628
629                handler();
630            }
631        };
632
633        // Do not enable nesting on the highest priority level. Older interrupt controllers couldn't
634        // properly mask the highest priority interrupt, and for CLIC we don't want to waste
635        // the cycles it takes to enable nesting unnecessarily.
636        if prio != Priority::max() as u8 {
637            unsafe {
638                riscv::interrupt::nested(handle_interrupts);
639            }
640        } else {
641            handle_interrupts();
642        }
643
644        cfg_select! {
645            interrupt_controller = "clic" => {
646                // In case the target uses the CLIC, it is mandatory to restore `mcause` register
647                // since it contains the former CPU priority. When executing `mret`,
648                // the hardware will restore the former threshold, from `mcause` to
649                // `mintstatus` CSR
650                unsafe { core::arch::asm!("csrw 0x342, {}", in(reg) mcause.bits()) }
651            }
652            _ => {
653                unsafe { change_current_runlevel(level) };
654            }
655        }
656    }
657}