3. GPIO子系统介绍:Linux GPIO子系统架构、gpiolib库、GPIO编号与控制器、设备树中的GPIO描述

GPIO(General Purpose Input/Output,通用输入输出)是嵌入式系统中最基础、最常用的外设接口之一。在Linux内核中,GPIO子系统提供了一套统一的框架,用于管理和操作SoC或外部GPIO扩展芯片的引脚。理解GPIO子系统的架构、核心库、编号机制以及设备树描述,是编写高质量GPIO驱动的基础。

3.1 Linux GPIO子系统整体架构

Linux GPIO子系统采用分层设计,从上到下依次为:GPIO消费者(Consumer)、GPIO核心层(gpiolib)、GPIO控制器驱动(Provider)。这种分层架构使得驱动开发者无需关心底层硬件细节,只需通过标准API即可操作GPIO。

架构核心思想: 消费者通过统一的API(如gpiod_get_value、gpiod_set_value)请求和使用GPIO,核心层负责管理GPIO编号、映射和资源分配,控制器驱动则直接操作硬件寄存器。
Linux GPIO子系统分层架构 Linux GPIO子系统分层架构 GPIO消费者层(Consumer) 用户空间驱动 / 内核驱动模块 / 板级代码 GPIO核心层(gpiolib) GPIO编号管理 / 映射 / 中断控制 / sysfs接口 gpio_chip注册 / 消费者API实现 GPIO控制器驱动(Provider) SoC内置GPIO / I2C/SPI扩展GPIO芯片驱动 实现gpio_chip结构体回调函数 硬件寄存器 / 物理引脚 说明:消费者通过gpiod_* API操作GPIO,核心层负责调度,控制器驱动完成硬件访问。 这种分层设计使得驱动代码与硬件解耦,便于移植和复用。

3.2 gpiolib库:核心功能与API

gpiolib是GPIO子系统的核心实现,位于内核源码的drivers/gpio/gpiolib.c中。它提供了以下关键功能:

  • GPIO编号管理: 维护全局的GPIO编号空间,支持动态或静态分配。
  • 消费者API: 提供gpiod_get()、gpiod_direction_output()、gpiod_set_value()等标准接口。
  • 中断支持: 将GPIO中断映射到内核中断系统,支持边沿触发和电平触发。
  • sysfs接口: 通过/sys/class/gpio/暴露给用户空间(旧接口,新设备树推荐使用libgpiod)。
  • ACPI/设备树支持: 解析设备树或ACPI表,自动映射GPIO资源。
开发建议: 在新项目中,优先使用基于描述符的API(gpiod_*系列),而不是已废弃的整数编号API(gpio_*系列)。描述符API更安全、更灵活,且与设备树深度集成。

下表对比了新旧两套API的关键差异:

特性 旧API(gpio_*) 新API(gpiod_*)
核心数据类型 int(GPIO编号) struct gpio_desc *(描述符指针)
请求GPIO gpio_request() gpiod_get() / devm_gpiod_get()
设置方向 gpio_direction_output() gpiod_direction_output()
读取值 gpio_get_value() gpiod_get_value()
设备树支持 需手动解析of_get_named_gpio() 自动解析,支持GPIO消费节点
资源管理 手动释放 支持devm_自动释放

3.3 GPIO编号与控制器

在Linux内核中,每个GPIO引脚都有一个全局唯一的GPIO编号。这个编号是系统级的,由内核在启动时根据注册的GPIO控制器动态分配。每个GPIO控制器对应一个struct gpio_chip结构体实例。

3.3.1 GPIO编号分配机制

GPIO编号的分配遵循以下规则:

  • 每个gpio_chip在注册时,内核会为其分配一段连续的编号范围。
  • 编号从0开始,依次递增,但不同控制器的编号范围可能不连续。
  • 通过/sys/kernel/debug/gpio可以查看当前系统的GPIO编号映射。
注意: GPIO编号是平台相关的,同一款SoC在不同板卡上的GPIO编号可能不同。因此,驱动代码中不应硬编码GPIO编号,而应通过设备树或平台数据获取。

3.3.2 gpio_chip结构体

每个GPIO控制器驱动必须实现并注册一个gpio_chip结构体,该结构体定义了控制器的属性和操作函数:

struct gpio_chip {
    const char              *label;
    struct device           *parent;
    struct module           *owner;

    int                     base;           // 起始编号,-1表示动态分配
    int                     ngpio;          // 本控制器管理的GPIO数量

