📘 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 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. 架构设计

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", &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 驱动程序设计原则

  1. 统一接口使用

    /* 推荐:使用 fwnode API */
    ret = fwnode_property_read_u32(dev_fwnode(dev), "reg", &reg);
    
    /* 不推荐:直接使用特定固件 API */
    // ret = of_property_read_u32(dev->of_node, "reg", &reg);
    
  2. 错误处理

    struct fwnode_handle *child;
    
    fwnode_for_each_child_node(fwnode, child) {
        ret = process_child_node(child);
        if (ret) {
            fwnode_handle_put(child);  /* 重要:释放引用 */
            return ret;
        }
    }
    
  3. 资源清理

    static void example_driver_cleanup(struct device *dev)
    {
        struct fwnode_handle *fwnode = dev_fwnode(dev);
    
        /* fwnode 本身由设备框架管理,无需手动释放 */
        /* 但子节点引用需要显式释放 */
    }
    

8.2 性能优化建议

  1. 缓存常用属性

    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;
    }
    
  2. 避免重复查找

    /* 不推荐:重复查找 */
    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 调试技巧

  1. 属性检查

    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");
    }
    
  2. 节点层次结构调试

    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 常见问题

  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");
    
  2. 节点引用泄漏

    /* 问题:忘记释放子节点引用 */
    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 调试工具

  1. 内核调试选项

    # 启用设备树调试
    echo 1 > /sys/kernel/debug/dynamic_debug/control
    echo 'file drivers/of/* +p' > /sys/kernel/debug/dynamic_debug/control
    
  2. sysfs 接口

    # 查看设备固件信息
    cat /sys/devices/.../firmware_node/...
    

10. 未来发展

10.1 发展趋势

  1. 性能优化:继续优化属性查找和缓存机制
  2. 功能扩展:支持更多固件格式和特性
  3. 工具改进:更好的调试和分析工具

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