设备树/ACPI 与 fwnode 内核基础设施

设备树

代码编写阶段

内核启动解析阶段

Linux 内核启动时,DT/ACPI 的解析和 fwnode 句柄的初始化是分两个阶段完成的:启动早期的"建骨架" 和驱动加载时的"用接口"。

⚙️ 第一阶段:启动早期,内核"建好骨架"

这个阶段发生在内核启动的非常早期,远在绝大多数驱动程序加载之前。此时,内核会完成 DT/ACPI 原始数据到内核内部数据结构的转换,并预先创建并填充好 fwnode 句柄。

  • 对于设备树 (Device Tree):

    1. 入口 :内核C语言入口函数 start_kernel() 会调用架构相关的 setup_arch()。

    2. 解析 :在 setup_arch() 中,会调用 setup_machine_fdt() 进行早期验证和扫描,随后调用 unflatten_device_tree()。这个函数负责将扁平化的设备树二进制块(DTB)"去扁平化",解析成一颗由 struct device_node 构成的树形结构。

    3. 嵌入 :struct device_node 结构体内部直接包含了一个 struct fwnode_handle fwnode 成员 。在创建设备节点时,内核会调用 fwnode_init() 来初始化这个内嵌的 fwnode_handle。至此,每个设备树节点都自带了一个 fwnode 句柄。

  • 对于 ACPI:

    1. 枚举 :内核的 ACPI 子系统在启动早期会枚举系统上的 ACPI 命名空间,并为每个枚举到的设备创建一个 struct acpi_device 结构体。

    2. 嵌入 :与设备树类似,在初始化 acpi_device 时,同样会调用 fwnode_init() 来初始化其内部的 fwnode_handle。此外,fwnode 的初始化正被调整到与设备树尽可能早的同一时机进行。

cpp 复制代码
/* 1. struct device_node 和 struct acpi_device 这两种数据结构实例,都是内核启动的早期由专门的软件工具通过解析 DTB / ? 构造出的。
** 2. struct device_node 和 struct acpi_device 这两种数据结构内部,都包含了 struct fwnode_handle 成员!
**    struct fwnode_handle 成员正是驱动程序在注册过程中通过它去访问 DT/ACPI 表达的硬件信息内容的工具。 */
//linux-6.18.2
struct device_node {
	const char *name;
	phandle phandle;
	const char *full_name;
	struct fwnode_handle fwnode;   //这里

	struct	property *properties;
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;
	struct	device_node *child;
	struct	device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
	struct	kobject kobj;
#endif
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

struct acpi_device {
	u32 pld_crc;
	int device_type;
	acpi_handle handle;		/* no handle for fixed hardware */
	struct fwnode_handle fwnode;  //这里
	struct list_head wakeup_list;
	struct list_head del_list;
	struct acpi_device_status status;
	struct acpi_device_flags flags;
	struct acpi_device_pnp pnp;
	struct acpi_device_power power;
	struct acpi_device_wakeup wakeup;
	struct acpi_device_perf performance;
	struct acpi_device_dir dir;
	struct acpi_device_data data;
	struct acpi_scan_handler *handler;
	struct acpi_hotplug_context *hp;
	struct acpi_device_software_nodes *swnodes;
	const struct acpi_gpio_mapping *driver_gpios;
	void *driver_data;
	struct device dev;
	unsigned int physical_node_count;
	unsigned int dep_unmet;
	struct list_head physical_node_list;
	struct mutex physical_node_lock;
	void (*remove)(struct acpi_device *);
};

完成这个阶段后,内核中已经有了代表硬件设备的 struct device_node 或 struct acpi_device 节点,更重要的是,每个节点内部都已经绑定好了一个随时可用的 struct fwnode_handle 。这是因为 struct device_node 或 struct acpi_device 这俩数据结构内部都内嵌了 struct fwnode_handle 指针成员。

🔗 第二阶段:驱动加载,开发者"使用接口"

当内核启动进入到后续阶段,开始加载各种设备驱动时,驱动程序开发者便可以使用第一阶段准备好的 fwnode 接口了。

  • 驱动获取 fwnode :驱动可以通过 dev_fwnode() 等辅助函数,轻松获取与它关联的 struct device 所对应的 fwnode_handle。

  • 调用统一接口 :拿到 fwnode_handle 后,驱动就可以调用 fwnode_property_read_*() 等一系列统一 API来读取属性。由于 fwnode_handle 在早期就已经被初始化并绑定了正确的操作函数集(fwnode_operations),内核可以自动判断并调用底层的 DT 或 ACPI 解析代码。

💎 总结

因此,整个流程可以清晰地概括为:

启动早期(start_kernel → setup_arch) :内核解析 DT/ACPI 原始数据,创建 device_node/acpi_device,并预先初始化并绑定好内部的 fwnode_handle。

驱动加载后期 :驱动开发者不再需要关心底层是 DT 还是 ACPI,直接使用统一的 fwnode 接口 API 即可访问由内核在早期就已经构建好的 fwnode 节点。

这种设计正是 fwnode 框架作为统一抽象层的价值所在:它在启动早期就完成了"翻译"和"绑定"工作,为后续驱动提供了一套整齐划一的"插座"(API),从而完美实现了你提到的"后续才由驱动调用 fwnode 接口访问这个树节点"。

所以,一句话,在驱动代码编写时,直接使用 fwnode 接口(正式名称:Unified Device Properties Interface)主动获取 Device Tree / ACPI 表达的硬件信息,而后参与后续的驱动注册过程。

fwnode - Unified Device Properties Interface

1. 引言

Linux fwnode(Firmware Node)统一设备模型是 Linux 内核中用于抽象不同固件描述机制的通用框架。它为设备树(Device Tree)、ACPI(Advanced Configuration and Power Interface)以及其他固件描述方式提供了统一的接口,使得驱动程序可以以相同的方式访问设备属性信息,而无需关心底层的固件实现细节。

2. 历史背景与发展历程

早期固件描述问题

在 fwnode 框架出现之前,Linux 内核面临以下挑战:

  • 平台依赖性强:ARM 平台主要使用 Device Tree,x86 平台使用 ACPI

  • 代码重复:驱动程序需要为不同固件格式编写重复的解析代码

  • 维护困难:跨平台驱动维护成本高,兼容性问题频发

    struct device *dev =...;
    int ret, irq;

    /* 检查设备是否关联了设备树节点 /
    if (dev->of_node) {
    /
    如果存在,则使用 of_* 系列 API 从设备树中读取属性 /
    ret = of_property_read_u32(dev->of_node, "interrupts", &irq);
    if (ret) {
    // 错误处理
    }
    } else if (ACPI_HANDLE(dev)) {
    /
    否则,检查设备是否有关联的 ACPI 句柄 /
    /
    使用 ACPI 特定的 API 来解析 _CRS (Current Resource Settings) /
    /
    这通常涉及到一套复杂、冗长的 ACPI 资源解析逻辑 /
    //... complex ACPI resource parsing logic...
    } else {
    /
    可能还有基于平台数据的传统硬编码方式 */
    //...
    }

发展时间线

Linux 内核社区对统一设备描述接口的探索始于 2014 年,旨在解决多固件接口并存带来的架构性问题。fwnode 框架正是这一架构演进的核心成果。以下是该框架的演进时间线和关键节点:

2.1 时间线与里程碑

时间 里程碑 描述
2014 概念提出 内核核心开发者 Rafael J. Wysocki 首次提出 "统一固件描述接口" 概念,目标是抽象 DT 与 ACPI 的异构性
2015 初始实现 fwnode_handle 和基本的抽象框架首次被合入内核主线(4.x 开始)
2016 支持 ACPI fwnode 开始全面支持基于 ACPI 的设备描述与节点绑定,实现与设备树等价的抽象访问
2017--2019 高级功能扩展 添加支持图形化拓扑(graph nodes)、软件节点(swnode)、引用计数、路径解析等高级能力
2020--现在 持续优化与应用拓展 在性能、可维护性和安全性方面不断改进,广泛应用于 I2C、SPI、GPIO、USB、MIPI、PCI 等通用设备驱动

2.2 推动者与维护者

fwnode 的设计和推进由内核电源管理子系统的核心维护者 Rafael J. Wysocki 主导,其他如 Andy Shevchenko 、Greg Kroah-Hartman 等开发者也为其在设备模型中的集成和扩展提供了大量贡献。

相关子系统涉及:

  • drivers/base(核心驱动模型)
  • drivers/of(设备树适配层)
  • drivers/acpi(ACPI 层封装)
  • drivers/base/swnode.c(软件节点支持)
  • include/linux/fwnode.h(核心 API 定义)

2.3 应用范围与影响力

