Skip to content

Instantly share code, notes, and snippets.

@MRobertEvers
Last active May 21, 2018 19:18
Show Gist options
  • Save MRobertEvers/bcc1b91c2a6ec21bcc7958c3d05c24a2 to your computer and use it in GitHub Desktop.
Save MRobertEvers/bcc1b91c2a6ec21bcc7958c3d05c24a2 to your computer and use it in GitHub Desktop.
ESP-IDF Interrupt Service Routines In IRAM

ISRs in ESP32 FreeRTOS

As of 5/21/18 According the the ESP-IDF readthedocs here, ISRs must be placed in IRAM.

After some investigation, I found this to not be entirely true. I noticed that their UART driver did not have its ISR placed in IRAM and I set out to find out why they could get away with this.

Furthermore, the [SPI flash drivers] state that they disable all non-IRAM ISRs. I found that this is only true if the driver for some ISR is installed in a way that notifies the SPI drivers that the new driver is not IRAM.

See the following links for explanation ISRs in IRAM: IRAM ISRs and IRAM ISRs and SPI

Those links indicate that ISRs can only be safely called during SPI Flash Write and Erase operations Only if the ISR is in IRAM. It looks like the issue is due to the fact that, if the ISR is not in IRAM, then the ISR is unable to be loaded from flash at the time that it is needed because flash is busy.

Behavior Tracing

I noticed in the UART driver code, that when it installs itself, it records whether or not the function is in IRAM. (It does this at run-time). From this, I suspected that it was perhaps getting disabled during times when we aren't allowed to access non-IRAM ISRs.

TL;DR: ESP-IDF automatically disables interrupts that are not located in IRAM. (Given they are initialized in a way that ESP-IDF expects)

Let’s start from the uart.c; this is where I started.

Uart_driver_install (file uart.c) This is setting up our UART interrupt handler and various other things. It calls Uart_isr_register with “uart_rx_intr_handler_default” as an argument.

Uart_isr_register (file uart.c) High level interface to put the ISR handler into the vector table. This calls esp_intr_alloc which is a wrapper for esp_inter_alloc_intrstatus. Our handler function pointer is passed as an argument

esp_inter_alloc_intrstatus (file intr_alloc.c) Allocates memory and performs actions required by some input flags. This calls (in the case of the UART) xt_set_intr_handler. Xt_set_intr_handler is the function that simply puts the function pointer onto the interrupt table, not much else. Esp_inter_alloc_intrstatus also does this…

if (flags&ESP_INTR_FLAG_IRAM) {
    vd->flags|=VECDESC_FL_INIRAM;
    non_iram_int_mask[cpu]&=~(1<<intr);
} else {
    vd->flags&=~VECDESC_FL_INIRAM;
    non_iram_int_mask[cpu]|=(1<<intr);
}

Basically, when we allocate an interrupt using this ESP interface, we have to tell this function whether our interrupt is in IRAM or not. At this point, had I not read about ISR’s needing disabled during flash writes, I probably would’ve been stumped. But I got lucky, and I suspected that the spi flash library somehow disables interrupts based on this flag. So I set out to verify this,

Jump to spi_flash_erase_range (file flash_ops.c) This function is used during OTA so I started here. I noticed this function calls spi_flash_guard_start. While it’s not stated specifically, I had a sneaking suspicion that this was disabling non-IRAM interrupts.

Spi_flash_guard_start (file flash_ops.c) ARGGG! This function is a wrapper for a configured function pointer so I can’t just right click->go to definition… I sense Find in Files in the near future. Anyway, the function pointer is store on a s_flash_guard_ops struct. This struct is set in a setter function spi_flash_guard_set

Find In Files: spi_flash_guard_set is called by start_cpu0_default (file cpu_start.c) This function does a bunch of stuff, but it passes a global struct g_flash_guard_default_ops to spi_flash_guard_set. Now I have to find where g_flash_guard_default_ops is assigned.

Find In Files: g_flash_guard_default_ops is defined in flash_ops.c with static values. Finally! The function pointer that Spi_flash_guard_start calls is spi_flash_disable_interrupts_caches_and_other_cpu!

Find In Files: spi_flash_disable_interrupts_caches_and_other_cpu (file cache_utils.c) The one we use is the first one because we are multicore. This function has the very promising code

 // Kill interrupts that aren't located in IRAM
 esp_intr_noniram_disable();

esp_inter_noniram_disable (file intr_alloc.c) THIS IS IT! This function disables the interrupts based on non_iram_intr_mask! At this point, I think its safe to assume interrupts are enabled with the appropriate function calls…

Documentation

It is in the docs, here. It doesn’t state how that mechanism works, nor does it state what we need to do in order for that mechanism to work with our code. It does however state that non-IRAM ISRs are disabled during SPI Flash Read and Write periods.

Take-Away I don’t think we need to slam all ISRs into IRAM. We just need to be sure to install ISRs the safe, ESP-IDF way, so that the SPI drivers disable them when they need to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment