(re-)introduce the rt feature on esp-hal (#3706)

Adds the `rt` feature which disables `esp_hal::init` and removes
runtime symbols from the global name space.

Disabling the `rt` feature is the recommended way to use esp-hal as a
library.
This commit is contained in:
Scott Mabin 2025-07-15 10:20:26 +01:00 committed by GitHub
parent 978f9083a6
commit 6db771c80e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 659 additions and 536 deletions

View File

@ -61,6 +61,7 @@ In general, the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines
- see [this example](https://github.com/esp-rs/esp-hal/blob/df2b7bd8472cc1d18db0d9441156575570f59bb3/esp-hal/src/spi/mod.rs#L15)
- e.g. `#[cfg_attr(feature = "defmt", derive(defmt::Format))]`
- Implementations of common, but unstable traits (e.g. `embassy_embedded_hal::SetConfig`) need to be gated with the `unstable` feature.
- Libraries depending on esp-hal, should disable the `rt` feature to avoid future compatibility concerns.
## API Surface

View File

@ -21,7 +21,7 @@ test = false
[dependencies]
cfg-if = "1.0.0"
critical-section = "1.2.0"
esp-hal = { version = "1.0.0-beta.1", path = "../esp-hal", features = ["requires-unstable"] }
esp-hal = { version = "1.0.0-beta.1", path = "../esp-hal", default-features = false, features = ["requires-unstable"] }
portable-atomic = "1.11.0"
static_cell = "2.1.0"

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the feature `requires-unstable` (#3772)
- `AnyPin::downcast`/`AnyPeripheral::downcast` to allow retrieving the original GPIO/peripheral type (#3783, #3784)
- Add `ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM` configuration option to pin the RMT driver in RAM (#3778).
- The `rt` feature (#3706)
### Changed
@ -35,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Adjusted ESP32-S2 and ESP-S3 memory region lengths to reflect those defined in ESP-IDF. (#3709)
- Changed the various `ConfigError` variant names to use a consistent word order. (#3782)
- Adjusted ESP32-S2 deep-sleep to hibernate for the Ext1WakeupSource (#3785)
- Libraries depending on esp-hal should now disable default features, so that only the final binary crate enables the `rt` feature (#3706)
### Fixed

View File

@ -83,11 +83,11 @@ esp32s3 = { version = "0.32.0", features = ["critical-section", "rt"], git = "ht
[target.'cfg(target_arch = "riscv32")'.dependencies]
riscv = { version = "0.12.1" }
esp-riscv-rt = { version = "0.11.0", path = "../esp-riscv-rt" }
esp-riscv-rt = { version = "0.11.0", path = "../esp-riscv-rt", optional = true }
[target.'cfg(target_arch = "xtensa")'.dependencies]
xtensa-lx = { version = "0.11.0", path = "../xtensa-lx" }
xtensa-lx-rt = { version = "0.19.0", path = "../xtensa-lx-rt" }
xtensa-lx-rt = { version = "0.19.0", path = "../xtensa-lx-rt", optional = true }
[build-dependencies]
cfg-if = "1.0.0"
@ -99,7 +99,7 @@ serde = { version = "1.0.219", default-features = false, features = ["der
jiff = { version = "0.2.10", default-features = false, features = ["static"] }
[features]
default = []
default = ["rt"]
# These features are considered private and unstable. They are not covered by
# semver guarantees and may change or be removed without notice.
@ -174,6 +174,21 @@ esp32s3 = [
"esp-metadata-generated/esp32s3",
]
## Runtime support
##
## If you are depending on `esp-hal` as a library, you should *not* enable the `rt` feature under any circumstance.
rt = [
"dep:xtensa-lx-rt",
"dep:esp-riscv-rt",
"esp32?/rt",
"esp32c2?/rt",
"esp32c3?/rt",
"esp32c6?/rt",
"esp32h2?/rt",
"esp32s2?/rt",
"esp32s3?/rt",
]
#! ### Logging Feature Flags
## Enable logging output using version 0.4 of the `log` crate.

View File

@ -1,7 +1,8 @@
use std::error::Error;
#[cfg(feature = "rt")]
use std::{
collections::HashMap,
env,
error::Error,
fs::{self, File},
io::{BufRead, Write},
path::{Path, PathBuf},
@ -55,11 +56,6 @@ fn main() -> Result<(), Box<dyn Error>> {
// Define all necessary configuration symbols for the configured device:
chip.define_cfgs();
// Place all linker scripts in `OUT_DIR`, and instruct Cargo how to find these
// files:
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display());
// emit config
println!("cargo:rerun-if-changed=./esp_config.yml");
let cfg_yaml = std::fs::read_to_string("./esp_config.yml")
@ -77,18 +73,27 @@ fn main() -> Result<(), Box<dyn Error>> {
}
}
if chip.is_xtensa() {
#[cfg(any(feature = "esp32", feature = "esp32s2"))]
File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?;
// Only emit linker directives if the `rt` feature is enabled
#[cfg(feature = "rt")]
{
// Place all linker scripts in `OUT_DIR`, and instruct Cargo how to find these
// files:
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let (irtc, drtc) = if cfg!(feature = "esp32s3") {
("rtc_fast_seg", "rtc_fast_seg")
} else {
("rtc_fast_iram_seg", "rtc_fast_dram_seg")
};
println!("cargo:rustc-link-search={}", out.display());
let alias = format!(
r#"
if chip.is_xtensa() {
#[cfg(any(feature = "esp32", feature = "esp32s2"))]
File::create(out.join("memory_extras.x"))?.write_all(&generate_memory_extras())?;
let (irtc, drtc) = if cfg!(feature = "esp32s3") {
("rtc_fast_seg", "rtc_fast_seg")
} else {
("rtc_fast_iram_seg", "rtc_fast_dram_seg")
};
let alias = format!(
r#"
REGION_ALIAS("ROTEXT", irom_seg);
REGION_ALIAS("RWTEXT", iram_seg);
REGION_ALIAS("RODATA", drom_seg);
@ -96,38 +101,39 @@ fn main() -> Result<(), Box<dyn Error>> {
REGION_ALIAS("RTC_FAST_RWTEXT", {irtc});
REGION_ALIAS("RTC_FAST_RWDATA", {drtc});
"#
);
);
fs::write(out.join("alias.x"), alias)?;
fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?;
} else {
// RISC-V devices:
fs::write(out.join("alias.x"), alias)?;
fs::copy("ld/xtensa/hal-defaults.x", out.join("hal-defaults.x"))?;
} else {
// RISC-V devices:
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/asserts.x",
out.join("asserts.x"),
)?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/hal-defaults.x",
out.join("hal-defaults.x"),
)?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/asserts.x",
out.join("asserts.x"),
)?;
preprocess_file(
&config_symbols,
&cfg,
"ld/riscv/hal-defaults.x",
out.join("hal-defaults.x"),
)?;
}
// With the architecture-specific linker scripts taken care of, we can copy all
// remaining linker scripts which are common to all devices:
copy_dir_all(&config_symbols, &cfg, "ld/sections", &out)?;
copy_dir_all(&config_symbols, &cfg, format!("ld/{}", chip.name()), &out)?;
}
// With the architecture-specific linker scripts taken care of, we can copy all
// remaining linker scripts which are common to all devices:
copy_dir_all(&config_symbols, &cfg, "ld/sections", &out)?;
copy_dir_all(&config_symbols, &cfg, format!("ld/{}", chip.name()), &out)?;
Ok(())
}
// ----------------------------------------------------------------------------
// Helper Functions
#[cfg(feature = "rt")]
fn copy_dir_all(
config_symbols: &[&str],
cfg: &HashMap<String, Value>,
@ -158,6 +164,7 @@ fn copy_dir_all(
}
/// A naive pre-processor for linker scripts
#[cfg(feature = "rt")]
fn preprocess_file(
config: &[&str],
cfg: &HashMap<String, Value>,
@ -200,6 +207,7 @@ fn preprocess_file(
Ok(())
}
#[cfg(feature = "rt")]
fn substitute_config(cfg: &HashMap<String, Value>, line: &str) -> String {
let mut result = String::new();
let mut chars = line.chars().peekable();
@ -237,7 +245,7 @@ fn substitute_config(cfg: &HashMap<String, Value>, line: &str) -> String {
result
}
#[cfg(feature = "esp32")]
#[cfg(all(feature = "esp32", feature = "rt"))]
fn generate_memory_extras() -> Vec<u8> {
let reserve_dram = if cfg!(feature = "__bluetooth") {
"0x10000"
@ -255,7 +263,7 @@ fn generate_memory_extras() -> Vec<u8> {
.to_vec()
}
#[cfg(feature = "esp32s2")]
#[cfg(all(feature = "esp32s2", feature = "rt"))]
fn generate_memory_extras() -> Vec<u8> {
let reserved_cache = if cfg!(feature = "psram") {
"0x4000"

View File

@ -43,6 +43,7 @@
//! let peripherals = esp_hal::init(config);
//! # {after_snippet}
//! ```
#![cfg_attr(not(feature = "rt"), expect(unused))]
use core::{cell::Cell, marker::PhantomData};

View File

@ -58,18 +58,17 @@ use portable_atomic::{AtomicPtr, Ordering};
use procmacros::ram;
use strum::EnumCount;
#[cfg(feature = "rt")]
use crate::interrupt::{self, DEFAULT_INTERRUPT_HANDLER};
use crate::{
gpio::{AnyPin, GpioBank, InputPin, set_int_enable},
interrupt::{self, DEFAULT_INTERRUPT_HANDLER, Priority},
gpio::{AnyPin, GPIO_LOCK, GpioBank, InputPin, set_int_enable},
interrupt::Priority,
peripherals::{GPIO, Interrupt},
sync::RawMutex,
};
/// Convenience constant for `Option::None` pin
pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new();
pub(super) static GPIO_LOCK: RawMutex = RawMutex::new();
pub(super) struct CFnPtr(AtomicPtr<()>);
impl CFnPtr {
pub const fn new() -> Self {
@ -88,6 +87,7 @@ impl CFnPtr {
}
}
#[cfg(feature = "rt")]
pub(crate) fn bind_default_interrupt_handler() {
// We first check if a handler is set in the vector table.
if let Some(handler) = interrupt::bound_handler(Interrupt::GPIO) {
@ -140,6 +140,7 @@ pub(super) fn set_interrupt_priority(interrupt: Interrupt, priority: Priority) {
/// status bits unchanged. This enables functions like `is_interrupt_set` to
/// work correctly.
#[ram]
#[cfg(feature = "rt")]
extern "C" fn default_gpio_interrupt_handler() {
GPIO_LOCK.lock(|| {
let banks = interrupt_status();

View File

@ -68,11 +68,12 @@ crate::unstable_module! {
mod asynch;
mod embedded_hal_impls;
pub(crate) mod interrupt;
use interrupt::*;
mod placeholder;
use core::fmt::Display;
use interrupt::*;
pub use placeholder::NoPin;
use portable_atomic::AtomicU32;
use strum::EnumCount;
@ -82,10 +83,13 @@ use crate::{
interrupt::{InterruptHandler, Priority},
peripherals::{GPIO, IO_MUX, Interrupt},
private::{self, Sealed},
sync::RawMutex,
};
define_io_mux_signals!();
pub(crate) static GPIO_LOCK: RawMutex = RawMutex::new();
/// Represents a pin-peripheral connection that, when dropped, disconnects the
/// peripheral from the pin.
///

View File

@ -84,6 +84,7 @@ mod xtensa;
pub mod software;
#[cfg(feature = "rt")]
#[unsafe(no_mangle)]
extern "C" fn EspDefaultHandler(_interrupt: crate::peripherals::Interrupt) {
panic!("Unhandled interrupt: {:?}", _interrupt);
@ -91,7 +92,18 @@ extern "C" fn EspDefaultHandler(_interrupt: crate::peripherals::Interrupt) {
/// Default (unhandled) interrupt handler
pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new(
unsafe { core::mem::transmute::<*const (), extern "C" fn()>(EspDefaultHandler as *const ()) },
{
unsafe extern "C" {
fn EspDefaultHandler(_interrupt: crate::peripherals::Interrupt);
}
unsafe {
core::mem::transmute::<
unsafe extern "C" fn(crate::peripherals::Interrupt),
extern "C" fn(),
>(EspDefaultHandler)
}
},
Priority::min(),
);

View File

@ -12,7 +12,9 @@
//! interrupt15() => Priority::Priority15
//! ```
#[cfg(feature = "rt")]
pub use esp_riscv_rt::TrapFrame;
use procmacros::ram;
use riscv::register::{mcause, mtvec};
#[cfg(not(plic))]
@ -206,58 +208,6 @@ impl TryFrom<u8> for Priority {
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static RESERVED_INTERRUPTS: &[usize] = PRIORITY_TO_INTERRUPT;
/// # Safety
///
/// This function is called from an assembly trap handler.
#[doc(hidden)]
#[unsafe(link_section = ".trap.rust")]
#[unsafe(export_name = "_start_trap_rust_hal")]
pub unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
assert!(
mcause::read().is_exception(),
"Arrived into _start_trap_rust_hal but mcause is not an exception!"
);
unsafe extern "C" {
fn ExceptionHandler(tf: *mut TrapFrame);
}
unsafe {
ExceptionHandler(trap_frame);
}
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub fn _setup_interrupts() {
unsafe extern "C" {
static _vector_table: *const u32;
}
unsafe {
// disable all known interrupts
// at least after the 2nd stage bootloader there are some interrupts enabled
// (e.g. UART)
for peripheral_interrupt in 0..255 {
crate::peripherals::Interrupt::try_from(peripheral_interrupt)
.map(|intr| {
#[cfg(multi_core)]
disable(Cpu::AppCpu, intr);
disable(Cpu::ProCpu, intr);
})
.ok();
}
let vec_table = &_vector_table as *const _ as usize;
mtvec::write(vec_table, mtvec::TrapMode::Vectored);
crate::interrupt::init_vectoring();
};
#[cfg(plic)]
unsafe {
core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX);
}
}
/// Enable an interrupt by directly binding it to a available CPU interrupt
///
/// Unless you are sure, you most likely want to use [`enable`] instead.
@ -367,8 +317,6 @@ pub(crate) fn bound_cpu_interrupt_for(_cpu: Cpu, interrupt: Interrupt) -> Option
}
mod vectored {
use procmacros::ram;
use super::*;
// Setup interrupts ready for vectoring
@ -394,7 +342,7 @@ mod vectored {
/// Get the interrupts configured for the core at the given priority
/// matching the given status
#[inline]
fn configured_interrupts(
pub(crate) fn configured_interrupts(
core: Cpu,
status: InterruptStatus,
priority: Priority,
@ -467,6 +415,413 @@ mod vectored {
Some(addr)
}
}
}
#[cfg(not(plic))]
mod classic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::{peripherals::INTERRUPT_CORE0, system::Cpu};
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
// First element is not used, just there to avoid a -1 in the interrupt handler.
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static INTERRUPT_TO_PRIORITY: [u8; 16] =
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
let cpu_interrupt_number = which as isize;
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_enable()
.modify(|r, w| unsafe { w.bits((1 << cpu_interrupt_number) | r.bits()) });
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
unsafe {
let intr = INTERRUPT_CORE0::regs();
let cpu_interrupt_number = which as isize;
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
intr.cpu_int_type().modify(|r, w| {
w.bits(
r.bits() & !(1 << cpu_interrupt_number)
| (interrupt_type << cpu_interrupt_number),
)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_pri(which as usize)
.write(|w| unsafe { w.map().bits(priority as u8) });
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
let cpu_interrupt_number = which as usize;
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_clear()
.write(|w| w.bits(1 << cpu_interrupt_number));
}
}
/// Get interrupt priority
#[inline]
pub(super) fn priority_by_core(_core: Cpu, cpu_interrupt: CpuInterrupt) -> Priority {
priority(cpu_interrupt)
}
/// Get interrupt priority - called by assembly code
#[inline]
pub(super) fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let intr = INTERRUPT_CORE0::regs();
unsafe {
core::mem::transmute::<u8, Priority>(
intr.cpu_int_pri(cpu_interrupt as usize).read().map().bits(),
)
}
}
/// Get the current run level (the level below which interrupts are masked).
pub fn current_runlevel() -> Priority {
let intr = INTERRUPT_CORE0::regs();
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits().saturating_sub(1) as u8;
unwrap!(Priority::try_from(prev_interrupt_priority))
}
/// Changes the current run level (the level below which interrupts are
/// masked), and returns the previous run level.
///
/// # Safety
///
/// This function must only be used to raise the runlevel and to restore it
/// to a previous value. It must not be used to arbitrarily lower the
/// runlevel.
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
let prev_interrupt_priority = current_runlevel();
// The CPU responds to interrupts `>= level`, but we want to also disable
// interrupts at `level` so we set the threshold to `level + 1`.
INTERRUPT_CORE0::regs()
.cpu_int_thresh()
.write(|w| unsafe { w.bits(level as u32 + 1) });
prev_interrupt_priority
}
#[cfg(feature = "rt")]
mod rt {
use super::*;
#[unsafe(link_section = ".trap")]
#[unsafe(no_mangle)]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
// Both C6 and H2 have 5 bits of code. The riscv crate masks 31 bits, which then
// causes a bounds check to be present.
let interrupt_id: usize = riscv::register::mcause::read().bits() & 0x1f;
let intr = INTERRUPT_CORE0::regs();
let interrupt_priority = unsafe {
intr.cpu_int_pri(0)
.as_ptr()
.add(interrupt_id)
.read_volatile()
};
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
intr.cpu_int_thresh()
.write(|w| unsafe { w.bits(interrupt_priority + 1) }); // set the prio threshold to 1 more than current interrupt prio
unsafe { riscv::interrupt::enable() };
}
prev_interrupt_priority
}
#[unsafe(link_section = ".trap")]
#[unsafe(no_mangle)]
unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_thresh()
.write(|w| unsafe { w.bits(stored_prio) });
}
/// Globally exported so assembly code can call it.
#[unsafe(no_mangle)]
unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
super::priority(cpu_interrupt)
}
}
}
#[cfg(plic)]
mod plic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::{peripherals::PLIC_MX, system::Cpu};
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 31;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
// don't use interrupts reserved for CLIC (0,3,4,7)
// for some reason also CPU interrupt 8 doesn't work by default since it's
// disabled after reset - so don't use that, too
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
// First element is not used, just there to avoid a -1 in the interrupt handler.
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static INTERRUPT_TO_PRIORITY: [u8; 20] = [
0, 1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
unsafe {
PLIC_MX::regs().mxint_enable().modify(|r, w| {
let old = r.cpu_mxint_enable().bits();
let new = old | (1 << (which as isize));
w.cpu_mxint_enable().bits(new)
});
}
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
unsafe {
PLIC_MX::regs().mxint_type().modify(|r, w| {
let old = r.cpu_mxint_type().bits();
let new = old & !(1 << (which as isize)) | (interrupt_type << (which as isize));
w.cpu_mxint_type().bits(new)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
unsafe {
PLIC_MX::regs()
.mxint_pri(which as usize)
.modify(|_, w| w.cpu_mxint_pri().bits(priority as u8));
}
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
PLIC_MX::regs().mxint_clear().modify(|r, w| {
let old = r.cpu_mxint_clear().bits();
let new = old | (1 << (which as isize));
w.cpu_mxint_clear().bits(new)
});
}
}
/// Get interrupt priority for the CPU
#[inline]
pub fn priority_by_core(_core: Cpu, cpu_interrupt: CpuInterrupt) -> Priority {
priority(cpu_interrupt)
}
#[inline]
/// Get interrupt priority.
pub fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let prio = PLIC_MX::regs()
.mxint_pri(cpu_interrupt as usize)
.read()
.cpu_mxint_pri()
.bits();
unsafe { core::mem::transmute::<u8, Priority>(prio) }
}
/// Get the current run level (the level below which interrupts are masked).
pub fn current_runlevel() -> Priority {
let prev_interrupt_priority = PLIC_MX::regs()
.mxint_thresh()
.read()
.cpu_mxint_thresh()
.bits()
.saturating_sub(1);
unwrap!(Priority::try_from(prev_interrupt_priority))
}
/// Changes the current run level (the level below which interrupts are
/// masked), and returns the previous run level.
///
/// # Safety
///
/// This function must only be used to raise the runlevel and to restore it
/// to a previous value. It must not be used to arbitrarily lower the
/// runlevel.
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
let prev_interrupt_priority = current_runlevel();
// The CPU responds to interrupts `>= level`, but we want to also disable
// interrupts at `level` so we set the threshold to `level + 1`.
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(level as u8 + 1) });
prev_interrupt_priority
}
#[cfg(feature = "rt")]
mod rt {
use super::*;
/// Get interrupt priority - called by assembly code
#[unsafe(no_mangle)]
pub(super) unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
super::priority(cpu_interrupt)
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
let interrupt_id: usize = riscv::register::mcause::read().code(); // MSB is whether its exception or interrupt.
let interrupt_priority = PLIC_MX::regs()
.mxint_pri(interrupt_id)
.read()
.cpu_mxint_pri()
.bits();
let prev_interrupt_priority = PLIC_MX::regs()
.mxint_thresh()
.read()
.cpu_mxint_thresh()
.bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(interrupt_priority + 1) });
unsafe {
riscv::interrupt::enable();
}
}
prev_interrupt_priority as u32
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(stored_prio as u8) });
}
}
}
#[cfg(feature = "rt")]
mod rt {
use esp_riscv_rt::TrapFrame;
use super::*;
/// # Safety
///
/// This function is called from an assembly trap handler.
#[doc(hidden)]
#[unsafe(link_section = ".trap.rust")]
#[unsafe(export_name = "_start_trap_rust_hal")]
unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
assert!(
mcause::read().is_exception(),
"Arrived into _start_trap_rust_hal but mcause is not an exception!"
);
unsafe extern "C" {
fn ExceptionHandler(tf: *mut TrapFrame);
}
unsafe {
ExceptionHandler(trap_frame);
}
}
#[doc(hidden)]
#[unsafe(no_mangle)]
unsafe fn _setup_interrupts() {
unsafe extern "C" {
static _vector_table: *const u32;
}
unsafe {
// disable all known interrupts
// at least after the 2nd stage bootloader there are some interrupts enabled
// (e.g. UART)
for peripheral_interrupt in 0..255 {
crate::peripherals::Interrupt::try_from(peripheral_interrupt)
.map(|intr| {
#[cfg(multi_core)]
disable(Cpu::AppCpu, intr);
disable(Cpu::ProCpu, intr);
})
.ok();
}
let vec_table = &_vector_table as *const _ as usize;
mtvec::write(vec_table, mtvec::TrapMode::Vectored);
crate::interrupt::init_vectoring();
};
#[cfg(plic)]
unsafe {
core::arch::asm!("csrw mie, {0}", in(reg) u32::MAX);
}
}
#[unsafe(no_mangle)]
#[ram]
@ -480,7 +835,7 @@ mod vectored {
let priority = INTERRUPT_TO_PRIORITY[cpu_intr as usize];
let prio: Priority = unsafe { core::mem::transmute(priority) };
let configured_interrupts = configured_interrupts(core, status, prio);
let configured_interrupts = vectored::configured_interrupts(core, status, prio);
for interrupt_nr in configured_interrupts.iterator() {
// Don't use `Interrupt::try_from`. It's slower and placed in flash
@ -582,330 +937,3 @@ mod vectored {
#[cfg(plic)]
interrupt_handler!(19);
}
#[cfg(not(plic))]
mod classic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::{peripherals::INTERRUPT_CORE0, system::Cpu};
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
// First element is not used, just there to avoid a -1 in the interrupt handler.
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static INTERRUPT_TO_PRIORITY: [u8; 16] =
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
let cpu_interrupt_number = which as isize;
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_enable()
.modify(|r, w| unsafe { w.bits((1 << cpu_interrupt_number) | r.bits()) });
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
unsafe {
let intr = INTERRUPT_CORE0::regs();
let cpu_interrupt_number = which as isize;
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
intr.cpu_int_type().modify(|r, w| {
w.bits(
r.bits() & !(1 << cpu_interrupt_number)
| (interrupt_type << cpu_interrupt_number),
)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_pri(which as usize)
.write(|w| unsafe { w.map().bits(priority as u8) });
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
let cpu_interrupt_number = which as usize;
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_clear()
.write(|w| w.bits(1 << cpu_interrupt_number));
}
}
/// Get interrupt priority
#[inline]
pub(super) fn priority_by_core(_core: Cpu, cpu_interrupt: CpuInterrupt) -> Priority {
unsafe { priority(cpu_interrupt) }
}
/// Get interrupt priority - called by assembly code
#[inline]
pub(super) unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let intr = INTERRUPT_CORE0::regs();
unsafe {
core::mem::transmute::<u8, Priority>(
intr.cpu_int_pri(cpu_interrupt as usize).read().map().bits(),
)
}
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
use super::mcause;
// Both C6 and H2 have 5 bits of code. The riscv crate masks 31 bits, which then
// causes a bounds check to be present.
let interrupt_id: usize = mcause::read().bits() & 0x1f;
let intr = INTERRUPT_CORE0::regs();
let interrupt_priority = unsafe {
intr.cpu_int_pri(0)
.as_ptr()
.add(interrupt_id)
.read_volatile()
};
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
intr.cpu_int_thresh()
.write(|w| unsafe { w.bits(interrupt_priority + 1) }); // set the prio threshold to 1 more than current interrupt prio
unsafe { riscv::interrupt::enable() };
}
prev_interrupt_priority
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
let intr = INTERRUPT_CORE0::regs();
intr.cpu_int_thresh()
.write(|w| unsafe { w.bits(stored_prio) });
}
/// Get the current run level (the level below which interrupts are masked).
pub fn current_runlevel() -> Priority {
let intr = INTERRUPT_CORE0::regs();
let prev_interrupt_priority = intr.cpu_int_thresh().read().bits().saturating_sub(1) as u8;
unwrap!(Priority::try_from(prev_interrupt_priority))
}
/// Changes the current run level (the level below which interrupts are
/// masked), and returns the previous run level.
///
/// # Safety
///
/// This function must only be used to raise the runlevel and to restore it
/// to a previous value. It must not be used to arbitrarily lower the
/// runlevel.
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
let prev_interrupt_priority = current_runlevel();
// The CPU responds to interrupts `>= level`, but we want to also disable
// interrupts at `level` so we set the threshold to `level + 1`.
INTERRUPT_CORE0::regs()
.cpu_int_thresh()
.write(|w| unsafe { w.bits(level as u32 + 1) });
prev_interrupt_priority
}
}
#[cfg(plic)]
mod plic {
use super::{CpuInterrupt, InterruptKind, Priority};
use crate::{peripherals::PLIC_MX, system::Cpu};
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static DISABLED_CPU_INTERRUPT: u32 = 31;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static EXTERNAL_INTERRUPT_OFFSET: u32 = 0;
// don't use interrupts reserved for CLIC (0,3,4,7)
// for some reason also CPU interrupt 8 doesn't work by default since it's
// disabled after reset - so don't use that, too
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static PRIORITY_TO_INTERRUPT: &[usize] =
&[1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
// First element is not used, just there to avoid a -1 in the interrupt handler.
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub(super) static INTERRUPT_TO_PRIORITY: [u8; 20] = [
0, 1, 2, 0, 0, 3, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
/// Enable a CPU interrupt
///
/// # Safety
///
/// Make sure there is an interrupt handler registered.
pub unsafe fn enable_cpu_interrupt(which: CpuInterrupt) {
unsafe {
PLIC_MX::regs().mxint_enable().modify(|r, w| {
let old = r.cpu_mxint_enable().bits();
let new = old | (1 << (which as isize));
w.cpu_mxint_enable().bits(new)
});
}
}
/// Set the interrupt kind (i.e. level or edge) of an CPU interrupt
///
/// The vectored interrupt handler will take care of clearing edge interrupt
/// bits.
pub fn set_kind(_core: Cpu, which: CpuInterrupt, kind: InterruptKind) {
let interrupt_type = match kind {
InterruptKind::Level => 0,
InterruptKind::Edge => 1,
};
unsafe {
PLIC_MX::regs().mxint_type().modify(|r, w| {
let old = r.cpu_mxint_type().bits();
let new = old & !(1 << (which as isize)) | (interrupt_type << (which as isize));
w.cpu_mxint_type().bits(new)
});
}
}
/// Set the priority level of an CPU interrupt
///
/// # Safety
///
/// Great care must be taken when using this function; avoid changing the
/// priority of interrupts 1 - 15.
pub unsafe fn set_priority(_core: Cpu, which: CpuInterrupt, priority: Priority) {
unsafe {
PLIC_MX::regs()
.mxint_pri(which as usize)
.modify(|_, w| w.cpu_mxint_pri().bits(priority as u8));
}
}
/// Clear a CPU interrupt
#[inline]
pub fn clear(_core: Cpu, which: CpuInterrupt) {
unsafe {
PLIC_MX::regs().mxint_clear().modify(|r, w| {
let old = r.cpu_mxint_clear().bits();
let new = old | (1 << (which as isize));
w.cpu_mxint_clear().bits(new)
});
}
}
/// Get interrupt priority
#[inline]
pub(super) unsafe extern "C" fn priority_by_core(
_core: Cpu,
cpu_interrupt: CpuInterrupt,
) -> Priority {
unsafe { priority(cpu_interrupt) }
}
/// Get interrupt priority - called by assembly code
#[inline]
pub(super) unsafe extern "C" fn priority(cpu_interrupt: CpuInterrupt) -> Priority {
let prio = PLIC_MX::regs()
.mxint_pri(cpu_interrupt as usize)
.read()
.cpu_mxint_pri()
.bits();
unsafe { core::mem::transmute::<u8, Priority>(prio) }
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _handle_priority() -> u32 {
let interrupt_id: usize = super::mcause::read().code(); // MSB is whether its exception or interrupt.
let interrupt_priority = PLIC_MX::regs()
.mxint_pri(interrupt_id)
.read()
.cpu_mxint_pri()
.bits();
let prev_interrupt_priority = PLIC_MX::regs()
.mxint_thresh()
.read()
.cpu_mxint_thresh()
.bits();
if interrupt_priority < 15 {
// leave interrupts disabled if interrupt is of max priority.
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(interrupt_priority + 1) });
unsafe {
riscv::interrupt::enable();
}
}
prev_interrupt_priority as u32
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".trap")]
pub(super) unsafe extern "C" fn _restore_priority(stored_prio: u32) {
riscv::interrupt::disable();
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(stored_prio as u8) });
}
/// Get the current run level (the level below which interrupts are masked).
pub fn current_runlevel() -> Priority {
let prev_interrupt_priority = PLIC_MX::regs()
.mxint_thresh()
.read()
.cpu_mxint_thresh()
.bits()
.saturating_sub(1);
unwrap!(Priority::try_from(prev_interrupt_priority))
}
/// Changes the current run level (the level below which interrupts are
/// masked), and returns the previous run level.
///
/// # Safety
///
/// This function must only be used to raise the runlevel and to restore it
/// to a previous value. It must not be used to arbitrarily lower the
/// runlevel.
pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
let prev_interrupt_priority = current_runlevel();
// The CPU responds to interrupts `>= level`, but we want to also disable
// interrupts at `level` so we set the threshold to `level + 1`.
PLIC_MX::regs()
.mxint_thresh()
.write(|w| unsafe { w.cpu_mxint_thresh().bits(level as u8 + 1) });
prev_interrupt_priority
}
}

View File

@ -1,8 +1,10 @@
//! Interrupt handling
use procmacros::ram;
use xtensa_lx::interrupt;
#[cfg(esp32)]
pub(crate) use xtensa_lx::interrupt::free;
#[cfg(feature = "rt")]
use xtensa_lx_rt::exception::Context;
pub use self::vectored::*;
@ -368,8 +370,6 @@ pub(crate) unsafe fn change_current_runlevel(level: Priority) -> Priority {
}
mod vectored {
use procmacros::ram;
use super::*;
/// Interrupt priority levels.
@ -468,7 +468,11 @@ mod vectored {
/// Get the interrupts configured for the core
#[inline(always)]
fn configured_interrupts(core: Cpu, status: InterruptStatus, level: u32) -> InterruptStatus {
pub(crate) fn configured_interrupts(
core: Cpu,
status: InterruptStatus,
level: u32,
) -> InterruptStatus {
unsafe {
let intr_map_base = match core {
Cpu::ProCpu => (*core0_interrupt_peripheral()).pro_mac_intr_map().as_ptr(),
@ -567,7 +571,7 @@ mod vectored {
// TODO use CpuInterrupt::LevelX.mask() // TODO make it const
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
static CPU_INTERRUPT_LEVELS: [u32; 8] = [
pub(crate) static CPU_INTERRUPT_LEVELS: [u32; 8] = [
0b_0000_0000_0000_0000_0000_0000_0000_0000, // Dummy level 0
0b_0000_0000_0000_0110_0011_0111_1111_1111, // Level_1
0b_0000_0000_0011_1000_0000_0000_0000_0000, // Level 2
@ -578,28 +582,79 @@ mod vectored {
0b_0000_0000_0000_0000_0100_0000_0000_0000, // Level 7
];
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
static CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000;
pub(crate) static CPU_INTERRUPT_INTERNAL: u32 = 0b_0010_0000_0000_0001_1000_1000_1100_0000;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
static CPU_INTERRUPT_EDGE: u32 = 0b_0111_0000_0100_0000_0000_1100_1000_0000;
pub(crate) static CPU_INTERRUPT_EDGE: u32 = 0b_0111_0000_0100_0000_0000_1100_1000_0000;
#[inline]
fn cpu_interrupt_nr_to_cpu_interrupt_handler(
number: u32,
) -> Option<unsafe extern "C" fn(save_frame: &mut Context)> {
use xtensa_lx_rt::*;
// we're fortunate that all esp variants use the same CPU interrupt layout
Some(match number {
6 => Timer0,
7 => Software0,
11 => Profiling,
14 => NMI,
15 => Timer1,
16 => Timer2,
29 => Software1,
_ => return None,
})
#[cfg(esp32)]
pub(crate) mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1111_1100_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0000_0000_0011,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::TG1_LACT_EDGE,
]
.contains(&interrupt)
}
}
#[cfg(esp32s2)]
pub(crate) mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1100_0000_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0011_1011_1111,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::TG1_LACT_EDGE,
Interrupt::SYSTIMER_TARGET0,
Interrupt::SYSTIMER_TARGET1,
Interrupt::SYSTIMER_TARGET2,
]
.contains(&interrupt)
}
}
#[cfg(esp32s3)]
pub(crate) mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::empty();
#[inline]
pub fn interrupt_is_edge(_interrupt: Interrupt) -> bool {
false
}
}
}
#[cfg(feature = "rt")]
mod rt {
use super::{vectored::*, *};
#[unsafe(no_mangle)]
#[ram]
unsafe fn __level_1_interrupt(save_frame: &mut Context) {
@ -696,74 +751,25 @@ mod vectored {
}
}
#[cfg(esp32)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1111_1100_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0000_0000_0011,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::TG1_LACT_EDGE,
]
.contains(&interrupt)
}
#[inline]
pub(crate) fn cpu_interrupt_nr_to_cpu_interrupt_handler(
number: u32,
) -> Option<unsafe extern "C" fn(save_frame: &mut Context)> {
use xtensa_lx_rt::*;
// we're fortunate that all esp variants use the same CPU interrupt layout
Some(match number {
6 => Timer0,
7 => Software0,
11 => Profiling,
14 => NMI,
15 => Timer1,
16 => Timer2,
29 => Software1,
_ => return None,
})
}
#[cfg(esp32s2)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::from(
0b0000_0000_0000_0000_0000_0000_0000_0000,
0b1100_0000_0000_0000_0000_0000_0000_0000,
0b0000_0000_0000_0000_0000_0011_1011_1111,
);
#[inline]
pub fn interrupt_is_edge(interrupt: Interrupt) -> bool {
[
Interrupt::TG0_T0_EDGE,
Interrupt::TG0_T1_EDGE,
Interrupt::TG0_WDT_EDGE,
Interrupt::TG0_LACT_EDGE,
Interrupt::TG1_T0_EDGE,
Interrupt::TG1_T1_EDGE,
Interrupt::TG1_WDT_EDGE,
Interrupt::TG1_LACT_EDGE,
Interrupt::SYSTIMER_TARGET0,
Interrupt::SYSTIMER_TARGET1,
Interrupt::SYSTIMER_TARGET2,
]
.contains(&interrupt)
}
}
#[cfg(esp32s3)]
mod chip_specific {
use super::*;
#[cfg_attr(place_switch_tables_in_ram, unsafe(link_section = ".rwtext"))]
pub static INTERRUPT_EDGE: InterruptStatus = InterruptStatus::empty();
#[inline]
pub fn interrupt_is_edge(_interrupt: Interrupt) -> bool {
false
}
}
}
mod raw {
use super::*;
// Raw handlers for CPU interrupts, assembly only.
unsafe extern "C" {
fn level4_interrupt(save_frame: &mut Context);
fn level5_interrupt(save_frame: &mut Context);

View File

@ -160,6 +160,23 @@
//!
//! You might want to consider using [`#[deny(clippy::mem_forget)`](https://rust-lang.github.io/rust-clippy/v0.0.212/index.html#mem_forget) in your project.
//!
//! ## Library usage
//!
//! If you intend to write a library that uses esp-hal, you should import it as follows:
//!
//! ```toml
//! [dependencies]
//! esp-hal = { version = "1", default-features = false } }
//! ```
//!
//! This ensures that the `rt` feature is not enabled, nor any chip features. The application that
//! uses your library will then be able to choose the chip feature it needs and enable `rt` such
//! that only the final user application calls [`init`].
//!
//! If your library depends on `unstable` features, you *must* use the `requires-unstable` feature,
//! and *not* the unstable feature itself. Doing so, improves the quality of the error messages if a
//! user hasn't enabled the unstable feature of esp-hal.
//!
//! [documentation]: https://docs.espressif.com/projects/rust/esp-hal/latest/
//! [examples]: https://github.com/esp-rs/esp-hal/tree/main/examples
//! [embedded-hal]: https://docs.rs/embedded-hal/latest/embedded_hal/
@ -192,13 +209,14 @@ use esp_rom_sys as _;
metadata!("build_info", CHIP_NAME, chip!());
#[cfg(riscv)]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[cfg(all(riscv, feature = "rt"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", feature = "rt"))))]
#[cfg_attr(not(feature = "unstable"), doc(hidden))]
pub use esp_riscv_rt::{self, riscv};
pub(crate) use peripherals::pac;
#[cfg(xtensa)]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[cfg(all(xtensa, feature = "rt"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", feature = "rt"))))]
#[cfg_attr(not(feature = "unstable"), doc(hidden))]
pub use xtensa_lx_rt::{self, xtensa_lx};
@ -236,6 +254,7 @@ pub mod uart;
mod macros;
#[cfg(feature = "rt")]
pub use procmacros::blocking_main as main;
#[cfg(any(lp_core, ulp_riscv_core))]
#[cfg(feature = "unstable")]
@ -356,6 +375,7 @@ unstable_driver! {
/// State of the CPU saved when entering exception or interrupt
#[instability::unstable]
#[cfg(feature = "rt")]
#[allow(unused_imports)]
pub mod trapframe {
#[cfg(riscv)]
@ -548,18 +568,19 @@ pub mod __macro_implementation {
#[instability::unstable]
pub const fn assert_is_persistable<T: super::Persistable>() {}
#[cfg(feature = "rt")]
#[cfg(riscv)]
pub use esp_riscv_rt::entry as __entry;
#[cfg(feature = "rt")]
#[cfg(xtensa)]
pub use xtensa_lx_rt::entry as __entry;
}
use crate::clock::CpuClock;
#[cfg(feature = "unstable")]
use crate::config::{WatchdogConfig, WatchdogStatus};
use crate::{
clock::{Clocks, CpuClock},
peripherals::Peripherals,
};
#[cfg(feature = "rt")]
use crate::{clock::Clocks, peripherals::Peripherals};
/// System configuration.
///
@ -603,6 +624,8 @@ pub struct Config {
/// let peripherals = init(Config::default());
/// # {after_snippet}
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
#[cfg(feature = "rt")]
pub fn init(config: Config) -> Peripherals {
crate::soc::pre_init();

View File

@ -299,6 +299,7 @@ macro_rules! ignore {
#[doc(hidden)]
macro_rules! metadata {
($category:literal, $key:ident, $value:expr) => {
#[cfg(feature = "rt")]
#[unsafe(link_section = concat!(".espressif.metadata"))]
#[used]
#[unsafe(export_name = concat!($category, ".", stringify!($key)))]

View File

@ -190,6 +190,7 @@ for_each_peripheral! {
impl Peripherals {
/// Returns all the peripherals *once*
#[inline]
#[cfg_attr(not(feature = "rt"), expect(dead_code))]
pub(crate) fn take() -> Self {
#[unsafe(no_mangle)]
static mut _ESP_HAL_DEVICE_PERIPHERALS: bool = false;

View File

@ -1,3 +1,5 @@
#![cfg_attr(not(feature = "rt"), expect(unused))]
use core::ops::Range;
use portable_atomic::{AtomicU8, Ordering};
@ -142,6 +144,7 @@ pub(crate) fn addr_in_range(addr: usize, range: Range<usize>) -> bool {
range.contains(&addr)
}
#[cfg(feature = "rt")]
#[cfg(riscv)]
#[unsafe(export_name = "hal_main")]
fn hal_main(a0: usize, a1: usize, a2: usize) -> ! {
@ -158,6 +161,7 @@ fn hal_main(a0: usize, a1: usize, a2: usize) -> ! {
}
#[cfg(xtensa)]
#[cfg(feature = "rt")]
#[unsafe(no_mangle)]
#[cfg_attr(esp32s3, unsafe(link_section = ".rwtext"))]
unsafe extern "C" fn ESP32Reset() -> ! {
@ -234,11 +238,13 @@ unsafe extern "C" fn ESP32Reset() -> ! {
unsafe { xtensa_lx_rt::Reset() }
}
#[cfg(feature = "rt")]
#[unsafe(export_name = "__stack_chk_fail")]
unsafe extern "C" fn stack_chk_fail() {
panic!("Stack corruption detected");
}
#[cfg(feature = "rt")]
fn setup_stack_guard() {
unsafe extern "C" {
static mut __stack_chk_guard: u32;

View File

@ -146,7 +146,7 @@ mod single_core {
if #[cfg(riscv)] {
if token != 0 {
unsafe {
esp_riscv_rt::riscv::interrupt::enable();
riscv::interrupt::enable();
}
}
} else if #[cfg(xtensa)] {

View File

@ -226,6 +226,7 @@ static PERIPHERAL_REF_COUNT: Mutex<RefCell<[usize; Peripheral::COUNT]>> =
/// Disable all peripherals.
///
/// Peripherals listed in [KEEP_ENABLED] are NOT disabled.
#[cfg_attr(not(feature = "rt"), expect(dead_code))]
pub(crate) fn disable_peripherals() {
// Take the critical section up front to avoid taking it multiple times.
critical_section::with(|cs| {

View File

@ -746,7 +746,7 @@ fn now() -> Instant {
Instant::from_ticks(ticks / div)
}
#[cfg(esp32)]
#[cfg(all(esp32, feature = "rt"))]
pub(crate) fn time_init() {
let apb = crate::Clocks::get().apb_clock.as_hz();
// we assume 80MHz APB clock source - there is no way to configure it in a

View File

@ -21,7 +21,7 @@ test = false
[dependencies]
cfg-if = "1.0.0"
critical-section = "1.2.0"
esp-hal = { version = "1.0.0-beta.1", path = "../esp-hal", features = ["requires-unstable"] }
esp-hal = { version = "1.0.0-beta.1", path = "../esp-hal", default-features = false, features = ["requires-unstable"] }
# ⚠️ Unstable dependencies that are part of the public API
heapless = "0.8.0"

View File

@ -1,7 +1,7 @@
pub use esp_hal::trapframe::TrapFrame;
use esp_wifi_sys::c_types;
use super::*;
pub use crate::hal::interrupt::TrapFrame;
pub(crate) fn new_task_context(
task: extern "C" fn(*mut c_types::c_void),

View File

@ -179,6 +179,7 @@ impl Package {
Package::EspConfig => features.push("build".to_owned()),
Package::EspHal => {
features.push("unstable".to_owned());
features.push("rt".to_owned());
if config.contains("psram") {
// TODO this doesn't test octal psram (since `ESP_HAL_CONFIG_PSRAM_MODE`
// defaults to `quad`) as it would require a separate build
@ -193,6 +194,7 @@ impl Package {
}
Package::EspWifi => {
features.push("esp-hal/unstable".to_owned());
features.push("esp-hal/rt".to_owned());
features.push("defmt".to_owned());
if config.contains("wifi") {
features.push("wifi".to_owned());
@ -213,12 +215,14 @@ impl Package {
}
Package::EspHalEmbassy => {
features.push("esp-hal/unstable".to_owned());
features.push("esp-hal/rt".to_owned());
features.push("defmt".to_owned());
features.push("executors".to_owned());
}
Package::EspIeee802154 => {
features.push("defmt".to_owned());
features.push("esp-hal/unstable".to_owned());
features.push("esp-hal/rt".to_owned());
}
Package::EspLpHal => {
if config.contains("lp_core") {
@ -251,12 +255,19 @@ impl Package {
match self {
Package::EspHal => {
// Make sure no additional features (e.g. no "unstable") still compiles:
// This checks if the `esp-hal` crate compiles with the no features (other than the
// chip selection)
// This tests that disabling the `rt` feature works
cases.push(vec![]);
// This checks if the `esp-hal` crate compiles _without_ the `unstable` feature
// enabled
cases.push(vec!["rt".to_owned()]);
}
Package::EspWifi => {
// Minimal set of features that when enabled _should_ still compile:
cases.push(vec![
"esp-hal/rt".to_owned(),
"esp-hal/unstable".to_owned(),
"builtin-scheduler".to_owned(),
]);

View File

@ -279,7 +279,9 @@ fn lint_package(
builder = builder.arg(arg.to_string());
}
builder = builder.arg(format!("--features={}", features.join(",")));
if !features.is_empty() {
builder = builder.arg(format!("--features={}", features.join(",")));
}
let builder = if fix {
builder.arg("--fix").arg("--lib").arg("--allow-dirty")