Now that we've taken all this time to establish the necessary principles of kernel programming to the point where you can write and load some simple modules and do some basic debugging, this is the first lesson in how to write a simple character driver and let's start by giving credit where credit is due since I'm getting some of this material from the classic book "Linux Device Drivers (3rd ed)" -- I'm simply updating some of that material and will be presenting it in bite-size pieces so that you can write your first character driver a bit at a time, and test it that way as well.
Note also that writing a character driver is part of this first course in kernel programming, but writing more specific drivers like a PCI or USB driver will come as separate courses some time down the road, if that's what you're interested in.
And so, to work.
As the authors of LDD3 explain it:
The goal of this chapter is to write a complete char device driver. We develop a character driver because this class is suitable for most simple hardware devices. Char drivers are also easier to understand than block drivers or network drivers (which we get to in later chapters).
Ergo, we're starting there, and also because much of what happens here will also apply to writing other, more complicated, types of drivers.
If you've worked with Linux much at all, you've already run across the
/dev directory of special device files, so let's clarify what's in there:
$ ls -l /dev crw-rw----+ 1 root audio 14, 12 2010-07-07 16:05 adsp crw------- 1 root video 10, 175 2010-07-07 16:05 agpgart crw-rw----+ 1 root audio 14, 4 2010-07-07 16:05 audio drwxr-xr-x 2 root root 660 2010-07-07 16:05 block drwxr-xr-x 2 root root 80 2010-07-07 16:05 bsg drwxr-xr-x 3 root root 60 2010-07-07 16:05 bus lrwxrwxrwx 1 root root 3 2010-07-07 16:05 cdrom -> sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 cdrw -> sr0 drwxr-xr-x 2 root root 3320 2010-07-07 16:05 char crw------- 1 root root 5, 1 2010-07-07 16:05 console lrwxrwxrwx 1 root root 11 2010-07-07 16:05 core -> /proc/kcore ... snip ...
For people new to
/dev, those files are not device drivers as some people (erroneously) like to refer to them -- they are simply special kinds of files that, in a way, refer mainly to device driver code running in kernel space.
The most common types of special files you'll find there are block ("b") and character ("c") special files, with a few directories and symlinks thrown in to add some aesthetic organization to the lot, but it's those first two types of special files that we care about since accessing them in particular ways gives us access to the underlying device driver that is allegedly handling the device to which that file corresponds. In short, given a properly written character device, accessing its corresponding device file with read and write operations will, ultimately, translate into I/O operations on the device itself.
And how does that mapping happen? I'm glad you asked.
While I'm sure most readers at this point know this, I'll explain it, anyway. When you attempt (from user space) to access a character device by operating on its special device file, the actual name of the device file is irrelevant -- all that matters are the major and minor numbers attached to that device file, because its those two values that uniquely determine what kernel code you're trying to access, and how.
As an example, consider (on my system):
$ ls -l /dev/sr0 brw-rw----+ 1 root cdrom 11, 0 2010-07-07 16:05 /dev/sr0 $
For now, ignore that this is a block device. What it clearly represents is my CD-ROM device, so if I wanted to access a CD-ROM, I would have to mount the device by its special file name
/dev/sr0. And from the above, you can see that that device's major device number is 11. What that means is that, somewhere in kernel space, there is driver code that knows how to talk to my CD-ROM, and the unique way to refer to that driver code from user space is to specify the major number of 11. Got that? CD-ROM driver is accessible via (block) device file with major number 11.
And what about that minor number of zero? While a device file's major number typically identifies the driver code for that device, the minor number more specifically identifies how you want to talk to that device or, more commonly, it identifies one of a number of equivalent instances of that device. For instance, if my system had two CD-ROM devices, it's almost a guarantee that I would see:
$ ls -l /dev/sr* brw-rw----+ 1 root cdrom 11, 0 2010-07-07 16:05 /dev/sr0 brw-rw----+ 1 root cdrom 11, 1 2010-07-07 16:05 /dev/sr1 $
or something like that where (unsurprisingly) exactly the same driver would be used to access both devices, but the minor number is used to identify which CD-ROM drive I'm talking to. But wait -- there's more.
Because the canonical name of a device is sometimes ugly and incomprehensible, it's typical to have one or more symlinks to that device file, simply for convenience. For my CD-ROM, I can check that with:
$ ls -l /dev | grep sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 cdrom -> sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 cdrw -> sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 dvd -> sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 dvdrw -> sr0 lrwxrwxrwx 1 root root 3 2010-07-07 16:05 scd0 -> sr0 brw-rw----+ 1 root cdrom 11, 0 2010-07-07 16:05 sr0 $
In short, to access my CD-ROM, I can use the canonical name for the device file, or I can use any of those symlinks just as easily.
Exercise for the student: Even if you don't know much about device files, scan the contents of
/dev and see what other device files have more convenient symlinks.
A good question, with two answers. Historically, the contents of the
/dev directory were static -- pre-loaded when you installed your OS -- so you typically had a
/dev directory with thousands upon thousands of device files, even for devices you didn't even have; the Linux distro was just playing it safe. And it wasn't that big a deal since device files are actually quite small; in fact, they don't take up any data space at all on the disk, they just cost you an inode (if you know what that is). But even though they didn't cost much in terms of space, it was still wasteful and messy to have a special device file for every imaginable device.
These days, your
/dev directory is almost certainly generated dynamically, probably by the
udev facility, so that when you examine the contents of
/dev, it's much more likely that what you see corresponds to what's actually on your system. But there's a better way to tell what drivers have registered with which major numbers on your Linux system -- the
$ cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 6 lp 7 vcs ... snip ... Block devices: 1 ramdisk 259 blkext 7 loop 8 sd 9 md 11 sr ... snip ... $
The contents of the
/proc/devices file is the primary source to tell you what drivers are registered, and at what major numbers, and you can see that, sure enough, the block device major number 11 appears to be associated with the "sr" driver -- there's my CD-ROM.
Some quick tips about the above:
/proc/devicesdoesn't list minor numbers).
And now that we've covered everything you need to know about devices in user space, let's hop into the kernel for the rest of this lesson to see how devices and major and minor numbers are handled there.
At this point, we can look at the internal (kernel space) representation of device numbers, and we can start in the kernel header file
typedef __u32 __kernel_dev_t; typedef __kernel_dev_t dev_t;
which tells us that a typedef for a "device" in kernel space is an unsigned 32-bit value, and it's of type
dev_t, at which point the header file
include/linux/kdev_t.h tells us everything else we need to know, starting with:
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
It should be obvious what the above is telling you -- that a device type identifier is represented by a type of
dev_t, that it's 32 bits long, and that it's partitioned into a leading 12-bit major number, plus a 20-bit minor number. Also, that you're not supposed to know about the 20 bits part and that all management of that kernel type should be done via those macros so the partitioning can change without notice and you'll never have to worry about it.
And on that note, we've covered the fundamentals of how to refer to device drivers in kernel space; in the next lesson, we'll get into how you use this information to register them with the system when you load them.