kernel/services/interrupts.html · kernel/services/interrupts.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Services

Interrupt

Hardware·software interrupt를 낮은 latency로 처리하고 nesting, locking, offload, sharing과 architecture별 vector 배치를 제어합니다.

Official pathkernel/services/interrupts.html
Source filekernel/services/interrupts.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

IRQ identity·priority·nesting부터 zero-latency와 direct ISR, build-time .intList parser, x86 IDT와 multi-level bit encoding까지 설명합니다.

Context

nested ISR stack

Latency

regular · direct · zero

Tables

vector · software ISR

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 A5E1DA9EC7407B07E6E567E081DF0E2C6132A83DE88A8AC079B38AD4C9099D61
.. _interrupts_v2:

Interrupts
##########

An :dfn:`interrupt service routine` (ISR) is a function that executes
asynchronously in response to a hardware or software interrupt.
An ISR normally preempts the execution of the current thread,
allowing the response to occur with very low overhead.
Thread execution resumes only once all ISR work has been completed.

.. contents::
    :local:
    :depth: 2

Concepts
********

Any number of ISRs can be defined (limited only by available RAM), subject to
the constraints imposed by underlying hardware.

An ISR has the following key properties:

* An **interrupt request (IRQ) signal** that triggers the ISR.
* A **priority level** associated with the IRQ.
* An **interrupt service routine** that is invoked to handle the interrupt.
* An **argument value** that is passed to that function.

An :abbr:`IDT (Interrupt Descriptor Table)` or a vector table is used
to associate a given interrupt source with a given ISR.
Only a single ISR can be associated with a specific IRQ at any given time.

Multiple ISRs can utilize the same function to process interrupts,
allowing a single function to service a device that generates
multiple types of interrupts or to service multiple devices
(usually of the same type). The argument value passed to an ISR's function
allows the function to determine which interrupt has been signaled.

The kernel provides a default ISR for all unused IDT entries. This ISR
generates a fatal system error if an unexpected interrupt is signaled.

The kernel supports **interrupt nesting**. This allows an ISR to be preempted
in mid-execution if a higher priority interrupt is signaled. The lower
priority ISR resumes execution once the higher priority ISR has completed
its processing.

An ISR executes in the kernel's **interrupt context**. This context has its
own dedicated stack area (or, on some architectures, stack areas). The size
of the interrupt context stack must be capable of handling the execution of
multiple concurrent ISRs if interrupt
nesting support is enabled.

.. important::
    Many kernel APIs can be used only by threads, and not by ISRs. In cases
    where a routine may be invoked by both threads and ISRs the kernel
    provides the :c:func:`k_is_in_isr` API to allow the routine to
    alter its behavior depending on whether it is executing as part of
    a thread or as part of an ISR.

.. _multi_level_interrupts:

Multi-level Interrupt Handling
==============================

A hardware platform can support more interrupt lines than natively-provided
through the use of one or more nested interrupt controllers.  Sources of
hardware interrupts are combined into one line that is then routed to
the parent controller.

If nested interrupt controllers are supported, :kconfig:option:`CONFIG_MULTI_LEVEL_INTERRUPTS`
should be enabled, and :kconfig:option:`CONFIG_2ND_LEVEL_INTERRUPTS` and
:kconfig:option:`CONFIG_3RD_LEVEL_INTERRUPTS` configured as well, based on the
hardware architecture.

A unique 32-bit interrupt number is assigned with information
embedded in it to select and invoke the correct Interrupt
Service Routine (ISR). Each interrupt level is given a byte within this 32-bit
number, providing support for up to four interrupt levels using this arch, as
illustrated and explained below:

.. code-block:: none

                 9             2   0
           _ _ _ _ _ _ _ _ _ _ _ _ _         (LEVEL 1)
         5       |         A   |
       _ _ _ _ _ _ _         _ _ _ _ _ _ _   (LEVEL 2)
         |   C                       B
       _ _ _ _ _ _ _                         (LEVEL 3)
               D

There are three interrupt levels shown here.

* '-' means interrupt line and is numbered from 0 (right most).
* LEVEL 1 has 12 interrupt lines, with two lines (2 and 9) connected
  to nested controllers and one device 'A' on line 4.
* One of the LEVEL 2 controllers has interrupt line 5 connected to
  a LEVEL 3 nested controller and one device 'C' on line 3.
* The other LEVEL 2 controller has no nested controllers but has one
  device 'B' on line 2.
* The LEVEL 3 controller has one device 'D' on line 2.

Here's how unique interrupt numbers are generated for each
hardware interrupt.  Let's consider four interrupts shown above
as A, B, C, and D:

.. code-block:: none

   A -> 0x00000004
   B -> 0x00000302
   C -> 0x00000409
   D -> 0x00030609

.. note::
   The bit positions for LEVEL 2 and onward are offset by 1, as 0 means that
   interrupt number is not present for that level. For our example, the LEVEL 3
   controller has device D on line 2, connected to the LEVEL 2 controller's line
   5, that is connected to the LEVEL 1 controller's line 9 (2 -> 5 -> 9).
   Because of the encoding offset for LEVEL 2 and onward, device D is given the
   number 0x00030609.

Preventing Interruptions
========================

In certain situations it may be necessary for the current thread to
prevent ISRs from executing while it is performing time-sensitive
or critical section operations.

A thread may temporarily prevent all IRQ handling in the system using
an **IRQ lock**. This lock can be applied even when it is already in effect,
so routines can use it without having to know if it is already in effect.
The thread must unlock its IRQ lock the same number of times it was locked
before interrupts can be once again processed by the kernel while the thread
is running.

.. important::
    The IRQ lock is thread-specific. If thread A locks out interrupts
    then performs an operation that puts itself to sleep (e.g. sleeping
    for N milliseconds), the thread's IRQ lock no longer applies once
    thread A is swapped out and the next ready thread B starts to
    run.

    This means that interrupts can be processed while thread B is
    running unless thread B has also locked out interrupts using its own
    IRQ lock.  (Whether interrupts can be processed while the kernel is
    switching between two threads that are using the IRQ lock is
    architecture-specific.)

    When thread A eventually becomes the current thread once again, the kernel
    re-establishes thread A's IRQ lock. This ensures thread A won't be
    interrupted until it has explicitly unlocked its IRQ lock.

    If thread A does not sleep but does make a higher-priority thread B
    ready, the IRQ lock will inhibit any preemption that would otherwise
    occur.  Thread B will not run until the next :ref:`reschedule point
    <scheduling_v2>` reached after releasing the IRQ lock.

Alternatively, a thread may temporarily **disable** a specified IRQ
so its associated ISR does not execute when the IRQ is signaled.
The IRQ must be subsequently **enabled** to permit the ISR to execute.

.. important::
    Disabling an IRQ prevents *all* threads in the system from being preempted
    by the associated ISR, not just the thread that disabled the IRQ.

