Chapter 7. Interrupts and Interrupt Handlers

Processors can be orders of magnitudes faster than the hardware they talk to; it is not ideal for the kernel to issue a request and wait for a response from slower hardware. Instead, the kernel must be free to go and handle other work, dealing with the hardware only after that hardware has actually completed its work. [p113]

How can the processor work with hardware without impacting the machine's overall performance? As one solution, polling incurs overhead, because it must occur repeatedly regardless of whether the hardware is active or ready. A better solution is to provide a mechanism for the hardware to signal to the kernel when attention is needed. This mechanism is called an interrupt. This chapter dicusses interrupts and how the kernel responds to them, with special functions called interrupt handlers.

Interrupts

An interrupt is produced by electronic signals from hardware devices and directed into input pins on an interrupt controller (a simple chip that multiplexes multiple interrupt lines into a single line to the processor):

  1. Upon receiving an interrupt, the interrupt controller sends a signal to the processor.
  2. The processor detects this signal and interrupts its current execution to handle the interrupt.
  3. The processor can then notify the operating system that an interrupt has occurred, and the operating system can handle the interrupt appropriately.

Different devices are associated with different interrupts using a unique value associated with each interrupt. This enables the operating system to differentiate between interrupts and to know which hardware device caused which interrupt. In turn, the operating system can service each interrupt with its corresponding handler.

These interrupt values are often called interrupt request (IRQ) lines:

Exceptions and Interrupts *

Exceptions are often discussed at the same time as interrupts. Unlike interrupts, exceptions occur synchronously with respect to the processor clock; they are often called synchronous interrupts. Exceptions are produced by the processor while executing instructions either in response to a programming error (e.g. divide by zero) or abnormal conditions that must be handled by the kernel (e.g. a page fault). Because many processor architectures handle exceptions in a similar manner to interrupts, the kernel infrastructure for handling the two is similar.

Simple definitions of the two:

System calls (one type of exception) on the x86 architecture are implemented by the issuance of a software interrupt, which traps into the kernel and causes execution of a special system call handler. Interrupts work in a similar way, except hardware (not software) issues interrupts.

Interrupt Handlers

An interrupt handler or interrupt service routine (ISR) is the function that the kernel runs in response to a specific interrupt:

In Linux, interrupt handlers are normal C functions, which match a specific prototype and thus enables the kernel to pass the handler information in a standard way. What differentiates interrupt handlers from other kernel functions is that the kernel invokes them in response to interrupts and that they run in a special context called interrupt context. This special context is occasionally called atomic context because code executing in this context is unable to block.

Because an interrupt can occur at any time, an interrupt handler can be executed at any time. It is imperative that the handler runs quickly, to resume execution of the interrupted code as soon as possible. It is important that

At the very least, an interrupt handler's job is to acknowledge the interrupt's receipt to the hardware. However, interrupt handlers can oftern have a large amount of work to perform.

Top Halves Versus Bottom Halves

These two goals of an interrupt handler conflict with one another:

Because of these competing goals, the processing of interrupts is split into two parts, or halves:

Linux provides various mechanisms for implementing bottom halves (discussed in Chapter 8).

For example using the network card:

  1. When network cards receive packets from the network, the network cards immediately issue an interrupt. This optimizes network throughput and latency and avoids timeouts. [p115]
  2. The kernel responds by executing the network card's registered interrupt.
  3. The interrupt runs, acknowledges the hardware, copies the new networking packets into main memory, and readies the network card for more packets. These jobs are the important, time-critical, and hardware-specific work.
    • The kernel generally needs to quickly copy the networking packet into main memory because the network data buffer on the networking card is fixed and miniscule in size, particularly compared to main memory. Delays in copying the packets can result in a buffer overrun, with incoming packets overwhelming the networking card's buffer and thus packets being dropped.
    • After the networking data is safely in the main memory, the interrupt's job is done, and it can return control of the system to whatever code was interrupted when the interrupt was generated.
  4. The rest of the processing and handling of the packets occurs later, in the bottom half.

This chapter discusses the top half. The next chapter covers the bottom.

