当前位置:首页>Linux>USB CDC设备在Linux系统(RK3506)通过udev枚举说明

USB CDC设备在Linux系统(RK3506)通过udev枚举说明

  • 2026-10-11 05:59:17
USB CDC设备在Linux系统(RK3506)通过udev枚举说明

USB CDC设备在Linux系统(RK3506)通过udev枚举说明

做了一个USB转多路RS485接口的硬件,这里说明Linux下驱动它的方法。驱动运行后,会生成n个/dev/ttyACMx设备文件,'x'是"0,1...7"这样的端口号,实测发现这个端口号与硬件物理端口顺序并不一致,于是利用 Linux 系统的 udev 机制实现设备文件名称与物理端口一致。当枚举的USB设备VID=1a86且PID=fe0c时,会为每一个/dev/ttyACMx设备再创建一个软链接,这个软链接的名字与物理端口号一致。当然直接修改/dev/ttyACMx这个文件名也是可以的,喜欢这种方式的可以修改脚本。如果要支持VID,PID不一样的设备,也要适当修改脚本。

USB CDC Linux 设备驱动

Linux系统内核源码默认支持USB CDC设备,但是一般的嵌入式系统默认不选择编译,需要用户通过make menuconfig命令打开配置界面,如下图,找到:"Device Drivers > USB Support"目录下,选择"USB Modem (CDC ACM) support",它依赖于"USB Device support",这个一般默认被选上的。

选择后重新编译内核,烧录运行后,当USB CDC设备插入时就会创建/dev/ttyACMx设备文件。

Linux udev

udev 是 Linux 内核 2.6 引入的用户空间设备管理器,现在是 systemd 的一部分(systemd-udevd),负责动态管理 /dev 目录下的设备节点、处理热插拔事件、自定义设备权限和命名,是所有 Linux 硬件交互的核心基础。

udev 特点:

·只在设备连接时动态创建节点,断开时自动删除

·支持固定设备名(基于厂商 ID、产品 ID 等唯一标识)

·可自定义设备权限、属主、属组

·设备热插拔时自动运行脚本(比如自动挂载 U 盘、启动传感器采集程序)

文件说明:

·99-usb-cdc-1a86-fe0c.rules

·udev 规则文件。

·匹配 SUBSYSTEM=="tty"、KERNEL=="ttyACM*"、idVendor=="1a86"、idProduct=="fe0c" 的串口设备。

·每创建一个 /dev/ttyACMx 设备,都会触发一次该规则。

·通过 PROGRAM 调用 /usr/lib/udev/usb-cdc-1a86-fe0c-name,并用脚本输出结果创建 /dev/ttyUSBCDC* 软链接。

·usb-cdc-1a86-fe0c-name

·udev helper 脚本。

·根据 USB interface number 计算软链接编号。

·只处理偶数 interface number,例如 00、02、04、06、08、0a、0c。

·不调用 udevadm,避免在 udev PROGRAM 中死锁。

目标安装路径

在 RK3506 rootfs 中,两个文件应安装到:

/etc/udev/rules.d/99-usb-cdc-1a86-fe0c.rules          /usr/lib/udev/usb-cdc-1a86-fe0c-name

注意:这两个文件要有可执行权限。 RK3506 SDK的 Buildroot overlay 中的对应位置是:

buildroot/board/rockchip/rk3506/fs-overlay/etc/udev/rules.d/99-usb-cdc-1a86-fe0c.rules          buildroot/board/rockchip/rk3506/fs-overlay/usr/lib/udev/usb-cdc-1a86-fe0c-name

RK3506实机运行效果如下:

调试命令

修改规则后,板端可重新加载 udev 规则:

udevadm control --reload-rules          udevadm trigger --subsystem-match=tty

查看当前软链接:

ls -l /dev/ttyUSBCDC*

查看设备 interface number:

udevadm info -a -n /dev/ttyACM0

注意事项

·udev 规则是按 /dev/ttyACMx 设备逐个触发,不是每插入一台 USB 设备只触发一次。

·脚本输出到 stdout 的内容会被 udev 用作 SYMLINK+="%c" 的名字,因此不要在脚本中随意 echo 调试信息。

·如果需要调试输出,建议临时写入 /tmp/*.log,不要写 stdout。

SYMLINK_STRIDE 当前按 7 口 USB CDC 设备设计,如设备串口数量变化,可手工修改该全局变量。

附录

·99-usb-cdc-1a86-fe0c.rules

# USB CDC 1a86:fe0c -> /dev/ttyUSBCDCy, ordered by interface number from 0          SUBSYSTEM=="tty", KERNEL=="ttyACM*", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="fe0c", \          IMPORT{builtin}="usb_id", \          PROGRAM="/usr/lib/udev/usb-cdc-1a86-fe0c-name %E{ID_USB_INTERFACE_NUM} %E{ID_PATH} %E{DEVPATH}", \          SYMLINK+="%c"

·usb-cdc-1a86-fe0c-name

#!/bin/sh          # USB CDC (VID=1a86 PID=fe0c) -> /dev/ttyUSBCDCy symlink namer          #y starts at hex(interface_number) / 2          #if /dev/ttyUSBCDCy exists, keep adding SYMLINK_STRIDE until it is free.          # Args: ID_USB_INTERFACE_NUM ID_PATH DEVPATH          # NOTE: must not call udevadm here (deadlock when run from udev PROGRAM).          PATH=/sbin:/bin:/usr/sbin:/usr/bin          VID="1a86"          PID="fe0c"          TTY_PREFIX="ttyACM"          SYMLINK_PREFIX="ttyUSBCDC"          SYMLINK_STRIDE=7          MAX_SYMLINK=511          iface_to_dec() {          val="$(printf '%s' "$1" | tr 'A-F' 'a-f')"          case "$val" in          ''|*[!0-9a-f]*) return 1 ;;          *[!0-9]*)          printf '%d' "0x$val"          ;;          *)          printf '%d' "$((10#$val))"          ;;          esac          }          iface_num_from_devpath() {          devpath="$1"          cur="$(readlink -f "/sys$devpath/device" 2>/dev/null)" || return 1          while [ -n "$cur" ] && [ "$cur" != "/" ]; do          if [ -f "$cur/bInterfaceNumber" ]; then          cat "$cur/bInterfaceNumber"          return 0          fi          cur="$(dirname "$cur")"          done          return 1          }          first_available_symlink() {          num="$1"          while [ "$num" -le "$MAX_SYMLINK" ]; do          if [ ! -e "/dev/${SYMLINK_PREFIX}${num}" ]; then          printf '%s\n' "$num"          return 0          fi          num=$((num + SYMLINK_STRIDE))          done          return 1          }          iface_num="$1"          devpath="$3"          [ -n "$devpath" ] || exit 1          if [ -z "$iface_num" ]; then          iface_num="$(iface_num_from_devpath "$devpath")" || exit 1          fi          iface_dec="$(iface_to_dec "$iface_num")" || exit 1          [ $((iface_dec % 2)) -eq 0 ] || exit 1          index=$((iface_dec / 2))          link_num="$(first_available_symlink "$index")" || exit 1          printf '%s%d\n' "$SYMLINK_PREFIX" "$link_num"

(End)

最新文章

随机文章