Pardon the Interruption

Interrupt handlers are an integral part of most device drivers. Learn to implement interrupt handlers and bottom halves.
Due to the indeterminate nature of I/O and speed mismatches between I/O devices and the central processing unit, devices request the processor’s attention by asserting certain hardware signals asynchronously. These hardware signals are called interrupts.
Each interrupting device is assigned an associated identifier called an Interrupt ReQuest number, or IRQ. When the processor detects that an interrupt has been generated on an IRQ, it abruptly stops what it’s doing and invokes an Interrupt Service Routine (ISR) registered for the corresponding IRQ. ISRs (also called interrupt handlers) execute in a special kernel context called the interrupt context and are an integral part of most device drivers.
This month, let’s learn the art of designing interrupt handlers using the example of a roller wheel device found in many cordless phones. You’ll also learn to use kernel mechanisms like tasklets for offloading heavy work from interrupt handlers.

The Interrupt Context

An interrupt handler is a critical piece of code that converses directly with the hardware. An interrupt handler is given the privilege of instant execution in the larger interest of system performance. However, if the interrupt handler isn’t quick and lightweight, it contradicts the overall design of the system.
To be pardoned for rudely interrupting the current thread of execution, interrupt handlers have to politely execute in a restricted environment called an interrupt context. Here’s a guide to interrupt handler etiquette (do’s and don’ts for code executing in interrupt context):
1.It is a jailable offense if your interrupt context code goes to sleep. Obviously, interrupt handlers cannot relinquish the processor by calling “sleepy” functions like schedule_timeout(), but there are other subtler and indirect problems to avoid, too. Before you invoke any kernel API from your interrupt handler, penetrate the nested invocation veil and ensure that the the API doesn’t internally trigger a blocking wait. For example, request_irq() or input_register_device() look harmless, but each calls kmalloc(). If your system’s free memory dips below a watermark, kmalloc() can go to sleep waiting for memory to get freed up!
2.To protect critical sections inside interrupt handlers, you can’t use a semaphore since it can sleep. Use a spinlock instead, and use it only if you must.
3.An interrupt handler cannot directly transfer data to and from user space, since it’s not connected to “user land” via process contexts. That brings up another reason why interrupt handlers cannot sleep. The scheduler works in the granularity of processes, so if interrupt handlers are scheduled out, how can they be put back into the run queue?
4.An interrupt handler is supposed to get out of the way quickly, but is also expected to get the job done. To circumvent this Catch-22, an interrupt handlers usually splits its work into two. The slim top half of the handler flags an acknowledgment, claiming that it’s serviced the interrupt, but in reality, offloads all the hard work to a fat bottom half. Execution of the bottom half is deferred to a later point in time when all interrupts are enabled. You’ll learn to develop bottom halves while discussing softirqs and tasklets later on.
5.An interrupt handler doesn’t have to be reentrant. When an interrupt handler is running, the corresponding IRQ is disabled until the handler returns. So, unlike process context code, different instances of the same handler won’t run simultaneously on multiple processors.
6.Interrupt handlers can be interrupted by handlers associated with other IRQ’s. You can prevent this nested interruption by specifically requesting the kernel to treat your interrupt handler as a ‘“fast” handler. Fast handlers run with all interrupts disabled on the local processor. However, before disabling interrupts or labeling your interrupt handler as “fast,” be aware that interrupt-off times are bad for system performance. More interrupt-off time causes more interrupt latency, or the delay before a generated interrupt is serviced. Interrupt latency is inversely proportional to the real time responsiveness of the system.
Unlike asynchronous interrupts generated by external hardware, there are classes of interrupts that arrive synchronously. A synchronous interrupt is so called because it doesn’t occur unexpectedly; instead, the processor itself generates synchronous interrupt by executing an instruction. Examples of synchronous interrupts are exceptions, which are used to report grave run time errors, and software interrupts, like the int 0×80 instruction used to implement system calls.
Both external and synchronous interrupts are handled by the kernel using identical mechanisms.

Assigning IRQ’s

Each device driver must connect its IRQ number to an interrupt handler. For this, the device driver must know the IRQ assigned to the device that it drives.
IRQ assignments can be straight-forward or may require complex probing. In the PC architecture, timer interrupts are assigned IRQ 0, and RTC interrupts answer to IRQ 8. Modern bus technologies like PCI are sophisticated enough to respond to queries regarding IRQ’s (assigned by the BIOS when it walks the bus during boot), and a driver for such a device can poke into earmarked regions in the device’s configuration space and figure out the IRQ. For older devices like ISA-based cards, the driver might have to leverage hardware-specific knowledge to probe and decipher the IRQ.
Consider the legacy serial port on your PC that isn’t capable of automatic enumeration. The BIOS knows the internals of the SuperIO chipset that features the UART, and assigns it the IRQ and I/O base address entered via the BIOS configuration screen. Details of the SuperIO chip is transparent to Linux, however. It merely hard-codes the IRQ and iobase address values in include/asm/serial.h, so the defaults in this header won’t work if you alter the UART IRQ values via the BIOS menu. To implement auto-probe, obtain the register layout of your SuperIO chipset from its data-sheet, read the IRQ values set by the BIOS, and use that when requesting the IRQ.

