Sunteți pe pagina 1din 6

Interfacing with Device Drivers

Kernel device drivers are the mechanism through which the underlying hardware is exposed to the
rest of the system. As a developer of embedded systems, you need to know how these device
drivers fit into the overall architecture and how to access them from user space programs. Your
system will probably have some novel pieces of hardware, and you will have to work out a way of
accessing them. In many cases, you will find that there are device drivers provided for you, and you
can achieve everything you want without writing any kernel code. For example, you can manipulate
GPIO pins and LEDs using files in sysfs, and there are libraries to access serial buses,
including SPI (Serial Peripheral Interface) and I2C (Inter-Integrated Circuit).

There are many places to find out how to write a device driver, but few to tell you why you would
want to and the choices you have in doing so. This is what I want to cover here. However, remember
that this is not a book dedicated to writing kernel device drivers and that the information given here is
to help you navigate the territory but not necessarily to set up home there. There are many good
books and articles that will help you to write device drivers, some of which are listed at the end of
this chapter.

In this chapter we will cover the following topics:

 The role of device drivers


 Character devices
 Block devices
 Network devices
 Finding out about drivers at runtime
 Finding the right device driver
 Device drivers in user space
 Writing a kernel device driver
 Discovering the hardware configuration

The role of device drivers

As I mentioned in Chapter 4, Configuring and Building the Kernel, one of the functions of the kernel
is to encapsulate the many hardware interfaces of a computer system and present them in a
consistent manner to user space programs. The kernel has frameworks designed to make it easy to
write a device driver, which is the piece of code that mediates between the kernel above and the
hardware below. A device driver maybe written to control physical devices such as a UART or an
MMC controller, or it may represent a virtual device such as the null device (/dev/null) or a
ramdisk. One driver may control multiple devices of the same kind.

Kernel device driver code runs at a high privilege level, as does the rest of the kernel. It has full
access to the processor address space and hardware registers. It can handle interrupts and DMA
transfers. It can make use of the sophisticated kernel infrastructure for synchronization and memory
management. However, you should be aware that there is a downside to this; if something goes
wrong in a buggy driver, it can go really wrong and bring the system down. Consequently, there is a
principle that device drivers should be as simple as possible by just providing information to
applications where the real decisions are made. You often hear this being expressed as no policy in
the kernel. It is the responsibility of user space to set the policy that governs the overall behavior of
the system. For example, the loading of kernel modules in response to external events, such as
plugging in a new USB device, is the responsibility of the user space program, udev, not the kernel.
The kernel just supplies a means of loading a kernel module.

In Linux, there are three main types of device driver:

 Character: This is for an unbuffered I/O with a rich range of functions and a thin layer
between the application code and the driver. It is the first choice when implementing custom
device drivers.
 Block: This has an interface tailored for block I/O to and from mass storage devices. There
is a thick layer of buffering designed to make disk reads and writes as fast as possible, which
makes it unsuitable for anything else.
 Network: This is similar to a block device but is used for transmitting and receiving network
packets rather than disk blocks.

There is also a fourth type that presents itself as a group of files in one of the pseudo file systems.
For example, you might access the GPIO driver through a group of files in /sys/class/gpio, as I
will describe later on in this chapter. Let's begin by looking in more detail at the three basic device
types.

Character devices

Character devices are identified in user space by a special file called a device node. This file name
is mapped to a device driver using the major and minor numbers associated with it. Broadly
speaking, the major number maps the device node to a particular device driver, and the minor
number tells the driver which interface is being accessed. For example, the device node of the first
serial port on the ARM Versatile PB is named /dev/ttyAMA0, and it has major number 204 and
minor number 64. The device node for the second serial port has the same major number, since it is
handled by the same device driver, but the minor number is 65. We can see the numbers for all four
serial ports from the directory listing here:

# ls -l /dev/ttyAMA*
crw-rw---- 1 root root 204, 64 Jan 1 1970 /dev/ttyAMA0
crw-rw---- 1 root root 204, 65 Jan 1 1970 /dev/ttyAMA1
crw-rw---- 1 root root 204, 66 Jan 1 1970 /dev/ttyAMA2
crw-rw---- 1 root root 204, 67 Jan 1 1970 /dev/ttyAMA3

The list of standard major and minor numbers can be found in the kernel documentation
in Documentation/devices.txt. The list does not get updated very often and does not include
the ttyAMA device described in the preceding paragraph. Nevertheless, if you look at the kernel
source code in drivers/tty/serial/amba-pl011.c, you will see where the major and minor
numbers are declared:

#define SERIAL_AMBA_MAJOR 204


#define SERIAL_AMBA_MINOR 64

Where there is more than one instance of a device, as with the ttyAMA driver, the convention for
forming the name of the device node is to take a base name, ttyAMA, and append the instance
number from 0 to 3 in this example.

As I mentioned in Chapter 5, Building a Root Filesystem, the device nodes can be created in several
ways:

 devtmpfs: The device node is created when the device driver registers a new device
interface using a base name supplied by the driver (ttyAMA) and an instance number.
 udev or mdev (without devtmpfs): Essentially the same as with devtmpfs, except that a
user space daemon program has to extract the device name from sysfs and create the node.
I will talk about sysfs
 mknod: If you are using static device nodes, they are created manually using mknod.

You may have the impression from the numbers I have used above that both major and minor
numbers are 8-bit numbers in the range 0 to 255. In fact, from Linux 2.6 onwards, the major number
is 12 bits long, which gives valid numbers from 1 to 4,095, and the minor number is 20 bits, from 0 to
1,048,575.

When you open a character device node, the kernel checks to see whether the major and minor
numbers fall into a range registered by a character device driver . If so, it passes the call to the
driver, otherwise the open call fails. The device driver can extract the minor number to find out which
hardware interface to use.

To write a program that accesses a device driver, you have to have some knowledge of how it
works. In other words, a device driver is not the same as a file: the things you do with it change the
state of the device. A simple example is the pseudo random number generator, urandom, which
returns bytes of random data every time you read it. Here is a program that does just this (you will
find the code in MELP/chapter_09/read-urandom):

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
int f;
unsigned int rnd;
int n;

f = open("/dev/urandom", O_RDONLY);
if (f < 0) {
perror("Failed to open urandom");
return 1;
}
n = read(f, &rnd, sizeof(rnd));
if (n != sizeof(rnd)) {
perror("Problem reading urandom");
return 1;
}
printf("Random number = 0x%x\n", rnd);
close(f);
return 0;
}

The nice thing about the Unix driver model is that once we know that there is a device
named urandom and that every time we read from it, it returns a fresh set of pseudo random data,
we don't need to know anything else about it. We can just use standard functions such
as open(2), read(2), and close(2).

You could use the stream I/O functions, fopen(3), fread(3),


and fclose(3) instead, but the buffering implicit in these functions
often causes unexpected behavior. For example, fwrite(3) usually
only writes to the user space buffer, not to the device. You would
need to call fflush(3) to force the buffer to be written out. Therefore,
it is best to not use stream I/O functions when calling device drivers.

Block devices

Block devices are also associated with a device node, which also has major and minor numbers.

Although character and block devices are identified using major and
minor numbers, they are in different namespaces. A character driver
with a major number 4 is in no way related to a block driver with a
major number 4.

With block devices, the major number is used to identify the device driver and the minor number is
used to identify the partition. Let's look at the MMC driver on the BeagleBone Black as an example:
# ls -l /dev/mmcblk*
brw-rw---- 1 root disk 179, 0 Jan 1 2000 /dev/mmcblk0
brw-rw---- 1 root disk 179, 1 Jan 1 2000 /dev/mmcblk0p1
brw-rw---- 1 root disk 179, 2 Jan 1 2000 /dev/mmcblk0p2
brw-rw---- 1 root disk 179, 8 Jan 1 2000 /dev/mmcblk1
brw-rw---- 1 root disk 179, 16 Jan 1 2000 /dev/mmcblk1boot0
brw-rw---- 1 root disk 179, 24 Jan 1 2000 /dev/mmcblk1boot1
brw-rw---- 1 root disk 179, 9 Jan 1 2000 /dev/mmcblk1p1
brw-rw---- 1 root disk 179, 10 Jan 1 2000 /dev/mmcblk1p2

