1. 引言
在嵌入式 Linux 开发中,设备树(Device Tree)是连接硬件与驱动的重要桥梁。对于初学者而言,理解设备树与 ARM 驱动的关系,是掌握嵌入式 Linux 驱动开发的关键一步。本文基于实际学习过程,总结设备树的基本概念、语法结构,以及如何基于设备树编写 ARM Linux 驱动程序,帮助读者建立从硬件描述到驱动实现的完整认知。
2. 为什么需要设备树
在早期的 Linux 内核中,ARM 平台大量使用板级文件(如 arch/arm/mach-xxx/board-xxx.c)来描述硬件资源。这种方式存在明显问题:
- 硬件信息与内核代码耦合严重,每更换一块板子就要修改内核源码;
- 内核镜像无法在不同硬件平台间复用;
- 社区维护成本高,代码冗余。
设备树(Device Tree,DT)的出现解决了上述问题。它将硬件描述从内核源码中剥离出来,以独立的数据结构描述 CPU、内存、外设、中断、GPIO 等资源。内核启动时解析设备树,动态匹配驱动,实现「一套内核,多种硬件」的目标。
3. 设备树基础语法
设备树文件通常以 .dts(Device Tree Source)为后缀,经过 dtc 工具编译为 .dtb(Device Tree Blob)二进制文件,供内核解析。
3.1 基本结构
一个最简单的设备树节点如下:
dts
/dts-v1/;
/ {
compatible = "vendor,board-name";
model = "My ARM Board";
chosen {
bootargs = "console=ttyS0,115200";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
led {
compatible = "myvendor,led";
reg = <0x40000000 0x10>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
};
};
3.2 关键属性说明
compatible:驱动与设备匹配的关键字符串,格式为"厂商,型号";reg:描述设备寄存器地址与长度,格式为<地址 长度>;interrupts:描述中断号、触发类型;status:可设为"okay"或"disabled",控制设备是否启用。
4. 设备树与驱动的匹配机制
Linux 内核中,驱动通过 of_match_table 与设备树节点进行匹配。当设备树中某个节点的 compatible 属性与驱动注册的匹配表一致时,内核便会调用驱动的 probe 函数。
c
static const struct of_device_id led_of_match[] = {
{ .compatible = "myvendor,led" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "my-led",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_driver);
5. 基于设备树的 ARM 驱动开发实战
下面以 LED 驱动为例,演示从设备树到驱动的完整流程。
5.1 设备树节点定义
dts
led: led@40000000 {
compatible = "myvendor,led";
reg = <0x40000000 0x10>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
5.2 驱动端获取资源
在 probe 函数中,通过设备树 API 获取寄存器地址、中断号等资源:
c
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
struct led_dev *dev;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* 获取寄存器地址 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dev->regs))
return PTR_ERR(dev->regs);
/* 获取中断号 */
dev->irq = platform_get_irq(pdev, 0);
if (dev->irq < 0)
return dev->irq;
platform_set_drvdata(pdev, dev);
return 0;
}
5.3 设备树 API 常用函数
| 函数 | 作用 |
|---|---|
of_property_read_u32() |
读取 32 位整数属性 |
of_property_read_string() |
读取字符串属性 |
of_get_named_gpio() |
获取 GPIO 编号 |
irq_of_parse_and_map() |
解析中断并映射 |
platform_get_resource() |
获取寄存器资源 |
6. 学习心得与常见坑点
6.1 心得总结
- 设备树本质是「数据驱动」思想,驱动代码与硬件描述解耦,可移植性大幅提升;
- 掌握
compatible匹配机制是理解设备树驱动的核心; - 多读内核源码中的
Documentation/devicetree/bindings/目录,是学习设备树规范的最佳途径。
6.2 常见坑点
compatible字符串拼写不一致导致驱动无法匹配;reg属性地址与芯片手册不一致,导致ioremap失败;- 忘记在设备树中设置
status = "okay",设备被内核跳过; - 中断号配置错误,导致
request_irq失败。
7. 总结
设备树是嵌入式 Linux 驱动开发中不可或缺的一环。通过本文的学习,我们从设备树的语法、匹配机制到实际驱动编写,建立了一个完整的知识框架。后续可以进一步深入设备树 overlays、pinctrl 子系统、GPIO 子系统等内容,逐步构建更扎实的嵌入式驱动开发能力。