Embedded Linux device drivers: Reading driver state at runtime

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

Finding out about drivers at runtime

Once you have a running Linux system, it is useful to know which device drivers are loaded and what state they are in. You can find out a lot by reading the files in /proc and /sys .

First of all, you can list the character and block device drivers currently loaded and active by reading /proc/devices :

  # cat /proc/devices   Character devices:     1 mem     2 pty     3 ttyp     4 /dev/vc/0     4 tty     4 ttyS     5 /dev/tty     5 /dev/console     5 /dev/ptmx     7 vcs    10 misc    13 input    29 fb    81 video4linux    89 i2c    90 mtd   116 alsa   128 ptm   136 pts   153 spi   180 usb   189 usb_device   204 ttySC   204 ttyAMA   207 ttymxc   226 drm   239 ttyLP   240 ttyTHS   241 ttySiRF   242 ttyPS   243 ttyWMT   244 ttyAS   245 ttyO   246 ttyMSM   247 ttyAML   248 bsg   249 iio   250 watchdog   251 ptp   252 pps   253 media   254 rtc   Block devices:   259 blkext     7 loop     8 sd    11 sr    31 mtdblock    65 sd    66 sd    67 sd    68 sd    69 sd    70 sd    71 sd   128 sd   129 sd   130 sd   131 sd   132 sd   133 sd   134 sd   135 sd   179 mmc

For each driver, you can see the major number and the base name. However, this does not tell you how many devices each driver is attached to. It only shows ttyAMA but gives you no clue that it is attached to four real serial ports. I will come back to that later when I look at sysfs .

Of course, network devices do not appear in this list, because they do not have device nodes. Instead, you can use tools such as ifconfig or ip to get a list of network devices:

# ip link show    1: lo:  mtu 65536 qdisc noqueue state    UNKNOWN mode DEFAULT       link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   2: eth0:  mtu 1500 qdisc    pfifo_fast state DOWN mode DEFAULT qlen 1000       link/ether 54:4a:16:bb:b7:03 brd ff:ff:ff:ff:ff:ff   3: usb0:  mtu 1500 qdisc    pfifo_fast state UP mode DEFAULT qlen 1000       link/ether aa:fb:7f:5e:a8:d5 brd ff:ff:ff:ff:ff:ff

You can also find out about devices attached to USB or PCI buses using the well-known commands: lsusb and lspci . There is information about them in the respective manual pages and plenty of online guides, so I will not describe them any further here.

The really interesting information is in sysfs , which is the next topic.

Getting information from sysfs

You can define sysfs in a pedantic way as a representation of kernel objects, attributes, and relationships. A kernel object is a directory , an attribute is a file , and a relationship is a symbolic link from one object to another. From a more practical point of view, since the Linux device driver model represents all devices and drivers as kernel objects, you can see the kernel's view of the system laid out before you by looking in /sys , as shown here:

# ls /sys   block     class     devices   fs        module   bus       dev       firmware  kernel    power

In the context of discovering information about devices and drivers, I will look at three of these directories: devices , class , and block .

The devices: /sys/devices

This is the kernel's view of the devices discovered since boot and how they are connected to each other. It is organized at the top level by the system bus, so what you see varies from one system to another. This is the QEMU emulation of the ARM Versatile:

  # ls /sys/devices   platform    software    system      tracepoint  virtual

There are three directories that are present on all systems:

  • system/ : This contains devices at the heart of the system, including CPUs and clocks.

  • virtual/ : This contains devices that are memory-based. You will find the memory devices that appear as /dev/null , /dev/random, and /dev/zero in virtual/mem . You will find the loopback device, lo , in virtual/net .

  • platform/ : This is a catch-all for devices that are not connected via a conventional hardware bus. This maybe almost everything on an embedded device.

The other devices appear in directories that correspond to actual system buses. For example, the PCI root bus, if there is one, appears as pci0000:00 .

Navigating this hierarchy is quite hard, because it requires some knowledge of the topology of your system, and the path-names become quite long and hard to remember. To make life easier, /sys/class and /sys/block offer two different views of the devices.

The drivers: /sys/class

