Block Device Drivers: Interrupts

Last month, we gave an introduction to block device drivers. This month, we look at some tricks that are useful when writing block device drivers, starting with the most basic “trick” of using hardware interrupts where available and describing some neat infrastructure that block device drivers can take advantage of by adding five lines of code and one function.

I am going to take the foo driver I started developing last month, and add interrupt service to it. It is hard to write good, detailed code for a hypothetical and vaguely defined device, so (as usual) if you want to understand better after reading this, take a look at some real devices. I suggest the hd and floppy devices; start from the do_hd_request() and do_fd_request() routines and follow the logic through.

static void do_foo_request(void) {
  if (foo_busy)
    /* another request is being processed;
       this one will automatically follow */
    foo_busy = 1;
static void foo_initialize_io(void) {
  if (CURRENT->cmd == READ) {
  } else {
    /* send hardware command to start io
       based on request; just a request to
       read if read and preparing data for
       entire write; write takes more code */
static void foo_read_intr(void) {
  int error=0;
    /* read data from device and put in
       CURRENT->buffer; set error=1 if error
       This is actually most of the function... */
    /* successful if no error */
    if (!CURRENT)
      /* allow new requests to be processed */
      foo_busy = 0;
    /* INIT_REQUEST will return if no requests */
    /* Now prepare to do I/O on next request */
static void foo_write_intr(void) {
        int error=0;
  /* data has been written. error=1 if error */
  /* successful if no error */
  if (!CURRENT)
    /* allow new requests to be processed */ foo_busy = 0;
/* INIT_REQUEST will return if no requests */
  /* Now prepare to do I/O on next request */

In blk.h, we need to add a few lines to the FOO_MAJOR section:

#define DEVICE_NAME "foobar"
#define DEVICE_REQUEST do_foo_request
#define DEVICE_INTR do_foo
#define DEVICE_NR(device) (MINOR(device) > 6)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)

Note that many functions are missing from this; this is the important part to understanding interrupt-driven device drivers; the “heart”, if you will. Also note that, obviously, I haven't tried to compile or run this hypothetical driver. I may have made some mistakes—you are bound to make mistakes of your own while writing your driver, and finding bugs in this skeleton will be good practice for finding bugs in your own driver, if you are so inclined. I do suggest that when you write your own driver, you start with code from some other working driver rather than starting from this skeleton code.


An explanation of some of the new ideas here is in order. The first new idea is (obviously, I hope) the use of interrupt routines to do part of servicing the hardware and walking down the request list. I used separate routines for reading and writing; this isn't fundamentally necessary, but it does generally help allow cleaner code and smaller, easier-to-read interrupt service routines. Most (all?) of the interrupt-driven device drivers of any kind in the real kernel use separate routines for reading and writing.

We also have a separate routine to do most of the I/O setup instead of doing it in the request() procedure. This is so that the interrupt routines can call the separate routings to set up the next request, if necessary, upon completion of a request. Again, this is a design feature that makes most real-world drivers smaller and easier to write and debug.


It must be noted that any routine that is called from an interrupt is different than all the other routines I have described so far. Routines called from an interrupt do not execute in the context of any calling user-level program and cannot write to user-level memory. They can only write to kernel memory. If they absolutely need to allocate memory, they must do so with the GFP_ATOMIC priority. In general, it is best for them to write into buffers allocated from user-process-context routines with priority GFP_KERNEL before the interrupt routines are set up. They also can wake up processes sleeping on an event, as end_request() does, but they cannot sleep themselves.


The header file blk.h provides some nice macros which are used here. I won't document them all (most are documented in The Linux Kernel Hackers' Guide, the KHG), but I will mention the ones I use, which are used to manage interrupts.

Instead of setting up interrupts manually, it is easier and better to use the SET_INTR() macro. (If you want to know how to set them up manually, read the definitions of SET_INTR in blk.h.) Easier because you just do SET_INTR(interrupt_handling_function), and better because if you set up automatic timeouts (which we will cover later), SET_INTR() automatically sets them up.

Then, when the interrupt has been serviced, the interrupt service routine (foo_read_intr() or foo_write_intr() above) clears the interrupt, so that spurious interrupts don't get delivered to a procedure that thinks that it is supposed to read or write to the current request. It is possible—it only takes a little more work—to provide an interrupt routing to handle spurious interrupts. If you are interested, read the hd driver.


Geek Guide
The DevOps Toolbox

Tools and Technologies for Scale and Reliability
by Linux Journal Editor Bill Childers

Get your free copy today

Sponsored by IBM

8 Signs You're Beyond Cron

Scheduling Crontabs With an Enterprise Scheduler
On Demand
Moderated by Linux Journal Contributor Mike Diehl

Sign up and watch now

Sponsored by Skybot