Lesson 17: Your first character device driver

Printer-friendly versionPrinter-friendly version

The grand plan for your character device driver

Assuming you're comfortable with the introductory content from the previous lesson, here's the plan for the next few lessons.

We're going to start by writing a simple character driver that does, effectively, nothing -- it will load, sit there and be completely useless, at which point we'll unload it and, over the course of the next few lessons, we'll add features to it little by little.

And what features are we going to add? Depending on what you want your driver to do, we can register routines for the driver that let you:

  • read from it,
  • write to it,
  • seek on it,
  • run ioctl() commands on it to control it in various ways,
  • mmap() its memory into user space,

and much more, and we'll do it all a bit at a time so you don't get overwhelmed.

And, finally, you'll be doing all of the above via the device file that corresponds to your driver so that, if you wanted to define what it meant to "read" data from your driver, you would (in user space) do something as simple as:

$ cat /dev/mychardrv  [or whatever you wanted to call it]

and your driver would have to know what it meant to be "read from," at which point it would pass back to user space, well, the "data" being read. If that sounds suspiciously like readable proc files from an earlier lesson, that's exactly what it's going to look like.

Finally, if you recall how you could interact with your loadable modules via module parameters or proc files, you can certainly still add those to your character driver, but that's independent of what we'll be talking about here. You'll see what I mean quickly enough.

NOTE: Don't forget that there's fairly comprehensive coverage of character device drivers here as well.

That dev_t structure again

As a quick refresher, recall that your drivers and device files are going to use a combination of major and minor device number for identification, where the appropriate typedef and macros to manipulate it is defined in the kernel header files include/linux/types.h:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t          dev_t;

and include/linux/kdev_t.h:

#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))

In short, the combination of a major and minor device number in kernel space is represented by a (12,20)-bit partitioning of an unsigned (32-bit) int, but you should never take advantage of that knowledge and you should always use the typedef and corresponding macros to create and interpret information like that.

The two steps of character device registration

As you're about to see, there are two basic steps to writing, compiling and loading your first character driver, then getting access to it. Those steps are:

  • When you load your character driver module, one of its initialization steps will be to "register" itself with the kernel at a particular major device number and one or more minor device numbers.
  • Independently, in user space, it will be your responsibility to create in /dev the corresponding character device file with the matching major and minor number that will act as your "gateway" to that driver.

Note carefully how (for the time being) those are two independent operations -- you're free to create and load a character driver that registers itself in the kernel at a specific major and minor device number but, if you don't have the matching special device file in user space, that driver is not going to do you much good.

Creating a special device file with mknod

Eventually, once you've loaded your character driver, you'll need to (manually for now) create the character special device file with the matching major and minor device number as your way of accessing your driver. And even though you have no driver yet, this is how you would do it.

Let's create a sample device file that won't conflict with any currently-loaded character drivers, so (on my system):

$ cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  ... snip ...
180 usb
189 usb_device
226 drm
251 hidraw
252 usbmon
253 bsg
254 rtc

The above list shows loaded character and their major numbers, so let's pick a number that's free -- say, 199, with arbitrary minor device number zero -- and, using root privilege, create a special device file that would correspond to a driver registered at that major and minor number:

$ sudo mknod /dev/mychardev c 199 0
$ ls -l /dev/mychardev
crw-r--r-- 1 root root 199, 0 2010-07-11 09:35 /dev/mychardev
$

It should be clear that the above command created a character device file with a given major and minor number (199 and 0), and you'll use this mknod command later to create the device file for your driver, but what does it mean to create such a file when there's no such driver behind it? Nothing:

$ cat /dev/mychardev
cat: /dev/mychardev: No such device or address
$

In short, you're free to create whatever character device file you want, but they won't have any value unless there's a matching driver in kernel space to "back it up," as it were. In any event, let's just get rid of that file since it clearly isn't going to do us any good at the moment:

$ sudo rm /dev/mychardev

Now let's talk about the driver you're about to write.

