RK3588 上 USB 转 CAN 认不到 can0?一次从误判到自编译驱动的完整排查实录

〇、环境信息

项目 值
板卡 RK3588(Firefly 官方固件)
内核 6.1.118(Rockchip/Firefly 6.1 BSP)
内核头文件 linux-headers-6.1-arm64_arm64.deb(6.1 固件自带,直接在板子 /boot/ 目录下)
USB 转 CAN VID:PID = 0c72:000c,Product 串为 XCAN-USB
宿主系统 Ubuntu 22.04 arm64

一、现象

USB 转 CAN 盒子插上后,lsusb 认得到设备,但怎么都找不到 can0:

$ lsusb
Bus 001 Device 003: ID 0c72:000c PEAK System PCAN-USB

$ ip -br link
eth0   UP     5c:f8:38:70:c2:37
eth1   DOWN   ...
lo     UNKNOWN
# 没有 can0

dmesg 里 USB 枚举是正常的:

[4325.154528] usb 1-1: new full-speed USB device number 2 using xhci-hcd
[4325.303249] usb 1-1: New USB device found, idVendor=0c72, idProduct=000c
[4325.303286] usb 1-1: Product: XCAN-USB

枚举成功之后就再无任何一行驱动日志了。


二、先建立一个核心认知:USB 枚举成功 ≠ CAN 驱动加载成功

这是整件事最关键的一句话。can0 在内核里是一个 netdev(网络设备),只有 CAN 设备驱动 probe 成功,内核才会创建它。

完整链路是这样的:

硬件插上
   │
   ▼
① USB 枚举  ──── lsusb 有设备 / dmesg "new full-speed USB device"
   │
   ▼
② 设备驱动匹配 ── dmesg "peak_usb 1-1:1.0: PEAK-USB ..."
   │
   ▼
③ SocketCAN netdev ── ip link 出现 can0
   │
   ▼
④ 应用层      ── candump / cansend

我们的断点在 ② → ③ 之间。另外提醒一句:can0 只会出现在 ip link / ifconfig -a,不会出现在 /dev 下,别去 /dev 里找。


三、第一个误判:find 找不到 .ko 不等于"内核没编 CAN"

一开始的判断依据是这条命令:

$ find /lib/modules/$(uname -r) -iname "*gs_usb*" -o -iname "*slcan*" -o -iname "*can_dev*"
# 空输出

于是判断"内核根本没编 CAN 子系统,需要重刷内核"。

这个判断是错的。

原因:当配置项是 =y(built-in,直接编进内核镜像)时,根本不会产生 .ko 文件。所以 find 为空的三种可能——没编、编成了 =y、编成了别的路径——它区分不了。

正确做法只有一个:直接查运行内核的 config。

zcat /proc/config.gz | grep -iE "CONFIG_CAN|CAN_USB"

四、看 config:其实是"只缺一个驱动"

关键几行长这样:

CONFIG_CAN=y                  ← SocketCAN 核心,已内建
CONFIG_CAN_RAW=y              ← 必开,已内建
CONFIG_CAN_DEV=y              ← 必开,已内建
CONFIG_CAN_NETLINK=y
CONFIG_MODVERSIONS is not set ← 后面很关键
...
# CONFIG_CAN_PEAK_USB is not set   ← 唯一缺口
# CONFIG_CAN_GS_USB is not set
# CONFIG_CAN_KVASER_USB is not set
...
CONFIG_CANFD_ROCKCHIP=y       ← 意外收获,见第九节
配置项 值 含义
CONFIG_CAN / CAN_RAW / CAN_DEV =y SocketCAN 协议栈完整可用
CONFIG_CAN_PEAK_USB not set 唯一的缺口
CONFIG_MODVERSIONS is not set 不校验符号 CRC,意味着自己编的模块不用担心版本签名

结论修正:不是"内核没有 CAN",而是"CAN 核心都在,只缺一个 USB 适配器驱动"。这也意味着 —— 不必重刷内核,单独编一个模块就行。

顺带说明:0c72:000c 是 PEAK System PCAN-USB 的官方 VID:PID,peak_usb 一个驱动覆盖 PCAN 全系(USB / USB Pro / USB FD),所以这种兼容方案的盒子可以走标准驱动。


五、第二个坑:厂商 headers 包是 “headers-only” 打包

既然要走"自编译模块"这条路,就得用和固件同版本的内核头文件。

