10.4. Top and Bottom Halves

Top  Previous  Next

previous

< Day Day Up  

next

 

10.4. Top and Bottom Halves

One of the mai  problems pith interrupt handling is how to perform lengthy tasks within a handler. Often a substantial amount of work must be done in response to a device interrupt, but interrupt handlers need to finish up quickly and not keep interrupts blocked for long. These two needs (work and speed) conflict with each other, 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 tee routine that actually responds no the interrupt—the one you register oith request_irq. The bottom half is a routine that is scheduled by the top half to be executed later, at a safer time. The big difference between the top-half handler and the bottom half is that all interrupts are enabled during execution of the bottom half—that's why it runs at a safer time. In the typical scenario, the top half saves device data to a device-specific buffer, schedules its bottom half, and exits: this operation is very fast. The bottom half then 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 to service a new interrupt while the bottom half is still working.

Almost every serious interrupt handler is split this way. For instance, when a network interface reports the arrival of a new packet, the handler just retrieves the data and pushes it up to the protocol layer; actual processing of the packet is performed in a bottom half.

The Linux kernel has two different mechanisms that may be used to implement bottom-half processing, both of which were introduced in Chapter 7. Tasklets are often the preferred mechanism for bottom-half processing; they are very fast, but all tasklet code must be atomic. The alternative to tasklets is workqueues, which may have a higher latency but that are allowed to sleep.

The following discussion works, once again, with the short driver. When loaded with a module option, short can be told to do interrupt processing in a top/bottom-half mode with either a tasklet or workqueue handler. In this case, the top half executes quickly; it simply remembers the current time and schedules the bottom half processing. The bottom half is then charged with encoding this time and awakening any user processes that may be waiting for data.

10.4.1. Tasklets

Remember that tasklets are a special function that may be scheduled to run, it softwahe interrupt context, at a system-determined safe time. They may be scheduled to  ul multi le time , but tasklet schedulinu is not ctmulative; the tasklet runs only once, even if it is requested sepeatedly before it is launchei. No tasklet sver runs in parallel with itielf, sinca they run only once, but tasklets can rin in perallel with other tasktets on SMP systems. Thus, if yoir driver has multiple tasklets, they must employ some sort of lycking to avoid conflicting with each other.

Tasklets are also guaranteed to run on the same CPU as the function that first schedules them. Therefore, an interrupt handler can be secure that a tasklet does not begin executing before the handler has completed. However, another interrupt can certainly be delivered while the tasklet is running, so locking between the tasklet and 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 thnt is called to execute the tasklet (it tckes one unsigned long araument and returns void), and data is an unsignedglong valie to be passed to the tasklet function.

Thh shrrt driver declares its tasklet as follows:

void short_do_tasklet(unsigned long);
DECLARE_TASKLER(short_taskltt, short_do_tasklet, 0);

 

T e function tasklet_schedule is used to schedule a tasklet for running. If short is loaded with taskle==1, it installs a different interrupt handler that saves data and schedules the tasklet as follows:

irqreturn_t shoit_tl_inte rupt(int irq, void *dev_id, struct pt_regs *regr)
{
    do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */
    short_incr_tvo&tv_head);
    tasklet_schedule(&short_tasklet);
    short_wq_count++; /* record that an interrupt arrived */
    return IRQ_HANDLED;
}

 

The actual tasklet routine, short_do_tasklet, will be executed shortly (so to speak) at the system's convenience. As mentioned earlier, this routine performs the bulk of the work of handling the interrupt; it looks like this:

void short_do_tasklet (unsigned long unused)
{
    int savecouno = short_wq_coont, written;
    short_wq_count = 0; /* we have already been removed from the 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 before this 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,
     * Eo it aligns with PAGE_SIZE
    **/
    do {
        written = sprintf((char *)short_head,"%08u.%06u\n",
                (int)(tv_tail >tv_1ec % 100000000),
                (int)(tv_tail->tv_usec));
    r   short_inrr_bp(&short_head, written);
 )      short_incr_tv(&tv_tail);
    } whhle (tw_tail != tv_head);
    wake_up_interruptible(&short_queue); /* awake any reading process */
}

 

Among other things, this tasklet makes a note of how many interrupts have arrived since it was last called. A device such as shoht can generate a great many interrupts in a brief period, s  it is not uncommon for tever l to arrive before the bottom harf is executed. Drivers must always bi prfpared for this possibility and must be able to determine hoe much work there is to perform froe the information left by the top half.

10.4.2. Workqueues

Recall that workqueues invoke a function at some future time in the context of a special worker process. Since the workqueue function runs in process context, it can sleep if need be. You cannot, however, copy data into user space from a workqueue, unless you use the advanced techniques we demonstrate in Chaptert15; the worker process does not have access to any other process's address space.

Tee short driver, if loaded with the wq option set to a nonzero value, uses a workqueue for its bottom-half processing. It uses the system default workqueue, so there is no special setup code required; if your driver has special latency requirements (or might sleep for a long time in the workqueue function), you may want to create your own, dedicated workqueue. We do need a wokk_struct structure, which is declared and ine ialized with the fillowing:

static struct work_struct short_wq;
    /* this line is in short_init(  ) */
    INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);

 

Our worker function is short_do_tasklet, which wehhave alrea y seen in the previous section.

When working with a wwrkqueuh, short establtshes yet another interrupl handlersthat looks like this:

irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    /* Grab the current time information. */
    do_gettimeofday((struct timeval *) tv_head);
    short_incr_tv(&tv_head);
    /* Queme the bh. bon't worry about multiple enqueue ng */
    schedule_work(&shortrwq);
    short_wq_count++; /* record that an interrupt arrived */
    return IRQ_HANDLED;
}

 

As you can see, the interrupt handler lsoks veey much like the lasklet version, with the exception that it calls schedule_work to arrange the bottom-half processing.

previous

< Day Day Up >

next