接前一篇文章:嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(5)
实战示例:LED 驱动中的设备树使用
讲了这么多API,现在我们把它们串起来,看看在实际驱动里是怎么用的。我们以LED硬件控制代码为例,完整走一遍流程。
第一步:查找节点
static const char* kIMX_AES_LED = "/imx_aes_led"; led.device_tree_node = of_find_node_by_path(kIMX_AES_LED); if (led.device_tree_node == NULL) { pr_err("dtsled node can not found!\n"); return -EINVAL; } pr_info("dtsled node has been found!\n");这里我们用路径查找节点。如果没找到,直接返回错误。注意这里还没释放引用,因为后面还要用这个节点。
第二步:读取属性(调试用)
/* 读取 compatible 属性 */ proper = of_find_property(led.device_tree_node, "compatible", NULL); if (proper == NULL) { pr_err("compatible property find failed\n"); } else { pr_info("compatible = %s\n", (char*)proper->value); } /* 读取 status 属性 */ ret = of_property_read_string(led.device_tree_node, "status", &str); if (ret < 0) { pr_err("status read failed!\n"); } else { pr_info("status = %s\n", str); }这两步主要是为了调试,确认我们找到了正确的节点,并且节点状态是"okay"。在实际生产代码里,这些调试信息可以去掉或改成pr_debug()。
第三步:读取reg属性
ret = of_property_read_u32_array(led.device_tree_node, "reg", regdata, 10); if (ret < 0) { pr_err("reg property read failed!\n"); of_node_put(led.device_tree_node); return -EINVAL; } pr_info("reg data:\n"); for (int i = 0; i < 10; i++) { pr_cont("%#X ", regdata[i]); } pr_cont("\n");这里我们读取reg属性的所有10个整数。注意出错处理里调用了of_node_put(),避免内存泄漏。
第四步:映射寄存器地址
led.ccm_ccgr1 = of_iomap(led.device_tree_node, 0); led.sw_mux_gpio = of_iomap(led.device_tree_node, 1); led.sw_pad_gpio = of_iomap(led.device_tree_node, 2); led.gpio_dr = of_iomap(led.device_tree_node, 3); led.gpio_gdir = of_iomap(led.device_tree_node, 4); if (!led.ccm_ccgr1 || !led.sw_mux_gpio || !led.sw_pad_gpio || !led.gpio_dr || !led.gpio_gdir) { pr_err("ioremap failed!\n"); of_node_put(led.device_tree_node); return -ENOMEM; }这里我们用of_iomap()一次性完成地址读取和映射。注意检查了所有映射是否成功,只要有一个失败就全部回滚。
第五步:硬件初始化
/* 使能 GPIO1 时钟 */ val = readl(led.ccm_ccgr1); pr_info("CCGR1 raw value: 0x%08x\n Bits: ", val); pr_bin_u32(val); pr_cont("\n"); val &= ~(3 << 26); /* 清除以前的设置 */ val |= (3 << 26); /* 设置新值 */ writel(val, led.ccm_ccgr1); /* 设置 GPIO1_IO03 复用功能为 GPIO */ writel(5, led.sw_mux_gpio); /* 设置 GPIO1_IO03 电气属性 */ writel(0x10B0, led.sw_pad_gpio); /* 设置 GPIO1_IO03 为输出功能 */ val = readl(led.gpio_gdir); val &= ~(3 << 3); /* 清除以前的设置 */ val |= (1 << 3); /* 设置为输出 */ writel(val, led.gpio_gdir); /* 默认关闭 LED (高电平) */ val = readl(led.gpio_dr); val |= (1 << 3); writel(val, led.gpio_dr);到这里,我们已经完成了从设备树读取配置到初始化硬件的完整流程。注意这里的寄存器操作(readl()/writel())操作的是映射后的虚拟地址,而不是设备树里的物理地址。
第六步:资源释放
void led_hw_deinit(void) { pr_info("Deinit LED Hardware\n"); if (led.ccm_ccgr1) { iounmap(led.ccm_ccgr1); led.ccm_ccgr1 = NULL; } /* ... 其他 iounmap ... */ if (led.device_tree_node) { of_node_put(led.device_tree_node); led.device_tree_node = NULL; } }卸载驱动时,释放所有映射的地址和节点引用。注意这里我们把指针设为NULL,防止 double-free。
常见错误及处理方法
在实际使用OF API 时,有几个常见的坑需要特别注意。
错误 1:忘记检查返回值
几乎所有OF API都有返回值,你必须检查它们:
/* 错误示例 */ struct device_node *node = of_find_node_by_path("/some-node"); /* 直接用 node,没检查 NULL */ of_property_read_u32(node, "some-prop", &val); /* 正确示例 */ struct device_node *node = of_find_node_by_path("/some-node"); if (!node) { pr_err("node not found\n"); return -ENODEV; } ret = of_property_read_u32(node, "some-prop", &val); if (ret) { pr_err("property read failed: %d\n", ret); of_node_put(node); return ret; }错误 2:忘记释放引用
这是内存泄漏的常见原因:
/* 错误示例 */ struct device_node *node = of_find_node_by_path("/some-node"); /* 用完后没有调用 of_node_put() */ /* 正确示例 */ struct device_node *node = of_find_node_by_path("/some-node"); /* ... 使用 node ... */ of_node_put(node);错误 3:数组长度不匹配
用of_property_read_u32_array()时,确保你分配的数组足够大:
/* 危险示例 */ u32 data[5]; of_property_read_u32_array(node, "reg", data, 10); /* 数组越界! */ /* 安全示例 */ int count = of_property_count_elems_of_size(node, "reg", sizeof(u32)); u32 *data = kmalloc(count * sizeof(u32), GFP_KERNEL); if (!data) return -ENOMEM; of_property_read_u32_array(node, "reg", data, count); /* ... 用完后 ... */ kfree(data);错误 4:重复映射
不要对同一个地址调用多次of_iomap():
/* 错误示例 */ void __iomem *addr1 = of_iomap(node, 0); void __iomem *addr2 = of_iomap(node, 0); /* 重复映射! */ /* 正确做法 */ void __iomem *addr = of_iomap(node, 0); /* 后续直接用 addr */更多内容请看下回。