Linux: Predictable Names For Multiple Identical USB-To-Serial-Converters
Date: 27 Dec, 2020
Reading Time: 5 min
This blog post demonstrates how to achieve predictable names for multiple identical USB-to-serial-converters.
Every time I come back to my Raspberry Pi (running some Linux distribution) for working with devices connected via serial port, I run into trouble when I want to use more than one USB-to-serial-converter at once.
The system simply names the USB-to-serial-converters like /dev/ttyUSB0
, /dev/ttyUSB1
, etc., but the suffixed number simply reflects which converter was plugged in first.
This is everything but useful when it comes to using these converters over longer periods of time (like for recording data), since there is no guarantee that the ordering will, for example, survive a reboot.
Fortunately many Linux distributions come with udev for managing various kinds of devices and this turned out to be the solution for my problem.
Prerequisites
-
Linux distribution using udev (in my case it was Arch Linux ARM)
-
two or more identical USB-to-serial-converters (this should also work for other USB devices)
-
each USB-to-serial-converter is always plugged into the same USB port
Custom udev Rules
First the relevant information for the new udev rules need to be determined.
The USB-to-serial-converters should be plugged in using a meaningful ordering, in my case I plugged in sensor 1 and then sensor 2.
In the following examples I assume the USB-to-serial-converters are /dev/ttyUSB0
(sensor 1) and /dev/ttyUSB1
(sensor 2), however other Linux distributions may choose different names.
Now udev is used to query the attributes for each USB-to-serial-converter ([…]
means I left out some output for brevity):
$ udevadm info --name=/dev/ttyUSB0 --attribute-walk 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/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0/tty/ttyUSB0': KERNEL=="ttyUSB0" SUBSYSTEM=="tty" (1) DRIVER=="" ATTR{power/control}=="auto" [...] looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0': KERNELS=="ttyUSB0" SUBSYSTEMS=="usb-serial" DRIVERS=="pl2303" ATTRS{port_number}=="0" [...] looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0': KERNELS=="1-1.3:1.0" SUBSYSTEMS=="usb" DRIVERS=="pl2303" ATTRS{authorized}=="1" [...] looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3': KERNELS=="1-1.3" (2) SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{authorized}=="1" [...] ATTRS{idProduct}=="2303" (3) ATTRS{idVendor}=="067b" (3) ATTRS{manufacturer}=="Prolific Technology Inc." [...] ATTRS{product}=="USB-Serial Controller" [...] looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1': KERNELS=="1-1" SUBSYSTEMS=="usb" [...] looking at parent device '/devices/platform/soc/3f980000.usb/usb1': KERNELS=="usb1" SUBSYSTEMS=="usb" [...] looking at parent device '/devices/platform/soc/3f980000.usb': KERNELS=="3f980000.usb" SUBSYSTEMS=="platform" [...] looking at parent device '/devices/platform/soc': KERNELS=="soc" SUBSYSTEMS=="platform" [...] looking at parent device '/devices/platform': KERNELS=="platform" SUBSYSTEMS=="" [...]
1 | the desired udev SUBSYSTEM (tty in this case) |
2 | the USB port the USB-to-serial-converter is plugged in (1-1.3 in this case) |
3 | the USB-to-serial-converter’s product and vendor id used for identifying the device itself (2303 and 067b in this case) |
It is really important to use the very first occurrence of each attribute, as some of them occur in parent devices, too. |
The above needs to be done for all plugged in USB-to-serial-converters. In my case this yields the following:
-
/dev/ttyUSB0
⇒SUBSYSTEM=="tty"
,KERNELS=="1-1.3"
,ATTRS{idVendor}=="067b"
,ATTRS{idProduct}=="2303"
-
/dev/ttyUSB1
⇒SUBSYSTEM=="tty"
,KERNELS=="1-1.5"
,ATTRS{idVendor}=="067b"
,ATTRS{idProduct}=="2303"
Apart from the KERNELS part everything is identical due to the fact that I am using two identical USB-to-serial-converters.
This information is however still important to tell udev for which devices the new rules shall apply.
|
The good news is, that this is already almost the udev rules to create!
So now the udev rules need to be added to the system.
Usually there is a directory somewhere under /etc
into which the rules shall be put.
In my case this directory is /etc/udev/rules.d/
, but it may be a different one depending on the actual Linux distribution.
Into this directory a new file 10-usb-serial.rules
(for example) is added:
1
2
ACTION=="add", SUBSYSTEM=="tty", KERNELS=="1-1.3", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="ttySensor1"
ACTION=="add", SUBSYSTEM=="tty", KERNELS=="1-1.5", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="ttySensor2"
Additionally, to the attributes from above, there is now ACTION=="add"
and SYMLINK+=ttySensorX
.
The first one tells udev to only trigger the rule if the udev event action is add
, i.e. the USB-to-serial-converter was plugged in or discovered during system boot.
The second one tells udev to add a new symbolic link /dev/ttySensorX
for the device matching the previous attributes.
For more information on udev rules see udev rule files documentation.
Now the udev rules need to be reloaded:
# udevadm control --reload
After this the USB-to-serial-converters need to be removed and plugged in again and this should make the new symbolic links appear ([…]
means I left out some output for brevity):
$ ls -lh /dev/tty* [...] lrwxrwxrwx 1 root root 7 Dec 21 03:51 /dev/ttySensor0 -> ttyUSB0 lrwxrwxrwx 1 root root 7 Dec 21 03:52 /dev/ttySensor1 -> ttyUSB1 [...] crw-rw---- 1 root uucp 188, 7 Dec 21 03:51 /dev/ttyUSB0 crw-rw---- 1 root uucp 188, 7 Dec 21 03:52 /dev/ttyUSB1
That’s it! The new symbolic links are always the same, given the respective USB-to-serial-converter was plugged into the same USB port. This is easy to see, when plugging in the USB-to-serial-converters in a different ordering than before, i.e. sensor 2 before sensor 1:
$ ls -lh /dev/tty* [...] lrwxrwxrwx 1 root root 7 Dec 21 03:56 /dev/ttySensor0 -> ttyUSB1 lrwxrwxrwx 1 root root 7 Dec 21 03:55 /dev/ttySensor1 -> ttyUSB0 [...] crw-rw---- 1 root uucp 188, 7 Dec 21 03:55 /dev/ttyUSB0 crw-rw---- 1 root uucp 188, 7 Dec 21 03:56 /dev/ttyUSB1
These symbolic links can be used in the same way the "real" devices can be used.