1. 设备树基础概念:什么是设备树、为什么需要设备树、设备树在Linux内核中的角色
各位同学,咱们今天聊点实在的。设备树(Device Tree)这东西,说白了就是一套描述硬件信息的“说明书”。你想想看,一个嵌入式板子上有CPU、内存、I2C控制器、SPI接口、GPIO引脚……这些东西怎么让Linux内核知道?
在早期,大家都是把硬件信息硬编码在代码里。每个板子一个board文件,里面全是平台设备、资源注册。我刚开始做嵌入式那会儿,最怕的就是换板子——换个Flash型号,得改代码重新编译内核。那叫一个痛苦。
1.1 什么是设备树
设备树是一种树形数据结构,用文本文件描述硬件信息。它的格式是.dts(Device Tree Source),编译后变成.dtb(Device Tree Blob),内核启动时解析它。
来看一个最简单的例子:
/dts-v1/;
/ {
model = "Rockchip RK3566 EVB1";
compatible = "rockchip,rk3566-evb1", "rockchip,rk3566";
memory@60000000 {
device_type = "memory";
reg = <0x60000000 0x40000000>;
};
chosen {
stdout-path = &uart2;
};
};
这个结构很直观:根节点“/”下面挂memory、chosen等子节点。每个节点描述一个硬件设备或系统属性。
1.2 为什么需要设备树
我遇到过不少刚入行的朋友问:以前没有设备树不也跑得好好的吗?嗯,确实能跑,但代价很大。
咱们看看没有设备树的时候,Linux是怎么处理硬件的:
| 对比项 | 传统方式(board文件) | 设备树方式 |
|---|---|---|
| 硬件描述位置 | arch/arm/mach-xxx/board-xxx.c | 独立的.dts文件 |
| 换板子代价 | 改代码、重新编译内核 | 换.dtb文件即可 |
| 内核维护负担 | 每个板子一个board文件,数量爆炸 | 统一设备树接口,代码复用 |
| 驱动与硬件耦合度 | 高,驱动里写死资源 | 低,驱动通过设备树获取资源 |
| 多平台支持 | 困难,每个平台单独维护 | 一套内核镜像支持多个板子 |
说白了,设备树解决了三个核心问题:
- 硬件与软件解耦——换硬件不用改内核代码
- 减少内核碎片——不用每个板子一个board文件
- 统一硬件描述语言——ARM、RISC-V、MIPS都用同一套语法
1.3 设备树在Linux内核中的角色
设备树在内核启动过程中扮演什么角色?我画了一张图,你看完就明白了:
从这张图你能看到,设备树在内核启动中扮演了三个关键角色:
- 硬件信息载体——Bootloader把.dtb传给内核,内核从中读取内存布局、外设地址、中断号等
- 驱动匹配依据——驱动通过compatible属性与设备树节点匹配,匹配成功就调用probe
- 资源分配蓝图——驱动从设备树获取reg、interrupts、GPIO、时钟等资源
1.4 设备树在Rockchip平台的特殊性
Rockchip平台对设备树的依赖非常重。为什么?因为Rockchip的SoC集成度很高,一个芯片里集成了CPU、GPU、NPU、ISP、VPU、多个显示控制器……
我列一下Rockchip设备树里常见的几大类节点:
| 节点类型 | 示例 | 说明 |
|---|---|---|
| CPU | cpu@0 | 描述CPU核心,包括频率、电压、冷却设备 |
| 内存 | memory@60000000 | DDR内存的起始地址和大小 |
| 时钟 | cru: clock-controller@ff760000 | 时钟复位控制器,管理所有外设时钟 |
| 引脚控制 | pinctrl | GPIO复用和电气特性配置 |
| 显示 | vop@fe040000 | 视频输出处理器,连接HDMI/DP/MIPI |
| 多媒体 | vpu@fdea0000 | 视频编解码单元 |
| NPU | npu@ffa00000 | 神经网络处理单元 |
你看,光是显示相关的节点就有VOP、HDMI TX、DP TX、MIPI DSI好几个。每个节点都有自己的时钟、中断、引脚配置。如果没有设备树,这些信息全塞在代码里,那维护起来简直是噩梦。
1.5 设备树文件组织
在Linux内核源码里,Rockchip的设备树文件放在这里:
arch/arm64/boot/dts/rockchip/
├── rk3566.dtsi # SoC级定义,所有RK3566芯片通用
├── rk3568.dtsi # SoC级定义,所有RK3568芯片通用
├── rk3566-evb1.dts # 板级定义,EVB1开发板
├── rk3566-evb2.dts # 板级定义,EVB2开发板
├── rk3568-evb1.dts # 板级定义,EVB1开发板
└── ... # 其他板子
这里有个重要的概念:.dtsi是头文件,.dts是板级文件。.dtsi定义SoC内部的所有硬件模块,.dts只定义板子特有的部分(比如用了哪个Flash、哪个GPIO接了LED)。
我举个例子,rk3566.dtsi里定义了I2C控制器:
i2c0: i2c@ff3f0000 {
compatible = "rockchip,rk3568-i2c", "rockchip,rk3399-i2c";
reg = <0x0 0xff3f0000 0x0 0x1000>;
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru CLK_I2C0>, <&cru PCLK_I2C0>;
clock-names = "i2c", "pclk";
pinctrl-0 = <&i2c0_xfer>;
status = "disabled";
};
然后在板级.dts里,只需要打开它并挂上设备:
&i2c0 {
status = "okay";
clock-frequency = <400000>;
hym8563: rtc@51 {
compatible = "haoyu,hym8563";
reg = <0x51>;
interrupts = <&gpio0 RK_PB1 IRQ_TYPE_LEVEL_LOW>;
};
};
嗯,这一章的内容就到这儿。设备树的概念其实不复杂,但它是整个嵌入式Linux硬件适配的基石。后面的章节我们会深入每个细节——从语法到实战,从时钟到pinctrl,一步步把Rockchip的设备树吃透。