一、Linux 驱动底层核心思想
1. 面向对象:万物皆对象,完成从抽象到具体的描述。
2. 设备与驱动分离:硬件信息放在设备树,驱动代码独立。内核运行时由设备树向驱动传递硬件资源,降低移植难度,减少重复代码编写。
3. 驱动分层思想:内核采用面向对象设计,同类设备定义基类;子类可继承基类函数,也可重写覆盖同名函数,依靠多态提升代码复用。内核拆分为多个独立子系统协同工作,pinctrl、GPIO、I2C、SPI 等子系统各司其职。
二、pinctrl 子系统
1. 基本概念
- Pin controller devices(引脚控制器):对应 IOMUX,管理引脚复用功能、电气特性配置,寄存器信息由芯片厂商预先写好。
- Pinctrl client devices:pinctrl 的客户端,即使用 pinctrl 引脚配置的外设设备。
作用:在设备树中配置引脚,无需手动读写 IOMUX 寄存器。
2. 设备树 DTS 编写(i.MX6ULL)
(1)iomux 控制器节点iomuxc@020e0000,compatible 属性fsl,imx6ul-iomuxc,对应内核源码drivers/pinctrl/freescale/pinctrl-imx6ul.c,模块通过arch_initcall初始化。
(2)自定义引脚组:使用标签分组,如pinctrl_led;fsl,pins存放引脚宏与电气参数,引脚宏定义在imx6ul-pinfunc.h。每个引脚条目格式为<mux_reg conf_reg input_reg mux_mode input_val>,分别对应复用寄存器偏移、配置寄存器偏移、输入寄存器偏移、复用模式和输入值。
(3)外设节点引用:
pinctrl-names:引脚状态列表,默认default,还可定义sleep、idle等状态;pinctrl-0:引用预先定义的引脚配置,对应 default 状态;pinctrl-1:可引用另一组引脚配置,对应 sleep 等状态;status:okay启用设备,disabled禁用设备。
(4)参考文档路径
内核中 pinctrl 设备树绑定规范文档:
Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt(飞思卡尔 i.MX 系列 pinctrl 绑定说明)Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt(通用 pinctrl 设备树绑定规范)
示例:
/* 引脚分组节点:定义LED引脚复用与电气属性 */ pinctrl_led: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0,电气特性的配置 */ >; }; /* LED外设设备节点:引用上面pinctrl引脚配置 */ gpioleds { #address-cells = <1>; #size-cells = <1>; compatible = "pute-gpioleds"; pinctrl-names = "default"; // 定义引脚的状态名称列表,引脚默认状态 pinctrl-0 = <&pinctrl_led>; // 引用pinctrl_led引脚配置节点 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; // 自定义属性,低电平有效 status = "okay"; // okay启用、disabled禁用、reserved保留 };3. pinctrl 开发流程
(1)在 iomuxc 节点内添加引脚分组,配置引脚复用、电气属性;
(2)在设备节点引用 pinctrl 配置;
(3)编译生成 dtb 设备树二进制文件,下载至开发板;
(4)修改驱动代码,删除手动 ioremap 映射 IOMUX 寄存器的旧代码;
(5)调试验证:查看/sys/firmware/devicetree确认设备树节点加载,加载驱动模块后查看 sysfs 属性,上层应用测试。
三、GPIO 子系统
1. 设备树 DTS 编写
(1)GPIO 控制器节点,compatible 属性匹配芯片 GPIO 驱动,包含寄存器基地址、中断相关属性,添加gpio-controller标识。
(2)外设节点通过gpios属性指定使用的 GPIO 编号与有效电平,GPIO_ACTIVE_LOW代表低电平有效。
2. GPIO 常用内核 API
| 函数 | 功能 | 返回值说明 |
|---|---|---|
| of_get_named_gpio | 从设备树节点解析获取 GPIO 编号 | 成功返回 GPIO 号,失败返回负数 |
| gpio_request | 申请 GPIO 硬件资源 | 成功返回 0,失败负数 (常见 - EBUSY) |
| devm_gpio_request | 申请 GPIO 资源,设备卸载自动释放资源 | 成功 0,失败负数 |
| gpio_free | 手动释放 GPIO 资源 | 无返回 |
| gpio_direction_output | 设置 GPIO 为输出模式 | 成功 0,失败负数 |
| gpio_direction_input | 设置 GPIO 为输入模式 | 成功 0,失败负数 |
| gpio_set_value | 设置 GPIO 输出高低电平 | 无返回 |
| gpio_get_value | 读取 GPIO 输入引脚电平 | 0 低电平,1 高电平 |
| gpio_to_irq | 由 GPIO 编号获取对应的中断号 | 成功返回中断号,失败负数 |
说明:
devm_gpio_request是gpio_request的资源管理版本,驱动卸载或 probe 失败时内核自动释放,无需手动调用gpio_free,推荐在 probe 函数中使用。此外,新内核推荐使用gpiod_系列 API(基于描述符),如devm_gpiod_get、gpiod_set_value,与设备树gpios属性配合更安全。
3. GPIO 开发步骤
- 删除旧驱动中 ioremap 映射 GPIO 寄存器、手动配置 PDIR 等寄存器代码;
- 使用
of_get_named_gpio解析设备树拿到 GPIO 编号; - 调用
gpio_request申请 GPIO 资源; - 设置 GPIO 输入 / 输出方向;输出可设置默认电平,输入可读取引脚状态;
- 使用完毕调用
gpio_free释放 GPIO 资源。
4. pinctrl 与 GPIO 子系统关系
- pinctrl:第一步,配置引脚复用功能,把引脚设置为 GPIO 功能,配置引脚电气属性。
- GPIO 子系统:第二步,引脚已经复用为 GPIO 后,控制引脚输入输出、读写高低电平。
顺序:pinctrl 配置引脚功能 → GPIO 子系统操作引脚。
四、非阻塞 IO(按键驱动案例)
1. 阻塞 IO 与非阻塞 IO 概念
- 阻塞 IO:应用调用 read 等函数,若无数据,进程休眠等待,直到有数据返回后唤醒。
- 非阻塞 IO:调用 read 时,如果没有数据,驱动不休眠,立刻返回错误,应用程序不会挂起。应用需要循环轮询,持续查询设备状态。
2. 非阻塞 IO 驱动开发要点
- 应用打开设备时,传入
O_NONBLOCK标记,驱动通过file->f_flags识别该标志。 - read 函数内判断:如果没有按键数据,并且是非阻塞模式,直接返回 - EAGAIN,不进入休眠。
- 阻塞模式下,无数据时调用
wait_event_interruptible进入等待队列休眠。 - 按键中断触发,采集按键状态,唤醒等待队列,应用读取按键值。
- read 返回时,使用
copy_to_user将按键数据拷贝到用户空间,并检查返回值,若拷贝失败返回 - EFAULT。
注意:非阻塞 IO 需要应用层不断轮询,会占用 CPU 资源,适合简单场景。若需更高效的事件通知机制,可配合
poll/epoll接口使用,驱动实现poll操作函数,在等待队列上注册,应用通过select/epoll等待可读事件,避免忙轮询。
3. 开发整体流程(按键驱动)
- 设备树:添加 pinctrl 引脚配置,在按键节点定义 gpios 属性;
- 驱动代码:通过 of 接口解析设备树,申请 GPIO 资源,配置引脚为输入;
- 注册 GPIO 中断,按键触发中断,记录按键状态;
- 在 read 接口判断
O_NONBLOCK标志,区分阻塞 / 非阻塞模式; - 应用 open 时传入 O_NONBLOCK,循环 read 轮询读取按键。
五、整体总结
基于设备树开发驱动,利用 pinctrl 完成引脚复用配置,再通过 GPIO 子系统操作引脚。pinctrl、GPIO 子系统依托 Linux 驱动分层、设备驱动分离思想,将硬件描述放到设备树,驱动只处理业务逻辑。在按键这类输入设备中,可结合非阻塞 IO,应用轮询查询设备状态,无数据时驱动不阻塞进程,实现非阻塞读取。
三个子系统协作关系清晰:pinctrl 负责引脚复用与电气属性配置,GPIO 子系统负责引脚电平读写,非阻塞 IO 机制负责应用与驱动之间的高效数据交互。理解这一链路,是掌握 Linux 字符设备驱动开发的关键基础。