==========
Interrupts
==========

`View slides <interrupts-slides.html>`_

.. slideconf::
   :autoslides: False
   :theme: single-level

Lecture objectives
==================

.. slide:: Interrupts
   :inline-contents: True
   :level: 2

   * Interrupts and exceptions (x86)

   * Interrupts and exceptions (Linux)

   * Deferrable work

   * Timers

What is an interrupt?
=====================

An interrupt is an event that alters the normal execution flow of a
program and can be generated by hardware devices or even by the CPU
itself. When an interrupt occurs the current flow of execution is
suspended and interrupt handler runs. After the interrupt handler runs
the previous execution flow is resumed.

Interrupts can be grouped into two categories based on the source of
the interrupt. They can also be grouped into two other categories based
on the ability to postpone or temporarily disable the interrupt:

.. slide:: Interrupts
   :inline-contents: True
   :level: 2

   * **synchronous**, generated by executing an instruction

   * **asynchronous**, generated by an external event

   * **maskable**

     * can be ignored

     * signaled via INT pin

   * **non-maskable**

     * cannot be ignored

     * signaled via NMI pin

Synchronous interrupts, usually named exceptions, handle conditions detected by the
processor itself in the course of executing an instruction. Divide by zero or
a system call are examples of exceptions.

Asynchronous interrupts, usually named interrupts, are external events generated
by I/O devices. For example a network card generates an interrupts to signal
that a packet has arrived.

Most interrupts are maskable, which means we can temporarily postpone
running the interrupt handler when we disable the interrupt until the
time the interrupt is re-enabled. However, there are a few critical
interrupts that can not be disabled/postponed.

Exceptions
----------

There are two sources for exceptions:

.. slide:: Exceptions
   :inline-contents: True
   :level: 2

   * processor detected

     - **faults**

     - **traps**

     - **aborts**

   * programmed

     - **int n**

Processor detected exceptions are raised when an abnormal condition is
detected while executing an instruction.

A fault is a type of exception that is reported before the execution of the
instruction and can be usually corrected. The saved EIP is the address of
the instruction that caused the fault, so after the fault is corrected
the program can re-execute the faulty instruction. (e.g page fault).

A trap is a type of exception that is reported after the execution of the
instruction in which the exception was detected. The saved EIP is the address
of the instruction after the instruction that caused the trap. (e.g debug trap).

Quiz: interrupt terminology
---------------------------

.. slide:: Quiz: interrupt terminology
   :inline-contents: True
   :level: 2

   For each of the following terms on the left select all the terms
   from right that best describe them.

   .. hlist::
      :columns: 2

      * Watchdog
      * Demand paging
      * Division by zero
      * Timer
      * System call
      * Breakpoint

      * Exception
      * Interrupt
      * Maskable
      * Nonmaskable
      * Trap
      * Fault



Hardware Concepts
=================

Programmable Interrupt Controller
---------------------------------

.. slide:: Programmable Interrupt Controller
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::

        +-----------+   NMI
        |           |
        |           |<----------+
        |           |
        |           |           +------------+
        |           |           |            |   IRQ0
        |           |           |            |<------------+ device0
        |    CPU    |           |            |   IRQ1
        |           |   INTR    |     PIC    |<------------+ device1
        |           |<----------+            |   IRQN
        |           |           |            |<------------+ deviceN
        |           |           |            |
        +-----------+           +------------+

A device supporting interrupts has an output pin used for signaling an Interrupt ReQuest. IRQ
pins are connected to a device named Programmable Interrupt Controller (PIC) which is connected
to CPU's INTR pin.

A PIC usually has a set of ports used to exchange information with the CPU. When a device
connected to one of the PIC's IRQ lines needs CPU attention the following flow happens:

   * device raises an interrupt on the corresponding IRQn pin
   * PIC converts the IRQ into a vector number and writes it to a port for CPU to read
   * PIC raises an interrupt on CPU INTR pin
   * PIC waits for CPU to acknowledge an interrupt before raising another interrupt
   * CPU acknowledges the interrupt then it starts handling the interrupt

