3、设备树基础语法:节点、属性与编译流程
设备树(Device Tree)是RK3588等ARM64平台下硬件描述的核心机制,它承担着将板级硬件拓扑、寄存器地址、中断映射、时钟关系等信息从内核源码中剥离出来的职责。对于嵌入式工程师而言,理解设备树的节点与属性语法,掌握dts/dtsi/dtb三种文件形态的关联与编译流程,是进行内核适配、驱动调试和系统裁剪的前提。本章将从语法要素、文件组织、编译工具链三个维度展开,并结合RK3588实际开发中的典型场景进行拆解。
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/先删除)。
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-evb1.dts中通过&i2c6引用I2C6节点,设置status = "okay"并配置引脚复用:pinctrl-0 = <&i2c6_xfer>。 - 在I2C6节点下新增触摸屏子节点,填写
compatible = "goodix,gt911"、reg = <0x5d>(I2C地址)、interrupt-parent = <&gpio1>、interrupts = <RK_PB2 IRQ_TYPE_LEVEL_LOW>。 - 执行
make dtbs编译,将生成的dtb烧录到开发板。 - 启动后通过
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 常见误区与排查方法
- 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。
操作步骤:
- 在
rk3588-evb1.dts中添加节点覆盖:
&uart2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart2m0_xfer>;
};
- 确认引脚复用组名称,通过
grep uart2m0 arch/arm64/boot/dts/rockchip/rk3588s.dtsi找到uart2m0_xfer定义。 - 重新编译dtb并烧录,重启后执行
ls /dev/ttyS*确认设备节点出现。 - 使用
stty -F /dev/ttyS2 115200配置波特率,通过串口助手发送数据验证收发功能。
结果对比:修改前/dev/ttyS2不存在,修改后设备节点正常注册,串口通信稳定。复盘发现,最初遗漏了pinctrl配置导致引脚无复用功能,添加后问题解决。
fdtdump和/proc/device-tree对比编译时与运行时的设备树差异,能快速定位问题根源。
3.5 本章小结
设备树语法是RK3588开发的基石,掌握节点层次、属性语义、文件组织与编译流程,是进行外设适配、驱动调试和系统裁剪的前提。本章通过语法解析、工具链说明和实战案例,建立了从dts源文件到内核设备注册的完整认知链路。在实际项目中,建议工程师养成“修改前备份dts、编译后反编译验证、运行时检查proc接口”的习惯,将设备树调试从经验驱动转变为流程驱动。