Adds the basic kernel objects' documentation describing the function of tasks, fiber and interrupt service routines. Adds the nanokernel objects' docuementation describing the function of the most important nanokernel objects. Adds the microkernel objects' documentation describing the function of the most important microkernel objects. Changes the index.rst file to include the Object Documentation. Change-Id: Ib35d973cc3575a7ecc32c4ab175e05cb298e3306 Signed-off-by: Rodrigo Caballero <rodrigo.caballero.abraham@intel.com> Signed-off-by: Anas Nashif <anas.nashif@intel.com>
359 lines
16 KiB
ReStructuredText
359 lines
16 KiB
ReStructuredText
Interrupt Service Routines
|
|
##########################
|
|
|
|
|
|
General Information
|
|
*******************
|
|
|
|
Interrupt Service Routines are execution threads that run in response to
|
|
a hardware or software interrupt. They will preempt the execution of
|
|
any task or fiber running at the time the interrupt occurs.
|
|
Consequently, ISRs react fastest to hardware. Routines in the
|
|
nanokernel wake up with a very low overhead.
|
|
|
|
.. warning::
|
|
|
|
ISRs prevent other parts of the system from running. Therefore,
|
|
all code in these routines should be confined to short, simple
|
|
routines.
|
|
|
|
.. todo:: Insert how an ISR can be installed both static and dynamic.
|
|
|
|
|
|
Both dynamic and static ISRs can be installed. See
|
|
`Installing a Dynamic ISR`_ and `Installing a Static ISR`_ for more
|
|
details. An ISR cannot be installed in the project file, only a task or
|
|
a driver initialization call can install an ISR.
|
|
|
|
When an ISR wakes up a fiber, there is only one context switch directly
|
|
to the fiber. When an ISR wakes up a task, there is first a context
|
|
switch to the nanokernel, and then another context switch to the task
|
|
in the microkernel.
|
|
|
|
|
|
Interrupt Stubs
|
|
***************
|
|
|
|
Interrupts stubs are small pieces of assembler code that connect your
|
|
ISR to the Interrupt Descriptor Table (IDT). The interrupt stub informs
|
|
the kernel when an interrupt is in progress, performs interrupt
|
|
controller specific work, invokes your ISR and informs the kernel when
|
|
the interrupt processing is complete. The stub address is registered in
|
|
the Interrupt Descriptor Table. The stub references your ISR and the
|
|
stubs can either be generated dynamically or statically.
|
|
|
|
|
|
Interrupt Service Routine APIs
|
|
******************************
|
|
|
|
The table lists the ISR Application Program Interfaces. There are a
|
|
number of calls that an ISR can use to switch between different
|
|
processing levels.
|
|
|
|
.. note::
|
|
|
|
Application Program Interfaces of the ISRs are architecture-
|
|
specific because they are implemented in the interrupt controller
|
|
device driver for that processor or board. The architecture specific
|
|
implementation can be found in the corresponding documentation for
|
|
each architecture.
|
|
|
|
+-------------------------+---------------------------+
|
|
| Call | Description |
|
|
+=========================+===========================+
|
|
| :c:func:`irq_enable()` | Enables a specific IRQ. |
|
|
+-------------------------+---------------------------+
|
|
| :c:func:`irq_disable()` | Disables a specific IRQ. |
|
|
+-------------------------+---------------------------+
|
|
| :c:func:`irq_lock()` | Locks out all interrupts. |
|
|
+-------------------------+---------------------------+
|
|
| :c:func:`irq_unlock()` | Unlocks all interrupts. |
|
|
+-------------------------+---------------------------+
|
|
|
|
|
|
Installing a Dynamic ISR
|
|
************************
|
|
|
|
Use :c:func:`irq_connect()` to install and connect an ISR stub
|
|
dynamically. :c:func:`irq_connect()` is processor-specific. There is no
|
|
API method to uninstall a dynamic ISR.
|
|
|
|
|
|
Installing a Static ISR
|
|
***********************
|
|
|
|
The contents of a static interrupt stub are complex and board specific.
|
|
They are generally created manually as part of the BSP. A stub is
|
|
installed statically into the Interrupt Descriptor Table using one of
|
|
the macros detailed in following table. The table lists the macros you
|
|
can use to identify and register your static ISRs into the Interrupt
|
|
Descriptor Table. The IA-32 interrupt descriptor allows for the setting
|
|
of the privilege level, DPL, at which the interrupt can be triggered.
|
|
Tiny Mountain assumes all device drivers are kernel mode (ring 0) as
|
|
opposed to user-mode (ring 3). Therefore, these macros always set the
|
|
DPL to 0.
|
|
|
|
The IDT Macros
|
|
==============
|
|
|
|
|
|
+--------------------------+-------------------------------------------------------------------------+
|
|
| Call | Description |
|
|
+==========================+=========================================================================+
|
|
| NANO_CPU_INT_REGISTER( ) | Use this macro to register a driver's |
|
|
| | interrupt |
|
|
| | handler statically when the vector number is known at compile time. |
|
|
+--------------------------+-------------------------------------------------------------------------+
|
|
| SYS_INT_REGISTER( ) | Use this macro to register a driver's |
|
|
| | interrupt handler statically when |
|
|
| | the vector number is not known at compile time but the priority and IRQ |
|
|
| | line are. The BSP is responsible for implementing this macro in board.h |
|
|
| | to generate a vector from the priority and IRQ line at compile time. |
|
|
| | The macro is intended to provide a level of abstraction between the BSP |
|
|
| | and the driver. |
|
|
+--------------------------+-------------------------------------------------------------------------+
|
|
|
|
|
|
Interrupt Descriptor Table
|
|
**************************
|
|
|
|
The Interrupt Descriptor Table (IDT) is a data structure that implements
|
|
an interrupt vector table used by the processor to determine the
|
|
correct response to interrupts and exceptions. To optimize boot
|
|
performance and increase security, Tiny Mountain implements targets
|
|
using a statically created Interrupt Descriptor Table, interrupt stubs
|
|
and exception stubs. A static Interrupt Descriptor Table improves boot
|
|
performance because:
|
|
|
|
* No CPU cycles are used to construct the Interrupt Descriptor Table
|
|
at boot up.
|
|
|
|
* No CPU cycles are used to create interrupt stubs at boot up.
|
|
|
|
* No CPU cycles are used to create exceptions stubs at run-time.
|
|
|
|
The statically created Interrupt Descriptor Table can still be updated
|
|
at run-time despite being write-protected. There may be instances where
|
|
updating the Interrupt Descriptor Table at run-time is required, for
|
|
example, in order to install dynamic interrupts. The decision of
|
|
whether a target implements dynamic or static interrupts is determined
|
|
at compile time automatically based on the configuration.
|
|
|
|
|
|
Securing the Interrupt Descriptor Table
|
|
***************************************
|
|
|
|
Typically the IDT resides in the data section. Enable the Section Write
|
|
Protection feature to move the Interrupt Descriptor Table to the rodata
|
|
section and to mark all pages of memory in which the Interrupt
|
|
Descriptor Table resides as read-only. Enabling the Section Write
|
|
Protection feature places dynamic interrupt stubs into the text section
|
|
protecting them. A system where execute in place, XIP, support is
|
|
enabled, assumes the text section and read-only data section reside in
|
|
read-only memory, such as flash memory or ROM. In this scenario dynamic
|
|
interrupt stubs are not possible. The Interrupt Descriptor Table cannot
|
|
be updated at runtime. Therefore enabling the Section Write Protection
|
|
feature blocks generating dynamic interrupt stubs and updating the
|
|
Interrupt Descriptor Table at runtime.
|
|
|
|
Note This implementation of XIP does not support a ROM-resident
|
|
Interrupt Descriptor Table. When the segmentation feature is enabled,
|
|
execution of code in the data segment is not allowed. If the
|
|
segmentation feature is enabled and section write protection is not
|
|
enabled, dynamic interrupt stubs move to the text section, but they are
|
|
still writable.
|
|
|
|
The following is an example of a dynamic interrupt stub for x86:
|
|
|
|
.. code-block:: c
|
|
|
|
static NANO_CPU_INT_STUB_DECL (deviceStub);
|
|
|
|
void deviceDriver (void)
|
|
|
|
{
|
|
|
|
.
|
|
.
|
|
.
|
|
|
|
nanoCpuIntConnect (deviceIRQ, devicePrio, deviceIntHandler,
|
|
deviceStub);
|
|
|
|
.
|
|
.
|
|
.
|
|
|
|
}
|
|
|
|
This feature is part of Tiny Mountain's enhanced security profile.
|
|
|
|
|
|
Working with ISRs
|
|
*****************
|
|
|
|
|
|
Triggering Interrupts
|
|
=====================
|
|
|
|
The processor starts up an ISR when a hardware interrupt is received.
|
|
When one of the interrupt pins of the processor core is triggered, the
|
|
processor jumps to the appropriate interrupt routine. To interface this
|
|
hardware event with software, Tiny Mountain allows you to attach an ISR
|
|
to the interrupt signal.
|
|
|
|
An ISR can interface with a fiber using the nanokernel Application
|
|
Program Interfaces. The ISR can wake up a task using the microkernel
|
|
synchronization objects, an event or invoking the event handler. The
|
|
nanokernel affords them the lowest startup overhead because ISRs are
|
|
triggered from the hardware level. No context switch is needed to start
|
|
up an ISR.
|
|
|
|
When an interrupt occurs, all fibers and all tasks wait until the
|
|
interrupt is handled. If an application is executing a task or a fiber
|
|
is running, it is interrupted until the ISR finishes.
|
|
|
|
An ISR implementation is typically very hardware-specific because it
|
|
interfaces directly with a hardware interrupt and starts to run because
|
|
of it. The details of how this happens are described in your processors
|
|
documentation.
|
|
|
|
Prototype your hardware-specific functionality in a task, before you
|
|
move it to the ISR code.
|
|
|
|
If an ISR calls a channel service with a signal action, any fiber
|
|
rescheduling resulting from this call is delayed until all interrupt
|
|
handlers terminate. Therefore, use only the nano_Isr Application
|
|
Program Interfaces, as these do not invoke the system kernel scheduler
|
|
for a signal action. Keep in mind that there is no need for a swap at
|
|
this point; the caller has the highest priority already. Once the last
|
|
stacked interrupt terminates, the nanokernel scheduler must be called
|
|
to verify if a swap from the task to a fiber is necessary.
|
|
|
|
An ISR must never call any blocking channel Application Program
|
|
Interface. It would block the current fiber and all other interrupt
|
|
handlers that are stacked below the ISR.
|
|
|
|
Tiny Mountain supports interrupt nesting. When an ISR is running, it can
|
|
be interrupted when a new interrupt is received.
|
|
|
|
|
|
Using Interrupt Service Routines
|
|
================================
|
|
|
|
If interrupts come in at high speed, parts of your code can be at the
|
|
ISR level. If code is at the interrupt level, it avoids a context
|
|
switch making it faster. If interrupts come in at low speeds, the ISR
|
|
should only wake up a fiber or a task. That fiber or task should do all
|
|
the processing, not the ISR, even if the task can be interrupted by
|
|
fibers and ISRs. Keep fibers and ISRs short to ensure predictability.
|
|
|
|
For example, take an application that implements an algorithm in an ISR.
|
|
Suppose the algorithm takes one second to finish calculating. The
|
|
application has a task in the background that interfaces with a host
|
|
machine to plot data on the screen. The task updates the screen image
|
|
five times per second to provide a smooth screen display. This
|
|
application as a whole does not behave predictably if an interrupt is
|
|
received. The ISR starts calculating for one second and causes an
|
|
unexpected delay. The same holds true if the algorithm is implemented
|
|
using a fiber. The user sees an interleaved screen output. This example
|
|
is extreme but it shows that short fibers and short ISRs make the
|
|
system more predictable.
|
|
|
|
|
|
Implementing Interrupt Service Routines
|
|
***************************************
|
|
|
|
Most processors require that ISRs be coded in assembler. To make the
|
|
implementation easier, several assembler macros are available to do the
|
|
most common jobs. Because the ISRs block all other processing, always
|
|
implement the actual handling of the interrupt in a fiber or a task.
|
|
Where to handle the interrupt is a design choice that must be made
|
|
while considering the performance of the processor and the frequency of
|
|
the interrupt.
|
|
|
|
|
|
Coordinating ISRs and Events
|
|
****************************
|
|
|
|
An ISR can send a signal from the nanokernel to the microkernel to
|
|
trigger an event. Your setup can work with an event handler, or without
|
|
one. If there is no event handler and your task is waiting for the
|
|
event, the ISR wakes up the task when it triggers the event. If you
|
|
have an event handler, the ISR triggers the event handler routine. This
|
|
event handler then determines if the task wakes up or not.
|
|
|
|
.. warning::
|
|
|
|
Implement or process a buffer in an event handler if you input
|
|
comes in at a high speed.
|
|
|
|
Command Packet Sets
|
|
*******************
|
|
|
|
A command packet set is a group of statically-allocated command packets.
|
|
A command packet is accessible to any application running in kernel
|
|
space. They are necessary when signaling a semaphore from an ISR via
|
|
:c:func:`Isr_sem_give()` since command packets are processed after the
|
|
ISR finishes. That makes stack-allocated command packets unsafe for
|
|
this purpose. A statically-allocated command packet is implicitly
|
|
released after being processed. Consequently, the operating system does
|
|
not track the use-status of any statically-allocated command packet.
|
|
|
|
There is a small but unavoidable risk of a command packet's processing
|
|
being incomplete before the ISR runs again and tries to reuse the
|
|
packet. To further minimize this risk Tiny Mountain introduces command
|
|
packet sets. Fundamentally, a command packet set is a simple ring
|
|
buffer. Retrieve command packets from the set using
|
|
:c:func:`cmdPktGet()`. Each command packet has to be processed in a
|
|
near-FIFO since no use-status checking is performed a packet is
|
|
retrieved. In order to minimize the risk of packet corruption from
|
|
premature reuse, drivers that have an ISR component should use their
|
|
own command packet set and not use a common set for many drivers.
|
|
Create a command packet set in global memory using:
|
|
|
|
.. code-block:: c
|
|
|
|
CMD_PKT_SET_INSTANCE(setVariableName, #ofCommandPacketsInSet);
|
|
|
|
|
|
Task Level Interrupt Processing
|
|
*******************************
|
|
|
|
The task level interrupt processing feature permits to service
|
|
interrupts at the task level, without having to develop kernel level
|
|
ISRs. The *MAX_NUM_TASK_DEVS* kernel configuration option specifies the
|
|
total number of devices needing task-level interrupt support.
|
|
|
|
The default setting of 0 disables the following interfaces:
|
|
:c:func:`task_irq_alloc()`, :c:func:`task_irq_free()`,
|
|
:c:func:`task_irq_ack()` and :c:func:`task_irq_test()`. Each device has
|
|
a well-known identifier in the range from 0 to *MAX_NUM_TASK_DEVS*-1.
|
|
|
|
Tiny Mountain allows kernel tasks to bind to devices at run-time by
|
|
calling :c:func:`task_irq_alloc()`. A task may bind itself to multiple
|
|
devices by calling this routine multiple times but a given device can
|
|
be bound to only a single task at any point in time. The registering
|
|
task specifies the device it wishes to use, the associated IRQ and
|
|
priority level for the device's interrupt. It gets the assigned
|
|
interrupt vector in return. The interrupt associated with the device is
|
|
enabled once the task has registered to use a device. Whenever the
|
|
device generates an interrupt, the kernel automatically runs an ISR
|
|
that disables the interrupt and records its occurrence.
|
|
|
|
The task associated with the device can use :c:func:`taskDevIntTest()`
|
|
to determine if the device's interrupt has occurred. Alternatively, it
|
|
can use :c:func:`task_irq_test_wait()` or
|
|
:c:func:`task_irq_test_wait_timeout()` to wait until an interrupt is
|
|
detected.
|
|
|
|
After the task took the appropriate action to service an interrupt
|
|
generated by the device, it calls :c:func:`task_irq_ack()` to re-enable
|
|
the device's interrupt. The task can call :c:func:`task_irq_free()` to
|
|
unbind itself from a device that it no longer wishes to use. If the
|
|
registered device needs change its priority level, it must first
|
|
unregister and then register again with the new priority. To provide
|
|
security against device misuse, a device should only be tested,
|
|
acknowledged, and deregistered by a task if that task registered the
|
|
device. Restrict which task can register a given device or use the
|
|
device after registration, at the shim layer. |