当前位置:首页>Linux>Linux(32)-GPIO子系统

Linux(32)-GPIO子系统

  • 2026-08-23 01:39:07
Linux(32)-GPIO子系统

Linux(32)-GPIO子系统

前言

GPIO(通用输入输出)是嵌入式开发里最常用到的硬件资源,无论是点亮LED、读取按键状态,还是控制外部传感器,都离不开它。在Linux内核中,GPIO子系统(gpiolib)屏蔽了不同SoC、不同扩展芯片的硬件差异,给上层驱动提供了统一的调用接口。

整体架构

GPIO子系统采用经典的三层分层架构,每层职责清晰,解耦度很高:

理解GPIO子系统,首先要搞懂三个核心数据结构的职责和关系:

struct gpio_desc(消费层)

struct gpio_desc 定义在 drivers/gpio/gpiolib.h(gpiolib内部私有头文件)

/**
 * struct gpio_desc - 每个GPIO引脚的描述符
 *
 * 每个物理GPIO引脚对应一个gpio_desc实例,
 * 记录该引脚的完整状态信息。
 *
 * 消费层驱动拿到的 struct gpio_desc * 是不透明指针,
 * 只能通过 gpiod_*() API间接操作。
 */

structgpio_desc {
structgpio_device *gdev;// 所属的GPIO控制器设备
unsignedlong *flags;         // 引脚状态标志位图

/* 以下字段在CONFIG_GPIOLIB_IRQCHIP开启时可用 */
#ifdef CONFIG_GPIOLIB_IRQCHIP
structirq_data *irq_data;// 关联的中断数据(可选)
#endif
};

flags 是一个位图,每个bit代表引脚的一种状态:

/* drivers/gpio/gpiolib.h */
#define FLAG_REQUESTED    0    // 已被申请(gpiod_get成功)
#define FLAG_IS_OUT       1    // 当前方向为输出(0=输入)
#define FLAG_EXPORT       2    // 已导出到sysfs
#define FLAG_SYSFS        3    // 通过sysfs接口申请的
#define FLAG_IS_HOGGED    4    // 被GPIO hog机制自动占用
#define FLAG_IS_HW_TIMESTAMP 5 // 硬件时间戳已启用
#define FLAG_USED_AS_IRQ  6    // 被用作中断源(已锁定)
#define FLAG_ACTIVE_LOW   7    // 低电平有效标志
#define FLAG_OPEN_DRAIN   8    // 开漏输出模式
#define FLAG_OPEN_SOURCE  9    // 开源输出模式
#define FLAG_BIAS_PULL_UP    10 // 内部上拉已使能
#define FLAG_BIAS_PULL_DOWN  11 // 内部下拉已使能
#define FLAG_BIAS_DISABLE    12 // 偏置已禁用
#define FLAG_TRANSITORY   13 // 瞬态标志(系统挂起时不保持状态)
#define FLAG_IS_IN        14 // 当前方向为输入

这些标志位由gpiolib核心层自动管理,消费层驱动通过 gpiod_*() API间接设置或查询。

核心字段详解

gdev — 所属控制器

structgpio_device *gdev;

指向该引脚所属的 gpio_device。通过这个指针,gpiolib可以从一个引脚描述符追溯到整个控制器:

// gpiolib内部通过desc找到对应的gpio_chip
structgpio_chip *gc = desc->gdev->chip;

flags — 状态标志位图

unsignedlong *flags;

记录引脚的当前状态。gpiolib在每次操作前都会检查相关标志位,例如:

  • 调用 gpiod_set_value() 前,检查 FLAG_REQUESTED 确保引脚已被申请
  • 调用 gpiod_set_value() 前,检查 FLAG_IS_OUT 确保引脚方向为输出
  • 调用 gpiod_direction_input() 前,检查 FLAG_USED_AS_IRQ 确保引脚未被锁定为中断

struct gpio_device(核心层)

这是gpiolib内部用来管理每个GPIO控制器的结构,每个注册的GPIO控制器都对应一个gpio_device实例。struct gpio_device 定义在内核源码 drivers/gpio/gpiolib.h 

/*
 * drivers/gpio/gpiolib.h
 *
 * struct gpio_device - 内部数据结构,表示一个 GPIO 控制器
 *
 * 注意: 此结构体是 gpiolib 内部私有的,外部驱动不可直接使用
 */

structgpio_device {
int             id;             // 控制器编号,对应 /dev/gpiochipN 中的 N
structdevicedev;// 内嵌的设备结构体
structcdevchrdev;// 字符设备,提供 /dev/gpiochipN 节点
structdevice   *mockdev;// 模拟设备,用于 sysfs 展示
structmodule   *owner;// 拥有此控制器的内核模块

structgpio_chip    *chip;// 指向控制器驱动注册的 gpio_chip
structgpio_desc    *descs;// 指向该控制器下所有 GPIO 描述符数组
int             base;           // 该控制器的起始 GPIO 全局编号
    u16             ngpio;          // 该控制器管理的 GPIO 数量
char            *label;         // 控制器标签名(用于调试)
void            *data;          // 控制器驱动的私有数据

structlist_headlist;// 链表节点,挂入全局 gpio_devices 链表

#ifdef CONFIG_PINCTRL
/*
     * 如果启用了 CONFIG_PINCTRL,GPIO 控制器可以描述
     * 它在 SoC 中服务的实际引脚范围,pinctrl 子系统
     * 会利用此信息将对应引脚配置为 GPIO 功能
     */

structlist_headpin_ranges;// pinctrl 引脚范围链表
#endif
};
  • id:控制器的唯一ID,对应我们常见的gpiochip0、gpiochip1里的编号;
  • chip:指向对应的gpio_chip,也就是硬件操作接口;
  • descs:指向该控制器下所有GPIO描述符的数组,长度等于chip->ngpio;
  • chrdev:字符设备结构,用户空间的/dev/gpiochipN节点就是它创建的。

struct gpio_chip(控制层)

这是控制器驱动的核心结构,定义在<linux/gpio/driver.h>中,控制器驱动需要填充它的回调函数来实现具体硬件操作:

/**
 * struct gpio_chip - abstract a GPIO controller
 *
 * 抽象一个GPIO控制器,每个物理GPIO控制器(SoC内置或外部扩展芯片)
 * 都对应一个 gpio_chip 实例。
 */

structgpio_chip {
/* ===== 基本标识字段 ===== */
constchar          *label;     // 控制器标签名
structgpio_device  *gpiodev;// 内部状态持有者(不透明)
structdevice       *parent;// 可选的父设备
structfwnode_handle *fwnode;// 提供此控制器属性的可选fwnode
structmodule       *owner;// 模块所有者

/* ===== 核心操作回调函数 ===== */
int     (*request)(struct gpio_chip *gc, unsignedint offset);
void    (*free)(struct gpio_chip *gc, unsignedint offset);
int     (*get_direction)(struct gpio_chip *gc, unsignedint offset);
int     (*direction_input)(struct gpio_chip *gc, unsignedint offset);
int     (*direction_output)(struct gpio_chip *gc, unsignedint offset, int value);
int     (*get)(struct gpio_chip *gc, unsignedint offset);
int     (*get_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);
int     (*set)(struct gpio_chip *gc, unsignedint offset, int value);
int     (*set_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);
int     (*set_config)(struct gpio_chip *gc, unsignedint offset, unsignedlong config);
int     (*to_irq)(struct gpio_chip *gc, unsignedint offset);
void    (*dbg_show)(struct seq_file *s, struct gpio_chip *gc);

/* ===== 可选回调 ===== */
int     (*init_valid_mask)(struct gpio_chip *gc, unsignedlong *valid_mask, unsignedint ngpios);
int     (*add_pin_ranges)(struct gpio_chip *gc);
int     (*en_hw_timestamp)(struct gpio_chip *gc, u32 offset, unsignedlong flags);
int     (*dis_hw_timestamp)(struct gpio_chip *gc, u32 offset, unsignedlong flags);

/* ===== 属性字段 ===== */
int             base;       // 起始GPIO编号(-1表示动态分配)
    u16             ngpio;      // 管理的GPIO数量
    u16             offset;     // 偏移量
constchar *const *names;   // 可选的引脚名称数组
bool            can_sleep;  // 操作是否可能睡眠

/* ===== 中断支持(CONFIG_GPIOLIB_IRQCHIP) ===== */
#ifdef CONFIG_GPIOLIB_IRQCHIP
structgpio_irq_chipirq;
#endif

/* ===== 设备树支持(CONFIG_OF_GPIO) ===== */
#if defined(CONFIG_OF_GPIO)
unsignedint of_gpio_n_cells;
bool (*of_node_instance_match)(struct gpio_chip *gc, unsignedint i);
int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags);
#endif
};

基本标识字段详解

  • label — 控制器标签名

一个字符串,用于标识这个GPIO控制器的功能名称,通常是部件号或SoC IP模块名称(如 "gpio-imx6ul-1"、"pca953x")。这个标签会显示在 /sys/kernel/debug/gpio 的调试输出中,方便开发者识别。

  • gpiodev — 内部状态持有者

指向gpiolib内部分配的 gpio_device 结构体,这是一个不透明的内部结构,控制器驱动不应该直接访问它。当控制器通过 gpiochip_add_data() 注册时,gpiolib会自动创建对应的 gpio_device 并回填此指针。

  • parent — 父设备

指向提供这些GPIO的父设备,通常是注册此控制器的 platform_device。设置此字段后,gpiolib会自动创建 /dev/gpiochipN 字符设备节点并挂到该父设备下。

  • fwnode — 固件节点

提供此控制器属性的可选固件节点句柄。在设备树系统中通常由gpiolib自动从 parent->fwnode 获取,大多数驱动无需手动设置。

核心操作回调函数详解

回调函数是 gpio_chip 的灵魂,控制器驱动通过实现这些函数来告诉gpiolib如何操作硬件。gpiolib在收到上层调用时,会转发到对应的回调函数。

request — GPIO申请回调

int (*request)(struct gpio_chip *gc, unsignedint offset);

调用时机:

  • 当上层驱动调用 gpiod_get() 申请某个GPIO时,gpiolib会调用此回调。

典型用途:

  • 使能该GPIO所属模块的时钟
  • 配置引脚复用为GPIO功能(调用pinctrl)
  • 初始化引脚默认状态

参数:

  • offset 是控制器内的硬件编号(0 到 ngpio-1)。
  • 返回值: 成功返回0,失败返回负错误码。可能会睡眠。
  • 是否必须: 可选。如果不需要芯片级初始化,可以不实现。

实际实现示例

staticintrockchip_gpio_request(struct gpio_chip *chip, unsigned offset)
{
/* 通知pinctrl子系统将此引脚配置为GPIO功能 */
return pinctrl_request_gpio(chip->base + offset);
}

free — GPIO释放回调

void (*free)(struct gpio_chip *gc, unsigned int offset);

调用时机:

  • 当上层驱动调用 gpiod_put() 释放GPIO时调用。

典型用途:

  • 禁用模块时钟
  • 恢复引脚默认状态
  • 释放pinctrl资源

实际实现示例

staticvoidrockchip_gpio_free(struct gpio_chip *chip, unsigned offset)
{
    pinctrl_free_gpio(chip->base + offset);
}

get_direction — 查询方向回调

int (*get_direction)(struct gpio_chip *gc, unsignedint offset);

调用时机:

  • 当上层调用 gpiod_get_direction() 或debugfs展示GPIO状态时调用。

返回值:

  • GPIO_LINE_DIRECTION_OUT (0) — 输出模式
  • GPIO_LINE_DIRECTION_IN (1) — 输入模式
  • 负错误码 — 查询失败

direction_input — 配置为输入

int (*direction_input)(struct gpio_chip *gc, unsignedint offset);

调用时机:

  • 当上层调用 gpiod_direction_input() 时,gpiolib转发到此回调。

典型实现(SoC内置GPIO):

  • 修改方向寄存器,将对应位清零(设为输入)。
staticintsamsung_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
structsamsung_pin_bank *bank = gpiochip_get_data(gc);
unsignedlong flags;
int ret;

    spin_lock_irqsave(&bank->slock, flags);
    ret = samsung_gpio_set_direction(gc, offset, true);  /* true = 输入 */
    spin_unlock_irqrestore(&bank->slock, flags);
return ret;
}

direction_output — 配置为输出

int (*direction_output)(struct gpio_chip *gc, unsignedint offset, int value);

调用时机:

当上层调用 gpiod_direction_output(desc, value) 时调用。

参数:

value 是逻辑值(gpiolib已经处理了Active Low转换),回调函数只需设置物理输出值。

