Embedded Linux device drivers: Device drivers in user space

Editor's Note: Embedded Linux has consistently ranked among the top operating systems used in embedded system 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 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.

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

Chapter 9. Interfacing with Device Drivers (Continued)
By Chris Simmonds

Device drivers in user space

Before you start writing a device driver, pause for a moment to consider whether it is really necessary. There are generic device drivers for many common types of device that allow you to interact with hardware directly from user space without having to write a line of kernel code. User space code is certainly easier to write and debug. It is also not covered by the GPL, although I don't feel that is a good reason in itself to do it this way.

These drivers fall into two broad categories: those that you control through files in sysfs, including GPIO and LEDs, and serial buses that expose a generic interface through a device node, such as I2C.


General-Purpose Input/Output (GPIO ) is the simplest form of digital interface since it gives you direct access to individual hardware pins, each of which can be in one of two states: either high or low. In most cases you can configure the GPIO pin to be either an input or an output. You can even use a group of GPIO pins to create higher level interfaces such as I2C or SPI by manipulating each bit in software, a technique that is called bit banging . The main limitation is the speed and accuracy of the software loops and the number of CPU cycles you want to dedicate to them. Generally speaking, it is hard to achieve timer accuracy better than a millisecond unless you configure a real-time kernel, as we shall see in Chapter 16, Real-Time Programming . More common use cases for GPIO are for reading push buttons and digital sensors and controlling LEDs, motors, and relays.

Most SoCs have a lot of GPIO bits, which are grouped together in GPIO registers, usually 32 bits per register. On-chip GPIO bits are routed through to GPIO pins on the chip package via a multiplexer, known as a pin mux . There maybe additional GPIO pins available off- chip in the power management chip, and in dedicated GPIO extenders, connected through I2C or SPI buses. All this diversity is handled by a kernel subsystem known as gpiolib , which is not actually a library but the infrastructure GPIO drivers use to expose I/O in a consistent way. There are details about the implementation of gpiolib in the kernel source in Documentation/gpio and the code for the drivers themselves is in drivers/gpio .

Applications can interact with gpiolib through files in the /sys/class/gpio directory. Here is an example of what you will see in there on a typical embedded board (a BeagleBone Black):

   # ls  /sys/class/gpio
   export  gpiochip0   gpiochip32  gpiochip64  gpiochip96  unexport

The directories named gpiochip0 through to gpiochip96 represent four GPIO registers, each with 32 GPIO bits. If you look in one of the gpiochip directories, you will see the following:

   # ls /sys/class/gpio/gpiochip96
   base  label   ngpio  power  subsystem  uevent

The file named base contains the number of the first GPIO pin in the register and ngpio contains the number of bits in the register. In this case, gpiochip96/base is 96 and gpiochip96/ngpio is 32, which tells you that it contains GPIO bits 96 to 127. It is possible for there to be a gap between the last GPIO in one register and the first GPIO in the next.

To control a GPIO bit from user space, you first have to export it from kernel space, which you do by writing the GPIO number to /sys/class/gpio/export . This example shows the process for GPIO 53, which is wired to user LED 0 on the BeagleBone Black:

# echo 53 > /sys/class/gpio/export

# ls /sys/class/gpio

export gpio53 gpiochip0 gpiochip32 gpiochip64 gpiochip96 unexport

Now, there is a new directory, gpio53 , which contains the files you need to control the pin.

If the GPIO bit is already claimed by the kernel, you will not be able to export it in this way.

The directory gpio53 contains these files:

   # ls /sys/class/gpio/gpio53
   active_low  direction   power      uevent
   device      edge        subsystem  value

The pin begins as an input. To change it to an output, write out to the direction file. The file value contains the current state of the pin, which is 0 for low and 1 for high. If it is an output; you can change the state by writing 0 or 1 to value . Sometimes, the meaning of low and high is reversed in hardware (hardware engineers enjoy doing that sort of thing), so writing 1 to active_low inverts the meaning of value such that a low voltage is reported as 1 and a high voltage as 0.

You can remove a GPIO from user space control by writing the GPIO number to /sys/class/gpio/unexport.

Handling interrupts from GPIO

