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

Advertisements

6 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

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

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