从设备树到 ARM 驱动:嵌入式 Linux 驱动开发学习总结

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 子系统等内容,逐步构建更扎实的嵌入式驱动开发能力。

相关推荐
dishugj1 小时前
HANA集群计划内切换与维护操作
java·linux·数据库
爱吃香菜的初学者3 小时前
十四.Linux——消息队列
linux·运维·开发语言
Apipi*10 小时前
30天速通Linux 第六章信号及信号处理
linux·运维·信号处理
天启HTTP11 小时前
爬虫防封核心原理:IP轮换机制与风控规避逻辑
linux·服务器·网络·爬虫·tcp/ip
广州浮点FLOATLIC14 小时前
许可证服务器迁移后软件打不开:研发 IT 怎样定位连接问题
linux·服务器·数据库
BLUcoding14 小时前
接口服务公网超时排查记录:SecureRandom 阻塞问题分析与解决
java·linux·springboot·aes·securerandom
AIgorithmGEEK14 小时前
[Linux]从手写报头到内核套路:序列化、反序列化与自定义协议全链路
linux·运维·服务器·网络·序列化·反序列化
Nil20814 小时前
leetcode 139单词拆分
linux·运维·服务器
Starry-sky(jing)14 小时前
BUG: unable to handle kernel paging request 完整排查:dmesg 四要素与三路定罪
linux·运维·服务器·内核·排障