Zero Latency Interrupts
-----------------------

Preventing interruptions by applying an IRQ lock may increase the observed
interrupt latency. A high interrupt latency, however, may not be acceptable
for certain low-latency use-cases.

The kernel addresses such use-cases by allowing interrupts with critical
latency constraints to execute at a priority level that cannot be blocked
by interrupt locking. These interrupts are defined as
*zero-latency interrupts*. The support for zero-latency interrupts requires
:kconfig:option:`CONFIG_ZERO_LATENCY_IRQS` to be enabled. In addition to that, the
flag :c:macro:`IRQ_ZERO_LATENCY` must be passed to :c:macro:`IRQ_CONNECT` or
:c:macro:`IRQ_DIRECT_CONNECT` macros to configure the particular interrupt
with zero latency.

Zero-latency interrupts are expected to be used to manage hardware events
directly, and not to interoperate with the kernel code at all. They should
treat all kernel APIs as undefined behavior (i.e. an application that uses the
APIs inside a zero-latency interrupt context is responsible for directly
verifying correct behavior). Zero-latency interrupts may not modify any data
inspected by kernel APIs invoked from normal Zephyr contexts and shall not
generate exceptions that need to be handled synchronously (e.g. kernel panic).

.. important::
    Zero-latency interrupts are supported on an architecture-specific basis.
    The feature is currently implemented in the ARM Cortex-M architecture
    variant.

Offloading ISR Work
===================

An ISR should execute quickly to ensure predictable system operation.
If time consuming processing is required the ISR should offload some or all
processing to a thread, thereby restoring the kernel's ability to respond
to other interrupts.

The kernel supports several mechanisms for offloading interrupt-related
processing to a thread.

* An ISR can signal a helper thread to do interrupt-related processing
  using a kernel object, such as a FIFO, LIFO, or semaphore.

* An ISR can instruct the system workqueue thread to execute a work item.
  (See :ref:`workqueues_v2`.)

When an ISR offloads work to a thread, there is typically a single context
switch to that thread when the ISR completes, allowing interrupt-related
processing to continue almost immediately. However, depending on the
priority of the thread handling the offload, it is possible that
the currently executing cooperative thread or other higher-priority threads
may execute before the thread handling the offload is scheduled.

Sharing interrupt lines
=======================

In the case of some hardware platforms, the same interrupt lines may be used
by different IPs. For example, interrupt 17 may be used by a DMA controller to
signal that a data transfer has been completed or by a DAI controller to signal
that the transfer FIFO has reached its watermark. To make this work, one would
have to either employ some special logic or find a workaround (for example, using
the shared_irq interrupt controller), which doesn't scale very well.

To solve this problem, one may use shared interrupts, which can be enabled using
:kconfig:option:`CONFIG_SHARED_INTERRUPTS`. Whenever an attempt to register
a second ISR/argument pair on the same interrupt line is made (using
:c:macro:`IRQ_CONNECT` or :c:func:`irq_connect_dynamic`), the interrupt line will
become shared, meaning the two ISR/argument pairs (previous one and the one that
has just been registered) will be invoked each time the interrupt is triggered.
The entities that make use of an interrupt line in the shared interrupt context
are known as clients. The maximum number of allowed clients for an interrupt is
controlled by :kconfig:option:`CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS`.

Interrupt sharing is transparent to the user. As such, the user may register
interrupts using :c:macro:`IRQ_CONNECT` and :c:func:`irq_connect_dynamic` as
they normally would. The interrupt sharing is taken care of behind the scenes.

Enabling the shared interrupt support and dynamic interrupt support will
allow users to dynamically disconnect ISRs using :c:func:`irq_disconnect_dynamic`.
After an ISR is disconnected, whenever the interrupt line for which it was
register gets triggered, the ISR will no longer get invoked.

Please note that enabling :kconfig:option:`CONFIG_SHARED_INTERRUPTS` will
result in a non-negligible increase in the binary size. Use with caution.

Implementation
**************

Defining a regular ISR
======================

An ISR is defined at runtime by calling :c:macro:`IRQ_CONNECT`. It must
then be enabled by calling :c:func:`irq_enable`.

.. important::
    IRQ_CONNECT() is not a C function and does some inline assembly magic
    behind the scenes. All its arguments must be known at build time.
    Drivers that have multiple instances may need to define per-instance
    config functions to configure each instance of the interrupt.

The following code defines and enables an ISR.

.. code-block:: c

    #define MY_DEV_IRQ  24       /* device uses IRQ 24 */
    #define MY_DEV_PRIO  2       /* device uses interrupt priority 2 */
    /* argument passed to my_isr(), in this case a pointer to the device */
    #define MY_ISR_ARG  DEVICE_GET(my_device)
    #define MY_IRQ_FLAGS 0       /* IRQ flags */

    void my_isr(void *arg)
    {
       ... /* ISR code */
    }

    void my_isr_installer(void)
    {
       ...
       IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS);
       irq_enable(MY_DEV_IRQ);
       ...
    }

Since the :c:macro:`IRQ_CONNECT` macro requires that all its parameters be
known at build time, in some cases this may not be acceptable. It is also
possible to install interrupts at runtime with
:c:func:`irq_connect_dynamic`. It is used in exactly the same way as
:c:macro:`IRQ_CONNECT`:

.. code-block:: c

    void my_isr_installer(void)
    {
       ...
       irq_connect_dynamic(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG,
                           MY_IRQ_FLAGS);
       irq_enable(MY_DEV_IRQ);
       ...
    }

Dynamic interrupts require the :kconfig:option:`CONFIG_DYNAMIC_INTERRUPTS` option to
be enabled. Removing or re-configuring a dynamic interrupt is currently
unsupported.

Defining a 'direct' ISR
=======================

Regular Zephyr interrupts introduce some overhead which may be unacceptable
for some low-latency use-cases. Specifically:

* The argument to the ISR is retrieved and passed to the ISR

* If power management is enabled and the system was idle, all the hardware
  will be resumed from low-power state before the ISR is executed, which can be
  very time-consuming

* Although some architectures will do this in hardware, other architectures
  need to switch to the interrupt stack in code

* After the interrupt is serviced, the OS then performs some logic to
  potentially make a scheduling decision.

Zephyr supports so-called 'direct' interrupts, which are installed via
:c:macro:`IRQ_DIRECT_CONNECT`. These direct interrupts have some special
implementation requirements and a reduced feature set; see the definition
of :c:macro:`IRQ_DIRECT_CONNECT` for details.

The following code demonstrates a direct ISR:

