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