-
Notifications
You must be signed in to change notification settings - Fork 791
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Adds a executor for the Xtensa arch based on the riscv32 implementation - Light sleep implemented with assembly, so we don't pull in the xtensa_lx crates (yet) - lock behind a nightly feature
- Loading branch information
Showing
4 changed files
with
163 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
use super::{raw, Spawner}; | ||
use crate::interrupt::{Interrupt, InterruptExt}; | ||
use atomic_polyfill::{AtomicBool, Ordering}; | ||
use core::marker::PhantomData; | ||
use core::ptr; | ||
|
||
/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa | ||
/// | ||
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); | ||
|
||
/// Xtensa Executor | ||
pub struct Executor { | ||
inner: raw::Executor, | ||
not_send: PhantomData<*mut ()>, | ||
} | ||
|
||
impl Executor { | ||
/// Create a new Executor. | ||
pub fn new() -> Self { | ||
Self { | ||
// use Signal_Work_Thread_Mode as substitute for local interrupt register | ||
inner: raw::Executor::new( | ||
|_| { | ||
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); | ||
}, | ||
ptr::null_mut(), | ||
), | ||
not_send: PhantomData, | ||
} | ||
} | ||
|
||
/// Run the executor. | ||
/// | ||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on | ||
/// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
/// the executor starts running the tasks. | ||
/// | ||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
/// for example by passing it as an argument to the initial tasks. | ||
/// | ||
/// This function requires `&'static mut self`. This means you have to store the | ||
/// Executor instance in a place where it'll live forever and grants you mutable | ||
/// access. There's a few ways to do this: | ||
/// | ||
/// - a [Forever](crate::util::Forever) (safe) | ||
/// - a `static mut` (unsafe) | ||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
/// | ||
/// This function never returns. | ||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { | ||
init(self.inner.spawner()); | ||
|
||
loop { | ||
unsafe { | ||
self.inner.poll(); | ||
// we do not care about race conditions between the load and store operations, interrupts | ||
// will only set this value to true. | ||
// if there is work to do, loop back to polling | ||
// TODO can we relax this? | ||
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { | ||
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); | ||
} | ||
// if not, wait for interrupt | ||
else { | ||
// waiti sets the PS.INTLEVEL therefore no critical section is needed | ||
core::arch::asm!("waiti 0"); /* wait for any prio interrupt */ | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Interrupt mode executor. | ||
/// | ||
/// This executor runs tasks in interrupt mode. The interrupt handler is set up | ||
/// to poll tasks, and when a task is woken the interrupt is pended from software. | ||
/// | ||
/// This allows running async tasks at a priority higher than thread mode. One | ||
/// use case is to leave thread mode free for non-async tasks. Another use case is | ||
/// to run multiple executors: one in thread mode for low priority tasks and another in | ||
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower | ||
/// priority ones. | ||
/// | ||
/// It is even possible to run multiple interrupt mode executors at different priorities, | ||
/// by assigning different priorities to the interrupts. For an example on how to do this, | ||
/// See the 'multiprio' example for 'embassy-nrf'. | ||
/// | ||
/// To use it, you have to pick an interrupt that won't be used by the hardware. | ||
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). | ||
/// If this is not the case, you may use an interrupt from any unused peripheral. | ||
/// | ||
/// It is somewhat more complex to use, it's recommended to use the thread-mode | ||
/// [`Executor`] instead, if it works for your use case. | ||
pub struct InterruptExecutor<I: Interrupt> { | ||
irq: I, | ||
inner: raw::Executor, | ||
not_send: PhantomData<*mut ()>, | ||
} | ||
|
||
impl<I: InterruptExt + 'static> InterruptExecutor<I> { | ||
/// Create a new Executor. | ||
pub fn new(mut irq: I) -> Self { | ||
let ctx = &mut irq as *mut I as *mut (); | ||
Self { | ||
irq, | ||
// takes the irq as an argument and pends it | ||
inner: raw::Executor::new( | ||
|ctx| unsafe { | ||
let i = &*(ctx as *mut I); | ||
// trigger the interrupt to indirectly call start() | ||
// when the interrupt is serviced | ||
i.pend(); | ||
}, | ||
ctx, | ||
), | ||
not_send: PhantomData, | ||
} | ||
} | ||
|
||
/// Start the executor. | ||
/// | ||
/// The `init` closure is called from interrupt mode, with a [`Spawner`] that spawns tasks on | ||
/// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
/// the interrupt is configured so that the executor starts running the tasks. | ||
/// Once the executor is started, `start` returns. | ||
/// | ||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
/// for example by passing it as an argument to the initial tasks. | ||
/// | ||
/// This function requires `&'static mut self`. This means you have to store the | ||
/// Executor instance in a place where it'll live forever and grants you mutable | ||
/// access. There's a few ways to do this: | ||
/// | ||
/// - a [Forever](crate::util::Forever) (safe) | ||
/// - a `static mut` (unsafe) | ||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
pub fn start(&'static mut self, init: impl FnOnce(Spawner) + Send) { | ||
self.irq.disable(); | ||
|
||
init(self.inner.spawner()); | ||
// set interrupt handler to call poll() using this executor, then disable interrupt | ||
self.irq.set_handler(|ctx| unsafe { | ||
// unpend interrupt first, so it may re-trigger if it is signalled again | ||
|
||
let executor = &*(ctx as *const Self); | ||
executor.irq.unpend(); | ||
executor.inner.poll(); | ||
}); | ||
self.irq.set_handler_context(self as *const _ as _); | ||
|
||
self.irq.enable(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters