3、设备树基础语法:节点、属性与编译流程

设备树(Device Tree)是RK3588等ARM64平台下硬件描述的核心机制,它承担着将板级硬件拓扑、寄存器地址、中断映射、时钟关系等信息从内核源码中剥离出来的职责。对于嵌入式工程师而言,理解设备树的节点与属性语法,掌握dts/dtsi/dtb三种文件形态的关联与编译流程,是进行内核适配、驱动调试和系统裁剪的前提。本章将从语法要素、文件组织、编译工具链三个维度展开,并结合RK3588实际开发中的典型场景进行拆解。

RK3588 设备树文件关系与编译流程 rk3588s.dtsi SoC级描述(CPU/中断/GIC) 包含 .dtsi 头文件 rk3588-evb.dtsi 板级公共配置(DDR/PMIC) include 公共外设节点 rk3588-evb1.dts 具体产品配置(GPIO/外设) 最终编译入口文件 rk3588-evb1.dtb 二进制设备树二进制 Bootloader加载并传递 dtc编译 编译工具链:dtc + cpp预处理器 cpp处理 #include/#define → dtc解析节点与属性 → 生成dtb 内核OF API解析 of_find_node_by_path / of_property_read_u32 / of_irq_get 关键语法要素 • 节点:/ { soc { uart0 { } } } • 属性:compatible / reg / status • reg: 描述地址空间 • interrupt: • 引用:&uart0 { status = "okay"; } • 覆盖:/delete-node/ /delete-property/ 编译流程(Makefile视角) cpp预处理 展开#include与宏 dtc语法解析 检查节点/属性合法性 dtb生成 二进制格式输出 fdtdump验证 反编译检查内容 常用命令:dtc -I dts -O dtb -o xxx.dtb xxx.dts / fdtdump xxx.dtb / dtc -I dtb -O dts

3.1 设备树节点与属性的核心语法

3.1.1 节点(Node)的层次结构与命名规则

设备树以树形结构描述硬件,根节点为“/”,其下挂载soc、chosen、memory、leds等子节点。每个节点代表一个硬件设备或总线控制器,节点名应遵循“名称@地址”的格式,其中地址部分用于区分同类型设备。例如RK3588的UART节点通常命名为uart0: serial@feb50000,其中uart0是label(标签),serial是设备类型,feb50000是该设备寄存器基地址。

节点之间通过父子关系表达硬件拓扑。在RK3588的rk3588s.dtsi中,可以看到典型的嵌套结构:根节点下包含soc节点,soc节点下再挂载i2c0、uart0、pcie2x1l0等外设控制器节点。这种层级关系与芯片内部的总线架构一一对应,驱动开发时通过of_find_node_by_path("/soc/uart0")即可定位到目标节点。

3.1.2 关键属性详解

属性名 含义 RK3588典型示例 驱动获取方式
compatible 设备兼容性标识,用于驱动匹配 compatible = "rockchip,rk3588-uart", "snps,dw-apb-uart" of_device_is_compatible()
reg 寄存器地址与长度,描述设备IO资源 reg = <0x0 0xfeb50000 0x0 0x100> platform_get_resource()
interrupts 中断号、触发类型与标志 interrupts = <GIC_SPI 330 IRQ_TYPE_LEVEL_HIGH> platform_get_irq()
interrupt-parent 指定中断控制器父节点 interrupt-parent = <&gic> 由内核中断子系统自动解析
clocks 时钟源引用与ID clocks = <&cru CLK_UART0>, <&cru PCLK_UART0> clk_get()
pinctrl-0 引脚复用配置 pinctrl-0 = <&uart0_xfer> 内核pinctrl子系统自动应用
status 设备启用状态(okay/disabled) status = "okay" 驱动探测时检查

其中reg属性需要特别关注其格式。在RK3588的64位地址空间中,reg通常使用两个32位单元表示地址、两个32位单元表示长度,即<0x0 0xfeb50000 0x0 0x100>。这种格式由根节点的#address-cells和#size-cells属性决定,前者为2表示地址占2个32位单元,后者为2表示长度占2个32位单元。驱动开发时若直接读取reg值,必须通过of_address_to_resource()转换,避免手动拼接地址导致错误。

3.1.3 节点引用与覆盖机制

在板级dts文件中,经常需要对SoC级dtsi中的节点进行修改。RK3588的BSP开发中,最常见的操作是通过label引用节点并覆盖属性:

// 在 rk3588-evb.dtsi 中
&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_xfer>;
};

// 删除不需要的节点
&i2c4 {
    status = "disabled";
};

// 删除整个节点
/delete-node/ &gmac1;

这种机制使得SoC级dtsi保持通用性,板级dts只做增量修改,极大提高了代码复用率。但需要注意,覆盖操作必须发生在引用之后,且同一节点不能重复定义相同属性(除非使用/delete-property/先删除)。

经验提示:在RK3588开发中,若发现修改dts后设备树未生效,首先检查是否在正确的dts文件中修改。编译系统通常以CONFIG_DTC指定的dts为最终入口,板级dtsi被include后,其修改优先级低于直接定义在dts中的内容。

3.2 dts/dtsi/dtb三种文件形态与编译流程

3.2.1 文件类型与职责划分

设备树源文件分为.dts(Device Tree Source)和.dtsi(Device Tree Source Include)两种文本格式,编译产物为.dtb(Device Tree Blob)二进制文件。在RK3588 SDK中,文件组织遵循“SoC级dtsi → 板级dtsi → 产品级dts”的层级:

  • SoC级dtsi(如rk3588s.dtsi):描述芯片内部所有IP的默认配置,包括CPU核心、GIC中断控制器、CRU时钟、DDR控制器、各外设控制器的寄存器地址与中断号。此文件由芯片原厂维护,一般不做修改。
  • 板级dtsi(如rk3588-evb.dtsi):描述某类开发板的公共配置,如PMIC型号、DDR容量、以太网PHY地址、USB端口分配等。同一系列开发板可共用此文件。
  • 产品级dts(如rk3588-evb1.dts):针对具体产品型号的最终配置,包含GPIO按键定义、LED指示灯、特定外设的使能与参数调整。编译系统直接编译此文件。

3.2.2 编译流程与工具链

设备树的编译并非简单的dtc一步完成,而是先经过C预处理器(cpp)展开宏和头文件,再交给dtc解析。RK3588 SDK的Makefile中,编译命令通常如下:

# 1. 预处理:展开 #include 和 #define
cpp -nostdinc -I arch/arm64/boot/dts/rockchip/ \
    -undef -D__DTS__ -x assembler-with-cpp \
    rk3588-evb1.dts rk3588-evb1.dts.preprocessed

# 2. dtc编译:生成dtb
dtc -I dts -O dtb -o rk3588-evb1.dtb \
    -@ -H epapr -R 8 -p 0 \
    rk3588-evb1.dts.preprocessed

# 3. 反编译验证(调试用)
dtc -I dtb -O dts -o rk3588-evb1.dts.debug rk3588-evb1.dtb

其中-@选项允许生成symbol表,支持运行时overlay;-H epapr指定头部格式;-R 8设置填充字节。实际开发中,内核编译系统会通过make dtbs自动完成上述步骤,但理解底层命令有助于排查编译错误。

3.2.3 编译产物在启动流程中的传递

生成的.dtb文件在RK3588启动流程中扮演关键角色。U-Boot从存储介质(eMMC/SD卡)加载boot.img时,会同时读取dtb分区或从boot.img中提取dtb,将其放置在内存特定地址(如0x0f000000),然后通过booti命令将dtb地址作为参数传递给内核。内核启动早期,setup_machine_fdt()解析dtb中的compatible属性匹配machine_desc,随后遍历所有节点完成设备注册。

实战场景:某项目基于RK3588定制底板,需要新增一个I2C接口的触摸屏。开发流程如下:
  1. 在rk3588-evb1.dts中通过&i2c6引用I2C6节点,设置status = "okay"并配置引脚复用:pinctrl-0 = <&i2c6_xfer>。
  2. 在I2C6节点下新增触摸屏子节点,填写compatible = "goodix,gt911"、reg = <0x5d>(I2C地址)、interrupt-parent = <&gpio1>、interrupts = <RK_PB2 IRQ_TYPE_LEVEL_LOW>。
  3. 执行make dtbs编译,将生成的dtb烧录到开发板。
  4. 启动后通过ls /dev/input/确认触摸设备节点出现,若未出现则使用fdtdump检查dtb中节点是否完整,再通过dmesg查看I2C探测日志。

3.3 中断与reg属性的深度解析

3.3.1 中断描述的三要素

RK3588使用GIC(Generic Interrupt Controller)作为中断控制器,设备树中interrupts属性由三部分组成:中断类型、中断号、触发标志。中断类型通常使用GIC_SPI(共享外设中断)或GIC_PPI(私有外设中断),中断号在RK3588数据手册中有明确编号,触发标志包括IRQ_TYPE_LEVEL_HIGH、IRQ_TYPE_EDGE_RISING等。例如UART0的中断描述为:

interrupts = <GIC_SPI 330 IRQ_TYPE_LEVEL_HIGH>;

