关注+星标公众号,不容错过精彩

作者:HywelStar
对于驱动工程师来说,第一步就是接触设备树比较多,本章节适用于有一定 Linux 驱动开发基础,希望系统理解 DTS 工作原理并掌握实战技巧的嵌入式工程师。内容涵盖:
在设备树出现之前,ARM Linux 内核代码里充斥着大量 board file。每换一块板子,就要在:
arch/arm/mach-xxx/下新增一个 .c 文件,把 GPIO、时钟、中断、外设地址等硬件描述全部硬编码进去。
2011 年,Linus Torvalds 在邮件列表中曾直言:
"This whole ARM thing is a fucking pain in the ass."—— ARM 板级代码的混乱让内核维护者忍无可忍。
当时 ARM Linux 内核里存在数百个 mach- 目录,大量重复代码,维护困难,合并痛苦。
设备树的核心思想:设备树(Device Tree)源于 PowerPC/OpenFirmware 体系,核心理念是:
.dts 文件.dtb,由 Bootloader 传给内核DTS(Device Tree Source)是文本格式,编译后生成 DTB(二进制)。
.dts / .dtsi │ ▼dtc (Device Tree Compiler) │ ▼.dtb(二进制,Bootloader 传给内核) │ ▼内核解析 │ ▼of_* API(Open Firmware API)/dts-v1/;/ { model = "My Board Rev1"; compatible = "myvendor,myboard";. #address-cells = <1>; #size-cells = <1>; cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a53"; reg = <0>; }; }; soc { uart0: serial@10000000 { compatible = "ns16550a"; reg = <0x10000000 0x100>; interrupts = <0 4 4>; clocks = <&clk_uart>; status = "okay"; }; };};
compatible = "fsl,imx6ul-uart", "fsl,imx6q-uart", "snps,dw-apb-uart";内核会按照顺序尝试匹配,找到第一个支持的驱动即停止。常见踩坑compatible 字符串必须与驱动中 of_device_id 表完全一致(包括大小写和连字符)。差一个字符,设备就不会工作,而且内核通常不会报错。
解释方式由父节点的:
决定。
#address-cells = <1>;#size-cells = <1>;reg = <0x10000000 0x1000>;含义:
0x100000000x1000#address-cells = <2>;#size-cells = <2>;reg = <0x0 0xFC000000 0x0 0x02000000>;常见 ARM GIC 三元组格式:
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;SoC 厂商通常提供 .dtsi,板级 .dts 通过 #include 引用:
#include "imx6ul.dtsi"&uart1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart1>;};&usdhc2 { status = "disabled";};理解内核如何消费 DTS,对调试至关重要。
Bootloader (U-Boot) │ ├─ 将 DTB 地址写入寄存器 │ └─ 跳转内核入口内核启动 │ ├─ setup_arch() │ └─ unflatten_device_tree() │ → 展开 device_node 树 │ ├─ of_platform_populate() │ → 创建 platform_device │ └─ 调用 platform_driver.probe()关键理解
of_platform_populate() 会为含 compatible 属性的节点创建 platform_device。
这就是为什么驱动的 probe() 会被触发。
myled: myled@0 { compatible = "myvendor,myled"; gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; led-name = "heartbeat"; status = "okay";};static conststruct of_device_id myled_of_match[] = { { .compatible = "myvendor,myled" }, { /* sentinel */ }};MODULE_DEVICE_TABLE(of, myled_of_match);static int myled_probe(struct platform_device *pdev){struct device *dev = &pdev->dev;struct gpio_desc *led_gpio; const char *led_name; if (of_property_read_string(dev->of_node, "led-name", &led_name)) led_name = "unknown"; led_gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW); if (IS_ERR(led_gpio)) return PTR_ERR(led_gpio); dev_info(dev, "LED [%s] probe OK\n", led_name); gpiod_set_value_cansleep(led_gpio, 1); return 0;}module_platform_driver(myled_driver);ls /proc/device-tree/cat /proc/device-tree/soc/serial@10000000/compatible | xxddtc -I fs /proc/device-tree -O dts -o /tmp/current.dtsls /sys/bus/platform/devices/ | grep myledls -la /sys/bus/platform/devices/myled@0/drivercat /sys/bus/platform/devices/myled@0/of_node/compatibledtc -@ -I dts -O dtb -o myled-overlay.dtbo myled-overlay.dtsmkdir /sys/kernel/config/device-tree/overlays/myledcp myled-overlay.dtbo /sys/kernel/config/device-tree/overlays/myled/dtbols /proc/device-tree/myled@0/status = "okay"父级 dtsi 如果是 "disabled",必须在板级 dts 中覆盖。
#address-cells 计算错误reg 格子数必须等于:
#address-cells + #size-cells95% 外设不工作根因在于引脚复用配置错误。
GIC SPI 中断在 DTS 里是相对编号。Linux 显示的中断号 = DTS 编号 + 32(GIC 偏移)。
注意 duplicate node 编译警告。
设备树本质上是硬件拓扑的结构化描述语言。它实现了:内核代码与硬件解耦,多板复用同一内核镜像,驱动自动匹配与动态加载理解 DTS 语法只是第一步,更重要的是理解:内核如何解析,如何触发驱动匹配,出问题如何调试。
往期推荐
嵌入式软件开发求职指南
A/B 分区 OTA 升级机制与 U-Boot 实现
嵌入式系统 OTA 固件升级
Bootloader从启动到内核记录
2026 AI编程入门的基础概念
AI 编程工具发展太快了
嵌入式Linux驱动开发常见坑
组播进阶:加组控制与实战踩坑
面试技巧-1.STAR法则
嵌入式软件面试八股文(四) - Linux 内核驱动篇
嵌入式软件面试八股文(三) - 数据结构
戳“阅读原文”一起来充电吧!