use core::{cell::UnsafeCell, mem::MaybeUninit, ptr::NonNull, sync::atomic::Ordering};
use embassy_executor::{SendSpawner, Spawner, raw};
use esp_hal::{
interrupt::{InterruptHandler, Priority, software::SoftwareInterrupt},
system::Cpu,
time::{Duration, Instant},
};
use macros::ram;
use portable_atomic::AtomicPtr;
use crate::{
SCHEDULER,
scheduler::SchedulerState,
task::{TaskPtr, read_thread_pointer},
};
struct SchedulerLocked<T> {
inner: UnsafeCell<T>,
}
unsafe impl<T: Send> Sync for SchedulerLocked<T> {}
unsafe impl<T: Send> Send for SchedulerLocked<T> {}
impl<T> SchedulerLocked<T> {
fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
fn with<'s>(&'s self, _scheduler: &'s mut SchedulerState) -> &'s mut T {
unsafe { &mut *self.inner.get() }
}
}
pub(crate) struct FlagsInner {
owner: TaskPtr,
waiting: Option<TaskPtr>,
set: bool,
}
impl FlagsInner {
fn take(&mut self) -> bool {
if self.set {
self.set = false;
true
} else {
self.waiting = Some(self.owner);
false
}
}
}
struct ThreadFlag {
inner: SchedulerLocked<FlagsInner>,
}
impl ThreadFlag {
fn new() -> Self {
let owner = SCHEDULER.with(|scheduler| {
if let Some(current_task) = NonNull::new(read_thread_pointer()) {
current_task
} else {
let current_cpu = Cpu::current();
NonNull::from(&scheduler.per_cpu[current_cpu as usize].main_task)
}
});
Self {
inner: SchedulerLocked::new(FlagsInner {
owner,
waiting: None,
set: false,
}),
}
}
fn with<R>(&self, scheduler: &mut SchedulerState, f: impl FnOnce(&mut FlagsInner) -> R) -> R {
let inner = self.inner.with(scheduler);
f(inner)
}
fn set(&self) {
SCHEDULER.with(|scheduler| {
let to_resume = self.with(scheduler, |inner| {
let to_resume = inner.waiting.take();
if to_resume.is_none() {
inner.set = true;
}
to_resume
});
if let Some(waiting) = to_resume {
scheduler.resume_task(waiting);
}
});
}
fn get(&self) -> bool {
SCHEDULER.with(|scheduler| self.with(scheduler, |inner| inner.set))
}
fn wait(&self) {
SCHEDULER.with(|scheduler| {
let owner_to_suspend = self.with(scheduler, |inner| {
if !inner.take() {
Some(inner.owner)
} else {
None
}
});
if let Some(owner) = owner_to_suspend {
scheduler.sleep_task_until(owner, Instant::EPOCH + Duration::MAX);
crate::task::yield_task();
}
});
}
}
#[unsafe(export_name = "__pender")]
#[ram]
fn __pender(context: *mut ()) {
match context as usize {
0 => unsafe { SoftwareInterrupt::<0>::steal().raise() },
1 => unsafe { SoftwareInterrupt::<1>::steal().raise() },
2 => unsafe { SoftwareInterrupt::<2>::steal().raise() },
3 => unsafe { SoftwareInterrupt::<3>::steal().raise() },
_ => {
let flags = unwrap!(unsafe { context.cast::<ThreadFlag>().as_ref() });
flags.set();
}
}
}
pub trait Callbacks {
fn before_poll(&mut self);
fn on_idle(&mut self);
}
#[cfg_attr(
multi_core,
doc = r"
If you want to start the executor on the second core, you will need to start the second core using [`crate::start_second_core`].
If you are looking for a way to run code on the second core without the scheduler, use the [`InterruptExecutor`].
"
)]
pub struct Executor {
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
}
impl Executor {
pub const fn new() -> Self {
Self {
executor: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
let flags = ThreadFlag::new();
struct NoHooks;
impl Callbacks for NoHooks {
fn before_poll(&mut self) {}
fn on_idle(&mut self) {}
}
self.run_inner(init, &flags, NoHooks)
}
pub fn run_with_callbacks(
&'static mut self,
init: impl FnOnce(Spawner),
callbacks: impl Callbacks,
) -> ! {
let flags = ThreadFlag::new();
struct Hooks<'a, CB: Callbacks>(CB, &'a ThreadFlag);
impl<CB: Callbacks> Callbacks for Hooks<'_, CB> {
fn before_poll(&mut self) {
self.0.before_poll()
}
fn on_idle(&mut self) {
if !self.1.get() {
self.0.on_idle();
}
}
}
self.run_inner(init, &flags, Hooks(callbacks, &flags))
}
fn run_inner(
&'static self,
init: impl FnOnce(Spawner),
flags: &ThreadFlag,
mut hooks: impl Callbacks,
) -> ! {
let executor = unsafe {
(&mut *self.executor.get()).write(raw::Executor::new(
(flags as *const ThreadFlag).cast::<()>().cast_mut(),
))
};
#[cfg(multi_core)]
if Cpu::current() != Cpu::ProCpu
&& crate::SCHEDULER
.with(|scheduler| !scheduler.per_cpu[Cpu::current() as usize].initialized)
{
panic!("Executor cannot be started: the scheduler is not running on the current CPU.");
}
init(executor.spawner());
loop {
hooks.before_poll();
unsafe { executor.poll() };
hooks.on_idle();
flags.wait();
}
}
}
impl Default for Executor {
fn default() -> Self {
Self::new()
}
}
pub struct InterruptExecutor<const SWI: u8> {
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
interrupt: SoftwareInterrupt<'static, SWI>,
}
const COUNT: usize = 4;
static INTERRUPT_EXECUTORS: [InterruptExecutorStorage; COUNT] =
[const { InterruptExecutorStorage::new() }; COUNT];
unsafe impl<const SWI: u8> Send for InterruptExecutor<SWI> {}
unsafe impl<const SWI: u8> Sync for InterruptExecutor<SWI> {}
struct InterruptExecutorStorage {
raw_executor: AtomicPtr<raw::Executor>,
}
impl InterruptExecutorStorage {
const fn new() -> Self {
Self {
raw_executor: AtomicPtr::new(core::ptr::null_mut()),
}
}
#[inline(always)]
unsafe fn get(&self) -> &raw::Executor {
unsafe { &*self.raw_executor.load(Ordering::Relaxed) }
}
fn set(&self, executor: *mut raw::Executor) {
self.raw_executor.store(executor, Ordering::Relaxed);
}
}
extern "C" fn handle_interrupt<const NUM: u8>() {
let swi = unsafe { SoftwareInterrupt::<NUM>::steal() };
swi.reset();
unsafe {
let executor = INTERRUPT_EXECUTORS[NUM as usize].get();
executor.poll();
}
}
impl<const SWI: u8> InterruptExecutor<SWI> {
#[inline]
pub const fn new(interrupt: SoftwareInterrupt<'static, SWI>) -> Self {
Self {
executor: UnsafeCell::new(MaybeUninit::uninit()),
interrupt,
}
}
pub fn start(&'static mut self, priority: Priority) -> SendSpawner {
unsafe {
(*self.executor.get()).write(raw::Executor::new((SWI as usize) as *mut ()));
INTERRUPT_EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
}
let swi_handler = match SWI {
0 => handle_interrupt::<0>,
1 => handle_interrupt::<1>,
2 => handle_interrupt::<2>,
3 => handle_interrupt::<3>,
_ => unreachable!(),
};
self.interrupt
.set_interrupt_handler(InterruptHandler::new(swi_handler, priority));
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
}
pub fn spawner(&'static self) -> SendSpawner {
if INTERRUPT_EXECUTORS[SWI as usize]
.raw_executor
.load(Ordering::Acquire)
.is_null()
{
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
}
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
}
}