Will see later how the CPU handles the interrupt. Notice that by
design PIC won't raise another interrupt until the CPU acknowledged
the current interrupt.

.. note::

   Once the interrupt is acknowledged by the CPU the interrupt
   controller can request another interrupt, regardless if the CPU
   finished handled the previous interrupt or not. Thus, depending on
   how the OS controls the CPU it is possible to have nested
   interrupts.

The interrupt controller allows each IRQ line to be individually
disabled. This allows simplifying design by making sure that interrupt
handlers are always executed serially.

Interrupt controllers in SMP systems
------------------------------------

In SMP systems we may have multiple interrupt controllers in the
systems.

For example, on the x86 architecture each core has a local APIC used
to process interrupts from locally connected devices like timers or
thermals sensors. Then there is an I/O APIC is used to distribute IRQ
from external devices to CPU cores.

.. slide:: Interrupt controllers in SMP systems
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::


              CPU0                             CPU1
        +-------------+                   +-------------+
        |             |                   |             |
        |             |local IRQs         |             |local IRQs
        |             +----------         |             +----------
        |             |                   |             |
        |  local APIC |                   |  local APIC |
        |             | LINT0, LINT1      |             | LINT0, LINT1
        |             +-------------      |             +-------------
        |             |                   |             |
        +-------+-----+                   +------+------+
                |                                |
                |                                |
                |                                |
        +-------+--------------------------------+------+
        |                                               |
        |    Interrupt Controller Communication BUS     |
        +----------------------+------------------------+
                               |
                               |
                      +--------+--------+
                      |                 |
                      |    I/O APIC     |
                      |                 |
                      +--------+--------+
                               |
                               |
                               |
                      External interrupts



Interrupt Control
-----------------

In order to synchronize access to shared data between the interrupt handler
and other potential concurrent activities such as driver initialization or
driver data processing, it is often required to enable and disable interrupts in
a controlled fashion.

This can be accomplished at several levels:

.. slide:: Enabling/disabling the interrupts
   :inline-contents: True
   :level: 2

   * at the device level

     * by programming the device control registers

   * at the PIC level

     * PIC can be programmed to disable a given IRQ line

   * at the CPU level; for example, on x86 one can use the following
     instructions:

    * cli (CLear Interrupt flag)
    * sti (SeT Interrupt flag)


Interrupt priorities
---------------------

Most architectures also support interrupt priorities. When this is
enabled, it permits interrupt nesting only for those interrupts that
have a higher priority than the current priority level.

.. slide:: Interrupt priorities
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::

                                   Process
                                   context
                                     |
                                     v
      IRQ10                          |       irq10 handler
      -----------------------------> +-------------+
                                                   |
      IRQ20 (lower priority)                       |
      -----------------------------> pending       v
                                                   |
      IRQ5 (higher priority)                       |             irq5 handler
      ----------------------------->               +-------->---------+
                                                                      |
                                                                      v
                                                                      |
                                                   +--------<---------+
                                                   |
                                                   v
                                                   |
                                    -------<-------+
                                                irq20 handler
      Pending IRQ20                 ------->-------+
                                                   |
                                                   v
                                                   |
                                    +--------------+
                                    |
                                    v


.. note::

   Not all architectures support interrupt priorities. It is also
   difficult to support defining a generic scheme for interrupt
   priorities for general use OSes and some kernels (Linux included)
   do not use interrupt priorities. On the other hand most RTOS use
   interrupt priorities since they are typically used in more
   constraint use-cases where it is easier to define interrupt
   priorities.


Quiz: hardware concepts
-----------------------

.. slide:: Quiz: hardware concepts
   :inline-contents: True
   :level: 2

   Which of the following statements are true?

   * The CPU can start processing a new interrupt before the current
     one is finished

   * Interrupts can be disabled at the device level

   * Lower priority interrupts can not preempt handlers for higher
     priority interrupts

   * Interrupts can be disabled at the interrupt controller level

   * On SMP systems the same interrupt can be routed to different CPUs

   * Interrupts can be disabled at the CPU level


