news 2026/5/1 9:20:45

Linux内核驱动——设备树原理与应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核驱动——设备树原理与应用

目录

一、设备树介绍

1.1 设备树基础概念

1.2 设备树相关文件

1.3 编译命令

二、设备树节点结构分析

三、引脚控制配置

四、Linux 内核驱动设计

4.1 驱动框架选择

4.1.1 传统字符设备驱动

4.1.2 基于 Platform 的标准驱动

4.2 GPIO 子系统的使用

五、驱动与设备树的匹配机制

5.1 匹配条件

5.2 备选匹配方式

六、完整驱动流程

七、总结


一、设备树介绍

1.1 设备树基础概念

设备树(Device Tree)是 Linux 内核引入的一种描述硬件结构的数据结构,主要用于嵌入式系统中。它将硬件描述与内核代码分离,使得内核可以更灵活地支持不同硬件平台。

Linux设备树

设备树的特点:

  • 以 “树状” 结构描述硬件资源。
    • 例如本地总线为树的 “主干” 在设备树里面称为 “根节点”, 挂载到本地总线的 IIC 总线、SPI 总线、UART 总线为树的 “枝干” ,在设备树里称为 “根节点的子节点”, IIC 总线下的IIC 设备不止一个,这些 “枝干” 又可以再分。
  • 设备树可以像头文件(.h文件)那样,一个设备树文件引用另外一个设备树文件, 这样可以实现 “代码” 的重用。
    • 例如多个硬件平台都使用 IMX6ULL 作为主控芯片, 那么我们可以将 IMX6ULL 芯片的硬件资源写到一个单独的设备树文件里面,一般使用 “.dtsi” 后缀, 其他设备树文件直接使用 “#include xxx.dtsi” 引用即可。

1.2 设备树相关文件

  • .dts:设备树源文件(Device Tree Source),是人类可读的文本格式。
  • .dtb:设备树二进制文件(Device Tree Blob),由 .dts 编译而来,供内核解析使用。

1.3 编译命令

make dtbs # 编译所有设备树 make xxx.dtb # 编译特定的xxx.dts文件

二、设备树节点结构分析

以 pt.dts 中的 LED 节点为例:

ptled { #address-cells = <1>; #size-cells = <1>; compatible = "pt-led"; name1 = "led"; status = "okay"; reg = <0x020E0068 0x04 0x020E02F4 0x04 0x0209C004 0x04 0x0209C000 0x04>; }; ptled_sub { #address-cells = <1>; #size-cells = <1>; compatible = "pt-led-sub"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ptled>; ptled-gpio = <&gpio1 3 GPIO_ACTIVE_HIGH>; status = "okay"; };

关键属性说明:

属性作用
#address-cells地址的单元数量
#size-cells大小的单元数量
compatible驱动匹配的关键字段,必须与驱动中的 of_match_table 匹配
pinctrl-*描述引脚复用配置,指向具体的 pinmux 配置节点
gpio-*定义 GPIO 引脚信息,包括控制器和编号
status表示设备状态,"okay" 表示启用,"disabled" 表示禁用
reg设备寄存器地址和大小

三、引脚控制配置

在设备树中,引脚功能通过 pinctrl 节点定义:

pinctrl_ptled: ptledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* pt led */ >; };
  • MX6UL_PAD_GPIO1_IO03__GPIO1_IO03:表示将该引脚配置为普通 GPIO 输出模式。
  • 0x10B0:寄存器配置值,决定电平、上拉等特性。

四、Linux 内核驱动设计

4.1 驱动框架选择

根据是否使用平台总线,分为两种方式:

4.1.1 传统字符设备驱动

这种方式直接从设备树中获取寄存器地址,然后通过 ioremap 映射到内核空间,最后直接操作寄存器。

优点

  • 实现简单直接
  • 不需要注册 platform 设备

缺点

  • 代码耦合度高
  • 不符合 Linux 内核的驱动模型规范
  • 难以进行设备的热插拔管理

关键代码分析

static int __init led_init(void) { struct device_node * pdts; const char * pcom = NULL; const char * pname = NULL; int i = 0; unsigned int led_array[8] = {0}; int ret = misc_register(&misc_dev); if(ret) goto err_misc_register; // 查找设备树节点 pdts = of_find_node_by_path("/ptled"); if(NULL == pdts) { ret = PTR_ERR(pdts); goto err_find_node; } ret = of_property_read_string(pdts, "compatible", &pcom); if(ret < 0) goto err_of_property_read; printk("led pcom = %s\n", pcom); ret = of_property_read_string(pdts, "name1", &pname); if(ret < 0) goto err_of_property_read; printk("led pname = %s\n", pname); // 读取寄存器地址数组 ret = of_property_read_u32_array(pdts, "reg", led_array, sizeof(led_array) / sizeof(led_array[0])); if(ret < 0) goto err_of_property_read; for(i = 0; i < sizeof(led_array) / sizeof(led_array[0]); i+=2) { printk("0x%x\t0x%x\n", led_array[i], led_array[i + 1]); } // 映射寄存器到内核空间 iomuxc_mux_ctl = ioremap(led_array[0], led_array[1]); iomuxc_pad_ctl = ioremap(led_array[2], led_array[3]); gpio1_gdir = ioremap(led_array[4], led_array[5]); gpio1_dr = ioremap(led_array[6], led_array[7]); printk("######################### misc led_init\n"); return 0; err_of_property_read: printk("ptled of_property_read failed\n"); err_find_node: printk("ptled find node failed\n"); err_misc_register: printk("misc led_init failed ret = %d\n", ret); misc_deregister(&misc_dev); return ret; }

这种方式直接读取设备树中定义的寄存器地址,然后通过 ioremap 映射到内核空间,最后直接操作寄存器。

4.1.2 基于 Platform 的标准驱动

这种方式使用了 Linux 内核的 platform 驱动模型,更加规范和灵活。

优点

  • 符合 Linux 内核驱动模型规范
  • 便于设备的热插拔管理
  • 代码结构清晰,易于维护
  • 可以利用内核提供的 GPIO 子系统,无需直接操作寄存器

缺点

  • 实现相对复杂
  • 需要了解 platform 驱动模型

关键代码分析

static int probe(struct platform_device * pdev) { struct device_node * pdts; int ret = misc_register(&misc_dev); if(ret) goto err_misc_register; // 查找设备树节点 pdts = of_find_node_by_path("/ptled_sub"); if(NULL == pdts) { ret = PTR_ERR(pdts); goto err_of_find; } // 通过设备树获取GPIO编号 led_gpio = of_get_named_gpio(pdts, "ptled-gpio", 0); if(led_gpio < 0) { ret = led_gpio; goto err_of_find; } // 申请GPIO并设置为输出 ret = gpio_request(led_gpio, "led"); if(ret < 0) goto err_gpio_request; gpio_direction_output(led_gpio, LED_OFF); printk("######################### led_driver probe\n"); return 0; err_gpio_request: printk("######################### led_driver gpio_request\n"); err_of_find: printk("######################### led_driver find node failed\n"); err_misc_register: misc_deregister(&misc_dev); printk("######################### led_driver misc register ret = %d\n", ret); return ret; }

这种方式通过 of_get_named_gpio 从设备树中获取 GPIO 编号,然后使用 GPIO 子系统提供的函数进行操作,无需直接操作寄存器。

4.2 GPIO 子系统的使用

在 platform 驱动模型中,可以使用 GPIO 子系统来操作 GPIO,而不需要直接操作寄存器。

gpio_request(); // 请求 GPIO gpio_direction_output(); // 设置输出方向 gpio_set_value(); // 写入电平 gpio_get_value(); // 读取电平 gpio_free(); // 释放资源

所有 GPIO 操作都应通过这些 API 进行,避免直接操作寄存器。

通过设备树获取GPIO:

led_gpio = of_get_named_gpio(pdts, "ptled-gpio", 0);

这个函数从设备树节点中获取 GPIO 编号,参数 "ptled-gpio" 对应设备树中的属性名。

五、驱动与设备树的匹配机制

5.1 匹配条件

驱动与设备树节点的绑定依赖于 compatible 字段:

static const struct of_device_id led_table[] = { {.compatible = "pt-led-sub"}, {} }; static struct platform_driver pdrv = { .probe = probe, .remove = remove, .driver = { .name = DEV_NAME, .of_match_table = led_table, }, };

当内核加载时,会遍历所有设备树节点,查找 compatible = "pt-led-sub" 的节点,并调用对应的 probe() 函数。

5.2 备选匹配方式

如果没有 compatible 属性,也可以使用 name 字段进行匹配:

if (of_property_read_string(pdt, "name1", &pname) == 0 && strcmp(pname, "led") == 0)

但这种方式不推荐,因为缺乏标准化且灵活性差。

六、完整驱动流程

  1. 编写设备树(dts/xx.dts)
    1. 定义外设节点
    2. 设置 compatible, gpio, pinctrl
  2. 在 dts/Makefile 中新增 xx.dtb
  3. 使用 make dtbs 指令编译为 xx.dtb 文件
  4. 将 xx.dtb 文件放置在 tftpboot 文件夹下
  5. 编写驱动程序(char/xxx.c)
    1. 使用 platform_driver 或 misc_device
    2. 通过 of_* 系列函数获取设备树信息
    3. 调用 GPIO API 控制硬件
    4. 使用 platform_driver_register 注册驱动
  6. 在 char/Makefile 中新增 xxx.o
  7. 在 char/Kconfig 中新增 config XXX ...
    • 注意将模块类型设置为 tristate
    • 将名称设置为 "This is my xxx!"
  8. 使用 make menuconfig 指令配置内核
    1. 取消选择自己添加的其余程序
    2. 将新增的 xxx 设置为 M,表示允许选择编译为模块
  9. 使用 make modules 指令编译生成 xxx.ko 文件
  10. 将 xxx.ko 文件放置在根目录 rootfs 下
  11. 在根目录下编写应用程序(rootfs/xxxx.c)
  12. 使用 arm-linux-gnueabihf-gcc xxxx.c -o xxxx 指令编译为可执行程序
  13. 启动 ARM 开发板,输入 setenv bootargs ... 命令
  14. 输入 tftp 0x80800000 zImage 和 tftp 0x83000000 xx.dtb 命令
  15. 输入 bootz 0x80800000 - 0x83000000 命令完成启动
  16. 输入 insmod xxx.ko 命令加载模块
  17. 输入 ./xxxx 命令执行应用程序

七、总结

设备树是 Linux 内核中描述硬件的重要机制,它将硬件描述与内核代码分离,提高了内核的可移植性和可维护性。在开发设备驱动时,应该优先使用 platform 驱动模型和 GPIO 子系统,而不是直接操作寄存器。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 11:40:20

IT Hit WebDAV Server Engine for .NET 15.4.16

IT Hit WebDAV Server Engine for .NET让您轻松地为 .NET 项目添加 WebDAV 功能。无论您是构建独立的 WebDAV 服务器&#xff0c;还是将 WebDAV 功能集成到 CMS、DMS 或 CRM 系统中&#xff0c;这款引擎都能帮您轻松搞定。您无需精通 WebDAV 协议或 XML——所有操作都通过直观的…

作者头像 李华
网站建设 2026/4/28 8:31:47

写论文省心了 8个AI论文工具测评:继续教育毕业论文写作全攻略

随着人工智能技术的不断进步&#xff0c;学术写作正迎来前所未有的变革。对于继续教育领域的学员而言&#xff0c;撰写毕业论文不仅是学业的重要环节&#xff0c;更是对知识整合与研究能力的全面检验。然而&#xff0c;从选题构思到文献综述、从大纲搭建到成文修改&#xff0c;…

作者头像 李华
网站建设 2026/5/1 1:39:24

免费vs付费AIGC工具:10款主流选项性能对比

&#xfffd;&#xfffd; 10大降AIGC平台核心对比速览 排名 工具名称 降AIGC效率 适用场景 免费/付费 1 askpaper ⭐⭐⭐⭐⭐ 学术论文精准降AI 付费 2 秒篇 ⭐⭐⭐⭐⭐ 快速降AIGC降重 付费 3 Aibiye ⭐⭐⭐⭐ 多学科论文降AI 付费 4 Aicheck ⭐⭐⭐⭐…

作者头像 李华
网站建设 2026/5/1 7:24:08

发展融、民生暖:首都都市圈协同规划的幸福密码

“这么近、那么美&#xff0c;周末到河北”&#xff0c;这句朗朗上口的旅游口号&#xff0c;正是京津冀老百姓的真实生活写照。 从北京出发&#xff0c;半小时去天津吃狗不理包子&#xff0c;1小时去张家口滑雪&#xff0c;1.5小时到承德避暑山庄&#xff0c;2小时奔赴秦皇岛看…

作者头像 李华
网站建设 2026/4/17 10:14:52

特殊函数(贝塞尔函数....)

要贯穿整个特殊函数的发展史&#xff0c;最好的例子莫过于**“振动”**这一物理现象。 我们可以通过圆鼓皮的振动&#xff08;或地震点源激发的波&#xff09;这一主线&#xff0c;看人类是如何从“解决一个具体麻烦”演进到“建立一套宇宙对称性语言”的。 第一阶段&#xf…

作者头像 李华
网站建设 2026/5/1 5:07:13

【Linux】du 命令查看文件和目录的磁盘占用

du 命令详解&#xff1a;查看文件和目录的磁盘占用 du&#xff08;disk usage&#xff09;是 Linux 中最常用的查看磁盘空间占用情况的命令之一。它可以统计文件、目录甚至整个文件系统的实际占用空间&#xff0c;非常适合排查“磁盘满了”“哪个目录吃空间”等场景。 下面从…

作者头像 李华