Embedded Linux device drivers: Understanding their role

July 23, 2018

CHRIS.SIMMONDS_#1-July 23, 2018

Editor's Note: Embedded Linux has consistently ranked among the top operating systems used in embedded systems design. With the rapid growth in interest in the Internet of Things (IoT), the ability of embedded Linux to serve multiple roles will prove vital in supporting diverse needs found at each layer of the IoT application hierarchy. In turn, the ability of engineers to master embedded Linux systems will become critical for achieving rapid, reliable development of more sophisticated systems. In Mastering Embedded Linux Programming - Second Edition, author Chris Simmonds takes the reader on a detailed tour across the breadth and depth of this important operating system, using detailed examples to illustrate each key point.

In this excerpt, Chapter 9, from the book, the author describes how kernel device drivers interact with system hardware and how developers can write device drivers and use them in their applications. This first installment from this excerpt introduces device drivers.

Adapted from Mastering Embedded Linux Programming - Second Edition, by Chris Simmonds.


Chapter 9. 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.

Continue reading on page two >>


< Previous
Page 1 of 2
Next >

Loading comments...