Embedded Linux device drivers: Device drivers in user space

August 08, 2018

CHRIS.SIMMONDS_#1-August 08, 2018

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.

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 article continues this excerpt. Be sure to read the earlier Part 1 and Part 2

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=%c\n",
           lseek(f, 0, SEEK_SET);
       poll_fds[0].fd = f;
       poll_fds[0].events = POLLPRI | POLLERR;
       while (1) {
           ret = poll(poll_fds, 1, -1);
           if (ret > 0) {
               n = read(f, &value, sizeof(value));
               printf("Button pressed: value=%c\n",
               lseek(f, 0, SEEK_SET);
       return 0;


Continue reading on page two >>


< Previous
Page 1 of 2
Next >

Loading comments...