08 GPIO子系统
GPIO子系统
GPIO子系统是 Linux 内核中用于管理和控制通用输入/输出引脚的核心框架,它为开发者提供了统一的接口来操作硬件上的 GPIO 引脚,使用前需要用pinctrl将该引脚的复用配置成GPIO
驱动架构
GPIO子系统的驱动同样遵循着“主机驱动和设备驱动分离”和“驱动分层”的思想,分为主机驱动层、核心层、设备驱动层
主机驱动层
主机(GPIO控制器)驱动一般由原厂提供,且也作为设备树中的一个节点
SoC内部对于GPIO一般都有专门的控制器外设,它直接位于SoC的内存空间,通过配置该外设的寄存器,从而控制某个具体的GPIO
1 | /{ |
gpio_chip
gpio_chip用于统一抽象GPIO控制器的操作接口,让上层和 gpiolib不需要关心底层寄存器实现,硬件上SoC中每个GPIO控制器,在驱动中都需要创建一个gpio_chip实例进行注册
1 | struct gpio_chip { |
调用链:
1 | gpiod_set_value(desc, 1) |
核心层
GPIO核心层的名字叫gpiolib,作用:
- 向下为gpio chip driver提供注册 struct gpio_chip 的接口:gpiochip_xxx()
- 向上为gpio consumer driver提供引用 gpio 的接口:gpiod_xxx()
- 实现字符设备的功能
- 注册 sysfs
1 | 一个 GPIO 控制器驱动注册一个 gpio_chip |
内核对于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.h,gpiolib_of.h,consumer.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 | gpios = <0 |
1 | // 从设备树中获取GPIO编号 |
新版API
- 方式1:设备树中的属性名必须叫==xxx-gpios==,xxx对应
cond_id形参,代表该引脚的的功能- 如果
con_id为NULL,直接查找gpios属性 - 如果
con_id非NULL,查找"<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 | struct gpio_desc *gpiod_get_from_of_node( |
gpio_desc
gpio_desc 是对单个引脚的抽象。 旧内核中的GPIO 编号(int 型变量)描述单个引脚
1 | struct gpio_desc { |
gpio_device
gpio_device 是 Linux 内核中用于管理单个 GPIO 控制器的核心对象,通过集成字符设备驱动框架,将对于GPIO Chip的控制能力暴露给应用层,每个控制器都对应一个 /dev/gpiochipN 设备节点
1 | struct gpio_device { |
设备驱动层
设备驱动层要用在设备树中加入GPIO属性,举个例子
1 | beep { |
与GPIO子系统相关的属性为beep-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;一般遵循以下格式:
1 | gpio-name = <&gpio_controller pin_number flags> |
调试
sysfs
ls /sys/class/gpio可以看本机的gpio控制器的信息,后面的数字代表该控制器的基号
1 | export gpiochip128 gpiochip64 unexport |
gpiochipX目录的结构如下:
1 | /sys/devices/platform/soc/2000000.aipsbus/20a4000.gpio/gpio/gpiochip64$:ls |
- ngpio:该控制器所含的引脚数量
- base:该控制器的基号:所管理的第一个GPIO引脚在全局GPIO编号空间中的起始偏移量
libgpiod
libgpiod 是一个用 C 语言编写的用于访问 gpio chardev 的库,同时里面包含了一些访问 gpio 的命令行工具,推荐优先采用这个库来访问gpio
1 | # 列出GPIO芯片 |
应用层控制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_desc,gpio_chip
