Introduction to Linux Device Driver Development

Prepared by: Richard A. Sevenich, rsevenic@netscape.net
Chapter 2: The Device Driver Module API for the 2.4 Series Kernel
2.0 Introduction
An operating Linux machine has two modes of operation:
• kernel (supervisor) mode
• user mode
For the code developer, these represent somewhat different environments. Like a number of other modern operating
systems, Linux provides a mechanism for adding/removing code to the kernel without requiring that the system be
rebooted. In Linux, this capability is provided by the loadable module. The initial kernel consists of statically linked
code. Loadable modules can be added to this static kernel during or subsequent to boot, thereby creating a largher
dynamic kernel.
The loadable module enforces a modular and consistent interface, allowing the monolithic Linux kernel to provide
some of the touted advantage of the microkernel approach. Additional advantages of loadable modules include
these:
• It is a convenient way for developers to design, implement, and debug new functionality.
• Because modules can be developed and added to a running kernel, there is no need for kernel recompilation.
• Further, unless the module exhibits pathological problems, there will be no need to reboot. This saves much
time.
• The capability of adding/removing modules dynamically keeps the system's memory footprint smaller - a
potential performance benefit if it results in fewer page faults.
• Once linked to the static kernel, the module performs as kernel code, not requiring further support such as
message passing - another performance boost.
It should be pointed out that the loadable module and kernel API's have continued to evolve along with Linux. This
lack of API stability is reasonable for this rapidly developing OS, but is a pain for developers and maintainers of this
code. Documentation for these API's has not been disseminated in a timely manner in the past, but there is now a
series of articles at
http://lwn.net/Articles/driver-porting
which deals with porting drivers from the 2.4 series to the 2.6 series.
In this chapter we will cover these topics:
• the format of a device driver module
• loading (installing) and unloading (removing) a device driver module
• interdependency among device driver modules
2.1 The Format of a Device Driver Module
A device driver may be manually loaded by the command insmod, and is thereby dynamically added to the kernel. It
can be subsequently unloaded by the command, rmmod. Each of these commands invokes a different entry point in
the device driver code:
• insmod invokes the device driver's function init_module
• rmmod invokes the device driver's function cleanup_module
The device driver can typically be used by a user program which might, for example, access other device driver
functions by library calls such as
• open, close
• read, write, ioctl
These library calls are intercepted by the VFS (virtual file system) and, if appropriate, are routed to the underlying
device driver functions meant to satisfy those calls.
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 1
2.1.1 The init_module Function
Every loadable module must have an init_module function. As mentioned above, this function is invoked when the
module is installed. It is responsible for allocating resources needed by the device driver e.g.
• interrupt request lines (irq's)
• the region(s) of I/O space occupied by the device memory
Additionally, init_module may register information with the kernel such as
• allocated resources
• device major number, if any
• location of other entry points for the device driver
2.1.2 The cleanup_module Function
The cleanup_module function is invoked when the module is unloaded. The kernel will not permit this operation to
succeed if the module is still in use. This function will release resources allocated to the device driver earlier.
2.1.3 Other Entry Points
The device driver typically has other functionality. For example, it may be a device that a user program can write to
and read from. This is typically handled by the user program calling the read and write library functions. Let's say
the user program invokes the read library call. This is basically a wrapper around an underlying system call. The
kernel will invoke the lower level device driver's read function. How does it know which device is being used? We'll
answer this later for specific cases, but, in essence, the kernel knows which device is involved based on information
registered with the kernel by the driver's init_module when the device driver module was loaded.
2.1.4 An Example Device Driver
Here we give a code listing of an example driver. There are explanatory comments throughout. Please read those
comments and make certain that you understand the related code.
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/version.h>
#include <linux/wrapper.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/param.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define true 1
#define false 0
/* This will be the name we choose for our device. We will also
use this as a prefix on functions such as the entry points
appearing in the file_operations struct.
*/
#define DEV_NAME "pp"
MODULE_LICENSE("GPL");
static int Major;
/* These are prototypes for residents of the file_operations struct */
static ssize_t pp_read(struct file *, char *, size_t, loff_t *);
static ssize_t pp_write(struct file *, const char *, size_t, loff_t *);
static int pp_open(struct inode *, struct file *);
static int pp_close(struct inode *, struct file *);
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 2
/* This is the file_operations struct. The init_module function
will register this with the kernel so the kernel will know all
the entry points it contains.
*/
struct file_operations Fops = {
owner: THIS_MODULE,
read: pp_read,
write: pp_write,
open: pp_open,
release: pp_close,
};
/* The pp_probe function does nothing here, but reminds us that a
'real' driver may need to probe for hardware resources. These
resources might later be allocated in init_module.
*/
static int pp_probe(void){
return 0;
}
/* The pp_read function is a stub, but at least does a printk,
for tracing purposes, when it is called.
*/
static ssize_t pp_read(struct file *file, char *buff, size_t ctr,
loff_t *woof)
{
printk(KERN_ALERT "\npp_read active.\n");
return 0;
}
/* The pp_write function is a stub, but at least does a printk,
for tracing purposes, when it is called.
*/
static ssize_t pp_write(struct file *file, const char *buff,
size_t ctr, loff_t *woof)
{
printk(KERN_ALERT "\npp_write active.\n");
return 0;
}
/* The pp_open function does a printk for tracing purposes. */
static int pp_open(struct inode *inode, struct file *file) {
printk(KERN_ALERT "\nAn instance of %s has been opened.\n",
DEV_NAME);
return 0;
}
/* The pp_close function does a printk for tracing purposes. */
static int pp_close(struct inode *inode, struct file *file) {
printk(KERN_ALERT "\nOne instance of %s has been closed.\n", DEV_NAME);
return 0;
}
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 3
/* Next we'll see that that init_module
* registers the file_operations struct so the kernel will know
about the entry points therein
* gets back a major number
* calls pp_probe, to look for hardware resources
Had hardware resources been found, they would need to be allocated
for use by this driver, probably within the scope of init_module.
*/
int init_module(void)
{
Major = register_chrdev( 0, DEV_NAME, &Fops);
if (Major < 0)
{
printk("Registration Failure!\n");
return Major;
}
if (pp_probe() < 0)
{
unregister_chrdev(Major, DEV_NAME);
printk(KERN_ALERT "pp_probe() failure!\n");
return -1;
}
printk(KERN_ALERT "\nRegistered %s, at major number = %d.\n\n",
DEV_NAME, Major);
printk("To use %s, you must create a device file.\n", DEV_NAME);
printk("If this has not already been done, then enter:\n");
printk(" mknod /dev/%s c %d 0\n\n", DEV_NAME, Major);
printk("Also set appropriate permissions for /dev/%s.\n\n", DEV_NAME);
return 0;
}
/* The cleanup_module function unregisters the driver and, in a
'real' driver would free up any resources allocated by
init_module.
*/
void cleanup_module(void)
{
int ret;
ret = unregister_chrdev(Major, DEV_NAME);
if (ret < 0)
printk(KERN_ALERT "\nUnregistration problem where ret = %d\n\n", ret);
else
printk(KERN_ALERT "\nUnregistered %s, at major number =
%d\n\n", DEV_NAME, Major);
}
2.2 Loading and Unloading a Device Driver Module
2.2.1 Manual loading and unloading
As mentioned in Chapter 1, once a device driver module is ready to compile, one does so with syntax such as
gcc -Wall -c -O2 -I/usr/src/linux/include my_driver.c
The '-c' tells gcc to create the file my_driver.o. The '-O2' turns on an appropriate level of compiler optimization so
that in-line functions get properly expanded. The '-I' allows one to specify the location of the include files if they are
not at the default location i.e. /usr/include.
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 4
Then one must assume super user privileges and manually load the module into the kernel via
insmod my_driver.o
which will inform you if unsuccessful. To see that the module is loaded, enter the command
lsmod
and look for your driver e.g. my_driver among the drivers listed. Alternatively, one could enter
cat /proc/modules
If you later wish to remove the module, enter
rmmod my_driver
Note that insmod, lsmod, and rsmod are in /sbin (some distributions don't include /sbin in the default path if you 'su'
into super user).
2.2.2 Autoloading at boot
Once your device driver is functionally complete and thoroughly debugged, you may want to have the system load it
automatically during system boot. We'll outline a brute force recipe for doing this, assuming that the driver name is
pp.o. Let's assume that the running kernel is version 2.4.2 and that we boot into level 3 (text console mode). Follow
these steps:
• Copy pp.o to /lib/modules/2.4.2/kernel/drivers/misc and ensure that its permissions are consistent with other
modules in that directory (e.g. 644).
• Put a script into init.d that simply says 'insmod /lib/modules/2.4.2/kernel/drivers/misc/pp.o'.
• Then put a link to that script into the rcn.d directory where 'n' means the startup runlevel e.g. rc3.d. For a RedHat
oriented distribution init.d and rcn.d are both in /etc/rc.d. However, in a Debian based system they are in /etc.
2.2.3 Loading on Demand, Unloading upon Extended Idleness
Support for loading modules on demand was incorporated in the developmental 1.3 kernel and has been present in
both stable and developmental branches since the start of the 2.0 kernel series. Originally, this was implemented
with the kerneld daemon which used System V IPC for the message passing support. Subsequently, kerneld was
replaced by kmod.
If the kernel discovers (perhaps via a user program making a system call) that an unloaded device driver is needed, it
requests the appropriate device driver module with request_module() which wakes up kmod for help. The latter
exec's modprobe, passing it the module's name. The modprobe function is a wrapper for the familiar insmod. There
is a configuration file for modprobe (/etc/modules.conf), which can be used to modify the behavior of modprobe on
a per module basis. The directory containing all currently available modules is /lib/modules/kernel_version/ where
kernel_version is the identifying label for the running kernel, e.g. 2.4.2. Both modprobe and depmod make use of
this directory. Note that depmod is called during boot to compute module interdependencies, a needed step for the
correct subsequent behavior of modprobe. Recall that the /lib/modules/kernel_version/ directory was created in the
last step of kernel configuration/recompilation when one enters 'make modules_install'.
The unloading of modules after a period where they have been inactive is accomplished by relying on the crond
daemon. Of course, there must be an appropriate entry in one of the files that crond checks for work to do.
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 5
An Example
Now let's assume that we have created a driver, say pp.o, and want to set it up for on-demand loading and for
automatic removal after it has been unused for some predetermined time. You may need to do some detective work
here because the scenario varies over kernel versions and distributions. We need to perform these steps - or
something rather like them:
• Put pp.o where modprobe will find it, i.e., into the /lib/modules/kernel_version/ kernel/drivers hierarchy e.g. 'cp
pp.o /lib/modules/2.4.2/kernel/drivers/char/'. Check that pp.o in this new location has appropriate permissions.
Next run 'depmod -a' and ignore its complaint about unresolved symbols.
• Let modprobe know the major number to name correspondence since a user program will essentially use the
major number. For example, make an entry into /etc/modules.conf along the lines of 'alias char-major-253 pp'
where we have assumed that pp is a character device driver with major number 253.
• Put a general remove module command somewhere in an entry scanned by crond; e.g., make an entry in /
etc/cron.d/kmod of form 0-59/2 * * * * root /sbin/rmmod -all. If you had to create a new kmod file, check that it
has appropriate permissions (755 is typical on my machine ... today). Then run 'crontab /etc/cron.d/kmod'.
• Note the 0-59/2 means to check every minute and do the rmmod if the minutes value is evenly divisible by 2.
However, the first time it reads the entry it records the fact that it needs to remove your newly installed module
marked as autoclean, but won't remove it until the next time. Hence the first time around, you could see up to a 4
minute delay on removal for our example.
See the references for further information. At this writing, some of the references refer to kerneld rather than kmod.
The functionality is similar, but also look at current scripts etc.
A Subtle Point
A module will not be removed via autoclean unless it has been used at least once since installed. It used to be true
that modules needed to keep count of the module's usage. Every time another instance was put into use, the driver
would invoke the macro MOD_INC_USE_COUNT. Similarly, each time an instance of a driver was taken out of
use, the driver would invoke the macro MOD_DEC_USE_COUNT. There has been an effort to move these macros
from the drivers and take care of usage tracking at a higher level. For character drivers, this newer approach is
adopted if the driver module uses THIS_MODULE in the owner field of the file_operations struct, as in our earlier
example:
struct file_operations Fops = {
owner: THIS_MODULE,
read: pp_read,
write: pp_write,
open: pp_open,
release: pp_close,
};
If you don't use THIS_MODULE as indicated, then use the MOD_XXX_USE_COUNT macros. If you don't use
either, confusion will result e.g. autoclean will not remove the module in the fashion intended in the prior
subsection.
Block drivers have the block_device_operations struct which provides the owner field in the same way.
There are many places where these macros are still employed in drivers - so their removal is not consistent
throughout the kernel. The recommendation would be to use the convention commonly used by other drivers of the
type you are designing and for the kernel series that you are using.
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 6
2.2.4 An Example User Program
A module to be loaded on demand will be loaded, for example, when a user program attempts to access it. The user
program listed below will attempt to access the driver shown back in section 2.1.4.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define DEVICE "/dev/pp"
int main() {
int ddfd = 0;
int ret = 0;
ddfd = open(DEVICE, O_RDWR);
if (ddfd < 0)
{
printf("\nOpen of %s failed.\n", DEVICE);
exit(-1);
}
printf("\nOpen of %s succeeded.\n", DEVICE);
ret = close(ddfd);
if (ret < 0)
{
printf("\nClosing %s failed.\n", DEVICE);
exit(-1);
}
printf("\n Close of %s succeeded.\n", DEVICE);
exit(0);
}
2.3 Interdependency among Device Driver Modules
We would hope that device drivers should be able to make use of other device drivers. This would allow sharing of
certain variables and/or functions where appropriate. Linux modules have this capability and the resulting
interdependence can be accommodated as described in this section. It should be noted that variables and functions
defined (implicitly) as global are exported from the module to the kernel symbol table by default. However, it is
generally considered better programming practice to control this explicitly. Linux provides macros to support this
'better practice' and that is the approach we'll advocate in this section. We'll assume that
CONFIG_MODVERSIONS is true for our kernel. The macros to be discussed affect the addition of symbols to the
module symbol table.
Here are the macros relevant to our discussion:
• EXPORT_SYMTAB - must be placed in the module code before #include <linux/module.h>. It supports the
export of a subset of the module's symbols to the module symbol table.
• EXPORT_NO_SYMBOLS - is an alternative to the prior macro and indicates that no symbols from the module
are to be exported. If neither of these first two macros are present, then all global symbols in the module are
exported. [Recall in C that a symbol can be global to a source file, but not exported by declaring it as static and
with global scope.]
• EXPORT_SYMBOL(some_symbol) - is used to export a specific symbol, and must appear in a module where
EXPORT_SYMTAB has been previously defined. With CONFIG_MODVERSIONS as true, the CRC
information is to be appended to the symbol when it is placed in the module symbol table. To ensure that this
happens is somewhat complicated and outside the scope of this introductory course (See Ch. 11 of Rubini and
Corbet for a thorough discussion).
• EXPORT_SYMBOL_NOVERS(some_symbol) - is like the prior macro, but does not append version (i.e. CRC)
information to the symbol placed in the module symbol table.
It is suggested that you instead EXPORT_SYMBOL_NOVERS, which will work with or without
CONFIG_MODVERSIONS being set.

R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 7
Symbols shared among modules make them interdependent. The kernel keeps track of this when modules are loaded
and unloaded. In particular, if Module A depends on Module B, then
• B must be installed before A
• B must be removed only after A
Example of Module B code
The following could be integrated with (added to) the 'driver' module.
/* shows placement of EXPORT_SYMTAB with respect to existing code
*/
#define __KERNEL__
#define MODULE
#define EXPORT_SYMTAB
#include <linux/module.h>
/* here are the globals perhaps to be used later by init_module
*/
int glb_ctr = 0;
void bump_glb_ctr(char * mod)
{
glb_ctr++;
printk("%s bumping global counter\n", mod);
printk("*** Global Counter == %d ***\n", glb_ctr);
}
/* the very end of the module is typically where these are placed
*/
EXPORT_SYMBOL_NOVERS(glb_ctr);
EXPORT_SYMBOL_NOVERS(bump_glb_ctr);
Module A could then, for example, use bump_glb_ctr() in its init_module.
2.4 References
Bryan Henderson, "Linux Loadable Kernel Module HOWTO", Revision v1.01, August 18, 2001. Available from
the Linux Documentation Project, http://www.linuxdoc.org.
Alessandro Rubini and Jonathan Corbet, Linux Device Drivers, 2nd Edition, O'Reilly (2001).
2.5 Activities
Activity 1
Create a simple driver with a printk in insmod and then arrange to have it load during bootup. Try it out and look for
the printk message.
Activity 2
Take the driver from Section 2.1.4 and the user program from Section 2.2.4 and arrange the driver so it will be
loaded on demand. Then with the driver not installed, run the user program and assure that the driver gets installed
upon that demand. Wait a sufficient time after the user program has finished to check and see that the driver has
been removed.
Activity 3

Build 3 modules, mod_A, mod_B, and mod_C such that
• A depends on B and C
• B depends on C
Carry out a study of their interdependence as related to
• sharing functions and variables
• order of loading/unloading
R.A. Sevenich © 2004 Introduction to Linux Device Driver Development 2 - 8