关于设备树的 device_for_each_child_node函数详解

1.关于设备树的 device_for_each_child_node函数详解

在 Linux 内核中,device_for_each_child_node 是一个用于遍历设备树(Device Tree)子节点的宏函数,主要用于 ARM 架构的设备树操作。以下是详细解析:


函数作用

device_for_each_child_node 用于遍历指定父节点(parent)下的所有子节点(child nodes),并对每个子节点执行用户定义的回调函数(callback)。常用于设备驱动中动态解析设备树结构。


函数原型

#define device_for_each_child_node(parent, child, callback, data) \
    of_for_each_child(parent, child) { \
        if (callback(child, data)) break; \
    } while (0)
  • 参数说明
    • parent:父设备节点(struct device_node *),表示要遍历的起始节点。
    • child:临时变量(struct device_node *),用于存储当前遍历到的子节点。
    • callback:回调函数,对每个子节点执行的操作。
    • data:传递给回调函数的自定义数据(可选)。

回调函数要求

回调函数需满足以下签名:

int callback(struct device_node *child, void *data);
  • 返回值
    • 返回 0:继续遍历下一个子节点。
    • 返回非零值:终止遍历。

使用示例

#include <linux/of.h>

static int my_child_callback(struct device_node *child, void *data) {
    const char *name;
    int ret;

    // 获取子节点名称
    name = child->name;
    
    // 示例操作:打印子节点名称
    pr_info("Found child node: %s\n", name);

    // 示例操作:检查特定属性
    ret = of_property_read_u32(child, "reg", &reg_value);
    if (ret) {
        pr_warn("No 'reg' property in %s\n", name);
        return 0; // 继续遍历
    }

    return 0; // 继续遍历
}

// 在驱动代码中调用
static int my_driver_probe(struct platform_device *pdev) {
    struct device_node *parent = pdev->dev.of_node;
    struct device_node *child;

    // 遍历父节点下的所有子节点
    device_for_each_child_node(parent, child, my_child_callback, NULL) {
        // 此处可对子节点进行进一步操作
    }

    return 0;
}

底层实现

  • device_for_each_child_node 实际上是对 of_for_each_child 宏的封装。
  • of_for_each_child 会通过 parent->child 链表遍历所有子节点,直到链表末尾(NULL)。

典型应用场景

  1. 动态解析子设备:在驱动初始化时,遍历父节点下的所有子节点,为每个子设备注册驱动或分配资源。
  2. 条件匹配:根据子节点的属性(如 compatible 字符串)筛选特定设备。
  3. 资源收集:读取子节点的 reginterrupts 等属性,配置硬件寄存器或中断。

注意事项

  1. 线程安全:设备树操作通常在驱动初始化阶段完成,无需考虑并发问题。
  2. 错误处理:回调函数中应谨慎处理属性读取失败的情况,避免驱动崩溃。
  3. 性能优化:避免在回调函数中执行耗时操作(如阻塞式 I/O)。

相关函数

  • of_get_next_child:手动遍历子节点(需维护指针)。
  • of_find_node_by_name:按名称查找子节点。
  • of_match_node:按 compatible 属性匹配节点。

通过 device_for_each_child_node,开发者可以高效地遍历设备树结构,实现硬件资源的动态配置,是 ARM 设备树驱动开发的核心工具之一。

2.结合设备树应用举例 

以下是一个完整的 Linux 内核模块开发示例,结合设备树遍历和 I2C 子系统,展示从设备树定义到驱动注册的全流程:


1. 设备树定义(DTS)

/ {
    i2c_bus: i2c@1 {
        compatible = "my-vendor,i2c-bus";
        reg = <0x1>;
        #address-cells = <1>;
        #size-cells = <0>;

        // 子设备1:温度传感器
        temp_sensor@48 {
            compatible = "my-vendor,temp-sensor";
            reg = <0x48>;
            label = "Temperature Sensor";
        };

        // 子设备2:EEPROM
        eeprom@50 {
            compatible = "my-vendor,eeprom";
            reg = <0x50>;
            label = "EEPROM";
        };
    };
};

2. 内核驱动代码(C)

#include <linux/module.h>
#include <linux/of.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>

