4. Linux GPIO 子系统框架:gpiolib 架构、gpio_chip 结构体、gpio_desc 描述符、sysfs 与字符设备接口

在全志 T113 平台上,GPIO 驱动并非直接操作寄存器,而是通过 Linux 内核标准的 GPIO 子系统(gpiolib) 进行管理。gpiolib 为内核空间和用户空间提供了统一的 GPIO 操作抽象层,屏蔽了不同 SoC 的寄存器差异。本章将深入剖析 gpiolib 的软件架构、核心数据结构以及用户空间访问接口。

核心要点: gpiolib 是 Linux 内核中管理 GPIO 的中间件,它将硬件控制器抽象为 gpio_chip,将每个引脚抽象为 gpio_desc,并提供 sysfs 和字符设备两种用户空间接口。

4.1 gpiolib 软件架构总览

gpiolib 采用分层设计,从上到下依次为:用户空间接口层、内核空间 API 层、gpiolib 核心层、芯片驱动层。每一层都依赖下一层提供的服务,并通过标准的数据结构进行交互。

gpiolib 软件架构分层图 用户空间接口层 sysfs (/sys/class/gpio) | 字符设备 (/dev/gpiochipX) 内核空间 API 层 gpio_request() / gpio_direction_output() / gpio_set_value() / gpio_to_irq() gpiolib 核心层 gpio_desc 管理 / 引脚号映射 / 中断映射 / 去抖处理 芯片驱动层 (gpio_chip) request() / free() / direction_input() / direction_output() / get() / set() / irq_handler() 说明:全志 T113 的 GPIO 驱动主要工作在芯片驱动层,实现 gpio_chip 回调函数。
架构理解: 驱动开发者的主要工作集中在最底层的 芯片驱动层,即实现 gpio_chip 结构体中的回调函数。上层的 API 和用户空间接口由内核 gpiolib 核心自动提供。

4.2 gpio_chip 结构体:GPIO 控制器的抽象

gpio_chip 是 gpiolib 中最核心的数据结构,用于描述一个 GPIO 控制器(例如全志 T113 的 PA、PB、PC 等端口组)。每个 GPIO 控制器对应一个 gpio_chip 实例。

成员名称 类型 描述
label const char * 控制器名称,如 "PA", "PB"
base int 该控制器管理的 GPIO 起始编号(全局唯一)
ngpio int 该控制器管理的 GPIO 引脚数量
parent struct device * 父设备指针,用于设备模型关联
request int (*)(struct gpio_chip *, unsigned offset) 请求引脚时调用,可做电源管理或冲突检测
free void (*)(struct gpio_chip *, unsigned offset) 释放引脚时调用
direction_input int (*)(struct gpio_chip *, unsigned offset) 设置引脚为输入方向
direction_output int (*)(struct gpio_chip *, unsigned offset, int value) 设置引脚为输出方向,并设置初始电平
get int (*)(struct gpio_chip *, unsigned offset) 读取引脚当前电平值
set void (*)(struct gpio_chip *, unsigned offset, int value) 设置引脚输出电平值
to_irq int (*)(struct gpio_chip *, unsigned offset) 将 GPIO 引脚映射为中断号
关键注意: 全志 T113 的 GPIO 驱动需要为每个端口组(PA/PB/PC/PD/PE/PF/PG/PH/PI)分别注册一个 gpio_chip 实例。每个实例的 base 值必须全局唯一且不重叠,通常由内核自动分配(传入 -1)。
gpio_chip 与硬件寄存器映射关系 gpio_chip (PA 端口) base = 0 ngpio = 22 label = "PA" direction_input() direction_output() get() / set() to_irq() 全志 T113 PA 端口硬件寄存器 PA_CFG0 (偏移 0x00) — 引脚功能配置 PA_CFG1 (偏移 0x04) — 引脚功能配置 PA_DAT (偏移 0x10) — 数据寄存器 PA_DRV0 (偏移 0x14) — 驱动能力配置 PA_PUL0 (偏移 0x1C) — 上拉/下拉配置 PA_INT_CFG (偏移 0x20) — 中断配置 PA_INT_CTL (偏移 0x24) — 中断控制

4.3 gpio_desc 描述符:GPIO 引脚的内核抽象

在 gpiolib 内部,每个 GPIO 引脚使用 gpio_desc 结构体描述。该结构体包含了引脚的状态、标志、标签以及所属的 gpio_chip 指针。内核通过 gpio_desc 实现对 GPIO 引脚的精细化管理。

struct gpio_desc {
    struct gpio_chip    *chip;      /* 所属的 gpio_chip */
    unsigned long        flags;     /* 引脚状态标志 */
    const char          *label;     /* 使用者标签 */
    int                  irq;       /* 关联的中断号 */
    /* ... 其他内部字段 */
};

关键标志位(flags)包括:

  • FLAG_REQUESTED — 引脚已被请求
  • FLAG_IS_OUT — 引脚配置为输出方向
  • FLAG_ACTIVE_LOW — 引脚逻辑电平反转
  • FLAG_OPEN_DRAIN — 开漏输出
  • FLAG_OPEN_SOURCE — 开源输出
  • FLAG_USED_AS_IRQ — 引脚被用作中断源
调试技巧: 在 /sys/kernel/debug/gpio 中可以查看所有 gpio_desc 的实时状态,包括是否被请求、方向、当前电平值等,是调试 GPIO 冲突的利器。

4.4 sysfs 接口:传统的用户空间 GPIO 访问方式

