GPIO子系统

GPIO子系统是 Linux 内核中用于管理和控制通用输入/输出引脚的核心框架,它为开发者提供了统一的接口来操作硬件上的 GPIO 引脚,使用前需要用pinctrl将该引脚的复用配置成GPIO

驱动架构

GPIO子系统的驱动同样遵循着“主机驱动和设备驱动分离”和“驱动分层”的思想,分为主机驱动层、核心层、设备驱动层

image-20251026172135537

主机驱动层

主机(GPIO控制器)驱动一般由原厂提供,且也作为设备树中的一个节点

SoC内部对于GPIO一般都有专门的控制器外设,它直接位于SoC的内存空间,通过配置该外设的寄存器,从而控制某个具体的GPIO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/{
soc{
aips1{
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
}
}
}

gpio_chip

gpio_chip用于统一抽象GPIO控制器的操作接口,让上层和 gpiolib不需要关心底层寄存器实现,硬件上SoC中每个GPIO控制器,在驱动中都需要创建一个gpio_chip实例进行注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct gpio_chip {
const char *label; // 控制器名称
struct device *parent; // 对应 platform/device
// 在驱动中申请/释放 GPIO 的回调
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
//设置某个 GPIO 为输入的函数
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
//设置 GPIO 为输出并写初值
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
// 读取 GPIO 电平
int (*get)(struct gpio_chip *chip, unsigned offset);
// 写 GPIO 电平
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
// 将某GPIO映射成中断号
int (*to_irq)(struct gpio_chip *gc, unsigned int offset);
int ngpio; // 该控制器管理的 GPIO 数量
unsigned long base; // 起始全局编号(legacy 用)
struct module *owner; // 所属模块
...
};

调用链:

1
2
3
4
5
6
7
8
9
10
gpiod_set_value(desc, 1)

gpiod_set_raw_value()

chip = desc->chip;
chip->set(chip, offset, 1) ← 调用具体 SoC 驱动实现

imx_gpio_set() / stm32_gpio_set() / ...

写硬件寄存器(GPIOx_ODR)

核心层

GPIO核心层的名字叫gpiolib,作用:

  • 向下为gpio chip driver提供注册 struct gpio_chip 的接口:gpiochip_xxx()
  • 向上为gpio consumer driver提供引用 gpio 的接口:gpiod_xxx()
  • 实现字符设备的功能
  • 注册 sysfs
1
2
3
4
5
6
7
一个 GPIO 控制器驱动注册一个 gpio_chip

gpiolib 会为其创建一个 gpio_device(对应一个 /dev/gpiochipX)

gpio_device 内部维护一个 gpio_desc 数组(每个引脚一个 desc)

驱动或子系统通过 gpio_desc 来操作具体引脚

内核对于GPIO的操作有2套API:

  • gpio_*:用GPIO编号(int变量)描述一个GPIO(正逐步移除)
  • gpiod_*:用struct gpio_desc描述一个GPIO
功能 旧版 API (gpio_*) 新版 API (gpiod_*)
申请 GPIO gpio_request() gpiod_get()
释放 GPIO gpio_free() gpiod_put()
设置方向 gpio_direction_input() gpiod_direction_input()
读写电平 gpio_get/set_value() gpiod_get/set_value()
中断绑定 gpio_to_irq() gpiod_to_irq()
依赖头文件 <linux/gpio.h> <linux/gpio/consumer.h>

设备树

和设备树相关的API不仅仅存在于设备树子系统(of.h),实际上每个驱动子系统一般都会有自己独立的属性,如果把所有驱动跟设备树相关的属性都放到同一个文件,那么就会太庞大了。所以Linux内核将不同子系统特定属性的读写,放到对应的核心层来实现。比如一些属性只有GPIO子系统才有,那对于这些属性的读写,就由GPIO子系统的核心层来实现:of_gpio.hgpiolib_of.hconsumer.h…好几个文件都有跟设备树相关的API

常见API:

函数 作用 文件
of_get_named_gpio(struct device_node *np, const char *propname, int index) 从节点的指定属性中解析出第 index 个 GPIO drivers/gpio/gpiolib-of.c
of_get_named_gpio_flags(struct device_node *np, const char *propname, int index, enum of_gpio_flags *flags) 同上,但同时返回极性标志(高低有效) 同上
of_gpio_named_count(struct device_node *np, const char *propname) 返回属性中 GPIO 的数量 同上

申请单个GPIO

旧版API:

  • 需要手动指定设备树中的属性名以及pin的index
    • index指的是,一个属性可能同时包含多个pin,比如下面的例子,index就是指这里的索引
1
2
3
4
gpios = <0
&gpio1 1 2
0
&gpio2 3 4>;
1
2
3
// 从设备树中获取GPIO编号
led_device->gpio_index = of_get_named_gpio(led_device->device_tree_node, "led-gpio", 0);
gpio_request(led_device->gpio_index, "led0");