其中330是UART0在GIC中的SPI中断号。驱动开发时,通过platform_get_irq(pdev, 0)获取中断号,再调用request_irq()注册中断处理函数。若设备有多个中断源(如TX、RX、错误),可在interrupts属性中依次列出,内核按顺序分配索引。

3.3.2 reg属性的地址映射机制

对于具有MMIO(内存映射IO)的设备,reg属性定义了寄存器空间的物理地址和大小。RK3588的PCIe控制器节点示例:

pcie2x1l0: pcie@fe180000 {
    compatible = "rockchip,rk3588-pcie";
    reg = <0x0 0xfe180000 0x0 0x10000>,
          <0x0 0xf4000000 0x0 0x100000>;
    reg-names = "apb-base", "dbi-base";
};

这里reg包含两组地址,分别对应APB配置寄存器和DMA接口寄存器,通过reg-names属性命名区分。驱动中使用of_iomap()或platform_get_resource()获取对应地址并映射到虚拟地址空间。需要注意的是,reg中的地址必须是物理地址,内核通过ioremap建立页表映射后才能访问。

3.3.3 常见误区与排查方法

风险提示:以下错误在RK3588设备树调试中高频出现:
  • reg地址越界:声明的寄存器长度超过实际硬件地址空间,导致访问冲突。排查方法:对照芯片手册确认寄存器基地址和偏移范围。
  • 中断号错误:使用了其他外设的中断号,导致中断无法触发或误触发。排查方法:查看/proc/interrupts确认中断是否注册成功。
  • compatible不匹配:驱动中的of_match_table与设备树中的compatible字符串不一致。排查方法:使用grep在内核源码中搜索compatible值。
  • status未设置:SoC级dtsi默认将外设设为disabled,板级dts忘记改为okay。排查方法:反编译dtb检查status属性。

3.4 设备树调试工具与实战案例

3.4.1 常用调试命令与工具

工具/命令 用途 典型用法
dtc 设备树编译/反编译 dtc -I dtb -O dts -o dump.dts rk3588-evb1.dtb
fdtdump 快速查看dtb内容 fdtdump rk3588-evb1.dtb | grep uart0
ls /proc/device-tree 查看内核实际使用的设备树 ls /proc/device-tree/soc/uart0
of_node sysfs 查看设备与设备树节点的绑定关系 ls -l /sys/bus/platform/devices/feb50000.serial/of_node
dmesg 查看设备树解析错误日志 dmesg | grep -i "of_node\|device tree"

3.4.2 案例:RK3588 UART调试完整闭环

背景与约束:某工业控制项目使用RK3588,需要启用UART2作为调试串口,但默认SDK中UART2被配置为disabled状态。硬件上UART2的TX/RX引脚连接到GPIO4_B0和GPIO4_B1,使用3.3V电平。

初始现象:系统启动后/dev/ttyS2节点不存在,dmesg无UART相关错误信息。

分析过程:首先反编译dtb确认UART2节点状态:

fdtdump rk3588-evb1.dtb | grep -A 10 "serial@feb60000"

发现status = "disabled"。进一步检查SoC级dtsi,确认UART2的寄存器基地址为0xfeb60000,中断号为331。

操作步骤:

  1. 在rk3588-evb1.dts中添加节点覆盖:
&uart2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m0_xfer>;
};
  1. 确认引脚复用组名称,通过grep uart2m0 arch/arm64/boot/dts/rockchip/rk3588s.dtsi找到uart2m0_xfer定义。
  2. 重新编译dtb并烧录,重启后执行ls /dev/ttyS*确认设备节点出现。
  3. 使用stty -F /dev/ttyS2 115200配置波特率,通过串口助手发送数据验证收发功能。

结果对比:修改前/dev/ttyS2不存在,修改后设备节点正常注册,串口通信稳定。复盘发现,最初遗漏了pinctrl配置导致引脚无复用功能,添加后问题解决。

经验总结:设备树调试遵循“先确认节点状态 → 再检查引脚复用 → 最后验证驱动匹配”的排查顺序。善用fdtdump和/proc/device-tree对比编译时与运行时的设备树差异,能快速定位问题根源。

3.5 本章小结

设备树语法是RK3588开发的基石,掌握节点层次、属性语义、文件组织与编译流程,是进行外设适配、驱动调试和系统裁剪的前提。本章通过语法解析、工具链说明和实战案例,建立了从dts源文件到内核设备注册的完整认知链路。在实际项目中,建议工程师养成“修改前备份dts、编译后反编译验证、运行时检查proc接口”的习惯,将设备树调试从经验驱动转变为流程驱动。