Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update our time driver for upcoming embassy changes #2701

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4474f43
Convert into c_char
bugadani Dec 18, 2024
6453b50
Add a timer-driven task
bugadani Nov 26, 2024
5f125db
Spawn another timer
bugadani Nov 28, 2024
645a4a3
Log
bugadani Nov 28, 2024
28bb85e
foo
bugadani Nov 30, 2024
738c9bb
Do not access current time on each schedule
bugadani Dec 1, 2024
2915980
Update generic queue
bugadani Dec 2, 2024
e733f5e
Minimize alarm priorities
bugadani Dec 6, 2024
9f3166f
Point to github with patches
bugadani Dec 6, 2024
70f6e0e
Fix build without any queue impl selected
bugadani Dec 6, 2024
bd4ae6e
Remove explicit generic-queue features
bugadani Dec 6, 2024
6d3f69f
Define cfgs, fix calling something uninitialized
bugadani Dec 6, 2024
f25e445
Clean up RefCell+generic queue
bugadani Dec 6, 2024
2dc7dd8
Fix arg order
bugadani Dec 6, 2024
cb2ae36
Feature
bugadani Dec 7, 2024
6c4b46a
Fix single integrated-timer queue
bugadani Dec 7, 2024
6c41a81
Fix next expiration when arming
bugadani Dec 7, 2024
4d98e51
Add note
bugadani Dec 8, 2024
7ddea70
Adjust impl to latest changes
bugadani Dec 8, 2024
0a61f3e
Local patch
bugadani Dec 7, 2024
9b669ea
Refactor the refactor refactor
bugadani Dec 10, 2024
9b9fa53
Track the timer item's owner
bugadani Dec 16, 2024
8d91cd4
Clear owner on dequeue
bugadani Dec 16, 2024
98d0395
Clean up
bugadani Dec 17, 2024
3014bce
Point at the right branch
bugadani Dec 17, 2024
acd0edd
Fix panic message
bugadani Dec 17, 2024
ea740cd
Hide private function
bugadani Dec 17, 2024
6d63fb4
Remove integrated-timer references
bugadani Dec 17, 2024
6e4e418
Point at upstream embassy
bugadani Dec 17, 2024
236f4b8
Configure via esp-config
bugadani Dec 17, 2024
09619bc
Document, clean up, fix
bugadani Dec 18, 2024
7eec7f7
Hack
bugadani Dec 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions esp-config/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,11 @@ fn capture_from_env(prefix: &str, configs: &mut HashMap<String, Value>) {
panic!("Invalid configuration options detected: {:?}", failed);
}

if !unknown.is_empty() {
panic!("Unknown configuration options detected: {:?}", unknown);
}
// FIXME: disabled for testing purposes, esp-hal rejects esp-hal-embassy
// configs because of this
// if !unknown.is_empty() {
// panic!("Unknown configuration options detected: {:?}", unknown);
// }
}

fn emit_configuration(
Expand Down
32 changes: 20 additions & 12 deletions esp-hal-embassy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ default-target = "riscv32imac-unknown-none-elf"
features = ["esp32c6"]

[dependencies]
critical-section = "1.2.0"
defmt = { version = "0.3.8", optional = true }
document-features = "0.2.10"
embassy-executor = { version = "0.6.3", optional = true }
embassy-time-driver = { version = "0.1.0", features = [ "tick-hz-1_000_000" ] }
esp-hal = { version = "0.22.0", path = "../esp-hal" }
log = { version = "0.4.22", optional = true }
macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
portable-atomic = "1.9.0"
static_cell = "2.1.0"
critical-section = "1.2.0"
defmt = { version = "0.3.8", optional = true }
document-features = "0.2.10"
embassy-executor = { version = "0.6.3", features = ["timer-item-payload-size-4"], optional = true }
embassy-sync = { version = "0.6.1" }
embassy-time = { version = "0.3.0" }
embassy-time-driver = { version = "0.1.0", features = [ "tick-hz-1_000_000" ] }
embassy-time-queue-driver = { version = "0.1.0", features = ["_generic-queue"] }
esp-config = { version = "0.2.0", path = "../esp-config" }
esp-hal = { version = "0.22.0", path = "../esp-hal" }
log = { version = "0.4.22", optional = true }
macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
portable-atomic = "1.9.0"
static_cell = "2.1.0"

[build-dependencies]
esp-build = { version = "0.1.0", path = "../esp-build" }
Expand All @@ -45,8 +49,12 @@ defmt = ["dep:defmt", "embassy-executor?/defmt", "esp-hal/defmt"]
log = ["dep:log"]
## Provide `Executor` and `InterruptExecutor`
executors = ["dep:embassy-executor", "esp-hal/__esp_hal_embassy"]
## Use the executor-integrated `embassy-time` timer queue.
integrated-timers = ["embassy-executor?/integrated-timers"]

[lints.rust]
unexpected_cfgs = "allow"

[patch.crates-io]
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" }
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" }
embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" }
57 changes: 52 additions & 5 deletions esp-hal-embassy/build.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::{error::Error, str::FromStr};
use std::{error::Error as StdError, str::FromStr};

use esp_build::assert_unique_used_features;
use esp_config::{generate_config, Value};
use esp_config::{generate_config, Error, Validator, Value};
use esp_metadata::{Chip, Config};

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), Box<dyn StdError>> {
// NOTE: update when adding new device support!
// Ensure that exactly one chip has been specified:
assert_unique_used_features!(
Expand Down Expand Up @@ -39,16 +39,63 @@ fn main() -> Result<(), Box<dyn Error>> {
config.define_symbols();

// emit config
generate_config(
let crate_config = generate_config(
"esp_hal_embassy",
&[(
"low-power-wait",
"Enables the lower-power wait if no tasks are ready to run on the thread-mode executor. This allows the MCU to use less power if the workload allows. Recommended for battery-powered systems. May impact analog performance.",
Value::Bool(true),
None
)],
),
(
"timer-queue",
"<p>The flavour of the timer queue provided by this crate. Accepts one of 'single-integrated', 'multiple-integrated' or 'generic'. Integrated queues require the 'executors' feature to be enabled.</p><p>If you use embassy-executor, the 'single-integrated' queue is recommended for ease of use, while the 'multiple-integrated' queue is recommended for performance. The 'generic' queue allows using embassy-time without the embassy executors.</p>",
Value::String(if cfg!(feature = "executors") {
String::from("single-integrated")
} else {
String::from("generic")
}),
Some(Validator::Custom(Box::new(|value| {
let Value::String(string) = value else {
return Err(Error::Validation(String::from("Expected a string")));
};

match string.as_str() {
"single-integrated" => Ok(()), // preferred for ease of use
"multiple-integrated" => Ok(()), // preferred for performance
"generic" => Ok(()), // allows using embassy-time without the embassy executors
_ => Err(Error::Validation(format!("Expected 'single-integrated', 'multiple-integrated' or 'generic', found {string}")))
}
})))
),
(
"generic-queue-size",
"The size of the generic queue. Only used if the `generic` timer-queue flavour is selected.",
Value::Integer(64),
Some(Validator::PositiveInteger),
),
],
true,
);

println!("cargo:rustc-check-cfg=cfg(integrated_timers)");
println!("cargo:rustc-check-cfg=cfg(single_queue)");
println!("cargo:rustc-check-cfg=cfg(generic_timers)");

match &crate_config["ESP_HAL_EMBASSY_TIMER_QUEUE"] {
Value::String(s) if s.as_str() == "single-integrated" => {
Copy link
Contributor Author

@bugadani bugadani Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to figure out passing config options to tests for this. We should also figure out testing multiple feature sets, or configs in this case - otherwise we won't be exhaustive in our async tests. Although only the integrated timer is our implementation, it would be interesting to test the time driver's ability to provide multiple alarms, too.

println!("cargo:rustc-cfg=integrated_timers");
println!("cargo:rustc-cfg=single_queue");
}
Value::String(s) if s.as_str() == "multiple-integrated" => {
println!("cargo:rustc-cfg=integrated_timers");
}
Value::String(s) if s.as_str() == "generic" => {
println!("cargo:rustc-cfg=generic_timers");
println!("cargo:rustc-cfg=single_queue");
}
_ => unreachable!(),
}

Ok(())
}
21 changes: 12 additions & 9 deletions esp-hal-embassy/src/executor/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use core::{cell::UnsafeCell, mem::MaybeUninit};

use embassy_executor::{raw, SendSpawner};
use embassy_executor::SendSpawner;
use esp_hal::{
interrupt::{self, software::SoftwareInterrupt, InterruptHandler},
Cpu,
};
use portable_atomic::{AtomicUsize, Ordering};

use super::InnerExecutor;

const COUNT: usize = 3 + cfg!(not(multi_core)) as usize;
static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() }; COUNT];

Expand All @@ -19,15 +21,15 @@ static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new()
/// software.
pub struct InterruptExecutor<const SWI: u8> {
core: AtomicUsize,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
executor: UnsafeCell<MaybeUninit<InnerExecutor>>,
interrupt: SoftwareInterrupt<SWI>,
}

unsafe impl<const SWI: u8> Send for InterruptExecutor<SWI> {}
unsafe impl<const SWI: u8> Sync for InterruptExecutor<SWI> {}

struct CallbackContext {
raw_executor: UnsafeCell<*mut raw::Executor>,
raw_executor: UnsafeCell<*mut InnerExecutor>,
}

impl CallbackContext {
Expand All @@ -37,11 +39,11 @@ impl CallbackContext {
}
}

fn get(&self) -> *mut raw::Executor {
fn get(&self) -> *mut InnerExecutor {
unsafe { *self.raw_executor.get() }
}

fn set(&self, executor: *mut raw::Executor) {
fn set(&self, executor: *mut InnerExecutor) {
unsafe { self.raw_executor.get().write(executor) };
}
}
Expand All @@ -52,7 +54,7 @@ extern "C" fn handle_interrupt<const NUM: u8>() {

unsafe {
let executor = unwrap!(EXECUTORS[NUM as usize].get().as_mut());
executor.poll();
executor.inner.poll();
}
}

Expand Down Expand Up @@ -99,7 +101,7 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
unsafe {
(*self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new((SWI as usize) as *mut ()));
.write(InnerExecutor::new(priority, (SWI as usize) as *mut ()));

EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
}
Expand All @@ -117,7 +119,8 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
.set_interrupt_handler(InterruptHandler::new(swi_handler, priority));

let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
executor.init();
executor.inner.spawner().make_send()
}

