Systems Programming

Device Drivers

Sources for this discussion:
  1. George Pajari. Writing UNIX Device Drivers. Addison-Wesley. 1991.
  2. Alessandro Rubini and Jonathan Corbet. Linux Device Drivers, 2nd edition. O'Reilly. 2001. (There is a 3rd edition available now.)
  3. Linux online documentation.
Device drivers are pieces of software that mediate between peripheral hardware and the rest of the system. Except for the CPU(s), memory, and some hardware counters, virtually every piece of hardware connected to the computer has a driver associated with it. Device drivers are part of the operating system, and so they are organized rather differently from regular appications.

Discussing device drivers is diffucult because, by their nature, they are specific to particular hardware, the internals of the operating system, and also to the details of how the hardware is connected to the system. For example, a serial connector (RS232, say) on your computer is typically supported by a chip called a UART (Universal Asychronous Receiver/Transmitter) or USART (Universal Synchrounous/Asychronous Receiver/Transmitter). You probably saw these chips in CS240. Each chip contains a receiver shift register and a transmitter shift register plus some control pins (including an all-important clock). A serial device driver has to know how to control these things, how to tell if the chip is ready to transmit, whether there is data that has been read, etc. But that's not enough. Is the chip wired into the computer so that it appears at a particular physical memory address (i.e., is it memory mapped), or is there a separate address space for I/O devices (a separate bus on the mother board or particular I/O control lines on the system bus)? These choices, in turn depend on the particular CPU and a variety of industry standards. When you've answered these questions and written a device driver, the likelihood that the code will be portable to another machine is slim (though standards help a lot). And the device knowledge transfer to a SCSI disk driver project is very small.

The OS side of the driver story is a little better. Operating systems are generally modularized so that there are a small number of internal device driver interfaces through which particular drivers interact with the rest of the OS.

In some operating systems, you can install a driver and reboot: the OS configures its device support at boot-time. The Unix tradition has been that you had to recompile some of the OS or at least relink the Unix kernel with the new driver to make a new vmunix file (the OS kernel executable), and then reboot.

Linux has improved the situation quite a bit. Linux supports something it calls modules, which are operating system components that can be installed either at boot time or dynamically while the system is running. (man insmod). Device drivers are one kind of Linux module.

Unix kernels distinguish different classes of driver depending on the kind of interaction the device will have with the kernel:

Device driver functionality is normally invoked via the VFS, that is, there is a file system abstraction between the device driver and the application code. This works very nicely for typical I/O operations like read() and write(). However, many devices offer functionality that is not readily mapped into file system operations. For example, a mouse driver can adjust a mouse's sensitivity, a display driver can often alter the contrast and resoultion of a device. There is a standard Unix way to talk to the driver without going through the file system abstraction: ioctl() takes a file descriptor, a device-specific request, and parameters.

We're not in Kansas Anymore

Devide drivers are part of the kernel, and are not application programs. That means

Device drivers typically need exclusive access to their I/O ports or I/O memory. How this is acheived is highly OS-specific. Linux has a registration scheme that we won't go into. However, you can see what is registered where by looking at /proc/ioports and /proc/iomem.

Some Linux Details

As I said above, previous Unix systems required new drivers to be statically linked to produce a new kernel which was then rebooted. The Linux module system works by having the insmod command invoke the module's init() function (using init_module()). This function registers the module's entry points with the operating system. The rmmod command invokes the cleanup_module() function, which calls the modules cleanup() function which unregisters the module's entry points.

Registration is important: The way drivers work is they export certain facilties. There are a wide variety of facilities (like serial ports). A registration request consists of the name of the facility and a pointer to a structure that depends on the facility. This structure will contain pointers to functions defined in the driver. Just as we saw with the VFS, the OS will use a kind of object oriented method dispatch to invoke the driver function.

In fact, a driver must export entry points corresponding to the file operations permitted on that type of device. The VFS file operations will ultimately point to driver entry points.

To get kernel and module functionality in Linux, you must define the symbols __KERNEL__ and MODULE and then use appropriate include files from the kernel source tree (usually in /usr/src/linux/include. It is wise to allow your code to configure itself for multiprocessors if appropriate:


  #include <linux/config.h>
  #ifdef CONFIG_SMP
  #  define __SMP__
  #endif
      

Example Drivers

Drivers typically involve several parts:

Both the texts above have relatively simple examples. Rubini and Corbet have a bunch of online examples. However, I thought it might be fun to peek a couple of drivers loaded on our system. It's best to start with character drivers, since they are the simplest. One example is the Linux driver for the MacIntosh mouse. Another supports serial I/O.


Author: Mark A. Sheldon
Modified: 09 May 2005