Interrupt handling on the x86 architecture
==========================================

This section will examine how interrupts are handled by the CPU on the
x86 architecture.

Interrupt Descriptor Table
--------------------------

The interrupt descriptor table (IDT) associates each interrupt or exception
identifier with a descriptor for the instructions that service the associated
event. We will name the identifier as vector number and the associated
instructions as interrupt/exception handler.

An IDT has the following characteristics:

.. slide:: Interrupt Descriptor Table
   :inline-contents: True
   :level: 2

   * it is used as a jump table by the CPU when a given vector is triggered
   * it is an array of 256 x 8 bytes entries
   * may reside anywhere in physical memory
   * processor locates IDT by the means of IDTR

Below we can find Linux IRQ vector layout. The first 32 entries are reserved
for exceptions, vector 128 is used for syscall interface and the rest are
used mostly for hardware interrupts handlers.

.. slide:: Linux IRQ vector layout
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::

    arch/x86/include/asm/irq_vectors.h
         +------+
         |  0   | 0..31, system traps and exceptions
         +------+
         |  1   |
         +------+
         |      |
         +------+
         |      |
         |      |
         |      |
         +------+
         |  32  |  32..127, device interrupts
         +------+
         |      |
         |      |
         |      |
         +------+
         | 128  |  int80 syscall interface
         +------+
         | 129  |  129..255, other interrupts
         +------+
         |      |
         |      |
         |      |
         +------+
         | 255  |
         +------+

On x86 an IDT entry has 8 bytes and it is named gate. There can be 3 types of gates:

  * interrupt gate, holds the address of an interrupt or exception handler.
    Jumping to the handler disables maskable interrupts (IF flag is cleared).
  * trap gates, similar to an interrupt gate but it does not disable maskable
    interrupts while jumping to interrupt/exception handler.
  * task gates (not used in Linux)

Let's have a look at several fields of an IDT entry:

   * segment selector, index into GDT/LDT to find the start of the code segment where
     the interrupt handlers reside
   * offset, offset inside the code segment
   * T, represents the type of gate
   * DPL, minimum privilege required for using the segments content.

