feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+229
View File
@@ -0,0 +1,229 @@
//! # Interrupt support
//!
//! ## Overview
//! This module routes one or more peripheral interrupt sources to any one
//! of the CPU’s peripheral interrupts.
//!
//! ## Configuration
//! Usually peripheral drivers offer a mechanism to register your interrupt
//! handler. e.g. the systimer offers `set_interrupt_handler` to register a
//! handler for a specific alarm. Other drivers might take an interrupt handler
//! as an optional parameter to their constructor.
//!
//! This is the preferred way to register handlers.
//!
//! There are additional ways to register interrupt handlers which are generally
//! only meant to be used in very special situations (mostly internal to the HAL
//! or the supporting libraries). Those are outside the scope of this
//! documentation.
//!
//! It is even possible, but not recommended, to bind an interrupt directly to a
//! CPU interrupt. This can offer lower latency, at the cost of more complexity
//! in the interrupt handler. See the `direct_vectoring.rs` example
//!
//! We reserve a number of CPU interrupts, which cannot be used; see
//! [`RESERVED_INTERRUPTS`].
//!
//! ## Example
//!
//! ### Using the peripheral driver to register an interrupt handler
//!
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! let mut sw_int =
//! SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
//! critical_section::with(|cs| {
//! sw_int
//! .software_interrupt0
//! .set_interrupt_handler(swint0_handler);
//! SWINT0
//! .borrow_ref_mut(cs)
//! .replace(sw_int.software_interrupt0);
//! });
//!
//! critical_section::with(|cs| {
//! SWINT0.borrow_ref(cs).as_ref().unwrap().raise();
//! });
//! #
//! # loop {}
//! # }
//!
//! # use core::cell::RefCell;
//! #
//! # use critical_section::Mutex;
//! # use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl};
//! # use esp_hal::interrupt::Priority;
//! # use esp_hal::interrupt::InterruptHandler;
//! #
//! static SWINT0: Mutex<RefCell<Option<SoftwareInterrupt<0>>>> =
//! Mutex::new(RefCell::new(None));
//!
//! #[handler(priority = Priority::Priority1)]
//! fn swint0_handler() {
//! // esp_println::println!("SW interrupt0");
//! critical_section::with(|cs| {
//! SWINT0.borrow_ref(cs).as_ref().unwrap().reset();
//! });
//! }
//! ```
use core::ops::BitAnd;
#[cfg(riscv)]
pub use self::riscv::*;
#[cfg(xtensa)]
pub use self::xtensa::*;
#[cfg(riscv)]
mod riscv;
#[cfg(xtensa)]
mod xtensa;
pub mod software;
/// An interrupt handler
#[cfg_attr(
multi_core,
doc = "**Note**: Interrupts are handled on the core they were setup on, if a driver is initialized on core 0, and moved to core 1, core 0 will still handle the interrupt."
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct InterruptHandler {
f: extern "C" fn(),
prio: Priority,
}
impl InterruptHandler {
/// Creates a new [InterruptHandler] which will call the given function at
/// the given priority.
pub const fn new(f: extern "C" fn(), prio: Priority) -> Self {
Self { f, prio }
}
/// The function to be called
#[inline]
pub fn handler(&self) -> extern "C" fn() {
self.f
}
/// Priority to be used when registering the interrupt
#[inline]
pub fn priority(&self) -> Priority {
self.prio
}
}
#[cfg(large_intr_status)]
const STATUS_WORDS: usize = 3;
#[cfg(very_large_intr_status)]
const STATUS_WORDS: usize = 4;
#[cfg(not(any(large_intr_status, very_large_intr_status)))]
const STATUS_WORDS: usize = 2;
/// Representation of peripheral-interrupt status bits.
#[derive(Clone, Copy, Default, Debug)]
pub struct InterruptStatus {
status: [u32; STATUS_WORDS],
}
impl InterruptStatus {
const fn empty() -> Self {
InterruptStatus {
status: [0u32; STATUS_WORDS],
}
}
#[cfg(large_intr_status)]
const fn from(w0: u32, w1: u32, w2: u32) -> Self {
Self {
status: [w0, w1, w2],
}
}
#[cfg(very_large_intr_status)]
const fn from(w0: u32, w1: u32, w2: u32, w3: u32) -> Self {
Self {
status: [w0, w1, w2, w3],
}
}
#[cfg(not(any(large_intr_status, very_large_intr_status)))]
const fn from(w0: u32, w1: u32) -> Self {
Self { status: [w0, w1] }
}
/// Is the given interrupt bit set
pub fn is_set(&self, interrupt: u16) -> bool {
(self.status[interrupt as usize / 32] & (1 << (interrupt as u32 % 32))) != 0
}
/// Set the given interrupt status bit
pub fn set(&mut self, interrupt: u16) {
self.status[interrupt as usize / 32] |= 1 << (interrupt as u32 % 32);
}
/// Return an iterator over the set interrupt status bits
pub fn iterator(&self) -> InterruptStatusIterator {
InterruptStatusIterator {
status: *self,
idx: 0,
}
}
}
impl BitAnd for InterruptStatus {
type Output = InterruptStatus;
fn bitand(self, rhs: Self) -> Self::Output {
#[cfg(large_intr_status)]
return Self::Output {
status: [
self.status[0] & rhs.status[0],
self.status[1] & rhs.status[1],
self.status[2] & rhs.status[2],
],
};
#[cfg(very_large_intr_status)]
return Self::Output {
status: [
self.status[0] & rhs.status[0],
self.status[1] & rhs.status[1],
self.status[2] & rhs.status[2],
self.status[3] & rhs.status[3],
],
};
#[cfg(not(any(large_intr_status, very_large_intr_status)))]
return Self::Output {
status: [
self.status[0] & rhs.status[0],
self.status[1] & rhs.status[1],
],
};
}
}
/// Iterator over set interrupt status bits
pub struct InterruptStatusIterator {
status: InterruptStatus,
idx: usize,
}
impl Iterator for InterruptStatusIterator {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
for i in self.idx..STATUS_WORDS {
if self.status.status[i] != 0 {
let bit = self.status.status[i].trailing_zeros();
self.idx = i;
self.status.status[i] &= !1 << bit;
return Some((bit + 32 * i as u32) as u8);
}
}
self.idx = usize::MAX;
None
}
}
+820
View File
@@ -0,0 +1,820 @@
//! Interrupt handling
//!
//! CPU interrupts 1 through 15 are reserved for each of the possible interrupt
//! priorities.
//!
//! On chips with a PLIC CPU interrupts 1,2,5,6,9 .. 19 are used.
//!
//! ```rust, ignore
//! interrupt1() => Priority::Priority1
//! interrupt2() => Priority::Priority2
//! ...
//! interrupt15() => Priority::Priority15
//! ```
pub use esp_riscv_rt::TrapFrame;
use riscv::register::{mcause, mtvec};
#[cfg(not(plic))]
pub use self::classic::*;
#[cfg(plic)]
pub use self::plic::*;
pub use self::vectored::*;
use super::InterruptStatus;
use crate::{
peripherals::{self, Interrupt},
Cpu,
};
/// Interrupt Error
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// The priority is not valid
InvalidInterruptPriority,
/// The CPU interrupt is a reserved interrupt
CpuInterruptReserved,
}
/// Interrupt kind
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptKind {
/// Level interrupt
Level,
/// Edge interrupt
Edge,
}
/// Enumeration of available CPU interrupts.
/// It is possible to create a handler for each of the interrupts. (e.g.
/// `interrupt3`)
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CpuInterrupt {
/// Interrupt number 1.
Interrupt1 = 1,
/// Interrupt number 2.
Interrupt2,
/// Interrupt number 3.
Interrupt3,
/// Interrupt number 4.
Interrupt4,
/// Interrupt number 5.
Interrupt5,
/// Interrupt number 6.
Interrupt6,
/// Interrupt number 7.
Interrupt7,
/// Interrupt number 8.
Interrupt8,
/// Interrupt number 9.
Interrupt9,
/// Interrupt number 10.
Interrupt10,
/// Interrupt number 11.
Interrupt11,
/// Interrupt number 12.
Interrupt12,
/// Interrupt number 13.
Interrupt13,
/// Interrupt number 14.
Interrupt14,
/// Interrupt number 15.
Interrupt15,
/// Interrupt number 16.
Interrupt16,
/// Interrupt number 17.
Interrupt17,
/// Interrupt number 18.
Interrupt18,
/// Interrupt number 19.
Interrupt19,
/// Interrupt number 20.
Interrupt20,
/// Interrupt number 21.
Interrupt21,
/// Interrupt number 22.
Interrupt22,
/// Interrupt number 23.
Interrupt23,
/// Interrupt number 24.
Interrupt24,
/// Interrupt number 25.
Interrupt25,
/// Interrupt number 26.
Interrupt26,
/// Interrupt number 27.
Interrupt27,
/// Interrupt number 28.
Interrupt28,
/// Interrupt number 29.
Interrupt29,
/// Interrupt number 30.
Interrupt30,
/// Interrupt number 31.
Interrupt31,
}
/// Interrupt priority levels.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Priority {
/// No priority.
None = 0,
/// Priority level 1.
Priority1,
/// Priority level 2.
Priority2,
/// Priority level 3.
Priority3,
/// Priority level 4.
Priority4,
/// Priority level 5.
Priority5,
/// Priority level 6.
Priority6,
/// Priority level 7.
Priority7,
/// Priority level 8.
Priority8,
/// Priority level 9.
Priority9,
/// Priority level 10.
Priority10,
/// Priority level 11.
Priority11,
/// Priority level 12.
Priority12,
/// Priority level 13.
Priority13,
/// Priority level 14.
Priority14,
/// Priority level 15.
Priority15,
}
impl Priority {
/// Maximum interrupt priority
pub const fn max() -> Priority {
Priority::Priority15
}
/// Minimum interrupt priority
pub const fn min() -> Priority {
Priority::Priority1
}
}
/// The interrupts reserved by the HAL
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub static RESERVED_INTERRUPTS: &[usize] = INTERRUPT_TO_PRIORITY;
/// # Safety
///
/// This function is called from an assembly trap handler.
#[doc(hidden)]
#[link_section = ".trap.rust"]
#[export_name = "_start_trap_rust_hal"]
pub unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
assert!(
mcause::read().is_exception(),
"Arrived into _start_trap_rust_hal but mcause is not an exception!"
);
extern "C" {
fn ExceptionHandler(tf: *mut TrapFrame);
}
ExceptionHandler(trap_frame);
}
#[doc(hidden)]
#[no_mangle]
pub fn _setup_interrupts() {
extern "C" {
static _vector_table: *const u32;
}
unsafe {
// disable all known interrupts
// at least after the 2nd stage bootloader there are some interrupts enabled
// (e.g. UART)
for peripheral_interrupt in 0..255 {
crate::soc::peripherals::Interrupt::try_from(peripheral_interrupt)
.map(|intr| {
#[cfg(multi_core)]
disable(Cpu::AppCpu, intr);
disable(Cpu::ProCpu, intr);
})
.ok();
}
let vec_table = &_vector_table as *const _ as usize;
mtvec::write(vec_table, mtvec::TrapMode::Vectored);
crate::interrupt::init_vectoring();
};
#[cfg(plic)]
unsafe {
core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX);
}
}
/// Enable an interrupt by directly binding it to a available CPU interrupt
///
/// Unless you are sure, you most likely want to use [`enable`] instead.
///
/// Trying using a reserved interrupt from [`RESERVED_INTERRUPTS`] will return
/// an error.
pub fn enable_direct(
interrupt: Interrupt,
level: Priority,
cpu_interrupt: CpuInterrupt,
) -> Result<(), Error> {
if RESERVED_INTERRUPTS.contains(&(cpu_interrupt as _)) {
return Err(Error::CpuInterruptReserved);
}
if matches!(level, Priority::None) {
return Err(Error::InvalidInterruptPriority);
}
unsafe {
map(Cpu::current(), interrupt, cpu_interrupt);
set_priority(Cpu::current(), cpu_interrupt, level);
enable_cpu_interrupt(cpu_interrupt);
}
Ok(())
}
/// Disable the given peripheral interrupt.
pub fn disable(_core: Cpu, interrupt: Interrupt) {
unsafe {
let interrupt_number = interrupt as isize;
let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32;
// set to 0 to disable the peripheral interrupt on chips with an interrupt
// controller other than PLIC use the disabled interrupt 31 otherwise
intr_map_base
.offset(interrupt_number)
.write_volatile(DISABLED_CPU_INTERRUPT);
}
}
/// Get status of peripheral interrupts
#[inline]
pub fn status(_core: Cpu) -> InterruptStatus {
#[cfg(large_intr_status)]
unsafe {
InterruptStatus::from(
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_0()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_1()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.int_status_reg_2()
.read()
.bits(),
)
}
#[cfg(very_large_intr_status)]
unsafe {
InterruptStatus::from(
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_0()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_1()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_2()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_3()
.read()
.bits(),
)
}
#[cfg(not(any(large_intr_status, very_large_intr_status)))]
unsafe {
InterruptStatus::from(
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_0()
.read()
.bits(),
(*crate::peripherals::INTERRUPT_CORE0::PTR)
.intr_status_reg_1()
.read()
.bits(),
)
}
}
/// Assign a peripheral interrupt to an CPU interrupt.
///
/// # Safety
///
/// Do not use CPU interrupts in the [`RESERVED_INTERRUPTS`].
pub unsafe fn map(_core: Cpu, interrupt: Interrupt, which: CpuInterrupt) {
let interrupt_number = interrupt as isize;
let cpu_interrupt_number = which as isize;
#[cfg(not(multi_core))]
let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32;
#[cfg(multi_core)]
let intr_map_base = match _core {
Cpu::ProCpu => crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32,
Cpu::AppCpu => crate::soc::registers::INTERRUPT_MAP_BASE_APP_CPU as *mut u32,
};
intr_map_base
.offset(interrupt_number)
.write_volatile(cpu_interrupt_number as u32 + EXTERNAL_INTERRUPT_OFFSET);
}
/// Get cpu interrupt assigned to peripheral interrupt
#[inline]
unsafe fn assigned_cpu_interrupt(interrupt: Interrupt) -> Option<CpuInterrupt> {
let interrupt_number = interrupt as isize;
let intr_map_base = crate::soc::registers::INTERRUPT_MAP_BASE as *mut u32;
let cpu_intr = intr_map_base.offset(interrupt_number).read_volatile();
if cpu_intr > 0 && cpu_intr != DISABLED_CPU_INTERRUPT {
Some(core::mem::transmute::<u32, CpuInterrupt>(
cpu_intr - EXTERNAL_INTERRUPT_OFFSET,
))
} else {
None
}
}
pub(crate) fn bound_cpu_interrupt_for(_cpu: Cpu, interrupt: Interrupt) -> Option<CpuInterrupt> {
unsafe { assigned_cpu_interrupt(interrupt) }
}
mod vectored {
use procmacros::ram;
use super::*;
// Setup interrupts ready for vectoring
#[doc(hidden)]
pub(crate) unsafe fn init_vectoring() {
for (prio, num) in PRIORITY_TO_INTERRUPT.iter().enumerate() {
set_kind(
Cpu::current(),
core::mem::transmute::<u32, CpuInterrupt>(*num as u32),
InterruptKind::Level,
);
set_priority(
Cpu::current(),
core::mem::transmute::<u32, CpuInterrupt>(*num as u32),
core::mem::transmute::<u8, Priority>((prio as u8) + 1),
);
enable_cpu_interrupt(core::mem::transmute::<u32, CpuInterrupt>(*num as u32));
}
}
/// Get the interrupts configured for the core at the given priority
/// matching the given status
#[inline]
fn configured_interrupts(
core: Cpu,
status: InterruptStatus,
priority: Priority,
) -> InterruptStatus {
unsafe {
let mut res = InterruptStatus::empty();
for interrupt_nr in status.iterator() {
// safety: cast is safe because of repr(u16)
if let Some(cpu_interrupt) =
assigned_cpu_interrupt(core::mem::transmute::<u16, Interrupt>(
interrupt_nr as u16,
))
{
if priority_by_core(core, cpu_interrupt) == priority {
res.set(interrupt_nr as u16);
}
}
}
res
}
}
/// Enables a interrupt at a given priority
///
/// Note that interrupts still need to be enabled globally for interrupts
/// to be serviced.
pub fn enable(interrupt: Interrupt, level: Priority) -> Result<(), Error> {
if matches!(level, Priority::None) {
return Err(Error::InvalidInterruptPriority);
}
unsafe {
let cpu_interrupt = core::mem::transmute::<u32, CpuInterrupt>(
PRIORITY_TO_INTERRUPT[(level as usize) - 1] as u32,
);
map(Cpu::current(), interrupt, cpu_interrupt);
enable_cpu_interrupt(cpu_interrupt);
}
Ok(())
}
/// Binds the given interrupt to the given handler.
///
/// # Safety
///
/// This will replace any previously bound interrupt handler
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn()) {
let ptr = &peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler as *const _
as *mut unsafe extern "C" fn();
ptr.write_volatile(handler);
}
/// Returns the currently bound interrupt handler.
pub fn bound_handler(interrupt: Interrupt) -> Option<unsafe extern "C" fn()> {
unsafe {
let addr = peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler;
if addr as usize == 0 {
return None;
}
Some(addr)
}
}
#[no_mangle]
#[ram]
unsafe fn handle_interrupts(cpu_intr: CpuInterrupt, context: &mut TrapFrame) {
let core = Cpu::current();
let status = status(core);
// this has no effect on level interrupts, but the interrupt may be an edge one
// so we clear it anyway
clear(core, cpu_intr);
let prio: Priority =
unsafe { core::mem::transmute(INTERRUPT_TO_PRIORITY[cpu_intr as usize - 1] as u8) };
let configured_interrupts = configured_interrupts(core, status, prio);
for interrupt_nr in configured_interrupts.iterator() {
// Don't use `Interrupt::try_from`. It's slower and placed in flash
let interrupt: Interrupt = unsafe { core::mem::transmute(interrupt_nr as u16) };
handle_interrupt(interrupt, context);
}
}
#[inline(always)]
unsafe fn handle_interrupt(interrupt: Interrupt, save_frame: &mut TrapFrame) {
extern "C" {
// defined in each hal
fn EspDefaultHandler(interrupt: Interrupt);
}
let handler = peripherals::__EXTERNAL_INTERRUPTS[interrupt as usize]._handler;
if core::ptr::eq(
handler as *const _,
EspDefaultHandler as *const unsafe extern "C" fn(),
) {
EspDefaultHandler(interrupt);
} else {
let handler: fn(&mut TrapFrame) =
core::mem::transmute::<unsafe extern "C" fn(), fn(&mut TrapFrame)>(handler);
handler(save_frame);
}
}
// The compiler generates quite unfortunate code for
// ```rust,ignore
// #[no_mangle]
// #[ram]
// unsafe fn interrupt1(context: &mut TrapFrame) {
// handle_interrupts(CpuInterrupt::Interrupt1, context)
// }
// ```
//
// Resulting in
// ```asm,ignore
// interrupt1:
// add sp,sp,-16
// sw ra,12(sp)
// sw s0,8(sp)
// add s0,sp,16
// mv a1,a0
// li a0,1
// lw ra,12(sp)
// lw s0,8(sp)
// add sp,sp,16
// auipc t1,0x0
// jr handle_interrupts
// ```
//
// We can do better manually - use Rust again once/if that changes
macro_rules! interrupt_handler {
($num:literal) => {
core::arch::global_asm! {
concat!(
r#"
.section .rwtext, "ax"
.global interrupt"#,$num,r#"
interrupt"#,$num,r#":
mv a1, a0
li a0,"#,$num,r#"
j handle_interrupts
"#
)
}
};
}
interrupt_handler!(1);
interrupt_handler!(2);
interrupt_handler!(3);
interrupt_handler!(4);
interrupt_handler!(5);
interrupt_handler!(6);
interrupt_handler!(7);
interrupt_handler!(8);
interrupt_handler!(9);
interrupt_handler!(10);
interrupt_handler!(11);
interrupt_handler!(12);
interrupt_handler!(13);
interrupt_handler!(14);
interrupt_handler!(15);
#[cfg(plic)]
interrupt_handler!(16);
#[cfg(plic)]
interrupt_handler!(17);
#[cfg(plic)]
interrupt_handler!(18);
#[cfg(plic)]
interrupt_handler!(19);
}
#[cfg(not(plic))]
mod classic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::Cpu;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static INTERRUPT_TO_PRIORITY: &[usize] =
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
let cpu_interrupt_number = which as isize;
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
intr.cpu_int_enable()
.modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits()));
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
unsafe {
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
let cpu_interrupt_number = which as isize;
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
intr.cpu_int_type().modify(|r, w| {
w.bits(
r.bits() & !(1 << cpu_interrupt_number)
| (interrupt_type << cpu_interrupt_number),
)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
intr.cpu_int_pri(which as usize)
.write(|w| w.map().bits(priority as u8));
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
let cpu_interrupt_number = which as isize;
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
intr.cpu_int_clear()
.write(|w| w.bits(1 << cpu_interrupt_number));
}
}
/// Get interrupt priority
#[inline]
pub(super) fn priority_by_core(_core: Cpu, cpu_interrupt: CpuInterrupt) -> Priority {
unsafe { priority(cpu_interrupt) }
}
/// Get interrupt priority - called by assembly code
#[inline]
pub(super) unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
core::mem::transmute::<u8, Priority>(
intr.cpu_int_pri(cpu_interrupt as usize).read().map().bits(),
)
}
#[no_mangle]
#[link_section = ".trap"]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
use super::mcause;
let interrupt_id: usize = mcause::read().code(); // MSB is whether its exception or interrupt.
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
let interrupt_priority = intr
.cpu_int_pri(0)
.as_ptr()
.add(interrupt_id)
.read_volatile();
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
intr.cpu_int_thresh()
.write(|w| w.bits(interrupt_priority + 1)); // set the prio threshold to 1 more than current interrupt prio
unsafe {
riscv::interrupt::enable();
}
}
prev_interrupt_priority
}
#[no_mangle]
#[link_section = ".trap"]
pub(super) unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR;
intr.cpu_int_thresh().write(|w| w.bits(stored_prio));
}
}
#[cfg(plic)]
mod plic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::Cpu;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 31;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
// don't use interrupts reserved for CLIC (0,3,4,7)
// for some reason also CPU interrupt 8 doesn't work by default since it's
// disabled after reset - so don't use that, too
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub(super) static INTERRUPT_TO_PRIORITY: &[usize] = &[
1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
unsafe {
let plic = &*crate::peripherals::PLIC_MX::PTR;
plic.mxint_enable().modify(|r, w| {
let old = r.cpu_mxint_enable().bits();
let new = old | 1 << (which as isize);
w.cpu_mxint_enable().bits(new)
});
}
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
unsafe {
let plic = &*crate::peripherals::PLIC_MX::PTR;
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
plic.mxint_type().modify(|r, w| {
let old = r.cpu_mxint_type().bits();
let new = old & !(1 << (which as isize)) | (interrupt_type << (which as isize));
w.cpu_mxint_type().bits(new)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
unsafe {
let plic = &*crate::peripherals::PLIC_MX::PTR;
plic.mxint_pri(which as usize)
.modify(|_, w| w.cpu_mxint_pri().bits(priority as u8));
}
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
let plic = &*crate::peripherals::PLIC_MX::PTR;
plic.mxint_clear().modify(|r, w| {
let old = r.cpu_mxint_clear().bits();
let new = old | (1 << (which as isize));
w.cpu_mxint_clear().bits(new)
});
}
}
/// Get interrupt priority
#[inline]
pub(super) unsafe extern "C" fn priority_by_core(
_core: Cpu,
cpu_interrupt: CpuInterrupt,
) -> Priority {
unsafe { priority(cpu_interrupt) }
}
/// Get interrupt priority - called by assembly code
#[inline]
pub(super) unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let plic = &*crate::peripherals::PLIC_MX::PTR;
let prio = plic
.mxint_pri(cpu_interrupt as usize)
.read()
.cpu_mxint_pri()
.bits();
core::mem::transmute::<u8, Priority>(prio)
}
#[no_mangle]
#[link_section = ".trap"]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
use super::mcause;
let plic = &*crate::peripherals::PLIC_MX::PTR;
let interrupt_id: usize = mcause::read().code(); // MSB is whether its exception or interrupt.
let interrupt_priority = plic.mxint_pri(interrupt_id).read().cpu_mxint_pri().bits();
let prev_interrupt_priority = plic.mxint_thresh().read().cpu_mxint_thresh().bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
plic.mxint_thresh()
.write(|w| w.cpu_mxint_thresh().bits(interrupt_priority + 1));
unsafe {
riscv::interrupt::enable();
}
}
prev_interrupt_priority as u32
}
#[no_mangle]
#[link_section = ".trap"]
pub(super) unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
let plic = &*crate::peripherals::PLIC_MX::PTR;
plic.mxint_thresh()
.write(|w| w.cpu_mxint_thresh().bits(stored_prio as u8));
}
}
+193
View File
@@ -0,0 +1,193 @@
//! # Software Interrupts
//!
//! The [`SoftwareInterruptControl`] struct gives access to the available
//! software interrupts.
//!
//! The [`SoftwareInterrupt`] struct allows raising or resetting software
//! interrupts using the [`raise()`][SoftwareInterrupt::raise] and
//! [`reset()`][SoftwareInterrupt::reset] methods.
//!
//! ## Examples
//!
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! let sw_ints =
//! SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
//!
//! // Take the interrupt you want to use.
//! let mut int0 = sw_ints.software_interrupt0;
//!
//! // Set up the interrupt handler. Do this in a critical section so the global
//! // contains the interrupt object before the interrupt is triggered.
//! critical_section::with(|cs| {
//! int0.set_interrupt_handler(interrupt_handler);
//! SWINT0.borrow_ref_mut(cs).replace(int0);
//! });
//! # }
//!
//! # use core::cell::RefCell;
//! # use critical_section::Mutex;
//! # use esp_hal::interrupt::software::{SoftwareInterrupt, SoftwareInterruptControl};
//! // ... somewhere outside of your main function
//!
//! // Define a shared handle to the software interrupt.
//! static SWINT0: Mutex<RefCell<Option<SoftwareInterrupt<0>>>> =
//! Mutex::new(RefCell::new(None));
//!
//! #[handler]
//! fn interrupt_handler() {
//! // esp_println::println!("SW interrupt0 handled");
//!
//! // Clear the interrupt request.
//! critical_section::with(|cs| {
//! SWINT0.borrow_ref(cs).as_ref().unwrap().reset();
//! });
//! }
//! ```
use crate::{interrupt::InterruptHandler, InterruptConfigurable};
/// A software interrupt can be triggered by software.
#[non_exhaustive]
pub struct SoftwareInterrupt<const NUM: u8>;
impl<const NUM: u8> SoftwareInterrupt<NUM> {
/// Sets the interrupt handler for this software-interrupt
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
let interrupt = match NUM {
0 => crate::peripherals::Interrupt::FROM_CPU_INTR0,
1 => crate::peripherals::Interrupt::FROM_CPU_INTR1,
2 => crate::peripherals::Interrupt::FROM_CPU_INTR2,
3 => crate::peripherals::Interrupt::FROM_CPU_INTR3,
_ => unreachable!(),
};
for core in crate::Cpu::other() {
crate::interrupt::disable(core, interrupt);
}
unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) };
unwrap!(crate::interrupt::enable(interrupt, handler.priority()));
}
/// Trigger this software-interrupt
pub fn raise(&self) {
cfg_if::cfg_if! {
if #[cfg(any(esp32c6, esp32h2))] {
let system = unsafe { &*crate::peripherals::INTPRI::PTR };
} else {
let system = unsafe { &*crate::peripherals::SYSTEM::PTR };
}
}
match NUM {
0 => system
.cpu_intr_from_cpu_0()
.write(|w| w.cpu_intr_from_cpu_0().set_bit()),
1 => system
.cpu_intr_from_cpu_1()
.write(|w| w.cpu_intr_from_cpu_1().set_bit()),
2 => system
.cpu_intr_from_cpu_2()
.write(|w| w.cpu_intr_from_cpu_2().set_bit()),
3 => system
.cpu_intr_from_cpu_3()
.write(|w| w.cpu_intr_from_cpu_3().set_bit()),
_ => unreachable!(),
};
}
/// Resets this software-interrupt
pub fn reset(&self) {
cfg_if::cfg_if! {
if #[cfg(any(esp32c6, esp32h2))] {
let system = unsafe { &*crate::peripherals::INTPRI::PTR };
} else {
let system = unsafe { &*crate::peripherals::SYSTEM::PTR };
}
}
match NUM {
0 => system
.cpu_intr_from_cpu_0()
.write(|w| w.cpu_intr_from_cpu_0().clear_bit()),
1 => system
.cpu_intr_from_cpu_1()
.write(|w| w.cpu_intr_from_cpu_1().clear_bit()),
2 => system
.cpu_intr_from_cpu_2()
.write(|w| w.cpu_intr_from_cpu_2().clear_bit()),
3 => system
.cpu_intr_from_cpu_3()
.write(|w| w.cpu_intr_from_cpu_3().clear_bit()),
_ => unreachable!(),
};
}
/// Unsafely create an instance of this peripheral out of thin air.
///
/// # Safety
///
/// You must ensure that you're only using one instance of this type at a
/// time.
#[inline]
pub unsafe fn steal() -> Self {
Self
}
}
impl<const NUM: u8> crate::peripheral::Peripheral for SoftwareInterrupt<NUM> {
type P = SoftwareInterrupt<NUM>;
#[inline]
unsafe fn clone_unchecked(&self) -> Self::P {
Self::steal()
}
}
impl<const NUM: u8> crate::private::Sealed for SoftwareInterrupt<NUM> {}
impl<const NUM: u8> InterruptConfigurable for SoftwareInterrupt<NUM> {
fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
SoftwareInterrupt::set_interrupt_handler(self, handler);
}
}
/// This gives access to the available software interrupts.
///
/// This struct contains several instances of software interrupts that can be
/// used for signaling between different parts of a program or system. Each
/// interrupt is identified by an index (0 to 3).
#[cfg_attr(
multi_core,
doc = r#"
Please note: Software interrupt 3 is reserved
for inter-processor communication when using
`esp-hal-embassy`."#
)]
#[non_exhaustive]
pub struct SoftwareInterruptControl {
/// Software interrupt 0.
pub software_interrupt0: SoftwareInterrupt<0>,
/// Software interrupt 1.
pub software_interrupt1: SoftwareInterrupt<1>,
/// Software interrupt 2.
pub software_interrupt2: SoftwareInterrupt<2>,
#[cfg(not(all(feature = "__esp_hal_embassy", multi_core)))]
/// Software interrupt 3. Only available when not using `esp-hal-embassy`,
/// or on single-core systems.
pub software_interrupt3: SoftwareInterrupt<3>,
}
impl SoftwareInterruptControl {
/// Create a new instance of the software interrupt control.
pub fn new(_peripheral: crate::peripherals::SW_INTERRUPT) -> Self {
SoftwareInterruptControl {
software_interrupt0: SoftwareInterrupt {},
software_interrupt1: SoftwareInterrupt {},
software_interrupt2: SoftwareInterrupt {},
#[cfg(not(all(feature = "__esp_hal_embassy", multi_core)))]
software_interrupt3: SoftwareInterrupt {},
}
}
}
+714
View File
@@ -0,0 +1,714 @@
//! Interrupt handling
use xtensa_lx::interrupt;
use xtensa_lx_rt::exception::Context;
pub use self::vectored::*;
use super::InterruptStatus;
use crate::{
peripherals::{self, Interrupt},
Cpu,
};
/// Interrupt Error
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// The given interrupt is not a valid interrupt
InvalidInterrupt,
/// The CPU interrupt is a reserved interrupt
CpuInterruptReserved,
}
/// Enumeration of available CPU interrupts
///
/// It's possible to create one handler per priority level. (e.g
/// `level1_interrupt`)
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u32)]
pub enum CpuInterrupt {
/// Level-triggered interrupt with priority 1.
Interrupt0LevelPriority1 = 0,
/// Level-triggered interrupt with priority 1.
Interrupt1LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt2LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt3LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt4LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt5LevelPriority1,
/// Timer 0 interrupt with priority 1.
Interrupt6Timer0Priority1,
/// Software-triggered interrupt with priority 1.
Interrupt7SoftwarePriority1,
/// Level-triggered interrupt with priority 1.
Interrupt8LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt9LevelPriority1,
/// Edge-triggered interrupt with priority 1.
Interrupt10EdgePriority1,
/// Profiling-related interrupt with priority 3.
Interrupt11ProfilingPriority3,
/// Level-triggered interrupt with priority 1.
Interrupt12LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt13LevelPriority1,
/// Non-maskable interrupt (NMI) with priority 7.
Interrupt14NmiPriority7,
/// Timer 1 interrupt with priority 3.
Interrupt15Timer1Priority3,
/// Timer 2 interrupt with priority 5.
Interrupt16Timer2Priority5,
/// Level-triggered interrupt with priority 1.
Interrupt17LevelPriority1,
/// Level-triggered interrupt with priority 1.
Interrupt18LevelPriority1,
/// Level-triggered interrupt with priority 2.
Interrupt19LevelPriority2,
/// Level-triggered interrupt with priority 2.
Interrupt20LevelPriority2,
/// Level-triggered interrupt with priority 2.
Interrupt21LevelPriority2,
/// Edge-triggered interrupt with priority 3.
Interrupt22EdgePriority3,
/// Level-triggered interrupt with priority 3.
Interrupt23LevelPriority3,
/// Level-triggered interrupt with priority 4.
Interrupt24LevelPriority4,
/// Level-triggered interrupt with priority 4.
Interrupt25LevelPriority4,
/// Level-triggered interrupt with priority 5.
Interrupt26LevelPriority5,
/// Level-triggered interrupt with priority 3.
Interrupt27LevelPriority3,
/// Edge-triggered interrupt with priority 4.
Interrupt28EdgePriority4,
/// Software-triggered interrupt with priority 3.
Interrupt29SoftwarePriority3,
/// Edge-triggered interrupt with priority 4.
Interrupt30EdgePriority4,
/// Edge-triggered interrupt with priority 5.
Interrupt31EdgePriority5,
}
impl CpuInterrupt {
fn from_u32(n: u32) -> Option<Self> {
if n < 32 {
Some(unsafe { core::mem::transmute::<u32, Self>(n) })
} else {
None
}
}
fn is_internal(self) -> bool {
matches!(
self,
Self::Interrupt6Timer0Priority1
| Self::Interrupt7SoftwarePriority1
| Self::Interrupt11ProfilingPriority3
| Self::Interrupt15Timer1Priority3
| Self::Interrupt16Timer2Priority5
| Self::Interrupt29SoftwarePriority3
)
}
fn is_peripheral(self) -> bool {
!self.is_internal()
}
}
/// The interrupts reserved by the HAL
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub static RESERVED_INTERRUPTS: &[usize] = &[
CpuInterrupt::Interrupt1LevelPriority1 as _,
CpuInterrupt::Interrupt19LevelPriority2 as _,
CpuInterrupt::Interrupt23LevelPriority3 as _,
CpuInterrupt::Interrupt10EdgePriority1 as _,
CpuInterrupt::Interrupt22EdgePriority3 as _,
];
pub(crate) fn setup_interrupts() {
// disable all known interrupts
// at least after the 2nd stage bootloader there are some interrupts enabled
// (e.g. UART)
for peripheral_interrupt in 0..255 {
crate::soc::peripherals::Interrupt::try_from(peripheral_interrupt)
.map(|intr| {
#[cfg(multi_core)]
disable(Cpu::AppCpu, intr);
disable(Cpu::ProCpu, intr);
})
.ok();
}
}
/// Enable an interrupt by directly binding it to a available CPU interrupt
///
/// Unless you are sure, you most likely want to use [`enable`] instead.
///
/// Trying using a reserved interrupt from [`RESERVED_INTERRUPTS`] will return
/// an error.
pub fn enable_direct(interrupt: Interrupt, cpu_interrupt: CpuInterrupt) -> Result<(), Error> {
if RESERVED_INTERRUPTS.contains(&(cpu_interrupt as _)) {
return Err(Error::CpuInterruptReserved);
}
unsafe {
map(Cpu::current(), interrupt, cpu_interrupt);
xtensa_lx::interrupt::enable_mask(
xtensa_lx::interrupt::get_mask() | 1 << cpu_interrupt as u32,
);
}
Ok(())
}
/// Assign a peripheral interrupt to an CPU interrupt
///
/// Note: this only maps the interrupt to the CPU interrupt. The CPU interrupt
/// still needs to be enabled afterwards
///
/// # Safety
///
/// Do not use CPU interrupts in the [`RESERVED_INTERRUPTS`].
pub unsafe fn map(core: Cpu, interrupt: Interrupt, which: CpuInterrupt) {
let interrupt_number = interrupt as isize;
let cpu_interrupt_number = which as isize;
let intr_map_base = match core {
Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map().as_ptr(),
#[cfg(multi_core)]
Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map().as_ptr(),
};
intr_map_base
.offset(interrupt_number)
.write_volatile(cpu_interrupt_number as u32);
}
/// Get cpu interrupt assigned to peripheral interrupt
pub(crate) fn bound_cpu_interrupt_for(cpu: Cpu, interrupt: Interrupt) -> Option<CpuInterrupt> {
let interrupt_number = interrupt as isize;
let intr_map_base = match cpu {
Cpu::ProCpu => unsafe { (*core0_interrupt_peripheral()).pro_mac_intr_map().as_ptr() },
#[cfg(multi_core)]
Cpu::AppCpu => unsafe { (*core1_interrupt_peripheral()).app_mac_intr_map().as_ptr() },
};
let cpu_intr = unsafe { intr_map_base.offset(interrupt_number).read_volatile() };
let cpu_intr = CpuInterrupt::from_u32(cpu_intr)?;
if cpu_intr.is_peripheral() {
Some(cpu_intr)
} else {
None
}
}
/// Disable the given peripheral interrupt
pub fn disable(core: Cpu, interrupt: Interrupt) {
unsafe {
let interrupt_number = interrupt as isize;
let intr_map_base = match core {
Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map().as_ptr(),
#[cfg(multi_core)]
Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map().as_ptr(),
};
// To disable an interrupt, map it to a CPU peripheral interrupt
intr_map_base
.offset(interrupt_number)
.write_volatile(CpuInterrupt::Interrupt16Timer2Priority5 as _);
}
}
/// Clear the given CPU interrupt
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
xtensa_lx::interrupt::clear(1 << which as u32);
}
}
/// Get status of peripheral interrupts
#[cfg(large_intr_status)]
pub fn status(core: Cpu) -> InterruptStatus {
unsafe {
match core {
Cpu::ProCpu => InterruptStatus::from(
(*core0_interrupt_peripheral())
.pro_intr_status_0()
.read()
.bits(),
(*core0_interrupt_peripheral())
.pro_intr_status_1()
.read()
.bits(),
(*core0_interrupt_peripheral())
.pro_intr_status_2()
.read()
.bits(),
),
#[cfg(multi_core)]
Cpu::AppCpu => InterruptStatus::from(
(*core1_interrupt_peripheral())
.app_intr_status_0()
.read()
.bits(),
(*core1_interrupt_peripheral())
.app_intr_status_1()
.read()
.bits(),
(*core1_interrupt_peripheral())
.app_intr_status_2()
.read()
.bits(),
),
}
}
}
/// Get status of peripheral interrupts
#[cfg(very_large_intr_status)]
pub fn status(core: Cpu) -> InterruptStatus {
unsafe {
match core {
Cpu::ProCpu => InterruptStatus::from(
(*core0_interrupt_peripheral())
.pro_intr_status_0()
.read()
.bits(),
(*core0_interrupt_peripheral())
.pro_intr_status_1()
.read()
.bits(),
(*core0_interrupt_peripheral())
.pro_intr_status_2()
.read()
.bits(),
(*core0_interrupt_peripheral())
.pro_intr_status_3()
.read()
.bits(),
),
#[cfg(multi_core)]
Cpu::AppCpu => InterruptStatus::from(
(*core1_interrupt_peripheral())
.app_intr_status_0()
.read()
.bits(),
(*core1_interrupt_peripheral())
.app_intr_status_1()
.read()
.bits(),
(*core1_interrupt_peripheral())
.app_intr_status_2()
.read()
.bits(),
(*core1_interrupt_peripheral())
.app_intr_status_3()
.read()
.bits(),
),
}
}
}
#[cfg(esp32)]
unsafe fn core0_interrupt_peripheral() -> *const crate::peripherals::dport::RegisterBlock {
crate::peripherals::DPORT::PTR
}
#[cfg(esp32)]
unsafe fn core1_interrupt_peripheral() -> *const crate::peripherals::dport::RegisterBlock {
crate::peripherals::DPORT::PTR
}
#[cfg(any(esp32s2, esp32s3))]
unsafe fn core0_interrupt_peripheral() -> *const crate::peripherals::interrupt_core0::RegisterBlock
{
crate::peripherals::INTERRUPT_CORE0::PTR
}
#[cfg(esp32s3)]
unsafe fn core1_interrupt_peripheral() -> *const crate::peripherals::interrupt_core1::RegisterBlock
{
crate::peripherals::INTERRUPT_CORE1::PTR
}
mod vectored {
use procmacros::ram;
use super::*;
/// Interrupt priority levels.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Priority {
/// No priority.
None = 0,
/// Priority level 1.
Priority1,
/// Priority level 2.
Priority2,
/// Priority level 3.
Priority3,
}
impl Priority {
/// Maximum interrupt priority
pub const fn max() -> Priority {
Priority::Priority3
}
/// Minimum interrupt priority
pub const fn min() -> Priority {
Priority::Priority1
}
}
impl CpuInterrupt {
#[inline]
fn level(&self) -> Priority {
match self {
CpuInterrupt::Interrupt0LevelPriority1
| CpuInterrupt::Interrupt1LevelPriority1
| CpuInterrupt::Interrupt2LevelPriority1
| CpuInterrupt::Interrupt3LevelPriority1
| CpuInterrupt::Interrupt4LevelPriority1
| CpuInterrupt::Interrupt5LevelPriority1
| CpuInterrupt::Interrupt6Timer0Priority1
| CpuInterrupt::Interrupt7SoftwarePriority1
| CpuInterrupt::Interrupt8LevelPriority1
| CpuInterrupt::Interrupt9LevelPriority1
| CpuInterrupt::Interrupt10EdgePriority1
| CpuInterrupt::Interrupt12LevelPriority1
| CpuInterrupt::Interrupt13LevelPriority1
| CpuInterrupt::Interrupt17LevelPriority1
| CpuInterrupt::Interrupt18LevelPriority1 => Priority::Priority1,
CpuInterrupt::Interrupt19LevelPriority2
| CpuInterrupt::Interrupt20LevelPriority2
| CpuInterrupt::Interrupt21LevelPriority2 => Priority::Priority2,
CpuInterrupt::Interrupt11ProfilingPriority3
| CpuInterrupt::Interrupt15Timer1Priority3
| CpuInterrupt::Interrupt22EdgePriority3
| CpuInterrupt::Interrupt27LevelPriority3
| CpuInterrupt::Interrupt29SoftwarePriority3
| CpuInterrupt::Interrupt23LevelPriority3 => Priority::Priority3,
// we direct these to None because we do not support interrupts at this level
// through Rust
CpuInterrupt::Interrupt24LevelPriority4
| CpuInterrupt::Interrupt25LevelPriority4
| CpuInterrupt::Interrupt28EdgePriority4
| CpuInterrupt::Interrupt30EdgePriority4
| CpuInterrupt::Interrupt31EdgePriority5
| CpuInterrupt::Interrupt16Timer2Priority5
| CpuInterrupt::Interrupt26LevelPriority5
| CpuInterrupt::Interrupt14NmiPriority7 => Priority::None,
}
}
}
/// Get the interrupts configured for the core
#[inline(always)]
fn configured_interrupts(core: Cpu, status: InterruptStatus, level: u32) -> InterruptStatus {
unsafe {
let intr_map_base = match core {
Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map().as_ptr(),
#[cfg(multi_core)]
Cpu::AppCpu => (*core1_interrupt_peripheral()).app_mac_intr_map().as_ptr(),
};
let mut res = InterruptStatus::empty();
for interrupt_nr in status.iterator() {
let i = interrupt_nr as isize;
let cpu_interrupt = intr_map_base.offset(i).read_volatile();
// safety: cast is safe because of repr(u32)
let cpu_interrupt: CpuInterrupt =
core::mem::transmute::<u32, CpuInterrupt>(cpu_interrupt);
let int_level = cpu_interrupt.level() as u8 as u32;
if int_level == level {
res.set(interrupt_nr as u16);
}
}
res
}
}
/// Enable the given peripheral interrupt
pub fn enable(interrupt: Interrupt, level: Priority) -> Result<(), Error> {
let cpu_interrupt =
interrupt_level_to_cpu_interrupt(level, chip_specific::interrupt_is_edge(interrupt))?;
unsafe {
map(Cpu::current(), interrupt, cpu_interrupt);
xtensa_lx::interrupt::enable_mask(
xtensa_lx::interrupt::get_mask() | 1 << cpu_interrupt as u32,
);
}
Ok(())
}
/// Binds the given interrupt to the given handler.
///
/// # Safety
///
/// This will replace any previously bound interrupt handler
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn()) {
let ptr = &peripherals::__INTERRUPTS[interrupt as usize]._handler as *const _
as *mut unsafe extern "C" fn();
ptr.write_volatile(handler);
}
/// Returns the currently bound interrupt handler.
pub fn bound_handler(interrupt: Interrupt) -> Option<unsafe extern "C" fn()> {
unsafe {
let addr = peripherals::__INTERRUPTS[interrupt as usize]._handler;
if addr as usize == 0 {
return None;
}
Some(addr)
}
}
fn interrupt_level_to_cpu_interrupt(
level: Priority,
is_edge: bool,
) -> Result<CpuInterrupt, Error> {
Ok(if is_edge {
match level {
Priority::None => return Err(Error::InvalidInterrupt),
Priority::Priority1 => CpuInterrupt::Interrupt10EdgePriority1,
Priority::Priority2 => return Err(Error::InvalidInterrupt),
Priority::Priority3 => CpuInterrupt::Interrupt22EdgePriority3,
}
} else {
match level {
Priority::None => return Err(Error::InvalidInterrupt),
Priority::Priority1 => CpuInterrupt::Interrupt1LevelPriority1,
Priority::Priority2 => CpuInterrupt::Interrupt19LevelPriority2,
Priority::Priority3 => CpuInterrupt::Interrupt23LevelPriority3,
}
})
}
// TODO use CpuInterrupt::LevelX.mask() // TODO make it const
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
static CPU_INTERRUPT_LEVELS: [u32; 8] = [
0b_0000_0000_0000_0000_0000_0000_0000_0000, // Dummy level 0
0b_0000_0000_0000_0110_0011_0111_1111_1111, // Level_1
0b_0000_0000_0011_1000_0000_0000_0000_0000, // Level 2
0b_0010_1000_1100_0000_1000_1000_0000_0000, // Level 3
0b_0101_0011_0000_0000_0000_0000_0000_0000, // Level 4
0b_1000_0100_0000_0001_0000_0000_0000_0000, // Level 5
0b_0000_0000_0000_0000_0000_0000_0000_0000, // Level 6
0b_0000_0000_0000_0000_0100_0000_0000_0000, // Level 7
];
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
static CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
static CPU_INTERRUPT_EDGE: u32 = 0b_0111_0000_0100_0000_0000_1100_1000_0000;
#[inline]
fn cpu_interrupt_nr_to_cpu_interrupt_handler(
number: u32,
) -> Option<unsafe extern "C" fn(u32, save_frame: &mut Context)> {
use xtensa_lx_rt::*;
// we're fortunate that all esp variants use the same CPU interrupt layout
Some(match number {
6 => Timer0,
7 => Software0,
11 => Profiling,
14 => NMI,
15 => Timer1,
16 => Timer2,
29 => Software1,
_ => return None,
})
}
// The linker script defines `__level_1_interrupt`, `__level_2_interrupt` and
// `__level_3_interrupt` as `handle_interrupts`
#[no_mangle]
#[ram]
unsafe fn handle_interrupts(level: u32, save_frame: &mut Context) {
let core = Cpu::current();
let cpu_interrupt_mask =
interrupt::get() & interrupt::get_mask() & CPU_INTERRUPT_LEVELS[level as usize];
if cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL != 0 {
// Let's handle CPU-internal interrupts (NMI, Timer, Software, Profiling).
// These are rarely used by the HAL.
// Mask the relevant bits
let cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL;
// Pick one
let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros();
// If the interrupt is edge triggered, we need to clear the request on the CPU's
// side.
if ((1 << cpu_interrupt_nr) & CPU_INTERRUPT_EDGE) != 0 {
interrupt::clear(1 << cpu_interrupt_nr);
}
if let Some(handler) = cpu_interrupt_nr_to_cpu_interrupt_handler(cpu_interrupt_nr) {
handler(level, save_frame);
}
} else {
let status = if (cpu_interrupt_mask & CPU_INTERRUPT_EDGE) != 0 {
// Next, handle edge triggered peripheral interrupts.
// If the interrupt is edge triggered, we need to clear the
// request on the CPU's side
interrupt::clear(cpu_interrupt_mask & CPU_INTERRUPT_EDGE);
// For edge interrupts we cannot rely on the peripherals' interrupt status
// registers, therefore call all registered handlers for current level.
chip_specific::INTERRUPT_EDGE
} else {
// Finally, check level-triggered peripheral sources.
// These interrupts are cleared by the peripheral.
status(core)
};
let configured_interrupts = configured_interrupts(core, status, level);
for interrupt_nr in configured_interrupts.iterator() {
// Don't use `Interrupt::try_from`. It's slower and placed in flash
let interrupt: Interrupt = unsafe { core::mem::transmute(interrupt_nr as u16) };
handle_interrupt(level, interrupt, save_frame);
}
}
}
#[ram]
unsafe fn handle_interrupt(level: u32, interrupt: Interrupt, save_frame: &mut Context) {
extern "C" {
// defined in each hal
fn EspDefaultHandler(level: u32, interrupt: Interrupt);
}
let handler = peripherals::__INTERRUPTS[interrupt as usize]._handler;
if core::ptr::eq(
handler as *const _,
EspDefaultHandler as *const unsafe extern "C" fn(),
) {
EspDefaultHandler(level, interrupt);
} else {
let handler: fn(&mut Context) =
core::mem::transmute::<unsafe extern "C" fn(), fn(&mut Context)>(handler);
handler(save_frame);
}
}
#[allow(clippy::unusual_byte_groupings)]
#[cfg(esp32)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1111_1100_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0000_0000_0011,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
use peripherals::Interrupt::*;
[
TG0_T0_EDGE,
TG0_T1_EDGE,
TG0_WDT_EDGE,
TG0_LACT_EDGE,
TG1_T0_EDGE,
TG1_T1_EDGE,
TG1_WDT_EDGE,
TG1_LACT_EDGE,
]
.contains(&interrupt)
}
}
#[cfg(esp32s2)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1100_0000_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0011_1011_1111,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
use peripherals::Interrupt::*;
[
TG0_T0_EDGE,
TG0_T1_EDGE,
TG0_WDT_EDGE,
TG0_LACT_EDGE,
TG1_T0_EDGE,
TG1_T1_EDGE,
TG1_WDT_EDGE,
TG1_LACT_EDGE,
SYSTIMER_TARGET0,
SYSTIMER_TARGET1,
SYSTIMER_TARGET2,
]
.contains(&interrupt)
}
}
#[cfg(esp32s3)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::empty();
#[inline]
pub fn interrupt_is_edge(_interrupt: Interrupt) -> bool {
false
}
}
}
mod raw {
use super::*;
extern "C" {
fn level4_interrupt(save_frame: &mut Context);
fn level5_interrupt(save_frame: &mut Context);
fn level6_interrupt(save_frame: &mut Context);
fn level7_interrupt(save_frame: &mut Context);
}
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __level_4_interrupt(_level: u32, save_frame: &mut Context) {
level4_interrupt(save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __level_5_interrupt(_level: u32, save_frame: &mut Context) {
level5_interrupt(save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __level_6_interrupt(_level: u32, save_frame: &mut Context) {
level6_interrupt(save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __level_7_interrupt(_level: u32, save_frame: &mut Context) {
level7_interrupt(save_frame)
}
}