udev rules for multiple interfaces

I just had to write a udev rule for Linux to interpret between several different endpoints on a single cellular modem, specifically this modem.  This shows up in Linux as 5 ttyUSB ports.  In order to make sure that the ports do not change around on me, I checked the following with udevadm:

$ udevadm info -a -n /dev/ttyUSB1

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.0/usb4/4-1/4-1.1/4-1.1:1.0/ttyUSB1/tty/ttyUSB1':
 KERNEL=="ttyUSB1"
 SUBSYSTEM=="tty"
 DRIVER==""

 looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.1/4-1.1:1.0/ttyUSB1':
 KERNELS=="ttyUSB1"
 SUBSYSTEMS=="usb-serial"
 DRIVERS=="option1"
 ATTRS{port_number}=="0"

 looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.1/4-1.1:1.0':
 KERNELS=="4-1.1:1.0"
 SUBSYSTEMS=="usb"
 DRIVERS=="option"
 ATTRS{bInterfaceClass}=="ff"
 ATTRS{bInterfaceSubClass}=="ff"
 ATTRS{bInterfaceProtocol}=="ff"
 ATTRS{bNumEndpoints}=="02"
 ATTRS{supports_autosuspend}=="1"
 ATTRS{bAlternateSetting}==" 0"
 ATTRS{bInterfaceNumber}=="00"

 looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.1':
 KERNELS=="4-1.1"
 SUBSYSTEMS=="usb"
 DRIVERS=="usb"
 ATTRS{bDeviceSubClass}=="00"
 ATTRS{bDeviceProtocol}=="00"
 ATTRS{devpath}=="1.1"
 ATTRS{idVendor}=="1bc7"
 ATTRS{speed}=="480"
 ATTRS{bNumInterfaces}==" 8"
 ATTRS{bConfigurationValue}=="1"
 ATTRS{bMaxPacketSize0}=="64"
 ATTRS{busnum}=="4"
 ATTRS{devnum}=="11"
 ATTRS{configuration}==""
 ATTRS{bMaxPower}=="500mA"
 ATTRS{authorized}=="1"
 ATTRS{bmAttributes}=="80"
 ATTRS{bNumConfigurations}=="1"
 ATTRS{maxchild}=="0"
 ATTRS{bcdDevice}=="0232"
 ATTRS{avoid_reset_quirk}=="0"
 ATTRS{quirks}=="0x0"
 ATTRS{serial}=="0123456789ABCDEF"
 ATTRS{version}==" 2.00"
 ATTRS{urbnum}=="194649"
 ATTRS{ltm_capable}=="no"
 ATTRS{manufacturer}=="Android"
 ATTRS{removable}=="unknown"
 ATTRS{idProduct}=="1201"
 ATTRS{bDeviceClass}=="00"
 ATTRS{product}=="Android"

 looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4/4-1':
 KERNELS=="4-1"
 SUBSYSTEMS=="usb"
 DRIVERS=="usb"
 ATTRS{bDeviceSubClass}=="00"
 ATTRS{bDeviceProtocol}=="01"
 ATTRS{devpath}=="1"
 ATTRS{idVendor}=="8087"
 ATTRS{speed}=="480"
 ATTRS{bNumInterfaces}==" 1"
 ATTRS{bConfigurationValue}=="1"
 ATTRS{bMaxPacketSize0}=="64"
 ATTRS{busnum}=="4"
 ATTRS{devnum}=="2"
 ATTRS{configuration}==""
 ATTRS{bMaxPower}=="0mA"
 ATTRS{authorized}=="1"
 ATTRS{bmAttributes}=="e0"
 ATTRS{bNumConfigurations}=="1"
 ATTRS{maxchild}=="8"
 ATTRS{bcdDevice}=="0000"
 ATTRS{avoid_reset_quirk}=="0"
 ATTRS{quirks}=="0x0"
 ATTRS{version}==" 2.00"
 ATTRS{urbnum}=="204"
 ATTRS{ltm_capable}=="no"
 ATTRS{removable}=="unknown"
 ATTRS{idProduct}=="0024"
 ATTRS{bDeviceClass}=="09"

 looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb4':
 KERNELS=="usb4"
 SUBSYSTEMS=="usb"
 DRIVERS=="usb"
 ATTRS{bDeviceSubClass}=="00"
 ATTRS{bDeviceProtocol}=="00"
 ATTRS{devpath}=="0"
 ATTRS{idVendor}=="1d6b"
 ATTRS{speed}=="480"
 ATTRS{bNumInterfaces}==" 1"
 ATTRS{bConfigurationValue}=="1"
 ATTRS{bMaxPacketSize0}=="64"
 ATTRS{authorized_default}=="1"
 ATTRS{busnum}=="4"
 ATTRS{devnum}=="1"
 ATTRS{configuration}==""
 ATTRS{bMaxPower}=="0mA"
 ATTRS{authorized}=="1"
 ATTRS{bmAttributes}=="e0"
 ATTRS{bNumConfigurations}=="1"
 ATTRS{maxchild}=="2"
 ATTRS{bcdDevice}=="0316"
 ATTRS{avoid_reset_quirk}=="0"
 ATTRS{quirks}=="0x0"
 ATTRS{serial}=="0000:00:1d.0"
 ATTRS{version}==" 2.00"
 ATTRS{urbnum}=="24"
 ATTRS{ltm_capable}=="no"
 ATTRS{manufacturer}=="Linux 3.16.0-4-amd64 ehci_hcd"
 ATTRS{removable}=="unknown"
 ATTRS{idProduct}=="0002"
 ATTRS{bDeviceClass}=="09"
 ATTRS{product}=="EHCI Host Controller"

 looking at parent device '/devices/pci0000:00/0000:00:1d.0':
 KERNELS=="0000:00:1d.0"
 SUBSYSTEMS=="pci"
 DRIVERS=="ehci-pci"
 ATTRS{irq}=="23"
 ATTRS{subsystem_vendor}=="0x1458"
 ATTRS{broken_parity_status}=="0"
 ATTRS{class}=="0x0c0320"
 ATTRS{companion}==""
 ATTRS{driver_override}=="(null)"
 ATTRS{consistent_dma_mask_bits}=="32"
 ATTRS{dma_mask_bits}=="32"
 ATTRS{local_cpus}=="00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,000000ff"
 ATTRS{device}=="0x1c26"
 ATTRS{uframe_periodic_max}=="100"
 ATTRS{enable}=="1"
 ATTRS{msi_bus}==""
 ATTRS{local_cpulist}=="0-7"
 ATTRS{vendor}=="0x8086"
 ATTRS{subsystem_device}=="0x5006"
 ATTRS{numa_node}=="-1"
 ATTRS{d3cold_allowed}=="1"

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

