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.

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>:

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

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.

Geek Guides

Pick up any e-commerce web or mobile app today, and you’ll be holding a mashup of interconnected applications and services from a variety of different providers. For instance, when you connect to Amazon’s e-commerce app, cookies, tags and pixels that are monitored by solutions like Exact Target, BazaarVoice, Bing, Shopzilla, Liveramp and Google Tag Manager track every action you take. You’re presented with special offers and coupons based on your viewing and buying patterns. If you find something you want for your birthday, a third party manages your wish list, which you can share through multiple social- media outlets or email to a friend. When you select something to buy, you find yourself presented with similar items as kind suggestions. And when you finally check out, you’re offered the ability to pay with promo codes, gifts cards, PayPal or a variety of credit cards.