    // 核心回调函数
    int (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    void (*set)(struct gpio_chip *chip, unsigned offset, int value);

    // 中断相关
    int (*to_irq)(struct gpio_chip *chip, unsigned offset);

    // 其他可选回调
    int (*request)(struct gpio_chip *chip, unsigned offset);
    void (*free)(struct gpio_chip *chip, unsigned offset);
};
GPIO编号与控制器映射关系 GPIO编号与控制器映射关系 gpio_chip A base = 0 ngpio = 32 编号范围: 0 ~ 31 gpio_chip B base = 32 ngpio = 16 编号范围: 32 ~ 47 gpio_chip C base = 48 ngpio = 8 编号范围: 48 ~ 55 全局GPIO编号空间 0 ~ 31 (A) | 32 ~ 47 (B) | 48 ~ 55 (C) | ... 后续控制器继续扩展 说明:每个gpio_chip拥有独立的base和ngpio,内核通过base+offset计算全局编号。 驱动中应使用gpio_to_desc()或gpiod_get_index()进行编号到描述符的转换。

3.4 设备树中的GPIO描述

设备树(Device Tree)是描述硬件拓扑的标准化方式,在ARM/ARM64等嵌入式平台中广泛使用。GPIO子系统的设备树描述主要涉及两个方面:GPIO控制器节点和GPIO消费节点。

3.4.1 GPIO控制器节点

GPIO控制器节点定义了SoC或外部芯片的GPIO控制器属性,包括引脚数量、编号基址、中断控制器关联等。以下是一个典型的GPIO控制器节点示例:

// SoC内置GPIO控制器
gpio0: gpio@ff710000 {
    compatible = "vendor,soc-gpio";
    reg = <0xff710000 0x1000>;
    gpio-controller;                    // 标记为GPIO控制器
    #gpio-cells = <2>;                 // 每个GPIO描述需要2个cell
    interrupt-controller;               // 同时也是中断控制器
    #interrupt-cells = <2>;            // 中断描述需要2个cell
    ngpios = <32>;                     // 本控制器有32个引脚
};

关键属性说明:

属性 含义 值示例
gpio-controller 标记节点为GPIO控制器 空属性(布尔值)
#gpio-cells GPIO描述所需的cell数量 2(通常为引脚号和标志位)
ngpios 控制器管理的GPIO数量 32
gpio-ranges GPIO与pinctrl引脚的映射关系 <0 0 32>

3.4.2 GPIO消费节点

外设驱动通过消费节点中的gpios或xxx-gpios属性来引用GPIO引脚。以下是一个按键设备使用GPIO中断的典型描述:

// 按键设备节点,使用GPIO中断
key-interrupt {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&key_pins>;

    button@0 {
        label = "KEY_POWER";
        linux,code = <KEY_POWER>;
        gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;   // GPIO0控制器,引脚15,低电平有效
        interrupts-extended = <&gpio0 15 IRQ_TYPE_EDGE_FALLING>; // 下降沿触发中断
        wakeup-source;                          // 支持唤醒系统
    };
};
GPIO描述格式: <&控制器节点 引脚号 标志位>。标志位常用值包括GPIO_ACTIVE_HIGH(0)和GPIO_ACTIVE_LOW(1),用于指示引脚的电气有效电平。

3.4.3 在驱动中解析设备树GPIO

内核驱动通过gpiod_get()系列函数从设备树中获取GPIO描述符。以下是一个按键中断驱动的设备树解析示例:

#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>

struct my_device {
    struct gpio_desc *key_gpio;
    int irq;
};

static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = dev_id;
    int val = gpiod_get_value(dev->key_gpio);
    pr_info("Key interrupt! GPIO value: %d\n", val);
    return IRQ_HANDLED;
}

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
    if (!mydev)
        return -ENOMEM;

    // 从设备树获取名为"gpios"的GPIO描述符
    mydev->key_gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
    if (IS_ERR(mydev->key_gpio)) {
        dev_err(dev, "Failed to get GPIO\n");
        return PTR_ERR(mydev->key_gpio);
    }

    // 将GPIO转换为中断号
    mydev->irq = gpiod_to_irq(mydev->key_gpio);
    if (mydev->irq < 0) {
        dev_err(dev, "Failed to get IRQ from GPIO\n");
        return mydev->irq;
    }

    // 注册中断处理函数
    ret = devm_request_irq(dev, mydev->irq, key_irq_handler,
                           IRQF_TRIGGER_FALLING, "key_irq", mydev);
    if (ret) {
        dev_err(dev, "Failed to request IRQ\n");
        return ret;
    }

    platform_set_drvdata(pdev, mydev);
    return 0;
}
设备树GPIO描述与驱动解析流程 设备树GPIO描述与驱动解析流程 设备树(.dts) key-interrupt { gpios = <&gpio0 15 0>; interrupts = <15 2>; }; 内核OF/GPIO核心 of_get_named_gpio_flags() gpiod_get_from_of_node() 解析#gpio-cells 返回gpio_desc指针 驱动代码(Consumer) devm_gpiod_get(dev, NULL, GPIOD_IN); gpiod_to_irq(gpio_desc); devm_request_irq(dev, irq, handler, ...); 说明:设备树中的GPIO描述由内核OF子系统解析,gpiolib负责映射到具体的gpio_chip和引脚偏移。 驱动开发者只需调用标准API,无需关心底层编号和控制器细节。

3.5 本章小结

本章深入介绍了Linux GPIO子系统的核心知识:

  • 分层架构: 消费者层、核心层(gpiolib)、控制器驱动层、硬件层,层间通过标准接口交互。
  • gpiolib库: 提供统一的消费者API和控制器注册机制,推荐使用基于描述符的新API。
  • GPIO编号与控制器: 每个gpio_chip管理一段连续编号,全局编号空间由内核动态维护。
  • 设备树描述: 通过gpio-controller和#gpio-cells定义控制器,消费节点使用gpios属性引用引脚。
实践要点: 在编写GPIO驱动时,始终遵循以下原则:1)使用设备树描述硬件连接;2)使用devm_系列API自动管理资源;3)优先使用gpiod_* API而非旧版gpio_* API;4)通过gpiod_to_irq()获取中断号,而非硬编码。