Missing /dev/serial/by-id on Debian variants.
Fix for missing /dev/serial/by-id on recent Debian derivatives due to a break in systemd.
Recently I rebuilt my 3D Printer’s Klipper environment setup which is powered via a couple of Udoo x86 SBCs that run DietPi for x86_64.
As Klipper talks to the MCU via USB, one needs to find the printer (or device) specific serial port and the easiest way is to get the path via /dev/serial/by-id
.
The problem
However, Debian 13 (Bookworm) didn’t have /dev/serial/by-id
available which I thought was unusual.
ls -lah /dev/serial/by-id
ls: cannot access '/dev/serial/by-id': No such file or directory
Digging into this, there’s a PR25246: udev: fix by-id symlinks by @yuwata from November 2022 for systemd that updates the 60-serial.rules configuration that broke the path from being created. However this fix hasn’t made it’s way into the later Debian releases it seems.
The Fix
The easiest way to fix this locally - knowing it will break in the future if you update your system and the file is overwritten, is to simply apply the changes in the change to your Debian source.
Backup your existing 60-serial.rules
file:
$ sudo cp /usr/lib/udev/rules.d/60-serial.rules /usr/lib/udev/rules.d/60-serial.rules.bak
Then replace it with the PR’s changes:
Github Raw:60-serial.rules
Get the full raw output from Github Raw: 60-serial.rules.
# do not edit this file, it will be overwritten on update
ACTION=="remove", GOTO="serial_end"
SUBSYSTEM!="tty", GOTO="serial_end"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
SUBSYSTEMS=="pci", ENV{ID_BUS}=="", ENV{ID_BUS}="pci", \
ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}", \
IMPORT{builtin}="hwdb --subsystem=pci"
# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
IMPORT{builtin}="path_id"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
ENV{ID_BUS}=="", GOTO="serial_end"
ENV{ID_SERIAL}=="", GOTO="serial_end"
ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"
LABEL="serial_end"
Once done, you can reload the rules with:
$ sudo udevadm control --reload-rules && sudo udevadm trigger
You’ll find the /dev/serial/by-id/
will have been created and mapped appropriately now :)
Why is this important?
Example, for the following lsusb
fragment:
Bus 001 Device 007: ID 8087:0ab6 Intel Corp. UDOO X86
Bus 001 Device 008: ID 1d50:614e OpenMoko, Inc. rp2040
...
Bus 001 Device 003: ID 1d50:614e OpenMoko, Inc. stm32f407xx
Bus 001 Device 002: ID 1a86:7523 QinHeng Electronics CH340 serial converter
We have the following devices listed - /dev/serial/by-path
:
pci-0000:00:14.0-usb-0:1:1.0-port0 -> ../../ttyUSB0
pci-0000:00:14.0-usb-0:2:1.0 -> ../../ttyACM0
pci-0000:00:14.0-usb-0:3.4:1.0 -> ../../ttyACM2
pci-0000:00:14.0-usb-0:4:1.0 -> ../../ttyACM1
But ideally, we’d want to know their unique-id - /dev/serial/by-id
:
usb-1a86_USB_Serial-if00-port0 -> ../../ttyUSB0
usb-Klipper_rp2040_E66118F5D7109236-if00 -> ../../ttyACM2
usb-Klipper_stm32f407xx_43002E000451323333353137-if00 -> ../../ttyACM0
usb-UDOO_UDOO_X86__K71212493-if00 -> ../../ttyACM1
As the ID above is what we’d be saving in the printer.cfg
for Klipper.
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_43002E000451323333353137-if00
restart_method: command
...