.. code-block:: c

    #define MY_DEV_IRQ  24       /* device uses IRQ 24 */
    #define MY_DEV_PRIO  2       /* device uses interrupt priority 2 */
    /* argument passed to my_isr(), in this case a pointer to the device */
    #define MY_IRQ_FLAGS 0       /* IRQ flags */

    ISR_DIRECT_DECLARE(my_isr)
    {
       do_stuff();
       ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency */
       return 1; /* We should check if scheduling decision should be made */
    }

    void my_isr_installer(void)
    {
       ...
       IRQ_DIRECT_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_IRQ_FLAGS);
       irq_enable(MY_DEV_IRQ);
       ...
    }

Installation of dynamic direct interrupts is supported on an
architecture-specific basis. (The feature is currently implemented in
ARM Cortex-M architecture variant. Dynamic direct interrupts feature is
exposed to the user via an ARM-only API.)

Sharing an interrupt line
=========================

The following code defines two ISRs using the same interrupt number.

.. code-block:: c

    #define MY_DEV_IRQ 24		/* device uses INTID 24 */
    #define MY_DEV_IRQ_PRIO 2		/* device uses interrupt priority 2 */
    /*  this argument may be anything */
    #define MY_FST_ISR_ARG INT_TO_POINTER(1)
    /*  this argument may be anything */
    #define MY_SND_ISR_ARG INT_TO_POINTER(2)
    #define MY_IRQ_FLAGS 0		/* IRQ flags */

    void my_first_isr(void *arg)
    {
       ... /* some magic happens here */
    }

    void my_second_isr(void *arg)
    {
       ... /* even more magic happens here */
    }

    void my_isr_installer(void)
    {
       ...
       IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
       IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
       ...
    }

The same restrictions regarding :c:macro:`IRQ_CONNECT` described in `Defining a regular ISR`_
are applicable here. If :kconfig:option:`CONFIG_SHARED_INTERRUPTS` is disabled, the above
code will generate a build error. Otherwise, the above code will result in the two ISRs
being invoked each time interrupt 24 is triggered.

If :kconfig:option:`CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS` is set to a value lower than 2
(current number of clients), a build error will be generated.

If dynamic interrupts are enabled, :c:func:`irq_connect_dynamic` will allow sharing interrupts
during runtime. Exceeding the configured maximum number of allowed clients will result in
a failed assertion.

Dynamically disconnecting an ISR
================================

The following code defines two ISRs using the same interrupt number. The second
ISR is disconnected during runtime.

.. code-block:: c

    #define MY_DEV_IRQ 24		/* device uses INTID 24 */
    #define MY_DEV_IRQ_PRIO 2		/* device uses interrupt priority 2 */
    /*  this argument may be anything */
    #define MY_FST_ISR_ARG INT_TO_POINTER(1)
    /*  this argument may be anything */
    #define MY_SND_ISR_ARG INT_TO_POINTER(2)
    #define MY_IRQ_FLAGS 0		/* IRQ flags */

    void my_first_isr(void *arg)
    {
       ... /* some magic happens here */
    }

    void my_second_isr(void *arg)
    {
       ... /* even more magic happens here */
    }

    void my_isr_installer(void)
    {
       ...
       IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
       IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
       ...
    }

    void my_isr_uninstaller(void)
    {
       ...
       irq_disconnect_dynamic(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
       ...
    }

The :c:func:`irq_disconnect_dynamic` call will result in interrupt 24 becoming
unshared, meaning the system will act as if the first :c:macro:`IRQ_CONNECT`
call never happened. This behaviour is only allowed if
:kconfig:option:`CONFIG_DYNAMIC_INTERRUPTS` is enabled, otherwise a linker
error will be generated.

Implementation Details
======================

Interrupt tables are set up at build time using some special build tools.  The
details laid out here apply to all architectures except x86, which are
covered in the `x86 Details`_ section below.

The invocation of :c:macro:`IRQ_CONNECT` will declare an instance of
struct _isr_list which is placed in a special .intList section.
This section is placed in compiled code on precompilation stages only.
It is meant to be used by Zephyr script to generate interrupt tables
and is removed from the final build.
The script implements different parsers to process the data from .intList section
and produce the required output.

The default parser generates C arrays filled with arguments and interrupt
handlers in a form of addresses directly taken from .intList section entries.
It works with all the architectures and compilers (with the exception mentioned above).
The limitation of this parser is the fact that after the arrays are generated
it is expected for the code not to relocate.
Any relocation on this stage may lead to the situation where the entry in the interrupt array
is no longer pointing to the function that was expected.
It means that this parser, being more compatible is limiting us from using Link Time Optimization.

The local isr declaration parser uses different approach to construct
the same arrays at binnary level.
All the entries to the arrays are declared and defined locally,
directly in the file where :c:macro:`IRQ_CONNECT` is used.
They are placed in a section with the unique, synthesized name.
The name of the section is then placed in .intList section and it is used to create linker script
to properly place the created entry in the right place in the memory.
This parser is now limited to the supported architectures and toolchains but in reward it keeps
the information about object relations for linker thus allowing the Link Time Optimization.

Implementation using C arrays
-----------------------------

This is the default configuration available for all Zephyr supported architectures.

Any invocation of :c:macro:`IRQ_CONNECT` will declare an instance of
struct _isr_list which is placed in a special .intList section:

.. code-block:: c

    struct _isr_list {
        /** IRQ line number */
        int32_t irq;
        /** Flags for this IRQ, see ISR_FLAG_* definitions */
        int32_t flags;
        /** ISR to call */
        void *func;
        /** Parameter for non-direct IRQs */
        void *param;
    };

Zephyr is built in two phases; the first phase of the build produces
``${ZEPHYR_PREBUILT_EXECUTABLE}``.elf which contains all the entries in
the .intList section preceded by a header:

.. code-block:: c

    struct {
        void *spurious_irq_handler;
        void *sw_irq_handler;
        uint32_t num_isrs;
        uint32_t num_vectors;
        struct _isr_list isrs[];  <- of size num_isrs
    };

This data consisting of the header and instances of struct _isr_list inside
``${ZEPHYR_PREBUILT_EXECUTABLE}``.elf is then used by the
gen_isr_tables.py script to generate a C file defining a vector table and
software ISR table that are then compiled and linked into the final
application.

The priority level of any interrupt is not encoded in these tables, instead
:c:macro:`IRQ_CONNECT` also has a runtime component which programs the desired
priority level of the interrupt to the interrupt controller. Some architectures
do not support the notion of interrupt priority, in which case the priority
argument is ignored.

Vector Table
~~~~~~~~~~~~
A vector table is generated when :kconfig:option:`CONFIG_GEN_IRQ_VECTOR_TABLE` is
enabled.  This data structure is used natively by the CPU and is simply an
array of function pointers, where each element n corresponds to the IRQ handler
for IRQ line n, and the function pointers are:

#. For 'direct' interrupts declared with :c:macro:`IRQ_DIRECT_CONNECT`, the
   handler function will be placed here.
#. For regular interrupts declared with :c:macro:`IRQ_CONNECT`, the address
   of the common software IRQ handler is placed here. This code does common
   kernel interrupt bookkeeping and looks up the ISR and parameter from the
   software ISR table.
#. For interrupt lines that are not configured at all, the address of the
   spurious IRQ handler will be placed here. The spurious IRQ handler
   causes a system fatal error if encountered.

Some architectures (such as the Nios II internal interrupt controller) have a
common entry point for all interrupts and do not support a vector table, in
which case the :kconfig:option:`CONFIG_GEN_IRQ_VECTOR_TABLE` option should be
disabled.

Some architectures may reserve some initial vectors for system exceptions
and declare this in a table elsewhere, in which case
CONFIG_GEN_IRQ_START_VECTOR needs to be set to properly offset the indices
in the table.

SW ISR Table
~~~~~~~~~~~~
This is an array of struct _isr_table_entry:

.. code-block:: c

    struct _isr_table_entry {
        void *arg;
        void (*isr)(void *);
    };

This is used by the common software IRQ handler to look up the ISR and its
argument and execute it. The active IRQ line is looked up in an interrupt
controller register and used to index this table.

Shared SW ISR Table
~~~~~~~~~~~~~~~~~~~

This is an array of struct z_shared_isr_table_entry:

.. code-block:: c

    struct z_shared_isr_table_entry {
        struct _isr_table_entry clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS];
        size_t client_num;
    };

