diff --git a/documentation/DEVELOPER-GUIDELINES.md b/documentation/DEVELOPER-GUIDELINES.md index 247663b0d..9f9f37fab 100644 --- a/documentation/DEVELOPER-GUIDELINES.md +++ b/documentation/DEVELOPER-GUIDELINES.md @@ -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 diff --git a/esp-hal-embassy/Cargo.toml b/esp-hal-embassy/Cargo.toml index 2fb26c7cc..739e76650 100644 --- a/esp-hal-embassy/Cargo.toml +++ b/esp-hal-embassy/Cargo.toml @@ -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" diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 401b05ec0..330153b70 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -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 diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index e249e517c..e7afa0d58 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -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. diff --git a/esp-hal/build.rs b/esp-hal/build.rs index 0c883ffad..1c0cef638 100644 --- a/esp-hal/build.rs +++ b/esp-hal/build.rs @@ -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> { // 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> { } } - 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> { 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, @@ -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, @@ -200,6 +207,7 @@ fn preprocess_file( Ok(()) } +#[cfg(feature = "rt")] fn substitute_config(cfg: &HashMap, line: &str) -> String { let mut result = String::new(); let mut chars = line.chars().peekable(); @@ -237,7 +245,7 @@ fn substitute_config(cfg: &HashMap, line: &str) -> String { result } -#[cfg(feature = "esp32")] +#[cfg(all(feature = "esp32", feature = "rt"))] fn generate_memory_extras() -> Vec { let reserve_dram = if cfg!(feature = "__bluetooth") { "0x10000" @@ -255,7 +263,7 @@ fn generate_memory_extras() -> Vec { .to_vec() } -#[cfg(feature = "esp32s2")] +#[cfg(all(feature = "esp32s2", feature = "rt"))] fn generate_memory_extras() -> Vec { let reserved_cache = if cfg!(feature = "psram") { "0x4000" diff --git a/esp-hal/src/clock/mod.rs b/esp-hal/src/clock/mod.rs index 9a94c5847..1f723e7cb 100644 --- a/esp-hal/src/clock/mod.rs +++ b/esp-hal/src/clock/mod.rs @@ -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}; diff --git a/esp-hal/src/gpio/interrupt.rs b/esp-hal/src/gpio/interrupt.rs index 273153f4c..99e604949 100644 --- a/esp-hal/src/gpio/interrupt.rs +++ b/esp-hal/src/gpio/interrupt.rs @@ -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(); diff --git a/esp-hal/src/gpio/mod.rs b/esp-hal/src/gpio/mod.rs index 829d111c8..486ba8f6e 100644 --- a/esp-hal/src/gpio/mod.rs +++ b/esp-hal/src/gpio/mod.rs @@ -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. /// diff --git a/esp-hal/src/interrupt/mod.rs b/esp-hal/src/interrupt/mod.rs index 58db1aaae..568ec5628 100644 --- a/esp-hal/src/interrupt/mod.rs +++ b/esp-hal/src/interrupt/mod.rs @@ -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(), ); diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index c0f49d706..8416df425 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -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 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::( + 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::(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::( - 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::(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 - } -} diff --git a/esp-hal/src/interrupt/xtensa.rs b/esp-hal/src/interrupt/xtensa.rs index 6818bd689..bcd70c242 100644 --- a/esp-hal/src/interrupt/xtensa.rs +++ b/esp-hal/src/interrupt/xtensa.rs @@ -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 { - 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 { + 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); diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs index 3d276c358..a708c30d7 100644 --- a/esp-hal/src/lib.rs +++ b/esp-hal/src/lib.rs @@ -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() {} + #[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(); diff --git a/esp-hal/src/macros.rs b/esp-hal/src/macros.rs index adafee7fa..09bd38ecc 100644 --- a/esp-hal/src/macros.rs +++ b/esp-hal/src/macros.rs @@ -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)))] diff --git a/esp-hal/src/peripherals.rs b/esp-hal/src/peripherals.rs index 059f45e45..7ee6f288d 100644 --- a/esp-hal/src/peripherals.rs +++ b/esp-hal/src/peripherals.rs @@ -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; diff --git a/esp-hal/src/soc/mod.rs b/esp-hal/src/soc/mod.rs index 25e310399..96910ca37 100644 --- a/esp-hal/src/soc/mod.rs +++ b/esp-hal/src/soc/mod.rs @@ -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) -> 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; diff --git a/esp-hal/src/sync.rs b/esp-hal/src/sync.rs index 0cfd402fe..6ab06c925 100644 --- a/esp-hal/src/sync.rs +++ b/esp-hal/src/sync.rs @@ -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)] { diff --git a/esp-hal/src/system.rs b/esp-hal/src/system.rs index be20a72b1..60750ceb9 100755 --- a/esp-hal/src/system.rs +++ b/esp-hal/src/system.rs @@ -226,6 +226,7 @@ static PERIPHERAL_REF_COUNT: Mutex> = /// 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| { diff --git a/esp-hal/src/time.rs b/esp-hal/src/time.rs index d8bdff89a..000205d8d 100644 --- a/esp-hal/src/time.rs +++ b/esp-hal/src/time.rs @@ -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 diff --git a/esp-ieee802154/Cargo.toml b/esp-ieee802154/Cargo.toml index 7620bff89..1d1c6cb6f 100644 --- a/esp-ieee802154/Cargo.toml +++ b/esp-ieee802154/Cargo.toml @@ -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" diff --git a/esp-wifi/src/preempt_builtin/preempt_riscv.rs b/esp-wifi/src/preempt_builtin/preempt_riscv.rs index 85b2d1d0c..ea3c8638f 100644 --- a/esp-wifi/src/preempt_builtin/preempt_riscv.rs +++ b/esp-wifi/src/preempt_builtin/preempt_riscv.rs @@ -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), diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 912738c94..5f5b83652 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -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(), ]); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fc7ab277d..63269b73e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -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")