565 lines
20 KiB
Rust
565 lines
20 KiB
Rust
use xtensa_lx::interrupt::{self, InterruptNumber};
|
|
use xtensa_lx_rt::exception::Context;
|
|
|
|
use crate::{
|
|
pac::{self, Interrupt},
|
|
Cpu,
|
|
};
|
|
|
|
/// Enumeration of available CPU interrupts
|
|
/// It's possible to create one handler per priority level. (e.g
|
|
/// `level1_interrupt`)
|
|
#[allow(unused)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[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,
|
|
}
|
|
|
|
/// Assign a peripheral interrupt to an CPU interrupt.
|
|
///
|
|
/// Great care **must** be taken when using this function with interrupt
|
|
/// vectoring (enabled by default). Avoid the following CPU interrupts:
|
|
/// - Interrupt1LevelPriority1
|
|
/// - Interrupt19LevelPriority2
|
|
/// - Interrupt23LevelPriority3
|
|
/// - Interrupt10EdgePriority1
|
|
/// - Interrupt22EdgePriority3
|
|
/// As they are preallocated for interrupt vectoring.
|
|
///
|
|
/// Note: this only maps the interrupt to the CPU interrupt. The CPU interrupt
|
|
/// still needs to be enabled afterwards
|
|
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(),
|
|
#[cfg(single_core)]
|
|
Cpu::AppCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(),
|
|
};
|
|
intr_map_base
|
|
.offset(interrupt_number)
|
|
.write_volatile(cpu_interrupt_number as u32);
|
|
}
|
|
|
|
/// 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(),
|
|
#[cfg(single_core)]
|
|
Cpu::AppCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(),
|
|
};
|
|
intr_map_base.offset(interrupt_number).write_volatile(0);
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
pub fn get_status(core: Cpu) -> u128 {
|
|
unsafe {
|
|
match core {
|
|
Cpu::ProCpu => {
|
|
((*core0_interrupt_peripheral())
|
|
.pro_intr_status_0
|
|
.read()
|
|
.bits() as u128)
|
|
| ((*core0_interrupt_peripheral())
|
|
.pro_intr_status_1
|
|
.read()
|
|
.bits() as u128)
|
|
<< 32
|
|
| ((*core0_interrupt_peripheral())
|
|
.pro_intr_status_2
|
|
.read()
|
|
.bits() as u128)
|
|
<< 64
|
|
}
|
|
#[cfg(multi_core)]
|
|
Cpu::AppCpu => {
|
|
((*core1_interrupt_peripheral())
|
|
.app_intr_status_0
|
|
.read()
|
|
.bits() as u128)
|
|
| ((*core1_interrupt_peripheral())
|
|
.app_intr_status_1
|
|
.read()
|
|
.bits() as u128)
|
|
<< 32
|
|
| ((*core1_interrupt_peripheral())
|
|
.app_intr_status_2
|
|
.read()
|
|
.bits() as u128)
|
|
<< 64
|
|
}
|
|
#[cfg(single_core)]
|
|
Cpu::AppCpu => {
|
|
((*core0_interrupt_peripheral())
|
|
.pro_intr_status_0
|
|
.read()
|
|
.bits() as u128)
|
|
| ((*core0_interrupt_peripheral())
|
|
.pro_intr_status_1
|
|
.read()
|
|
.bits() as u128)
|
|
<< 32
|
|
| ((*core0_interrupt_peripheral())
|
|
.pro_intr_status_2
|
|
.read()
|
|
.bits() as u128)
|
|
<< 64
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
unsafe fn core0_interrupt_peripheral() -> *const crate::pac::dport::RegisterBlock {
|
|
crate::pac::DPORT::PTR
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
unsafe fn core1_interrupt_peripheral() -> *const crate::pac::dport::RegisterBlock {
|
|
crate::pac::DPORT::PTR
|
|
}
|
|
|
|
#[cfg(esp32s2)]
|
|
unsafe fn core0_interrupt_peripheral() -> *const crate::pac::interrupt::RegisterBlock {
|
|
crate::pac::INTERRUPT::PTR
|
|
}
|
|
|
|
#[cfg(esp32s3)]
|
|
unsafe fn core0_interrupt_peripheral() -> *const crate::pac::interrupt_core0::RegisterBlock {
|
|
crate::pac::INTERRUPT_CORE0::PTR
|
|
}
|
|
|
|
#[cfg(esp32s3)]
|
|
unsafe fn core1_interrupt_peripheral() -> *const crate::pac::interrupt_core1::RegisterBlock {
|
|
crate::pac::INTERRUPT_CORE1::PTR
|
|
}
|
|
|
|
#[cfg(feature = "vectored")]
|
|
pub use vectored::*;
|
|
|
|
#[cfg(feature = "vectored")]
|
|
mod vectored {
|
|
use procmacros::ram;
|
|
|
|
use super::*;
|
|
use crate::get_core;
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum Error {
|
|
InvalidInterrupt,
|
|
}
|
|
|
|
/// Interrupt priority levels.
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
pub enum Priority {
|
|
None = 0,
|
|
Priority1,
|
|
Priority2,
|
|
Priority3,
|
|
}
|
|
|
|
impl Priority {
|
|
pub fn max() -> Priority {
|
|
Priority::Priority3
|
|
}
|
|
|
|
pub 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]
|
|
fn get_configured_interrupts(core: Cpu, mut status: u128) -> [u128; 8] {
|
|
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(),
|
|
#[cfg(single_core)]
|
|
Cpu::AppCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map.as_ptr(),
|
|
};
|
|
|
|
let mut levels = [0u128; 8];
|
|
|
|
while status != 0 {
|
|
let interrupt_nr = status.trailing_zeros();
|
|
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(cpu_interrupt);
|
|
let level = cpu_interrupt.level() as u8 as usize;
|
|
|
|
levels[level] |= 1 << i;
|
|
status &= !(1u128 << interrupt_nr);
|
|
}
|
|
|
|
levels
|
|
}
|
|
}
|
|
|
|
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(get_core(), interrupt, cpu_interrupt);
|
|
|
|
xtensa_lx::interrupt::enable_mask(
|
|
xtensa_lx::interrupt::get_mask() | 1 << cpu_interrupt as u32,
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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
|
|
const 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
|
|
];
|
|
const CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000;
|
|
const 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,
|
|
})
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[link_section = ".rwtext"]
|
|
unsafe fn __level_1_interrupt(level: u32, save_frame: &mut Context) {
|
|
handle_interrupts(level, save_frame)
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[link_section = ".rwtext"]
|
|
unsafe fn __level_2_interrupt(level: u32, save_frame: &mut Context) {
|
|
handle_interrupts(level, save_frame)
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[link_section = ".rwtext"]
|
|
unsafe fn __level_3_interrupt(level: u32, save_frame: &mut Context) {
|
|
handle_interrupts(level, save_frame)
|
|
}
|
|
|
|
#[ram]
|
|
unsafe fn handle_interrupts(level: u32, save_frame: &mut Context) {
|
|
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 (cpu_interrupt_mask & 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 {
|
|
if (cpu_interrupt_mask & CPU_INTERRUPT_EDGE) != 0 {
|
|
let cpu_interrupt_mask = cpu_interrupt_mask & CPU_INTERRUPT_EDGE;
|
|
let cpu_interrupt_nr = cpu_interrupt_mask.trailing_zeros();
|
|
interrupt::clear(1 << cpu_interrupt_nr);
|
|
|
|
// for edge interrupts cannot rely on the interrupt status
|
|
// register, therefore call all registered
|
|
// handlers for current level
|
|
let interrupt_levels =
|
|
get_configured_interrupts(crate::get_core(), chip_specific::INTERRUPT_EDGE);
|
|
let interrupt_mask = interrupt_levels[level as usize];
|
|
let mut interrupt_mask = interrupt_mask & chip_specific::INTERRUPT_EDGE;
|
|
loop {
|
|
let interrupt_nr = interrupt_mask.trailing_zeros();
|
|
if let Ok(interrupt) = pac::Interrupt::try_from(interrupt_nr as u16) {
|
|
handle_interrupt(level, interrupt, save_frame)
|
|
} else {
|
|
break;
|
|
}
|
|
interrupt_mask &= !(1u128 << interrupt_nr);
|
|
}
|
|
} else {
|
|
// finally check periperal sources and fire of handlers from pac
|
|
// peripheral mapped interrupts are cleared by the peripheral
|
|
let status = get_status(crate::get_core());
|
|
let interrupt_levels = get_configured_interrupts(crate::get_core(), status);
|
|
let interrupt_mask = status & interrupt_levels[level as usize];
|
|
let interrupt_nr = interrupt_mask.trailing_zeros();
|
|
|
|
// Interrupt::try_from can fail if interrupt already de-asserted:
|
|
// silently ignore
|
|
if let Ok(interrupt) = pac::Interrupt::try_from(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 = pac::__INTERRUPTS[interrupt.number() as usize]._handler;
|
|
if handler as *const _ == EspDefaultHandler as *const unsafe extern "C" fn() {
|
|
EspDefaultHandler(level, interrupt);
|
|
} else {
|
|
let handler: fn(&mut Context) = core::mem::transmute(handler);
|
|
handler(save_frame);
|
|
}
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
mod chip_specific {
|
|
use super::*;
|
|
pub const INTERRUPT_EDGE: u128 =
|
|
0b_0000_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0011_1111_1100_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0000;
|
|
#[inline]
|
|
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
|
|
use pac::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::*;
|
|
pub const INTERRUPT_EDGE: u128 =
|
|
0b_0000_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0011_1011_1111_1100_0000_0000_0000_0000_0000_0000_0000__0000_0000_0000_0000_0000_0000_0000_0000;
|
|
#[inline]
|
|
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
|
|
use pac::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::*;
|
|
pub const INTERRUPT_EDGE: u128 = 0;
|
|
#[inline]
|
|
pub fn interrupt_is_edge(_interrupt: Interrupt) -> bool {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
mod raw {
|
|
use super::*;
|
|
|
|
extern "C" {
|
|
#[cfg(not(feature = "vectored"))]
|
|
fn level1_interrupt(save_frame: &mut Context);
|
|
#[cfg(not(feature = "vectored"))]
|
|
fn level2_interrupt(save_frame: &mut Context);
|
|
#[cfg(not(feature = "vectored"))]
|
|
fn level3_interrupt(save_frame: &mut Context);
|
|
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"]
|
|
#[cfg(not(feature = "vectored"))]
|
|
unsafe fn __level_1_interrupt(_level: u32, save_frame: &mut Context) {
|
|
level1_interrupt(save_frame)
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[link_section = ".rwtext"]
|
|
#[cfg(not(feature = "vectored"))]
|
|
unsafe fn __level_2_interrupt(_level: u32, save_frame: &mut Context) {
|
|
level2_interrupt(save_frame)
|
|
}
|
|
|
|
#[no_mangle]
|
|
#[link_section = ".rwtext"]
|
|
#[cfg(not(feature = "vectored"))]
|
|
unsafe fn __level_3_interrupt(_level: u32, save_frame: &mut Context) {
|
|
level3_interrupt(save_frame)
|
|
}
|
|
|
|
#[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)
|
|
}
|
|
}
|