Page 1 of 2
Interrupt handlers chaining on ESP32
Posted: Wed Sep 10, 2025 12:28 pm
by vvb333007
Hello,
I'd like to install my own interrupt handler (e.g. GPIO interrupt or RMT interrupt, one of the ETS_ interrupt sources), but also save previous handler address to call it after my ISR is finished.
Is this doable in ESP-IDF?
Another question is: if I save original interrupt handler entry, and then simply "j original_handler" can I get away with that?
I need to "intercept" some interrupts to count statistics (bytes transferred/received, errors, interrupt count etc)
Thanks!
PS: any relevant links with examples or just working code which works with interrupts, may be using xt_utils are greatly appreciated
Re: Interrupt handlers chaining on ESP32
Posted: Thu Sep 11, 2025 11:58 am
by ahsrabrifat
You can hook into most interrupts in ESP-IDF, but the recommended way is not to directly patch the vector table. Instead, you usually register a handler with esp_intr_alloc() (or the newer esp_intr_alloc_intrstatus()) for the specific interrupt source.
Re: Interrupt handlers chaining on ESP32
Posted: Thu Sep 11, 2025 1:53 pm
by MicroController
Another question is: if I save original interrupt handler entry, and then simply "j original_handler" can I get away with that?
Only if both your handler and the original handler are written in assembly and use the call0 ABI.
Note that most interrupts are "pre-handled"/wrapped by the
same assembly handler in the IDF which does some housekeeping and then
dispatches to the actual individual ISRs (written in C/call4 ABI, so no tail-calling).
Re: Interrupt handlers chaining on ESP32
Posted: Mon Sep 15, 2025 5:24 pm
by vvb333007
Another question is: if I save original interrupt handler entry, and then simply "j original_handler" can I get away with that?
Only if both your handler and the original handler are written in assembly and use the call0 ABI.
Note that most interrupts are "pre-handled"/wrapped by the
same assembly handler in the IDF which does some housekeeping and then
dispatches to the actual individual ISRs (written in C/call4 ABI, so no tail-calling).
Yes, right, ESP-IDF handles, say GPIO interrupts via its own handler, which was once set via esp_intr_alloc(ETS_..)
1. Which interrupt vector number is used for that? How interrupt number relates to interrupt source? For example, why Level5 interrupt maps to interrupt vector #15
2. If I inject my own binary code (saving original bytes) straight to the interrupt handler? I mean, I inject there something
"j my_interceptor_routine", then "j" from the interceptor to original code?
My problem is that I want to intercept certain interrupts, namely GPIO interrupt. User sketch may not use GPIO interrupts or it can register a global GPIO handler or it can use GPIO ISR Service and register per-GPIO interrupt. I'd like to keep application working as it should but intercept all GPIO interrupts before application can process them.
I did similar things on different archs (i386, powerpc) but esp32 is a bit different so I am seeking an advice.
Also I didn't understand why I should use call0 abi if I intercept interrupts by writing to the vector table. Why can't I do my stuff in my interceptor (increment counters) and then "j" to original entrypoint? Registers are saved at that point as I can understand so I am free to use any of them.
Re: Interrupt handlers chaining on ESP32
Posted: Mon Sep 15, 2025 5:33 pm
by vvb333007
Note that most interrupts are "pre-handled"/wrapped by the
same assembly handler in the IDF which does some housekeeping and then
dispatches to the actual individual ISRs (written in C/call4 ABI, so no tail-calling).
Thanks for the links. After looking into the sources I found that both xt_set_interrupt_handler and xt_set_exception_handler return previous handle, yay! So chaining seems to be possible
Re: Interrupt handlers chaining on ESP32
Posted: Tue Sep 16, 2025 8:39 am
by MicroController
Also I didn't understand why I should use call0 abi if I intercept interrupts by writing to the vector table. Why can't I do my stuff in my interceptor (increment counters) and then "j" to original entrypoint? Registers are saved at that point as I can understand so I am free to use any of them.
What I wanted to point out is that you cannot jump to a function when using the the windowed (call8/call4) ABI; each ENTRY instruction needs a corresponding RETW, and when your function has an ENTRY and you jump to another function, that'll be two ENTRYs but only one RETW.
If your function is an actual hardware-called interrupt handler, effectively call0 ABI, it cannot jump to a call4/call8 function. It can however jump to another similar interrupt handler provided that other handler also returns via RFE. So chaining interrupt handlers via jump is possible but only for lowest-level/hardware-called assembly handlers.
Re: Interrupt handlers chaining on ESP32
Posted: Fri Sep 19, 2025 3:07 am
by vvb333007
What I wanted to point out is that you cannot jump to a function when using the the windowed (call8/call4) ABI; each ENTRY instruction needs a corresponding RETW, and when your function has an ENTRY and you jump to another function, that'll be two ENTRYs but only one RETW.
Ooh got it.
But if I am chaining interrupts which are compiled as call0 ABI, this is possible, right?
Making call4 or call8 from an interrupt handler is not possible, I assume.
Thanks for the information!
Re: Interrupt handlers chaining on ESP32
Posted: Fri Sep 19, 2025 5:04 am
by vvb333007
So, summarizing:
Xtensa interrupts vector table consist of entries "j Handler". If I change that to "j My_handler" and from My_handler I can do "j Handler".
My_handler must be written in assembler, because we don't need any prologue/epilogue. Question: can I get away with a C function which has __attribute__((naked))?
Re: Interrupt handlers chaining on ESP32
Posted: Fri Sep 19, 2025 5:45 am
by ok-home
This won't help you much in solving your problem.
As far as I understand, you want to intercept specific interrupts from the peripheral.
Given that esp_intr_alloc(ETS_..) builds another interrupt table (this table is already from the usual chains of c-style functions) for interrupts for the peripheral, usually level 1-3, depending on the shared/level/cpu flags. you will have to disassemble each interrupt for belonging to the peripheral in order to understand what caused the interruption.
So if you want to log your handlers, just add a logger to them, if you want to log the handlers of the idf drivers (uart, rmt...), use the callback provided by the idf and insert the loggers there.
you can see how esp_intr_alloc makes a shared table, and you can also find the address of this table there.
Re: Interrupt handlers chaining on ESP32
Posted: Fri Sep 19, 2025 8:06 am
by MicroController
Question: can I get away with a C function which has __attribute__((naked))?
Rather not. C needs a stack frame, the required size of which for a given function is determined by and known only to the compiler. A naked function does not set up a stack frame, so you'd have to do it manually; but you can't because you don't know how large it has to be.
Edit: Also, the Xtensa gcc doesn't seem to support naked ("warning: 'naked' attribute directive ignored")