头文件从哪来(参考 Firefly 官方维基 《获取 Linux-Headers》):

  • Kernel 6.1:SDK 默认会把 linux-headers 编译进 extboot,板子的 /boot/ 目录下直接就有 linux-headers-6.1-arm64_arm64.deb —— 安装步骤见 《安装 Linux-Headers》。
  • Kernel 5.10 / 4.19:需要到 Firefly 官方《资料下载》页面按板卡下载,在「资源」处找名称含 linux-headers 的包。
  • 有定制需求或需要与固件严格一致的版本,官方建议直接用 SDK 自己制作:./build.sh kerneldeb(产物为 linux-headers-x.xx.xxx_x.xx.xxx-xxx_arm64.deb)。

装好后 /lib/modules/$(uname -r)/build 会指向 /usr/src/linux-headers-6.1-arm64。然后开始编译 —— 报错来了:

$ sudo make modules_prepare
make[1]: *** No rule to make target 'arch/arm64/tools/gen-cpucaps.awk',
          needed by 'arch/arm64/include/generated/asm/cpucaps.h'.  Stop.
make: *** [arch/arm64/Makefile:178: archprepare] Error 2

补上 arch/arm64/tools/ 目录后又变成:

$ sudo make M=drivers/net/can/usb/peak_usb modules
make[1]: *** No rule to make target '.../peak_usb/pcan_usb_core.o'.  Stop.

看一眼目录就明白发生了什么:

$ ls /usr/src/linux-headers-6.1-arm64/drivers/net/can/usb/peak_usb/
Makefile          ← 只有一个 Makefile

根因

厂商的 headers 包按 headers-only 方式打包:保留 Makefile / Kconfig / 头文件 / 预生成物,剥掉了所有驱动源码 .c。
(这类包通常是为构建 DTB 准备的,所以 ./scripts/builddtb.sh 能跑,但 make modules_prepare 跑不通 —— 两回事。)

关键机制:make M= 可以绕过 prepare

翻一下顶层 Makefile 的依赖关系:

  • make modules / make modules_prepare 是 in-tree 分支,会触发 archprepare,要整棵树的源码 —— 残缺的树必然失败;
  • make M=<目录> modules 走的是 KBUILD_EXTMOD(外部模块)分支,不触发 archprepare。

所以:不用去补那棵残缺的树,把驱动源码补齐,就能直接编译。


六、动手:自编译 peak_usb 模块

6.1 取源码(纯上游驱动,厂商没有改动)

peak_usb 是标准上游驱动,直接从 kernel.org 按与运行内核完全相同的 tag 拉:

mkdir -p ~/peak_usb && cd ~/peak_usb

B="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/net/can/usb/peak_usb"
for f in Makefile pcan_usb.c pcan_usb_core.c pcan_usb_core.h \
         pcan_usb_fd.c pcan_usb_pro.c pcan_usb_pro.h; do
  curl -fsSL "$B/$f?h=v$(uname -r)" -o "$f" && echo "OK   $f" || echo "FAIL $f"
done

ls -l    # 必须是 7 个文件

:warning: 两个注意点

  1. ?h=v$(uname -r) 里的 tag 必须和 uname -r 严格一致(本例 v6.1.118)。用错版本会导致结构体布局不一致,模块加载后可能直接崩。
  2. 这个目录下只有 7 个文件,没有 pcan_usb.h / pcan_usb_fd.h,别凭印象去找。

看一下它的 Makefile,其实就两行:

obj-$(CONFIG_CAN_PEAK_USB) += peak_usb.o
peak_usb-y = pcan_usb_core.o pcan_usb.o pcan_usb_pro.o pcan_usb_fd.o

第二行是 peak_usb-y,无条件(不是 peak_usb-$(CONFIG_...))——所以那 4 个 .c 一个都不能少,少一个就报 No rule to make target 'xxx.o'。

6.2 编译前必查一项(静默杀手)

grep CONFIG_CAN_PEAK_USB /usr/src/linux-headers-$(uname -r)/include/config/auto.conf
# 期望输出:CONFIG_CAN_PEAK_USB=m

这一条非常重要。 kbuild 只读构建树里的 include/config/auto.conf。如果 CONFIG_CAN_PEAK_USB 不是 =m,那么 obj-$(CONFIG_CAN_PEAK_USB) 会展开成空 —— 编译命令会显示"成功",但一个 .ko 都不产出,你会以为是自己放错了目录,白折腾半天。

如果没有这一项:

cd /usr/src/linux-headers-$(uname -r)
sudo zcat /proc/config.gz > .config
sudo ./scripts/config --module CAN_PEAK_USB
sudo make olddefconfig

6.3 编译(独立目录,不污染系统)

cd ~/peak_usb
make -C /lib/modules/$(uname -r)/build M=$PWD modules -j$(nproc)

