Writing a Linux Driver

The main goal of this article is to learn what a driver is, how to implement a driver for Linux and how to integrate it into the operating system—an article for the experienced C programmer.

An Actual Example of a Driver

The first question we answer is: why use Linux as an example
of how to write a driver? The answer is twofold: all the source
files are available in Linux, and I have a working example at my
lab in UPM-DISAM, Spain.

However, both the directory structure and the driver
interface with the kernel are OS dependent. Indeed small changes
may appear from one version or release to the next. For example,
several things changed from Linux 1.2.x to Linux 2.0.x, such as the
prototypes of the driver functions, the kernel configuration method
and the Makefiles for kernel compilation.

The device we have selected for our explanation is the MRV-4
Mobile Robot from the U.S. company Denning-Brach International
Robotics. Although the robot uses a PC with a specific board for
hardware interfacing (a motor/sonar card), the company does not
supply a driver for Linux. Nevertheless, all the source files of
the software, which control the robot through the motor/sonar card,
are available in C language for MS-DOS. The solution is to write a
driver for Linux. In the example, we use kernel release 2.0.24,
although it will also work in later versions with few
modifications.

The mobile platform is composed of a set of wheels coupled
with two motors (the drive and the steer), a set of 24 sonars which
act as proximity sensors for obstacle detection and a set of
bumpers which detect collisions. We need to implement a driver
with, at least, the following services
(init,
open and
release are mandatory):

write: to send
linear and angular velocity commands

read: to read
sonar measures and encoder values

three interrupt
handlers: to store sonar measures when a sonar echo is
received, to implement an emergency stop when a bumper detects a
collision and to stop the steer motor when the wheels are located
at 0 (zero) degrees and a go to home flag is
active

ioctl commands:
go to home which sends a constant angular
velocity to the wheels and activates the go to
home flag; and configuration of motors and sonars

The go to home service allows the user
to stop the wheels at an initial position which is always the same
(0 degrees). The incoming values from sonars and encoders, as well
as the velocity commands, might be part of the main loop of the
control program of the robot.

Returning to the initial scheme (Figure 1), the device is the
MRV-4 robot, the hardware interface is the motor/sonar card, the
source file of the driver will be mrv4.c, the new kernel we will
generate will be vmlinuz, the user program for kernel testing will
be mrv4test.c and the device will
be /dev/mrv4 (see Figure 6).

Figure 6. mrv4hard MRV-4 Scheme

General Programming Considerations

To build a driver, these are the steps to follow:

Program the driver source files, giving special
attention to the kernel interface.

Integrate the driver into the kernel, including in
the kernel source calls to the driver functions.

Configure and compile the new kernel.

Test the driver, writing a user program.

The directory structure of the Linux source files can be
described as follows: the /usr/src contains subdirectories such as
/xview and /linux. Inside the /linux directory, the different parts
of the kernel are classified into subdirectories: init, kernel,
ipc, drivers, etc. The directory /usr/src/linux/drivers/ contains
the driver sources, classified into categories such as block, char,
net, etc.

Another interesting directory is /usr/include, where the main
header files, such as stdio.h, are located. It contains two special
subdirectories:

/usr/include/system/, which includes system header
files, such as types.h

/usr/include/linux/, which includes the Linux
kernel headers such as lp.h, serial.h, mem.h and mrv4.h.

The first task when programming the source files of a driver
is to select a name to identify it uniquely, such as hd, sd, fd,
lp, etc. In our case we decided to use mrv4. Our driver is going to
be a character driver, so we will write the source into the file
/usr/src/linux/drivers/char/mrv4.c, and its header into
/usr/include/linux/mrv4.h.

The second task is to implement the driver I/O functions. In
our case, mrv4_open(),
mrv4_read(),
mrv4_write(),
mrv4_ioctl() and
mrv4_release().

Special care must be taken when programming the driver
because of the following limitations:

Standard library functions are not
available.

Some floating-point operations are not
available.

Stack size is limited.

It is not possible to wait for events, because the
kernel, and so all the processes, are stopped.

The OS functions supported at kernel level are, of course,
only those functions programmed inside it:

kmalloc(),
kfree(): memory management

cli(),
sti(): enable/disable
interrupts

add_timer(),
init_timer(),
del_timer(): timing
management

request_irq(),
free_irq(): irq management

inb_p(),
outb_p(): port management

memcpy_*fs(): data
management

printk():
input/output

register_*dev(),
unregister_*dev(): device
management

*sleep_on(),
wake_up*(): process
management

Detailed information on these functions is given in
Johnson's Guide (see Resources) or even
inside the kernel source files.

This article twice uses the term "protected mode" where it should be using the term "supervisor mode".

Protected mode is a mode of the Intel x86 processor which provides various protection features, such as memory protection and the ability to disable privileged instructions. Under Linux, all software runs in protected mode, but user applications run at a different privilege level to the kernel.

Of course this article is quite out of date (though surprisingly much of it is still relevant) but on this point it was wrong even back when it was written.