307 lines
13 KiB
ReStructuredText
307 lines
13 KiB
ReStructuredText
=====================================================================
|
|
High Performance: Zero Latency Interrupts, Maskable nested interrupts
|
|
=====================================================================
|
|
|
|
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 a 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 circumstances.
|
|
|
|
In addition, critical sections that are required in various places
|
|
throughout the RTOS can pause interrupt handling momentarily. This
|
|
increases the latency for those interrupts which become pending during a
|
|
critical section. As this is likely to occur for some instances of an
|
|
interrupt and not others, the interrupt latency varies from time to time
|
|
(experiences *jitter*). Like the added latency discussed above, that
|
|
jitter might be an issue in some circumstances.
|
|
|
|
**Terminology:** The concepts discussed in this guide are not unique to
|
|
NuttX. Other RTOSes have similar concepts but will use different
|
|
terminology. The `Nucleus <https://www.embedded.com/design/operating-systems/4461604/Interrupts-in-the-Nucleus-SE-RTOS>`_
|
|
RTOS, for example, uses 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:
|
|
|
|
* Your custom interrupt handler will not have collected its state into
|
|
the ``struct xcptcontext`` container. Therefore, it cannot communicate
|
|
with operating system. Your custom interrupt handler has been taken
|
|
"out of the game" and can no longer work with the system.
|
|
|
|
* If your custom interrupt is truly going to be *high performance* then
|
|
you will also have to support nested interrupts! The custom interrupt
|
|
must have a high priority and must be able interrupt the generic
|
|
interrupt handling logic. Otherwise, it will be occasionally delayed
|
|
when there is a collision between your custom interrupt and other,
|
|
lower priority interrupts.
|
|
|
|
Getting Back into the Game
|
|
==========================
|
|
|
|
As mentioned, the custom interrupt handler cannot use most of the
|
|
services 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:
|
|
|
|
* The custom logic would connect with the *PendSV* interrupt using the
|
|
standard ``irq_attach()`` interface.
|
|
|
|
* In the custom interrupt handler, it would schedule the *PendSV*
|
|
interrupt when it needs to communicate with the OS.
|
|
|
|
* The *PendSV* interrupt is dispatched through the generic interrupt
|
|
system so when the attached *PendSV* interrupt is handled, it will be
|
|
in a context where it can perform any necessary OS interactions.
|
|
|
|
With the ARMv7_M architecture, the *PendSV* interrupt can be generated
|
|
with:
|
|
|
|
.. code-block:: c
|
|
|
|
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.
|
|
|
|
The following table shows the priority levels of the Cortex-M family:
|
|
|
|
.. code-block::
|
|
|
|
IRQ type Priority
|
|
Dataabort 0x00
|
|
High prio IRQ1 0x20 (Zero-latency interrupt)
|
|
High prio IRQ2 0x30 (Can't call OS API in ISR)
|
|
SVC 0x70
|
|
Disable IRQ 0x80
|
|
(critical-section)
|
|
Low prio IRQ 0xB0
|
|
PendSV 0xE0
|
|
|
|
As you can see, the priority levels of the zero-latency interrupts can
|
|
beyond the critical section and SVC.
|
|
But High prio IRQ can't call OS API.
|
|
|
|
|
|
Maskable Nested Interrupts
|
|
==========================
|
|
|
|
The ARM Cortex-M family supports a feature called *BASEPRI* that can be
|
|
used to disable interrupts at a priority level below a certain level.
|
|
This feature can be used to support maskable nested interrupts.
|
|
|
|
Maskable nested interrupts differ from zero-latency interrupts in
|
|
that they obey the interrupt masking mechanisms of the system.
|
|
For example, setting the BASEPRI register to a specific threshold will
|
|
block all interrupts of a lower or equal priority.
|
|
However, high-priority interrupts (such as Non-Maskable Interrupts
|
|
or zero-latency interrupts) are unaffected by these masks.
|
|
|
|
This is useful when you have a high-priority interrupt that needs to
|
|
be able to interrupt the system, but you also have lower-priority
|
|
interrupts that you want to be able to mask.
|
|
|
|
The following table shows the priority levels of the Cortex-M family:
|
|
|
|
.. code-block::
|
|
|
|
IRQ type Priority
|
|
Dataabort 0x00
|
|
SVC 0x70
|
|
Disable IRQ 0x80
|
|
(critical-section)
|
|
High prio IRQ1 0x90 (Maskable nested interrupt)
|
|
High prio IRQ2 0xA0 (Can call OS API in ISR)
|
|
Low prio IRQ 0xB0
|
|
PendSV 0xE0
|
|
|
|
As you can see, the priority levels of the maskable nested interrupts
|
|
are between the critical section and the low-priority interrupts.
|
|
And High prio IRQ can call OS API in ISR.
|
|
|
|
|
|
Nested Interrupt Handling
|
|
=========================
|
|
|
|
Some general notes about nested interrupt handling are provided in
|
|
:doc:`nestedinterrupts`. 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:
|
|
|
|
* The MCU should support interrupt prioritization so that the custom
|
|
interrupt can be scheduled with a higher priority.
|
|
|
|
* The generic interrupt handlers currently disable interrupts during
|
|
interrupts. Instead, they must be able to keep the custom interrupt
|
|
enabled throughout interrupt process but still prevent re-entrancy by
|
|
other standard interrupts (This can be done by setting an interrupt
|
|
base priority level in the Cortex-M family).
|
|
|
|
* The custom interrupt handler can now interrupt the generic interrupt
|
|
handler at any place. Is the logic safe in all cases to be
|
|
interrupted? Sometimes interrupt handlers place the MCU in momentarily
|
|
perverse states while registers are being manipulated. Make sure that
|
|
it is safe to take interrupts at any time (or else keep the interrupts
|
|
disabled in the critical times).
|
|
|
|
* Will the custom interrupt handler have all of the resources it needs
|
|
in place when it occurs? Will it have a valid stack pointer? (In the
|
|
Cortex-M implementation, for example, the MSP may not be valid when
|
|
the custom interrupt handler is entered).
|
|
|
|
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
|
|
------------
|
|
|
|
* ``CONFIG_ARCH_HAVE_IRQPRIO``. Support for prioritized interrupt
|
|
support must be enabled.
|
|
|
|
* Floating Point Registers. If used with a Cortex-M4 that supports
|
|
hardware floating point, you cannot use hardware floating point in the
|
|
high priority interrupt handler UNLESS you use the common vector logic
|
|
that supports saving of floating point registers on all interrupts.
|
|
|
|
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:
|
|
|
|
* If you select ``CONFIG_ARCH_RAMVECTORS``, then vectors will be kept in
|
|
RAM and the system will support the interface: ``int
|
|
up_ramvec_attach(int irq, up_vector_t vector)``. That interface can be
|
|
used to attach your C interrupt handler to the vector at run time.
|
|
|
|
* Alternatively, you could keep your vectors in FLASH but in order to
|
|
this, you would have to develop your own custom vector table.
|
|
|
|
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:
|
|
|
|
* :doc:`/platforms/arm/stm32f1/boards/viewtool-stm32f107/index` Description of
|
|
the configuration
|
|
|
|
* ``nuttx/boards/arm/stm32/viewtool-stm32f107/highpri`` Test configuration
|
|
|
|
* ``nuttx/boards/arm/stm32/viewtool-stm32f107/src/stm32_highpri`` Test
|
|
driver.
|
|
|