3. 内核源码结构:GPIO子系统核心源码目录与关键数据结构
GPIO 子系统的内核源码位于 drivers/gpio/ 目录下,是 Linux 通用 GPIO 框架的核心实现。理解该目录的组织结构以及两个关键数据结构——gpio_chip 和 gpio_desc——是掌握 GPIO 驱动开发的基础。
3.1 核心源码目录结构
drivers/gpio/ 目录按功能模块划分,主要包含以下文件与子目录:
| 文件/目录 | 作用 | 说明 |
|---|---|---|
gpiolib.c |
GPIO 核心库 | 实现 GPIO 号分配、gpio_chip 注册/注销、sysfs 接口等核心逻辑 |
gpiolib-of.c |
设备树支持 | 解析设备树中 GPIO 控制器节点,完成 gpio_chip 与设备树绑定 |
gpiolib-acpi.c |
ACPI 支持 | 在 x86 平台通过 ACPI 表获取 GPIO 控制器信息 |
gpiolib-sysfs.c |
用户空间接口 | 提供 /sys/class/gpio/ 下的操作文件(已逐步被 chardev 替代) |
gpiolib-cdev.c |
字符设备接口 | 实现 /dev/gpiochipN 字符设备,支持 ioctl 操作(推荐方式) |
gpio-mmio.c |
通用 MMIO 驱动 | 为内存映射型 GPIO 控制器提供通用实现,减少重复代码 |
gpio-xxx.c |
具体芯片驱动 | 如 gpio-omap.c、gpio-rockchip.c,每个文件对应一个 SoC 或外设 |
学习建议: 初学者应优先阅读
gpiolib.c 和 gpiolib-cdev.c,这两个文件涵盖了 GPIO 子系统的核心注册流程和用户空间交互机制。
3.2 关键数据结构概览
GPIO 子系统围绕两个核心数据结构构建:gpio_chip 描述硬件控制器,gpio_desc 描述单个引脚。两者通过 gpio_device 桥接,形成完整的引脚管理链路。
3.2.1 gpio_chip:硬件控制器抽象
gpio_chip 是驱动开发者最常接触的结构体,定义在 include/linux/gpio/driver.h 中。它代表一个 GPIO 控制器实例,包含操作引脚所需的所有方法和属性。
struct gpio_chip {
const char *label; // 控制器名称
struct gpio_device *gpiodev; // 指向关联的 gpio_device
struct device *parent; // 父设备(通常是 platform_device)
struct module *owner; // 所属模块
int base; // 起始 GPIO 号(-1 表示动态分配)
u16 ngpio; // 本控制器管理的引脚数量
const char *const *names; // 引脚名称数组(可选)
// ---- 核心操作回调 ----
int (*request)(struct gpio_chip *gc, unsigned int offset);
void (*free)(struct gpio_chip *gc, unsigned int offset);
int (*direction_input)(struct gpio_chip *gc, unsigned int offset);
int (*direction_output)(struct gpio_chip *gc, unsigned int offset, int value);
int (*get)(struct gpio_chip *gc, unsigned int offset);
void (*set)(struct gpio_chip *gc, unsigned int offset, int value);
// ---- 中断相关 ----
int (*to_irq)(struct gpio_chip *gc, unsigned int offset);
void (*irq_handler)(struct gpio_chip *gc, unsigned int offset);
int (*irq_type)(struct gpio_chip *gc, unsigned int offset, unsigned int type);
// ---- 其他 ----
unsigned long *valid_mask; // 有效引脚掩码
bool can_sleep; // 访问是否需要睡眠
int irq; // 控制器中断号
};
关键字段解读:
base和ngpio:定义该控制器在全局 GPIO 空间中的起始编号和引脚数量。建议将base设为 -1,由内核动态分配,避免冲突。direction_input/direction_output:必须实现,否则引脚无法切换方向。get/set:读写引脚电平,是驱动最底层的硬件操作。to_irq:将引脚偏移量映射为 Linux 中断号,是实现 GPIO 中断的关键。
3.2.2 gpio_desc:引脚描述符
gpio_desc 是内核内部对单个 GPIO 引脚的描述,定义在 include/linux/gpio/consumer.h 中。驱动开发者通过 gpiod_ 系列 API 操作的是 gpio_desc,而非直接操作 gpio_chip。
struct gpio_desc {
struct gpio_device *gdev; // 所属的 gpio_device
unsigned long flags; // 标志位(方向、已申请、中断等)
const char *label; // 使用者标签(如 "led0")
int hwnum; // 在控制器内的偏移量
struct list_head list; // 链表节点
};
设计意图:
gpio_desc 将引脚的使用信息(如方向、是否已申请、中断状态)与硬件操作分离。驱动开发者只需持有 gpio_desc 指针,即可通过 gpiod_set_value()、gpiod_direction_output() 等 API 操作引脚,无需关心底层 gpio_chip 的细节。
3.3 数据结构关系图
下图展示了 gpio_chip、gpio_device 和 gpio_desc 之间的层次关系:
3.4 注册与使用流程
驱动开发者编写 GPIO 控制器驱动时,通常遵循以下步骤:
- 分配并初始化
gpio_chip:设置base、ngpio、label,并实现direction_input、direction_output、get、set等回调。 - 调用
gpiochip_add_data():将gpio_chip注册到内核 GPIO 框架,内核自动创建对应的gpio_device和gpio_desc数组。 - 用户空间或内核其他模块:通过
gpiod_get()获取gpio_desc,再调用gpiod_set_value()、gpiod_direction_output()等 API 操作引脚。
注意事项:
- 注册前必须确保
gpio_chip的owner字段设置为THIS_MODULE,防止模块被意外卸载。 - 如果控制器支持中断,必须实现
to_irq回调,否则无法将引脚用作中断源。 - 在设备树匹配的驱动中,通常通过
devm_gpiochip_add_data()注册,该函数会自动处理资源释放。