An Example Interrupt Handler

Now that you’ve learned the basics of interrupt handling, let’s implement a simple interrupt handler for an example roller wheel device. The roller device is capable of 3 movements: clockwise rotation, counterclockwise rotation, and key-press. The device is wired such that any of these movements interrupt the processor on IRQ 5. Three low order bits of General Purpose I/O (GPIO) Port D of the processor are connected to the roller device. The waveforms generated on these pins corresponding to different wheel movements are shown in Figure One. The job of the interrupt handler is to decipher the wheel movements by looking at the Port D data register.
The driver has to first request the IRQ number and register an associated interrupt handler with the kernel:
#define ROLLER_IRQ  5

static irqreturn_t roller_interrupt (int irq, void * dev_id,
struct pt_regs * regs);

if (request_irq (ROLLER_IRQ, roller_isr,
SA_INTERRUPT, "roll", NULL)) {
printk(KERN_ERR "roll: Can’t register IRQ %d\n", ROLLER_IRQ);
return -EIO;
}
Driver initialization isn’t a good place for requesting an IRQ, since that can hog the valuable resource even when the device isn’t in use. Hence, device drivers usually request the IRQ during device open() and free it from the close() entry point.
Let’s take a closer look at the arguments passed to request_irq(). The IRQ number isn’t queried or probed, but hard-coded to ROLLER_IRQ in this simple case, as per the hardware connection. roller_isr() is the interrupt handler routine. Its prototype specifies a return type of irqreturn_t, which can be IRQ_HANDLED if the interrupt was handled successfully or IRQ_NONE if it wasn’t. (The return value assumes more significance for I/O technologies like PCI, where multiple devices can share the same IRQ.) SA_INTERRUPT specifies that this interrupt handler has to be treated as a “fast” handler. The next argument, “roll”, is used to identify this device in data generated by files like /proc/interrupts. The last parameter is relevant only for shared interrupt handlers and is used to identify each device sharing the IRQ line.
Listing One shows the implementation of the interrupt handler. Look at Figure One side by side with this listing.
Listing One: The Roller Interrupt Handler

spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD (roller_poll);

static irqreturn_t
roller_interrupt (int irq, void * dev_id,
struct pt_regs * regs);
{
int i, PA_t, PA_delta_t, movement = 0;

/* Get the waveforms from bits 0, 1 and 2
of Port D as shown in Figure One */
PA_t = PORTD & 0×07;

/* Wait till the state of the pins change.
* (Add some timeout to the loop) */
for (i=0; (PA_t==PA_delta_t); i++){
PA_delta_t = PORTD & 0×07;
}

switch (PA_t){
case 0:
switch (PA_delta_t){
case 1:
movement = ANTICLOCKWISE;
break;
case 2:
movement = CLOCKWISE;
break;
case 4:
movement = KEYPRESSED;
break;
}
break;
case 1:
switch (PA_delta_t){
case 3:
movement = ANTICLOCKWISE;
break;
case 0:
movement = CLOCKWISE;
break;
}
break;
case 2:
switch (PA_delta_t){
case 0:
movement = ANTICLOCKWISE;
break;
case 3:
movement = CLOCKWISE;
break;
}
break;
case 3:
switch (PA_delta_t){
case 2:
movement = ANTICLOCKWISE;
break;
case 1:
movement = CLOCKWISE;
break;
}
case 4:
movement = KEYPRESSED;
break;
}

spin_lock (&roller_lock);

/* Store the wheel movement in a buffer for
later access by the read()/poll() entry points */

spin_unlock (&roller_lock);

/* Wake up the poll entry point that might have
gone to sleep, waiting for a wheel movement */
wake_up_interruptible (&roller_poll);

return IRQ_HANDLED;
}

Listing One assumes that driver entry points like read() and poll() operate in tandem with roller_interrupt(). For example, once the handler deciphers the wheel movement, it wakes up any waiting poll() thread that might have gone to sleep in response to a select() system call issued by an application like the X Window System.

Softirqs and Tasklets

