Let’s look at a real interrupt handler, from the real-time clock (RTC) driver, found in
drivers/char/rtc.c. An RTC is found in many machines, including PCs. It is a device, separate from the system timer, which sets the system clock, provides an alarm, or supplies a periodic timer. On most architectures, the system clock is set by writing the desired time into a specific register or I/O range. Any alarm or periodic timer functionality is normally implemented via interrupt.The interrupt is equivalent to a real-world clock alarm: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. One of its duties is to register 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 interrupt line is stored in rtc_irq.This variable is set to the RTC interrupt for a given architecture. On a PC, the RTC is located at IRQ 8.The second parameter is the interrupt handler, rtc_interrupt, which is willing to share the interrupt line with other handlers, thanks to the IRQF_SHARED flag. From the fourth parameter, you can see that the driver name is rtc. Because this device shares the interrupt line, it passes a unique per-device value for dev.
Finally, the handler itself:
ptg Writing an Interrupt Handler 121
static irqreturn_t rtc_interrupt(int irq, void *dev) {
/*
* 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;
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. First, note the spin lock calls:The first set ensures that rtc_irq_data is not accessed concurrently by another processor on an SMP machine, and the second set protects rtc_callback from the same. Locks are discussed in Chapter 10,“Kernel Synchronization Methods.”
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.
Next, if an RTC periodic timer is set, it is updated via mod_timer().Timers are dis-cussed in Chapter 11,“Timers and Time Management.”
The final bunch of 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 reg-istered and executed on each RTC interrupt.
ptg Finally, 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 mecha-nism 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. Recall that process context is the mode of operation the kernel is in while it is executing on behalf of a process—for example, executing a system call or running a kernel thread. In process con-text, the current macro points to the associated task. Furthermore, because a process is coupled to the kernel in process context, process context can sleep or otherwise invoke the scheduler.
Interrupt context, on the other hand, is not associated with a process.The current macro is not relevant (although it points to the interrupted process).Without a backing process, interrupt context cannot sleep—how would it ever reschedule? Therefore, you cannot call certain functions from interrupt context. If a function sleeps, you cannot use it from your interrupt handler—this limits the functions that one can call from an interrupt handler.
Interrupt context is time-critical because the interrupt handler interrupts other code.
Code should be quick and simple. Busy looping is possible, but discouraged.This is an important point; always keep in mind that your interrupt handler has interrupted other code (possibly even another interrupt handler on a different line!). Because of this asyn-chronous nature, it is imperative that all interrupt handlers be as quick and as simple as possible. As much as possible, work should be pushed out from the interrupt handler and performed in a bottom half, which runs at a more convenient time.
The setup of an interrupt handler’s stacks is a configuration option. Historically, inter-rupt handlers did not receive their own stacks. Instead, they would share the stack of the process that they interrupted.1 The kernel stack is two pages in size; typically, that is 8KB on 32-bit architectures and 16KB on 64-bit architectures. Because in this setup interrupt handlers share the stack, they must be exceptionally frugal with what data they allocate there. Of course, the kernel stack is limited to begin with, so all kernel code should be cautious.
Early in the 2.6 kernel process, an option was added to reduce the stack size from two pages down to one, providing only a 4KB stack on 32-bit systems.This reduced memory pressure because every process on the system previously needed two pages of contiguous, nonswappable kernel memory.To cope with the reduced stack size, interrupt handlers were given their own stack, one stack per processor, one page in size.This stack is referred to as the interrupt stack. Although the total size of the interrupt stack is half that of the original shared stack, the average stack space available is greater because interrupt handlers get the full page of memory to themselves.
1 A process is always running. When nothing else is schedulable, the idle task runs.
ptg