use xtensa_lx::interrupt;
pub(crate) use xtensa_lx::interrupt::free;
use xtensa_lx_rt::exception::Context;
pub use self::vectored::*;
use super::InterruptStatus;
use crate::{pac, peripherals::Interrupt, system::Cpu};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
InvalidInterrupt,
CpuInterruptReserved,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u32)]
pub enum CpuInterrupt {
Interrupt0LevelPriority1 = 0,
Interrupt1LevelPriority1,
Interrupt2LevelPriority1,
Interrupt3LevelPriority1,
Interrupt4LevelPriority1,
Interrupt5LevelPriority1,
Interrupt6Timer0Priority1,
Interrupt7SoftwarePriority1,
Interrupt8LevelPriority1,
Interrupt9LevelPriority1,
Interrupt10EdgePriority1,
Interrupt11ProfilingPriority3,
Interrupt12LevelPriority1,
Interrupt13LevelPriority1,
Interrupt14NmiPriority7,
Interrupt15Timer1Priority3,
Interrupt16Timer2Priority5,
Interrupt17LevelPriority1,
Interrupt18LevelPriority1,
Interrupt19LevelPriority2,
Interrupt20LevelPriority2,
Interrupt21LevelPriority2,
Interrupt22EdgePriority3,
Interrupt23LevelPriority3,
Interrupt24LevelPriority4,
Interrupt25LevelPriority4,
Interrupt26LevelPriority5,
Interrupt27LevelPriority3,
Interrupt28EdgePriority4,
Interrupt29SoftwarePriority3,
Interrupt30EdgePriority4,
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()
}
}
#[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() {
for peripheral_interrupt in 0..255 {
Interrupt::try_from(peripheral_interrupt)
.map(|intr| {
#[cfg(multi_core)]
disable(Cpu::AppCpu, intr);
disable(Cpu::ProCpu, intr);
})
.ok();
}
}
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(())
}
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);
}
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
}
}
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(),
};
intr_map_base
.offset(interrupt_number)
.write_volatile(CpuInterrupt::Interrupt16Timer2Priority5 as _);
}
}
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
xtensa_lx::interrupt::clear(1 << which as u32);
}
}
#[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(),
),
}
}
}
#[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::pac::dport::RegisterBlock {
pac::DPORT::PTR
}
#[cfg(esp32)]
unsafe fn core1_interrupt_peripheral() -> *const crate::pac::dport::RegisterBlock {
pac::DPORT::PTR
}
#[cfg(any(esp32s2, esp32s3))]
unsafe fn core0_interrupt_peripheral() -> *const crate::pac::interrupt_core0::RegisterBlock {
pac::INTERRUPT_CORE0::PTR
}
#[cfg(esp32s3)]
unsafe fn core1_interrupt_peripheral() -> *const crate::pac::interrupt_core1::RegisterBlock {
pac::INTERRUPT_CORE1::PTR
}
pub(crate) fn current_runlevel() -> Priority {
let ps: u32;
unsafe { core::arch::asm!("rsr.ps {0}", out(reg) ps) };
let prev_interrupt_priority = ps as u8 & 0x0F;
unwrap!(Priority::try_from(prev_interrupt_priority))
}
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
let token: u32;
match level {
Priority::None => core::arch::asm!("rsil {0}, 0", out(reg) token),
Priority::Priority1 => core::arch::asm!("rsil {0}, 1", out(reg) token),
Priority::Priority2 => core::arch::asm!("rsil {0}, 2", out(reg) token),
Priority::Priority3 => core::arch::asm!("rsil {0}, 3", out(reg) token),
};
let prev_interrupt_priority = token as u8 & 0x0F;
unwrap!(Priority::try_from(prev_interrupt_priority))
}
mod vectored {
use procmacros::ram;
use super::*;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Priority {
None = 0,
Priority1,
Priority2,
Priority3,
}
impl Priority {
pub const fn max() -> Priority {
Priority::Priority3
}
pub const fn min() -> Priority {
Priority::Priority1
}
}
impl TryFrom<u8> for Priority {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Priority::None),
1 => Ok(Priority::Priority1),
2 => Ok(Priority::Priority2),
3 => Ok(Priority::Priority3),
_ => Err(Error::InvalidInterrupt),
}
}
}
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,
CpuInterrupt::Interrupt24LevelPriority4
| CpuInterrupt::Interrupt25LevelPriority4
| CpuInterrupt::Interrupt28EdgePriority4
| CpuInterrupt::Interrupt30EdgePriority4
| CpuInterrupt::Interrupt31EdgePriority5
| CpuInterrupt::Interrupt16Timer2Priority5
| CpuInterrupt::Interrupt26LevelPriority5
| CpuInterrupt::Interrupt14NmiPriority7 => Priority::None,
}
}
}
#[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();
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
}
}
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(())
}
pub unsafe fn bind_interrupt(interrupt: Interrupt, handler: unsafe extern "C" fn()) {
let ptr = &pac::__INTERRUPTS[interrupt as usize]._handler as *const _
as *mut unsafe extern "C" fn();
ptr.write_volatile(handler);
}
pub fn bound_handler(interrupt: Interrupt) -> Option<unsafe extern "C" fn()> {
unsafe {
let addr = pac::__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,
}
})
}
#[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, 0b_0000_0000_0000_0110_0011_0111_1111_1111, 0b_0000_0000_0011_1000_0000_0000_0000_0000, 0b_0010_1000_1100_0000_1000_1000_0000_0000, 0b_0101_0011_0000_0000_0000_0000_0000_0000, 0b_1000_0100_0000_0001_0000_0000_0000_0000, 0b_0000_0000_0000_0000_0000_0000_0000_0000, 0b_0000_0000_0000_0000_0100_0000_0000_0000, ];
#[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::*;
Some(match number {
6 => Timer0,
7 => Software0,
11 => Profiling,
14 => NMI,
15 => Timer1,
16 => Timer2,
29 => Software1,
_ => return None,
})
}
#[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 cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_INTERNAL;
let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros();
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 {
interrupt::clear(cpu_interrupt_mask & CPU_INTERRUPT_EDGE);
chip_specific::INTERRUPT_EDGE
} else {
status(core)
};
let configured_interrupts = configured_interrupts(core, status, level);
for interrupt_nr in configured_interrupts.iterator() {
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" {
fn EspDefaultHandler(level: u32, interrupt: Interrupt);
}
let handler = pac::__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);
}
}
#[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 {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::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 {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::TG1_LACT_EDGE,
Interrupt::SYSTIMER_TARGET0,
Interrupt::SYSTIMER_TARGET1,
Interrupt::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)
}
}