Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fix typo

Nested Interrupts

Are Nested Interrupts Needed?

Most NuttX architectures do not support nested interrupts: Interrupts are disabled when the interrupt is entered and restored when the interrupt returns. Being able to handle nested interrupt is critical in simple architectures where a lot of interrupt level processing is performed: In this case, you can prioritize interrupts and assure that the highest priority interrupt processing is not delayed by lower level interrupt processing.

In an RTOS model, however, all interrupt processing should be as brief as possible; any extended processing should be deferred to a user task and not performed in the interrupt handler. However, you may find a need to have nested interrupt handling in NuttX too. The lack of support of nested interrupts is not inherently an issue with NuttX and need not be the case; it should be a simple matter to modify the interrupt handling so that interrupts are nested.

Layered Interrupt Handling Architecture

Interrupt handling occurs in several files. In most implementations, there are several layers of interrupt handling logic:

  1. Some low-level logic, usually in assembly language, that catches the interrupt and determines the IRQ number. Consider arch/arm/src/armv7-m/up_exception.S as an example for the Cortex-M family.
  2. That low-level logic than calls some MCU-specific, intermediate level function usually called up_doirq(). An example is arch/arm/src/armv7-m/up_doirq.c.
  3. That MCU-specific function then calls the NuttX common interrupt dispatching logic irq_dispatch() that can be found at sched/irq_dispatch.c

How to Implement Nested Interrupts in the Layered Interrupt Handling

...

Architecture

The logic in these first two levels that would have to change to support nested interrupt handling. Here is one technical approach to do that:

...

A generic up_doirq() might look like the following. It can be very simple because interrupts are disabled:

Code Block

  uint32_t *up_doirq(int irq, uint32_t *regs)
  {
    /* Current regs non-zero indicates that we are processing an interrupt;
   * current_regs is also used to manage interrupt level context switches.

...

Code Block
   */

  current_regs = regs;
Code Block
    /* Deliver the IRQ */
Code Block
    irq_dispatch(irq, regs);
Code Block
    /* If a context switch occurred while processing the interrupt then
   * current_regs may have change value.  If we return any value

...

Code Block

   different
   * from the input regs, then the lower level will know that a context
   * switch occurred during interrupt processing.
   */

  regs = (uint32_t*)current_regs;
    current_regs = NULL;
    return regs;
  }

What has to change to support nested interrupts is:

...

So the modified version of up_doirq() would be as follows. Here we assume that interrupts are enabled.

Code Block

  uint32_t *up_doirq(int irq, uint32_t *regs)
  {
    irqstate_t flags;
Code Block
    /* Current regs non-zero indicates that we are processing an interrupt;
   * regs holds the state of the interrupted logic; current_regs holds

...

 the
   * state of the interrupted user task.  current_regs should, therefor,

...


   * only be modified for outermost interrupt handler (when g_nestlevel == 0)

...

Code Block
   */

  flags = irqsave();
    if (g_nestlevel == 0)
      {
        current_regs = regs;
      }
    g_nestlevel++
    irqrestore(flags);
Code Block
    /* Deliver the IRQ */
Code Block
    irq_dispatch(irq, regs);
Code Block
    /* Context switches are indicated by the returned value of this function.
   * If a context switch occurred while processing the interrupt

...

 then
   * current_regs may have change value.  If we return any value

...

Code Block

   different
   * from the input regs, then the lower level will know that a context
   * switch occurred during interrupt processing.  Context switching should
   * only be performed when the outermost interrupt handler returns.
   */

  flags = irqsave();
    g_nestlevel--;
    if (g_nestlevel == 0)
      {
        regs = (uint32_t*)current_regs;
        current_regs = NULL;
      }
Code Block
    /* Note that interrupts are left disabled.  This needed if context switch
   * will be performed.  But, any case, the correct interrupt state

...

Code Block
 should
   * be restored when returning from the interrupt.
   */

  return regs;
  }

NOTE: An alternative, cleaner design might also be possible. If one were to defer all context switching to a PendSV handler, then the interrupts could vector to the do_irq() logic and then all interrupts would be naturally nestable.

SVCall vs PendSV

An issue that may be related to nested interrupt handling is the use of the SVCall exceptions in NuttX. The SVCall exception is used as a classic software interrupt in NuttX for performing context switches, user- to kernel-mode changes (and vice versa), and also for system calls when NuttX is built as a kernel.

...

The PendSV exception is another mechanism offered by the Cortex architecture. It has been suggested that some of these issues with the SVCall exception could be avoided by using the PendSV interrupt. The architecture that would use the PendSV exception instead of the SVCall interrupt is not clear in my mind. But I will keep this note here for future reference if this were to become as issue.

What Could Go Wrong?

Whenever you deal with logic at software hardware interface, lots of things can go wrong. But, aside from that general risk, the only specific NuttX risk issue is that you may uncover some subtle interrupt level logic that assumes that interrupts are already disabled. In those cases, additional critical sections may be needed inside of the interrupt level processing. The likelihood of such a thing is probably pretty low, but cannot be fully discounted.