/// Get a SendSpawner for this executor
Expand All @@ -132,6 +135,6 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
}
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
executor.inner.spawner().make_send()
}
}
33 changes: 33 additions & 0 deletions esp-hal-embassy/src/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use embassy_executor::raw;
use esp_hal::interrupt::Priority;

pub use self::{interrupt::*, thread::*};
#[cfg(not(single_queue))]
use crate::timer_queue::TimerQueue;

mod interrupt;
mod thread;
Expand All @@ -22,3 +27,31 @@ fn __pender(context: *mut ()) {
_ => unreachable!(),
}
}

#[repr(C)]
pub(crate) struct InnerExecutor {
inner: raw::Executor,
#[cfg(not(single_queue))]
pub(crate) timer_queue: TimerQueue,
}

impl InnerExecutor {
/// Create a new executor.
///
/// When the executor has work to do, it will call the pender function and
/// pass `context` to it.
///
/// See [`Executor`] docs for details on the pender.
pub(crate) fn new(_prio: Priority, context: *mut ()) -> Self {
Self {
inner: raw::Executor::new(context),
#[cfg(not(single_queue))]
timer_queue: TimerQueue::new(_prio),
}
}

pub(crate) fn init(&self) {
#[cfg(not(single_queue))]
self.timer_queue.set_context(self as *const _ as *mut ());
}
}
19 changes: 13 additions & 6 deletions esp-hal-embassy/src/executor/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use core::marker::PhantomData;

use embassy_executor::{raw, Spawner};
use esp_hal::Cpu;
use embassy_executor::Spawner;
#[cfg(multi_core)]
use esp_hal::{interrupt::software::SoftwareInterrupt, macros::handler};
use esp_hal::{interrupt::Priority, Cpu};
#[cfg(low_power_wait)]
use portable_atomic::{AtomicBool, Ordering};

use super::InnerExecutor;

pub(crate) const THREAD_MODE_CONTEXT: usize = 16;

/// global atomic used to keep track of whether there is work to do since sev()
Expand Down Expand Up @@ -55,7 +57,7 @@ pub(crate) fn pend_thread_mode(_core: usize) {
create one instance per core. The executors don't steal tasks from each other."
)]
pub struct Executor {
inner: raw::Executor,
inner: InnerExecutor,
not_send: PhantomData<*mut ()>,
}

Expand All @@ -74,7 +76,10 @@ This will use software-interrupt 3 which isn't available for anything else to wa
}

Self {
inner: raw::Executor::new((THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut ()),
inner: InnerExecutor::new(
Priority::Priority1,
(THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut (),
),
not_send: PhantomData,
}
}
Expand All @@ -100,13 +105,15 @@ This will use software-interrupt 3 which isn't available for anything else to wa
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
self.inner.init();

init(self.inner.inner.spawner());

#[cfg(low_power_wait)]
let cpu = Cpu::current() as usize;

loop {
unsafe { self.inner.poll() };
unsafe { self.inner.inner.poll() };

#[cfg(low_power_wait)]
Self::wait_impl(cpu);
Expand Down
21 changes: 18 additions & 3 deletions esp-hal-embassy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@
//! Embassy **must** be initialized by calling the [init] function. This
//! initialization must be performed *prior* to spawning any tasks.
//!
//! Initialization requires a number of timers to be passed in. The number of
//! timers required depends on the timer queue flavour used, as well as the
//! number of executors started. If you use the `multiple-integrated` timer
//! queue flavour, then you need to pass as many timers as you start executors.
//! In other cases, you can pass a single timer.
//!
//! ## Configuration
//!
//! You can configure the behaviour of the embassy runtime by using the
//! following environment variables:
#![doc = ""]
#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_embassy_config_table.md"))]
#![doc = ""]
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
Expand All @@ -49,8 +62,9 @@ pub use self::executor::{Executor, InterruptExecutor};
use self::time_driver::{EmbassyTimer, Timer};

#[cfg(feature = "executors")]
mod executor;
pub(crate) mod executor;
mod time_driver;
mod timer_queue;

macro_rules! mk_static {
($t:ty,$val:expr) => {{
Expand Down Expand Up @@ -142,8 +156,9 @@ impl_array!(4);
/// - A mutable static array of `OneShotTimer` instances
/// - A 2, 3, 4 element array of `AnyTimer` instances
///
/// Note that if you use the `integrated-timers` feature,
/// you need to pass as many timers as you start executors.
/// Note that if you use the `multiple-integrated` timer-queue flavour, then
/// you need to pass as many timers as you start executors. In other cases,
/// you can pass a single timer.
///
/// # Examples
///
Expand Down
Loading
Loading