期望输出:

  CC [M]  /home/firefly/peak_usb/pcan_usb_core.o
  CC [M]  /home/firefly/peak_usb/pcan_usb.o
  CC [M]  /home/firefly/peak_usb/pcan_usb_pro.o
  CC [M]  /home/firefly/peak_usb/pcan_usb_fd.o
  LD [M]  /home/firefly/peak_usb/peak_usb.o
  MODPOST /home/firefly/peak_usb/Module.symvers
  CC [M]  /home/firefly/peak_usb/peak_usb.mod.o
  LD [M]  /home/firefly/peak_usb/peak_usb.ko

为什么用独立目录? 因为 make M= 本身就是 kbuild 的外部模块模式,它认的是 -C 指向的那棵构建树,源码放在哪其实都行(放树内原位 drivers/net/can/usb/peak_usb/ 也一样能编)。放独立目录的好处是:不动 /usr/src、不用 root、可以 git 管理、换板子能直接复用。

6.4 加载

调试期最快(重启失效):

sudo insmod ~/peak_usb/peak_usb.ko

正式 / 开机自动加载:

D=/lib/modules/$(uname -r)/kernel/drivers/net/can/usb/peak_usb
sudo mkdir -p $D
sudo cp ~/peak_usb/peak_usb.ko $D/
sudo depmod -a
echo peak_usb | sudo tee /etc/modules-load.d/can.conf

重启后 lsmod 里就能看到 peak_usb,不用再手动 insmod。

6.5 起接口

sudo ip link set can0 up type can bitrate 500000
ip -details link show can0        # 看 state UP / ERROR-ACTIVE

sudo apt install -y can-utils
candump -e any

七、踩坑清单(建议收藏)

# 现象 真正原因 对策
1 find /lib/modules -name "*.ko" 找不到 编成 =y 时根本没有 .ko 必须查 zcat /proc/config.gz,别用 find 下结论
2 No rule to make target 'gen-cpucaps.awk' headers 包缺 arch/arm64/tools/ 从上游同版本补齐该目录
3 No rule to make target 'pcan_usb_core.o' headers 包剥掉了驱动 .c 源码 从上游补齐源文件,这不是配置问题
4 编译"成功"但没有 .ko auto.conf 里 CONFIG_XXX 不是 =m 补 config → make olddefconfig
5 No such file or directory: <M目录>/Makefile M= 目录里没有任何源码 先确认 7 个文件都在
6 lsmod 显示 Used by 0 USB 设备驱动不由模块引用计数持有 正常现象,probe 成败要看 dmesg
7 out-of-tree 目录里 Module.symvers 是 0 字节 那是 MODPOST 的产物,不是输入 CONFIG_MODVERSIONS=n 时无害
8 invalid module format / vermagic 不符 编译用的源码版本 ≠ uname -r tag 必须用 v$(uname -r)
9 模块已加载但 dmesg 无 probe 日志 设备没触发 probe 重新插拔 USB 即可

八、延伸:要不要干脆用板载 CAN?

排查过程中有个意外发现:CONFIG_CANFD_ROCKCHIP=y —— RK3588 自带的 CAN-FD 控制器驱动已经编进内核了,只是设备树里没 enable。

如果你是要把 CAN 用在设备本体内部总线(关节驱动器、BMS、传感器),板载方案明显更合适:

对比项 板载 CAN-FD USB 转 CAN
占用资源 无 占用 USB 带宽
中断延迟 直连中断,可控 USB 事务调度,抖动较大
稳定性 无枚举 / 掉线风险 有
速率 支持 CAN-FD 数据段(最高 5 Mbps) 通常仅 CAN 2.0
前提 设备树 enable + 引脚已引出 插上就能用

启用方式(&can0 节点 + 对应 pinmux 组,具体组号要对照板子原理图):

&can0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&can0m0_pins>;
};

我的选择:产品固件走板载 CAN-FD,USB 盒子留着做调试期抓包和外部设备接入 —— 两者不冲突。


九、小结

  1. can0 是 netdev,USB 枚举成功和 CAN 驱动加载成功是两条独立的链路,前者成功不代表后者。
  2. 判断"内核有没有编 CAN"只能查 /proc/config.gz,find 找不到 .ko 什么也证明不了。
  3. 厂商 headers 包普遍是 headers-only 打包,源码要自己补;而 make M= 走外部模块分支,不触发 archprepare,是绕过残缺树的正确姿势。
  4. 编译外置模块的最大陷阱是 auto.conf:config 不对时命令会"成功"但无产物,排查时极易误判。
  5. 板载 CAN-FD 在稳定性、延迟、速率上都优于 USB 转换,产品化阶段值得优先考虑。
1 Like