找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1248

积分

0

好友

184

主题
发表于 4 天前 | 查看: 10| 回复: 0

最近与一位从MCU转型嵌入式Linux的同事交流,当我称赞他已成为一名出色的Linux驱动工程师时,他笑着纠正道:“你错了,我现在是‘设备树工程师’。” 这句自嘲背后,反映出一个深刻的变化:如今很多成熟的驱动,开发人员只需修改设备树配置即可完成适配,过程便捷,甚至无需重新编译内核。设备树的引入,从根本上改变了传统的嵌入式Linux驱动开发模式。

设备树的引入背景

设备树的概念起源于Open Firmware标准(IEEE 1275)。其在Linux社区的广泛采纳,与一次著名的“抱怨”密切相关。2011年,Linux创始人Linus Torvalds在ARM Linux邮件列表中直言不讳地批评当时的ARM架构支持方式“糟糕透了”,并强烈建议ARM社区采用设备树技术。

这一建议迅速得到响应。到2012年,设备树已成为所有新ARM SoC的强制要求。如今,它已成为Linux内核支持的主流架构的标准配置。这一变革使驱动工程师的工作重心发生了显著偏移——从传统的寄存器操作、中断处理等底层编码,转向了大量设备树文件的编写、调试与维护工作。

在设备树普及之前(例如Linux 2.6时代),ARM平台的板级信息分散在 arch/arm/mach-xxxarch/arm/plat-xxx 目录下的大量C文件中。每个开发板都需要维护自己的 platform_devicei2c_board_infospi_board_info 等数据结构。

以经典的SMDK2440开发板为例,其板级文件 mach-smdk2440.c 中包含了大量硬件相关的定义:

static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
    .lcdcon5 = S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE |
               S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN |
               S3C2410_LCDCON5_HWSWP,
    // ... 其他配置
};

static struct platform_device *smdk2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_iis,
    // ... 其他设备
};

当硬件发生变更,例如更换LCD型号或新增外设时,开发人员必须修改这些板级文件并重新编译整个内核。不同开发板间代码重复率高,维护成本巨大。这种模式正是促使社区寻求变革的原因,也催生了运维/DevOps中对于标准化和自动化配置的普遍需求。

设备树带来的改变:解耦与复用

设备树引入后,硬件配置信息被转移至独立的设备树源文件(.dts/.dtsi)中,驱动程序得以专注于通用逻辑的实现。

以连接SPI Flash为例,传统模式需要在板级C文件中定义 spi_board_info

static struct spi_board_info smdk2440_spi_devices[] __initdata = {
    {
        .modalias = "spi-flash",
        .max_speed = 5000000,
        .bus_num = 0,
        .chip_select = 0,
        .platform_data = &smdk2440_spi_flash_pdata,
    },
};

而采用设备树后,相同的配置信息被写入DTS文件:

spi1: spi@70100000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "arm,pl022";
    reg = <0x70100000 0x1000>;
    interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
    flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <5000000>;
    };
};

当硬件连接变化时,只需修改设备树文件,驱动代码通常无需改动。这种 “硬件描述与驱动代码解耦” 的理念,极大地提升了代码的复用性和项目的可维护性,这也正是那位同事自嘲为“设备树工程师”的由来。

设备树的设计原理与运作机制

设备树的核心在于采用了一种结构化的硬件描述语言。它以树形结构组织,用节点(node)表示硬件实体,用属性(property)描述其特征,并通过标签(label)、引用(phandle)和包含文件(.dtsi)实现配置的复用与平台间的解耦。简单来说,它将驱动配置“脚本化”了。

设备树源文件(.dts)会被编译成二进制文件(.dtb)。系统启动时,Bootloader(如U-Boot)将DTB加载到内存特定位置并传递给内核。内核在启动过程中解析DTB,将其中的每个节点转换为 struct device_node 结构体,最终在内存中构建出完整的硬件拓扑视图。

驱动程序则通过内核提供的一系列 of_*(Open Firmware)函数来获取设备树中描述的硬件资源。常用函数包括:

  • of_find_compatible_node():根据 compatible 属性查找设备节点。
  • of_property_read_u32():读取32位整型属性值。
  • of_get_named_gpio():获取GPIO编号。
  • of_irq_get():获取中断号。
  • platform_get_resource():获取内存、I/O等资源。

例如,在一个LED驱动的probe函数中,可以这样从设备树获取GPIO引脚信息:

struct device_node *node = pdev->dev.of_node;
int led_gpio = of_get_named_gpio(node, "led-gpio", 0);
if (led_gpio < 0) {
    dev_err(&pdev->dev, "Failed to get led-gpio property\n");
    return -ENODEV;
}
// ... 使用 led_gpio 进行初始化

设备树的引入本质上是一种数据驱动的配置思想。系统根据传递给内核的硬件描述数据做出初始化决策,而非依赖硬编码的选择。这使得同一个内核镜像能够支持多种不同的硬件平台,灵活性大增。

此外,设备树通过 “绑定”(bindings) 规范实现了标准化的接口机制。这些绑定文档规定了特定硬件在设备树中的标准描述方式,涵盖了数据总线、中断、GPIO连接等。Linux内核在 Documentation/devicetree/bindings/ 目录下维护了大量此类文档,确保了不同厂商硬件的描述一致性,降低了驱动开发的熟悉成本。

设备树面临的挑战

尽管设备树带来了巨大好处,但在实际应用中仍面临一些挑战:

  1. 兼容性字符串泛滥:不同厂商甚至同一厂商的不同项目,可能为功能相似的设备定义不同的 compatible 字符串,影响了驱动的通用性。
  2. 硬件快速迭代:芯片版本更新快,寄存器布局和特性可能发生变化。虽然有些项目尝试在 compatible 属性中加入版本号后缀(如 "vendor,device-v1.2"),但这增加了驱动匹配逻辑的复杂性。

无论如何,设备树已经成为现代嵌入式Linux系统不可或缺的一部分,它深刻定义了当今驱动开发的形态,让工程师能更高效地应对复杂的硬件生态。




上一篇:解析抖音直播千万级QPS架构设计:高并发场景下的实战方案
下一篇:基于OpenTelemetry实现WebSocket全链路可观测:AI应用实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 20:47 , Processed in 0.109428 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表