设备树/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_kernelsetup_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 ShevchenkoGreg 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_getfwnode_irq_getdev_fwnode()),社区在邮件列表、LWN 文章、内核文档里就直接用 "fwnode" 指代整个框架。你说 "fwnode 框架",大家都懂,没有任何歧义。

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

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

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

    内容 位置
    核心抽象 fwnode_handlefwnode_operations include/linux/fwnode.h
    通用 API device_property_*fwnode_property_*fwnode_graph_* include/linux/property.hdrivers/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,它成了内核里"固件无关节点"的通用货币。

相关推荐
waiting9711182 小时前
Ubuntu 26.04 + Android14安装与编译教程
android·linux·ubuntu
qetfw2 小时前
Debian 部署 phpMyAdmin:Apache、PHP 与 MariaDB 管理界面配置
linux·debian·php·apache
青瓦梦滋3 小时前
NAT技术
linux·服务器·网络·网络协议·tcp/ip
啦啦啦啦啦zzzz3 小时前
自定义日志缓冲区(上)c++20
linux·服务器·c++·网络编程
邪修king3 小时前
Re: Linux系统篇(十八):进程篇(七): 进程深度解析:从 fork 创建到退出的完整旅程(附写时拷贝原理 + 代码实战)
java·linux·运维
Golden_Chen3 小时前
Win10 WSL安装ubuntu
linux
RobinDevNotes3 小时前
RoCEv2如何扛起大模型训练网络
linux·网络·ai·网络linux
亿朝亿夕4 小时前
linux基础命令
linux
modelmd4 小时前
Linux SIGTERM 信号
linux