staticintsamsung_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int value)
{
structsamsung_pin_bank *bank = gpiochip_get_data(gc);
unsignedlong flags;
int ret;

    spin_lock_irqsave(&bank->slock, flags);
    samsung_gpio_set_value(gc, offset, value);            /* 先设置输出值 */
    ret = samsung_gpio_set_direction(gc, offset, false);  /* 再设为输出方向 */
    spin_unlock_irqrestore(&bank->slock, flags);
return ret;
}

get — 读取电平

int (*get)(struct gpio_chip *gc, unsignedint offset);

调用时机:

  • 当上层调用 gpiod_get_value() 时调用。

返回值:

  • 0 = 低电平,1 = 高电平,负错误码 = 读取失败。
  • 重要细节:
    • 对于输入引脚,读取的是引脚上的实际物理电平
    • 对于输出引脚,读取的通常是输出锁存器的值(即上次设置的值),而非引脚实际电平。部分SoC支持读取输出引脚的实际电平,取决于硬件设计
staticintrockchip_gpio_get(struct gpio_chip *gc, unsigned offset)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
    u32 data;

/* 读取EXT_PORT寄存器,获取引脚实际输入电平 */
    data = readl(bank->reg_base + GPIO_EXT_PORT);
    data >>= offset;
    data &= 1;
return data;
}

set — 设置输出电平

int (*set)(struct gpio_chip *gc, unsignedint offset, int value);

调用时机:

  • 当上层调用 gpiod_set_value() 时调用。

参数:

  • value 是逻辑值(gpiolib已经处理了Active Low转换),回调函数直接设置物理输出。
staticvoidrockchip_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
void __iomem *reg = bank->reg_base + GPIO_SWPORT_DR;
unsignedlong flags;
    u32 data;

    spin_lock_irqsave(&bank->slock, flags);
    data = readl(reg);          /* 读-改-写,不影响其他引脚 */
    data &= ~BIT(offset);
if (value)
        data |= BIT(offset);
    writel(data, reg);
    spin_unlock_irqrestore(&bank->slock, flags);
}

注意:set 回调中通常需要加自旋锁保护,因为读-改-写操作不是原子的,多个上下文可能同时操作同一个寄存器的不同位。

set_multiple — 批量设置输出电平


int (*set_multiple)(struct gpio_chip *gc, unsignedlong *mask, unsignedlong *bits);

调用时机:

  • 当上层调用 gpiod_set_array_value() 时调用。

参数说明:

  • mask:位掩码,标记要设置哪些引脚

  • bits:输入位图,存放要设置的值优势:

  • 可以一次性修改整个寄存器,避免多次读-改-写操作。

是否必须:

  • 可选。如果不实现,gpiolib会退化为逐个调用 set()。

set_config — 通用电气配置

int (*set_config)(struct gpio_chip *gc, unsignedint offset, unsignedlong config);

调用时机:

  • 当上层调用 gpiod_set_config() 或 gpiod_set_debounce() 时调用。
  • 参数: config 使用与pinconf相同的打包配置格式(通过 pinconf_to_config_packed() 生成),支持:
  • 消抖时间(PIN_CONFIG_INPUT_DEBOUNCE)
  • 开漏输出(PIN_CONFIG_DRIVE_OPEN_DRAIN)
  • 开源输出(PIN_CONFIG_DRIVE_OPEN_SOURCE)
  • 上拉/下拉(PIN_CONFIG_BIAS_PULL_UP / PIN_CONFIG_BIAS_PULL_DOWN)
  • 返回值: 不支持时返回 -ENOTSUPP。

典型实现:

很多控制器驱动直接将此回调指向 gpiochip_generic_config(),由pinctrl子系统处理。

/* Samsung驱动的实现 */
staticconststructgpio_chipsamsung_gpiolib_chip = {
    .set_config = gpiochip_generic_config,  /* 转发到pinctrl */
    ...
};

to_irq — GPIO转中断

int (*to_irq)(struct gpio_chip *gc, unsignedint offset);
  • 调用时机: 当上层调用 gpiod_to_irq() 时调用。
  • 返回值: 正数IRQ编号表示成功,负错误码表示不支持。
staticintrockchip_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
structrockchip_pin_bank *bank = gc_to_pin_bank(gc);
unsignedint virq;

if (!bank->domain)
return -ENXIO;

/* 通过irq_domain创建GPIO到IRQ的映射 */
    virq = irq_create_mapping(bank->domain, offset);
return (virq) ? : -ENXIO;
}

dbg_show — 自定义debugfs输出

void (*dbg_show)(struct seq_file *s, struct gpio_chip *gc);

调用时机:

  • 当用户读取 /sys/kernel/debug/gpio 时调用。

典型用途:

  • 显示额外的硬件状态信息,如上下拉配置、驱动能力、中断使能状态等。如果不实现,gpiolib使用默认输出格式。

构建过程

一切从设备树(.dts文件)开始。SoC的GPIO控制器在设备树中被描述为一个节点:

一、起点:设备树中的GPIO控制器节点

/* 以 i.MX6UL 的 GPIO1 为例 */
gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio""fsl,imx35-gpio";
    reg = <0x0209c0000x4000>;          /* 寄存器物理基地址和大小 */
    interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_GPIO1>;
    gpio-controller;                    /* 标记:我是一个GPIO控制器 */
#gpio-cells = <2>;                  /* 每个GPIO用2个cell描述(引脚号+标志) */
    interrupt-controller;               /* 同时也是一个中断控制器 */
#interrupt-cells = <2>;
    status = "okay";
};

内核启动时,设备树解析器(drivers/of/fdt.c)会遍历所有节点,提取每个节点的 compatible 属性。

二、总线匹配:compatible 找到驱动

GPIO控制器挂在 platform 总线上。总线匹配逻辑(drivers/base/platform.c)会用设备树节点的 compatible 字符串,去匹配所有已注册的 platform_driver 的 of_match_table:

/* drivers/gpio/gpio-mxc.c — i.MX系列GPIO控制器驱动 */

staticconststructof_device_idmxc_gpio_dt_ids[] = {
    { .compatible = "fsl,imx1-gpio",   .data = &mxc_gpio_devdata[IMX1_GPIO] },
    { .compatible = "fsl,imx21-gpio",  .data = &mxc_gpio_devdata[IMX21_GPIO] },
    { .compatible = "fsl,imx35-gpio",  .data = &mxc_gpio_devdata[IMX35_GPIO] },
    { .compatible = "fsl,imx6ul-gpio", .data = &mxc_gpio_devdata[IMX6UL_GPIO] },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxc_gpio_dt_ids);

staticstructplatform_drivermxc_gpio_driver = {
    .driver = {
        .name           = "gpio-mxc",
        .of_match_table = mxc_gpio_dt_ids,   /* ← 匹配表 */
    },
    .probe  = mxc_gpio_probe,                 /* ← 匹配成功后的入口 */
};
设备树节点: compatible = "fsl,imx6ul-gpio"
        │
        ▼
platform总线匹配逻辑
        │
        │  遍历所有 platform_driver 的 of_match_table
        │  mxc_gpio_driver.of_match_table 中包含 "fsl,imx6ul-gpio"
        │
        ▼
匹配成功 → 内核自动调用 mxc_gpio_probe(pdev)

三、probe函数:填充 gpio_chip 并触发注册

mxc_gpio_probe() 是三个结构体串联的触发点。在这个函数中,控制器驱动工程师做两件事:填充 gpio_chip(控制层),然后调用注册接口。

staticintmxc_gpio_probe(struct platform_device *pdev)
{
structmxc_gpio_port *port;

    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);

/* 从设备树获取硬件资源 */
    port->base = devm_platform_ioremap_resource(pdev, 0);  /* 映射寄存器 */
    port->irq  = platform_get_irq(pdev, 0);                 /* 获取中断号 */

/* ★ 填充 gpio_chip —— 告诉gpiolib"怎么操作我的硬件" */
    port->gc.label            = dev_name(&pdev->dev);
    port->gc.parent           = &pdev->dev;
    port->gc.owner            = THIS_MODULE;
    port->gc.base             = -1;              /* 动态分配全局编号 */
    port->gc.ngpio            = 32;              /* 本控制器有32个引脚 */

/* 核心操作回调 —— 直接操作硬件寄存器的函数 */
    port->gc.request          = gpiochip_generic_request;
    port->gc.free             = gpiochip_generic_free;
    port->gc.get_direction    = mxc_gpio_get_direction;
    port->gc.direction_input  = mxc_gpio_direction_input;
    port->gc.direction_output = mxc_gpio_direction_output;
    port->gc.get              = mxc_gpio_get;
    port->gc.set              = mxc_gpio_set;
    port->gc.to_irq           = mxc_gpio_to_irq;

/* ★★ 注册!触发三个结构体的串联 ★★ */
return devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
}

此时,只有 gpio_chip 存在,它还是"孤立的"——没有被任何内部结构持有。gpio_chip 只是控制器驱动在栈/堆上填充好的一个结构体,等待gpiolib核心层来"收容"它。

四、核心注册函数:三个结构体的串联全过程

devm_gpiochip_add_data() 只是一个资源管理包装,最终调用链为:

devm_gpiochip_add_data()
    → gpiochip_add_data()         (宏展开)
        → gpiochip_add_data_with_key()   (真正的核心函数)

gpiochip_add_data_with_key() 定义在 drivers/gpio/gpiolib.c,是整个GPIO子系统的注册核心。

第1步:分配 gpio_device

gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);

gpiolib核心层为这个GPIO控制器分配一个 gpio_device 实例。此时 gpio_device 是一个空壳。

第2步:建立 gpio_device ↔ gpio_chip 双向指针

gdev->chip = chip;        /* gpio_device → gpio_chip */
chip->gpiodev = gdev;     /* gpio_chip → gpio_device(反向指针) */

这是第一个关键连接。 gpio_device 持有 gpio_chip,gpio_chip 也反向持有 gpio_device。两者从此绑定。

第3步:初始化 gpio_device 的设备模型字段

gdev->dev.bus = &gpio_bus_type;
dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
device_initialize(&gdev->dev);

if (chip->parent) {
    gdev->dev.parent = chip->parent;
    gdev->dev.of_node = chip->parent->of_node;
}

给 gpio_device 内嵌的 struct device 设置名称(如 gpiochip0)、父设备、设备树节点。这就是为什么在 /sys/bus/gpio/devices/ 下能看到 gpiochip0 等节点。

第4步:分配 gpio_desc 数组

gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);
gdev->ngpio = chip->ngpio;

根据 gpio_chip.ngpio(本例为32),一次性分配32个 gpio_desc,挂在 gpio_device.descs 下。这是第二个关键连接

第5步:分配全局GPIO编号并挂入全局链表

if (base < 0) {   /* chip->base == -1,动态分配 */
    base = gpiochip_find_base(chip->ngpio);
    chip->base = base;
}
gdev->base = base;

gpiodev_add_to_list(gdev);   /* 挂入全局链表 gpio_devices */

在全局GPIO编号空间中分配一段连续编号(如0~31),然后将 gpio_device 通过其 list 字段挂入全局链表 gpio_devices。

第6步:初始化每个 gpio_desc

for (i = 0; i < chip->ngpio; i++) {
structgpio_desc *desc = &gdev->descs[i];

    desc->gdev = gdev;    /* ★ 第三个关键连接:每个desc指回所属的gpio_device */

/* 通过 gpio_chip 的 get_direction 回调读取硬件初始方向 */
if (chip->get_direction) {
int dir = chip->get_direction(chip, i);
if (!dir)
            set_bit(FLAG_IS_OUT, &desc->flags);
    }
}

这是第三个关键连接。 每个 gpio_desc 都通过 gdev 指针指回所属的 gpio_device,而 gpio_device 又持有 gpio_chip。至此,三个结构体形成了完整的指针链。

第7步:设备树解析与字符设备创建

of_gpiochip_add(chip);           /* 解析设备树中引用本控制器的GPIO属性 */
gpiochip_setup_dev(gdev);        /* 创建 /dev/gpiochipN 字符设备 */

of_gpiochip_add() 会设置 of_xlate 回调,使gpiolib能正确翻译设备树中 <&gpio1 5 GPIO_ACTIVE_HIGH> 这样的GPIO说明符。

串联完成后的完整指针关系

