4、内核源码获取与配置:下载Linux内核源码、配置内核(开启CAN支持、配置CAN驱动)、编译内核与设备树

好,到了真正动手的环节了。前面我们把开发环境搭好,交叉工具链就位,现在该请出主角——Linux内核源码。

这一章我会带你走完三个步骤:下载源码 → 配置内核 → 编译内核与设备树。说白了,就是给我们的网关板子“定制”一个能跑CAN总线的操作系统内核。

核心目标:生成一个支持CAN控制器驱动、且设备树正确描述硬件连接的内核镜像(zImage)和设备树文件(.dtb)。

4.1 获取Linux内核源码

我个人习惯从内核官网或芯片厂商的维护分支获取源码。对于嵌入式网关,我建议直接用芯片厂商提供的长期支持(LTS)内核,因为驱动补丁和BSP支持更完整。

以NXP i.MX6ULL平台为例,我常用的是linux-imx分支,基于Linux 4.19或5.10 LTS。

# 方式一:从内核官网下载主线LTS版本
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.120.tar.xz
tar -xf linux-5.10.120.tar.xz
cd linux-5.10.120

# 方式二:从芯片厂商Git仓库克隆(我推荐这个)
git clone -b imx_5.10.120_2.1.0 https://github.com/nxp-imx/linux-imx.git
cd linux-imx

我的经验:下载后先检查 arch/arm/configs/ 目录下有没有你板子的默认配置文件。比如 imx_v7_defconfig 或 imx6ull_defconfig。有这个文件,后面配置能省一半时间。

4.2 配置内核:开启CAN支持

内核配置是门手艺活。你想想看,一个内核有上万个开关选项,我们只需要把跟CAN相关的打开就行。

我一般分三步走:

  1. 加载默认配置——基于芯片厂商的defconfig做起点
  2. 开启CAN子系统——网络设备中的CAN协议栈
  3. 配置具体CAN控制器驱动——比如FlexCAN、M_CAN或SJA1000

4.2.1 加载并开始配置

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

进入menuconfig界面后,按/键可以搜索关键字。我习惯先搜CAN,看看哪些选项还没开。

4.2.2 必须开启的CAN选项

下面这个表格是我做项目时整理的“CAN配置清单”,照着勾就不会漏。

配置路径 选项名 说明
Networking support → CAN bus subsystem support CONFIG_CAN CAN协议栈总开关,必须选
CAN bus subsystem support → CAN Device Drivers CONFIG_CAN_DEV CAN设备驱动框架
CAN Device Drivers → CAN raw protocol CONFIG_CAN_RAW RAW套接字,应用层收发必备
CAN Device Drivers → CAN broadcast manager CONFIG_CAN_BCM 广播管理,网关路由时常用
CAN Device Drivers → CAN Gateway CONFIG_CAN_GW CAN网关路由功能,做网关必须开
CAN Device Drivers → Platform CAN drivers with Netlink support CONFIG_CAN_FLEXCAN FlexCAN控制器驱动(i.MX6ULL用这个)

注意:如果你用的是M_CAN控制器(比如STM32MP1或某些TI平台),要选 CONFIG_CAN_M_CAN 而不是FlexCAN。选错驱动,内核启动时CAN设备根本不会注册。

4.2.3 配置FlexCAN驱动细节

进入 CAN Device Drivers → Platform CAN drivers with Netlink support,找到FlexCAN选项。我建议把它编译进内核(<*>),而不是模块(<M>)。为什么?因为网关设备通常需要CAN在系统启动时就可用,模块加载会有延迟。

# 确认FlexCAN被选中
# 路径: CAN Device Drivers → Platform CAN drivers with Netlink support
# 选项: [*] FlexCAN driver support

另外,别忘了检查时钟频率和中断配置。FlexCAN驱动需要知道CAN控制器的时钟源频率,这个通常在设备树里指定,但内核配置里也有默认值。

4.3 配置设备树:描述CAN硬件

设备树(Device Tree)是嵌入式Linux的“硬件说明书”。内核通过它知道:CAN控制器挂在哪条总线上、中断号是多少、时钟源是什么。

以i.MX6ULL为例,FlexCAN控制器在芯片内部,连接在aips-bus上。设备树节点大概长这样:

&can1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_flexcan1;
    xceiver-supply = &reg_can_3v3;
    status = "okay";
};

&can2 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_flexcan2;
    xceiver-supply = &reg_can_3v3;
    status = "okay";
};

这里有个坑——引脚复用。我曾经在一个项目里忘了配置pinctrl,结果CAN控制器注册成功了,但引脚没被正确复用,示波器一看TX/RX全是高电平,根本发不出数据。

避坑指南:在 imx6ull-pinfunc.h 里找到FlexCAN的引脚定义,确保UART、SPI等外设没有和CAN引脚冲突。我习惯先在芯片参考手册里画一张“引脚分配表”,再写设备树。

4.4 编译内核与设备树

配置完成后,就是编译。命令很简单,但参数别写错。

# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage

# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 dtbs

# 编译内核模块(如果选了模块)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules

编译完成后,在 arch/arm/boot/ 目录下会生成 zImage,在 arch/arm/boot/dts/ 下生成 .dtb 文件(比如 imx6ull-myboard.dtb)。

4.4.1 验证编译产物

我每次编译完都会做两件事:

  1. 检查内核配置是否包含CAN:grep CONFIG_CAN .config,确认选项是 =y 或 =m
  2. 反编译设备树验证:dtc -I dtb -O dts -o output.dts imx6ull-myboard.dtb,看看CAN节点是不是 status = "okay"

我的习惯:把 zImage 和 .dtb 复制到一个单独的 deploy/ 目录,用日期命名备份。比如 deploy/20240520/zImage。这样调试时能快速回退到某个已知正常的版本。

4.5 本章知识结构图

下面这张图梳理了从源码到启动镜像的完整流程,以及每个环节的关键产出物。

内核源码获取与配置流程 内核源码获取与配置流程 获取内核源码 加载默认配置 (defconfig) 配置内核 (menuconfig) 开启CAN子系统 配置设备树 编译 (make zImage dtbs) 产出物 zImage + .dtb + 模块(.ko) 💡 关键检查点:编译后务必验证 .config 中 CONFIG_CAN=y 且设备树中 CAN 节点 status = "okay"

4.6 常见问题与排查

编译过程中可能会遇到几个典型问题,我列出来供你参考:

问题现象 可能原因 解决方法
编译报错 undefined reference to 'can_rx_register' CAN核心模块未编译进内核 检查 CONFIG_CAN 和 CONFIG_CAN_DEV 是否开启
设备树编译报 syntax error dts文件中括号或分号不匹配 用 dtc -I dts -O dtb 单独检查语法
内核启动后找不到CAN设备 设备树中CAN节点 status = "disabled" 改为 "okay",并确认引脚复用配置正确
FlexCAN驱动报 wrong clock source 设备树中 clock-frequency 与实际不符 查芯片手册确认CAN时钟源频率,修正设备树

曾经有个坑:我遇到过编译通过、设备树也正确,但CAN就是无法收发。最后发现是内核配置里把 CONFIG_CAN_RAW 编译成了模块(=m),而根文件系统里没加载这个模块。所以,如果你不是模块化文件系统,建议把CAN相关选项都编进内核。