⇦ prev | ⇱ home | next ⇨ |
10.6. Interrupt-Driven I/OWhenever a data transfer to or from the managed hardware might be delayed for any reason, the driver writer should implement buffering. Data buffers help to detach data transmission and reception from the write and read system calls, and overall system performance benefits. A good buffering mechanism leads to interrupt-driven I/O, in which an input buffer 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 at interrupt time. An example of interrupt-driven output is the implementation of /dev/shortprint. For interrupt-driven data transfer to happen successfully, the hardware should be able to generate interrupts with the following semantics:
The timing relationships between a read or write and the actual arrival of data were introduced in Section 6.2.3 in Chapter 6. 10.6.1. A Write-Buffering ExampleWe have mentioned the shortprint driver a couple of times; now it is time to actually take a look. This module implements a very simple, output-oriented driver for the parallel port; it is sufficient, however, to enable the printing of files. If you chose to test this driver out, however, remember that you must pass the printer a file in a format it understands; not all printers respond well when given a stream of arbitrary data. The shortprint driver maintains a one-page circular output buffer. When a user-space process writes data to the device, that data is fed into the buffer, but the write method does not actually perform any I/O. Instead, the core of shortp_write looks like this: while (written < count) { /* Hang out until some buffer space is available. */ space = shortp_out_space( ); if (space <= 0) { if (wait_event_interruptible(shortp_out_queue, (space = shortp_out_space( )) > 0)) goto out; } /* Move data into the buffer. */ if ((space + written) > count) space = count - written; if (copy_from_user((char *) shortp_out_head, buf, space)) { up(&shortp_out_sem); return -EFAULT; } shortp_incr_out_bp(&shortp_out_head, space); buf += space; written += space; /* If no output is active, make it active. */ spin_lock_irqsave(&shortp_out_lock, flags); if (! shortp_output_active) shortp_start_output( ); spin_unlock_irqrestore(&shortp_out_lock, flags); } out: *f_pos += written; A semaphore (shortp_out_sem) controls access to the circular buffer; shortp_write obtains that semaphore just prior to the code fragment above. While holding the semaphore, it attempts to feed data into the circular buffer. The function shortp_out_space returns the amount of contiguous space available (so there is no need to worry about buffer wraps); if that amount is 0, the driver waits until some space is freed. It then copies as much data as it can into the buffer. Once there is data to output, shortp_write must ensure that the data is written to the device. The actual writing is done by way of a workqueue function; shortp_write must kick that function off if it is not already running. After obtaining a separate spinlock that controls access to variables used on the consumer side of the output buffer (including shortp_output_active), it calls shortp_start_output if need be. Then it's just a matter of noting how much data was "written" to the buffer and returning. The function that starts the output process looks like the following: static void shortp_start_output(void) { if (shortp_output_active) /* Should never happen */ return; /* Set up our 'missed interrupt' timer */ shortp_output_active = 1; shortp_timer.expires = jiffies + TIMEOUT; add_timer(&shortp_timer); /* And get the process going. */ queue_work(shortp_workqueue, &shortp_work); } The reality of dealing with hardware is that you can, occasiona
|