Registering an Interrupt Handler

Each device has one associated driver. If that device uses interrupts (and most do), that driver must register one interrupt handler.

Drivers can register an interrupt handler and enable a given interrupt line for handling with the function request_irq(), which is declared in <linux/interrupt.h>:

include/linux/interrupt.h#L117

/* request_irq: allocate a given interrupt line */
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev)

The first parameter, irq, specifies the interrupt number to allocate

The second parameter, handler, is a function pointer to the actual interrupt handler that services this interrupt. This function is invoked whenever the operating system receives the interrupt.

include/linux/interrupt.h#L80

typedef irqreturn_t (*irq_handler_t)(int, void *);

Note the specific prototype of the handler function: It takes two parameters and has a return value of irqreturn_t.

Interrupt Handler Flags

The third parameter, flags, can be either zero or a bit mask of one or more of the flags defined in <linux/interrupt.h>. The most important of these flags are:

include/linux/interrupt.h#L56

The fourth parameter, name, is name of the device associated with the interrupt. For example, this value for the keyboard interrupt on a PC is "keyboard". These text names are used by /proc/irq and /proc/interrupts.

The fifth parameter, dev, is used for shared interrupt lines. When an interrupt handler is freed (discussed later), dev provides a unique cookie to enable the removal of only the desired interrupt handler from the interrupt line. Without this parameter, it would be impossible for the kernel to know which handler to remove on a given interrupt line. You can pass NULL here if the line is not shared, but you must pass a unique cookie if your interrupt line is shared. This pointer is also passed into the interrupt handler on each invocation. A common practice is to pass the driver's device structure. This pointer is unique and might be useful to have within the handlers.

request_irq() returns zero on success and nonzero value indicates an error, in which case the specified interrupt handler was not registered. A common error is -EBUSY, which denotes that the given interrupt line is already in use (and either the current user or you did not specify IRQF_SHARED).

request_irq() cannot be called from interrupt context (other situations where code cannot block), because it can block. It is a common mistake to call request_irq() when it is unsafe to sleep. On registration, an entry corresponding to the interrupt is created in /proc/irq. The function proc_mkdir() creates new procfs entries. This function calls proc_create() to set up the new procfs entries, which in turn calls kmalloc() to allocate memory. kmalloc() can sleep (Chapter 12).

An Interrupt Example

In a driver, requesting an interrupt line and installing a handler is done via request_irq():

if (request_irq(irqn, my_interrupt, IRQF_SHARED, "my_device", my_dev)) {
    printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);
    return -EIO;
}

In this example:

On failure, the code prints an error and returns. If the call returns zero, the handler has been successfully installed. From that point forward, the handler is invoked in response to an interrupt. It is important to initialize hardware and register an interrupt handler in the proper order to prevent the interrupt handler from running before the device is fully initialized.

Freeing an Interrupt Handler

When your driver unloads, you need to unregister your interrupt handler and disable the interrupt line, by calling:

include/linux/interrupt.h#L147

void free_irq(unsigned int irq, void *dev)

In either case (shared or unshared), if dev is non-NULL, it must match the desired handler. A call to free_irq() must be made from process context.

The following table reviews the functions for registering and deregistering an interrupt handler.

Function Description
request_irq() Register a given interrupt handler on a given interrupt line.
free_irq() Unregister a given interrupt handler; if no handlers remain on the line, the given interrupt line is disabled.

Writing an Interrupt Handler

The following is a declaration of an interrupt handler, which matches the prototype of the handler argument given to request_irq():

static irqreturn_t intr_handler(int irq, void *dev)

The return value of an interrupt handler is the special type irqreturn_t, which has two special values:

Alternatively, IRQ_RETVAL(val) may be used. If val is nonzero, this macro returns IRQ_HANDLED. Otherwise, the macro returns IRQ_NONE.

These special values are used to let the kernel know whether devices are issuing spurious (unrequested) interrupts. If all the interrupt handlers on a given interrupt line return IRQ_NONE, then the kernel can detect the problem.