This table keeps track of the registered clients for each of the interrupt
lines. Whenever an interrupt line becomes shared, :c:func:`z_shared_isr` will
replace the currently registered ISR in _sw_isr_table. This special ISR will
iterate through the list of registered clients and invoke the ISRs.

Implementation using linker script
----------------------------------

This way of prepare and parse .isrList section to implement interrupt vectors arrays
is called local isr declaration.
The name comes from the fact that all the entries to the arrays that would create
interrupt vectors are created locally in place of invocation of :c:macro:`IRQ_CONNECT` macro.
Then automatically generated linker scripts are used to place it in the right place in the memory.

This option requires enabling by the choose of :kconfig:option:`ISR_TABLES_LOCAL_DECLARATION`.
If this configuration is supported by the used architecture and toolchaing the
:kconfig:option:`ISR_TABLES_LOCAL_DECLARATION_SUPPORTED` is set.
See details of this option for the information about currently supported configurations.

Any invocation of :c:macro:`IRQ_CONNECT` or `IRQ_DIRECT_CONNECT` will declare an instance of struct
_isr_list_sname which is placde in a special .intList section:

.. code-block:: c

    struct _isr_list_sname {
        /** IRQ line number */
        int32_t irq;
        /** Flags for this IRQ, see ISR_FLAG_* definitions */
        int32_t flags;
        /** The section name */
        const char sname[];
    };

Note that the section name is placed in flexible array member.
It means that the size of the initialized structure will warry depending on the
structure name length.
The whole entry is used by the script during the build of the application
and has all the information needed for proper interrupt placement.

Beside of the _isr_list_sname the :c:macro:`IRQ_CONNECT` macro generates an entry
that would be the part of the interrupt array:

.. code-block:: c

    struct _isr_table_entry {
        const void *arg;
        void (*isr)(const void *);
    };

This array is placed in a section with the name saved in _isr_list_sname structure.

The values created by :c:macro:`IRQ_DIRECT_CONNECT` macro depends on the architecture.
It can be changed to variable that points to a interrupt handler:

.. code-block:: c

    static uintptr_t <unique name> = ((uintptr_t)func);

Or to actually naked function that implements a jump to the interrupt handler:

.. code-block:: c

    static void <unique name>(void)
    {
        __asm(ARCH_IRQ_VECTOR_JUMP_CODE(func));
    }

Similar like for :c:macro:`IRQ_CONNECT`, the created variable or function is placed
in a section, saved in _isr_list_sname section.

Files generated by the script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The interrupt tables generator script creates 3 files:
isr_tables.c, isr_tables_swi.ld, and isr_tables_vt.ld.

The isr_tables.c will contain all the structures for interrupts, direct interrupts and
shared interrupts (if enabled). This file implements only all the structures that
are not implemented by the application, leaving a comment where the interrupt
not implemented here can be found.

Then two linker files are used. The isr_tables_vt.ld file is included in place
where the interrupt vectors are required to be placed in the selected architecture.
The isr_tables_swi.ld file describes the placement of the software interrupt table
elements. The separated file is required as it might be placed in writable on nonwritable
section, depending on the current configuration.

x86 Details
-----------

The x86 architecture has a special type of vector table called the Interrupt
Descriptor Table (IDT) which must be laid out in a certain way per the x86
processor documentation.  It is still fundamentally a vector table, and the
:ref:`gen_idt.py` tool uses the .intList section to create it. However, on APIC-based
systems the indexes in the vector table do not correspond to the IRQ line. The
first 32 vectors are reserved for CPU exceptions, and all remaining vectors (up
to index 255) correspond to the priority level, in groups of 16. In this
scheme, interrupts of priority level 0 will be placed in vectors 32-47, level 1
48-63, and so forth. When the :ref:`gen_idt.py` tool is constructing the IDT, when it
configures an interrupt it will look for a free vector in the appropriate range
for the requested priority level and set the handler there.

On x86 when an interrupt or exception vector is executed by the CPU, there is
no foolproof way to determine which vector was fired, so a software ISR table
indexed by IRQ line is not used. Instead, the :c:macro:`IRQ_CONNECT` call
creates a small assembly language function which calls the common interrupt
code in :c:func:`_interrupt_enter` with the ISR and parameter as arguments.
It is the address of this assembly interrupt stub which gets placed in the IDT.
For interrupts declared with :c:macro:`IRQ_DIRECT_CONNECT` the parameterless
ISR is placed directly in the IDT.

On systems where the position in the vector table corresponds to the
interrupt's priority level, the interrupt controller needs to know at
runtime what vector is associated with an IRQ line. :ref:`gen_idt.py` additionally
creates an _irq_to_interrupt_vector array which maps an IRQ line to its
configured vector in the IDT. This is used at runtime by :c:macro:`IRQ_CONNECT`
to program the IRQ-to-vector association in the interrupt controller.

For dynamic interrupts, the build must generate some 4-byte dynamic interrupt
stubs, one stub per dynamic interrupt in use. The number of stubs is controlled
by the :kconfig:option:`CONFIG_X86_DYNAMIC_IRQ_STUBS` option. Each stub pushes an
unique identifier which is then used to fetch the appropriate handler function
and parameter out of a table populated when the dynamic interrupt was
connected.

Going Beyond the Default Supported Number of Interrupts
-------------------------------------------------------

