Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 55 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,20 @@ jobs:
# confident that they link.
- name: check esp32-hal (common features)
run: cd esp32-hal/ && cargo check --examples --features=eh1,ufmt
- name: check esp32-hal (async)
run: cd esp32-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32-hal (async, gpio)
run: cd esp32-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async
- name: check esp32-hal (async, spi)
run: cd esp32-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async
- name: check esp32-hal (async, serial)
run: cd esp32-hal/ && cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async
- name: check esp32-hal (async, i2c)
run: cd esp32-hal/ && cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async
- name: check esp32-hal (embassy)
run: |
cd esp32-hal/
cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0,embassy-executor-thread
cargo check --example=embassy_multicore --features=embassy,embassy-time-timg0,embassy-executor-thread
cargo check --example=embassy_multicore_interrupt --features=embassy,embassy-time-timg0,embassy-executor-interrupt
cargo check --example=embassy_multiprio --features=embassy,embassy-time-timg0,embassy-executor-interrupt
- name: check esp32-hal (embassy, async)
run: |
cd esp32-hal/
cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async,embassy-executor-thread
# Ensure documentation can be built
- name: rustdoc
run: cd esp32-hal/ && cargo doc --features=eh1
Expand Down Expand Up @@ -315,17 +319,19 @@ jobs:
run: cd esp32s2-hal/ && cargo check --examples --features=eh1,ufmt
# FIXME: `time-systick` feature disabled for now, see 'esp32s2-hal/Cargo.toml'.
# - name: check esp32s2-hal (async, systick)
# run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32s2-hal (async, timg0)
run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32s2-hal (async, gpio)
run: cd esp32s2-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async
- name: check esp32s2-hal (async, spi)
run: cd esp32s2-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async
- name: check esp32s2-hal (async, serial)
run: cd esp32s2-hal/ && cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async
- name: check esp32s2-hal (async, i2c)
run: cd esp32s2-hal/ && cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async
# run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick,executor
- name: check esp32-hal (embassy, timg0)
run: |
cd esp32s2-hal/
cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0,embassy-executor-thread
cargo check --example=embassy_multiprio --features=embassy,embassy-time-timg0,embassy-executor-interrupt
- name: check esp32-hal (embassy, timg0, async)
run: |
cd esp32s2-hal/
cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async,embassy-executor-thread
# Ensure documentation can be built
- name: rustdoc
run: cd esp32s2-hal/ && cargo doc --features=eh1
Expand Down Expand Up @@ -354,18 +360,34 @@ jobs:
# confident that they link.
- name: check esp32s3-hal (common features)
run: cd esp32s3-hal/ && cargo check --examples --features=eh1,ufmt
- name: check esp32s3-hal (async, systick)
run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32s3-hal (async, timg0)
run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32s3-hal (async, gpio)
run: cd esp32s3-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async
- name: check esp32s3-hal (async, spi)
run: cd esp32s3-hal/ && cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async
- name: check esp32s3-hal (async, serial)
run: cd esp32s3-hal/ && cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async
- name: check esp32s3-hal (async, i2c)
run: cd esp32s3-hal/ && cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async
- name: check esp32-hal (embassy, timg0)
run: |
cd esp32s3-hal/
cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0,embassy-executor-thread
cargo check --example=embassy_multicore --features=embassy,embassy-time-timg0,embassy-executor-thread
cargo check --example=embassy_multicore_interrupt --features=embassy,embassy-time-timg0,embassy-executor-interrupt
cargo check --example=embassy_multiprio --features=embassy,embassy-time-timg0,embassy-executor-interrupt
- name: check esp32-hal (embassy, systick)
run: |
cd esp32s3-hal/
cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick,embassy-executor-thread
cargo check --example=embassy_multicore --features=embassy,embassy-time-systick,embassy-executor-thread
cargo check --example=embassy_multicore_interrupt --features=embassy,embassy-time-systick,embassy-executor-interrupt
cargo check --example=embassy_multiprio --features=embassy,embassy-time-systick,embassy-executor-interrupt
- name: check esp32-hal (embassy, timg0, async)
run: |
cd esp32s3-hal/
cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_spi --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_serial --features=embassy,embassy-time-timg0,async,embassy-executor-thread
cargo check --example=embassy_i2c --features=embassy,embassy-time-timg0,async,embassy-executor-thread
- name: check esp32-hal (embassy, systick, async)
run: |
cd esp32s3-hal/
cargo check --example=embassy_wait --features=embassy,embassy-time-systick,async,embassy-executor-thread
cargo check --example=embassy_spi --features=embassy,embassy-time-systick,async,embassy-executor-thread
cargo check --example=embassy_serial --features=embassy,embassy-time-systick,async,embassy-executor-thread
cargo check --example=embassy_i2c --features=embassy,embassy-time-systick,async,embassy-executor-thread
- name: check esp32s3-hal (octal psram)
run: cd esp32s3-hal/ && cargo check --example=octal_psram --features=opsram_2m --release # This example requires release!
# Ensure documentation can be built
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add PARL_IO TX driver for ESP32-C6 / ESP32-H2 (#733)
- Implement `ufmt_write::uWrite` trait for USB Serial JTAG (#751)
- Add HMAC peripheral support (#755)
- Add multicore-aware embassy executor for Xtensa MCUs (#723).
- Add interrupt-executor for Xtensa MCUs (#723).

### Changed

Expand Down
8 changes: 6 additions & 2 deletions esp-hal-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ usb-device = { version = "0.2.9", optional = true }
# async
embedded-hal-async = { version = "=1.0.0-rc.1", optional = true }
embedded-io-async = { version = "0.5.0", optional = true }
embassy-executor = { version = "0.2.1", features = ["pender-callback", "integrated-timers"], optional = true } # pender is temporary
embassy-sync = { version = "0.2.0", optional = true }
embassy-time = { version = "0.1.3", features = ["nightly"], optional = true }
embassy-futures = { version = "0.1.0", optional = true }
Expand Down Expand Up @@ -99,8 +100,11 @@ ufmt = ["ufmt-write"]
vectored = ["procmacros/interrupt"]

# Implement the `embedded-hal-async==1.0.0-alpha.x` traits
async = ["embedded-hal-async", "eh1", "embassy-sync", "embassy-futures", "embedded-io-async"]
embassy = ["embassy-time"]
async = ["embedded-hal-async", "eh1", "embassy-sync", "embassy-futures", "embedded-io-async"]
embassy = ["embassy-time"]

embassy-executor-thread = ["embassy", "embassy-executor"]
embassy-executor-interrupt = ["embassy", "embassy-executor"]

embassy-time-systick = []
embassy-time-timg0 = []
Expand Down
5 changes: 5 additions & 0 deletions esp-hal-common/src/embassy/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[cfg(xtensa)]
pub use xtensa::*;

#[cfg(xtensa)]
mod xtensa;
179 changes: 179 additions & 0 deletions esp-hal-common/src/embassy/executor/xtensa/interrupt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//! Multicore-aware interrupt-mode executor.
use core::{
cell::UnsafeCell,
marker::PhantomData,
mem::MaybeUninit,
ptr,
sync::atomic::{AtomicUsize, Ordering},
};

use embassy_executor::{
raw::{self, Pender},
SendSpawner,
};
#[cfg(dport)]
use peripherals::DPORT as SystemPeripheral;
#[cfg(system)]
use peripherals::SYSTEM as SystemPeripheral;

use crate::{get_core, interrupt, peripherals};

static FROM_CPU_IRQ_USED: AtomicUsize = AtomicUsize::new(0);

pub trait SwPendableInterrupt {
fn enable(priority: interrupt::Priority);
fn pend();
fn clear();
}

macro_rules! from_cpu {
($cpu:literal) => {
paste::paste! {
pub struct [<FromCpu $cpu>];

/// We don't allow using the same interrupt for multiple executors.

impl [<FromCpu $cpu>] {
fn set_bit(value: bool) {
let system = unsafe { &*SystemPeripheral::PTR };
system
.[<cpu_intr_from_cpu_ $cpu>]
.write(|w| w.[<cpu_intr_from_cpu_ $cpu>]().bit(value));
}
}

impl SwPendableInterrupt for [<FromCpu $cpu>] {
fn enable(priority: interrupt::Priority) {
let mask = 1 << $cpu;
if FROM_CPU_IRQ_USED.fetch_or(mask, Ordering::SeqCst) & mask != 0 {
panic!(concat!("FROM_CPU_", $cpu, " is already used by a different executor."));
}

interrupt::enable(peripherals::Interrupt::[<FROM_CPU_INTR $cpu>], priority).unwrap();
}

fn pend() {
Self::set_bit(true);
}

fn clear() {
Self::set_bit(false);
}
}
}
};
}

from_cpu!(1);
from_cpu!(2);
from_cpu!(3);

/// 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.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call
/// [`Self::on_interrupt()`]
///
/// ```rust,ignore
/// #[interrupt]
/// fn FROM_CPU_INTR1() {
/// unsafe { INT_EXECUTOR.on_interrupt() }
/// }
/// ```
pub struct InterruptExecutor<SWI>
where
SWI: SwPendableInterrupt,
{
core: AtomicUsize,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
_interrupt: PhantomData<SWI>,
}

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

impl<SWI> InterruptExecutor<SWI>
where
SWI: SwPendableInterrupt,
{
/// Create a new `InterruptExecutor`.
#[inline]
pub const fn new() -> Self {
Self {
core: AtomicUsize::new(usize::MAX),
executor: UnsafeCell::new(MaybeUninit::uninit()),
_interrupt: PhantomData,
}
}

/// Executor interrupt callback.
///
/// # Safety
///
/// You MUST call this from the interrupt handler, and from nowhere else.
// TODO: it would be pretty sweet if we could register our own interrupt handler
Copy link
Member

Choose a reason for hiding this comment

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

This would be pretty nice, however, I can't think of a way to do it with the current interrupt registering mechanism. Unless we do some weird stuff with weak linkage, but that would introduce errors on its own. I think the examples for now explain the steps that need to be taken well enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure about weak linkage, in theory (unless there are details I need to know but don't) we should be able to copy peripherals::__INTERRUPTS into RAM, change https://github.com/esp-rs/esp-hal/blob/6c2659f9e4db7fdc1d5431c267b56ec6caa02ebe/esp-hal-common/src/interrupt/xtensa.rs#L440C9-L440C87 to use the copied handlers and roll with that. I do agree that it's not absolutely necessary

// when vectoring is enabled. The user shouldn't need to provide the handler for
// us.
pub unsafe fn on_interrupt(&'static self) {
SWI::clear();
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.poll();
}

/// Start the executor at the given priority level.
///
/// This initializes the executor, enables the interrupt, and returns.
/// The executor keeps running in the background through the interrupt.
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A
/// [`SendSpawner`] is returned instead of a [`Spawner`] because the
/// executor effectively runs in a different "thread" (the interrupt),
/// so spawning tasks on it is effectively sending them.
///
/// To obtain a [`Spawner`] for this executor, use
/// [`Spawner::for_current_executor()`] from a task running in it.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call
/// [`Self::on_interrupt()`]
///
/// This method already enables (unmasks) the interrupt, you must NOT do it
/// yourself.
///
/// [`Spawner`]: embassy_executor::Spawner
/// [`Spawner::for_current_executor()`]: embassy_executor::Spawner::for_current_executor()
pub fn start(&'static self, priority: interrupt::Priority) -> SendSpawner {
if self
.core
.compare_exchange(
usize::MAX,
get_core() as usize,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_err()
{
panic!("InterruptExecutor::start() called multiple times on the same executor.");
}

unsafe {
(*self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new(Pender::new_from_callback(
|_| SWI::pend(),
ptr::null_mut(),
)))
}

SWI::enable(priority);

let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
}
}
13 changes: 13 additions & 0 deletions esp-hal-common/src/embassy/executor/xtensa/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Multicore-aware embassy executors.

#[cfg(feature = "embassy-executor-interrupt")]
pub mod interrupt;

#[cfg(feature = "embassy-executor-interrupt")]
pub use interrupt::*;

#[cfg(feature = "embassy-executor-thread")]
pub mod thread;

#[cfg(feature = "embassy-executor-thread")]
pub use thread::*;
Loading