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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

### Breaking

- `CpuControl::start_app_core()` now takes an `FnOnce` closure (#739)

## [0.11.0] - 2023-08-10

### Added
Expand Down
114 changes: 82 additions & 32 deletions esp-hal-common/src/soc/esp32/cpu_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,22 @@
//! ## Overview
//!
//! This module provides essential functionality for controlling
//! and managing the CPU cores on the `ESP32` chip, allowing for fine-grained
//! control over their execution and cache behavior. It is used in scenarios
//! where precise control over CPU core operation is required, such as
//! multi-threading or power management.
//!
//! The `CpuControl` struct represents the CPU control module and is responsible
//! for managing the behavior and operation of the CPU cores. It is typically
//! initialized with the `SystemCpuControl` struct, which is provided by the
//! `system` module.
//! and managing the APP (second) CPU core on the `ESP32` chip. It is used to
//! start and stop program execution on the APP core.
//!
//! ## Example
//!
//! ```no_run
//! static mut APP_CORE_STACK: Stack<8192> = Stack::new();
//!
//! let counter = Mutex::new(RefCell::new(0));
//!
//! let mut cpu_control = CpuControl::new(system.cpu_control);
//! let mut cpu1_fnctn = || {
//! let cpu1_fnctn = || {
//! cpu1_task(&mut timer1, &counter);
//! };
//! let _guard = cpu_control
//! .start_app_core(unsafe { &mut APP_CORE_STACK }, &mut cpu1_fnctn)
//! .start_app_core(unsafe { &mut APP_CORE_STACK }, cpu1_fnctn)
//! .unwrap();
//!
//! loop {
Expand All @@ -36,6 +30,7 @@
//! ```
//!
//! Where `cpu1_task()` may be defined as:
//!
//! ```no_run
//! fn cpu1_task(
//! timer: &mut Timer<Timer0<TIMG1>>,
Expand All @@ -53,27 +48,63 @@
//! }
//! ```

use core::marker::PhantomData;
use core::{
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
};

use xtensa_lx::set_stack_pointer;

use crate::Cpu;

/// Data type for a properly aligned stack of N bytes
#[repr(C, align(64))]
// Xtensa ISA 10.5: [B]y default, the
// stack frame is 16-byte aligned. However, the maximal alignment allowed for a
// TIE ctype is 64-bytes. If a function has any wide-aligned (>16-byte aligned)
// data type for their arguments or the return values, the caller has to ensure
// that the SP is aligned to the largest alignment right before the call.
//
// ^ this means that we should be able to get away with 16 bytes of alignment
// because our root stack frame has no arguments and no return values.
//
// This alignment also doesn't align the stack frames, only the end of stack.
// Stack frame alignment depends on the SIZE as well as the placement of the
// array.
#[repr(C, align(16))]
pub struct Stack<const SIZE: usize> {
/// Memory to be used for the stack
pub mem: [u8; SIZE],
pub mem: MaybeUninit<[u8; SIZE]>,
}

impl<const SIZE: usize> Stack<SIZE> {
/// Construct a stack of length SIZE, initialized to 0
const _ALIGNED: () = assert!(SIZE % 16 == 0); // Make sure stack top is aligned, too.

/// Construct a stack of length SIZE, uninitialized
#[allow(path_statements)]
pub const fn new() -> Stack<SIZE> {
Stack { mem: [0_u8; SIZE] }
Self::_ALIGNED;

Stack {
mem: MaybeUninit::uninit(),
}
}

pub const fn len(&self) -> usize {
SIZE
}

pub fn bottom(&mut self) -> *mut u32 {
self.mem.as_mut_ptr() as *mut u32
}

pub fn top(&mut self) -> *mut u32 {
unsafe { self.bottom().add(SIZE) }
}
}

static mut START_CORE1_FUNCTION: Option<&'static mut (dyn FnMut() + 'static)> = None;
// Pointer to the closure that will be executed on the second core. The closure
// is copied to the core's stack.
static mut START_CORE1_FUNCTION: Option<*mut ()> = None;

static mut APP_CORE_STACK_TOP: Option<*mut u32> = None;

Expand Down Expand Up @@ -206,10 +237,8 @@ impl CpuControl {
}

fn enable_cache(&mut self, core: Cpu) {
let spi0 = unsafe { &(*crate::peripherals::SPI0::ptr()) };

let dport_control = crate::peripherals::DPORT::PTR;
let dport_control = unsafe { &*dport_control };
let spi0 = unsafe { &*crate::peripherals::SPI0::ptr() };
let dport_control = unsafe { &*crate::peripherals::DPORT::ptr() };

match core {
Cpu::ProCpu => {
Expand All @@ -227,7 +256,10 @@ impl CpuControl {
};
}

unsafe fn start_core1_init() -> ! {
unsafe fn start_core1_init<F>() -> !
where
F: FnOnce(),
{
// disables interrupts
xtensa_lx::interrupt::set_mask(0);

Expand All @@ -250,23 +282,31 @@ impl CpuControl {
}

match START_CORE1_FUNCTION.take() {
Some(entry) => (*entry)(),
Some(entry) => {
let entry = unsafe { ManuallyDrop::take(&mut *entry.cast::<ManuallyDrop<F>>()) };
entry();
loop {
unsafe { internal_park_core(crate::get_core()) };
}
}
None => panic!("No start function set"),
}

panic!("Return from second core's entry");
}

/// Start the APP (second) core
///
/// The second core will start running the closure `entry`.
///
/// Dropping the returned guard will park the core.
pub fn start_app_core<'a, const SIZE: usize>(
pub fn start_app_core<'a, const SIZE: usize, F>(
&mut self,
stack: &'static mut Stack<SIZE>,
entry: &'a mut (dyn FnMut() + Send),
) -> Result<AppCoreGuard<'a>, Error> {
entry: F,
) -> Result<AppCoreGuard<'a>, Error>
where
F: FnOnce(),
F: Send + 'a,
{
let dport_control = crate::peripherals::DPORT::PTR;
let dport_control = unsafe { &*dport_control };

Expand All @@ -283,17 +323,27 @@ impl CpuControl {
self.flush_cache(Cpu::AppCpu);
self.enable_cache(Cpu::AppCpu);

// We don't want to drop this, since it's getting moved to the other core.
let entry = ManuallyDrop::new(entry);

unsafe {
let stack_size = (stack.mem.len() - 4) & !0xf;
APP_CORE_STACK_TOP = Some((stack as *mut _ as usize + stack_size) as *mut u32);
let stack_bottom = stack.bottom().cast::<u8>();

// Push `entry` to an aligned address at the (physical) bottom of the stack.
// The second core will copy it into its proper place, then calls it.
let align_offset = stack_bottom.align_offset(core::mem::align_of::<F>());
let entry_dst = stack_bottom.add(align_offset).cast::<ManuallyDrop<F>>();

entry_dst.write(entry);

let entry_fn: &'static mut (dyn FnMut() + 'static) = core::mem::transmute(entry);
let entry_fn = entry_dst.cast::<()>();
START_CORE1_FUNCTION = Some(entry_fn);
APP_CORE_STACK_TOP = Some(stack.top());
}

dport_control.appcpu_ctrl_d.write(|w| unsafe {
w.appcpu_boot_addr()
.bits(Self::start_core1_init as *const u32 as u32)
.bits(Self::start_core1_init::<F> as *const u32 as u32)
});

dport_control
Expand Down
Loading