use core::marker::PhantomData;
use embassy_executor::Spawner;
#[cfg(all(low_power_wait, multi_core))]
use esp_hal::interrupt::software::SoftwareInterrupt;
use esp_hal::{interrupt::Priority, system::Cpu};
use portable_atomic::{AtomicBool, Ordering};
use super::InnerExecutor;
pub(crate) const THREAD_MODE_CONTEXT: usize = 16;
static SIGNAL_WORK_THREAD_MODE: [AtomicBool; Cpu::COUNT] =
[const { AtomicBool::new(false) }; Cpu::COUNT];
pub(crate) fn pend_thread_mode(core: usize) {
SIGNAL_WORK_THREAD_MODE[core].store(true, Ordering::Relaxed);
#[cfg(all(low_power_wait, multi_core))]
if core != Cpu::current() as usize {
unsafe { SoftwareInterrupt::<3>::steal().raise() };
}
}
pub trait Callbacks {
fn before_poll(&mut self);
fn on_idle(&mut self);
}
#[cfg_attr(multi_core, doc = "")]
#[cfg_attr(
multi_core,
doc = "This executor is safe to use on multiple cores. You need to
create one instance per core. The executors don't steal tasks from each other."
)]
pub struct Executor {
inner: InnerExecutor,
cpu: Cpu,
not_send: PhantomData<*mut ()>,
}
impl Executor {
#[cfg_attr(
all(multi_core, low_power_wait),
doc = r#"
This will use software-interrupt 3 which isn't available for anything else to wake the other core(s)."#
)]
pub fn new() -> Self {
let cpu = Cpu::current();
Self {
inner: InnerExecutor::new(
Priority::Priority1,
(THREAD_MODE_CONTEXT + cpu as usize) as *mut (),
),
cpu,
not_send: PhantomData,
}
}
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
#[cfg_attr(not(low_power_wait), expect(dead_code, reason = "cpu index is unused"))]
struct NoHooks(usize);
impl Callbacks for NoHooks {
fn before_poll(&mut self) {
#[cfg(low_power_wait)]
SIGNAL_WORK_THREAD_MODE[self.0].store(false, Ordering::Relaxed);
}
fn on_idle(&mut self) {}
}
self.run_inner(init, NoHooks(self.cpu as usize))
}
pub fn run_with_callbacks(
&'static mut self,
init: impl FnOnce(Spawner),
callbacks: impl Callbacks,
) -> ! {
struct Hooks<'a, CB: Callbacks>(CB, &'a AtomicBool);
impl<CB: Callbacks> Callbacks for Hooks<'_, CB> {
fn before_poll(&mut self) {
self.1.store(false, Ordering::Relaxed);
self.0.before_poll()
}
fn on_idle(&mut self) {
if !self.1.load(Ordering::Acquire) {
self.0.on_idle();
}
}
}
self.run_inner(
init,
Hooks(callbacks, &SIGNAL_WORK_THREAD_MODE[self.cpu as usize]),
)
}
fn run_inner(&'static mut self, init: impl FnOnce(Spawner), mut hooks: impl Callbacks) -> ! {
#[cfg(all(multi_core, low_power_wait))]
unwrap!(esp_hal::interrupt::enable(
esp_hal::peripherals::Interrupt::FROM_CPU_INTR3,
Priority::min(),
));
self.inner.init();
init(self.inner.inner.spawner());
loop {
hooks.before_poll();
unsafe { self.inner.inner.poll() };
hooks.on_idle();
#[cfg(low_power_wait)]
Self::wait_impl(self.cpu as usize);
}
}
#[cfg(all(xtensa, low_power_wait))]
#[macros::ram]
fn wait_impl(cpu: usize) {
let token: u32;
unsafe { core::arch::asm!("rsil {0}, 5", out(reg) token) };
if SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::Acquire) {
unsafe {
core::arch::asm!(
"wsr.ps {0}",
"rsync",
in(reg) token
);
}
} else {
match token & 0x0F {
0 => unsafe { core::arch::asm!("waiti 0") },
1 => unsafe { core::arch::asm!("waiti 1") },
2 => unsafe { core::arch::asm!("waiti 2") },
3 => unsafe { core::arch::asm!("waiti 3") },
4 => unsafe { core::arch::asm!("waiti 4") },
_ => unsafe { core::arch::asm!("waiti 5") },
}
}
}
#[cfg(all(riscv, low_power_wait))]
fn wait_impl(cpu: usize) {
critical_section::with(|_| {
if !SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::Relaxed) {
unsafe { core::arch::asm!("wfi") };
}
});
}
}
impl Default for Executor {
fn default() -> Self {
Self::new()
}
}