The return type irqreturn_t which is simply an int. This value provides backward compatibility with earlier kernels, which did not have this feature. Before 2.6, interrupt handlers returned void. Drivers may simply typedef irqreturn_t to void and define the different return values to no-ops and then work in 2.4 without further modification.

The interrupt handler is normally static because it is never called directly from another file.

The role of the interrupt handler depends entirely on the device and its reasons for issuing the interrupt.

Reentrancy and Interrupt Handlers *

Interrupt handlers in Linux need not be reentrant. When a given interrupt handler is executing, the corresponding interrupt line is masked out on all processors, preventing another interrupt on the same line from being received. Normally all other interrupts are enabled, so other interrupts are serviced, but the current line is always disabled. Consequently, the same interrupt handler is never invoked concurrently to service a nested interrupt. This greatly simplifies writing your interrupt handler.

Shared Handlers

A shared handler is similar to a nonshared handler, but has three main differences:

All drivers sharing the interrupt line must meet the previous requirements. If any one device does not share fairly, none can share the line. The call to request_irq() with IRQF_SHARED specified can succeed only if:

Shared handlers, however, can mix usage of IRQF_DISABLED.

When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware has such a feature.

A Real-Life Interrupt Handler

The real-time clock (RTC) driver is a real interrupt handler, which can be found in drivers/char/rtc.c.

It is a device (separate from the system timer) which can do the following:

On most architectures, the system clock is set by writing the desired time into a specific register or I/O range. An alarm or periodic timer functionality is normally implemented via interrupt. The interrupt is equivalent to a real-world clock alarm and the receipt of the interrupt is analogous to a buzzing alarm.

When the RTC driver loads, the function rtc_init() is invoked to initialize the driver, which includes registering the interrupt handler:

/* register rtc_interrupt on rtc_irq */
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) {
        printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
        return -EIO;
}

In this example:

The handler code is:

drivers/char/rtc.c#L239

static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
    /*
     *  Can be an alarm interrupt, update complete interrupt,
     *  or a periodic interrupt. We store the status in the
     *  low byte and the number of interrupts received since
     *  the last read in the remainder of rtc_irq_data.
     */

    spin_lock(&rtc_lock);
    rtc_irq_data += 0x100;
    rtc_irq_data &= ~0xff;
    if (is_hpet_enabled()) {
        /*
         * In this case it is HPET RTC interrupt handler
         * calling us, with the interrupt information
         * passed as arg1, instead of irq.
         */
        rtc_irq_data |= (unsigned long)irq & 0xF0;
    } else {
        rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
    }

    if (rtc_status & RTC_TIMER_ON)
        mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);

    spin_unlock(&rtc_lock);

    /* Now do the rest of the actions */
    spin_lock(&rtc_task_lock);
    if (rtc_callback)
        rtc_callback->func(rtc_callback->private_data);
    spin_unlock(&rtc_task_lock);
    wake_up_interruptible(&rtc_wait);

    kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);

    return IRQ_HANDLED;
}

This function is invoked whenever the machine receives the RTC interrupt.

  1. Note the spin lock calls:
    • The first set ensures that rtc_irq_data is not accessed concurrently by another processor on an SMP machine. The rtc_irq_data variable is an unsigned long that stores information about the RTC and is updated on each interrupt to reflect the status of the interrupt.
    • The second set protects rtc_callback, similarly to the first one. Locks are discussed in Chapter 10 Kernel Synchronization Methods
  2. If an RTC periodic timer is set, it is updated via mod_timer(). Timers are discussed in Chapter 11 Timers and Time Management.
  3. The code under the comment "now do the rest of the actions" executes a possible preset callback function.The RTC driver enables a callback function to be registered and executed on each RTC interrupt.
  4. This function returns IRQ_HANDLED to signify that it properly handled this device. Because the interrupt handler does not support sharing, and there is no mechanism for the RTC to detect a spurious interrupt, this handler always returns IRQ_HANDLED.

Interrupt Context

When executing an interrupt handler, the kernel is in interrupt context.

Difference from the process context *

As discussed in Chapter 3, the process context is the mode of operation the kernel is in while it is executing on behalf of a process, such as executing a system call or running a kernel thread.

