Dynamic Kernels: Modularized Device Drivers

This is the first in a series of four articles co-authored by Alessandro Rubini and Georg Zezchwitz which present a practical approach to writing Linux device drivers as kernel loadable modules. This installment presents and introduction to thte topic, preparing the reader to understand next month's installment.
Loading and unloading

Since the major number is recorded inside the filesystem node that applications use to access the device, dynamic allocation of the major number means that you can't create your nodes once, and keep them in /dev forever. You need to recreate them each time you load your module.

The scripts in this page are the ones I use to load and to unload my modules. A little editing will suit your own module: you only need to change the module name and the device name.

The mknod command creates a device node with a given major and minor number (I'll talk about minor numbers in the next installment), and chmod gives the desired permissions to the new devices.

Though some of you may dislike creating (and changing permissions) any time the system is booted, there is nothing strange in it. If you are concerned about becoming root to perform the task, remember that insmod itself must be issued with root privileges.

The loading script can be conveniently called drvname_load, where drvname is the prefix you use to identify your driver; the same one used in the name argument passed to register_chrdrv(). The script can be invoked by hand during driver development, and by rc.local after module installation. Remember that insmod looks both in the current directory and in the installation directory (somewhere in /lib/modules) for modules to install.

If your module depends on other modules or if your system setup is somehow peculiar, you can invoke modprobe instead of insmod. The modprobe utility is a refined version of insmod which manages module dependencies and conditional loading. The tool is quite powerful and well documented. If your driver needs exotic handling, you're better off reading the manpage.

At time of writing, however, none of the standard tools handles generation of device nodes for automatically allocated major numbers, and I can't even conceive how they could know the names and minor numbers of your driver. This means that a custom script is needed in any case.

Here's drvname_load:

#!/bin/sh
# Install the drvname driver,
# including creating device nodes.

# FILE and DEV may be the same.
# The former is the object file to load,
# the latter is the official name within
#  the kernel.

FILE="drvname"
DEV="devicename"

/sbin/insmod -f $FILE $*  || \
 {echo "$DEV not inserted" ; exit 1}

# retrieve major just assigned
major=`grep $DEV /proc/devices | \
  awk "{print \\$1}"`

# make defice nodes
cd /dev
rm -f mynode0 mynode1

mknod mynode0 c $major 0
mknod mynode1 c $major 1

# edit this line to suit your needs
chmod go+rw mynode0 mynode1

And drvname_unload:

#!/bin/sh
# Unload the drvname driver

FILE="drvname"
DEV="devicename"

/sbin/rmmod $FILE $* || \
 {echo "$DEV not removed" ; exit 1}

# remove device nodes
cd /dev
rm -f mynode0 mynode1
Allocating Resources

The next important task of init_module() is allocating any resources needed by the driver for correct operation. We call any tiny piece of the computer a “resource”, where “piece” is a logical (or software) representation of a physical part of the computer. Usually a driver will request memory, I/O ports, and IRQ lines.

Programmers are familiar with requesting memory. The kmalloc() function will do it, and you can use it exactly like it was malloc(). Requesting I/O ports, on the contrary, is unusual. They're there, free of charge. There is no “I/O port fault” equivalent of a “segmentation fault”. However, writing to I/O ports belonging to other devices can still crash your system.

Linux implements essentially the same policy for I/O ports as is used for memory. The only real difference is in the CPU not generating exceptions when you write to a port address that you have not requested. Port registering, like memory registering, is also useful to help the kernel's housekeeping tidy.

If you ever scratched your head about the port address to assign to your newly acquired board, you'll soon forget the feeling: cat /proc/ioports and cat /proc/interrupts will quickly uncover the secrets of your own hardware.

Registering I/O ports you use is a little more complicated than requesting memory, because you often have to “probe” to find out where your device is. To avoid “probing” ports that other devices have already registered, you can call check_region() to ask if the region you are considering looking in is already claimed. Do this once for each region as you probe. Once you find the device, use the request_region() function to reserve the region. When your device is removed, it should call release_region() to free the ports. Here are the function declarations from <linux/ioports.h>:

int check_region(unsigned int from,
                 unsigned int extent);
void request_region(unsigned int from,
                    unsigned int extent,
                    const char *name);
void release_region(unsigned int from,
                    unsigned int extent);

The from argument is the beginning of a contiguous region, or range, of I/O ports, the extent is the number of ports in the region, and name is the name of the driver.

If you forget to register your I/O ports, nothing bad will happen, unless you have two such misbehaving drivers, or you need the information to fit a new board in your computer. If you forget to release ports when unloading, any subsequent program accessing the /proc/ioports file will “Oops”, because the driver name will refer to unmapped memory. Besides, you won't be able to load your driver again, because your own ports are no longer available. Thus, you should be careful to free your ports.

A similar allocation policy exists for IRQ lines (see <linux/sched.h>):

int request_irq(uint irq,
           void (*handler)(int, struct pt_regs *),
           ulong flags, const char *name);
void free_irq(uint irq);

Note again that name is what appears in the /proc/ files, and thus should be rather myhardware than mydrv.

If you forget to register IRQ lines, your interrupt handler won't be called; if you forget to unregister, you won't be able to read /proc/interrupts. In addition, if the board continues generating irq's after your handler is unloaded, something weird may happen (I can't tell exactly, because it never happened to me, and I'm not likely to try it in order to document it here). [I think you get a kernel panic, but I've never managed (or tried) to make it happen, either—ED]

The last point I'd like to touch here is introduced by Linus's comment in <linux/io.h>: you have to find your hardware. If you want to make usable drivers, you have to autodetect your devices. Autodetection is vital if you want to distribute your driver to the general public, but don't call it “Plug and Play”, since that is now a trademark.

The hardware should detect both the ioports and the irq number. If the board doesn't tell which IRQ line it will use, you can go through a trial and error technique—it works great, if you do it carefully. The technique will be covered in a later installment.

When you know the irq number of your device, you should use free_irq() to release it before returning from module_init(). You can request it again when your device is actually opened. If you keep hold of the interrupt, you won't be able to multiplex hardware on it (and the i386 has too few IRQ lines to allow wasting them). Thus I run plip and my frame grabber on the same interrupt without unloading any module—I just open only one of them at a time.

Unfortunately, there exist some rare times where autodetection won't work, so you must provide a way to pass information to the driver about ports and irqs. A probe will usually fail only during system boot, when the first drivers have access to several unregistered devices, and can mistake another device for the one it looks for. Sometimes probing for a device can be “destructive” for another device, preventing its future initialization. Both these problems shouldn't happen to a module, which comes last, and thus can't request ports belonging to other devices. Nonetheless, a way to disable autodetection and force values in the driver is an important feature to implement. At least, it's easier than autodetection, and can help you in successfully loading the module before autodetection is there.

Load-time configuration will be the first topic of next issue, where the full source of init_module() and cleanup_module will be uncovered.

______________________

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix