esp-radio: remove scheduler lifecycle management, remove idle task (#4093)

* Start scheduler on preempt init, remove deinit

* Static-allocate the main task context

* taskless idle

* Remove the need for an idle task
This commit is contained in:
Dániel Buga 2025-09-15 14:47:39 +02:00 committed by GitHub
parent c1c226a20d
commit 9d596ca752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 237 additions and 252 deletions

View File

@ -1,13 +1,13 @@
//! This crate allows using esp-radio on top of esp-hal, without any other OS.
//!
//! This crate needs to be initialized with an esp-hal timer before the scheduler can be started.
//! This crate requires an esp-hal timer to operate.
//!
//! ```rust, no_run
#![doc = esp_hal::before_snippet!()]
//! use esp_hal::timer::timg::TimerGroup;
//!
//! let timg0 = TimerGroup::new(peripherals.TIMG0);
//! esp_preempt::init(timg0.timer0);
//! esp_preempt::start(timg0.timer0);
//!
//! // You can now start esp-radio:
//! // let esp_radio_controller = esp_radio::init().unwrap();
@ -35,7 +35,6 @@ mod wait_queue;
pub(crate) use esp_alloc::InternalMemory;
use esp_hal::{
Blocking,
system::Cpu,
timer::{AnyTimer, OneShotTimer},
};
pub(crate) use scheduler::SCHEDULER;
@ -102,7 +101,7 @@ where
}
}
/// Initializes the scheduler.
/// Starts the scheduler.
///
/// # The `timer` argument
///
@ -116,12 +115,19 @@ where
/// For an example, see the [crate-level documentation][self].
#[cfg_attr(
multi_core,
doc = " \nNote that `esp_radio::init()` must be called on the same core as `esp_preempt::init()`."
doc = " \nNote that `esp_radio::init()` must be called on the same core as `esp_preempt::start()`."
)]
pub fn init(timer: impl TimerSource) {
pub fn start(timer: impl TimerSource) {
SCHEDULER.with(move |scheduler| {
scheduler.time_driver = Some(TimeDriver::new(timer.timer()));
scheduler.runs_on = Cpu::current();
scheduler.setup(TimeDriver::new(timer.timer()));
// allocate the default tasks
task::allocate_main_task(scheduler);
task::setup_multitasking();
unwrap!(scheduler.time_driver.as_mut()).start();
task::yield_task();
})
}

View File

@ -23,7 +23,6 @@ use crate::{
TaskPtr,
},
timer::TimeDriver,
timer_queue,
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@ -69,6 +68,7 @@ pub(crate) struct SchedulerState {
/// Pointer to the current task.
pub(crate) current_task: Option<TaskPtr>,
idle_context: CpuContext,
/// A list of all allocated tasks
pub(crate) all_tasks: TaskList<TaskAllocListElement>,
@ -91,6 +91,7 @@ impl SchedulerState {
Self {
runs_on: Cpu::ProCpu,
current_task: None,
idle_context: CpuContext::new(),
all_tasks: TaskList::new(),
run_queue: RunQueue::new(),
to_delete: TaskList::new(),
@ -100,6 +101,36 @@ impl SchedulerState {
}
}
pub(crate) fn setup(&mut self, time_driver: TimeDriver) {
self.time_driver = Some(time_driver);
self.runs_on = Cpu::current();
task::set_idle_hook_entry(&mut self.idle_context);
}
pub(crate) fn create_task(
&mut self,
task: extern "C" fn(*mut c_void),
param: *mut c_void,
task_stack_size: usize,
priority: usize,
) -> TaskPtr {
let mut task = Box::new_in(
Task::new(task, param, task_stack_size, priority),
InternalMemory,
);
task.heap_allocated = true;
let task_ptr = NonNull::from(Box::leak(task));
self.all_tasks.push(task_ptr);
if self.run_queue.mark_task_ready(task_ptr) {
task::yield_task();
}
debug!("Task created: {:?}", task_ptr);
task_ptr
}
fn delete_marked_tasks(&mut self) {
while let Some(to_delete) = self.to_delete.pop() {
trace!("delete_marked_tasks {:?}", to_delete);
@ -112,10 +143,12 @@ impl SchedulerState {
}
fn run_scheduler(&mut self, task_switch: impl FnOnce(*mut CpuContext, *mut CpuContext)) {
let mut priority = {
let current_task = unsafe { unwrap!(self.current_task).as_ref() };
let mut priority = if let Some(current_task) = self.current_task {
let current_task = unsafe { current_task.as_ref() };
current_task.ensure_no_stack_overflow();
current_task.priority
Some(current_task.priority)
} else {
None
};
self.delete_marked_tasks();
@ -146,20 +179,8 @@ impl SchedulerState {
self.run_queue.mark_task_ready(current_task);
}
// At least one task must be ready to run. If there are none, we can't do anything - we
// can't just WFI from an interrupt handler. We should create an idle task that WFIs for us,
// and can be replaced with auto light sleep.
let mut next_task = unwrap!(self.run_queue.pop(), "There are no tasks ready to run.");
// Were we able to select a new task?
if Some(next_task) != self.current_task {
debug_assert_eq!(
next_task.state(),
task::TaskState::Ready,
"task: {:?}",
next_task
);
let next_task = self.run_queue.pop();
if next_task != self.current_task {
trace!("Switching task {:?} -> {:?}", self.current_task, next_task);
// If the current task is deleted, we can skip saving its context. We signal this by
@ -169,19 +190,48 @@ impl SchedulerState {
} else {
core::ptr::null_mut()
};
let next_context = unsafe { &raw mut next_task.as_mut().cpu_context };
let next_context = if let Some(mut next) = next_task {
priority = Some(next.priority(&mut self.run_queue));
unsafe { &raw mut next.as_mut().cpu_context }
} else if current_context.is_null() {
// The current task has been deleted, we can't use its stack. Let's assume the main
// task's context has a valid stack pointer for us.
cfg_if::cfg_if! {
if #[cfg(xtensa)] {
self.idle_context.A1 = unsafe { task::MAIN_TASK.cpu_context.A1 };
} else {
self.idle_context.sp = unsafe { task::MAIN_TASK.cpu_context.sp };
}
}
&raw mut self.idle_context
} else {
// As the stack pointer, let's just use the current value.
cfg_if::cfg_if! {
if #[cfg(xtensa)] {
self.idle_context.A1 = esp_hal::xtensa_lx::get_stack_pointer() as u32;
} else {
let current_sp;
unsafe { core::arch::asm!("mv {0}, sp", out(reg) current_sp); }
self.idle_context.sp = current_sp;
}
}
&raw mut self.idle_context
};
task_switch(current_context, next_context);
self.current_task = Some(next_task);
priority = next_task.priority(&mut self.run_queue);
// If we went to idle, this will be None and we won't mess up the main task's stack.
self.current_task = next_task;
}
// TODO maybe we don't need to do this every time?
// The current task is not in the run queue. If the run queue on the current priority level
// is empty, the current task is the only one running at its priority level. In this
// case, we don't need time slicing.
let arm_next_timeslice_tick = !self.run_queue.is_level_empty(priority);
let arm_next_timeslice_tick = priority
.map(|p| !self.run_queue.is_level_empty(p))
.unwrap_or(false);
time_driver.arm_next_wakeup(arm_next_timeslice_tick);
}
@ -229,12 +279,16 @@ impl SchedulerState {
}
}
fn delete_task(&mut self, to_delete: TaskPtr) {
fn delete_task(&mut self, mut to_delete: TaskPtr) {
self.remove_from_all_queues(to_delete);
unsafe {
let task = Box::from_raw_in(to_delete.as_ptr(), InternalMemory);
core::mem::drop(task);
if to_delete.as_ref().heap_allocated {
let task = Box::from_raw_in(to_delete.as_ptr(), InternalMemory);
core::mem::drop(task);
} else {
to_delete.as_mut().thread_semaphore = None;
}
}
}
@ -272,26 +326,11 @@ impl Scheduler {
task_stack_size: usize,
priority: u32,
) -> TaskPtr {
let task = Box::new_in(
Task::new(task, param, task_stack_size, priority as usize),
InternalMemory,
);
let task_ptr = NonNull::from(Box::leak(task));
SCHEDULER.with(|state| {
state.all_tasks.push(task_ptr);
if state.run_queue.mark_task_ready(task_ptr) {
task::yield_task();
}
});
debug!("Task created: {:?}", task_ptr);
task_ptr
self.with(|state| state.create_task(task, param, task_stack_size, priority as usize))
}
pub(crate) fn sleep_until(&self, wake_at: Instant) -> bool {
SCHEDULER.with(|scheduler| scheduler.sleep_until(wake_at))
self.with(|scheduler| scheduler.sleep_until(wake_at))
}
}
@ -320,28 +359,6 @@ impl esp_radio_preempt_driver::Scheduler for Scheduler {
})
}
fn enable(&self) {
// allocate the default tasks
task::allocate_main_task();
task::spawn_idle_task();
task::setup_multitasking();
self.with(|scheduler| unwrap!(scheduler.time_driver.as_mut()).start());
task::yield_task();
}
fn disable(&self) {
self.with(|scheduler| unwrap!(scheduler.time_driver.as_mut()).stop());
task::disable_multitasking();
// Note that deleting tasks leaks resources, because we don't know how to free memory
// allocated by the deleted tasks.
task::delete_all_tasks();
timer_queue::reset();
}
fn yield_task(&self) {
task::yield_task();
}

View File

@ -8,7 +8,13 @@ use allocator_api2::boxed::Box;
pub(crate) use arch_specific::*;
use esp_radio_preempt_driver::semaphore::{SemaphoreHandle, SemaphorePtr};
use crate::{InternalMemory, SCHEDULER, run_queue::RunQueue, wait_queue::WaitQueue};
use crate::{
InternalMemory,
SCHEDULER,
run_queue::RunQueue,
scheduler::SchedulerState,
wait_queue::WaitQueue,
};
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -195,7 +201,7 @@ pub(crate) struct Task {
pub cpu_context: CpuContext,
pub thread_semaphore: Option<SemaphorePtr>,
pub state: TaskState,
pub _allocated_stack: Box<[MaybeUninit<u32>], InternalMemory>,
pub _allocated_stack: *mut [MaybeUninit<u32>],
pub priority: usize,
pub wakeup_at: u64,
@ -215,6 +221,9 @@ pub(crate) struct Task {
/// The list of tasks scheduled for deletion
pub delete_list_item: TaskListItem,
/// Whether the task was allocated on the heap.
pub(crate) heap_allocated: bool,
}
const STACK_CANARY: u32 = 0xDEEDBAAD;
@ -233,11 +242,11 @@ impl Task {
let task_stack_size_words = task_stack_size / 4;
let mut stack = Box::<[u32], _>::new_uninit_slice_in(task_stack_size_words, InternalMemory);
let stack_top = unsafe { stack.as_mut_ptr().add(task_stack_size_words).cast() };
stack[0] = MaybeUninit::new(STACK_CANARY);
let stack = Box::leak(stack) as *mut [MaybeUninit<u32>];
let stack_top = unsafe { stack.cast::<u32>().add(task_stack_size_words).cast() };
Task {
cpu_context: new_task_context(task_fn, param, stack_top),
thread_semaphore: None,
@ -252,16 +261,21 @@ impl Task {
ready_queue_item: TaskListItem::None,
timer_queue_item: TaskListItem::None,
delete_list_item: TaskListItem::None,
heap_allocated: false,
}
}
pub(crate) fn ensure_no_stack_overflow(&self) {
// TODO: fix this for main task
if self._allocated_stack.is_empty() {
return;
}
assert_eq!(
unsafe { self._allocated_stack[0].assume_init() },
// This cast is safe to do from MaybeUninit<u32> because this is the word we've written
// during initialization.
unsafe { self._allocated_stack.cast::<u32>().read() },
STACK_CANARY,
"Stack overflow detected in {:?}",
self as *const Task
@ -279,82 +293,48 @@ impl Drop for Task {
}
}
pub(super) fn allocate_main_task() {
// This context will be filled out by the first context switch.
let task = Box::new_in(
Task {
cpu_context: CpuContext::default(),
thread_semaphore: None,
state: TaskState::Ready,
_allocated_stack: Box::<[u32], _>::new_uninit_slice_in(0, InternalMemory),
current_queue: None,
priority: 1,
// This context will be filled out by the first context switch.
// We allocate the main task statically, because there is always a main task. If deleted, we simply
// don't deallocate this.
pub(crate) static mut MAIN_TASK: Task = Task {
cpu_context: CpuContext::new(),
thread_semaphore: None,
state: TaskState::Ready,
_allocated_stack: core::ptr::slice_from_raw_parts_mut(core::ptr::null_mut(), 0),
current_queue: None,
priority: 0,
wakeup_at: 0,
wakeup_at: 0,
alloc_list_item: TaskListItem::None,
ready_queue_item: TaskListItem::None,
timer_queue_item: TaskListItem::None,
delete_list_item: TaskListItem::None,
},
InternalMemory,
);
let main_task_ptr = NonNull::from(Box::leak(task));
alloc_list_item: TaskListItem::None,
ready_queue_item: TaskListItem::None,
timer_queue_item: TaskListItem::None,
delete_list_item: TaskListItem::None,
heap_allocated: false,
};
pub(super) fn allocate_main_task(scheduler: &mut SchedulerState) {
let mut main_task_ptr = unwrap!(NonNull::new(&raw mut MAIN_TASK));
debug!("Main task created: {:?}", main_task_ptr);
SCHEDULER.with(|state| {
debug_assert!(
state.current_task.is_none(),
"Tried to allocate main task multiple times"
);
unsafe {
let main_task = main_task_ptr.as_mut();
// The main task is already running, no need to add it to the ready queue.
state.all_tasks.push(main_task_ptr);
state.current_task = Some(main_task_ptr);
state.run_queue.mark_task_ready(main_task_ptr);
})
}
pub(crate) fn spawn_idle_task() {
let ptr = SCHEDULER.create_task(idle_task, core::ptr::null_mut(), 4096, 0);
debug!("Idle task created: {:?}", ptr);
}
pub(crate) extern "C" fn idle_task(_: *mut c_void) {
loop {
// TODO: make this configurable.
#[cfg(xtensa)]
unsafe {
core::arch::asm!("waiti 0");
}
#[cfg(riscv)]
unsafe {
core::arch::asm!("wfi");
}
// Reset main task properties. The rest should be cleared when the task is deleted.
main_task.priority = 0;
main_task.state = TaskState::Ready;
}
}
pub(super) fn delete_all_tasks() {
trace!("delete_all_tasks");
let mut all_tasks = SCHEDULER.with(|state| {
// Since we delete all tasks, we walk through the allocation list - we just need to clear
// the lists.
state.to_delete = TaskList::new();
state.run_queue = RunQueue::new();
debug_assert!(
scheduler.current_task.is_none(),
"Tried to allocate main task multiple times"
);
// Clear the current task.
state.current_task = None;
// Take the allocation list
core::mem::take(&mut state.all_tasks)
});
while let Some(task) = all_tasks.pop() {
unsafe {
let task = Box::from_raw_in(task.as_ptr(), InternalMemory);
core::mem::drop(task);
}
}
// The main task is already running, no need to add it to the ready queue.
scheduler.all_tasks.push(main_task_ptr);
scheduler.current_task = Some(main_task_ptr);
scheduler.run_queue.mark_task_ready(main_task_ptr);
}
pub(super) fn with_current_task<R>(mut cb: impl FnMut(&mut Task) -> R) -> R {

View File

@ -1,10 +1,6 @@
use core::ffi::c_void;
use esp_hal::{
interrupt::{self, software::SoftwareInterrupt},
peripherals::Interrupt,
riscv::register,
};
use esp_hal::{interrupt::software::SoftwareInterrupt, riscv::register};
use crate::SCHEDULER;
@ -102,6 +98,25 @@ pub struct CpuContext {
pub mstatus: usize,
}
impl CpuContext {
/// Creates a new, zeroed out context.
pub const fn new() -> Self {
unsafe { core::mem::zeroed() }
}
}
extern "C" fn idle_hook() -> ! {
loop {
unsafe { core::arch::asm!("wfi") };
}
}
pub(crate) fn set_idle_hook_entry(idle_context: &mut CpuContext) {
// Point idle context PC at the assembly that calls the idle hook. We need a new stack
// frame for the idle task on the main stack.
idle_context.pc = idle_hook as usize;
}
pub(crate) fn new_task_context(
task: extern "C" fn(*mut c_void),
param: *mut c_void,
@ -286,10 +301,6 @@ pub(crate) fn setup_multitasking() {
unsafe { SoftwareInterrupt::<2>::steal() }.set_interrupt_handler(swint2_handler);
}
pub(crate) fn disable_multitasking() {
interrupt::disable(esp_hal::system::Cpu::ProCpu, Interrupt::FROM_CPU_INTR2);
}
#[esp_hal::ram]
extern "C" fn swint2_handler() {
let swi = unsafe { SoftwareInterrupt::<2>::steal() };

View File

@ -5,6 +5,27 @@ use esp_hal::{xtensa_lx, xtensa_lx_rt};
use crate::SCHEDULER;
extern "C" fn idle_hook() -> ! {
loop {
unsafe { core::arch::asm!("waiti 0") };
}
}
#[unsafe(naked)]
extern "C" fn idle_entry() -> ! {
core::arch::naked_asm!("call4 {idle_hook}", idle_hook = sym idle_hook);
}
pub(crate) fn set_idle_hook_entry(idle_context: &mut CpuContext) {
// Point idle context PC at the assembly that calls the idle hook. We need a new stack
// frame for the idle task on the main stack.
idle_context.PC = idle_entry as usize as u32;
// Set a valid processor status value
let current_ps;
unsafe { core::arch::asm!("rsr.ps {0}", out(reg) current_ps, options(nostack)) };
idle_context.PS = current_ps;
}
pub(crate) fn new_task_context(
task_fn: extern "C" fn(*mut c_void),
param: *mut c_void,
@ -61,10 +82,6 @@ pub(crate) fn setup_multitasking() {
}
}
pub(crate) fn disable_multitasking() {
xtensa_lx::interrupt::disable_mask(SW_INTERRUPT);
}
#[allow(non_snake_case)]
#[esp_hal::ram]
#[cfg_attr(not(esp32), unsafe(export_name = "Software0"))]

View File

@ -86,11 +86,6 @@ impl TimeDriver {
self.timer.listen();
}
pub(crate) fn stop(&mut self) {
self.timer.unlisten();
self.timer.stop();
}
pub(crate) fn handle_alarm(&mut self, mut on_task_ready: impl FnMut(TaskPtr)) {
let mut timer_queue = core::mem::take(&mut self.timer_queue);

View File

@ -323,12 +323,6 @@ impl TimerImplementation for Timer {
register_timer_implementation!(Timer);
pub(crate) fn reset() {
TIMER_QUEUE.inner.with(|q| {
*q = TimerQueueInner::new();
});
}
/// Entry point for the timer task responsible for handling scheduled timer
/// events.
///

View File

@ -34,8 +34,6 @@ use crate::semaphore::SemaphorePtr;
unsafe extern "Rust" {
fn esp_preempt_initialized() -> bool;
fn esp_preempt_enable();
fn esp_preempt_disable();
fn esp_preempt_yield_task();
fn esp_preempt_yield_task_from_isr();
fn esp_preempt_current_task() -> *mut c_void;
@ -68,18 +66,6 @@ macro_rules! scheduler_impl {
<$t as $crate::Scheduler>::initialized(&$name)
}
#[unsafe(no_mangle)]
#[inline]
fn esp_preempt_enable() {
<$t as $crate::Scheduler>::enable(&$name)
}
#[unsafe(no_mangle)]
#[inline]
fn esp_preempt_disable() {
<$t as $crate::Scheduler>::disable(&$name)
}
#[unsafe(no_mangle)]
#[inline]
fn esp_preempt_yield_task() {
@ -166,14 +152,6 @@ macro_rules! scheduler_impl {
/// unimplemented!()
/// }
///
/// fn enable(&self) {
/// unimplemented!()
/// }
///
/// fn disable(&self) {
/// unimplemented!()
/// }
///
/// fn yield_task(&self) {
/// unimplemented!()
/// }
@ -225,12 +203,6 @@ pub trait Scheduler: Send + Sync + 'static {
/// up.
fn initialized(&self) -> bool;
/// This function is called by `esp-radio` to start the task scheduler.
fn enable(&self);
/// This function is called by `esp-radio` to stop the task scheduler.
fn disable(&self);
/// This function is called by `esp_radio` to yield control to another task.
fn yield_task(&self);
@ -287,18 +259,6 @@ pub fn initialized() -> bool {
unsafe { esp_preempt_initialized() }
}
/// Starts the task scheduler.
#[inline]
pub fn enable() {
unsafe { esp_preempt_enable() }
}
/// Stops the task scheduler.
#[inline]
pub fn disable() {
unsafe { esp_preempt_disable() }
}
/// Yields control to another task.
#[inline]
pub fn yield_task() {

View File

@ -11,7 +11,7 @@ Furthermore, `esp_wifi::init` no longer requires `RNG` or a timer.
```diff
-let esp_wifi_ctrl = esp_wifi::init(timg0.timer0, Rng::new()).unwrap();
+esp_preempt::init(timg0.timer0);
+esp_preempt::start(timg0.timer0);
+let esp_wifi_ctrl = esp_radio::init().unwrap();
```

View File

@ -229,9 +229,6 @@ impl Drop for Controller<'_> {
shutdown_radio_isr();
// This shuts down the task switcher and timer tick interrupt.
preempt::disable();
#[cfg(esp32)]
// Allow using `ADC2` again
release_adc2(unsafe { esp_hal::Internal::conjure() });
@ -271,7 +268,7 @@ impl Drop for Controller<'_> {
/// use esp_hal::timer::timg::TimerGroup;
///
/// let timg0 = TimerGroup::new(peripherals.TIMG0);
/// esp_preempt::init(timg0.timer0);
/// esp_preempt::start(timg0.timer0);
///
/// // You can now start esp-radio:
/// let esp_radio_controller = esp_radio::init().unwrap();
@ -303,9 +300,6 @@ pub fn init<'d>() -> Result<Controller<'d>, InitializationError> {
setup_radio_isr();
// This initializes the task switcher
preempt::enable();
wifi_set_log_verbose();
init_radio_clocks();

View File

@ -22,7 +22,7 @@ async fn main(_s: Spawner) {
let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
static RADIO: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
let radio = RADIO.init(esp_radio::init().unwrap());

View File

@ -26,7 +26,7 @@ async fn main(_s: Spawner) {
let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
static RADIO: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
let radio = RADIO.init(esp_radio::init().unwrap());

View File

@ -40,7 +40,7 @@ async fn main(_spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -41,7 +41,7 @@ async fn main(spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -27,7 +27,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -38,7 +38,7 @@ fn main() -> ! {
let delay = Delay::new();
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -43,7 +43,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
// Set event handlers for wifi before init to avoid missing any.
let mut connections = 0u32;

View File

@ -47,7 +47,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -69,7 +69,7 @@ fn main() -> ! {
}
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -44,7 +44,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -58,7 +58,7 @@ async fn main(spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -73,7 +73,7 @@ async fn main(spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -49,7 +49,7 @@ async fn main(spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -33,7 +33,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -81,7 +81,7 @@ async fn main(spawner: Spawner) -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -41,7 +41,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -26,7 +26,7 @@ fn _esp_radio_can_be_reinited() {
let p = esp_hal::init(esp_hal::Config::default());
let timg0: TimerGroup<'_, _> = TimerGroup::new(p.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
{
let _init = esp_radio::init().unwrap();
@ -58,7 +58,7 @@ mod tests {
#[test]
fn test_controller_comms(peripherals: Peripherals) {
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let init = esp_radio::init().unwrap();
let mut connector = BleConnector::new(&init, peripherals.BT);
@ -108,7 +108,7 @@ mod tests {
#[test]
fn test_dropping_controller_during_reset(peripherals: Peripherals) {
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let init = esp_radio::init().unwrap();
let mut connector = BleConnector::new(&init, peripherals.BT);

View File

@ -94,7 +94,7 @@ mod tests {
#[test]
fn fpu_stays_enabled_with_wifi(peripherals: Peripherals) {
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let _init = esp_radio::init().unwrap();
let mut sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
@ -138,7 +138,7 @@ mod tests {
}
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let _init = esp_radio::init().unwrap();
let mut sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);

View File

@ -7,6 +7,7 @@
#![no_std]
#![no_main]
use defmt::info;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
#[cfg(target_arch = "riscv32")]
use esp_hal::riscv::interrupt::free as interrupt_free;
@ -16,6 +17,7 @@ use esp_hal::{
clock::CpuClock,
interrupt::{Priority, software::SoftwareInterruptControl},
peripherals::{Peripherals, TIMG0},
time::{Duration, Instant},
timer::timg::TimerGroup,
};
use esp_hal_embassy::InterruptExecutor;
@ -25,7 +27,7 @@ use static_cell::StaticCell;
#[allow(unused)] // compile test
fn baremetal_preempt_can_be_initialized_with_any_timer(timer: esp_hal::timer::AnyTimer<'static>) {
esp_preempt::init(timer);
esp_preempt::start(timer);
}
#[embassy_executor::task]
@ -34,7 +36,7 @@ async fn try_init(
timer: TIMG0<'static>,
) {
let timg0 = TimerGroup::new(timer);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
match esp_radio::init() {
Ok(_) => signal.signal(None),
@ -45,8 +47,6 @@ async fn try_init(
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
mod tests {
use defmt::info;
use super::*;
#[init]
@ -71,7 +71,7 @@ mod tests {
#[test]
fn test_init_fails_cs(peripherals: Peripherals) {
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let init = critical_section::with(|_| esp_radio::init());
@ -81,7 +81,7 @@ mod tests {
#[test]
fn test_init_fails_interrupt_free(peripherals: Peripherals) {
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let init = interrupt_free(|| esp_radio::init());
@ -115,7 +115,7 @@ mod tests {
#[cfg(soc_has_wifi)]
fn test_wifi_can_be_initialized(mut p: Peripherals) {
let timg0 = TimerGroup::new(p.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl =
&*mk_static!(esp_radio::Controller<'static>, esp_radio::init().unwrap());
@ -128,6 +128,19 @@ mod tests {
let _wifi = esp_radio::wifi::new(&esp_radio_ctrl, p.WIFI.reborrow()).unwrap();
}
#[test]
fn test_esp_preempt_sleep_wakes_up(p: Peripherals) {
use esp_radio_preempt_driver as preempt;
let timg0 = TimerGroup::new(p.TIMG0);
esp_preempt::start(timg0.timer0);
let now = Instant::now();
preempt::usleep(10_000);
assert!(now.elapsed() >= Duration::from_millis(10));
}
#[test]
fn test_esp_preempt_priority_inheritance(p: Peripherals) {
use core::ffi::c_void;
@ -137,8 +150,7 @@ mod tests {
use preempt::semaphore::{SemaphoreHandle, SemaphoreKind};
let timg0 = TimerGroup::new(p.TIMG0);
esp_preempt::init(timg0.timer0);
preempt::enable();
esp_preempt::start(timg0.timer0);
// We need three tasks to test priority inheritance:
// - A high priority task that will attempt to acquire the mutex.
@ -235,6 +247,5 @@ mod tests {
info!("Low: wait for tasks to finish");
test_context.ready_semaphore.take(None);
preempt::disable();
}
}

View File

@ -76,7 +76,7 @@ async fn main(spawner: Spawner) -> ! {
let server_address: Ipv4Addr = HOST_IP.parse().expect("Invalid HOST_IP address");
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = &*mk_static!(Controller<'static>, esp_radio::init().unwrap());

View File

@ -62,7 +62,7 @@ fn main() -> ! {
let server_address: Ipv4Addr = HOST_IP.parse().expect("Invalid HOST_IP address");
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();

View File

@ -36,7 +36,7 @@ fn main() -> ! {
esp_alloc::heap_allocator!(size: 72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_preempt::init(timg0.timer0);
esp_preempt::start(timg0.timer0);
let esp_radio_ctrl = esp_radio::init().unwrap();