当前位置: 首页 > news >正文

嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(6)

接前一篇文章:嵌入式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 */

更多内容请看下回。

http://www.jsqmd.com/news/995108/

相关文章:

  • 基于multisim的温度测量与控制电路设计
  • MPC8343EA时钟与热管理设计:从PLL配置到散热器选型实战
  • 终极M3U8视频下载指南:如何快速下载和合并HLS流媒体视频
  • 告别鼠标手!用这些Altium Designer 20隐藏快捷键,把你的PCB设计速度提上来
  • MC9S12NE64单芯片以太网微控制器:从硬件设计到低功耗网络节点开发实战
  • 动手实现‘诚实但好奇’云环境下的安全最近邻搜索(Python示例)
  • 【趣解】Tomcat、Nginx、Redis:中间件界的“三剑客“
  • 2026上海GEO优化公司推荐榜:基于真实客户回访数据的深度选型指南 - 资讯纵览
  • 如何实现个性化定制:Mi-Create 为小米穿戴设备打造专属表盘的完整指南
  • 保姆级教程:用Node.js复现拼多多anti_content加密(附完整可运行代码)
  • Figma中文界面汉化插件:5分钟告别英文设计障碍
  • 2026年重庆市场知名小程序开发公司,哪家才是可靠之选? - 资讯纵览
  • 实战指南:5个核心场景深度解析League Toolkit如何提升你的英雄联盟游戏体验
  • 用STC89C52+DS1302+LCD1602做个桌面电子钟,附串口调试和闹钟设置完整代码
  • 云函数平台兼容性探讨
  • OpenCore Legacy Patcher完整指南:4步解决老旧Mac升级难题
  • 终极暗黑3按键助手:D3KeyHelper免费开源工具完整使用指南
  • okbiye 论文降重降 AIGC:双维度优化破解高校双重检测关卡
  • 实测AI教材写作工具,低查重快速生成,满足多样化教学需求
  • 给你的Modbus TCP通信加个‘监听器’:深入玩转modbus_tk的Hook函数
  • 3分钟搞定跨平台表情符号:Noto Emoji终极解决方案
  • 2026新加坡靠谱高中办学排行 附适配/避坑指南 - 互联网科技品牌测评
  • 金蝶云K3/Cloud C#控制台调用模板:含配置文件、认证与单据增查改完整示例
  • 2026 海南注册公司全指南:税收优惠 | 政策流程 | 费用明细 | 代办避坑及本土机构 TOP6 - 资讯纵览
  • SpringBoot项目集成海康威视SDK踩坑记:从获取通道号到RTSP地址拼接的完整流程
  • AI 生成代码质量评估实战指南
  • 全球城市与一级行政区中英文名称及三字母代码XML数据集(含双语映射)
  • 2026年万字论文AI写作软件测评:5款工具长篇支持对比 - 掌桥科研-AI论文写作
  • 超越默认值:如何根据你的计算体系(金属/半导体/绝缘体)微调VASP的INCAR参数?
  • 当代码跑得比测试快,QA 团队如何反超