全局链表 gpio_devices
    │
    ├──▶ gpio_device (gpiochip0)
    │       │
    │       ├── chip ──────────▶ gpio_chip
    │       │                      │
    │       │                      ├── .get()            → 读寄存器
    │       │                      ├── .set()            → 写寄存器
    │       │                      ├── .direction_*()    → 改方向寄存器
    │       │                      ├── .to_irq()         → 创建IRQ映射
    │       │                      ├── base = 0
    │       │                      ├── ngpio = 32
    │       │                      └── gpiodev ──────────┐ (反向指针)
    │       │                                            │
    │       ├── descs ──▶ gpio_desc[0]                   │
    │       │               ├── gdev ────────────────────┘ (指回gdev)
    │       │               ├── flags = 0
    │       │               └── (未申请, 未使用)
    │       │
    │       │            gpio_desc[1]
    │       │               ├── gdev ──→ (同上)
    │       │               └── ...
    │       │
    │       │            ... (共32个desc)
    │       │
    │       │            gpio_desc[31]
    │       │               ├── gdev ──→ (同上)
    │       │               └── ...
    │       │
    │       ├── ngpio = 32
    │       ├── base = 0
    │       └── list ──→ 下一个 gpio_device
    │
    ├──▶ gpio_device (gpiochip1)
    │       ├── chip ──→ gpio_chip (另一个控制器)
    │       ├── descs ──→ gpio_desc[0] ... gpio_desc[31]
    │       └── ...
    │
    └──▶ ...

消费层如何沿这条链路操作GPIO

当消费层驱动(如LED驱动)通过设备树引用一个GPIO时:

/* 设备树中引用GPIO */
leds {
    compatible = "gpio-leds";
    led0 {
        gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;   /* 引用gpio1的第5号引脚 */
    };
};

消费层驱动调用 gpiod_get() 获取描述符:

/* LED驱动 probe 中 */
structgpio_desc *led_gpio = gpiod_get(&pdev->devNULLGPIOD_OUT_LOW);

gpiolib内部通过设备树解析,找到 gpio1 对应的 gpio_device,再定位到 descs[5],返回这个 gpio_desc 的不透明指针给消费层。 之后消费层调用 gpiod_set_value(led_gpio, 1) 时,gpiolib内部正是沿着三个结构体的指针链完成操作:

/* gpiolib内部实现(简化) */
voidgpiod_set_value(struct gpio_desc *desc, int value)
{
/* 第1步:desc → gpio_device */
structgpio_device *gdev = desc->gdev;

/* 第2步:gpio_device → gpio_chip */
structgpio_chip *gc = gdev->chip;

/* 第3步:计算引脚在控制器内的硬件偏移 */
unsignedint offset = gpio_chip_hwgpio(desc);

/* 第4步:处理 Active Low */
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
        value = !value;

/* 第5步:调用 gpio_chip 的 set 回调,直达硬件 */
    gc->set(gc, offset, value);
}

两次指针解引用(desc->gdev->chip),就从一个不透明的引脚描述符直达硬件操作函数。 这就是三个结构体串联的核心价值。

完整时序

时间线 ──────────────────────────────────────────────────────────→

① 设备树解析
   内核解析 .dtb,发现 compatible = "fsl,imx6ul-gpio" 的节点
   创建 platform_device
        │
② 总线匹配
   platform总线用 compatible 匹配 of_match_table
   匹配成功 → 调用 mxc_gpio_probe(pdev)
        │
③ probe 函数执行
   驱动从设备树获取寄存器地址、中断号、时钟
   填充 gpio_chip(get/set/direction_* 等回调)
   调用 devm_gpiochip_add_data()
        │
④ gpiochip_add_data_with_key() 内部
   ┌─ kzalloc gpio_device          ← gpio_device 诞生
   ├─ gdev->chip = chip            ← gpio_device 持有 gpio_chip
   ├─ chip->gpiodev = gdev         ← gpio_chip 反向持有 gpio_device
   ├─ kcalloc gpio_desc[32]        ← gpio_desc 数组诞生
   ├─ for: desc[i].gdev = gdev     ← 每个 gpio_desc 指回 gpio_device
   ├─ gpiodev_add_to_list(gdev)    ← 挂入全局链表
   ├─ of_gpiochip_add(chip)        ← 设备树GPIO翻译初始化
   └─ gpiochip_setup_dev(gdev)     ← 创建 /dev/gpiochipN
        │
⑤ 注册完成
   三个结构体串联就绪
   消费层驱动可以开始使用 gpiod_*() API
        │
⑥ 消费层使用
   gpiod_get() → 拿到 gpio_desc *
   gpiod_set_value(desc, 1)
       → desc->gdev->chip->set()  → 操作硬件寄存器

例子

设计一个完整的综合示例项目,场景是一个自定义平台设备 myboard,板上包含:

  • 3个LED(RGB,批量控制)
  • 1个按键(带中断,消抖)
  • 1个可选的蜂鸣器GPIO
  • 1个字符设备,供用户空间控制所有GPIO

设备树

/*
 * 文件: myboard-gpio.dtsi
 * 说明: 自定义板级设备树片段,演示GPIO子系统新API的设备树配置
 *
 * 假设平台: 基于NXP i.MX6ULL或类似SoC
 * gpio1: 控制器1, 引脚0~31
 * gpio2: 控制器2, 引脚0~31
 */


/ {
/*
     * 自定义平台设备节点
     * 驱动通过 compatible = "myboard,gpio-demo" 匹配
     */

    myboard_gpio_demo {
        compatible = "myboard,gpio-demo";

/*
         * LED GPIO配置:
         * - 属性名 led-gpios, 驱动中 con_id = "led"
         * - 红色LED: gpio1引脚5, 高电平点亮 (GPIO_ACTIVE_HIGH)
         * - 绿色LED: gpio1引脚6, 高电平点亮
         * - 蓝色LED: gpio1引脚7, 低电平点亮 (GPIO_ACTIVE_LOW)
         *
         * 演示API: devm_gpiod_get_index(), gpiod_set_value() (自动处理Active Low)
         */

        led-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>,   /* 红色LED */
                    <&gpio1 6 GPIO_ACTIVE_HIGH>,   /* 绿色LED */
                    <&gpio1 7 GPIO_ACTIVE_LOW>;    /* 蓝色LED (低电平有效) */

/*
         * 按键GPIO配置:
         * - 属性名 button-gpios, 驱动中 con_id = "button"
         * - gpio2引脚3, 低电平有效 (按键按下接地)
         * - 支持中断
         *
         * 演示API: devm_gpiod_get(), gpiod_to_irq(), gpiod_set_debounce(),
         *          gpiod_direction_input(), gpiod_is_active_low()
         */

        button-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;

/*
         * 可选蜂鸣器GPIO配置:
         * - 属性名 buzzer-gpios, 驱动中 con_id = "buzzer"
         * - gpio1引脚12, 高电平有效
         * - 使用 _optional 变体, 如果设备树中删除此属性, 驱动不会报错
         *
         * 演示API: devm_gpiod_get_optional()
         */

        buzzer-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;

/*
         * 测试GPIO数组:
         * - 属性名 test-gpios, 驱动中 con_id = "test"
         * - 4个GPIO, 用于演示批量操作
         *
         * 演示API: devm_gpiod_get_array(), gpiod_set_array_value()
         */

        test-gpios = <&gpio1 20 GPIO_ACTIVE_HIGH>,
                     <&gpio1 21 GPIO_ACTIVE_HIGH>,
                     <&gpio1 22 GPIO_ACTIVE_HIGH>,
                     <&gpio1 23 GPIO_ACTIVE_HIGH>;
    };
};

/*
 * GPIO控制器节点 (以i.MX6ULL为例, 通常已在SoC dtsi中定义)
 * 这里仅展示控制器端需要的关键属性
 */

&gpio1 {
    gpio-controller;
#gpio-cells = <2>;        /* 第一个cell=引脚号, 第二个cell=标志 */
    interrupt-controller;
#interrupt-cells = <2>;
};

&gpio2 {
    gpio-controller;
#gpio-cells = <2>;
    interrupt-controller;
#interrupt-cells = <2>;
};

内核驱动代码

/*
 * 文件: myboard_gpio_demo.c
 * 说明: GPIO子系统新API综合演示驱动
 *
 * 覆盖的消费端API:
 *   devm_gpiod_get / devm_gpiod_get_index / devm_gpiod_get_optional
 *   devm_gpiod_get_array / gpiod_count
 *   gpiod_direction_input / gpiod_direction_output
 *   gpiod_get_value / gpiod_set_value / gpiod_get_direction
 *   gpiod_get_value_cansleep / gpiod_set_value_cansleep
 *   gpiod_set_array_value
 *   gpiod_to_irq / gpiod_cansleep / gpiod_is_active_low
 *   gpiod_set_debounce / gpiod_set_consumer_name
 *   gpiod_toggle_active_low / gpiod_set_config
 *
 * 覆盖的提供端API:
 *   gpiochip_add_data / gpiochip_remove
 *   struct gpio_chip 各回调函数
 */


#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/platform_device.h>
#include<linux/gpio/consumer.h>
#include<linux/gpio/driver.h>
#include<linux/interrupt.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/delay.h>
#include<linux/of.h>
#include<linux/of_device.h>

#define DEVICE_NAME     "myboard_gpio"
#define CLASS_NAME      "myboard_gpio_class"
#define MAX_LEDS        3
#define MAX_TEST_GPIOS  8

/* ============================================================
 * 用户空间与驱动之间的ioctl命令定义
 * ============================================================ */

#define MYBOARD_IOC_MAGIC   'M'
#define MYBOARD_LED_ON      _IOW(MYBOARD_IOC_MAGIC, 1, int)   /* 点亮指定LED */
#define MYBOARD_LED_OFF     _IOW(MYBOARD_IOC_MAGIC, 2, int)   /* 熄灭指定LED */
#define MYBOARD_LED_TOGGLE  _IOW(MYBOARD_IOC_MAGIC, 3, int)   /* 翻转指定LED */
#define MYBOARD_BTN_READ    _IOR(MYBOARD_IOC_MAGIC, 4, int)   /* 读取按键状态 */
#define MYBOARD_BUZZER_ON   _IO(MYBOARD_IOC_MAGIC, 5)         /* 打开蜂鸣器 */
#define MYBOARD_BUZZER_OFF  _IO(MYBOARD_IOC_MAGIC, 6)         /* 关闭蜂鸣器 */
#define MYBOARD_TEST_SET    _IOW(MYBOARD_IOC_MAGIC, 7, int)   /* 批量设置测试GPIO */
#define MYBOARD_INFO        _IOR(MYBOARD_IOC_MAGIC, 8, int)   /* 获取GPIO信息 */

/* ============================================================
 * GPIO信息结构体, 用于传递给用户空间
 * ============================================================ */

structmyboard_gpio_info {
int led_count;                    /* LED数量 */
int button_present;               /* 按键是否存在 */
int button_active_low;            /* 按键是否低电平有效 */
int buzzer_present;               /* 蜂鸣器是否存在 */
int buzzer_active_low;            /* 蜂鸣器是否低电平有效 */
int test_gpio_count;              /* 测试GPIO数量 */
int button_irq;                   /* 按键中断号 */
int button_cansleep;              /* 按键GPIO是否可睡眠 */
};

/* ============================================================
 * 驱动私有数据结构体
 * ============================================================ */

structmyboard_gpio_data {
structdevice *dev;

/* LED相关 */
structgpio_desc *led_gpios[MAX_LEDS];
int led_count;

/* 按键相关 */
structgpio_desc *button_gpio;
int button_irq;
unsignedlong button_press_count;

/* 蜂鸣器 (可选) */
structgpio_desc *buzzer_gpio;

/* 测试GPIO数组 */
structgpio_descs *test_gpios;

/* 字符设备 */
dev_t devt;
structcdevcdev;
structclass *cls;
structdevice *char_dev;

/* 虚拟GPIO控制器 (演示提供端API) */
structgpio_chipvchip;
int vchip_base;
unsignedlong vchip_state;        /* 虚拟GPIO的状态位图 */
};

staticstructmyboard_gpio_data *g_data;/* 全局指针, 供中断处理使用 */

/* ============================================================
 * 虚拟GPIO控制器回调函数 (演示提供端API)
 *
 * 这个虚拟控制器模拟了4个GPIO引脚, 实际并不操作硬件,
 * 仅用于演示 gpio_chip 的各回调函数如何实现
 * ============================================================ */


/*
 * request回调: GPIO被申请时调用
 * 典型用途: 使能时钟、配置引脚复用等
 */

staticintvchip_request(struct gpio_chip *gc, unsignedint offset)
{
    pr_info("myboard: vchip request gpio %u\n", offset);
return0;
}

/*
 * free回调: GPIO被释放时调用
 * 典型用途: 禁用时钟、恢复引脚默认状态等
 */

staticvoidvchip_free(struct gpio_chip *gc, unsignedint offset)
{
    pr_info("myboard: vchip free gpio %u\n", offset);
}