Interrupt context is not associated with a process.

Interrupt context is time-critical, because the interrupt handler interrupts other code.

Stacks of an interrupt handler *

The setup of an interrupt handler's stacks is a configuration option.

Your interrupt handler should not care what stack setup is in use or what the size of the kernel stack is. Always use an absolute minimum amount of stack space.

Implementing Interrupt Handlers

The implementation of the interrupt handling system in Linux is architecture-dependent. The implementation depends on the processor, the type of interrupt controller used, and the design of the architecture and machine.

The following figure is a diagram of the path an interrupt takes through hardware and the kernel.

Figure 7.1 The path that an interrupt takes from hardware and on through the kernel.

Interrupt from a device to the processor *

  1. A device issues an interrupt by sending an electric signal over its bus to the interrupt controller.
  2. If the interrupt line is enabled (they can be masked out), the interrupt controller sends the interrupt to the processor.
    • In most architectures, this is accomplished by an electrical signal sent over a special pin to the processor.
  3. If interrupts are not disabled in the processor, the processor immediately stops what it is doing, disables the interrupt system, and jumps to a predefined location in memory and executes the code located there. This predefined point is set up by the kernel and is the entry point for interrupt handlers.

Interrupt in the kernel *

The interrupt in the kernel begins at this predefined entry point (which is similar to system calls that enter the kernel through a predefined exception handler):

  1. For each interrupt line, the processor jumps to a unique location in memory and executes the code located there. In this manner, the kernel knows the IRQ number (the interrupt value) of the incoming interrupt.
  2. The initial entry point (assembly entry routine) saves the interrupt value and stores the current register values of the interrupted task on the stack.
  3. Then, the kernel calls do_IRQ().

From this point, most of the interrupt handling code is written in C, but is still architecture-dependent.

The do_IRQ() function is declared as (kernel/irq/handle.c#L449):

unsigned int do_IRQ(struct pt_regs regs)
  1. Because the C calling convention places function arguments at the top of the stack, the pt_regs structure contains the initial register values and the interrupt value, which were previously saved on the stack in the assembly entry routine. do_IRQ() can extract the interrupt value.
  2. After the interrupt line is calculated, do_IRQ() acknowledges the receipt of the interrupt and disables interrupt delivery on the line.

On normal PC machines, these operations are handled by mask_and_ack_8259A() (arch/x86/kernel/i8259.c#L147).

Next, do_IRQ() ensures that a valid handler is registered on the line and that it is enabled and not currently executing. If so, it calls handle_IRQ_event(), defined in kernel/irq/handle.c, to run the installed interrupt handlers for the line.

kernel/irq/handle.c#L368

/**
 * handle_IRQ_event - irq action chain handler
 * @irq:    the interrupt number
 * @action: the interrupt action chain for this irq
 *
 * Handles the action chain of an irq event
 */
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    irqreturn_t ret, retval = IRQ_NONE;
    unsigned int status = 0;

    if (!(action->flags & IRQF_DISABLED))
        local_irq_enable_in_hardirq();

    do {
        trace_irq_handler_entry(irq, action);
        ret = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, ret);

        switch (ret) {
        case IRQ_WAKE_THREAD:
            /*
             * Set result to handled so the spurious check
             * does not trigger.
             */
            ret = IRQ_HANDLED;

            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                warn_no_thread(irq, action);
                break;
            }

            /*
             * Wake up the handler thread for this
             * action. In case the thread crashed and was
             * killed we just pretend that we handled the
             * interrupt. The hardirq handler above has
             * disabled the device interrupt, so no irq
             * storm is lurking.
             */
            if (likely(!test_bit(IRQTF_DIED,
                         &action->thread_flags))) {
                set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
                wake_up_process(action->thread);
            }

            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            status |= action->flags;
            break;

        default:
            break;
        }

        retval |= ret;
        action = action->next;
    } while (action);

    if (status & IRQF_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
    local_irq_disable();

    return retval;
}
  1. Since the processor disabled interrupts, they are turned back on if IRQF_DISABLED was not specified during the handler's registration.
    • IRQF_DISABLED specifies that the handler must be run with interrupts disabled.
  2. Each potential handler is executed in a loop. If this line is not shared, the loop terminates after the first iteration. Otherwise, all handlers are executed.
  3. add_interrupt_randomness() is called if IRQF_SAMPLE_RANDOM was specified during registration. This function uses the timing of the interrupt to generate entropy for the random number generator.
  4. Interrupts are again disabled (do_IRQ() expects them still to be disabled) and the function returns.

