3. GPIO子系统概述:Linux GPIO子系统架构、gpiolib、pinctrl子系统的关系与分工
在嵌入式Linux系统中,GPIO(通用输入输出)是最基础且使用最频繁的硬件资源之一。为了统一管理不同SoC厂商的GPIO控制器差异,并实现引脚复用、电气属性配置等复杂功能,Linux内核抽象出了GPIO子系统与pinctrl子系统。本章将深入剖析这两大子系统的架构层次、核心组件以及它们之间的协作关系。
3.1 Linux GPIO子系统整体架构
GPIO子系统采用典型的内核空间分层设计,从上至下依次为:用户空间接口层、GPIO核心层(gpiolib)、以及硬件驱动层。这种分层设计使得上层开发者无需关心底层寄存器细节,而底层驱动开发者只需实现标准接口即可。
3.2 gpiolib:GPIO核心库
gpiolib 是GPIO子系统的核心实现,位于 drivers/gpio/gpiolib.c。它负责管理系统中所有的GPIO控制器,并为内核其他模块和用户空间提供统一的访问接口。
3.2.1 gpiolib的核心数据结构
| 数据结构 | 文件位置 | 核心作用 |
|---|---|---|
struct gpio_chip |
include/linux/gpio/driver.h | 描述一个GPIO控制器实例,包含操作回调、基编号、ngpio等 |
struct gpio_device |
drivers/gpio/gpiolib.h | gpio_chip的设备抽象,管理设备树节点、引用计数、中断域 |
struct gpio_desc |
drivers/gpio/gpiolib.h | GPIO描述符,代表一个具体的GPIO引脚,包含标志、标签、引用计数 |
struct gpio_irq_chip |
include/linux/gpio/driver.h | GPIO中断控制器描述,用于将GPIO引脚映射为中断源 |
3.2.2 gpiolib提供的核心API
gpiolib将API分为三类:消费者API(供其他驱动使用)、提供者API(供GPIO控制器驱动使用)和内部管理API。
gpiod_* API,而非传统的 gpio_* 整数编号API。描述符API支持设备树自动解析、引用计数和更严格的类型安全。
/* 消费者API示例:获取并操作GPIO */
#include <linux/gpio/consumer.h>
/* 从设备树获取GPIO描述符(主动低电平) */
struct gpio_desc *led_desc;
led_desc = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(led_desc)) {
dev_err(&pdev->dev, "Failed to get GPIO\n");
return PTR_ERR(led_desc);
}
/* 设置GPIO输出高电平 */
gpiod_set_value(led_desc, 1);
/* 读取GPIO输入值 */
int val = gpiod_get_value(led_desc);
/* 释放GPIO */
gpiod_put(led_desc);
/* 提供者API示例:注册GPIO控制器 */
#include <linux/gpio/driver.h>
static struct gpio_chip my_gpio_chip = {
.label = "my-gpio-controller",
.owner = THIS_MODULE,
.request = my_gpio_request,
.free = my_gpio_free,
.direction_input = my_gpio_direction_input,
.direction_output = my_gpio_direction_output,
.get = my_gpio_get,
.set = my_gpio_set,
.base = -1, /* 动态分配基编号 */
.ngpio = 32,
.can_sleep = false,
};
/* 在probe函数中注册 */
int my_probe(struct platform_device *pdev)
{
struct gpio_chip *chip = &my_gpio_chip;
chip->parent = &pdev->dev;
chip->of_node = pdev->dev.of_node;
return gpiochip_add_data(chip, NULL);
}
3.3 pinctrl子系统:引脚控制核心
pinctrl子系统(Pin Control Subsystem)负责管理SoC的引脚复用(Pin Muxing)、电气属性(驱动强度、上拉/下拉)和引脚分组(Pin Groups)。它解决了多个功能模块(如GPIO、UART、I2C)共享同一物理引脚时的冲突问题。
3.3.1 pinctrl的核心概念
- 引脚控制器 (Pin Controller):描述SoC中能够控制引脚复用的硬件单元,对应设备树中的
pinctrl节点。 - 引脚状态 (Pin State):一组引脚配置的集合,通常包括
default(默认)、sleep(休眠)、idle(空闲)等状态。 - 引脚组 (Pin Group):逻辑上相关的引脚集合,例如“uart0_tx/rx”组。
- 引脚功能 (Pin Function):引脚组可以切换到的功能模式,例如“gpio”、“uart”、“i2c”等。
3.3.2 pinctrl与GPIO子系统的关系
在大多数现代SoC中,GPIO控制器和pinctrl控制器是紧密耦合的。一个物理引脚既可以作为普通GPIO使用,也可以作为特定外设接口(如SPI、I2C)的引脚。pinctrl子系统负责在两者之间进行切换。
3.4 三大组件的关系与分工总结
GPIO子系统、gpiolib和pinctrl子系统在Linux内核中各司其职,又相互依赖。下表清晰展示了它们的分工边界:
| 组件 | 核心职责 | 管理对象 | 关键接口/文件 | 与设备树的关系 |
|---|---|---|---|---|
| GPIO子系统 | 提供GPIO的输入/输出、中断、用户空间访问 | GPIO引脚编号、描述符、中断 | gpiod_get/set_value, /dev/gpiochipX |
解析 gpios 属性,绑定 gpio-controller |
| gpiolib | GPIO核心库,管理gpio_chip注册、消费者API实现 | gpio_chip, gpio_desc, gpio_device | gpiochip_add_data, gpiod_get |
通过 of_gpio 解析设备树GPIO描述 |
| pinctrl子系统 | 引脚复用、电气属性、引脚分组与状态管理 | 引脚组、功能、状态、配置参数 | pinctrl_register, pinctrl_select_state |
解析 pinctrl-* 属性,配置引脚复用 |
3.4.1 典型工作流程:GPIO请求时的协作
- 驱动调用:消费者驱动调用
gpiod_get()请求一个GPIO。 - gpiolib解析:gpiolib根据设备树中的
gpios属性找到对应的gpio_chip和引脚偏移。 - pinctrl回调:gpiolib调用
pinctrl_gpio_request(),通知pinctrl子系统该引脚即将被用作GPIO。 - 引脚复用切换:pinctrl子系统检查该引脚当前是否被其他功能占用,若冲突则返回错误;否则将引脚复用切换到GPIO模式。
- GPIO初始化:gpiolib调用
gpio_chip->request()回调,完成硬件寄存器初始化。 - 返回描述符:gpiolib返回
struct gpio_desc给消费者驱动,后续操作直接通过描述符进行。
pinctrl-0 配置为UART功能,又被某个GPIO消费者节点引用,内核会在启动时检测到引脚冲突并报错。务必确保设备树中引脚功能配置的唯一性。
3.5 设备树中的角色体现
在设备树中,GPIO控制器和pinctrl控制器通常定义在同一个节点或相邻节点中。以下是一个典型的SoC设备树片段:
/* SoC.dtsi - GPIO控制器与pinctrl控制器定义 */
soc {
#address-cells = <1>;
#size-cells = <1>;
/* pinctrl控制器:负责引脚复用和电气属性 */
pinctrl: pinctrl@10000000 {
compatible = "vendor,soc-pinctrl";
reg = <0x10000000 0x1000>;
#pinctrl-cells = <2>;
/* 定义一组引脚配置:UART0默认状态 */
uart0_default: uart0-default {
function = "uart0";
pins = "GPIO0", "GPIO1";
bias-pull-up;
drive-strength = <4>;
};
};
/* GPIO控制器:依赖pinctrl进行引脚复用 */
gpio0: gpio@10001000 {
compatible = "vendor,soc-gpio";
reg = <0x10001000 0x100>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>; /* 关联pinctrl,0-31号引脚 */
};
};
/* board.dts - 板级设备树,使用GPIO */
leds {
compatible = "gpio-leds";
led-red {
label = "red-led";
gpios = <&gpio0 15 GPIO_ACTIVE_LOW>; /* GPIO0控制器的第15号引脚 */
pinctrl-0 = <&led_pins>; /* 关联pinctrl状态 */
default-state = "off";
};
};
上述代码清晰地展示了:gpio0 节点通过 gpio-ranges 属性与 pinctrl 节点建立关联,而消费者节点(leds)则通过 gpios 和 pinctrl-0 同时使用两个子系统的服务。
3.6 本章小结
- GPIO子系统 是面向消费者的高层抽象,提供简洁的GPIO操作API。
- gpiolib 是GPIO子系统的核心实现,管理控制器注册、描述符分配和中断映射。
- pinctrl子系统 是底层的引脚资源管理器,负责引脚复用和电气属性配置。
- 三者通过 回调机制 和 设备树属性 紧密协作,共同完成引脚资源的分配与配置。
- 在设备树中,
gpio-controller、gpio-ranges、pinctrl-*属性是连接三大组件的关键纽带。