As discussed earlier, an interrupt handlers has conflicting requirements: it’s responsible for the bulk of device data processing, and it must exit as fast as possible. To bail out of this situation, interrupt handlers are designed in two parts, a hurried and harried top half that interacts with the hardware, and a relaxed bottom half that does most of the processing with all interrupts enabled. Bottom halves are synchronous like software interrupts, since the kernel decides when to execute them. There are different mechanisms available in the kernel to defer work to a bottom half. They are, softirqs, tasklets, and work queues.
A softirq is the basic bottom half mechanism and has strong locking requirements. A softirq is used only by a few performance sensitive subsystems, specifically the networking layer, the SCSI layer, and kernel timers.
Tasklets are built on top of softirqs and are easier to use. It’s recommended that use tasklets, unless you have crucial scalability or speed requirements. A primary difference between a softirq and a tasklet is that the former is reentrant while the latter isn’t. Different instances of a softirq can run simultaneously on different processors, but this is not the case with tasklets.
To illustrate usage of softirqs and tasklets, assume that the roller wheel in the previous example has inherent hardware problems (say, the wheel gets stuck occasionally), resulting in the generation of out-of-spec waveforms. To get around this, you want to capture the wave stream, run some analysis on it, and dynamically switch between an interrupt mode and a polled mode. The solution: capture the wave stream from the interrupt handler and do the analysis from a bottom half.
Listing Two implements this using softirqs, while Listing Three uses tasklets. Both use a simplified variant of Listing One. This reduces the handler to two functions: roller_capture(), which grabs a wave snippet from Port D, and roller_analyze(), which runs an algorithmic analysis on the wave and stores the wheel movements in a buffer for later use.
Listing Two: Using softirqs to fix an errant pointing device

void __init roller_init ()
{
/* … */

/* First, add an entry for ROLLER_SOFT_IRQ in
the enum list in include/linux/interrupt.h */

/* Open the softirq */
open_soft_irq (ROLLER_SOFT_IRQ, roller_analyze, NULL);
}

/* The Bottom Half */
void roller_analyze ()
{
/* … */
}

static irqreturn_t
roller_interrupt (int irq, void * dev_id, struct pt_regs * regs);
{
roller_capture (); /* Capture the wave stream */
raise_soft_irq (ROLLER_SOFT_IRQ); /* Mark softirq as pending */

return IRQ_HANDLED;
}

Listing Three: Using tasklets to fix an errant pointing device

struct roller_device_struct { /* Device-specific struct */
/* … */

struct tasklet_struct tsklt;
/* … */
}

void __init roller_init ()
{
struct roller_device_struct * dev_struct;
/* … */

/* Initialize tasklet */
tasklet_init (&dev_struct->tsklt, roller_analyze, dev);
}

/* The Bottom Half */
void roller_analyze ()
{
/* … */
}

static irqreturn_t
roller_interrupt (int irq, void * dev_id, struct pt_regs * regs);
{
struct roller_device_struct * dev_struct;

roller_capture (); /* Capture the wave stream */
tasklet_schedule (&dev_struct->tsklt); /* Mark tasklet as pending */

return IRQ_HANDLED;
}

raise_softirq() in Listing Two and tasklet_schedule() in Listing Three announce that the corresponding bottom halves are pending execution. The kernel executes them in the next opportunity. This can be during exit from interrupt handlers or via a kernel thread called ksoftirqd.
So far, you’ve seen the differences between interrupt handlers and bottom halves, but there are a few similarities too. Interrupt handlers and tasklets are both not reentrant. and neither can go to sleep. Also, interrupt handlers, tasklets, and softirqs cannot be preempted.
The work queue is a third way to defer work from interrupt handlers. A work queue executes in process context and can go to sleep. So, you can use drowsy functions like semaphores from work queues. (Work queues were discussed in an earlier column.)

Looking at the Sources

Entry and exit points for the kernel interrupt handling infrastructure reside in arch/your-arch/kernel/entry.S. The core interrupt handling code is generic and lives in the kernel/irq/ directory. The architecture-specific portions can be found in arch/your-arch/kernel/irq.c. The function do_IRQ() defined in this file is a good place to start your journey into the kernel interrupt handling mechanism.
The kernel softirq and tasklet implementations live in kernel/softirq.c. This file also contains additional functions that offer more fine-grained control over softirqs and tasklets. Look at include/linux/interrupt.h for softirq vector enumerations and prototypes required to implement your interrupt handler.
For a real-life example on writing interrupt handlers and bottom halves, start from the handler that is part of drivers/net/8390.c and follow the trail into the networking stack.

Sreekrishnan Venkateswaran has been working for IBM India for over ten years. His recent projects include porting Linux to a pacemaker programmer and writing firmware for a lung biopsy device. You can reach Krishnan at class="emailaddress">krishhna@gmail.com.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62