From 3daab42854b71518c4ec1ba969b321ef36488aa4 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Wed, 18 Mar 2026 06:22:54 +1100 Subject: [PATCH 01/47] Add IRQ handler, add custom R-type instruction macros, move out reset/interrupt vector assembly --- esp-lp-hal/CHANGELOG.md | 1 + esp-lp-hal/src/lib.rs | 130 +++++++++++++++++++---- esp-lp-hal/src/ulp_riscv_interrupt_ops.S | 101 ++++++++++++++++++ esp-lp-hal/src/ulp_riscv_vectors.S | 93 ++++++++++++++++ 4 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 esp-lp-hal/src/ulp_riscv_interrupt_ops.S create mode 100644 esp-lp-hal/src/ulp_riscv_vectors.S diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md index ea09a09a016..63804eb104a 100644 --- a/esp-lp-hal/CHANGELOG.md +++ b/esp-lp-hal/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `wake_hp_core` for ESP32-S2 and ESP32-S3 (#5133) - Add `OutputOpenDrain` GPIO type (#5131) +- ESP32-S2, ESP32-S3: Added private ULP interrupt handler to prevent uncleared interrupts from hanging the core (#5134) ### Changed - Changed ULP entrypoint to allow `main()` to return, and halt, to the ULP Timer can be used (#5134) diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index face90d51e5..b1506173558 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -55,6 +55,18 @@ cfg_if::cfg_if! { } } +// ULP interrupt bitflags from: +// https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 +cfg_if::cfg_if! { + if #[cfg(any(esp32s2,esp32s3))] { + const ULP_RISCV_TIMER_INT : u32 = 1 << 0; /* Internal Timer Interrupt */ + const ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT : u32 = 1 << 1; /* EBREAK, ECALL or Illegal instruction */ + const ULP_RISCV_BUS_ERROR_INT : u32 = 1 << 2; /* Bus Error (Unaligned Memory Access) */ + const ULP_RISCV_PERIPHERAL_INTERRUPT : u32 = 1 << 31; /* RTC Peripheral Interrupt */ + const ULP_RISCV_INTERNAL_INTERRUPT : u32 = ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT; + } +} + pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; /// Wake up the HP core @@ -111,34 +123,112 @@ loop: "# ); +#[cfg(any(esp32s2, esp32s3))] +// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. +global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); + +// Assembly containing the reset_vector and irq_vector instructions. +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./ulp_riscv_vectors.S")); + #[cfg(any(esp32s2, esp32s3))] global_asm!( r#" - .section .text.vectors - .global irq_vector - .global reset_vector + .balign 0x10 + .section .init + __start: + /* setup the stack pointer */ + la sp, __stack_top -/* The reset vector, jumps to startup code */ -reset_vector: - j __start + /* Custom instruction to un-mask the interrupts */ + /* waitirq_insn zero */ + maskirq_insn zero, zero -/* Interrupt handler */ -.balign 16 -irq_vector: - ret + call ulp_riscv_rescue_from_monitor + call rust_main - .section .text + loop: + j loop + "# +); -__start: - /* setup the stack pointer */ - la sp, __stack_top +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(no_mangle)] +unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { + // This interrupt handler is placeholder - it simply checks the interrupt flags, and clears + // them. This function is based on the ESP-IDF implementation found here: + // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 - call ulp_riscv_rescue_from_monitor - call rust_main -loop: - j loop -"# -); + // ULP Internal Interrupts + if (q1 & ULP_RISCV_INTERNAL_INTERRUPT) > 0 { + // TODO + } + + // External/Peripheral interrupts + if (q1 & ULP_RISCV_PERIPHERAL_INTERRUPT) > 0 { + // RTC Peripheral interrupts + let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); + + if cocpu_int_st > 0 { + // Clear the interrupt + unsafe { &*pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(cocpu_int_st) }); + } + + // RTC IO interrupts + let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); + + if rtcio_int_st > 0 { + // Clear the interrupt + unsafe { &*pac::RTC_IO::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(rtcio_int_st) }); + } + } +} + +/// Enter a critical section (disable interrupts) +#[cfg(any(esp32s2, esp32s3))] +pub fn ulp_disable_interrupts() { + // Enter a critical section by disabling all interrupts + // This inline assembly construct uses the t0 register and is equivalent to: + // > li t0, 0x80000007 + // > maskirq_insn(zero, t0) // Mask all interrupt bits + // + // The mask 0x80000007 represents: + // Bit 31 - RTC peripheral interrupt + // Bit 2 - Bus error + // Bit 1 - Ebreak / Ecall / Illegal Instruction + // Bit 0 - Internal Timer + // + unsafe { + core::arch::asm!("li t0, 0x80000007", ".word 0x0602e00b"); + } +} + +/// Exit a critical section (re-enable interrupts) +#[cfg(any(esp32s2, esp32s3))] +pub fn ulp_enable_interrupts() { + // Exit a critical section by enabling all interrupts + // This inline assembly construct is equivalent to: + // > maskirq_insn(zero, zero) + unsafe { + core::arch::asm!(".word 0x0600600b"); + } +} + +/// Wait for any (even unmasked) interrupt +#[cfg(any(esp32s2, esp32s3))] +pub fn ulp_waitirq() -> u32 { + // Wait for pending interrupt, return pending interrupt mask + // waitirq a0 + let result: u32; + unsafe { + core::arch::asm!(".word 0x0800400B", out("a0") result); + } + result +} /// Entry point to the ULP program #[unsafe(link_section = ".init.rust")] diff --git a/esp-lp-hal/src/ulp_riscv_interrupt_ops.S b/esp-lp-hal/src/ulp_riscv_interrupt_ops.S new file mode 100644 index 00000000000..74d4fbe9392 --- /dev/null +++ b/esp-lp-hal/src/ulp_riscv_interrupt_ops.S @@ -0,0 +1,101 @@ +/** + * Defines the assembly macros needed for custom instructions on the ULP RISCV core. + * Adapted from the following ESP-IDF header: + * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h + * With additional waitirq_insn from PicoRV32: + * https://github.com/YosysHQ/picorv32/blob/87c89acc18994c8cf9a2311e871818e87d304568/firmware/start.S + * A quote from the ESP-IDF header file, is shown below. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * This header file defines custom instructions for interrupt handling on the * + * ULP RISC-V. The architecture of the processor and therefore, the interrupt * + * handling is based on the PicoRV32 CPU. Details about the operations are * + * available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +.set regnum_zero,0 +.set regnum_ra, 1 +.set regnum_sp, 2 +.set regnum_gp, 3 +.set regnum_tp, 4 +.set regnum_t0, 5 +.set regnum_t1, 6 +.set regnum_t2, 7 +.set regnum_s0, 8 +.set regnum_s1, 9 +.set regnum_a0, 10 +.set regnum_a1, 11 +.set regnum_a2, 12 +.set regnum_a3, 13 +.set regnum_a4, 14 +.set regnum_a5, 15 +.set regnum_a6, 16 +.set regnum_a7, 17 +.set regnum_s2, 18 +.set regnum_s3, 19 +.set regnum_s4, 20 +.set regnum_s5, 21 +.set regnum_s6, 22 +.set regnum_s7, 23 +.set regnum_s8, 24 +.set regnum_s9, 25 +.set regnum_s10,26 +.set regnum_s11,27 +.set regnum_t3, 28 +.set regnum_t4, 29 +.set regnum_t5, 30 +.set regnum_t6, 31 + +/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */ +.set regnum_q0, 0 +.set regnum_q1, 1 +.set regnum_q2, 2 +.set regnum_q3, 3 + +/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA + * with the same opcode of custom0 (0001011). + */ +.macro r_type_insn _f7, _rs2, _rs1, _f3, _rd, _opc + .word (((\_f7) << 25) | ((\_rs2) << 20) | ((\_rs1) << 15) | ((\_f3) << 12) | ((\_rd) << 7) | ((\_opc) << 0)) +.endm + +/** + * Instruction: getq rd, qs + * Description: This instruction copies the value of Qx into a general purpose register rd + */ +.macro getq_insn _rd _qs + r_type_insn 0b0000000, 0, "regnum_\_qs", 0b100, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: setq qd, rs + * Description: This instruction copies the value of general purpose register rs to Qx + */ +.macro setq_insn _qd _rs + r_type_insn 0b0000001, 0, "regnum_\_rs", 0b010, "regnum_\_qd", 0b0001011 +.endm + +/** + * Instruction: retirq + * Description: This instruction copies the value of Q0 to CPU PC, and renables interrupts + */ +.macro retirq_insn + r_type_insn 0b0000010, 0, 0, 0b000, 0, 0b0001011 +.endm + +/** + * Instruction: maskirq rd, rs + * Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value + * of register rs to to IRQ mask. + */ +.macro maskirq_insn _rd _rs + r_type_insn 0b0000011, 0, "regnum_\_rs", 0b110, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: waitirq rd + * Description: Pause execution until any interrupt (masked or unmasked) becomes pending. Stores the pending IRQ bitmask into register rd. + */ +.macro waitirq_insn _rd + r_type_insn 0b0000100, 0, 0, 0b100, "regnum_\_rd", 0b0001011 +.endm diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S new file mode 100644 index 00000000000..f6b8f46245f --- /dev/null +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -0,0 +1,93 @@ +/* Much of this assembly was sourced from the following ESP-IDF files... +* ...irq handler macros: +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S +* ...critical section assembly +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_utils.h +* ...riscv halt code +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c +*/ + +.equ SAVE_REGS, 17 +.equ CONTEXT_SIZE, (SAVE_REGS * 4) + +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. + * + * Note: We don't save the callee-saved s0-s11 registers to save space + */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, 0(sp) + sw tp, 4(sp) + sw t0, 8(sp) + sw t1, 12(sp) + sw t2, 16(sp) + sw a0, 20(sp) + sw a1, 24(sp) + sw a2, 28(sp) + sw a3, 32(sp) + sw a4, 36(sp) + sw a5, 40(sp) + sw a6, 44(sp) + sw a7, 48(sp) + sw t3, 52(sp) + sw t4, 56(sp) + sw t5, 60(sp) + sw t6, 64(sp) +.endm + +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, 0(sp) + lw tp, 4(sp) + lw t0, 8(sp) + lw t1, 12(sp) + lw t2, 16(sp) + lw a0, 20(sp) + lw a1, 24(sp) + lw a2, 28(sp) + lw a3, 32(sp) + lw a4, 36(sp) + lw a5, 40(sp) + lw a6, 44(sp) + lw a7, 48(sp) + lw t3, 52(sp) + lw t4, 56(sp) + lw t5, 60(sp) + lw t6, 64(sp) + addi sp,sp, \cxt_size +.endm + +.section .text.vectors +.global irq_vector +.global reset_vector + +/* The reset vector, jumps to startup code */ +reset_vector: + /* no more than 16 bytes here ! */ + j __start + +/* Interrupt handler */ +.balign 0x10 +irq_vector: + /* Save the general gurpose register context before handling the interrupt */ + save_general_regs + + /* Fetch the interrupt status from the custom q1 register into a0 */ + getq_insn a0, q1 + + /* Call the global interrupt handler. The interrupt status is passed as the argument in a0. + * We do not re-enable interrupts before calling the handler as ULP RISC-V does not + * support nested interrupts. + */ + jal _ulp_riscv_interrupt_handler + + /* Restore the register context after returning from the C interrupt handler */ + restore_general_regs + + /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ + retirq_insn \ No newline at end of file From 5e2846e806a523dd57ed737e190da4067dc93f26 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 21 Mar 2026 07:17:13 +1100 Subject: [PATCH 02/47] Moving some riscv-rt stuff into here --- esp-lp-hal/Cargo.toml | 7 ++ esp-lp-hal/build.rs | 24 ++-- esp-lp-hal/ld/exceptions-ulp.x | 23 ++++ esp-lp-hal/ld/interrupts-ulp.x | 25 ++++ esp-lp-hal/ld/link-ulp.x | 186 ++++++++++++++++++++++------- esp-lp-hal/ld/memory-ulp.x | 19 +++ esp-lp-hal/src/lib.rs | 21 ---- esp-lp-hal/src/ulp_riscv_vectors.S | 25 +++- 8 files changed, 250 insertions(+), 80 deletions(-) create mode 100644 esp-lp-hal/ld/exceptions-ulp.x create mode 100644 esp-lp-hal/ld/interrupts-ulp.x create mode 100644 esp-lp-hal/ld/memory-ulp.x diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 18129cbe297..1fa1c634376 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -49,6 +49,13 @@ esp32c6-lp = { version = "0.3.0", features = ["critical-section"], option esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } +# riscv-rt TODOs for adapting esp-lp-hal +# 1. Check that the overriden _setup_interrupt and _start_trap functions work, +# with the interrupt/exception handling hooks provided by riscv-rt. +# 2. Add the custom picorv32 assembly instructions somewhere, exported as inlined rust functions +# 3. Add a critical section implementation +riscv-rt = { version = "0.17.1", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"] } + [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs index f07dca41629..cf6abc84886 100644 --- a/esp-lp-hal/build.rs +++ b/esp-lp-hal/build.rs @@ -9,18 +9,28 @@ fn main() -> Result<(), Box> { // Define all necessary configuration symbols for the configured device: chip.define_cfgs(); - // Copy the required linker script to the `out` directory: + // Copy the required linker scripts to the `out` directory: + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_dir.display()); + let source_file = match chip { - Chip::Esp32c6 => "ld/link-lp.x", - Chip::Esp32s2 | Chip::Esp32s3 => "ld/link-ulp.x", + Chip::Esp32c6 => { + fs::write(out_dir.join("link.x"), include_bytes!("ld/link-lp.x"))?; + }, + Chip::Esp32s2 | Chip::Esp32s3 => { + fs::write(out_dir.join("link.x"), include_bytes!("ld/link-ulp.x"))?; + fs::write(out_dir.join("memory.x"), include_bytes!("ld/memory-ulp.x"))?; + fs::write(out_dir.join("interrupts.x"), include_bytes!("ld/interrupts-ulp.x"))?; + fs::write(out_dir.join("exceptions.x"), include_bytes!("ld/exceptions-ulp.x"))?; + }, _ => unreachable!(), }; - // Put the linker script somewhere the linker can find it: - let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - println!("cargo:rustc-link-search={}", out.display()); - fs::copy(source_file, out.join("link.x"))?; + println!("cargo:rerun-if-changed=ld/link-lp.x"); println!("cargo:rerun-if-changed=ld/link-ulp.x"); + println!("cargo:rerun-if-changed=ld/memory-ulp.x"); + println!("cargo:rerun-if-changed=ld/interrupts-ulp.x"); + println!("cargo:rerun-if-changed=ld/exceptions-ulp.x"); // Done! Ok(()) diff --git a/esp-lp-hal/ld/exceptions-ulp.x b/esp-lp-hal/ld/exceptions-ulp.x new file mode 100644 index 00000000000..e9d3f8fbd15 --- /dev/null +++ b/esp-lp-hal/ld/exceptions-ulp.x @@ -0,0 +1,23 @@ +/* # EXCEPTION HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA + + If the `no-exceptions` feature is DISABLED, this file will be included in link.x.in. + If the `no-exceptions` feature is ENABLED, this file will be ignored. +*/ + +/* It is possible to define a special handler for each exception type. + By default, all exceptions are handled by ExceptionHandler. However, + users can override these alias by defining the symbol themselves */ +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); diff --git a/esp-lp-hal/ld/interrupts-ulp.x b/esp-lp-hal/ld/interrupts-ulp.x new file mode 100644 index 00000000000..2440be77227 --- /dev/null +++ b/esp-lp-hal/ld/interrupts-ulp.x @@ -0,0 +1,25 @@ +/* # CORE INTERRUPT HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA + + If the `no-interrupts` feature is DISABLED, this file will be included in link.x.in. + If the `no-interrupts` feature is ENABLED, this file will be ignored. +*/ + +/* It is possible to define a special handler for each interrupt type. + By default, all interrupts are handled by DefaultHandler. However, users can + override these alias by defining the symbol themselves */ +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +/* When vectored trap mode is enabled, each interrupt source must implement its own + trap entry point. By default, all interrupts start in _DefaultHandler_trap. + However, users can override these alias by defining the symbol themselves */ +PROVIDE(_start_SupervisorSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorExternal_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineExternal_trap = _start_DefaultHandler_trap); diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 7e6926a7fa1..ba95d662edb 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -1,53 +1,147 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ +/* NOTE: Adapted from riscv_rt/link.x */ +INCLUDE memory.x +INCLUDE interrupts.x +INCLUDE exceptions.x -ENTRY(reset_vector) +/* Default abort entry point. If no abort symbol is provided, then abort maps to _default_abort. */ +EXTERN(_default_abort); +PROVIDE(abort = _default_abort); -CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; +/* Trap for exceptions triggered during initialization. If the execution reaches this point, it + means that there is a bug in the boot code. If no _pre_init_trap symbol is provided, then + _pre_init_trap defaults to _default_abort. Note that _pre_init_trap must be 4-byte aligned */ +PROVIDE(_pre_init_trap = _default_abort); -MEMORY -{ - ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM -} +/* Multi-processor hook function (for multi-core targets only). If no _mp_hook symbol + is provided, then _mp_hook maps to _default_mp_hook, which leaves HART 0 running while + the other HARTS stuck in a busy loop. Note that _default_mp_hook cannot be overwritten. + We use PROVIDE to avoid compilation errors in single hart targets, not to allow users + to overwrite the symbol. */ +PROVIDE(_default_mp_hook = abort); +PROVIDE(_mp_hook = _default_mp_hook); + +/* Default trap entry point. If not _start_trap symbol is provided, then _start_trap maps to + _default_start_trap, which saves caller saved registers, calls _start_trap_rust, restores + caller saved registers and then returns. Note that _start_trap must be 4-byte aligned */ +/* EXTERN(_default_start_trap);*/ +/* PROVIDE(_start_trap = _default_start_trap); */ + +/* Default interrupt setup entry point. If not _setup_interrupts symbol is provided, then + _setup_interrupts maps to _default_setup_interrupts, which in direct mode sets the value + of the xtvec register to _start_trap and, in vectored mode, sets its value to + _vector_table and enables vectored mode. */ +/* EXTERN(_default_setup_interrupts); */ +PROVIDE(_setup_interrupts = _setup_interrupts); + +/* Default main routine. If no hal_main symbol is provided, then hal_main maps to main, which + is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main + instead of main directly allow HALs to inject code before jumping to user main. */ +PROVIDE(hal_main = main); + +/* Default exception handler. By default, the exception handler is abort. + Users can override this alias by defining the symbol themselves */ +PROVIDE(ExceptionHandler = abort); + +/* Default interrupt handler. By default, the interrupt handler is abort. + Users can override this alias by defining the symbol themselves */ +PROVIDE(DefaultHandler = abort); + +/* Default interrupt trap entry point. When vectored trap mode is enabled, + the riscv-rt crate provides an implementation of this function, which saves caller saved + registers, calls the the DefaultHandler ISR, restores caller saved registers and returns. + Note, however, that this provided implementation cannot be overwritten. We use PROVIDE + to avoid compilation errors in direct mode, not to allow users to overwrite the symbol. */ +PROVIDE(_start_DefaultHandler_trap = _start_trap); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); SECTIONS { - . = ORIGIN(ram); - - .text : - { - *(.text.vectors) /* Default reset vector must link to offset 0x0 */ - - KEEP(*(.init)); - KEEP(*(.init.rust)); - *(.text) - *(.text*) - } >ram - - .rodata ALIGN(4): - { - *(.rodata) - *(.rodata*) - } > ram - - .data ALIGN(4): - { - *(.data) - *(.data*) - *(.sdata) - *(.sdata*) - } > ram - - .bss ALIGN(4) : - { - *(.bss) - *(.bss*) - *(.sbss) - *(.sbss*) - } >ram - - __stack_top = ORIGIN(ram) + LENGTH(ram); + PROVIDE(_stext = ORIGIN(RAM)); + + .text ALIGN(_stext,4) : + { + /* + * Default reset vector must link to offset 0x0, + * and interrupt handler must link to offset 0x10. + */ + KEEP(*(.init.vectors)); + KEEP(*(.init)); + KEEP(*(.init.rust)); + KEEP(*(.trap.rust)); + *(.text.abort); + *(.text .text.*); + + . = ALIGN(4); + __etext = .; + } > REGION_TEXT + + .rodata ALIGN(4) : + { + *(.rodata .rodata.*); + } > REGION_RODATA + + .bss : + { + __sbss = .; + *(.bss .bss.*); + . = ALIGN(4); + __ebss = .; + } > REGION_BSS + + .data : AT(LOADADDR(.rodata) + SIZEOF(.rodata)) + { + __sidata = LOADADDR(.data); + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.data .data.*); + . = ALIGN(4); + __edata = .; + } > RAM + + PROVIDE(_heap_size = 0); + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + __sheap = .; + . += _heap_size; + . = ALIGN(4); + __eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + __estack = .; + . = _stack_start; + __sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + /* Discard .eh_frame, we are not doing unwind on panic so it is not needed */ + /DISCARD/ : + { + *(.eh_frame); + } } + +/* Do not exceed this mark in the error messages below | */ +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); diff --git a/esp-lp-hal/ld/memory-ulp.x b/esp-lp-hal/ld/memory-ulp.x new file mode 100644 index 00000000000..b19d59b980f --- /dev/null +++ b/esp-lp-hal/ld/memory-ulp.x @@ -0,0 +1,19 @@ +CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; + +MEMORY +{ + RAM(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM +} + +REGION_ALIAS("REGION_TEXT", RAM); +REGION_ALIAS("REGION_RODATA", RAM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +_stext = ORIGIN(REGION_TEXT) + 0x0; /* Load .text region at 0x0 */ +_heap_size = 0; /* Disable heap */ +_max_hart_id = 0; /* One harts present */ +_hart_stack_size = SIZEOF(.stack); +_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK); diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index b1506173558..5eeb2f5373a 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -131,27 +131,6 @@ global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); #[cfg(any(esp32s2, esp32s3))] global_asm!(include_str!("./ulp_riscv_vectors.S")); -#[cfg(any(esp32s2, esp32s3))] -global_asm!( - r#" - .balign 0x10 - .section .init - __start: - /* setup the stack pointer */ - la sp, __stack_top - - /* Custom instruction to un-mask the interrupts */ - /* waitirq_insn zero */ - maskirq_insn zero, zero - - call ulp_riscv_rescue_from_monitor - call rust_main - - loop: - j loop - "# -); - #[cfg(any(esp32s2, esp32s3))] #[unsafe(no_mangle)] unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S index f6b8f46245f..e9282b7ea79 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -62,18 +62,22 @@ addi sp,sp, \cxt_size .endm -.section .text.vectors -.global irq_vector +.section .init.vectors .global reset_vector +.global ulp_riscv_rescue_from_monitor +.global _start +.global _start_trap +.global _start_trap_rust +.global _setup_interrupts /* The reset vector, jumps to startup code */ reset_vector: /* no more than 16 bytes here ! */ - j __start + j _start /* Interrupt handler */ .balign 0x10 -irq_vector: +_start_trap: /* Save the general gurpose register context before handling the interrupt */ save_general_regs @@ -84,10 +88,19 @@ irq_vector: * We do not re-enable interrupts before calling the handler as ULP RISC-V does not * support nested interrupts. */ - jal _ulp_riscv_interrupt_handler + jal _start_trap_rust /* Restore the register context after returning from the C interrupt handler */ restore_general_regs /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ - retirq_insn \ No newline at end of file + retirq_insn + +.section .init.rust +_setup_interrupts: + /* Custom instruction to un-mask the interrupts */ + /* waitirq_insn zero */ + maskirq_insn zero, zero + /* Allow ULP to run */ + call ulp_riscv_rescue_from_monitor + ret From 7f4bdd7e953a6a8b34f474b8b19ab19f90e9b9b3 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 21 Mar 2026 13:20:58 +1100 Subject: [PATCH 03/47] ULP is working, no interrupt handling yet --- esp-hal-procmacros/src/lp_core.rs | 2 +- esp-lp-hal/Cargo.toml | 3 +- esp-lp-hal/build.rs | 2 +- esp-lp-hal/ld/link-ulp.x | 9 +++++- esp-lp-hal/src/lib.rs | 45 ++++++++++++++++++++++++++++++ esp-lp-hal/src/ulp_riscv_vectors.S | 15 ++-------- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs index b26ac6f6c01..18241c6f7e6 100644 --- a/esp-hal-procmacros/src/lp_core.rs +++ b/esp-hal-procmacros/src/lp_core.rs @@ -190,7 +190,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { quote!( #[allow(non_snake_case)] #[unsafe(export_name = "main")] - pub fn __risc_v_rt__main() { + pub fn __esp_lp_hal__main() { #[unsafe(export_name = #magic_symbol_name)] static ULP_MAGIC: [u32; 0] = [0u32; 0]; diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 1fa1c634376..20430d7bac6 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -54,7 +54,8 @@ esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], option # with the interrupt/exception handling hooks provided by riscv-rt. # 2. Add the custom picorv32 assembly instructions somewhere, exported as inlined rust functions # 3. Add a critical section implementation -riscv-rt = { version = "0.17.1", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"] } +#riscv-rt = { version = "0.17.1", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } +riscv-rt = { version = "0.19.0", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs index cf6abc84886..32900f060e4 100644 --- a/esp-lp-hal/build.rs +++ b/esp-lp-hal/build.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out_dir.display()); - let source_file = match chip { + match chip { Chip::Esp32c6 => { fs::write(out_dir.join("link.x"), include_bytes!("ld/link-lp.x"))?; }, diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index ba95d662edb..50c45f9f700 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -36,7 +36,14 @@ PROVIDE(_setup_interrupts = _setup_interrupts); /* Default main routine. If no hal_main symbol is provided, then hal_main maps to main, which is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main instead of main directly allow HALs to inject code before jumping to user main. */ -PROVIDE(hal_main = main); +/* PROVIDE(hal_main = main); */ + +/* riscv-rt will jump to the entrypoint of esp-lp-hal, which is rust_main */ +/* rust_main will then jumps to main() */ +/* which itself is wrapped using esp-lp-hal::entry, to add the ULP_MAGIC symbols lol. */ +/* SO the full boot procedure is... */ +/* reset_vector --> _start --> _start_rust + (_setup_interrupts) --> $hal_main --> main */ +PROVIDE(hal_main = rust_main); /* Default exception handler. By default, the exception handler is abort. Users can override this alias by defining the symbol themselves */ diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 5eeb2f5373a..53fa2f67bcc 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,11 +38,19 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; +// This crate needs to bring in some other global assembly +extern crate riscv_rt; + +/// Re-exported interrupt APIs from riscv-rt / riscv crates. +pub use riscv_rt::{external_interrupt,core_interrupt,exception,TrapFrame}; +pub use riscv::interrupt::{Interrupt,Exception}; + /// The prelude pub mod prelude { pub use procmacros::entry; } + cfg_if::cfg_if! { if #[cfg(esp32c6)] { // LP_FAST_CLK is not very accurate, for now use a rough estimate @@ -131,9 +139,43 @@ global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); #[cfg(any(esp32s2, esp32s3))] global_asm!(include_str!("./ulp_riscv_vectors.S")); +/// riscv-rt redefinition which unmasks the interrupts. +/// Called during _start_rust, which is prior to rust_main, +/// which is prior to main(). +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(export_name = "_setup_interrupts")] +pub fn setup_interrupts() { + // disable interrupt handling for now... + // ulp_enable_interrupts(); + unsafe { ulp_riscv_rescue_from_monitor() }; +} + +// /// Overriden riscv-rt function for _start_trap_rust, +// /// since ULP core has no xcause register. +// #[cfg(any(esp32s2, esp32s3))] +// #[unsafe(link_section = ".trap.rust")] +// #[unsafe(export_name = "_start_trap_rust")] +// pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { +// unsafe extern "C" { +// fn _dispatch_core_interrupt(code: usize); +// fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); +// } + +// // match xcause::read().cause() { +// // #[cfg(not(feature = "v-trap"))] +// // xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), +// // xcause::Trap::Exception(code) => _dispatch_exception(&*trap_frame, code), +// // } +// } + #[cfg(any(esp32s2, esp32s3))] #[unsafe(no_mangle)] +// #[unsafe(link_section = ".trap.rust")] +// #[unsafe(export_name = "_start_trap_rust")] unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { + // TODO: Figure out how to call the riscv-rt interrupt handler, + // instead of/after this custom one! :) + // This interrupt handler is placeholder - it simply checks the interrupt flags, and clears // them. This function is based on the ESP-IDF implementation found here: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 @@ -169,6 +211,7 @@ unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { /// Enter a critical section (disable interrupts) #[cfg(any(esp32s2, esp32s3))] +#[inline(always)] pub fn ulp_disable_interrupts() { // Enter a critical section by disabling all interrupts // This inline assembly construct uses the t0 register and is equivalent to: @@ -188,6 +231,7 @@ pub fn ulp_disable_interrupts() { /// Exit a critical section (re-enable interrupts) #[cfg(any(esp32s2, esp32s3))] +#[inline(always)] pub fn ulp_enable_interrupts() { // Exit a critical section by enabling all interrupts // This inline assembly construct is equivalent to: @@ -199,6 +243,7 @@ pub fn ulp_enable_interrupts() { /// Wait for any (even unmasked) interrupt #[cfg(any(esp32s2, esp32s3))] +#[inline(always)] pub fn ulp_waitirq() -> u32 { // Wait for pending interrupt, return pending interrupt mask // waitirq a0 diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S index e9282b7ea79..a9a81f02fdc 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -64,11 +64,9 @@ .section .init.vectors .global reset_vector -.global ulp_riscv_rescue_from_monitor .global _start .global _start_trap -.global _start_trap_rust -.global _setup_interrupts +.global _ulp_riscv_interrupt_handler /* The reset vector, jumps to startup code */ reset_vector: @@ -88,19 +86,10 @@ _start_trap: * We do not re-enable interrupts before calling the handler as ULP RISC-V does not * support nested interrupts. */ - jal _start_trap_rust + jal _ulp_riscv_interrupt_handler /* Restore the register context after returning from the C interrupt handler */ restore_general_regs /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ retirq_insn - -.section .init.rust -_setup_interrupts: - /* Custom instruction to un-mask the interrupts */ - /* waitirq_insn zero */ - maskirq_insn zero, zero - /* Allow ULP to run */ - call ulp_riscv_rescue_from_monitor - ret From 30172281f94c7a3c974e6733cf9c5db8236534d0 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 22 Mar 2026 08:34:52 +1100 Subject: [PATCH 04/47] Updated interrupt handler to save/restore TrapFrame like riscv-rt, but having other issues with riscv-rt --- esp-lp-hal/Cargo.toml | 2 +- esp-lp-hal/ld/exceptions-ulp.x | 28 +++-- esp-lp-hal/ld/interrupts-ulp.x | 23 ++-- esp-lp-hal/ld/link-ulp.x | 3 +- esp-lp-hal/src/lib.rs | 192 +++++++++++++++++++++-------- esp-lp-hal/src/ulp_riscv_vectors.S | 97 +++++++++------ 6 files changed, 221 insertions(+), 124 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 20430d7bac6..607d09513e6 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -55,7 +55,7 @@ esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], option # 2. Add the custom picorv32 assembly instructions somewhere, exported as inlined rust functions # 3. Add a critical section implementation #riscv-rt = { version = "0.17.1", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } -riscv-rt = { version = "0.19.0", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } +riscv-rt = { version = "0.19.0", features = [ "no-interrupts", "no-exceptions", "no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/ld/exceptions-ulp.x b/esp-lp-hal/ld/exceptions-ulp.x index e9d3f8fbd15..dd4ecf8945b 100644 --- a/esp-lp-hal/ld/exceptions-ulp.x +++ b/esp-lp-hal/ld/exceptions-ulp.x @@ -7,17 +7,19 @@ /* It is possible to define a special handler for each exception type. By default, all exceptions are handled by ExceptionHandler. However, users can override these alias by defining the symbol themselves */ -PROVIDE(InstructionMisaligned = ExceptionHandler); -PROVIDE(InstructionFault = ExceptionHandler); -PROVIDE(IllegalInstruction = ExceptionHandler); +/* PROVIDE(InstructionMisaligned = ExceptionHandler); */ +/* PROVIDE(InstructionFault = ExceptionHandler); */ +/* PROVIDE(IllegalInstruction = ExceptionHandler); */ +/* PROVIDE(Breakpoint = ExceptionHandler); */ +/* PROVIDE(LoadMisaligned = ExceptionHandler); */ +/* PROVIDE(LoadFault = ExceptionHandler); */ +/* PROVIDE(StoreMisaligned = ExceptionHandler); */ +/* PROVIDE(StoreFault = ExceptionHandler); */ +/* PROVIDE(UserEnvCall = ExceptionHandler); */ +/* PROVIDE(SupervisorEnvCall = ExceptionHandler); */ +/* PROVIDE(MachineEnvCall = ExceptionHandler); */ +/* PROVIDE(InstructionPageFault = ExceptionHandler); */ +/* PROVIDE(LoadPageFault = ExceptionHandler); */ +/* PROVIDE(StorePageFault = ExceptionHandler); */ PROVIDE(Breakpoint = ExceptionHandler); -PROVIDE(LoadMisaligned = ExceptionHandler); -PROVIDE(LoadFault = ExceptionHandler); -PROVIDE(StoreMisaligned = ExceptionHandler); -PROVIDE(StoreFault = ExceptionHandler); -PROVIDE(UserEnvCall = ExceptionHandler); -PROVIDE(SupervisorEnvCall = ExceptionHandler); -PROVIDE(MachineEnvCall = ExceptionHandler); -PROVIDE(InstructionPageFault = ExceptionHandler); -PROVIDE(LoadPageFault = ExceptionHandler); -PROVIDE(StorePageFault = ExceptionHandler); +PROVIDE(MisalignedAccess = ExceptionHandler); diff --git a/esp-lp-hal/ld/interrupts-ulp.x b/esp-lp-hal/ld/interrupts-ulp.x index 2440be77227..b97fd85b036 100644 --- a/esp-lp-hal/ld/interrupts-ulp.x +++ b/esp-lp-hal/ld/interrupts-ulp.x @@ -7,19 +7,12 @@ /* It is possible to define a special handler for each interrupt type. By default, all interrupts are handled by DefaultHandler. However, users can override these alias by defining the symbol themselves */ -PROVIDE(SupervisorSoft = DefaultHandler); -PROVIDE(MachineSoft = DefaultHandler); -PROVIDE(SupervisorTimer = DefaultHandler); -PROVIDE(MachineTimer = DefaultHandler); -PROVIDE(SupervisorExternal = DefaultHandler); -PROVIDE(MachineExternal = DefaultHandler); +/* PROVIDE(SupervisorSoft = DefaultHandler); */ +/* PROVIDE(MachineSoft = DefaultHandler); */ +/* PROVIDE(SupervisorTimer = DefaultHandler); */ +/* PROVIDE(MachineTimer = DefaultHandler); */ +/* PROVIDE(SupervisorExternal = DefaultHandler); */ +/* PROVIDE(MachineExternal = DefaultHandler); */ -/* When vectored trap mode is enabled, each interrupt source must implement its own - trap entry point. By default, all interrupts start in _DefaultHandler_trap. - However, users can override these alias by defining the symbol themselves */ -PROVIDE(_start_SupervisorSoft_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineSoft_trap = _start_DefaultHandler_trap); -PROVIDE(_start_SupervisorTimer_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineTimer_trap = _start_DefaultHandler_trap); -PROVIDE(_start_SupervisorExternal_trap = _start_DefaultHandler_trap); -PROVIDE(_start_MachineExternal_trap = _start_DefaultHandler_trap); +PROVIDE(Timer = DefaultHandler); +PROVIDE(Peripheral = DefaultHandler); \ No newline at end of file diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 50c45f9f700..f6225b8c67f 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -37,7 +37,6 @@ PROVIDE(_setup_interrupts = _setup_interrupts); is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main instead of main directly allow HALs to inject code before jumping to user main. */ /* PROVIDE(hal_main = main); */ - /* riscv-rt will jump to the entrypoint of esp-lp-hal, which is rust_main */ /* rust_main will then jumps to main() */ /* which itself is wrapped using esp-lp-hal::entry, to add the ULP_MAGIC symbols lol. */ @@ -58,7 +57,7 @@ PROVIDE(DefaultHandler = abort); registers, calls the the DefaultHandler ISR, restores caller saved registers and returns. Note, however, that this provided implementation cannot be overwritten. We use PROVIDE to avoid compilation errors in direct mode, not to allow users to overwrite the symbol. */ -PROVIDE(_start_DefaultHandler_trap = _start_trap); +/* PROVIDE(_start_DefaultHandler_trap = _start_trap); */ /* # Pre-initialization function */ /* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 53fa2f67bcc..49d90b299fb 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -39,11 +39,74 @@ pub use esp32s2_ulp as pac; pub use esp32s3_ulp as pac; // This crate needs to bring in some other global assembly -extern crate riscv_rt; +// extern crate riscv_rt; -/// Re-exported interrupt APIs from riscv-rt / riscv crates. +// /// Re-exported interrupt APIs from riscv-rt / riscv crates. +// pub use riscv_rt::{TrapFrame}; pub use riscv_rt::{external_interrupt,core_interrupt,exception,TrapFrame}; -pub use riscv::interrupt::{Interrupt,Exception}; +use riscv_rt::{InterruptNumber,ExceptionNumber,CoreInterruptNumber}; + +/// Custom CoreInterrupts for ULP +#[riscv::pac_enum(unsafe CoreInterruptNumber)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +#[allow(dead_code)] +pub enum UlpCoreInterrupt { + /// Interrupt caused by ULP Timer + Timer = 0, + /// Interrupt caused by external peripheral (RTC_IO or SENS) + Peripheral = 1, +} + +unsafe impl InterruptNumber for UlpCoreInterrupt { + const MAX_INTERRUPT_NUMBER: usize = Self::Peripheral as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> riscv_rt::result::Result { + match value { + 0 => Ok(Self::Timer), + 1 => Ok(Self::Peripheral), + _ => Err(riscv_rt::result::Error::InvalidVariant(value)), + } + } +} + +unsafe impl CoreInterruptNumber for UlpCoreInterrupt {} + +/// CPU Exceptions for ULP +#[riscv::pac_enum(unsafe ExceptionNumber)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +#[allow(dead_code)] +pub enum UlpException { + /// EBREAK, ECALL, or Illegal Instruction + Breakpoint = 0, + /// Bus Error / Misaligned Load or Store + MisalignedAccess = 1, +} + +unsafe impl ExceptionNumber for UlpException { + const MAX_EXCEPTION_NUMBER: usize = Self::MisalignedAccess as usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> riscv_rt::result::Result { + match value { + 0 => Ok(Self::Breakpoint), + 1 => Ok(Self::MisalignedAccess), + _ => Err(riscv_rt::result::Error::InvalidVariant(value)), + } + } +} /// The prelude pub mod prelude { @@ -66,11 +129,17 @@ cfg_if::cfg_if! { // ULP interrupt bitflags from: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 cfg_if::cfg_if! { - if #[cfg(any(esp32s2,esp32s3))] { + if #[cfg(any(esp32s2,esp32s3))] + { + #[allow(unused)] const ULP_RISCV_TIMER_INT : u32 = 1 << 0; /* Internal Timer Interrupt */ + #[allow(unused)] const ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT : u32 = 1 << 1; /* EBREAK, ECALL or Illegal instruction */ + #[allow(unused)] const ULP_RISCV_BUS_ERROR_INT : u32 = 1 << 2; /* Bus Error (Unaligned Memory Access) */ + #[allow(unused)] const ULP_RISCV_PERIPHERAL_INTERRUPT : u32 = 1 << 31; /* RTC Peripheral Interrupt */ + #[allow(unused)] const ULP_RISCV_INTERNAL_INTERRUPT : u32 = ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT; } } @@ -131,65 +200,62 @@ loop: "# ); -#[cfg(any(esp32s2, esp32s3))] -// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. -global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); - -// Assembly containing the reset_vector and irq_vector instructions. -#[cfg(any(esp32s2, esp32s3))] -global_asm!(include_str!("./ulp_riscv_vectors.S")); - -/// riscv-rt redefinition which unmasks the interrupts. -/// Called during _start_rust, which is prior to rust_main, -/// which is prior to main(). -#[cfg(any(esp32s2, esp32s3))] -#[unsafe(export_name = "_setup_interrupts")] -pub fn setup_interrupts() { - // disable interrupt handling for now... - // ulp_enable_interrupts(); - unsafe { ulp_riscv_rescue_from_monitor() }; -} - -// /// Overriden riscv-rt function for _start_trap_rust, -// /// since ULP core has no xcause register. +/// ULP version of _riscv_trap_rust handler, adapted from riscv-rt. +/// This is necessary, because the ULP core does not have an xcause register. // #[cfg(any(esp32s2, esp32s3))] // #[unsafe(link_section = ".trap.rust")] -// #[unsafe(export_name = "_start_trap_rust")] -// pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { -// unsafe extern "C" { -// fn _dispatch_core_interrupt(code: usize); -// fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); -// } - -// // match xcause::read().cause() { -// // #[cfg(not(feature = "v-trap"))] -// // xcause::Trap::Interrupt(code) => _dispatch_core_interrupt(code), -// // xcause::Trap::Exception(code) => _dispatch_exception(&*trap_frame, code), -// // } -// } - -#[cfg(any(esp32s2, esp32s3))] #[unsafe(no_mangle)] -// #[unsafe(link_section = ".trap.rust")] -// #[unsafe(export_name = "_start_trap_rust")] -unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { - // TODO: Figure out how to call the riscv-rt interrupt handler, - // instead of/after this custom one! :) - +unsafe extern "C" fn _ulp_trap_rust(trap_frame: *const TrapFrame, irq_bitmask : u32) { // This interrupt handler is placeholder - it simply checks the interrupt flags, and clears // them. This function is based on the ESP-IDF implementation found here: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 - // ULP Internal Interrupts - if (q1 & ULP_RISCV_INTERNAL_INTERRUPT) > 0 { - // TODO + // // Provided by riscv-rt + // unsafe extern "C" { + // fn _dispatch_core_interrupt(code: usize); + // fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); + // } + + // ULP Internal Interrupts & Exceptions + if (irq_bitmask & ULP_RISCV_TIMER_INT) > 0 { + // // ULP_RISCV_TIMER_INT /* Internal Timer Interrupt */ + // unsafe { + // _dispatch_core_interrupt(UlpCoreInterrupt::Timer.number()); + // } + } + if (irq_bitmask & ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT) > 0 { + // // ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT /* EBREAK, ECALL or Illegal instruction */ + // unsafe { + // _dispatch_exception(&*trap_frame, UlpException::Breakpoint.number()) + // }; + } + if (irq_bitmask & ULP_RISCV_BUS_ERROR_INT) > 0 { + // // ULP_RISCV_BUS_ERROR_INT /* Bus Error (Unaligned Memory Access) */ + // unsafe { + // // TODO: Inspect instruction to determine if it was instruction, load, or store misalignment. + // // for now, lets assume it was just instruction misaligned. + // _dispatch_exception(&*trap_frame, UlpException::MisalignedAccess.number()) + // }; } - // External/Peripheral interrupts - if (q1 & ULP_RISCV_PERIPHERAL_INTERRUPT) > 0 { + // External/Peripheral interrupts - marked as MachineExternal. + // This core interrupt will need to delegate further to do other interrupt handling I think??? Unsure??? + if (irq_bitmask & ULP_RISCV_PERIPHERAL_INTERRUPT) > 0 { // RTC Peripheral interrupts let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); + // RTC IO interrupts + let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); + const ADDRESS: u32 = 0x1000; + let ptr = ADDRESS as *mut u32; + unsafe { ptr.write_volatile(0xABABABAB) }; + + // // External interrupt, handler will need to decode it. + // unsafe { + // _dispatch_core_interrupt(UlpCoreInterrupt::Peripheral.number()); + // } + + // Clear the interrupt flags, if they were raised at the start of this ISR call. if cocpu_int_st > 0 { // Clear the interrupt unsafe { &*pac::SENS::PTR } @@ -197,9 +263,6 @@ unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { .write(|w| unsafe { w.bits(cocpu_int_st) }); } - // RTC IO interrupts - let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); - if rtcio_int_st > 0 { // Clear the interrupt unsafe { &*pac::RTC_IO::PTR } @@ -209,6 +272,27 @@ unsafe extern "C" fn _ulp_riscv_interrupt_handler(q1: u32) { } } + +#[cfg(any(esp32s2, esp32s3))] +// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. +global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); + +// Assembly containing the reset_vector and irq_vector instructions. +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./ulp_riscv_vectors.S")); + +/// riscv-rt redefinition which unmasks the interrupts. +/// Called during _start_rust, which is prior to rust_main, +/// which is prior to main(). +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(export_name = "_setup_interrupts")] +#[unsafe(link_section = ".init.rust")] +pub fn setup_interrupts() { + // disable interrupt handling for now... + ulp_enable_interrupts(); + unsafe { ulp_riscv_rescue_from_monitor() }; +} + /// Enter a critical section (disable interrupts) #[cfg(any(esp32s2, esp32s3))] #[inline(always)] diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S index a9a81f02fdc..5eee8587124 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -7,7 +7,7 @@ * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c */ -.equ SAVE_REGS, 17 +.equ SAVE_REGS, 16 .equ CONTEXT_SIZE, (SAVE_REGS * 4) /* Macro which first allocates space on the stack to save general @@ -16,49 +16,52 @@ * can be overridden. * * Note: We don't save the callee-saved s0-s11 registers to save space + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. */ .macro save_general_regs cxt_size=CONTEXT_SIZE addi sp, sp, -\cxt_size sw ra, 0(sp) - sw tp, 4(sp) - sw t0, 8(sp) - sw t1, 12(sp) - sw t2, 16(sp) - sw a0, 20(sp) - sw a1, 24(sp) - sw a2, 28(sp) - sw a3, 32(sp) - sw a4, 36(sp) - sw a5, 40(sp) - sw a6, 44(sp) - sw a7, 48(sp) - sw t3, 52(sp) - sw t4, 56(sp) - sw t5, 60(sp) - sw t6, 64(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw t3, 16(sp) + sw t4, 20(sp) + sw t5, 24(sp) + sw t6, 28(sp) + sw a0, 32(sp) + sw a1, 36(sp) + sw a2, 40(sp) + sw a3, 44(sp) + sw a4, 48(sp) + sw a5, 52(sp) + sw a6, 56(sp) + sw a7, 60(sp) .endm /* Restore the general purpose registers (excluding gp) from the context on * the stack. The context is then deallocated. The default size is CONTEXT_SIZE - * but it can be overridden. */ + * but it can be overridden. + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ .macro restore_general_regs cxt_size=CONTEXT_SIZE lw ra, 0(sp) - lw tp, 4(sp) - lw t0, 8(sp) - lw t1, 12(sp) - lw t2, 16(sp) - lw a0, 20(sp) - lw a1, 24(sp) - lw a2, 28(sp) - lw a3, 32(sp) - lw a4, 36(sp) - lw a5, 40(sp) - lw a6, 44(sp) - lw a7, 48(sp) - lw t3, 52(sp) - lw t4, 56(sp) - lw t5, 60(sp) - lw t6, 64(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw t3, 16(sp) + lw t4, 20(sp) + lw t5, 24(sp) + lw t6, 28(sp) + lw a0, 32(sp) + lw a1, 36(sp) + lw a2, 40(sp) + lw a3, 44(sp) + lw a4, 48(sp) + lw a5, 52(sp) + lw a6, 56(sp) + lw a7, 60(sp) addi sp,sp, \cxt_size .endm @@ -66,7 +69,7 @@ .global reset_vector .global _start .global _start_trap -.global _ulp_riscv_interrupt_handler +.global _ulp_trap_rust /* The reset vector, jumps to startup code */ reset_vector: @@ -74,22 +77,38 @@ reset_vector: j _start /* Interrupt handler */ +/* Saves registers ra,t0-6,a0-7, then calls _ulp_trap_rust, which will call _start_trap_rust. */ .balign 0x10 _start_trap: /* Save the general gurpose register context before handling the interrupt */ save_general_regs - /* Fetch the interrupt status from the custom q1 register into a0 */ - getq_insn a0, q1 + /* Store the return address (x1) and stack pointer (x2) in the q registers. */ + setq_insn q2, ra + setq_insn q3, sp + + /* Store the pointer to the stored registers (TrapFrame) in a0. + * This will be the first argument to _ulp_trap_rust. + */ + addi a0, sp, 0 + + /* Fetch the interrupt status from the custom q1 register into a1. + * This will be the second argumet to _ulp_trap_rust. + */ + getq_insn a1, q1 /* Call the global interrupt handler. The interrupt status is passed as the argument in a0. * We do not re-enable interrupts before calling the handler as ULP RISC-V does not * support nested interrupts. */ - jal _ulp_riscv_interrupt_handler + jal ra,_ulp_trap_rust + + /* Restore the return address and stack pointer from the q registers */ + getq_insn ra, q2 + getq_insn sp, q3 /* Restore the register context after returning from the C interrupt handler */ restore_general_regs /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ - retirq_insn + retirq_insn \ No newline at end of file From 0503f7ff5caf7d8ce234cba95929b70d4637aa6a Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 22 Mar 2026 08:58:32 +1100 Subject: [PATCH 05/47] Really basic interrupt handling is working, adapted from picorv32-rt --- esp-lp-hal/Cargo.toml | 12 +- esp-lp-hal/build.rs | 10 +- esp-lp-hal/ld/exceptions-ulp.x | 25 -- esp-lp-hal/ld/interrupts-ulp.x | 18 -- esp-lp-hal/ld/link-ulp.x | 158 ++++------- esp-lp-hal/ld/memory-ulp.x | 19 -- esp-lp-hal/src/lib.rs | 437 ++++++++++++++++------------- esp-lp-hal/src/ulp_riscv_vectors.S | 57 +++- 8 files changed, 337 insertions(+), 399 deletions(-) delete mode 100644 esp-lp-hal/ld/exceptions-ulp.x delete mode 100644 esp-lp-hal/ld/interrupts-ulp.x delete mode 100644 esp-lp-hal/ld/memory-ulp.x diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 607d09513e6..771553f5bac 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -49,14 +49,6 @@ esp32c6-lp = { version = "0.3.0", features = ["critical-section"], option esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } -# riscv-rt TODOs for adapting esp-lp-hal -# 1. Check that the overriden _setup_interrupt and _start_trap functions work, -# with the interrupt/exception handling hooks provided by riscv-rt. -# 2. Add the custom picorv32 assembly instructions somewhere, exported as inlined rust functions -# 3. Add a critical section implementation -#riscv-rt = { version = "0.17.1", features = ["no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } -riscv-rt = { version = "0.19.0", features = [ "no-interrupts", "no-exceptions", "no-mhartid", "no-xie-xip", "no-xtvec", "single-hart"], path = "/home/leigh/Git/riscv/riscv-rt" } - [dev-dependencies] panic-halt = "0.2.0" @@ -77,9 +69,9 @@ debug = [ # Target the ESP32-C6. esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb"] # Target the ESP32-S2. -esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core"] +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core" ] # Target the ESP32-S3. -esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core"] +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core" ] #! ### Trait Implementation Feature Flags ## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs index 32900f060e4..f5da78d1901 100644 --- a/esp-lp-hal/build.rs +++ b/esp-lp-hal/build.rs @@ -16,21 +16,15 @@ fn main() -> Result<(), Box> { match chip { Chip::Esp32c6 => { fs::write(out_dir.join("link.x"), include_bytes!("ld/link-lp.x"))?; - }, + } Chip::Esp32s2 | Chip::Esp32s3 => { fs::write(out_dir.join("link.x"), include_bytes!("ld/link-ulp.x"))?; - fs::write(out_dir.join("memory.x"), include_bytes!("ld/memory-ulp.x"))?; - fs::write(out_dir.join("interrupts.x"), include_bytes!("ld/interrupts-ulp.x"))?; - fs::write(out_dir.join("exceptions.x"), include_bytes!("ld/exceptions-ulp.x"))?; - }, + } _ => unreachable!(), }; println!("cargo:rerun-if-changed=ld/link-lp.x"); println!("cargo:rerun-if-changed=ld/link-ulp.x"); - println!("cargo:rerun-if-changed=ld/memory-ulp.x"); - println!("cargo:rerun-if-changed=ld/interrupts-ulp.x"); - println!("cargo:rerun-if-changed=ld/exceptions-ulp.x"); // Done! Ok(()) diff --git a/esp-lp-hal/ld/exceptions-ulp.x b/esp-lp-hal/ld/exceptions-ulp.x deleted file mode 100644 index dd4ecf8945b..00000000000 --- a/esp-lp-hal/ld/exceptions-ulp.x +++ /dev/null @@ -1,25 +0,0 @@ -/* # EXCEPTION HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA - - If the `no-exceptions` feature is DISABLED, this file will be included in link.x.in. - If the `no-exceptions` feature is ENABLED, this file will be ignored. -*/ - -/* It is possible to define a special handler for each exception type. - By default, all exceptions are handled by ExceptionHandler. However, - users can override these alias by defining the symbol themselves */ -/* PROVIDE(InstructionMisaligned = ExceptionHandler); */ -/* PROVIDE(InstructionFault = ExceptionHandler); */ -/* PROVIDE(IllegalInstruction = ExceptionHandler); */ -/* PROVIDE(Breakpoint = ExceptionHandler); */ -/* PROVIDE(LoadMisaligned = ExceptionHandler); */ -/* PROVIDE(LoadFault = ExceptionHandler); */ -/* PROVIDE(StoreMisaligned = ExceptionHandler); */ -/* PROVIDE(StoreFault = ExceptionHandler); */ -/* PROVIDE(UserEnvCall = ExceptionHandler); */ -/* PROVIDE(SupervisorEnvCall = ExceptionHandler); */ -/* PROVIDE(MachineEnvCall = ExceptionHandler); */ -/* PROVIDE(InstructionPageFault = ExceptionHandler); */ -/* PROVIDE(LoadPageFault = ExceptionHandler); */ -/* PROVIDE(StorePageFault = ExceptionHandler); */ -PROVIDE(Breakpoint = ExceptionHandler); -PROVIDE(MisalignedAccess = ExceptionHandler); diff --git a/esp-lp-hal/ld/interrupts-ulp.x b/esp-lp-hal/ld/interrupts-ulp.x deleted file mode 100644 index b97fd85b036..00000000000 --- a/esp-lp-hal/ld/interrupts-ulp.x +++ /dev/null @@ -1,18 +0,0 @@ -/* # CORE INTERRUPT HANDLERS DESCRIBED IN THE STANDARD RISC-V ISA - - If the `no-interrupts` feature is DISABLED, this file will be included in link.x.in. - If the `no-interrupts` feature is ENABLED, this file will be ignored. -*/ - -/* It is possible to define a special handler for each interrupt type. - By default, all interrupts are handled by DefaultHandler. However, users can - override these alias by defining the symbol themselves */ -/* PROVIDE(SupervisorSoft = DefaultHandler); */ -/* PROVIDE(MachineSoft = DefaultHandler); */ -/* PROVIDE(SupervisorTimer = DefaultHandler); */ -/* PROVIDE(MachineTimer = DefaultHandler); */ -/* PROVIDE(SupervisorExternal = DefaultHandler); */ -/* PROVIDE(MachineExternal = DefaultHandler); */ - -PROVIDE(Timer = DefaultHandler); -PROVIDE(Peripheral = DefaultHandler); \ No newline at end of file diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index f6225b8c67f..b6df8fa6120 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -1,132 +1,74 @@ -/* NOTE: Adapted from riscv_rt/link.x */ -INCLUDE memory.x -INCLUDE interrupts.x -INCLUDE exceptions.x - -/* Default abort entry point. If no abort symbol is provided, then abort maps to _default_abort. */ -EXTERN(_default_abort); -PROVIDE(abort = _default_abort); - -/* Trap for exceptions triggered during initialization. If the execution reaches this point, it - means that there is a bug in the boot code. If no _pre_init_trap symbol is provided, then - _pre_init_trap defaults to _default_abort. Note that _pre_init_trap must be 4-byte aligned */ -PROVIDE(_pre_init_trap = _default_abort); - -/* Multi-processor hook function (for multi-core targets only). If no _mp_hook symbol - is provided, then _mp_hook maps to _default_mp_hook, which leaves HART 0 running while - the other HARTS stuck in a busy loop. Note that _default_mp_hook cannot be overwritten. - We use PROVIDE to avoid compilation errors in single hart targets, not to allow users - to overwrite the symbol. */ -PROVIDE(_default_mp_hook = abort); -PROVIDE(_mp_hook = _default_mp_hook); - -/* Default trap entry point. If not _start_trap symbol is provided, then _start_trap maps to - _default_start_trap, which saves caller saved registers, calls _start_trap_rust, restores - caller saved registers and then returns. Note that _start_trap must be 4-byte aligned */ -/* EXTERN(_default_start_trap);*/ -/* PROVIDE(_start_trap = _default_start_trap); */ - -/* Default interrupt setup entry point. If not _setup_interrupts symbol is provided, then - _setup_interrupts maps to _default_setup_interrupts, which in direct mode sets the value - of the xtvec register to _start_trap and, in vectored mode, sets its value to - _vector_table and enables vectored mode. */ -/* EXTERN(_default_setup_interrupts); */ -PROVIDE(_setup_interrupts = _setup_interrupts); - -/* Default main routine. If no hal_main symbol is provided, then hal_main maps to main, which - is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main - instead of main directly allow HALs to inject code before jumping to user main. */ -/* PROVIDE(hal_main = main); */ -/* riscv-rt will jump to the entrypoint of esp-lp-hal, which is rust_main */ -/* rust_main will then jumps to main() */ -/* which itself is wrapped using esp-lp-hal::entry, to add the ULP_MAGIC symbols lol. */ -/* SO the full boot procedure is... */ -/* reset_vector --> _start --> _start_rust + (_setup_interrupts) --> $hal_main --> main */ -PROVIDE(hal_main = rust_main); - -/* Default exception handler. By default, the exception handler is abort. - Users can override this alias by defining the symbol themselves */ -PROVIDE(ExceptionHandler = abort); - -/* Default interrupt handler. By default, the interrupt handler is abort. - Users can override this alias by defining the symbol themselves */ -PROVIDE(DefaultHandler = abort); - -/* Default interrupt trap entry point. When vectored trap mode is enabled, - the riscv-rt crate provides an implementation of this function, which saves caller saved - registers, calls the the DefaultHandler ISR, restores caller saved registers and returns. - Note, however, that this provided implementation cannot be overwritten. We use PROVIDE - to avoid compilation errors in direct mode, not to allow users to overwrite the symbol. */ -/* PROVIDE(_start_DefaultHandler_trap = _start_trap); */ - -/* # Pre-initialization function */ -/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, - then the function this points to will be called before the RAM is initialized. */ -PROVIDE(__pre_init = default_pre_init); +/* NOTE: Adapted from picorv32-rt/link.x */ + +ENTRY(_initjmp) + +CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; + +MEMORY +{ + RAM(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM +} + +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); + +/* # Abort function */ +/* Allow user to optionally override the default abort implementation. */ +PROVIDE(abort = default_abort); + +/* Default exception/interrupt handlers, may be overridden by user. + * TODO: Improve interrupt ergonomics. +*/; +PROVIDE(TimerInterrupt = default_timer_interrupt); +PROVIDE(IllegalInstructionException = default_exception_handler); +PROVIDE(BusErrorException = default_exception_handler); +PROVIDE(SensInterrupt = default_sens_interrupt); +PROVIDE(GpioInterrupt = default_gpio_interrupt); + SECTIONS { - PROVIDE(_stext = ORIGIN(RAM)); + . = ORIGIN(RAM); - .text ALIGN(_stext,4) : + .text : { - /* - * Default reset vector must link to offset 0x0, - * and interrupt handler must link to offset 0x10. + /* Put reset handler first in .text section so it ends up + * as the entry point of the program. */ - KEEP(*(.init.vectors)); + KEEP(*(.initjmp)); + . = ALIGN(0x10); + KEEP(*(.trap)); KEEP(*(.init)); KEEP(*(.init.rust)); KEEP(*(.trap.rust)); - *(.text.abort); - *(.text .text.*); + *(.text .text.*) + } >RAM - . = ALIGN(4); - __etext = .; - } > REGION_TEXT - - .rodata ALIGN(4) : + .rodata ALIGN(4): { - *(.rodata .rodata.*); - } > REGION_RODATA + *(.rodata .rodata.*) + } >RAM - .bss : + .data ALIGN(4): { - __sbss = .; - *(.bss .bss.*); - . = ALIGN(4); - __ebss = .; - } > REGION_BSS - - .data : AT(LOADADDR(.rodata) + SIZEOF(.rodata)) - { - __sidata = LOADADDR(.data); - __sdata = .; /* Must be called __global_pointer$ for linker relaxations to work. */ PROVIDE(__global_pointer$ = . + 0x800); - *(.data .data.*); - . = ALIGN(4); - __edata = .; - } > RAM + *(.data .data.*) + *(.sdata .sdata.*) + } >RAM - PROVIDE(_heap_size = 0); - - /* fictitious region that represents the memory available for the heap */ - .heap (NOLOAD) : + .bss ALIGN(4): { - __sheap = .; - . += _heap_size; - . = ALIGN(4); - __eheap = .; - } > RAM + *(.bss .bss.*) + *(.sbss .sbss.*) + } >RAM + /* fictitious region that represents the memory available for the stack */ - .stack (NOLOAD) : + .stack (NOLOAD): { - __estack = .; + . = ALIGN(4); . = _stack_start; - __sstack = .; - } > RAM + } >RAM /* fake output .got section */ /* Dynamic relocations are unsupported. This section is only used to detect diff --git a/esp-lp-hal/ld/memory-ulp.x b/esp-lp-hal/ld/memory-ulp.x deleted file mode 100644 index b19d59b980f..00000000000 --- a/esp-lp-hal/ld/memory-ulp.x +++ /dev/null @@ -1,19 +0,0 @@ -CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; - -MEMORY -{ - RAM(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM -} - -REGION_ALIAS("REGION_TEXT", RAM); -REGION_ALIAS("REGION_RODATA", RAM); -REGION_ALIAS("REGION_DATA", RAM); -REGION_ALIAS("REGION_BSS", RAM); -REGION_ALIAS("REGION_HEAP", RAM); -REGION_ALIAS("REGION_STACK", RAM); - -_stext = ORIGIN(REGION_TEXT) + 0x0; /* Load .text region at 0x0 */ -_heap_size = 0; /* Disable heap */ -_max_hart_id = 0; /* One harts present */ -_hart_stack_size = SIZEOF(.stack); -_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK); diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 49d90b299fb..5aa7e724a15 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,82 +38,11 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; -// This crate needs to bring in some other global assembly -// extern crate riscv_rt; - -// /// Re-exported interrupt APIs from riscv-rt / riscv crates. -// pub use riscv_rt::{TrapFrame}; -pub use riscv_rt::{external_interrupt,core_interrupt,exception,TrapFrame}; -use riscv_rt::{InterruptNumber,ExceptionNumber,CoreInterruptNumber}; - -/// Custom CoreInterrupts for ULP -#[riscv::pac_enum(unsafe CoreInterruptNumber)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -#[allow(dead_code)] -pub enum UlpCoreInterrupt { - /// Interrupt caused by ULP Timer - Timer = 0, - /// Interrupt caused by external peripheral (RTC_IO or SENS) - Peripheral = 1, -} - -unsafe impl InterruptNumber for UlpCoreInterrupt { - const MAX_INTERRUPT_NUMBER: usize = Self::Peripheral as usize; - - #[inline] - fn number(self) -> usize { - self as usize - } - - #[inline] - fn from_number(value: usize) -> riscv_rt::result::Result { - match value { - 0 => Ok(Self::Timer), - 1 => Ok(Self::Peripheral), - _ => Err(riscv_rt::result::Error::InvalidVariant(value)), - } - } -} - -unsafe impl CoreInterruptNumber for UlpCoreInterrupt {} - -/// CPU Exceptions for ULP -#[riscv::pac_enum(unsafe ExceptionNumber)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(usize)] -#[allow(dead_code)] -pub enum UlpException { - /// EBREAK, ECALL, or Illegal Instruction - Breakpoint = 0, - /// Bus Error / Misaligned Load or Store - MisalignedAccess = 1, -} - -unsafe impl ExceptionNumber for UlpException { - const MAX_EXCEPTION_NUMBER: usize = Self::MisalignedAccess as usize; - - #[inline] - fn number(self) -> usize { - self as usize - } - - #[inline] - fn from_number(value: usize) -> riscv_rt::result::Result { - match value { - 0 => Ok(Self::Breakpoint), - 1 => Ok(Self::MisalignedAccess), - _ => Err(riscv_rt::result::Error::InvalidVariant(value)), - } - } -} - /// The prelude pub mod prelude { pub use procmacros::entry; } - cfg_if::cfg_if! { if #[cfg(esp32c6)] { // LP_FAST_CLK is not very accurate, for now use a rough estimate @@ -200,108 +129,95 @@ loop: "# ); -/// ULP version of _riscv_trap_rust handler, adapted from riscv-rt. -/// This is necessary, because the ULP core does not have an xcause register. -// #[cfg(any(esp32s2, esp32s3))] -// #[unsafe(link_section = ".trap.rust")] -#[unsafe(no_mangle)] -unsafe extern "C" fn _ulp_trap_rust(trap_frame: *const TrapFrame, irq_bitmask : u32) { - // This interrupt handler is placeholder - it simply checks the interrupt flags, and clears - // them. This function is based on the ESP-IDF implementation found here: - // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 +// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); +// Assembly containing the reset, trap, and startup assembly procedures.. +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./ulp_riscv_vectors.S")); - // // Provided by riscv-rt - // unsafe extern "C" { - // fn _dispatch_core_interrupt(code: usize); - // fn _dispatch_exception(trap_frame: &TrapFrame, code: usize); - // } - - // ULP Internal Interrupts & Exceptions - if (irq_bitmask & ULP_RISCV_TIMER_INT) > 0 { - // // ULP_RISCV_TIMER_INT /* Internal Timer Interrupt */ - // unsafe { - // _dispatch_core_interrupt(UlpCoreInterrupt::Timer.number()); - // } - } - if (irq_bitmask & ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT) > 0 { - // // ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT /* EBREAK, ECALL or Illegal instruction */ - // unsafe { - // _dispatch_exception(&*trap_frame, UlpException::Breakpoint.number()) - // }; - } - if (irq_bitmask & ULP_RISCV_BUS_ERROR_INT) > 0 { - // // ULP_RISCV_BUS_ERROR_INT /* Bus Error (Unaligned Memory Access) */ - // unsafe { - // // TODO: Inspect instruction to determine if it was instruction, load, or store misalignment. - // // for now, lets assume it was just instruction misaligned. - // _dispatch_exception(&*trap_frame, UlpException::MisalignedAccess.number()) - // }; - } +/// Called prior to main(), to prepare ULP core +/// for execution, and enable interrupts. +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(link_section = ".init.rust")] +#[inline(always)] +pub fn lp_core_pre_init() { + // Exit 'monitor mode' + unsafe { ulp_riscv_rescue_from_monitor() }; + // Unmask the interrupts + ulp_interrupts_enable(); +} - // External/Peripheral interrupts - marked as MachineExternal. - // This core interrupt will need to delegate further to do other interrupt handling I think??? Unsure??? - if (irq_bitmask & ULP_RISCV_PERIPHERAL_INTERRUPT) > 0 { - // RTC Peripheral interrupts - let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); - // RTC IO interrupts - let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); - - const ADDRESS: u32 = 0x1000; - let ptr = ADDRESS as *mut u32; - unsafe { ptr.write_volatile(0xABABABAB) }; - - // // External interrupt, handler will need to decode it. - // unsafe { - // _dispatch_core_interrupt(UlpCoreInterrupt::Peripheral.number()); - // } - - // Clear the interrupt flags, if they were raised at the start of this ISR call. - if cocpu_int_st > 0 { - // Clear the interrupt - unsafe { &*pac::SENS::PTR } - .sar_cocpu_int_clr() - .write(|w| unsafe { w.bits(cocpu_int_st) }); +/// Entry point to the ULP program +#[unsafe(link_section = ".init.rust")] +#[unsafe(export_name = "_start_rust")] +unsafe extern "C" fn lp_core_startup() -> ! { + unsafe { + unsafe extern "Rust" { + // This symbol will be provided by the user via `#[entry]` + fn main(); } - if rtcio_int_st > 0 { - // Clear the interrupt - unsafe { &*pac::RTC_IO::PTR } - .status_w1tc() - .write(|w| unsafe { w.bits(rtcio_int_st) }); + #[cfg(any(esp32s2, esp32s3))] + lp_core_pre_init(); + + #[cfg(esp32c6)] + if (*pac::LP_CLKRST::PTR) + .lp_clk_conf() + .read() + .fast_clk_sel() + .bit_is_set() + { + CPU_CLOCK = XTAL_D2_CLK_HZ; } + + main(); + ulp_riscv_halt(); } } - -#[cfg(any(esp32s2, esp32s3))] -// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. -global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); - -// Assembly containing the reset_vector and irq_vector instructions. #[cfg(any(esp32s2, esp32s3))] -global_asm!(include_str!("./ulp_riscv_vectors.S")); +#[unsafe(link_section = ".init.rust")] +#[unsafe(no_mangle)] +unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { + // Rescue RISC-V core from monitor state. + unsafe { &*pac::RTC_CNTL::PTR } + .cocpu_ctrl() + .modify(|_, w| w.cocpu_done().clear_bit().cocpu_shut_reset_en().clear_bit()); +} -/// riscv-rt redefinition which unmasks the interrupts. -/// Called during _start_rust, which is prior to rust_main, -/// which is prior to main(). -#[cfg(any(esp32s2, esp32s3))] -#[unsafe(export_name = "_setup_interrupts")] +/// Stops the ULP core, called from itself. #[unsafe(link_section = ".init.rust")] -pub fn setup_interrupts() { - // disable interrupt handling for now... - ulp_enable_interrupts(); - unsafe { ulp_riscv_rescue_from_monitor() }; +fn ulp_riscv_halt() -> ! { + #[cfg(any(esp32s2, esp32s3))] + { + unsafe { &*pac::RTC_CNTL::PTR } + .cocpu_ctrl() + .modify(|_, w| unsafe { + w.cocpu_shut_2_clk_dis().bits(0x3F); + w.cocpu_done().set_bit(); + w.cocpu_shut_reset_en().set_bit() + }); + } + + // All chips will enter a no-op loop, when halting. + loop { + unsafe { + core::arch::asm!("addi x0, x0, 0"); // no-op + } + } } -/// Enter a critical section (disable interrupts) +/// TODO: Move custom ULP instructions into their own module, +/// and cleanup the assembly files at the same time. +/// Disable all interrupts #[cfg(any(esp32s2, esp32s3))] #[inline(always)] -pub fn ulp_disable_interrupts() { +pub fn ulp_interrupts_disable() { // Enter a critical section by disabling all interrupts // This inline assembly construct uses the t0 register and is equivalent to: // > li t0, 0x80000007 // > maskirq_insn(zero, t0) // Mask all interrupt bits - // // The mask 0x80000007 represents: // Bit 31 - RTC peripheral interrupt // Bit 2 - Bus error @@ -313,10 +229,10 @@ pub fn ulp_disable_interrupts() { } } -/// Exit a critical section (re-enable interrupts) +/// Enable all interrupts #[cfg(any(esp32s2, esp32s3))] #[inline(always)] -pub fn ulp_enable_interrupts() { +pub fn ulp_interrupts_enable() { // Exit a critical section by enabling all interrupts // This inline assembly construct is equivalent to: // > maskirq_insn(zero, zero) @@ -325,9 +241,8 @@ pub fn ulp_enable_interrupts() { } } -/// Wait for any (even unmasked) interrupt +/// Wait for any (masked or unmasked) interrupt #[cfg(any(esp32s2, esp32s3))] -#[inline(always)] pub fn ulp_waitirq() -> u32 { // Wait for pending interrupt, return pending interrupt mask // waitirq a0 @@ -338,58 +253,186 @@ pub fn ulp_waitirq() -> u32 { result } -/// Entry point to the ULP program -#[unsafe(link_section = ".init.rust")] -#[unsafe(export_name = "rust_main")] -unsafe extern "C" fn lp_core_startup() -> ! { +/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, +// which is currently hand-written in ulp_riscv_vectors.S +/// Registers saved in trap handler +#[cfg(any(esp32s2, esp32s3))] +#[repr(C)] +#[derive(Debug)] +pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. + pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. + pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. + pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. + pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. + pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. + pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. + pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. + pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. + pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. + pub a5: usize, + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. + pub a6: usize, + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. + pub a7: usize, +} + +/// Trap entry point rust (_start_trap_rust) +/// `irqs` is a bitmask of IRQs to handle. +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(link_section = ".trap.rust")] +#[unsafe(export_name = "_start_trap_rust")] +pub extern "C" fn ulp_start_trap_rust(trap_frame: *const TrapFrame, irqs: u32) { + unsafe extern "C" { + fn trap_handler(regs: &TrapFrame, pending_irqs: u32); + } + unsafe { - unsafe extern "Rust" { - fn main(); - } + // dispatch trap to handler + trap_handler(&*trap_frame, irqs); + } +} - #[cfg(esp32c6)] - if (*pac::LP_CLKRST::PTR) - .lp_clk_conf() - .read() - .fast_clk_sel() - .bit_is_set() - { - CPU_CLOCK = XTAL_D2_CLK_HZ; +/// Creates the trap_handler() function. +/// This is macro is used later in this file. +/// This style of macro is usually provided +/// for users to hook their own handlers, but here it's just used +/// as a quick way to generate the bit-mask code :) +#[cfg(any(esp32s2, esp32s3))] +macro_rules! ulp_interrupts { + (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { + if $pending_irqs & (1 << $n) != 0 { + #[allow(unused_unsafe)] + unsafe { $handler($regs); } + } + }; + ( $( $irq:literal : $handler:ident ),* ) => { + /// Called by _start_trap_rust, this trap handler will call other interrupt handling + /// functions depending on the bits set in pending_irqs. + #[unsafe(no_mangle)] + pub extern "C" fn trap_handler(regs: *const TrapFrame, pending_irqs: u32) { + let regs = unsafe { regs.as_ref().unwrap() }; + $( + ulp_interrupts!(@interrupt($irq, pending_irqs, regs, $handler)); + )* } + }; +} - main(); - ulp_riscv_halt(); - } +/// Default timer interrupt handler. +/// Users may override TimerInterrupt to change this behaviour. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_timer_interrupt(_regs: &TrapFrame) { + // Does nothing. } +/// Default illegal instruction or bus error exception handler. +/// Users may override IllegalInstructionException, and/or BusErrorException, to change this +/// behaviour. #[cfg(any(esp32s2, esp32s3))] -#[unsafe(link_section = ".init.rust")] +#[allow(dead_code)] #[unsafe(no_mangle)] -unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { - // Rescue RISC-V core from monitor state. - unsafe { &*pac::RTC_CNTL::PTR } - .cocpu_ctrl() - .modify(|_, w| w.cocpu_done().clear_bit().cocpu_shut_reset_en().clear_bit()); +pub fn default_exception_handler(_regs: &TrapFrame) { + panic!("Unhandled exception!"); } -/// Stops the ULP core, called from itself. -#[unsafe(link_section = ".init.rust")] -fn ulp_riscv_halt() -> ! { - #[cfg(any(esp32s2, esp32s3))] - { - unsafe { &*pac::RTC_CNTL::PTR } - .cocpu_ctrl() - .modify(|_, w| unsafe { - w.cocpu_shut_2_clk_dis().bits(0x3F); - w.cocpu_done().set_bit(); - w.cocpu_shut_reset_en().set_bit() - }); +/// Default interrupt handler for RTC Peripheral interrupts (SENS). +/// Users may override SensInterrupt to change this behaviour. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_sens_interrupt(_sar_cocpu_int_st: u32) { + // does nothing +} + +/// Default interrupt handler for RTC IO interrupts (GPIO). +/// Users may override GpioInterrupt to change this behaviour. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_gpio_interrupt(_pin_status: u32) { + // pin status contains the GPIO irq bits. + // it has already been right-shifted by 10, + // so Bit 0 of pin_status == GPIO0 :) +} + +unsafe extern "Rust" { + fn TimerInterrupt(regs: &TrapFrame); + fn IllegalInstructionException(regs: &TrapFrame); + fn BusErrorException(regs: &TrapFrame); +} + +/// Peripheral interrupt handler for the IRQ bit 31. +/// Checks the peripheral interrupt flags, and calls further interrupt handlers as appropriate. +/// Clears interrupt flags automatically. +#[cfg(any(esp32s2, esp32s3))] +#[unsafe(no_mangle)] +fn _dispatch_peripheral_interrupt(_regs: &TrapFrame) { + // This function is based on the ESP-IDF implementation found here: + // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 + + unsafe extern "Rust" { + fn SensInterrupt(sar_cocpu_int_st: u32); + fn GpioInterrupt(pin_status: u32); } - // All chips will enter a no-op loop, when halting. - loop { + // RTC Peripheral interrupts + let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); + // RTC IO interrupts + let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); + + // Call handler, and clear the interrupt flags, if they were raised at the start of this ISR + // call. + + if cocpu_int_st > 0 { unsafe { - core::arch::asm!("addi x0, x0, 0"); // no-op + SensInterrupt(cocpu_int_st); + } + + // Clear the interrupt + unsafe { &*pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(cocpu_int_st) }); + } + + if rtcio_int_st > 0 { + // Right-shift by 10, to remove the offset, and make it so GPIO0 == Bit 0. + unsafe { + GpioInterrupt(rtcio_int_st >> 10); } + + // Clear the interrupt + unsafe { &*pac::RTC_IO::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(rtcio_int_st) }); } } + +// Define the trap_handler function, +// which calls interrupt symbol names, +// which may be overriden by the user at link-time. +ulp_interrupts!( + 0: TimerInterrupt, + 1: IllegalInstructionException, + 2: BusErrorException, + 31: _dispatch_peripheral_interrupt +); diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S index 5eee8587124..e0ecca32979 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -65,20 +65,38 @@ addi sp,sp, \cxt_size .endm -.section .init.vectors -.global reset_vector -.global _start -.global _start_trap -.global _ulp_trap_rust +.section .initjmp, "ax" +.global _initjmp +/* Power-on reset vector, placed at address 0x0 */ +_initjmp: + /* no more than 16 bytes here */ + jal zero, _start -/* The reset vector, jumps to startup code */ -reset_vector: - /* no more than 16 bytes here ! */ - j _start +/* Initialisation routine */ +.section .init, "ax" +.global _start +_start: + .cfi_startproc + .cfi_undefined ra + /* Set the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Set the stack pointer */ + la sp, _stack_start + add s0, sp, zero + /* Begin the rust program */ + jal zero, _start_rust + .cfi_endproc -/* Interrupt handler */ -/* Saves registers ra,t0-6,a0-7, then calls _ulp_trap_rust, which will call _start_trap_rust. */ -.balign 0x10 +/* Hardware trap vector, placed at address 0x10 + * Handles all interrupts and exceptions for the ULP core. + * Saves registers ra,t0-6,a0-7, then calls _start_trap_rust. + */ +.section .trap, "ax" +.global _start_trap _start_trap: /* Save the general gurpose register context before handling the interrupt */ save_general_regs @@ -101,7 +119,7 @@ _start_trap: * We do not re-enable interrupts before calling the handler as ULP RISC-V does not * support nested interrupts. */ - jal ra,_ulp_trap_rust + jal ra,_start_trap_rust /* Restore the return address and stack pointer from the q registers */ getq_insn ra, q2 @@ -111,4 +129,15 @@ _start_trap: restore_general_regs /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ - retirq_insn \ No newline at end of file + retirq_insn + +/* Default abort function, + * places the chip into a loop. + */ +.section .init +.global default_abort +default_abort: + /* Option 1, Infinite loop */ + j abort + /* Option 2, reset to start */ + /* jal zero,_start */ \ No newline at end of file From ef032505ebeea6045e15b20d1c2d72d36ade2118 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Mon, 23 Mar 2026 08:10:13 +1100 Subject: [PATCH 06/47] Undo some non-functional formatting changes to reduce churn --- esp-hal-procmacros/src/lp_core.rs | 2 +- esp-lp-hal/Cargo.toml | 4 +- esp-lp-hal/build.rs | 20 ++-- esp-lp-hal/ld/link-ulp.x | 88 ++++++---------- esp-lp-hal/src/lib.rs | 160 +++++++++++++---------------- esp-lp-hal/src/ulp_riscv_vectors.S | 31 ++---- 6 files changed, 124 insertions(+), 181 deletions(-) diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs index 18241c6f7e6..b26ac6f6c01 100644 --- a/esp-hal-procmacros/src/lp_core.rs +++ b/esp-hal-procmacros/src/lp_core.rs @@ -190,7 +190,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { quote!( #[allow(non_snake_case)] #[unsafe(export_name = "main")] - pub fn __esp_lp_hal__main() { + pub fn __risc_v_rt__main() { #[unsafe(export_name = #magic_symbol_name)] static ULP_MAGIC: [u32; 0] = [0u32; 0]; diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 771553f5bac..18129cbe297 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -69,9 +69,9 @@ debug = [ # Target the ESP32-C6. esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb"] # Target the ESP32-S2. -esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core" ] +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core"] # Target the ESP32-S3. -esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core" ] +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core"] #! ### Trait Implementation Feature Flags ## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and diff --git a/esp-lp-hal/build.rs b/esp-lp-hal/build.rs index f5da78d1901..f07dca41629 100644 --- a/esp-lp-hal/build.rs +++ b/esp-lp-hal/build.rs @@ -9,21 +9,17 @@ fn main() -> Result<(), Box> { // Define all necessary configuration symbols for the configured device: chip.define_cfgs(); - // Copy the required linker scripts to the `out` directory: - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - println!("cargo:rustc-link-search={}", out_dir.display()); - - match chip { - Chip::Esp32c6 => { - fs::write(out_dir.join("link.x"), include_bytes!("ld/link-lp.x"))?; - } - Chip::Esp32s2 | Chip::Esp32s3 => { - fs::write(out_dir.join("link.x"), include_bytes!("ld/link-ulp.x"))?; - } + // Copy the required linker script to the `out` directory: + let source_file = match chip { + Chip::Esp32c6 => "ld/link-lp.x", + Chip::Esp32s2 | Chip::Esp32s3 => "ld/link-ulp.x", _ => unreachable!(), }; - println!("cargo:rerun-if-changed=ld/link-lp.x"); + // Put the linker script somewhere the linker can find it: + let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + fs::copy(source_file, out.join("link.x"))?; println!("cargo:rerun-if-changed=ld/link-ulp.x"); // Done! diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index b6df8fa6120..43b57a488ba 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -1,23 +1,23 @@ -/* NOTE: Adapted from picorv32-rt/link.x */ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ -ENTRY(_initjmp) +ENTRY(reset_vector) CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; MEMORY { - RAM(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM + ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM } -PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); - -/* # Abort function */ -/* Allow user to optionally override the default abort implementation. */ -PROVIDE(abort = default_abort); - -/* Default exception/interrupt handlers, may be overridden by user. - * TODO: Improve interrupt ergonomics. -*/; +/* Default exception/interrupt handlers, + * which may be overridden by the user. + * TODO: Supply proc-macro attributes to make it easier for users + * to override these symbols. + */ PROVIDE(TimerInterrupt = default_timer_interrupt); PROVIDE(IllegalInstructionException = default_exception_handler); PROVIDE(BusErrorException = default_exception_handler); @@ -27,69 +27,43 @@ PROVIDE(GpioInterrupt = default_gpio_interrupt); SECTIONS { - . = ORIGIN(RAM); + . = ORIGIN(ram); .text : { - /* Put reset handler first in .text section so it ends up - * as the entry point of the program. - */ - KEEP(*(.initjmp)); + /* Power-on-reset must be placed at address 0x0 */ + KEEP(*(.reset)); + /* ULP will jump to 0x10 when an interrupt trap occurs */ . = ALIGN(0x10); KEEP(*(.trap)); KEEP(*(.init)); KEEP(*(.init.rust)); KEEP(*(.trap.rust)); *(.text .text.*) - } >RAM + } >ram .rodata ALIGN(4): { - *(.rodata .rodata.*) - } >RAM + *(.rodata) + *(.rodata*) + } >ram .data ALIGN(4): { - /* Must be called __global_pointer$ for linker relaxations to work. */ PROVIDE(__global_pointer$ = . + 0x800); - *(.data .data.*) - *(.sdata .sdata.*) - } >RAM + *(.data) + *(.data*) + *(.sdata) + *(.sdata*) + } >ram .bss ALIGN(4): { - *(.bss .bss.*) - *(.sbss .sbss.*) - } >RAM - - - /* fictitious region that represents the memory available for the stack */ - .stack (NOLOAD): - { - . = ALIGN(4); - . = _stack_start; - } >RAM + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + } >ram - /* fake output .got section */ - /* Dynamic relocations are unsupported. This section is only used to detect - relocatable code in the input files and raise an error if relocatable code - is found */ - .got (INFO) : - { - KEEP(*(.got .got.*)); - } - - /* Discard .eh_frame, we are not doing unwind on panic so it is not needed */ - /DISCARD/ : - { - *(.eh_frame); - } + __stack_top = ORIGIN(ram) + LENGTH(ram); } - -/* Do not exceed this mark in the error messages below | */ -ASSERT(SIZEOF(.got) == 0, " -.got section detected in the input files. Dynamic relocations are not -supported. If you are linking to C code compiled using the `gcc` crate -then modify your build script to compile the C code _without_ the --fPIC flag. See the documentation of the `gcc::Config.fpic` method for -details."); diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 5aa7e724a15..1d0566157d4 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -55,24 +55,6 @@ cfg_if::cfg_if! { } } -// ULP interrupt bitflags from: -// https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 -cfg_if::cfg_if! { - if #[cfg(any(esp32s2,esp32s3))] - { - #[allow(unused)] - const ULP_RISCV_TIMER_INT : u32 = 1 << 0; /* Internal Timer Interrupt */ - #[allow(unused)] - const ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT : u32 = 1 << 1; /* EBREAK, ECALL or Illegal instruction */ - #[allow(unused)] - const ULP_RISCV_BUS_ERROR_INT : u32 = 1 << 2; /* Bus Error (Unaligned Memory Access) */ - #[allow(unused)] - const ULP_RISCV_PERIPHERAL_INTERRUPT : u32 = 1 << 31; /* RTC Peripheral Interrupt */ - #[allow(unused)] - const ULP_RISCV_INTERNAL_INTERRUPT : u32 = ULP_RISCV_TIMER_INT | ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT | ULP_RISCV_BUS_ERROR_INT; - } -} - pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; /// Wake up the HP core @@ -136,21 +118,9 @@ global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); #[cfg(any(esp32s2, esp32s3))] global_asm!(include_str!("./ulp_riscv_vectors.S")); -/// Called prior to main(), to prepare ULP core -/// for execution, and enable interrupts. -#[cfg(any(esp32s2, esp32s3))] -#[unsafe(link_section = ".init.rust")] -#[inline(always)] -pub fn lp_core_pre_init() { - // Exit 'monitor mode' - unsafe { ulp_riscv_rescue_from_monitor() }; - // Unmask the interrupts - ulp_interrupts_enable(); -} - /// Entry point to the ULP program #[unsafe(link_section = ".init.rust")] -#[unsafe(export_name = "_start_rust")] +#[unsafe(export_name = "rust_main")] unsafe extern "C" fn lp_core_startup() -> ! { unsafe { unsafe extern "Rust" { @@ -159,7 +129,10 @@ unsafe extern "C" fn lp_core_startup() -> ! { } #[cfg(any(esp32s2, esp32s3))] - lp_core_pre_init(); + { + ulp_riscv_rescue_from_monitor(); + ulp_interrupts_enable(); + } #[cfg(esp32c6)] if (*pac::LP_CLKRST::PTR) @@ -336,74 +309,33 @@ macro_rules! ulp_interrupts { }; } -/// Default timer interrupt handler. -/// Users may override TimerInterrupt to change this behaviour. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_timer_interrupt(_regs: &TrapFrame) { - // Does nothing. -} - -/// Default illegal instruction or bus error exception handler. -/// Users may override IllegalInstructionException, and/or BusErrorException, to change this -/// behaviour. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_exception_handler(_regs: &TrapFrame) { - panic!("Unhandled exception!"); -} - -/// Default interrupt handler for RTC Peripheral interrupts (SENS). -/// Users may override SensInterrupt to change this behaviour. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_sens_interrupt(_sar_cocpu_int_st: u32) { - // does nothing -} - -/// Default interrupt handler for RTC IO interrupts (GPIO). -/// Users may override GpioInterrupt to change this behaviour. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_gpio_interrupt(_pin_status: u32) { - // pin status contains the GPIO irq bits. - // it has already been right-shifted by 10, - // so Bit 0 of pin_status == GPIO0 :) -} - -unsafe extern "Rust" { - fn TimerInterrupt(regs: &TrapFrame); - fn IllegalInstructionException(regs: &TrapFrame); - fn BusErrorException(regs: &TrapFrame); -} - /// Peripheral interrupt handler for the IRQ bit 31. /// Checks the peripheral interrupt flags, and calls further interrupt handlers as appropriate. /// Clears interrupt flags automatically. #[cfg(any(esp32s2, esp32s3))] #[unsafe(no_mangle)] -fn _dispatch_peripheral_interrupt(_regs: &TrapFrame) { +fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { // This function is based on the ESP-IDF implementation found here: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 unsafe extern "Rust" { - fn SensInterrupt(sar_cocpu_int_st: u32); + // fn SensInterrupt(sar_cocpu_int_st: u32); + fn SensInterrupt(sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R); fn GpioInterrupt(pin_status: u32); } // RTC Peripheral interrupts - let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); + // let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); + let cocpu_int_st = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read(); + let cocpu_int_st_bits = cocpu_int_st.bits(); + // RTC IO interrupts let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); // Call handler, and clear the interrupt flags, if they were raised at the start of this ISR // call. - if cocpu_int_st > 0 { + if cocpu_int_st_bits > 0 { unsafe { SensInterrupt(cocpu_int_st); } @@ -411,11 +343,10 @@ fn _dispatch_peripheral_interrupt(_regs: &TrapFrame) { // Clear the interrupt unsafe { &*pac::SENS::PTR } .sar_cocpu_int_clr() - .write(|w| unsafe { w.bits(cocpu_int_st) }); + .write(|w| unsafe { w.bits(cocpu_int_st_bits) }); } if rtcio_int_st > 0 { - // Right-shift by 10, to remove the offset, and make it so GPIO0 == Bit 0. unsafe { GpioInterrupt(rtcio_int_st >> 10); } @@ -427,12 +358,67 @@ fn _dispatch_peripheral_interrupt(_regs: &TrapFrame) { } } -// Define the trap_handler function, -// which calls interrupt symbol names, -// which may be overriden by the user at link-time. +// ULP interrupt bitflags from: +// https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 +// const ULP_RISCV_TIMER_INT : u32 = 1 << 0; /* Internal Timer Interrupt +// */ const ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT : u32 = 1 << 1; /* EBREAK, ECALL or +// Illegal instruction */ const ULP_RISCV_BUS_ERROR_INT : u32 = 1 << 2; /* Bus +// Error (Unaligned Memory Access) */ const ULP_RISCV_PERIPHERAL_INTERRUPT : u32 = 1 << +// 31; /* RTC Peripheral Interrupt */ +unsafe extern "Rust" { + fn TimerInterrupt(regs: &TrapFrame); + fn IllegalInstructionException(regs: &TrapFrame); + fn BusErrorException(regs: &TrapFrame); +} +// Create the trap_handler function ulp_interrupts!( 0: TimerInterrupt, 1: IllegalInstructionException, 2: BusErrorException, - 31: _dispatch_peripheral_interrupt + 31: dispatch_peripheral_interrupt ); + +/// Default timer interrupt handler. +/// Users may override TimerInterrupt to change this behaviour. +/// This handler is dispatched by the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_timer_interrupt(_regs: &TrapFrame) { + // Does nothing. +} + +/// Default illegal instruction or bus error exception handler. +/// Users may override IllegalInstructionException, and/or BusErrorException, +/// to change this behaviour. +/// This handler is dispatched by the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_exception_handler(_regs: &TrapFrame) { + panic!("Unhandled exception!"); +} + +/// Default interrupt handler for RTC Peripheral interrupts (SENS). +/// Users may override SensInterrupt to change this behaviour. +/// This handler is dispatched by the dispatch_peripheral_interrupt() function, +/// which is itself called from the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_sens_interrupt(_sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R) { + // pub fn default_sens_interrupt(_sar_cocpu_int_st: u32) { + // does nothing +} + +/// Default interrupt handler for RTC IO interrupts (GPIO). +/// Users may override GpioInterrupt to change this behaviour. +/// This handler is dispatched by the dispatch_peripheral_interrupt() function, +/// which is itself called from the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[unsafe(no_mangle)] +pub fn default_gpio_interrupt(_pin_status: u32) { + // pin status contains the GPIO irq bits, + // where bit 0 == GPIO0 +} diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_vectors.S index e0ecca32979..fc7c9ea07cb 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_vectors.S @@ -65,10 +65,10 @@ addi sp,sp, \cxt_size .endm -.section .initjmp, "ax" -.global _initjmp +.section .reset, "ax" +.global reset_vector /* Power-on reset vector, placed at address 0x0 */ -_initjmp: +reset_vector: /* no more than 16 bytes here */ jal zero, _start @@ -76,20 +76,17 @@ _initjmp: .section .init, "ax" .global _start _start: - .cfi_startproc - .cfi_undefined ra - /* Set the global pointer & thread pointer */ + /* Setup the global pointer & thread pointer */ .option push .option norelax la gp, __global_pointer$ addi tp, gp, 0 .option pop - /* Set the stack pointer */ - la sp, _stack_start + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top add s0, sp, zero /* Begin the rust program */ - jal zero, _start_rust - .cfi_endproc + jal zero, rust_main /* Hardware trap vector, placed at address 0x10 * Handles all interrupts and exceptions for the ULP core. @@ -106,12 +103,12 @@ _start_trap: setq_insn q3, sp /* Store the pointer to the stored registers (TrapFrame) in a0. - * This will be the first argument to _ulp_trap_rust. + * This will be the first argument to _start_trap_rust. */ addi a0, sp, 0 /* Fetch the interrupt status from the custom q1 register into a1. - * This will be the second argumet to _ulp_trap_rust. + * This will be the second argumet to _start_trap_rust. */ getq_insn a1, q1 @@ -131,13 +128,3 @@ _start_trap: /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ retirq_insn -/* Default abort function, - * places the chip into a loop. - */ -.section .init -.global default_abort -default_abort: - /* Option 1, Infinite loop */ - j abort - /* Option 2, reset to start */ - /* jal zero,_start */ \ No newline at end of file From 8f9e92f00dbe90e97af7151e57460a531452598a Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Mon, 23 Mar 2026 21:19:41 +1100 Subject: [PATCH 07/47] Added some gross interrupt binding macros, re-used the SENS interrupts from esp-pacs --- esp-lp-hal/Cargo.toml | 8 +- esp-lp-hal/examples/interrupt_counter.rs | 54 +++++++ esp-lp-hal/ld/link-ulp.x | 39 ++++- esp-lp-hal/src/gpio.rs | 6 +- esp-lp-hal/src/lib.rs | 190 +++++++++++++++++------ 5 files changed, 241 insertions(+), 56 deletions(-) create mode 100644 esp-lp-hal/examples/interrupt_counter.rs diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 18129cbe297..cfd1e5b23fb 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -46,8 +46,8 @@ procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = riscv = { version = "0.15", features = ["critical-section-single-hart"] } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section","rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section","rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "0e83f46" } [dev-dependencies] panic-halt = "0.2.0" @@ -84,6 +84,10 @@ embedded-io = ["dep:embedded-io-06", "dep:embedded-io-07"] name = "blinky" required-features = [] +[[example]] +name = "interrupt_counter" +required-features = ["esp32s3"] + [[example]] name = "i2c" required-features = ["esp32c6"] diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs new file mode 100644 index 00000000000..241077d7432 --- /dev/null +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -0,0 +1,54 @@ +//! ULP interrupt-based counter example. +//! Increments a 32 bit counter value at a known point in memory, +//! whenever the ULP program is run. If GPIO0 is pressed, reset the counter. + +#![no_std] +#![no_main] +extern crate panic_halt; +// TODO: I'd prefer if these were proc-macros, which could be used as function attributes. +// This would 1) look nicer, and 2) provide better discoverability/type-hinting of the +// interrupts available for use. +use esp_lp_hal::sens_interrupt; +use esp_lp_hal::{gpio::Input, gpio_interrupt, prelude::*}; + +// Shared memory address. +const ADDRESS: u32 = 0x1000; + +pub fn on_start() { + // Did we get a startup interrupt? If so, increment counter + let ptr = ADDRESS as *mut u32; + let i = unsafe { ptr.read_volatile() }; + unsafe { + ptr.write_volatile(i + 1); + } +} + +sens_interrupt!(RISCV_START_INT, on_start); + +pub fn on_button() { + // Reset the counter + let ptr = ADDRESS as *mut u32; + unsafe { + ptr.write_volatile(0); + } +} + +gpio_interrupt!(GPIO0, on_button); + +#[entry] +fn main(mut _stomp_pin: Input<0>) { + // Enable start-up interrupt, called shortly after boot. + unsafe { &*esp_lp_hal::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_start_int_ena().set_bit()); + // Enable PIN0 interrupt, falling edge trigger. + // 0: GPIO interrupt disabled + // 1: rising edge trigger + // 2: falling edge trigger + // 3: any edge trigger + // 4: low level trigger + // 5: high level trigger. + unsafe { &*esp_lp_hal::pac::RTC_IO::PTR } + .pin0() + .write(|w| unsafe { w.int_type().bits(2) }); +} diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 43b57a488ba..6d6e66f2c16 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -18,12 +18,47 @@ MEMORY * TODO: Supply proc-macro attributes to make it easier for users * to override these symbols. */ -PROVIDE(TimerInterrupt = default_timer_interrupt); PROVIDE(IllegalInstructionException = default_exception_handler); PROVIDE(BusErrorException = default_exception_handler); -PROVIDE(SensInterrupt = default_sens_interrupt); PROVIDE(GpioInterrupt = default_gpio_interrupt); +/* External interrupt handlers, all do nothing by default */ +PROVIDE(TOUCH_DONE_INT = noop_interrupt_handler); +PROVIDE(TOUCH_INACTIVE_INT = noop_interrupt_handler); +PROVIDE(TOUCH_ACTIVE_INT = noop_interrupt_handler); +PROVIDE(SARADC1_DONE_INT = noop_interrupt_handler); +PROVIDE(SARADC2_DONE_INT = noop_interrupt_handler); +PROVIDE(TSENS_DONE_INT = noop_interrupt_handler); +PROVIDE(RISCV_START_INT = noop_interrupt_handler); +PROVIDE(SW_INT = noop_interrupt_handler); +PROVIDE(SWD_INT = noop_interrupt_handler); +PROVIDE(TOUCH_TIME_OUT_INT = noop_interrupt_handler); +PROVIDE(TOUCH_APPROACH_LOOP_DONE_INT = noop_interrupt_handler); +PROVIDE(TOUCH_SCAN_DONE_INT = noop_interrupt_handler); + +/* Default GPIO interrupt handlers */ +PROVIDE(GPIO0 = noop_interrupt_handler); +PROVIDE(GPIO1 = noop_interrupt_handler); +PROVIDE(GPIO2 = noop_interrupt_handler); +PROVIDE(GPIO3 = noop_interrupt_handler); +PROVIDE(GPIO4 = noop_interrupt_handler); +PROVIDE(GPIO5 = noop_interrupt_handler); +PROVIDE(GPIO6 = noop_interrupt_handler); +PROVIDE(GPIO7 = noop_interrupt_handler); +PROVIDE(GPIO8 = noop_interrupt_handler); +PROVIDE(GPIO9 = noop_interrupt_handler); +PROVIDE(GPIO10 = noop_interrupt_handler); +PROVIDE(GPIO11 = noop_interrupt_handler); +PROVIDE(GPIO12 = noop_interrupt_handler); +PROVIDE(GPIO13 = noop_interrupt_handler); +PROVIDE(GPIO14 = noop_interrupt_handler); +PROVIDE(GPIO15 = noop_interrupt_handler); +PROVIDE(GPIO16 = noop_interrupt_handler); +PROVIDE(GPIO17 = noop_interrupt_handler); +PROVIDE(GPIO18 = noop_interrupt_handler); +PROVIDE(GPIO19 = noop_interrupt_handler); +PROVIDE(GPIO20 = noop_interrupt_handler); +PROVIDE(GPIO21 = noop_interrupt_handler); SECTIONS { diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index 2b64136c795..f1582e75ab0 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -26,10 +26,12 @@ cfg_if::cfg_if! { if #[cfg(esp32c6)] { type LpIo = crate::pac::LP_IO; - const MAX_GPIO_PIN: u8 = 7; + /// Maximum GPIO pin number. + pub const MAX_GPIO_PIN: u8 = 7; } else { type LpIo = crate::pac::RTC_IO; - const MAX_GPIO_PIN: u8 = 21; + /// Maximum GPIO pin number. + pub const MAX_GPIO_PIN: u8 = 21; } } diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 1d0566157d4..298f9d42c31 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -37,6 +37,7 @@ pub use esp32c6_lp as pac; pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; +pub use pac::interrupt as sens_interrupt; /// The prelude pub mod prelude { @@ -229,6 +230,7 @@ pub fn ulp_waitirq() -> u32 { /// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, // which is currently hand-written in ulp_riscv_vectors.S /// Registers saved in trap handler +#[doc(hidden)] #[cfg(any(esp32s2, esp32s3))] #[repr(C)] #[derive(Debug)] @@ -270,6 +272,7 @@ pub struct TrapFrame { /// Trap entry point rust (_start_trap_rust) /// `irqs` is a bitmask of IRQs to handle. #[cfg(any(esp32s2, esp32s3))] +#[doc(hidden)] #[unsafe(link_section = ".trap.rust")] #[unsafe(export_name = "_start_trap_rust")] pub extern "C" fn ulp_start_trap_rust(trap_frame: *const TrapFrame, irqs: u32) { @@ -299,6 +302,7 @@ macro_rules! ulp_interrupts { ( $( $irq:literal : $handler:ident ),* ) => { /// Called by _start_trap_rust, this trap handler will call other interrupt handling /// functions depending on the bits set in pending_irqs. + #[doc(hidden)] #[unsafe(no_mangle)] pub extern "C" fn trap_handler(regs: *const TrapFrame, pending_irqs: u32) { let regs = unsafe { regs.as_ref().unwrap() }; @@ -313,17 +317,12 @@ macro_rules! ulp_interrupts { /// Checks the peripheral interrupt flags, and calls further interrupt handlers as appropriate. /// Clears interrupt flags automatically. #[cfg(any(esp32s2, esp32s3))] +#[doc(hidden)] #[unsafe(no_mangle)] fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { // This function is based on the ESP-IDF implementation found here: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 - unsafe extern "Rust" { - // fn SensInterrupt(sar_cocpu_int_st: u32); - fn SensInterrupt(sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R); - fn GpioInterrupt(pin_status: u32); - } - // RTC Peripheral interrupts // let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); let cocpu_int_st = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read(); @@ -332,13 +331,11 @@ fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { // RTC IO interrupts let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); - // Call handler, and clear the interrupt flags, if they were raised at the start of this ISR - // call. + // Call handler, and clear the interrupt flags, if they + // were raised at the start of this ISR call. if cocpu_int_st_bits > 0 { - unsafe { - SensInterrupt(cocpu_int_st); - } + dispatch_sens_interrupt(cocpu_int_st); // Clear the interrupt unsafe { &*pac::SENS::PTR } @@ -347,9 +344,7 @@ fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { } if rtcio_int_st > 0 { - unsafe { - GpioInterrupt(rtcio_int_st >> 10); - } + dispatch_rtcio_interrupt(rtcio_int_st >> 10); // Clear the interrupt unsafe { &*pac::RTC_IO::PTR } @@ -358,67 +353,162 @@ fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { } } +/// Dispatcher for RTC Peripheral interrupts (SENS). +/// This function is called from dispatch_peripheral_interrupt() function, +/// which is itself called from the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[doc(hidden)] +#[unsafe(no_mangle)] +pub fn dispatch_sens_interrupt(sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R) { + // by default, this will attempt to service the __EXTERNAL_INTERRUPTS vectors. + use pac::__EXTERNAL_INTERRUPTS; + let bitflags = sar_cocpu_int_st.bits(); + // iterate over bits set and call the handler vectors + for i in 0..__EXTERNAL_INTERRUPTS.len() { + // Check bit set + if (bitflags & (1 << i)) != 0 { + unsafe { (__EXTERNAL_INTERRUPTS[i]._handler)() }; + } + } +} + +/// Dispatcher for RTC IO (GPIO) interrupts. +/// This function is called from dispatch_peripheral_interrupt() function, +/// which is itself called from the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[doc(hidden)] +#[unsafe(no_mangle)] +pub fn dispatch_rtcio_interrupt(pin_status: u32) { + // iterate over bits set and call the handler vectors + for i in 0..crate::rtcio_interrupt::__RTC_IO_INTERRUPTS.len() { + // Check bit set + if (pin_status & (1 << i)) != 0 { + unsafe { (crate::rtcio_interrupt::__RTC_IO_INTERRUPTS[i]._handler)() }; + } + } +} + +/// Default interrupt handler, does nothing. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[doc(hidden)] +#[unsafe(no_mangle)] +pub fn noop_interrupt_handler() {} + // ULP interrupt bitflags from: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 -// const ULP_RISCV_TIMER_INT : u32 = 1 << 0; /* Internal Timer Interrupt -// */ const ULP_RISCV_EBREAK_ECALL_ILLEGAL_INSN_INT : u32 = 1 << 1; /* EBREAK, ECALL or -// Illegal instruction */ const ULP_RISCV_BUS_ERROR_INT : u32 = 1 << 2; /* Bus -// Error (Unaligned Memory Access) */ const ULP_RISCV_PERIPHERAL_INTERRUPT : u32 = 1 << -// 31; /* RTC Peripheral Interrupt */ unsafe extern "Rust" { - fn TimerInterrupt(regs: &TrapFrame); fn IllegalInstructionException(regs: &TrapFrame); fn BusErrorException(regs: &TrapFrame); } // Create the trap_handler function ulp_interrupts!( - 0: TimerInterrupt, 1: IllegalInstructionException, 2: BusErrorException, 31: dispatch_peripheral_interrupt ); -/// Default timer interrupt handler. -/// Users may override TimerInterrupt to change this behaviour. -/// This handler is dispatched by the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_timer_interrupt(_regs: &TrapFrame) { - // Does nothing. -} - /// Default illegal instruction or bus error exception handler. /// Users may override IllegalInstructionException, and/or BusErrorException, /// to change this behaviour. /// This handler is dispatched by the trap_handler() function. #[cfg(any(esp32s2, esp32s3))] #[allow(dead_code)] +#[doc(hidden)] #[unsafe(no_mangle)] pub fn default_exception_handler(_regs: &TrapFrame) { panic!("Unhandled exception!"); } -/// Default interrupt handler for RTC Peripheral interrupts (SENS). -/// Users may override SensInterrupt to change this behaviour. -/// This handler is dispatched by the dispatch_peripheral_interrupt() function, -/// which is itself called from the trap_handler() function. +/// TODO: Clean this up and merge into GPIO probably. +/// Hacky GPIO interrupt vector table, similar to how it's done in ulp-pacs. #[cfg(any(esp32s2, esp32s3))] #[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_sens_interrupt(_sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R) { - // pub fn default_sens_interrupt(_sar_cocpu_int_st: u32) { - // does nothing -} +mod rtcio_interrupt { + unsafe extern "C" { + fn GPIO0(); + fn GPIO1(); + fn GPIO2(); + fn GPIO3(); + fn GPIO4(); + fn GPIO5(); + fn GPIO6(); + fn GPIO7(); + fn GPIO8(); + fn GPIO9(); + fn GPIO10(); + fn GPIO11(); + fn GPIO12(); + fn GPIO13(); + fn GPIO14(); + fn GPIO15(); + fn GPIO16(); + fn GPIO17(); + fn GPIO18(); + fn GPIO19(); + fn GPIO20(); + fn GPIO21(); + } -/// Default interrupt handler for RTC IO interrupts (GPIO). -/// Users may override GpioInterrupt to change this behaviour. -/// This handler is dispatched by the dispatch_peripheral_interrupt() function, -/// which is itself called from the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[unsafe(no_mangle)] -pub fn default_gpio_interrupt(_pin_status: u32) { - // pin status contains the GPIO irq bits, - // where bit 0 == GPIO0 + use crate::{gpio::MAX_GPIO_PIN, pac::Vector}; + + #[doc(hidden)] + #[unsafe(link_section = ".rwtext")] + #[unsafe(no_mangle)] + pub static __RTC_IO_INTERRUPTS: [Vector; (MAX_GPIO_PIN + 1) as usize] = [ + Vector { _handler: GPIO0 }, + Vector { _handler: GPIO1 }, + Vector { _handler: GPIO2 }, + Vector { _handler: GPIO3 }, + Vector { _handler: GPIO4 }, + Vector { _handler: GPIO5 }, + Vector { _handler: GPIO6 }, + Vector { _handler: GPIO7 }, + Vector { _handler: GPIO8 }, + Vector { _handler: GPIO9 }, + Vector { _handler: GPIO10 }, + Vector { _handler: GPIO11 }, + Vector { _handler: GPIO12 }, + Vector { _handler: GPIO13 }, + Vector { _handler: GPIO14 }, + Vector { _handler: GPIO15 }, + Vector { _handler: GPIO16 }, + Vector { _handler: GPIO17 }, + Vector { _handler: GPIO18 }, + Vector { _handler: GPIO19 }, + Vector { _handler: GPIO20 }, + Vector { _handler: GPIO21 }, + ]; + + #[macro_export] + #[doc = r" Assigns a handler to an interrupt"] + #[doc = r""] + #[doc = r" This macro takes two arguments: the name of an interrupt and the path to the"] + #[doc = r" function that will be used as the handler of that interrupt. That function"] + #[doc = r" must have signature `fn()`."] + #[doc = r""] + #[doc = r" # Example"] + #[doc = r""] + #[doc = r" ``` ignore"] + #[doc = r" interrupt!(GPIO0, button);"] + #[doc = r""] + #[doc = r" fn button() {"] + #[doc = r#" print!("Pressed");"#] + #[doc = r" }"] + #[doc = r""] + macro_rules! gpio_interrupt { + ($ NAME : ident , $ path : path) => { + #[allow(non_snake_case)] + #[unsafe(no_mangle)] + pub extern "C" fn $NAME() { + let f: fn() = $path; + f(); + } + }; + } + + #[allow(unused)] + pub(crate) use gpio_interrupt; } From 6b4f2b5e99df8132ac3e4940bc8ea85ae27a57ba Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Mon, 23 Mar 2026 22:21:33 +1100 Subject: [PATCH 08/47] Hacky .listen() and .unlisten() methods on GPIO pins --- esp-lp-hal/examples/interrupt_counter.rs | 22 +++---- esp-lp-hal/src/gpio.rs | 81 ++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 241077d7432..d2298c125ce 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -8,8 +8,12 @@ extern crate panic_halt; // TODO: I'd prefer if these were proc-macros, which could be used as function attributes. // This would 1) look nicer, and 2) provide better discoverability/type-hinting of the // interrupts available for use. -use esp_lp_hal::sens_interrupt; -use esp_lp_hal::{gpio::Input, gpio_interrupt, prelude::*}; +use esp_lp_hal::{ + gpio::{Event, Input}, + gpio_interrupt, + prelude::*, + sens_interrupt, +}; // Shared memory address. const ADDRESS: u32 = 0x1000; @@ -36,19 +40,11 @@ pub fn on_button() { gpio_interrupt!(GPIO0, on_button); #[entry] -fn main(mut _stomp_pin: Input<0>) { +fn main(mut boot_pin: Input<0>) { // Enable start-up interrupt, called shortly after boot. unsafe { &*esp_lp_hal::pac::SENS::PTR } .sar_cocpu_int_ena() .write(|w| w.sar_cocpu_start_int_ena().set_bit()); - // Enable PIN0 interrupt, falling edge trigger. - // 0: GPIO interrupt disabled - // 1: rising edge trigger - // 2: falling edge trigger - // 3: any edge trigger - // 4: low level trigger - // 5: high level trigger. - unsafe { &*esp_lp_hal::pac::RTC_IO::PTR } - .pin0() - .write(|w| unsafe { w.int_type().bits(2) }); + + boot_pin.listen(Event::FallingEdge); } diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index f1582e75ab0..5e84ebaa23d 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -89,6 +89,51 @@ impl From for bool { } } +/// Event used to trigger interrupts. +#[cfg(any(esp32s2, esp32s3))] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum Event { + /// Interrupts trigger on rising pin edge. + RisingEdge = 1, + /// Interrupts trigger on falling pin edge. + FallingEdge = 2, + /// Interrupts trigger on either rising or falling pin edges. + AnyEdge = 3, + /// Interrupts trigger on low level + LowLevel = 4, + /// Interrupts trigger on high level + HighLevel = 5, +} + +/// Set GPIO event listening. +/// +/// - `N`: the pin to configure +/// - `int_ena`: boolean, enable or disable +/// - `int_type`: interrupt type, see [Event] +/// - `_wake_up`: whether to wake up from light sleep +#[cfg(any(esp32s2, esp32s3))] +fn set_int_enable(int_ena: bool, int_type: Event, _wake_up: bool) { + // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); + let gpio_base = 0xa400 + 0x28 + ((N as usize) * 4); + let gpio_pin = gpio_base as *mut u32; + + // Bits 7:9 - if set to 0: GPIO interrupt disable, + // if set to 1: rising edge trigger, + // if set to 2: falling edge trigger, + // if set to 3: any edge trigger, + // if set to 4: low level trigger, + // if set to 5: high level trigger + let mut pin_setting = unsafe { gpio_pin.read_volatile() }; + let int_type_mask: u32 = 0xFFFFFFFF ^ (0x111 << 7); + pin_setting &= int_type_mask; + if int_ena { + pin_setting |= (int_type as u32) << 7; + } + + // Bit 10, wakeup enable (not implemented) + unsafe { gpio_pin.write_volatile(pin_setting) } +} + /// Flexible pin driver. /// Provides a common implementation for input and output pins. pub struct Flex {} @@ -149,6 +194,18 @@ impl Flex { let level = self.output_level(); self.set_level(!level); } + + /// Listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn listen(&mut self, event: Event) { + set_int_enable::(true, event, false); + } + + /// Un-listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn unlisten(&mut self) { + set_int_enable::(false, Event::AnyEdge, false); + } } /// Digital input. @@ -168,6 +225,18 @@ impl Input { pub fn level(&self) -> Level { self.pin.level() } + + /// Listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn listen(&mut self, event: Event) { + self.pin.listen(event); + } + + /// Un-listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } } /// Digital output. @@ -226,6 +295,18 @@ impl OutputOpenDrain { pub fn toggle(&mut self) { self.pin.toggle() } + + /// Listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn listen(&mut self, event: Event) { + self.pin.listen(event); + } + + /// Un-listen for interrupts. + #[cfg(any(esp32s2, esp32s3))] + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } } // Used by the `entry` procmacro: From ccfc8d8924713f663752385b89abe812a39b4638 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 24 Mar 2026 07:54:15 +1100 Subject: [PATCH 09/47] Add GPIO wakeup method to LP gpio pins. --- esp-lp-hal/examples/interrupt_counter.rs | 2 + esp-lp-hal/src/gpio.rs | 113 ++++++++++++++++++----- 2 files changed, 93 insertions(+), 22 deletions(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index d2298c125ce..56aeb0a996f 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -2,6 +2,8 @@ //! Increments a 32 bit counter value at a known point in memory, //! whenever the ULP program is run. If GPIO0 is pressed, reset the counter. +//% CHIPS: esp32s3 + #![no_std] #![no_main] extern crate panic_halt; diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index 5e84ebaa23d..e29cb126b4f 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -105,35 +105,74 @@ pub enum Event { HighLevel = 5, } +#[cfg(any(esp32s2, esp32s3))] +impl From for Event { + fn from(value: WakeEvent) -> Self { + match value { + WakeEvent::LowLevel => Event::LowLevel, + WakeEvent::HighLevel => Event::HighLevel, + } + } +} + +/// Event used to wake up from light sleep. +#[cfg(any(esp32s2, esp32s3))] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum WakeEvent { + /// Wake on low level + LowLevel = 4, + /// Wake on high level + HighLevel = 5, +} + /// Set GPIO event listening. /// /// - `N`: the pin to configure -/// - `int_ena`: boolean, enable or disable -/// - `int_type`: interrupt type, see [Event] -/// - `_wake_up`: whether to wake up from light sleep +/// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will +/// leave the int_type setting as-is. +/// - `wake_up`: whether to wake up from light sleep. #[cfg(any(esp32s2, esp32s3))] -fn set_int_enable(int_ena: bool, int_type: Event, _wake_up: bool) { +fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) { // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); + // TODO: Add LpIo::regs().pins(number : usize) to esp-pacs let gpio_base = 0xa400 + 0x28 + ((N as usize) * 4); let gpio_pin = gpio_base as *mut u32; - // Bits 7:9 - if set to 0: GPIO interrupt disable, - // if set to 1: rising edge trigger, - // if set to 2: falling edge trigger, - // if set to 3: any edge trigger, - // if set to 4: low level trigger, - // if set to 5: high level trigger + // Read the current setting let mut pin_setting = unsafe { gpio_pin.read_volatile() }; - let int_type_mask: u32 = 0xFFFFFFFF ^ (0x111 << 7); - pin_setting &= int_type_mask; - if int_ena { + + // Write int_type if specified + if let Some(int_type) = int_type { + // Bits 7:9 + // - 0: GPIO interrupt disable + // - 1: rising edge trigger + // - 2: falling edge trigger + // - 3: any edge trigger + // - 4: low level trigger + // - 5: high level trigger + let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); + pin_setting &= int_type_mask; pin_setting |= (int_type as u32) << 7; } - // Bit 10, wakeup enable (not implemented) + // Bit 10, wakeup enable. + // Used with UlpCoreWakeupSource::Gpio. + let wakeup_en_mask: u32 = 0xFFFFFFFF ^ (0b1 << 10); + pin_setting &= wakeup_en_mask; + if wakeup_enable { + pin_setting |= 0b1 << 10; + } + unsafe { gpio_pin.write_volatile(pin_setting) } } +#[cfg(any(esp32s2, esp32s3))] +fn clear_pin_interrupt() { + unsafe { &*LpIo::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(1 << N) }); +} + /// Flexible pin driver. /// Provides a common implementation for input and output pins. pub struct Flex {} @@ -198,13 +237,35 @@ impl Flex { /// Listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn listen(&mut self, event: Event) { - set_int_enable::(true, event, false); + self.clear_interrupts(); + enable_pin_interrupt::(Some(event as u8), false); } /// Un-listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn unlisten(&mut self) { - set_int_enable::(false, Event::AnyEdge, false); + enable_pin_interrupt::(Some(0), false); + self.clear_interrupts(); + } + + /// Clear pin interrupts + #[cfg(any(esp32s2, esp32s3))] + pub fn clear_interrupts(&mut self) { + clear_pin_interrupt::(); + } + + /// Enable pin as a wake-up source. + /// This will change the interrupt type, when enabling. + /// Interrupt type will not be changed, when disabling. + #[cfg(any(esp32s2, esp32s3))] + pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { + self.clear_interrupts(); + if enable { + let int_evt = Event::from(event); + enable_pin_interrupt::(Some(int_evt as u8), true); + } else { + enable_pin_interrupt::(None, true); + } } } @@ -220,23 +281,27 @@ impl Input { let pin = Flex::::new(); Self { pin } } - /// Get the current pin input level. pub fn level(&self) -> Level { self.pin.level() } - /// Listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn listen(&mut self, event: Event) { self.pin.listen(event); } - /// Un-listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn unlisten(&mut self) { self.pin.unlisten(); } + /// Enable pin as a wake-up source. + /// This will change the interrupt type, when enabling. + /// Interrupt type will not be changed, when disabling. + #[cfg(any(esp32s2, esp32s3))] + pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { + self.pin.wakeup_enable(enable, event); + } } /// Digital output. @@ -251,7 +316,6 @@ impl Output { let pin = Flex::::new(); Self { pin } } - /// Set the output level. pub fn set_level(&mut self, level: Level) { self.pin.set_level(level); @@ -295,18 +359,23 @@ impl OutputOpenDrain { pub fn toggle(&mut self) { self.pin.toggle() } - /// Listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn listen(&mut self, event: Event) { self.pin.listen(event); } - /// Un-listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn unlisten(&mut self) { self.pin.unlisten(); } + /// Enable pin as a wake-up source. + /// This will change the interrupt type, when enabling. + /// Interrupt type will not be changed, when disabling. + #[cfg(any(esp32s2, esp32s3))] + pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { + self.pin.wakeup_enable(enable, event); + } } // Used by the `entry` procmacro: From 8bd4cb6f6242b0e16367ce40544618a97efa1bde Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 24 Mar 2026 08:58:58 +1100 Subject: [PATCH 10/47] Hide interrupt macros on non-supported chips --- esp-lp-hal/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 298f9d42c31..d9458053f27 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -37,6 +37,7 @@ pub use esp32c6_lp as pac; pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; +#[cfg(any(esp32s2, esp32s3))] pub use pac::interrupt as sens_interrupt; /// The prelude @@ -399,11 +400,14 @@ pub fn noop_interrupt_handler() {} // ULP interrupt bitflags from: // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 +#[cfg(any(esp32s2, esp32s3))] unsafe extern "Rust" { fn IllegalInstructionException(regs: &TrapFrame); fn BusErrorException(regs: &TrapFrame); } + // Create the trap_handler function +#[cfg(any(esp32s2, esp32s3))] ulp_interrupts!( 1: IllegalInstructionException, 2: BusErrorException, From 8d0749ea0a47a607d140f8ac2761b10c651446a4 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 28 Mar 2026 18:20:49 +1100 Subject: [PATCH 11/47] Exploring a cleaner interrupt binding approach --- esp-lp-hal/Cargo.toml | 4 +- esp-lp-hal/examples/interrupt_counter.rs | 33 +- esp-lp-hal/ld/link-ulp.x | 49 +-- esp-lp-hal/src/interrupts.rs | 325 +++++++++++++++ esp-lp-hal/src/lib.rs | 391 +----------------- esp-lp-hal/src/lp_start.S | 32 ++ esp-lp-hal/src/ulp_riscv_interrupt_ops.S | 101 ----- ...{ulp_riscv_vectors.S => ulp_riscv_start.S} | 104 ++++- 8 files changed, 493 insertions(+), 546 deletions(-) create mode 100644 esp-lp-hal/src/interrupts.rs create mode 100644 esp-lp-hal/src/lp_start.S delete mode 100644 esp-lp-hal/src/ulp_riscv_interrupt_ops.S rename esp-lp-hal/src/{ulp_riscv_vectors.S => ulp_riscv_start.S} (53%) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index aad528af1d8..4963a4c9f86 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -46,8 +46,8 @@ procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = riscv = { version = "0.15", features = ["critical-section-single-hart"] } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section","rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section","rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 56aeb0a996f..d17b6af058d 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -12,34 +12,37 @@ extern crate panic_halt; // interrupts available for use. use esp_lp_hal::{ gpio::{Event, Input}, - gpio_interrupt, + interrupts::{GpioInterruptPin, SensInterruptStatus, gpio_interrupt, sens_interrupt}, prelude::*, - sens_interrupt, }; // Shared memory address. const ADDRESS: u32 = 0x1000; -pub fn on_start() { - // Did we get a startup interrupt? If so, increment counter - let ptr = ADDRESS as *mut u32; - let i = unsafe { ptr.read_volatile() }; - unsafe { - ptr.write_volatile(i + 1); +pub fn on_start(status: SensInterruptStatus) { + if status == SensInterruptStatus::RISCV_START_INT { + // Did we get a startup interrupt? If so, increment counter + let ptr = ADDRESS as *mut u32; + let i = unsafe { ptr.read_volatile() }; + unsafe { + ptr.write_volatile(i + 1); + } } } -sens_interrupt!(RISCV_START_INT, on_start); +sens_interrupt!(on_start); -pub fn on_button() { - // Reset the counter - let ptr = ADDRESS as *mut u32; - unsafe { - ptr.write_volatile(0); +pub fn on_button(pin: GpioInterruptPin) { + // Reset the counter on GPIO0 + if pin == 0 { + let ptr = ADDRESS as *mut u32; + unsafe { + ptr.write_volatile(0); + } } } -gpio_interrupt!(GPIO0, on_button); +gpio_interrupt!(on_button); #[entry] fn main(mut boot_pin: Input<0>) { diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 6d6e66f2c16..2ef26cd9a3a 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -13,52 +13,9 @@ MEMORY ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM } -/* Default exception/interrupt handlers, - * which may be overridden by the user. - * TODO: Supply proc-macro attributes to make it easier for users - * to override these symbols. - */ -PROVIDE(IllegalInstructionException = default_exception_handler); -PROVIDE(BusErrorException = default_exception_handler); -PROVIDE(GpioInterrupt = default_gpio_interrupt); - -/* External interrupt handlers, all do nothing by default */ -PROVIDE(TOUCH_DONE_INT = noop_interrupt_handler); -PROVIDE(TOUCH_INACTIVE_INT = noop_interrupt_handler); -PROVIDE(TOUCH_ACTIVE_INT = noop_interrupt_handler); -PROVIDE(SARADC1_DONE_INT = noop_interrupt_handler); -PROVIDE(SARADC2_DONE_INT = noop_interrupt_handler); -PROVIDE(TSENS_DONE_INT = noop_interrupt_handler); -PROVIDE(RISCV_START_INT = noop_interrupt_handler); -PROVIDE(SW_INT = noop_interrupt_handler); -PROVIDE(SWD_INT = noop_interrupt_handler); -PROVIDE(TOUCH_TIME_OUT_INT = noop_interrupt_handler); -PROVIDE(TOUCH_APPROACH_LOOP_DONE_INT = noop_interrupt_handler); -PROVIDE(TOUCH_SCAN_DONE_INT = noop_interrupt_handler); - -/* Default GPIO interrupt handlers */ -PROVIDE(GPIO0 = noop_interrupt_handler); -PROVIDE(GPIO1 = noop_interrupt_handler); -PROVIDE(GPIO2 = noop_interrupt_handler); -PROVIDE(GPIO3 = noop_interrupt_handler); -PROVIDE(GPIO4 = noop_interrupt_handler); -PROVIDE(GPIO5 = noop_interrupt_handler); -PROVIDE(GPIO6 = noop_interrupt_handler); -PROVIDE(GPIO7 = noop_interrupt_handler); -PROVIDE(GPIO8 = noop_interrupt_handler); -PROVIDE(GPIO9 = noop_interrupt_handler); -PROVIDE(GPIO10 = noop_interrupt_handler); -PROVIDE(GPIO11 = noop_interrupt_handler); -PROVIDE(GPIO12 = noop_interrupt_handler); -PROVIDE(GPIO13 = noop_interrupt_handler); -PROVIDE(GPIO14 = noop_interrupt_handler); -PROVIDE(GPIO15 = noop_interrupt_handler); -PROVIDE(GPIO16 = noop_interrupt_handler); -PROVIDE(GPIO17 = noop_interrupt_handler); -PROVIDE(GPIO18 = noop_interrupt_handler); -PROVIDE(GPIO19 = noop_interrupt_handler); -PROVIDE(GPIO20 = noop_interrupt_handler); -PROVIDE(GPIO21 = noop_interrupt_handler); +/* Default interrupt handlers */ +PROVIDE(SensInterrupt = DefaultHandler); +PROVIDE(GpioInterrupt = DefaultHandler); SECTIONS { diff --git a/esp-lp-hal/src/interrupts.rs b/esp-lp-hal/src/interrupts.rs new file mode 100644 index 00000000000..8dd6a2421d6 --- /dev/null +++ b/esp-lp-hal/src/interrupts.rs @@ -0,0 +1,325 @@ +//! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. +//! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. +use crate::pac; + +// #[cfg(any(esp32s2, esp32s3))] +// pub use pac::interrupt as sens_interrupt; +// use crate::gpio::MAX_GPIO_PIN; + +/// Argument passed to GpioInterrupt handler. +pub type GpioInterruptPin = u32; +/// Argument passed to SensInterrupt handler. +pub use pac::Interrupt as SensInterruptStatus; + +const STATUS_WORDS: usize = 1; + +/// Representation of peripheral-interrupt status bits. +#[doc(hidden)] +#[derive(Clone, Copy, Default, Debug)] +pub struct InterruptStatus { + status: [u32; STATUS_WORDS], +} + +impl InterruptStatus { + /// Is the given interrupt bit set + pub fn is_set(&self, interrupt: u8) -> bool { + (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 + } + + /// Set the given interrupt status bit + pub fn set(&mut self, interrupt: u8) { + self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); + } + + /// Return an iterator over the set interrupt status bits + pub fn iterator(&self) -> InterruptStatusIterator { + InterruptStatusIterator { + status: *self, + idx: 0, + } + } +} + +impl From for InterruptStatus { + fn from(value: u32) -> Self { + Self { status: [value] } + } +} + +/// Iterator over set interrupt status bits +#[doc(hidden)] +#[derive(Debug, Clone)] +pub struct InterruptStatusIterator { + status: InterruptStatus, + idx: usize, +} + +impl Iterator for InterruptStatusIterator { + type Item = u8; + + fn next(&mut self) -> Option { + for i in self.idx..STATUS_WORDS { + if self.status.status[i] != 0 { + let bit = self.status.status[i].trailing_zeros(); + self.status.status[i] ^= 1 << bit; + self.idx = i; + return Some((bit + 32 * i as u32) as u8); + } + } + self.idx = usize::MAX; + None + } +} + +/// TODO: Move custom ULP instructions into their own module, +/// and cleanup the assembly files at the same time. +/// Disable all interrupts +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +pub fn disable() { + // Enter a critical section by disabling all interrupts + // This inline assembly construct uses the t0 register and is equivalent to: + // > li t0, 0x80000007 + // > maskirq_insn(zero, t0) // Mask all interrupt bits + // The mask 0x80000007 represents: + // Bit 31 - RTC peripheral interrupt + // Bit 2 - Bus error + // Bit 1 - Ebreak / Ecall / Illegal Instruction + // Bit 0 - Internal Timer + // + unsafe { + core::arch::asm!("li t0, 0x80000007", ".word 0x0602e00b"); + } +} + +/// Enable all interrupts +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +pub fn enable() { + // Exit a critical section by enabling all interrupts + // This inline assembly construct is equivalent to: + // > maskirq_insn(zero, zero) + unsafe { + core::arch::asm!(".word 0x0600600b"); + } +} + +/// Wait for any (masked or unmasked) interrupt +#[cfg(any(esp32s2, esp32s3))] +pub fn waitirq() -> u32 { + // Wait for pending interrupt, return pending interrupt mask + // waitirq a0 + let result: u32; + unsafe { + core::arch::asm!(".word 0x0800400B", out("a0") result); + } + result +} + +/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, +// which is currently hand-written in ulp_riscv_vectors.S +/// Registers saved in trap handler +#[doc(hidden)] +#[cfg(any(esp32s2, esp32s3))] +#[repr(C)] +#[derive(Debug)] +pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. + pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. + pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. + pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. + pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. + pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. + pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. + pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. + pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. + pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. + pub a5: usize, + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. + pub a6: usize, + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. + pub a7: usize, +} + +/// Trap entry point rust (_start_trap_rust) +/// `irqs` is a bitmask of IRQs to handle. +#[cfg(any(esp32s2, esp32s3))] +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +#[unsafe(export_name = "_start_trap_rust")] +pub extern "C" fn ulp_start_trap_rust(trap_frame: *const TrapFrame, irqs: u32) { + unsafe extern "C" { + fn trap_handler(regs: &TrapFrame, pending_irqs: u32); + } + + unsafe { + // dispatch trap to handler + trap_handler(&*trap_frame, irqs); + } +} + +/// Creates the trap_handler() function. +/// This is macro is used later in this file. +/// This style of macro is usually provided +/// for users to hook their own handlers, but here it's just used +/// as a quick way to generate the bit-mask code :) +#[cfg(any(esp32s2, esp32s3))] +macro_rules! build_trap_handler { + (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { + if $pending_irqs & (1 << $n) != 0 { + #[allow(unused_unsafe)] + unsafe { $handler($regs); } + } + }; + ( $( $irq:literal : $handler:ident ),* ) => { + /// Called by _start_trap_rust, this trap handler will call other interrupt handling + /// functions depending on the bits set in pending_irqs. + #[doc(hidden)] + #[unsafe(no_mangle)] + pub extern "C" fn trap_handler(regs: *const TrapFrame, pending_irqs: u32) { + let regs = unsafe { regs.as_ref().unwrap() }; + $( + build_trap_handler!(@interrupt($irq, pending_irqs, regs, $handler)); + )* + } + }; +} + +/// Default interrupt handler, does nothing. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub fn DefaultHandler() {} + +/// Default illegal instruction or bus error exception handler. +/// This handler is dispatched by the trap_handler() function. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[allow(non_snake_case)] +#[doc(hidden)] +#[unsafe(no_mangle)] +pub fn DefaultExceptionHandler(_regs: &TrapFrame) { + panic!("Unhandled exception!"); +} + +// Create the trap_handler function +#[cfg(any(esp32s2, esp32s3))] +build_trap_handler!( + 1: DefaultExceptionHandler, + 2: DefaultExceptionHandler, + 31: dispatch_peripheral_interrupt +); + +/// Peripheral interrupt handler for the IRQ bit 31. +/// Checks the SENS and RTC_IO interrup status, and dispatches further interrupt handlers as +/// appropriate. +#[cfg(any(esp32s2, esp32s3))] +#[doc(hidden)] +#[unsafe(no_mangle)] +fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { + // This function is based on the ESP-IDF implementation found here: + // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 + unsafe extern "Rust" { + fn SensInterrupt(status: SensInterruptStatus); + fn GpioInterrupt(pin: GpioInterruptPin); + } + + // Iterate for any SENS interrupt flags + let cocpu_int_st_bits = + InterruptStatus::from(unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits()); + // Iterate over the 1 bit positions + for bit in cocpu_int_st_bits.iterator() { + // Convert into the named interrupt enumeration + match SensInterruptStatus::try_from(bit) { + Ok(stat) => { + // Call handler, and clear the interrupt bit. + unsafe { SensInterrupt(stat) }; + + unsafe { &*pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(1 << bit) }); + } + Err(_) => {} + } + } + + // RTC IO interrupts. + let rtcio_int_st_bits = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); + let rtcio_int_st = InterruptStatus::from(rtcio_int_st_bits); + // Iterate over the 1 bit positions + for bit in rtcio_int_st.iterator() { + // Call handler, and clear the interrupt bit. + // Pin must have 10 subtracted from it, due to register offset. + unsafe { GpioInterrupt((bit - 10) as u32) }; + + unsafe { &*pac::RTC_IO::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(1 << bit) }); + } +} + +// Macros bind user functions to SensInterrupt and GpioInterrupt. + +#[macro_export] +#[doc = r" Assigns a handler to GpioInterrupt"] +#[doc = r""] +#[doc = r" This macro takes one argument: path to the function that"] +#[doc = r" will be used as the handler of that interrupt. The function"] +#[doc = r" must have signature `fn(pin : GpioInterruptPin)`."] +#[doc = r""] +#[doc = r" # Example"] +#[doc = r""] +#[doc = r" ``` ignore"] +#[doc = r" gpio_interrupt!(buttons_handler);"] +#[doc = r""] +#[doc = r" fn buttons_handler(_pin : GpioInterruptPin) {"] +#[doc = r#" print!("A GPIO pin was pressed!");"#] +#[doc = r" }"] +#[doc = r""] +macro_rules! gpio_interrupt { + ($ path : path) => { + #[allow(non_snake_case)] + #[unsafe(no_mangle)] + pub extern "C" fn GpioInterrupt(pin: GpioInterruptPin) { + let f: fn(pin: GpioInterruptPin) = $path; + f(pin); + } + }; +} + +#[macro_export] +#[doc = r" Assigns a handler to SensInterrupt"] +macro_rules! sens_interrupt { + ($ path : path) => { + #[allow(non_snake_case)] + #[unsafe(no_mangle)] + pub extern "C" fn SensInterrupt(status: SensInterruptStatus) { + let f: fn(status: SensInterruptStatus) = $path; + f(status); + } + }; +} + +#[allow(unused)] +pub use gpio_interrupt; +#[allow(unused)] +pub use sens_interrupt; diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index d9458053f27..3de72cd73f4 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -37,8 +37,10 @@ pub use esp32c6_lp as pac; pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; + +/// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) #[cfg(any(esp32s2, esp32s3))] -pub use pac::interrupt as sens_interrupt; +pub mod interrupts; /// The prelude pub mod prelude { @@ -59,6 +61,12 @@ cfg_if::cfg_if! { pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; +// Assembly containing the reset, trap, and startup assembly procedures.. +#[cfg(esp32c6)] +global_asm!(include_str!("./lp_start.S")); +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./ulp_riscv_start.S")); + /// Wake up the HP core pub fn wake_hp_core() { #[cfg(esp32c6)] @@ -75,51 +83,6 @@ pub fn wake_hp_core() { .write(|w| w.rtc_sw_cpu_int().set_bit()); } -#[cfg(esp32c6)] -global_asm!( - r#" - .section .init.vector, "ax" - /* This is the vector table. It is currently empty, but will be populated - * with exception and interrupt handlers when this is supported - */ - - .align 0x4, 0xff - .global _vector_table - .type _vector_table, @function -_vector_table: - .option push - .option norvc - - .rept 32 - nop - .endr - - .option pop - .size _vector_table, .-_vector_table - - .section .init, "ax" - .global reset_vector - -/* The reset vector, jumps to startup code */ -reset_vector: - j __start - -__start: - /* setup the stack pointer */ - la sp, __stack_top - call rust_main -loop: - j loop -"# -); - -// Include macros which define custom RISCV R-Type instructions, used for interrupt handling. -#[cfg(any(esp32s2, esp32s3))] -global_asm!(include_str!("./ulp_riscv_interrupt_ops.S")); -// Assembly containing the reset, trap, and startup assembly procedures.. -#[cfg(any(esp32s2, esp32s3))] -global_asm!(include_str!("./ulp_riscv_vectors.S")); - /// Entry point to the ULP program #[unsafe(link_section = ".init.rust")] #[unsafe(export_name = "rust_main")] @@ -133,7 +96,7 @@ unsafe extern "C" fn lp_core_startup() -> ! { #[cfg(any(esp32s2, esp32s3))] { ulp_riscv_rescue_from_monitor(); - ulp_interrupts_enable(); + interrupts::enable(); } #[cfg(esp32c6)] @@ -182,337 +145,3 @@ fn ulp_riscv_halt() -> ! { } } } - -/// TODO: Move custom ULP instructions into their own module, -/// and cleanup the assembly files at the same time. -/// Disable all interrupts -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn ulp_interrupts_disable() { - // Enter a critical section by disabling all interrupts - // This inline assembly construct uses the t0 register and is equivalent to: - // > li t0, 0x80000007 - // > maskirq_insn(zero, t0) // Mask all interrupt bits - // The mask 0x80000007 represents: - // Bit 31 - RTC peripheral interrupt - // Bit 2 - Bus error - // Bit 1 - Ebreak / Ecall / Illegal Instruction - // Bit 0 - Internal Timer - // - unsafe { - core::arch::asm!("li t0, 0x80000007", ".word 0x0602e00b"); - } -} - -/// Enable all interrupts -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn ulp_interrupts_enable() { - // Exit a critical section by enabling all interrupts - // This inline assembly construct is equivalent to: - // > maskirq_insn(zero, zero) - unsafe { - core::arch::asm!(".word 0x0600600b"); - } -} - -/// Wait for any (masked or unmasked) interrupt -#[cfg(any(esp32s2, esp32s3))] -pub fn ulp_waitirq() -> u32 { - // Wait for pending interrupt, return pending interrupt mask - // waitirq a0 - let result: u32; - unsafe { - core::arch::asm!(".word 0x0800400B", out("a0") result); - } - result -} - -/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, -// which is currently hand-written in ulp_riscv_vectors.S -/// Registers saved in trap handler -#[doc(hidden)] -#[cfg(any(esp32s2, esp32s3))] -#[repr(C)] -#[derive(Debug)] -pub struct TrapFrame { - /// `x1`: return address, stores the address to return to after a function call or interrupt. - pub ra: usize, - /// `x5`: temporary register `t0`, used for intermediate values. - pub t0: usize, - /// `x6`: temporary register `t1`, used for intermediate values. - pub t1: usize, - /// `x7`: temporary register `t2`, used for intermediate values. - pub t2: usize, - /// `x28`: temporary register `t3`, used for intermediate values. - pub t3: usize, - /// `x29`: temporary register `t4`, used for intermediate values. - pub t4: usize, - /// `x30`: temporary register `t5`, used for intermediate values. - pub t5: usize, - /// `x31`: temporary register `t6`, used for intermediate values. - pub t6: usize, - /// `x10`: argument register `a0`. Used to pass the first argument to a function. - pub a0: usize, - /// `x11`: argument register `a1`. Used to pass the second argument to a function. - pub a1: usize, - /// `x12`: argument register `a2`. Used to pass the third argument to a function. - pub a2: usize, - /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. - pub a3: usize, - /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. - pub a4: usize, - /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. - pub a5: usize, - /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. - pub a6: usize, - /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. - pub a7: usize, -} - -/// Trap entry point rust (_start_trap_rust) -/// `irqs` is a bitmask of IRQs to handle. -#[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -#[unsafe(link_section = ".trap.rust")] -#[unsafe(export_name = "_start_trap_rust")] -pub extern "C" fn ulp_start_trap_rust(trap_frame: *const TrapFrame, irqs: u32) { - unsafe extern "C" { - fn trap_handler(regs: &TrapFrame, pending_irqs: u32); - } - - unsafe { - // dispatch trap to handler - trap_handler(&*trap_frame, irqs); - } -} - -/// Creates the trap_handler() function. -/// This is macro is used later in this file. -/// This style of macro is usually provided -/// for users to hook their own handlers, but here it's just used -/// as a quick way to generate the bit-mask code :) -#[cfg(any(esp32s2, esp32s3))] -macro_rules! ulp_interrupts { - (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { - if $pending_irqs & (1 << $n) != 0 { - #[allow(unused_unsafe)] - unsafe { $handler($regs); } - } - }; - ( $( $irq:literal : $handler:ident ),* ) => { - /// Called by _start_trap_rust, this trap handler will call other interrupt handling - /// functions depending on the bits set in pending_irqs. - #[doc(hidden)] - #[unsafe(no_mangle)] - pub extern "C" fn trap_handler(regs: *const TrapFrame, pending_irqs: u32) { - let regs = unsafe { regs.as_ref().unwrap() }; - $( - ulp_interrupts!(@interrupt($irq, pending_irqs, regs, $handler)); - )* - } - }; -} - -/// Peripheral interrupt handler for the IRQ bit 31. -/// Checks the peripheral interrupt flags, and calls further interrupt handlers as appropriate. -/// Clears interrupt flags automatically. -#[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -#[unsafe(no_mangle)] -fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { - // This function is based on the ESP-IDF implementation found here: - // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 - - // RTC Peripheral interrupts - // let cocpu_int_st: u32 = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits(); - let cocpu_int_st = unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read(); - let cocpu_int_st_bits = cocpu_int_st.bits(); - - // RTC IO interrupts - let rtcio_int_st: u32 = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); - - // Call handler, and clear the interrupt flags, if they - // were raised at the start of this ISR call. - - if cocpu_int_st_bits > 0 { - dispatch_sens_interrupt(cocpu_int_st); - - // Clear the interrupt - unsafe { &*pac::SENS::PTR } - .sar_cocpu_int_clr() - .write(|w| unsafe { w.bits(cocpu_int_st_bits) }); - } - - if rtcio_int_st > 0 { - dispatch_rtcio_interrupt(rtcio_int_st >> 10); - - // Clear the interrupt - unsafe { &*pac::RTC_IO::PTR } - .status_w1tc() - .write(|w| unsafe { w.bits(rtcio_int_st) }); - } -} - -/// Dispatcher for RTC Peripheral interrupts (SENS). -/// This function is called from dispatch_peripheral_interrupt() function, -/// which is itself called from the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub fn dispatch_sens_interrupt(sar_cocpu_int_st: pac::sens::sar_cocpu_int_st::R) { - // by default, this will attempt to service the __EXTERNAL_INTERRUPTS vectors. - use pac::__EXTERNAL_INTERRUPTS; - let bitflags = sar_cocpu_int_st.bits(); - // iterate over bits set and call the handler vectors - for i in 0..__EXTERNAL_INTERRUPTS.len() { - // Check bit set - if (bitflags & (1 << i)) != 0 { - unsafe { (__EXTERNAL_INTERRUPTS[i]._handler)() }; - } - } -} - -/// Dispatcher for RTC IO (GPIO) interrupts. -/// This function is called from dispatch_peripheral_interrupt() function, -/// which is itself called from the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub fn dispatch_rtcio_interrupt(pin_status: u32) { - // iterate over bits set and call the handler vectors - for i in 0..crate::rtcio_interrupt::__RTC_IO_INTERRUPTS.len() { - // Check bit set - if (pin_status & (1 << i)) != 0 { - unsafe { (crate::rtcio_interrupt::__RTC_IO_INTERRUPTS[i]._handler)() }; - } - } -} - -/// Default interrupt handler, does nothing. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub fn noop_interrupt_handler() {} - -// ULP interrupt bitflags from: -// https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L16 -#[cfg(any(esp32s2, esp32s3))] -unsafe extern "Rust" { - fn IllegalInstructionException(regs: &TrapFrame); - fn BusErrorException(regs: &TrapFrame); -} - -// Create the trap_handler function -#[cfg(any(esp32s2, esp32s3))] -ulp_interrupts!( - 1: IllegalInstructionException, - 2: BusErrorException, - 31: dispatch_peripheral_interrupt -); - -/// Default illegal instruction or bus error exception handler. -/// Users may override IllegalInstructionException, and/or BusErrorException, -/// to change this behaviour. -/// This handler is dispatched by the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub fn default_exception_handler(_regs: &TrapFrame) { - panic!("Unhandled exception!"); -} - -/// TODO: Clean this up and merge into GPIO probably. -/// Hacky GPIO interrupt vector table, similar to how it's done in ulp-pacs. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -mod rtcio_interrupt { - unsafe extern "C" { - fn GPIO0(); - fn GPIO1(); - fn GPIO2(); - fn GPIO3(); - fn GPIO4(); - fn GPIO5(); - fn GPIO6(); - fn GPIO7(); - fn GPIO8(); - fn GPIO9(); - fn GPIO10(); - fn GPIO11(); - fn GPIO12(); - fn GPIO13(); - fn GPIO14(); - fn GPIO15(); - fn GPIO16(); - fn GPIO17(); - fn GPIO18(); - fn GPIO19(); - fn GPIO20(); - fn GPIO21(); - } - - use crate::{gpio::MAX_GPIO_PIN, pac::Vector}; - - #[doc(hidden)] - #[unsafe(link_section = ".rwtext")] - #[unsafe(no_mangle)] - pub static __RTC_IO_INTERRUPTS: [Vector; (MAX_GPIO_PIN + 1) as usize] = [ - Vector { _handler: GPIO0 }, - Vector { _handler: GPIO1 }, - Vector { _handler: GPIO2 }, - Vector { _handler: GPIO3 }, - Vector { _handler: GPIO4 }, - Vector { _handler: GPIO5 }, - Vector { _handler: GPIO6 }, - Vector { _handler: GPIO7 }, - Vector { _handler: GPIO8 }, - Vector { _handler: GPIO9 }, - Vector { _handler: GPIO10 }, - Vector { _handler: GPIO11 }, - Vector { _handler: GPIO12 }, - Vector { _handler: GPIO13 }, - Vector { _handler: GPIO14 }, - Vector { _handler: GPIO15 }, - Vector { _handler: GPIO16 }, - Vector { _handler: GPIO17 }, - Vector { _handler: GPIO18 }, - Vector { _handler: GPIO19 }, - Vector { _handler: GPIO20 }, - Vector { _handler: GPIO21 }, - ]; - - #[macro_export] - #[doc = r" Assigns a handler to an interrupt"] - #[doc = r""] - #[doc = r" This macro takes two arguments: the name of an interrupt and the path to the"] - #[doc = r" function that will be used as the handler of that interrupt. That function"] - #[doc = r" must have signature `fn()`."] - #[doc = r""] - #[doc = r" # Example"] - #[doc = r""] - #[doc = r" ``` ignore"] - #[doc = r" interrupt!(GPIO0, button);"] - #[doc = r""] - #[doc = r" fn button() {"] - #[doc = r#" print!("Pressed");"#] - #[doc = r" }"] - #[doc = r""] - macro_rules! gpio_interrupt { - ($ NAME : ident , $ path : path) => { - #[allow(non_snake_case)] - #[unsafe(no_mangle)] - pub extern "C" fn $NAME() { - let f: fn() = $path; - f(); - } - }; - } - - #[allow(unused)] - pub(crate) use gpio_interrupt; -} diff --git a/esp-lp-hal/src/lp_start.S b/esp-lp-hal/src/lp_start.S new file mode 100644 index 00000000000..a4d217e2aff --- /dev/null +++ b/esp-lp-hal/src/lp_start.S @@ -0,0 +1,32 @@ +/* Start-up assembly for ESP32-C6 chip */ +.section .init.vector, "ax" +/* This is the vector table. It is currently empty, but will be populated + * with exception and interrupt handlers when this is supported + */ +.align 0x4, 0xff +.global _vector_table +.type _vector_table, @function +_vector_table: + .option push + .option norvc + + .rept 32 + nop + .endr + + .option pop + .size _vector_table, .-_vector_table + + .section .init, "ax" + .global reset_vector + +/* The reset vector, jumps to startup code */ +reset_vector: + j __start + +__start: + /* setup the stack pointer */ + la sp, __stack_top + call rust_main +loop: + j loop \ No newline at end of file diff --git a/esp-lp-hal/src/ulp_riscv_interrupt_ops.S b/esp-lp-hal/src/ulp_riscv_interrupt_ops.S deleted file mode 100644 index 74d4fbe9392..00000000000 --- a/esp-lp-hal/src/ulp_riscv_interrupt_ops.S +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Defines the assembly macros needed for custom instructions on the ULP RISCV core. - * Adapted from the following ESP-IDF header: - * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h - * With additional waitirq_insn from PicoRV32: - * https://github.com/YosysHQ/picorv32/blob/87c89acc18994c8cf9a2311e871818e87d304568/firmware/start.S - * A quote from the ESP-IDF header file, is shown below. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * This header file defines custom instructions for interrupt handling on the * - * ULP RISC-V. The architecture of the processor and therefore, the interrupt * - * handling is based on the PicoRV32 CPU. Details about the operations are * - * available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - */ - -.set regnum_zero,0 -.set regnum_ra, 1 -.set regnum_sp, 2 -.set regnum_gp, 3 -.set regnum_tp, 4 -.set regnum_t0, 5 -.set regnum_t1, 6 -.set regnum_t2, 7 -.set regnum_s0, 8 -.set regnum_s1, 9 -.set regnum_a0, 10 -.set regnum_a1, 11 -.set regnum_a2, 12 -.set regnum_a3, 13 -.set regnum_a4, 14 -.set regnum_a5, 15 -.set regnum_a6, 16 -.set regnum_a7, 17 -.set regnum_s2, 18 -.set regnum_s3, 19 -.set regnum_s4, 20 -.set regnum_s5, 21 -.set regnum_s6, 22 -.set regnum_s7, 23 -.set regnum_s8, 24 -.set regnum_s9, 25 -.set regnum_s10,26 -.set regnum_s11,27 -.set regnum_t3, 28 -.set regnum_t4, 29 -.set regnum_t5, 30 -.set regnum_t6, 31 - -/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */ -.set regnum_q0, 0 -.set regnum_q1, 1 -.set regnum_q2, 2 -.set regnum_q3, 3 - -/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA - * with the same opcode of custom0 (0001011). - */ -.macro r_type_insn _f7, _rs2, _rs1, _f3, _rd, _opc - .word (((\_f7) << 25) | ((\_rs2) << 20) | ((\_rs1) << 15) | ((\_f3) << 12) | ((\_rd) << 7) | ((\_opc) << 0)) -.endm - -/** - * Instruction: getq rd, qs - * Description: This instruction copies the value of Qx into a general purpose register rd - */ -.macro getq_insn _rd _qs - r_type_insn 0b0000000, 0, "regnum_\_qs", 0b100, "regnum_\_rd", 0b0001011 -.endm - -/** - * Instruction: setq qd, rs - * Description: This instruction copies the value of general purpose register rs to Qx - */ -.macro setq_insn _qd _rs - r_type_insn 0b0000001, 0, "regnum_\_rs", 0b010, "regnum_\_qd", 0b0001011 -.endm - -/** - * Instruction: retirq - * Description: This instruction copies the value of Q0 to CPU PC, and renables interrupts - */ -.macro retirq_insn - r_type_insn 0b0000010, 0, 0, 0b000, 0, 0b0001011 -.endm - -/** - * Instruction: maskirq rd, rs - * Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value - * of register rs to to IRQ mask. - */ -.macro maskirq_insn _rd _rs - r_type_insn 0b0000011, 0, "regnum_\_rs", 0b110, "regnum_\_rd", 0b0001011 -.endm - -/** - * Instruction: waitirq rd - * Description: Pause execution until any interrupt (masked or unmasked) becomes pending. Stores the pending IRQ bitmask into register rd. - */ -.macro waitirq_insn _rd - r_type_insn 0b0000100, 0, 0, 0b100, "regnum_\_rd", 0b0001011 -.endm diff --git a/esp-lp-hal/src/ulp_riscv_vectors.S b/esp-lp-hal/src/ulp_riscv_start.S similarity index 53% rename from esp-lp-hal/src/ulp_riscv_vectors.S rename to esp-lp-hal/src/ulp_riscv_start.S index fc7c9ea07cb..1db0b793a88 100644 --- a/esp-lp-hal/src/ulp_riscv_vectors.S +++ b/esp-lp-hal/src/ulp_riscv_start.S @@ -1,4 +1,106 @@ -/* Much of this assembly was sourced from the following ESP-IDF files... +/** + * Defines the assembly macros needed for custom instructions on the ULP RISCV core. + * Adapted from the following ESP-IDF header: + * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h + * With additional waitirq_insn from PicoRV32: + * https://github.com/YosysHQ/picorv32/blob/87c89acc18994c8cf9a2311e871818e87d304568/firmware/start.S + * A quote from the ESP-IDF header file, is shown below. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * This header file defines custom instructions for interrupt handling on the * + * ULP RISC-V. The architecture of the processor and therefore, the interrupt * + * handling is based on the PicoRV32 CPU. Details about the operations are * + * available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +.set regnum_zero,0 +.set regnum_ra, 1 +.set regnum_sp, 2 +.set regnum_gp, 3 +.set regnum_tp, 4 +.set regnum_t0, 5 +.set regnum_t1, 6 +.set regnum_t2, 7 +.set regnum_s0, 8 +.set regnum_s1, 9 +.set regnum_a0, 10 +.set regnum_a1, 11 +.set regnum_a2, 12 +.set regnum_a3, 13 +.set regnum_a4, 14 +.set regnum_a5, 15 +.set regnum_a6, 16 +.set regnum_a7, 17 +.set regnum_s2, 18 +.set regnum_s3, 19 +.set regnum_s4, 20 +.set regnum_s5, 21 +.set regnum_s6, 22 +.set regnum_s7, 23 +.set regnum_s8, 24 +.set regnum_s9, 25 +.set regnum_s10,26 +.set regnum_s11,27 +.set regnum_t3, 28 +.set regnum_t4, 29 +.set regnum_t5, 30 +.set regnum_t6, 31 + +/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */ +.set regnum_q0, 0 +.set regnum_q1, 1 +.set regnum_q2, 2 +.set regnum_q3, 3 + +/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA + * with the same opcode of custom0 (0001011). + */ +.macro r_type_insn _f7, _rs2, _rs1, _f3, _rd, _opc + .word (((\_f7) << 25) | ((\_rs2) << 20) | ((\_rs1) << 15) | ((\_f3) << 12) | ((\_rd) << 7) | ((\_opc) << 0)) +.endm + +/** + * Instruction: getq rd, qs + * Description: This instruction copies the value of Qx into a general purpose register rd + */ +.macro getq_insn _rd _qs + r_type_insn 0b0000000, 0, "regnum_\_qs", 0b100, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: setq qd, rs + * Description: This instruction copies the value of general purpose register rs to Qx + */ +.macro setq_insn _qd _rs + r_type_insn 0b0000001, 0, "regnum_\_rs", 0b010, "regnum_\_qd", 0b0001011 +.endm + +/** + * Instruction: retirq + * Description: This instruction copies the value of Q0 to CPU PC, and renables interrupts + */ +.macro retirq_insn + r_type_insn 0b0000010, 0, 0, 0b000, 0, 0b0001011 +.endm + +/** + * Instruction: maskirq rd, rs + * Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value + * of register rs to to IRQ mask. + */ +.macro maskirq_insn _rd _rs + r_type_insn 0b0000011, 0, "regnum_\_rs", 0b110, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: waitirq rd + * Description: Pause execution until any interrupt (masked or unmasked) becomes pending. Stores the pending IRQ bitmask into register rd. + */ +.macro waitirq_insn _rd + r_type_insn 0b0000100, 0, 0, 0b100, "regnum_\_rd", 0b0001011 +.endm + +/* Much of the following assembly was sourced from the following ESP-IDF files... * ...irq handler macros: * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S * ...critical section assembly From d344dfc83f952e5024c07bc4ce1f3800dea78559 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 29 Mar 2026 10:01:29 +1100 Subject: [PATCH 12/47] Satisfy clippy --- esp-lp-hal/src/interrupts.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/esp-lp-hal/src/interrupts.rs b/esp-lp-hal/src/interrupts.rs index 8dd6a2421d6..170e822642b 100644 --- a/esp-lp-hal/src/interrupts.rs +++ b/esp-lp-hal/src/interrupts.rs @@ -1,10 +1,8 @@ //! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. //! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. -use crate::pac; +use core::ptr::NonNull; -// #[cfg(any(esp32s2, esp32s3))] -// pub use pac::interrupt as sens_interrupt; -// use crate::gpio::MAX_GPIO_PIN; +use crate::pac; /// Argument passed to GpioInterrupt handler. pub type GpioInterruptPin = u32; @@ -164,14 +162,18 @@ pub struct TrapFrame { #[doc(hidden)] #[unsafe(link_section = ".trap.rust")] #[unsafe(export_name = "_start_trap_rust")] -pub extern "C" fn ulp_start_trap_rust(trap_frame: *const TrapFrame, irqs: u32) { +pub extern "C" fn ulp_start_trap_rust(trap_frame: *const u32, irqs: u32) { unsafe extern "C" { fn trap_handler(regs: &TrapFrame, pending_irqs: u32); } unsafe { - // dispatch trap to handler - trap_handler(&*trap_frame, irqs); + // 'trap_frame' pointer safety: + // _start_trap must place a valid address in a0, prior to calling _start_trap_rust. + trap_handler( + NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), + irqs, + ); } } @@ -193,8 +195,7 @@ macro_rules! build_trap_handler { /// functions depending on the bits set in pending_irqs. #[doc(hidden)] #[unsafe(no_mangle)] - pub extern "C" fn trap_handler(regs: *const TrapFrame, pending_irqs: u32) { - let regs = unsafe { regs.as_ref().unwrap() }; + pub extern "C" fn trap_handler(regs: &TrapFrame, pending_irqs: u32) { $( build_trap_handler!(@interrupt($irq, pending_irqs, regs, $handler)); )* @@ -249,16 +250,13 @@ fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { // Iterate over the 1 bit positions for bit in cocpu_int_st_bits.iterator() { // Convert into the named interrupt enumeration - match SensInterruptStatus::try_from(bit) { - Ok(stat) => { - // Call handler, and clear the interrupt bit. - unsafe { SensInterrupt(stat) }; + if let Ok(stat) = SensInterruptStatus::try_from(bit) { + // Call handler, and clear the interrupt bit. + unsafe { SensInterrupt(stat) }; - unsafe { &*pac::SENS::PTR } - .sar_cocpu_int_clr() - .write(|w| unsafe { w.bits(1 << bit) }); - } - Err(_) => {} + unsafe { &*pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(1 << bit) }); } } From 2c1a07e5f75172dd7ead69756d97b0f3d4e768cf Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 29 Mar 2026 12:14:06 +1100 Subject: [PATCH 13/47] Incomplete migration of a bunch of interrupt handling stuffs --- esp-lp-hal/Cargo.toml | 1 + esp-lp-hal/src/gpio.rs | 53 ++++ .../src/{interrupts.rs => interrupt.rs} | 253 +++++++++++++++++- esp-lp-hal/src/lib.rs | 4 +- 4 files changed, 307 insertions(+), 4 deletions(-) rename esp-lp-hal/src/{interrupts.rs => interrupt.rs} (57%) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 4963a4c9f86..c8075540f92 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -48,6 +48,7 @@ esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } +portable-atomic = { version = "1.13.1", default-features = false, features = ["unsafe-assume-single-core"] } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index e29cb126b4f..2352d5d727b 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -22,6 +22,14 @@ //! } //! } //! ``` +use crate::interrupt::{ + Interrupt, + InterruptConfigurable, + InterruptHandler, + Priority, + USER_INTERRUPT_HANDLER, + user_gpio_interrupt_handler, +}; cfg_if::cfg_if! { if #[cfg(esp32c6)] { @@ -35,6 +43,51 @@ cfg_if::cfg_if! { } } +/// General Purpose Input/Output driver +pub struct Io { + _io_peripheral: LpIo, +} + +impl Io { + /// Initialize the I/O driver. + pub fn new(_io_peripheral: LpIo) -> Self { + Io { + _io_peripheral: _io_peripheral, + } + } + + #[doc = "Registers an interrupt handler for all GPIO pins."] + /// Note that when using interrupt handlers registered by this function, or + /// by defining a `#[no_mangle] unsafe extern "C" fn GPIO()` function, we do + /// **not** clear the interrupt status register or the interrupt enable + /// setting for you. Based on your use case, you need to do one of this + /// yourself: + /// + /// - Disabling the interrupt enable setting for the GPIO pin allows you to handle an event once + /// per call to [`listen()`]. Using this method, the [`is_interrupt_set()`] method will return + /// `true` if the interrupt is set even after your handler has finished running. + /// - Clearing the interrupt status register allows you to handle an event repeatedly after + /// [`listen()`] is called. Using this method, [`is_interrupt_set()`] will return `false` + /// after your handler has finished running. + /// + /// [`listen()`]: Input::listen + /// [`is_interrupt_set()`]: Input::is_interrupt_set + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + USER_INTERRUPT_HANDLER.store(handler.handler().callback()); + + crate::interrupt::bind_handler( + Interrupt::GPIO, + InterruptHandler::new(user_gpio_interrupt_handler, Priority::max()), + ); + } +} + +impl crate::interrupt::InterruptConfigurable for Io { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + /// Digital input or output level. #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] pub enum Level { diff --git a/esp-lp-hal/src/interrupts.rs b/esp-lp-hal/src/interrupt.rs similarity index 57% rename from esp-lp-hal/src/interrupts.rs rename to esp-lp-hal/src/interrupt.rs index 170e822642b..4221c314ffb 100644 --- a/esp-lp-hal/src/interrupts.rs +++ b/esp-lp-hal/src/interrupt.rs @@ -2,15 +2,264 @@ //! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. use core::ptr::NonNull; -use crate::pac; +use portable_atomic::{AtomicPtr, Ordering}; +use procmacros::handler; + +use crate::{interrupt, pac}; /// Argument passed to GpioInterrupt handler. pub type GpioInterruptPin = u32; /// Argument passed to SensInterrupt handler. pub use pac::Interrupt as SensInterruptStatus; -const STATUS_WORDS: usize = 1; +#[doc = r"Enumeration of all the interrupts."] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u16)] +pub enum Interrupt { + #[doc = "0 - SENS"] + SENS = 0, + #[doc = "1 - GPIO"] + GPIO = 1, +} + +#[doc = r" TryFromInterruptError"] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Copy, Clone)] +pub struct TryFromInterruptError(()); +impl Interrupt { + #[doc = r" Attempt to convert a given value into an `Interrupt`"] + #[inline] + pub fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Interrupt::SENS), + 1 => Ok(Interrupt::GPIO), + _ => Err(TryFromInterruptError(())), + } + } +} + +/// Convenience constant for `Option::None` pin +pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); + +pub(super) struct CFnPtr(AtomicPtr<()>); +impl CFnPtr { + pub const fn new() -> Self { + Self(AtomicPtr::new(core::ptr::null_mut())) + } + + pub fn store(&self, f: extern "C" fn()) { + self.0.store(f as *mut (), Ordering::Relaxed); + } + + pub fn call(&self) { + let ptr = self.0.load(Ordering::Relaxed); + if !ptr.is_null() { + unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() }; + } + } +} + +/// If no handler is allocated, panic. +#[unsafe(no_mangle)] +extern "C" fn EspDefaultHandler() { + panic!("Unhandled interrupt"); +} + +/// Default (unhandled) interrupt handler +pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new( + { + unsafe extern "C" { + fn EspDefaultHandler(); + } + + unsafe { + core::mem::transmute::(EspDefaultHandler) + } + }, + Priority::max(), +); + +/// The default GPIO interrupt handler, when the user has not set one. +/// +/// This handler will disable all pending interrupts and leave the interrupt +/// status bits unchanged. This enables functions like `is_interrupt_set` to +/// work correctly. +#[handler] +fn default_gpio_interrupt_handler() { + // Does nothing. + // DEFAULT_INTERRUPT_HANDLER.handler().callback()(); +} + +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) { + // // We only allow binding the default handler if nothing else is bound. + // // This prevents silently overwriting RTIC's interrupt handler, if using GPIO. + // if handler != DEFAULT_INTERRUPT_HANDLER.handler() { + // // The user has configured an interrupt handler they wish to use. + // info!("Not using default GPIO interrupt handler: already bound in vector table"); + // return; + // } + // } + + // // The vector table doesn't contain a custom entry. Still, the + // // peripheral interrupt may already be bound to something else. + // for cpu in cores() { + // if interrupt::mapped_to(cpu, Interrupt::GPIO).is_some() { + // info!("Not using default GPIO interrupt handler: peripheral interrupt already in + // use"); return; + // } + // } + + interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); + + // // On ESP32, there are separate interrupt status registers for each core, we need to enable + // the // interrupt handler on each core otherwise GPIOs listening on the App CPU will not + // receive // interrupts. + // #[cfg(esp32)] + // crate::interrupt::enable_on_cpu( + // crate::system::Cpu::AppCpu, + // Interrupt::GPIO, + // Priority::Priority1, + // ); +} + +/// The user GPIO interrupt handler, when the user has set one. +/// +/// The user handler is responsible for clearing the interrupt status bits or disabling +/// the interrupts. +pub extern "C" fn user_gpio_interrupt_handler() { + // Call the user handler before clearing interrupts. The user can use the enable + // bits to determine which interrupts they are interested in. Clearing the + // interrupt status or enable bits have no effect on the rest of the + // interrupt handler. + USER_INTERRUPT_HANDLER.call(); +} +/// Binds the given handler to a peripheral interrupt. +/// +/// The interrupt handler will be enabled at the specified priority level. +/// +/// The interrupt handler will be called on the core where it is registered. +/// Only one interrupt handler can be bound to a peripheral interrupt. +pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { + todo!("Port this function to esp-lp-hal") + // unsafe { + // let vector = vector_entry(interrupt); + // let ptr = (&raw const vector._handler).cast::().cast_mut(); + // // On RISC-V MCUs we may be protecting the trap section using a watchpoint. + // // If we do, we need to temporarily disable this protection. + // #[cfg(all(riscv, write_vec_table_monitoring))] + // if crate::soc::trap_section_protected() { + // crate::debugger::DEBUGGER_LOCK.lock(|| { + // let wp = crate::debugger::clear_watchpoint(1); + // ptr.write_volatile(handler.handler().address()); + // crate::debugger::restore_watchpoint(1, wp); + // }); + // enable(interrupt, handler.priority()); + // return; + // } + // ptr.write_volatile(handler.handler().address()); + // } + // enable(interrupt, handler.priority()); +} + +/// Trait implemented by drivers which allow the user to set an +/// [InterruptHandler] +pub trait InterruptConfigurable { + #[doc = "Registers an interrupt handler for the peripheral."] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. Some peripherals offer a shared interrupt handler for + /// multiple purposes. It's the users duty to honor this. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [DEFAULT_INTERRUPT_HANDLER] + fn set_interrupt_handler(&mut self, handler: InterruptHandler); +} + +/// Represents an ISR callback function +#[derive(Copy, Clone)] +pub struct IsrCallback { + f: extern "C" fn(), +} + +impl IsrCallback { + /// Construct a new callback from the callback function. + pub fn new(f: extern "C" fn()) -> Self { + // a valid fn pointer is non zero + Self { f } + } + + /// Returns the address of the callback. + pub fn address(self) -> usize { + self.f as usize + } + + /// The callback function. + /// + /// This is aligned and can be called. + pub fn callback(self) -> extern "C" fn() { + self.f + } +} + +impl PartialEq for IsrCallback { + fn eq(&self, other: &Self) -> bool { + core::ptr::fn_addr_eq(self.callback(), other.callback()) + } +} + +/// Interrupt priority levels. +/// For esp-lp-hal there is only one priority level, and this is only included +/// so that the API is compatible with esp-hal handler macro. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +#[non_exhaustive] +pub enum Priority { + /// Priority level 1. + Priority1 = 1, +} + +impl Priority { + /// Maximum interrupt priority + pub const fn max() -> Priority { + Priority::Priority1 + } + + /// Minimum interrupt priority + pub const fn min() -> Priority { + Priority::Priority1 + } +} + +/// An interrupt handler +#[derive(Copy, Clone)] +pub struct InterruptHandler { + f: extern "C" fn(), + prio: Priority, +} + +impl InterruptHandler { + /// Creates a new [InterruptHandler] which will call the given function + pub const fn new(f: extern "C" fn(), prio: Priority) -> Self { + Self { f, prio } + } + + /// The Isr callback. + #[inline] + pub fn handler(&self) -> IsrCallback { + IsrCallback::new(self.f) + } + + /// Priority to be used when registering the interrupt + #[inline] + pub fn priority(&self) -> Priority { + self.prio + } +} + +const STATUS_WORDS: usize = 1; /// Representation of peripheral-interrupt status bits. #[doc(hidden)] #[derive(Clone, Copy, Default, Debug)] diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 3de72cd73f4..c890199936b 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -40,7 +40,7 @@ pub use esp32s3_ulp as pac; /// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) #[cfg(any(esp32s2, esp32s3))] -pub mod interrupts; +pub mod interrupt; /// The prelude pub mod prelude { @@ -96,7 +96,7 @@ unsafe extern "C" fn lp_core_startup() -> ! { #[cfg(any(esp32s2, esp32s3))] { ulp_riscv_rescue_from_monitor(); - interrupts::enable(); + interrupt::enable(); } #[cfg(esp32c6)] From 6df37f50820e3272b9962933116e029fcb9b8167 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Mon, 30 Mar 2026 07:11:59 +1100 Subject: [PATCH 14/47] Semi-functional port of Io interrupt handling APIs --- esp-lp-hal/ld/link-ulp.x | 4 +- esp-lp-hal/src/gpio.rs | 120 +++++++- esp-lp-hal/src/interrupt.rs | 600 +++++++++++++++++++----------------- esp-lp-hal/src/lib.rs | 3 +- 4 files changed, 425 insertions(+), 302 deletions(-) diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 2ef26cd9a3a..0c287815679 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -14,8 +14,8 @@ MEMORY } /* Default interrupt handlers */ -PROVIDE(SensInterrupt = DefaultHandler); -PROVIDE(GpioInterrupt = DefaultHandler); +PROVIDE(SENS_INT = DefaultHandler); +PROVIDE(GPIO_INT = DefaultHandler); SECTIONS { diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index 2352d5d727b..92ad299f10f 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -22,13 +22,11 @@ //! } //! } //! ``` -use crate::interrupt::{ - Interrupt, - InterruptConfigurable, - InterruptHandler, - Priority, - USER_INTERRUPT_HANDLER, - user_gpio_interrupt_handler, +use procmacros::handler; + +use crate::{ + interrupt, + interrupt::{CFnPtr, Interrupt, InterruptHandler, Priority}, }; cfg_if::cfg_if! { @@ -43,6 +41,38 @@ cfg_if::cfg_if! { } } +/// Convenience constant for `Option::None` pin +pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); + +/// The user GPIO interrupt handler, when the user has set one. +/// +/// The user handler is responsible for clearing the interrupt status bits or disabling +/// the interrupts. +#[handler] +fn user_gpio_interrupt_handler() { + // Call the user handler before clearing interrupts. The user can use the enable + // bits to determine which interrupts they are interested in. Clearing the + // interrupt status or enable bits have no effect on the rest of the + // interrupt handler. + USER_INTERRUPT_HANDLER.call(); +} + +/// The default GPIO interrupt handler, when the user has not set one. +/// +/// This handler will disable all pending interrupts and leave the interrupt +/// status bits unchanged. This enables functions like `is_interrupt_set` to +/// work correctly. +#[handler] +fn default_gpio_interrupt_handler() { + let status = gpio_interrupt_status(); + gpio_interrupt_disable(status); +} + +#[doc(hidden)] +pub fn bind_default_interrupt_handler() { + interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); +} + /// General Purpose Input/Output driver pub struct Io { _io_peripheral: LpIo, @@ -77,7 +107,8 @@ impl Io { crate::interrupt::bind_handler( Interrupt::GPIO, - InterruptHandler::new(user_gpio_interrupt_handler, Priority::max()), + user_gpio_interrupt_handler, + // InterruptHandler::new(user_gpio_interrupt_handler, Priority::min()), ); } } @@ -178,6 +209,32 @@ pub enum WakeEvent { HighLevel = 5, } +/// Read the interrupt status of all pins +#[cfg(any(esp32s2, esp32s3))] +#[inline] +pub fn gpio_interrupt_status() -> u32 { + // Bit 0 == GPIO0, in the returned value + unsafe { &*LpIo::PTR }.status().read().bits() >> 10 +} + +/// Clear the interrupt status for a bit mask of pins +#[cfg(any(esp32s2, esp32s3))] +#[inline] +pub fn gpio_interrupt_clear(pinmask: u32) { + // expects pinmask bit 0 == GPIO0 + unsafe { &*LpIo::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(pinmask << 10) }); +} + +/// Disable interrupts for a bit mask of pins +#[cfg(any(esp32s2, esp32s3))] +#[inline] +fn gpio_interrupt_disable(_pinmask: u32) { + // expects pinmask bit 0 == GPIO0 + // todo!("Implement masked interrupt disabling."); +} + /// Set GPIO event listening. /// /// - `N`: the pin to configure @@ -188,7 +245,8 @@ pub enum WakeEvent { fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) { // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); // TODO: Add LpIo::regs().pins(number : usize) to esp-pacs - let gpio_base = 0xa400 + 0x28 + ((N as usize) * 4); + let gpio_bank_reg = LpIo::ptr() as usize; + let gpio_base = gpio_bank_reg + 0x28 + ((N as usize) * 4); let gpio_pin = gpio_base as *mut u32; // Read the current setting @@ -221,9 +279,13 @@ fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) #[cfg(any(esp32s2, esp32s3))] fn clear_pin_interrupt() { - unsafe { &*LpIo::PTR } - .status_w1tc() - .write(|w| unsafe { w.bits(1 << N) }); + gpio_interrupt_clear(1 << N); +} + +#[cfg(any(esp32s2, esp32s3))] +fn is_interrupt_set() -> bool { + let stat = gpio_interrupt_status(); + (stat & (1 << N)) != 0 } /// Flexible pin driver. @@ -290,7 +352,7 @@ impl Flex { /// Listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn listen(&mut self, event: Event) { - self.clear_interrupts(); + self.clear_interrupt(); enable_pin_interrupt::(Some(event as u8), false); } @@ -298,21 +360,27 @@ impl Flex { #[cfg(any(esp32s2, esp32s3))] pub fn unlisten(&mut self) { enable_pin_interrupt::(Some(0), false); - self.clear_interrupts(); + self.clear_interrupt(); } /// Clear pin interrupts #[cfg(any(esp32s2, esp32s3))] - pub fn clear_interrupts(&mut self) { + pub fn clear_interrupt(&mut self) { clear_pin_interrupt::(); } + /// Read pin interrupt + #[cfg(any(esp32s2, esp32s3))] + pub fn is_interrupt_set(&mut self) -> bool { + is_interrupt_set::() + } + /// Enable pin as a wake-up source. /// This will change the interrupt type, when enabling. /// Interrupt type will not be changed, when disabling. #[cfg(any(esp32s2, esp32s3))] pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { - self.clear_interrupts(); + self.clear_interrupt(); if enable { let int_evt = Event::from(event); enable_pin_interrupt::(Some(int_evt as u8), true); @@ -348,6 +416,16 @@ impl Input { pub fn unlisten(&mut self) { self.pin.unlisten(); } + /// Clear pin interrupts + #[cfg(any(esp32s2, esp32s3))] + pub fn clear_interrupt(&mut self) { + clear_pin_interrupt::(); + } + /// Read pin interrupt + #[cfg(any(esp32s2, esp32s3))] + pub fn is_interrupt_set(&mut self) -> bool { + is_interrupt_set::() + } /// Enable pin as a wake-up source. /// This will change the interrupt type, when enabling. /// Interrupt type will not be changed, when disabling. @@ -422,6 +500,16 @@ impl OutputOpenDrain { pub fn unlisten(&mut self) { self.pin.unlisten(); } + /// Clear pin interrupts + #[cfg(any(esp32s2, esp32s3))] + pub fn clear_interrupt(&mut self) { + clear_pin_interrupt::(); + } + /// Read pin interrupt + #[cfg(any(esp32s2, esp32s3))] + pub fn is_interrupt_set(&mut self) -> bool { + is_interrupt_set::() + } /// Enable pin as a wake-up source. /// This will change the interrupt type, when enabling. /// Interrupt type will not be changed, when disabling. diff --git a/esp-lp-hal/src/interrupt.rs b/esp-lp-hal/src/interrupt.rs index 4221c314ffb..62fccc6882f 100644 --- a/esp-lp-hal/src/interrupt.rs +++ b/esp-lp-hal/src/interrupt.rs @@ -2,32 +2,66 @@ //! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. use core::ptr::NonNull; +/// Argument passed to SensInterrupt handler. +pub use pac::Interrupt as SensInterruptStatus; use portable_atomic::{AtomicPtr, Ordering}; -use procmacros::handler; - -use crate::{interrupt, pac}; +// use procmacros::handler; +use crate::pac; /// Argument passed to GpioInterrupt handler. pub type GpioInterruptPin = u32; -/// Argument passed to SensInterrupt handler. -pub use pac::Interrupt as SensInterruptStatus; + +// Interrupt vector table +// TODO: Merge back into esp-pacs +// ALT-TODO: Setup an __INTERRUPTS table in here, which contains a superset of +// __EXTERNAL_INTERRUPTS, and other peripheral interrupts not captured by the PAC. +// e.g. bits 0..=31 would be for 'external interrupts', +// and bits 32..=N would be for 'peripheral interrupts', or similar. +// This might allow the interrupt handling code in here to be more portable, maybe... +unsafe extern "C" { + fn GPIO_INT(); + fn SENS_INT(); +} + +/// Default interrupt handler, does nothing. +#[cfg(any(esp32s2, esp32s3))] +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub fn DefaultHandler() {} + +#[doc(hidden)] +#[repr(C)] +pub union Vector { + pub _handler: unsafe extern "C" fn(), + pub _reserved: usize, +} + +#[doc(hidden)] +#[unsafe(link_section = ".rwtext")] +#[unsafe(no_mangle)] +pub static __INTERRUPTS: [Vector; Interrupt::len()] = + [Vector { _handler: SENS_INT }, Vector { _handler: GPIO_INT }]; #[doc = r"Enumeration of all the interrupts."] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] #[repr(u16)] pub enum Interrupt { #[doc = "0 - SENS"] SENS = 0, #[doc = "1 - GPIO"] GPIO = 1, + #[doc(hidden)] + __LAST__, // Marker to auto-detect enum length. } #[doc = r" TryFromInterruptError"] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] pub struct TryFromInterruptError(()); + impl Interrupt { - #[doc = r" Attempt to convert a given value into an `Interrupt`"] + #[doc = r"Attempt to convert a given value into an `Interrupt`"] #[inline] pub fn try_from(value: u8) -> Result { match value { @@ -36,10 +70,59 @@ impl Interrupt { _ => Err(TryFromInterruptError(())), } } + + #[doc = r"Get the total number of interrupts."] + #[inline] + pub const fn len() -> usize { + Self::__LAST__ as usize + } +} + +const STATUS_WORDS: usize = 1; + +/// Representation of peripheral-interrupt status bits. +#[doc(hidden)] +#[derive(Clone, Copy, Default, Debug)] +pub struct InterruptStatus { + status: [u32; STATUS_WORDS], +} + +impl InterruptStatus { + /// Get status of peripheral interrupts + pub fn current() -> Self { + let mut status_bits = 0b00; + // Check SENS status + status_bits |= + ((unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits() != 0) as u32) << 0; + // Check GPIO status + status_bits |= ((unsafe { &*pac::RTC_IO::PTR }.status().read().bits() != 0) as u32) << 1; + InterruptStatus::from(status_bits) + } + + /// Is the given interrupt bit set + pub fn is_set(&self, interrupt: u8) -> bool { + (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 + } + + /// Set the given interrupt status bit + pub fn set(&mut self, interrupt: u8) { + self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); + } + + /// Return an iterator over the set interrupt status bits + pub fn iterator(&self) -> InterruptStatusIterator { + InterruptStatusIterator { + status: *self, + idx: 0, + } + } } -/// Convenience constant for `Option::None` pin -pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); +impl From for InterruptStatus { + fn from(value: u32) -> Self { + Self { status: [value] } + } +} pub(super) struct CFnPtr(AtomicPtr<()>); impl CFnPtr { @@ -59,81 +142,46 @@ impl CFnPtr { } } -/// If no handler is allocated, panic. -#[unsafe(no_mangle)] -extern "C" fn EspDefaultHandler() { - panic!("Unhandled interrupt"); +// /// If no handler is allocated, panic. +// #[unsafe(no_mangle)] +// extern "C" fn EspDefaultHandler() { +// panic!("Unhandled interrupt"); +// } +// /// Default (unhandled) interrupt handler +// pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new( +// { +// unsafe extern "C" { +// fn EspDefaultHandler(); +// } + +// unsafe { +// core::mem::transmute::(EspDefaultHandler) +// } +// }, +// Priority::min(), +// ); + +// Peripheral interrupt API. + +fn vector_entry(interrupt: Interrupt) -> &'static Vector { + &__INTERRUPTS[interrupt as usize] } -/// Default (unhandled) interrupt handler -pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new( - { - unsafe extern "C" { - fn EspDefaultHandler(); - } +/// Returns the currently bound interrupt handler. +pub fn bound_handler(interrupt: Interrupt) -> Option { + unsafe { + let vector = vector_entry(interrupt); - unsafe { - core::mem::transmute::(EspDefaultHandler) + let addr = vector._handler; + if addr as usize == 0 { + return None; } - }, - Priority::max(), -); - -/// The default GPIO interrupt handler, when the user has not set one. -/// -/// This handler will disable all pending interrupts and leave the interrupt -/// status bits unchanged. This enables functions like `is_interrupt_set` to -/// work correctly. -#[handler] -fn default_gpio_interrupt_handler() { - // Does nothing. - // DEFAULT_INTERRUPT_HANDLER.handler().callback()(); -} -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) { - // // We only allow binding the default handler if nothing else is bound. - // // This prevents silently overwriting RTIC's interrupt handler, if using GPIO. - // if handler != DEFAULT_INTERRUPT_HANDLER.handler() { - // // The user has configured an interrupt handler they wish to use. - // info!("Not using default GPIO interrupt handler: already bound in vector table"); - // return; - // } - // } - - // // The vector table doesn't contain a custom entry. Still, the - // // peripheral interrupt may already be bound to something else. - // for cpu in cores() { - // if interrupt::mapped_to(cpu, Interrupt::GPIO).is_some() { - // info!("Not using default GPIO interrupt handler: peripheral interrupt already in - // use"); return; - // } - // } - - interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); - - // // On ESP32, there are separate interrupt status registers for each core, we need to enable - // the // interrupt handler on each core otherwise GPIOs listening on the App CPU will not - // receive // interrupts. - // #[cfg(esp32)] - // crate::interrupt::enable_on_cpu( - // crate::system::Cpu::AppCpu, - // Interrupt::GPIO, - // Priority::Priority1, - // ); -} - -/// The user GPIO interrupt handler, when the user has set one. -/// -/// The user handler is responsible for clearing the interrupt status bits or disabling -/// the interrupts. -pub extern "C" fn user_gpio_interrupt_handler() { - // Call the user handler before clearing interrupts. The user can use the enable - // bits to determine which interrupts they are interested in. Clearing the - // interrupt status or enable bits have no effect on the rest of the - // interrupt handler. - USER_INTERRUPT_HANDLER.call(); + Some(IsrCallback::new(core::mem::transmute::< + unsafe extern "C" fn(), + extern "C" fn(), + >(addr))) + } } /// Binds the given handler to a peripheral interrupt. @@ -143,24 +191,12 @@ pub extern "C" fn user_gpio_interrupt_handler() { /// The interrupt handler will be called on the core where it is registered. /// Only one interrupt handler can be bound to a peripheral interrupt. pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { - todo!("Port this function to esp-lp-hal") - // unsafe { - // let vector = vector_entry(interrupt); - // let ptr = (&raw const vector._handler).cast::().cast_mut(); - // // On RISC-V MCUs we may be protecting the trap section using a watchpoint. - // // If we do, we need to temporarily disable this protection. - // #[cfg(all(riscv, write_vec_table_monitoring))] - // if crate::soc::trap_section_protected() { - // crate::debugger::DEBUGGER_LOCK.lock(|| { - // let wp = crate::debugger::clear_watchpoint(1); - // ptr.write_volatile(handler.handler().address()); - // crate::debugger::restore_watchpoint(1, wp); - // }); - // enable(interrupt, handler.priority()); - // return; - // } - // ptr.write_volatile(handler.handler().address()); - // } + unsafe { + let vector = vector_entry(interrupt); + let ptr = (&raw const vector._handler).cast::().cast_mut(); + ptr.write_volatile(handler.handler().address()); + } + // ULP unused // enable(interrupt, handler.priority()); } @@ -259,40 +295,6 @@ impl InterruptHandler { } } -const STATUS_WORDS: usize = 1; -/// Representation of peripheral-interrupt status bits. -#[doc(hidden)] -#[derive(Clone, Copy, Default, Debug)] -pub struct InterruptStatus { - status: [u32; STATUS_WORDS], -} - -impl InterruptStatus { - /// Is the given interrupt bit set - pub fn is_set(&self, interrupt: u8) -> bool { - (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 - } - - /// Set the given interrupt status bit - pub fn set(&mut self, interrupt: u8) { - self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); - } - - /// Return an iterator over the set interrupt status bits - pub fn iterator(&self) -> InterruptStatusIterator { - InterruptStatusIterator { - status: *self, - idx: 0, - } - } -} - -impl From for InterruptStatus { - fn from(value: u32) -> Self { - Self { status: [value] } - } -} - /// Iterator over set interrupt status bits #[doc(hidden)] #[derive(Debug, Clone)] @@ -318,8 +320,25 @@ impl Iterator for InterruptStatusIterator { } } -/// TODO: Move custom ULP instructions into their own module, -/// and cleanup the assembly files at the same time. +/// Setup interrupt handlers, including any default ones +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +pub fn setup_interrupts() { + crate::gpio::bind_default_interrupt_handler(); +} + +/// Enable all interrupts +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +pub fn enable() { + // Exit a critical section by enabling all interrupts + // This inline assembly construct is equivalent to: + // > maskirq_insn(zero, zero) + unsafe { + core::arch::asm!(".word 0x0600600b"); + } +} + /// Disable all interrupts #[cfg(any(esp32s2, esp32s3))] #[inline(always)] @@ -339,18 +358,6 @@ pub fn disable() { } } -/// Enable all interrupts -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn enable() { - // Exit a critical section by enabling all interrupts - // This inline assembly construct is equivalent to: - // > maskirq_insn(zero, zero) - unsafe { - core::arch::asm!(".word 0x0600600b"); - } -} - /// Wait for any (masked or unmasked) interrupt #[cfg(any(esp32s2, esp32s3))] pub fn waitirq() -> u32 { @@ -426,147 +433,174 @@ pub extern "C" fn ulp_start_trap_rust(trap_frame: *const u32, irqs: u32) { } } -/// Creates the trap_handler() function. -/// This is macro is used later in this file. -/// This style of macro is usually provided -/// for users to hook their own handlers, but here it's just used -/// as a quick way to generate the bit-mask code :) -#[cfg(any(esp32s2, esp32s3))] -macro_rules! build_trap_handler { - (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { - if $pending_irqs & (1 << $n) != 0 { - #[allow(unused_unsafe)] - unsafe { $handler($regs); } - } - }; - ( $( $irq:literal : $handler:ident ),* ) => { - /// Called by _start_trap_rust, this trap handler will call other interrupt handling - /// functions depending on the bits set in pending_irqs. - #[doc(hidden)] - #[unsafe(no_mangle)] - pub extern "C" fn trap_handler(regs: &TrapFrame, pending_irqs: u32) { - $( - build_trap_handler!(@interrupt($irq, pending_irqs, regs, $handler)); - )* - } - }; -} - -/// Default interrupt handler, does nothing. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[allow(non_snake_case)] -#[unsafe(no_mangle)] -pub fn DefaultHandler() {} - -/// Default illegal instruction or bus error exception handler. -/// This handler is dispatched by the trap_handler() function. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[allow(non_snake_case)] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub fn DefaultExceptionHandler(_regs: &TrapFrame) { - panic!("Unhandled exception!"); -} - -// Create the trap_handler function -#[cfg(any(esp32s2, esp32s3))] -build_trap_handler!( - 1: DefaultExceptionHandler, - 2: DefaultExceptionHandler, - 31: dispatch_peripheral_interrupt -); - -/// Peripheral interrupt handler for the IRQ bit 31. -/// Checks the SENS and RTC_IO interrup status, and dispatches further interrupt handlers as -/// appropriate. +/// Called by _start_trap_rust, this trap handler will call other interrupt handling +/// functions depending on the bits set in pending_irqs. #[cfg(any(esp32s2, esp32s3))] #[doc(hidden)] #[unsafe(no_mangle)] -fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { - // This function is based on the ESP-IDF implementation found here: - // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 - unsafe extern "Rust" { - fn SensInterrupt(status: SensInterruptStatus); - fn GpioInterrupt(pin: GpioInterruptPin); - } +pub extern "C" fn trap_handler(_regs: &TrapFrame, pending_irqs: u32) { + // Dispatch peripheral interrupt + if pending_irqs & (1 << 31) != 0 { + let status = InterruptStatus::current(); + + // Iterate the active interrupts, fetch their handler, and call it if set. + for interrupt_nr in status.iterator() { + // New, null-ptr-checking code. + if let Ok(i) = Interrupt::try_from(interrupt_nr) { + if let Some(handler) = bound_handler(i) { + handler.callback()(); + } + } - // Iterate for any SENS interrupt flags - let cocpu_int_st_bits = - InterruptStatus::from(unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits()); - // Iterate over the 1 bit positions - for bit in cocpu_int_st_bits.iterator() { - // Convert into the named interrupt enumeration - if let Ok(stat) = SensInterruptStatus::try_from(bit) { - // Call handler, and clear the interrupt bit. - unsafe { SensInterrupt(stat) }; - - unsafe { &*pac::SENS::PTR } - .sar_cocpu_int_clr() - .write(|w| unsafe { w.bits(1 << bit) }); + // Original code + // unsafe { + // let handler = crate::interrupt::__INTERRUPTS[interrupt_nr as usize]._handler; + // handler(); + // } } } - - // RTC IO interrupts. - let rtcio_int_st_bits = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); - let rtcio_int_st = InterruptStatus::from(rtcio_int_st_bits); - // Iterate over the 1 bit positions - for bit in rtcio_int_st.iterator() { - // Call handler, and clear the interrupt bit. - // Pin must have 10 subtracted from it, due to register offset. - unsafe { GpioInterrupt((bit - 10) as u32) }; - - unsafe { &*pac::RTC_IO::PTR } - .status_w1tc() - .write(|w| unsafe { w.bits(1 << bit) }); - } -} - -// Macros bind user functions to SensInterrupt and GpioInterrupt. - -#[macro_export] -#[doc = r" Assigns a handler to GpioInterrupt"] -#[doc = r""] -#[doc = r" This macro takes one argument: path to the function that"] -#[doc = r" will be used as the handler of that interrupt. The function"] -#[doc = r" must have signature `fn(pin : GpioInterruptPin)`."] -#[doc = r""] -#[doc = r" # Example"] -#[doc = r""] -#[doc = r" ``` ignore"] -#[doc = r" gpio_interrupt!(buttons_handler);"] -#[doc = r""] -#[doc = r" fn buttons_handler(_pin : GpioInterruptPin) {"] -#[doc = r#" print!("A GPIO pin was pressed!");"#] -#[doc = r" }"] -#[doc = r""] -macro_rules! gpio_interrupt { - ($ path : path) => { - #[allow(non_snake_case)] - #[unsafe(no_mangle)] - pub extern "C" fn GpioInterrupt(pin: GpioInterruptPin) { - let f: fn(pin: GpioInterruptPin) = $path; - f(pin); - } - }; -} - -#[macro_export] -#[doc = r" Assigns a handler to SensInterrupt"] -macro_rules! sens_interrupt { - ($ path : path) => { - #[allow(non_snake_case)] - #[unsafe(no_mangle)] - pub extern "C" fn SensInterrupt(status: SensInterruptStatus) { - let f: fn(status: SensInterruptStatus) = $path; - f(status); - } - }; } -#[allow(unused)] -pub use gpio_interrupt; -#[allow(unused)] -pub use sens_interrupt; +// /// Creates the trap_handler() function. +// /// This is macro is used later in this file. +// /// This style of macro is usually provided +// /// for users to hook their own handlers, but here it's just used +// /// as a quick way to generate the bit-mask code :) +// #[cfg(any(esp32s2, esp32s3))] +// macro_rules! build_trap_handler { +// (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { +// if $pending_irqs & (1 << $n) != 0 { +// #[allow(unused_unsafe)] +// unsafe { $handler($regs); } +// } +// }; +// ( $( $irq:literal : $handler:ident ),* ) => { +// /// Called by _start_trap_rust, this trap handler will call other interrupt handling +// /// functions depending on the bits set in pending_irqs. +// #[doc(hidden)] +// #[unsafe(no_mangle)] +// pub extern "C" fn trap_handler(regs: &TrapFrame, pending_irqs: u32) { +// $( +// build_trap_handler!(@interrupt($irq, pending_irqs, regs, $handler)); +// )* +// } +// }; +// } + +// /// Default interrupt handler, does nothing. +// #[cfg(any(esp32s2, esp32s3))] +// #[allow(dead_code)] +// #[doc(hidden)] +// #[allow(non_snake_case)] +// #[unsafe(no_mangle)] +// pub fn DefaultHandler() {} + +// /// Default illegal instruction or bus error exception handler. +// /// This handler is dispatched by the trap_handler() function. +// #[cfg(any(esp32s2, esp32s3))] +// #[allow(dead_code)] +// #[allow(non_snake_case)] +// #[doc(hidden)] +// #[unsafe(no_mangle)] +// pub fn DefaultExceptionHandler(_regs: &TrapFrame) { +// panic!("Unhandled exception!"); +// } + +// // Create the trap_handler function +// #[cfg(any(esp32s2, esp32s3))] +// build_trap_handler!( +// // 1: DefaultExceptionHandler, +// // 2: DefaultExceptionHandler, +// 31: dispatch_peripheral_interrupt +// ); + +// /// Peripheral interrupt handler for the IRQ bit 31. +// /// Checks the SENS and RTC_IO interrup status, and dispatches further interrupt handlers as +// /// appropriate. +// #[cfg(any(esp32s2, esp32s3))] +// #[doc(hidden)] +// #[unsafe(no_mangle)] +// fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { +// // This function is based on the ESP-IDF implementation found here: +// // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 +// unsafe extern "Rust" { +// fn SensInterrupt(status: SensInterruptStatus); +// fn GpioInterrupt(pin: GpioInterruptPin); +// } + +// // Iterate for any SENS interrupt flags +// let cocpu_int_st_bits = InterruptStatus::from(unsafe { &*pac::SENS::PTR +// }.sar_cocpu_int_st().read().bits()); // Iterate over the 1 bit positions +// for bit in cocpu_int_st_bits.iterator() { +// // Convert into the named interrupt enumeration +// if let Ok(stat) = SensInterruptStatus::try_from(bit) { +// // Call handler, and clear the interrupt bit. +// unsafe { SensInterrupt(stat) }; + +// unsafe { &*pac::SENS::PTR } +// .sar_cocpu_int_clr() +// .write(|w| unsafe { w.bits(1 << bit) }); +// } +// } + +// // RTC IO interrupts. +// let rtcio_int_st_bits = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); +// let rtcio_int_st = InterruptStatus::from(rtcio_int_st_bits); +// // Iterate over the 1 bit positions +// for bit in rtcio_int_st.iterator() { +// // Call handler, and clear the interrupt bit. +// // Pin must have 10 subtracted from it, due to register offset. +// unsafe { GpioInterrupt((bit - 10) as u32) }; + +// unsafe { &*pac::RTC_IO::PTR } +// .status_w1tc() +// .write(|w| unsafe { w.bits(1 << bit) }); +// } +// } + +// // Macros bind user functions to SensInterrupt and GpioInterrupt. + +// #[macro_export] +// #[doc = r" Assigns a handler to GpioInterrupt"] +// #[doc = r""] +// #[doc = r" This macro takes one argument: path to the function that"] +// #[doc = r" will be used as the handler of that interrupt. The function"] +// #[doc = r" must have signature `fn(pin : GpioInterruptPin)`."] +// #[doc = r""] +// #[doc = r" # Example"] +// #[doc = r""] +// #[doc = r" ``` ignore"] +// #[doc = r" gpio_interrupt!(buttons_handler);"] +// #[doc = r""] +// #[doc = r" fn buttons_handler(_pin : GpioInterruptPin) {"] +// #[doc = r#" print!("A GPIO pin was pressed!");"#] +// #[doc = r" }"] +// #[doc = r""] +// macro_rules! gpio_interrupt { +// ($ path : path) => { +// #[allow(non_snake_case)] +// #[unsafe(no_mangle)] +// pub extern "C" fn GpioInterrupt(pin: GpioInterruptPin) { +// let f: fn(pin: GpioInterruptPin) = $path; +// f(pin); +// } +// }; +// } + +// #[macro_export] +// #[doc = r" Assigns a handler to SensInterrupt"] +// macro_rules! sens_interrupt { +// ($ path : path) => { +// #[allow(non_snake_case)] +// #[unsafe(no_mangle)] +// pub extern "C" fn SensInterrupt(status: SensInterruptStatus) { +// let f: fn(status: SensInterruptStatus) = $path; +// f(status); +// } +// }; +// } + +// #[allow(unused)] +// pub use gpio_interrupt; +// #[allow(unused)] +// pub use sens_interrupt; diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index c890199936b..9d3e86b692a 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -44,7 +44,7 @@ pub mod interrupt; /// The prelude pub mod prelude { - pub use procmacros::entry; + pub use procmacros::{entry, handler}; } cfg_if::cfg_if! { @@ -96,6 +96,7 @@ unsafe extern "C" fn lp_core_startup() -> ! { #[cfg(any(esp32s2, esp32s3))] { ulp_riscv_rescue_from_monitor(); + interrupt::setup_interrupts(); interrupt::enable(); } From f03e89b7e9f383156ab89371ccc0001df49b2646 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 31 Mar 2026 07:21:41 +1100 Subject: [PATCH 15/47] Cleanup feature gating, add non-working critical section impl --- esp-lp-hal/Cargo.toml | 11 +++-- esp-lp-hal/examples/interrupt_counter.rs | 62 ++++++++++++------------ esp-lp-hal/src/critical_section.rs | 20 ++++++++ esp-lp-hal/src/gpio.rs | 20 ++++---- esp-lp-hal/src/interrupt.rs | 4 +- esp-lp-hal/src/lib.rs | 4 ++ 6 files changed, 74 insertions(+), 47 deletions(-) create mode 100644 esp-lp-hal/src/critical_section.rs diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index c8075540f92..3ff333648e3 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -43,12 +43,13 @@ embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } nb = { version = "1.1.0", optional = true } procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -riscv = { version = "0.15", features = ["critical-section-single-hart"] } +riscv = { version = "0.15" } +portable-atomic = { version = "1", default-features = false } +critical-section = { version = "1.1.1", features = ["restore-state-bool"] , optional = true } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "663c742" } -portable-atomic = { version = "1.13.1", default-features = false, features = ["unsafe-assume-single-core"] } [dev-dependencies] panic-halt = "0.2.0" @@ -68,11 +69,11 @@ debug = [ # Chip Support Feature Flags # Target the ESP32-C6. -esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb"] +esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb", "riscv/critical-section-single-hart"] # Target the ESP32-S2. -esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core"] +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core", "dep:critical-section" ] # Target the ESP32-S3. -esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core"] +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core", "dep:critical-section" ] #! ### Trait Implementation Feature Flags ## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index d17b6af058d..7f0735a002f 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -7,49 +7,49 @@ #![no_std] #![no_main] extern crate panic_halt; -// TODO: I'd prefer if these were proc-macros, which could be used as function attributes. -// This would 1) look nicer, and 2) provide better discoverability/type-hinting of the -// interrupts available for use. + use esp_lp_hal::{ - gpio::{Event, Input}, - interrupts::{GpioInterruptPin, SensInterruptStatus, gpio_interrupt, sens_interrupt}, + gpio::{self, Event, Input, Io}, + interrupt, + pac::Peripherals, prelude::*, }; // Shared memory address. const ADDRESS: u32 = 0x1000; -pub fn on_start(status: SensInterruptStatus) { - if status == SensInterruptStatus::RISCV_START_INT { - // Did we get a startup interrupt? If so, increment counter - let ptr = ADDRESS as *mut u32; - let i = unsafe { ptr.read_volatile() }; - unsafe { - ptr.write_volatile(i + 1); - } +fn increment_counter() { + let ptr = ADDRESS as *mut u32; + let i = unsafe { ptr.read_volatile() }; + unsafe { + ptr.write_volatile(i + 1); } } - -sens_interrupt!(on_start); - -pub fn on_button(pin: GpioInterruptPin) { - // Reset the counter on GPIO0 - if pin == 0 { - let ptr = ADDRESS as *mut u32; - unsafe { - ptr.write_volatile(0); - } +fn reset_counter() { + let ptr = ADDRESS as *mut u32; + unsafe { + ptr.write_volatile(0); } } -gpio_interrupt!(on_button); - #[entry] -fn main(mut boot_pin: Input<0>) { - // Enable start-up interrupt, called shortly after boot. - unsafe { &*esp_lp_hal::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_start_int_ena().set_bit()); +fn main(mut boot_button: Input<0>) { + // Get the io peripheral, and bind a handler to it. + // let peripherals = Peripherals::take().unwrap(); // Requires critical_section + let peripherals = unsafe { Peripherals::steal() }; + let mut io = Io::new(peripherals.RTC_IO); + io.set_interrupt_handler(gpio_interrupt_handler); + boot_button.listen(Event::FallingEdge); + + increment_counter(); +} - boot_pin.listen(Event::FallingEdge); +#[handler] +fn gpio_interrupt_handler() { + // TODO: Create an enum for each GPIO pin? Maybe? + let status = gpio::gpio_interrupt_status(); + if status & (0b1) != 0 { + reset_counter(); + } + gpio::gpio_interrupt_clear(status); } diff --git a/esp-lp-hal/src/critical_section.rs b/esp-lp-hal/src/critical_section.rs new file mode 100644 index 00000000000..a83977083c6 --- /dev/null +++ b/esp-lp-hal/src/critical_section.rs @@ -0,0 +1,20 @@ +use critical_section::RawRestoreState; + +use crate::interrupt; + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState { + interrupt::disable(); + true + } + + unsafe fn release(was_active: RawRestoreState) { + // Only re-enable interrupts if they were enabled before the critical section. + if was_active { + interrupt::enable(); + } + } +} diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio.rs index 92ad299f10f..ba7e970d782 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio.rs @@ -22,11 +22,13 @@ //! } //! } //! ``` +#[cfg(any(esp32s2, esp32s3))] use procmacros::handler; +#[cfg(any(esp32s2, esp32s3))] use crate::{ interrupt, - interrupt::{CFnPtr, Interrupt, InterruptHandler, Priority}, + interrupt::{CFnPtr, Interrupt, InterruptHandler}, }; cfg_if::cfg_if! { @@ -42,12 +44,14 @@ cfg_if::cfg_if! { } /// Convenience constant for `Option::None` pin +#[cfg(any(esp32s2, esp32s3))] pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); /// The user GPIO interrupt handler, when the user has set one. /// /// The user handler is responsible for clearing the interrupt status bits or disabling /// the interrupts. +#[cfg(any(esp32s2, esp32s3))] #[handler] fn user_gpio_interrupt_handler() { // Call the user handler before clearing interrupts. The user can use the enable @@ -62,12 +66,14 @@ fn user_gpio_interrupt_handler() { /// This handler will disable all pending interrupts and leave the interrupt /// status bits unchanged. This enables functions like `is_interrupt_set` to /// work correctly. +#[cfg(any(esp32s2, esp32s3))] #[handler] fn default_gpio_interrupt_handler() { let status = gpio_interrupt_status(); gpio_interrupt_disable(status); } +#[cfg(any(esp32s2, esp32s3))] #[doc(hidden)] pub fn bind_default_interrupt_handler() { interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); @@ -81,11 +87,10 @@ pub struct Io { impl Io { /// Initialize the I/O driver. pub fn new(_io_peripheral: LpIo) -> Self { - Io { - _io_peripheral: _io_peripheral, - } + Io { _io_peripheral } } + #[cfg(any(esp32s2, esp32s3))] #[doc = "Registers an interrupt handler for all GPIO pins."] /// Note that when using interrupt handlers registered by this function, or /// by defining a `#[no_mangle] unsafe extern "C" fn GPIO()` function, we do @@ -105,14 +110,11 @@ impl Io { pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { USER_INTERRUPT_HANDLER.store(handler.handler().callback()); - crate::interrupt::bind_handler( - Interrupt::GPIO, - user_gpio_interrupt_handler, - // InterruptHandler::new(user_gpio_interrupt_handler, Priority::min()), - ); + crate::interrupt::bind_handler(Interrupt::GPIO, user_gpio_interrupt_handler); } } +#[cfg(any(esp32s2, esp32s3))] impl crate::interrupt::InterruptConfigurable for Io { fn set_interrupt_handler(&mut self, handler: InterruptHandler) { self.set_interrupt_handler(handler); diff --git a/esp-lp-hal/src/interrupt.rs b/esp-lp-hal/src/interrupt.rs index 62fccc6882f..8847f71df85 100644 --- a/esp-lp-hal/src/interrupt.rs +++ b/esp-lp-hal/src/interrupt.rs @@ -47,6 +47,7 @@ pub static __INTERRUPTS: [Vector; Interrupt::len()] = #[doc = r"Enumeration of all the interrupts."] #[derive(Copy, Clone, PartialEq, Eq)] #[repr(u16)] +#[non_exhaustive] pub enum Interrupt { #[doc = "0 - SENS"] SENS = 0, @@ -92,8 +93,7 @@ impl InterruptStatus { pub fn current() -> Self { let mut status_bits = 0b00; // Check SENS status - status_bits |= - ((unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits() != 0) as u32) << 0; + status_bits |= (unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits() != 0) as u32; // Check GPIO status status_bits |= ((unsafe { &*pac::RTC_IO::PTR }.status().read().bits() != 0) as u32) << 1; InterruptStatus::from(status_bits) diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 9d3e86b692a..e98fa4f7604 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,6 +38,10 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; +/// Critical section implementation for ULP cores +#[cfg(any(esp32s2, esp32s3))] +pub mod critical_section; + /// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) #[cfg(any(esp32s2, esp32s3))] pub mod interrupt; From fce1661377943b06d6509cb0b318f96e736f8f03 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 07:46:43 +1100 Subject: [PATCH 16/47] Updated critical section implementation to handle nesting properly --- esp-lp-hal/Cargo.toml | 6 +- esp-lp-hal/examples/interrupt_counter.rs | 3 +- esp-lp-hal/src/critical_section.rs | 18 +++--- esp-lp-hal/src/interrupt.rs | 79 ++++++++++-------------- 4 files changed, 43 insertions(+), 63 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 1831b71ab28..153a8b16827 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -45,11 +45,11 @@ nb = { version = "1.1.0", optional = true } procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } -critical-section = { version = "1.1.1", features = ["restore-state-bool"] , optional = true } +critical-section = { version = "1.1.1", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 7f0735a002f..d6e39cac168 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -35,8 +35,7 @@ fn reset_counter() { #[entry] fn main(mut boot_button: Input<0>) { // Get the io peripheral, and bind a handler to it. - // let peripherals = Peripherals::take().unwrap(); // Requires critical_section - let peripherals = unsafe { Peripherals::steal() }; + let peripherals = Peripherals::take(); let mut io = Io::new(peripherals.RTC_IO); io.set_interrupt_handler(gpio_interrupt_handler); boot_button.listen(Event::FallingEdge); diff --git a/esp-lp-hal/src/critical_section.rs b/esp-lp-hal/src/critical_section.rs index a83977083c6..67a47c250c2 100644 --- a/esp-lp-hal/src/critical_section.rs +++ b/esp-lp-hal/src/critical_section.rs @@ -2,19 +2,17 @@ use critical_section::RawRestoreState; use crate::interrupt; -struct CriticalSection; -critical_section::set_impl!(CriticalSection); +struct UlpCriticalSection; +critical_section::set_impl!(UlpCriticalSection); -unsafe impl critical_section::Impl for CriticalSection { +unsafe impl critical_section::Impl for UlpCriticalSection { unsafe fn acquire() -> RawRestoreState { - interrupt::disable(); - true + interrupt::disable() } - unsafe fn release(was_active: RawRestoreState) { - // Only re-enable interrupts if they were enabled before the critical section. - if was_active { - interrupt::enable(); - } + unsafe fn release(previous_state: RawRestoreState) { + // Only re-enable interrupts if that were enabled before the critical section. + // interrupt::enable(); + interrupt::maskirq(previous_state); } } diff --git a/esp-lp-hal/src/interrupt.rs b/esp-lp-hal/src/interrupt.rs index 8847f71df85..89b4971b753 100644 --- a/esp-lp-hal/src/interrupt.rs +++ b/esp-lp-hal/src/interrupt.rs @@ -142,25 +142,6 @@ impl CFnPtr { } } -// /// If no handler is allocated, panic. -// #[unsafe(no_mangle)] -// extern "C" fn EspDefaultHandler() { -// panic!("Unhandled interrupt"); -// } -// /// Default (unhandled) interrupt handler -// pub const DEFAULT_INTERRUPT_HANDLER: InterruptHandler = InterruptHandler::new( -// { -// unsafe extern "C" { -// fn EspDefaultHandler(); -// } - -// unsafe { -// core::mem::transmute::(EspDefaultHandler) -// } -// }, -// Priority::min(), -// ); - // Peripheral interrupt API. fn vector_entry(interrupt: Interrupt) -> &'static Vector { @@ -196,7 +177,8 @@ pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { let ptr = (&raw const vector._handler).cast::().cast_mut(); ptr.write_volatile(handler.handler().address()); } - // ULP unused + // Unused, because ULP does not have distinct + // interrupt sources for every interrupt type we want to handle // enable(interrupt, handler.priority()); } @@ -327,35 +309,43 @@ pub fn setup_interrupts() { crate::gpio::bind_default_interrupt_handler(); } -/// Enable all interrupts +/// Set the IRQ Mask, returns the previous mask value. #[cfg(any(esp32s2, esp32s3))] #[inline(always)] -pub fn enable() { - // Exit a critical section by enabling all interrupts - // This inline assembly construct is equivalent to: - // > maskirq_insn(zero, zero) +pub fn maskirq(new_mask: u32) -> u32 { + let old_mask: u32; unsafe { - core::arch::asm!(".word 0x0600600b"); + // This instruction copies the value of the register IRQ Mask to the register rd, + // and copies the value of register rs to IRQ Mask. + core::arch::asm!( + "maskirq_insn {}, {}", + out(reg) old_mask, + in(reg) new_mask + ); } + old_mask } -/// Disable all interrupts +/// Enable all interrupts by setting IRQ mask #[cfg(any(esp32s2, esp32s3))] #[inline(always)] -pub fn disable() { - // Enter a critical section by disabling all interrupts - // This inline assembly construct uses the t0 register and is equivalent to: - // > li t0, 0x80000007 - // > maskirq_insn(zero, t0) // Mask all interrupt bits - // The mask 0x80000007 represents: - // Bit 31 - RTC peripheral interrupt - // Bit 2 - Bus error - // Bit 1 - Ebreak / Ecall / Illegal Instruction - // Bit 0 - Internal Timer - // - unsafe { - core::arch::asm!("li t0, 0x80000007", ".word 0x0602e00b"); - } +pub fn enable() { + maskirq(0x0); +} + +/// Disable all interrupts by clearing IRQ mask, +/// returns the previous IRQ Mask +#[cfg(any(esp32s2, esp32s3))] +#[inline(always)] +pub fn disable() -> u32 { + // Enter a critical section by disabling all interrupts. + // Type Bit Description + // Internal 0 Internal timer interrupt + // Internal 1 EBREAK/ECALL or Illegal Instruction + // Internal 2 BUS Error (Unaligned Memory Access) + // External 31 RTC peripheral interrupts + let mask = (1 << 31) | (1 << 2) | (1 << 1) | (1 << 0); + maskirq(mask) } /// Wait for any (masked or unmasked) interrupt @@ -445,18 +435,11 @@ pub extern "C" fn trap_handler(_regs: &TrapFrame, pending_irqs: u32) { // Iterate the active interrupts, fetch their handler, and call it if set. for interrupt_nr in status.iterator() { - // New, null-ptr-checking code. if let Ok(i) = Interrupt::try_from(interrupt_nr) { if let Some(handler) = bound_handler(i) { handler.callback()(); } } - - // Original code - // unsafe { - // let handler = crate::interrupt::__INTERRUPTS[interrupt_nr as usize]._handler; - // handler(); - // } } } } From 250fe93894063f533e3d67cff6034860d4ad528e Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 08:24:22 +1100 Subject: [PATCH 17/47] WIP Switch to using interrupt vectors exported by PAC --- esp-lp-hal/Cargo.toml | 14 +- esp-lp-hal/examples/interrupt_counter.rs | 11 +- esp-lp-hal/ld/link-ulp.x | 3 + esp-lp-hal/src/critical_section.rs | 7 +- esp-lp-hal/src/gpio/conjure.rs | 31 ++ esp-lp-hal/src/gpio/ehal.rs | 77 +++ esp-lp-hal/src/gpio/interrupt.rs | 128 +++++ esp-lp-hal/src/{gpio.rs => gpio/mod.rs} | 262 +--------- esp-lp-hal/src/interrupt.rs | 589 ----------------------- esp-lp-hal/src/interrupt/generic.rs | 230 +++++++++ esp-lp-hal/src/interrupt/mod.rs | 145 ++++++ esp-lp-hal/src/interrupt/riscv_ulp.rs | 138 ++++++ esp-lp-hal/src/lib.rs | 2 +- 13 files changed, 787 insertions(+), 850 deletions(-) create mode 100644 esp-lp-hal/src/gpio/conjure.rs create mode 100644 esp-lp-hal/src/gpio/ehal.rs create mode 100644 esp-lp-hal/src/gpio/interrupt.rs rename esp-lp-hal/src/{gpio.rs => gpio/mod.rs} (61%) delete mode 100644 esp-lp-hal/src/interrupt.rs create mode 100644 esp-lp-hal/src/interrupt/generic.rs create mode 100644 esp-lp-hal/src/interrupt/mod.rs create mode 100644 esp-lp-hal/src/interrupt/riscv_ulp.rs diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 153a8b16827..5c9654a99ff 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -47,9 +47,9 @@ riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } critical-section = { version = "1.1.1", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "879efa6" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } [dev-dependencies] panic-halt = "0.2.0" @@ -87,7 +87,13 @@ name = "blinky" required-features = [] [[example]] -name = "interrupt_counter" +name = "interrupt_counter_s2" +path = "./examples/interrupt_counter.rs" +required-features = ["esp32s2"] + +[[example]] +name = "interrupt_counter_s3" +path = "./examples/interrupt_counter.rs" required-features = ["esp32s3"] [[example]] diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index d6e39cac168..735014c39e7 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -2,14 +2,14 @@ //! Increments a 32 bit counter value at a known point in memory, //! whenever the ULP program is run. If GPIO0 is pressed, reset the counter. -//% CHIPS: esp32s3 +//% CHIPS: esp32s2, esp32s3 #![no_std] #![no_main] extern crate panic_halt; use esp_lp_hal::{ - gpio::{self, Event, Input, Io}, + gpio::{Event, Input, Io, gpio_interrupt_clear, gpio_interrupt_status}, interrupt, pac::Peripherals, prelude::*, @@ -34,10 +34,10 @@ fn reset_counter() { #[entry] fn main(mut boot_button: Input<0>) { - // Get the io peripheral, and bind a handler to it. let peripherals = Peripherals::take(); let mut io = Io::new(peripherals.RTC_IO); io.set_interrupt_handler(gpio_interrupt_handler); + boot_button.listen(Event::FallingEdge); increment_counter(); @@ -45,10 +45,9 @@ fn main(mut boot_button: Input<0>) { #[handler] fn gpio_interrupt_handler() { - // TODO: Create an enum for each GPIO pin? Maybe? - let status = gpio::gpio_interrupt_status(); + let status = gpio_interrupt_status(); if status & (0b1) != 0 { reset_counter(); } - gpio::gpio_interrupt_clear(status); + gpio_interrupt_clear(status); } diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 0c287815679..26da1d075a8 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* Provides interrupt vector symbols */ +INCLUDE device.x + ENTRY(reset_vector) CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; diff --git a/esp-lp-hal/src/critical_section.rs b/esp-lp-hal/src/critical_section.rs index 67a47c250c2..37723fa4da2 100644 --- a/esp-lp-hal/src/critical_section.rs +++ b/esp-lp-hal/src/critical_section.rs @@ -7,12 +7,11 @@ critical_section::set_impl!(UlpCriticalSection); unsafe impl critical_section::Impl for UlpCriticalSection { unsafe fn acquire() -> RawRestoreState { - interrupt::disable() + interrupt::disable_cpu_interrupts() } unsafe fn release(previous_state: RawRestoreState) { - // Only re-enable interrupts if that were enabled before the critical section. - // interrupt::enable(); - interrupt::maskirq(previous_state); + // Only re-enable interrupts if they were enabled before the critical section. + interrupt::mask_cpu_interrupts(previous_state); } } diff --git a/esp-lp-hal/src/gpio/conjure.rs b/esp-lp-hal/src/gpio/conjure.rs new file mode 100644 index 00000000000..5721d96d0aa --- /dev/null +++ b/esp-lp-hal/src/gpio/conjure.rs @@ -0,0 +1,31 @@ +use super::{Input, MAX_GPIO_PIN, Output, OutputOpenDrain}; + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_output() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Output::::new()) + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_output_open_drain() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(OutputOpenDrain::::new()) + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_input() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Input::::new()) + } +} diff --git a/esp-lp-hal/src/gpio/ehal.rs b/esp-lp-hal/src/gpio/ehal.rs new file mode 100644 index 00000000000..f9c604ba285 --- /dev/null +++ b/esp-lp-hal/src/gpio/ehal.rs @@ -0,0 +1,77 @@ +use super::{Input, Level, Output, OutputOpenDrain}; + +impl embedded_hal::digital::ErrorType for Input { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::ErrorType for Output { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::ErrorType for OutputOpenDrain { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::InputPin for Input { + fn is_high(&mut self) -> Result { + Ok(self.level() == Level::High) + } + + fn is_low(&mut self) -> Result { + Ok(self.level() == Level::Low) + } +} + +impl embedded_hal::digital::OutputPin for Output { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::Low); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::High); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for Output { + fn is_set_high(&mut self) -> Result { + Ok(self.output_level() == Level::High) + } + + fn is_set_low(&mut self) -> Result { + Ok(self.output_level() == Level::Low) + } +} + +impl embedded_hal::digital::InputPin for OutputOpenDrain { + fn is_high(&mut self) -> Result { + Ok(self.level() == Level::High) + } + + fn is_low(&mut self) -> Result { + Ok(self.level() == Level::Low) + } +} + +impl embedded_hal::digital::OutputPin for OutputOpenDrain { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::Low); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::High); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for OutputOpenDrain { + fn is_set_high(&mut self) -> Result { + Ok(self.output_level() == Level::High) + } + + fn is_set_low(&mut self) -> Result { + Ok(self.output_level() == Level::Low) + } +} diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs new file mode 100644 index 00000000000..98fd1f2f90b --- /dev/null +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -0,0 +1,128 @@ +pub use interrupt::{Interrupt, InterruptHandler, InterruptStatus}; +pub use procmacros::handler; + +use super::{Io, LpIo}; +use crate::interrupt; + +/// Convenience constant for `Option::None` pin +pub(super) static USER_INTERRUPT_HANDLER: interrupt::CFnPtr = interrupt::CFnPtr::new(); + +impl crate::interrupt::InterruptConfigurable for Io { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +/// The user GPIO interrupt handler, when the user has set one. +/// +/// The user handler is responsible for clearing the interrupt status bits or disabling +/// the interrupts. +#[handler] +pub fn user_gpio_interrupt_handler() { + // Call the user handler before clearing interrupts. The user can use the enable + // bits to determine which interrupts they are interested in. Clearing the + // interrupt status or enable bits have no effect on the rest of the + // interrupt handler. + USER_INTERRUPT_HANDLER.call(); +} + +/// The default GPIO interrupt handler, when the user has not set one. +/// +/// This handler will disable all pending interrupts and leave the interrupt +/// status bits unchanged. This enables functions like `is_interrupt_set` to +/// work correctly. +#[handler] +fn default_gpio_interrupt_handler() { + let status = gpio_interrupt_status(); + gpio_interrupt_disable(status); +} + +#[doc(hidden)] +pub fn bind_default_interrupt_handler() { + interrupt::bind_handler(Interrupt::GPIO_INT, default_gpio_interrupt_handler); +} + +/// Read the interrupt status of all pins +/// Bit 0 == GPIO0, in the returned value +#[inline] +pub fn gpio_interrupt_status() -> u32 { + unsafe { &*LpIo::PTR }.status().read().bits() >> 10 +} + +/// Clear the interrupt status for a bit mask of pins +/// Expects pinmask bit 0 == GPIO0 +#[inline] +pub fn gpio_interrupt_clear(pinmask: u32) { + unsafe { &*LpIo::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(pinmask << 10) }); +} + +/// Disable interrupts for a bit mask of pins +fn gpio_interrupt_disable(pin_mask: u32) { + // expects pinmask bit 0 == GPIO0 + let pin_iter = InterruptStatus::from(pin_mask).iterator(); + let gpio_bank_reg = LpIo::ptr() as usize; + let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); + + for n in pin_iter { + let gpio_base = gpio_bank_reg + 0x28 + ((n as usize) * 4); + let gpio_pin = gpio_base as *mut u32; + // Read settings, clear int_type flags, write settings + let mut pin_setting = unsafe { gpio_pin.read_volatile() }; + pin_setting &= int_type_mask; + unsafe { gpio_pin.write_volatile(pin_setting) } + } +} + +/// Set GPIO event listening. +/// +/// - `N`: the pin to configure +/// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will +/// leave the int_type setting as-is. +/// - `wake_up`: whether to wake up from light sleep. +pub fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) { + // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); + // TODO: Add LpIo::regs().pins(number : usize) to esp-pacs + let gpio_bank_reg = LpIo::ptr() as usize; + let gpio_base = gpio_bank_reg + 0x28 + ((N as usize) * 4); + let gpio_pin = gpio_base as *mut u32; + + // Read the current setting + let mut pin_setting = unsafe { gpio_pin.read_volatile() }; + + // Write int_type if specified + if let Some(int_type) = int_type { + // Bits 7:9 + // - 0: GPIO interrupt disable + // - 1: rising edge trigger + // - 2: falling edge trigger + // - 3: any edge trigger + // - 4: low level trigger + // - 5: high level trigger + let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); + pin_setting &= int_type_mask; + pin_setting |= (int_type as u32) << 7; + } + + // Bit 10, wakeup enable. + // Used with UlpCoreWakeupSource::Gpio. + let wakeup_en_mask: u32 = 0xFFFFFFFF ^ (0b1 << 10); + pin_setting &= wakeup_en_mask; + if wakeup_enable { + pin_setting |= 0b1 << 10; + } + + unsafe { gpio_pin.write_volatile(pin_setting) } +} + +/// Clear pin interrupt +pub fn clear_pin_interrupt() { + gpio_interrupt_clear(1 << N); +} + +/// Read pin interrupt status +pub fn is_interrupt_set() -> bool { + let stat = gpio_interrupt_status(); + (stat & (1 << N)) != 0 +} diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio/mod.rs similarity index 61% rename from esp-lp-hal/src/gpio.rs rename to esp-lp-hal/src/gpio/mod.rs index ba7e970d782..273be87a485 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -22,14 +22,20 @@ //! } //! } //! ``` -#[cfg(any(esp32s2, esp32s3))] -use procmacros::handler; + +/// Used by the `entry` procmacro +pub mod conjure; +pub use conjure::*; + +#[cfg(feature = "embedded-hal")] +/// Provides embedded-hal trait impls +pub mod ehal; #[cfg(any(esp32s2, esp32s3))] -use crate::{ - interrupt, - interrupt::{CFnPtr, Interrupt, InterruptHandler}, -}; +/// Provides GPIO interrupt support +pub mod interrupt; +#[cfg(any(esp32s2, esp32s3))] +pub use interrupt::*; cfg_if::cfg_if! { if #[cfg(esp32c6)] { @@ -43,42 +49,6 @@ cfg_if::cfg_if! { } } -/// Convenience constant for `Option::None` pin -#[cfg(any(esp32s2, esp32s3))] -pub(super) static USER_INTERRUPT_HANDLER: CFnPtr = CFnPtr::new(); - -/// The user GPIO interrupt handler, when the user has set one. -/// -/// The user handler is responsible for clearing the interrupt status bits or disabling -/// the interrupts. -#[cfg(any(esp32s2, esp32s3))] -#[handler] -fn user_gpio_interrupt_handler() { - // Call the user handler before clearing interrupts. The user can use the enable - // bits to determine which interrupts they are interested in. Clearing the - // interrupt status or enable bits have no effect on the rest of the - // interrupt handler. - USER_INTERRUPT_HANDLER.call(); -} - -/// The default GPIO interrupt handler, when the user has not set one. -/// -/// This handler will disable all pending interrupts and leave the interrupt -/// status bits unchanged. This enables functions like `is_interrupt_set` to -/// work correctly. -#[cfg(any(esp32s2, esp32s3))] -#[handler] -fn default_gpio_interrupt_handler() { - let status = gpio_interrupt_status(); - gpio_interrupt_disable(status); -} - -#[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -pub fn bind_default_interrupt_handler() { - interrupt::bind_handler(Interrupt::GPIO, default_gpio_interrupt_handler); -} - /// General Purpose Input/Output driver pub struct Io { _io_peripheral: LpIo, @@ -109,15 +79,7 @@ impl Io { /// [`is_interrupt_set()`]: Input::is_interrupt_set pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { USER_INTERRUPT_HANDLER.store(handler.handler().callback()); - - crate::interrupt::bind_handler(Interrupt::GPIO, user_gpio_interrupt_handler); - } -} - -#[cfg(any(esp32s2, esp32s3))] -impl crate::interrupt::InterruptConfigurable for Io { - fn set_interrupt_handler(&mut self, handler: InterruptHandler) { - self.set_interrupt_handler(handler); + crate::interrupt::bind_handler(Interrupt::GPIO_INT, user_gpio_interrupt_handler); } } @@ -211,87 +173,11 @@ pub enum WakeEvent { HighLevel = 5, } -/// Read the interrupt status of all pins -#[cfg(any(esp32s2, esp32s3))] -#[inline] -pub fn gpio_interrupt_status() -> u32 { - // Bit 0 == GPIO0, in the returned value - unsafe { &*LpIo::PTR }.status().read().bits() >> 10 -} - -/// Clear the interrupt status for a bit mask of pins -#[cfg(any(esp32s2, esp32s3))] -#[inline] -pub fn gpio_interrupt_clear(pinmask: u32) { - // expects pinmask bit 0 == GPIO0 - unsafe { &*LpIo::PTR } - .status_w1tc() - .write(|w| unsafe { w.bits(pinmask << 10) }); -} - -/// Disable interrupts for a bit mask of pins -#[cfg(any(esp32s2, esp32s3))] -#[inline] -fn gpio_interrupt_disable(_pinmask: u32) { - // expects pinmask bit 0 == GPIO0 - // todo!("Implement masked interrupt disabling."); -} - -/// Set GPIO event listening. -/// -/// - `N`: the pin to configure -/// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will -/// leave the int_type setting as-is. -/// - `wake_up`: whether to wake up from light sleep. -#[cfg(any(esp32s2, esp32s3))] -fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) { - // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); - // TODO: Add LpIo::regs().pins(number : usize) to esp-pacs - let gpio_bank_reg = LpIo::ptr() as usize; - let gpio_base = gpio_bank_reg + 0x28 + ((N as usize) * 4); - let gpio_pin = gpio_base as *mut u32; - - // Read the current setting - let mut pin_setting = unsafe { gpio_pin.read_volatile() }; - - // Write int_type if specified - if let Some(int_type) = int_type { - // Bits 7:9 - // - 0: GPIO interrupt disable - // - 1: rising edge trigger - // - 2: falling edge trigger - // - 3: any edge trigger - // - 4: low level trigger - // - 5: high level trigger - let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); - pin_setting &= int_type_mask; - pin_setting |= (int_type as u32) << 7; - } - - // Bit 10, wakeup enable. - // Used with UlpCoreWakeupSource::Gpio. - let wakeup_en_mask: u32 = 0xFFFFFFFF ^ (0b1 << 10); - pin_setting &= wakeup_en_mask; - if wakeup_enable { - pin_setting |= 0b1 << 10; - } - - unsafe { gpio_pin.write_volatile(pin_setting) } -} - -#[cfg(any(esp32s2, esp32s3))] -fn clear_pin_interrupt() { - gpio_interrupt_clear(1 << N); -} - -#[cfg(any(esp32s2, esp32s3))] -fn is_interrupt_set() -> bool { - let stat = gpio_interrupt_status(); - (stat & (1 << N)) != 0 -} - /// Flexible pin driver. /// Provides a common implementation for input and output pins. +/// Currently hidden, as it is not intended to be used directly, +/// because it does not handle changing the pin modes. +#[doc(hidden)] pub struct Flex {} #[allow(clippy::new_without_default)] @@ -520,119 +406,3 @@ impl OutputOpenDrain { self.pin.wakeup_enable(enable, event); } } - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_output() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(Output::::new()) - } -} - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_output_open_drain() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(OutputOpenDrain::::new()) - } -} - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_input() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(Input::::new()) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for Input { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for Output { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for OutputOpenDrain { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::InputPin for Input { - fn is_high(&mut self) -> Result { - Ok(self.level() == Level::High) - } - - fn is_low(&mut self) -> Result { - Ok(self.level() == Level::Low) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::OutputPin for Output { - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::Low); - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::High); - Ok(()) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::StatefulOutputPin for Output { - fn is_set_high(&mut self) -> Result { - Ok(self.output_level() == Level::High) - } - - fn is_set_low(&mut self) -> Result { - Ok(self.output_level() == Level::Low) - } -} - -// OutputOpenDrain -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::InputPin for OutputOpenDrain { - fn is_high(&mut self) -> Result { - Ok(self.level() == Level::High) - } - - fn is_low(&mut self) -> Result { - Ok(self.level() == Level::Low) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::OutputPin for OutputOpenDrain { - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::Low); - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::High); - Ok(()) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::StatefulOutputPin for OutputOpenDrain { - fn is_set_high(&mut self) -> Result { - Ok(self.output_level() == Level::High) - } - - fn is_set_low(&mut self) -> Result { - Ok(self.output_level() == Level::Low) - } -} diff --git a/esp-lp-hal/src/interrupt.rs b/esp-lp-hal/src/interrupt.rs deleted file mode 100644 index 89b4971b753..00000000000 --- a/esp-lp-hal/src/interrupt.rs +++ /dev/null @@ -1,589 +0,0 @@ -//! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. -//! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. -use core::ptr::NonNull; - -/// Argument passed to SensInterrupt handler. -pub use pac::Interrupt as SensInterruptStatus; -use portable_atomic::{AtomicPtr, Ordering}; - -// use procmacros::handler; -use crate::pac; -/// Argument passed to GpioInterrupt handler. -pub type GpioInterruptPin = u32; - -// Interrupt vector table -// TODO: Merge back into esp-pacs -// ALT-TODO: Setup an __INTERRUPTS table in here, which contains a superset of -// __EXTERNAL_INTERRUPTS, and other peripheral interrupts not captured by the PAC. -// e.g. bits 0..=31 would be for 'external interrupts', -// and bits 32..=N would be for 'peripheral interrupts', or similar. -// This might allow the interrupt handling code in here to be more portable, maybe... -unsafe extern "C" { - fn GPIO_INT(); - fn SENS_INT(); -} - -/// Default interrupt handler, does nothing. -#[cfg(any(esp32s2, esp32s3))] -#[allow(dead_code)] -#[doc(hidden)] -#[allow(non_snake_case)] -#[unsafe(no_mangle)] -pub fn DefaultHandler() {} - -#[doc(hidden)] -#[repr(C)] -pub union Vector { - pub _handler: unsafe extern "C" fn(), - pub _reserved: usize, -} - -#[doc(hidden)] -#[unsafe(link_section = ".rwtext")] -#[unsafe(no_mangle)] -pub static __INTERRUPTS: [Vector; Interrupt::len()] = - [Vector { _handler: SENS_INT }, Vector { _handler: GPIO_INT }]; - -#[doc = r"Enumeration of all the interrupts."] -#[derive(Copy, Clone, PartialEq, Eq)] -#[repr(u16)] -#[non_exhaustive] -pub enum Interrupt { - #[doc = "0 - SENS"] - SENS = 0, - #[doc = "1 - GPIO"] - GPIO = 1, - #[doc(hidden)] - __LAST__, // Marker to auto-detect enum length. -} - -#[doc = r" TryFromInterruptError"] -#[derive(Copy, Clone)] -pub struct TryFromInterruptError(()); - -impl Interrupt { - #[doc = r"Attempt to convert a given value into an `Interrupt`"] - #[inline] - pub fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Interrupt::SENS), - 1 => Ok(Interrupt::GPIO), - _ => Err(TryFromInterruptError(())), - } - } - - #[doc = r"Get the total number of interrupts."] - #[inline] - pub const fn len() -> usize { - Self::__LAST__ as usize - } -} - -const STATUS_WORDS: usize = 1; - -/// Representation of peripheral-interrupt status bits. -#[doc(hidden)] -#[derive(Clone, Copy, Default, Debug)] -pub struct InterruptStatus { - status: [u32; STATUS_WORDS], -} - -impl InterruptStatus { - /// Get status of peripheral interrupts - pub fn current() -> Self { - let mut status_bits = 0b00; - // Check SENS status - status_bits |= (unsafe { &*pac::SENS::PTR }.sar_cocpu_int_st().read().bits() != 0) as u32; - // Check GPIO status - status_bits |= ((unsafe { &*pac::RTC_IO::PTR }.status().read().bits() != 0) as u32) << 1; - InterruptStatus::from(status_bits) - } - - /// Is the given interrupt bit set - pub fn is_set(&self, interrupt: u8) -> bool { - (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 - } - - /// Set the given interrupt status bit - pub fn set(&mut self, interrupt: u8) { - self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); - } - - /// Return an iterator over the set interrupt status bits - pub fn iterator(&self) -> InterruptStatusIterator { - InterruptStatusIterator { - status: *self, - idx: 0, - } - } -} - -impl From for InterruptStatus { - fn from(value: u32) -> Self { - Self { status: [value] } - } -} - -pub(super) struct CFnPtr(AtomicPtr<()>); -impl CFnPtr { - pub const fn new() -> Self { - Self(AtomicPtr::new(core::ptr::null_mut())) - } - - pub fn store(&self, f: extern "C" fn()) { - self.0.store(f as *mut (), Ordering::Relaxed); - } - - pub fn call(&self) { - let ptr = self.0.load(Ordering::Relaxed); - if !ptr.is_null() { - unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() }; - } - } -} - -// Peripheral interrupt API. - -fn vector_entry(interrupt: Interrupt) -> &'static Vector { - &__INTERRUPTS[interrupt as usize] -} - -/// Returns the currently bound interrupt handler. -pub fn bound_handler(interrupt: Interrupt) -> Option { - unsafe { - let vector = vector_entry(interrupt); - - let addr = vector._handler; - if addr as usize == 0 { - return None; - } - - Some(IsrCallback::new(core::mem::transmute::< - unsafe extern "C" fn(), - extern "C" fn(), - >(addr))) - } -} - -/// Binds the given handler to a peripheral interrupt. -/// -/// The interrupt handler will be enabled at the specified priority level. -/// -/// The interrupt handler will be called on the core where it is registered. -/// Only one interrupt handler can be bound to a peripheral interrupt. -pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { - unsafe { - let vector = vector_entry(interrupt); - let ptr = (&raw const vector._handler).cast::().cast_mut(); - ptr.write_volatile(handler.handler().address()); - } - // Unused, because ULP does not have distinct - // interrupt sources for every interrupt type we want to handle - // enable(interrupt, handler.priority()); -} - -/// Trait implemented by drivers which allow the user to set an -/// [InterruptHandler] -pub trait InterruptConfigurable { - #[doc = "Registers an interrupt handler for the peripheral."] - #[doc = ""] - /// Note that this will replace any previously registered interrupt - /// handlers. Some peripherals offer a shared interrupt handler for - /// multiple purposes. It's the users duty to honor this. - /// - /// You can restore the default/unhandled interrupt handler by using - /// [DEFAULT_INTERRUPT_HANDLER] - fn set_interrupt_handler(&mut self, handler: InterruptHandler); -} - -/// Represents an ISR callback function -#[derive(Copy, Clone)] -pub struct IsrCallback { - f: extern "C" fn(), -} - -impl IsrCallback { - /// Construct a new callback from the callback function. - pub fn new(f: extern "C" fn()) -> Self { - // a valid fn pointer is non zero - Self { f } - } - - /// Returns the address of the callback. - pub fn address(self) -> usize { - self.f as usize - } - - /// The callback function. - /// - /// This is aligned and can be called. - pub fn callback(self) -> extern "C" fn() { - self.f - } -} - -impl PartialEq for IsrCallback { - fn eq(&self, other: &Self) -> bool { - core::ptr::fn_addr_eq(self.callback(), other.callback()) - } -} - -/// Interrupt priority levels. -/// For esp-lp-hal there is only one priority level, and this is only included -/// so that the API is compatible with esp-hal handler macro. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(u8)] -#[non_exhaustive] -pub enum Priority { - /// Priority level 1. - Priority1 = 1, -} - -impl Priority { - /// Maximum interrupt priority - pub const fn max() -> Priority { - Priority::Priority1 - } - - /// Minimum interrupt priority - pub const fn min() -> Priority { - Priority::Priority1 - } -} - -/// An interrupt handler -#[derive(Copy, Clone)] -pub struct InterruptHandler { - f: extern "C" fn(), - prio: Priority, -} - -impl InterruptHandler { - /// Creates a new [InterruptHandler] which will call the given function - pub const fn new(f: extern "C" fn(), prio: Priority) -> Self { - Self { f, prio } - } - - /// The Isr callback. - #[inline] - pub fn handler(&self) -> IsrCallback { - IsrCallback::new(self.f) - } - - /// Priority to be used when registering the interrupt - #[inline] - pub fn priority(&self) -> Priority { - self.prio - } -} - -/// Iterator over set interrupt status bits -#[doc(hidden)] -#[derive(Debug, Clone)] -pub struct InterruptStatusIterator { - status: InterruptStatus, - idx: usize, -} - -impl Iterator for InterruptStatusIterator { - type Item = u8; - - fn next(&mut self) -> Option { - for i in self.idx..STATUS_WORDS { - if self.status.status[i] != 0 { - let bit = self.status.status[i].trailing_zeros(); - self.status.status[i] ^= 1 << bit; - self.idx = i; - return Some((bit + 32 * i as u32) as u8); - } - } - self.idx = usize::MAX; - None - } -} - -/// Setup interrupt handlers, including any default ones -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn setup_interrupts() { - crate::gpio::bind_default_interrupt_handler(); -} - -/// Set the IRQ Mask, returns the previous mask value. -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn maskirq(new_mask: u32) -> u32 { - let old_mask: u32; - unsafe { - // This instruction copies the value of the register IRQ Mask to the register rd, - // and copies the value of register rs to IRQ Mask. - core::arch::asm!( - "maskirq_insn {}, {}", - out(reg) old_mask, - in(reg) new_mask - ); - } - old_mask -} - -/// Enable all interrupts by setting IRQ mask -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn enable() { - maskirq(0x0); -} - -/// Disable all interrupts by clearing IRQ mask, -/// returns the previous IRQ Mask -#[cfg(any(esp32s2, esp32s3))] -#[inline(always)] -pub fn disable() -> u32 { - // Enter a critical section by disabling all interrupts. - // Type Bit Description - // Internal 0 Internal timer interrupt - // Internal 1 EBREAK/ECALL or Illegal Instruction - // Internal 2 BUS Error (Unaligned Memory Access) - // External 31 RTC peripheral interrupts - let mask = (1 << 31) | (1 << 2) | (1 << 1) | (1 << 0); - maskirq(mask) -} - -/// Wait for any (masked or unmasked) interrupt -#[cfg(any(esp32s2, esp32s3))] -pub fn waitirq() -> u32 { - // Wait for pending interrupt, return pending interrupt mask - // waitirq a0 - let result: u32; - unsafe { - core::arch::asm!(".word 0x0800400B", out("a0") result); - } - result -} - -/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, -// which is currently hand-written in ulp_riscv_vectors.S -/// Registers saved in trap handler -#[doc(hidden)] -#[cfg(any(esp32s2, esp32s3))] -#[repr(C)] -#[derive(Debug)] -pub struct TrapFrame { - /// `x1`: return address, stores the address to return to after a function call or interrupt. - pub ra: usize, - /// `x5`: temporary register `t0`, used for intermediate values. - pub t0: usize, - /// `x6`: temporary register `t1`, used for intermediate values. - pub t1: usize, - /// `x7`: temporary register `t2`, used for intermediate values. - pub t2: usize, - /// `x28`: temporary register `t3`, used for intermediate values. - pub t3: usize, - /// `x29`: temporary register `t4`, used for intermediate values. - pub t4: usize, - /// `x30`: temporary register `t5`, used for intermediate values. - pub t5: usize, - /// `x31`: temporary register `t6`, used for intermediate values. - pub t6: usize, - /// `x10`: argument register `a0`. Used to pass the first argument to a function. - pub a0: usize, - /// `x11`: argument register `a1`. Used to pass the second argument to a function. - pub a1: usize, - /// `x12`: argument register `a2`. Used to pass the third argument to a function. - pub a2: usize, - /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. - pub a3: usize, - /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. - pub a4: usize, - /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. - pub a5: usize, - /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. - pub a6: usize, - /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. - pub a7: usize, -} - -/// Trap entry point rust (_start_trap_rust) -/// `irqs` is a bitmask of IRQs to handle. -#[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -#[unsafe(link_section = ".trap.rust")] -#[unsafe(export_name = "_start_trap_rust")] -pub extern "C" fn ulp_start_trap_rust(trap_frame: *const u32, irqs: u32) { - unsafe extern "C" { - fn trap_handler(regs: &TrapFrame, pending_irqs: u32); - } - - unsafe { - // 'trap_frame' pointer safety: - // _start_trap must place a valid address in a0, prior to calling _start_trap_rust. - trap_handler( - NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), - irqs, - ); - } -} - -/// Called by _start_trap_rust, this trap handler will call other interrupt handling -/// functions depending on the bits set in pending_irqs. -#[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -#[unsafe(no_mangle)] -pub extern "C" fn trap_handler(_regs: &TrapFrame, pending_irqs: u32) { - // Dispatch peripheral interrupt - if pending_irqs & (1 << 31) != 0 { - let status = InterruptStatus::current(); - - // Iterate the active interrupts, fetch their handler, and call it if set. - for interrupt_nr in status.iterator() { - if let Ok(i) = Interrupt::try_from(interrupt_nr) { - if let Some(handler) = bound_handler(i) { - handler.callback()(); - } - } - } - } -} - -// /// Creates the trap_handler() function. -// /// This is macro is used later in this file. -// /// This style of macro is usually provided -// /// for users to hook their own handlers, but here it's just used -// /// as a quick way to generate the bit-mask code :) -// #[cfg(any(esp32s2, esp32s3))] -// macro_rules! build_trap_handler { -// (@interrupt ($n:literal, $pending_irqs:expr, $regs:expr, $handler:ident)) => { -// if $pending_irqs & (1 << $n) != 0 { -// #[allow(unused_unsafe)] -// unsafe { $handler($regs); } -// } -// }; -// ( $( $irq:literal : $handler:ident ),* ) => { -// /// Called by _start_trap_rust, this trap handler will call other interrupt handling -// /// functions depending on the bits set in pending_irqs. -// #[doc(hidden)] -// #[unsafe(no_mangle)] -// pub extern "C" fn trap_handler(regs: &TrapFrame, pending_irqs: u32) { -// $( -// build_trap_handler!(@interrupt($irq, pending_irqs, regs, $handler)); -// )* -// } -// }; -// } - -// /// Default interrupt handler, does nothing. -// #[cfg(any(esp32s2, esp32s3))] -// #[allow(dead_code)] -// #[doc(hidden)] -// #[allow(non_snake_case)] -// #[unsafe(no_mangle)] -// pub fn DefaultHandler() {} - -// /// Default illegal instruction or bus error exception handler. -// /// This handler is dispatched by the trap_handler() function. -// #[cfg(any(esp32s2, esp32s3))] -// #[allow(dead_code)] -// #[allow(non_snake_case)] -// #[doc(hidden)] -// #[unsafe(no_mangle)] -// pub fn DefaultExceptionHandler(_regs: &TrapFrame) { -// panic!("Unhandled exception!"); -// } - -// // Create the trap_handler function -// #[cfg(any(esp32s2, esp32s3))] -// build_trap_handler!( -// // 1: DefaultExceptionHandler, -// // 2: DefaultExceptionHandler, -// 31: dispatch_peripheral_interrupt -// ); - -// /// Peripheral interrupt handler for the IRQ bit 31. -// /// Checks the SENS and RTC_IO interrup status, and dispatches further interrupt handlers as -// /// appropriate. -// #[cfg(any(esp32s2, esp32s3))] -// #[doc(hidden)] -// #[unsafe(no_mangle)] -// fn dispatch_peripheral_interrupt(_regs: &TrapFrame) { -// // This function is based on the ESP-IDF implementation found here: -// // https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c#L110 -// unsafe extern "Rust" { -// fn SensInterrupt(status: SensInterruptStatus); -// fn GpioInterrupt(pin: GpioInterruptPin); -// } - -// // Iterate for any SENS interrupt flags -// let cocpu_int_st_bits = InterruptStatus::from(unsafe { &*pac::SENS::PTR -// }.sar_cocpu_int_st().read().bits()); // Iterate over the 1 bit positions -// for bit in cocpu_int_st_bits.iterator() { -// // Convert into the named interrupt enumeration -// if let Ok(stat) = SensInterruptStatus::try_from(bit) { -// // Call handler, and clear the interrupt bit. -// unsafe { SensInterrupt(stat) }; - -// unsafe { &*pac::SENS::PTR } -// .sar_cocpu_int_clr() -// .write(|w| unsafe { w.bits(1 << bit) }); -// } -// } - -// // RTC IO interrupts. -// let rtcio_int_st_bits = unsafe { &*pac::RTC_IO::PTR }.status().read().bits(); -// let rtcio_int_st = InterruptStatus::from(rtcio_int_st_bits); -// // Iterate over the 1 bit positions -// for bit in rtcio_int_st.iterator() { -// // Call handler, and clear the interrupt bit. -// // Pin must have 10 subtracted from it, due to register offset. -// unsafe { GpioInterrupt((bit - 10) as u32) }; - -// unsafe { &*pac::RTC_IO::PTR } -// .status_w1tc() -// .write(|w| unsafe { w.bits(1 << bit) }); -// } -// } - -// // Macros bind user functions to SensInterrupt and GpioInterrupt. - -// #[macro_export] -// #[doc = r" Assigns a handler to GpioInterrupt"] -// #[doc = r""] -// #[doc = r" This macro takes one argument: path to the function that"] -// #[doc = r" will be used as the handler of that interrupt. The function"] -// #[doc = r" must have signature `fn(pin : GpioInterruptPin)`."] -// #[doc = r""] -// #[doc = r" # Example"] -// #[doc = r""] -// #[doc = r" ``` ignore"] -// #[doc = r" gpio_interrupt!(buttons_handler);"] -// #[doc = r""] -// #[doc = r" fn buttons_handler(_pin : GpioInterruptPin) {"] -// #[doc = r#" print!("A GPIO pin was pressed!");"#] -// #[doc = r" }"] -// #[doc = r""] -// macro_rules! gpio_interrupt { -// ($ path : path) => { -// #[allow(non_snake_case)] -// #[unsafe(no_mangle)] -// pub extern "C" fn GpioInterrupt(pin: GpioInterruptPin) { -// let f: fn(pin: GpioInterruptPin) = $path; -// f(pin); -// } -// }; -// } - -// #[macro_export] -// #[doc = r" Assigns a handler to SensInterrupt"] -// macro_rules! sens_interrupt { -// ($ path : path) => { -// #[allow(non_snake_case)] -// #[unsafe(no_mangle)] -// pub extern "C" fn SensInterrupt(status: SensInterruptStatus) { -// let f: fn(status: SensInterruptStatus) = $path; -// f(status); -// } -// }; -// } - -// #[allow(unused)] -// pub use gpio_interrupt; -// #[allow(unused)] -// pub use sens_interrupt; diff --git a/esp-lp-hal/src/interrupt/generic.rs b/esp-lp-hal/src/interrupt/generic.rs new file mode 100644 index 00000000000..3b6a27643f3 --- /dev/null +++ b/esp-lp-hal/src/interrupt/generic.rs @@ -0,0 +1,230 @@ +use portable_atomic::{AtomicPtr, Ordering}; + +use super::{current_interrupts, enable}; +pub use crate::pac::Interrupt; +use crate::pac::{__EXTERNAL_INTERRUPTS, Vector}; + +/// Hard-coded limit of a single u32 interrupt status bank, +/// for all ULP / LP chips. +const MAX_IRQ_STATUS_WORDS: usize = 1; + +/// Returns the currently bound interrupt handler. +pub fn bound_handler(interrupt: Interrupt) -> Option { + unsafe { + let vector = vector_entry(interrupt); + + let addr = vector._handler; + if addr as usize == 0 { + return None; + } + + Some(IsrCallback::new(core::mem::transmute::< + unsafe extern "C" fn(), + extern "C" fn(), + >(addr))) + } +} + +fn vector_entry(interrupt: Interrupt) -> &'static Vector { + &__EXTERNAL_INTERRUPTS[interrupt as usize] +} + +/// Binds the given handler to a peripheral interrupt. +/// +/// The interrupt handler will be enabled at the specified priority level. +/// +/// The interrupt handler will be called on the core where it is registered. +/// Only one interrupt handler can be bound to a peripheral interrupt. +pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { + unsafe { + let vector = vector_entry(interrupt); + let ptr = (&raw const vector._handler).cast::().cast_mut(); + ptr.write_volatile(handler.handler().address()); + } + enable(interrupt, handler.priority()); +} + +/// Trait implemented by drivers which allow the user to set an +/// [InterruptHandler] +pub trait InterruptConfigurable { + #[doc = "Registers an interrupt handler for the peripheral."] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. Some peripherals offer a shared interrupt handler for + /// multiple purposes. It's the users duty to honor this. + fn set_interrupt_handler(&mut self, handler: InterruptHandler); +} + +/// Represents an ISR callback function +#[derive(Copy, Clone)] +pub struct IsrCallback { + f: extern "C" fn(), +} + +impl IsrCallback { + /// Construct a new callback from the callback function. + pub fn new(f: extern "C" fn()) -> Self { + // a valid fn pointer is non zero + Self { f } + } + + /// Returns the address of the callback. + pub fn address(self) -> usize { + self.f as usize + } + + /// The callback function. + /// + /// This is aligned and can be called. + pub fn callback(self) -> extern "C" fn() { + self.f + } +} + +impl PartialEq for IsrCallback { + fn eq(&self, other: &Self) -> bool { + core::ptr::fn_addr_eq(self.callback(), other.callback()) + } +} + +/// Interrupt priority levels. +/// For esp-lp-hal there is only one priority level, and this is only included +/// so that the API is compatible with esp-hal handler macro. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +#[non_exhaustive] +pub enum Priority { + /// Priority level 1. + Priority1 = 1, +} + +impl Priority { + /// Maximum interrupt priority + pub const fn max() -> Priority { + Priority::Priority1 + } + + /// Minimum interrupt priority + pub const fn min() -> Priority { + Priority::Priority1 + } +} + +/// An interrupt handler +#[derive(Copy, Clone)] +pub struct InterruptHandler { + f: extern "C" fn(), + prio: Priority, +} + +impl InterruptHandler { + /// Creates a new [InterruptHandler] which will call the given function + pub const fn new(f: extern "C" fn(), prio: Priority) -> Self { + Self { f, prio } + } + + /// The Isr callback. + #[inline] + pub fn handler(&self) -> IsrCallback { + IsrCallback::new(self.f) + } + + /// Priority to be used when registering the interrupt + #[inline] + pub fn priority(&self) -> Priority { + self.prio + } +} + +/// Iterator over set interrupt status bits +#[doc(hidden)] +#[derive(Debug, Clone)] +pub struct InterruptStatusIterator { + status: InterruptStatus, + idx: usize, +} + +impl InterruptStatusIterator { + /// Create a new InterruptStatusIterator + pub fn new(status: InterruptStatus) -> Self { + Self { status, idx: 0 } + } +} + +impl Iterator for InterruptStatusIterator { + type Item = u8; + + fn next(&mut self) -> Option { + for i in self.idx..MAX_IRQ_STATUS_WORDS { + if self.status.status[i] != 0 { + let bit = self.status.status[i].trailing_zeros(); + self.status.status[i] ^= 1 << bit; + self.idx = i; + return Some((bit + 32 * i as u32) as u8); + } + } + self.idx = usize::MAX; + None + } +} + +/// Ergonomic C function pointer wrapper, with null-pointer checking +pub struct CFnPtr(AtomicPtr<()>); + +impl CFnPtr { + /// Create a new null pointer + pub const fn new() -> Self { + Self(AtomicPtr::new(core::ptr::null_mut())) + } + + /// Store pointer to a C function + pub fn store(&self, f: extern "C" fn()) { + self.0.store(f as *mut (), Ordering::Relaxed); + } + + /// Call the stored C function, if set + pub fn call(&self) { + let ptr = self.0.load(Ordering::Relaxed); + if !ptr.is_null() { + unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() }; + } + } +} + +/// Representation of peripheral-interrupt status bits. +#[doc(hidden)] +#[derive(Clone, Copy, Default, Debug)] +pub struct InterruptStatus { + status: [u32; MAX_IRQ_STATUS_WORDS], +} + +impl InterruptStatus { + /// Get status of interrupts + /// This bitmask should match the Interrupt enum. + pub fn current() -> Self { + Self { + status: [current_interrupts()], + } + } + + /// Is the given interrupt bit set + pub fn is_set(&self, interrupt: u8) -> bool { + (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 + } + + /// Set the given interrupt status bit + pub fn set(&mut self, interrupt: u8) { + self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); + } + + /// Return an iterator over the set interrupt status bits + pub fn iterator(&self) -> InterruptStatusIterator { + InterruptStatusIterator::new(*self) + } +} + +impl From for InterruptStatus { + fn from(value: u32) -> Self { + Self { status: [value] } + } +} diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs new file mode 100644 index 00000000000..11135bc0986 --- /dev/null +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -0,0 +1,145 @@ +/// Portable interrupt binding and handling code +pub mod generic; +pub use generic::*; + +// RISCV ULP specific interrupt handlers +pub mod riscv_ulp; +pub use riscv_ulp::*; + +/// Setup interrupt handlers, including any default ones +#[inline(always)] +pub fn setup_interrupts() { + crate::gpio::bind_default_interrupt_handler(); + enable_cpu_interrupts(); +} + +/// Returns a bitmask of active interrupts +#[cfg(esp32s2)] +fn current_interrupts() -> u32 { + let mut status_bits: u32 = 0b0; + // Add the SENS peripheral status, which is broken out into individual bit flags + let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); + status_bits |= (sens_status.cocpu_touch_done_int_st().bit_is_set() as u32) << 0; + status_bits |= (sens_status.cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; + status_bits |= (sens_status.cocpu_touch_active_int_st().bit_is_set() as u32) << 2; + status_bits |= (sens_status.cocpu_saradc1_int_st().bit_is_set() as u32) << 3; + status_bits |= (sens_status.cocpu_saradc2_int_st().bit_is_set() as u32) << 4; + status_bits |= (sens_status.cocpu_tsens_int_st().bit_is_set() as u32) << 5; + status_bits |= (sens_status.cocpu_start_int_st().bit_is_set() as u32) << 6; + status_bits |= (sens_status.cocpu_sw_int_st().bit_is_set() as u32) << 7; + status_bits |= (sens_status.cocpu_swd_int_st().bit_is_set() as u32) << 8; + // Add the GPIO peripheral status, which is 1 if any of the GPIO pins have an interrupt set. + let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); + status_bits |= ((gpio_status != 0) as u32) << 9; + status_bits +} + +/// Returns a bitmask of active interrupts +#[cfg(esp32s3)] +fn current_interrupts() -> u32 { + let mut status_bits: u32 = 0b0; + // Add the SENS peripheral status, which is broken out into individual bit flags + let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); + status_bits |= (sens_status.sar_cocpu_touch_done_int_st().bit_is_set() as u32) << 0; + status_bits |= (sens_status.sar_cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; + status_bits |= (sens_status.sar_cocpu_touch_active_int_st().bit_is_set() as u32) << 2; + status_bits |= (sens_status.sar_cocpu_saradc1_int_st().bit_is_set() as u32) << 3; + status_bits |= (sens_status.sar_cocpu_saradc2_int_st().bit_is_set() as u32) << 4; + status_bits |= (sens_status.sar_cocpu_tsens_int_st().bit_is_set() as u32) << 5; + status_bits |= (sens_status.sar_cocpu_start_int_st().bit_is_set() as u32) << 6; + status_bits |= (sens_status.sar_cocpu_sw_int_st().bit_is_set() as u32) << 7; + status_bits |= (sens_status.sar_cocpu_swd_int_st().bit_is_set() as u32) << 8; + status_bits |= (sens_status.sar_cocpu_touch_timeout_int_st().bit_is_set() as u32) << 9; + status_bits |= (sens_status + .sar_cocpu_touch_approach_loop_done_int_st() + .bit_is_set() as u32) + << 10; + status_bits |= (sens_status.sar_cocpu_touch_scan_done_int_st().bit_is_set() as u32) << 11; + // Add the GPIO peripheral status, which is 1 if any of the GPIO pins have an interrupt set. + let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); + status_bits |= ((gpio_status != 0) as u32) << 12; + status_bits +} + +/// Enables a peripheral interrupt at a given priority, using vectored CPU interrupts. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt +/// for the specified priority level. +#[inline] +pub fn enable(interrupt: Interrupt, _level: Priority) { + #[cfg(esp32s3)] + match interrupt { + Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_done_int_ena().set_bit()), + Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_inactive_int_ena().set_bit()), + Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_active_int_ena().set_bit()), + Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_saradc1_int_ena().set_bit()), + Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_saradc2_int_ena().set_bit()), + Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_tsens_int_ena().set_bit()), + Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_start_int_ena().set_bit()), + Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_sw_int_ena().set_bit()), + Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_swd_int_ena().set_bit()), + Interrupt::TOUCH_TIME_OUT_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_timeout_int_ena().set_bit()), + Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_approach_loop_done_int_ena().set_bit()), + Interrupt::TOUCH_SCAN_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_scan_done_int_ena().set_bit()), + Interrupt::GPIO_INT => 0, + }; + + #[cfg(esp32s2)] + match interrupt { + Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_done_int_ena().set_bit()), + Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_inactive_int_ena().set_bit()), + Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_active_int_ena().set_bit()), + Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_saradc1_int_ena().set_bit()), + Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_saradc2_int_ena().set_bit()), + Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_tsens_int_ena().set_bit()), + Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_start_int_ena().set_bit()), + Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_sw_int_ena().set_bit()), + Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_swd_int_ena().set_bit()), + Interrupt::GPIO_INT => 0, + }; +} diff --git a/esp-lp-hal/src/interrupt/riscv_ulp.rs b/esp-lp-hal/src/interrupt/riscv_ulp.rs new file mode 100644 index 00000000000..5cd5610723b --- /dev/null +++ b/esp-lp-hal/src/interrupt/riscv_ulp.rs @@ -0,0 +1,138 @@ +//! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. +//! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. +use core::ptr::NonNull; + +use super::{Interrupt, InterruptStatus, bound_handler}; + +/// Set the IRQ Mask, returns the previous mask value. +/// IRQ Type Bit Description +/// Internal 0 Internal timer interrupt +/// Internal 1 EBREAK/ECALL or Illegal Instruction +/// Internal 2 BUS Error (Unaligned Memory Access) +/// External 31 RTC peripheral interrupts +#[inline(always)] +pub fn mask_cpu_interrupts(new_mask: u32) -> u32 { + let old_mask: u32; + unsafe { + core::arch::asm!( + "maskirq_insn {}, {}", + out(reg) old_mask, + in(reg) new_mask + ); + } + old_mask +} + +/// Enable all CPU interrupts by setting IRQ mask +#[inline(always)] +pub fn enable_cpu_interrupts() { + mask_cpu_interrupts(0x0); +} + +/// Disable all CPU interrupts by clearing IRQ mask, +/// returns the previous IRQ Mask +#[inline(always)] +pub fn disable_cpu_interrupts() -> u32 { + let mask = (1 << 31) | (1 << 2) | (1 << 1) | (1 << 0); + mask_cpu_interrupts(mask) +} + +/// Wait for any (masked or unmasked) CPU interrupt +pub fn wait_cpu_interrupt() -> u32 { + let result: u32; + unsafe { + core::arch::asm!( + "waitirq_insn {}", + out(reg) result, + ) + } + result +} + +/// Trap entry point rust (_start_trap_rust) +/// `irqs` is a bitmask of IRQs to handle. +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +#[unsafe(export_name = "_start_trap_rust")] +pub extern "C" fn ulp_start_trap_rust(trap_frame: *const u32, irqs: u32) { + unsafe extern "C" { + fn trap_handler(regs: &TrapFrame, pending_irqs: u32); + } + + unsafe { + // 'trap_frame' pointer safety: + // _start_trap must place a valid address in a0, prior to calling _start_trap_rust. + trap_handler( + NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), + irqs, + ); + } +} + +/// Called by _start_trap_rust, this trap handler will call other interrupt handling +/// functions depending on the bits set in pending_irqs. +#[doc(hidden)] +#[unsafe(no_mangle)] +pub extern "C" fn trap_handler(_regs: &TrapFrame, pending_irqs: u32) { + // Dispatch peripheral interrupt + if pending_irqs & (1 << 31) != 0 { + let status = InterruptStatus::current(); + + // Iterate the active interrupts, fetch their handler, and call it if set. + for interrupt_nr in status.iterator() { + if let Ok(i) = Interrupt::try_from(interrupt_nr) { + if let Some(handler) = bound_handler(i) { + handler.callback()(); + } + } + } + } +} + +/// Default interrupt handler, does nothing. +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub fn DefaultHandler() {} + +/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, +// which is currently hand-written in ulp_riscv_vectors.S +/// Registers saved in trap handler +#[doc(hidden)] +#[repr(C)] +#[derive(Debug)] +pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. + pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. + pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. + pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. + pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. + pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. + pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. + pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. + pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. + pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. + pub a5: usize, + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. + pub a6: usize, + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. + pub a7: usize, +} diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index e98fa4f7604..3a9942c31b4 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -40,6 +40,7 @@ pub use esp32s3_ulp as pac; /// Critical section implementation for ULP cores #[cfg(any(esp32s2, esp32s3))] +#[doc(hidden)] pub mod critical_section; /// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) @@ -101,7 +102,6 @@ unsafe extern "C" fn lp_core_startup() -> ! { { ulp_riscv_rescue_from_monitor(); interrupt::setup_interrupts(); - interrupt::enable(); } #[cfg(esp32c6)] From 2c31ed096c2a6dcff63c606a15c4e6927160d3ba Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 17:56:24 +1100 Subject: [PATCH 18/47] Update interrupt_counter.rs to align with ESP-HAL example --- esp-lp-hal/examples/interrupt_counter.rs | 35 ++++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 735014c39e7..6c35889fdac 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -1,21 +1,22 @@ //! ULP interrupt-based counter example. -//! Increments a 32 bit counter value at a known point in memory, -//! whenever the ULP program is run. If GPIO0 is pressed, reset the counter. - -//% CHIPS: esp32s2, esp32s3 +//! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is run. +//! If GPIO0 is pressed, resets the counter. #![no_std] #![no_main] + extern crate panic_halt; +use core::cell::RefCell; + +use critical_section::Mutex; use esp_lp_hal::{ - gpio::{Event, Input, Io, gpio_interrupt_clear, gpio_interrupt_status}, + gpio::{Event, Input, Io}, interrupt, pac::Peripherals, prelude::*, }; -// Shared memory address. const ADDRESS: u32 = 0x1000; fn increment_counter() { @@ -32,22 +33,32 @@ fn reset_counter() { } } +// NOTE: Normally this would contain Option>, +// but for some reason the ULP core is crashing when +// .unwrap() is used on anything... +static BUTTON: Mutex>> = Mutex::new(RefCell::new(Input::<0>::new())); + #[entry] -fn main(mut boot_button: Input<0>) { +fn main(mut button: Input<0>) { let peripherals = Peripherals::take(); let mut io = Io::new(peripherals.RTC_IO); io.set_interrupt_handler(gpio_interrupt_handler); - boot_button.listen(Event::FallingEdge); - increment_counter(); + + critical_section::with(|cs| { + button.listen(Event::FallingEdge); + *BUTTON.borrow_ref_mut(cs) = button; + }); } #[handler] fn gpio_interrupt_handler() { - let status = gpio_interrupt_status(); - if status & (0b1) != 0 { + if critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).is_interrupt_set()) { + // The button was the source of the interrupt reset_counter(); } - gpio_interrupt_clear(status); + + // Clear the interrupt + critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).clear_interrupt()); } From f714b6faaab0504685b1f37cb2fa4fa397565d61 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 18:08:36 +1100 Subject: [PATCH 19/47] Added interupt::disable() function --- esp-lp-hal/examples/interrupt_counter.rs | 10 ++- esp-lp-hal/src/interrupt/mod.rs | 87 +++++++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 6c35889fdac..04e48a886ef 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -12,7 +12,7 @@ use core::cell::RefCell; use critical_section::Mutex; use esp_lp_hal::{ gpio::{Event, Input, Io}, - interrupt, + interrupt::{self, Interrupt}, pac::Peripherals, prelude::*, }; @@ -44,7 +44,7 @@ fn main(mut button: Input<0>) { let mut io = Io::new(peripherals.RTC_IO); io.set_interrupt_handler(gpio_interrupt_handler); - increment_counter(); + interrupt::bind_handler(Interrupt::RISCV_START_INT, startup_interrupt_handler); critical_section::with(|cs| { button.listen(Event::FallingEdge); @@ -62,3 +62,9 @@ fn gpio_interrupt_handler() { // Clear the interrupt critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).clear_interrupt()); } + +#[handler] +fn startup_interrupt_handler() { + increment_counter(); + interrupt::disable(Interrupt::RISCV_START_INT); +} diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 11135bc0986..a6e29e1abd6 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -61,13 +61,12 @@ fn current_interrupts() -> u32 { status_bits } -/// Enables a peripheral interrupt at a given priority, using vectored CPU interrupts. +/// Enables a peripheral interrupt. /// /// Note that interrupts still need to be enabled globally for interrupts /// to be serviced. /// -/// Internally, this function maps the interrupt to the appropriate CPU interrupt -/// for the specified priority level. +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. #[inline] pub fn enable(interrupt: Interrupt, _level: Priority) { #[cfg(esp32s3)] @@ -143,3 +142,85 @@ pub fn enable(interrupt: Interrupt, _level: Priority) { Interrupt::GPIO_INT => 0, }; } + +/// Disables a peripheral interrupt. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. +#[inline] +pub fn disable(interrupt: Interrupt) { + #[cfg(esp32s3)] + match interrupt { + Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_done_int_ena().clear_bit()), + Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_inactive_int_ena().clear_bit()), + Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_active_int_ena().clear_bit()), + Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_saradc1_int_ena().clear_bit()), + Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_saradc2_int_ena().clear_bit()), + Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_tsens_int_ena().clear_bit()), + Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_start_int_ena().clear_bit()), + Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_sw_int_ena().clear_bit()), + Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_swd_int_ena().clear_bit()), + Interrupt::TOUCH_TIME_OUT_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_timeout_int_ena().clear_bit()), + Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_approach_loop_done_int_ena().clear_bit()), + Interrupt::TOUCH_SCAN_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.sar_cocpu_touch_scan_done_int_ena().clear_bit()), + Interrupt::GPIO_INT => 0, + }; + + #[cfg(esp32s2)] + match interrupt { + Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_done_int_ena().clear_bit()), + Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_inactive_int_ena().clear_bit()), + Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_touch_active_int_ena().clear_bit()), + Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_saradc1_int_ena().clear_bit()), + Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_saradc2_int_ena().clear_bit()), + Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_tsens_int_ena().clear_bit()), + Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_start_int_ena().clear_bit()), + Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_sw_int_ena().clear_bit()), + Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| w.cocpu_swd_int_ena().clear_bit()), + Interrupt::GPIO_INT => 0, + }; +} From d35fcbdb38557dffbca9acea37044bc5a35f6f69 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 18:16:50 +1100 Subject: [PATCH 20/47] Make clippy happy again --- esp-lp-hal/src/interrupt/generic.rs | 1 + esp-lp-hal/src/interrupt/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esp-lp-hal/src/interrupt/generic.rs b/esp-lp-hal/src/interrupt/generic.rs index 3b6a27643f3..f62f31e44f1 100644 --- a/esp-lp-hal/src/interrupt/generic.rs +++ b/esp-lp-hal/src/interrupt/generic.rs @@ -171,6 +171,7 @@ impl Iterator for InterruptStatusIterator { /// Ergonomic C function pointer wrapper, with null-pointer checking pub struct CFnPtr(AtomicPtr<()>); +#[allow(clippy::new_without_default)] impl CFnPtr { /// Create a new null pointer pub const fn new() -> Self { diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index a6e29e1abd6..8860144c2aa 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -19,7 +19,7 @@ fn current_interrupts() -> u32 { let mut status_bits: u32 = 0b0; // Add the SENS peripheral status, which is broken out into individual bit flags let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); - status_bits |= (sens_status.cocpu_touch_done_int_st().bit_is_set() as u32) << 0; + status_bits |= sens_status.cocpu_touch_done_int_st().bit_is_set() as u32; status_bits |= (sens_status.cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; status_bits |= (sens_status.cocpu_touch_active_int_st().bit_is_set() as u32) << 2; status_bits |= (sens_status.cocpu_saradc1_int_st().bit_is_set() as u32) << 3; @@ -40,7 +40,7 @@ fn current_interrupts() -> u32 { let mut status_bits: u32 = 0b0; // Add the SENS peripheral status, which is broken out into individual bit flags let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); - status_bits |= (sens_status.sar_cocpu_touch_done_int_st().bit_is_set() as u32) << 0; + status_bits |= sens_status.sar_cocpu_touch_done_int_st().bit_is_set() as u32; status_bits |= (sens_status.sar_cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; status_bits |= (sens_status.sar_cocpu_touch_active_int_st().bit_is_set() as u32) << 2; status_bits |= (sens_status.sar_cocpu_saradc1_int_st().bit_is_set() as u32) << 3; From e081c2f9bd470414d4012d0f43a3e8403effc3ec Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 18:39:01 +1100 Subject: [PATCH 21/47] Remove duplicate example name causing havok in CI --- esp-lp-hal/Cargo.toml | 8 +------- esp-lp-hal/examples/interrupt_counter.rs | 2 ++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 5c9654a99ff..9bb0f48c02e 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -87,13 +87,7 @@ name = "blinky" required-features = [] [[example]] -name = "interrupt_counter_s2" -path = "./examples/interrupt_counter.rs" -required-features = ["esp32s2"] - -[[example]] -name = "interrupt_counter_s3" -path = "./examples/interrupt_counter.rs" +name = "interrupt_counter" required-features = ["esp32s3"] [[example]] diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 04e48a886ef..a413894905f 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -2,6 +2,8 @@ //! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is run. //! If GPIO0 is pressed, resets the counter. +//% CHIPS: esp32s3 + #![no_std] #![no_main] From 20e8d0eb89f1c62bff3bfe4c995751e3aa596996 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 20:26:02 +1100 Subject: [PATCH 22/47] Update CHANGELOG.md --- esp-lp-hal/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md index 63804eb104a..8fe2a4fc896 100644 --- a/esp-lp-hal/CHANGELOG.md +++ b/esp-lp-hal/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `wake_hp_core` for ESP32-S2 and ESP32-S3 (#5133) - Add `OutputOpenDrain` GPIO type (#5131) -- ESP32-S2, ESP32-S3: Added private ULP interrupt handler to prevent uncleared interrupts from hanging the core (#5134) +- ESP32-S2, ESP32-S3: Added ULP interrupt support (#5134) ### Changed - Changed ULP entrypoint to allow `main()` to return, and halt, to the ULP Timer can be used (#5134) From abdbca37494c342d63a1b6b69bf3412d7ac67de2 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 3 Apr 2026 20:28:28 +1100 Subject: [PATCH 23/47] Update link-ulp.x --- esp-lp-hal/ld/link-ulp.x | 4 ---- 1 file changed, 4 deletions(-) diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 26da1d075a8..239fc7a4c3e 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -16,10 +16,6 @@ MEMORY ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM } -/* Default interrupt handlers */ -PROVIDE(SENS_INT = DefaultHandler); -PROVIDE(GPIO_INT = DefaultHandler); - SECTIONS { . = ORIGIN(ram); From 6c9d54d4192786251c86265ea8885e2f7013016d Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 7 Apr 2026 20:19:03 +1000 Subject: [PATCH 24/47] Clear `DEVICE_PERIPHERALS` PAC variable to prevent `Peripheral::take().unwrap()` panic, while using ULP Timer. --- esp-lp-hal/Cargo.toml | 8 ++++---- esp-lp-hal/src/lib.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 9bb0f48c02e..cc47b4d3be8 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -45,11 +45,11 @@ nb = { version = "1.1.0", optional = true } procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } -critical-section = { version = "1.1.1", features = ["restore-state-u32"], optional = true } +critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "250be223" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 3a9942c31b4..cb9976548ba 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -96,8 +96,18 @@ unsafe extern "C" fn lp_core_startup() -> ! { unsafe extern "Rust" { // This symbol will be provided by the user via `#[entry]` fn main(); + + // This variable is provided by the PAC, and used to + // detect multiple calls to Peripherals::take(). + static mut DEVICE_PERIPHERALS: bool; } + // The pac::DEVICE_PERIPHERALS variable is re-zero-ed on start, + // to prevent it from persisting between calls to main(). + // This prevents ULP-Timer-triggered-main()-calls from panicking + // on their second loop. + DEVICE_PERIPHERALS = false; + #[cfg(any(esp32s2, esp32s3))] { ulp_riscv_rescue_from_monitor(); From fecb21fb98884602c50f7660c888aadac2e26587 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 7 Apr 2026 20:19:03 +1000 Subject: [PATCH 25/47] Dont overwrite code region in lp_core::load_lp_code, until run is called --- esp-hal-procmacros/src/lp_core.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs index b26ac6f6c01..fcb465e778b 100644 --- a/esp-hal-procmacros/src/lp_core.rs +++ b/esp-hal-procmacros/src/lp_core.rs @@ -355,10 +355,6 @@ pub fn load_lp_code(input: TokenStream, fs: impl Filesystem) -> TokenStream { static #rtc_code_start: u32; } - unsafe { - core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len()); - } - impl LpCoreCode { pub fn run( &self, @@ -366,6 +362,9 @@ pub fn load_lp_code(input: TokenStream, fs: impl Filesystem) -> TokenStream { wakeup_source: LpCoreWakeupSource, #(_: #args),* ) { + unsafe { + core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len()); + } lp_core.run(wakeup_source); } } From 899f0a293f4483b14a8af3d97d76df3008e8d28d Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 12 Apr 2026 16:37:42 +1000 Subject: [PATCH 26/47] Update interrupt_counter.rs --- esp-lp-hal/examples/interrupt_counter.rs | 70 +++++++++++++----------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index a413894905f..a7a1d6a78d3 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -19,54 +19,60 @@ use esp_lp_hal::{ prelude::*, }; -const ADDRESS: u32 = 0x1000; +const ADDRESS: usize = 0x1000; -fn increment_counter() { - let ptr = ADDRESS as *mut u32; - let i = unsafe { ptr.read_volatile() }; - unsafe { - ptr.write_volatile(i + 1); - } -} -fn reset_counter() { - let ptr = ADDRESS as *mut u32; - unsafe { - ptr.write_volatile(0); - } -} - -// NOTE: Normally this would contain Option>, -// but for some reason the ULP core is crashing when -// .unwrap() is used on anything... -static BUTTON: Mutex>> = Mutex::new(RefCell::new(Input::<0>::new())); +static BUTTON: Mutex>>> = Mutex::new(RefCell::new(None)); #[entry] fn main(mut button: Input<0>) { - let peripherals = Peripherals::take(); + let peripherals = Peripherals::take().unwrap(); let mut io = Io::new(peripherals.RTC_IO); io.set_interrupt_handler(gpio_interrupt_handler); - interrupt::bind_handler(Interrupt::RISCV_START_INT, startup_interrupt_handler); - critical_section::with(|cs| { button.listen(Event::FallingEdge); - *BUTTON.borrow_ref_mut(cs) = button; + BUTTON.borrow_ref_mut(cs).replace(button); }); + + interrupt::bind_handler(Interrupt::RISCV_START_INT, startup_interrupt_handler); } #[handler] -fn gpio_interrupt_handler() { - if critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).is_interrupt_set()) { - // The button was the source of the interrupt - reset_counter(); +fn startup_interrupt_handler() { + // Increment the counter every time RISCV_START_INT is triggered + unsafe { + let counter = ADDRESS as *mut u32; + counter.write_volatile(counter.read_volatile() + 1); } - // Clear the interrupt - critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).clear_interrupt()); + // On entry, immediately disable any more start-up interrupts. + // This is needed, because RISCV_START_INT is driven from the ULP Timer, + // which may be called multiple times before main() has finished execution. + interrupt::disable(Interrupt::RISCV_START_INT); } #[handler] -fn startup_interrupt_handler() { - increment_counter(); - interrupt::disable(Interrupt::RISCV_START_INT); +fn gpio_interrupt_handler() { + // Check if BUTTON has an interrupt pending + if critical_section::with(|cs| { + BUTTON + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .is_interrupt_set() + }) { + // The button was the source of the interrupt, reset the counter to 0. + unsafe { + let counter = ADDRESS as *mut u32; + counter.write_volatile(0); + } + } + + critical_section::with(|cs| { + BUTTON + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); } From d8c36236355c01b1b6e0d5d5c5d5b5eea7155bfb Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 12 Apr 2026 21:47:20 +1000 Subject: [PATCH 27/47] Address comments, revert procmacro change to avoid breaking user code mysteriously --- esp-hal-procmacros/src/lp_core.rs | 7 +- esp-lp-hal/CHANGELOG.md | 1 + esp-lp-hal/examples/interrupt_counter.rs | 2 +- esp-lp-hal/src/interrupt/generic.rs | 4 +- esp-lp-hal/src/interrupt/mod.rs | 253 ++++++----------------- esp-lp-hal/src/lib.rs | 3 +- 6 files changed, 68 insertions(+), 202 deletions(-) diff --git a/esp-hal-procmacros/src/lp_core.rs b/esp-hal-procmacros/src/lp_core.rs index fcb465e778b..b26ac6f6c01 100644 --- a/esp-hal-procmacros/src/lp_core.rs +++ b/esp-hal-procmacros/src/lp_core.rs @@ -355,6 +355,10 @@ pub fn load_lp_code(input: TokenStream, fs: impl Filesystem) -> TokenStream { static #rtc_code_start: u32; } + unsafe { + core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len()); + } + impl LpCoreCode { pub fn run( &self, @@ -362,9 +366,6 @@ pub fn load_lp_code(input: TokenStream, fs: impl Filesystem) -> TokenStream { wakeup_source: LpCoreWakeupSource, #(_: #args),* ) { - unsafe { - core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len()); - } lp_core.run(wakeup_source); } } diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md index 8fe2a4fc896..f06a1c69a1b 100644 --- a/esp-lp-hal/CHANGELOG.md +++ b/esp-lp-hal/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix panic when handling buffers larger than 7 bytes in `LpI2c.write` and `LpI2c.read` (#4694) - ESP32-S2, ESP32-S3: Aligned `ulp_riscv_halt()` with ESP_IDF by setting the `cocpu_shut_reset_en` bit, squashing a rare bug where halting the chip would hang (#5134) +- ESP32-S2, ESP32-S3: Prevent `Peripherals::take().unwrap()` from panicking on second ULP Timer `main()`-loop, by clearing `DEVICE_PERIPHERALS` variable on start. ### Removed diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index a7a1d6a78d3..4f0b0617b57 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -48,7 +48,7 @@ fn startup_interrupt_handler() { // On entry, immediately disable any more start-up interrupts. // This is needed, because RISCV_START_INT is driven from the ULP Timer, // which may be called multiple times before main() has finished execution. - interrupt::disable(Interrupt::RISCV_START_INT); + interrupt::set_enabled(Interrupt::RISCV_START_INT, false); } #[handler] diff --git a/esp-lp-hal/src/interrupt/generic.rs b/esp-lp-hal/src/interrupt/generic.rs index f62f31e44f1..4da71036c1b 100644 --- a/esp-lp-hal/src/interrupt/generic.rs +++ b/esp-lp-hal/src/interrupt/generic.rs @@ -1,6 +1,6 @@ use portable_atomic::{AtomicPtr, Ordering}; -use super::{current_interrupts, enable}; +use super::{current_interrupts, set_enabled}; pub use crate::pac::Interrupt; use crate::pac::{__EXTERNAL_INTERRUPTS, Vector}; @@ -41,7 +41,7 @@ pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { let ptr = (&raw const vector._handler).cast::().cast_mut(); ptr.write_volatile(handler.handler().address()); } - enable(interrupt, handler.priority()); + set_enabled(interrupt, true); } /// Trait implemented by drivers which allow the user to set an diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 8860144c2aa..7bb1ef851c1 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -14,213 +14,78 @@ pub fn setup_interrupts() { } /// Returns a bitmask of active interrupts -#[cfg(esp32s2)] fn current_interrupts() -> u32 { let mut status_bits: u32 = 0b0; - // Add the SENS peripheral status, which is broken out into individual bit flags - let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); - status_bits |= sens_status.cocpu_touch_done_int_st().bit_is_set() as u32; - status_bits |= (sens_status.cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; - status_bits |= (sens_status.cocpu_touch_active_int_st().bit_is_set() as u32) << 2; - status_bits |= (sens_status.cocpu_saradc1_int_st().bit_is_set() as u32) << 3; - status_bits |= (sens_status.cocpu_saradc2_int_st().bit_is_set() as u32) << 4; - status_bits |= (sens_status.cocpu_tsens_int_st().bit_is_set() as u32) << 5; - status_bits |= (sens_status.cocpu_start_int_st().bit_is_set() as u32) << 6; - status_bits |= (sens_status.cocpu_sw_int_st().bit_is_set() as u32) << 7; - status_bits |= (sens_status.cocpu_swd_int_st().bit_is_set() as u32) << 8; - // Add the GPIO peripheral status, which is 1 if any of the GPIO pins have an interrupt set. - let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); - status_bits |= ((gpio_status != 0) as u32) << 9; - status_bits -} + // The SENS peripheral status is broken out into individual bit flags + let sens_status = unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_st() + .read() + .bits(); + status_bits |= sens_status; -/// Returns a bitmask of active interrupts -#[cfg(esp32s3)] -fn current_interrupts() -> u32 { - let mut status_bits: u32 = 0b0; - // Add the SENS peripheral status, which is broken out into individual bit flags - let sens_status = unsafe { &*crate::pac::SENS::PTR }.sar_cocpu_int_st().read(); - status_bits |= sens_status.sar_cocpu_touch_done_int_st().bit_is_set() as u32; - status_bits |= (sens_status.sar_cocpu_touch_inactive_int_st().bit_is_set() as u32) << 1; - status_bits |= (sens_status.sar_cocpu_touch_active_int_st().bit_is_set() as u32) << 2; - status_bits |= (sens_status.sar_cocpu_saradc1_int_st().bit_is_set() as u32) << 3; - status_bits |= (sens_status.sar_cocpu_saradc2_int_st().bit_is_set() as u32) << 4; - status_bits |= (sens_status.sar_cocpu_tsens_int_st().bit_is_set() as u32) << 5; - status_bits |= (sens_status.sar_cocpu_start_int_st().bit_is_set() as u32) << 6; - status_bits |= (sens_status.sar_cocpu_sw_int_st().bit_is_set() as u32) << 7; - status_bits |= (sens_status.sar_cocpu_swd_int_st().bit_is_set() as u32) << 8; - status_bits |= (sens_status.sar_cocpu_touch_timeout_int_st().bit_is_set() as u32) << 9; - status_bits |= (sens_status - .sar_cocpu_touch_approach_loop_done_int_st() - .bit_is_set() as u32) - << 10; - status_bits |= (sens_status.sar_cocpu_touch_scan_done_int_st().bit_is_set() as u32) << 11; - // Add the GPIO peripheral status, which is 1 if any of the GPIO pins have an interrupt set. + // The GPIO peripheral status, is a single bit result, which is 1 if any GPIO interrupt status + // bit is 1. let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); - status_bits |= ((gpio_status != 0) as u32) << 12; - status_bits -} + let any_gpio_interrupt = gpio_status != 0; -/// Enables a peripheral interrupt. -/// -/// Note that interrupts still need to be enabled globally for interrupts -/// to be serviced. -/// -/// Internally, this function maps the interrupt to the appropriate CPU interrupt. -#[inline] -pub fn enable(interrupt: Interrupt, _level: Priority) { + #[cfg(esp32s2)] + const SENS_BITFLAGS_LEN: usize = 9; #[cfg(esp32s3)] - match interrupt { - Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_done_int_ena().set_bit()), - Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_inactive_int_ena().set_bit()), - Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_active_int_ena().set_bit()), - Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_saradc1_int_ena().set_bit()), - Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_saradc2_int_ena().set_bit()), - Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_tsens_int_ena().set_bit()), - Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_start_int_ena().set_bit()), - Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_sw_int_ena().set_bit()), - Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_swd_int_ena().set_bit()), - Interrupt::TOUCH_TIME_OUT_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_timeout_int_ena().set_bit()), - Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_approach_loop_done_int_ena().set_bit()), - Interrupt::TOUCH_SCAN_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_scan_done_int_ena().set_bit()), - Interrupt::GPIO_INT => 0, - }; + const SENS_BITFLAGS_LEN: usize = 12; - #[cfg(esp32s2)] - match interrupt { - Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_done_int_ena().set_bit()), - Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_inactive_int_ena().set_bit()), - Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_active_int_ena().set_bit()), - Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_saradc1_int_ena().set_bit()), - Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_saradc2_int_ena().set_bit()), - Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_tsens_int_ena().set_bit()), - Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_start_int_ena().set_bit()), - Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_sw_int_ena().set_bit()), - Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_swd_int_ena().set_bit()), - Interrupt::GPIO_INT => 0, - }; + if any_gpio_interrupt { + status_bits |= 1 << SENS_BITFLAGS_LEN; + } + + status_bits } -/// Disables a peripheral interrupt. +/// Enables or disables a peripheral interrupt. /// /// Note that interrupts still need to be enabled globally for interrupts /// to be serviced. /// /// Internally, this function maps the interrupt to the appropriate CPU interrupt. #[inline] -pub fn disable(interrupt: Interrupt) { - #[cfg(esp32s3)] - match interrupt { - Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_done_int_ena().clear_bit()), - Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_inactive_int_ena().clear_bit()), - Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_active_int_ena().clear_bit()), - Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_saradc1_int_ena().clear_bit()), - Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_saradc2_int_ena().clear_bit()), - Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_tsens_int_ena().clear_bit()), - Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_start_int_ena().clear_bit()), - Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_sw_int_ena().clear_bit()), - Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_swd_int_ena().clear_bit()), - Interrupt::TOUCH_TIME_OUT_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_timeout_int_ena().clear_bit()), - Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_approach_loop_done_int_ena().clear_bit()), - Interrupt::TOUCH_SCAN_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.sar_cocpu_touch_scan_done_int_ena().clear_bit()), - Interrupt::GPIO_INT => 0, - }; - - #[cfg(esp32s2)] - match interrupt { - Interrupt::TOUCH_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_done_int_ena().clear_bit()), - Interrupt::TOUCH_INACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_inactive_int_ena().clear_bit()), - Interrupt::TOUCH_ACTIVE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_touch_active_int_ena().clear_bit()), - Interrupt::SARADC1_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_saradc1_int_ena().clear_bit()), - Interrupt::SARADC2_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_saradc2_int_ena().clear_bit()), - Interrupt::TSENS_DONE_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_tsens_int_ena().clear_bit()), - Interrupt::RISCV_START_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_start_int_ena().clear_bit()), - Interrupt::SW_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_sw_int_ena().clear_bit()), - Interrupt::SWD_INT => unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| w.cocpu_swd_int_ena().clear_bit()), - Interrupt::GPIO_INT => 0, - }; +pub fn set_enabled(interrupt: Interrupt, enable: bool) { + // Enable/disable SENS interrupts + unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| { + #[cfg(esp32s3)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.sar_cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.sar_cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.sar_cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.sar_cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.sar_cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.sar_cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.sar_cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.sar_cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.sar_cocpu_swd_int_ena().bit(enable), + Interrupt::TOUCH_TIME_OUT_INT => w.sar_cocpu_touch_timeout_int_ena().bit(enable), + Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => { + w.sar_cocpu_touch_approach_loop_done_int_ena().bit(enable) + } + Interrupt::TOUCH_SCAN_DONE_INT => w.sar_cocpu_touch_scan_done_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + #[cfg(esp32s2)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.cocpu_swd_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + }); + // GPIO interrupt requires no enable/disable handling here, + // as it is handled by the gpio module on a per-pin basis. } diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index cb9976548ba..14fdc09d74a 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -40,8 +40,7 @@ pub use esp32s3_ulp as pac; /// Critical section implementation for ULP cores #[cfg(any(esp32s2, esp32s3))] -#[doc(hidden)] -pub mod critical_section; +mod critical_section; /// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) #[cfg(any(esp32s2, esp32s3))] From d203bae79ecee99a7b92fd8d9353967e369ce4a0 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 12 Apr 2026 22:25:03 +1000 Subject: [PATCH 28/47] Added HP core example --- .../ulp_timer_counter/.cargo/config.toml | 24 +++++++ .../lp_core/ulp_timer_counter/Cargo.toml | 32 +++++++++ .../lp_core/ulp_timer_counter/src/main.rs | 68 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml create mode 100644 examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml create mode 100644 examples/peripheral/lp_core/ulp_timer_counter/src/main.rs diff --git a/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml b/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] diff --git a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml new file mode 100644 index 00000000000..94c28bc8eac --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "ulp-timer-counter" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +esp-backtrace = { path = "../../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../../esp-println", features = ["log-04"] } + +[features] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs new file mode 100644 index 00000000000..f56ad4c16a8 --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs @@ -0,0 +1,68 @@ +//! Demonstrates running the ULP core on a roughly 1Hz timer. +//! +//! Code on ULP core increments a counter at a fixed memory address, every time it runs. +//! The current value is printed by the HP core. +//! When the user presses the GPIO0 button, it will fire an interrupt on the ULP core, and reset the +//! counter. +//! +//! ⚠️ Make sure to first compile the `esp-lp-hal/examples/interrupt_counter.rs` example ⚠️ +//! +//! The following wiring is assumed: +//! - BUTTON => GPIO0 + +//% CHIPS: esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + gpio::rtc_io::LowPowerInput, + load_lp_code, + main, + ulp_core::{UlpCore, UlpCoreTimerCycles, UlpCoreWakeupSource}, +}; +use esp_println::{print, println}; + +esp_bootloader_esp_idf::esp_app_desc!(); + +// Roughly 1Hz on ESP32S3 +#[cfg(feature = "esp32s3")] +const ULP_SLEEP_CYCLES: u32 = 530; +// Roughly 1Hz on ESP32S2 +#[cfg(feature = "esp32s2")] +const ULP_SLEEP_CYCLES: u32 = 1060; + +#[main] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + // configure GPIO0 as LP input pin, which will reset the counter + let ulp_counter_reset_btn = LowPowerInput::new(peripherals.GPIO0); + + let mut lp_core = UlpCore::new(peripherals.ULP_RISCV_CORE); + + // load code to LP core + let lp_core_code = load_lp_code!( + "../../../../esp-lp-hal/target/riscv32imc-unknown-none-elf/release/examples/interrupt_counter" + ); + + // start LP core + lp_core_code.run( + &mut lp_core, + UlpCoreWakeupSource::Timer(UlpCoreTimerCycles::new(ULP_SLEEP_CYCLES)), + ulp_counter_reset_btn, + ); + + println!("ulp core run"); + + const ADDR: usize = 0x5000_1000; + + let data = (ADDR) as *const u32; + loop { + print!("Current {:x} \u{000d}", unsafe { + data.read_volatile() + }); + } +} From fc52c21b2fa8bc07689952f2b92bdabb59e9370c Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 12 Apr 2026 22:31:16 +1000 Subject: [PATCH 29/47] Update changelog --- esp-lp-hal/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md index f06a1c69a1b..ffe4e0ca136 100644 --- a/esp-lp-hal/CHANGELOG.md +++ b/esp-lp-hal/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `wake_hp_core` for ESP32-S2 and ESP32-S3 (#5133) - Add `OutputOpenDrain` GPIO type (#5131) -- ESP32-S2, ESP32-S3: Added ULP interrupt support (#5134) +- ESP32-S2, ESP32-S3: Added ULP interrupt support (#5206) ### Changed - Changed ULP entrypoint to allow `main()` to return, and halt, to the ULP Timer can be used (#5134) @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix panic when handling buffers larger than 7 bytes in `LpI2c.write` and `LpI2c.read` (#4694) - ESP32-S2, ESP32-S3: Aligned `ulp_riscv_halt()` with ESP_IDF by setting the `cocpu_shut_reset_en` bit, squashing a rare bug where halting the chip would hang (#5134) -- ESP32-S2, ESP32-S3: Prevent `Peripherals::take().unwrap()` from panicking on second ULP Timer `main()`-loop, by clearing `DEVICE_PERIPHERALS` variable on start. +- ESP32-S2, ESP32-S3: Prevent `Peripherals::take().unwrap()` from panicking on second ULP Timer `main()`-loop, by clearing `DEVICE_PERIPHERALS` variable on start (#5206) ### Removed From 83fbf871f3ff9ba539c2bcdecee8ed4938458d59 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 12 Apr 2026 22:48:28 +1000 Subject: [PATCH 30/47] Update HP core example --- .../peripheral/lp_core/ulp_timer_counter/Cargo.toml | 10 +++++----- .../peripheral/lp_core/ulp_timer_counter/src/main.rs | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml index 94c28bc8eac..128aab73e62 100644 --- a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml +++ b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml @@ -19,11 +19,11 @@ esp32s3 = [ "esp-bootloader-esp-idf/esp32s3", "esp-hal/esp32s3", ] -esp32s2 = [ - "esp-backtrace/esp32s2", - "esp-bootloader-esp-idf/esp32s2", - "esp-hal/esp32s2", -] +#esp32s2 = [ +# "esp-backtrace/esp32s2", +# "esp-bootloader-esp-idf/esp32s2", +# "esp-hal/esp32s2", +#] [profile.release] debug = true diff --git a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs index f56ad4c16a8..503b3ac7709 100644 --- a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs +++ b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs @@ -10,7 +10,9 @@ //! The following wiring is assumed: //! - BUTTON => GPIO0 -//% CHIPS: esp32s2 esp32s3 +//% CHIPS: esp32s3 +//! (ESP32S2 also supported, but not listed above, as the interrupt_counter example is only built +//! for esp32s3 in CI) #![no_std] #![no_main] From c87cabae9b8fd5a1ffcbd7a6f144c8d69e6e8ef3 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Mon, 13 Apr 2026 07:15:05 +1000 Subject: [PATCH 31/47] Add ULP-core APIs for GPIO wakeup --- esp-hal/src/soc/esp32s2/ulp_core.rs | 12 ++++++++++++ esp-hal/src/soc/esp32s3/ulp_core.rs | 12 ++++++++++++ esp-lp-hal/Cargo.toml | 4 ++++ esp-lp-hal/examples/gpio_wakeup.rs | 29 +++++++++++++++++++++++++++++ esp-lp-hal/src/gpio/interrupt.rs | 14 ++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 esp-lp-hal/examples/gpio_wakeup.rs diff --git a/esp-hal/src/soc/esp32s2/ulp_core.rs b/esp-hal/src/soc/esp32s2/ulp_core.rs index c9f8a0fb9e0..28836f0932c 100644 --- a/esp-hal/src/soc/esp32s2/ulp_core.rs +++ b/esp-hal/src/soc/esp32s2/ulp_core.rs @@ -51,6 +51,8 @@ pub enum UlpCoreWakeupSource { /// Wakeup after the ULP Timer has elapsed. /// The actual period between wake-ups is affected by the runtime duration of the ULP program. Timer(UlpCoreTimerCycles), + /// Wakeup on GPIO state + Gpio, } /// ULP Timer cycles are clocked at a rate of approximately 8MHz / 32768 = ~244 Hz. @@ -149,6 +151,11 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .cocpu_ctrl() .modify(|_, w| w.cocpu_done_force().set_bit()); + // Disable GPIO wakeup + rtc_cntl + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().clear_bit()); + ulp_config_wakeup_source(wakeup_src); // Select RISC-V as the ULP_TIMER trigger target @@ -188,5 +195,10 @@ fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { .ulp_cp_timer() .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); } + UlpCoreWakeupSource::Gpio => { + LPWR::regs() + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().set_bit()); + } } } diff --git a/esp-hal/src/soc/esp32s3/ulp_core.rs b/esp-hal/src/soc/esp32s3/ulp_core.rs index b24712457f9..5056e78dd80 100644 --- a/esp-hal/src/soc/esp32s3/ulp_core.rs +++ b/esp-hal/src/soc/esp32s3/ulp_core.rs @@ -51,6 +51,8 @@ pub enum UlpCoreWakeupSource { /// Wakeup after the ULP Timer has elapsed. /// The actual period between wake-ups is affected by the runtime duration of the ULP program. Timer(UlpCoreTimerCycles), + /// Wakeup on GPIO state + Gpio, } /// ULP Timer cycles are clocked at a rate of approximately 17.5MHz / 32768 = ~534 Hz. @@ -174,6 +176,11 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .cocpu_ctrl() .modify(|_, w| w.cocpu_clkgate_en().set_bit()); + // Disable GPIO wakeup + rtc_cntl + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().clear_bit()); + ulp_config_wakeup_source(wakeup_src); // Select RISC-V as the ULP_TIMER trigger target @@ -220,5 +227,10 @@ fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { .ulp_cp_timer() .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); } + UlpCoreWakeupSource::Gpio => { + LPWR::regs() + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().set_bit()); + } } } diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index cc47b4d3be8..dac772f3c23 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -90,6 +90,10 @@ required-features = [] name = "interrupt_counter" required-features = ["esp32s3"] +[[example]] +name = "gpio_wakeup" +required-features = ["esp32s3"] + [[example]] name = "i2c" required-features = ["esp32c6"] diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs new file mode 100644 index 00000000000..a54c8c08610 --- /dev/null +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -0,0 +1,29 @@ +//! ULP GPIO-wakeup example. +//! Increments a 32 bit counter value, while GPIO0 is pressed. + +//% CHIPS: esp32s3 + +#![no_std] +#![no_main] + +extern crate panic_halt; + +use esp_lp_hal::{gpio::Input, prelude::*}; + +const ADDRESS: usize = 0x1000; + +#[entry] +fn main(mut _button: Input<0>) { + // Clear the GPIO wake-up flag + esp_lp_hal::gpio::gpio_wakeup_clear(); + + // Increment counter + let ptr = ADDRESS as *mut u32; + unsafe { + let count = ptr.read_volatile(); + ptr.write_volatile(count + 1); + } + + // Re-enable the wakeup bit + esp_lp_hal::gpio::gpio_wakeup_enable(true); +} diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs index 98fd1f2f90b..ba440cee79c 100644 --- a/esp-lp-hal/src/gpio/interrupt.rs +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -126,3 +126,17 @@ pub fn is_interrupt_set() -> bool { let stat = gpio_interrupt_status(); (stat & (1 << N)) != 0 } + +/// Global GPIO wakeup enable/disable +pub fn gpio_wakeup_enable(enable: bool) { + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); +} + +/// Clear GPIO wakeup status +pub fn gpio_wakeup_clear() { + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); +} From 30be2346d49c7e971b970dbe6edf123de67b76e3 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 14 Apr 2026 07:09:51 +1000 Subject: [PATCH 32/47] Bump PAC version, add GPIO wakeup_enable to rtc_io pins, update examples --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/gpio/rtc_io.rs | 26 +++++++- esp-lp-hal/Cargo.toml | 8 +-- esp-lp-hal/examples/gpio_wakeup.rs | 29 +++++---- esp-lp-hal/examples/interrupt_counter.rs | 2 +- esp-lp-hal/src/gpio/interrupt.rs | 65 +++++++------------ esp-lp-hal/src/gpio/mod.rs | 32 +-------- .../ulp_gpio_wakeup/.cargo/config.toml | 24 +++++++ .../lp_core/ulp_gpio_wakeup/Cargo.toml | 32 +++++++++ .../lp_core/ulp_gpio_wakeup/src/main.rs | 57 ++++++++++++++++ .../lp_core/ulp_timer_counter/Cargo.toml | 10 +-- .../lp_core/ulp_timer_counter/src/main.rs | 4 +- 12 files changed, 192 insertions(+), 98 deletions(-) create mode 100644 examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml create mode 100644 examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml create mode 100644 examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 8f3c4943d2f..48d77d6f89d 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cache configuration options for ESP32-S2 (#5306) - C5: Add PSRAM support (#5317) - C61: Add PSRAM support (#5325) +- ESP32-S2, ESP32-S3: Add `wakeup_enable()` method to `LowPowerInput` and `LowPowerOutputOpenDrain`, allowing ULP core to be woken up from GPIO. (#5134) ### Changed diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index 084f5236a5b..23f81c3cf79 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -33,7 +33,7 @@ use core::marker::PhantomData; use super::{InputPin, OutputPin, RtcPin}; use crate::{ - gpio::RtcFunction, + gpio::{RtcFunction, WakeEvent}, peripherals::{GPIO, RTC_IO}, }; @@ -92,6 +92,7 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { this.input_enable(true); this.pullup_enable(false); this.pulldown_enable(false); + this.wakeup_enable(None); this } @@ -115,6 +116,17 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { .touch_pad(PIN as usize) .modify(|_, w| w.rde().bit(enable)); } + + /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. + pub fn wakeup_enable(&self, event: Option) { + let pin_reg = GPIO::regs().pin(PIN as usize); + + if let Some(evt) = event { + pin_reg.write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); + } else { + pin_reg.write(|w| w.wakeup_enable().bit(false)); + } + } } /// A GPIO open-drain output pin configured for low power operation @@ -139,6 +151,7 @@ impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { this.pullup_enable(true); this.pulldown_enable(false); this.output_enable(true); + this.wakeup_enable(None); this } @@ -182,4 +195,15 @@ impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { .pin(PIN as usize) .modify(|_, w| w.pad_driver().bit(enable)); } + + /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. + pub fn wakeup_enable(&self, event: Option) { + let pin_reg = GPIO::regs().pin(PIN as usize); + + if let Some(evt) = event { + pin_reg.write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); + } else { + pin_reg.write(|w| w.wakeup_enable().bit(false)); + } + } } diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index dac772f3c23..c2d12b92ddf 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -47,9 +47,9 @@ riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "5ff8e30e" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } [dev-dependencies] panic-halt = "0.2.0" @@ -88,7 +88,7 @@ required-features = [] [[example]] name = "interrupt_counter" -required-features = ["esp32s3"] +#required-features = ["esp32s3"] [[example]] name = "gpio_wakeup" diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs index a54c8c08610..978d89b272d 100644 --- a/esp-lp-hal/examples/gpio_wakeup.rs +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -1,29 +1,34 @@ -//! ULP GPIO-wakeup example. -//! Increments a 32 bit counter value, while GPIO0 is pressed. +//! ULP GPIO wakeup example. +//! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is woken +//! up. The HP core will configure GPIO0 to wake up the ULP core, when pressed. -//% CHIPS: esp32s3 +//% CHIPS: esp32s3,esp32s2 #![no_std] #![no_main] extern crate panic_halt; -use esp_lp_hal::{gpio::Input, prelude::*}; +use esp_lp_hal::{ + prelude::*, + gpio::{ + Input,gpio_wakeup_clear,gpio_wakeup_enable + } +}; const ADDRESS: usize = 0x1000; #[entry] fn main(mut _button: Input<0>) { - // Clear the GPIO wake-up flag - esp_lp_hal::gpio::gpio_wakeup_clear(); + // Clear the global GPIO wake-up flag + gpio_wakeup_clear(); - // Increment counter - let ptr = ADDRESS as *mut u32; + // Increment the counter unsafe { - let count = ptr.read_volatile(); - ptr.write_volatile(count + 1); + let counter = ADDRESS as *mut u32; + counter.write_volatile(counter.read_volatile() + 1); } - // Re-enable the wakeup bit - esp_lp_hal::gpio::gpio_wakeup_enable(true); + // Re-enable the global GPIO wakeup flag + gpio_wakeup_enable(true); } diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 4f0b0617b57..67435316454 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -2,7 +2,7 @@ //! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is run. //! If GPIO0 is pressed, resets the counter. -//% CHIPS: esp32s3 +//% CHIPS: esp32s3,esp32s2 #![no_std] #![no_main] diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs index ba440cee79c..d143b96301c 100644 --- a/esp-lp-hal/src/gpio/interrupt.rs +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -62,16 +62,10 @@ pub fn gpio_interrupt_clear(pinmask: u32) { fn gpio_interrupt_disable(pin_mask: u32) { // expects pinmask bit 0 == GPIO0 let pin_iter = InterruptStatus::from(pin_mask).iterator(); - let gpio_bank_reg = LpIo::ptr() as usize; - let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); - for n in pin_iter { - let gpio_base = gpio_bank_reg + 0x28 + ((n as usize) * 4); - let gpio_pin = gpio_base as *mut u32; - // Read settings, clear int_type flags, write settings - let mut pin_setting = unsafe { gpio_pin.read_volatile() }; - pin_setting &= int_type_mask; - unsafe { gpio_pin.write_volatile(pin_setting) } + unsafe { &*LpIo::PTR } + .pin(n as usize) + .write(|w| unsafe { w.int_type().bits(0) }); } } @@ -81,39 +75,16 @@ fn gpio_interrupt_disable(pin_mask: u32) { /// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will /// leave the int_type setting as-is. /// - `wake_up`: whether to wake up from light sleep. -pub fn enable_pin_interrupt(int_type: Option, wakeup_enable: bool) { - // let GPIO_BASE = unsafe { &*LpIo::PTR }.pin0().as_ptr().addr(); - // TODO: Add LpIo::regs().pins(number : usize) to esp-pacs - let gpio_bank_reg = LpIo::ptr() as usize; - let gpio_base = gpio_bank_reg + 0x28 + ((N as usize) * 4); - let gpio_pin = gpio_base as *mut u32; - - // Read the current setting - let mut pin_setting = unsafe { gpio_pin.read_volatile() }; - - // Write int_type if specified - if let Some(int_type) = int_type { - // Bits 7:9 - // - 0: GPIO interrupt disable - // - 1: rising edge trigger - // - 2: falling edge trigger - // - 3: any edge trigger - // - 4: low level trigger - // - 5: high level trigger - let int_type_mask: u32 = 0xFFFFFFFF ^ (0b111 << 7); - pin_setting &= int_type_mask; - pin_setting |= (int_type as u32) << 7; - } - - // Bit 10, wakeup enable. - // Used with UlpCoreWakeupSource::Gpio. - let wakeup_en_mask: u32 = 0xFFFFFFFF ^ (0b1 << 10); - pin_setting &= wakeup_en_mask; - if wakeup_enable { - pin_setting |= 0b1 << 10; - } - - unsafe { gpio_pin.write_volatile(pin_setting) } +pub fn enable_pin_interrupt(int_type: u8) { + let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); + + // - 0: GPIO interrupt disable + // - 1: rising edge trigger + // - 2: falling edge trigger + // - 3: any edge trigger + // - 4: low level trigger + // - 5: high level trigger + gpio_pin.write(|w| unsafe { w.int_type().bits(int_type) }); } /// Clear pin interrupt @@ -129,6 +100,11 @@ pub fn is_interrupt_set() -> bool { /// Global GPIO wakeup enable/disable pub fn gpio_wakeup_enable(enable: bool) { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); + #[cfg(esp32s3)] unsafe { &*crate::pac::RTC_CNTL::PTR } .rtc_ulp_cp_timer() .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); @@ -136,6 +112,11 @@ pub fn gpio_wakeup_enable(enable: bool) { /// Clear GPIO wakeup status pub fn gpio_wakeup_clear() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); + #[cfg(esp32s3)] unsafe { &*crate::pac::RTC_CNTL::PTR } .rtc_ulp_cp_timer() .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); diff --git a/esp-lp-hal/src/gpio/mod.rs b/esp-lp-hal/src/gpio/mod.rs index 273be87a485..7126101dd74 100644 --- a/esp-lp-hal/src/gpio/mod.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -241,13 +241,13 @@ impl Flex { #[cfg(any(esp32s2, esp32s3))] pub fn listen(&mut self, event: Event) { self.clear_interrupt(); - enable_pin_interrupt::(Some(event as u8), false); + enable_pin_interrupt::(event as u8); } /// Un-listen for interrupts. #[cfg(any(esp32s2, esp32s3))] pub fn unlisten(&mut self) { - enable_pin_interrupt::(Some(0), false); + enable_pin_interrupt::(0); self.clear_interrupt(); } @@ -262,20 +262,6 @@ impl Flex { pub fn is_interrupt_set(&mut self) -> bool { is_interrupt_set::() } - - /// Enable pin as a wake-up source. - /// This will change the interrupt type, when enabling. - /// Interrupt type will not be changed, when disabling. - #[cfg(any(esp32s2, esp32s3))] - pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { - self.clear_interrupt(); - if enable { - let int_evt = Event::from(event); - enable_pin_interrupt::(Some(int_evt as u8), true); - } else { - enable_pin_interrupt::(None, true); - } - } } /// Digital input. @@ -314,13 +300,6 @@ impl Input { pub fn is_interrupt_set(&mut self) -> bool { is_interrupt_set::() } - /// Enable pin as a wake-up source. - /// This will change the interrupt type, when enabling. - /// Interrupt type will not be changed, when disabling. - #[cfg(any(esp32s2, esp32s3))] - pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { - self.pin.wakeup_enable(enable, event); - } } /// Digital output. @@ -398,11 +377,4 @@ impl OutputOpenDrain { pub fn is_interrupt_set(&mut self) -> bool { is_interrupt_set::() } - /// Enable pin as a wake-up source. - /// This will change the interrupt type, when enabling. - /// Interrupt type will not be changed, when disabling. - #[cfg(any(esp32s2, esp32s3))] - pub fn wakeup_enable(&mut self, enable: bool, event: WakeEvent) { - self.pin.wakeup_enable(enable, event); - } } diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml b/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml b/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml new file mode 100644 index 00000000000..147eac5bfe6 --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "ulp-gpio-wakeup" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +esp-backtrace = { path = "../../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../../esp-println", features = ["log-04"] } + +[features] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs new file mode 100644 index 00000000000..0d0dec6221b --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs @@ -0,0 +1,57 @@ +//! Demonstrates running the ULP core being woken up from GPIO0 button press. +//! +//! Code on ULP core increments a counter at a fixed memory address, every time it runs. +//! The current value is printed by the HP core. +//! When the user presses the GPIO0 button, the ULP core will be woken up, incrementing the counter. +//! +//! ⚠️ Make sure to first compile the `esp-lp-hal/examples/gpio_wakeup.rs` example ⚠️ +//! +//! The following wiring is assumed: +//! - BUTTON => GPIO0 + +//% CHIPS: esp32s3,esp32s2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + gpio::{WakeEvent, rtc_io::LowPowerInput}, + load_lp_code, + main, + ulp_core::{UlpCore, UlpCoreWakeupSource}, +}; +use esp_println::{print, println}; + +esp_bootloader_esp_idf::esp_app_desc!(); + +#[main] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + // configure GPIO0 as LP input pin, and enable wakeup on it. + let ulp_wakeup_btn = LowPowerInput::new(peripherals.GPIO0); + ulp_wakeup_btn.wakeup_enable(Some(WakeEvent::LowLevel)); + + let mut lp_core = UlpCore::new(peripherals.ULP_RISCV_CORE); + + // load code to LP core + let lp_core_code = load_lp_code!( + "../../../../esp-lp-hal/target/riscv32imc-unknown-none-elf/release/examples/gpio_wakeup" + ); + + // start LP core + lp_core_code.run(&mut lp_core, UlpCoreWakeupSource::Gpio, ulp_wakeup_btn); + + println!("ulp core run"); + + const ADDR: usize = 0x5000_1000; + + let data = (ADDR) as *const u32; + loop { + print!("Current {:x} \u{000d}", unsafe { + data.read_volatile() + }); + } +} diff --git a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml index 128aab73e62..94c28bc8eac 100644 --- a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml +++ b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml @@ -19,11 +19,11 @@ esp32s3 = [ "esp-bootloader-esp-idf/esp32s3", "esp-hal/esp32s3", ] -#esp32s2 = [ -# "esp-backtrace/esp32s2", -# "esp-bootloader-esp-idf/esp32s2", -# "esp-hal/esp32s2", -#] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] [profile.release] debug = true diff --git a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs index 503b3ac7709..4dee5d14891 100644 --- a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs +++ b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs @@ -10,9 +10,7 @@ //! The following wiring is assumed: //! - BUTTON => GPIO0 -//% CHIPS: esp32s3 -//! (ESP32S2 also supported, but not listed above, as the interrupt_counter example is only built -//! for esp32s3 in CI) +//% CHIPS: esp32s3,esp32s2 #![no_std] #![no_main] From 12841ea6520274b47d6a255238033e698124d1c9 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 14 Apr 2026 09:34:57 +1000 Subject: [PATCH 33/47] Update gpio_wakeup.rs --- esp-lp-hal/examples/gpio_wakeup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs index 978d89b272d..4431ac60127 100644 --- a/esp-lp-hal/examples/gpio_wakeup.rs +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -2,7 +2,7 @@ //! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is woken //! up. The HP core will configure GPIO0 to wake up the ULP core, when pressed. -//% CHIPS: esp32s3,esp32s2 +//% CHIPS: esp32s3 esp32s2 #![no_std] #![no_main] From 5672c72110db83e33b7d852d1f4be875b03aedac Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 14 Apr 2026 09:35:37 +1000 Subject: [PATCH 34/47] Update interrupt_counter.rs --- esp-lp-hal/examples/interrupt_counter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 67435316454..0b342c1928c 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -2,7 +2,7 @@ //! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is run. //! If GPIO0 is pressed, resets the counter. -//% CHIPS: esp32s3,esp32s2 +//% CHIPS: esp32s3 esp32s2 #![no_std] #![no_main] From f5e008f0a7678592f45fa99ad17f9d98af641959 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 14 Apr 2026 19:15:41 +1000 Subject: [PATCH 35/47] Remove feature flag requirement on lp examples, use CHIPS to gate them instead --- esp-lp-hal/Cargo.toml | 4 ++-- esp-lp-hal/examples/gpio_wakeup.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index c2d12b92ddf..af54fbbd4f6 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -88,11 +88,11 @@ required-features = [] [[example]] name = "interrupt_counter" -#required-features = ["esp32s3"] +required-features = [] [[example]] name = "gpio_wakeup" -required-features = ["esp32s3"] +required-features = [] [[example]] name = "i2c" diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs index 4431ac60127..9930ce0b309 100644 --- a/esp-lp-hal/examples/gpio_wakeup.rs +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -10,10 +10,8 @@ extern crate panic_halt; use esp_lp_hal::{ + gpio::{Input, gpio_wakeup_clear, gpio_wakeup_enable}, prelude::*, - gpio::{ - Input,gpio_wakeup_clear,gpio_wakeup_enable - } }; const ADDRESS: usize = 0x1000; From 53d72f1b4f1c087549d8c722634d8d5755b1ac26 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Fri, 17 Apr 2026 07:56:27 +1000 Subject: [PATCH 36/47] Added 'interrupts' feature flag for ULP core interrupt support. Added GPIO wakeup example for ULP cores. --- esp-hal/src/gpio/rtc_io.rs | 20 +- esp-hal/src/soc/esp32s2/ulp_core.rs | 5 - esp-hal/src/soc/esp32s3/ulp_core.rs | 5 - esp-lp-hal/Cargo.toml | 3 +- esp-lp-hal/examples/gpio_wakeup.rs | 10 +- esp-lp-hal/examples/interrupt_counter.rs | 2 +- esp-lp-hal/src/gpio/interrupt.rs | 100 ++++----- esp-lp-hal/src/gpio/mod.rs | 191 +++++++++--------- .../src/{ => interrupt}/critical_section.rs | 6 +- esp-lp-hal/src/interrupt/mod.rs | 116 +++-------- esp-lp-hal/src/interrupt/riscv_ulp.rs | 84 ++++++++ esp-lp-hal/src/lib.rs | 57 ++++-- 12 files changed, 295 insertions(+), 304 deletions(-) rename esp-lp-hal/src/{ => interrupt}/critical_section.rs (73%) diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index 23f81c3cf79..8d381b734a1 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -85,7 +85,6 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { P: InputPin + RtcPin + 'd, { pin.rtc_set_config(true, true, RtcFunction::Rtc); - let this = Self { phantom: PhantomData, }; @@ -119,12 +118,13 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. pub fn wakeup_enable(&self, event: Option) { - let pin_reg = GPIO::regs().pin(PIN as usize); + let pin_reg_rtc = RTC_IO::regs().pin(PIN as usize); if let Some(evt) = event { - pin_reg.write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); + pin_reg_rtc + .write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); } else { - pin_reg.write(|w| w.wakeup_enable().bit(false)); + pin_reg_rtc.write(|w| unsafe { w.int_type().bits(0).wakeup_enable().bit(false) }); } } } @@ -151,7 +151,6 @@ impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { this.pullup_enable(true); this.pulldown_enable(false); this.output_enable(true); - this.wakeup_enable(None); this } @@ -195,15 +194,4 @@ impl<'d, const PIN: u8> LowPowerOutputOpenDrain<'d, PIN> { .pin(PIN as usize) .modify(|_, w| w.pad_driver().bit(enable)); } - - /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. - pub fn wakeup_enable(&self, event: Option) { - let pin_reg = GPIO::regs().pin(PIN as usize); - - if let Some(evt) = event { - pin_reg.write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); - } else { - pin_reg.write(|w| w.wakeup_enable().bit(false)); - } - } } diff --git a/esp-hal/src/soc/esp32s2/ulp_core.rs b/esp-hal/src/soc/esp32s2/ulp_core.rs index 28836f0932c..32da04eaf23 100644 --- a/esp-hal/src/soc/esp32s2/ulp_core.rs +++ b/esp-hal/src/soc/esp32s2/ulp_core.rs @@ -151,11 +151,6 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .cocpu_ctrl() .modify(|_, w| w.cocpu_done_force().set_bit()); - // Disable GPIO wakeup - rtc_cntl - .ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().clear_bit()); - ulp_config_wakeup_source(wakeup_src); // Select RISC-V as the ULP_TIMER trigger target diff --git a/esp-hal/src/soc/esp32s3/ulp_core.rs b/esp-hal/src/soc/esp32s3/ulp_core.rs index 5056e78dd80..05dd4c6c882 100644 --- a/esp-hal/src/soc/esp32s3/ulp_core.rs +++ b/esp-hal/src/soc/esp32s3/ulp_core.rs @@ -176,11 +176,6 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .cocpu_ctrl() .modify(|_, w| w.cocpu_clkgate_en().set_bit()); - // Disable GPIO wakeup - rtc_cntl - .ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().clear_bit()); - ulp_config_wakeup_source(wakeup_src); // Select RISC-V as the ULP_TIMER trigger target diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index af54fbbd4f6..0343a872c35 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -58,7 +58,8 @@ panic-halt = "0.2.0" esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated", features = ["build-script"] } [features] -default = ["embedded-hal"] +default = ["embedded-hal", "interrupts"] +interrupts = [] ## Enable debug features in the HAL (used for development). debug = [ diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs index 9930ce0b309..240e771c25f 100644 --- a/esp-lp-hal/examples/gpio_wakeup.rs +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -9,16 +9,13 @@ extern crate panic_halt; -use esp_lp_hal::{ - gpio::{Input, gpio_wakeup_clear, gpio_wakeup_enable}, - prelude::*, -}; +use esp_lp_hal::{gpio::Input, gpio_wakeup_clear, prelude::*}; const ADDRESS: usize = 0x1000; #[entry] fn main(mut _button: Input<0>) { - // Clear the global GPIO wake-up flag + // Clear the global GPIO wake-up flag, to prevent repeated wake-ups gpio_wakeup_clear(); // Increment the counter @@ -26,7 +23,4 @@ fn main(mut _button: Input<0>) { let counter = ADDRESS as *mut u32; counter.write_volatile(counter.read_volatile() + 1); } - - // Re-enable the global GPIO wakeup flag - gpio_wakeup_enable(true); } diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs index 0b342c1928c..64266729f25 100644 --- a/esp-lp-hal/examples/interrupt_counter.rs +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -30,7 +30,7 @@ fn main(mut button: Input<0>) { io.set_interrupt_handler(gpio_interrupt_handler); critical_section::with(|cs| { - button.listen(Event::FallingEdge); + button.listen(Event::FallingEdge, false); BUTTON.borrow_ref_mut(cs).replace(button); }); diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs index d143b96301c..fe69337ad93 100644 --- a/esp-lp-hal/src/gpio/interrupt.rs +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -5,7 +5,7 @@ use super::{Io, LpIo}; use crate::interrupt; /// Convenience constant for `Option::None` pin -pub(super) static USER_INTERRUPT_HANDLER: interrupt::CFnPtr = interrupt::CFnPtr::new(); +pub static USER_INTERRUPT_HANDLER: interrupt::CFnPtr = interrupt::CFnPtr::new(); impl crate::interrupt::InterruptConfigurable for Io { fn set_interrupt_handler(&mut self, handler: InterruptHandler) { @@ -26,47 +26,46 @@ pub fn user_gpio_interrupt_handler() { USER_INTERRUPT_HANDLER.call(); } -/// The default GPIO interrupt handler, when the user has not set one. -/// -/// This handler will disable all pending interrupts and leave the interrupt -/// status bits unchanged. This enables functions like `is_interrupt_set` to -/// work correctly. -#[handler] -fn default_gpio_interrupt_handler() { - let status = gpio_interrupt_status(); - gpio_interrupt_disable(status); -} - -#[doc(hidden)] -pub fn bind_default_interrupt_handler() { - interrupt::bind_handler(Interrupt::GPIO_INT, default_gpio_interrupt_handler); +/// Event type used to trigger interrupts. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum Event { + /// Interrupts trigger on rising pin edge. + RisingEdge = 1, + /// Interrupts trigger on falling pin edge. + FallingEdge = 2, + /// Interrupts trigger on either rising or falling pin edges. + AnyEdge = 3, + /// Interrupts trigger on low level + LowLevel = 4, + /// Interrupts trigger on high level + HighLevel = 5, } /// Read the interrupt status of all pins /// Bit 0 == GPIO0, in the returned value #[inline] pub fn gpio_interrupt_status() -> u32 { - unsafe { &*LpIo::PTR }.status().read().bits() >> 10 + #[cfg(any(esp32s2, esp32s3))] + { + unsafe { &*LpIo::PTR }.status().read().bits() >> 10 + } + + #[cfg(esp32c6)] + { + todo!() + } } /// Clear the interrupt status for a bit mask of pins /// Expects pinmask bit 0 == GPIO0 #[inline] pub fn gpio_interrupt_clear(pinmask: u32) { + #[cfg(any(esp32s2, esp32s3))] unsafe { &*LpIo::PTR } .status_w1tc() .write(|w| unsafe { w.bits(pinmask << 10) }); -} - -/// Disable interrupts for a bit mask of pins -fn gpio_interrupt_disable(pin_mask: u32) { - // expects pinmask bit 0 == GPIO0 - let pin_iter = InterruptStatus::from(pin_mask).iterator(); - for n in pin_iter { - unsafe { &*LpIo::PTR } - .pin(n as usize) - .write(|w| unsafe { w.int_type().bits(0) }); - } + #[cfg(esp32c6)] + todo!() } /// Set GPIO event listening. @@ -76,15 +75,13 @@ fn gpio_interrupt_disable(pin_mask: u32) { /// leave the int_type setting as-is. /// - `wake_up`: whether to wake up from light sleep. pub fn enable_pin_interrupt(int_type: u8) { - let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); - - // - 0: GPIO interrupt disable - // - 1: rising edge trigger - // - 2: falling edge trigger - // - 3: any edge trigger - // - 4: low level trigger - // - 5: high level trigger - gpio_pin.write(|w| unsafe { w.int_type().bits(int_type) }); + #[cfg(any(esp32s2, esp32s3))] + { + let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); + gpio_pin.write(|w| unsafe { w.int_type().bits(int_type) }); + } + #[cfg(esp32c6)] + todo!() } /// Clear pin interrupt @@ -98,26 +95,13 @@ pub fn is_interrupt_set() -> bool { (stat & (1 << N)) != 0 } -/// Global GPIO wakeup enable/disable -pub fn gpio_wakeup_enable(enable: bool) { - #[cfg(esp32s2)] - unsafe { &*crate::pac::RTC_CNTL::PTR } - .ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); - #[cfg(esp32s3)] - unsafe { &*crate::pac::RTC_CNTL::PTR } - .rtc_ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); -} - -/// Clear GPIO wakeup status -pub fn gpio_wakeup_clear() { - #[cfg(esp32s2)] - unsafe { &*crate::pac::RTC_CNTL::PTR } - .ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); - #[cfg(esp32s3)] - unsafe { &*crate::pac::RTC_CNTL::PTR } - .rtc_ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); +/// Enable / disable pin wakeup +pub fn pin_wakeup_enable(en: bool) { + #[cfg(any(esp32s2, esp32s3))] + { + let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); + gpio_pin.write(|w| w.wakeup_enable().bit(en)); + } + #[cfg(esp32c6)] + todo!() } diff --git a/esp-lp-hal/src/gpio/mod.rs b/esp-lp-hal/src/gpio/mod.rs index 7126101dd74..0bc58651ea5 100644 --- a/esp-lp-hal/src/gpio/mod.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -31,11 +31,20 @@ pub use conjure::*; /// Provides embedded-hal trait impls pub mod ehal; -#[cfg(any(esp32s2, esp32s3))] /// Provides GPIO interrupt support +// TODO: Implement GPIO interrupts for ESP32C6 LP core +#[cfg(feature = "interrupts")] pub mod interrupt; -#[cfg(any(esp32s2, esp32s3))] -pub use interrupt::*; +#[cfg(feature = "interrupts")] +pub use interrupt::{ + Event, + Interrupt, + InterruptHandler, + USER_INTERRUPT_HANDLER, + gpio_interrupt_clear, + gpio_interrupt_status, + user_gpio_interrupt_handler, +}; cfg_if::cfg_if! { if #[cfg(esp32c6)] { @@ -60,7 +69,7 @@ impl Io { Io { _io_peripheral } } - #[cfg(any(esp32s2, esp32s3))] + #[cfg(feature = "interrupts")] #[doc = "Registers an interrupt handler for all GPIO pins."] /// Note that when using interrupt handlers registered by this function, or /// by defining a `#[no_mangle] unsafe extern "C" fn GPIO()` function, we do @@ -137,42 +146,6 @@ impl From for bool { } } -/// Event used to trigger interrupts. -#[cfg(any(esp32s2, esp32s3))] -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub enum Event { - /// Interrupts trigger on rising pin edge. - RisingEdge = 1, - /// Interrupts trigger on falling pin edge. - FallingEdge = 2, - /// Interrupts trigger on either rising or falling pin edges. - AnyEdge = 3, - /// Interrupts trigger on low level - LowLevel = 4, - /// Interrupts trigger on high level - HighLevel = 5, -} - -#[cfg(any(esp32s2, esp32s3))] -impl From for Event { - fn from(value: WakeEvent) -> Self { - match value { - WakeEvent::LowLevel => Event::LowLevel, - WakeEvent::HighLevel => Event::HighLevel, - } - } -} - -/// Event used to wake up from light sleep. -#[cfg(any(esp32s2, esp32s3))] -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub enum WakeEvent { - /// Wake on low level - LowLevel = 4, - /// Wake on high level - HighLevel = 5, -} - /// Flexible pin driver. /// Provides a common implementation for input and output pins. /// Currently hidden, as it is not intended to be used directly, @@ -237,30 +210,44 @@ impl Flex { self.set_level(!level); } - /// Listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn listen(&mut self, event: Event) { - self.clear_interrupt(); - enable_pin_interrupt::(event as u8); - } + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + // Validate the inputs + if wakeup_enable { + match event { + Event::LowLevel | Event::HighLevel => {}, + _ => { + panic!("GPIO wakeup is only supported with LowLevel or HighLevel event types."); + } + } + } + + self.clear_interrupt(); + interrupt::enable_pin_interrupt::(event as u8); + interrupt::pin_wakeup_enable::(wakeup_enable); + } - /// Un-listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn unlisten(&mut self) { - enable_pin_interrupt::(0); - self.clear_interrupt(); - } + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + interrupt::enable_pin_interrupt::(0); + interrupt::pin_wakeup_enable::(false); + self.clear_interrupt(); + } - /// Clear pin interrupts - #[cfg(any(esp32s2, esp32s3))] - pub fn clear_interrupt(&mut self) { - clear_pin_interrupt::(); - } + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } - /// Read pin interrupt - #[cfg(any(esp32s2, esp32s3))] - pub fn is_interrupt_set(&mut self) -> bool { - is_interrupt_set::() + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } } } @@ -280,25 +267,28 @@ impl Input { pub fn level(&self) -> Level { self.pin.level() } - /// Listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn listen(&mut self, event: Event) { - self.pin.listen(event); - } - /// Un-listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn unlisten(&mut self) { - self.pin.unlisten(); - } - /// Clear pin interrupts - #[cfg(any(esp32s2, esp32s3))] - pub fn clear_interrupt(&mut self) { - clear_pin_interrupt::(); - } - /// Read pin interrupt - #[cfg(any(esp32s2, esp32s3))] - pub fn is_interrupt_set(&mut self) -> bool { - is_interrupt_set::() + + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + self.pin.listen(event, wakeup_enable); + } + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } } } @@ -357,24 +347,27 @@ impl OutputOpenDrain { pub fn toggle(&mut self) { self.pin.toggle() } - /// Listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn listen(&mut self, event: Event) { - self.pin.listen(event); - } - /// Un-listen for interrupts. - #[cfg(any(esp32s2, esp32s3))] - pub fn unlisten(&mut self) { - self.pin.unlisten(); - } - /// Clear pin interrupts - #[cfg(any(esp32s2, esp32s3))] - pub fn clear_interrupt(&mut self) { - clear_pin_interrupt::(); - } - /// Read pin interrupt - #[cfg(any(esp32s2, esp32s3))] - pub fn is_interrupt_set(&mut self) -> bool { - is_interrupt_set::() + + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + self.pin.listen(event, wakeup_enable); + } + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } } } diff --git a/esp-lp-hal/src/critical_section.rs b/esp-lp-hal/src/interrupt/critical_section.rs similarity index 73% rename from esp-lp-hal/src/critical_section.rs rename to esp-lp-hal/src/interrupt/critical_section.rs index 37723fa4da2..28fb567bb00 100644 --- a/esp-lp-hal/src/critical_section.rs +++ b/esp-lp-hal/src/interrupt/critical_section.rs @@ -2,10 +2,10 @@ use critical_section::RawRestoreState; use crate::interrupt; -struct UlpCriticalSection; -critical_section::set_impl!(UlpCriticalSection); +struct CriticalSection; +critical_section::set_impl!(CriticalSection); -unsafe impl critical_section::Impl for UlpCriticalSection { +unsafe impl critical_section::Impl for CriticalSection { unsafe fn acquire() -> RawRestoreState { interrupt::disable_cpu_interrupts() } diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 7bb1ef851c1..0577152beec 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -1,91 +1,29 @@ -/// Portable interrupt binding and handling code -pub mod generic; -pub use generic::*; - -// RISCV ULP specific interrupt handlers -pub mod riscv_ulp; -pub use riscv_ulp::*; - -/// Setup interrupt handlers, including any default ones -#[inline(always)] -pub fn setup_interrupts() { - crate::gpio::bind_default_interrupt_handler(); - enable_cpu_interrupts(); -} - -/// Returns a bitmask of active interrupts -fn current_interrupts() -> u32 { - let mut status_bits: u32 = 0b0; - // The SENS peripheral status is broken out into individual bit flags - let sens_status = unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_st() - .read() - .bits(); - status_bits |= sens_status; - - // The GPIO peripheral status, is a single bit result, which is 1 if any GPIO interrupt status - // bit is 1. - let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); - let any_gpio_interrupt = gpio_status != 0; - - #[cfg(esp32s2)] - const SENS_BITFLAGS_LEN: usize = 9; - #[cfg(esp32s3)] - const SENS_BITFLAGS_LEN: usize = 12; - - if any_gpio_interrupt { - status_bits |= 1 << SENS_BITFLAGS_LEN; +cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + // RISCV ULP specific interrupt handlers, critical section + #[cfg(any(esp32s2, esp32s3))] + pub mod riscv_ulp; + #[cfg(any(esp32s2, esp32s3))] + pub use riscv_ulp::*; + #[cfg(any(esp32s2, esp32s3))] + mod critical_section; + + /// Portable interrupt binding and handling code + pub mod generic; + pub use generic::*; + } else { + /// If interrupt handling is not enabled, need to provide a stubs functions for + /// start_trap_rust and DefaultHandler. + #[doc(hidden)] + #[allow(dead_code)] + #[unsafe(link_section = ".trap.rust")] + #[unsafe(export_name = "_start_trap_rust")] + pub extern "C" fn stub_start_trap_rust(_trap_frame: *const u32, _irqs: u32) {} + + #[doc(hidden)] + #[allow(dead_code)] + #[unsafe(export_name = "DefaultHandler")] + pub fn stub_default_handler() {} } - - status_bits -} - -/// Enables or disables a peripheral interrupt. -/// -/// Note that interrupts still need to be enabled globally for interrupts -/// to be serviced. -/// -/// Internally, this function maps the interrupt to the appropriate CPU interrupt. -#[inline] -pub fn set_enabled(interrupt: Interrupt, enable: bool) { - // Enable/disable SENS interrupts - unsafe { &*crate::pac::SENS::PTR } - .sar_cocpu_int_ena() - .write(|w| { - #[cfg(esp32s3)] - match interrupt { - Interrupt::TOUCH_DONE_INT => w.sar_cocpu_touch_done_int_ena().bit(enable), - Interrupt::TOUCH_INACTIVE_INT => w.sar_cocpu_touch_inactive_int_ena().bit(enable), - Interrupt::TOUCH_ACTIVE_INT => w.sar_cocpu_touch_active_int_ena().bit(enable), - Interrupt::SARADC1_DONE_INT => w.sar_cocpu_saradc1_int_ena().bit(enable), - Interrupt::SARADC2_DONE_INT => w.sar_cocpu_saradc2_int_ena().bit(enable), - Interrupt::TSENS_DONE_INT => w.sar_cocpu_tsens_int_ena().bit(enable), - Interrupt::RISCV_START_INT => w.sar_cocpu_start_int_ena().bit(enable), - Interrupt::SW_INT => w.sar_cocpu_sw_int_ena().bit(enable), - Interrupt::SWD_INT => w.sar_cocpu_swd_int_ena().bit(enable), - Interrupt::TOUCH_TIME_OUT_INT => w.sar_cocpu_touch_timeout_int_ena().bit(enable), - Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => { - w.sar_cocpu_touch_approach_loop_done_int_ena().bit(enable) - } - Interrupt::TOUCH_SCAN_DONE_INT => w.sar_cocpu_touch_scan_done_int_ena().bit(enable), - // Ignore any other non-SENS interrupts - _ => w, - } - #[cfg(esp32s2)] - match interrupt { - Interrupt::TOUCH_DONE_INT => w.cocpu_touch_done_int_ena().bit(enable), - Interrupt::TOUCH_INACTIVE_INT => w.cocpu_touch_inactive_int_ena().bit(enable), - Interrupt::TOUCH_ACTIVE_INT => w.cocpu_touch_active_int_ena().bit(enable), - Interrupt::SARADC1_DONE_INT => w.cocpu_saradc1_int_ena().bit(enable), - Interrupt::SARADC2_DONE_INT => w.cocpu_saradc2_int_ena().bit(enable), - Interrupt::TSENS_DONE_INT => w.cocpu_tsens_int_ena().bit(enable), - Interrupt::RISCV_START_INT => w.cocpu_start_int_ena().bit(enable), - Interrupt::SW_INT => w.cocpu_sw_int_ena().bit(enable), - Interrupt::SWD_INT => w.cocpu_swd_int_ena().bit(enable), - // Ignore any other non-SENS interrupts - _ => w, - } - }); - // GPIO interrupt requires no enable/disable handling here, - // as it is handled by the gpio module on a per-pin basis. } diff --git a/esp-lp-hal/src/interrupt/riscv_ulp.rs b/esp-lp-hal/src/interrupt/riscv_ulp.rs index 5cd5610723b..e1744ee859d 100644 --- a/esp-lp-hal/src/interrupt/riscv_ulp.rs +++ b/esp-lp-hal/src/interrupt/riscv_ulp.rs @@ -4,6 +4,90 @@ use core::ptr::NonNull; use super::{Interrupt, InterruptStatus, bound_handler}; +/// Setup interrupt handlers, including any default ones +#[inline(always)] +pub fn setup_interrupts() { + enable_cpu_interrupts(); +} + +/// Enables or disables a peripheral interrupt. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. +#[inline] +pub fn set_enabled(interrupt: Interrupt, enable: bool) { + // Enable/disable SENS interrupts + unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| { + #[cfg(esp32s3)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.sar_cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.sar_cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.sar_cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.sar_cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.sar_cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.sar_cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.sar_cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.sar_cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.sar_cocpu_swd_int_ena().bit(enable), + Interrupt::TOUCH_TIME_OUT_INT => w.sar_cocpu_touch_timeout_int_ena().bit(enable), + Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => { + w.sar_cocpu_touch_approach_loop_done_int_ena().bit(enable) + } + Interrupt::TOUCH_SCAN_DONE_INT => w.sar_cocpu_touch_scan_done_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + #[cfg(esp32s2)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.cocpu_swd_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + }); + + // GPIO interrupt requires no enable/disable handling here, + // as it is handled by the gpio module on a per-pin basis. +} + +/// Returns a bitmask of active interrupts +pub fn current_interrupts() -> u32 { + let mut status_bits: u32 = 0b0; + // The SENS peripheral status is broken out into individual bit flags + let sens_status = unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_st() + .read() + .bits(); + status_bits |= sens_status; + + // The GPIO peripheral status, is a single bit result, which is 1 if any GPIO interrupt status + // bit is 1. + let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); + let any_gpio_interrupt = gpio_status != 0; + + #[cfg(esp32s2)] + const SENS_BITFLAGS_LEN: usize = 9; + #[cfg(esp32s3)] + const SENS_BITFLAGS_LEN: usize = 12; + + if any_gpio_interrupt { + status_bits |= 1 << SENS_BITFLAGS_LEN; + } + + status_bits +} + /// Set the IRQ Mask, returns the previous mask value. /// IRQ Type Bit Description /// Internal 0 Internal timer interrupt diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 14fdc09d74a..79d81c950aa 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,12 +38,9 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; -/// Critical section implementation for ULP cores -#[cfg(any(esp32s2, esp32s3))] -mod critical_section; - -/// Interrupt handling for RISCV ULP cores (ESP32-S2,ESP32-S3) -#[cfg(any(esp32s2, esp32s3))] +/// Interrupt handling APIs. +/// Currently implemented for RISCV ULP cores (ESP32-S2,ESP32-S3) +/// Functions are gated internally by stub functions. pub mod interrupt; /// The prelude @@ -87,6 +84,30 @@ pub fn wake_hp_core() { .write(|w| w.rtc_sw_cpu_int().set_bit()); } +/// Global GPIO wakeup enable/disable +pub fn gpio_wakeup_enable(enable: bool) { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); +} + +/// Clear the Global GPIO wakeup status +pub fn gpio_wakeup_clear() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); +} + /// Entry point to the ULP program #[unsafe(link_section = ".init.rust")] #[unsafe(export_name = "rust_main")] @@ -101,18 +122,18 @@ unsafe extern "C" fn lp_core_startup() -> ! { static mut DEVICE_PERIPHERALS: bool; } + #[cfg(any(esp32s2, esp32s3))] + ulp_riscv_rescue_from_monitor(); + + #[cfg(feature = "interrupts")] + interrupt::setup_interrupts(); + // The pac::DEVICE_PERIPHERALS variable is re-zero-ed on start, // to prevent it from persisting between calls to main(). // This prevents ULP-Timer-triggered-main()-calls from panicking // on their second loop. DEVICE_PERIPHERALS = false; - #[cfg(any(esp32s2, esp32s3))] - { - ulp_riscv_rescue_from_monitor(); - interrupt::setup_interrupts(); - } - #[cfg(esp32c6)] if (*pac::LP_CLKRST::PTR) .lp_clk_conf() @@ -139,7 +160,6 @@ unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { } /// Stops the ULP core, called from itself. -#[unsafe(link_section = ".init.rust")] fn ulp_riscv_halt() -> ! { #[cfg(any(esp32s2, esp32s3))] { @@ -152,10 +172,9 @@ fn ulp_riscv_halt() -> ! { }); } - // All chips will enter a no-op loop, when halting. - loop { - unsafe { - core::arch::asm!("addi x0, x0, 0"); // no-op - } - } + // All chips will enter an infinite, when halting. + // It's important that no 'nop' or 'wfi' is performed inside this loop, + // so that the chip silicon can properly detect a ULP halt. + #[allow(clippy::empty_loop)] + loop {} } From 787b431a68d11ea80f6ed7463d074660b74ebdea Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 18 Apr 2026 13:50:53 +1000 Subject: [PATCH 37/47] Tweak gpio_wakeup_enable, update deps --- esp-lp-hal/Cargo.toml | 4 ++-- esp-lp-hal/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 99b7574d0ca..055253cf4a0 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -42,11 +42,11 @@ embedded-hal-nb = { version = "1.0.0", optional = true } embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true } embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } nb = { version = "1.1.0", optional = true } -procmacros = { version = "0.21.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } +procmacros = { version = "0.22.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } -esp-metadata-generated = { version = "0.3.0", path = "../esp-metadata-generated" } +esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 79d81c950aa..8968187a1f0 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -85,15 +85,15 @@ pub fn wake_hp_core() { } /// Global GPIO wakeup enable/disable -pub fn gpio_wakeup_enable(enable: bool) { +pub fn gpio_wakeup_enable() { #[cfg(esp32s2)] unsafe { &*crate::pac::RTC_CNTL::PTR } .ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(true)); #[cfg(esp32s3)] unsafe { &*crate::pac::RTC_CNTL::PTR } .rtc_ulp_cp_timer() - .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(enable)); + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(true)); } /// Clear the Global GPIO wakeup status From a0b0b3f99106bb66cfabd4bca6cec05a68fff36f Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 18 Apr 2026 14:01:04 +1000 Subject: [PATCH 38/47] Hide interrupt support from ESP32C6 until implemented --- esp-lp-hal/src/interrupt/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 0577152beec..26cc1a265e3 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -10,7 +10,9 @@ cfg_if::cfg_if! { mod critical_section; /// Portable interrupt binding and handling code + #[cfg(any(esp32s2, esp32s3))] pub mod generic; + #[cfg(any(esp32s2, esp32s3))] pub use generic::*; } else { /// If interrupt handling is not enabled, need to provide a stubs functions for From 013b2f217754bf661bdfc6810dd678128befb184 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 18 Apr 2026 14:11:58 +1000 Subject: [PATCH 39/47] Fix rtc_io.wakeup_enable on esp32s2 --- esp-hal/src/gpio/rtc_io.rs | 17 +++++++++++++++++ .../lp_core/ulp_gpio_wakeup/src/main.rs | 2 +- .../lp_core/ulp_timer_counter/src/main.rs | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index 8d381b734a1..4a5ba0b7165 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -117,14 +117,31 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { } /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. + #[cfg(any(esp32s2, esp32s3))] pub fn wakeup_enable(&self, event: Option) { let pin_reg_rtc = RTC_IO::regs().pin(PIN as usize); if let Some(evt) = event { + #[cfg(esp32s3)] pin_reg_rtc .write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); + #[cfg(esp32s2)] + pin_reg_rtc.write(|w| unsafe { + w.gpio_pin_int_type() + .bits(evt as u8) + .gpio_pin_wakeup_enable() + .bit(true) + }); } else { + #[cfg(esp32s3)] pin_reg_rtc.write(|w| unsafe { w.int_type().bits(0).wakeup_enable().bit(false) }); + #[cfg(esp32s2)] + pin_reg_rtc.write(|w| unsafe { + w.gpio_pin_int_type() + .bits(0) + .gpio_pin_wakeup_enable() + .bit(false) + }); } } } diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs index 0d0dec6221b..23744277378 100644 --- a/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs @@ -9,7 +9,7 @@ //! The following wiring is assumed: //! - BUTTON => GPIO0 -//% CHIPS: esp32s3,esp32s2 +//% CHIPS: esp32s3 esp32s2 #![no_std] #![no_main] diff --git a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs index 4dee5d14891..38b92b06411 100644 --- a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs +++ b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs @@ -10,7 +10,7 @@ //! The following wiring is assumed: //! - BUTTON => GPIO0 -//% CHIPS: esp32s3,esp32s2 +//% CHIPS: esp32s3 esp32s2 #![no_std] #![no_main] From ebfd35ff547cc20c31a702c7567ca0c6493b596b Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 18 Apr 2026 14:25:09 +1000 Subject: [PATCH 40/47] Add more stub implementations for esp32c6, so CI can pass --- esp-lp-hal/Cargo.toml | 2 +- esp-lp-hal/src/gpio/interrupt.rs | 4 ++ esp-lp-hal/src/gpio/mod.rs | 10 +++- esp-lp-hal/src/interrupt/lp_core.rs | 53 +++++++++++++++++++ esp-lp-hal/src/interrupt/mod.rs | 12 +++-- .../interrupt/{riscv_ulp.rs => ulp_core.rs} | 0 6 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 esp-lp-hal/src/interrupt/lp_core.rs rename esp-lp-hal/src/interrupt/{riscv_ulp.rs => ulp_core.rs} (100%) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 055253cf4a0..91f99c83053 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -47,7 +47,7 @@ riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs index fe69337ad93..bda570aab46 100644 --- a/esp-lp-hal/src/gpio/interrupt.rs +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -1,6 +1,7 @@ pub use interrupt::{Interrupt, InterruptHandler, InterruptStatus}; pub use procmacros::handler; +#[allow(unused_imports)] use super::{Io, LpIo}; use crate::interrupt; @@ -58,6 +59,7 @@ pub fn gpio_interrupt_status() -> u32 { /// Clear the interrupt status for a bit mask of pins /// Expects pinmask bit 0 == GPIO0 +#[allow(unused_variables)] #[inline] pub fn gpio_interrupt_clear(pinmask: u32) { #[cfg(any(esp32s2, esp32s3))] @@ -74,6 +76,7 @@ pub fn gpio_interrupt_clear(pinmask: u32) { /// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will /// leave the int_type setting as-is. /// - `wake_up`: whether to wake up from light sleep. +#[allow(unused_variables)] pub fn enable_pin_interrupt(int_type: u8) { #[cfg(any(esp32s2, esp32s3))] { @@ -96,6 +99,7 @@ pub fn is_interrupt_set() -> bool { } /// Enable / disable pin wakeup +#[allow(unused_variables)] pub fn pin_wakeup_enable(en: bool) { #[cfg(any(esp32s2, esp32s3))] { diff --git a/esp-lp-hal/src/gpio/mod.rs b/esp-lp-hal/src/gpio/mod.rs index 0bc58651ea5..969bf942844 100644 --- a/esp-lp-hal/src/gpio/mod.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -88,7 +88,15 @@ impl Io { /// [`is_interrupt_set()`]: Input::is_interrupt_set pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { USER_INTERRUPT_HANDLER.store(handler.handler().callback()); - crate::interrupt::bind_handler(Interrupt::GPIO_INT, user_gpio_interrupt_handler); + #[cfg(any(esp32s2, esp32s3))] + { + crate::interrupt::bind_handler(Interrupt::GPIO_INT, user_gpio_interrupt_handler); + } + + #[cfg(esp32c6)] + { + todo!() + } } } diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs new file mode 100644 index 00000000000..d6a3e58bb4b --- /dev/null +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -0,0 +1,53 @@ +//! Interrupt handling for ESP32-C6 +// use core::ptr::NonNull; +// use super::{Interrupt, InterruptStatus, bound_handler}; +use super::Interrupt; + +/// Setup interrupt handlers, including any default ones +#[inline(always)] +pub fn setup_interrupts() { + enable_cpu_interrupts(); +} + +/// Enables or disables a peripheral interrupt. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. +#[inline] +pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { + todo!() +} + +/// Returns a bitmask of active interrupts +pub fn current_interrupts() -> u32 { + todo!() +} + +/// Set the IRQ Mask, returns the previous mask value. +#[inline(always)] +pub fn mask_cpu_interrupts(_new_mask: u32) -> u32 { + todo!() +} + +/// Enable all CPU interrupts by setting IRQ mask +#[inline(always)] +pub fn enable_cpu_interrupts() { + mask_cpu_interrupts(0x0); +} + +/// Disable all CPU interrupts by clearing IRQ mask, +/// returns the previous IRQ Mask +#[inline(always)] +pub fn disable_cpu_interrupts() -> u32 { + mask_cpu_interrupts(0xFFFF); + todo!() +} + +/// Default interrupt handler, does nothing. +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub fn DefaultHandler() {} diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 26cc1a265e3..f9d18d5303a 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -3,16 +3,20 @@ cfg_if::cfg_if! { { // RISCV ULP specific interrupt handlers, critical section #[cfg(any(esp32s2, esp32s3))] - pub mod riscv_ulp; + pub mod ulp_core; #[cfg(any(esp32s2, esp32s3))] - pub use riscv_ulp::*; + pub use ulp_core::*; + // ULP-core critical section implementation #[cfg(any(esp32s2, esp32s3))] mod critical_section; + #[cfg(esp32c6)] + pub mod lp_core; + #[cfg(esp32c6)] + pub use lp_core::*; + /// Portable interrupt binding and handling code - #[cfg(any(esp32s2, esp32s3))] pub mod generic; - #[cfg(any(esp32s2, esp32s3))] pub use generic::*; } else { /// If interrupt handling is not enabled, need to provide a stubs functions for diff --git a/esp-lp-hal/src/interrupt/riscv_ulp.rs b/esp-lp-hal/src/interrupt/ulp_core.rs similarity index 100% rename from esp-lp-hal/src/interrupt/riscv_ulp.rs rename to esp-lp-hal/src/interrupt/ulp_core.rs From f396396c7185f8414037b04f08c34561feeaa65a Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 18 Apr 2026 15:52:10 +1000 Subject: [PATCH 41/47] Fix C6 compilation due to stray todo --- esp-lp-hal/ld/link-lp.x | 3 +++ esp-lp-hal/src/interrupt/critical_section.rs | 6 +++--- esp-lp-hal/src/interrupt/lp_core.rs | 11 +++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/esp-lp-hal/ld/link-lp.x b/esp-lp-hal/ld/link-lp.x index f0471d64e06..951d4e6a8c8 100644 --- a/esp-lp-hal/ld/link-lp.x +++ b/esp-lp-hal/ld/link-lp.x @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* Provides interrupt vector symbols */ +INCLUDE device.x + ENTRY(reset_vector) VECTOR_TABLE_LENGTH = 0x80; diff --git a/esp-lp-hal/src/interrupt/critical_section.rs b/esp-lp-hal/src/interrupt/critical_section.rs index 28fb567bb00..37723fa4da2 100644 --- a/esp-lp-hal/src/interrupt/critical_section.rs +++ b/esp-lp-hal/src/interrupt/critical_section.rs @@ -2,10 +2,10 @@ use critical_section::RawRestoreState; use crate::interrupt; -struct CriticalSection; -critical_section::set_impl!(CriticalSection); +struct UlpCriticalSection; +critical_section::set_impl!(UlpCriticalSection); -unsafe impl critical_section::Impl for CriticalSection { +unsafe impl critical_section::Impl for UlpCriticalSection { unsafe fn acquire() -> RawRestoreState { interrupt::disable_cpu_interrupts() } diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs index d6a3e58bb4b..f250cfba22d 100644 --- a/esp-lp-hal/src/interrupt/lp_core.rs +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -5,9 +5,7 @@ use super::Interrupt; /// Setup interrupt handlers, including any default ones #[inline(always)] -pub fn setup_interrupts() { - enable_cpu_interrupts(); -} +pub fn setup_interrupts() {} /// Enables or disables a peripheral interrupt. /// @@ -17,7 +15,7 @@ pub fn setup_interrupts() { /// Internally, this function maps the interrupt to the appropriate CPU interrupt. #[inline] pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { - todo!() + // todo!() } /// Returns a bitmask of active interrupts @@ -34,14 +32,15 @@ pub fn mask_cpu_interrupts(_new_mask: u32) -> u32 { /// Enable all CPU interrupts by setting IRQ mask #[inline(always)] pub fn enable_cpu_interrupts() { - mask_cpu_interrupts(0x0); + // mask_cpu_interrupts(0x0); + todo!() } /// Disable all CPU interrupts by clearing IRQ mask, /// returns the previous IRQ Mask #[inline(always)] pub fn disable_cpu_interrupts() -> u32 { - mask_cpu_interrupts(0xFFFF); + // mask_cpu_interrupts(0xFFFF); todo!() } From e7a3515ffbfe9f930772efd3b5f639a39111c455 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 19 Apr 2026 10:06:23 +1000 Subject: [PATCH 42/47] Clean-up of RTC/LP IO wakeup_enable method. Implementing WakeupFromLpCoreWakeupSource for ESP32C6. --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/gpio/lp_io.rs | 13 ++++++- esp-hal/src/gpio/rtc_io.rs | 36 +++++++------------ esp-hal/src/rtc_cntl/sleep/esp32s2.rs | 4 +-- esp-hal/src/rtc_cntl/sleep/esp32s3.rs | 4 +-- esp-hal/src/rtc_cntl/sleep/mod.rs | 8 ++--- esp-hal/src/soc/esp32c6/lp_core.rs | 4 +++ esp-lp-hal/Cargo.toml | 6 ++-- esp-lp-hal/src/lib.rs | 4 +++ .../src/_build_script_utils.rs | 4 ++- esp-metadata/devices/esp32c6.toml | 1 + 11 files changed, 48 insertions(+), 37 deletions(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 0a4606b9099..2946e686431 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- ESP32-S2, ESP32-S3: Renamed `UlpWakeupSource` to `WakeFromUlpCoreWakeupSource`, to differentiate it from `UlpCoreWakeupSource` (#5206) ### Fixed diff --git a/esp-hal/src/gpio/lp_io.rs b/esp-hal/src/gpio/lp_io.rs index a48deb6dc53..90b393ab986 100644 --- a/esp-hal/src/gpio/lp_io.rs +++ b/esp-hal/src/gpio/lp_io.rs @@ -30,7 +30,10 @@ use core::marker::PhantomData; use super::{InputPin, OutputPin, RtcPin}; -use crate::peripherals::{GPIO, LP_AON, LP_IO}; +use crate::{ + gpio::WakeEvent, + peripherals::{GPIO, LP_AON, LP_IO}, +}; /// A GPIO output pin configured for low power operation pub struct LowPowerOutput<'d, const PIN: u8> { @@ -111,6 +114,14 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { .gpio(PIN as usize) .modify(|_, w| w.fun_wpd().bit(enable)); } + + /// Allows this pin to wakeup the LP core, when LpCoreWakeupSource::Gpio is used. + pub fn wakeup_enable(&self, event: Option) { + let pin_reg = LP_IO::regs().pin(PIN as usize); + let wake_en = event.is_some(); + let int_type = event.map_or(0, |e| e as u8); + pin_reg.write(|w| unsafe { w.int_type().bits(int_type).wakeup_enable().bit(wake_en) }); + } } /// A GPIO open-drain output pin configured for low power operation diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index 4a5ba0b7165..bd682997daf 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -117,32 +117,20 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { } /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. - #[cfg(any(esp32s2, esp32s3))] pub fn wakeup_enable(&self, event: Option) { let pin_reg_rtc = RTC_IO::regs().pin(PIN as usize); - - if let Some(evt) = event { - #[cfg(esp32s3)] - pin_reg_rtc - .write(|w| unsafe { w.int_type().bits(evt as u8).wakeup_enable().bit(true) }); - #[cfg(esp32s2)] - pin_reg_rtc.write(|w| unsafe { - w.gpio_pin_int_type() - .bits(evt as u8) - .gpio_pin_wakeup_enable() - .bit(true) - }); - } else { - #[cfg(esp32s3)] - pin_reg_rtc.write(|w| unsafe { w.int_type().bits(0).wakeup_enable().bit(false) }); - #[cfg(esp32s2)] - pin_reg_rtc.write(|w| unsafe { - w.gpio_pin_int_type() - .bits(0) - .gpio_pin_wakeup_enable() - .bit(false) - }); - } + let wake_en = event.is_some(); + let int_type = event.map_or(0, |e| e as u8); + + #[cfg(esp32s3)] + pin_reg_rtc.write(|w| unsafe { w.int_type().bits(int_type).wakeup_enable().bit(wake_en) }); + #[cfg(esp32s2)] + pin_reg_rtc.write(|w| unsafe { + w.gpio_pin_int_type() + .bits(int_type) + .gpio_pin_wakeup_enable() + .bit(wake_en) + }); } } diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s2.rs b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs index 02f29ed89c6..bd0e3927dc6 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32s2.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs @@ -2,7 +2,7 @@ use super::{ Ext0WakeupSource, Ext1WakeupSource, TimerWakeupSource, - UlpWakeupSource, + WakeFromUlpCoreWakeupSource, WakeSource, WakeTriggers, WakeupLevel, @@ -74,7 +74,7 @@ pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; /// RTC memory wait cycles. pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; -impl WakeSource for UlpWakeupSource { +impl WakeSource for WakeFromUlpCoreWakeupSource { fn apply( &self, _rtc: &Rtc<'_>, diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs index bd520c32724..4b215269059 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs @@ -2,7 +2,7 @@ use super::{ Ext0WakeupSource, Ext1WakeupSource, TimerWakeupSource, - UlpWakeupSource, + WakeFromUlpCoreWakeupSource, WakeSource, WakeTriggers, WakeupLevel, @@ -82,7 +82,7 @@ pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; /// RTC memory wait cycles. pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; -impl WakeSource for UlpWakeupSource { +impl WakeSource for WakeFromUlpCoreWakeupSource { fn apply( &self, _rtc: &Rtc<'_>, diff --git a/esp-hal/src/rtc_cntl/sleep/mod.rs b/esp-hal/src/rtc_cntl/sleep/mod.rs index d6b455131ee..72bb8052c02 100644 --- a/esp-hal/src/rtc_cntl/sleep/mod.rs +++ b/esp-hal/src/rtc_cntl/sleep/mod.rs @@ -375,15 +375,15 @@ impl Default for WakeFromLpCoreWakeupSource { /// /// This wakeup source can be used to wake up from both light and deep sleep. #[cfg(any(esp32s2, esp32s3))] -pub struct UlpWakeupSource { +pub struct WakeFromUlpCoreWakeupSource { wake_on_interrupt: bool, wake_on_trap: bool, clear_interrupts_on_sleep: bool, } #[cfg(any(esp32s2, esp32s3))] -impl UlpWakeupSource { - /// Create a new instance of `WakeFromUlpWakeupSource` +impl WakeFromUlpCoreWakeupSource { + /// Create a new instance of `WakeFromUlpCoreWakeupSource` pub const fn new() -> Self { Self { wake_on_interrupt: true, @@ -421,7 +421,7 @@ impl UlpWakeupSource { } #[cfg(any(esp32s2, esp32s3))] -impl Default for UlpWakeupSource { +impl Default for WakeFromUlpCoreWakeupSource { fn default() -> Self { Self::new() } diff --git a/esp-hal/src/soc/esp32c6/lp_core.rs b/esp-hal/src/soc/esp32c6/lp_core.rs index c0595827d15..1c4f1adab03 100644 --- a/esp-hal/src/soc/esp32c6/lp_core.rs +++ b/esp-hal/src/soc/esp32c6/lp_core.rs @@ -26,6 +26,8 @@ use crate::peripherals::{LP_AON, LP_CORE, LP_PERI, LPWR, PMU}; pub enum LpCoreWakeupSource { /// Wakeup source from the HP (High Performance) CPU. HpCpu, + /// Wakeup source from LP IO interrupt status + LpIo, } /// Clock sources for the LP core @@ -119,6 +121,7 @@ fn ulp_lp_core_run(wakeup_src: LpCoreWakeupSource) { // Set wake-up sources let src = match wakeup_src { LpCoreWakeupSource::HpCpu => 0x01, + LpCoreWakeupSource::LpIo => 0x04, }; pmu.lp_cpu_pwr1() .modify(|_, w| unsafe { w.lp_cpu_wakeup_en().bits(src) }); @@ -133,5 +136,6 @@ fn ulp_lp_core_run(wakeup_src: LpCoreWakeupSource) { LpCoreWakeupSource::HpCpu => { pmu.hp_lp_cpu_comm().write(|w| w.hp_trigger_lp().set_bit()); } + LpCoreWakeupSource::LpIo => {} } } diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 91f99c83053..87e33a79647 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -47,9 +47,9 @@ riscv = { version = "0.15" } portable-atomic = { version = "1", default-features = false } critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "86d386a" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } [dev-dependencies] panic-halt = "0.2.0" diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 8968187a1f0..0a1474950d3 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -106,6 +106,10 @@ pub fn gpio_wakeup_clear() { unsafe { &*crate::pac::RTC_CNTL::PTR } .rtc_ulp_cp_timer() .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); + #[cfg(esp32c6)] + unsafe { &*crate::pac::LP_IO::PTR } + .status_w1tc() + .write(|w| unsafe { w.status_w1tc().bits(0xff) }); } /// Entry point to the ULP program diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index e1b0826dc41..4955e116178 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -2399,6 +2399,7 @@ impl Chip { "gpio_support_deepsleep_wakeup", "uart_support_wakeup_int", "pm_support_ext1_wakeup", + "riscv_coproc_supported", "adc_driver_supported", "aes_driver_supported", "assist_debug_driver_supported", @@ -2674,6 +2675,7 @@ impl Chip { "cargo:rustc-cfg=gpio_support_deepsleep_wakeup", "cargo:rustc-cfg=uart_support_wakeup_int", "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=riscv_coproc_supported", "cargo:rustc-cfg=adc_driver_supported", "cargo:rustc-cfg=aes_driver_supported", "cargo:rustc-cfg=assist_debug_driver_supported", @@ -5807,6 +5809,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_twai1)"); println!("cargo:rustc-check-cfg=cfg(lp_core)"); println!("cargo:rustc-check-cfg=cfg(pm_support_beacon_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(riscv_coproc_supported)"); println!("cargo:rustc-check-cfg=cfg(etm_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(lp_uart_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(ulp_riscv_driver_supported)"); @@ -5841,7 +5844,6 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_dma_copy)"); println!("cargo:rustc-check-cfg=cfg(soc_has_ulp_riscv_core)"); println!("cargo:rustc-check-cfg=cfg(ulp_riscv_core)"); - println!("cargo:rustc-check-cfg=cfg(riscv_coproc_supported)"); println!("cargo:rustc-check-cfg=cfg(usb_otg_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_gcm)"); println!("cargo:rustc-check-cfg=cfg(dedicated_gpio_needs_initialization)"); diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index 9803876e796..ebcb2978193 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -30,6 +30,7 @@ symbols = [ "gpio_support_deepsleep_wakeup", "uart_support_wakeup_int", "pm_support_ext1_wakeup", + "riscv_coproc_supported", ] [device.soc] From c0f152489048455eac3a0866cf7c4fb3ca6e01ca Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sat, 25 Apr 2026 17:12:46 +1000 Subject: [PATCH 43/47] Start fixing up the ESP32C6-LP interrupt handling --- esp-lp-hal/Cargo.toml | 16 +- esp-lp-hal/examples/i2c.rs | 2 +- esp-lp-hal/ld/link-lp.x | 2 + esp-lp-hal/src/gpio/mod.rs | 2 +- esp-lp-hal/src/interrupt/critical_section.rs | 11 +- esp-lp-hal/src/interrupt/lp_core.rs | 94 +++++++--- esp-lp-hal/src/interrupt/mod.rs | 10 +- .../src/interrupt/{generic.rs => shared.rs} | 100 ++++++++++- esp-lp-hal/src/interrupt/ulp_core.rs | 166 ++++++------------ esp-lp-hal/src/lib.rs | 4 +- esp-lp-hal/src/lp_start.S | 136 +++++++++++--- 11 files changed, 351 insertions(+), 192 deletions(-) rename esp-lp-hal/src/interrupt/{generic.rs => shared.rs} (64%) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 87e33a79647..8896465f930 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -43,13 +43,13 @@ embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } nb = { version = "1.1.0", optional = true } procmacros = { version = "0.22.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -riscv = { version = "0.15" } +critical-section = { version = "1.2.0", features = [ "restore-state-bool" ] } +riscv = { version = "0.15", default-features = false } portable-atomic = { version = "1", default-features = false } -critical-section = { version = "1.2.0", features = ["restore-state-u32"], optional = true } esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "f0a1b2c" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } [dev-dependencies] panic-halt = "0.2.0" @@ -70,11 +70,11 @@ debug = [ # Chip Support Feature Flags # Target the ESP32-C6. -esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb", "riscv/critical-section-single-hart"] +esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb" ] # Target the ESP32-S2. -esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core", "dep:critical-section" ] +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core" ] # Target the ESP32-S3. -esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core", "dep:critical-section" ] +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core" ] #! ### Trait Implementation Feature Flags ## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and diff --git a/esp-lp-hal/examples/i2c.rs b/esp-lp-hal/examples/i2c.rs index 65e854d8bd5..9d07f744f62 100644 --- a/esp-lp-hal/examples/i2c.rs +++ b/esp-lp-hal/examples/i2c.rs @@ -17,7 +17,7 @@ use panic_halt as _; #[entry] fn main(mut i2c: LpI2c) -> ! { - let _peripherals = esp32c6_lp::Peripherals::take().unwrap(); + let _peripherals = esp_lp_hal::pac::Peripherals::take().unwrap(); loop { let mut data = [0u8; 22]; diff --git a/esp-lp-hal/ld/link-lp.x b/esp-lp-hal/ld/link-lp.x index 951d4e6a8c8..5699ea2d630 100644 --- a/esp-lp-hal/ld/link-lp.x +++ b/esp-lp-hal/ld/link-lp.x @@ -39,6 +39,8 @@ SECTIONS KEEP(*(.init)); KEEP(*(.init.rust)); + KEEP(*(.trap)); + KEEP(*(.trap.rust)); *(.text) *(.text*) } > ram diff --git a/esp-lp-hal/src/gpio/mod.rs b/esp-lp-hal/src/gpio/mod.rs index 969bf942844..278736a7951 100644 --- a/esp-lp-hal/src/gpio/mod.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -234,7 +234,7 @@ impl Flex { } } - self.clear_interrupt(); + // self.clear_interrupt(); interrupt::enable_pin_interrupt::(event as u8); interrupt::pin_wakeup_enable::(wakeup_enable); } diff --git a/esp-lp-hal/src/interrupt/critical_section.rs b/esp-lp-hal/src/interrupt/critical_section.rs index 37723fa4da2..b8b24652c54 100644 --- a/esp-lp-hal/src/interrupt/critical_section.rs +++ b/esp-lp-hal/src/interrupt/critical_section.rs @@ -2,16 +2,15 @@ use critical_section::RawRestoreState; use crate::interrupt; -struct UlpCriticalSection; -critical_section::set_impl!(UlpCriticalSection); +struct CriticalSection; +critical_section::set_impl!(CriticalSection); -unsafe impl critical_section::Impl for UlpCriticalSection { +unsafe impl critical_section::Impl for CriticalSection { unsafe fn acquire() -> RawRestoreState { - interrupt::disable_cpu_interrupts() + interrupt::machine_interrupt_enable(false) } unsafe fn release(previous_state: RawRestoreState) { - // Only re-enable interrupts if they were enabled before the critical section. - interrupt::mask_cpu_interrupts(previous_state); + interrupt::machine_interrupt_enable(previous_state); } } diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs index f250cfba22d..1af101daeec 100644 --- a/esp-lp-hal/src/interrupt/lp_core.rs +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -1,11 +1,13 @@ -//! Interrupt handling for ESP32-C6 -// use core::ptr::NonNull; -// use super::{Interrupt, InterruptStatus, bound_handler}; +//! Interrupt handling for ESP32-C6 LP core +use riscv::{CoreInterruptNumber, InterruptNumber}; + use super::Interrupt; /// Setup interrupt handlers, including any default ones #[inline(always)] -pub fn setup_interrupts() {} +pub fn setup_interrupts() { + machine_interrupt_enable(true); +} /// Enables or disables a peripheral interrupt. /// @@ -18,35 +20,75 @@ pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { // todo!() } -/// Returns a bitmask of active interrupts -pub fn current_interrupts() -> u32 { +/// Returns a bitmask of active peripheral interrupts +pub fn status() -> u32 { todo!() } -/// Set the IRQ Mask, returns the previous mask value. +/// Returns the cause of a machine interrupt, +/// which contains the exception code, and if a peripheral interrupt was flagged. +#[unsafe(link_section = ".trap.rust")] #[inline(always)] -pub fn mask_cpu_interrupts(_new_mask: u32) -> u32 { - todo!() +pub fn trap_cause() -> riscv::interrupt::Trap { + let mcause_reg = riscv::register::mcause::read(); + mcause_reg.cause() } -/// Enable all CPU interrupts by setting IRQ mask -#[inline(always)] -pub fn enable_cpu_interrupts() { - // mask_cpu_interrupts(0x0); - todo!() +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +enum LpMachineInterrupt { + MachineSoft = 3, // Global enable/disable bit in mstatus + Peripheral = 30, // Custom interrupt for peripherals } -/// Disable all CPU interrupts by clearing IRQ mask, -/// returns the previous IRQ Mask -#[inline(always)] -pub fn disable_cpu_interrupts() -> u32 { - // mask_cpu_interrupts(0xFFFF); - todo!() +unsafe impl InterruptNumber for LpMachineInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 31_usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> riscv::result::Result { + match value { + 3 => Ok(Self::MachineSoft), + 30 => Ok(Self::Peripheral), + _ => Err(riscv::result::Error::InvalidVariant(value)), + } + } } -/// Default interrupt handler, does nothing. -#[allow(dead_code)] -#[doc(hidden)] -#[allow(non_snake_case)] -#[unsafe(no_mangle)] -pub fn DefaultHandler() {} +unsafe impl CoreInterruptNumber for LpMachineInterrupt {} + +/// Set the mie register (or equivalent), +/// returning the previous value. +#[inline(always)] +pub fn machine_interrupt_enable(enable: bool) -> bool { + // ESP32C6-LP must enable both the + // global machine soft interrupt, (mie bit of the mstatus register), + // and also the external peripheral interrupt (bit 30 in mie register). + + let mut mstatus_reg = riscv::register::mstatus::read(); + let mut mie_reg = riscv::register::mie::read(); + + // The original value of the global interrupt enable bit, will be returned. + let old_mie_enable = mstatus_reg.mie(); + + // Update register values + if enable { + mstatus_reg.set_mie(true); + mie_reg.enable(LpMachineInterrupt::Peripheral); + } else { + mstatus_reg.set_mie(false); + mie_reg.disable(LpMachineInterrupt::Peripheral); + } + + // Write them back to memory + unsafe { + riscv::register::mstatus::write(mstatus_reg); + riscv::register::mie::write(mie_reg); + } + + old_mie_enable +} diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index f9d18d5303a..80aba847960 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -6,18 +6,18 @@ cfg_if::cfg_if! { pub mod ulp_core; #[cfg(any(esp32s2, esp32s3))] pub use ulp_core::*; - // ULP-core critical section implementation - #[cfg(any(esp32s2, esp32s3))] - mod critical_section; #[cfg(esp32c6)] pub mod lp_core; #[cfg(esp32c6)] pub use lp_core::*; + // Machine-mode critical section implementation. + mod critical_section; + /// Portable interrupt binding and handling code - pub mod generic; - pub use generic::*; + pub mod shared; + pub use shared::*; } else { /// If interrupt handling is not enabled, need to provide a stubs functions for /// start_trap_rust and DefaultHandler. diff --git a/esp-lp-hal/src/interrupt/generic.rs b/esp-lp-hal/src/interrupt/shared.rs similarity index 64% rename from esp-lp-hal/src/interrupt/generic.rs rename to esp-lp-hal/src/interrupt/shared.rs index 4da71036c1b..9bf42c21c4b 100644 --- a/esp-lp-hal/src/interrupt/generic.rs +++ b/esp-lp-hal/src/interrupt/shared.rs @@ -1,6 +1,8 @@ +use core::ptr::NonNull; + use portable_atomic::{AtomicPtr, Ordering}; -use super::{current_interrupts, set_enabled}; +use super::{set_enabled, status, trap_cause}; pub use crate::pac::Interrupt; use crate::pac::{__EXTERNAL_INTERRUPTS, Vector}; @@ -203,9 +205,7 @@ impl InterruptStatus { /// Get status of interrupts /// This bitmask should match the Interrupt enum. pub fn current() -> Self { - Self { - status: [current_interrupts()], - } + Self { status: [status()] } } /// Is the given interrupt bit set @@ -229,3 +229,95 @@ impl From for InterruptStatus { Self { status: [value] } } } + +/// Trap entry point rust (_start_trap_rust) +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +#[unsafe(export_name = "_start_trap_rust")] +pub extern "C" fn start_trap_rust(trap_frame: *const u32) { + unsafe { + match trap_cause() { + riscv::interrupt::Trap::Interrupt(code) => { + dispatch_interrupt(code); + } + riscv::interrupt::Trap::Exception(code) => { + dispatch_exception( + NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), + code, + ); + } + } + } +} + +#[doc(hidden)] +// #[unsafe(link_section = ".trap.rust")] +pub fn dispatch_exception(_trap_frame: &TrapFrame, _code: usize) { + // loop {} +} + +/// Called by _start_trap_rust, this trap handler will call other interrupt handling +/// functions depending on the bits set in pending_irqs. +#[doc(hidden)] +// #[unsafe(link_section = ".trap.rust")] +pub fn dispatch_interrupt(_code: usize) { + // Dispatch peripheral interrupt + let status = InterruptStatus::current(); + + // Iterate the active interrupts, fetch their handler, and call it if set. + for interrupt_nr in status.iterator() { + if let Ok(i) = Interrupt::try_from(interrupt_nr) { + if let Some(handler) = bound_handler(i) { + handler.callback()(); + } + } + } +} + +/// Default interrupt handler, does nothing. +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub fn DefaultHandler() {} + +/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, +// which is currently hand-written in ulp_riscv_vectors.S +/// Registers saved in trap handler +#[doc(hidden)] +#[repr(C)] +#[derive(Debug)] +pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. + pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. + pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. + pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. + pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. + pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. + pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. + pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. + pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. + pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. + pub a5: usize, + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. + pub a6: usize, + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. + pub a7: usize, +} diff --git a/esp-lp-hal/src/interrupt/ulp_core.rs b/esp-lp-hal/src/interrupt/ulp_core.rs index e1744ee859d..fb759a92a27 100644 --- a/esp-lp-hal/src/interrupt/ulp_core.rs +++ b/esp-lp-hal/src/interrupt/ulp_core.rs @@ -1,13 +1,11 @@ //! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. //! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. -use core::ptr::NonNull; - -use super::{Interrupt, InterruptStatus, bound_handler}; +use super::Interrupt; /// Setup interrupt handlers, including any default ones #[inline(always)] pub fn setup_interrupts() { - enable_cpu_interrupts(); + machine_interrupt_enable(true); } /// Enables or disables a peripheral interrupt. @@ -62,7 +60,7 @@ pub fn set_enabled(interrupt: Interrupt, enable: bool) { } /// Returns a bitmask of active interrupts -pub fn current_interrupts() -> u32 { +pub fn status() -> u32 { let mut status_bits: u32 = 0b0; // The SENS peripheral status is broken out into individual bit flags let sens_status = unsafe { &*crate::pac::SENS::PTR } @@ -88,15 +86,27 @@ pub fn current_interrupts() -> u32 { status_bits } -/// Set the IRQ Mask, returns the previous mask value. -/// IRQ Type Bit Description -/// Internal 0 Internal timer interrupt -/// Internal 1 EBREAK/ECALL or Illegal Instruction -/// Internal 2 BUS Error (Unaligned Memory Access) -/// External 31 RTC peripheral interrupts +/// Set the mie register (or equivalent), +/// returning the previous value. #[inline(always)] -pub fn mask_cpu_interrupts(new_mask: u32) -> u32 { +pub fn machine_interrupt_enable(enable: bool) -> bool { + // Does not affect the internal exception bits, + // which are always enabled (unmasked, value here is 0). + // IRQ Type Bit Description + // Internal 0 Internal timer interrupt + // Internal 1 EBREAK/ECALL or Illegal Instruction + // Internal 2 BUS Error (Unaligned Memory Access) + // External 31 RTC peripheral interrupts let old_mask: u32; + + let disable_exceptions: u32 = 0b111; + let disable_bit: u32 = 1 << 31; + let new_mask: u32 = if enable { + 0b0 + } else { + disable_bit | disable_exceptions + }; + unsafe { core::arch::asm!( "maskirq_insn {}, {}", @@ -104,119 +114,41 @@ pub fn mask_cpu_interrupts(new_mask: u32) -> u32 { in(reg) new_mask ); } - old_mask -} -/// Enable all CPU interrupts by setting IRQ mask -#[inline(always)] -pub fn enable_cpu_interrupts() { - mask_cpu_interrupts(0x0); + // Return previous enabled value, + // where enable == 0 (unmasked) + (old_mask & disable_bit) == 0 } -/// Disable all CPU interrupts by clearing IRQ mask, -/// returns the previous IRQ Mask +/// Returns the cause of a machine interrupt, +/// which contains the exception code, and if a peripheral interrupt was flagged. +#[unsafe(link_section = ".trap.rust")] #[inline(always)] -pub fn disable_cpu_interrupts() -> u32 { - let mask = (1 << 31) | (1 << 2) | (1 << 1) | (1 << 0); - mask_cpu_interrupts(mask) -} - -/// Wait for any (masked or unmasked) CPU interrupt -pub fn wait_cpu_interrupt() -> u32 { - let result: u32; +pub fn trap_cause() -> riscv::interrupt::Trap { + // Exception ID, Description + // 2, Illegal instructions + // 3, Breakpoints (EBREAK) + // 6, Misaligned atomic instructions + // + // Bit 31 is used to indicate an interrupt. + + // mcause register does not exist on ULP cores, + // so the Trap must be formed using q-registers + let cause: u32; unsafe { core::arch::asm!( - "waitirq_insn {}", - out(reg) result, - ) - } - result -} - -/// Trap entry point rust (_start_trap_rust) -/// `irqs` is a bitmask of IRQs to handle. -#[doc(hidden)] -#[unsafe(link_section = ".trap.rust")] -#[unsafe(export_name = "_start_trap_rust")] -pub extern "C" fn ulp_start_trap_rust(trap_frame: *const u32, irqs: u32) { - unsafe extern "C" { - fn trap_handler(regs: &TrapFrame, pending_irqs: u32); - } - - unsafe { - // 'trap_frame' pointer safety: - // _start_trap must place a valid address in a0, prior to calling _start_trap_rust. - trap_handler( - NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), - irqs, + "getq_insn {}, q1", + out(reg) cause ); } -} -/// Called by _start_trap_rust, this trap handler will call other interrupt handling -/// functions depending on the bits set in pending_irqs. -#[doc(hidden)] -#[unsafe(no_mangle)] -pub extern "C" fn trap_handler(_regs: &TrapFrame, pending_irqs: u32) { - // Dispatch peripheral interrupt - if pending_irqs & (1 << 31) != 0 { - let status = InterruptStatus::current(); - - // Iterate the active interrupts, fetch their handler, and call it if set. - for interrupt_nr in status.iterator() { - if let Ok(i) = Interrupt::try_from(interrupt_nr) { - if let Some(handler) = bound_handler(i) { - handler.callback()(); - } - } - } - } -} + let interrupt: bool = (cause & (0b1 << 31)) != 0; -/// Default interrupt handler, does nothing. -#[allow(dead_code)] -#[doc(hidden)] -#[allow(non_snake_case)] -#[unsafe(no_mangle)] -pub fn DefaultHandler() {} - -/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, -// which is currently hand-written in ulp_riscv_vectors.S -/// Registers saved in trap handler -#[doc(hidden)] -#[repr(C)] -#[derive(Debug)] -pub struct TrapFrame { - /// `x1`: return address, stores the address to return to after a function call or interrupt. - pub ra: usize, - /// `x5`: temporary register `t0`, used for intermediate values. - pub t0: usize, - /// `x6`: temporary register `t1`, used for intermediate values. - pub t1: usize, - /// `x7`: temporary register `t2`, used for intermediate values. - pub t2: usize, - /// `x28`: temporary register `t3`, used for intermediate values. - pub t3: usize, - /// `x29`: temporary register `t4`, used for intermediate values. - pub t4: usize, - /// `x30`: temporary register `t5`, used for intermediate values. - pub t5: usize, - /// `x31`: temporary register `t6`, used for intermediate values. - pub t6: usize, - /// `x10`: argument register `a0`. Used to pass the first argument to a function. - pub a0: usize, - /// `x11`: argument register `a1`. Used to pass the second argument to a function. - pub a1: usize, - /// `x12`: argument register `a2`. Used to pass the third argument to a function. - pub a2: usize, - /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. - pub a3: usize, - /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. - pub a4: usize, - /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. - pub a5: usize, - /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. - pub a6: usize, - /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. - pub a7: usize, + if interrupt { + let code = (cause & 0b111) as usize; + riscv::interrupt::Trap::Interrupt(code) + } else { + let code = (cause & 0b1111) as usize; + riscv::interrupt::Trap::Exception(code) + } } diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 0a1474950d3..76a19de42bc 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,9 +38,7 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; -/// Interrupt handling APIs. -/// Currently implemented for RISCV ULP cores (ESP32-S2,ESP32-S3) -/// Functions are gated internally by stub functions. +/// Interrupt handling APIs pub mod interrupt; /// The prelude diff --git a/esp-lp-hal/src/lp_start.S b/esp-lp-hal/src/lp_start.S index a4d217e2aff..f3bfffe5bc6 100644 --- a/esp-lp-hal/src/lp_start.S +++ b/esp-lp-hal/src/lp_start.S @@ -1,32 +1,126 @@ -/* Start-up assembly for ESP32-C6 chip */ +/* Start-up assembly for ESP32C6-LP chip */ +.org 0x0 .section .init.vector, "ax" -/* This is the vector table. It is currently empty, but will be populated - * with exception and interrupt handlers when this is supported + +/* This is the vector table. For LP core, only vector 30 is used. + * Base of the vector table is set by 'mtvec', defaulting to 0x0. + * + * To enable interrupts globally, set the MIE bit of mstatus. + * To enable Interrupt 30, set the 30th bit of mie CSR. + * After the interrupt is triggered, the LP CPU jumps to mtvec + 4 ∗ 30 + * After enterring the interrupt handler, users need to read LPPERI_INTERRUPT_SOURCE_REG + * to get the peripheral that triggered the interrupt and process the interrupt. + * + * For exceptions, the handler address is the base address of the vector table in the mtvec CSR + * The core fetches instructions from address 0x50000080 after reset. + * When the core starts up, the base address of the vector table is initialized to the boot address 0x50000000. */ -.align 0x4, 0xff -.global _vector_table -.type _vector_table, @function -_vector_table: - .option push - .option norvc - .rept 32 - nop - .endr - .option pop - .size _vector_table, .-_vector_table +.equ SAVE_REGS, 16 +.equ CONTEXT_SIZE, (SAVE_REGS * 4) + +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. + * + * Note: We don't save the callee-saved s0-s11 registers to save space + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw t3, 16(sp) + sw t4, 20(sp) + sw t5, 24(sp) + sw t6, 28(sp) + sw a0, 32(sp) + sw a1, 36(sp) + sw a2, 40(sp) + sw a3, 44(sp) + sw a4, 48(sp) + sw a5, 52(sp) + sw a6, 56(sp) + sw a7, 60(sp) +.endm + +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw t3, 16(sp) + lw t4, 20(sp) + lw t5, 24(sp) + lw t6, 28(sp) + lw a0, 32(sp) + lw a1, 36(sp) + lw a2, 40(sp) + lw a3, 44(sp) + lw a4, 48(sp) + lw a5, 52(sp) + lw a6, 56(sp) + lw a7, 60(sp) + addi sp,sp, \cxt_size +.endm - .section .init, "ax" - .global reset_vector +/* Place exception handler at 0x0 */ +.global exception_vector +.type exception_vector, @function +exception_vector: + j _start_trap -/* The reset vector, jumps to startup code */ +/* Place 1 vector interrupt handler at 0x78 */ +.org 0x78 +.global interrupt_vector +.type interrupt_vector, @function +interrupt_vector: + j _start_trap + +/* The reset vector, placed at 0x80, the default reset value. */ +.org 0x80 +.section .init, "ax" +.global reset_vector reset_vector: j __start __start: - /* setup the stack pointer */ + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ la sp, __stack_top - call rust_main -loop: - j loop \ No newline at end of file + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + +.section .trap, "ax" +.global _start_trap +_start_trap: + /* Save the general gurpose register context before handling the interrupt */ + save_general_regs + + /* Default trap handler, delegates to _start_trap_rust. */ + jal ra,_start_trap_rust + + /* Restore the register context after returning from the C interrupt handler */ + restore_general_regs + /* mret to exit */ + mret + +.section .trap.rust, "ax" +.align 4 \ No newline at end of file From 6e5fa4d9da3fd03d04eeb0f3833c41f34c2a8069 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Tue, 28 Apr 2026 06:49:49 +1000 Subject: [PATCH 44/47] Added interrupt::clear() function --- esp-hal/src/gpio/rtc_io.rs | 2 +- esp-lp-hal/src/interrupt/lp_core.rs | 5 +++++ esp-lp-hal/src/interrupt/shared.rs | 6 +++--- esp-lp-hal/src/interrupt/ulp_core.rs | 20 ++++++++++++++++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index bd682997daf..607ebc5b5d5 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -91,7 +91,7 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { this.input_enable(true); this.pullup_enable(false); this.pulldown_enable(false); - this.wakeup_enable(None); + // this.wakeup_enable(None); this } diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs index 1af101daeec..c381375c8dd 100644 --- a/esp-lp-hal/src/interrupt/lp_core.rs +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -20,6 +20,11 @@ pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { // todo!() } +/// Clears a peripheral interrupt. +pub fn clear(interrupt: Interrupt) { + todo!() +} + /// Returns a bitmask of active peripheral interrupts pub fn status() -> u32 { todo!() diff --git a/esp-lp-hal/src/interrupt/shared.rs b/esp-lp-hal/src/interrupt/shared.rs index 9bf42c21c4b..d9bdbc9f17c 100644 --- a/esp-lp-hal/src/interrupt/shared.rs +++ b/esp-lp-hal/src/interrupt/shared.rs @@ -251,15 +251,15 @@ pub extern "C" fn start_trap_rust(trap_frame: *const u32) { } #[doc(hidden)] -// #[unsafe(link_section = ".trap.rust")] +#[unsafe(link_section = ".trap.rust")] pub fn dispatch_exception(_trap_frame: &TrapFrame, _code: usize) { - // loop {} + loop {} } /// Called by _start_trap_rust, this trap handler will call other interrupt handling /// functions depending on the bits set in pending_irqs. #[doc(hidden)] -// #[unsafe(link_section = ".trap.rust")] +#[unsafe(link_section = ".trap.rust")] pub fn dispatch_interrupt(_code: usize) { // Dispatch peripheral interrupt let status = InterruptStatus::current(); diff --git a/esp-lp-hal/src/interrupt/ulp_core.rs b/esp-lp-hal/src/interrupt/ulp_core.rs index fb759a92a27..01f6c7f2ad3 100644 --- a/esp-lp-hal/src/interrupt/ulp_core.rs +++ b/esp-lp-hal/src/interrupt/ulp_core.rs @@ -3,7 +3,6 @@ use super::Interrupt; /// Setup interrupt handlers, including any default ones -#[inline(always)] pub fn setup_interrupts() { machine_interrupt_enable(true); } @@ -14,7 +13,6 @@ pub fn setup_interrupts() { /// to be serviced. /// /// Internally, this function maps the interrupt to the appropriate CPU interrupt. -#[inline] pub fn set_enabled(interrupt: Interrupt, enable: bool) { // Enable/disable SENS interrupts unsafe { &*crate::pac::SENS::PTR } @@ -59,6 +57,24 @@ pub fn set_enabled(interrupt: Interrupt, enable: bool) { // as it is handled by the gpio module on a per-pin basis. } +/// Clears a peripheral interrupt. +/// For GPIO_INT, clears all interrupts. +pub fn clear(interrupt: Interrupt) { + // Enable/disable SENS interrupts + match interrupt { + Interrupt::GPIO_INT => { + crate::gpio::interrupt::gpio_interrupt_clear( + crate::gpio::interrupt::gpio_interrupt_status(), + ); + } + _ => { + unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(interrupt as u32) }); + } + } +} + /// Returns a bitmask of active interrupts pub fn status() -> u32 { let mut status_bits: u32 = 0b0; From 294ba4bdc5ec35baa01897ca8ca9ee5efda8d2da Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Wed, 29 Apr 2026 09:10:17 +1000 Subject: [PATCH 45/47] Brought back gpio_wakeup_disable --- esp-lp-hal/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 76a19de42bc..67149cc5624 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -82,7 +82,7 @@ pub fn wake_hp_core() { .write(|w| w.rtc_sw_cpu_int().set_bit()); } -/// Global GPIO wakeup enable/disable +/// Global GPIO wakeup enable pub fn gpio_wakeup_enable() { #[cfg(esp32s2)] unsafe { &*crate::pac::RTC_CNTL::PTR } @@ -94,6 +94,18 @@ pub fn gpio_wakeup_enable() { .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(true)); } +/// Global GPIO wakeup disable +pub fn gpio_wakeup_disable() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(false)); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(false)); +} + /// Clear the Global GPIO wakeup status pub fn gpio_wakeup_clear() { #[cfg(esp32s2)] From 662d24f2b2bcf49ebaaef6f3ce0836fd9b4bb77b Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 3 May 2026 08:44:53 +1000 Subject: [PATCH 46/47] Bump esp-pacs to use un-released commits --- esp-lp-hal/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 8896465f930..71aecaeb0f1 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -47,9 +47,9 @@ critical-section = { version = "1.2.0", features = [ "restore-state-bool" ] } riscv = { version = "0.15", default-features = false } portable-atomic = { version = "1", default-features = false } esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "cba62523" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "46b575d9" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "c6f21b60" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "c6f21b60" } [dev-dependencies] panic-halt = "0.2.0" From 9b066aa0c35d56b93cc1f61eb4b6629ec764cd21 Mon Sep 17 00:00:00 2001 From: Leigh Oliver Date: Sun, 3 May 2026 14:32:48 +1000 Subject: [PATCH 47/47] Remove _start_trap assembly code when `interrupts` feature disabled --- esp-hal/src/soc/esp32s3/ulp_core.rs | 70 ++++++++++++------- esp-lp-hal/src/asm/lp_start.S | 47 +++++++++++++ .../lp_start_with_interrupts.S} | 7 +- esp-lp-hal/src/asm/ulp_riscv_start.S | 40 +++++++++++ .../ulp_riscv_start_with_interrupts.S} | 5 +- esp-lp-hal/src/interrupt/critical_section.rs | 17 ++++- esp-lp-hal/src/interrupt/lp_core.rs | 2 +- esp-lp-hal/src/interrupt/mod.rs | 19 ++--- esp-lp-hal/src/interrupt/shared.rs | 10 +-- esp-lp-hal/src/lib.rs | 16 ++++- 10 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 esp-lp-hal/src/asm/lp_start.S rename esp-lp-hal/src/{lp_start.S => asm/lp_start_with_interrupts.S} (97%) create mode 100644 esp-lp-hal/src/asm/ulp_riscv_start.S rename esp-lp-hal/src/{ulp_riscv_start.S => asm/ulp_riscv_start_with_interrupts.S} (98%) diff --git a/esp-hal/src/soc/esp32s3/ulp_core.rs b/esp-hal/src/soc/esp32s3/ulp_core.rs index d344665e0df..a5a4c6972d1 100644 --- a/esp-hal/src/soc/esp32s3/ulp_core.rs +++ b/esp-hal/src/soc/esp32s3/ulp_core.rs @@ -105,7 +105,7 @@ impl<'d> UlpCore<'d> { /// Stops the ULP core. pub fn stop(&mut self) { - ulp_stop(); + ulp_halt(); } /// Runs the ULP core with the specified wakeup source. @@ -114,28 +114,61 @@ impl<'d> UlpCore<'d> { } } -fn ulp_stop() { +fn ulp_timer_resume() { + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); +} + +fn ulp_timer_stop() { let rtc_cntl = LPWR::regs(); rtc_cntl .ulp_cp_timer() .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); +} +fn ulp_timer_period(cycles: u32) { + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ulp_cp_timer_1() + .write(|w| unsafe { w.ulp_cp_timer_slp_cycle().bits(cycles << 8) }); + rtc_cntl + .ulp_cp_ctrl() + .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); +} + +fn ulp_halt() { + ulp_timer_stop(); + let rtc_cntl = LPWR::regs(); // suspends the ulp operation rtc_cntl .cocpu_ctrl() .modify(|_, w| w.cocpu_done().set_bit()); - // Resets the processor rtc_cntl .cocpu_ctrl() .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); +} + +fn ulp_reset() { + let rtc_cntl = LPWR::regs(); + + rtc_cntl.cocpu_ctrl().write(|w| { + w.cocpu_shut().clear_bit(); + w.cocpu_done().clear_bit(); + w.cocpu_shut_reset_en().clear_bit() + }); crate::rom::ets_delay_us(20); - // above doesn't seem to halt the ULP core - this will - rtc_cntl - .cocpu_ctrl() - .modify(|_, w| w.cocpu_clkgate_en().clear_bit()); + rtc_cntl.cocpu_ctrl().write(|w| { + w.cocpu_shut().set_bit(); + w.cocpu_done().set_bit(); + w.cocpu_shut_reset_en().set_bit() + }); + + crate::rom::ets_delay_us(20); } fn ulp_run(wakeup_src: UlpCoreWakeupSource) { @@ -196,34 +229,21 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .ulp_cp() .clear_bit_by_one() }); - - rtc_cntl - .cocpu_ctrl() - .modify(|_, w| w.cocpu_clkgate_en().set_bit()); } fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { // ESP-IDF source: https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_riscv.c#L87 - fn configure_timer(cycles: u32) { - LPWR::regs() - .ulp_cp_timer_1() - .write(|w| unsafe { w.ulp_cp_timer_slp_cycle().bits(cycles << 8) }); - // enable the timer - LPWR::regs() - .ulp_cp_ctrl() - .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); - LPWR::regs() - .ulp_cp_timer() - .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); - } + match wakeup_src { UlpCoreWakeupSource::HpCpu => { // wake-up immediately - configure_timer(0); + ulp_timer_period(0); + ulp_timer_resume(); } UlpCoreWakeupSource::Timer(sleep_cycles) => { // configure timer duration - configure_timer(sleep_cycles.cycles()); + ulp_timer_period(sleep_cycles.cycles()); + ulp_timer_resume(); } UlpCoreWakeupSource::Gpio => { LPWR::regs() diff --git a/esp-lp-hal/src/asm/lp_start.S b/esp-lp-hal/src/asm/lp_start.S new file mode 100644 index 00000000000..77f3177e891 --- /dev/null +++ b/esp-lp-hal/src/asm/lp_start.S @@ -0,0 +1,47 @@ +/* Start-up assembly for ESP32C6-LP chip */ +.org 0x0 +.section .init.vector, "ax" + +/* Place exception handler at 0x0 */ +.global exception_vector +.type exception_vector, @function +exception_vector: + j _start_trap + +/* Place vector interrupt handler (number 30) at 0x78 */ +.org 0x78 +.global interrupt_vector +.type interrupt_vector, @function +interrupt_vector: + j _start_trap + +/* The reset vector, placed at 0x80, the default reset value. */ +.org 0x80 +.section .init, "ax" +.global reset_vector +reset_vector: + j __start + +__start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + +/* Abort loop */ +abort: + jal zero, abort + +.section .trap, "ax" +.global _start_trap +_start_trap: + /* abort immediately */ + j ulp_riscv_halt + mret \ No newline at end of file diff --git a/esp-lp-hal/src/lp_start.S b/esp-lp-hal/src/asm/lp_start_with_interrupts.S similarity index 97% rename from esp-lp-hal/src/lp_start.S rename to esp-lp-hal/src/asm/lp_start_with_interrupts.S index f3bfffe5bc6..a90fdd68200 100644 --- a/esp-lp-hal/src/lp_start.S +++ b/esp-lp-hal/src/asm/lp_start_with_interrupts.S @@ -81,7 +81,7 @@ exception_vector: j _start_trap -/* Place 1 vector interrupt handler at 0x78 */ +/* Place vector interrupt handler (number 30) at 0x78 */ .org 0x78 .global interrupt_vector .type interrupt_vector, @function @@ -120,7 +120,4 @@ _start_trap: /* Restore the register context after returning from the C interrupt handler */ restore_general_regs /* mret to exit */ - mret - -.section .trap.rust, "ax" -.align 4 \ No newline at end of file + mret \ No newline at end of file diff --git a/esp-lp-hal/src/asm/ulp_riscv_start.S b/esp-lp-hal/src/asm/ulp_riscv_start.S new file mode 100644 index 00000000000..c345b362a8b --- /dev/null +++ b/esp-lp-hal/src/asm/ulp_riscv_start.S @@ -0,0 +1,40 @@ + /* Disable compressed instructions */ + .option norvc + .section .reset, "ax" + .global reset_vector + /* Power-on reset vector, placed at address 0x0 */ +reset_vector: + /* no more than 16 bytes here */ + /* disable interrupts and jump to _start */ + /* lui a0,0x80000 */ + /* addi a0,a0,7 */ + /* .insn 4, 0x0605658b */ + jal zero, _start + + /* Initialisation routine */ + .section .init, "ax" + .global _start +_start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + + /* Abort loop */ +abort: + jal zero, abort + + /* Hardware trap vector, placed at address 0x10 + * This will abort + */ + .section .trap, "ax" + .global _start_trap +_start_trap: + ret \ No newline at end of file diff --git a/esp-lp-hal/src/ulp_riscv_start.S b/esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S similarity index 98% rename from esp-lp-hal/src/ulp_riscv_start.S rename to esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S index 1db0b793a88..a8d323dd09c 100644 --- a/esp-lp-hal/src/ulp_riscv_start.S +++ b/esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S @@ -1,3 +1,5 @@ + /* Disable compressed instructions */ + .option norvc /** * Defines the assembly macros needed for custom instructions on the ULP RISCV core. * Adapted from the following ESP-IDF header: @@ -228,5 +230,4 @@ _start_trap: restore_general_regs /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ - retirq_insn - + retirq_insn \ No newline at end of file diff --git a/esp-lp-hal/src/interrupt/critical_section.rs b/esp-lp-hal/src/interrupt/critical_section.rs index b8b24652c54..d12f178e1f0 100644 --- a/esp-lp-hal/src/interrupt/critical_section.rs +++ b/esp-lp-hal/src/interrupt/critical_section.rs @@ -1,16 +1,27 @@ use critical_section::RawRestoreState; +#[cfg(feature = "interrupts")] use crate::interrupt; struct CriticalSection; critical_section::set_impl!(CriticalSection); +/// Machine-mode critical section implementation for +/// LP and ULP cores. unsafe impl critical_section::Impl for CriticalSection { unsafe fn acquire() -> RawRestoreState { - interrupt::machine_interrupt_enable(false) + #[cfg(feature = "interrupts")] + { + interrupt::machine_interrupt_enable(false) + } + #[cfg(not(feature = "interrupts"))] + { + true + } } - unsafe fn release(previous_state: RawRestoreState) { - interrupt::machine_interrupt_enable(previous_state); + unsafe fn release(_previous_state: RawRestoreState) { + #[cfg(feature = "interrupts")] + interrupt::machine_interrupt_enable(_previous_state); } } diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs index c381375c8dd..1948d6db257 100644 --- a/esp-lp-hal/src/interrupt/lp_core.rs +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -21,7 +21,7 @@ pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { } /// Clears a peripheral interrupt. -pub fn clear(interrupt: Interrupt) { +pub fn clear(_interrupt: Interrupt) { todo!() } diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs index 80aba847960..2ee7f6d41b0 100644 --- a/esp-lp-hal/src/interrupt/mod.rs +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -12,24 +12,19 @@ cfg_if::cfg_if! { #[cfg(esp32c6)] pub use lp_core::*; - // Machine-mode critical section implementation. - mod critical_section; - /// Portable interrupt binding and handling code pub mod shared; pub use shared::*; } else { - /// If interrupt handling is not enabled, need to provide a stubs functions for - /// start_trap_rust and DefaultHandler. - #[doc(hidden)] - #[allow(dead_code)] - #[unsafe(link_section = ".trap.rust")] - #[unsafe(export_name = "_start_trap_rust")] - pub extern "C" fn stub_start_trap_rust(_trap_frame: *const u32, _irqs: u32) {} - + // If interrupt handling is not enabled, need to provide a stub DefaultHandler, + // to make the linker happy. #[doc(hidden)] - #[allow(dead_code)] #[unsafe(export_name = "DefaultHandler")] + #[unsafe(link_section = ".trap.rust")] pub fn stub_default_handler() {} } } + +/// LP core critical section implementation. +/// Works with or without the interrupts feature. +mod critical_section; diff --git a/esp-lp-hal/src/interrupt/shared.rs b/esp-lp-hal/src/interrupt/shared.rs index d9bdbc9f17c..576ecf4760a 100644 --- a/esp-lp-hal/src/interrupt/shared.rs +++ b/esp-lp-hal/src/interrupt/shared.rs @@ -253,6 +253,7 @@ pub extern "C" fn start_trap_rust(trap_frame: *const u32) { #[doc(hidden)] #[unsafe(link_section = ".trap.rust")] pub fn dispatch_exception(_trap_frame: &TrapFrame, _code: usize) { + #[allow(clippy::empty_loop)] loop {} } @@ -266,10 +267,10 @@ pub fn dispatch_interrupt(_code: usize) { // Iterate the active interrupts, fetch their handler, and call it if set. for interrupt_nr in status.iterator() { - if let Ok(i) = Interrupt::try_from(interrupt_nr) { - if let Some(handler) = bound_handler(i) { - handler.callback()(); - } + if let Ok(i) = Interrupt::try_from(interrupt_nr) + && let Some(handler) = bound_handler(i) + { + handler.callback()(); } } } @@ -279,6 +280,7 @@ pub fn dispatch_interrupt(_code: usize) { #[doc(hidden)] #[allow(non_snake_case)] #[unsafe(no_mangle)] +#[unsafe(link_section = ".trap.rust")] pub fn DefaultHandler() {} /// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 67149cc5624..831744c6e63 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -61,10 +61,18 @@ cfg_if::cfg_if! { pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; // Assembly containing the reset, trap, and startup assembly procedures.. +#[cfg(not(feature = "interrupts"))] #[cfg(esp32c6)] -global_asm!(include_str!("./lp_start.S")); +global_asm!(include_str!("./asm/lp_start.S")); +#[cfg(feature = "interrupts")] +#[cfg(esp32c6)] +global_asm!(include_str!("./asm/lp_start_with_interrupts.S")); +#[cfg(not(feature = "interrupts"))] +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./asm/ulp_riscv_start.S")); +#[cfg(feature = "interrupts")] #[cfg(any(esp32s2, esp32s3))] -global_asm!(include_str!("./ulp_riscv_start.S")); +global_asm!(include_str!("./asm/ulp_riscv_start_with_interrupts.S")); /// Wake up the HP core pub fn wake_hp_core() { @@ -174,7 +182,9 @@ unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { } /// Stops the ULP core, called from itself. -fn ulp_riscv_halt() -> ! { +#[unsafe(link_section = ".init.rust")] +#[unsafe(no_mangle)] +unsafe extern "C" fn ulp_riscv_halt() -> ! { #[cfg(any(esp32s2, esp32s3))] { unsafe { &*pac::RTC_CNTL::PTR }