Linux Device Drivers 2nd Edition (779877), страница 64
Текст из файла (страница 64)
If you don’t care about the internal details, you can skip to the next section.The internals of interrupt handling on the x86This description has been extrapolated from arch/i386/kernel/irq.c, arch/i386/kernel/i8259.c, and include/asm-i386/hw_irq.h as they appear in the 2.4 kernels;although the general concepts remain the same, the hardware details differ onother platforms.The lowest level of interrupt handling resides in assembly code declared as macrosin hw_irq.h and expanded in i8259.c.
Each interrupt is connected to the functiondo_IRQ, defined in irq.c.The first thing do_IRQ does is to acknowledge the interrupt so that the interruptcontroller can go on to other things. It then obtains a spinlock for the given IRQnumber, thus preventing any other CPU from handling this IRQ. It clears a coupleof status bits (including one called IRQ_WAITING that we’ll look at shortly), andthen looks up the handler(s) for this particular IRQ. If there is no handler, there’snothing to do; the spinlock is released, any pending tasklets and bottom halvesare run, and do_IRQ returns.Usually, however, if a device is interrupting there is a handler registered as well.The function handle_IRQ_event is called to actually invoke the handlers. It startsby testing a global interrupt lock bit; if that bit is set, the processor will spin until itis cleared. Calling cli sets this bit, thus blocking handling of interrupts; the normalinterrupt handling mechanism does not set this bit, and thus allows further processing of interrupts.
If the handler is of the slow variety, interrupts are reenabledin the hardware and the handler is invoked. Then it’s just a matter of cleaning up,running tasklets and bottom halves, and getting back to regular work. The ‘‘regularwork’’ may well have changed as a result of an interrupt (the handler couldwake_up a process, for example), so the last thing that happens on return from aninterrupt is a possible rescheduling of the processor.26322 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt HandlingProbing for IRQs is done by setting the IRQ_WAITING status bit for each IRQ thatcurrently lacks a handler. When the interrupt happens, do_IRQ clears that bit andthen returns, since no handler is registered.
pr obe_irq_off, when called by a driver,need only search for the IRQ that no longer has IRQ_WAITING set.Implementing a HandlerSo far, we’ve learned to register an interrupt handler, but not to write one. Actually, there’s nothing unusual about a handler—it’s ordinary C code.The only peculiarity is that a handler runs at interrupt time and therefore sufferssome restrictions on what it can do. These restrictions are the same as those wesaw with task queues. A handler can’t transfer data to or from user space, becauseit doesn’t execute in the context of a process.
Handlers also cannot do anythingthat would sleep, such as calling sleep_on, allocating memory with anything otherthan GFP_ATOMIC, or locking a semaphore. Finally, handlers cannot call schedule.The role of an interrupt handler is to give feedback to its device about interruptreception and to read or write data according to the meaning of the interruptbeing serviced. The first step usually consists of clearing a bit on the interfaceboard; most hardware devices won’t generate other interrupts until their ‘‘interruptpending’’ bit has been cleared. Some devices don’t require this step because theydon’t have an ‘‘interrupt-pending’’ bit; such devices are a minority, although theparallel port is one of them. For that reason, short does not have to clear such abit.A typical task for an interrupt handler is awakening processes sleeping on thedevice if the interrupt signals the event they’re waiting for, such as the arrival ofnew data.To stick with the frame grabber example, a process could acquire a sequence ofimages by continuously reading the device; the read call blocks before readingeach frame, while the interrupt handler awakens the process as soon as each newframe 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 minimumof time, independent of its being a fast or slow handler. If a long computationneeds to be performed, the best approach is to use a tasklet or task queue toschedule computation at a safer time (see ‘‘Task Queues’’ in Chapter 6).Our sample code in short makes use of the interrupt to call do_gettimeofday andprint the current time to a page-sized circular buffer.
It then awakens any readingprocess because there is now data available to be read.26422 June 2001 16:39http://openlib.org.uaImplementing a Handlervoid short_interrupt(int irq, void *dev_id, struct pt_regs *regs){struct timeval tv;int written;do_gettimeofday(&tv);/* Write a 16-byte record. 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));short_incr_bp(&short_head, written);wake_up_interruptible(&short_queue); /* wake any reading process */}This code, though simple, represents the typical job of an interrupt handler. It, inturn, calls short_incr_bp, which is defined as follows:static inline void short_incr_bp(volatile unsigned long *index,int delta){unsigned long new = *index + delta;barrier (); /* Don’t optimize 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 bufferwithout ever exposing an incorrect value.
By assigning only the final value andplacing a barrier to keep the compiler from optimizing things, it is possible tomanipulate the circular buffer pointers safely without locks.The device file used to read the buffer being filled at interrupt time is /dev/shortint. This device special file, together with /dev/shortprint, wasn’t introduced inChapter 8, because its use is specific to interrupt handling. The internals of/dev/shortint are specifically tailored for interrupt generation and reporting.
Writingto the device generates one interrupt every other byte; reading the device givesthe time when each interrupt was reported.If you connect together pins 9 and 10 of the parallel connector, you can generateinterrupts by raising the high bit of the parallel data byte. This can be accomplished by writing binary data to /dev/short0 or by writing anything to/dev/shortint.*The following code implements read and write for /dev/shortint.* The shortint device accomplishes its task by alternately writing 0x00 and 0xff to the parallel port.26522 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt Handlingssize_t short_i_read (struct file *filp, char *buf, size_t count,loff_t *f_pos){int count0;while (short_head == short_tail) {interruptible_sleep_on(&short_queue);if (signal_pending (current)) /* a signal arrived */return -ERESTARTSYS; /* tell the fs layer to handle it *//* else, loop */}/* count0 is the number of readable data bytes */count0 = short_head - short_tail;if (count0 < 0) /* wrapped */count0 = short_buffer + PAGE_SIZE - short_tail;if (count0 < count) count = count0;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 *buf, size_t count,loff_t *f_pos){int written = 0, odd = *f_pos & 1;unsigned long address = short_base; /* output to the paralleldata latch */if (use_mem) {while (written < count)writeb(0xff * ((++written + odd) & 1), address);} else {while (written < count)outb(0xff * ((++written + odd) & 1), address);}*f_pos += count;return written;}The other device special file, /dev/shortprint, uses the parallel port to drive aprinter, and you can use it if you want to avoid soldering a wire between pin 9and 10 of a D-25 connector.
The write implementation of shortprint uses a circularbuffer to store data to be printed, while the read implementation is the one justshown (so you can read the time your printer takes to eat each character).In order to support printer operation, the interrupt handler has been slightly modified from the one just shown, adding the ability to send the next data byte to theprinter if there is more data to transfer.26622 June 2001 16:39http://openlib.org.uaImplementing a HandlerUsing ArgumentsThough short ignores them, three arguments are passed to an interrupt handler:irq, dev_id, and regs. Let’s look at the role of each.The interrupt number (int irq) is useful as information you may print in yourlog messages, if any. Although it had a role in pre-2.0 kernels, when no dev_idexisted, dev_id serves that role much better.The second argument, void *dev_id, is a sort of ClientData; a void * argument is passed to request_irq, and this same pointer is then passed back as anargument to the handler when the interrupt happens.You’ll usually pass a pointer to your device data structure in dev_id, so a driverthat manages several instances of the same device doesn’t need any extra code inthe interrupt handler to find out which device is in charge of the current interruptevent.
Typical use of the argument in an interrupt handler is as follows:static void 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 open 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 */);/*....*/return 0;}The last argument, struct pt_regs *regs, is rarely used. It holds a snapshotof the processor’s context before the processor entered interrupt code.
The registers can be used for monitoring and debugging; they are not normally needed forregular device driver tasks.Enabling and Disabling InterruptsWe have already seen the sti and cli functions, which can enable and disable allinterrupts. Sometimes, however, it’s useful for a driver to enable and disable interrupt reporting for its own IRQ line only. The kernel offers three functions for thispurpose, all declared in <asm/irq.h>:26722 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt Handlingvoid 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 theprogrammable interrupt controller (PIC), thus disabling or enabling IRQs across allprocessors. Calls to these functions can be nested—if disable_irq is called twice insuccession, two enable_irq calls will be required before the IRQ is truly reenabled.It is possible to call these functions from an interrupt handler, but enabling yourown IRQ while handling it is not usually good practice.disable_irq will not only disable the given interrupt, but will also wait for a currently executing interrupt handler, if any, to complete.
disable_irq_nosync, on theother hand, returns immediately. Thus, using the latter will be a little faster, butmay leave your driver open to race conditions.But why disable an interrupt? Sticking to the parallel port, let’s look at the plip network interface. A 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 asfour data bits and a clock/handshake signal.