A Simple Recipe for udev Rules

There is no shortage of tutorials available on the net for building udev rules in Linux, and I don’t mean to belabor the point here or be excessively redundant, but it’s taken me a bit of trial and error to find a simple approach that works well. Here’s a simple recipe for defining a udev rule to alias a device.

In my case, I’ve got a LOT of USB/Serial devices connected to a single machine – my home automation controller. If I have everything nicely configured and happen to reboot the server with a device inadvertently unplugged, my nice configuration goes to hell in a handbasket – my Insteon PLM which was once known as /dev/ttyUSB4 now becomes /dev/ttyUSB3 and MisterHouse loses it’s mind.

Enter udev rules. This very capable replacement for the nightmare that was Linux hotplug allows for the definition of aliases for known devices. Let’s say that I want to refer to my AAG TAI603B weather station bus master as /dev/aag-weather. I know that this device uses the Silicon Labs CP210x driver; a quick dmesg shows the following:

[    3.627452] usb 5-1: reset full speed USB device using uhci_hcd and address 2
[    3.763150] usb 5-1: cp210x converter now attached to ttyUSB4
[    3.763195] usbcore: registered new interface driver cp210x
[    3.763201] cp210x: v0.09:Silicon Labs CP210x RS232 serial adaptor driver
[    3.907972]   alloc irq_desc for 22 on node -1
[    3.907980]   alloc kstat_irqs on node -1

So I know that the device is registered at ttyUSB4 (at least this time). Executing the following I can determine the full device path:

$ sudo udevadm info -q path -n /dev/ttyUSB4
/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/ttyUSB4/tty/ttyUSB4

Using this path, I can then query udev for the device node information:

$ sudo udevadm info -a -p /devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/ttyUSB4/tty/ttyUSB4

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/ttyUSB4/tty/ttyUSB4':
    KERNEL=="ttyUSB4"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/ttyUSB4':
    KERNELS=="ttyUSB4"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="cp210x"
    ATTRS{port_number}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0':
    KERNELS=="5-1:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="cp210x"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceSubClass}=="00"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{modalias}=="usb:v10C4pEA60d0100dc00dsc00dp00icFFisc00ip00"
    ATTRS{supports_autosuspend}=="0"
    ATTRS{interface}=="CP2102 USB to UART Bridge Controller"

  looking at parent device '/devices/pci0000:00/0000:00:1d.3/usb5/5-1':
    KERNELS=="5-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="53123"
    ATTRS{idVendor}=="10c4"
    ATTRS{idProduct}=="ea60"
    ATTRS{bcdDevice}=="0100"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="5"
    ATTRS{devnum}=="2"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Silicon Labs"
    ATTRS{product}=="CP2102 USB to UART Bridge Controller"
    ATTRS{serial}=="0001"

  looking at parent device '/devices/pci0000:00/0000:00:1d.3/usb5':
    KERNELS=="usb5"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{urbnum}=="55"
    ATTRS{idVendor}=="1d6b"
    ATTRS{idProduct}=="0001"
    ATTRS{bcdDevice}=="0206"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="5"
    ATTRS{devnum}=="1"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Linux 2.6.32.atom.mh.01 uhci_hcd"
    ATTRS{product}=="UHCI Host Controller"
    ATTRS{serial}=="0000:00:1d.3"
    ATTRS{authorized_default}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.3':
    KERNELS=="0000:00:1d.3"
    SUBSYSTEMS=="pci"
    DRIVERS=="uhci_hcd"
    ATTRS{vendor}=="0x8086"
    ATTRS{device}=="0x27cb"
    ATTRS{subsystem_vendor}=="0x8086"
    ATTRS{subsystem_device}=="0x574d"
    ATTRS{class}=="0x0c0300"
    ATTRS{irq}=="16"
    ATTRS{local_cpus}=="f"
    ATTRS{local_cpulist}=="0-3"
    ATTRS{modalias}=="pci:v00008086d000027CBsv00008086sd0000574Dbc0Csc03i00"
    ATTRS{enable}=="1"
    ATTRS{broken_parity_status}=="0"
    ATTRS{msi_bus}==""

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

A couple of things to note here:

  • Each section (beginning with “looking at”) represents a single device node.
  • Device nodes are listed from the most specific (the driver) to the least specific (the pci bus)

We want to match on the lowest-level node possible which uniquely identifies the device on the system. Since I only have one device using this particular chipset, I can use the following node for identification:

    KERNELS=="ttyUSB4"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="cp210x"
    ATTRS{port_number}=="0"

We’ll skip the first entry (“ttyUSB4”), as this could easily change based upon other devices plugged in to the USB bus. Create a new file in /etc/udev/rules.d named “50-usb-serial.rules” and add the following line:

SUBSYSTEMS=="usb-serial",DRIVERS=="cp210x",ATTRS{port_number}=="0",SYMLINK+="aag-weather"

Save the file and execute the following to immediately trigger the udev rules:

$ sudo udevadm trigger

looking in /dev, I now see my newly created device node:

$ ls -al /dev/aag-weather 
lrwxrwxrwx 1 root root 7 Mar 28 22:42 /dev/aag-weather -> ttyUSB4

Pretty sweet. I can now safely refer to this device as /dev/aag-weather in application configuration scripts without fear that it will unexpectedly move around on me.

A couple of additional points to make:

  • A rule can match a device node using attributes of a single node and (optionally) that of it’s IMMEDIATE parent node.
  • Make sure to select attributes which uniquely identify the device on your system!
  • Try to use the lowest-level node representing the device.

4 thoughts on “A Simple Recipe for udev Rules”

  1. First of all thanks for the explaining this subject in a simple way…

    But, what if I need to create aliases for same devices more then one?
    I mean; I have got 10 USB devices which all same branded/produced and has same chipset.
    So, how should I add stable aliases to the rules file in this situation?

    1. If you have 10 devices which return the same IDs to the udev subsystem, the short answer is that it’s not possible. The rule system requires that each device be uniquely identifiable. It may be possible to extend the mechanism to do more in-depth probing, but that’s definitely a more advanced use case than I’ve had to deal with.

    2. If the devices are identical, you’ll need to follow the above procedure adding the ATTRS{serial}==”xxxx” to your rules file. Then each item can be uniquely identified.

Leave a Reply

Your email address will not be published. Required fields are marked *