mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 20:30:35 +00:00
Esp storage implement multi core strategies (#4082)
* First draft of multicore strategies implementation * Implemented second core active detection mechanism Removed dependency to esp-hal * Guarded flash unlock as well Made esp_metadata_generated only check for chips if not building with "emulation" Added more documentation * Applied suggestions * Restored second core active detection for esp32 * Flipped check of stall condition on esp32 * Implement defmt::Format and common traits --------- Co-authored-by: Dániel Buga <bugadani@gmail.com>
This commit is contained in:
parent
555922b887
commit
acf3327fa3
@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
## [v0.8.0] - 2025-09-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added strategies for dealing with multi-core systems (#4082)
|
||||||
|
|
||||||
## [v0.7.0] - 2025-07-16
|
## [v0.7.0] - 2025-07-16
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ bench = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
embedded-storage = "0.3.1"
|
embedded-storage = "0.3.1"
|
||||||
procmacros = { version = "0.19.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
|
procmacros = { version = "0.19.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
esp-sync = { version = "0.0.0", path = "../esp-sync", optional = true }
|
esp-sync = { version = "0.0.0", path = "../esp-sync", optional = true }
|
||||||
@ -29,6 +30,15 @@ defmt = { version = "1.0.1", optional = true }
|
|||||||
# Unstable dependencies that are not (strictly) part of the public API
|
# Unstable dependencies that are not (strictly) part of the public API
|
||||||
document-features = "0.2.11"
|
document-features = "0.2.11"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "riscv32")'.dependencies]
|
||||||
|
riscv = { version = "0.14.0", optional = true}
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "xtensa")'.dependencies]
|
||||||
|
xtensa-lx = { version = "0.12.0", path = "../xtensa-lx", optional = true }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
esp-metadata-generated = { version = "0.1.0", path = "../esp-metadata-generated", features = ["build-script"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["critical-section"]
|
default = ["critical-section"]
|
||||||
|
|
||||||
@ -53,10 +63,10 @@ esp32c6 = ["esp-rom-sys/esp32c6", "esp-sync/esp32c6"]
|
|||||||
##
|
##
|
||||||
esp32h2 = ["esp-rom-sys/esp32h2", "esp-sync/esp32h2"]
|
esp32h2 = ["esp-rom-sys/esp32h2", "esp-sync/esp32h2"]
|
||||||
##
|
##
|
||||||
esp32 = ["esp-rom-sys/esp32", "esp-sync/esp32"]
|
esp32 = ["esp-rom-sys/esp32", "esp-sync/esp32", "xtensa-lx"]
|
||||||
##
|
##
|
||||||
esp32s2 = ["esp-rom-sys/esp32s2", "esp-sync/esp32s2"]
|
esp32s2 = ["esp-rom-sys/esp32s2", "esp-sync/esp32s2"]
|
||||||
##
|
##
|
||||||
esp32s3 = ["esp-rom-sys/esp32s3", "esp-sync/esp32s3"]
|
esp32s3 = ["esp-rom-sys/esp32s3", "esp-sync/esp32s3", "xtensa-lx"]
|
||||||
## Used for testing on a host.
|
## Used for testing on a host.
|
||||||
emulation = []
|
emulation = []
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
|
use esp_metadata_generated::Chip;
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
|
if !cfg!(feature = "emulation") {
|
||||||
|
// Load the configuration file for the configured device:
|
||||||
|
let chip = Chip::from_cargo_feature()?;
|
||||||
|
|
||||||
|
// Define all necessary configuration symbols for the configured device:
|
||||||
|
chip.define_cfgs();
|
||||||
|
}
|
||||||
if cfg!(feature = "esp32") {
|
if cfg!(feature = "esp32") {
|
||||||
match std::env::var("OPT_LEVEL") {
|
match std::env::var("OPT_LEVEL") {
|
||||||
Ok(level) if std::env::var("CI").is_err() => {
|
Ok(level) if std::env::var("CI").is_err() => {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
use crate::chip_specific;
|
use crate::chip_specific;
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
use crate::multi_core::MultiCoreStrategy;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@ -17,6 +19,12 @@ pub enum FlashStorageError {
|
|||||||
NotAligned,
|
NotAligned,
|
||||||
/// Address or length out of bounds.
|
/// Address or length out of bounds.
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
|
/// Cannot write to flash as more than one core is running.
|
||||||
|
/// Either manually suspend the other core, or use one of the available strategies:
|
||||||
|
/// * [`FlashStorage::multicore_auto_park`]
|
||||||
|
/// * [`FlashStorage::multicore_ignore`]
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
OtherCoreRunning,
|
||||||
/// Other error with the given error code.
|
/// Other error with the given error code.
|
||||||
Other(i32),
|
Other(i32),
|
||||||
}
|
}
|
||||||
@ -38,6 +46,8 @@ pub fn check_rc(rc: i32) -> Result<(), FlashStorageError> {
|
|||||||
pub struct FlashStorage {
|
pub struct FlashStorage {
|
||||||
pub(crate) capacity: usize,
|
pub(crate) capacity: usize,
|
||||||
unlocked: bool,
|
unlocked: bool,
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
pub(crate) multi_core_strategy: MultiCoreStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FlashStorage {
|
impl Default for FlashStorage {
|
||||||
@ -57,6 +67,8 @@ impl FlashStorage {
|
|||||||
let mut storage = FlashStorage {
|
let mut storage = FlashStorage {
|
||||||
capacity: 0,
|
capacity: 0,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
multi_core_strategy: MultiCoreStrategy::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(any(feature = "esp32", feature = "esp32s2")))]
|
#[cfg(not(any(feature = "esp32", feature = "esp32s2")))]
|
||||||
@ -126,9 +138,16 @@ impl FlashStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn internal_erase(&mut self, sector: u32) -> Result<(), FlashStorageError> {
|
pub(crate) fn internal_erase(&mut self, sector: u32) -> Result<(), FlashStorageError> {
|
||||||
self.unlock_once()?;
|
#[cfg(multi_core)]
|
||||||
|
let unpark = self.multi_core_strategy.pre_write()?;
|
||||||
|
|
||||||
check_rc(chip_specific::spiflash_erase_sector(sector))
|
self.unlock_once()?;
|
||||||
|
check_rc(chip_specific::spiflash_erase_sector(sector))?;
|
||||||
|
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
self.multi_core_strategy.post_write(unpark);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn internal_write(
|
pub(crate) fn internal_write(
|
||||||
@ -136,12 +155,19 @@ impl FlashStorage {
|
|||||||
offset: u32,
|
offset: u32,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Result<(), FlashStorageError> {
|
) -> Result<(), FlashStorageError> {
|
||||||
self.unlock_once()?;
|
#[cfg(multi_core)]
|
||||||
|
let unpark = self.multi_core_strategy.pre_write()?;
|
||||||
|
|
||||||
|
self.unlock_once()?;
|
||||||
check_rc(chip_specific::spiflash_write(
|
check_rc(chip_specific::spiflash_write(
|
||||||
offset,
|
offset,
|
||||||
bytes.as_ptr() as *const u32,
|
bytes.as_ptr() as *const u32,
|
||||||
bytes.len() as u32,
|
bytes.len() as u32,
|
||||||
))
|
))?;
|
||||||
|
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
self.multi_core_strategy.post_write(unpark);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ mod chip_specific;
|
|||||||
mod buffer;
|
mod buffer;
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(multi_core)]
|
||||||
|
mod multi_core;
|
||||||
|
|
||||||
pub use common::{FlashStorage, FlashStorageError};
|
pub use common::{FlashStorage, FlashStorageError};
|
||||||
|
|
||||||
pub mod ll;
|
pub mod ll;
|
||||||
|
220
esp-storage/src/multi_core.rs
Normal file
220
esp-storage/src/multi_core.rs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
//! Only available/needed on multi-core systems like the ESP32-S3 and ESP32
|
||||||
|
|
||||||
|
use crate::{FlashStorage, common::FlashStorageError};
|
||||||
|
|
||||||
|
#[cfg(esp32)]
|
||||||
|
mod registers {
|
||||||
|
pub(crate) const OPTIONS0: u32 = 0x3ff4_8000;
|
||||||
|
pub(crate) const SW_CPU_STALL: u32 = 0x3ff4_80ac;
|
||||||
|
pub(crate) const APPCPU_CTRL_B: u32 = 0x3FF0_0030;
|
||||||
|
pub(crate) const APPCPU_CTRL_C: u32 = 0x3FF0_0034;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(esp32s3)]
|
||||||
|
mod registers {
|
||||||
|
pub(crate) const OPTIONS0: u32 = 0x6000_8000;
|
||||||
|
pub(crate) const SW_CPU_STALL: u32 = 0x6000_80bc;
|
||||||
|
pub(crate) const CORE_1_CONTROL_0: u32 = 0x600c_0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const C0_VALUE_PRO: u32 = 0x02 << 2;
|
||||||
|
const C1_VALUE_PRO: u32 = 0x21 << 26;
|
||||||
|
const C0_VALUE_APP: u32 = 0x02;
|
||||||
|
const C1_VALUE_APP: u32 = 0x21 << 20;
|
||||||
|
|
||||||
|
/// Strategy to use on a multi core system where writing to the flash needs exclusive access from
|
||||||
|
/// one core
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub(crate) enum MultiCoreStrategy {
|
||||||
|
/// Flash writes simply fail if the second core is active while attempting a write (default
|
||||||
|
/// behavior)
|
||||||
|
Error,
|
||||||
|
/// Auto park the other core before writing. Un-park it when writing is complete
|
||||||
|
AutoPark,
|
||||||
|
/// Ignore that the other core is active.
|
||||||
|
/// This is useful if the second core is known to not fetch instructions from the flash for the
|
||||||
|
/// duration of the write. This is unsafe to use.
|
||||||
|
Ignore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlashStorage {
|
||||||
|
/// Enable auto parking of the second core before writing to flash
|
||||||
|
/// The other core will be automatically un-parked when the write is complete
|
||||||
|
pub fn multicore_auto_park(mut self) -> FlashStorage {
|
||||||
|
self.multi_core_strategy = MultiCoreStrategy::AutoPark;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do not check if the second core is active before writing to flash.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Only enable this if you are sure that the second core is not fetching instructions from the
|
||||||
|
/// flash during the write
|
||||||
|
pub unsafe fn multicore_ignore(mut self) -> FlashStorage {
|
||||||
|
self.multi_core_strategy = MultiCoreStrategy::Ignore;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn raw_core() -> usize {
|
||||||
|
// This method must never return UNUSED_THREAD_ID_VALUE
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(all(multi_core, riscv))] {
|
||||||
|
riscv::register::mhartid::read()
|
||||||
|
} else if #[cfg(all(multi_core, xtensa))] {
|
||||||
|
(xtensa_lx::get_processor_id() & 0x2000) as usize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Cpu {
|
||||||
|
/// The first core
|
||||||
|
ProCpu = 0,
|
||||||
|
/// The second core
|
||||||
|
AppCpu = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cpu {
|
||||||
|
/// Returns the core other than the one which this function is called on
|
||||||
|
#[inline(always)]
|
||||||
|
fn other() -> Cpu {
|
||||||
|
// This works for both RISCV and Xtensa because both
|
||||||
|
// get_raw_core functions return zero, _or_ something
|
||||||
|
// greater than zero; 1 in the case of RISCV and 0x2000
|
||||||
|
// in the case of Xtensa.
|
||||||
|
match raw_core() {
|
||||||
|
0 => Cpu::AppCpu,
|
||||||
|
#[cfg(all(multi_core, riscv))]
|
||||||
|
1 => Cpu::ProCpu,
|
||||||
|
#[cfg(all(multi_core, xtensa))]
|
||||||
|
0x2000 => Cpu::ProCpu,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_c0_c1_bits(self) -> (u32, u32) {
|
||||||
|
match self {
|
||||||
|
Cpu::ProCpu => (C0_VALUE_PRO, C1_VALUE_PRO),
|
||||||
|
Cpu::AppCpu => (C0_VALUE_APP, C1_VALUE_APP),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Park or un-park the core
|
||||||
|
#[inline(always)]
|
||||||
|
fn park_core(self, park: bool) {
|
||||||
|
let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
|
||||||
|
let options0 = registers::OPTIONS0 as *mut u32;
|
||||||
|
|
||||||
|
// Write 0x2 to options0 to stall a particular core
|
||||||
|
// offset for app cpu: 0
|
||||||
|
// offset for pro cpu: 2
|
||||||
|
|
||||||
|
// Write 0x21 to sw_cpu_stall to allow stalling of a particular core
|
||||||
|
// offset for app cpu: 20
|
||||||
|
// offset for pro cpu: 26
|
||||||
|
|
||||||
|
let (c0_bits, c1_bits) = self.get_c0_c1_bits();
|
||||||
|
unsafe {
|
||||||
|
let mut c0 = options0.read_volatile() & !(c0_bits);
|
||||||
|
let mut c1 = sw_cpu_stall.read_volatile() & !(c1_bits);
|
||||||
|
if park {
|
||||||
|
c0 |= c0_bits;
|
||||||
|
c1 |= c1_bits;
|
||||||
|
}
|
||||||
|
sw_cpu_stall.write_volatile(c1);
|
||||||
|
options0.write_volatile(c0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the core is running
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
// If the core is the app cpu we need to check first if it was even enabled
|
||||||
|
if let Cpu::AppCpu = *self {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(esp32s3)] {
|
||||||
|
// CORE_1_RUNSTALL in bit 0 -> needs to be 0 to not stall
|
||||||
|
// CORE_1_CLKGATE_EN in bit 1 -> needs to be 1 to even be enabled
|
||||||
|
let core_1_control_0 = registers::CORE_1_CONTROL_0 as *mut u32;
|
||||||
|
if unsafe { core_1_control_0.read_volatile() } & 0x03 != 0x02 {
|
||||||
|
// If the core is not enabled we can take this shortcut
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if #[cfg(esp32)] {
|
||||||
|
// DPORT_APPCPU_CLKGATE_EN in APPCPU_CTRL_B bit 0 -> needs to be 1 to even be enabled
|
||||||
|
// DPORT_APPCPU_RUNSTALL in APPCPU_CTRL_C bit 0 -> needs to be 0 to not stall
|
||||||
|
let appcpu_ctrl_b = registers::APPCPU_CTRL_B as *mut u32;
|
||||||
|
if unsafe { appcpu_ctrl_b.read_volatile() } & 0x01 != 0x01 {
|
||||||
|
// If the core is not enabled we can take this shortcut
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let appcpu_ctrl_c = registers::APPCPU_CTRL_C as *mut u32;
|
||||||
|
if unsafe { appcpu_ctrl_c.read_volatile() } & 0x01 == 0x01 {
|
||||||
|
// If the core is stalled we can take this shortcut
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
|
||||||
|
let options0 = registers::OPTIONS0 as *mut u32;
|
||||||
|
|
||||||
|
let (c0_bits, c1_bits) = self.get_c0_c1_bits();
|
||||||
|
let (c0, c1) = unsafe {
|
||||||
|
(
|
||||||
|
options0.read_volatile() & c0_bits,
|
||||||
|
sw_cpu_stall.read_volatile() & c1_bits,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
!(c0 == c0_bits && c1 == c1_bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiCoreStrategy {
|
||||||
|
/// Perform checks/Prepare for a flash write according to the current strategy
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `True` if the other core needs to be un-parked by post_write
|
||||||
|
/// * `False` otherwise
|
||||||
|
pub(crate) fn pre_write(&self) -> Result<bool, FlashStorageError> {
|
||||||
|
match self {
|
||||||
|
MultiCoreStrategy::Error => {
|
||||||
|
if Cpu::other().is_running() {
|
||||||
|
Err(FlashStorageError::OtherCoreRunning)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiCoreStrategy::AutoPark => {
|
||||||
|
let other_cpu = Cpu::other();
|
||||||
|
if other_cpu.is_running() {
|
||||||
|
other_cpu.park_core(true);
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MultiCoreStrategy::Ignore => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform post-write actions
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `True` if the other core needs to be un-parked by post_write
|
||||||
|
/// * `False` otherwise
|
||||||
|
pub(crate) fn post_write(&self, unpark: bool) {
|
||||||
|
if let MultiCoreStrategy::AutoPark = self {
|
||||||
|
if unpark {
|
||||||
|
Cpu::other().park_core(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user