Documentation: Add "High Performance, Zero Latency Interrupts"
* Documentation/guides/zerolatencyinterrupts.rst: New. This document is converted from CWIKI (see [1]) to reStructuredText and brought into the Documentation tree in the repo. [1] https://cwiki.apache.org/confluence/display/NUTTX/High+Performance%2C+Zero+Latency+Interrupts
This commit is contained in:
parent
f61dc72892
commit
242127556c
|
@ -0,0 +1,249 @@
|
|||
=========================================
|
||||
High Performance, Zero Latency 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 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 <https://www.embedded.com/design/operating-systems/4461604/Interrupts-in-the-Nucleus-SE-RTOS>`_
|
||||
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:
|
||||
|
||||
- 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 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:
|
||||
|
||||
- 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 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.
|
||||
|
||||
Nested Interrupt Handling
|
||||
=========================
|
||||
|
||||
Some general notes about nested interrupt handling are provided in
|
||||
another `Wiki page <https://cwiki.apache.org/confluence/display/NUTTX/Nested+Interrupts>`_.
|
||||
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:
|
||||
|
||||
- nuttx/boards/arm/stm32/viewtool-stm32f107/README.txt. Description
|
||||
of the configuration
|
||||
|
||||
- nuttx/boards/arm/stm32/viewtool-stm32f107/highpri. Test
|
||||
configuration
|
||||
|
||||
- nuttx/boards/arm/stm32/viewtool-stm32f107/src/stm32_highpri. Test
|
||||
driver.
|
||||
|
Loading…
Reference in New Issue