当前位置:首页>Linux>设备树:每一个 Linux 驱动工程师的起点

设备树:每一个 Linux 驱动工程师的起点

  • 2026-09-02 20:16:51
设备树:每一个 Linux 驱动工程师的起点

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

作者:HywelStar

 本文面向对象

对于驱动工程师来说,第一步就是接触设备树比较多,本章节适用于有一定 Linux 驱动开发基础,希望系统理解 DTS 工作原理并掌握实战技巧的嵌入式工程师。内容涵盖:

  • • DTS 诞生背景
  • • 语法结构
  • • 内核解析流程
  • • platform 驱动匹配机制
  • • 常见踩坑与调试技巧

1. 为什么需要设备树?

在设备树出现之前,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 传给内核
  • • 一个内核镜像支持多块板子

2. DTS 语法

DTS(Device Tree Source)是文本格式,编译后生成 DTB(二进制)。

工具链关系

.dts / .dtsi        │        ▼dtc (Device Tree Compiler)        │        ▼.dtb(二进制,Bootloader 传给内核)        │        ▼内核解析        │        ▼of_* API(Open Firmware API)

▍2.1 基本结构

/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";        };    };};

▍2.2 三个最重要的属性

2.2.1 compatible —— 驱动匹配的核心

compatible = "fsl,imx6ul-uart",             "fsl,imx6q-uart",             "snps,dw-apb-uart";

内核会按照顺序尝试匹配,找到第一个支持的驱动即停止。常见踩坑compatible 字符串必须与驱动中 of_device_id 表完全一致(包括大小写和连字符)。差一个字符,设备就不会工作,而且内核通常不会报错。

2.2.2 reg —— 地址与大小

解释方式由父节点的:

决定。

32 位地址示例

#address-cells = <1>;#size-cells = <1>;reg = <0x10000000 0x1000>;

含义:

  • • 地址 = 0x10000000
  • • 大小 = 0x1000

64 位地址示例

#address-cells = <2>;#size-cells = <2>;reg = <0x0 0xFC000000 0x0 0x02000000>;

2.2.3 interrupts —— 中断描述

常见 ARM GIC 三元组格式:

interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
字段
含义
0/1
SPI / PPI
中断号
硬件中断号
触发方式
上升沿 / 电平高等

▍2.3 dtsi 复用与 Overlay

SoC 厂商通常提供 .dtsi,板级 .dts 通过 #include 引用:

#include "imx6ul.dtsi"&uart1 {    status = "okay";    pinctrl-names = "default";    pinctrl-0 = <&pinctrl_uart1>;};&usdhc2 {    status = "disabled";};

3. 内核解析 DTS 的完整流程

理解内核如何消费 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() 会被触发。

4. platform 驱动与 DTS 匹配实战

▍4.1 DTS 侧

myled: myled@0 {    compatible = "myvendor,myled";    gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;    led-name = "heartbeat";    status = "okay";};

▍4.2 驱动侧

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);

5. 调试技巧

▍5.1 检查 DTB 是否正确加载

ls /proc/device-tree/cat /proc/device-tree/soc/serial@10000000/compatible | xxddtc -I fs /proc/device-tree -O dts -o /tmp/current.dts

▍5.2 检查驱动是否匹配

ls /sys/bus/platform/devices/ | grep myledls -la /sys/bus/platform/devices/myled@0/drivercat /sys/bus/platform/devices/myled@0/of_node/compatible

▍5.3 动态加载 DT Overlay

dtc -@ -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/

6. 五个常见踩坑总结

坑 1:忘记设置 status = "okay"

父级 dtsi 如果是 "disabled",必须在板级 dts 中覆盖。

坑 2:#address-cells 计算错误

reg 格子数必须等于:

#address-cells + #size-cells

坑 3:pinctrl 未配置

95% 外设不工作根因在于引脚复用配置错误。

坑 4:中断号偏移混淆

GIC SPI 中断在 DTS 里是相对编号。Linux 显示的中断号 = DTS 编号 + 32(GIC 偏移)。

坑 5:重复 include 导致节点冲突

注意 duplicate node 编译警告。

总结

设备树本质上是硬件拓扑的结构化描述语言。它实现了:内核代码与硬件解耦,多板复用同一内核镜像,驱动自动匹配与动态加载理解 DTS 语法只是第一步,更重要的是理解:内核如何解析,如何触发驱动匹配,出问题如何调试。

往期推荐

嵌入式软件开发求职指南

A/B 分区 OTA 升级机制与 U-Boot 实现

嵌入式系统 OTA 固件升级

Bootloader从启动到内核记录

2026 AI编程入门的基础概念

AI 编程工具发展太快了

嵌入式Linux驱动开发常见坑

组播进阶:加组控制与实战踩坑

面试技巧-1.STAR法则

嵌入式软件面试八股文(四) - Linux 内核驱动篇

嵌入式软件面试八股文(三) - 数据结构

戳“阅读原文”一起来充电吧!

最新文章

随机文章