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", ®_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
)。
典型应用场景
- 动态解析子设备:在驱动初始化时,遍历父节点下的所有子节点,为每个子设备注册驱动或分配资源。
- 条件匹配:根据子节点的属性(如
compatible
字符串)筛选特定设备。 - 资源收集:读取子节点的
reg
、interrupts
等属性,配置硬件寄存器或中断。
注意事项
- 线程安全:设备树操作通常在驱动初始化阶段完成,无需考虑并发问题。
- 错误处理:回调函数中应谨慎处理属性读取失败的情况,避免驱动崩溃。
- 性能优化:避免在回调函数中执行耗时操作(如阻塞式 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. 完整流程解析
- 设备树定义:
- 定义一个I2C总线节点(
i2c@1
),包含两个子设备。 - 子设备通过
compatible
属性声明类型,reg
属性指定I2C地址。
- 定义一个I2C总线节点(
- 驱动初始化:
- 匹配表:
of_device_id
数组将驱动与设备树节点关联。 - I2C算法:实现
master_xfer
和functionality
函数(示例中简化)。 - 适配器结构体:描述I2C控制器的属性和算法。
- 匹配表:
- Probe函数:
- 绑定到设备树节点后,初始化I2C适配器并注册到内核。
- 使用
device_for_each_child_node
遍历子节点,动态注册子设备。
- 子设备注册:
- 回调函数
i2c_child_handler
解析子节点的compatible
和reg
属性。 - 调用
i2c_new_device
为每个子设备创建I2C客户端。
- 回调函数
- 模块管理:
module_init
和module_exit
注册/注销平台驱动。platform_driver
结构体定义驱动的探测和移除逻辑。
5. 测试与验证
-
编译模块:
make sudo insmod my_i2c_driver.ko
查看日志:
dmesg | grep "I2C bus"
-
检测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;
}
关键点说明
- 权限控制:
- 文件权限为
S_IWUSR | S_IWGRP
,表示仅允许写入。 - 用户空间写入需权限(如
root
或组权限)。
- 文件权限为
- store 函数:
- 参数
buf
包含用户写入的数据。 - 使用
kstrtoul
等函数解析输入,避免缓冲区溢出。 - 返回写入字节数(
count
)或错误码(如-EINVAL
)。
- 参数
- 错误处理:
- 验证输入合法性,防止无效数据导致内核崩溃。
- 使用
dev_err
输出错误日志,便于调试。
- 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_IWUSR ) | NULL | 必须 | 控制命令 |
DEVICE_ATTR_RW | 读写(`S_IRUGO | S_IWUSR`) | 必须 | 必须 |
注意事项
- 安全性:确保
store
函数对输入进行严格验证,避免内核崩溃或安全漏洞。 - 原子性:复杂操作应使用锁机制(如
mutex
)保证原子性。 - 兼容性:内核版本 >= 2.6.18 支持此宏,新版本推荐使用
device_property_write
等接口。
通过 DEVICE_ATTR_WO
,驱动可以安全地暴露控制接口给用户空间,实现硬件的灵活配置。