In the Driver's Seat

Device Driver. Two words that strike fear in the hearts of
programmers and users of Unix systems. It's dark, mysterious
territory filled with strange names like splx() and wakeup(). Only
the very bravest dabble here, because it's all very difficult and
dangerous.

Well, maybe.

But once you understand a little bit about device drivers, what
they do and how they work, it's not all that mysterious. And it's
not much more dangerous than just logging in as root.

Even if you have no desire to actually write device drivers, you do
need to know what they do and how the system gathers them all
together to make a Unix kernel. Having just a little knowledge here
can help you with installing other people's drivers, and sometimes
even with problems that crop up later.

At it's heart, a device driver is just a piece of software code
that manages access to a piece of hardware: a printer, a multiport
card or the hard drive, for example. What makes drivers different
from stand-alone programs is that they are actually part of the
Unix kernel: the kernel is made up from the base code that is Unix
itself plus all the driver code that tells it how to talk to the
hardware it is running on.

The driver has the responsibility of hiding the details of the
hardware from the operating system and, ultimately, from you as the
user. This is important, because hardware changes all the time.
Neither you nor Unix itself wants to work with the details of a
hard drive, for instance. You want to 'cat' a file, and Unix
translates that into "I need Block Number 3025 from the disk". How
to get block 3025 depends on a number of factors: what kind of hard
drive is this (IDE,SCSI,ESDI)? What's it's geometry (Cylinders,
Heads)? It's the driver code that has the answers to all that and
is thus able to quietly come up with the desired block.

If that were all there was too it, why not just have the kernel
know all about SCSI and IDE to start with? Build it in, why
complicate the issue? Well, we use IDE and SCSI hard drives now,
but in a few years our mass storage may be based on light!
Obviously our overworked Unix kernel can't be prepared to handle
whatever strange storage device pops up in the future. But, a
driver can be written for that device that will handle the
nitty-gritty details and give Unix what it wants: block number
4077, please.

So what make device drivers so hard to understand? Nothing, really.
All a device driver does is take data from one place and transfer
it to another. That's not the kind of task that causes anguish to
the average programmer. There are two things that make this process
difficult: programming around interrupts, and the miscellaneous
quirks of the actual hardware device itself.

I'll take up interrupts first. If you don't already understand what
a hardware interrupt is, think of it as a way for a device (such as
a printer port) to get the computer's attention. That is exactly
how they work: there is an electrical line on the CPU called INT
(for Interrupt, of course). The details of this get a little
complicated because the device doesn't use that line directly.
Instead, the "Hey, I need something" call goes through something
called a Programmable Interrupt Controller. To make it even worse,
there are actually two of these PIC's. All this complication is
necessary because (in general) each device has it's own interrupt
number. For example, COM1 (tty1A) uses Interrupt 4, lp0 uses
Interrupt 5, and so on. While it is possible for two devices to
share the same interrupt number, it is obviously much cleaner and
simpler to have a unique interrupt for each separate device.

Leaving out the gory details, a device can signal an interrupt
through the PIC's and ultimately the CPU gets tapped on the
shoulder. As part of acknowledging the tap, the CPU finds out the
interrupt number. This is where the term "Interrupt Vector" comes
into play. At this point, the CPU knows that the device that uses
interrupt 4 (for example) needs attention. That's a great piece of
news, but what's the CPU supposed to do about it? CPU's execute
instructions, so somebody had better have provided some executable
code that knows what to do with Interrupt 4! In fact, each device
driver will have provided such code, and will have specifically
identified it as being the "interrupt routine". At the time of
linking together all the device drivers, a "vector table" is
constructed that effectively says "If you get an Interrupt 4, jump
to this address and start executing code."

Taking the printer as an example, the INT line is asserted when the
printer is done printing the last character you sent to it. The
purpose behind this is so that your program (or somebody else's
program) can continue doing useful work in the time between sending
a character to the printer and the time that the printer is ready
to accept another character. The "interrupt" effectively says "I'm
done with that, send me more".