NVIC: Disabling Interrupts on ARM Cortex-M and the Need for a Memory Barrier Instruction

Mastering interrupts is critical to make an embedded application reentrant. The challenge with reentrancy is that things might be implemented in a wrong way and the issue might just show up sporadically (see “EnterCritical() and ExitCritical(): Why Things are Failing Badly“). The ARM Cortex interrupt controller is named NVIC (Nested Vectored Interrupt Controller).

ARM Cortex NVIC Registers

ARM Cortex NVIC Registers

As the ‘nested’ in NVIC indicates, that controller supports nested interrupts which is a good thing from an interrupt latency and flexibility perspective. I can use the NVIC to selectively disable/enable interrupts. If done properly, I don’t have to disable system-wide the interrupts: I can narrow down my interrupt locking to a minimum of interrupts.

The NVIC has the following registers to enable/disable interrupts (one bit for each vector number):

  1. NVIC_ISER: Interrupt Set Enable Register to enable an interrupt source
  2. NVIC_ICER: Interrupt Clear Enable Register to disable an interrupt source
  3. NVIC_ISPR: Interrupt Set Pending Register to raise an interrupt
  4. NVIC_ISCR: Interrupt Clear Pending Register to clear a pending interrupt

The following shows e.g. the bits to enable DMA interrupts on a Freescale KL25Z device:

NVIC ISER

NVIC ISER

To disable an interrupt source, I can do this in the following CMSIS way:

NVIC_DisableIRQ(device_IRQn); // Disable interrupt

with the right IRQ number. However, there is a possible problem with the architecture.

💡 It is a false thinking that with these modern processors things will be ‘done immediately’: because of the internal pipelining, caching, busses and propagation delays settings will not have an immediate impact. Instead, there might be some delay.

So if an interrupt is just happening before the disable instruction, it might still happen after I have disabled the interrupt:

Interrupt Disabling Delay

Interrupt Disabling Delay (Source: ARM)

That might or might not be a problem for my design. But if I want to create a critical section to prevent that an interrupt is happening that way, I need to add ‘memory barriers’ instructions (see http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHEBHFF.html):

  • DSB: Data Synchronization Barrier. Ensures that all explicit data memory transfers before the DSB are completed before any instructions after the DSB is executed.
  • ISB: Instruction Synchronization Barrier. Ensures that the effects of all context altering operations prior to the ISB are recognized by subsequent instructions. This results in a flushing of the instruction pipeline, with the instruction after the ISB being re-fetched.

ARM recommends first to use a DSB, followed by an ISB:

NVIC_DisableIRQ(device_IRQn); // Disable interrupt
__DSB();
__ISB();

Memory barrier instructions are necessary if I don’t want to have a pending interrupt triggered, or if need to access the something in the peripheral space which is related to the interrupt source, e.g. changing the interrupt vector or a peripheral setting which for example would change the vector location. Or in other words where any interrupt activity of that peripheral would be a problem.

Summary

Not thinking through the fact that there are propagation delays in the ARM Cortex M0/M4 architecture can lead to flawed interrupt handling. The nasty thing is that the problem will occur only rarely, and it will be hard to track down. Adding a memory barrier might be the golden bullet to solve your problem too :-).

Happy Interrupting 🙂

Links

10 thoughts on “NVIC: Disabling Interrupts on ARM Cortex-M and the Need for a Memory Barrier Instruction

  1. Theoretically , when Interrupt comes and if it is not disabled , the mcu should flush the existing pipeline including NVIC_DisableIRQ(device_IRQn); as mentioned in post and should start executing the ISR.
    Or you mean to say process of Interrupt getting registerred takes some time , So actually triggerred trigerred interrupt from hardware eventhough is few cycles erlier than NVIC_DisableIRQ(device_IRQn); then also there is a chance that NVIC_DisableIRQ(device_IRQn) instruction will executed then it will branch to ISR,
    If this is the case and we are using the Memory Barrier instruction to avoid this, then in that case what would happen to triggerred interrupt we will be loosing it ?

    Like

    • To my understanding, if an interrupt gets triggered, it is triggered in the peripheral, but has not made it to the NVIC yet. So I disable the interrupt in NVIC, it might still come through, so after NVIC_DisableIRQ() it is not yet really disabled. To my understanding and with my testing, if the the interrupt trigger happened (as shown in the picture), and if I do a memory barrier, the memory barrier will make sure that I still get the interrupt, and that indeed after the barrier the interrupt will be really disabled. In case I have globally disabled the interrupts, the interrupt will be pending and will be triggering after I re-enable the interrupt flag.

      Liked by 1 person

      • DSB instructions are processor-centric i.e. the load/store unit has honored their sequence but this *does not* mean that peripherals have completed their actions, especially if they are running in separate clock domains from the processor, and especially if there is resynchronization logic between those domains.

        I like the paranoid approach – to a read from one of the registers are the last significant write. This absolutely ensures that the previous operations have completed in the peripheral and the processor sequencing knows that the state is now coherent.

        Liked by 1 person

  2. Dave Edwards: The “read after write” approach for registers sounds like a good idea. I expect that this technique depends on the registers being declarded as ‘volatile’, to prevent compiler optimizations. Am I right?

    What about memories, particularly external memory? It there are wait states required, is the DSB strategy sufficient? Or should the “read after write” technique be used there also, with the variable declared as volitile?

    Like

  3. Hi all,

    I’ve just ran into a similar issue with a Kinetis KV5x MCU (Cortex-M7) and a PIT ISR. The interrupt was fired correctly with the configured period, but always TWICE in approx. 500ns. This seemed to be due to the clearing of the interrupt flag inside the ISR, which was stuck inside the pipeline when the processor exited the handler, saw that the flag is still set and re-entered the ISR, before the flag finally was cleared in the peripheral. A __DSB(); at the end of the ISR fixed the problem. Maybe this would be a good idea to do generally at the end of every ISR which clears its interrupt flag, at least in very short ISRs. And clear the flag at the beginning, not at the end.

    Regards,
    Philipp

    Like

    • Hi Philipp,
      I have started adding __DSB() to the end of all interrupt routines on all ARM Cortex because fo the ARM errata 838869 (search for that on the net): if there is a store-immediate pending it could vector into a wrong interrupt routine. Outsch! I was affected by that silicon bug badly in one of my projects.

      Like

  4. Newer versions of the CMSIS headers, where NVIC_DisableIRQ() is defined, include the calls to __DSB() and __ISB() as part of NVIC_DisableIRQ().

    Liked by 1 person

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.