/*
 * get_direction回调: 查询引脚当前方向
 * 返回: 0 = 输出 (GPIO_LINE_DIRECTION_OUT)
 *       1 = 输入 (GPIO_LINE_DIRECTION_IN)
 *
 * 强烈推荐实现此回调, debugfs依赖它显示正确的方向信息
 */

staticintvchip_get_direction(struct gpio_chip *gc, unsignedint offset)
{
/* 虚拟控制器中所有引脚都是输出 */
return GPIO_LINE_DIRECTION_OUT;
}

/*
 * direction_input回调: 配置引脚为输入
 * 虚拟控制器不支持输入, 返回错误
 */

staticintvchip_direction_input(struct gpio_chip *gc, unsignedint offset)
{
    pr_warn("myboard: vchip does not support input direction\n");
return -ENOTSUPP;
}

/*
 * direction_output回调: 配置引脚为输出并设置初始值
 * 参数: offset - 引脚编号, value - 初始逻辑值
 */

staticintvchip_direction_output(struct gpio_chip *gc, unsignedint offset, int value)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);

if (value)
        set_bit(offset, &data->vchip_state);
else
        clear_bit(offset, &data->vchip_state);

    pr_info("myboard: vchip set output gpio %u = %d\n", offset, value);
return0;
}

/*
 * get回调: 读取引脚电平
 * 返回: 0 = 低电平, 1 = 高电平
 */

staticintvchip_get(struct gpio_chip *gc, unsignedint offset)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);

return test_bit(offset, &data->vchip_state) ? 1 : 0;
}

/*
 * get_multiple回调: 批量读取多个引脚电平
 * mask: 位掩码, 标记要读取哪些引脚
 * bits: 输出位图, 存放读取结果
 *
 * 批量操作比逐个调用get()效率更高
 */

staticintvchip_get_multiple(struct gpio_chip *gc, unsignedlong *mask,
unsignedlong *bits)

{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
int i;

    for_each_set_bit(i, mask, gc->ngpio) {
if (test_bit(i, &data->vchip_state))
            set_bit(i, bits);
else
            clear_bit(i, bits);
    }
return0;
}

/*
 * set回调: 设置输出引脚的电平值
 * 参数: offset - 引脚编号, value - 逻辑值
 */

staticvoidvchip_set(struct gpio_chip *gc, unsignedint offset, int value)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);

if (value)
        set_bit(offset, &data->vchip_state);
else
        clear_bit(offset, &data->vchip_state);

    pr_info("myboard: vchip set gpio %u = %d\n", offset, value);
}

/*
 * set_multiple回调: 批量设置多个输出引脚的电平
 * mask: 位掩码, 标记要设置哪些引脚
 * bits: 输入位图, 存放要设置的值
 *
 * 批量操作可以一次性写入硬件寄存器, 避免多次总线访问
 */

staticvoidvchip_set_multiple(struct gpio_chip *gc, unsignedlong *mask,
unsignedlong *bits)

{
structmyboard_gpio_data *data = gpiochip_get_data(gc);
int i;

    for_each_set_bit(i, mask, gc->ngpio) {
if (test_bit(i, bits))
            set_bit(i, &data->vchip_state);
else
            clear_bit(i, &data->vchip_state);
    }
}

/*
 * set_config回调: 配置GPIO的电气属性
 * config: 使用pinconf打包格式的配置参数
 *
 * 支持: 消抖、开漏/开源、上下拉等
 * 不支持时返回 -ENOTSUPP
 */

staticintvchip_set_config(struct gpio_chip *gc, unsignedint offset,
unsignedlong config)

{
/* 虚拟控制器不支持任何电气配置 */
return -ENOTSUPP;
}

/*
 * dbg_show回调: 自定义debugfs输出
 * 在 /sys/kernel/debug/gpio 中显示额外信息
 */

staticvoidvchip_dbg_show(struct seq_file *s, struct gpio_chip *gc)
{
structmyboard_gpio_data *data = gpiochip_get_data(gc);

    seq_printf(s, "virtual state: 0x%04lx\n", data->vchip_state);
}

/* ============================================================
 * 按键中断处理函数
 *
 * 演示API:
 *   gpiod_get_value_cansleep() - 在中断线程中读取按键状态
 *   gpiod_is_active_low()      - 判断有效电平
 * ============================================================ */

staticirqreturn_tmyboard_button_isr(int irq, void *dev_id)
{
structmyboard_gpio_data *data = dev_id;
int val;

/*
     * 在线程化中断上下文中, 可以使用cansleep版本的读取函数
     * 对于I2C/SPI总线上的GPIO扩展芯片, 必须使用cansleep版本
     */

    val = gpiod_get_value_cansleep(data->button_gpio);

/*
     * 注意: gpiod_get_value_cansleep() 返回的是逻辑值
     * 如果GPIO配置为GPIO_ACTIVE_LOW, 按下时返回1 (逻辑有效)
     * 无需手动处理Active Low转换
     */

    data->button_press_count++;
    dev_info(data->dev, "Button pressed! count=%lu, logical_val=%d\n",
             data->button_press_count, val);

return IRQ_HANDLED;
}

/* ============================================================
 * 字符设备文件操作
 * ============================================================ */


/*
 * ioctl处理函数: 用户空间通过ioctl控制GPIO
 *
 * 演示API:
 *   gpiod_set_value()           - 设置LED
 *   gpiod_get_value()           - 读取按键
 *   gpiod_set_array_value()     - 批量设置测试GPIO
 *   gpiod_is_active_low()       - 查询Active Low属性
 *   gpiod_cansleep()            - 查询是否可睡眠
 *   gpiod_get_direction()       - 查询方向
 *   gpiod_toggle_active_low()   - 切换Active Low
 */

staticlongmyboard_ioctl(struct file *file, unsignedint cmd, unsignedlong arg)
{
structmyboard_gpio_data *data = file->private_data;
int ret = 0;
int val;

switch (cmd) {

/* ---- LED控制 ---- */
case MYBOARD_LED_ON:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
/*
         * gpiod_set_value: 设置逻辑值
         * 如果LED配置为GPIO_ACTIVE_LOW, val=1会自动驱动物理低电平
         * 驱动无需关心硬件极性
         */

        gpiod_set_value(data->led_gpios[val], 1);
        dev_info(data->dev, "LED %d ON (logical)\n", val);
break;

case MYBOARD_LED_OFF:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
        gpiod_set_value(data->led_gpios[val], 0);
        dev_info(data->dev, "LED %d OFF (logical)\n", val);
break;

case MYBOARD_LED_TOGGLE:
if (get_user(val, (int __user *)arg))
return -EFAULT;
if (val < 0 || val >= data->led_count)
return -EINVAL;
        {
/*
             * gpiod_get_value: 读取当前逻辑值
             * 返回值已考虑Active Low属性
             */

int cur = gpiod_get_value(data->led_gpios[val]);
            gpiod_set_value(data->led_gpios[val], !cur);
            dev_info(data->dev, "LED %d toggled: %d -> %d\n", val, cur, !cur);
        }
break;

/* ---- 按键读取 ---- */
case MYBOARD_BTN_READ:
if (!data->button_gpio)
return -ENODEV;
        {
/*
             * gpiod_cansleep: 判断GPIO操作是否可能睡眠
             * 对于I2C/SPI扩展GPIO返回非零值, 必须使用cansleep版本
             */

int cansleep = gpiod_cansleep(data->button_gpio);
int direction = gpiod_get_direction(data->button_gpio);

if (cansleep)
                val = gpiod_get_value_cansleep(data->button_gpio);
else
                val = gpiod_get_value(data->button_gpio);

            dev_info(data->dev, "Button: val=%d, cansleep=%d, direction=%s\n",
                     val, cansleep,
                     direction == GPIO_LINE_DIRECTION_IN ? "in" : "out");
        }
if (put_user(val, (int __user *)arg))
return -EFAULT;
break;

/* ---- 蜂鸣器控制 ---- */
case MYBOARD_BUZZER_ON:
if (!data->buzzer_gpio)
return -ENODEV;
        gpiod_set_value(data->buzzer_gpio, 1);
        dev_info(data->dev, "Buzzer ON\n");
break;

case MYBOARD_BUZZER_OFF:
if (!data->buzzer_gpio)
return -ENODEV;
        gpiod_set_value(data->buzzer_gpio, 0);
        dev_info(data->dev, "Buzzer OFF\n");
break;

/* ---- 测试GPIO批量操作 ---- */
case MYBOARD_TEST_SET:
if (!data->test_gpios)
return -ENODEV;
if (get_user(val, (int __user *)arg))
return -EFAULT;
        {
/*
             * gpiod_set_array_value: 批量设置多个GPIO
             * 参数:
             *   array_size    - GPIO数量
             *   desc_array    - 描述符数组
             *   array_info    - 可选的优化信息 (NULL表示不使用)
             *   value_bitmap  - 值位图, 每个bit对应一个GPIO的逻辑值
             *
             * 如果所有GPIO属于同一控制器, 底层会合并为一次寄存器写入
             */

unsignedlong bitmap = val;

            ret = gpiod_set_array_value(data->test_gpios->ndescs,
                                        data->test_gpios->desc,
NULL,           /* array_info */
                                        &bitmap);
            dev_info(data->dev, "Test GPIOs set: bitmap=0x%lx, ret=%d\n",
                     bitmap, ret);
        }
break;

/* ---- 获取GPIO信息 ---- */
case MYBOARD_INFO:
        {
structmyboard_gpio_infoinfo = {0};

            info.led_count = data->led_count;
            info.button_present = (data->button_gpio != NULL) ? 1 : 0;
            info.buzzer_present = (data->buzzer_gpio != NULL) ? 1 : 0;
            info.test_gpio_count = data->test_gpios ? data->test_gpios->ndescs : 0;
            info.button_irq = data->button_irq;

if (data->button_gpio) {
/*
                 * gpiod_is_active_low: 判断GPIO是否低电平有效
                 * 返回1表示Active Low, 0表示Active High
                 */

                info.button_active_low = gpiod_is_active_low(data->button_gpio);
                info.button_cansleep = gpiod_cansleep(data->button_gpio);
            }

if (data->buzzer_gpio)
                info.buzzer_active_low = gpiod_is_active_low(data->buzzer_gpio);

if (copy_to_user((void __user *)arg, &info, sizeof(info)))
return -EFAULT;
        }
break;

default:
        ret = -ENOTTY;
break;
    }

return ret;
}

staticintmyboard_open(struct inode *inode, struct file *file)
{
    file->private_data = g_data;
return0;
}

staticintmyboard_release(struct inode *inode, struct file *file)
{
return0;
}

staticconststructfile_operationsmyboard_fops = {
    .owner          = THIS_MODULE,
    .open           = myboard_open,
    .release        = myboard_release,
    .unlocked_ioctl = myboard_ioctl,
};

/* ============================================================
 * 平台驱动probe函数
 *
 * 设备匹配后由内核自动调用, 完成所有GPIO的获取和初始化
 * ============================================================ */

staticintmyboard_gpio_probe(struct platform_device *pdev)
{
structmyboard_gpio_data *data;
structdevice *dev = &pdev->dev;
int ret, i;

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

    data->dev = dev;
    g_data = data;

/* ==========================================================
     * 第一步: 获取LED GPIO (演示 devm_gpiod_get_index)
     *
     * 设备树属性: led-gpios = <&gpio1 5 ...>, <&gpio1 6 ...>, <&gpio1 7 ...>
     * con_id = "led", 通过idx区分不同的LED
     *
     * devm_gpiod_get_index 参数说明:
     *   dev     - 设备指针, 用于关联设备树节点
     *   con_id  - 功能名称, 对应设备树中 "led-gpios" 的前缀
     *   idx     - 索引, 从0开始
     *   flags   - 初始方向和电气属性
     *
     * GPIOD_OUT_LOW: 配置为输出, 初始逻辑值低 (LED熄灭)
     * 如果设备树中标记为GPIO_ACTIVE_LOW, 初始物理电平为高
     * ========================================================== */

    data->led_count = 0;
for (i = 0; i < MAX_LEDS; i++) {
/*
         * 使用 _optional 变体: 如果索引超出设备树定义的数量,
         * 返回NULL而不是错误, 方便灵活处理不同硬件配置
         */

        data->led_gpios[i] = devm_gpiod_get_index(dev, "led", i, GPIOD_OUT_LOW);
if (IS_ERR(data->led_gpios[i])) {
            ret = PTR_ERR(data->led_gpios[i]);
if (ret == -ENOENT)
break;  /* 没有更多LED, 正常退出循环 */
            dev_err(dev, "Failed to get LED GPIO %d: %d\n", i, ret);
return ret;
        }

/*
         * gpiod_set_consumer_name: 设置消费者标签
         * 在 /sys/kernel/debug/gpio 中显示, 方便调试追踪
         */

        {
char name[16];
snprintf(name, sizeof(name), "led:%d", i);
            gpiod_set_consumer_name(data->led_gpios[i], name);
        }

        data->led_count++;
        dev_info(dev, "LED %d: active_low=%d\n", i,
                 gpiod_is_active_low(data->led_gpios[i]));
    }
    dev_info(dev, "Found %d LEDs\n", data->led_count);

/* ==========================================================
     * 第二步: 获取按键GPIO (演示 devm_gpiod_get)
     *
     * 设备树属性: button-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>
     * con_id = "button"
     *
     * GPIOD_IN: 配置为输入模式
     * 由于是GPIO_ACTIVE_LOW, 按键按下时 gpiod_get_value() 返回1
     * ========================================================== */

    data->button_gpio = devm_gpiod_get(dev, "button", GPIOD_IN);
if (IS_ERR(data->button_gpio)) {
        ret = PTR_ERR(data->button_gpio);
        dev_err(dev, "Failed to get button GPIO: %d\n", ret);
return ret;
    }

/*
     * gpiod_set_consumer_name: 标记按键GPIO的用途
     */

    gpiod_set_consumer_name(data->button_gpio, "user-button");

/*
     * gpiod_set_debounce: 设置消抖时间 (单位: 微秒)
     * 20ms消抖, 防止机械按键抖动产生多次中断
     * 如果控制器不支持消抖, 返回 -ENOTSUPP, 不影响后续功能
     */

    ret = gpiod_set_debounce(data->button_gpio, 20000);
if (ret)
        dev_warn(dev, "Debounce not supported: %d (will use software debounce)\n", ret);

/*
     * gpiod_set_config: 通用配置接口
     * 可以配置消抖、开漏、上下拉等, 使用pinconf打包格式
     * 这里演示配置10K上拉 (如果控制器支持)
     *
     * 注意: 实际使用中需要包含 <linux/pinctrl/pinconf-generic.h>
     * 并使用 PIN_CONFIG_BIAS_PULL_UP 等宏来打包配置参数
     */

/*
     * unsigned long config = pinconf_to_config_packed(PIN_CONFIG_BIAS_PULL_UP, 10000);
     * ret = gpiod_set_config(data->button_gpio, config);
     */


/*
     * gpiod_to_irq: 将GPIO描述符转换为IRQ编号
     * 返回正数IRQ编号表示成功
     * 返回负错误码表示不支持中断
     */

    data->button_irq = gpiod_to_irq(data->button_gpio);
if (data->button_irq < 0) {
        dev_warn(dev, "Button GPIO does not support IRQ: %d\n", data->button_irq);
        data->button_irq = 0;
    } else {
/*
         * request_threaded_irq: 申请线程化中断
         * - handler=NULL: 不使用硬中断处理函数
         * - thread_fn=myboard_button_isr: 中断线程处理函数
         * - IRQF_TRIGGER_FALLING: 下降沿触发 (按键按下)
         * - IRQF_ONESHOT: 保证线程执行期间中断保持屏蔽
         */

        ret = request_threaded_irq(data->button_irq,
NULL,                    /* 硬中断处理 */
                                   myboard_button_isr,      /* 线程化处理 */
                                   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"myboard-button",
                                   data);
if (ret) {
            dev_err(dev, "Failed to request IRQ %d: %d\n", data->button_irq, ret);
            data->button_irq = 0;
        } else {
            dev_info(dev, "Button IRQ %d registered\n", data->button_irq);
        }
    }

/* ==========================================================
     * 第三步: 获取可选蜂鸣器GPIO (演示 devm_gpiod_get_optional)
     *
     * 设备树属性: buzzer-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>
     *
     * _optional 变体的特点:
     * - 如果设备树中定义了该属性, 行为与 devm_gpiod_get() 完全一致
     * - 如果设备树中没有定义该属性, 返回NULL (而不是-ENOENT错误)
     * - 其他错误 (如-ENOMEM) 仍然正常返回
     *
     * 适用场景: 某些硬件版本有蜂鸣器, 某些没有
     * ========================================================== */

    data->buzzer_gpio = devm_gpiod_get_optional(dev, "buzzer", GPIOD_OUT_LOW);
if (IS_ERR(data->buzzer_gpio)) {
        ret = PTR_ERR(data->buzzer_gpio);
        dev_err(dev, "Failed to get buzzer GPIO: %d\n", ret);
return ret;
    }
if (data->buzzer_gpio) {
        gpiod_set_consumer_name(data->buzzer_gpio, "buzzer");
        dev_info(dev, "Buzzer GPIO found, active_low=%d\n",
                 gpiod_is_active_low(data->buzzer_gpio));
    } else {
        dev_info(dev, "No buzzer GPIO defined in device tree\n");
    }

/* ==========================================================
     * 第四步: 获取测试GPIO数组 (演示 devm_gpiod_get_array)
     *
     * 设备树属性: test-gpios = <&gpio1 20 ...>, <&gpio1 21 ...>, ...
     *
     * devm_gpiod_get_array 一次性获取所有GPIO, 返回:
     *   struct gpio_descs {
     *       unsigned int ndescs;       // GPIO数量
     *       struct gpio_desc *desc[];  // 描述符柔性数组
     *   };
     *
     * 批量操作优势: 如果所有GPIO属于同一控制器,
     * gpiod_set_array_value() 可以合并为一次寄存器写入
     * ========================================================== */

    data->test_gpios = devm_gpiod_get_array(dev, "test", GPIOD_OUT_LOW);
if (IS_ERR(data->test_gpios)) {
        ret = PTR_ERR(data->test_gpios);
        dev_warn(dev, "Failed to get test GPIO array: %d (non-fatal)\n", ret);
        data->test_gpios = NULL;
    } elseif (data->test_gpios) {
        dev_info(dev, "Test GPIO array: %d GPIOs\n", data->test_gpios->ndescs);

/*
         * gpiod_count: 查询设备关联的某个功能的GPIO数量
         * 与 data->test_gpios->ndescs 结果一致
         */

int count = gpiod_count(dev, "test");
        dev_info(dev, "gpiod_count(\"test\") = %d\n", count);
    }

/* ==========================================================
     * 第五步: 注册虚拟GPIO控制器 (演示提供端API)
     *
     * 这个虚拟控制器模拟4个GPIO引脚, 用于演示:
     * - gpio_chip 结构体的填充
     * - gpiochip_add_data() 注册
     * - 各回调函数的实现
     *
     * 实际项目中, 提供端API通常用于:
     * - SoC内部的GPIO控制器驱动
     * - I2C/SPI GPIO扩展芯片驱动 (如PCA953x, MCP23S17)
     * - FPGA中的软核GPIO控制器
     * ========================================================== */

    data->vchip.label = "myboard-vchip";
    data->vchip.parent = dev;
    data->vchip.owner = THIS_MODULE;

/* 核心回调函数 */
    data->vchip.request           = vchip_request;
    data->vchip.free              = vchip_free;
    data->vchip.get_direction     = vchip_get_direction;
    data->vchip.direction_input   = vchip_direction_input;
    data->vchip.direction_output  = vchip_direction_output;
    data->vchip.get               = vchip_get;
    data->vchip.get_multiple      = vchip_get_multiple;
    data->vchip.set               = vchip_set;
    data->vchip.set_multiple      = vchip_set_multiple;
    data->vchip.set_config        = vchip_set_config;
    data->vchip.dbg_show          = vchip_dbg_show;

/* 属性配置 */
    data->vchip.base = -1;       /* -1表示动态分配GPIO编号 */
    data->vchip.ngpio = 4;       /* 4个虚拟GPIO */
    data->vchip.can_sleep = false/* 纯内存操作, 不会睡眠 */

/*
     * gpiochip_add_data: 向gpiolib注册GPIO控制器
     * data指针会在回调函数中通过 gpiochip_get_data() 取回
     *
     * 注册成功后, 在 /sys/kernel/debug/gpio 中可以看到:
     *   gpiochipN: GPIOs xxx-xxx, parent: platform/myboard_gpio_demo, myboard-vchip:
     *
     * 推荐使用 devm_gpiochip_add_data() 自动管理生命周期
     */

    ret = devm_gpiochip_add_data(dev, &data->vchip, data);
if (ret) {
        dev_err(dev, "Failed to register virtual gpiochip: %d\n", ret);
return ret;
    }
    dev_info(dev, "Virtual GPIO chip registered (4 GPIOs)\n");

/* ==========================================================
     * 第六步: 创建字符设备, 暴露用户空间接口
     * ========================================================== */

    ret = alloc_chrdev_region(&data->devt, 01, DEVICE_NAME);
if (ret) {
        dev_err(dev, "Failed to alloc chrdev: %d\n", ret);
return ret;
    }

    cdev_init(&data->cdev, &myboard_fops);
    data->cdev.owner = THIS_MODULE;

    ret = cdev_add(&data->cdev, data->devt, 1);
if (ret) {
        dev_err(dev, "Failed to add cdev: %d\n", ret);
goto err_unreg_chrdev;
    }

    data->cls = class_create(CLASS_NAME);
if (IS_ERR(data->cls)) {
        ret = PTR_ERR(data->cls);
goto err_del_cdev;
    }

    data->char_dev = device_create(data->cls, dev, data->devt, NULL, DEVICE_NAME);
if (IS_ERR(data->char_dev)) {
        ret = PTR_ERR(data->char_dev);
goto err_del_class;
    }

    dev_info(dev, "Character device /dev/%s created\n", DEVICE_NAME);

/* ==========================================================
     * 第七步: 初始状态演示
     * ========================================================== */


/* 逐个点亮LED, 演示 gpiod_set_value 的Active Low自动处理 */
for (i = 0; i < data->led_count; i++) {
        gpiod_set_value(data->led_gpios[i], 1);  /* 逻辑高 = 点亮 */
        dev_info(dev, "LED %d: logical=1, active_low=%d\n",
                 i, gpiod_is_active_low(data->led_gpios[i]));
    }

/* 批量设置测试GPIO, 演示 gpiod_set_array_value */
if (data->test_gpios && data->test_gpios->ndescs > 0) {
unsignedlong bitmap = 0x0A;  /* 二进制: 1010, 即GPIO 1和3为高 */

        ret = gpiod_set_array_value(data->test_gpios->ndescs,
                                    data->test_gpios->desc,
NULL,
                                    &bitmap);
        dev_info(dev, "Test GPIOs initial set: bitmap=0x%lx, ret=%d\n",
                 bitmap, ret);
    }

    platform_set_drvdata(pdev, data);
    dev_info(dev, "Probe completed successfully\n");
return0;

err_del_class:
    class_destroy(data->cls);
err_del_cdev:
    cdev_del(&data->cdev);
err_unreg_chrdev:
    unregister_chrdev_region(data->devt, 1);
return ret;
}

/* ============================================================
 * 平台驱动remove函数
 *
 * devm_系列API会自动释放大部分资源, 这里只需处理
 * 非devm管理的资源
 * ============================================================ */

staticintmyboard_gpio_remove(struct platform_device *pdev)
{
structmyboard_gpio_data *data = platform_get_drvdata(pdev);

/* 释放中断 */
if (data->button_irq)
        free_irq(data->button_irq, data);

/*
     * 注意: 以下资源由devm_自动管理, 无需手动释放:
     * - GPIO描述符 (devm_gpiod_get系列)
     * - GPIO控制器 (devm_gpiochip_add_data)
     * - 内存 (devm_kzalloc)
     *
     * 但字符设备需要手动清理 (未使用devm版本)
     */

    device_destroy(data->cls, data->devt);
    class_destroy(data->cls);
    cdev_del(&data->cdev);
    unregister_chrdev_region(data->devt, 1);

    dev_info(data->dev, "Driver removed, button was pressed %lu times\n",
             data->button_press_count);
return0;
}

/* ============================================================
 * 设备树匹配表
 * ============================================================ */

