Writing udev rules for development boards
Tags:When developing hardware, one encounters two problems:
- The JTAG adapter (or the like) isn’t visible to the JTAG programmer running as a process under an unprivileged user.
- There are approximately eleven USB-Serial adapters plugged in and they arrange themselves through a fair dice roll on every reboot.
Sometimes *cough*FTDI*cough* you could even encounter both with the same device! Exciting!
This note is Linux-specific.
Matching a device series
A device series is usually uniquely identified by a VID/PID pair (unless the vendor decided to cheap out on a license from the USB consortium and didn’t bother open-sourcing their design and applying via Openmoko, which happens more often than you’d expect).
A VID/PID pair can be identified using lsusb
:
Bus 001 Device 005: ID 0403:6010 Future Technology Devices International, Ltd FT2232C Dual USB-UART/FIFO IC
Then, create a file such as /etc/udev/rules.d/99-ftdi.rules
using the VID/PID pair:
ACTION=="add", ATTR{idVendor}=="0403", ATTR{idProduct}=="6010", MODE:="666"
Some other attributes that can be used are ATTR{manufacturer}
, ATTR{product}
and
ATTR{serial}
, which are listed when calling lsusb -d 0403:6010
at
the end of the rows iManufacturer
, iProduct
and iSerial
. These are especially useful
if the vendor was in fact too cheap for an unique VID/PID pair.
Matching a specific device (endpoint)
A device endpoint is usually uniquely identified by the combination of manufacturer string, product string, serial string, and endpoint number. (In case there’s only one endpoint, like in most simple USB-serial ICs, that can be dropped.) If you know the device file, udev can tell everything you need:
$ udevadm info -q all /dev/ttyUSB3 P: /devices/pci0000:00/0000:00:1c.4/0000:04:00.0/usb3/3-2/3-2:1.1/ttyUSB3/tty/ttyUSB3 N: ttyUSB3 S: serial/by-id/usb-Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_XXXXXXXX-if01-port0 S: serial/by-path/pci-0000:04:00.0-usb-0:2:1.1-port0 E: DEVLINKS=/dev/serial/by-id/usb-Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_XXXXXXXX-if01-port0 /dev/serial/by-path/pci-0000:04:00.0-usb-0:2:1.1-port0 E: DEVNAME=/dev/ttyUSB3 E: DEVPATH=/devices/pci0000:00/0000:00:1c.4/0000:04:00.0/usb3/3-2/3-2:1.1/ttyUSB3/tty/ttyUSB3 E: ID_BUS=usb E: ID_MODEL=CP2105_Dual_USB_to_UART_Bridge_Controller E: ID_MODEL_ENC=CP2105\x20Dual\x20USB\x20to\x20UART\x20Bridge\x20Controller E: ID_MODEL_FROM_DATABASE=CP210x UART Bridge E: ID_MODEL_ID=ea70 E: ID_PATH=pci-0000:04:00.0-usb-0:2:1.1 E: ID_PATH_TAG=pci-0000_04_00_0-usb-0_2_1_1 E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller E: ID_REVISION=0100 E: ID_SERIAL=Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_XXXXXXXX E: ID_SERIAL_SHORT=XXXXXXXX E: ID_TYPE=generic E: ID_USB_DRIVER=cp210x E: ID_USB_INTERFACES=:ff0000: E: ID_USB_INTERFACE_NUM=01 E: ID_VENDOR=Silicon_Labs E: ID_VENDOR_ENC=Silicon\x20Labs E: ID_VENDOR_FROM_DATABASE=Cygnal Integrated Products, Inc. E: ID_VENDOR_ID=10c4 E: MAJOR=188 E: MINOR=3 E: SUBSYSTEM=tty E: TAGS=:systemd: E: USEC_INITIALIZED=14656077412643
Then, create a file such as /etc/udev/rules.d/99-cp2105.rules using the full identifier and interface number:
ACTION=="add", SUBSYSTEM=="tty", \ ENV{ID_SERIAL}=="Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_XXXXXXXX", ENV{ID_USB_INTERFACE_NUM}="01", \ SYMLINK+="cp2105_i1"
This will create a file /dev/cp2105_i1
when this specific IC is connected to USB, no matter
where. Or, use the path:
ACTION=="add", SUBSYSTEM=="tty", \ ENV{ID_MODEL}=="CP2105_Dual_USB_to_UART_Bridge_Controller", \ ENV{ID_PATH_TAG}=="pci-0000_04_00_0-usb-0_2_1_1", ENV{ID_USB_INTERFACE_NUM}="01", \ SYMLINK+="cp2105_i1"
This will create the device file when any IC from this series is connected to this specific USB port. This is of course more fragile, but works with devices that don’t have unique serial numbers.