接前一篇文章嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API5实战示例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 */更多内容请看下回。
嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(6)
接前一篇文章嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API5实战示例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 */更多内容请看下回。