fwnode 框架的引入,极大地提升了驱动的跨平台兼容性和开发效率。它已经成为 Linux 内核中中大型设备驱动的标准架构组件:

  • ✅ 已迁移子系统示例:

    • SPI 控制器(如 DesignWare SPI、Mediatek SPI)
    • I2C 控制器
    • GPIO 子系统
    • USB Host/Device 控制器
    • CSI/MIPI 摄像头接口
    • PCI host bridge 初始化代码
    • 多媒体子系统中的 graph-based 描述(如 HDMI、DSI、V4L2)
  • ✅ 典型优势:

    • 同一驱动无需判断 dev->of_node 还是 ACPI_HANDLE(),直接使用 dev_fwnode() 即可统一访问设备信息
    • 支持 DT/ACPI/SWNode 无缝集成,便于平台代码抽象
    • 使驱动代码更具可测试性与模块化能力

2.4 关键版本节点(内核版本参考)

Linux 版本 演进亮点
4.1--4.4 初始 fwnode_handle 框架建立
4.5--4.9 ACPI fwnode 封装完善
4.10--4.19 引入 swnode,支持虚拟设备
5.0--5.4 图形拓扑、引用计数增强;device_get_match_data() 推广
5.5--5.15+ 广泛推广至 I2C/SPI/USB 驱动中,默认采用 fwnode 访问

3. fwnode 核心知识点

  1. fwnode 正式名字:Unified Device Properties Interface(统一设备属性接口)。 这是 2014 年 Rafael Wysocki / Mika Westerberg 提交补丁系列时的原始命名(背景正是 Intel 的 ARM64 服务器要用 ACPI 启动、复用 DT 驱动),随 v3.19 合入主线。今天内核提交信息里这个模块的前缀也常用 device property:。

    fwnode(= firmware node,固件节点)是这套框架的中心数据结构 struct fwnode_handle 的名字 ,因为它出现在所有 API 前缀里(fwnode_get、fwnode_irq_get、dev_fwnode()),社区在邮件列表、LWN 文章、内核文档里就直接用 "fwnode" 指代整个框架。你说 "fwnode 框架",大家都懂,没有任何歧义。

  2. fwnode 是一个 Linux 内核的软件框架的基础设施,而非 ALSA/V4L2 这样的内核子系统。DT/ACPI 各自的解析器在启动早期把固件描述解析为节点树(内嵌 fwnode 句柄);fwnode 是凌驾其上的统一、只读访问层,驱动与核心框架在设备绑定生命周期内按需拉取。

    Device Tree 是一份"静态的干货物料单",ACPI 是一份"动态的智能交互式地图" 。它们虽然"长相"和"脾气"完全不同,但干的是同一件活:作为输入提交给 fwnode 基础上设施,经过解析后把他们表达的硬件信息提供给内核驱动使用。 而 fwnode 就是那个能把"干货物料单"和"智能地图"都读懂的"万能翻译器"。

  3. fwnode 是一层横向接口,源代码散布在:

    层 内容 位置
    核心抽象 fwnode_handle、fwnode_operations include/linux/fwnode.h
    通用 API device_property_*、fwnode_property_*、fwnode_graph_* include/linux/property.h、drivers/base/property.c
    DT 后端 of_fwnode_ops drivers/of/property.c
    ACPI 后端 _DSD 解析 + ops drivers/acpi/property.c
    软件节点后端 software node drivers/base/swnode.c

    fwnode = 统一设备属性接口这套框架的社区通称,本体是一层接口抽象(一个头文件 + drivers/base 的通用实现 + 各固件后端),而非内核里的独立子系统。 顺带一提,如今它已超出 DT/ACPI 适配的初衷------irq domain(irq_fwspec)、GPIO、IIO、media graph 都直接消费 fwnode,它成了内核里"固件无关节点"的通用货币。

相关推荐
未济5 天前
linux 配置环境变量
linux
傲世仙尊5 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
_艾伦 耶格尔.5 天前
进程间通信
linux
Liuqy-055 天前
Linux IO编程——静态库、动态库
linux
彧azz6 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
-梅6 天前
linux(8) 软硬链接
linux·运维·服务器
AIgorithmGEEK6 天前
[Linux]线程三部曲(上):一个执行流的诞生——从操作系统一路拆到 pthread_create
linux·线程·pid
琥珀色糖6 天前
SimlpeHttp
linux·服务器
qeen876 天前
【Linux】操作系统之进程介绍(二)
linux·笔记·学习·进程
Zenova EdgeOS6 天前
Linux ss 工业边缘网络分析实战
linux·python·边缘计算·工业网关