新版API

  • 方式1:设备树中的属性名必须叫==xxx-gpios==,xxx对应cond_id形参,代表该引脚的的功能
    • 如果 con_idNULL,直接查找 gpios 属性
    • 如果 con_idNULL,查找 "<con_id>-gpios" 属性(如 "led-gpios"
1
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags);
  • 方式2:同样需要手动指定设备树中的属性名以及pin的index,但是返回的是gpio_desc
1
2
3
4
5
6
7
struct gpio_desc *gpiod_get_from_of_node(
struct device_node *node,
const char *propname, // 自定义属性名(如 "my-gpios")
int index,
enum gpiod_flags flags,
const char *label
);

gpio_desc

gpio_desc 是对单个引脚的抽象。 旧内核中的GPIO 编号(int 型变量)描述单个引脚

1
2
3
4
5
6
7
8
9
struct gpio_desc {
struct gpio_device *gdev; // 指向所属 gpio_device
unsigned long flags; // 状态标志位 (input/output/active-low等)
struct device *dev; // sysfs 节点对应的设备
const char *label; // 用户自定义标签
struct gpio_chip *chip; // 对应的 gpio_chip
struct gpio_irq_chip *irqchip; // 关联的中断控制器(若支持)
...
};

gpio_device

gpio_device 是 Linux 内核中用于管理单个 GPIO 控制器的核心对象,通过集成字符设备驱动框架,将对于GPIO Chip的控制能力暴露给应用层,每个控制器都对应一个 /dev/gpiochipN 设备节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct gpio_device {
struct device dev; // 与设备模型集成的 device 对象
struct module *owner; // 所属模块
struct gpio_chip *chip; // 指向对应的 gpio_chip
struct cdev chardev; // 对应 /dev/gpiochipN 的字符设备
struct device *mockdev; // sysfs 模拟设备

struct list_head list; // 链入全局 gpio_devices 列表
unsigned int id; // 控制器编号(gpiochipN 中的 N)
const char *label; // 控制器标签,如 “gpio-0”

unsigned int base; // 起始编号(旧接口用)
unsigned int ngpio; // 该控制器管理的引脚数量
struct gpio_desc *descs; // 引脚描述符数组(ngpio 个)

/* 用于字符设备接口 */
struct mutex mutex; // 互斥锁
struct device_node *of_node; // 对应的设备树节点
struct list_head irq_list; // 对应中断列表
...
};

设备驱动层

设备驱动层要用在设备树中加入GPIO属性,举个例子

1
2
3
4
5
6
7
8
9
beep {
#address-cells = <1>;
#size-cells = <1>;
compatible = "beep";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
};

与GPIO子系统相关的属性为beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;一般遵循以下格式:

1
2
gpio-name = <&gpio_controller pin_number flags>
// flags: GPIO_ACTIVE_HIGH/LOW

调试

sysfs

ls /sys/class/gpio可以看本机的gpio控制器的信息,后面的数字代表该控制器的基号

1
2
export       gpiochip128  gpiochip64   unexport
gpiochip0 gpiochip32 gpiochip96

gpiochipX目录的结构如下:

1
2
/sys/devices/platform/soc/2000000.aipsbus/20a4000.gpio/gpio/gpiochip64$:ls
base device label ngpio power subsystem uevent
  • ngpio:该控制器所含的引脚数量
  • base:该控制器的基号:所管理的第一个GPIO引脚在全局GPIO编号空间中的起始偏移量

libgpiod

libgpiod 是一个用 C 语言编写的用于访问 gpio chardev 的库,同时里面包含了一些访问 gpio 的命令行工具,推荐优先采用这个库来访问gpio

1
2
3
4
5
6
7
8
9
10
11
# 列出GPIO芯片
gpiodetect

# 查看chip的线
gpioinfo

# 设置输出高电平
gpioset gpiochip0 17=1

# 读取输入电平
gpioget gpiochip0 17

应用层控制GPIO

应用层控制GPIO有以下几种方式

方式 特点 适用场景
/sys/class/gpio 接口(传统 sysfs) 最常见、最简单;但已废弃 旧版系统、调试用
对gpiolib创建的字符设备进行IO操作 官方推荐;基于 character device 新系统(内核 ≥ 4.8)
通过驱动导出的接口(ioctl、sysfs 属性、设备文件) 自定义控制方式 特殊硬件驱动
通过 /dev/mem 直接映射寄存器 不推荐;需 root 权限 硬件调试、无驱动情况
通过LED等其他子系统间接控制 间接控制(通过更高层框架) 控制灯、按键等

面试题

1.谈一下你对GPIO子系统的理解

  • 控制 GPIO 引脚的输入、输出
  • 注册和管理中断
  • 提供用户态访问接口
  • 支持设备树描述 GPIO
  • 为上层驱动屏蔽底层硬件差异

2.内核驱动框架是如何描述一个GPIO的属性和操作方法

  • gpio_descgpio_chip

参考链接