〇、环境信息
| 项目 | 值 |
|---|---|
| 板卡 | 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 个文件
两个注意点
?h=v$(uname -r)里的 tag 必须和uname -r严格一致(本例v6.1.118)。用错版本会导致结构体布局不一致,模块加载后可能直接崩。- 这个目录下只有 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 盒子留着做调试期抓包和外部设备接入 —— 两者不冲突。
九、小结
can0是 netdev,USB 枚举成功和 CAN 驱动加载成功是两条独立的链路,前者成功不代表后者。- 判断"内核有没有编 CAN"只能查
/proc/config.gz,find找不到.ko什么也证明不了。 - 厂商 headers 包普遍是 headers-only 打包,源码要自己补;而
make M=走外部模块分支,不触发archprepare,是绕过残缺树的正确姿势。 - 编译外置模块的最大陷阱是
auto.conf:config 不对时命令会"成功"但无产物,排查时极易误判。 - 板载 CAN-FD 在稳定性、延迟、速率上都优于 USB 转换,产品化阶段值得优先考虑。