.. slide:: Interrupt descriptor table entry (gate)
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::

     63                            47         42                  32
    +------------------------------+---+---+----+---+---------------+
    |                              |   | D |    |   |               |
    |         offset (16..31       | P | P |    | T |               |
    |                              |   | L |    |   |               |
    +------------------------------+---+---+----+---+---------------+
    |                              |                                |
    |       segment selector       |        offset (0..15)          |
    |                              |                                |
    +------------------------------+--------------------------------+
     31                             15                             0


Interrupt handler address
-------------------------

In order to find the interrupt handler address we first need to find the start
address of the code segment where interrupt handler resides. For this we
use the segment selector to index into GDT/LDT where we can find the corresponding
segment descriptor. This will provide the start address kept in the 'base' field.
Using base address and the offset we can now go to the start of the interrupt handler.


.. slide:: Interrupt handler address
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::


                      Interrupt Descriptor
          +----------------------------------------------+
          |                                              |
          |  +------------------+  +--------+  +------+  |
          |  | segment selector |  |  offset|  |  PL  |  |
          |  +----+-------------+  +---+----+  +------+  |
          |       |                    |                 |
          +----------------------------------------------+
                  |                    |
                  |                    |
    +-------------+                    +---------------------------->  +---------------+
    |                                                               ^  |  ISR address  |
    |                   Segment Descriptor                          |  +---------------+
    |     +----------------------------------------------+          |
    |     |                                              |          |
    +---->|  +------------------+  +--------+  +------+  |          |
          |  |      base        |  |  limit |  |  PL  |  |          |
          |  +---------+--------+  +--------+  +------+  |          |
          |            |                                 |          |
          +----------------------------------------------+          |
                       |                                            |
                       +--------------------------------------------+


Stack of interrupt handler
--------------------------

Similar to control transfer to a normal function, a control transfer
to an interrupt or exception handler uses the stack to store the 
information needed for returning to the interrupted code.

As can be seen in the figure below, an interrupt pushes the EFLAGS register
before saving the address of the interrupted instruction. Certain types
of exceptions also cause an error code to be pushed on the stack to help
debug the exception.


.. slide:: Interrupt handler stack
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::


        w/o privilege transition                     w/ privilege transition

    +   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |                     | OLD SS:ESP           |      OLD SS         | NEW SS:ESP from TSS
    |   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |     OLD EFLAGS      |                      |     OLD ESP         |
    |   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |     OLD CS          |                      |     OLD EFLAGS      |
    |   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |     OLD EIP         |                      |       OLD CS        |
    |   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |    (error code)     | NEW SS:ESP           |      OLD EIP        |
    |   +---------------------+                      +---------------------+
    |   |                     |                      |                     |
    |   |                     |                      |    (error code)     |  NEW SS:ESP
    |   |                     |                      +---------------------+
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    |   |                     |                      |                     |
    v   +---------------------+                      +---------------------+


Handling an interrupt request
-----------------------------

After an interrupt request has been generated the processor runs a sequence of
events that eventually end up with running the kernel interrupt handler:


.. slide:: Handling an interrupt request
   :inline-contents: True
   :level: 2


   * CPU checks the current privilege level
   * if need to change privilege level

      * change stack with the one associated with new privilege
      * save old stack information on the new stack

   * save EFLAGS, CS, EIP on stack
   * save error code on stack in case of an abort
   * execute the kernel interrupt handler

Returning from an interrupt handler
-----------------------------------

Most architectures offer special instructions to clean up the stack and resume
the execution after the interrupt handler has been executed. On x86 IRET is used
to return from an interrupt handler. IRET is similar to RET except that IRET
increments ESP by extra four bytes (because of the flags on stack) and moves the
saved flags into EFLAGS register.

To resume the execution after an interrupt the following sequence is used (x86):

.. slide:: Returning from an interrupt
   :inline-contents: True
   :level: 2

   * pop the error code (in case of an abort)
   * call IRET

     * pops values from the stack and restore the following register: CS, EIP, EFLAGS
     * if privilege level changed returns to the old stack and old privilege level

Inspecting the x86 interrupt handling
-------------------------------------

.. slide:: Inspecting the x86 interrupt handling
   :inline-contents: True
   :level: 2

   |_|

   .. asciicast:: ../res/intr_x86.cast


Quiz: x86 interrupt handling
----------------------------

.. slide:: Quiz: x86 interrupt handling
   :inline-contents: True
   :level: 2

   The following gdb commands are used to determine the handler for
   the int80 based system call exception. Select and arrange the
   commands or output of the commands in the correct order.

   .. code-block:: gdb

      (void *) 0xc15de780 <entry_SYSENTER_32>

      set $idtr_addr=($idtr_entry>>48<<16)|($idtr_entry&0xffff)

      print (void*)$idtr_addr

      set $idtr = 0xff800000

      (void *) 0xc15de874 <entry_INT80_32>

      set $idtr = 0xff801000

      set $idtr_entry = *(uint64_t*)($idtr + 8 * 128)

      monitor info registers

Interrupt handling in Linux
===========================

In Linux the interrupt handling is done in three phases: critical, immediate and
deferred.

In the first phase the kernel will run the generic interrupt handler that
determines the interrupt number, the interrupt handler for this particular
interrupt and the interrupt controller. At this point any timing critical
actions will also be performed (e.g. acknowledge the interrupt at the interrupt
controller level). Local processor interrupts are disabled for the duration of
this phase and continue to be disabled in the next phase.

In the second phase, all of the device driver's handlers associated with this
interrupt will be executed. At the end of this phase, the interrupt controller's
"end of interrupt" method is called to allow the interrupt controller to
reassert this interrupt. The local processor interrupts are enabled at this
point.

.. note::

   It is possible that one interrupt is associated with multiple
   devices and in this case it is said that the interrupt is
   shared. Usually, when using shared interrupts it is the
   responsibility of the device driver to determine if the interrupt
   is target to its device or not.

Finally, in the last phase of interrupt handling interrupt context deferrable
actions will be run. These are also sometimes known as "bottom half" of the
interrupt (the upper half being the part of the interrupt handling that runs
with interrupts disabled). At this point, interrupts are enabled on the local
processor.

.. slide:: Interrupt handling in Linux
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::


              phase 1
        +----------------+
        |    critical    |               phase 2
        +----------------+         +-----------------+
        |                |         |    immediate    |               phase 3
        | - IRQ disabled |         +-----------------+          +----------------+
        | - ACK IRQ      +-----+   |                 |          |   deferred     |
        |                |     +---> - IRQ disabled  |          +----------------+
        +----------------+         | - device handler|          |                |
                                   | - EOI IRQ       +-----+    | - IRQ enabled  |
                                   +-----------------+     +----> - execute later|
                                                                |                |
                                                                +----------------+


Nested interrupts and exceptions
--------------------------------

Linux used to support nested interrupts but this was removed some time
ago in order to avoid increasingly complex solutions to stack
overflows issues - allow just one level of nesting, allow multiple
levels of nesting up to a certain kernel stack depth, etc.

However, it is still possible to have nesting between exceptions and
interrupts but the rules are fairly restrictive:

.. slide:: IRQ and exception nesting in Linux
   :inline-contents: True
   :level: 2

   * an exception (e.g. page fault, system call) can not preempt an interrupt;
     if that occurs it is considered a bug

   * an interrupt can preempt an exception

   * an interrupt can not preempt another interrupt (it used to be possible)


The diagram below shows the possible nesting scenarios:

.. slide:: Interrupt/Exception nesting
   :inline-contents: True
   :level: 2

   |_|

   .. ditaa::

                     +                                    ^     +                 ^
                     |                                    |     |                 |
                     | Syscall                            | IRQi|                 |
          User Mode  | Exception (e.g. page fault)        |     |                 |
                     |                                    |     |                 |
                     +------------------------------------+-----+-----------------+
                     |                                iret|     | iret^ IRQj| iret|
                     |                                    |     |     |     |     |
        Kernel Mode  v-------+      ^-------+      ^------+     v-----+     v-----+
                             |      |       |      |
                         IRQi|  iret|   IRQj|  iret|
                             v------+       v------+

Interrupt context
-----------------

While an interrupt is handled (from the time the CPU jumps to the interrupt
handler until the interrupt handler returns - e.g.  IRET is issued) it is said
that code runs in "interrupt context".

Code that runs in interrupt context has the following properties:

.. slide:: Interrupt context
   :inline-contents: True
   :level: 2

    * it runs as a result of an IRQ (not of an exception)
    * there is no well defined process context associated
    * not allowed to trigger a context switch (no sleep, schedule, or user memory access)

Deferrable actions
------------------

Deferrable actions are used to run callback functions at a later time. If
deferrable actions scheduled from an interrupt handler, the associated callback
function will run after the interrupt handler has completed.

There are two large categories of deferrable actions: those that run in
interrupt context and those that run in process context.

The purpose of interrupt context deferrable actions is to avoid doing too much
work in the interrupt handler function. Running for too long with interrupts
disabled can have undesired effects such as increased latency or poor system
performance due to missing other interrupts (e.g. dropping network packets
because the CPU did not react in time to dequeue packets from the network
interface and the network card buffer is full).

Deferrable actions have APIs to: **initialize** an instance, **activate** or
**schedule** the action and **mask/disable** and **unmask/enable** the execution
of the callback function. The latter is used for synchronization purposes between
the callback function and other contexts.

Typically the device driver will initialize the deferrable action
structure during the device instance initialization and will activate
/ schedule the deferrable action from the interrupt handler.

.. slide:: Deferrable actions
   :inline-contents: False
   :level: 2


    * Schedule callback functions to run at a later time

    * Interrupt context deferrable actions

    * Process context deferrable actions

    * APIs for initialization, scheduling, and masking

Soft IRQs
---------

Soft IRQs is the term used for the low-level mechanism that implements deferring
work from interrupt handlers but that still runs in interrupt context.

.. slide:: Soft IRQs
   :inline-contents: True
   :level: 2

    Soft IRQ APIs:

      * initialize: :c:func:`open_softirq`
      * activation: :c:func:`raise_softirq`
      * masking: :c:func:`local_bh_disable`, :c:func:`local_bh_enable`

    Once activated, the callback function :c:func:`do_softirq` runs either:

      * after an interrupt handler or
      * from the ksoftirqd kernel thread


Since softirqs can reschedule themselves or other interrupts can occur that
reschedules them, they can potentially lead to (temporary) process starvation if
checks are not put into place. Currently, the Linux kernel does not allow
running soft irqs for more than :c:macro:`MAX_SOFTIRQ_TIME` or rescheduling for
more than :c:macro:`MAX_SOFTIRQ_RESTART` consecutive times.

Once these limits are reached a special kernel thread, **ksoftirqd** is woken up
and all of the rest of pending soft irqs will be run from the context of this
kernel thread.

.. slide:: ksoftirqd
   :inline-contents: False
   :level: 2

    * minimum priority kernel thread
    * runs softirqs after certain limits are reached
    * tries to achieve good latency and avoid process starvation

Soft irqs usage is restricted, they are use by a handful of subsystems that have
low latency requirements and high frequency:

.. slide:: Types of soft IRQs
   :inline-contents: True
   :level: 2

   .. code-block:: c

      /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
         frequency threaded job scheduling. For almost all the purposes
	 tasklets are more than enough. F.e. all serial device BHs et
	 al. should be converted to tasklets, not to softirqs.
      */

      enum
      {
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
      };


Packet flood example
---------------------

The following screencast will look at what happens when we flood the
system with a large number of packets. Since at least a part of the
packet processing is happening in softirq we should expect the CPU to
spend most of the time running softirqs but the majority of that
should be in the context of the `ksoftirqd` thread.

.. slide:: Packet flood example
   :inline-contents: True
   :level: 2

   |_|

   .. asciicast:: ../res/ksoftirqd-packet-flood.cast


Tasklets
--------

.. slide:: Tasklets
   :inline-contents: True
   :level: 2

   Tasklets are a dynamic type (not limited to a fixed number) of
   deferred work running in interrupt context.

   Tasklets API:

    * initialization: :c:func:`tasklet_init`
    * activation: :c:func:`tasklet_schedule`
    * masking: :c:func:`tasklet_disable`, :c:func:`tasklet_enable`

   Tasklets are implemented on top of two dedicated softirqs:
   :c:macro:`TASKLET_SOFITIRQ` and :c:macro:`HI_SOFTIRQ`

   Tasklets are also serialized, i.e. the same tasklet can only execute on one processor.


Workqueues
----------

 .. slide:: Workqueues
   :inline-contents: True
   :level: 2

   Workqueues are a type of deferred work that runs in process context.

   They are implemented on top of kernel threads.

   Workqueues API:

    * init: :c:macro:`INIT_WORK`
    * activation: :c:func:`schedule_work`

Timers
------

.. slide:: Timers
   :inline-contents: True
   :level: 2

    Timers are implemented on top of the :c:macro:`TIMER_SOFTIRQ`

    Timer API:

    * initialization: :c:func:`setup_timer`
    * activation: :c:func:`mod_timer`

Deferrable actions summary
--------------------------

Here is a cheat sheet which summarizes Linux deferrable actions:


.. slide:: Deferrable actions summary
   :inline-contents: True
   :level: 2

    * softIRQ

      * runs in interrupt context
      * statically allocated
      * same handler may run in parallel on multiple cores

    * tasklet

      * runs in interrupt context
      * can be dynamically allocated
      * same handler runs are serialized

    * workqueues

      * run in process context

Quiz: Linux interrupt handling
------------------------------

.. slide:: Quiz: Linux interrupt handling
   :inline-contents: True
   :level: 2

   Which of the following phases of interrupt handling runs with
   interrupts disabled at the CPU level?

   * Critical

   * Immediate

   * Deferred