staticconststructof_device_idmyboard_gpio_of_match[] = {
    { .compatible = "myboard,gpio-demo" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myboard_gpio_of_match);

/* ============================================================
 * 平台驱动结构体
 * ============================================================ */

staticstructplatform_drivermyboard_gpio_driver = {
    .driver = {
        .name = "myboard-gpio-demo",
        .of_match_table = myboard_gpio_of_match,
        .owner = THIS_MODULE,
    },
    .probe = myboard_gpio_probe,
    .remove = myboard_gpio_remove,
};

module_platform_driver(myboard_gpio_driver);

MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("GPIO Subsystem New API Demo Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:myboard-gpio-demo");

用户空间应用代码

/*
 * 文件: myboard_gpio_app.c
 * 说明: 用户空间GPIO控制应用程序
 *
 * 编译: gcc -o myboard_gpio_app myboard_gpio_app.c
 * 运行: ./myboard_gpio_app
 *
 * 同时演示libgpiod库的使用 (可选)
 * 编译libgpiod版本: gcc -o myboard_gpio_app myboard_gpio_app.c -lgpiod
 */


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<signal.h>

/* ioctl命令定义, 必须与驱动中一致 */
#define MYBOARD_IOC_MAGIC   'M'
#define MYBOARD_LED_ON      _IOW(MYBOARD_IOC_MAGIC, 1, int)
#define MYBOARD_LED_OFF     _IOW(MYBOARD_IOC_MAGIC, 2, int)
#define MYBOARD_LED_TOGGLE  _IOW(MYBOARD_IOC_MAGIC, 3, int)
#define MYBOARD_BTN_READ    _IOR(MYBOARD_IOC_MAGIC, 4, int)
#define MYBOARD_BUZZER_ON   _IO(MYBOARD_IOC_MAGIC, 5)
#define MYBOARD_BUZZER_OFF  _IO(MYBOARD_IOC_MAGIC, 6)
#define MYBOARD_TEST_SET    _IOW(MYBOARD_IOC_MAGIC, 7, int)
#define MYBOARD_INFO        _IOR(MYBOARD_IOC_MAGIC, 8, int)

/* GPIO信息结构体, 必须与驱动中一致 */
structmyboard_gpio_info {
int led_count;
int button_present;
int button_active_low;
int buzzer_present;
int buzzer_active_low;
int test_gpio_count;
int button_irq;
int button_cansleep;
};

staticint fd = -1;
staticvolatileint running = 1;

/* 信号处理: Ctrl+C优雅退出 */
staticvoidsignal_handler(int sig)
{
    running = 0;
}

/* ============================================================
 * 方式一: 通过ioctl接口控制GPIO (使用自定义驱动)
 * ============================================================ */


staticvoiddemo_ioctl_interface(void)
{
int ret, val;
structmyboard_gpio_infoinfo;

printf("\n========== ioctl接口演示 ==========\n\n");

/* 获取GPIO信息 */
    ret = ioctl(fd, MYBOARD_INFO, &info);
if (ret < 0) {
        perror("ioctl MYBOARD_INFO");
return;
    }

printf("GPIO信息:\n");
printf("  LED数量:        %d\n", info.led_count);
printf("  按键存在:       %s\n", info.button_present ? "是" : "否");
printf("  按键Active Low: %s\n", info.button_active_low ? "是" : "否");
printf("  按键可睡眠:     %s\n", info.button_cansleep ? "是" : "否");
printf("  按键中断号:     %d\n", info.button_irq);
printf("  蜂鸣器存在:     %s\n", info.buzzer_present ? "是" : "否");
printf("  蜂鸣器Active Low: %s\n", info.buzzer_active_low ? "是" : "否");
printf("  测试GPIO数量:   %d\n", info.test_gpio_count);
printf("\n");

/* LED逐个点亮演示 */
printf("--- LED逐个点亮 ---\n");
for (int i = 0; i < info.led_count; i++) {
printf("  点亮LED %d...\n", i);
        ioctl(fd, MYBOARD_LED_ON, &i);
        usleep(500000);  /* 500ms */

printf("  熄灭LED %d...\n", i);
        ioctl(fd, MYBOARD_LED_OFF, &i);
    }

/* LED流水灯效果 */
printf("\n--- LED流水灯 (3轮) ---\n");
for (int round = 0; round < 3; round++) {
for (int i = 0; i < info.led_count; i++) {
            ioctl(fd, MYBOARD_LED_ON, &i);
            usleep(200000);  /* 200ms */
            ioctl(fd, MYBOARD_LED_OFF, &i);
        }
    }

/* LED翻转演示 */
printf("\n--- LED翻转 ---\n");
for (int i = 0; i < info.led_count; i++) {
printf("  翻转LED %d\n", i);
        ioctl(fd, MYBOARD_LED_TOGGLE, &i);
    }
    usleep(500000);
for (int i = 0; i < info.led_count; i++) {
        ioctl(fd, MYBOARD_LED_TOGGLE, &i);  /* 恢复 */
    }

/* 按键读取演示 */
if (info.button_present) {
printf("\n--- 按键读取 ---\n");
        ioctl(fd, MYBOARD_BTN_READ, &val);
printf("  按键当前状态: %d (1=按下, 0=释放)\n", val);
    }

/* 蜂鸣器演示 */
if (info.buzzer_present) {
printf("\n--- 蜂鸣器演示 ---\n");
printf("  蜂鸣器开启 500ms...\n");
        ioctl(fd, MYBOARD_BUZZER_ON);
        usleep(500000);
        ioctl(fd, MYBOARD_BUZZER_OFF);
printf("  蜂鸣器关闭\n");
    }

/* 测试GPIO批量操作演示 */
if (info.test_gpio_count > 0) {
printf("\n--- 测试GPIO批量操作 ---\n");
for (int pattern = 0; pattern < (1 << info.test_gpio_count); pattern++) {
            ioctl(fd, MYBOARD_TEST_SET, &pattern);
printf("  设置bitmap=0x%X\n", pattern);
            usleep(200000);
        }
    }
}

/* ============================================================
 * 方式二: 通过libgpiod库直接操作GPIO (不依赖自定义驱动)
 *
 * libgpiod是Linux推荐的用户空间GPIO操作库,
 * 通过 /dev/gpiochipN 字符设备接口访问GPIO
 *
 * 注意: 使用libgpiod时需要先关闭自定义驱动, 避免GPIO冲突
 * ============================================================ */


#ifdef USE_LIBGPIOD
#include<gpiod.h>

staticvoiddemo_libgpiod_interface(void)
{
structgpiod_chip *chip;
structgpiod_line *line;
structgpiod_line_bulkbulk;
constchar *chip_name = "gpiochip0";
int ret;

printf("\n========== libgpiod接口演示 ==========\n\n");

/*
     * gpiod_chip_open: 打开GPIO控制器字符设备
     * 对应内核中的 /dev/gpiochipN
     */

    chip = gpiod_chip_open_by_name(chip_name);
if (!chip) {
        perror("gpiod_chip_open_by_name");
return;
    }
printf("已打开 %s\n", chip_name);

/*
     * gpiod_chip_get_line: 获取指定偏移的GPIO线
     * 对应设备树中 &gpio1 5 的偏移5
     */

    line = gpiod_chip_get_line(chip, 5);
if (!line) {
        perror("gpiod_chip_get_line");
goto close_chip;
    }

/*
     * gpiod_line_request_output: 请求GPIO为输出模式
     * 参数: 消费者名称, 初始值
     */

    ret = gpiod_line_request_output(line, "myboard_app"0);
if (ret < 0) {
        perror("gpiod_line_request_output");
goto close_chip;
    }

/* 闪烁LED */
printf("通过libgpiod控制GPIO 5闪烁...\n");
for (int i = 0; i < 5 && running; i++) {
        gpiod_line_set_value(line, 1);
printf("  GPIO 5 = HIGH\n");
        usleep(500000);

        gpiod_line_set_value(line, 0);
printf("  GPIO 5 = LOW\n");
        usleep(500000);
    }

/* 释放GPIO */
    gpiod_line_release(line);

/*
     * 批量操作演示: 请求多个GPIO
     */

printf("\n--- 批量操作演示 ---\n");
    {
structgpiod_line *lines[4];
int values[4] = {1010};
unsignedint offsets[4] = {20212223};

for (int i = 0; i < 4; i++) {
            lines[i] = gpiod_chip_get_line(chip, offsets[i]);
if (!lines[i]) {
                perror("gpiod_chip_get_line (bulk)");
goto close_chip;
            }
        }

/* 批量请求为输出 */
        ret = gpiod_line_request_bulk_output(lines, "myboard_bulk", values);
if (ret < 0) {
            perror("gpiod_line_request_bulk_output");
goto close_chip;
        }

printf("批量设置: GPIO 20=1, 21=0, 22=1, 23=0\n");
        usleep(1000000);

/* 批量释放 */
        gpiod_line_release_bulk(lines);
    }

/*
     * 中断/事件监听演示: 监听按键GPIO的边沿事件
     */

printf("\n--- 中断事件监听演示 (监听GPIO 3, 按Ctrl+C退出) ---\n");
    line = gpiod_chip_get_line(chip, 3);
if (line) {
/*
         * gpiod_line_request_falling_edge_events: 请求下降沿事件
         * 对应内核中的 gpiod_to_irq() + request_irq()
         */

        ret = gpiod_line_request_falling_edge_events(line, "myboard_event");
if (ret == 0) {
structgpiod_line_eventevent;

while (running) {
/*
                 * gpiod_line_event_wait: 等待事件, 超时1秒
                 * 阻塞式等待, 类似内核中的中断等待
                 */

                ret = gpiod_line_event_wait(line,
                    &(struct timespec){.tv_sec = 1, .tv_nsec = 0},
                    &event);
if (ret == 0) {
printf("  检测到下降沿事件! sec=%ld nsec=%ld\n",
                           event.ts.tv_sec, event.ts.tv_nsec);
                }
            }
            gpiod_line_release(line);
        }
    }

close_chip:
    gpiod_chip_close(chip);
printf("已关闭 %s\n", chip_name);
}
#endif/* USE_LIBGPIOD */

/* ============================================================
 * 主函数
 * ============================================================ */

intmain(int argc, char *argv[])
{
    signal(SIGINT, signal_handler);

printf("====================================\n");
printf("  GPIO子系统新API用户空间演示程序\n");
printf("====================================\n");

/* 打开字符设备 */
    fd = open("/dev/myboard_gpio", O_RDWR);
if (fd < 0) {
        perror("open /dev/myboard_gpio");
printf("提示: 请确保已加载myboard_gpio_demo内核模块\n");
return-1;
    }
printf("已打开 /dev/myboard_gpio (fd=%d)\n", fd);

/* 方式一: 通过ioctl接口 */
    demo_ioctl_interface();

#ifdef USE_LIBGPIOD
/* 方式二: 通过libgpiod库 (编译时加 -DUSE_LIBGPIOD -lgpiod) */
printf("\n注意: 使用libgpiod前请先卸载自定义驱动 (rmmod myboard_gpio_demo)\n");
printf("是否继续libgpiod演示? (y/n): ");
char c = getchar();
if (c == 'y' || c == 'Y') {
        close(fd);
        fd = -1;
        demo_libgpiod_interface();
    }
#endif

if (fd >= 0)
        close(fd);

printf("\n程序退出\n");
return0;
}

消费端API(Consumer)

API函数
使用位置
功能说明
devm_gpiod_get()
probe - 按键获取
获取单个GPIO
devm_gpiod_get_index()
probe - LED获取
按索引获取GPIO
devm_gpiod_get_optional()
probe - 蜂鸣器获取
获取可选GPIO
devm_gpiod_get_array()
probe - 测试GPIO获取
批量获取GPIO数组
gpiod_count()
probe - 测试GPIO
查询GPIO数量
gpiod_direction_input()
隐含在GPIOD_IN中
配置为输入
gpiod_direction_output()
隐含在GPIOD_OUT_LOW中
配置为输出
gpiod_get_value()
ioctl - LED读取/按键读取
读取逻辑值
gpiod_set_value()
ioctl - LED/蜂鸣器控制
设置逻辑值
gpiod_get_value_cansleep()
中断处理/ioctl
可睡眠读取
gpiod_set_value_cansleep()
(预留接口)
可睡眠设置
gpiod_set_array_value()
probe/ioctl - 批量操作
批量设置GPIO
gpiod_to_irq()
probe - 按键中断
GPIO转IRQ
gpiod_cansleep()
ioctl - 按键信息
判断是否可睡眠
gpiod_is_active_low()
probe/ioctl
查询Active Low
gpiod_set_debounce()
probe - 按键消抖
设置消抖时间
gpiod_set_consumer_name()
probe - 所有GPIO
设置调试标签
gpiod_get_direction()
ioctl - 按键信息
查询方向
gpiod_toggle_active_low()
(预留接口)
切换Active Low
gpiod_set_config()
(注释中演示)
通用配置

提供端API(Provider)

API函数/结构体
使用位置
功能说明
devm_gpiochip_add_data()
probe - 虚拟控制器
注册GPIO控制器
gpiochip_get_data()
所有vchip回调
获取私有数据
struct gpio_chip
probe - 填充
控制器结构体
.request
 回调
vchip_request
GPIO申请回调
.free
 回调
vchip_free
GPIO释放回调
.get_direction
 回调
vchip_get_direction
查询方向回调
.direction_input
 回调
vchip_direction_input
设置输入回调
.direction_output
 回调
vchip_direction_output
设置输出回调
.get
 回调
vchip_get
读取电平回调
.get_multiple
 回调
vchip_get_multiple
批量读取回调
.set
 回调
vchip_set
设置电平回调
.set_multiple
 回调
vchip_set_multiple
批量设置回调
.set_config
 回调
vchip_set_config
电气配置回调
.dbg_show
 回调
vchip_dbg_show
debugfs输出回调

pinctrl 与GPIO子系统的协作关系

一、起点:设备树中的两个控制器节点

与GPIO子系统不同,pinctrl与GPIO的协作从设备树中两个独立的控制器节点开始——一个描述引脚控制器(pinctrl),一个描述GPIO控制器(gpio),它们通过 gpio-ranges 属性建立映射关系。

/* 以 Rockchip RK3568 为例 */

/* ① Pinctrl控制器节点 —— 描述所有引脚的复用和电气配置能力 */
pinctrl: pinctrl {
    compatible = "rockchip,rk3568-pinctrl";
    rockchip,grf = <&grf>;
#address-cells = <2>;
#size-cells = <2>;
    ranges;

/* 引脚配置子节点(供其他设备引用) */
    i2c3_pins: i2c3-pins {
        rockchip,pins =
            <3 RK_PB6 1 &pcfg_pull_up>,   /* GPIO3_PB6 → I2C3_SDA */
            <3 RK_PB7 1 &pcfg_pull_up>;   /* GPIO3_PB7 → I2C3_SCL */
    };

    uart1_pins: uart1-pins {
        rockchip,pins =
            <1 RK_PB0 1 &pcfg_pull_up>,   /* GPIO1_PB0 → UART1_TX */
            <1 RK_PB1 1 &pcfg_pull_up>;   /* GPIO1_PB1 → UART1_RX */
    };

/* ② GPIO控制器节点 —— 作为pinctrl的子节点存在 */
    gpio0: gpio@fdd60000 {
        compatible = "rockchip,gpio-bank";
        reg = <0x00xfdd600000x00x100>;
        interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru PCLK_GPIO0>;
        gpio-controller;
#gpio-cells = <2>;
        interrupt-controller;
#interrupt-cells = <2>;

/* ★★★ 关键:建立GPIO编号到Pinctrl引脚编号的映射 ★★★ */
        gpio-ranges = <&pinctrl 0032>;
/*                     ↑    ↑  ↑  ↑
         *                     │    │  │  └── 数量:32个引脚
         *                     │    │  └───── pinctrl侧起始引脚编号
         *                     │    └──────── GPIO侧起始偏移
         *                     └───────────── pinctrl控制器引用
         *
         * 含义:本GPIO控制器的第0~31号引脚,
         *       对应pinctrl中第0~31号引脚
         */

    };

    gpio1: gpio@fe740000 {
        compatible = "rockchip,gpio-bank";
        reg = <0x00xfe7400000x00x100>;
        gpio-controller;
#gpio-cells = <2>;
        gpio-ranges = <&pinctrl 03232>;
/* 本GPIO控制器的第0~31号引脚,
         * 对应pinctrl中第32~63号引脚 */

    };
};

设备树中的关键信息:

属性
位置
含义
gpio-controller
GPIO节点
标记这是一个GPIO控制器
#gpio-cells = <2>
GPIO节点
GPIO说明符用2个cell(引脚号+标志)
gpio-ranges
GPIO节点
建立GPIO编号到Pinctrl引脚编号的映射
compatible = "rockchip,rk3568-pinctrl"
Pinctrl节点
匹配pinctrl驱动

二、两条独立的注册链路

Pinctrl控制器和GPIO控制器是两个独立的平台设备,各自有独立的驱动和probe函数,各自注册到各自的子系统。但它们必须在GPIO被使用前完成协作准备。

时间线 ──────────────────────────────────────────────────────────→

① Pinctrl控制器 probe 先执行
   pinctrl_probe()
       │
       ├── 填充 pinctrl_desc
       │     ├── pins[]      — 所有物理引脚描述
       │     ├── pctlops     — 引脚组枚举操作
       │     ├── pmxops      — 复用选择操作(含gpio_request_enable)
       │     └── confops     — 电气配置操作
       │
       ├── devm_pinctrl_register()
       │     │
       │     ├── 分配 pinctrl_dev
       │     ├── 注册到全局pinctrl_dev_list链表
       │     └── 解析设备树中的引脚配置节点
       │
       └── pinctrl注册完成 ✓
              │
              │  (pinctrl必须先于GPIO注册,否则GPIO申请会失败)
              ▼
② GPIO控制器 probe 后执行
   gpio_probe()
       │
       ├── 填充 gpio_chip
       │     ├── .request = gpiochip_generic_request  ← 关键!
       │     ├── .free    = gpiochip_generic_free     ← 关键!
       │     ├── .get / .set / .direction_*
       │     └── ngpio = 32
       │
       ├── devm_gpiochip_add_data()
       │     │
       │     ├── 分配 gpio_device
       │     ├── 分配 gpio_desc 数组
       │     ├── 串联三个结构体
       │     │
       │     ├── of_gpiochip_add_pin_range()  ← 关键!
       │     │     │
       │     │     ├── 解析设备树中的 gpio-ranges 属性
       │     │     ├── 找到对应的 pinctrl_dev
       │     │     └── 调用 gpiochip_add_pin_range()
       │     │           │
       │     │           └── 在pinctrl_dev中创建
       │     │               pinctrl_gpio_range 结构体
       │     │               挂入 pinctrl_dev->gpio_ranges 链表
       │     │
       │     └── 创建 /dev/gpiochipN
       │
       └── GPIO注册完成 ✓
              │
              │  此时两条链路已汇合:
              │  GPIO编号 ←→ Pinctrl引脚编号 映射已建立
              ▼
③ 消费层驱动可以安全地使用GPIO

三、核心桥梁:gpio-ranges 的解析过程

gpio-ranges 是连接两个子系统的唯一纽带。当GPIO控制器注册时,gpiolib会自动解析它:

设备树中的声明

gpio-ranges = <&pinctrl 03232>;

四个参数的含义:

<&pinctrl  03232>
    │       │   │   │
    │       │   │   └── npins:    映射的引脚数量(32个)
    │       │   └────── pin_base: pinctrl侧的起始引脚编号(32号)
    │       └────────── base:     GPIO侧的起始偏移(0号)
    └────────────────── pinctrl控制器引用

这个GPIO控制器的第0~31号引脚,对应pinctrl中第32~63号引脚。

内核解析调用链

/* 注册GPIO控制器时自动触发 */
devm_gpiochip_add_data()
    → gpiochip_add_data_with_key()
        → of_gpiochip_add()
            → of_gpiochip_add_pin_range()

of_gpiochip_add_pin_range() 的核心逻辑:

/* drivers/gpio/gpiolib-of.c */
staticintof_gpiochip_add_pin_range(struct gpio_chip *chip)
{
structdevice_node *np = chip->of_node;
int index = 0;

/* 循环解析 gpio-ranges 属性中的每一组映射 */
for (;;) {
structof_phandle_argspinspec;
structpinctrl_dev *pctldev;
int ret;

/* 从设备树读取一组 gpio-ranges */
        ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges",
3, index, &pinspec);
if (ret)
break;  /* 没有更多映射了 */

/* 根据 &pinctrl 引用找到对应的 pinctrl_dev */
        pctldev = of_pinctrl_get(pinspec.np);

/* 建立映射关系:
         * chip->base + pinspec.args[0]  →  GPIO侧起始编号
         * pinspec.args[1]               →  pinctrl侧起始引脚号
         * pinspec.args[2]               →  引脚数量
         */

        gpiochip_add_pin_range(chip,
                               pinctrl_dev_get_devname(pctldev),
                               pinspec.args[0],   /* GPIO侧偏移 */
                               pinspec.args[1],   /* pinctrl侧引脚号 */
                               pinspec.args[2]);  /* 数量 */

        index++;
    }
}

