Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[How To] Force a specific USB device to a certain TTY

Scenario:

  • multiple USB devices plugged via hub to a host (Linux OS based),
  • multiple services/programs interacting with TTY running on top (e.g. GPSd)

Problem:

At boot TTY are randomly assigned to devices causing depending services/programs instabilities. They could indeed fail to start because of different TTY configurations.

Solution:

  • Assign un-mutable TTY names to USB devices by creating symbolic links of physical devices
  • Configure then services/programs to point to these symbolic TTYs

Therefore, the short answer is: customize udev rules.

udev: overview

udev allows a Linux system to use consistent names for devices such as removable drives and printers, which in turn allows users to experience predictable behavior when devices are added or removed from the system. In synthesis, it represents Linux dynamic device manager.

udev consists of:

  • a configuration file /etc/udev/udev.conf,
  • permission files, and
  • rules files

Rules files are used to determine the TTY used for removable drives currently available in the system. Every line within rule files defines how a specific device attribute is mapped to a dedicated device file.

The default udev rules file is /etc/udev/rules.d/50-udev.rules and should not be modified by a user. To create new rules, add a new file in the same directory (/etc/udev/rules.d/) keeping in mind the following conventions:

  • All rules files must have a filename that ends with the .rules extension
  • Files are read in ascending order

Therefore, to create a customized file read before the default one, just type for example /etc/udev/rules.d/49-my.rules.

Use case

Execute lsusb to see all the currently detected USB devices printed out with an essential amount of info.

$ lsusb
 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 001 Device 002: ID 8087:8000 Intel Corp. 
 Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
 Bus 002 Device 002: ID 2109:2812  
 Bus 002 Device 003: ID 1a40:0101 Terminus Technology Inc. Hub
 Bus 003 Device 002: ID 2109:0812  
 Bus 002 Device 004: ID 1546:01a8 U-Blox AG 
 Bus 002 Device 005: ID 2341:8036 Arduino SA Leonardo (CDC ACM, HID)
 Bus 002 Device 006: ID 12d1:14db Huawei Technologies Co., Ltd. E353/E3131
 Bus 002 Device 007: ID 0529:0001 Aladdin Knowledge Systems HASP v0.06

In this case, two USB devices are connected via hub to the host:

  • a USB GPS Receiver U-Blox AG, and
  • an Arduino platform

This means, they could be connected to either the device /dev/ttyACM0 or /dev/ttyACM1 (randomly selected). Besides, their services require a static configuration of the expected TTY.

Therefore, a solution here is creating a customized udev rule assigning the U-Blox device to a symbolic /dev/ttyGPS.

Write a rule

Execute udevadm info -a -p $(udevadm info -q path -n /dev/ttyACM0) to increase the verbosity of the USB details. The output will look like the one reported below:

looking at device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.2/2-3.2:1.0/tty/ttyACM0':
    KERNEL=="ttyACM0"
    SUBSYSTEM=="tty"
    DRIVER==""

[...]

looking at parent device '/devices/pci0000:00/0000:00:14.0/usb2/2-3/2-3.2':
    KERNELS=="2-3.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="40406"
    ATTRS{idVendor}=="1546"
    ATTRS{idProduct}=="01a8"
    ATTRS{bcdDevice}=="0201"
    ATTRS{bDeviceClass}=="02"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="2"
    ATTRS{devnum}=="4"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="u-blox AG - www.u-blox.com"
    ATTRS{product}=="u-blox GNSS receiver"

[...]

Assuming I need a rule to be read before the default ones, open the file /etc/udev/rules.d/49-custom.rules and write down the deducted information as for the GPS case below:

# U-Blox symbolic link of the driver to a customized one
KERNEL=="ttyACM[0-9]*", SUBSYSTEM=="tty", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK="ttyGPS"

Finalize services configuration

Hence, services/programs configurations must be setup accordingly. Considering the GPS case, make sure the configuration file (/etc/sysconfig/gpsd or /etc/default/gpsd) contains the following line: DEVICE="/dev/ttyGPS" For the Arduino case, modify the configuration file opportunely. Restart the machine to apply the changes.

Sources and research material

@geosohaib
Copy link

geosohaib commented May 19, 2022

@edro15 Thank you for the kind reply. I think device path is the unique attribute but I have not tried it yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment