Writing a Linux 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).
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
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.