Block Device Drivers: Optimization
Optimization is almost easy to write about, and sounds glamorous, but error handling is really where the rubber meets the road. I'm not even going to try to really cover error handling in this column. It is a very complex subject, and different for each piece of hardware. If you have read this far, you should have the basic knowledge you need to start reading the block device drivers in the kernel. By reading all the error handling there, you will begin to understand how to do error handling nicely. Consider reading the drivers in this sequence: ramdisk.c, hd.c, and floppy.c.
As you write your driver, you will probably start with very simple error handling. As you use your driver, you are likely to discover more error cases to handle. Sometimes you will find that you need to redesign some components of your driver to get error handling correct. As an example, until very recently, error handling in the floppy driver was not very good. You could mount a write-protected floppy read-write and cause serious problems when you then tried to write to the floppy. Also, if you tried (for instance) to write a tar archive on a write-protected floppy, you would get a stream of errors as the driver reset the floppy drive and kept assuming the error would go away. It took a significant rewrite of the floppy driver to solve that problem correctly.
This isn't really specific to block device drivers, but it is certainly necessary to know. In issue 9, I gave an example initialization function, but it was mostly pseudo-code and didn't cover many of the things that you need to do to work with real devices. For instance, it doesn't explain how to grab a DMA channel, nor does it explain how to grab an IRQ line.
The easiest way to deal with IRQ and DMA is to allocate both the IRQ line and DMA channel while initializing the device. It's not the best way, but it is the easiest way to start out while you are writing your device driver. When you want to figure out exactly when to allocate and free them, you can read other device drivers. The floppy device driver, for instance, has functions floppy_grab_irq_and_dma() and floppy_release_irq_and_dma() which do exactly what they say, and are used not only in the initialization code, but all through the rest of the driver.
The floppy_grab_irq_and_dma() function is a good place to start to learn how to allocate IRQ lines and DMA channels. According to <asm/dma.h>, the IRQ line should be allocated first and released last.
We'll look at IRQs first. request_irq() takes four arguments. The first argument to request_irq() is the number of the IRQ line to allocate. The second is the interrupt service routine to call when an interrupt is received. The third is a flag which is either set to SA_INTERRUPT or something else (presumably 0), which determines whether the argument passed to the interrupt service routine is a pointer to a register structure (0) or the number of the interrupt (SA_INTERRUPT), and also whether the interrupt is a “fast” interrupt handler (SA_INTERRUPT) or a “slow” interrupt handler (0). A “slow” interrupt handler is one where more processing is done when the interrupt handler returns, including possibly running the scheduler to choose a new process to become the active one. A “fast” interrupt handler does as little as possible. The fourth argument is the name of the device driver.
request_irq() is simpler. It takes two arguments, the first of which is the IRQ channel, and the second of which is the name of the device driver.
The corresponding freeing functions are even simpler. free_dma() takes only the number of the DMA channel, and free_irq() takes only the number of the IRQ.
Of course, there is far more to using IRQs, and even more so to using DMA, than allocating and freeing lines and channels, but this is a start. The start, to be pedantic. Read <asm/dma.h>, kernel/dma.c, <asm/irq.h>, and kernel/irq.c for details; they are very readable, and have many useful comments.
K. D. Nguyen sent me some e-mail after reading the January issue of Linux Journal, echoing a wish I have heard from other people as well.
I have been reading two books on Unix device drivers, the KHG, and the Kernel Korner articles since last issue. I feel like I can write some device drivers. But unfortunately, there seems to be something missing from all the books and articles about Unix device drivers. It is the lack of a practice environment. We, the device driver beginners, can only read and look at some device driver code under Linux and try to understand how they work. It would be more fun if there were some hardware or device kit that let us really do some exercise on what we just read about writing Unix device drivers (rather than buying a new color printer and then begging the manufacture for the specs to write a new challenging device driver). Of course, for the meantime, I will keep reading.
There is no real practice environment. The easiest way to start learning is to write a ramdisk driver. Beyond that, many real devices are actually fairly simple. Dive in! It's hard to dabble at the water's edge when you are writing kernel code for a monolithic OS, regardless of whether you are writing code for a simple ramdisk or for a toy device kit or for a real device. The learning curve is quite steep, but that means that in a short time of strenuous learning, you really pick up most of what you need to write basic device drivers.
Also, what features would a practice device support, and what would it do? It's a hard question to answer and one I'm not going to attempt—and I think that manufacturers won't either. Since you are as likely to screw up your entire system writing a driver for a practice device as for a real one, you might as well work on a real device. There are real devices as simple as any toy device.
Michael K. Johnson is the editor of Linux Journal, and is also the author of the Linux Kernel Hackers' Guide (the KHG). He is using this column to develop and expand on the KHG.