Back in do_IRQ(), the function cleans up and returns to the initial entry point, which then jumps to ret_from_intr().

The routine ret_from_intr() (arch/x86/kernel/entry_64.S#L820) is written in assembly, as with the initial entry code. This routine checks whether a reschedule is pending (this implies that need_resched is set, as discussed in Chapter 4):

On x86, the initial assembly routines are located in arch/x86/kernel/entry_64.S (entry_32.S for 32-bit x86) and the C methods are located in arch/x86/kernel/irq.c. Other supported architectures are similar.

/proc/interrupts

Procfs is a virtual filesystem that exists only in kernel memory and is typically mounted at /proc. Reading or writing files in procfs invokes kernel functions that simulate reading or writing from a real file. The /proc/interrupts file is populated with statistics related to interrupts on the system.

      CPU0
0:    3602371  XT-PIC  timer
1:    3048     XT-PIC  i8042
2:    0        XT-PIC  cascade
4:    2689466  XT-PIC  uhci-hcd,  eth0
5:    0        XT-PIC  EMU10K1
12:   85077    XT-PIC  uhci-hcd
15:   24571    XT-PIC  aic7xxx
NMI:  0
LOC:  3602236
ERR:  0

procfs code is located primarily in fs/proc. The function that provides /proc/interrupts architecture-dependent and named show_interrupts() (include/linux/interrupt.h#L607).

Interrupt Control

The Linux kernel implements a family of interfaces for manipulating the state of interrupts . These interfaces enable you to disable the interrupt system for the current processor or mask out an interrupt line for the entire machine. These routines are all architecture-dependent and can be found in <asm/system.h> and <asm/irq.h>.

Controlling the interrupt system provides synchronization.

However, neither disabling interrupt delivery nor disabling kernel preemption provides any protection from concurrent access from another processor. Because Linux supports multiple processors, kernel code generally needs to obtain some sort of lock to prevent another processor from accessing shared data simultaneously. These locks are often obtained in conjunction with disabling local interrupts.

Chapters 9 and [Chapter 10])(ch10.md) discuss the various problems of synchronization and their solutions.

Disabling and Enabling Interrupts

To disable interrupts locally for the current processor (only the current processor) and then later reenable them, do the following:

local_irq_disable();
/* interrupts are disabled .. */
local_irq_enable();

These functions are usually implemented as a single assembly operation, which depends on the architecture. On x86, local_irq_disable() is a simple cli and local_irq_enable() is a simple sti instruction. cli and sti are the assembly calls to clear and set the interrupt flag, respectively. In other words, they disable and enable interrupt delivery on the issuing processor.

However, a common concern is a to restore interrupts to a previous state. It is much safer to save the state of the interrupt system before disabling it. Then, when you are ready to reenable interrupts, you simply restore them to their original state [p128]:

unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */
/* ... */
local_irq_restore(flags); /* interrupts are restored to their previous state */

Note that these methods are implemented at least in part as macros, so the flags parameter (which must be defined as an unsigned long) is seemingly passed by value. This parameter contains architecture-specific data containing the state of the interrupt systems. Because at least one supported architecture, such as SPARC, incorporates stack information into the value, flags cannot be passed to another function (specifically, it must remain on the same stack frame). For this reason, the call to save and the call to restore interrupts must occur in the same function.

All the previous functions can be called from both interrupt and process context.

No More Global cli() *

The kernel formerly provided a function, cli() to disable interrupts on all processors in the system. If another processor called this method, it would have to wait until interrupts were enabled before continuing. The corresponding enable call was named sti().

These interfaces were deprecated during 2.5, and consequently all interrupt synchronization must now use a combination of local interrupt control and spin locks (discussed in Chapter 9). This means that code that previously only had to disable interrupts globally to ensure mutual-exclusive access to shared data now needs to do a bit more work.

Previously, driver writers could assume a cli() used in their interrupt handlers and anywhere else the shared data was accessed would provide mutual exclusion.

The advantages of removing the global cli() are:

Disabling a Specific Interrupt Line

The previous section covers functions that disable all interrupt delivery for an entire processor. In some cases, it is useful to disable only a specific interrupt line for the entire system. This is called masking out an interrupt line.

To disable delivery of a devices' interrupts before manipulating its state, Linux provides four interfaces for this task:

void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);