Exercise for the student: Linux kernel guru Greg Kroah-Hartman reminds me that I shouldn't just cavalierly suggest using any currently unused character major number (like 199) as a test for the above since a lot of major numbers are officially allocated to some devices. Take a look at the kernel tree documentation file Documentation/devices.txt, where you'll notice the snippet:

199 char        Veritas volume manager (VxVM) volumes
        0 = /dev/vx/rdsk/*/*          First volume
        1 = /dev/vx/rdsk/*/*          Second volume
        ...

which means that if you unthinkingly use that major number for a test and you have that proprietary driver installed, ugly things might happen.

Greg suggests that you stick to playing with major numbers (defined in that same file):

240-254 char    LOCAL/EXPERIMENTAL USE

In the end, the only rule is to not test with major device numbers that are clearly allocated and already mentioned in /proc/devices.

Allocating and freeing character device numbers

One of the most important things your character driver will do is "register" with the kernel at a major and minor device (or possibly, more than one minor number), and I'm going to make this short.

The routine prototypes for doing this can be seen in the kernel header file include/linux/fs.h:

extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
extern void unregister_chrdev_region(dev_t, unsigned);
static inline void unregister_chrdev(unsigned int major, const char *name)

Historically, the register_chrdev_* routines were used to register a driver at a specific major number, but because you could never predict if anyone else registered at that number already, the newer (and strongly encouraged) technique is to use alloc_chrdev_region(), which (as you'll see shortly) simply asks the registration code to give you some available major number, which you can query quickly enough from user space. Again, let me emphasize -- using the older register_chrdev_* routines to demand a specific major number is strongly discouraged, so we'll ignore it from now on.

And at this point, let's look at a sample driver.

Character driver -- pass one

Here's your first (trivial) character driver -- chardrv.c:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

#include <linux/types.h>   // for dev_t typedef
#include <linux/kdev_t.h>  // for format_dev_t
#include <linux/fs.h>      // for alloc_chrdev_region()

static dev_t mydev;             // (major,minor) value
static char buffer[64];         // optional, for debugging

static int __init chardrv_in(void)
{
    printk(KERN_INFO "module chardrv being loaded.\n");

    alloc_chrdev_region(&mydev, 0, 1, "rday");
    printk(KERN_INFO "%s\n", format_dev_t(buffer, mydev));

    return 0;
}

static void __exit chardrv_out(void)
{
    printk(KERN_INFO "module chardrv being unloaded.\n");

    unregister_chrdev_region(mydev, 1);
}

module_init(chardrv_in);
module_exit(chardrv_out);

MODULE_AUTHOR("Robert P. J. Day, http://crashcourse.ca");
MODULE_LICENSE("GPL");

Obviously, set it up in a new directory, create a matching Makefile, run make to ensure that it builds, and now let's talk about it.

Exercise for the student: Without even discussing what the above is doing yet, compile the module and load it, verify that doing that adds the appropriate character device entry to the /proc/devices file, then unload the module. We're obviously not done talking about this but, at the very least, verify that this simple exercise works for you.

So ... let's talk about that code, shall we?

Assuming you've verified that you can build and load that modular (and trivial) "character" driver, let's step through the code one line at a time so you understand what just happened there.

Beyond the standard header files you're used to by now, the additional header files are for the character driver-related content and routines you're about to use.

This declaration:

static dev_t mydev;

as you already know, is the typedef to be used to represent a combination of major and minor device number, and you should only ever use that typedef and the appropriate macros to manipulate that data object. While you might know that that typedef is actually a 32-bit unsigned int, none of your code should ever take advantage of that.

The next declaration:

static char buffer[64];

is quite optional, and is used later only for pretty printing the contents of the dev_t object. You'll see what I mean shortly. And here's where the good stuff starts.

Register for at least one character major and minor device number:

alloc_chrdev_region(
   &mydev,  // the address of the dev_t object to put the results
   0,       // the starting minor number to allocate
   1,       // how many minor numbers to allocate
   "rday"   // the name shown in /proc/devices
);

What the above does should be obvious -- you're asking to register your character driver at some available major device number, starting with minor device number zero, and asking for a single minor number (obviously, you can ask for more, which we'll get into in a later lesson). As long as that succeeds, you should see the result in the /proc/devices file, which will tell you which major device number you were allocated.