// --- 步骤1:定义设备树匹配表 ---
static const struct of_device_id my_i2c_of_match[] = {
    { .compatible = "my-vendor,i2c-bus" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);

// --- 步骤2:定义I2C适配器算法 ---
static int my_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) {
    // 实现I2C传输逻辑(此处为简化示例,实际需硬件操作)
    return num; // 假设所有消息传输成功
}

static u32 my_i2c_func(struct i2c_adapter *adap) {
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm my_i2c_algo = {
    .master_xfer = my_i2c_xfer,
    .functionality = my_i2c_func,
};

// --- 步骤3:定义I2C适配器结构体 ---
static struct i2c_adapter my_i2c_adapter = {
    .owner   = THIS_MODULE,
    .class   = I2C_CLASS_HWMON | I2C_CLASS_SPD,
    .algo    = &my_i2c_algo,
    .dev     = {
        .of_node   = NULL, // 由驱动填充
        .init_name = "my-i2c-adapter",
    },
};

// --- 步骤4:回调函数处理子节点 ---
static int i2c_child_handler(struct device_node *child, void *data) {
    struct i2c_adapter *adap = data;
    const char *compatible;
    struct i2c_board_info info = {0};
    struct i2c_client *client;

    // 读取compatible属性
    if (of_property_read_string(child, "compatible", &compatible)) {
        dev_err(adap->dev.parent, "Missing 'compatible' in %s\n", child->name);
        return 0;
    }

    // 根据compatible注册不同设备
    if (!strcmp(compatible, "my-vendor,temp-sensor")) {
        strlcpy(info.type, "temp_sensor", I2C_NAME_SIZE);
    } else if (!strcmp(compatible, "my-vendor,eeprom")) {
        strlcpy(info.type, "eeprom", I2C_NAME_SIZE);
    } else {
        dev_warn(adap->dev.parent, "Unknown device: %s\n", compatible);
        return 0;
    }

    // 读取reg属性作为I2C地址
    of_property_read_u32(child, "reg", &info.addr);

    // 注册I2C客户端
    client = i2c_new_device(adap, &info);
    if (client) {
        dev_info(adap->dev.parent, "Registered %s at 0x%02x\n",
                 info.type, info.addr);
    } else {
        dev_err(adap->dev.parent, "Failed to register %s\n", info.type);
    }

    return 0;
}

// --- 步骤5:Probe函数(绑定设备树节点) ---
static int my_i2c_probe(struct platform_device *pdev) {
    struct device_node *parent = pdev->dev.of_node;
    struct device_node *child;

    // 初始化I2C适配器
    my_i2c_adapter.dev.parent = &pdev->dev;
    my_i2c_adapter.dev.of_node = parent;

    // 注册I2C适配器到内核
    if (i2c_add_adapter(&my_i2c_adapter)) {
        dev_err(&pdev->dev, "Failed to add I2C adapter\n");
        return -ENODEV;
    }

    // 遍历子节点并注册设备
    device_for_each_child_node(parent, child, i2c_child_handler, &my_i2c_adapter);

    dev_info(&pdev->dev, "I2C bus initialized\n");
    return 0;
}

// --- 步骤6:Remove函数(清理资源) ---
static int my_i2c_remove(struct platform_device *pdev) {
    i2c_del_adapter(&my_i2c_adapter);
    dev_info(&pdev->dev, "I2C bus removed\n");
    return 0;
}

// --- 步骤7:定义Platform驱动结构体 ---
static struct platform_driver my_i2c_driver = {
    .driver = {
        .name   = "my-i2c-driver",
        .of_match_table = of_match_ptr(my_i2c_of_match),
    },
    .probe  = my_i2c_probe,
    .remove = my_i2c_remove,
};

// --- 步骤8:模块初始化/退出函数 ---
static int __init my_i2c_init(void) {
    return platform_driver_register(&my_i2c_driver);
}

static void __exit my_i2c_exit(void) {
    platform_driver_unregister(&my_i2c_driver);
}

module_init(my_i2c_init);
module_exit(my_i2c_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My I2C Bus Driver with Device Tree Support");

3. Makefile

obj-m += my_i2c_driver.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

4. 完整流程解析

  1. 设备树定义
    • 定义一个I2C总线节点(i2c@1),包含两个子设备。
    • 子设备通过 compatible 属性声明类型,reg 属性指定I2C地址。
  2. 驱动初始化
    • 匹配表of_device_id 数组将驱动与设备树节点关联。
    • I2C算法:实现 master_xfer 和 functionality 函数(示例中简化)。
    • 适配器结构体:描述I2C控制器的属性和算法。
  3. Probe函数
    • 绑定到设备树节点后,初始化I2C适配器并注册到内核。
    • 使用 device_for_each_child_node 遍历子节点,动态注册子设备。
  4. 子设备注册
    • 回调函数 i2c_child_handler 解析子节点的 compatible 和 reg 属性。
    • 调用 i2c_new_device 为每个子设备创建I2C客户端。
  5. 模块管理
    • module_init 和 module_exit 注册/注销平台驱动。
    • platform_driver 结构体定义驱动的探测和移除逻辑。

5. 测试与验证

  1. 编译模块

    make
    sudo insmod my_i2c_driver.ko

    查看日志

    dmesg | grep "I2C bus"
  2. 检测I2C设备

    i2cdetect -l # 列出I2C总线
    i2cdetect -y 1 # 扫描总线1上的设备

6. 关键点总结

  • 设备树驱动分离:硬件描述(DTS)与驱动逻辑(C代码)解耦。
  • 动态注册:通过遍历子节点,无需硬编码设备信息。
  • 模块化设计:支持热插拔和动态配置,提升代码复用性。

此示例完整展示了从设备树定义到驱动实现的流程,适用于I2C、SPI等总线型设备的驱动开发。

 7.函数调用关系图

1.模块初始化调用关系(模块框架逻辑) 

2.适配器结果体 

 

3.探索函数(主要逻辑 )

4.代码分析完整结构图(因为大,不方便网也观看,所以上面进行了截图,如果下载看,用这个图更方便) 

 

3.关于驱动开发的 DEVICE_ATTR_WO 

在 Linux 内核驱动开发中,DEVICE_ATTR_WO 是一个用于创建 只写(Write-Only)sysfs 属性的宏,属于设备属性(Device Attribute)机制的一部分。以下是详细解析和示例:


核心作用

DEVICE_ATTR_WO 用于在 /sys/devices/.../ 目录下生成一个 只允许写入 的文件,用户空间程序可以通过向该文件写入数据来触发驱动中的特定操作,但无法读取其内容。


宏定义解析

DEVICE_ATTR_WO 的典型定义如下(内核源码 include/linux/device.h):

#define DEVICE_ATTR_WO(_name, _store) \
DEVICE_ATTR(_name, S_IWUSR | S_IWGRP, NULL, _store)
  • 参数
    • _name:属性文件名(如 reset)。
    • _store:写入回调函数(store 函数)。
  • 权限S_IWUSR | S_IWGRP 表示文件可被 所有者写 和 组写
  • show 函数:设为 NULL,表示禁止读取操作。

使用场景

  • 控制硬件状态:如触发设备复位、切换 GPIO 引脚电平。
  • 配置参数:写入配置数据(无需读取当前值)。
  • 触发动作:执行一次性操作(如清除错误日志)。

代码示例

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

// 定义设备结构体
struct my_device {
    struct device *dev;
    int reset_count;
};

// 写入回调函数
static ssize_t reset_store(struct device *dev, 
                          struct device_attribute *attr,
                          const char *buf, 
                          size_t count) {
    struct my_device *mydev = dev_get_drvdata(dev);
    unsigned long val;

    // 解析用户输入(示例:将字符串转换为整数)
    if (kstrtoul(buf, 10, &val)) {
        dev_err(dev, "Invalid input\n");
        return -EINVAL;
    }

    // 执行操作(示例:触发复位)
    if (val == 1) {
        mydev->reset_count++;
        dev_info(dev, "Device reset! Count: %d\n", mydev->reset_count);
        // 实际硬件操作(如写寄存器、触发中断等)
    }

    return count;
}

// 定义只写属性
static DEVICE_ATTR_WO(reset); // 生成 /sys/devices/.../reset

// 驱动初始化
static int my_driver_probe(struct platform_device *pdev) {
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
    if (!mydev)
        return -ENOMEM;

    mydev->dev = &pdev->dev;
    platform_set_drvdata(pdev, mydev);

    // 创建sysfs属性
    ret = device_create_file(mydev->dev, &dev_attr_reset);
    if (ret) {
        dev_err(&pdev->dev, "Failed to create sysfs entry\n");
        return ret;
    }

    return 0;
}

// 驱动退出
static int my_driver_remove(struct platform_device *pdev) {
    struct my_device *mydev = platform_get_drvdata(pdev);
    device_remove_file(mydev->dev, &dev_attr_reset);
    return 0;
}

关键点说明

  1. 权限控制
    • 文件权限为 S_IWUSR | S_IWGRP,表示仅允许写入。
    • 用户空间写入需权限(如 root 或组权限)。
  2. store 函数
    • 参数 buf 包含用户写入的数据。
    • 使用 kstrtoul 等函数解析输入,避免缓冲区溢出。
    • 返回写入字节数(count)或错误码(如 -EINVAL)。
  3. 错误处理
    • 验证输入合法性,防止无效数据导致内核崩溃。
    • 使用 dev_err 输出错误日志,便于调试。
  4. sysfs 文件操作
    • device_create_file 创建属性文件。
    • device_remove_file 在驱动卸载时清理。

用户空间操作

# 写入数据(触发复位)
echo 1 > /sys/devices/platform/my_device/reset

# 读取会失败(只写属性)
cat /sys/devices/platform/my_device/reset
# 输出:cat: reset: Permission denied

对比其他属性宏

读写权限show 函数store 函数典型用途
DEVICE_ATTR_RO只读(S_IRUGO必须NULL状态查询
DEVICE_ATTR_WO只写(S_IWUSRNULL必须控制命令
DEVICE_ATTR_RW读写(`S_IRUGOS_IWUSR`)必须必须

注意事项

  1. 安全性:确保 store 函数对输入进行严格验证,避免内核崩溃或安全漏洞。
  2. 原子性:复杂操作应使用锁机制(如 mutex)保证原子性。
  3. 兼容性:内核版本 >= 2.6.18 支持此宏,新版本推荐使用 device_property_write 等接口。

通过 DEVICE_ATTR_WO,驱动可以安全地暴露控制接口给用户空间,实现硬件的灵活配置。

 

<think>嗯,用户问的是fwnode和of_node函数有什么区别。首先,我需要回想一下Linux内核中设备树相关的结构体。记得在内核里,设备树处理涉及到两种结构:of_node属于Open Firmware(OF)的节点,而fwnode是更通用的firmware节点抽象。 用户可能是开发者,遇到了设备树相关的开发问题,或者在调试驱动时需要理解这两种结构体的区别。他们可能对设备树的处理机制不太清楚,或者在使用某些API时遇到了困惑,需要明确两者的使用场景和区别。 我应该先解释两者的基本概念。of_node是专门用于设备树(DT)的,而fwnode是后来引入的,用于支持不同的firmware描述方式,比如ACPI和DT。所以,fwnode更通用,可以代表不同来源的节点,而of_node是特定于DT的。 然后需要比较它们的API函数。比如,of_property_read_u32对应的是fwnode_property_read_u32。这时候要指出,虽然功能相似,但fwnode函数可以处理多种firmware类型,而of的函数只能用于DT。 还要提到转换函数,比如to_of_node,这样用户知道如何在两者之间转换。同时,应用场景方面,当驱动需要同时支持ACPI和DT时,应该使用fwnode的API,而纯DT驱动的代码则可能直接使用of_node函数。 可能用户的实际问题是在编写跨firmware驱动的兼容代码,或者在内核模块中遇到API选择的问题。这时候需要强调,使用fwnode的API可以提高代码的可移植性,适应不同的固件环境。 另外,要注意内核版本的变化,因为fwnode是在较新的内核中引入的,用于统一不同firmware的处理方式。如果用户的内核版本较旧,可能主要使用of_node函数,而在新版本中推荐使用fwnode的抽象层。 最后,总结两者的区别和适用场景,帮助用户根据实际需求选择合适的API,并理解为什么会有这两种不同的结构存在。</think>在 Linux 内核设备模型(Device Tree / ACPI)中,`fwnode` 和 `of_node` 是两种 **描述设备硬件资源** 的核心结构,它们的区别主要体现在 **设计层级** 和 **适用范围** 上: --- ### **核心区别对照表** | 特征 | of_node | fwnode | |---------------------|----------------------------------|---------------------------------| | **所属体系** | Open Firmware (OF) / 设备树专用 | 通用固件描述抽象层 | | **内核起源** | 传统设备树驱动架构 | 统一 ACPI/DT 的新抽象层 | | **数据结构** | `struct device_node` | `struct fwnode_handle` | | **适用场景** | 纯设备树 (DT) 系统 | 同时支持 DT/ACPI 的混合系统 | | **函数前缀** | `of_` (如 `of_property_read_u32`) | `fwnode_` (如 `fwnode_property_read_u32`) | --- ### **功能层级分析** #### 1. **of_node 体系** ```c // 典型设备树节点操作 struct device_node *np = dev->of_node; of_property_read_u32(np, "clock-frequency", &val); ``` - **直接绑定设备树**: `of_node` 是设备树节点 (`device_node`) 的直接指针,仅适用于 DT 系统 - **专用 API**: 函数前缀为 `of_`,如 `of_get_child_by_name()`, `of_irq_get()` #### 2. **fwnode 体系** ```c // 通用固件节点操作 struct fwnode_handle *fwnode = dev_fwnode(dev); fwnode_property_read_u32(fwnode, "max-speed", &speed); ``` - **抽象层设计**: `fwnode` 是通用固件节点句柄,可表示 DT 节点、ACPI 设备或其他固件类型 - **统一 API**: 函数前缀为 `fwnode_`,通过 `fwnode_operations` 实现多态行为 --- ### **关键转换关系** ```c // of_node 转 fwnode(向下兼容) struct fwnode_handle *fwnode = of_node_to_fwnode(np); // fwnode 转 of_node(需类型验证) if (is_of_node(fwnode)) { struct device_node *np = to_of_node(fwnode); } ``` --- ### **典型应用场景** #### **应使用 of_node 的情况** ✅ - 纯设备树驱动开发(如嵌入式定制硬件) - 需要直接操作设备树特定属性(如 `reg`, `interrupts`) - 兼容旧版内核 (< 4.0) #### **应使用 fwnode 的情况** ✅ - 需要同时支持 ACPI 和 DT 的通用驱动(如 PCI/USB 设备) - 处理与固件无关的通用资源(如 GPIO、时钟) - 新驱动开发(内核 >= 4.0) --- ### **API 对比示例** | 操作 | of_node 实现 | fwnode 实现 | |-----------------------|----------------------------------|----------------------------------| | 读取整型属性 | `of_property_read_u32()` | `fwnode_property_read_u32()` | | 获取子节点 | `of_get_child_by_name()` | `fwnode_get_named_child_node()` | | 遍历子节点 | `for_each_child_of_node()` | `fwnode_for_each_child_node()` | | 获取中断信息 | `of_irq_get()` | `fwnode_irq_get()` | --- ### **设计演进背景** - **历史阶段**: - 早期内核仅支持设备树 → 催生 `of_node` 体系 - 随着 ACPI 在 x86 平台的普及 → 需要统一抽象层 → 引入 `fwnode` - **兼容性策略**: `fwnode` 包含 `of_node` 的转换接口,实现渐进式迁移 --- ### **调试技巧** ```bash # 查看设备树节点信息 $ cat /proc/device-tree/path/to/node/property-name # 检查 fwnode 类型 (gdb) p ((struct fwnode_handle *)fwnode)->ops # 若输出含 `of_fwnode_ops` 则源自 DT ``` --- ### **总结建议** - **新驱动优先使用 fwnode API** → 提高跨平台兼容性 - **旧驱动维护保持 of_node 逻辑** → 避免破坏现有功能 - **混合系统需类型检查** → 使用 `is_of_node()` 确保安全转换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值