In many cases, a GPIO input can be configured to generate an interrupt when it changes state, which allows you to wait for the interrupt rather than polling in an inefficient software loop. If the GPIO bit can generate interrupts, the file called edge exists. Initially, it has the value called none , meaning that it does not generate interrupts. To enable interrupts, you can set it to one of these values:

  • rising : Interrupt on rising edge

  • falling : Interrupt on falling edge

  • both : Interrupt on both rising and falling edges

  • none : No interrupts (default)

You can wait for an interrupt using the poll() function with POLLPRI as the event. If you want to wait for a rising edge on GPIO 48, you first enable the interrupts:

   # echo 48 > /sys/class/gpio/export
   # echo falling > /sys/class/gpio/gpio48/edge

Then, you use poll(2) to wait for the change, as shown in this code example, which you can see in the book code archive in MELP/chapter_09/gpio-int/gpio-int.c:

  #include <stdio.h>   #include <unistd.h>   #include <sys/types.h>   #include <sys/stat.h>   #include <fcntl.h>   #include <poll.h>   int main(int argc, char *argv[])   {       int f;       struct pollfd poll_fds[1];       int ret;       char value[4];       int n;       f = open("/sys/class/gpio/gpio48/value", O_RDONLY);       if (f == -1) {           perror("Can't open gpio48");           return 1; }       n = read(f, &value, sizeof(value));       if (n > 0) {           printf("Initial value=%cn",                  value[0]);           lseek(f, 0, SEEK_SET);       }       poll_fds[0].fd = f;       poll_fds[0].events = POLLPRI | POLLERR;       while (1) {           printf("Waitingn");           ret = poll(poll_fds, 1, -1);           if (ret > 0) {               n = read(f, &value, sizeof(value));               printf("Button pressed: value=%cn",                       value[0]);               lseek(f, 0, SEEK_SET);           }       }       return 0;   }


LEDs are often controlled though a GPIO pin, but there is another kernel subsystem that offers more specialized control specific to the purpose. The leds kernel subsystem adds the ability to set brightness, should the LED have that ability, and it can handle LEDs connected in other ways than a simple GPIO pin. It can be configured to trigger the LED on an event such as block device access or just a heartbeat to show that the device is working. You will have to configure your kernel with the option, CONFIG_LEDS_CLASS , and with the LED trigger actions that are appropriate to you. There is more information on Documentation/leds/, and the drivers are in drivers/leds/.

As with GPIOs, LEDs are controlled through an interface in sysfs in the directory /sys/class/leds . In the case of the BeagleBone Black, the names of the LEDs are encoded in the device tree in the form devicename:colour:function , as shown here:

   # ls /sys/class/leds
   beaglebone:green:heartbeat  beaglebone:green:usr2
   beaglebone:green:mmc0       beaglebone:green:usr3

Now, we can look at the attributes of one of the LEDs, noting that the shell requires that the colon characters, ':', in the path name have to be preceded by a backslash escape character, '':

# cd /sys/class/leds/beaglebone:green:usr2

# ls

brightness max_brightness subsystem uevent
device power trigger

The brightness file controls the brightness of the LED and can be a number between 0 (off ) and max_brightness (fully on ). If the LED doesn't support intermediate brightness, any non-zero value turns it on. The file called trigger lists the events that trigger the LED to turn on. The list of triggers is implementation dependent. Here is an example:

# cat trigger
none mmc0 mmc1 timer oneshot heartbeat backlight gpio [cpu0]

The trigger currently selected is shown in square brackets. You can change it by writing one of the other triggers to the file. If you want to control the LED entirely through brightness , select none . If you set the trigger to timer , two extra files appear that allow you to set the on and off times in milliseconds:

# echo timer > trigger

# ls

brightness delay_on max_brightness subsystem uevent
delay_off device power trigger

# cat delay_on


# cat /sys/class/leds/beaglebone:green:heartbeat/delay_off

If the LED has on-chip timer hardware, the blinking takes place without interrupting the CPU.


I2C is a simple low speed 2-wire bus that is common on embedded boards, typically used to access peripherals that are not on the SoC, such as display controllers, camera sensors, GPIO extenders, and so on. There is a related standard known as system management bus (SMBus ) that is found on PCs, which is used to access temperature and voltage sensors. SMBus is a subset of I2C.

