1. GPIO子系统概述
1.1 GPIO子系统在Linux内核中的角色
GPIO(General Purpose Input/Output)子系统是Linux内核中负责管理通用输入输出引脚的核心框架。它为内核驱动和用户空间程序提供了一套统一、抽象的API,用于控制SoC或外设芯片上的数字引脚。
GPIO子系统在内核中的核心角色包括:
- 引脚抽象:将物理引脚映射为逻辑编号,屏蔽不同SoC的寄存器差异。
- 方向控制:统一管理引脚的输入/输出方向切换。
- 电平读写:提供安全的读/写接口,避免竞态条件。
- 中断管理:将GPIO引脚变化映射为内核中断,支持边沿触发和电平触发。
- 用户空间访问:通过sysfs或chardev接口,允许用户态程序控制GPIO。
关键概念: GPIO子系统是"引脚功能"的最终使用者,它不直接操作引脚复用寄存器,而是通过Pin Control子系统完成引脚功能配置。
1.2 GPIO子系统与Pin Control子系统的关系
Pin Control子系统(pinctrl)是GPIO子系统的上游依赖。两者分工明确:
| 维度 | GPIO子系统 | Pin Control子系统 |
|---|---|---|
| 核心职责 | 引脚电平读写、方向控制、中断触发 | 引脚复用(mux)、电气特性(drive strength、pull-up/down) |
| 操作对象 | GPIO编号(逻辑抽象) | 引脚编号(物理bank + pin) |
| 配置时机 | 运行时动态切换 | 设备初始化时静态配置 |
| 依赖关系 | 依赖pinctrl完成引脚功能选择 | 不依赖GPIO子系统 |
| 用户接口 | sysfs、chardev、gpiod API | 设备树pinctrl-*属性、debugfs |
典型的配置流程是:Pin Control子系统先将引脚复用为GPIO功能,然后GPIO子系统接管该引脚的电平读写和中断管理。两者通过设备树中的pinctrl-0属性和gpio-controller标识建立关联。
实践要点: 在设备树中,一个引脚节点通常同时包含
pinctrl-0 = <&pinmux_gpio>和gpio-controller属性,前者由pinctrl解析,后者由GPIO子系统识别。
1.3 GPIO子系统架构图
下图展示了GPIO子系统在内核中的分层架构及其与Pin Control子系统的协作关系:
1.4 架构分层说明
GPIO子系统的分层设计遵循Linux内核的通用驱动模型,从上到下依次为:
- 用户空间接口层:提供sysfs和chardev两种访问方式。sysfs适用于简单场景,chardev(libgpiod)是推荐的新接口,支持批量操作和事件监听。
- 内核GPIO核心层(gpiolib):实现通用的GPIO管理逻辑,包括编号分配、请求/释放、方向控制、值读写、中断映射等。所有GPIO芯片驱动都注册到这一层。
- GPIO芯片驱动层:由具体SoC厂商实现,填充
gpio_chip结构体中的回调函数,操作硬件寄存器。 - GPIO中断控制器层:将GPIO引脚变化映射为内核中断,通常与GPIO芯片驱动紧密耦合,通过
irq_chip结构体实现。 - 硬件抽象层:直接操作SoC的GPIO寄存器组和中断控制器寄存器。
常见误区: GPIO子系统不负责引脚复用配置。如果设备树中未正确配置pinctrl,即使GPIO驱动加载成功,引脚也可能无法正常工作(例如输出无电平变化)。务必先确认pinctrl状态。
1.5 关键数据结构关系
GPIO子系统的核心数据结构及其关系如下表所示:
| 数据结构 | 所在层 | 主要字段/作用 |
|---|---|---|
gpio_chip |
芯片驱动 | base(起始编号)、ngpio(引脚数)、request/set/get/direction_input回调 |
gpio_desc |
核心层 | 描述一个GPIO引脚的描述符,包含flags、label、chip引用 |
irq_chip |
中断控制器 | irq_request_resources、irq_set_type、irq_mask、irq_unmask回调 |
gpio_device |
核心层 | 封装gpio_chip和chardev设备,管理用户空间访问 |
pinctrl_dev |
Pin Control | 引脚控制器设备,管理复用和电气配置 |
在驱动开发中,最常见的操作流程是:
// 1. 定义gpio_chip结构体
static struct gpio_chip my_gpio_chip = {
.label = "my-gpio",
.base = -1, // 动态分配
.ngpio = 32,
.request = my_gpio_request,
.free = my_gpio_free,
.direction_input = my_gpio_direction_input,
.direction_output = my_gpio_direction_output,
.get = my_gpio_get,
.set = my_gpio_set,
.to_irq = my_gpio_to_irq,
};
// 2. 注册到gpiolib
int ret = gpiochip_add_data(&my_gpio_chip, NULL);
if (ret) {
pr_err("Failed to add GPIO chip\n");
return ret;
}
// 3. 关联中断控制器(可选)
ret = gpiochip_irqchip_add(&my_gpio_chip, &my_irq_chip, 0,
handle_edge_irq, IRQ_TYPE_NONE);
if (ret) {
pr_err("Failed to add irq chip\n");
gpiochip_remove(&my_gpio_chip);
return ret;
}
开发建议: 新驱动应优先使用
gpiod API(基于描述符)而非旧的gpio_* API(基于整数编号)。描述符API支持设备树映射、引用计数和更安全的错误处理。