Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
3daab42
Add IRQ handler, add custom R-type instruction macros, move out reset…
leighleighleigh Mar 17, 2026
5e2846e
Moving some riscv-rt stuff into here
leighleighleigh Mar 20, 2026
7f4bdd7
ULP is working, no interrupt handling yet
leighleighleigh Mar 21, 2026
3017228
Updated interrupt handler to save/restore TrapFrame like riscv-rt, bu…
leighleighleigh Mar 21, 2026
0503f7f
Really basic interrupt handling is working, adapted from picorv32-rt
leighleighleigh Mar 21, 2026
ef03250
Undo some non-functional formatting changes to reduce churn
leighleighleigh Mar 22, 2026
8f9e92f
Added some gross interrupt binding macros, re-used the SENS interrupt…
leighleighleigh Mar 23, 2026
6b4f2b5
Hacky .listen() and .unlisten() methods on GPIO pins
leighleighleigh Mar 23, 2026
6cccbf1
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Mar 23, 2026
ccfc8d8
Add GPIO wakeup method to LP gpio pins.
leighleighleigh Mar 23, 2026
8bd4cb6
Hide interrupt macros on non-supported chips
leighleighleigh Mar 23, 2026
f50d455
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Mar 27, 2026
8d0749e
Exploring a cleaner interrupt binding approach
leighleighleigh Mar 28, 2026
d344dfc
Satisfy clippy
leighleighleigh Mar 28, 2026
2c1a07e
Incomplete migration of a bunch of interrupt handling stuffs
leighleighleigh Mar 29, 2026
6df37f5
Semi-functional port of Io interrupt handling APIs
leighleighleigh Mar 29, 2026
f03e89b
Cleanup feature gating, add non-working critical section impl
leighleighleigh Mar 30, 2026
2a6ed84
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 2, 2026
fce1661
Updated critical section implementation to handle nesting properly
leighleighleigh Apr 2, 2026
250fe93
WIP Switch to using interrupt vectors exported by PAC
leighleighleigh Apr 2, 2026
2c31ed0
Update interrupt_counter.rs to align with ESP-HAL example
leighleighleigh Apr 3, 2026
f714b6f
Added interupt::disable() function
leighleighleigh Apr 3, 2026
d35fcbd
Make clippy happy again
leighleighleigh Apr 3, 2026
e081c2f
Remove duplicate example name causing havok in CI
leighleighleigh Apr 3, 2026
20e8d0e
Update CHANGELOG.md
leighleighleigh Apr 3, 2026
4e63d55
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 3, 2026
abdbca3
Update link-ulp.x
leighleighleigh Apr 3, 2026
407ca83
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 7, 2026
6c9d54d
Clear `DEVICE_PERIPHERALS` PAC variable to prevent `Peripheral::take(…
leighleighleigh Apr 7, 2026
fecb21f
Dont overwrite code region in lp_core::load_lp_code, until run is called
leighleighleigh Apr 7, 2026
d2343bf
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 11, 2026
899f0a2
Update interrupt_counter.rs
leighleighleigh Apr 12, 2026
d8c3623
Address comments, revert procmacro change to avoid breaking user code…
leighleighleigh Apr 12, 2026
d203bae
Added HP core example
leighleighleigh Apr 12, 2026
fc52c21
Update changelog
leighleighleigh Apr 12, 2026
83fbf87
Update HP core example
leighleighleigh Apr 12, 2026
c87caba
Add ULP-core APIs for GPIO wakeup
leighleighleigh Apr 12, 2026
30be234
Bump PAC version, add GPIO wakeup_enable to rtc_io pins, update examples
leighleighleigh Apr 13, 2026
c7b203c
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 13, 2026
12841ea
Update gpio_wakeup.rs
leighleighleigh Apr 13, 2026
5672c72
Update interrupt_counter.rs
leighleighleigh Apr 13, 2026
f5e008f
Remove feature flag requirement on lp examples, use CHIPS to gate the…
leighleighleigh Apr 14, 2026
53d72f1
Added 'interrupts' feature flag for ULP core interrupt support. Added…
leighleighleigh Apr 16, 2026
1e252b2
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 18, 2026
787b431
Tweak gpio_wakeup_enable, update deps
leighleighleigh Apr 18, 2026
a0b0b3f
Hide interrupt support from ESP32C6 until implemented
leighleighleigh Apr 18, 2026
013b2f2
Fix rtc_io.wakeup_enable on esp32s2
leighleighleigh Apr 18, 2026
ebfd35f
Add more stub implementations for esp32c6, so CI can pass
leighleighleigh Apr 18, 2026
f396396
Fix C6 compilation due to stray todo
leighleighleigh Apr 18, 2026
e7a3515
Clean-up of RTC/LP IO wakeup_enable method. Implementing WakeupFromLp…
leighleighleigh Apr 19, 2026
c0f1524
Start fixing up the ESP32C6-LP interrupt handling
leighleighleigh Apr 25, 2026
6e5fa4d
Added interrupt::clear() function
leighleighleigh Apr 27, 2026
294ba4b
Brought back gpio_wakeup_disable
leighleighleigh Apr 28, 2026
e6f5ef2
Merged main into leighleighleigh/ulp-interrupt-handler
leighleighleigh May 2, 2026
662d24f
Bump esp-pacs to use un-released commits
leighleighleigh May 2, 2026
9b066aa
Remove _start_trap assembly code when `interrupts` feature disabled
leighleighleigh May 3, 2026
7ac400f
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-lp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
130 changes: 110 additions & 20 deletions esp-lp-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"));
Comment thread
leighleighleigh marked this conversation as resolved.
Outdated

// 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")]
Expand Down
101 changes: 101 additions & 0 deletions esp-lp-hal/src/ulp_riscv_interrupt_ops.S
Original file line number Diff line number Diff line change
@@ -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
93 changes: 93 additions & 0 deletions esp-lp-hal/src/ulp_riscv_vectors.S
Original file line number Diff line number Diff line change
@@ -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
Loading