第4章:Linux内核移植——从源码到设备树

内核移植,说白了就是让Linux内核认识你的硬件板子。很多新手一上来就急着改代码,结果连内核怎么编译的都没搞明白。我个人习惯,先花半天把整个流程走通,再回头抠细节。

4.1 内核源码获取与配置

瑞芯微官方提供了完整的BSP包,里面包含了内核源码、交叉编译工具链、U-Boot等。你可以在Rockchip的GitHub仓库或者百度网盘上找到对应芯片的源码包。

我的建议: 别用最新的LTS内核去移植RK平台,坑太多。直接用Rockchip官方维护的分支,比如 rk-5.10 或 rk-6.1,这些分支已经集成了大量平台补丁。

拿到源码后,第一步是配置内核。RK平台通常提供三类配置方式:

配置方式 命令示例 适用场景
默认配置 make ARCH=arm64 rockchip_linux_defconfig 快速验证,适合首次编译
菜单配置 make ARCH=arm64 menuconfig 按需裁剪驱动和功能
碎片配置 make ARCH=arm64 savedefconfig 生成最小化配置文件

我在项目中遇到过一个问题:用 menuconfig 改完配置后,下次编译时发现改动没生效。后来才意识到,menuconfig 改的是 .config 文件,而 rockchip_linux_defconfig 是默认配置。如果你改了 .config,下次执行 make defconfig 会被覆盖掉。正确的做法是:

# 先加载默认配置
make ARCH=arm64 rockchip_linux_defconfig
# 再手动调整
make ARCH=arm64 menuconfig
# 保存为新的defconfig
make ARCH=arm64 savedefconfig
cp defconfig arch/arm64/configs/my_board_defconfig
注意: 交叉编译时一定要指定 CROSS_COMPILE 环境变量。我曾经忘了加,结果编译出来的内核在板子上跑不起来,排查了半天才发现是用了本机的gcc。

4.2 设备树(DTS)基础与语法

设备树,说白了就是描述硬件信息的配置文件。内核通过它来知道你的板子上有哪些外设、它们挂在哪里、怎么初始化。没有设备树,内核就是个瞎子。

DTS的语法其实很简单,核心就三个概念:节点、属性和值。来看一个典型的RK3588设备树片段:

/dts-v1/;
#include "rk3588.dtsi"

/ {
    model = "Rockchip RK3588 EVB1 Board";
    compatible = "rockchip,rk3588-evb1", "rockchip,rk3588";

    chosen {
        stdout-path = &uart2;
    };

    memory@a00000 {
        device_type = "memory";
        reg = <0x0 0xa00000 0x0 0x80000000>;
    };
};

&uart2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m0_xfer>;
};

这里有几个关键点:

  • /dts-v1/;:版本声明,必须放在文件开头
  • #include:可以像C语言一样包含头文件,.dtsi 是SoC级描述,.dts 是板级描述
  • &uart2:引用标签,用来覆盖或追加已有节点的属性
  • status = "okay":使能某个外设,默认是 "disabled"

核心原则: SoC级通用描述放 .dtsi,板级差异放 .dts。这样换板子时只需要改 .dts,不用动SoC的公共部分。

嗯,这里要注意一个坑:设备树的 reg 属性,地址和大小是成对出现的。比如 reg = <0x0 0xa00000 0x0 0x80000000>,表示起始地址 0xa00000,大小 0x80000000(2GB)。如果你写错了地址范围,内核可能连内存都认不全。

4.3 RK平台DTS文件修改实战

光说不练假把式。我们拿一个实际场景来走一遍:给RK3588的板子添加一个SPI设备。

首先,找到板级DTS文件。RK3588的板级DTS通常在 arch/arm64/boot/dts/rockchip/ 目录下,比如 rk3588-evb1.dts。打开它,你会看到它已经 #include 了 rk3588.dtsi。

接下来,在板级DTS中使能SPI控制器:

&spi2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
    max-freq = <50000000>;

    spidev@0 {
        compatible = "rockchip,spidev";
        reg = <0>;
        spi-max-frequency = <25000000>;
    };
};

这里我踩过一个坑:pinctrl 的引脚配置必须和原理图对得上。RK3588的SPI2有多个复用选项,比如 spi2m0、spi2m1、spi2m2,你得查芯片手册确认你的板子用的是哪一组引脚。我曾经因为选错了复用组,SPI死活不通,最后拿示波器量才发现CLK根本没输出。

修改完DTS后,编译生成DTB:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs

生成的DTB文件在 arch/arm64/boot/dts/rockchip/rk3588-evb1.dtb。把它烧录到板子的 boot 分区,重启后就能在 /dev/spidev2.0 看到设备节点了。

调试技巧: 如果设备节点没出现,先检查内核日志 dmesg | grep spi。常见错误是引脚冲突或者时钟配置不对。我一般会在DTS里加上 status = "okay" 后,先编译一个最小系统验证,避免被其他驱动干扰。

最后,我们来看一下整个内核移植过程中,设备树扮演的角色。下面这张图能帮你理清思路:

RK平台内核移植与设备树关系图 内核源码 设备树源文件(.dts/.dtsi) 交叉编译工具链 编译过程:make ARCH=arm64 CROSS_COMPILE=... dtbs 内核镜像(Image) 设备树二进制(.dtb) 板级运行:U-Boot加载内核+DTB → 内核解析设备树 → 驱动匹配 说明:设备树是连接硬件与内核的桥梁,修改DTS后必须重新编译DTB并烧录到板子

你看,整个流程其实不复杂。内核源码和DTS是输入,交叉编译工具链是加工工具,最终产出内核镜像和DTB。U-Boot启动时把这两个文件加载到内存,内核解析DTB后就知道怎么初始化硬件了。

曾经踩过的坑: 我有一块RK3568的板子,改了DTS后编译烧录,结果系统起不来。查了半天,发现是DTS里引用的一个GPIO在原理图上被复用了。从那以后,我每次改DTS都会先对照原理图过一遍引脚分配,确保没有冲突。

嗯,关于内核移植和设备树,今天就先聊这么多。记住一句话:DTS改得好,移植没烦恼。多花点时间把设备树写对,后面驱动开发会省很多事。