Here, mmcblk0 is the microSD card slot, which has a card that has two partitions, and mmcblk1 is
the eMMC chip that also has two partitions. The major number for the MMC block driver is 179 (you
can look it up in devices.txt). The minor numbers are used in ranges to identify different physical
MMC devices, and the partitions of the storage medium that are on that device. In the case of the
MMC driver, the ranges are eight minor numbers per device: the minor numbers from 0 to 7 are for
the first device, the numbers from 8 to 15 are for the second, and so on. Within each range, the first
minor number represents the entire device as raw sectors, and the others represent up to seven
partitions. On eMMC chips, there are two 128 KiB areas of memory reserved for use by a
bootloader. These are represented as devices: mmcblk1boot0 and mmcblk1boot1, and they have
minor numbers 16 and 24.

As another example, you are probably aware of the SCSI disk driver, known as sd, which is used to
control a range of disks that use the SCSI command set, which includes SCSI, SATA, USB mass
storage, and universal flash storage (UFS). It has the major number 8 and ranges of 16 minor
numbers per interface (or disk). The minor numbers from 0 to 15 are for the first interface with device
nodes named sda up to sda15, the numbers from 16 to 31 are for the second disk with device
nodes sdb up to sdb15, and so on. This continues up to the 16 disk from 240 to 255 with the node
name sdp. There are other major numbers reserved for them because SCSI disks are so popular,
but we needn't worry about that here.

Both the MMC and SCSI block drivers expect to find a partiton table at the start of the disk. The
partition table is created using utilities such as fdisk, sfidsk, or parted.

A user space program can open and interact with a block device directly via the device node. This is
not a common thing to do, though, and is usually only done to perform administrative operations
such as creating partitions, formatting a partition with a filesystem, and mounting. Once the
filesystem is mounted, you interact with the block device indirectly through the files in that filesystem.

Network devices

Network devices are not accessed through device nodes, and they do not have major and minor
numbers. Instead, a network device is allocated a name by the kernel, based on a string and an
instance number. Here is an example of the way a network driver registers an interface:

my_netdev = alloc_netdev(0, "net%d", NET_NAME_UNKNOWN, netdev_setup);


ret = register_netdev(my_netdev);
This creates a network device named net0 the first time it is called, net1 the second time, and so
on. More common names are lo, eth0, and wlan0. Note that this is the name it starts off with;
device managers, such as udev, may change it to something different later on.

Usually, the network interface name is only used when configuring the network using utilities, such
as ip and ifconfig, to establish a network address and route. Thereafter, you interact with the
network driver indirectly by opening sockets, and letting the network layer decide how to route them
to the right interface.

However, it is possible to access network devices directly from user space by creating a socket and
using the ioctl commands listed in include/linux/sockios.h. For example, this program
uses SIOCGIFHWADDR to query the driver for the hardware (MAC) address (the code is in
MELP/chapter_09/show-mac-addresses):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <net/if.h>

int main(int argc, char *argv[])


{
int s;
int ret;
struct ifreq ifr;
int i;
if (argc != 2) {
printf("Usage %s [network interface]\n", argv[0]);
return 1;
}
s = socket(PF_INET, SOCK_DGRAM, 0);
if (s < 0) {
perror("socket");
return 1;
}
strcpy(ifr.ifr_name, argv[1]);
ret = ioctl(s, SIOCGIFHWADDR, &ifr);
if (ret < 0) {
perror("ioctl");
return 1;
}
for (i = 0; i < 6; i++)
printf("%02x:", (unsigned char)ifr.ifr_hwaddr.sa_data
[i]);
printf("\n");
close(s);
return 0;
}

S-ar putea să vă placă și