四、映射结构体:pinctrl_gpio_range

解析完成后,pinctrl核心层会创建一个 pinctrl_gpio_range 结构体,挂入 pinctrl_dev 的 gpio_ranges 链表:

/* include/linux/pinctrl/pinctrl.h */
structpinctrl_gpio_range {
structlist_headnode;/* 链表节点,挂入pinctrl_dev->gpio_ranges */
constchar *name;             /* 范围名称 */
unsignedint id;              /* 唯一ID */
unsignedint base;            /* GPIO侧起始编号 */
unsignedint pin_base;        /* Pinctrl侧起始引脚编号 */
unsignedint npins;           /* 映射的引脚数量 */
unsignedconst *pins;         /* 可选:逐个引脚映射表(非连续时使用) */
structgpio_chip *gc;/* 反向指向GPIO控制器 */
};

以RK3568的gpio1为例,注册完成后:

pinctrl_dev (rk3568-pinctrl)
    │
    ├── gpio_ranges 链表
    │     │
    │     ├── range[0]:
    │     │     base     = 0      ← gpio0的第0号引脚
    │     │     pin_base = 0      ← pinctrl的第0号引脚
    │     │     npins    = 32     ← 共32
    │     │     gc       → gpio_chip (gpio0)
    │     │
    │     └── range[1]:
    │           base     = 0      ← gpio1的第0号引脚
    │           pin_base = 32     ← pinctrl的第32号引脚
    │           npins    = 32     ← 共32
    │           gc       → gpio_chip (gpio1)
    │
    └── pin_desc_tree
          ├── pin[0]  → "GPIO0_A0"
          ├── pin[1]  → "GPIO0_A1"
          ├── ...
          ├── pin[32] → "GPIO1_A0"
          ├── pin[33] → "GPIO1_A1"
          └── ...

这个映射表的作用: 当gpiolib收到一个GPIO操作请求时,可以通过这个映射表将GPIO编号转换为pinctrl引脚编号,从而调用pinctrl的相应接口。

五、协作的核心场景:GPIO申请时的冲突检查

两个子系统协作最关键的场景发生在消费层驱动申请GPIO时。gpiolib会回调pinctrl,确保该引脚没有被复用为其他功能。 完整调用链

消费层驱动调用 gpiod_get()
    │
    │  设备树中: gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
    │  含义:使用gpio1控制器的第5号引脚
    ▼
gpiod_get()
    → gpiod_request()
        → gpiod_request_commit()
            │
            │  设置 FLAG_REQUESTED
            │
            ├── chip->request(chip, offset)
            │   │
            │   │  chip->request 指向 gpiochip_generic_request
            │   ▼
            │   gpiochip_generic_request(chip, offset)
            │       │
            │       │  检查是否有pin_ranges
            │       │  如果有,说明pinctrl存在,需要通知pinctrl
            │       ▼
            │       pinctrl_gpio_request(chip->gpiodev->base + offset)
            │           │
            │           │  参数是GPIO全局编号
            │           ▼
            │           pinctrl_gpio_request()
            │               │
            │               ├── ① 查表:遍历 gpio_ranges 链表
            │               │      找到包含该GPIO编号的 range
            │               │      将GPIO编号转换为pinctrl引脚编号
            │               │
            │               │      pin = gpio_to_pin(range, gpio)
            │               │      例如:gpio1的第5号引脚
            │               │            → 全局GPIO编号 = 32+5 = 37
            │               │            → pinctrl引脚号 = 32+5 = 37
            │               │
            │               ├── ② 冲突检查:该引脚是否已被复用?
            │               │      调用 pinmux_request_gpio()
            │               │      检查该引脚当前是否被I2C/UART/SPI占用
            │               │
            │               ├── ③ 如果空闲:调用 pmxops->gpio_request_enable()
            │               │      通知pinctrl驱动:"这个引脚现在作为GPIO使用了"
            │               │      pinctrl驱动将引脚复用寄存器切换到GPIO模式
            │               │
            │               └── ④ 如果被占用:返回 -EBUSY
            │                      gpiod_request 失败
            │                      消费层驱动收到错误
            │
            └── 申请成功,返回 gpio_desc *

关键函数源码