When generating interrupts in the multi-level configuration, 8-bits per level is the default
mask used when determining which level a given interrupt code belongs to. This can become
a problem when dealing with CPUs that support more than 255 interrupts per single
aggregator. In this case it may be desirable to override these defaults and use a custom
number of bits per level. Regardless of how many bits used for each level, the sum of
the total bits used between all levels must sum to be less than or equal to 32-bits,
fitting into a single 32-bit integer. To modify the bit total per level, override the
default 8 in `Kconfig.multilevel` by setting :kconfig:option:`CONFIG_1ST_LEVEL_INTERRUPT_BITS`
for the  first level, :kconfig:option:`CONFIG_2ND_LEVEL_INTERRUPT_BITS` for the second level and
:kconfig:option:`CONFIG_3RD_LEVEL_INTERRUPT_BITS` for the third level. These masks control the
length of the bit masks and shift to apply when generating interrupt values, when checking the
interrupts level and converting interrupts to a different level. The logic controlling
this can be found in :file:`irq_multilevel.h`

Suggested Uses
**************

Use a regular or direct ISR to perform interrupt processing that requires a
very rapid response, and can be done quickly without blocking.

.. note::
    Interrupt processing that is time consuming, or involves blocking,
    should be handed off to a thread. See `Offloading ISR Work`_ for
    a description of various techniques that can be used in an application.

Configuration Options
*********************

Related configuration options:

* :kconfig:option:`CONFIG_ISR_STACK_SIZE`

Additional architecture-specific and device-specific configuration options
also exist.

API Reference
*************

.. doxygengroup:: isr_apis

Part 3

한국어 전문 번역

Interrupt service routine(ISR)은 hardware 또는 software interrupt에 응답해 비동기로 실행되는 함수입니다. 보통 현재 thread를 preempt해 매우 낮은 overhead로 응답하며, 모든 ISR 작업이 끝난 뒤에야 thread 실행이 재개됩니다.

개념

기반 hardware의 제약 안에서 RAM이 허용하는 만큼 ISR을 정의할 수 있습니다. ISR은 자신을 trigger하는 IRQ signal, IRQ priority, 호출될 service routine, 그 함수에 전달할 argument를 가집니다.

Interrupt Descriptor Table(IDT) 또는 vector table이 interrupt source와 ISR을 연결합니다. 특정 IRQ에는 한 시점에 ISR 하나만 연결할 수 있습니다.

여러 ISR이 같은 함수를 공유해 한 device의 여러 interrupt type이나 같은 종류의 여러 device를 처리할 수 있습니다. ISR argument로 어느 interrupt가 signal됐는지 구분합니다.

사용하지 않는 모든 IDT entry에는 kernel default ISR이 들어갑니다. 예상하지 못한 interrupt가 발생하면 fatal system error를 만듭니다.

Kernel은 interrupt nesting을 지원합니다. 더 높은 priority interrupt가 오면 실행 중인 낮은 priority ISR도 중간에 preempt되고, 높은 priority 처리가 끝난 뒤 이어서 실행합니다.

ISR은 전용 stack 영역을 가진 kernel interrupt context에서 실행합니다. Architecture에 따라 stack이 여러 개일 수 있으며, nesting을 켰다면 동시에 중첩될 수 있는 여러 ISR 실행량을 견디도록 stack 크기를 잡아야 합니다.

Multi-level interrupt 처리

Hardware가 native line보다 많은 interrupt source를 지원하려면 nested interrupt controller를 둘 수 있습니다. 여러 source를 한 line으로 합쳐 parent controller로 전달합니다.

Nested controller를 사용하면 CONFIG_MULTI_LEVEL_INTERRUPTS를 켜고 hardware 구조에 따라 CONFIG_2ND_LEVEL_INTERRUPTS와 CONFIG_3RD_LEVEL_INTERRUPTS도 구성합니다.

올바른 ISR을 선택하도록 level 정보를 담은 고유 32-bit interrupt number를 사용합니다. 각 level에 1 byte를 배정하므로 이 방식으로 최대 4 level을 표현할 수 있습니다.

          9             2   0
    _ _ _ _ _ _ _ _ _ _ _ _ _         (LEVEL 1)
  5       |         A   |
_ _ _ _ _ _ _         _ _ _ _ _ _ _   (LEVEL 2)
  |   C                       B
_ _ _ _ _ _ _                         (LEVEL 3)
        D
예제의 3-level interrupt controller 연결 관계
LEVEL 1 · 12 lines

line 4 → device A
line 2 → LEVEL 2 controller B
line 9 → LEVEL 2 controller C

LEVEL 2 · controller B

line 2 → device B
nested controller 없음

LEVEL 2 · controller C

line 3 → device C
line 5 → LEVEL 3 controller

LEVEL 3

line 2 → device D
경로 2 → 5 → 9

Encoded IRQ: A 0x00000004 · B 0x00000302 · C 0x00000409 · D 0x00030609

  • -는 오른쪽부터 0으로 번호를 매긴 interrupt line입니다.
  • LEVEL 1에는 line 12개가 있고 2와 9가 nested controller로, line 4가 device A로 이어집니다.
  • LEVEL 2 controller 하나는 line 5가 LEVEL 3으로, line 3이 device C로 이어집니다.
  • 다른 LEVEL 2 controller에는 nested controller가 없고 line 2에 device B가 있습니다.
  • LEVEL 3 controller의 line 2에는 device D가 있습니다.

A, B, C, D의 고유 interrupt number는 다음과 같습니다.

A -> 0x00000004
B -> 0x00000302
C -> 0x00000409
D -> 0x00030609

Interrupt 차단

시간 민감 작업이나 critical section 동안 현재 thread가 ISR 실행을 막아야 할 수 있습니다.

Thread는 IRQ lock으로 system의 모든 IRQ 처리를 잠시 막을 수 있습니다. 이미 lock 상태에서도 다시 적용할 수 있으므로 routine은 기존 상태를 몰라도 됩니다. Lock한 횟수와 같은 횟수만큼 unlock해야 현재 thread가 실행 중일 때 kernel이 interrupt를 다시 처리합니다.

또는 특정 IRQ 하나를 disable해 signal이 와도 연결 ISR이 실행되지 않게 하고, 나중에 enable할 수 있습니다.

Zero-latency interrupt

IRQ lock은 관측 interrupt latency를 늘릴 수 있습니다. 매우 낮은 latency가 필요한 경우 lock으로 막을 수 없는 priority에서 실행되는 zero-latency interrupt를 사용합니다. CONFIG_ZERO_LATENCY_IRQS를 켜고 IRQ_CONNECT 또는 IRQ_DIRECT_CONNECT에 IRQ_ZERO_LATENCY flag를 전달합니다.

ISR 작업 offload

예측 가능한 system 동작을 위해 ISR은 빨리 끝나야 합니다. 오래 걸리는 처리는 thread에 넘겨 kernel이 다른 interrupt에 응답할 수 있게 합니다.

  • FIFO, LIFO, semaphore 같은 kernel object로 helper thread에 작업을 알립니다.
  • System workqueue thread가 work item을 실행하게 합니다.

