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

相关推荐
沫璃染墨6 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
AI_Lab017 小时前
宝塔用了两年,有没有必要换成轻量方案?
linux·运维·服务器
by209998 小时前
Linux GDB 调试与 CGDB 使用
linux·运维·服务器·经验分享
星恒随风8 小时前
Linux网络编程:应用层协议究竟是什么?从网络计算器到TCP Socket抽象基类完整实现
linux·网络·笔记·tcp/ip
secondyoung10 小时前
Cortex-R52与TriCore详细对比
arm开发·单片机·嵌入式硬件·mcu
feng_you_ying_li11 小时前
linux之网络层
linux·运维·网络
未来之窗软件服务11 小时前
Linux 运维日志删除的多种方式—东方仙盟
linux·运维·服务器·仙盟创梦ide·东方仙盟
小雉系统11 小时前
BcoreOS实战:用一个升级包,让Rocky Linux跑进Debian服务器
linux·运维·服务器·debian
thethefighter11 小时前
快速预览Word、WPS秒开,支持空格弹窗
linux·word·wps·快速预览·空格弹窗·nhdeepview
用户83514140494911 小时前
把日志和寄存器值喂给AI做故障定位,我的实际问法
linux·ubuntu