OF函数实战:从设备树节点到驱动代码的完整链路解析
对于已经具备Linux驱动开发经验的工程师来说,设备树的引入既带来了硬件描述的标准化,也带来了新的挑战。当你面对一块搭载新传感器的开发板,如何快速从设备树节点中提取关键配置信息并整合到驱动代码中,成为项目推进的关键环节。本文将以实战为导向,通过一个完整的I2C传感器驱动开发案例,深入解析OF函数的使用技巧和最佳实践。
1. 设备树节点定位与解析基础
在开始编写驱动之前,我们首先需要理解设备树中传感器节点的结构。假设我们有一个I2C温度传感器,其在设备树中的描述可能如下:
&i2c1 {
status = "okay";
temperature_sensor: tmp451@4c {
compatible = "ti,tmp451";
reg = <0x4c>;
vcc-supply = <®_3v3>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
#thermal-sensor-cells = <1>;
data-reg = <0x00 0x01 0x02>;
alarm-threshold = /bits/ 8 <75>;
};
};
要访问这个节点,我们首先需要获取其设备节点指针。Linux内核提供了多种查找节点的OF函数,每种都有其适用场景:
/* 通过完整路径查找节点 */
struct device_node *np1 = of_find_node_by_path("/soc/i2c@021a0000/tmp451@4c");
/* 通过节点名查找(不推荐,容易冲突) */
struct device_node *np2 = of_find_node_by_name(NULL, "tmp451@4c");
/* 通过兼容性字符串查找(最常用) */
struct device_node *np3 = of_find_compatible_node(NULL, NULL, "ti,tmp451");
在实际开发中,我们通常在驱动的probe函数中通过设备指针获取对应的节点:
static int tmp451_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device_node *np = dev->of_node;
if (!np) {
dev_err(dev, "No device tree node found\n");
return -ENODEV;
}
/* 后续处理 */
return 0;
}
提示:虽然
of_find_node_by_name和of_find_node_by_path在某些场景下很方便,但在生产代码中建议优先使用of_find_compatible_node,因为它提供了更好的设备兼容性保证。
2. 属性读取:从基础到高级技巧
获取设备节点后,下一步是读取其中的属性值。OF函数提供了丰富的属性读取API,覆盖了从简单数值到复杂数组的各种数据类型。
2.1 基本数值属性读取
对于简单的数值属性,如寄存器地址、中断号等,可以使用直接读取函数:
u32 reg_addr;
int ret;
/* 读取32位无符号整数 */
ret = of_property_read_u32(np, "reg", ®_addr);
if (ret) {
dev_err(dev, "Failed to read reg property: %d\n", ret);
return ret;
}
/* 读取8位无符号整数 */
u8 threshold;
ret = of_property_read_u8(np, "alarm-threshold", &threshold);
if (ret) {
dev_warn(dev, "No alarm threshold specified, using default\n");
threshold = 80; /* 默认值 */
}
2.2 数组属性读取
对于包含多个数值的数组属性,如多寄存器地址、配置序列等,需要使用数组读取函数:
/* 确定数组元素数量 */
int elem_count = of_property_count_elems_of_size(np, "data-reg", sizeof(u32));
if (elem_count < 0) {
dev_err(dev, "Invalid data-reg property\n");
return elem_count;
}
/* 动态分配内存并读取数组 */
u32 *data_regs = kmalloc_array(elem_count, sizeof(u32), GFP_KERNEL);
if (!data_regs)
return -ENOMEM;
ret = of_property_read_u32_array(np, "data-reg", data_regs, elem_count);
if (ret) {
dev_err(dev, "Failed to read data-reg array: %d\n", ret);
kfree(data_regs);
return ret;
}
/* 使用数组数据 */
for (int i = 0; i < elem_count; i++) {
dev_info(dev, "Data register %d: 0x%02x\n", i, data_regs[i]);
}
kfree(data_regs);
2.3 字符串属性读取
字符串属性的读取相对简单,但需要注意内存管理:
const char *sensor_type;
ret = of_property_read_string(np, "sensor-type", &sensor_type);
if (ret) {
dev_info(dev, "No sensor type specified\n");
} else {
dev_info(dev, "Sensor type: %s\n", sensor_type);
}
注意:
of_property_read_string返回的指针指向设备树节点内部的字符串存储空间,不需要手动释放,但也不应该修改其内容。
3. 复杂属性处理与资源管理
在实际驱动开发中,我们经常需要处理中断、寄存器映射等复杂资源。OF函数提供了专门的API来处理这些场景。
3.1 中断资源处理
设备树中的中断属性通常包含中断号和中断类型信息:
int irq_num;
irq_num = of_irq_get(np, 0); /* 获取第一个中断 */
if (irq_num < 0) {
dev_err(dev, "Failed to get IRQ: %d\n", irq_num);
return irq_num;
}
/* 注册中断处理程序 */
ret = devm_request_irq(dev, irq_num, tmp451_irq_handler,
IRQF_TRIGGER_LOW, "tmp451", priv);
if (ret) {
dev_err(dev, "Failed to request IRQ %d: %d\n", irq_num, ret);
return ret;
}
3.2 寄存器资源映射
对于内存映射的寄存器,可以使用of_iomap直接获取映射后的虚拟地址:
void __iomem *base_addr;
/* 获取并映射寄存器区域 */
base_addr = of_iomap(np, 0);
if (!base_addr) {
dev_err(dev, "Failed to map registers\n");
return -ENOMEM;
}
/* 使用映射的寄存器 */
u32 config_value = ioread32(base_addr + CONFIG_REG_OFFSET);
iowrite32(new_value, base_addr + CONTROL_REG_OFFSET);
/* 在驱动卸载时取消映射 */
iounmap(base_addr);
对于更复杂的资源管理,可以使用of_address_to_resource获取完整的资源信息:
struct resource res;
ret = of_address_to_resource(np, 0, &res);
if (ret) {
dev_err(dev, "Failed to get resource: %d\n", ret);
return ret;
}
dev_info(dev, "Resource start: 0x%08llx, end: 0x%08llx, flags: 0x%lx\n",
res.start, res.end, res.flags);
4. 高级技巧与错误处理实战
在实际项目中,健壮的错误处理和调试技巧同样重要。以下是一些实战中总结的经验。
4.1 属性读取的错误处理模式
建议使用统一的错误处理模式来管理属性读取:
struct sensor_config {
u32 reg_base;
u8 threshold;
u32 data_regs[3];
int irq_num;
const char *type;
};
static int parse_device_tree(struct device *dev, struct sensor_config *cfg)
{
struct device_node *np = dev->of_node;
int ret;
int elem_count;
/* 读取必需属性 */
ret = of_property_read_u32(np, "reg", &cfg->reg_base);
if (ret) {
dev_err(dev, "Missing required 'reg' property\n");
return ret;
}
/* 读取可选属性并提供默认值 */
ret = of_property_read_u8(np, "alarm-threshold", &cfg->threshold);
if (ret)
cfg->threshold = 80; /* 默认阈值 */
/* 处理数组属性 */
elem_count = of_property_count_elems_of_size(np, "data-reg", sizeof(u32));
if (elem_count > 0) {
if (elem_count > ARRAY_SIZE(cfg->data_regs)) {
dev_err(dev, "Too many data registers: %d\n", elem_count);
return -EINVAL;
}
ret = of_property_read_u32_array(np, "data-reg", cfg->data_regs, elem_count);
if (ret) {
dev_err(dev, "Failed to read data-reg array: %d\n", ret);
return ret;
}
}
/* 处理中断 */
cfg->irq_num = of_irq_get(np, 0);
if (cfg->irq_num < 0 && cfg->irq_num != -ENXIO) {
dev_err(dev, "Failed to get IRQ: %d\n", cfg->irq_num);
return cfg->irq_num;
}
/* 处理字符串 */
ret = of_property_read_string(np, "sensor-type", &cfg->type);
if (ret)
cfg->type = "unknown";
return 0;
}
4.2 调试技巧与日志输出
在开发过程中,详细的调试信息可以帮助快速定位问题:
static void debug_print_device_tree(struct device *dev, struct device_node *np)
{
struct property *prop;
const char *compat;
int count;
dev_info(dev, "Device tree node: %s\n", np->full_name);
/* 打印兼容性字符串 */
of_property_read_string(np, "compatible", &compat);
dev_info(dev, "Compatible: %s\n", compat);
/* 打印所有属性 */
for_each_property_of_node(np, prop) {
if (prop->length > 0) {
dev_info(dev, "Property: %s, length: %d\n", prop->name, prop->length);
/* 对于数值属性,尝试解析并打印 */
if (of_property_count_elems_of_size(np, prop->name, sizeof(u32)) > 0) {
u32 values[10];
count = of_property_read_u32_array(np, prop->name, values,
min_t(int, of_property_count_elems_of_size(np, prop->name, sizeof(u32)), 10));
if (count > 0) {
dev_info(dev, " Values: ");
for (int i = 0; i < count; i++)
dev_info(dev, "0x%x ", values[i]);
dev_info(dev, "\n");
}
}
}
}
}
4.3 常见错误排查指南
在实际开发中,经常会遇到一些典型问题:
问题1:属性读取返回-EINVAL
- 检查属性名拼写是否正确
- 确认属性确实存在于设备树中
- 使用
of_get_property验证属性存在性
问题2:数组读取失败
- 使用
of_property_count_elems_of_size确认元素数量和类型匹配 - 检查设备树中数组格式是否正确
问题3:中断获取失败
- 确认设备树中中断定义正确
- 检查中断父节点是否正确设置
- 使用
of_irq_get的返回值判断具体错误类型
问题4:寄存器映射失败
- 确认寄存器地址范围正确
- 检查设备树中reg属性的格式和#address-cells/#size-cells设置
我在多个传感器驱动项目中发现,最常出现的问题往往是设备树节点路径错误或属性名拼写错误。建议在驱动初始化时添加详细的调试输出,确保节点和属性都能正确读取。另外,对于可选属性,一定要提供合理的默认值,避免因为设备树中缺少可选属性而导致驱动初始化失败。
通过本文介绍的OF函数使用技巧和实战经验,你应该能够快速地将设备树中的硬件配置信息整合到驱动代码中,构建稳定可靠的Linux设备驱动。记住,良好的错误处理和详细的日志输出是驱动开发成功的关键因素。
802



被折叠的 条评论
为什么被折叠?