보통 ISR이 끝날 때 helper thread로 context switch 한 번만 하면 interrupt 후속 처리를 거의 즉시 이어갈 수 있습니다. 다만 offload thread priority에 따라 현재 cooperative thread나 더 높은 priority thread가 먼저 실행될 수 있습니다.

Interrupt line 공유

일부 platform에서는 같은 interrupt line을 여러 IP가 사용합니다. 예를 들어 line 17을 DMA transfer 완료와 DAI FIFO watermark가 함께 쓸 수 있습니다. 별도 workaround는 확장성이 낮으므로 shared interrupt를 사용할 수 있습니다.

CONFIG_SHARED_INTERRUPTS를 켜고 같은 line에 두 번째 ISR/argument pair를 IRQ_CONNECT 또는 irq_connect_dynamic으로 등록하면 line이 shared가 됩니다. 이후 trigger마다 기존 pair와 새 pair를 모두 호출합니다. Line을 이용하는 entity를 client라고 하며 최대 client 수는 CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS가 정합니다.

Sharing 처리는 내부에서 이루어지므로 user는 평소와 같은 API로 등록합니다. Shared와 dynamic interrupt 지원을 함께 켜면 irq_disconnect_dynamic으로 ISR을 동적으로 제거할 수 있고, 제거 후에는 그 line trigger에 호출되지 않습니다.

구현

일반 ISR 정의

IRQ_CONNECT로 ISR을 정의하고 irq_enable()로 활성화합니다.

#define MY_DEV_IRQ  24       /* device uses IRQ 24 */
#define MY_DEV_PRIO  2       /* device uses interrupt priority 2 */
/* argument passed to my_isr(), in this case a pointer to the device */
#define MY_ISR_ARG  DEVICE_GET(my_device)
#define MY_IRQ_FLAGS 0       /* IRQ flags */

void my_isr(void *arg)
{
   ... /* ISR code */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS);
   irq_enable(MY_DEV_IRQ);
   ...
}

Build time에 모든 parameter를 알 수 없다면 irq_connect_dynamic으로 runtime에 설치합니다.

void my_isr_installer(void)
{
   ...
   irq_connect_dynamic(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG,
                       MY_IRQ_FLAGS);
   irq_enable(MY_DEV_IRQ);
   ...
}

Dynamic interrupt에는 CONFIG_DYNAMIC_INTERRUPTS가 필요합니다. 이 원문 절은 dynamic interrupt 제거·재구성을 지원하지 않는다고 설명하지만 뒤 shared interrupt 절은 shared client를 irq_disconnect_dynamic()으로 제거하는 조건을 별도로 설명합니다.

Direct ISR 정의

Regular interrupt에는 argument 회수·전달, idle 상태의 hardware resume, software interrupt-stack switch, 처리 후 scheduling 판단 때문에 overhead가 생길 수 있습니다.

IRQ_DIRECT_CONNECT로 설치하는 direct interrupt는 common path를 줄인 대신 특수 구현 조건과 축소된 feature set을 가집니다.

#define MY_DEV_IRQ  24       /* device uses IRQ 24 */
#define MY_DEV_PRIO  2       /* device uses interrupt priority 2 */
/* argument passed to my_isr(), in this case a pointer to the device */
#define MY_IRQ_FLAGS 0       /* IRQ flags */

ISR_DIRECT_DECLARE(my_isr)
{
   do_stuff();
   ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency */
   return 1; /* We should check if scheduling decision should be made */
}

void my_isr_installer(void)
{
   ...
   IRQ_DIRECT_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_IRQ_FLAGS);
   irq_enable(MY_DEV_IRQ);
   ...
}

Dynamic direct interrupt는 architecture-specific이며 현재 ARM Cortex-M에서 ARM 전용 API로 노출됩니다.

Interrupt line 공유 구현

다음 code는 같은 interrupt number에 ISR 두 개를 정의합니다.

#define MY_DEV_IRQ 24               /* device uses INTID 24 */
#define MY_DEV_IRQ_PRIO 2           /* device uses interrupt priority 2 */
/*  this argument may be anything */
#define MY_FST_ISR_ARG INT_TO_POINTER(1)
/*  this argument may be anything */
#define MY_SND_ISR_ARG INT_TO_POINTER(2)
#define MY_IRQ_FLAGS 0              /* IRQ flags */

void my_first_isr(void *arg)
{
   ... /* some magic happens here */
}

