深入解析 Linux Kernel 中的设备树:使用、修改与实际应用
目录
- 什么是设备树?基础概念与结构
- 核心组件
- 设备树的使用:从加载到驱动绑定
- 加载过程
- 实际使用场景
- 设备树的修改:自定义硬件配置
- 修改步骤
- 常见 pitfalls 与注意事项
- 实际示例:常见设备的设备树配置
- 示例 1: GPIO(通用输入输出)
- 示例 2: CLK(时钟)
- 示例 3: I2C(总线)
- 示例 4: Flash(NAND 或 SPI Flash)
- 示例 5: SD 卡(MMC/SD)
- 示例 6: RGB 屏(LCD 显示,如 RGB40 接口)
- 高级话题:兼容性与最佳实践
什么是设备树?基础概念与结构
设备树(Device Tree,简称 DT)是一种数据结构,用于描述硬件配置,而非通过代码硬编码。它源于 OpenFirmware 标准,后来被 Linux Kernel 采用,尤其在 ARM、PowerPC 和 RISC-V 等架构中广泛使用。
核心组件
- 设备树源文件(.dts 文件):这是人类可读的文本文件,使用类似 C 的语法描述硬件。每个节点代表一个设备或总线,属性(properties)定义其特性,如地址、引脚和兼容字符串。
- 设备树二进制文件(.dtb 文件):通过编译 .dts 生成的二进制 blob,内核在启动时加载它。
- 设备树覆盖(.dtbo 文件):用于动态修改设备树,常用于插件模块或运行时调整。
设备树的结构是一个树状层次:
- 根节点(/):代表整个系统。
- 子节点:如 /cpus、/memory、/soc(System on Chip)。
- 属性示例:
compatible
:字符串列表,定义设备兼容的驱动(如 “ti,am335x-bone-black”)。reg
:寄存器地址和大小。interrupts
:中断号。phandle
:节点引用,用于交叉链接。
例如,一个简单的 .dts 片段:
/ {model = "My Custom Board";compatible = "mycompany,mycustomboard";chosen {bootargs = "console=ttyO0,115200n8";};memory@0 {device_type = "memory";reg = <0x80000000 0x20000000>; /* 512MB RAM at 0x80000000 */};
};
这定义了板子型号、兼容性、引导参数和内存布局。
设备树的使用:从加载到驱动绑定
加载过程
- Bootloader 阶段:如 U-Boot,将 .dtb 加载到内存,并传递给内核(通过 ATAGS 或 DTB 指针)。
- 内核初始化:Kernel 解析 DT,构建设备模型(device model)。函数如
of_find_node_by_path()
用于查找节点。 - 驱动绑定:内核使用
compatible
属性匹配驱动。驱动通过of_match_device()
获取配置。
实际使用场景
在嵌入式系统中,设备树简化了多板支持。例如,同一内核映像可通过不同 .dtb 运行在多种硬件上。调试时,用 dtc
工具反编译 .dtb:dtc -I dtb -O dts -o output.dts input.dtb
。
设备树的修改:自定义硬件配置
修改设备树是嵌入式开发的常态,尤其是添加新设备或调整引脚。过程包括编辑 .dts、编译和测试。
修改步骤
- 找到源文件:Kernel 源代码的
arch/arm/boot/dts/
或arch/arm64/boot/dts/
目录下有 .dts 文件。复制一个相似的作为模板。 - 编辑节点:
- 添加设备:创建新节点,指定父总线(如 i2c@0)。
- 修改属性:如更改 GPIO 引脚或时钟频率。
- 编译:
- 用 Device Tree Compiler(dtc):
dtc -I dts -O dtb -o output.dtb input.dts
。 - 在 Kernel 构建中:
make dtbs
生成所有 .dtb。
- 用 Device Tree Compiler(dtc):
- 测试:烧写到设备,检查
/proc/device-tree/
或用dtc
验证。
注意:修改后需确保兼容性。使用 #include
包含通用 .dtsi 文件,以复用代码。
常见 pitfalls 与注意事项
- 兼容性实现:
compatible
属性是关键。它允许多级回退,例如 “mycompany,custom-gpu”, “generic-gpu”。内核优先匹配最具体的驱动,如果失败则回退。这对于 GPU 等复杂设备尤为重要——如 NVIDIA GPU 的 Tegra 系列,使用 DT 定义 VRAM 和中断,避免硬编码。 - 引脚复用:许多 SoC 有 pinmux 系统,在 DT 中用
pinctrl
属性配置。 - 调试工具:用
fdt
命令在 U-Boot 中查看/修改 DT;内核日志(dmesg)显示解析错误。
实际示例:常见设备的设备树配置
让我们通过具体例子讲解,如 GPIO、CLK、I2C,以及 MPU6050、Flash、SD 卡和 RGB 屏(假设 RGB40 指 RGB LCD 显示屏)。
示例 1: GPIO(通用输入输出)
GPIO 用于控制 LED、按钮等。
gpio-leds {compatible = "gpio-leds";pinctrl-names = "default";pinctrl-0 = <&led_pins>;status-led {label = "status";gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;default-state = "on";};
};
- 修改:更改
gpios
的引脚号,编译后 LED 会自动点亮。 - 驱动:内核的 gpio-leds 驱动自动绑定。
示例 2: CLK(时钟)
时钟管理确保设备同步。
clocks {#address-cells = <1>;#size-cells = <0>;osc: oscillator {compatible = "fixed-clock";#clock-cells = <0>;clock-frequency = <24000000>;};
};
- 修改:调整
clock-frequency
以匹配晶振。 - 使用:其他节点引用它,如
clocks = <&osc>;
。
示例 3: I2C(总线)
I2C 用于连接传感器。
i2c@7000c000 {compatible = "ti,omap4-i2c";reg = <0x7000c000 0x100>;interrupts = <56>;#address-cells = <1>;#size-cells = <0>;clock-frequency = <400000>;mpu6050@68 {compatible = "invensense,mpu6050";reg = <0x68>;interrupt-parent = <&gpio1>;interrupts = <18 IRQ_TYPE_EDGE_RISING>;};
};
- MPU6050(IMU 传感器):添加此节点,定义地址和中断。修改:调整中断引脚以适配你的板子。
- 驱动修改:如果需要自定义,编辑 drivers/iio/imu/mpu6050.c,使用 of_property_read_*() 读取 DT 属性。
示例 4: Flash(NAND 或 SPI Flash)
用于存储固件。
spi0 {flash@0 {compatible = "jedec,spi-nor";reg = <0>;spi-max-frequency = <40000000>;m25p,fast-read;partition@0 {label = "boot";reg = <0x00000000 0x00040000>;};};
};
- 修改:更改分区大小,编译后用 mtd-utils 访问。
示例 5: SD 卡(MMC/SD)
mmc1 {compatible = "ti,omap4-hsmmc";reg = <0x4809c000 0x400>;interrupts = <83>;ti,needs-special-reset;dmas = <&sdma 61>, <&sdma 62>;dma-names = "tx", "rx";bus-width = <4>;cd-gpios = <&gpio6 27 GPIO_ACTIVE_LOW>; /* Card detect */
};
- 修改:调整
bus-width
为 8 以支持更快速度。
示例 6: RGB 屏(LCD 显示,如 RGB40 接口)
假设是一个并行 RGB 接口的 LCD。
panel {compatible = "simple-panel";power-supply = <&vcc_lcd>;backlight = <&backlight>;port {panel_in: endpoint {remote-endpoint = <&dpi_out>;};};
};dpi: dpi@0 {compatible = "ti,dpi";pinctrl-names = "default";pinctrl-0 = <&dpi_pins>;
};
- 修改:定义分辨率如
display-timings { ... };
。对于 GPU 兼容:如果使用 DRM 驱动,确保compatible
匹配如 “rockchip,rk3288-mali”,DT 中添加 VRAM 分配。 - 注意:GPU 如 Mali 或 Adreno,需要 DT 定义时钟、电源域和中断,以实现无缝集成。
这些示例基于常见 SoC 如 TI OMAP 或 Rockchip。实际中,从板子供应商的 .dts 开始修改。
高级话题:兼容性与最佳实践
- 兼容性实现(如 GPU):DT 的
compatible
允许多厂商支持。例如,GPU 节点可有 “arm,mali-400”, “generic-gpu”,内核驱动使用列表匹配。修改时,优先添加具体字符串,避免破坏上游兼容。 - 最佳实践:
- 用 .dtsi 抽象通用部分。
- 验证:用
dtc -W
检查警告。 - 版本控制:Kernel 5.x+ 支持更多绑定(如 YAML schema 检查)。
- 安全:启用 DT 签名以防篡改。