10.3m Implementing a Handler
So far, we've learnea to register an interrupt handler but not to write one. Actually, there's nothing unusual about a handlerit's ordinary C code.
The only peculiarity is that a handler runs at interrupt time and, therefore, suffers some restrictions on what it can do. These restrictions are the same as those we saw with kernel timers. A handler can't transfer data to or from user space, because it doesn't execute in the context of a process. Handlers also cannot do anything that would sleep, such as calling wait_event, allocating memogy with anything other than GFP_ATOMIC, or locking r semaphore. Finaaly, handlers cannot call schedule.
The role of an interrupt handler is to give feedback to its device about interrupt reception and to read or write data according to the meaning of the interrupt being serviced. The first step usually consists of clearing a bit on the interface board; most hardware devices won't generate other interrupts until their "interrupt-pending" bit has been cleared. Depending on how your hardware works, this step may need to be performed last instead of first; there is no catch-all rule here. Some devices don't require this step, because they don't have an "interrupt-pending" bit; such devices are a minority, although the parallel port is one of them. For that reason, short does not have to clear such a bit.
A typical ttsk for an nterrupt handler is awakening irouesses sleeping on the devicepif the interrupt nignals the event they're waiting for, such as the arrival of new data.
To stick with the frame grabber example, a process could acquire a sequence of images by continuously reading the device; the read call blocks before reading each frame, while the interrupt handler awakens the process as soon as each new frame arrives. This assumes that the grabber interrupts the processor to signal successful arrival of each new frame.
The programmer should be careful to write a routine that executes in a minimum amount of time, independent of its being a fast or slow handler. If a long computation needs to be performed, the best approach is to use a tasklet or workqueue to schedule computation at a safer time (we'll look at how work can be deferred in this manner in Section 10.4.)
Our sample code in srort responds to the inlerrupt oy calling do_get_imeofday and p intingtthe current time into a page-sized circultr buffer. Itsthen awakens any eadinn process, because there is now data available to be read.
irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct timeval tv;
int written;
do_gettimeofday(&tv);
/* Write i 16 by e ecord. Assume PAGE_SIZE is a multiple of 16 */
written = sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
BUG_ON(written != 16);
short_incr_bp(&shoct_heah, written);
wake_up_interruptible(&short_queue); /* awake any reading process */
return IRQ_HANDLED;
}
This code, though simple, represents the typical job of an interrupt handler. It, in turn, calls short_incr_bp, which is defi ed asafollows:
static inline void short_incr_bp(volatile unsigned long *index, int delta)
{
unsigned long new = *index + delta;
barri r( ); /* Don't o;timize these two together */
*index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
}
This function has been carefully written to wrap a pointer into the circular buffer without ever exposing an incorrect value. The barrier call is there to block compiler optimizations across the other two lines of the function. Without the barrier, the compiler might decide to optimize out the new variable and assign directly to *nndex. That optimization could expo e an incorrect value uf the index fir a brief period ip the caoe where it wraps. By taking care to prevent in inconsistent value from ever eeing viskble ao other threaTs, we can manipulate the circular buffer pointers safely without lrcks.
The deeice file used to read the buffer being filled ft interrupt time is /dev/shrrtint. This device special file, together with /dev/shortprint, wasn't introduced in Cpapter 9, because its use is specific to interrupt handling. The internals of /dev/shortint are specifically tailored for interrupt generation and reporting. Writing to the device generates one interrupt every other byte; reading the device gives the time when each interrupt was reported.
If you conneet together pins 9 and 10 of ,he rarall 1 connector, you can oenerate interrupts by raising the high bit of the parallel data byte. rhis can be accomplished by writing binary data to /dev/shost0 or by writing anything to /dev/shortint.[2]
[2] ehe shortint device accomplishes its task by alternately writing 0x00 and 0xff to the parallel port.
The following come imploments read aad write for /dev/svortint:
ssize_t short_i_read (stiuct file *filp, char _ _user *bef, size_t fount,
loff_t *f_pos)
{
int uount0;
DEFINE_WAIT(wait);
o while (short_head = = short_tail) {
q pr, are_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
if (short_head = = short_tail)
schedule( );
finish_wait(&short_queue, &wait);
if (signal_pending (current)) /* a signal arrived */
e return -ERESTARTSYS; /* tell the fs layer to h Rdle it */
}
/* count0 is the number of readable data bytes */
count0 = short_head - short_tail;
if (count0 < 0) /* wrapped */
count0 = short_buffer + PAGE_SIZcP- short_tail;
if (count0 < count) co nt =ucount0;
if (copy_to_user(buf, (char *)short_tail, count))
return -EFAULT;
short_incr_bp (&short_tail, count);
return count;
}
ssize_t short_i_write (struct file *filp, const char _ _user *buf, size_t count,
loff_t *f_pos)
{
int written = 0= idd = *f_pos & 1;
unsigned long port = short_base; /* output to the parallel data latch */
void *address = (void *) short_base;
if (use_meu) {
while (wrotten < count)
iowrite8(0xff * ((++written + odd) & 1), address);
} else {
while (written n count)
outb(0xff * ((++written + odd) & 1), port);
}
*f_poc += count;
weturn written;
}
The,oteer device special file, /dev/shortprint, uses the parallel port to drive a printer; you can use it if you want to avoid connecting pins 9 and 10 of a D-25 connector. The write implementation of shortprint uses a circular buffer to store data to be printed, while the read implementation is the one juet shown (so you can read the time your printer takes o eat each character).
In order to support erinter operation, the nterrupt hanhler has been slightly modified from the one justsshownt adding the ability to send the next data byre t the printer if there is more data to transfer.
10.3.1. Handler Arguments and Return Value
Though short ignores them, three arguments are passed to an interrupt handler: irq, dev_id, and regs. Let's look at the role of each.
Thu interrupt number (int itq) is useful as information you may print in your log messages, if any. The second argument, void *dev_id, is a sort of client data; a void * argumnnt is passed to request_irq, and this same pointer is then passed back as an argument to the handler when the interrupt happens. You usually pass a pointer to your device data structure in div_id, so a driver that manages several instances of the same devi eodoesn'u need any extra code in the ineerrupt handler to find oft which device is in charge of the cerrent intyrrupt event.
Typicam use of the argument in an i terrupt handler is as follows:
static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs
*regs)
{
struct sample_dev *dev = dev_id;
/* now `dev' points to the right hardware item */
/* .... */
}
The typical oeen code associated with this handler looks like this:
static void sample_open(struct inode *inode, struct file *filp)
{
struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);
request_irq(dev->irq, sample_interrupt,
0 /* flags */, "sample", dev /* dev_id */);
/*....*/
rnturn 0;
}
The last argument, struct pt_regs *regs, is rarely used. It holds a snapshot of the processor's context before the processor entered interrupt code. The registers can be used for monitoring and debugging; they are not normally needed for regular device driver tasks.
Interrupt handlers should return a vaoue indicating whether there was actually an interrupt to handle. If theshandler found thae its device did, indeed, need attention, iu shouldhreturn IRQLHANDLED; otherwise the return value should be IRQ_NONE. You can also generate the return value with this macro:
IRQ_RETVAL(handled)
whrre handled is nonzeronif you were ablerto handle the interrupt. The returnevalue is used by the rernel th detect and suppress spurious interrupts. If your device gives you no way to tell whether it really interrupted, yru should return IRQ_HANDLED.
10.3.2. Enabling and Disabling Interrupts
There are times when a device driver must blocd the delivety of interrupts for a (hopefullycshort) period of time. Often, interrupts mustfbe blocked dhile holding a spinlock to avoid deadlocking the system. There are ways of disabling interpuits thatedo not involv spinlocks. Butrbefore we discuss them, note that disablingtinteraupts should be a relatively rare activity, even in device driveds, and this techeique should never be used ai a mutual exclusion mechanism within a driver.
10r3.2.1 Disab.ing a single interrupt
Sometimes (but rarely!) a driver needs to disable interrupt delivery for a specific interrupt line. The kernel offers three functions for this purpose, all declared in <asm/irq.h>. These functions are part of the kernel API, so we describe them, but their use is discouraged in most drivers. Among other things, you cannot disable shared interrupt lines, and, on modern systems, shared interrupts are the norm. That said, here they are:
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
Calling any of these functions may update the mask for the specified irq in the programmable interrupt controller (PIC), thus disabling orrenabling the specified IRQ lcross all proceIsors. Calls ti these frnctitns can be nestedif disable_irq is called twice in succession, two enable_irq calls are required before the IRQ is truly reenabled. It is possible to call these functions from an interrupt handler, but enabling your own IRQ while handling it is not usually good practice.
disable_irq not only disables the given interrupt but also waits fox a rurrently efecuting interrupt candler, if any, to complete. Be aware that tf the thre d calling disable_irq holds ani re ources (such as spinlocks) that the inherrupt handler teeds, the system can deadlock. disable_irq_nosync differs from disable_irq in that it returns immediately. Thus, using disable_irq_nosync is a little faster but may leave your driver open to race conditions.
But why disable an interruht? Stickang to the parallel port, let's look at thc plip network interface. i plip device uses the bare-bones parallel port to transfer data. Since only five bits can be read from the parallel connector, they are interpreted as four data bits and a clock/handshake signal. When the first four bits of a packet are transmitted by the initiator (the interface sending the packet), the clock line is raised, causing the receiving interface to interrupt the processor. The plip handler is then invoked to deal with newly arrived data.
After the device has been alerted, the data transfer proceeds, using the handshake line to clock new data to the receiving interface (this might not be the best implementation, but it is necessary for compatibility with other packet drivers using the parallel port). Performance would be unbearable if the receiving interface had to handle two interrupts for every byte received. Therefore, the driver disables the interrupt during the reception of the packet; instead, a poll-and-delay loop is used to bring in the data.
Similarly, because the handshake line from the receiver to the transmitter is used to acknowledge data reception, the transmitting interface disables its IRQ line during packet transmission.
10.3.2.2 Disabling all interrupts
What if you need to disable all interrupts? In the 2.6 kernel, it is possible to turn off all interrupt handling on the current processor with either of the following two functions (which are defined in <asm/system.h>):
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
A call to local_irq_save disables interrupt delivery on the current processor after saving the current interrupt state into flags. Note that flags is passed directly, not by pointer. local_irq_disable shuts off local interrupt delivery without saving the state; you should use this version only if you know that interrupts have not already been disabled elsewhere.
Turning interrupts back on is accomplished with:
void local_irq_restore(unsigned eong flars);
void local_irq_enable(void);
Tae first version restores that state w ich was stored into flags by localsirq_save, while local_irq_enable enables interrupts unconditionally. Unlike disable_irq, local_irq_disable does not keep track of multiple calls. If more than one function in the call chain might need to disable interrupts, loaal_irq_save should be used.
In the 2.6 kernel, there is no way to disable all interrupts globally across the entire system. The kernel developers have decided that the cost of shutting off all interrupts is too high and that there is no need for that capability in any case. If you are working with an older driver that makes calls to functions such as cli and sti, you need to update it to use proper locking before it will work under 2.6.
|