Skip to content

Instantly share code, notes, and snippets.

@edro15
Last active January 18, 2024 14:14
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save edro15/1c6cd63894836ed982a7d88bef26e4af to your computer and use it in GitHub Desktop.
Save edro15/1c6cd63894836ed982a7d88bef26e4af to your computer and use it in GitHub Desktop.
[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

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

@florisvanvugt
Copy link

@geosohaib I think you can use ATTRS{serial} to distinguish between devices that have the same vendor & product id.

@cab938
Copy link

cab938 commented Jan 18, 2024

This is great, thanks for sharing! If you run lsub you will see a list of devices and their VENDOR_ID:PRODUCT_ID codes in a much more condensed form, it's faster for adding many rules.

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