Generic Interrupt Handling

NuttX includes a generic interrupt handling subsystem that makes it convenient to deal with interrupts using only IRQ numbers. In order to integrate with this generic interrupt handling system, the platform specific code is expected to collect all thread state into an container, struct xcptcontext. This container represents the full state of the thread and can be saved, restored, and exchanged as a unit of thread.

While this state saving has many useful benefits, it does require processing time. It was reported to me that this state saving required about two microseconds on an STM32F4Discovery board. That added interrupt latency might be an issue in some circumstance.

Terminology: The concepts discussed in this Wiki are not unique to NuttX. Other RTOS have similar concepts but will use different terminology. The Nucleus RTOS, for example use the terms Native and Managed interrupts.

Bypassing the Generic Interrupt Handling

Most modern MCUs (such as the ARM Cortex-M family) receive and dispatch interrupts through a vector table. The vector table is a table in memory. Each entry in the table holds the address of an interrupt handler corresponding to different interrupts. When the interrupt occurs, the hardware fetches the corresponding interrupt handler address and gives control to the interrupt handler.

In the implementation of the generic interrupt handler, these vectored interrupts are not used as intended by the hardware designer. Rather, they are used to obtain an IRQ number and then to transfer control to the common, generic interrupt handling logic.

One way to achieve higher performance interrupts and still retain the benefits of the generic interrupt handling logic is to simply replace an interrupt handler address in the vector table with a different interrupt handler; one that does not vector to the generic interrupt handling logic logic, but rather to your custom code.

Often, the vector table is in ROM. So you can hard-code a special interrupt vector by modifying the ROM vector table so that the specific entry points to your custom interrupt handler. Or, if the architecture permits, you can use a vector table in RAM. Then you can freely attach and detach custom vector handlers by writing directly to the vector table. The ARM Cortex-M port provides interfaces to support this mode when the CONFIG_ARCH_RAMVECTORS option is enabled.

So what is the downside? There are two:

Getting Back into the Game

As mentioned, the custom interrupt handler can not use most of the service of the OS since it has not created a struct xcptcontext container. So it needs a mechanism to "get back into the game" when it needs to interact with the operating system to, for example, post a semaphore, signal a thread, or send a message.

The ARM Cortex-M family supports a special way to do this using the PendSV interrupt:

With the ARMv7_M architecture, the PendSV interrupt can be generated with:

  up_trigger_irq(NVIC_IRQ_PENDSV);

On other architectures, it may be possible to do something like a software interrupt from the custom interrupt handler to accomplish the same thing.

The custom logic would be needed to communicate the events of interest between the high priority interrupt handler and PendSV interrupt handler. A detailed discussion of that custom logic is beyond the scope of this Wiki page.

Nested Interrupt Handling

Some general notes about nested interrupt handling are provided in another Wiki page. In this case, handling the nested custom interrupt is simpler because the generic interrupt handler is not re-entered. Rather, the generic interrupt handler must simply be made to co-exist with the custom interrupt interrupt handler.

Modifications may be required to the generic interrupt handling logic to accomplish. A few points need to be made here:

Some of these issues are complex and so you should expect some complexity in getting the nested interrupt handler to work.

Cortex-M3/4 Implementation

Such high priority, nested interrupt handler has been implemented for the Cortex-M3/4 families.
The following paragraphs will summarize that implementation.

Configuration Options

CONFIG_ARCH_HIPRI_INTERRUPT

If CONFIG_ARMV7M_USEBASEPRI is selected, then interrupts will be disabled by setting the BASEPRI register to NVIC_SYSH_DISABLE_PRIORITY so that most interrupts will not have execution priority. SVCall must have execution priority in all cases.

In the normal cases, interrupts are not nest-able and all interrupts run at an execution priority between NVIC_SYSH_PRIORITY_MIN and NVIC_SYSH_PRIORITY_MAX (with NVIC_SYSH_PRIORITY_MAX reserved for SVCall).

If, in addition, CONFIG_ARCH_HIPRI_INTERRUPT is defined, then special high priority interrupts are supported. These are not "nested" in the normal sense of the word. These high priority interrupts can interrupt normal processing but execute outside of OS (although they can "get back into the game" via a PendSV interrupt).

Disabling the High Priority Interrupt

In the normal course of things, interrupts must occasionally be disabled using the up_irq_save() inline function to prevent contention in use of resources that may be shared between interrupt level and non-interrupt level logic. Now the question arises, if we are using the BASEPRI to disable interrupts and have high priority interrupts enabled (CONFIG_ARCH_HIPRI_INTERRUPT=y), do we disable all interrupts except SVCall (we cannot disable SVCall interrupts)? Or do we only disable the "normal" interrupts?

If we are using the BASEPRI register to disable interrupts, then the answer is that we must disable ONLY the normal interrupts. That is because we cannot disable SVCALL interrupts and we cannot permit SVCAll interrupts running at a higher priority than the high priority interrupts. Otherwise, they will introduce jitter in the high priority interrupt response time.

Hence, if you need to disable the high priority interrupt, you will have to disable the interrupt either at the peripheral that generates the interrupt or at the interrupt controller, the NVIC. Disabling global interrupts via the BASEPRI register cannot affect high priority interrupts.

Dependencies

Configuring High Priority Interrupts

How do you specify a high priority interrupt? You need to do two things:

First, You need to change the address in the vector table so that the high priority interrupt vectors to your special C interrupt handler. There are two ways to do this:

Second, you need to set the priority of your interrupt to NVIC to NVIC_SYSH_HIGH_PRIORITY using the standard interface: int up_prioritize_irq(int irq, int priority);

Example Code

You can find an example that tests the high priority, nested interrupts in the NuttX source: