4. GPIO 控制接口:核心 API 详解

在 Linux 内核驱动中,GPIO(通用输入输出)的软件控制通过一组标准内核 API 完成。本章聚焦于四个最基础的接口:gpio_request、gpio_direction_output、gpio_set_value 和 gpio_free。它们是驱动与硬件交互的“握手协议”,理解其生命周期与调用顺序是编写稳定驱动的前提。

4.1 接口全景:从申请到释放的生命周期

GPIO 控制遵循“申请 → 配置 → 操作 → 释放”的严格流程。任何跳过或顺序错误都可能导致资源泄漏或硬件异常。

核心原则: 每个 GPIO 在使用前必须通过 gpio_request 获得独占所有权,使用后必须通过 gpio_free 归还。内核不提供自动垃圾回收。
GPIO 控制生命周期流程图 GPIO 控制生命周期 gpio_request gpio_direction_output gpio_set_value gpio_free 💡 虚线箭头表示:释放后可重新申请并再次配置输出,形成“配置-操作-释放-再配置”循环。

4.2 接口原型与参数详解

四个接口均定义在 #include <linux/gpio.h> 中。下表列出其完整原型、参数含义及返回值。

函数 原型 参数说明 返回值
gpio_request int gpio_request(unsigned gpio, const char *label) gpio: GPIO 编号;label: 用于调试的标签字符串 0 成功;负值错误码(如 -EBUSY)
gpio_direction_output int gpio_direction_output(unsigned gpio, int value) gpio: 已申请的 GPIO 编号;value: 初始输出电平(0/1) 0 成功;负值错误码
gpio_set_value void gpio_set_value(unsigned gpio, int value) gpio: 已配置为输出的 GPIO;value: 0 或 1 无返回值
gpio_free void gpio_free(unsigned gpio) gpio: 要释放的 GPIO 编号 无返回值
调试提示: gpio_request 的 label 参数会出现在 /sys/kernel/debug/gpio 中,建议使用有意义的名称(如 "my_led"),便于排查资源冲突。

4.3 典型调用序列与代码模板

以下代码展示了一个完整的 GPIO 输出控制序列,包含错误处理和资源释放。

#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/delay.h>

#define LED_GPIO  17   // 假设 GPIO 17 连接 LED

static int gpio_demo_init(void)
{
    int ret;

    // 步骤 1: 申请 GPIO
    ret = gpio_request(LED_GPIO, "demo_led");
    if (ret) {
        pr_err("Failed to request GPIO %d: %d\n", LED_GPIO, ret);
        return ret;
    }

    // 步骤 2: 配置为输出,初始电平为 0(LED 灭)
    ret = gpio_direction_output(LED_GPIO, 0);
    if (ret) {
        pr_err("Failed to set direction for GPIO %d: %d\n", LED_GPIO, ret);
        gpio_free(LED_GPIO);   // 失败时立即释放
        return ret;
    }

    // 步骤 3: 操作 GPIO(闪烁一次)
    gpio_set_value(LED_GPIO, 1);   // LED 亮
    msleep(500);                   // 等待 500ms
    gpio_set_value(LED_GPIO, 0);   // LED 灭

    return 0;
}

static void gpio_demo_exit(void)
{
    // 步骤 4: 释放 GPIO
    gpio_set_value(LED_GPIO, 0);   // 确保释放前为安全状态
    gpio_free(LED_GPIO);
}
常见错误: 在 gpio_direction_output 失败后直接返回,忘记调用 gpio_free 会导致 GPIO 资源泄漏,后续驱动无法申请该引脚。

4.4 接口行为对比:原子性、上下文与功耗

不同接口在原子性、可调用上下文和功耗影响上存在差异,驱动开发者需根据场景选择。

接口 原子性 可调用上下文 功耗影响 典型场景
gpio_request 非原子(可能睡眠) 进程上下文 无直接功耗影响 模块初始化、probe 函数
gpio_direction_output 非原子(可能睡眠) 进程上下文 配置引脚驱动能力 初始化阶段
gpio_set_value 原子(快速路径) 任意上下文(含中断) 动态功耗(电平翻转) 中断处理、高频 PWM
gpio_free 非原子(可能睡眠) 进程上下文 无直接功耗影响 模块卸载、错误回滚
接口上下文与原子性对比 接口上下文与原子性对比 进程上下文(可睡眠) gpio_request gpio_direction_output gpio_free 任意上下文(原子操作) gpio_set_value ⚠️ 在中断上下文调用 gpio_request 或 gpio_free 会导致内核调度错误,必须使用 gpio_set_value 的原子版本。

4.5 错误处理与资源管理最佳实践

  • 检查返回值: gpio_request 和 gpio_direction_output 必须检查返回值,失败时执行清理。
  • 释放顺序: 先调用 gpio_set_value 将引脚置于安全电平(如低电平),再调用 gpio_free。
  • 避免重复申请: 同一 GPIO 编号在未释放前再次 gpio_request 会返回 -EBUSY。
  • 使用 devm_ 版本: 在平台驱动中优先使用 devm_gpio_request,设备移除时自动释放。
经验法则: 在驱动的 probe 函数中申请并配置 GPIO,在 remove 函数中释放。错误路径必须包含 goto 标签统一清理,避免重复代码。

4.6 本章小结