After I got the info, I noticed that the one thing that was different between all of the ttyUSB devices was ATTRS{bInterfaceNumber}.  So, I wrote a rule that looked like this:

ATTRS{idProduct}=="1201", ATTRS{idVendor}=="1bc7", ATTRS{bInterfaceNumber}=="00", SYMLINK+="cellular_1"

However, this did not produce the expected result of a new symlink.  It appears as though you can’t get the udev information for a product/vendor and the interface number in the same rule.  However, there are environment variables that are set which you can use.  If you run udevadm test, you can see what exactly the environment variables are that are set:

$ udevadm test --action=add `udevadm info -q path -n /dev/ttyUSB1`

...skip a bunch of data...
ACTION=add
DEVLINKS=/dev/serial/by-id/usb-Android_Android_0123456789ABCDEF-if00-port0 /dev/serial/by-path/pci-0000:00:1d.0-usb-0:1.1:1.0-port0
DEVNAME=/dev/ttyUSB1
DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.1/4-1.1:1.0/ttyUSB1/tty/ttyUSB1
ID_BUS=usb
ID_MODEL=Android
ID_MODEL_ENC=Android
ID_MODEL_ID=1201
ID_PATH=pci-0000:00:1d.0-usb-0:1.1:1.0
ID_PATH_TAG=pci-0000_00_1d_0-usb-0_1_1_1_0
ID_REVISION=0232
ID_SERIAL=Android_Android_0123456789ABCDEF
ID_SERIAL_SHORT=0123456789ABCDEF
ID_TYPE=generic
ID_USB_DRIVER=option
ID_USB_INTERFACES=:ffffff:ff4201:ff0000:080650:
ID_USB_INTERFACE_NUM=00
ID_VENDOR=Android
ID_VENDOR_ENC=Android
ID_VENDOR_FROM_DATABASE=Telit Wireless Solutions
ID_VENDOR_ID=1bc7
MAJOR=188
MINOR=1
SUBSYSTEM=tty
TAGS=:systemd:
USEC_INITIALIZED=91898933

These variables can also be used in udev rules to do differentiate between different devices.  The two that we are interested in are ID_VENDOR_ID and ID_MODEL_ID, as these correspond to ATTRS{idProduct} and ATTRS{idVendor}.  Our new rule becomes:

DRIVERS=="option", ATTRS{bInterfaceNumber}=="00", ENV{ID_MODEL_ID}=="1201", ENV{ID_VENDOR_ID}=="1bc7", SYMLINK+="cellular_1"

The DRIVERS part of the rule may not be strictly necessary, but it can’t hurt it.

Leave a Reply

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