/* drivers/gpio/gpiolib.c */
intgpiochip_generic_request(struct gpio_chip *chip, unsigned offset)
{
#ifdef CONFIG_PINCTRL
/* 如果该控制器没有注册pin_ranges,说明不依赖pinctrl,直接返回成功 */
if (list_empty(&chip->gpiodev->pin_ranges))
return0;
#endif
/* 有pinctrl,通知pinctrl子系统 */
return pinctrl_gpio_request(chip->gpiodev->base + offset);
}
/* drivers/pinctrl/core.c */
intpinctrl_gpio_request(unsigned gpio)
{
structpinctrl_dev *pctldev;
structpinctrl_gpio_range *range;
int pin;

/* ① 通过gpio_ranges链表,找到对应的pinctrl_dev和range */
    ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
if (ret)
return ret;

    mutex_lock(&pctldev->mutex);

/* ② GPIO编号 → pinctrl引脚编号 */
    pin = gpio_to_pin(range, gpio);

/* ③ 冲突检查 + 切换到GPIO模式 */
    ret = pinmux_request_gpio(pctldev, range, pin, gpio);

    mutex_unlock(&pctldev->mutex);
return ret;
}
/* drivers/pinctrl/pinmux.c */
intpinmux_request_gpio(struct pinctrl_dev *pctldev,
                        struct pinctrl_gpio_range *range,
unsigned pin, unsigned gpio)

{
/* 检查该引脚是否已被其他功能占用 */
if (desc->mux_usecount && !desc->gpio) {
/* 已被I2C/UART/SPI等占用,拒绝GPIO申请 */
return -EBUSY;
    }

/* 调用pinctrl驱动的 gpio_request_enable 回调 */
if (pctldev->desc->pmxops->gpio_request_enable) {
        ret = pctldev->desc->pmxops->gpio_request_enable(pctldev, range, pin);
    }

/* 标记该引脚现在被GPIO占用 */
    desc->mux_usecount = 1;
    desc->gpio = gpio;
return0;
}

六、另一个关键场景:方向切换时的协作

当消费层驱动调用 gpiod_direction_input() 或 gpiod_direction_output() 时,gpiolib同样会通知pinctrl:

/* gpiolib内部实现(简化) */
intgpiod_direction_input(struct gpio_desc *desc)
{
structgpio_chip *gc = desc->gdev->chip;
unsignedint offset = gpio_chip_hwgpio(desc);

/* ① 通知pinctrl:这个引脚要切换为输入方向 */
if (gc->request) {
/* 部分驱动在direction_input中也会调用pinctrl */
    }
    pinctrl_gpio_direction_input(gc->gpiodev->base + offset);

/* ② 调用gpio_chip的direction_input回调,操作硬件寄存器 */
return gc->direction_input(gc, offset);
}
/* drivers/pinctrl/core.c */
intpinctrl_gpio_direction_input(unsigned gpio)
{
structpinctrl_dev *pctldev;
structpinctrl_gpio_range *range;
int pin;

/* GPIO编号 → pinctrl引脚编号 */
    pinctrl_get_device_gpio_range(gpio, &pctldev, &range);
    pin = gpio_to_pin(range, gpio);

/* 调用pinctrl驱动的 gpio_set_direction 回调 */
return pctldev->desc->pmxops->gpio_set_direction(pctldev, range, pin, true);
/*                                                                    ↑
    *                                                              true = 输入方向
    */

}

pinctrl驱动的 gpio_set_direction 回调会修改SoC的方向寄存器,确保引脚的电气方向与GPIO子系统的逻辑方向一致。

非GPIO场景:pinctrl独立完成复用配置

当引脚不是作为GPIO使用,而是作为I2C/UART/SPI等外设功能时,pinctrl独立工作,完全不经过GPIO子系统:

/* 设备树中I2C控制器引用pinctrl配置 */
&i2c3 {
    status = "okay";
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2c3_pins>;     /* 引用pinctrl中的引脚配置 */
};

调用链:

I2C控制器 probe()
    │
    │  内核自动调用(驱动框架层面,在probe之前)
    ▼
pinctrl_select_default_state(dev)
    │
    ├── 解析设备树中 pinctrl-0 引用的 i2c3_pins 节点
    │
    ├── 调用 pinctrl_ops->dt_node_to_map()
    │   将设备树节点转换为 pinctrl_map 数组
    │
    ├── 调用 pinmux_ops->set_mux()
    │   将引脚复用寄存器切换到I2C功能
    │   (此时这些引脚不再是GPIO模式)
    │
    └── 调用 pinconf_ops->pin_config_set()
        配置上拉电阻、驱动强度等电气特性

此时这些引脚被I2C功能独占,如果GPIO子系统尝试申请这些引脚,pinctrl会返回 -EBUSY。

完整交汇图

┌─────────────────────────────────────────────────────────────────────┐
│                    Pinctrl 子系统                                   │
│                                                                     │
│  全局链表 pinctrl_dev_list                                          │
│      │                                                              │
│      ├──▶ pinctrl_dev (rk3568-pinctrl)                             │
│      │       │                                                      │
│      │       ├── desc ──▶ pinctrl_desc                              │
│      │       │               ├── pins[] ── 所有物理引脚描述         │
│      │       │               ├── pctlops ── 引脚组枚举              │
│      │       │               ├── pmxops ── 复用选择                 │
│      │       │               │         ├── set_mux()                │
│      │       │               │         ├── gpio_request_enable() ◀──┼── GPIO申请时回调
│      │       │               │         ├── gpio_disable_free()   ◀──┼── GPIO释放时回调
│      │       │               │         └── gpio_set_direction()  ◀──┼── 方向切换时回调
│      │       │               └── confops ── 电气配置                │
│      │       │                                                      │
│      │       ├── gpio_ranges 链表  ◀─────────────────────────────── │ ── 映射表
│      │       │     │                                                │    GPIO编号
│      │       │     ├── range[0]: base=0, pin_base=0, npins=32       │    ←→
│      │       │     │              gc → gpio_chip(gpio0)             │    Pinctrl引脚号
│      │       │     │                                                │
│      │       │     └── range[1]: base=0, pin_base=32, npins=32      │
│      │       │                    gc → gpio_chip(gpio1)             │
│      │       │                                                      │
│      │       └── pin_desc_tree                                      │
│      │             ├── pin[0]  → "GPIO0_A0" (mux_usecount, gpio)   │
│      │             ├── pin[1]  → "GPIO0_A1"                        │
│      │             ├── ...                                          │
│      │             └── pin[63] → "GPIO1_D7"                        │
│      │                                                              │
└──────┼──────────────────────────────────────────────────────────────┘
       │
       │  gpio_ranges 映射表
       │  将GPIO编号转换为Pinctrl引脚编号
       │
┌──────┼──────────────────────────────────────────────────────────────┐
│      │          GPIO 子系统                                         │
│      ▼                                                              │
│  全局链表 gpio_devices                                              │
│      │                                                              │
│      ├──▶ gpio_device (gpiochip0)                                  │
│      │       ├── chip ──▶ gpio_chip                                 │
│      │       │              ├── .request = gpiochip_generic_request │
│      │       │              │        ↓ 内部调用                     │
│      │       │              │   pinctrl_gpio_request() ─────────────┼──→ 查gpio_ranges
│      │       │              │                                        │   → 冲突检查
│      │       │              ├── .free = gpiochip_generic_free       │   → gpio_request_enable
│      │       │              │        ↓ 内部调用                     │
│      │       │              │   pinctrl_gpio_free() ────────────────┼──→ gpio_disable_free
│      │       │              │                                        │
│      │       │              ├── .get / .set / .direction_*          │
│      │       │              └── base=0, ngpio=32                    │
│      │       │                                                      │
│      │       └── descs ──▶ gpio_desc[0..31]                        │
│      │                                                              │
│      ├──▶ gpio_device (gpiochip1)                                  │
│      │       ├── chip ──▶ gpio_chip                                 │
│      │       │              ├── .request = gpiochip_generic_request │
│      │       │              └── base=32, ngpio=32                   │
│      │       └── descs ──▶ gpio_desc[0..31]                        │
│      │                                                              │
└─────────────────────────────────────────────────────────────────────┘

维度
Pinctrl子系统
GPIO子系统
核心问题
这个引脚连到了哪个外设?电气特性是什么?
这个引脚当前是高还是低?输入还是输出?
操作对象
Pin Group、Function、Mux寄存器、Pull-up/down
gpio_desc、逻辑值(0/1)、方向(Input/Output)
典型配置
复用选择(Mux)、驱动强度、上下拉、开漏/推挽
方向控制、逻辑读写、中断触发边沿
硬件对应
Pinmux/Pinconf寄存器组
GPIO Data/Direction寄存器组
使用者
设备驱动(初始化时)、GPIO子系统本身
外设驱动(运行时)、用户空间(libgpiod)
操作时机
设备probe前自动配置、运行时动态切换
设备probe中申请、运行时随时读写
抽象模型
pinctrl_dev + pinmux_ops + pinconf_ops
gpio_chip + gpio_desc

Pinctrl管"引脚的物理状态"(连到哪个外设、电气特性),GPIO管"引脚的逻辑功能"(高低电平、输入输出方向)。GPIO是Pinctrl的一种复用功能(function = gpio),两者通过 gpio-ranges 映射表和 gpiochip_generic_request 回调实现协作。

时序图

时间线 ──────────────────────────────────────────────────────────→

① 设备树解析
   内核解析 .dtb,发现两个控制器节点:
   ├── pinctrl: compatible = "rockchip,rk3568-pinctrl"
   └── gpio0:   compatible = "rockchip,gpio-bank"
                gpio-ranges = <&pinctrl 0032>
        │
② Pinctrl控制器 probe(先执行)
   pinctrl_probe()
   ├── 填充 pinctrl_desc(pins[]、pctlops、pmxops、confops)
   ├── devm_pinctrl_register()
   │     ├── 分配 pinctrl_dev
   │     ├── 注册到全局链表
   │     └── 解析设备树中的引脚配置子节点(i2c3_pins、uart1_pins等)
   └── pinctrl就绪 ✓
        │
③ GPIO控制器 probe(后执行)
   gpio_probe()
   ├── 填充 gpio_chip
   │     ├── .request = gpiochip_generic_request  ← 桥接pinctrl
   │     ├── .free    = gpiochip_generic_free
   │     └── .get/.set/.direction_*
   ├── devm_gpiochip_add_data()
   │     ├── 分配 gpio_device、gpio_desc 数组
   │     ├── 串联三个结构体
   │     │
   │     ├── of_gpiochip_add_pin_range()  ← 解析 gpio-ranges
   │     │     ├── 找到 pinctrl_dev
   │     │     └── 创建 pinctrl_gpio_range,挂入 gpio_ranges 链表
   │     │
   │     └── 创建 /dev/gpiochipN
   └── GPIO就绪 ✓
        │
        │  ★ 两条链路在此汇合 ★
        │  GPIO编号 ←→ Pinctrl引脚编号 映射已建立
        │
④ 消费层驱动使用GPIO
   gpiod_get()
   → gpiod_request()
       → chip->request()
           → gpiochip_generic_request()
               → pinctrl_gpio_request()
                   → 查 gpio_ranges 映射表
                   → GPIO编号转换为pinctrl引脚编号
                   → 冲突检查(引脚是否被I2C/UART占用?)
                   → 调用 gpio_request_enable() 切换到GPIO模式
                   → 返回成功
        │
⑤ 消费层操作GPIO
   gpiod_set_value(desc, 1)
   → desc->gdev->chip->set()  → 操作GPIO数据寄存器
   (此时pinctrl不参与,GPIO子系统独立操作硬件)
        │
⑥ 消费层释放GPIO
   gpiod_put(desc)
   → gpiod_free()
       → chip->free()
           → gpiochip_generic_free()
               → pinctrl_gpio_free()
                   → 调用 gpio_disable_free()
                   → 释放引脚的GPIO占用标记

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-23 06:07:09 HTTP/2.0 GET : https://f.mffb.com.cn/a/511887.html
  2. 运行时间 : 0.241494s [ 吞吐率:4.14req/s ] 内存消耗:4,984.09kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=1e9d6a55155c96b860932a8ec04b18d1
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001062s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001839s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.004856s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.001764s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001762s ]
  6. SELECT * FROM `set` [ RunTime:0.000648s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001923s ]
  8. SELECT * FROM `article` WHERE `id` = 511887 LIMIT 1 [ RunTime:0.002349s ]
  9. UPDATE `article` SET `lasttime` = 1787436429 WHERE `id` = 511887 [ RunTime:0.031458s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.005221s ]
  11. SELECT * FROM `article` WHERE `id` < 511887 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001565s ]
  12. SELECT * FROM `article` WHERE `id` > 511887 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.003720s ]
  13. SELECT * FROM `article` WHERE `id` < 511887 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.003644s ]
  14. SELECT * FROM `article` WHERE `id` < 511887 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.005291s ]
  15. SELECT * FROM `article` WHERE `id` < 511887 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.014658s ]
0.245184s