之前学过gpio子系统的基础知识,以及旧的接口。
如下:
现在一般不使用上面讲的基于 GPIO 编号的传统接口(逐步被淘汰),而是推荐使用基于
gpio_desc的现代接口
struct gpio_desc
struct gpio_desc是 Linux 内核中用于描述和管理 GPIO 引脚的核心数据结构,它是对底层 GPIO 硬件的抽象,提供了统一的接口来操作 GPIO(如配置方向、读写电平、申请 / 释放等)。1.
gpio_desc简介在早期 Linux 内核中,GPIO 操作依赖直接的GPIO 编号 (如
gpio = 12),但这种方式存在扩展性问题(编号空间有限、跨平台兼容性差)。gpio_desc的引入解决了这些问题:
- 统一抽象 :将每个 GPIO 引脚封装为
struct gpio_desc实例,屏蔽不同硬件平台(如不同 SOC 的 GPIO 控制器)的差异。- 资源管理:跟踪 GPIO 的使用状态(已申请 / 未申请、方向、活跃电平极性等),避免资源冲突。
- 功能扩展 :支持中断、休眠等高级特性,为 GPIO 操作提供标准化接口(如
gpiod_set_value()、gpiod_direction_output()等)。2.
struct gpio_desc的关键成员(简化版)
struct gpio_desc { struct gpio_chip *chip; // 指向该 GPIO 所属的控制器(芯片) unsigned int offset; // 在控制器内的偏移量(如第 3 个引脚) const char *label; // GPIO 的标签(如 "user-led") unsigned long flags; // 标志位(如 GPIOF_ACTIVE_LOW 低电平有效) // ... 其他成员(引用计数、中断信息等) };
chip:关联到管理该 GPIO 的控制器驱动(struct gpio_chip),负责底层硬件操作(如寄存器读写)。offset:GPIO 在控制器内的编号(如控制器有 32 个引脚,offset范围 0~31)。flags:记录 GPIO 的属性(如是否低电平有效、是否已申请等)。gpio_desc 是 Linux 内核 GPIO 子系统的核心数据结构,是现代 GPIO 子系统(相较于早期直接使用 GPIO 编号的旧方式)实现设备与 GPIO 资源关联、状态管理、功能控制的核心载体。
3. gpio_desc 的核心定位:GPIO 资源的 "描述符"
可以将
struct gpio_desc理解为 "GPIO 引脚的身份证 + 控制接口"------ 它不仅记录了一个 GPIO 引脚的硬件属性(如归属的 GPIO 控制器、引脚编号、电气特性等),还提供了内核操作该 GPIO 的统一入口(如设置输出电平、读取输入状态、配置中断等)。在早期 Linux GPIO 子系统中,开发者直接使用 GPIO 全局编号 (如
gpio = 123)操作引脚,但这种方式存在明显缺陷:
- 全局编号缺乏硬件上下文,无法区分不同 GPIO 控制器下的引脚;
- 无法统一管理引脚的状态(如是否已被占用、电气属性配置);
- 扩展性差,难以支持复杂的 GPIO 功能(如中断、复用)。
而
gpio_desc的引入正是为了解决这些问题:将 GPIO 引脚的 "资源信息" 与 "操作接口" 封装为一个结构体,让内核和驱动通过这个 "描述符" 间接操作 GPIO,实现了 GPIO 资源的统一管理和抽象。4. gpio_desc 的关键作用(在 GPIO 子系统中的角色)
gpio_desc是 GPIO 子系统中连接 "硬件控制器""驱动层""应用层" 的核心桥梁,主要承担以下职责:(1)记录 GPIO 引脚的硬件与状态信息
struct gpio_desc内部包含多个关键字段(不同内核版本字段略有差异,核心逻辑一致),例如:
struct gpio_chip *chip:指向该 GPIO 所属的 GPIO 控制器驱动 (gpio_chip是 GPIO 控制器的抽象,负责与硬件寄存器交互);unsigned int line:该 GPIO 在所属控制器中的 本地编号 (如控制器gpiochip0下的第 5 个引脚,line = 5);unsigned long flags:GPIO 的状态标志,如:
GPIO_FLAG_REQUESTED:标记该 GPIO 已被申请(避免重复占用);GPIO_FLAG_ACTIVE_LOW:标记该 GPIO 为 "低电平有效";GPIO_FLAG_IS_OUT:标记该 GPIO 当前为输出模式;const char *label:GPIO 的用途标签(如 "led_usr""button_pwr",方便调试)。这些字段让内核能清晰掌握每个 GPIO 的 "归属""状态""用途",避免资源冲突和错误配置。
(2)作为 GPIO 操作的统一入口
GPIO 子系统提供的所有核心操作 API(如设置电平、读取状态、配置方向),均以
gpio_desc为参数,而非直接使用 GPIO 编号。例如:
gpiod_set_value(struct gpio_desc *desc, int value):设置 GPIO 输出电平(value=1高电平,value=0低电平);gpiod_get_value(struct gpio_desc *desc):读取 GPIO 输入电平;gpiod_direction_output(struct gpio_desc *desc, int value):将 GPIO 配置为输出模式,并设置初始电平;gpiod_request(struct gpio_desc *desc, const char *label):申请使用该 GPIO(会检查是否已被占用,避免冲突)。你在之前提供的
create_gpio_led函数中,也能看到这些 API 的实际使用,例如:
// 将 GPIO 配置为输出模式,并设置初始状态(state) ret = gpiod_direction_output(led_dat->gpiod, state); // 读取 GPIO 当前电平(用于获取默认状态) state = !!gpiod_get_value_cansleep(led_dat->gpiod);(3)衔接设备树与 GPIO 资源分配
在设备树(Device Tree)场景下,驱动通常通过设备树节点的
gpios属性(如gpios = <&gpio1 5 GPIO_ACTIVE_LOW>)声明所需的 GPIO 资源。此时,GPIO 子系统会通过devm_gpiod_get()等函数,将设备树中描述的 GPIO 资源解析为对应的gpio_desc结构体 ,并传递给驱动 ------gpio_desc在这里承担了 "设备树描述" 与 "实际硬件引脚" 的映射角色。5. 与早期 GPIO 编号的关系
gpio_desc是对早期 "GPIO 全局编号" 的封装和升级,两者的转换关系如下:
- 从编号到描述符:
gpio_to_desc(unsigned int gpio)(如你代码中led_dat->gpiod = gpio_to_desc(template->gpio));- 从描述符到编号:
desc_to_gpio(const struct gpio_desc *desc)(现代驱动已很少直接使用编号)。内核推荐优先使用
gpio_desc相关 API (如devm_gpiod_get、gpiod_set_value),而非直接操作 GPIO 编号,以保证代码的可移植性和资源管理的安全性。总结
gpio_desc是 Linux GPIO 子系统的 "核心数据结构",它封装了 GPIO 引脚的硬件属性、状态信息和操作接口,实现了 GPIO 资源的统一管理、设备树衔接和跨平台兼容,是现代 Linux 驱动操作 GPIO 的 "标准入口"。
基于 gpio_desc 的现代接口(推荐)
【旧接口】
int gpio(已废弃)【新接口】
struct gpiod_desc *(现代标准)
一、旧 GPIO 接口(legacy,整数编号)
1. 申请 / 释放
int gpio_request(unsigned gpio, const char *label); void gpio_free(unsigned gpio);2. 方向
int gpio_direction_input(unsigned gpio); int gpio_direction_output(unsigned gpio, int value);3. 读写
int gpio_get_value(unsigned gpio); void gpio_set_value(unsigned gpio, int value);4. 校验
int gpio_is_valid(unsigned gpio);5. 设备树获取
int of_get_gpio(struct device_node *np, int index); int of_get_gpio_flags(struct device_node *np, int index, unsigned long *flags);6. 转换
struct gpio_desc *gpio_to_desc(unsigned gpio); int desc_to_gpio(struct gpio_desc *desc);
二、新 GPIO 接口(modern,gpiod 描述符)
1. 从设备树获取(最常用)
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_desc *devm_gpiod_get_optional(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_desc *devm_gpiod_get_index(struct device *dev, const char *con_id, int idx, enum gpiod_flags flags);2. 申请 / 释放(非 devm)
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags); void gpiod_put(struct gpio_desc *desc);3. 方向
int gpiod_direction_input(struct gpio_desc *desc); int gpiod_direction_output(struct gpio_desc *desc, int value);4. 读写电平
int gpiod_get_value(struct gpio_desc *desc); void gpiod_set_value(struct gpio_desc *desc, int value);5. 校验(你要的 gpiod 版本!)
bool gpiod_is_valid(const struct gpio_desc *desc);6. 设备树解析(gpiod 新版)
struct gpio_desc *gpiod_get_from_of_node(struct device_node *np, const char *propname, int index, enum gpiod_flags flags, const char *label);7. 转换
int desc_to_gpio(const struct gpio_desc *desc); struct gpio_desc *gpio_to_desc(unsigned gpio);8. 错误判断(非常重要)
bool IS_ERR(const void *ptr); int PTR_ERR(const void *ptr);
三、新旧接口 完整一一对应表(最终版)
功能 旧接口(int gpio) 新接口(gpiod *) 申请 gpio_request devm_gpiod_get 释放 gpio_free devm 自动释放 /gpiod_put 读输入 gpio_get_value gpiod_get_value 写输出 gpio_set_value gpiod_set_value 方向输入 gpio_direction_input gpiod_direction_input 方向输出 gpio_direction_output gpiod_direction_output 校验是否有效 gpio_is_valid gpiod_is_valid 从设备树获取 of_get_gpio devm_gpiod_get 从设备树获取(带 flags) of_get_gpio_flags devm_gpiod_get 转换为 desc gpio_to_desc desc_to_gpio 判断错误 无 IS_ERR / PTR_ERR
现代驱动标准写法示例
struct gpio_desc *reset; reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(reset)) return PTR_ERR(reset); if (gpiod_is_valid(reset)) { gpiod_set_value(reset, 0); msleep(10); gpiod_set_value(reset, 1); msleep(10); }
附:devm_前缀
devm_前缀到底是什么?一句话终极结论
devm_= 内核帮你自动释放资源,不用你手动写释放代码!全称:Device Managed Resource(设备托管资源)
一、先讲人话
你写驱动,经常要做这些事:
- 申请内存
- 申请 GPIO
- 申请中断
- 注册设备
- 打开时钟
正常流程:
申请 → 使用 → 用完必须手动释放忘记释放 = 内存泄漏 / 资源卡死
加了
devm_之后:
申请 → 使用 → 设备拔掉时,内核自动帮你释放!你完全不用写释放代码!
二、最直观对比(一看就懂)
不带
devm_(手动版,麻烦)
// 申请 gpio_request(gpio, "test"); // ... 驱动逻辑 ... // 卸载驱动时必须手动释放! gpio_free(gpio);❌ 缺点:
- 必须在 remove 函数里写释放
- 容易忘
- 容易漏
- 容易 crash
带
devm_(自动托管版,推荐)
// 申请(内核自动托管) devm_gpio_request(gpio, "test"); // ... 驱动逻辑 ... // 不用写 gpio_free!✅ 优点:
- 设备移除时内核自动释放
- 永不泄漏
- 代码少一半
- 驱动更安全
三、
devm_不是 GPIO 专属!所有驱动资源都有 devm_ 版本
比如:
devm_kmalloc // 申请内存,自动释放 devm_gpio_request // 申请GPIO,自动释放 devm_gpiod_get // 新GPIO,自动释放 devm_regulator_get // 电源,自动释放 devm_ioremap // 地址映射,自动释放 devm_request_irq // 中断,自动释放只要看到
devm_,就等于:我不用管释放,内核会帮我处理!
关于新旧接口
好多驱动里都看到使用旧的GPIO接口
你观察得完全正确 !Linux 内核里90% 的老驱动、网卡驱动、WiFi 驱动、嵌入式驱动,全在用旧 GPIO(int 型)。
我告诉你最真实、最扎心、最业内的 4 个原因,你一下就懂了:
1. 历史原因:老内核不支持新 GPIO
- 旧 GPIO API:Linux 2.6 ~ 3.10 都能用
- 新 GPIO(gpiod):Linux 3.17 以后才出现
很多嵌入式产品(路由器、摄像头、Android)还在用 Linux 3.10 / 4.4 / 4.9 这种老内核 它们根本没有新 GPIO API,只能用旧的!
2. 驱动太老,厂商懒得改
像你看到的:
- dm9000
- sic80211 WiFi
- aic8800
- 以太网 MAC
- 串口、I2C、SPI
这些驱动写于 10~20 年前 厂商只保证能用,不保证升级新 API
→ 能用就行,不重构!
3. 旧 GPIO 写法简单粗暴
老工程师习惯了:
int gpio = of_get_named_gpio_flags(...); gpio_is_valid(gpio); devm_gpio_request_one(...); gpio_set_value(gpio, 1);就 4 行,简单直接。
新 GPIO 要写:
struct gpio_desc *gpiod = devm_gpiod_get(...); if (IS_ERR(gpiod)) ...; gpiod_set_value...很多人懒得改。
4. 内核保留兼容层,旧代码不会报错
Linux 内核向后兼容 旧 GPIO 函数并没有被删除 只是标记为 deprecated(不推荐) 不是 删除(deleted)
所以:旧代码能跑 → 厂商绝对不改
🔥 最重要结论(你必须记住)
旧 GPIO 现在依然大量存在,而且会长期存在!
新 GPIO 是未来,但旧 GPIO 是现在!
那你应该怎么学?
必须 两套都学:
- 旧 GPIO(int) → 看老驱动、厂商驱动、嵌入式驱动必备
- 新 GPIO(gpiod) → 新内核、新驱动、官方规范、写新驱动用
你现在的情况:
- 看驱动 → 遇到旧 GPIO 占 90%
- 写新驱动 → 必须用新 GPIO
所以你必须两套都懂!
我给你一句最真实的行业实话
** 旧 GPIO 是现实,新 GPIO 是规范。
看懂旧的,你能读 90% 驱动;
会写新的,你能写 100% 新驱动。**
旧 GPIO 完整标准模板
以后你在任何驱动里看到 GPIO 代码,套这个模板就能秒懂。
旧 GPIO(int 编号)全套标准流程(驱动里 99% 都是这样)
1. 定义变量
int reset_gpio; unsigned long gpio_flags; int ret;2. 从设备树获取 GPIO(OF 函数)
reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &gpio_flags);3. 判断 GPIO 是否有效
if (!gpio_is_valid(reset_gpio)) { dev_err(dev, "no reset gpio\n"); return -ENODEV; }4. 申请 GPIO(带 devm_ 自动释放)
ret = devm_gpio_request_one(dev, reset_gpio, gpio_flags, "dev_reset"); if (ret) return ret;5. 操作 GPIO
gpio_set_value(reset_gpio, 0); msleep(10); gpio_set_value(reset_gpio, 1); msleep(20);6. 读取 GPIO(输入时)
int val = gpio_get_value(irq_gpio);
旧 GPIO 最常用、最核心的 8 个函数(必须记)
这些就是你在驱动里反复看到的那一批:
of_get_named_gpio_flagsof_get_gpiogpio_is_validdevm_gpio_request_onegpio_requestgpio_direction_outputgpio_set_valuegpio_get_value没有更多了!驱动里就这 8 个来回用。
一句话总结旧 GPIO 风格
int 编号 + of_get 拿编号 + gpio_is_valid + devm_gpio_request + gpio_set/get
这就是你在 dm9000、以太网、WiFi、各类老驱动里看到的统一套路。