设备树
代码编写阶段
内核启动解析阶段
Linux 内核启动时,DT/ACPI 的解析和 fwnode 句柄的初始化是分两个阶段完成的:启动早期的"建骨架" 和驱动加载时的"用接口"。
⚙️ 第一阶段:启动早期,内核"建好骨架"
这个阶段发生在内核启动的非常早期,远在绝大多数驱动程序加载之前。此时,内核会完成 DT/ACPI 原始数据到内核内部数据结构的转换,并预先创建并填充好 fwnode 句柄。
-
对于设备树 (Device Tree):
-
入口 :内核C语言入口函数
start_kernel()会调用架构相关的setup_arch()。 -
解析 :在
setup_arch()中,会调用setup_machine_fdt()进行早期验证和扫描,随后调用unflatten_device_tree()。这个函数负责将扁平化的设备树二进制块(DTB)"去扁平化",解析成一颗由struct device_node构成的树形结构。 -
嵌入 :
struct device_node结构体内部直接包含了一个struct fwnode_handle fwnode成员 。在创建设备节点时,内核会调用fwnode_init()来初始化这个内嵌的fwnode_handle。至此,每个设备树节点都自带了一个fwnode句柄。
-
-
对于 ACPI:
-
枚举 :内核的 ACPI 子系统在启动早期会枚举系统上的 ACPI 命名空间,并为每个枚举到的设备创建一个
struct acpi_device结构体。 -
嵌入 :与设备树类似,在初始化
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 核心知识点
-
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 框架",大家都懂,没有任何歧义。 -
fwnode 是一个 Linux 内核的软件框架的基础设施,而非 ALSA/V4L2 这样的内核子系统。DT/ACPI 各自的解析器在启动早期把固件描述解析为节点树(内嵌 fwnode 句柄);fwnode 是凌驾其上的统一、只读访问层,驱动与核心框架在设备绑定生命周期内按需拉取。
Device Tree 是一份"静态的干货物料单",ACPI 是一份"动态的智能交互式地图" 。它们虽然"长相"和"脾气"完全不同,但干的是同一件活:作为输入提交给 fwnode 基础上设施,经过解析后把他们表达的硬件信息提供给内核驱动使用。 而
fwnode就是那个能把"干货物料单"和"智能地图"都读懂的"万能翻译器"。 -
fwnode 是一层横向接口,源代码散布在:
层 内容 位置 核心抽象 fwnode_handle、fwnode_operationsinclude/linux/fwnode.h通用 API device_property_*、fwnode_property_*、fwnode_graph_*include/linux/property.h、drivers/base/property.cDT 后端 of_fwnode_opsdrivers/of/property.cACPI 后端 _DSD解析 + opsdrivers/acpi/property.c软件节点后端 software node drivers/base/swnode.cfwnode = 统一设备属性接口这套框架的社区通称,本体是一层接口抽象(一个头文件 + drivers/base 的通用实现 + 各固件后端),而非内核里的独立子系统。 顺带一提,如今它已超出 DT/ACPI 适配的初衷------irq domain(
irq_fwspec)、GPIO、IIO、media graph 都直接消费 fwnode,它成了内核里"固件无关节点"的通用货币。