Linux Device Drivers 2nd Edition (779877), страница 65
Текст из файла (страница 65)
When the first four bits of a packetare transmitted by the initiator (the interface sending the packet), the clock line israised, causing the receiving interface to interrupt the processor. The plip handleris then invoked to deal with newly arrived data.After the device has been alerted, the data transfer proceeds, using the handshakeline 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 theparallel port).
Performance would be unbearable if the receiving interface had tohandle two interrupts for every byte received. The driver therefore disables theinterrupt during the reception of the packet; instead, a poll-and-delay loop is usedto bring in the data.Similarly, since the handshake line from the receiver to the transmitter is used toacknowledge data reception, the transmitting interface disables its IRQ line duringpacket transmission.Finally, it’s interesting to note that the SPARC and M68k implementations defineboth the disable_irq and enable_irq symbols as pointers rather than functions. Thistrick allows the kernel to assign the pointers at boot time according to the actualplatform being run.
The C-language semantics to use the function are the same onall Linux systems, independent of whether this trick is used or not, which helpsavoid some tedious coding of conditionals.26822 June 2001 16:39http://openlib.org.uaTasklets and Bottom-Half ProcessingTasklets and Bottom-Half ProcessingOne of the main problems with interrupt handling is how to perform longish taskswithin a handler. Often a substantial amount of work must be done in response toa device interrupt, but interrupt handlers need to finish up quickly and not keepinterrupts blocked for long.
These two needs (work and speed) conflict with eachother, leaving the driver writer in a bit of a bind.Linux (along with many other systems) resolves this problem by splitting the interrupt handler into two halves. The so-called top half is the routine that actuallyresponds to the interrupt—the one you register with request_irq. The bottom halfis a routine that is scheduled by the top half to be executed later, at a safer time.The use of the term bottom half in the 2.4 kernel can be a bit confusing, in that itcan mean either the second half of an interrupt handler or one of the mechanismsused to implement this second half, or both.
When we refer to a bottom half weare speaking generally about a bottom half; the old Linux bottom-half implementation is referred to explicitly with the acronym BH.But what is a bottom half useful for?The big difference between the top-half handler and the bottom half is that allinterrupts are enabled during execution of the bottom half—that’s why it runs at asafer time. In the typical scenario, the top half saves device data to a device-specific buffer, schedules its bottom half, and exits: this is very fast. The bottom halfthen performs whatever other work is required, such as awakening processes,starting up another I/O operation, and so on. This setup permits the top half toservice a new interrupt while the bottom half is still working.Every serious interrupt handler is split this way.
For instance, when a networkinterface reports the arrival of a new packet, the handler just retrieves the data andpushes it up to the protocol layer; actual processing of the packet is performed ina bottom half.One thing to keep in mind with bottom-half processing is that all of the restrictions that apply to interrupt handlers also apply to bottom halves. Thus, bottomhalves cannot sleep, cannot access user space, and cannot invoke the scheduler.The Linux kernel has two different mechanisms that may be used to implementbottom-half processing. Tasklets were introduced late in the 2.3 developmentseries; they are now the preferred way to do bottom-half processing, but they arenot portable to earlier kernel versions.
The older bottom-half (BH) implementationexists in even very old kernels, though it is implemented with tasklets in 2.4. We’lllook at both mechanisms here. In general, device drivers writing new code shouldchoose tasklets for their bottom-half processing if possible, though portability considerations may determine that the BH mechanism needs to be used instead.26922 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt HandlingThe following discussion works, once again, with the short driver. When loadedwith a module option, short can be told to do interrupt processing in a top/bottom-half mode, with either a tasklet or bottom-half handler. In this case, the tophalf executes quickly; it simply remembers the current time and schedules the bottom half processing.
The bottom half is then charged with encoding this time andawakening any user processes that may be waiting for data.TaskletsWe have already had an introduction to tasklets in Chapter 6, so a quick reviewshould suffice here. Remember that tasklets are a special function that may bescheduled to run, in interrupt context, at a system-determined safe time. They maybe scheduled to run multiple times, but will only run once. No tasklet will everrun in parallel with itself, since they only run once, but tasklets can run in parallelwith other tasklets on SMP systems.
Thus, if your driver has multiple tasklets, theymust employ some sort of locking to avoid conflicting with each other.Tasklets are also guaranteed to run on the same CPU as the function that firstschedules them. An interrupt handler can thus be secure that a tasklet will notbegin executing before the handler has completed. However, another interrupt cancertainly be delivered while the tasklet is running, so locking between the taskletand the interrupt handler may still be required.Tasklets must be declared with the DECLARE_TASKLET macro:DECLARE_TASKLET(name, function, data);name is the name to be given to the tasklet, function is the function that iscalled to execute the tasklet (it takes one unsigned long argument and returnsvoid), and data is an unsigned long value to be passed to the tasklet function.The short driver declares its tasklet as follows:void short_do_tasklet (unsigned long);DECLARE_TASKLET (short_tasklet, short_do_tasklet, 0);The function tasklet_schedule is used to schedule a tasklet for running.
If short isloaded with tasklet=1, it installs a different interrupt handler that saves dataand schedules the tasklet as follows:void short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs){do_gettimeofday((struct timeval *) tv_head); /* cast to stop’volatile’ warning */short_incr_tv(&tv_head);tasklet_schedule(&short_tasklet);short_bh_count++; /* record that an interrupt arrived */}27022 June 2001 16:39http://openlib.org.uaTasklets and Bottom-Half ProcessingThe actual tasklet routine, short_do_tasklet, will be executed shortly at the system’sconvenience.
As mentioned earlier, this routine performs the bulk of the work ofhandling the interrupt; it looks like this:void short_do_tasklet (unsigned long unused){int savecount = short_bh_count, written;short_bh_count = 0; /* we have already been removed from queue *//** The bottom half reads the tv array, filled by the top half,* and prints it to the circular text buffer, which is then consumed* by reading processes*//* First write the number of interrupts that occurred beforethis bh */written = sprintf((char *)short_head,"bh after %6i\n",savecount);short_incr_bp(&short_head, written);/** Then, write the time values. Write exactly 16 bytes at a time,* so it aligns with PAGE_SIZE*/do {written = sprintf((char *)short_head,"%08u.%06u\n",(int)(tv_tail->tv_sec % 100000000),(int)(tv_tail->tv_usec));short_incr_bp(&short_head, written);short_incr_tv(&tv_tail);} while (tv_tail != tv_head);wake_up_interruptible(&short_queue); /* wake any reading process */}Among other things, this tasklet makes a note of how many interrupts have arrivedsince it was last called.
A device like short can generate a great many interrupts ina brief period, so it is not uncommon for several to arrive before the bottom half isexecuted. Drivers must always be prepared for this possibility, and must be able todetermine how much work there is to perform from the information left by the tophalf.The BH MechanismUnlike tasklets, old-style BH bottom halves have been around almost as long asthe Linux kernel itself. They show their age in a number of ways. For example, allBH bottom halves are predefined in the kernel, and there can be a maximum of32 of them. Since they are predefined, bottom halves cannot be used directly bymodules, but that is not actually a problem, as we will see.27122 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt HandlingWhenever some code wants to schedule a bottom half for running, it callsmark_bh.
In the older BH implemention, mark_bh would set a bit in a bit mask,allowing the corresponding bottom-half handler to be found quickly at runtime. Inmodern kernels, it just calls tasklet_schedule to schedule the bottom-half routinefor execution.Marking bottom halves is defined in <linux/interrupt.h> asvoid mark_bh(int nr);Here, nr is the ‘‘number’’ of the BH to activate. The number is a symbolic constant defined in <linux/interrupt.h> that identifies the bottom half to run.The function that corresponds to each bottom half is provided by the driver thatowns the bottom half.
For example, when mark_bh(SCSI_BH) is called, thefunction being scheduled for execution is scsi_bottom_half_handler, which is partof the SCSI driver.As mentioned earlier, bottom halves are static objects, so a modularized driverwon’t be able to register its own BH. There’s no support for dynamic allocation ofBH bottom halves, and it’s unlikely there ever will be. Fortunately, the immediatetask queue can be used instead.The rest of this section lists some of the most interesting bottom halves. It thendescribes how the kernel runs a BH bottom half, which you should understand inorder to use bottom halves properly.Several BH bottom halves declared by the kernel are interesting to look at, and afew can even be used by a driver, as introduced earlier.
These are the most interesting BHs:IMMEDIATE_BHThis is the most important bottom half for driver writers. The function beingscheduled runs (with run_task_queue) the tq_immediate task queue. Adriver (like a custom module) that doesn’t own a bottom half can use theimmediate queue as if it were its own BH. After registering a task in thequeue, the driver must mark the BH in order to have its code actually executed; how to do this was introduced in “The immediate queue,” in Chapter 6.TQUEUE_BHThis BH is activated at each timer tick if a task is registered in tq_timer. Inpractice, a driver can implement its own BH using tq_timer. The timerqueue introduced in “The timer queue” in Chapter 6 is a BH, but there’s noneed to call mark_bh for it.TIMER_BHThis BH is marked by do_timer, the function in charge of the clock tick.