3. GPIO子系统架构:Linux GPIO子系统整体框架、gpiolib核心层、gpio_chip与gpio_desc数据结构解析
在嵌入式Linux网关开发中,GPIO(通用输入输出)是最基础也最关键的硬件抽象资源之一。Linux内核通过GPIO子系统为驱动开发者提供了一套统一的、与硬件平台无关的编程接口。本章将深入剖析GPIO子系统的整体框架,重点解析gpiolib核心层、gpio_chip与gpio_desc两大核心数据结构,帮助你在网关硬件抽象层(HAL)中正确设计GPIO驱动模型。
3.1 Linux GPIO子系统整体框架
Linux GPIO子系统采用分层架构设计,从上到下依次为:用户空间接口层、内核空间API层、gpiolib核心层和硬件驱动层。这种分层结构使得上层应用和内核模块无需关心底层GPIO控制器的具体实现细节。
gpio_chip回调函数来操作具体寄存器。
3.2 gpiolib核心层:资源管理与抽象
gpiolib是GPIO子系统的中枢神经,它负责以下关键职责:
- GPIO号管理:维护全局GPIO号到
gpio_desc的映射表,支持动态和静态分配。 - 描述符生命周期:管理
gpio_desc的分配、引用计数和释放。 - 设备树解析:解析设备树中的GPIO控制器节点和GPIO引用(
gpios属性)。 - 中断映射:将GPIO中断请求映射到Linux IRQ子系统。
- 用户空间接口:提供sysfs和字符设备两种用户态访问路径。
| 核心功能 | 对应接口/机制 | 说明 |
|---|---|---|
| GPIO号分配 | gpiochip_add() / gpiochip_add_data() |
注册gpio_chip时自动分配基号 |
| 描述符获取 | gpiod_get() / gpiod_get_index() |
基于设备树节点获取GPIO描述符 |
| 描述符释放 | gpiod_put() |
减少引用计数,释放资源 |
| 设备树解析 | of_get_named_gpiod_flags() |
解析gpios属性中的GPIO号和标志 |
| 中断映射 | gpiod_to_irq() |
将GPIO描述符转换为IRQ号 |
gpio_chip的回调函数间接完成。这种设计使得核心层可以在不修改的情况下支持任意数量的GPIO控制器。
3.3 gpio_chip数据结构解析
gpio_chip是GPIO控制器驱动的核心数据结构,每个GPIO控制器(无论是SoC内置的还是外部扩展芯片)都需要实例化并注册一个gpio_chip。该结构体定义在linux/gpio/driver.h中。
struct gpio_chip {
const char *label;
struct device *parent;
struct module *owner;
int base; // GPIO基号,-1表示动态分配
u16 ngpio; // 本控制器管理的GPIO数量
const char *const *names; // GPIO名称数组(可选)
// ---- 核心回调函数 ----
int (*request)(struct gpio_chip *chip, unsigned int offset);
void (*free)(struct gpio_chip *chip, unsigned int offset);
int (*direction_input)(struct gpio_chip *chip, unsigned int offset);
int (*direction_output)(struct gpio_chip *chip, unsigned int offset, int value);
int (*get)(struct gpio_chip *chip, unsigned int offset);
void (*set)(struct gpio_chip *chip, unsigned int offset, int value);
// ---- 中断相关 ----
int (*to_irq)(struct gpio_chip *chip, unsigned int offset);
// ---- 批量操作(可选) ----
int (*get_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
// ---- 内部管理字段 ----
struct gpio_desc *gpiodev; // 指向描述符数组
struct device *dev; // GPIO设备
struct list_head list; // 全局链表节点
unsigned long *valid_mask; // 有效GPIO掩码
};
base字段建议设置为-1,让gpiolib自动分配GPIO号,避免与其他控制器冲突。ngpio必须与实际硬件支持的GPIO数量一致,不能多也不能少。- 如果控制器不支持中断,
to_irq回调应返回-ENXIO。 - 批量操作回调
get_multiple/set_multiple可以显著提高多GPIO操作效率,建议实现。
3.4 gpio_desc数据结构解析
gpio_desc是GPIO描述符,代表一个具体的GPIO引脚。它是内核中GPIO操作的统一句柄,所有上层API(如gpiod_set_value())最终都通过gpio_desc找到对应的gpio_chip和偏移量。该结构体定义在drivers/gpio/gpiolib.h中(内部头文件)。
struct gpio_desc {
struct gpio_chip *chip; // 所属的gpio_chip
unsigned long flags; // 描述符标志(方向、请求状态等)
const char *label; // 使用者标签(用于调试)
int offset; // 在chip内的偏移量(0 ~ ngpio-1)
int hwnum; // 硬件编号(通常等于offset)
struct list_head list; // 用于全局描述符链表
#ifdef CONFIG_GPIO_CDEV
struct gpio_v2_line_info info; // 字符设备信息
#endif
};
| 字段 | 类型 | 说明 |
|---|---|---|
chip |
struct gpio_chip * |
指向所属GPIO控制器的指针,通过它调用硬件操作 |
flags |
unsigned long |
包含方向位、请求位、中断标志、开漏/开源配置等 |
label |
const char * |
记录哪个驱动或模块使用了该GPIO,便于调试和冲突排查 |
offset |
int |
在gpio_chip中的引脚索引,范围[0, ngpio-1] |
hwnum |
int |
硬件编号,通常与offset相同,但某些控制器可能不同 |
3.5 GPIO操作流程:从API到底层硬件
理解GPIO子系统的数据流对于编写正确的驱动至关重要。以下是一个典型的GPIO输出操作流程:
- 获取描述符:驱动调用
gpiod_get(dev, "led"),gpiolib根据设备树节点查找对应的gpio_desc。 - 设置方向:调用
gpiod_direction_output(desc, 0),gpiolib设置desc->flags中的方向位,然后调用chip->direction_output(chip, offset, 0)。 - 设置值:调用
gpiod_set_value(desc, 1),gpiolib检查desc->flags确认方向为输出,然后调用chip->set(chip, offset, 1)。 - 硬件操作:
chip->set回调函数操作GPIO控制器的寄存器,将对应引脚电平拉高。 - 释放资源:驱动卸载时调用
gpiod_put(desc),减少引用计数并释放描述符。
gpio_desc的HAL API,例如hal_gpio_init()、hal_gpio_set()、hal_gpio_get()。这些API内部调用gpiolib标准接口,上层模块(如LED管理、按键检测、复位控制)无需直接操作gpio_chip或设备树。
3.6 设备树与GPIO子系统的绑定
设备树是连接硬件描述与GPIO子系统的桥梁。一个典型的GPIO控制器设备树节点如下:
gpio0: gpio@ff730000 {
compatible = "snps,dw-apb-gpio";
reg = <0xff730000 0x1000>;
#gpio-cells = <2>; // 第一个cell是引脚号,第二个是标志
ngpios = <32>;
gpio-controller; // 声明这是一个GPIO控制器
interrupt-controller;
interrupts = <0 82 4>;
};
当其他设备节点引用GPIO时,使用gpios属性:
led-red {
compatible = "gpio-leds";
gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; // GPIO0控制器,引脚12,低电平有效
label = "red-led";
};
gpiolib在解析设备树时,会通过of_get_named_gpiod_flags()函数完成以下工作:
- 根据
&gpio0找到对应的gpio_chip实例。 - 根据引脚号
12计算gpio_desc在数组中的偏移量。 - 解析标志
GPIO_ACTIVE_LOW并存入desc->flags。 - 返回
gpio_desc指针给调用者。
GPIO_ACTIVE_HIGH(0):高电平有效,默认值。GPIO_ACTIVE_LOW(1):低电平有效,gpiolib会自动反转电平值。GPIO_OPEN_DRAIN(16):开漏输出。GPIO_OPEN_SOURCE(32):开源输出。GPIO_PULL_UP(64):内部上拉。GPIO_PULL_DOWN(128):内部下拉。
3.7 本章小结
本章详细解析了Linux GPIO子系统的分层架构,重点剖析了gpio_chip和gpio_desc两个核心数据结构。在网关硬件抽象层设计中,理解这些底层机制有助于:
- 正确实现自定义GPIO控制器的
gpio_chip驱动。 - 合理使用gpiolib API进行GPIO资源管理。
- 准确解析设备树中的GPIO绑定信息。
- 设计高效、可移植的HAL GPIO抽象接口。