This is a view of the device drivers presented by their type. In other words, it is a software view rather than a hardware view. Each of the subdirectories represents a class of driver and is implemented by a component of the driver framework. For example, UART devices are managed by the tty layer, and you will find them in /sys/class/tty . Likewise, you will find network devices in /sys/class/net, input devices such as the keyboard, the touchscreen, and the mouse in /sys/class/input , and so on.

There is a symbolic link in each subdirectory for each instance of that type of device pointing to its representation in /sys/device .

To take a concrete example, let's look at the serial ports on the Versatile PB. First of all, we can see that there are four of them:

  # ls -d /sys/class/tty/ttyAMA*   /sys/class/tty/ttyAMA0   /sys/class/tty/ttyAMA2   /sys/class/tty/ttyAMA1   /sys/class/tty/ttyAMA3

Each directory is a representation of the kernel object that is associated with an instance of a device interface. Looking within one of these directories, we can see the attributes of the object, represented as files, and the relationships with other objects, represented by links:

The link called device points to the hardware object for the device. The link named subsystem points back to the parent subsystem, /sys/class/tty. The remaining directory entries are attributes. Some are specific to a serial port, such as xmit_fifo_size , and others apply to many types of device such as the interrupt number, irq , and the device number, dev . Some attribute files are writable and allow you to tune parameters in the driver at runtime.

The dev attribute is particularly interesting. If you look at its value, you will find the following:

   # cat /sys/class/tty/ttyAMA0/dev
   204:64

These are the major and minor numbers of this device. This attribute is created when the driver registered this interface. It is from this file that udev and mdev find the major and minor numbers of the device driver.

The block drivers: /sys/block

There is one more view of the device model that is important to this discussion: the block driver view that you will find in /sys/block . There is a subdirectory for each block device. This example is taken from a BeagleBone Black:

  # ls /sys/block   loop0  loop4  mmcblk0       ram0   ram12  ram2  ram6   loop1  loop5  mmcblk1       ram1   ram13  ram3  ram7   loop2  loop6  mmcblk1boot0  ram10  ram14  ram4  ram8   loop3  loop7  mmcblk1boot1  ram11  ram15  ram5  ram9

If you look into mmcblk1 , which is the eMMC chip on this board, you can see the attributes of the interface and the partitions within it:

The conclusion, then, is that you can learn a lot about the devices (the hardware) and the drivers (the software) that are present on a system by reading sysfs .

Finding the right device driver

A typical embedded board is based on a reference design from the manufacturer with changes to make it suitable for a particular application. The BSP that comes with the reference board should support all of the peripherals on that board. But, then you customize the design, perhaps by adding a temperature sensor attached via I2C, some lights and buttons connected via GPIO pins, a display panel via a MIPI interface, or many other things. Your job is to create a custom kernel to control all of these, but where do you start to look for device drivers to support all of these peripherals?

The most obvious place to look is the driver support page on the manufacturer's website, or you could ask them directly. In my experience, this seldom gets the result you want; hardware manufacturers are not particularly Linux-savvy, and they often give you misleading information. They may have proprietary drivers as binary blobs or they may have source code but for a different version of the kernel than the one you have. So, by all means try this route. Personally, I will always try to find an open source driver for the task in hand.

There maybe support in your kernel already: there are many thousands of drivers in mainline Linux and there are many vendor-specific drivers in the vendor kernels. Begin by running make menuconfig (or xconfig ) and search for the product name or number. If you do not find an exact match, try more generic searches, allowing for the fact that most drivers handle a range of products from the same family. Next, try searching through the code in the drivers directory (grep is you friend here).

If you still don't have a driver, you can try searching online and asking in the relevant forums to see if there is a driver for a later version of Linux. If you find one, you should seriously consider updating the BSP to use the later kernel. Sometimes this is not practical, and so it may have to think of backporting the driver to your kernel. If the kernel versions are similar, it maybe easy, but if they are more than 12 to 18 months apart, the chances are that the code will have changed to the extent that you will have to rewrite a chunk of the driver to integrate it with your kernel. If all of the above fail, you will have to find a solution yourself by writing the missing kernel driver. But, this is not always necessary, I will show in the next section.

The next article in this series will describe how to work with drivers in user space.

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.