#[cfg(feature = "esp-radio")]
use core::ffi::c_void;
use core::sync::atomic::Ordering;
pub(crate) use esp_hal::trapframe::TrapFrame as CpuContext;
#[cfg(not(esp32))]
use esp_hal::xtensa_lx::interrupt;
use esp_hal::{interrupt::software::SoftwareInterrupt, ram};
#[cfg(multi_core)]
use esp_hal::{
interrupt::{InterruptHandler, Priority},
system::Cpu,
};
use portable_atomic::AtomicPtr;
#[cfg(feature = "rtos-trace")]
use crate::TraceEvents;
use crate::{
SCHEDULER,
task::{IdleFn, Task},
};
static IDLE_HOOK: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
#[unsafe(naked)]
extern "C" fn idle_entry() -> ! {
core::arch::naked_asm!("
.literal idle_hook_fn, {idle_hook_fn}
l32r a2, idle_hook_fn
l32i.n a2, a2, 0
callx4 a2
", idle_hook_fn = sym IDLE_HOOK);
}
const PS_EXCM: u32 = 1 << 4;
const PS_UM: u32 = 1 << 5;
const PS_WOE: u32 = 1 << 18;
#[cfg(feature = "esp-radio")]
const PS_CALLINC_CALL4: u32 = 1 << 16;
pub(crate) fn set_idle_hook_entry(idle_context: &mut CpuContext, hook_fn: IdleFn) {
IDLE_HOOK.store(hook_fn as *mut (), Ordering::Relaxed);
idle_context.PC = idle_entry as *const () as u32;
idle_context.PS = PS_EXCM | PS_UM | PS_WOE;
idle_context.THREADPTR = 0;
}
#[inline(always)]
pub(crate) fn read_thread_pointer() -> *mut Task {
let tp: *mut Task;
unsafe { core::arch::asm!("rur.threadptr {0}", out(reg) tp, options(nostack)) };
tp
}
#[inline(always)]
pub(crate) fn write_thread_pointer(task: *mut Task) {
unsafe { core::arch::asm!("wur.threadptr {0}", in(reg) task, options(nostack)) };
}
#[cfg(feature = "esp-radio")]
pub(crate) fn new_task_context(
task_fn: extern "C" fn(*mut c_void),
param: *mut c_void,
stack_top: *mut (),
) -> CpuContext {
let stack_top = stack_top as u32;
let stack_top = stack_top - (stack_top % 16);
unsafe {
*((stack_top - 4) as *mut u32) = 0;
*((stack_top - 8) as *mut u32) = 0;
*((stack_top - 12) as *mut u32) = stack_top;
*((stack_top - 16) as *mut u32) = 0;
}
CpuContext {
PC: super::task_wrapper as *const () as u32,
A0: 0,
A1: stack_top,
A6: task_fn as usize as u32,
A7: param as usize as u32,
PS: PS_EXCM | PS_UM | PS_WOE | PS_CALLINC_CALL4,
..Default::default()
}
}
pub(crate) fn task_switch(
current_context: *mut CpuContext,
next_context: *mut CpuContext,
trap_frame: &mut CpuContext,
) {
if !current_context.is_null() {
unsafe { core::ptr::copy_nonoverlapping(trap_frame, current_context, 1) };
}
unsafe { core::ptr::copy_nonoverlapping(next_context, trap_frame, 1) };
}
#[cfg(not(esp32))]
const SW_INTERRUPT: u32 = 1 << 7;
pub(crate) fn setup_multitasking<const IRQ: u8>(mut _irq: SoftwareInterrupt<'static, IRQ>) {
#[cfg(not(esp32))]
unsafe {
interrupt::enable_mask(SW_INTERRUPT);
}
#[cfg(multi_core)]
{
_irq.set_interrupt_handler(InterruptHandler::new(
unsafe {
core::mem::transmute::<*const (), extern "C" fn()>(
cross_core_yield_handler as *const (),
)
},
Priority::min(),
));
}
}
#[cfg(multi_core)]
pub(crate) fn setup_smp<const IRQ: u8>(irq: SoftwareInterrupt<'static, IRQ>) {
setup_multitasking(irq);
}
#[allow(non_snake_case)]
#[ram]
#[cfg(not(esp32))]
#[unsafe(export_name = "Software0")]
fn task_switch_interrupt(context: &mut CpuContext) {
unsafe { interrupt::clear(SW_INTERRUPT) };
trigger_task_switch(context);
}
#[inline]
pub(crate) fn yield_task() {
#[cfg(feature = "rtos-trace")]
{
rtos_trace::trace::marker_begin(TraceEvents::YieldTask as u32);
rtos_trace::trace::marker_end(TraceEvents::YieldTask as u32);
}
#[cfg(not(esp32))]
unsafe {
interrupt::set(SW_INTERRUPT);
}
#[cfg(esp32)]
match Cpu::current() {
Cpu::ProCpu => unsafe { SoftwareInterrupt::<'static, 0>::steal() }.raise(),
Cpu::AppCpu => unsafe { SoftwareInterrupt::<'static, 1>::steal() }.raise(),
}
}
#[cfg(multi_core)]
#[ram]
extern "C" fn cross_core_yield_handler(context: &mut CpuContext) {
match Cpu::current() {
Cpu::ProCpu => unsafe { SoftwareInterrupt::<'static, 0>::steal() }.reset(),
Cpu::AppCpu => unsafe { SoftwareInterrupt::<'static, 1>::steal() }.reset(),
}
trigger_task_switch(context);
}
#[cfg_attr(esp32s3, ram)]
fn trigger_task_switch(context: &mut CpuContext) {
SCHEDULER.with(|scheduler| scheduler.switch_task(context));
}