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)两个阶段。完整的编译流水线如下图所示:
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获取硬件资源完成控制器初始化。