void my_second_isr(void *arg)
{
   ... /* even more magic happens here */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

IRQ_CONNECT의 build-time 제약은 그대로 적용됩니다. CONFIG_SHARED_INTERRUPTS가 꺼져 있으면 build error이고, 켜져 있으면 interrupt 24마다 두 ISR을 모두 호출합니다. CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS가 2보다 작아도 build error입니다. Dynamic interrupt에서는 runtime sharing이 가능하지만 최대 client 수를 넘으면 assertion이 실패합니다.

ISR 동적 연결 해제

다음 code는 같은 line의 ISR 두 개를 정의한 뒤 첫 ISR을 runtime에 제거합니다.

#define MY_DEV_IRQ 24               /* device uses INTID 24 */
#define MY_DEV_IRQ_PRIO 2           /* device uses interrupt priority 2 */
/*  this argument may be anything */
#define MY_FST_ISR_ARG INT_TO_POINTER(1)
/*  this argument may be anything */
#define MY_SND_ISR_ARG INT_TO_POINTER(2)
#define MY_IRQ_FLAGS 0              /* IRQ flags */

void my_first_isr(void *arg)
{
   ... /* some magic happens here */
}

void my_second_isr(void *arg)
{
   ... /* even more magic happens here */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

void my_isr_uninstaller(void)
{
   ...
   irq_disconnect_dynamic(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

irq_disconnect_dynamic 호출 뒤 interrupt 24는 더 이상 shared가 아니며 첫 IRQ_CONNECT가 없었던 것처럼 동작합니다. CONFIG_DYNAMIC_INTERRUPTS가 꺼져 있으면 linker error가 발생합니다.

Interrupt table 구현 상세

Interrupt table은 특수 build tool로 build time에 구성합니다. 다음 설명은 별도 x86 절을 제외한 architecture에 적용됩니다.

IRQ_CONNECT는 struct _isr_list instance를 precompile 단계에서만 존재하는 특별한 .intList section에 선언합니다. Zephyr script가 이를 parse해 interrupt table을 만들고 final build에서는 section을 제거합니다.

Default parser는 .intList entry의 주소를 그대로 사용해 argument와 handler C array를 만듭니다. 대부분 architecture/compiler와 호환되지만 array 생성 뒤 code relocation이 일어나면 pointer가 틀릴 수 있어 Link Time Optimization 사용을 제한합니다.

Local ISR declaration parser는 IRQ_CONNECT를 쓴 file 안에서 array entry를 직접 선언하고 unique synthesized section에 둡니다. Section 이름을 .intList에 기록해 linker script가 올바른 memory 위치에 배치합니다. 지원 architecture/toolchain이 제한되지만 object 관계를 linker에 유지해 LTO를 허용합니다.

C array 구현

모든 Zephyr architecture가 사용할 수 있는 default 구성입니다. IRQ_CONNECT마다 다음 _isr_list가 .intList에 들어갑니다.

struct _isr_list {
    /** IRQ line number */
    int32_t irq;
    /** Flags for this IRQ, see ISR_FLAG_* definitions */
    int32_t flags;
    /** ISR to call */
    void *func;
    /** Parameter for non-direct IRQs */
    void *param;
};

Zephyr build 1단계가 만드는 ${ZEPHYR_PREBUILT_EXECUTABLE}.elf에는 header 뒤에 모든 _isr_list entry가 들어갑니다.

struct {
    void *spurious_irq_handler;
    void *sw_irq_handler;
    uint32_t num_isrs;
    uint32_t num_vectors;
    struct _isr_list isrs[];  <- of size num_isrs
};

gen_isr_tables.py가 이 header와 entry로 vector table과 software ISR table을 정의하는 C file을 만들고, final application에 compile·link합니다. Priority는 table에 encode하지 않고 IRQ_CONNECT runtime 부분이 controller에 설정합니다. Priority 개념이 없는 architecture에서는 인자를 무시합니다.

Vector table

CONFIG_GEN_IRQ_VECTOR_TABLE을 켜면 CPU가 native로 쓰는 function pointer array를 만듭니다. Index n은 IRQ line n에 대응합니다.

  1. IRQ_DIRECT_CONNECT로 선언한 direct ISR은 handler 자체를 둡니다.
  2. Regular IRQ_CONNECT는 common software IRQ handler 주소를 둡니다. 이 code가 kernel bookkeeping 뒤 software ISR table에서 실제 ISR과 parameter를 찾습니다.
  3. 구성하지 않은 line은 trigger 시 fatal error를 만드는 spurious IRQ handler를 둡니다.

Nios II internal controller처럼 모든 interrupt가 common entry를 쓰고 vector table을 지원하지 않는 architecture는 CONFIG_GEN_IRQ_VECTOR_TABLE을 꺼야 합니다. Initial vector 일부를 system exception용으로 다른 table에 예약하면 CONFIG_GEN_IRQ_START_VECTOR로 index offset을 지정합니다.

Software ISR table

Common software IRQ handler가 ISR과 argument를 찾는 _isr_table_entry array입니다.

struct _isr_table_entry {
    void *arg;
    void (*isr)(void *);
};

Interrupt controller register에서 active IRQ line을 읽고 이 table index로 사용해 실제 ISR을 실행합니다.

Shared software ISR table

각 interrupt line에 등록된 client를 추적하는 z_shared_isr_table_entry array입니다.

struct z_shared_isr_table_entry {
    struct _isr_table_entry clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS];
    size_t client_num;
};

Line이 shared가 되면 z_shared_isr가 _sw_isr_table의 기존 ISR을 대신하며 등록 client 목록을 순회해 각 ISR을 호출합니다.

Linker script 구현

Local ISR declaration 방식은 IRQ_CONNECT 호출 위치에 vector entry를 만들고 자동 linker script로 memory의 정확한 위치에 배치합니다. ISR_TABLES_LOCAL_DECLARATION으로 켜며 architecture/toolchain이 지원하면 ISR_TABLES_LOCAL_DECLARATION_SUPPORTED가 설정됩니다.

IRQ_CONNECT 또는 IRQ_DIRECT_CONNECT는 section 이름을 flexible array member로 담은 _isr_list_sname을 .intList에 만듭니다.

struct _isr_list_sname {
    /** IRQ line number */
    int32_t irq;
    /** Flags for this IRQ, see ISR_FLAG_* definitions */
    int32_t flags;
    /** The section name */
    const char sname[];
};

IRQ_CONNECT는 interrupt array가 될 다음 entry도 만들고, _isr_list_sname에 저장한 이름의 section에 배치합니다.

struct _isr_table_entry {
    const void *arg;
    void (*isr)(const void *);
};

IRQ_DIRECT_CONNECT가 만드는 값은 architecture에 따라 handler를 가리키는 variable이거나 handler로 jump하는 naked function일 수 있습니다.

static uintptr_t <unique name> = ((uintptr_t)func);
static void <unique name>(void)
{
    __asm(ARCH_IRQ_VECTOR_JUMP_CODE(func));
}

두 경우 모두 생성 variable/function은 _isr_list_sname에 기록한 section에 배치됩니다.

생성 파일

Interrupt table generator는 isr_tables.c, isr_tables_swi.ld, isr_tables_vt.ld 세 파일을 만듭니다.

isr_tables.c는 application이 직접 구현하지 않은 regular·direct·shared interrupt 구조체를 정의하고, 다른 곳에 구현된 interrupt 위치에는 comment를 남깁니다. isr_tables_vt.ld는 architecture가 vector를 요구하는 위치에 포함되고, isr_tables_swi.ld는 software ISR table element 배치를 기술합니다. 현재 구성에 따라 writable 또는 non-writable section에 둘 수 있어 linker file을 분리합니다.

x86 상세

x86 IDT는 processor 규격에 맞춘 특수 vector table이며 gen_idt.py가 .intList로 생성합니다. APIC system에서는 vector index가 IRQ line과 직접 대응하지 않습니다. 처음 32 vector는 CPU exception용이고, 32~255는 16개씩 priority group입니다. Priority 0은 vector 32~47, priority 1은 48~63을 사용하며 tool이 요청 priority 범위에서 빈 vector를 고릅니다.

x86 CPU가 interrupt/exception vector를 실행한 뒤 어느 vector였는지 완벽하게 알아낼 방법이 없으므로 IRQ line index의 software ISR table을 쓰지 않습니다. IRQ_CONNECT가 ISR과 parameter를 _interrupt_enter에 넘기는 작은 assembly stub을 만들고 그 주소를 IDT에 넣습니다. Direct interrupt는 parameter 없는 ISR을 IDT에 직접 넣습니다.

Vector 위치가 priority에 대응하는 system에서는 controller가 runtime에 IRQ와 vector 관계를 알아야 합니다. gen_idt.py가 _irq_to_interrupt_vector mapping array도 만들고 IRQ_CONNECT가 controller를 program할 때 사용합니다.

Dynamic interrupt에는 사용 interrupt마다 4-byte stub 하나가 필요하며 CONFIG_X86_DYNAMIC_IRQ_STUBS가 개수를 정합니다. 각 stub은 unique identifier를 push하고, dynamic connect 때 채운 table에서 handler와 parameter를 찾는 데 사용합니다.

기본 interrupt 수 초과

Multi-level encoding은 기본적으로 level당 8 bit라 한 aggregator에서 255개를 넘는 CPU에는 부족할 수 있습니다. Level별 bit 수를 바꿀 수 있지만 합계는 32-bit 이하여야 합니다. Kconfig.multilevel의 기본 8을 CONFIG_1ST_LEVEL_INTERRUPT_BITS, CONFIG_2ND_LEVEL_INTERRUPT_BITS, CONFIG_3RD_LEVEL_INTERRUPT_BITS로 override합니다. 이 mask들은 interrupt value 생성, level 판별, level 간 변환의 mask 길이와 shift를 제어하며 logic은 irq_multilevel.h에 있습니다.

권장 용도

매우 빠른 응답이 필요하고 block 없이 빨리 끝낼 수 있는 interrupt 처리에 regular 또는 direct ISR을 사용합니다.

설정 옵션

CONFIG_ISR_STACK_SIZE로 interrupt context stack 크기를 설정합니다. Architecture와 device별 추가 option도 있습니다.

API 참고: isr_apis

IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p)

IRQ handler를 초기화합니다. Handler가 실제 interrupt를 처리하기 전에 irq_enable()로 IRQ를 활성화해야 합니다. 호출 형태는 runtime이지만 모든 인자는 compiler가 build time에 계산할 수 있어야 합니다.

매개변수

  • irq_p: IRQ line 번호
  • priority_p: interrupt priority
  • isr_p: ISR 주소
  • isr_param_p: ISR에 전달할 parameter
  • flags_p: architecture-specific IRQ configuration flag
IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p)

Common interrupt handling code를 거치지 않는 성능 중심 direct ISR을 연결합니다. Parameter 전달, software stack switch, 자동 scheduling 판단과 PM idle exit가 생략되거나 선택적으로 바뀌므로 vector table에 직접 놓아도 안전하게 작성해야 합니다. 모든 인자는 build time constant여야 합니다.

매개변수

  • irq_p: IRQ line 번호
  • priority_p: interrupt priority
  • isr_p: direct ISR 주소
  • flags_p: architecture-specific flag
ISR_DIRECT_HEADER()

모든 direct ISR 시작에서 실행할 최소 architecture-specific 준비 작업을 수행합니다. 인자와 반환값이 없습니다.

ISR_DIRECT_FOOTER(check_reschedule)

Direct ISR 끝에서 EOI 같은 최소 architecture-specific 종료 작업을 수행합니다. check_reschedule이 non-zero이면 ready thread를 위한 scheduling logic도 확인합니다. Software가 stack switch와 nesting을 추적하는 system에서는 non-nested interrupt일 때만 z_swap()을 호출해야 합니다.

매개변수

  • check_reschedule: non-zero이면 scheduling logic 추가 실행
ISR_DIRECT_PM()

Direct ISR header와 footer 사이에서 선택적으로 PM idle exit logic을 수행합니다. 생략할 수 있지만 system resume가 필요한지 직접 판단해야 합니다.

ISR_DIRECT_DECLARE(name)

Direct ISR을 올바른 함수 형식으로 선언하고 HEADER와 FOOTER를 자동 포함합니다. Scheduling 판단이 필요할 수 있으면 non-zero를 반환합니다. Regular/fast interrupt 형식이 구분되는 architecture에서는 fast 형식 code를 생성합니다.

ISR_DIRECT_DECLARE(my_isr)
{
        bool done = do_stuff();
        ISR_DIRECT_PM(); // done after do_stuff() due to latency concerns
        if (!done) {
            return 0; // don't bother checking if we have to z_swap()
        }

        k_sem_give(some_sem);
        return 1;
 }

매개변수

  • name: ISR symbol 이름
irq_lock()

현재 CPU의 interrupt를 잠그고 이전 disable 상태를 나타내는 architecture-dependent key를 반환합니다. Recursive하게 호출할 수 있지만 얻은 key를 역순으로 irq_unlock()에 하나씩 전달해야 합니다. Supervisor mode 전용이며 memory barrier 역할도 합니다. Thread에서 호출하면 lock은 그 thread가 실행 중일 때만 적용됩니다.

반환값

  • lock-out key: 호출 전 interrupt disable 상태를 나타내는 값
irq_unlock(key)

연결된 irq_lock()의 효과를 되돌립니다. Lock 횟수만큼 역순 key로 호출해야 interrupt가 다시 활성화됩니다. Supervisor mode 전용이며 memory barrier 역할을 하고 ISR에서도 호출할 수 있습니다.

매개변수

  • key: irq_lock()이 반환한 lock-out key
irq_enable(irq)

지정 source의 IRQ를 활성화합니다.

매개변수

  • irq: IRQ line
irq_disable(irq)

지정 source의 IRQ를 비활성화합니다.

매개변수

  • irq: IRQ line
irq_is_enabled(irq)

지정 IRQ source의 활성화 상태를 반환합니다.

매개변수

  • irq: IRQ line

반환값

  • true/false: interrupt enable 상태
irq_connect_dynamic(irq, priority, routine, parameter, flags)

인자를 build time에 알 수 없을 때 dynamic interrupt를 구성합니다.

매개변수

  • irq: IRQ line
  • priority: interrupt priority
  • routine: ISR
  • parameter: ISR parameter
  • flags: architecture-specific flag

반환값

  • vector: 이 interrupt에 할당된 vector
irq_disconnect_dynamic(irq, priority, routine, parameter, flags)

Shared interrupt client 목록에서 routine/parameter pair를 제거합니다. Line이 shared가 아니면 관련 software ISR table entry를 기본 (NULL, z_irq_spurious)로 바꿉니다.

매개변수

  • irq: IRQ line
  • priority: interrupt priority
  • routine: ISR
  • parameter: ISR parameter
  • flags: architecture-specific flag

반환값

  • 0: 성공
  • negative: 실패
k_is_in_isr()

현재 code가 interrupt level에서 실행 중인지 판별해 thread와 ISR 경로를 구분할 수 있게 합니다. ISR에서도 호출할 수 있습니다.

반환값

  • false: thread context
  • true: ISR context
k_is_preempt_thread()

현재 code가 preemptible thread에서 실행 중인지 판별합니다. ISR이 아니고, priority가 preemptible range이며, scheduler를 잠그지 않은 세 조건을 모두 만족해야 non-zero입니다.

반환값

  • 0: ISR 또는 cooperative thread
  • non-zero: preemptible thread
k_is_pre_kernel()

Startup이 main task 전, 즉 kernel이 완전히 active해지기 전 단계인지 판별합니다. ISR에서도 호출할 수 있습니다.

반환값

  • true: post-kernel initialization 이전
  • false: post-kernel initialization 중 또는 이후

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.