I2C is a master-slave protocol with the master being one or more host controllers on the SoC. Slaves have a 7-bit address assigned by the manufacturer (read the data sheet), allowing up to 128 nodes per bus, but 16 are reserved, so only 112 nodes are allowed in practice. The master may initiate a read or write transactions with one of the slaves. Frequently, the first byte is used to specify a register on the slave, and the remaining bytes are the data read from or written to that register.

There is one device node for each host controller, for example, this SoC has four:

   # ls -l /dev/i2c*
   crw-rw—- 1 root i2c 89, 0 Jan  1 00:18 /dev/i2c-0
   crw-rw—- 1 root i2c 89, 1 Jan  1 00:18 /dev/i2c-1
   crw-rw—- 1 root i2c 89, 2 Jan  1 00:18 /dev/i2c-2
   crw-rw—- 1 root i2c 89, 3 Jan  1 00:18 /dev/i2c-3

The device interface provides a series of ioctl commands that query the host controller and send the read and write commands to I2C slaves. There is a package named i2c- tools , which uses this interface to provide basic command-line tools to interact with I2C devices. The tools are as follows:

  • i2cdetect : This lists the I2C adapters, and probes the bus

  • i2cdump : This dumps data from all the registers of an I2C peripheral

  • i2cget : This reads data from an I2C slave

  • i2cset : This writes data to an I2C slave

The i2c-tools package is available in Buildroot and the Yocto Project as well as most mainstream distributions. So, long as you know the address and protocol of the slave, writing a user space program to talk to the device is straightforward. The example that follows shows how to read the first four bytes from the AT24C512B EEPROM that is mounted on the BeagleBone Black on I2C bus 0, slave address 0x50 (the code is in MELP/chapter_09/i2c-example ):

  #include <stdio.h>   #include <unistd.h>   #include <fcntl.h>   #include <sys/ioctl.h>   #include <linux/i2c-dev.h>   #define I2C_ADDRESS 0x50   int main(void)   {       int f;       int n;       char buf[10];       f = open("/dev/i2c-0", O_RDWR);       /* Set the address of the i2c slave device */       ioctl(f, I2C_SLAVE, I2C_ADDRESS);       /* Set the 16-bit address to read from to 0 */       buf[0] = 0; /* address byte 1 */       buf[1] = 0; /* address byte 2 */       n = write(f, buf, 2);       /* Now read 4 bytes from that address */       n = read(f, buf, 4);       printf("0x%x 0x%x0 0x%x 0x%xn",       buf[0], buf[1], buf[2], buf[3]);       close(f);       return 0;   }
There is more information about the Linux implementation of I2C in Documentation/i2c/dev-interface . The host controller drivers are in drivers/i2c/busses .

Serial Peripheral Interface (SPI)

The SPI bus is similar to I2C, but is a lot faster, up to tens of MHz. The interface uses four wires with separate send and receive lines, which allow it to operate in full duplex. Each chip on the bus is selected with a dedicated chip select line. It is commonly used to connect to touchscreen sensors, display controllers, and serial NOR flash devices.

As with I2C, it is a master-slave protocol with most SoCs implementing one or more master host controllers. There is a generic SPI device driver, which you can enable through the kernel configuration CONFIG_SPI_SPIDEV . It creates a device node for each SPI controller, which allows you to access SPI chips from user space. The device nodes are named spidev[bus].[chip select]:

# ls -l /dev/spi*
crw-rw—- 1 root root 153, 0 Jan 1 00:29 /dev/spidev1.0

For examples of using the spidev interface, refer to the example code in Documentation/spi .

The next article in this series will discuss the issues related to writing a kernel device driver.

Reprinted with permission from Packt Publishing. Copyright © 2017 Packt Publishing

Chris Simmonds is a software consultant and trainer living in southern England. He has almost two decades of experience in designing and building open-source embedded systems. He is the founder and chief consultant at 2net Ltd, which provides professional training and mentoring services in embedded Linux, Linux device drivers, and Android platform development. He has trained engineers at many of the biggest companies in the embedded world, including ARM, Qualcomm, Intel, Ericsson, and General Dynamics. He is a frequent presenter at open source and embedded conferences, including the Embedded Linux Conference and Embedded World. You can see some of his work on the Inner Penguin blog at www.2net.co.uk.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.