Next, purely for debugging purposes, you can print the result of the allocation to your /var/log/messages file with:

printk(KERN_INFO "%s\n", format_dev_t(buffer, mydev));

And, finally, at module unload time, it's your responsibility to unregister your character driver:

unregister_chrdev_region(mydev, 1);

where the mydev variable knows the major device number and starting minor device number, and all you need to do is supply how many minor device numbers you asked for in the first place (in this case, one).

Simple, no?

Exercise for the student: Tweak the sample program to ask for several minor device numbers, not just one, and check the resulting difference in the /proc/devices file after you load the module. Make sure you unregister precisely that number of minor device numbers in the module's exit routine.

By the way, make sure you understand that you can't do anything with that character "driver" yet. It's in subsequent lessons where you'll learn how to add read and write and ioctl functionality to your "driver."

What can possibly go wrong?

It should be obvious that you should never count on your device number allocation working, which is why all of those registration invocations in your module entry routine should look something like:

int result;
...
result = alloc_chrdev_region(...);

if (result < 0) {
    printk(KERN_WARNING "Failed to allocate major/minor numbers");
    return result;
}

For the time being, though, you can get away with being lazy and just assuming that allocating those numbers will always work.

Bonus exercise for the truly ambitious

If you're feeling ambitious, you can try the following and see if it works, but it's not required to continue on in the course so don't worry if you decide to pass on it.

Consider the simple act of listing the /proc/devices file to see the character drivers that are currently registered with the system:

$ 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 ...

Note that all that's listed is the driver name and the major device number, not the minor device number(s), which might be useful information, so let's do something about that.

First, let's examine where all that output is generated, and that would be in the kernel source file fs/proc/devices.c, particularly this snippet:

static int devinfo_show(struct seq_file *f, void *v)
{
        int i = *(loff_t *) v;

        if (i < CHRDEV_MAJOR_HASH_SIZE) {
                if (i == 0)
                        seq_printf(f, "Character devices:\n");
                chrdev_show(f, i);   <-- there
        }

OK, so, even without totally understanding that code, we can see that the information about each loaded character driver is printed by the chrdev_show() routine, which lives in the source file fs/char_dev.c:

void chrdev_show(struct seq_file *f, off_t offset)
{
        struct char_device_struct *cd;

        if (offset < CHRDEV_MAJOR_HASH_SIZE) {
                mutex_lock(&chrdevs_lock);
                for (cd = chrdevs[offset]; cd; cd = cd->next)
                        seq_printf(f, "%3d %s\n", cd->major, cd->name);
                mutex_unlock(&chrdevs_lock);
        }
}

and, clearly, it's that call to seq_printf() that's printing each line -- more specifically, you can see that it's printing the major number, followed by the driver name, which is of course exactly what you see when you list the /proc/devices file. So could we print more information there? Sure.

First, we need to see the layout of the char_device_struct structure that's being referenced and, conveniently, it's just above that routine in the same file:

static struct char_device_struct {
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct cdev *cdev;              /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

and that should tell you everything you need to know -- given the definition of that structure, you're certainly welcome to enhance that call to seq_printf() to print any additional fields you want per line, so that's your exercise -- modify the kernel source file fs/char_dev.c, then rebuild a whole new kernel, reboot to it and see the difference when you print the contents of the /proc/devices file. Is there any potential drawback to doing that?

Coming next -- starting to turn your character driver into an actual, you know, driver.

Comments

kernel rebuild

If you only modify one file, rebuilding the kernel will be very quick, unless you happen to modify a commonly used header file :)

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <br> <pre> <h1> <h2> <h3> <h4>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.