sysfs 接口通过 /sys/class/gpio 目录暴露 GPIO 操作,适合简单的控制场景。虽然在新内核中逐渐被字符设备接口取代,但在嵌入式开发中仍广泛使用。

sysfs 路径 操作 说明
/sys/class/gpio/export 写入 GPIO 编号 导出 GPIO 到用户空间
/sys/class/gpio/unexport 写入 GPIO 编号 取消导出 GPIO
/sys/class/gpio/gpioN/direction 读/写 读取或设置方向(in/out/high/low)
/sys/class/gpio/gpioN/value 读/写 读取或设置电平值(0/1)
/sys/class/gpio/gpioN/edge 读/写 设置中断触发边沿(none/rising/falling/both)
/sys/class/gpio/gpioN/active_low 读/写 设置逻辑电平反转(0/1)

使用示例(操作全志 T113 的 PA0,假设 GPIO 编号为 0):

# 导出 GPIO0
echo 0 > /sys/class/gpio/export

# 设置为输出方向,初始高电平
echo high > /sys/class/gpio/gpio0/direction

# 输出低电平
echo 0 > /sys/class/gpio/gpio0/value

# 设置为输入方向
echo in > /sys/class/gpio/gpio0/direction

# 读取电平
cat /sys/class/gpio/gpio0/value

# 设置上升沿中断
echo rising > /sys/class/gpio/gpio0/edge

# 取消导出
echo 0 > /sys/class/gpio/unexport
注意事项: sysfs 接口每次操作都需要经过文件系统层,存在较大的延迟(通常 10-100 μs),不适合高频翻转场景。对于高速 GPIO 翻转(如 SPI 模拟、PWM 模拟),应使用字符设备接口或内核空间操作。

4.5 字符设备接口:新一代用户空间 GPIO 访问方式

从 Linux 4.8 开始,内核引入了基于字符设备的 GPIO 用户空间接口,通过 /dev/gpiochipX 设备文件提供更高效、更灵活的 GPIO 操作。该接口支持批量操作、事件监听和更精细的控制。

字符设备接口的核心数据结构:

struct gpiohandle_request {
    unsigned int lineoffsets[GPIOHANDLES_MAX];  /* 引脚偏移数组 */
    unsigned int flags;                          /* 操作标志 */
    unsigned int default_values[GPIOHANDLES_MAX];/* 默认输出值 */
    char consumer_label[32];                     /* 使用者标签 */
    unsigned int lines;                          /* 操作引脚数量 */
    int fd;                                      /* 返回的文件描述符 */
};

struct gpioevent_request {
    unsigned int lineoffset;                     /* 引脚偏移 */
    unsigned int handleflags;                    /* 句柄标志 */
    unsigned int eventflags;                     /* 事件标志 */
    char consumer_label[32];                     /* 使用者标签 */
    int fd;                                      /* 返回的事件文件描述符 */
};

字符设备接口的优势:

  • 低延迟: 通过 ioctl 系统调用,减少文件系统开销
  • 批量操作: 一次 ioctl 可同时操作多个 GPIO 引脚
  • 事件驱动: 支持 epoll/select 监听 GPIO 中断事件
  • 更安全: 支持细粒度的权限控制
sysfs 与字符设备接口对比 sysfs 接口 1. 用户空间写入 /sys/class/gpio/export 2. 内核 gpiolib 调用 gpio_request() 3. 操作 direction/value 文件 4. 内核调用 gpio_direction_*() / gpio_get/set() 5. 最终调用 gpio_chip 回调函数 延迟较高,适合低频控制 字符设备接口 1. 用户空间 open("/dev/gpiochip0") 2. ioctl(GPIO_GET_LINEHANDLE_IOCTL) 3. 内核 gpiolib 直接操作 gpio_desc 4. ioctl(GPIOHANDLE_SET_LINE_VALUES_IOCTL) 5. 直接调用 gpio_chip 回调函数 延迟低,支持批量与事件监听

4.6 全志 T113 GPIO 编号映射规则

全志 T113 的 GPIO 编号由 gpiolib 自动分配,但了解其映射规则有助于调试。每个端口组对应一个 gpio_chip,其 base 值由内核动态分配。通常的映射关系如下:

端口组 引脚范围 典型 base 值 引脚数量
PA PA0 ~ PA21 0 22
PB PB0 ~ PB15 22 16
PC PC0 ~ PC7 38 8
PD PD0 ~ PD25 46 26
PE PE0 ~ PE15 72 16
PF PF0 ~ PF7 88 8
PG PG0 ~ PG15 96 16
PH PH0 ~ PH11 112 12
PI PI0 ~ PI15 124 16
快速计算: 例如要操作 PC3,其全局 GPIO 编号 = PC 的 base 值 (38) + 引脚偏移 (3) = 41。在 sysfs 中操作时,应 echo 41 > /sys/class/gpio/export。

4.7 本章小结

本章详细介绍了 Linux GPIO 子系统的核心框架:

  • gpiolib 架构 采用四层设计,将用户空间、内核 API、核心管理和芯片驱动分离。
  • gpio_chip 是 GPIO 控制器的抽象,驱动开发者需要实现其回调函数来操作硬件寄存器。
  • gpio_desc 是每个 GPIO 引脚的内核描述符,包含状态标志和关联信息。
  • sysfs 接口 适合简单控制,但延迟较高;字符设备接口 性能更优,支持批量操作和事件监听。
  • 全志 T113 的 GPIO 编号映射遵循端口组 + 偏移的规则,理解此映射对调试至关重要。