4、设备树文件结构:dts/dtsi/dtb/dtc工具链及编译流程

设备树(Device Tree)是嵌入式Linux系统中描述硬件拓扑结构的核心机制。在网关驱动加载流程中,设备树承担着将CAN控制器、SPI总线、GPIO等外设资源从Bootloader传递到内核的桥梁作用。理解.dts、.dtsi、.dtb以及dtc工具链的编译流程,是正确初始化CAN控制器的前提。

4.1 设备树文件类型与作用

设备树源文件采用树形结构描述硬件节点,主要包含三种文件类型:

文件后缀 全称 作用 是否可编译
.dts Device Tree Source 顶层设备树源文件,描述单板级硬件 是(直接输入dtc)
.dtsi Device Tree Source Include 公共设备树头文件,被.dts通过#include引用 否(仅作为包含文件)
.dtb Device Tree Blob 二进制设备树,由dtc编译生成,供内核解析 否(编译产物)
.dtbo Device Tree Blob Overlay 设备树覆盖文件,用于运行时动态修改设备树 是(需特殊编译选项)
实践要点:在网关项目中,.dtsi通常存放SoC级CAN控制器IP核的公共描述(如i.MX8M的FlexCAN),而.dts则定义具体板级上CAN收发器的GPIO使能引脚、时钟频率等差异化配置。

4.2 设备树源文件结构示例

以下是一个典型的CAN控制器设备树节点定义,展示.dtsi与.dts的协作关系:

// imx8mm.dtsi —— SoC级公共描述
soc@0 {
    bus@30800000 {
        flexcan1: can@30890000 {
            compatible = "fsl,imx8mm-flexcan", "fsl,imx6q-flexcan";
            reg = <0x30890000 0x10000>;
            interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&clk IMX8MM_CLK_CAN1>,
                     <&clk IMX8MM_CLK_CAN1_OSC>;
            clock-names = "ipg", "per";
            fsl,stop-mode = <&gpr 0x10 3>;
            status = "disabled";
        };
    };
};

// my-gateway.dts —— 板级具体配置
#include "imx8mm.dtsi"

&flexcan1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_flexcan1>;
    xceiver-supply = <®_can_3v3>;  // CAN收发器电源
    fsl,clk-source = <0>;             // 时钟源选择
    status = "okay";                   // 使能CAN控制器
};
关键字段说明:
  • compatible:驱动匹配的核心字符串,内核通过该字段找到对应CAN驱动。
  • reg:CAN控制器的寄存器基地址和地址范围。
  • interrupts:中断号与触发类型,CAN接收/错误中断依赖此配置。
  • clocks:CAN控制器的工作时钟和位时钟,直接影响波特率计算。
  • status:节点使能开关,"okay"表示启用,"disabled"表示禁用。

4.3 dtc工具链与编译流程

设备树的编译流程涉及预处理器(cpp)和设备树编译器(dtc)两个阶段。完整的编译流水线如下图所示:

设备树编译流程(dts → dtb) .dts 源文件 .dtsi 头文件 .dts 绑定宏 cpp 预处理器 展开 #include / #define .dts.pre 预处理文件 dtc 编译器 -I dts -O dtb .dtb 二进制文件 Linux 内核 of_* 解析 编译命令示例: dtc -I dts -O dtb -o my.dtb my.dts

4.4 编译命令与Makefile集成

在实际网关开发中,设备树编译通常集成在Linux内核的Kbuild系统中。以下是手动编译和自动编译两种方式:

4.4.1 手动编译命令

# 步骤1:预处理(展开#include和#define)
cpp -nostdinc -I include -I arch/arm64/boot/dts/freescale \
    -undef -x assembler-with-cpp \
    my-gateway.dts my-gateway.dts.pre

# 步骤2:dtc编译生成dtb
dtc -I dts -O dtb -o my-gateway.dtb my-gateway.dts.pre

# 或者一步完成(推荐)
dtc -I dts -O dtb -o my-gateway.dtb my-gateway.dts

4.4.2 内核Makefile集成

# arch/arm64/boot/dts/freescale/Makefile
dtb-$(CONFIG_ARCH_MXC) += my-gateway.dtb

# 编译时自动调用:
# make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
常见编译错误与排查:
  • “Error: phandle is not defined”:引用了未定义的&label,检查.dtsi中是否遗漏节点标签。
  • “Error: syntax error”:检查分号、花括号、逗号是否匹配,设备树对语法严格敏感。
  • “Warning: unit address mismatch”:节点名中的地址与reg属性首地址不一致,需对齐。
  • “FATAL: Cannot allocate memory”:设备树文件过大或递归包含导致栈溢出,检查#include循环引用。

4.5 设备树覆盖(Overlay)与动态加载

在网关产品中,有时需要在运行时动态添加或修改CAN控制器配置(如热插拔CAN模块)。设备树覆盖(.dtbo)机制支持这种场景:

// can-extension-overlay.dts
/dts-v1/;
/plugin/;

&flexcan2 {
    status = "okay";
    pinctrl-0 = <&pinctrl_flexcan2>;
    xceiver-supply = <®_can_5v>;
};

// 编译覆盖文件
dtc -I dts -O dtb -@ -o can-extension-overlay.dtbo can-extension-overlay.dts

// 内核加载覆盖(通过configfs)
mkdir /sys/kernel/config/device-tree/overlays/can-ext
cat can-extension-overlay.dtbo > /sys/kernel/config/device-tree/overlays/can-ext/dtbo
覆盖编译关键选项:-@选项用于生成符号表(symbols),使覆盖文件能够解析基设备树中的节点标签。没有-@选项,覆盖文件无法正确引用&flexcan2。

4.6 设备树在CAN控制器初始化中的关键作用

设备树解析完成后,内核of_*系列API将硬件资源传递给CAN驱动。以下表格总结了设备树属性与CAN驱动初始化参数的对应关系:

设备树属性 驱动获取API 初始化用途
reg of_iomap() 映射CAN控制器寄存器地址空间
interrupts platform_get_irq() 注册CAN中断处理函数
clocks clk_get() 获取CAN工作时钟,计算波特率分频系数
pinctrl-* pinctrl_bind_pins() 配置CAN_TX/CAN_RX引脚复用功能
xceiver-supply regulator_get() 使能CAN收发器电源(如TJA1040的VCC)
fsl,clk-source of_property_read_u32() 选择CAN控制器时钟源(振荡器/外设时钟)
初始化流程总结:设备树编译生成的.dtb由Bootloader(如U-Boot)加载到内存,内核启动时通过unflatten_device_tree()解析为节点树,CAN驱动在probe()回调中调用of_match_table匹配compatible字符串,随后通过上述API获取硬件资源完成控制器初始化。