| ================= |
| 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: |
| |
| #. 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. |
| |
| #. 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``. |
| |
| #. 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: |
| |
| #. Add a global variable, say ``g_nestlevel``, that counts the interrupt |
| nesting level. It would have an initial value of zero; it would be |
| incremented on each interrupt entry and decremented on interrupt exit |
| (making sure that interrupts are disabled in each case because |
| incrementing and decrementing are not usually atomic operations). |
| |
| #. At the lowest level, there is usually some assembly language logic |
| that will switch from the user's stack to a special interrupt level |
| stack. This behavior is controlled ``CONFIG_ARCH_INTERRUPTSTACK``. |
| The logic here would have to change in the following way: If |
| ``g_nestlevel`` is zero then behave as normal, switching from the |
| user to the interrupt stack; if ``g_nestlevel`` is greater than zero, |
| then do not switch stacks. In this latter case, we are already using |
| the interrupt stack. |
| |
| #. In the middle-level, MCU-specific is where the ``g_nestlevel`` would |
| be increment. And here some additional decision must be made based on |
| the state of ``g_nestlevel``. If ``g_nestlevel`` is zero, then we |
| have interrupted user code and we need to handle the context |
| information specially and handle interrupt level context switches. If |
| ``g_nestlevel`` is greater than zero, then the interrupt handler was |
| interrupt by an interrupt. In this case, the interrupt handling must |
| always return to the interrupt handler. No context switch can occur |
| here. No context switch can occur until the outermost, nested |
| interrupt handler returns to the user task. |
| |
| #. You would also need to support some kind of critical section within |
| interrupt handlers to prevent nested interrupts. For example, within |
| the logic of functions like ``up_block_task()``. Such logic must be |
| atomic in any case. |
| |
| **NOTE 1**: The ARMv7-M could also be configured to use separate MSP and |
| PSP stacks with the interrupt processing using the MSP stack and the |
| tasks all using the PSP stacks. This is not compatible with certain |
| parts of the existing design and would be more effort, but could result |
| in a better solution. |
| |
| **NOTE 2**: SMP has this same issue as 2 but it is addressed |
| differently: With SMP there is an array of stacks indexed by the CPU |
| number so that all CPUs get to have an interrupt stack. See for |
| example, |
| `LC823450 <https://bitbucket.org/nuttx/nuttx/src/ca4ef377fb789ddc3e70979b28acb6730ff6a98c/arch/arm/src/lc823450/chip.h>`_ |
| or |
| `i.MX6 <https://bitbucket.org/nuttx/nuttx/src/ca4ef377fb789ddc3e70979b28acb6730ff6a98c/arch/arm/src/imx6/chip.h>`_ |
| SMP logic. |
| |
| A generic ``up_doirq()`` might look like the following. It can be very |
| simple because interrupts are disabled: |
| |
| .. code-block:: c |
| |
| 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. |
| */ |
| |
| current_regs = regs; |
| |
| /* Deliver the IRQ */ |
| |
| irq_dispatch(irq, regs); |
| |
| /* If a context switch occurred while processing the interrupt then |
| * current_regs may have change value. If we return any value 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: |
| |
| #. If we are nested, then we must retain the original value of |
| ``current_regs``. This will be need when the outermost interrupt |
| handler returns in order to handle interrupt level context switches. |
| |
| #. If we are nested, then we need to always return the same value of |
| ``regs`` that was received. |
| |
| So the modified version of ``up_doirq()`` would be as follows. Here we |
| assume that interrupts are enabled. |
| |
| .. code-block:: c |
| |
| uint32_t *up_doirq(int irq, uint32_t *regs) |
| { |
| irqstate_t flags; |
| |
| /* 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, therefore, |
| * only be modified for outermost interrupt handler (when g_nestlevel == 0) |
| */ |
| |
| flags = irqsave(); |
| if (g_nestlevel == 0) |
| { |
| current_regs = regs; |
| } |
| g_nestlevel++ |
| irqrestore(flags); |
| |
| /* Deliver the IRQ */ |
| |
| irq_dispatch(irq, regs); |
| |
| /* 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 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; |
| } |
| |
| /* Note that interrupts are left disabled. This needed if context switch |
| * will be performed. But, any case, the correct interrupt state 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. |
| |
| ``SVCall`` exceptions are never performed from interrupt level, handler |
| mode processing; only from thread mode logic. The ``SVCall`` exception |
| is used as follows to perform the system call: |
| |
| * All interrupts are disabled: There are a few steps the must be |
| performed in a critical section. Those setups and the ``SVCall`` must |
| work as a single, uninterrupted atomic action. |
| |
| * A special register setup is put in place: Parameters are passed to the |
| ``SVCall`` in registers just as with a normal function call. |
| |
| * The Cortex SVC instruction is executed. This causes the ``SVCall`` |
| exception which is dispatched to the ``SVCall`` exception handler. |
| This exception must occur while the input register setup is in place; |
| it cannot be deferred and perform at some later time. The ``SVCall`` |
| exception handler decodes the registers and performs the requested |
| operation. If no context switch occurs, the ``SVCall`` will return to |
| the caller immediately. |
| |
| * Upon return interrupts will be re-enabled. |
| |
| So what does this have to do with nested interrupt handling? Since |
| interrupts are disabled throughout the ``SVCall`` sequence, nothing |
| really. However, there are some concerns because if the ``BASEPRI`` is |
| used to disable interrupts then the ``SVCall`` exception must have the |
| highest priority: The ``BASEPRI`` register is set to disable all |
| interrupt except for the ``SVCall``. |
| |
| The motivation for supporting nested interrupts is, presumably, to make |
| sure that certain high priority interrupts are not delayed by lower |
| processing interrupt handling. Since the ``SVCall`` exception has |
| highest priority, it will delay all other interrupts (but, of course, |
| disabling interrupt also delays all other interrupts). |
| |
| 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. |