引言
GPIO是高频使用的硬件资源,所以Linux专门搞了一套GPIO子系统来对其进行管理,目的就是方便大家使用片上或板上的GPIO资源。
虽然之前已经利用GPIO子系统来实现过LED的驱动和按键的中断驱动了,但是对GPIO子系统的原理和结构还是比较模糊的,所以在这篇博文中仔细学习一下GPIO子系统。
之前用GPIO子系统来实现LED的驱动和按键的中断驱动的博文链接如下:
http://iyenn.com/rec/1709341.html
http://iyenn.com/rec/1709342.html
重要概念:对各个GPIO口的操作,是通过GPIO控制器实现的
对各个 GPIO 口(GPIO 引脚)的操作,是通过 GPIO 控制器(GPIO Controller)实现的。
在 Linux 内核中,GPIO 控制器由 GPIO 控制器驱动 管理,并通过 GPIO 子系统(gpiolib) 统一提供 API,使用户空间和其他内核驱动可以方便地访问 GPIO 口。
GPIO 控制器(GPIO Controller) 是一个管理多个 GPIO 引脚的硬件模块,通常集成在 SoC(如 IMX6ULL)中。它的主要作用是:
- 通过 寄存器 控制 GPIO 口的 输入/输出 模式
- 允许 使能/禁用 GPIO 口
- 提供 中断控制(如果支持)
- 管理 复用功能(Muxing)(部分引脚可能被复用为其他功能,如 I2C、UART、PWM)
在 Linux 中,GPIO 控制器通常会注册为一个 gpio_chip
,并由 GPIO 子系统(gpiolib)
进行统一管理。对于 IMX6ULL,GPIO 控制器由 gpio-mxc
驱动管理。
GPIO子系统(gpiolib)的原理
问:gpiolib
是不是就是我们所说的GPIO子系统?
是的,gpiolib
就是我们通常所说的 GPIO 子系统(GPIO Subsystem)!
1. 什么是 gpiolib
?
gpiolib
(GPIO Library)是 Linux 内核中的 GPIO 子系统,它提供了一套 统一的 GPIO 处理框架,用于管理不同 SoC 平台的 GPIO 控制器。
它的主要功能包括:
- 管理 GPIO 控制器(gpiochip):支持多个 GPIO 控制器(比如
gpiochip0
、gpiochip1
)。 - 提供
/sys/class/gpio/
接口(Legacy sysfs,较老的方法)。 - 支持
gpiod_*()
API(更现代的libgpiod
接口)。 - 支持 GPIO 消息通知(如
gpio_request_irq()
处理中断)。 - 抽象不同硬件的 GPIO 访问方式(比如基于 I2C、SPI、SoC 内部 GPIO 控制器)。
2. gpiolib
与 GPIO 子系统的关系
在 Linux 内核架构中:
- GPIO 子系统 =
gpiolib
+ 各个 GPIO 驱动 - GPIO 控制器(gpiochip)是具体的硬件设备,而
gpiolib
负责提供管理框架。
可以简单理解为:
Linux 内核
└── GPIO 子系统(gpiolib)
├── GPIO 控制器驱动(如 `fsl-imx6ul-gpio`)
├── GPIO 用户接口(sysfs、libgpiod、device tree)
├── GPIO 虚拟总线(/sys/bus/gpio/)
- 1
- 2
- 3
- 4
- 5
3.gpio_chip
结构体的介绍(核心数据结构)
gpio_chip
结构体是 Linux 内核 GPIO 子系统(gpiolib) 用来表示 GPIO 控制器(GPIO Controller) 的数据结构,每个 GPIO 控制器都必须有一个对应的 gpio_chip
结构体。可以说它是GPIO 子系统(gpiolib)的核心数据结构。
struct gpio_chip {
const char *label; // 控制器名称
struct device *parent; // 父设备
int base; // GPIO 编号基地址(-1 代表动态分配)
u16 ngpio; // 控制的 GPIO 数量
struct module *owner; // 所属驱动模块
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
作用
- 代表 一个 GPIO 控制器,它可以管理多个 GPIO 引脚。
- 定义了 GPIO 操作函数(如
set()
、get()
、direction_output()
)。 - 由
gpiolib
统一管理,并向用户空间提供/sys/class/gpio/
接口。
GPIO子系统的正常工作需要BSP的支持
GPIO子系统使得我们在屏蔽硬件底层操作的情况下,方便的实现对GPIO口的控制。由于这套系统存在,我们的系统启动后就能直接操作GPIO口。
底层的操作包含在BSP(Board Support Package,板级支持包) 中,所以gpio_chip
结构体通常由 BSP(Board Support Package,板级支持包) 提供。
所以GPIO子系统的实现是需要BSP支持的。
4. gpiolib
的工作方式
当 GPIO 控制器驱动(比如 fsl-imx6ul-gpio
)加载时,它会:
-
注册
gpio_chip
结构体:- 将
gpio_chip
结构体通过gpiochip_add_data()
或devm_gpiochip_add_data()
向gpiolib
注册 ,从而实现GPIO 控制器的注册。 gpio_chip
结构体注册完成后,gpiochip
设备(即GPIO控制器)会出现在/sys/class/gpio/gpiochipX
,同时也会出现在/sys/bus/gpio/devices/
。★★★
- 将
-
管理 GPIO 资源:
- 允许用户空间访问 GPIO(通过用户接口
sysfs
或libgpiod
实现)。 - 提供
gpiod_get()
/gpiod_direction_output()
/gpiod_set_value()
等 API 供内核调用。
- 允许用户空间访问 GPIO(通过用户接口
-
支持 GPIO 消息通知:
- 处理 GPIO 输入/输出方向配置。
- 允许 GPIO 触发中断(IRQ)。
5. 结论
✅ gpiolib
就是 GPIO 子系统,它提供了统一的 GPIO 处理框架。
✅ GPIO 控制器驱动(如 fsl-imx6ul-gpio
)通过 gpiochip_add_data()
接口向 gpiolib
注册,成为 gpiochipX
设备。
✅ 用户可以通过 sysfs
(较老方式)或 libgpiod
(新方式)访问 GPIO,gpiolib
负责调度。
所以,GPIO 子系统 = gpiolib
,它是 Linux 内核管理 GPIO 资源的核心机制! ?
片外的GPIO资源如何管理?
GPIO 子系统(gpiolib)通常用于管理片上(SoC 内部)的 GPIO 资源,而不是直接管理片外(外部扩展)的 GPIO 资源。
1. 片上 GPIO(On-Chip GPIO)
片上的 GPIO 指的是 SoC 自带的 GPIO 控制器所管理的 GPIO 引脚,例如:
- i.MX6ULL 的
gpio1
~gpio5
(如209c000.gpio
)。 - 这些 GPIO 控制器通常由 SoC 内部的寄存器控制,并通过
gpiolib
统一管理。 - 在设备树(DTS)中,片上 GPIO 资源通常是这样定义的:
gpio1: gpio@209c000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x0209C000 0x4000>; gpio-controller; #gpio-cells = <2>; };
- 1
- 2
- 3
- 4
- 5
- 6
✅ 片上 GPIO 受 gpiolib
管理,出现在 /sys/class/gpio/
和 /sys/bus/gpio/devices/
。
2. 片外 GPIO(Off-Chip GPIO)
片外 GPIO 指的是 通过 I2C、SPI、PCI 或其他接口扩展的 GPIO 设备,例如:
- I2C GPIO 扩展芯片(如 PCF8574、PCA9535)。
- SPI GPIO 扩展芯片(如 MCP23S17)。
- FPGA 或 CPLD 作为 GPIO 扩展。
这些 GPIO 并不是 SoC 自带的,而是通过 外部接口(I2C/SPI/PCI)连接,所以:
- 片外 GPIO 需要额外的驱动来管理。
- GPIO 子系统不会直接管理这些 GPIO 控制器,但可以通过驱动将它们注册为
gpio_chip
,让gpiolib
统一管理。
比如,I2C GPIO 扩展芯片 PCA9535 的设备树定义:
&i2c1 {
gpio_expander@20 {
compatible = "nxp,pca9535";
reg = <0x20>;
gpio-controller;
#gpio-cells = <2>;
};
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这样,PCA9535 的 16 个 GPIO 口就会在 GPIO 子系统 里注册为 gpiochipX
,用户可以像操作片上 GPIO 那样来使用它们。
✅ 片外 GPIO 不是 SoC 自带的,但可以通过 gpio_chip
机制让 gpiolib
统一管理。
3. gpiolib
是否能管理片外 GPIO?
gpiolib
本身不直接管理片外 GPIO,但 如果片外 GPIO 控制器的驱动支持 gpiolib
,那么它也可以被 GPIO 子系统管理。
? 这就是为什么 I2C/SPI GPIO 扩展芯片可以出现在 /sys/class/gpio/
里!
✅ 如果片外 GPIO 的驱动实现了 gpio_chip
机制,它就能被 GPIO 子系统管理。
❌ 如果驱动没有实现 gpio_chip
,那它就不会出现在 GPIO 子系统中,而是由其他子系统(I2C/SPI/PCI)管理。
4. 结论
✔ GPIO 子系统(gpiolib
)主要管理 SoC 片上的 GPIO 资源。
✔ 片外 GPIO(I2C/SPI 扩展的 GPIO)默认不受 gpiolib
直接管理,但可以通过 gpio_chip
机制注册到 GPIO 子系统。
✔ 如果片外 GPIO 驱动支持 gpio_chip
,它就能像片上 GPIO 一样被操作。
如何查看GPIO子系统里有哪些GPIO控制器,每个GPIO控制器的详细信息怎么查看
在上面对“gpiolib的工作方式”的叙述中,我们知道当 GPIO 控制器的驱动加载时,它会利用函数 gpiochip_add_data()
或 devm_gpiochip_add_data()
向 gpiolib
向 gpiolib
注册gpio_chip
结构体,注册完成后,,gpiochip
设备(即GPIO控制器)会出现在 /sys/class/gpio/gpiochipX
,同时也会出现在 /sys/bus/gpio/devices/
。
所以我们查看目录/sys/bus/gpio/devices/
或目录/sys/class/gpio/
就可以知道系统中有哪些GPIO控制器。
在我的开发板上运行下面的命令:
ls /sys/bus/gpio/devices/
- 1
结果如下:
从上面的运行结果中可以看出,一共有6个GPIO控制器的gpiochip
结构体,IMX6ULL本来只有5组GPIO口,即5个GPIO控制器,那么为什么会有6个GPIO控制器呢?请往后面看,后面有答案。
再运行下面的命令:
ls /sys/class/gpio/
- 1
从上面的运行结果中可以看出,系统中有6个与GPIO相关的设备类,每个名字后面的数字代表其对应的GPIO口的超始编号。IMX6ULL本来只有5组GPIO口,那么为什么会有6个GPIO的设备类?请往后面看,后面有答案。
下面来回答上面两条命令遗留的问题。
可以用下面命令来查看每个gpio_chip
结构体的详细情况:
cat /sys/kernel/debug/gpio
- 1
运行结果如下:
gpiochip0: GPIOs 0-31, parent: platform/209c000.gpio, 209c000.gpio:
gpio-5 ( |goodix_ts_int ) in hi IRQ
gpio-19 ( |cd ) in hi IRQ
gpio-20 ( |spi_imx ) out hi
gpiochip1: GPIOs 32-63, parent: platform/20a0000.gpio, 20a0000.gpio:
gpiochip2: GPIOs 64-95, parent: platform/20a4000.gpio, 20a4000.gpio:
gpio-68 ( |lcdif_rst ) out hi
gpiochip3: GPIOs 96-127, parent: platform/20a8000.gpio, 20a8000.gpio:
gpio-120 ( |spi_imx ) in lo
gpio-122 ( |spi_imx ) in lo
gpiochip4: GPIOs 128-159, parent: platform/20ac000.gpio, 20ac000.gpio:
gpio-130 ( |goodix_ts_rst ) out hi
gpio-134 ( |phy-reset ) out hi
gpio-135 ( |spi32766.0 ) out hi
gpio-136 ( |? ) out lo
gpio-137 ( |phy-reset ) out hi
gpio-138 ( |spi4 ) out hi
gpio-139 ( |spi4 ) out lo
gpiochip5: GPIOs 504-511, parent: spi/spi32766.0, 74hc595, can sleep:
gpio-505 ( |? ) out hi
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
因为一个gpiochip
结构体代表一个GPIO控制器,所以从上面的运行结果就可以看到每个GPIO控制器的详情情况了。
我们可以看到,之所以系统中出现了6个GPIO控制器,是因为板上通过SPI总线连接74hc595形成了扩展GPIO(SPI总线是一种串行总线,而74hc595
能将串行数据转换为并行输出),即SPI总线和74hc595共同构成了一个含GPIO控制器功能和GPIO功能的结构。这个扩展的GPIO被识别成了gpiochip5
结构体,所以多了一个GPIO控制器。
从这里也可以看出,片外的GPIO资源的确如前面所述,也是可以通过GPIO子系统(gpiolib)来管理的。
另外,从运行结果我们可以看出,实际上片内的GPIO的驱动是通过platform
来实现的,相关的证据在下图中用红线勾画出来了:
从运行结果我们还可以得出下面的信息:
gpiochip0对应于IMX6ULL的GPIO1,并把GPIO1的各GPIO口分别编号为0~31【一共32个GPIO口】,其寄存器基地址为209c000
;
gpiochip1对应于IMX6ULL的GPIO2,并把GPIO2的各GPIO口分别编号为32~63【一共32个GPIO口】,其寄存器基地址为20a0000
;
gpiochip2对应于IMX6ULL的GPIO3,并把GPIO3的各GPIO口分别编号为64~95【一共32个GPIO口】,其寄存器基地址为20a4000
;
gpiochip3对应于IMX6ULL的GPIO4,并把GPIO4的各GPIO口分别编号为96~127【一共32个GPIO口】,其寄存器基地址为20a8000
;
gpiochip4对应于IMX6ULL的GPIO5,并把GPIO5的各GPIO口分别编号为128~159【一共32个GPIO口】,其寄存器基地址为20ac000
;
有了上面的信息,我们就可以计算出某个具体的GPIO口的编号。当然在博文 http://iyenn.com/rec/1709342.html 中我们可以用函数of_get_gpio_flags()
获取到某个GPIO口的编号。
我们可以看芯片手册,发现这些基地址是对的,如下图所示:
终端中如何利用GPIO子系统(gpiolib)操作GPIO口
现在我要在终端中对GPIO5_IO03
进行操作,通过它控制LED2:
根据上面的得到的信息:
gpiochip0对应于IMX6ULL的GPIO1,并把GPIO1的各GPIO口分别编号为0~31【一共32个GPIO口】,其寄存器基地址为
209c000
;
gpiochip1对应于IMX6ULL的GPIO2,并把GPIO2的各GPIO口分别编号为32~63【一共32个GPIO口】,其寄存器基地址为20a0000
;
gpiochip2对应于IMX6ULL的GPIO3,并把GPIO3的各GPIO口分别编号为64~95【一共32个GPIO口】,其寄存器基地址为20a4000
;
gpiochip3对应于IMX6ULL的GPIO4,并把GPIO4的各GPIO口分别编号为96~127【一共32个GPIO口】,其寄存器基地址为20a8000
;
gpiochip4对应于IMX6ULL的GPIO5,并把GPIO5的各GPIO口分别编号为128~159【一共32个GPIO口】,其寄存器基地址为20ac000
;
可以计算出GPIO5_IO03
的编号为128+3=131 【注意:具体的GPIO口是从0开始编号的,但是GPIO组的组号是从1开始编号的。】
有了 GPIO的编号就可以用下面的方法对其进行操作了:
方式 1:利用sysfs伪文件系统(旧方法)
使用下面的命令:
echo 131 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio131/direction
echo 1 > /sys/class/gpio/gpio131/value
- 1
- 2
- 3
实际上是通过 sysfs伪文件系统 /sys/class/gpio/
间接操作 GPIO 控制器,让它设置 GPIO131 的方向和输出值。
这三条命令的详细解释见 http://iyenn.com/rec/1709312.html 【搜索“一个实际的例子分析”】
上面的三条命令运行完后,在下面的原理图基础上,灯灭了。
再运行下面的命令,灯又亮了:
echo 0 > /sys/class/gpio/gpio131/value
- 1
但这种方式在 Linux 5.10 及以上版本中已被废弃,官方推荐使用 libgpiod
。
方式 2:libgpiod(gpiod API)
(新方法,推荐)
# 设置 GPIO131 为高电平
gpioset gpiochip0 131=1
- 1
- 2
这种方法直接调用 GPIO 控制器驱动提供的 API,比 sysfs
更高效。虽然资料显示,libgpiod(gpiod API)
在Linux_4.8被引入,在4.11中被稳定,而我当前用的版本为4.9,有可能有,有可能没有,我实测了下,没有…
程序中如何如何利用GPIO子系统(gpiolib)操作GPIO口
详见 http://iyenn.com/rec/1709286.html
问题探讨:为什么没有相关的设备文件生成?
在看下面的解释前可以先看下博文 http://iyenn.com/rec/1709312.html 的第1个目录中我对“sysfs伪文件”和“设备文件”的区分【搜索“sysfs伪文件系统实际上和设备文件很类似”】。由于GPIO子系统(gpiolib)并不使用驱动程序的那套东西,所以没有设备文件的生成,但也可以通过sysfs伪文件去控制GPIO子系统中的那些GPIO口。
GPIO 子系统并不会为每个 GPIO 控制器创建设备文件。
在 Linux 内核中,GPIO 控制器被抽象为 gpio_chip
结构,并由 gpiolib
统一管理,但它 不会作为字符设备(/dev/
设备文件)直接暴露给用户空间。
1. GPIO 控制器不会创建 /dev/gpioX
这样的设备文件
与 I2C、SPI、UART 这些子系统不同,GPIO 控制器:
- 不会 通过
/dev/
创建设备文件(如/dev/gpioX
)。 - 其控制方式 主要通过 sysfs、ioctl 或
libgpiod
进行。 - 在
/sys/class/gpio/
仅提供了一个接口,而不是传统的设备节点。
为什么 GPIO 控制器不创建 /dev/
设备文件?
-
GPIO 本质上是单个引脚,而不是一个完整的字符/块设备
- I2C、SPI、UART 是面向数据流的接口,需要
/dev/
设备文件进行读写操作。 - 而 GPIO 只是 高低电平的开关,没有连续的数据流,因此通常不使用
/dev/
设备文件。
- I2C、SPI、UART 是面向数据流的接口,需要
-
GPIO 控制器被
gpiolib
统一管理- Linux 采用
gpio_chip
结构来管理 GPIO 控制器,每个gpio_chip
负责多个 GPIO 引脚。 - 用户访问 GPIO 时,通常通过
/sys/class/gpio/
或libgpiod
访问,而不是通过设备文件。
- Linux 采用
2. 那么 GPIO 控制器在系统中表现为什么?
虽然 没有 /dev/gpioX
这样的设备文件,但 GPIO 控制器仍然在 /sys
下可见,主要体现在以下几个地方:
(1) /sys/class/gpio/
(用户空间控制 GPIO)
ls /sys/class/gpio/
- 1
可能会看到:
export gpiochip0 gpiochip32 gpiochip64 gpiochip96 unexport
- 1
gpiochipX
代表不同的 GPIO 控制器,每个控制器管理多个 GPIO 口。export
/unexport
用于申请或释放 GPIO 口。
查看某个 GPIO 控制器的信息:
cat /sys/class/gpio/gpiochip0/label
- 1
可能输出:
gpio-mxc
- 1
表示这个 GPIO 控制器由 gpio-mxc
驱动管理。
(2) /sys/kernel/debug/gpio
(调试 GPIO 口状态)
如果内核开启了 CONFIG_GPIO_SYSFS
选项,可以通过以下命令查看所有 GPIO 口的状态:
cat /sys/kernel/debug/gpio
- 1
示例输出:
gpiochip0: GPIOs 0-31, parent: platform/30200000.gpio, gpio-mxc:
gpio-10 (sysfs ) out lo
gpio-20 (sysfs ) in hi
- 1
- 2
- 3
这表示:
gpiochip0
管理GPIO0~GPIO31
GPIO10
通过sysfs
导出,方向是 输出,当前电平是 低GPIO20
通过sysfs
导出,方向是 输入,当前电平是 高
(3) /sys/bus/gpio/
(GPIO 设备管理)
ls /sys/bus/gpio/
- 1
可能看到:
devices drivers drivers_autoprobe drivers_probe uevent
- 1
但 /sys/bus/gpio/drivers/
目录通常为空,因为 大部分 GPIO 控制器不会在 gpio
总线下注册驱动,而是作为 platform device 存在。
3. 其他子系统的对比
子系统 | 设备文件 (/dev/ ) | 主要管理方式 |
---|---|---|
GPIO | ❌ 无 | sysfs (旧)、libgpiod (推荐) |
I2C | ✅ /dev/i2c-X | i2c-dev 通过 ioctl 访问 |
SPI | ✅ /dev/spidevX.X | spidev 进行数据传输 |
UART | ✅ /dev/ttySx | 通过 read/write/ioctl 访问 |
可以看出,GPIO 不像 I2C、SPI、UART 那样需要 /dev/
设备文件,而是通过 sysfs
或 libgpiod
进行管理。
4. 结论
✅ GPIO 子系统不会创建 /dev/gpioX
这样的设备文件,因为 GPIO 只是开关信号,不是数据流设备。
✅ GPIO 控制器在 /sys/class/gpio/
中可见,但它们作为 gpio_chip
由 gpiolib
统一管理,而不是独立的设备文件。
✅ 推荐使用 libgpiod
而不是 sysfs
进行 GPIO 操作,因为 sysfs
方式在Linux 5.10中已被废弃(不过我使用的4.9版本里还没有引入,是4.11才正式引入的。)。



评论记录:
回复评论: