ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/8 15:40:04 拓冰建站 浏览量
设备树/ACPI 与 fwnode 内核基础设施 设备树代码编写阶段内核启动解析阶段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的初始化正被调整到与设备树尽可能早的同一时机进行。/* 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 Interface1. 引言Linux fwnodeFirmware Node统一设备模型是 Linux 内核中用于抽象不同固件描述机制的通用框架。它为设备树Device Tree、ACPIAdvanced Configuration and Power Interface以及其他固件描述方式提供了统一的接口使得驱动程序可以以相同的方式访问设备属性信息而无需关心底层的固件实现细节。2. 历史背景与发展历程早期固件描述问题在 fwnode 框架出现之前Linux 内核面临以下挑战平台依赖性强ARM 平台主要使用 Device Treex86 平台使用 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支持 ACPIfwnode 开始全面支持基于 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/acpiACPI 层封装drivers/base/swnode.c软件节点支持include/linux/fwnode.h核心 API 定义2.3 应用范围与影响力fwnode 框架的引入极大地提升了驱动的跨平台兼容性和开发效率。它已经成为 Linux 内核中中大型设备驱动的标准架构组件✅已迁移子系统示例SPI 控制器如 DesignWare SPI、Mediatek SPII2C 控制器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.9ACPI 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通用 APIdevice_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 nodedrivers/base/swnode.cfwnode 统一设备属性接口这套框架的社区通称本体是一层接口抽象一个头文件 drivers/base 的通用实现 各固件后端而非内核里的独立子系统。顺带一提如今它已超出 DT/ACPI 适配的初衷——irq domainirq_fwspec、GPIO、IIO、media graph 都直接消费 fwnode它成了内核里“固件无关节点”的通用货币。到这里开始介绍 v4l2 驱动的通用注册过程DTS 源码port/endpoint 描述连接 │ DTC 编译器 ▼ DTB 二进制 │ 内核启动时解析 ▼ device_node 树内存中的树形结构 │ 驱动用 of_graph/fwnode_graph API 遍历 ▼ v4l2_fwnode_endpointV4L2 封装好的 endpoint 信息基于设备树编写源代码设备树源代码本身只能表达树形逻辑这里需要在 DTS 的源代码内部加上 OF Graph 语法描述这部分增量以赋予 DTS 能够表达硬件之间的 “图逻辑” 的关系在 V4L2 里就是 DAG 有向无环图逻辑。编译 DTS 源代码得到 DTB 二进制数据。在 Linux 内核启动早期由专用的软件工具会主动解析这个 DTB 二进制数据构造出struct device_node之类的 C 语言能够直接访问的数据实例对象。接着待驱动 probe 时其 probe 函数内部会调用 fwnode API 接口去直接从stuct device_node实例里面获取 DTS 描述的相关的硬件信息。fwnode 框架是内核为了一套 API 同时服务 OF(设备树)、ACPI、软件节点(swnode)三种固件来源而建的抽象层。struct fwnode_operations(fwnode.h:140 起)是一张 vtable:get_name/get_parent/graph_get_remote_endpoint/property_read_string_array… 每种固件后端各自实现一份 ops(OF 后端在完整内核的drivers/of/property.c,of_fwnode_ops)。V4L2 框架(v4l2-async.c、v4l2-fwnode.c)里只写fwnode_graph_get_*、fwnode_property_*,内部按 ops 分发到当前平台的后端实现——所以同一份 V4L2 代码在 DT 板子和 ACPI 板子上都能跑。fwnode框架的精髓在于抽象。它不直接操作struct device_node而是操作统一的struct fwnode_handle。struct device_node通过内嵌fwnode_handle的方式将自己“注册”进这个框架并通过to_of_node()等函数实现双向转换。好到这里标准流程的场景下从 DTS 到 fwnode 的流程就走完了已经把 DTS 描述的硬件信息交给了驱动 probe 函数内部进行必要的利用了。