Understanding udev and Basic USB Devices in Linux

03 October 2018

In this article, I would like to share my experience using udev in a Linux OS.

udev is a device manager for the Linux kernel. udev manages device nodes in the /dev directory and handles all user space events raised while hardware devices are added into the system or removed from it. As an analogy to the Windows OS where we can find our hardware in control panel -> device manager, in the Linux OS the plugged hardware devices are listed as device nodes (device file) under the /dev directory. There are different types of device node, such as character devices, block devices, pseudo devices, etc 1. Since in this article I'll write about USB devices, a USB device (it can be a hard disk or a USB stick) is an example of the block device. A block device can also be called as a block special file which is a file that refers to a device 2. Examples for the character devices are serial ports and sound cards.

So why we should care about udev? If you want to be a Linux application developer or a Linux system administrator, you should care about udev. udev allows a developer to control or interact with a hardware device. A simple example is an automount of a USB stick in a Linux environment. There are many more interesting usecases which involve the trigger of a plugged USB device, since most of the consumer hardwares nowadays use USB as the standard device interface.

The Basic of USB devices in a Linux system

First, after we plugged an USB stick we can check the information of the device by typing lsusb on the terminal console. We will see something like this:

Bus 002 Device 002: ID 090c:1000 Silicon Motion, Inc. - Taiwan 64MB QDI U2 DISK
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

and we will see the existence of the device node /dev/sda1/ or /dev/sdb1/.

The command mount printout the list of all mounted file system. We can check if our USB stick is automounted or not. Using this command mount | grep /dev/sd, we can filter out the output to show only the USB devices. An output example of a mounted USB stick with FAT filesystem from the mount command is:

/dev/sda1 on /tmp/media/sda1 type vfat (ro,nosuid,nodev,noexec,noatime,gid=1001,fmask=0002,dmask=0002,allow_utime=0020,codepage=437,iocharset=utf8,shortname=mixed,utf8,errors=remount-ro)

Not every system will mount automatically a plugged USB device. To cross-check that all USB devices are mounted, we can run the command lsblk to list all the block devices in the system.

udev

Now, we can use the path /dev/sda1/to ask udev about how does it see the device. The command udevadm info -a -p $(udevadm info -q path -n /dev/sda1) returns these below outputs from the device to the parent description:

looking at device '/devices/platform/ee0b0000.pci/pci0001:01/0001:01:02.0/usb2/2-1/2-1:1.0/host5/target5:0:0/5:0:0:0/block/sda/sda1':
    KERNEL=="sda1"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{ro}=="0"
    ATTR{size}=="15669248"
    ATTR{stat}=="     173       22     1886      224      151       73    33084   239620        0     3351   239843"
    ATTR{partition}=="1"
    ATTR{start}=="2048"
    ATTR{discard_alignment}=="0"
    ATTR{alignment_offset}=="0"
    ATTR{inflight}=="       0        0"

looking at parent device '/devices/platform/ee0b0000.pci/pci0001:01/0001:01:02.0/usb2/2-1/2-1:1.0/host5/target5:0:0/5:0:0:0/block/sda':
    KERNELS=="sda"
    SUBSYSTEMS=="block"
    DRIVERS==""
    ATTRS{ro}=="0"
    ATTRS{size}=="15671296"
    ATTRS{stat}=="     219       22     2686      282      151       73    33084   239620        0     3390   239901"
    ATTRS{range}=="16"
    ATTRS{discard_alignment}=="0"
    ATTRS{events}=="media_change"
    ATTRS{ext_range}=="256"
    ATTRS{events_poll_msecs}=="-1"
    ATTRS{alignment_offset}=="0"
    ATTRS{inflight}=="       0        0"
    ATTRS{removable}=="1"
    ATTRS{capability}=="51"
    ATTRS{events_async}==""

looking at parent device '/devices/platform/ee0b0000.pci/pci0001:01/0001:01:02.0/usb2/2-1/2-1:1.0/host5/target5:0:0/5:0:0:0':
    KERNELS=="5:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{rev}=="1100"
    ATTRS{type}=="0"
    ATTRS{scsi_level}=="7"
    ATTRS{model}=="USB DISK        "
    ATTRS{state}=="running"
    ATTRS{queue_type}=="none"
    ATTRS{iodone_cnt}=="0x24e"
    ATTRS{iorequest_cnt}=="0x24e"
    ATTRS{device_busy}=="0"
    ATTRS{evt_capacity_change_reported}=="0"
    ATTRS{timeout}=="30"
    ATTRS{evt_media_change}=="0"
    ATTRS{max_sectors}=="240"
    ATTRS{ioerr_cnt}=="0x1"
    ATTRS{queue_depth}=="1"
    ATTRS{vendor}=="SMI     "
    ATTRS{evt_soft_threshold_reached}=="0"
    ATTRS{device_blocked}=="0"
    ATTRS{evt_mode_parameter_change_reported}=="0"
    ATTRS{evt_lun_change_reported}=="0"
    ATTRS{evt_inquiry_change_reported}=="0"
    ATTRS{iocounterbits}=="32"
    ATTRS{eh_timeout}=="10"

Based on this information, we can create a udev rule to trigger the bash or shell to execute a script or an application. udev rules are very useful and flexible. udev rules are defined into files with the .rules extension. We can place the file either in the folder /usr/lib/udev/rules.d or in the system-installed directory /etc/udev/rules.d/.

The convention name for the files uses number as a prefix, for instance 50-udev-default.rules. Note that the rules files installed in the folder /etc/udev/rules.d overrides those with the same name installed in the system-installed directory.

Other possible usecases for such a udev rules are:

  • Permission and ownership modification of a device node
  • Renaming a device node or network interface

Since I want to create a rule only for a USB on /dev/sda1, I have to see only the first part and create this rules :

ACTION=="add", SUBSYSTEM=="block", KERNEL=="sda1", RUN+="/bin/bash <path>/myscript.sh -a add -p '%p' "
ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sda1", RUN+="/bin/bash <path>/myscript.sh -a remove -p '%p' "

The above rules consist two actions: add and remove. The add action means that if the USB device is plugged, the <path>/myscript.sh will be executed with its parameters -a add -p '%p'. The remove action means that the <path>/myscript.sh will be executed if the USB device unplugged.

Alternatively, we can handle the remove action by skipping the code lines in the rules file using:

ACTION=="remove", GOTO="rule_end"
...
...
LABEL="rule_end"

Afterwards, we pass the information to <path>/myscript.sh for example with: '$attr{devnum}', '$attr{product}', '%p'. Please note that it's ATTRS in the rules file, not ATTR.

These links give us more deep information: