news 2026/4/18 3:53:20

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/3/14 6:44:31

闭眼入!10个AI论文网站测评,专科生毕业论文写作必备工具推荐

面对日益繁重的学术任务&#xff0c;专科生在撰写毕业论文时常常面临选题困难、资料查找效率低、格式规范不熟悉等问题。为了帮助广大专科生高效完成论文写作&#xff0c;笔者基于2026年的实测数据与用户真实反馈&#xff0c;对市面上主流的AI论文工具进行了系统测评。本次测评…

作者头像 李华
网站建设 2026/4/15 18:20:24

Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示

Flutter for OpenHarmony 可视化教学&#xff1a;A* 寻路算法的交互式演示 在人工智能、游戏开发和机器人导航等领域&#xff0c;路径规划&#xff08;Pathfinding&#xff09; 是一项基础而关键的技术。其中&#xff0c;A*&#xff08;A-Star&#xff09;算法因其高效性与最优…

作者头像 李华
网站建设 2026/4/8 9:21:01

AI赋能论文写作:11款跨学科工具推荐

近年来&#xff0c;人工智能语言模型的飞速发展彻底改变了学术研究的格局。尤其是自 2022 年 11 月 OpenAI 发布 ChatGPT 以来&#xff0c;AI 工具在学术界的应用日益广泛&#xff0c;帮助科研人员节省时间、提高效率&#xff0c;从而专注于更有价值的任务。AI 学术工具本质上是…

作者头像 李华
网站建设 2026/3/17 7:01:47

php python+vue员工签到管理系统毕设开题报告

目录系统概述技术选型功能模块创新点应用场景开发计划项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作系统概述 员工签到管理系统基于PHP、Python和Vue.js技术栈开发&#xff0c;实现员工考勤、签到记录、…

作者头像 李华
网站建设 2026/4/16 20:15:48

php python+vue大学校排课管理信息系统设计开题报告

目录 校排课管理信息系统设计背景系统技术栈选择系统核心功能模块系统创新点预期成果技术实现示例&#xff08;关键代码片段&#xff09; 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 校排课管理信息系…

作者头像 李华
网站建设 2026/4/9 14:50:26

10大AIGC工具实测:免费版vs付费版功能差异

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

作者头像 李华