Calls to these functions nest. For each call to disable_irq() or disable_irq_nosync() on a given interrupt line, a corresponding call to enable_irq() is required. Only on the last call to enable_irq() is the interrupt line actually enabled. For example, if disable_irq() is called twice, the interrupt line is not actually reenabled until the second call to enable_irq().

All three of these functions can be called from interrupt or process context and do not sleep. If calling from interrupt context, be careful, for example, not to enable an interrupt line while you are handling it. Recall that the interrupt line of a handler is masked out while it is serviced.

It is rude to disable an interrupt line shared among multiple interrupt handlers, because disabling the line disables interrupt delivery for all devices on the line. Therefore, drivers for newer devices tend not to use these interfaces:

Status of the Interrupt System

It is often useful to know:

The macro irqs_disabled(), defined in <asm/system.h>, returns nonzero if the interrupt system on the local processor is disabled. Otherwise, it returns zero.

Two macros, defined in <linux/hardirq.h>, provide an interface to check the kernel's current context:

in_interrupt()
in_irq()

If in_interrupt() returns zero, the kernel is in process context. This is useful if you want to check whether you are in process context, that is, you want to ensure you are not in interrupt context, which is often the case because code wants to do something that can only be done from process context, such as sleep.

The following table is a summary of the interrupt control methods and their description.

Function Description
local_irq_disable() Disables local interrupt delivery
local_irq_enable() Enables local interrupt delivery
local_irq_save() Saves the current state of local interrupt delivery and then disables it
local_irq_restore() Restores local interrupt delivery to the given state
disable_irq() Disables the given interrupt line and ensures no handler on the line is executing before returning
disable_irq_nosync() Disables the given interrupt line
enable_irq() Enables the given interrupt line
irqs_disabled() Returns nonzero if local interrupt delivery is disabled; otherwise returns zero
in_interrupt() Returns nonzero if in interrupt context and zero if in process context
in_irq() Returns nonzero if currently executing an interrupt handler and zero otherwise

Conclusion

This chapter discussed at interrupts, a hardware resource used by devices to asynchronously signal the processor. Interrupts are used by hardware to interrupt the operating system.

Most modern hardware uses interrupts to communicate with operating systems.The device driver, which manages a given piece of hardware, registers an interrupt handler to respond to and process interrupts issued from their associated hardware. Work performed in interrupts includes:

The kernel provides interfaces for:

Because interrupts interrupt other executing code (processes, the kernel itself, and even other interrupt handlers), they must execute quickly. Often there is a lot of work to do and to balance the large amount of work with the need for quick execution, the kernel divides the work of processing interrupts into two halves. The interrupt handler, the top half, was discussed in this chapter. The next chapter looks at the bottom half.

Doubts and Solution

Verbatim

p119 on interrupt handlers

The interrupt handler is normally static because it is never called directly from another file.

Question: What does it mean?

p120 on shared handlers

Shared handlers, however, can mix usage of IRQF_DISABLED.

Question: What does it mean?

p128 on disabling and enabling interrupts

The local_irq_disable() routine is dangerous if interrupts were already disabled prior to its invocation?

Question: Why is it dangerous?

Solution: local_irq_disable() may have similar calling mechanisms as disable_irq() functions discussed in p129: calls to these functions nest. If disable_irq() is called twice, the interrupt line is not actually reenabled until the second call to enable_irq().