4. 设备树基础
设备树(Device Tree, DT)是描述硬件拓扑与资源配置的树形数据结构,是 Linux 内核实现跨架构驱动移植的核心抽象层。本章从 DT 语法出发,通过 ARM 与 RISC-V 平台实例,建立设备树绑定文档的编写规范,为后续驱动移植打下基础。
核心认知:设备树将硬件描述从内核源码中剥离,使得同一份内核镜像可以支持不同硬件配置。驱动通过设备树节点获取寄存器地址、中断号、时钟频率等资源,无需硬编码。
4.1 设备树语法基础
设备树源文件(.dts)采用树形层级结构,每个节点代表一个硬件设备或总线。基本语法元素包括:
- 节点(Node): 以
node-name@unit-address格式定义,unit-address通常为寄存器基址。 - 属性(Property): 键值对,用于描述设备特性,如
reg、interrupts、compatible。 - 标签(Label): 为节点提供别名,便于引用,如
uart0: serial@10000000。 - 包含文件: 使用
#include引入 .dtsi 头文件,实现 SoC 级描述复用。
语法速查表: 常用属性及其含义
| 属性 | 格式 | 说明 |
|---|---|---|
compatible |
"vendor,device" |
驱动匹配字符串,格式为"厂商,设备型号" |
reg |
<address length> |
寄存器地址与长度,支持多段 |
interrupts |
<irq_type> |
中断号与触发类型 |
clocks |
<phandle> |
时钟句柄,引用时钟控制器节点 |
status |
"okay" / "disabled" |
设备启用状态 |
以下是一个典型的 UART 设备节点示例:
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <33 4>;
clocks = <&clk_uart 0>;
status = "okay";
};
常见错误: 忘记在
reg 属性中指定地址长度,或 compatible 字符串未遵循"厂商,设备"格式,会导致驱动无法匹配。
4.2 设备树结构可视化
下图展示了一个简化的设备树层级结构,包含根节点、CPU、内存、中断控制器和两个外设节点:
4.3 ARM 平台设备树实例
以 ARM Cortex-A 系列 SoC 为例,设备树通常包含 GIC(通用中断控制器)、PL011 UART、GPIO 等标准 IP 的描述。以下是一个简化的 ARM 平台设备树片段:
/dts-v1/;
#include "soc-arm.dtsi"
/ {
model = "ARM Development Board";
compatible = "arm,vexpress";
chosen {
bootargs = "console=ttyAMA0,115200";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
soc {
#address-cells = <2>;
#size-cells = <2>;
uart0: serial@1c090000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x0 0x1c090000 0x0 0x1000>;
interrupts = <0 37 4>;
clocks = <&clk_uart0 &clk_uart0>;
clock-names = "uartclk", "apb_pclk";
};
gpio0: gpio@1c000000 {
compatible = "arm,pl061", "arm,primecell";
reg = <0x0 0x1c000000 0x0 0x1000>;
interrupts = <0 40 4>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
ARM 平台关键点:
- 使用
arm,pl011和arm,primecell双 compatible 字符串,提供兼容性。 - 中断描述采用
<中断域 中断号 触发类型>格式,GIC 域通常为 0(SPI)。 - 时钟通过
clocks属性引用,clock-names区分不同时钟角色。
4.4 RISC-V 平台设备树实例
RISC-V 平台设备树与 ARM 在结构上高度相似,但中断控制器使用 PLIC(平台级中断控制器)或 CLINT(核心本地中断控制器)。以下是一个 RISC-V 64 位 SoC 的设备树示例:
/dts-v1/;
/ {
#address-cells = <2>;
#size-cells = <2>;
compatible = "riscv-virtio";
chosen {
bootargs = "console=ttyS0,115200";
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <10000000>;
cpu@0 {
device_type = "cpu";
reg = <0>;
compatible = "riscv";
riscv,isa = "rv64imafdc";
mmu-type = "riscv,sv48";
};
};
soc {
#address-cells = <2>;
#size-cells = <2>;
uart0: serial@10000000 {
compatible = "ns16550a";
reg = <0x0 0x10000000 0x0 0x100>;
interrupts = <10>;
interrupt-parent = <&plic>;
clock-frequency = <3686400>;
};
plic: interrupt-controller@c000000 {
compatible = "riscv,plic0";
reg = <0x0 0xc000000 0x0 0x4000000>;
interrupts-extended = <&cpu0 11 &cpu0 9>;
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
};
RISC-V 与 ARM 差异:
- RISC-V 使用
riscv,isa属性描述指令集扩展,ARM 使用compatible描述 CPU 型号。 - PLIC 中断控制器使用
interrupts-extended属性连接 CPU 核,而非 ARM 的interrupt-parent链。 - RISC-V 设备树通常包含
timebase-frequency属性,用于定时器校准。
4.5 设备树绑定文档
设备树绑定文档(DT Binding)是描述设备节点属性格式的规范文件,通常以 YAML 格式存放在内核源码 Documentation/devicetree/bindings/ 目录下。绑定文档是驱动开发者与设备树编写者之间的契约。
一个标准的绑定文档包含以下部分:
| 章节 | 内容 | 示例 |
|---|---|---|
title |
设备名称与描述 | Vendor UART controller |
maintainers |
维护者列表 | John Doe <john@example.com> |
properties |
必选与可选属性定义 | compatible, reg, interrupts |
required |
必选属性列表 | compatible, reg |
examples |
完整设备树节点示例 | 包含地址、中断、时钟的节点 |
以下是一个简化的 YAML 格式绑定文档示例:
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/serial/vendor-uart.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Vendor UART Controller
maintainers:
- John Doe <john@example.com>
properties:
compatible:
items:
- enum:
- vendor,uart-v1
- vendor,uart-v2
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
maxItems: 2
required:
- compatible
- reg
- interrupts
additionalProperties: false
examples:
- |
uart@10000000 {
compatible = "vendor,uart-v1";
reg = <0x10000000 0x1000>;
interrupts = <33 4>;
clocks = <&clk_uart 0>;
};
...
绑定文档检查: 提交设备树补丁前,务必运行
make dt_binding_check 验证 YAML 格式,运行 make dtbs_check 验证设备树实例。未通过检查的绑定文档会被内核社区拒绝。
4.6 设备树编译与反编译
设备树编译器(dtc)将 .dts 源文件编译为 .dtb 二进制文件,也可将 .dtb 反编译为 .dts。常用命令如下:
| 操作 | 命令 | 说明 |
|---|---|---|
| 编译 | dtc -I dts -O dtb -o board.dtb board.dts |
将 .dts 编译为 .dtb |
| 反编译 | dtc -I dtb -O dts -o board.dts board.dtb |
将 .dtb 反编译为 .dts |
| 检查 | dtc -I dts -O dtb -o /dev/null board.dts |
仅检查语法,不输出文件 |
| 生成符号 | dtc -@ -I dts -O dtb -o board.dtb board.dts |
生成符号节点,支持叠加层 |
调试技巧: 在 Linux 启动后,可通过
/sys/firmware/devicetree/base 查看当前使用的设备树内容,或使用 dtc -I fs -O dts /sys/firmware/devicetree/base 导出为 .dts 格式。
4.7 设备树移植要点
从 ARM 向 RISC-V 移植驱动时,设备树需要关注以下差异:
- 中断模型: ARM 使用 GIC(通用中断控制器),RISC-V 使用 PLIC/CLINT。中断号范围和触发类型定义不同。
- 地址空间: RISC-V 64 位通常使用
#address-cells = <2>,ARM 32 位使用<1>。 - 时钟描述: ARM 平台常使用
fixed-clock或clk-provider,RISC-V 平台可能直接使用clock-frequency属性。 - CPU 描述: RISC-V 使用
riscv,isa描述指令集,ARM 使用compatible描述 CPU 型号。
下图总结了 ARM 与 RISC-V 设备树的关键差异: