📘 Linux Firmware Node (fwnode) 统一设备模型
📘Linux Firmware Node (fwnode) 统一设备模型
1. 引言
Linux fwnode(Firmware Node)统一设备模型是 Linux 内核中用于抽象不同固件描述机制的通用框架。它为设备树(Device Tree)、ACPI(Advanced Configuration and Power Interface)以及其他固件描述方式提供了统一的接口,使得驱动程序可以以相同的方式访问设备属性信息,而无需关心底层的固件实现细节。
2. 历史背景与发展历程
2.1 早期固件描述问题
在 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 {
/* 可能还有基于平台数据的传统硬编码方式 */
//...
}
2.2 发展时间线
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. 架构设计
3.1 整体架构
┌─────────────────────┐
│ Driver Layer │ ← 驱动程序层
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ fwnode APIs │ ← 统一 API 层
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ fwnode Operations │ ← 操作函数层
└─────┬───┬───┬───────┘
│ │ │
┌─────▼─┐ │ ┌─▼─────┐
│ DT │ │ │ ACPI │ ← 固件实现层
│fwnode │ │ │fwnode │
└───────┘ │ └───────┘
│
┌─────▼─────┐
│ Other │
│ fwnode │
└───────────┘
3.2 核心数据结构
/**
* struct fwnode_handle - firmware node handle
* @secondary: 指向次要固件节点的指针
* @ops: 操作函数集合指针
* @dev: 关联的设备指针
*/
struct fwnode_handle {
struct fwnode_handle *secondary;
const struct fwnode_operations *ops;
struct device *dev;
struct list_head suppliers;
struct list_head consumers;
u8 flags;
};
3.3 操作函数接口
/**
* struct fwnode_operations - fwnode 操作函数集
*/
struct fwnode_operations {
struct fwnode_handle *(*get)(struct fwnode_handle *fwnode);
void (*put)(struct fwnode_handle *fwnode);
bool (*device_is_available)(const struct fwnode_handle *fwnode);
const void *(*device_get_match_data)(const struct fwnode_handle *fwnode,
const struct device *dev);
bool (*property_present)(const struct fwnode_handle *fwnode,
const char *propname);
int (*property_read_int_array)(const struct fwnode_handle *fwnode,
const char *propname,
unsigned int elem_size, void *val,
size_t nval);
int (*property_read_string_array)(const struct fwnode_handle *fwnode,
const char *propname, const char **val,
size_t nval);
const char *(*get_name)(const struct fwnode_handle *fwnode);
const char *(*get_name_prefix)(const struct fwnode_handle *fwnode);
struct fwnode_handle *(*get_parent)(const struct fwnode_handle *fwnode);
struct fwnode_handle *(*get_next_child_node)(const struct fwnode_handle *fwnode,
struct fwnode_handle *child);
struct fwnode_handle *(*get_named_child_node)(const struct fwnode_handle *fwnode,
const char *name);
int (*get_reference_args)(const struct fwnode_handle *fwnode,
const char *prop, const char *nargs_prop,
unsigned int nargs, unsigned int index,
struct fwnode_reference_args *args);
struct fwnode_handle *(*graph_get_next_endpoint)(const struct fwnode_handle *fwnode,
struct fwnode_handle *prev);
struct fwnode_handle *(*graph_get_remote_endpoint)(const struct fwnode_handle *fwnode);
struct fwnode_handle *(*graph_get_port_parent)(struct fwnode_handle *fwnode);
int (*graph_parse_endpoint)(const struct fwnode_handle *fwnode,
struct fwnode_endpoint *endpoint);
void *(*iomap)(struct fwnode_handle *fwnode, int index);
int (*irq_get)(const struct fwnode_handle *fwnode, unsigned int index);
int (*add_links)(struct fwnode_handle *fwnode);
};
4. 核心组件详解
4.1 fwnode_handle 结构体
fwnode_handle 是整个框架的核心,它代表一个固件节点的抽象:
// 示例:获取和释放 fwnode
struct fwnode_handle *fwnode_get(struct fwnode_handle *fwnode)
{
if (!fwnode)
return NULL;
return fwnode->ops->get ? fwnode->ops->get(fwnode) : fwnode;
}
void fwnode_put(struct fwnode_handle *fwnode)
{
if (!fwnode)
return;
if (fwnode->ops->put)
fwnode->ops->put(fwnode);
}
4.2 设备属性操作
4.2.1 属性存在性检查
bool fwnode_property_present(const struct fwnode_handle *fwnode,
const char *propname)
{
bool ret;
if (IS_ERR_OR_NULL(fwnode))
return false;
ret = fwnode_call_bool_op(fwnode, property_present, propname);
if (!ret && !IS_ERR_OR_NULL(fwnode->secondary))
ret = fwnode_call_bool_op(fwnode->secondary, property_present, propname);
return ret;
}
4.2.2 整数属性读取
int fwnode_property_read_u32_array(const struct fwnode_handle *fwnode,
const char *propname, u32 *val, size_t nval)
{
int ret;
ret = fwnode_call_int_op(fwnode, property_read_int_array,
propname, sizeof(u32), val, nval);
if (ret == -EINVAL && !IS_ERR_OR_NULL(fwnode->secondary))
ret = fwnode_call_int_op(fwnode->secondary, property_read_int_array,
propname, sizeof(u32), val, nval);
return ret;
}
4.2.3 字符串属性读取
int fwnode_property_read_string_array(const struct fwnode_handle *fwnode,
const char *propname, const char **val,
size_t nval)
{
int ret;
ret = fwnode_call_int_op(fwnode, property_read_string_array,
propname, val, nval);
if (ret == -EINVAL && !IS_ERR_OR_NULL(fwnode->secondary))
ret = fwnode_call_int_op(fwnode->secondary, property_read_string_array,
propname, val, nval);
return ret;
}
4.3 节点遍历操作
4.3.1 子节点遍历
#define fwnode_for_each_child_node(fwnode, child) \
for (child = fwnode_get_next_child_node(fwnode, NULL); child; \
child = fwnode_get_next_child_node(fwnode, child))
struct fwnode_handle *fwnode_get_next_child_node(const struct fwnode_handle *fwnode,
struct fwnode_handle *child)
{
if (IS_ERR_OR_NULL(fwnode))
return NULL;
return fwnode_call_ptr_op(fwnode, get_next_child_node, child);
}
4.3.2 父节点获取
struct fwnode_handle *fwnode_get_parent(const struct fwnode_handle *fwnode)
{
if (IS_ERR_OR_NULL(fwnode))
return NULL;
return fwnode_call_ptr_op(fwnode, get_parent);
}
4.4 图形节点支持
图形节点用于描述设备间的连接关系,特别是在多媒体子系统中:
/**
* struct fwnode_endpoint - 端点描述结构体
*/
struct fwnode_endpoint {
unsigned int port;
unsigned int id;
const struct fwnode_handle *local_fwnode;
};
int fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode,
struct fwnode_endpoint *endpoint)
{
memset(endpoint, 0, sizeof(*endpoint));
endpoint->local_fwnode = fwnode;
return fwnode_call_int_op(fwnode, graph_parse_endpoint, endpoint);
}
5. 实现机制
5.1 Device Tree fwnode 实现
static const struct fwnode_operations of_fwnode_ops = {
.get = of_fwnode_get,
.put = of_fwnode_put,
.device_is_available = of_fwnode_device_is_available,
.device_get_match_data = of_fwnode_device_get_match_data,
.property_present = of_fwnode_property_present,
.property_read_int_array = of_fwnode_property_read_int_array,
.property_read_string_array = of_fwnode_property_read_string_array,
.get_name = of_fwnode_get_name,
.get_name_prefix = of_fwnode_get_name_prefix,
.get_parent = of_fwnode_get_parent,
.get_next_child_node = of_fwnode_get_next_child_node,
.get_named_child_node = of_fwnode_get_named_child_node,
.get_reference_args = of_fwnode_get_reference_args,
.graph_get_next_endpoint = of_fwnode_graph_get_next_endpoint,
.graph_get_remote_endpoint = of_fwnode_graph_get_remote_endpoint,
.graph_get_port_parent = of_fwnode_graph_get_port_parent,
.graph_parse_endpoint = of_fwnode_graph_parse_endpoint,
.iomap = of_fwnode_iomap,
.irq_get = of_fwnode_irq_get,
.add_links = of_fwnode_add_links,
};
const struct fwnode_handle *of_fwnode_handle(const struct device_node *node)
{
return node ? &node->fwnode : NULL;
}
5.2 ACPI fwnode 实现
static const struct fwnode_operations acpi_fwnode_ops = {
.get = acpi_fwnode_get,
.put = acpi_fwnode_put,
.device_is_available = acpi_fwnode_device_is_available,
.device_get_match_data = acpi_fwnode_device_get_match_data,
.property_present = acpi_fwnode_property_present,
.property_read_int_array = acpi_fwnode_property_read_int_array,
.property_read_string_array = acpi_fwnode_property_read_string_array,
.get_name = acpi_fwnode_get_name,
.get_name_prefix = acpi_fwnode_get_name_prefix,
.get_parent = acpi_fwnode_get_parent,
.get_next_child_node = acpi_fwnode_get_next_child_node,
.get_named_child_node = acpi_fwnode_get_named_child_node,
.get_reference_args = acpi_fwnode_get_reference_args,
.graph_get_next_endpoint = acpi_fwnode_graph_get_next_endpoint,
.graph_get_remote_endpoint = acpi_fwnode_graph_get_remote_endpoint,
.graph_get_port_parent = acpi_fwnode_graph_get_port_parent,
.graph_parse_endpoint = acpi_fwnode_graph_parse_endpoint,
.iomap = acpi_fwnode_iomap,
.irq_get = acpi_fwnode_irq_get,
.add_links = acpi_fwnode_add_links,
};
6. 使用流程
6.1 典型使用流程
1. 获取设备的 fwnode_handle
↓
2. 检查属性是否存在
↓
3. 读取属性值
↓
4. 处理子节点(如需要)
↓
5. 释放 fwnode 引用
6.2 代码示例
/**
* 示例驱动程序使用 fwnode API
*/
static int example_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *fwnode = dev_fwnode(dev);
struct fwnode_handle *child;
u32 reg_value;
const char *clock_name;
int ret;
/* 检查必要属性是否存在 */
if (!fwnode_property_present(fwnode, "reg")) {
dev_err(dev, "Missing 'reg' property\n");
return -ENODEV;
}
/* 读取寄存器地址 */
ret = fwnode_property_read_u32(fwnode, "reg", ®_value);
if (ret) {
dev_err(dev, "Failed to read 'reg' property: %d\n", ret);
return ret;
}
/* 读取时钟名称 */
ret = fwnode_property_read_string(fwnode, "clock-names", &clock_name);
if (!ret)
dev_info(dev, "Using clock: %s\n", clock_name);
/* 遍历子节点 */
fwnode_for_each_child_node(fwnode, child) {
const char *child_name;
ret = fwnode_property_read_string(child, "label", &child_name);
if (!ret)
dev_info(dev, "Found child: %s\n", child_name);
}
return 0;
}
7. 高级功能
7.1 引用计数管理
fwnode 框架实现了自动引用计数管理,确保节点在使用期间不会被释放:
static inline struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode)
{
return fwnode_get(fwnode);
}
static inline void fwnode_handle_put(struct fwnode_handle *fwnode)
{
fwnode_put(fwnode);
}
7.2 设备链接管理
fwnode 支持设备间依赖关系的自动管理:
int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup)
{
struct fwnode_link *link;
int ret = 0;
mutex_lock(&fwnode_link_lock);
list_for_each_entry(link, &sup->suppliers, s_hook)
if (link->consumer == con)
goto out;
link = kzalloc(sizeof(*link), GFP_KERNEL);
if (!link) {
ret = -ENOMEM;
goto out;
}
link->supplier = sup;
link->consumer = con;
list_add(&link->s_hook, &sup->suppliers);
list_add(&link->c_hook, &con->consumers);
out:
mutex_unlock(&fwnode_link_lock);
return ret;
}
7.3 图形节点扩展
支持复杂的设备连接图描述:
struct fwnode_handle *fwnode_graph_get_next_endpoint(const struct fwnode_handle *fwnode,
struct fwnode_handle *prev)
{
if (IS_ERR_OR_NULL(fwnode))
return NULL;
return fwnode_call_ptr_op(fwnode, graph_get_next_endpoint, prev);
}
struct fwnode_handle *fwnode_graph_get_remote_endpoint(const struct fwnode_handle *fwnode)
{
if (IS_ERR_OR_NULL(fwnode))
return NULL;
return fwnode_call_ptr_op(fwnode, graph_get_remote_endpoint);
}
8. 最佳实践
8.1 驱动程序设计原则
-
统一接口使用
/* 推荐:使用 fwnode API */ ret = fwnode_property_read_u32(dev_fwnode(dev), "reg", ®); /* 不推荐:直接使用特定固件 API */ // ret = of_property_read_u32(dev->of_node, "reg", ®);
-
错误处理
struct fwnode_handle *child; fwnode_for_each_child_node(fwnode, child) { ret = process_child_node(child); if (ret) { fwnode_handle_put(child); /* 重要:释放引用 */ return ret; } }
-
资源清理
static void example_driver_cleanup(struct device *dev) { struct fwnode_handle *fwnode = dev_fwnode(dev); /* fwnode 本身由设备框架管理,无需手动释放 */ /* 但子节点引用需要显式释放 */ }
8.2 性能优化建议
-
缓存常用属性
struct example_data { u32 cached_reg; const char *cached_name; }; static int example_cache_properties(struct device *dev, struct example_data *data) { struct fwnode_handle *fwnode = dev_fwnode(dev); fwnode_property_read_u32(fwnode, "reg", &data->cached_reg); fwnode_property_read_string(fwnode, "name", &data->cached_name); return 0; }
-
避免重复查找
/* 不推荐:重复查找 */ if (fwnode_property_present(fwnode, "enable-gpios")) { fwnode_property_read_u32(fwnode, "enable-gpios", &gpio); } /* 推荐:一次性读取并检查返回值 */ ret = fwnode_property_read_u32(fwnode, "enable-gpios", &gpio); if (!ret) { /* 使用 gpio 值 */ }
8.3 调试技巧
-
属性检查
static void debug_print_properties(struct device *dev) { struct fwnode_handle *fwnode = dev_fwnode(dev); if (fwnode_property_present(fwnode, "reg")) dev_dbg(dev, "Has 'reg' property\n"); if (fwnode_property_present(fwnode, "interrupts")) dev_dbg(dev, "Has 'interrupts' property\n"); }
-
节点层次结构调试
static void debug_print_node_hierarchy(struct fwnode_handle *fwnode, int level) { struct fwnode_handle *child; const char *name; name = fwnode_get_name(fwnode); pr_debug("%*s%s\n", level * 2, "", name ?: "<unnamed>"); fwnode_for_each_child_node(fwnode, child) { debug_print_node_hierarchy(child, level + 1); } }
9. 故障排查
9.1 常见问题
-
属性不存在错误
/* 问题:假设属性存在 */ fwnode_property_read_u32(fwnode, "missing-prop", &value); /* 解决:检查返回值 */ ret = fwnode_property_read_u32(fwnode, "maybe-missing", &value); if (ret == -EINVAL) dev_warn(dev, "Property 'maybe-missing' not found, using default\n");
-
节点引用泄漏
/* 问题:忘记释放子节点引用 */ fwnode_for_each_child_node(fwnode, child) { if (error_condition) return -EFAULT; /* 泄漏了 child 引用 */ } /* 解决:正确释放引用 */ fwnode_for_each_child_node(fwnode, child) { if (error_condition) { fwnode_handle_put(child); return -EFAULT; } }
9.2 调试工具
-
内核调试选项
# 启用设备树调试 echo 1 > /sys/kernel/debug/dynamic_debug/control echo 'file drivers/of/* +p' > /sys/kernel/debug/dynamic_debug/control
-
sysfs 接口
# 查看设备固件信息 cat /sys/devices/.../firmware_node/...
10. 未来发展
10.1 发展趋势
- 性能优化:继续优化属性查找和缓存机制
- 功能扩展:支持更多固件格式和特性
- 工具改进:更好的调试和分析工具
10.2 相关技术
- 设备资源管理:与 devres 框架的深度集成
- 电源管理:与 PM 框架的协作优化
- 热插拔支持:动态设备管理能力增强
11. 结论
Linux fwnode 统一设备模型为内核提供了一套优雅的固件抽象层,有效解决了跨平台驱动开发的复杂性问题。通过提供统一的 API 接口,它简化了驱动程序的开发和维护,提高了代码的可重用性和可移植性。
随着 Linux 内核的持续发展,fwnode 框架将继续演进,为更多的硬件平台和使用场景提供支持。对于内核开发者而言,深入理解和正确使用 fwnode API 是编写高质量、跨平台驱动程序的关键技能。
附录
- 相关源码路径:
drivers/base/property.c
drivers/of/property.c
drivers/acpi/property.c
include/linux/fwnode.h
- 官方文档:
Documentation/devicetree/bindings/
Documentation/driver-api/fwnode.rst