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 之间的层次关系:

GPIO 子系统核心数据结构关系 struct gpio_chip 硬件控制器抽象 base, ngpio, get/set, to_irq struct gpio_device 桥接层 id, descs[], chip, base struct gpio_desc 引脚描述符 gdev, flags, label, hwnum 关系说明: 1. 每个 gpio_chip 对应一个 gpio_device(1:1 关系) 2. 每个 gpio_device 包含 ngpio 个 gpio_desc(1:N 关系) 驱动开发流程 实现 gpio_chip → 注册到 gpio_device → 通过 gpio_desc 操作引脚 (驱动开发者主要关注 gpio_chip 的实现)

3.4 注册与使用流程

驱动开发者编写 GPIO 控制器驱动时,通常遵循以下步骤:

  1. 分配并初始化 gpio_chip:设置 base、ngpio、label,并实现 direction_input、direction_output、get、set 等回调。
  2. 调用 gpiochip_add_data():将 gpio_chip 注册到内核 GPIO 框架,内核自动创建对应的 gpio_device 和 gpio_desc 数组。
  3. 用户空间或内核其他模块:通过 gpiod_get() 获取 gpio_desc,再调用 gpiod_set_value()、gpiod_direction_output() 等 API 操作引脚。
注意事项:
  • 注册前必须确保 gpio_chip 的 owner 字段设置为 THIS_MODULE,防止模块被意外卸载。
  • 如果控制器支持中断,必须实现 to_irq 回调,否则无法将引脚用作中断源。
  • 在设备树匹配的驱动中,通常通过 devm_gpiochip_add_data() 注册,该函数会自动处理资源释放。

3.5 本章小结