Linux Device Drivers 2nd Edition (779877), страница 67
Текст из файла (страница 67)
A shared handler must be able to distinguish between interruptsthat it needs to handle and interrupts generated by other devices.Loading short with the option shared=1 installs the following handler instead ofthe default:void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs){int value, written;struct timeval tv;/* If it wasn’t short, return immediately */value = inb(short_base);if (!(value & 0x80)) return;/* clear the interrupting bit */outb(value & 0x7F, short_base);27622 June 2001 16:39http://openlib.org.uaInterrupt Sharing/* the rest is unchanged */do_gettimeofday(&tv);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 */}An explanation is due here.
Since the parallel port has no ‘‘interrupt-pending’’ bitto check, the handler uses the ACK bit for this purpose. If the bit is high, the interrupt being reported is for short, and the handler clears the bit.The handler resets the bit by zeroing the high bit of the parallel interface’s dataport —short assumes that pins 9 and 10 are connected together. If one of the otherdevices sharing the IRQ with short generates an interrupt, short sees that its ownline is still inactive and does nothing.A full-featured driver probably splits the work into top and bottom halves, ofcourse, but that’s easy to add and does not have any impact on the code thatimplements sharing. A real driver would also likely use the dev_id argument todetermine which, of possibly many, devices might be interrupting.Note that if you are using a printer (instead of the jumper wire) to test interruptmanagement with short, this shared handler won’t work as advertised, because theprinter protocol doesn’t allow for sharing, and the driver can’t know whether theinterrupt was from the printer or not.The /proc InterfaceInstalling shared handlers in the system doesn’t affect /pr oc/stat, which doesn’teven know about handlers.
However, /pr oc/interrupts changes slightly.All the handlers installed for the same interrupt number appear on the same lineof /pr oc/interrupts. The following output shows how shared interrupt handlers aredisplayed:0:1:2:5:9:10:12:13:15:NMI:LOC:ERR:CPU0221142161354010516207603104504603721136755544117004441169870CPU122002860136582051600390312222471747013223984411700444116986IO-APIC-edgeIO-APIC-edgeXT-PICIO-APIC-levelIO-APIC-levelIO-APIC-levelIO-APIC-edgeXT-PICIO-APIC-edgetimerkeyboardcascadeeth0acpi, es1370aic7xxxPS/2 Mousefpuide127722 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt HandlingThe shared interrupt line here is IRQ 9; the active handlers are listed on one line,separated by commas.
Here the power management subsystem (‘‘acpi’’) is sharingthis IRQ with the sound card (‘‘es1370’’). The kernel is unable to distinguish interrupts from these two sources, and will invoke each interrupt handlers in the driverfor each interrupt.Interrupt-Driven I/OWhenever a data transfer to or from the managed hardware might be delayed forany reason, the driver writer should implement buffering. Data buffers help todetach data transmission and reception from the write and read system calls, andoverall system performance benefits.A good buffering mechanism leads to interrupt-driven I/O, in which an inputbuffer is filled at interrupt time and is emptied by processes that read the device;an output buffer is filled by processes that write to the device and is emptied atinterrupt time.
An example of interrupt-driven output is the implementation of/dev/shortint.For interrupt-driven data transfer to happen successfully, the hardware should beable to generate interrupts with the following semantics:•For input, the device interrupts the processor when new data has arrived andis ready to be retrieved by the system processor. The actual actions to performdepend on whether the device uses I/O ports, memory mapping, or DMA.•For output, the device delivers an interrupt either when it is ready to acceptnew data or to acknowledge a successful data transfer. Memory-mapped andDMA-capable devices usually generate interrupts to tell the system they aredone with the buffer.The timing relationships between a read or write and the actual arrival of datawere introduced in “Blocking and Nonblocking Operations”, in Chapter 5.
Butinterrupt-driven I/O introduces the problem of synchronizing concurrent access toshared data items and all the issues related to race conditions. The next sectioncovers this related topic in some depth.Race ConditionsWe have already seen race conditions come up a number of times in the previouschapters. Whereas race conditions can happen at any time on SMP systems,uniprocessor systems, to this point, have had to worry about them rather less.** Note, however, that the kernel developers are seriously considering making all kernelcode preemptable at almost any time, making locking mandatory even on uniprocessorsystems.27822 June 2001 16:39http://openlib.org.uaRace ConditionsInterrupts, however, can bring with them a whole new set of race conditions, evenon uniprocessor systems. Since an interrupt can happen at any time, it can causethe interrupt handler to be executed in the middle of an arbitrary piece of drivercode.
Thus, any device driver that is working with interrupts—and that is most ofthem — must be very concerned with race conditions. For this reason, we lookmore closely at race conditions and their prevention in this chapter.Dealing with race conditions is one of the trickiest aspects of programming,because the related bugs are subtle and very difficult to reproduce, and it’s hard totell when there is a race condition between interrupt code and the driver methods.The programmer must take great care to avoid corruption of data or metadata.Different techniques can be employed to prevent data corruption, and we willintroduce the most common ones.
We won’t show complete code because the bestcode for each situation depends on the operating mode of the device beingdriven, and on the programmer’s taste. All of the drivers in this book, however,protect themselves against race conditions, so examples can be found in the sample code.The most common ways of protecting data from concurrent access are as follows:•Using a circular buffer and avoiding shared variables•Using spinlocks to enforce mutual exclusion•Using lock variables that are atomically incremented and decrementedNote that semaphores are not listed here. Because locking a semaphore can put aprocess to sleep, semaphores may not be used in interrupt handlers.Whatever approach you choose, you still need to decide what to do when accessing a variable that can be modified at interrupt time.
In simple cases, such a variable can simply be declared as volatile to prevent the compiler fromoptimizing access to its value (for example, it prevents the compiler from holdingthe value in a register for the whole duration of a function). However, the compiler generates suboptimal code whenever volatile variables are involved, soyou might choose to resort to some sort of locking instead. In more complicatedsituations, there is no choice but to use some sort of locking.Using Circular BuffersUsing a circular buffer is an effective way of handling concurrent-access problems;the best way to deal with concurrent access is to perform no concurrent accesswhatsoever.The circular buffer uses an algorithm called ‘‘producer and consumer’’: one playerpushes data in and the other pulls data out.
Concurrent access is avoided if there27922 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt Handlingis exactly one producer and exactly one consumer. There are two examples ofproducer and consumer in short. In one case, the reading process is waiting toconsume data that is produced at interrupt time; in the other, the bottom half consumes data produced by the top half.Two pointers are used to address a circular buffer: head and tail. head is thepoint at which data is being written and is updated only by the producer of thedata. Data is being read from tail, which is updated only by the consumer. Asmentioned earlier, if data is written at interrupt time, you must be careful whenaccessing head multiple times. You should either declare it as volatile or usesome sort of locking.The circular buffer runs smoothly, except when it fills up.