
这篇文章整理自课程第 06-08 章。它们是后续 UART、SPI、USB、摄像头、传感器等章节真正的地基:pin 怎么切、GPIO 怎么管、irq 怎么进内核。
源文件地址:git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
06_Pinctrl 学习总结
TL;DR:这一章真正要学会的,不是"把某个引脚拉高拉低",而是把
pinctrl看成一套给 GPIO、I2C、UART 等外设服务的"引脚复用 + 状态管理 + 配置下发"框架。07_GPIO会继续解释为什么很多时候你在驱动里只写gpiod_get(),背后却已经借道 pinctrl 把引脚切成 GPIO 功能。
章节定位
06_Pinctrl 是从"具体外设驱动"切换到"芯片级公共基础设施"的第一章。
前面的 04_I2C、05_Input 已经让你看到:同一个引脚既可能接到 GPIO,也可能接到 I2C、UART、LCD 等模块。真正麻烦的地方不是"寄存器怎么读写",而是:
- 这组引脚到底归谁用
- 当前应该复用成什么功能
- 需要不要上拉、下拉、开漏、驱动强度这些配置
- 设备在
default、sleep等不同状态下,引脚应该怎样切换
这就是 pinctrl 的职责。
这章和前后知识点的关系可以这样看:
- 往前承接
04_I2C:I2C 能工作,不只是控制器驱动写对了,还要先把引脚复用成I2C_SCL/I2C_SDA,并配置为 open drain。 - 往后承接
07_GPIO:GPIO 看起来像"最普通的引脚",但它仍然需要先通过 pinctrl 选成功能;很多芯片里 GPIO 和 pinctrl 甚至是强耦合的。 - 往后也服务于
09_UART、11_SPI等章节:这些子系统表面不同,落到设备树和板级 bring-up 时,都会先碰到 pinctrl。
一句话概括:pinctrl 不是某个单一设备的驱动,而是整块 SoC 的引脚资源调度层。
学习主线建议
这一章最容易学乱,因为它同时涉及设备树、平台设备、内核框架和芯片厂商驱动。对初学者来说,建议按下面这条主线走:
- 先回答"为什么需要 pinctrl"
先看01_Pinctrl子系统视频介绍.md和02_使用Pinctrl要掌握的重要概念.docx。
只解决一个问题:为什么 pinctrl 不等于"配置某个 GPIO 电平",而是"统一管理引脚复用和状态"。 - 再看 STM32MP157 上 client 怎么写
看03_Pinctrl子系统使用示例_基于STM32MP157.md。
先搞清楚pinctrl-names、pinctrl-0、pinctrl-1这些设备树属性在描述什么。 - 再理解内核里真正保存了哪些对象
看04_Pinctrl子系统主要数据结构.md。
这一步要把pinctrl_desc、pinctrl_dev、pinctrl、pinctrl_state、pinctrl_map、pinctrl_setting之间的关系建立起来。 - 然后顺着两条链路读
一条是 pin controller 自己怎么被构造出来,看05_Pincontroller构造过程情景分析_基于STM32MP157.md。
另一条是 client 侧怎么在really_probe时拿到状态并应用,看06_client端使用pinctrl过程的情景分析_基于STM32MP157.md。 - 最后用虚拟驱动把抽象跑通
看07_编写虚拟的Pinctrl驱动程序.md、08_调试虚拟的Pinctrl驱动程序.md,再对照source/A7/06_Pinctrl/02_virtual_pinctrl_ok。
这一步的重点不是"虚拟驱动能做什么实际硬件功能",而是"你终于能看见 dt -> map -> state -> set_mux/pinconf 这条链怎么落地"。
如果只保留一条最短主线,请记住:
为什么需要 pinctrl -> 设备树里怎么描述 state -> 内核里 state/map/setting 怎么保存 -> client 何时自动选中 default state -> 虚拟 pinctrl 驱动怎样把整套框架跑通
Pinctrl 子系统的核心对象
先记一句总线索:
pin controller 驱动负责把"芯片有哪些 pin、哪些 group、哪些 function、怎样配置"注册给内核;client 设备负责在设备树里声明"我想用哪种 state";pinctrl core 则在合适时机把 state 展开成一串 mux/config setting 并真正下发。
1. pin controller 和 client 是两类角色
| 角色 | 它是谁 | 你要抓住什么 |
|---|---|---|
| pin controller | 提供服务的那一侧 | 它知道芯片上有哪些 pin、group、function,以及如何执行 mux 和 config |
| client device | 使用服务的那一侧 | 它不直接描述寄存器,只是在设备树里声明"我在 default/sleep 时该用哪些引脚状态" |
对于 STM32MP157,&pinctrl { ... } 里的节点是服务端资源;&i2c1 { pinctrl-names = ...; pinctrl-0 = <...>; } 这种写法是客户端声明。
2. pinctrl_desc 和 pinctrl_dev:服务端的"注册表"和"实例"
| 对象 | 作用 | 初学者理解方式 |
|---|---|---|
pinctrl_desc |
驱动提供的描述信息 | 类似"我要把这颗 SoC 的 pinctrl 能力告诉内核" |
pinctrl_dev |
注册后的 pin controller 实例 | 类似"内核里真正可被查找和调用的 pin controller 设备" |
pinctrl_desc 里最关键的是三组操作集:
pinctrl_ops:列举 pin、group,并把设备树节点转换成pinctrl_mappinmux_ops:把某组引脚切成某种 functionpinconf_ops:给 pin 或 group 下发配置
所以 pinctrl 不是单个函数,而是三类能力的组合:
- 枚举和命名引脚
- 复用引脚
- 配置引脚
3. pinctrl、pinctrl_state、pinctrl_setting:client 侧的状态机
| 对象 | 它保存什么 | 你要抓住什么 |
|---|---|---|
pinctrl |
某个 device 的全部 pinctrl 上下文 | 里面有这个设备的所有 state |
pinctrl_state |
一个命名状态,比如 default、sleep |
它下面挂着一组 setting |
pinctrl_setting |
真正要执行的一个动作 | 不是 mux 就是 config |
这套对象回答的是:某个设备在某个状态下,到底要执行哪些引脚动作。
例如:
pinctrl-0 = <&i2c1_pins_mx>;表示default状态引用一个 pinctrl 节点- 这个节点会先被解析成若干
pinctrl_map - 再被转换成若干
pinctrl_setting pinctrl_select_state()最终逐个 setting 执行
所以初学者一定要把这组关系看成:
设备树 state 节点 -> pinctrl_map -> pinctrl_setting -> set_mux/pinconf_apply_setting
4. pinctrl_map 是"设备树语言"和"内核动作"的中间层
设备树写的是:
- 这几个
groups - 复用为哪些
functions - 配哪些
configs
但内核执行时不能直接拿文本属性去写寄存器,所以要先变成统一的 pinctrl_map。
在课程的虚拟驱动里,一个 pin 会被拆成两个 map:
- 一个
PIN_MAP_TYPE_MUX_GROUP - 一个
PIN_MAP_TYPE_CONFIGS_PIN
这正好把"复用"和"配置"拆开了。
5. state 不是可有可无的命名,而是电源管理和驱动协作的关键
这是本章最容易被忽略的点。
很多人第一次看到:
pinctrl-names = "default", "sleep";pinctrl-0 = <...>;pinctrl-1 = <...>;
会以为只是"写法要求"。
其实 state 反映的是设备在不同阶段的引脚需求:
default:设备正常工作sleep:设备休眠,可能切到模拟态、GPIO 态或省电配置
所以 pinctrl 不是一次性的上电初始化,而是围绕设备生命周期切换引脚状态。
源码与实验地图
这一章不要从某个大厂 BSP 文件一头扎进去。最有效的办法是让每份资料只承担一个问题。
| 资料或源码 | 在主线里的角色 | 重点看什么 | 你要验证什么 |
|---|---|---|---|
03_Pinctrl子系统使用示例_基于STM32MP157.md |
先从设备树侧建立直觉 | &pinctrl 中定义 i2c1_pins_mx、i2c1_sleep_pins_mx,以及 &i2c1 中的 pinctrl-names/pinctrl-0/pinctrl-1 |
client 侧并不直接写寄存器,而是引用 pin controller 中预先定义好的 state |
04_Pinctrl子系统主要数据结构.md |
建立对象模型 | pinctrl_desc、pinctrl_dev、pinctrl、pinctrl_state、pinctrl_map、pinctrl_setting |
以后看到 dt_node_to_map、pinctrl_select_state 时,知道内核到底在搬运什么对象 |
05_Pincontroller构造过程情景分析_基于STM32MP157.md |
看服务端注册链路 | stm32_pctl_probe、stm32_pctrl_create_pins_tab、stm32_pctrl_build_state、devm_pinctrl_register |
芯片厂商驱动要先把 pin controller 自己注册出来,client 才有得用 |
06_client端使用pinctrl过程的情景分析_基于STM32MP157.md |
看 client 自动绑定链路 | really_probe -> pinctrl_bind_pins -> devm_pinctrl_get -> pinctrl_dt_to_map -> pinctrl_lookup_state -> pinctrl_select_state |
很多平台驱动里没显式写 pinctrl 代码,不代表 pinctrl 没生效,而是框架在 probe 阶段已经代劳 |
02_virtual_pinctrl_ok/virtual_pinctrl_driver.c |
看最小 pin controller 驱动 | virtual_pinctrl_probe、virtual_dt_node_to_map、virtual_pmx_set、virtual_pinconf_set |
亲眼看到一段设备树怎么被翻译成 mux/config 动作 |
02_virtual_pinctrl_ok/virtual_pinctrl_client.c |
看最小 client | platform_driver 的 probe/remove 都很轻 |
这个 client 没主动调用 pinctrl API,也能在绑定阶段触发 default state,说明"自动绑定"确实存在 |
08_调试虚拟的Pinctrl驱动程序.md |
看实验入口 | 设备树片段、make dtbs、模块编译与 insmod、debugfs 观察点 |
这章真正可执行的实验,不是做 I2C 通信,而是确认 dt -> pinctrl -> client 的链路已经打通 |
至少先盯住这 4 个关键源码入口
source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
virtual_pinctrl_probe
这是 pin controller 侧的注册入口。你要确认:驱动不是直接"开始操作引脚",而是先组好pinctrl_desc,再devm_pinctrl_register()。source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
virtual_dt_node_to_map
这是设备树翻译入口。你要确认:groups/functions/configs不是直接执行,而是先被拆成pinctrl_map。source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
virtual_pmx_set和virtual_pinconf_set
这是动作下发入口。你要确认:mux 和 config 是两条不同的执行路径。source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_client.c
virtual_client_probe
这个 probe 很轻,恰好能验证"client 不一定需要显式写 pinctrl 调用,框架绑定阶段已经做了一部分工作"。
推荐实验顺序
- 先读
03_Pinctrl子系统使用示例_基于STM32MP157.md,只对着设备树看default/sleep两个状态。 - 再读
04_Pinctrl子系统主要数据结构.md,把state -> map -> setting的关系画出来。 - 然后读
05和06两篇"情景分析",分别站在 pin controller 和 client 两侧看框架。 - 最后再看
02_virtual_pinctrl_ok源码,把virtual_dt_node_to_map、virtual_pmx_set、virtual_pinconf_set连成一条线。 - 如果你手上有板子,再按
08_调试虚拟的Pinctrl驱动程序.md做最小实验,并观察:insmod时的内核打印/sys/kernel/debug/pinctrl/.../pins/sys/kernel/debug/pinctrl/.../pingroups
初学者最容易卡住的地方
- 卡点 1:把 pinctrl 理解成"GPIO 之前额外多做一步配置"。
这太窄了。pinctrl 面向的是整颗芯片的引脚资源,不只是 GPIO,也包括 I2C、UART、SPI、LCD 等复用需求。 - 卡点 2:分不清 pin controller 和 GPIO controller。
pin controller 决定"这个 pin 接到哪个模块、配成什么状态";GPIO controller 决定"已经是 GPIO 之后,方向是什么、值是多少、能不能转中断"。 - 卡点 3:把
group当成"多个 pin 的固定数组",把function当成"寄存器值"。
课程里更重要的是概念,不是某家芯片的具体命名。group是一起被复用的 pin 集合,function是这组 pin 想承担的外设功能。 - 卡点 4:不知道
pinctrl-0指向的节点为什么还会再被拆分。
因为设备树只是描述语言,内核要执行时必须先转成统一的pinctrl_map/pinctrl_setting。 - 卡点 5:以为驱动代码里没写
pinctrl_select_state(),说明 pinctrl 没参与。
错。很多场景下,really_probe -> pinctrl_bind_pins已经自动帮你选了default状态。 - 卡点 6:觉得
default和sleep只是名字。
它们其实是设备生命周期的一部分,跟功耗和板级稳定性直接相关。 - 卡点 7:只盯寄存器,不看 debugfs。
学这一章时,/sys/kernel/debug/pinctrl/...的价值非常高。它能帮助你确认 pin、group、owner、state 是否真的建立起来。
学完后的迁移方向
学完本章后,你至少应该能做到什么
- 能用自己的话解释:为什么 pinctrl 不是"单纯配置引脚电平",而是"引脚复用 + 配置 + 状态管理"的公共框架。
- 能看懂设备树中的
pinctrl-names、pinctrl-0、pinctrl-1在表达什么。 - 能说清
pinctrl_desc/pinctrl_dev是服务端对象,pinctrl/pinctrl_state/pinctrl_setting是 client 侧对象。 - 能理解
dt_node_to_map的意义:把设备树描述翻译成内核可执行的设置项。 - 能用虚拟驱动看懂 pin controller 注册、state 解析、mux/config 下发的最小流程。
之后怎么迁移到真实驱动开发
- 学
07_GPIO时,你会更容易理解为什么gpiod_get()有时能"顺带"把引脚切成 GPIO 功能。 - 学
09_UART、11_SPI时,你会自然先去设备树里看pinctrl-0和pinctrl-1,而不是一上来怀疑驱动主逻辑。 - 遇到板级 bring-up 问题时,你会优先从 pinctrl state、引脚冲突、sleep/default 配置去排查,而不是只盯外设寄存器。
一句话概括这章的迁移价值:
以后你再看任何外设驱动,都会先意识到"设备能不能工作"往往取决于它有没有拿到正确的引脚状态。
资料边界说明
本章同时参考了:
doc_pic/06_Pinctrl下的 Markdown 讲义02_使用Pinctrl要掌握的重要概念.docx的可提取正文source/A7/06_Pinctrl下的虚拟 pinctrl 示例源码
其中 docx 能提取出"pin controller/client、state、group/function、自动切换 default/sleep"等核心文字,但原文中的一些流程图和排版细节无法完整还原。所以这份总结里涉及对象关系和执行路径的部分,是结合 Markdown 讲义目录、docx 正文和虚拟源码交叉补全的,没有对图示中看不见的细节做臆测。
07_GPIO 学习总结
TL;DR:这一章真正要学会的,不是"再写一遍点灯寄存器代码",而是把 GPIO 看成
GPIO Controller + gpio_chip + gpio_desc + gpiod_* 接口 + 设备树组成的统一子系统。它一头连着06_Pinctrl,负责把引脚切成 GPIO;另一头连着08_Interrupt,负责把 GPIO 再变成中断源。
章节定位
07_GPIO 是把"板级引脚资源"落到"设备驱动可直接使用的 I/O 接口"的一章。
如果说 06_Pinctrl 回答的是"这些 pin 应该接到谁、处于什么状态",那么 07_GPIO 回答的就是:
- 当某个 pin 已经被切成 GPIO 功能后,驱动怎样统一地获取它
- 怎样设置方向、读值、写值
- GPIO 控制器驱动自己要向内核注册什么
- GPIO 子系统如何和 pinctrl 建立映射
- 在没有专门驱动时,怎样用 sysfs 快速验证一个引脚
这章和前后知识点的关系可以这样看:
- 往前承接
06_Pinctrl:很多人觉得 GPIO 最简单,实际上 GPIO 能不能用,前提还是引脚已经被切成 GPIO 功能。 - 往后承接
08_Interrupt:很多按键中断、外部事件中断,第一步往往都是gpio_to_irq()或设备树里的 GPIO 中断描述。 - 也回扣更前面的设备模型:GPIO 驱动和 LED、Key 这类设备驱动一样,还是 platform 设备 + 设备树 + 标准接口。
一句话概括:GPIO 子系统把"不同芯片、不同扩展器、不同设备树写法"统一成了一组标准的获取和控制接口。
学习主线建议
这一章的信息很多,但主线其实很清楚。建议按下面顺序学:
- 先回答"为什么还要 GPIO 子系统"
先看01_GPIO子系统视频介绍.md和02_使用GPIO子系统要掌握的重要概念.docx。
先建立一个核心认识:GPIO 子系统的价值,不是帮你省一行寄存器代码,而是让驱动在不同板子间尽量复用。 - 再学怎么用,不急着学怎么实现
看03_基于GPIO子系统的LED驱动程序.docx和01_led/leddrv.c。
只关心三件事:设备树里怎么写led-gpios、probe 里怎么gpiod_get()、打开设备后怎样gpiod_direction_output()和gpiod_set_value()。 - 然后再看 GPIO 子系统内部有哪些对象
看05_GPIO子系统层次与数据结构.md。
这一步一定要把gpio_chip、gpio_device、gpio_desc的职责分开。 - 再看 STM32MP157 的真实实现
看06_STM32MP157的GPIO驱动源码分析.md。
重点不是把厂商代码一行行背下来,而是确认:STM32MP157 的 GPIO 控制器驱动其实和 pinctrl 紧密耦合。 - 最后再学"GPIO 和 pinctrl 怎么串起来"
看09_GPIO子系统与Pinctrl子系统的交互.md、10_编程_GPIO使用Pinctrl.md,再对照04_gpio_use_pinctrl_ok。
这一步会解决一个大疑问:为什么很多时候你没在设备树里单独写 GPIO pin 的 pinctrl 节点,gpiod_get()仍然能工作。
如果只保留一条强制主线,请记住:
设备树里描述 GPIO -> 驱动里 gpiod_get/gpiod_set_value -> GPIO 内核对象 gpio_chip/gpio_desc -> 虚拟 GPIO 控制器注册 -> gpio-ranges 把 GPIO 和 pinctrl 串起来
GPIO 子系统的核心对象
先记一句最重要的话:
client 驱动平时直接接触的是 gpio_desc 和 gpiod_* 接口;GPIO 控制器驱动要注册的是 gpio_chip;内核会基于这些信息组织出一个 gpio_device,把所有 GPIO line 管起来。
1. gpio_chip、gpio_device、gpio_desc 的分工
| 对象 | 它是什么 | 你要抓住什么 |
|---|---|---|
gpio_chip |
GPIO 控制器驱动提供的能力集合 | 描述"我有多少个 GPIO、怎么设方向、怎么读写、怎么转中断" |
gpio_device |
内核为某个控制器建立的管理对象 | 里面挂着多个 GPIO line,对上服务 GPIOLIB |
gpio_desc |
某一个 GPIO line 的描述符 | client 驱动真正拿来操作的对象 |
初学者可以这样理解:
gpio_chip像"控制器驱动的 vtable"gpio_desc像"某一根线的句柄"gpiod_get()就是在帮你拿到这根线对应的gpio_desc
2. descriptor-based 接口和 legacy 接口
课程里明确给了两套接口:
- 新接口:
gpiod_* - 老接口:
gpio_*
这章应优先记新接口,因为它更贴近设备树和现代驱动写法。
| 动作 | 推荐接口 | 你要抓住什么 |
|---|---|---|
| 获取 GPIO | gpiod_get() / devm_gpiod_get() |
从设备树的 [name]-gpios 属性拿到 GPIO |
| 设置方向 | gpiod_direction_input/output() |
GPIO 的方向是获取后再设置 |
| 读写值 | gpiod_get_value() / gpiod_set_value() |
这里用的是"逻辑值",不是简单物理高低电平 |
| 释放资源 | gpiod_put() / devm_* 自动释放 |
推荐使用 devm_ 版本减少出错路径 |
3. GPIO_ACTIVE_LOW 说的是"逻辑语义",不是"驱动写反了"
这是初学者最容易在点灯实验里卡住的点。
设备树里如果写:
dts
led-gpios = <&gpioa 10 GPIO_ACTIVE_LOW>;
表示这颗 LED 的"点亮"逻辑是低电平有效。
所以:
gpiod_set_value(desc, 1)表示输出"逻辑 1"- 真正出去的物理电平,可能是低电平
这就是为什么课程里反复强调:gpiod_* 操作的是逻辑语义,不是直接暴露电平寄存器。
4. GPIO controller 不一定在 SoC 内部,也可能是扩展器
01_GPIO子系统视频介绍.md 特别强调了一点:
- SoC 自带 GPIO,访问很快
- I2C/SPI 扩展的 GPIO expander,访问可能会睡眠
这意味着 GPIO 子系统设计时从一开始就不是只服务"片上寄存器 GPIO",而是想抽象出一层统一访问接口。
这也是为什么本章不要把 GPIO 想得太"低级"。它其实是一个标准化很强的公共子系统。
5. gpio-ranges:GPIO 和 pinctrl 的桥
这是本章最关键、也最容易忽略的结构。
GPIO 有自己的编号空间,pinctrl 也有自己的编号空间。二者要想协作,必须先建立映射关系。
设备树里通常这样写:
dts
gpio-ranges = <&pinctrlA 0 128 12>;
这不是业务配置,而是在告诉内核:
- GPIO 控制器里的第 0 个 GPIO
- 对应 pinctrl 里的第 128 个 pin
- 一共映射 12 个
有了这层关系,当 gpiod_get() 触发 gpiochip_generic_request() 时,pinctrl 才知道该去切哪一个 pin。
源码与实验地图
这一章最适合边读边验证,不要只看接口表。
| 资料或源码 | 在主线里的角色 | 重点看什么 | 你要验证什么 |
|---|---|---|---|
03_基于GPIO子系统的LED驱动程序.docx |
从 client 驱动视角学会"怎么用 GPIO" | 设备树里的 led-gpios,probe 里的 gpiod_get(),open/write 里的 gpiod_direction_output()、gpiod_set_value() |
GPIO 子系统真正带来的好处,是驱动代码不再绑死某块板子的寄存器地址 |
source/A7/07_GPIO/01_led/leddrv.c |
最小 client 驱动样板 | chip_demo_gpio_probe、gpiod_get、gpiod_direction_output、gpiod_set_value |
学会"先拿 desc,再设方向,再读写"的固定套路 |
05_GPIO子系统层次与数据结构.md |
建立对象模型 | gpio_chip、gpio_device、gpio_desc 的关系,以及 GPIOLIB 上下接口 |
以后看真实控制器驱动时,不会只盯寄存器,能看出它是在注册一个 gpio_chip |
06_STM32MP157的GPIO驱动源码分析.md |
看真实 SoC 的实现位置 | stm32_pctl_probe 里对子 bank 的注册、stm32_gpiolib_register_bank、gpiochip_add_data |
STM32MP157 的 GPIO 和 pinctrl 关系很近,GPIO 控制器注册就发生在 pinctrl 驱动里 |
source/A7/07_GPIO/03_virtual_gpio_ok/virtual_gpio_driver.c |
看最小 GPIO 控制器驱动 | virtual_gpio_probe、direction_input/output、get/set、devm_gpiochip_add_data |
一个 GPIO 控制器驱动的核心,就是填好 gpio_chip 再注册 |
09_GPIO子系统与Pinctrl子系统的交互.md |
学会解释"GPIO 为什么会自动借道 pinctrl" | gpio-ranges、gpiochip_generic_request、pinctrl_request_gpio、gpio_request_enable |
以后看到 gpiod_get() 成功,不会误以为 pinctrl 没参与 |
source/A7/07_GPIO/04_gpio_use_pinctrl_ok/03_virtual_gpio_ok/virtual_gpio_driver.c |
看 GPIO 侧如何触发 pinctrl | g_virt_gpio->request = gpiochip_generic_request |
GPIO 控制器只要提供 request,GPIOLIB 就能把 pinctrl 串起来 |
source/A7/07_GPIO/04_gpio_use_pinctrl_ok/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c |
看 pinctrl 侧如何响应 GPIO 请求 | virtual_pmx_gpio_request_enable |
这就是"GPIO 走后门切 pinctrl"的最直观落点 |
11_GPIO子系统的sysfs接口.md |
提供最小排障入口 | /sys/class/gpio/gpiochipXXX、/sys/kernel/debug/gpio、export/unexport |
当驱动还没写完时,先验证 GPIO 号、方向和值是不是通的 |
至少先盯住这 4 个关键源码入口
source/A7/07_GPIO/01_led/leddrv.c
chip_demo_gpio_probe
验证点:gpiod_get(&pdev->dev, "led", 0)会去匹配设备树中的led-gpios。source/A7/07_GPIO/01_led/leddrv.c
led_drv_open和led_drv_write
验证点:client 驱动真正常用的 GPIO 动作就是"设方向"和"写逻辑值"。source/A7/07_GPIO/03_virtual_gpio_ok/virtual_gpio_driver.c
virtual_gpio_probe
验证点:控制器驱动的核心不在业务逻辑,而在于组装gpio_chip并devm_gpiochip_add_data()。source/A7/07_GPIO/04_gpio_use_pinctrl_ok/03_virtual_gpio_ok/virtual_gpio_driver.c
.request = gpiochip_generic_request
再对照04_gpio_use_pinctrl_ok/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c里的.gpio_request_enable
验证点:GPIO 和 pinctrl 的协作不是玄学,而是一条清楚的 request 流。
推荐实验顺序
- 先从
01_led开始,把leddrv.c读顺。 - 再根据
04_在100ASK_STM32MMP157上机实验.docx的流程,在设备树中加入myled { led-gpios = <&gpioa 10 GPIO_ACTIVE_LOW>; };并注意屏蔽默认 LED 节点,避免引脚冲突。 - 然后看
05_GPIO子系统层次与数据结构.md,把gpio_chip/gpio_desc的对象关系补齐。 - 再读
03_virtual_gpio_ok/virtual_gpio_driver.c,确认"最小 GPIO 控制器驱动"长什么样。 - 最后读
09、10两篇,再对照04_gpio_use_pinctrl_ok,把gpio-ranges、gpiochip_generic_request、gpio_request_enable连成一条线。 - 如果要快速排障,再用
11_GPIO子系统的sysfs接口.md里的命令确认:- 当前系统有哪些 gpiochip
- base 和 ngpio 是多少
- 某个全局 GPIO 号能否 export、读写
初学者最容易卡住的地方
- 卡点 1:把 GPIO 子系统理解成"寄存器封装"。
这太低估它了。GPIO 子系统真正统一的是"设备树描述 + 控制器驱动 + 客户驱动"的整套接口。 - 卡点 2:分不清
gpio_chip和gpio_desc。
gpio_chip属于控制器驱动,gpio_desc属于 client 获取到的单根线句柄。 - 卡点 3:不知道
gpiod_get(dev, "led", ...)为什么会去找led-gpios。
规则就是[name]-gpios。名字对不上,gpiod_get()就拿不到。 - 卡点 4:把
GPIO_ACTIVE_LOW当成"这个驱动写反了"。
它表达的是逻辑有效电平,gpiod_set_value()用的是逻辑值,不是单纯的物理高低电平。 - 卡点 5:以为 STM32MP157 上用了 GPIO,就必须显式写一段 pinctrl 配置把它切成 GPIO。
很多时候不需要。课程已经说明,GPIO 和 pinctrl 关系很紧,gpiod_get()可能会借道 pinctrl 自动完成这一步。 - 卡点 6:把 sysfs 里的 GPIO 编号当成固定编号。
不对。sysfs 用的是全局编号,通常等于gpiochip的 base 加 offset,base 可能随着系统配置变化。 - 卡点 7:没意识到默认设备树节点可能和你自己的实验冲突。
STM32MP157 的点灯实验里,默认gpio-leds已经占用了某些引脚,所以课程明确要求先把旧节点status = "disabled"。 - 卡点 8:以为 sysfs 就能替代驱动。
简单读写值时可以,但涉及中断、复杂时序、统一设备模型时,还是得写驱动。
学完后的迁移方向
学完本章后,你至少应该能做到什么
- 能说清 GPIO 子系统为什么存在,而不是只会"点灯"。
- 能从设备树里的
[name]-gpios出发,写出gpiod_get()、gpiod_direction_output()、gpiod_set_value()的最小 client 驱动。 - 能解释
gpio_chip、gpio_desc、gpio-ranges的角色。 - 能理解 GPIO 和 pinctrl 的协作路径,不会再把二者当成完全独立的模块。
- 能用 sysfs 和
/sys/kernel/debug/gpio做最小验证和排障。
之后怎么迁移到真实驱动开发
- 学
08_Interrupt时,你会很自然地把"GPIO 输入事件"和"GPIO 转中断"连起来。 - 写按键、复位脚、电源使能脚、背光控制脚这类驱动时,你会优先用 descriptor-based 接口,而不是先去找寄存器地址。
- 看 GPIO expander 或 SoC GPIO bank 驱动时,你会知道先找
gpio_chip的注册入口,而不是从零猜数据结构。
一句话概括这章的迁移价值:
以后你再写任何和引脚有关的驱动,都会先问自己:这是在写 client,还是在写 GPIO controller;这是在操作逻辑值,还是在误把物理电平当接口。
资料边界说明
本章同时参考了:
doc_pic/07_GPIO下的 Markdown 讲义02_使用GPIO子系统要掌握的重要概念.docx03_基于GPIO子系统的LED驱动程序.docx04_在100ASK_STM32MMP157上机实验.docxsource/A7/07_GPIO下的 LED、虚拟 GPIO、GPIO 使用 pinctrl 的示例源码
其中 docx 能提取出设备树写法、gpiod_* 接口、STM32MP157 实验步骤等关键文字,但原文中的截图和少量板级图示无法完整还原。因此这份总结里凡是涉及"GPIO 和 pinctrl 的自动协作""虚拟 GPIO 控制器最小实现"的部分,都是结合 Markdown 讲义和源码目录补全的,没有虚构未读到的图示细节。
08_Interrupt 学习总结
TL;DR:这一章真正要学会的,不只是
request_irq()的用法,而是把整条链路看通:异常向量表 -> GIC/EXTI -> irq_domain -> irq_desc/irq_chip -> handler。前面的07_GPIO只是把引脚读到了,这一章才解释"为什么按键一按,CPU 最终会跑到你的 ISR"。
章节定位
08_Interrupt 是前 8 章里跨度最大、抽象层最多的一章。
它同时覆盖了三层问题:
- 硬件层:中断源、EXTI、GIC、CPU 之间怎样连
- 内核框架层:
irq_domain、irq_desc、irq_chip、request_irq()怎样配合 - 驱动实现层:一级中断控制器、链式下级中断控制器、层级中断控制器分别怎么写
这章和前后章节的关系可以这样看:
- 往前承接
07_GPIO:GPIO 章节解决"怎么拿到某根线",这一章解决"这根线怎么变成中断并最终调用 ISR"。 - 往后服务
09_UART以及几乎所有真实驱动:串口接收、I2C 触摸屏、SPI 设备、网卡、DMA,最终都离不开中断框架。 - 也回扣
02_同步与互斥:中断上下文是什么、为什么中断处理要快、为什么后半部不能乱睡眠,这些都和并发模型直接相关。
一句话概括:这章是在解释 Linux 驱动里最常见的"事件驱动"基础设施。
学习主线建议
这一章如果直接从 irq_domain_add_hierarchy() 开始看,几乎必定迷路。建议强制按下面顺序学:
- 先把"异常"和"中断"分清
先看01_异常与中断的概念及处理流程.docx。
只回答三个问题:什么是异常、什么是中断、CPU 处理异常时为什么要先跳异常向量表。 - 再看 Linux 为什么要把中断处理拆得这么复杂
看02_07文档合集.docx和08_中断相关的其他驱动程序.md。
重点理解:上半部/下半部、softirq/tasklet/workqueue、threaded irq 这些机制是为了解决"中断处理必须快"。 - 再看硬件路径
看09_中断的硬件框架.md和10_GIC介绍与编程.md。
把 STM32MP157 上的链路记成:
GPIO/外设 -> EXTI(如果适用) -> GIC -> CPU - 再看 Linux 内核里怎么表示这条链
看12_GIC驱动程序对中断的处理流程.md、13_GIC驱动程序分析.md。
这一步要把hwirq、virq、irq_domain、irq_desc、irq_chip这些对象理顺。 - 最后再学两类下级中断控制器
先看14_两类中断控制器处理流程_链式和层级.md,再分别看15/16/17/18/19。
这一步不是在学三套 API,而是在学三种不同的心智模型:一级、链式、层级。
如果只保留一条最短主线,请记住:
异常向量表 -> GIC/EXTI 硬件路径 -> irq_domain 把 hwirq 变成 virq -> request_irq 绑定 handler -> 链式与层级中断控制器的区别
Interrupt 子系统的核心对象
先记一句最关键的话:
硬件告诉你的是 hwirq,Linux 驱动里申请的是 virq;二者之间靠 irq_domain 建立映射,而真正执行屏蔽/使能/EOI 的能力由 irq_chip 提供。
1. 异常向量表:CPU 进入中断世界的入口
CPU 不是直接"调用你的中断函数"。
硬件上发生中断后,CPU 会先跳到异常向量表对应入口,再经过体系结构相关代码进入 Linux 的中断入口。
初学者只需要抓住这条主线:
- 发生异常或中断
- CPU 跳异常向量表
- 保存现场
- 进入体系结构相关入口
- 再进入 Linux 的中断分发框架
所以 request_irq() 注册的 handler,距离"硬件刚发出中断"其实已经隔了好几层。
2. GIC:ARM 平台上的一级中断控制器
课程里把 GIC 讲得很清楚:它至少包含两块重要逻辑
- Distributor:管理中断源的使能、优先级、目标 CPU
- CPU Interface:把当前最高优先级的 pending 中断真正送给 CPU
同时要分清三类中断号:
- SGI:0-15,软件生成
- PPI:16-31,CPU 私有外设中断
- SPI:32 开始,共享外设中断
对 STM32MP157 这类 Cortex-A7 平台,很多外设最后都会走到 GIC。
3. hwirq、virq、irq_domain
这是全章最重要、最容易混的对象。
| 名词 | 它是什么 | 你要抓住什么 |
|---|---|---|
hwirq |
硬件中断号 | 来自 GIC、GPIO 控制器、EXTI 等硬件视角 |
virq |
Linux 分配的虚拟中断号 | request_irq() 申请的是它 |
irq_domain |
二者的映射和解析规则 | 负责把设备树中的中断描述翻译成 (hwirq, virq) 关系 |
所以不要再把"设备树里写的中断号"和"驱动里 request 的 irq 号"当成一回事。
课程在 12_GIC驱动程序对中断的处理流程.md 里给出了最小一级模型:
- 硬件发出 GIC 的某个
hwirq - GIC 驱动读取寄存器得到这个
hwirq - 通过 GIC domain 找到
virq - 调用
irq_desc[virq].handle_irq
4. irq_desc 和 irq_chip
| 对象 | 它保存什么 | 你要抓住什么 |
|---|---|---|
irq_desc |
某个 virq 的总体描述 |
包括 handler、action 链表、irq_data 等 |
irq_chip |
一组中断控制操作 | mask、unmask、ack、eoi 等 |
最简单的理解方式是:
irq_desc像"这个 virq 的总档案"irq_chip像"怎么操作这个中断控制器硬件"
当 handle_level_irq、handle_edge_irq 运行时,它们会借助 irq_chip 去做 mask/ack/unmask,再调用 action 链表里的用户 handler。
5. 链式中断控制器和层级中断控制器
这是本章第二个大关。
| 类型 | 硬件关系 | 你要抓住什么 |
|---|---|---|
链式 chained |
下级多个中断共享上级一个父中断 | 父 handler 里必须先"分辨是谁触发的" |
层级 hierarchy |
下级每个中断和上级一个中断一一对应 | 不需要单独的"分辨步骤",但要把父子 domain 和 irq_chip 递归串起来 |
课程明确指出:这个分类是为了帮助理解代码,不是 Linux 官方文档里的术语定义。
初学者可以这样记:
- 链式:先收到一个总中断,再在子控制器里查明细
- 层级:父子中断号一层层映射下去,某个 virq 同时存在于父子 domain
源码与实验地图
这一章不要只刷 API 名字,最有效的办法是用"由浅入深"的 5 个入口。
| 资料或源码 | 在主线里的角色 | 重点看什么 | 你要验证什么 |
|---|---|---|---|
01_异常与中断的概念及处理流程.docx |
建立最上层心智模型 | 异常向量表、保存现场、恢复现场 | 中断不是直接跳你的 ISR,而是先经过 CPU 的异常处理机制 |
09_中断的硬件框架.md |
看 STM32MP157 硬件路径 | GPIO/EXTI/GIC/CPU 的连接关系,EXTI 只提供有限线路选择 | 在 STM32MP157 上,很多 GPIO 中断并不是"GPIO 自己直接进 CPU",而是经过 EXTI 再进 GIC |
source/A7/08_Interrupt/01_simple/gpio_key_drv.c |
最小中断驱动样板 | of_get_gpio_flags、gpio_to_irq、request_irq |
最基础的驱动用法是:先拿 GPIO,再转成 irq,再注册 handler |
source/A7/08_Interrupt/02_gic/gic.c |
直接理解 GIC 寄存器语义 | gic_init、gic_enable_irq、get_gic_irq、clear_gic_irq |
GIC 的软件本质就是:配置 Distributor/CPU interface,读 IAR,写 EOIR |
12_GIC驱动程序对中断的处理流程.md + 13_GIC驱动程序分析.md |
建立 Linux 中断核心对象模型 | 一级控制器下的 irq_domain、irq_desc、irq_chip 配合 |
hwirq -> virq -> irq_desc.handle_irq 是整个框架的主骨架 |
source/A7/08_Interrupt/04_virtual_int_controller_legacy_ok/virtual_int_controller.c |
看链式控制器最老、最直观的实现 | irq_set_chained_handler_and_data、irq_domain_add_legacy、irq_set_chip_and_handler |
链式控制器的核心是先挂父级 handler,再在子控制器里分辨 hwirq |
source/A7/08_Interrupt/05_virtual_int_controller_linear_ok/virtual_int_controller.c |
看链式控制器按需分配版 | irq_domain_add_linear |
linear 和 legacy 的关键区别不是流程,而是 irq_desc 的分配方式 |
source/A7/08_Interrupt/07_virtual_int_controller_hierarchy_ok/virtual_int_controller.c |
看层级控制器最关键的递归关系 | irq_domain_add_hierarchy、irq_domain_set_hwirq_and_chip、irq_domain_alloc_irqs_parent、irq_chip_unmask_parent |
层级控制器的难点在于"一个 virq 同时被父子 domain 共同组织" |
16_legacy方式代码的上机实验.md、19_层级中断控制器驱动程序上机实验.md |
看可执行实验入口 | upper_hwirq_base、devmem 触发 GIC pending 位、设备树片段 |
课程提供的虚拟控制器实验,不依赖真实按键,而是直接往 GIC pending 位写值触发 |
至少先盯住这 5 个关键源码入口
source/A7/08_Interrupt/01_simple/gpio_key_drv.c
gpio_key_probe
验证点:最小驱动路径就是GPIO -> gpio_to_irq -> request_irq。source/A7/08_Interrupt/02_gic/gic.c
gic_init、get_gic_irq、clear_gic_irq
验证点:GIC 最核心的动作就是初始化、取当前中断 ID、写 EOIR 结束处理。source/A7/08_Interrupt/04_virtual_int_controller_legacy_ok/virtual_int_controller.c
virtual_intc_irq_handler
验证点:链式控制器一定有一个"父级进来后再细分 hwirq"的步骤。source/A7/08_Interrupt/04_virtual_int_controller_legacy_ok/virtual_int_controller.c
virtual_intc_irq_map
验证点:给virq绑定irq_chip和handle_level_irq,是把子中断真正接进 Linux 分发框架的关键一步。source/A7/08_Interrupt/07_virtual_int_controller_hierarchy_ok/virtual_int_controller.c
virtual_intc_domain_alloc
验证点:层级控制器不是自己单干,它要一边设置本级irq_chip,一边递归向父 domain 申请和关联中断。
推荐实验顺序
- 先通读
01_异常与中断的概念及处理流程.docx,只把异常向量表和"保存现场"搞懂。 - 再看
09_中断的硬件框架.md,把 STM32MP157 上GPIO -> EXTI -> GIC -> CPU的路径画出来。 - 然后读
01_simple/gpio_key_drv.c,确认一个普通驱动到底怎样申请中断。 - 再读
02_gic/gic.c,把IAR/EOIR/ISENABLER这些名字和动作对上。 - 接着看
12、13两篇,把irq_domain、irq_desc、irq_chip这三个对象真正建立起来。 - 最后按
14 -> 15 -> 16 -> 17 -> 18 -> 19的顺序学:- 先理解链式和层级差异
- 再看 legacy
- 再看 linear
- 最后看 hierarchy
STM32MP157 上机实验最小观察点
如果你要跑课程里的虚拟控制器实验,最少先确认这几件事:
- 设备树里父中断控制器写对了,像:
interrupt-parent = <&intc>; - 虚拟控制器子节点确实是
interrupt-controller,并声明了#interrupt-cells = <2>; - 对于 STM32MP157,课程示例里选的是保留的 SPI 210,对应直接写 GIC pending 时会落到 GIC 中断号 242 的寄存器位计算。
- 触发实验不是等真实按键,而是按文档里的
devmem 0xa002121c 32 0x40000之类的命令直接置位 pending bit。
初学者最容易卡住的地方
- 卡点 1:分不清异常和中断。
中断只是异常的一种。CPU 先按异常机制进入处理流程,再进一步进入中断分发框架。 - 卡点 2:把设备树里的中断号、GIC 的硬件中断号、驱动里
request_irq()的参数当成同一个数字。
它们经常不是同一个概念。一定要区分hwirq和virq。 - 卡点 3:不知道
irq_domain到底干什么。
它的本质就是"解析中断描述,并建立(hwirq, virq)的映射和初始化规则"。 - 卡点 4:以为
request_irq()之后内核就会直接调用你的 handler。
中间还隔着irq_desc.handle_irq、irq_chip的 mask/ack/unmask/EOI 等流程。 - 卡点 5:把链式和层级中断控制器混成一种。
链式要先"分辨是谁触发的";层级不需要单独分辨,但要递归处理父子 domain 和 irq_chip。 - 卡点 6:忽略 STM32MP157 上 EXTI 的限制。
EXTI 线路是有限的,同号位的PAx/PBx/...不是都能同时独立占用同一条 EXTI 线。 - 卡点 7:把
legacy和linear看成两种完全不同的中断模型。
它们都属于链式模型,主要差在irq_desc的分配方式。 - 卡点 8:实验里只看
request_irq()是否成功,不看触发链路。
这章真正要验证的是:设备树 -> domain -> virq -> handler 这整条链是不是通了。
学完后的迁移方向
学完本章后,你至少应该能做到什么
- 能用自己的话解释异常向量表、中断控制器、CPU 三者的关系。
- 能说清 GIC 在 Linux 中断体系里的位置,以及
irq_domain为什么存在。 - 能分清
hwirq和virq,不再把设备树中断号和request_irq()的参数混为一谈。 - 能看懂一个最小 GPIO 中断驱动:从设备树拿资源、注册 handler、在 ISR 里读取状态。
- 能解释链式中断控制器和层级中断控制器的核心区别。
之后怎么迁移到真实驱动开发
- 学
09_UART时,你会更容易理解串口驱动为什么既要配寄存器又要注册中断。 - 看触摸屏、网卡、DMA、MMC 这类驱动时,你会知道先找
request_irq()的入口,再顺着virq追到irq_domain和父控制器。 - 调中断问题时,你会优先检查:
- 设备树中断描述是否正确
- 父中断控制器是否绑定对了
- trigger type 是否匹配
- 是链式还是层级
- GIC pending/enable 状态是否真的变化
一句话概括这章的迁移价值:
以后你再看到"中断不触发"这类问题,不会只会怀疑 request_irq(),而会从异常入口、父中断控制器、domain 映射、irq_chip 动作这几层系统地排查。
资料边界说明
本章同时参考了:
doc_pic/08_Interrupt下的 Markdown 讲义01_异常与中断的概念及处理流程.docx02_07文档合集.docxsource/A7/08_Interrupt下的简单按键、GIC、虚拟 legacy/linear/hierarchy 控制器源码
其中两份 docx 能提取出"异常向量表、保存现场、Linux 中断演进"等关键正文,但原始图示和部分流程图无法完整还原。所以这份总结里关于 irq_domain、链式与层级的关系、以及实验触发路径的表述,是结合 Markdown 讲义和课程源码交叉补全的,没有虚构未读到的图形细节。