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}"

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}"


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.

serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_43002E000451323333353137-if00 
restart_method: command

Related Articles