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