STM32MP157 Linux驱动学习笔记(二):硬件资源地基(Pinctrl/GPIO/Interrupt)

这篇文章整理自课程第 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_I2C05_Input 已经让你看到:同一个引脚既可能接到 GPIO,也可能接到 I2C、UART、LCD 等模块。真正麻烦的地方不是"寄存器怎么读写",而是:

  • 这组引脚到底归谁用
  • 当前应该复用成什么功能
  • 需要不要上拉、下拉、开漏、驱动强度这些配置
  • 设备在 defaultsleep 等不同状态下,引脚应该怎样切换

这就是 pinctrl 的职责。

这章和前后知识点的关系可以这样看:

  • 往前承接 04_I2C:I2C 能工作,不只是控制器驱动写对了,还要先把引脚复用成 I2C_SCL/I2C_SDA,并配置为 open drain。
  • 往后承接 07_GPIO:GPIO 看起来像"最普通的引脚",但它仍然需要先通过 pinctrl 选成功能;很多芯片里 GPIO 和 pinctrl 甚至是强耦合的。
  • 往后也服务于 09_UART11_SPI 等章节:这些子系统表面不同,落到设备树和板级 bring-up 时,都会先碰到 pinctrl。

一句话概括:pinctrl 不是某个单一设备的驱动,而是整块 SoC 的引脚资源调度层。

学习主线建议

这一章最容易学乱,因为它同时涉及设备树、平台设备、内核框架和芯片厂商驱动。对初学者来说,建议按下面这条主线走:

  1. 先回答"为什么需要 pinctrl"
    先看 01_Pinctrl子系统视频介绍.md02_使用Pinctrl要掌握的重要概念.docx
    只解决一个问题:为什么 pinctrl 不等于"配置某个 GPIO 电平",而是"统一管理引脚复用和状态"。
  2. 再看 STM32MP157 上 client 怎么写
    03_Pinctrl子系统使用示例_基于STM32MP157.md
    先搞清楚 pinctrl-namespinctrl-0pinctrl-1 这些设备树属性在描述什么。
  3. 再理解内核里真正保存了哪些对象
    04_Pinctrl子系统主要数据结构.md
    这一步要把 pinctrl_descpinctrl_devpinctrlpinctrl_statepinctrl_mappinctrl_setting 之间的关系建立起来。
  4. 然后顺着两条链路读
    一条是 pin controller 自己怎么被构造出来,看 05_Pincontroller构造过程情景分析_基于STM32MP157.md
    另一条是 client 侧怎么在 really_probe 时拿到状态并应用,看 06_client端使用pinctrl过程的情景分析_基于STM32MP157.md
  5. 最后用虚拟驱动把抽象跑通
    07_编写虚拟的Pinctrl驱动程序.md08_调试虚拟的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_descpinctrl_dev:服务端的"注册表"和"实例"

对象 作用 初学者理解方式
pinctrl_desc 驱动提供的描述信息 类似"我要把这颗 SoC 的 pinctrl 能力告诉内核"
pinctrl_dev 注册后的 pin controller 实例 类似"内核里真正可被查找和调用的 pin controller 设备"

pinctrl_desc 里最关键的是三组操作集:

  • pinctrl_ops:列举 pin、group,并把设备树节点转换成 pinctrl_map
  • pinmux_ops:把某组引脚切成某种 function
  • pinconf_ops:给 pin 或 group 下发配置

所以 pinctrl 不是单个函数,而是三类能力的组合:

  • 枚举和命名引脚
  • 复用引脚
  • 配置引脚

3. pinctrlpinctrl_statepinctrl_setting:client 侧的状态机

对象 它保存什么 你要抓住什么
pinctrl 某个 device 的全部 pinctrl 上下文 里面有这个设备的所有 state
pinctrl_state 一个命名状态,比如 defaultsleep 它下面挂着一组 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_mxi2c1_sleep_pins_mx,以及 &i2c1 中的 pinctrl-names/pinctrl-0/pinctrl-1 client 侧并不直接写寄存器,而是引用 pin controller 中预先定义好的 state
04_Pinctrl子系统主要数据结构.md 建立对象模型 pinctrl_descpinctrl_devpinctrlpinctrl_statepinctrl_mappinctrl_setting 以后看到 dt_node_to_mappinctrl_select_state 时,知道内核到底在搬运什么对象
05_Pincontroller构造过程情景分析_基于STM32MP157.md 看服务端注册链路 stm32_pctl_probestm32_pctrl_create_pins_tabstm32_pctrl_build_statedevm_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_probevirtual_dt_node_to_mapvirtual_pmx_setvirtual_pinconf_set 亲眼看到一段设备树怎么被翻译成 mux/config 动作
02_virtual_pinctrl_ok/virtual_pinctrl_client.c 看最小 client platform_driverprobe/remove 都很轻 这个 client 没主动调用 pinctrl API,也能在绑定阶段触发 default state,说明"自动绑定"确实存在
08_调试虚拟的Pinctrl驱动程序.md 看实验入口 设备树片段、make dtbs、模块编译与 insmod、debugfs 观察点 这章真正可执行的实验,不是做 I2C 通信,而是确认 dt -> pinctrl -> client 的链路已经打通

至少先盯住这 4 个关键源码入口

  1. source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
    virtual_pinctrl_probe
    这是 pin controller 侧的注册入口。你要确认:驱动不是直接"开始操作引脚",而是先组好 pinctrl_desc,再 devm_pinctrl_register()
  2. source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
    virtual_dt_node_to_map
    这是设备树翻译入口。你要确认:groups/functions/configs 不是直接执行,而是先被拆成 pinctrl_map
  3. source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_driver.c
    virtual_pmx_setvirtual_pinconf_set
    这是动作下发入口。你要确认:mux 和 config 是两条不同的执行路径。
  4. source/A7/06_Pinctrl/02_virtual_pinctrl_ok/virtual_pinctrl_client.c
    virtual_client_probe
    这个 probe 很轻,恰好能验证"client 不一定需要显式写 pinctrl 调用,框架绑定阶段已经做了一部分工作"。

推荐实验顺序

  1. 先读 03_Pinctrl子系统使用示例_基于STM32MP157.md,只对着设备树看 default/sleep 两个状态。
  2. 再读 04_Pinctrl子系统主要数据结构.md,把 state -> map -> setting 的关系画出来。
  3. 然后读 0506 两篇"情景分析",分别站在 pin controller 和 client 两侧看框架。
  4. 最后再看 02_virtual_pinctrl_ok 源码,把 virtual_dt_node_to_mapvirtual_pmx_setvirtual_pinconf_set 连成一条线。
  5. 如果你手上有板子,再按 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:觉得 defaultsleep 只是名字。
    它们其实是设备生命周期的一部分,跟功耗和板级稳定性直接相关。
  • 卡点 7:只盯寄存器,不看 debugfs。
    学这一章时,/sys/kernel/debug/pinctrl/... 的价值非常高。它能帮助你确认 pin、group、owner、state 是否真的建立起来。

学完后的迁移方向

学完本章后,你至少应该能做到什么

  • 能用自己的话解释:为什么 pinctrl 不是"单纯配置引脚电平",而是"引脚复用 + 配置 + 状态管理"的公共框架。
  • 能看懂设备树中的 pinctrl-namespinctrl-0pinctrl-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_UART11_SPI 时,你会自然先去设备树里看 pinctrl-0pinctrl-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 子系统把"不同芯片、不同扩展器、不同设备树写法"统一成了一组标准的获取和控制接口。

学习主线建议

这一章的信息很多,但主线其实很清楚。建议按下面顺序学:

  1. 先回答"为什么还要 GPIO 子系统"
    先看 01_GPIO子系统视频介绍.md02_使用GPIO子系统要掌握的重要概念.docx
    先建立一个核心认识:GPIO 子系统的价值,不是帮你省一行寄存器代码,而是让驱动在不同板子间尽量复用。
  2. 再学怎么用,不急着学怎么实现
    03_基于GPIO子系统的LED驱动程序.docx01_led/leddrv.c
    只关心三件事:设备树里怎么写 led-gpios、probe 里怎么 gpiod_get()、打开设备后怎样 gpiod_direction_output()gpiod_set_value()
  3. 然后再看 GPIO 子系统内部有哪些对象
    05_GPIO子系统层次与数据结构.md
    这一步一定要把 gpio_chipgpio_devicegpio_desc 的职责分开。
  4. 再看 STM32MP157 的真实实现
    06_STM32MP157的GPIO驱动源码分析.md
    重点不是把厂商代码一行行背下来,而是确认:STM32MP157 的 GPIO 控制器驱动其实和 pinctrl 紧密耦合。
  5. 最后再学"GPIO 和 pinctrl 怎么串起来"
    09_GPIO子系统与Pinctrl子系统的交互.md10_编程_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_descgpiod_* 接口;GPIO 控制器驱动要注册的是 gpio_chip;内核会基于这些信息组织出一个 gpio_device,把所有 GPIO line 管起来。

1. gpio_chipgpio_devicegpio_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_probegpiod_getgpiod_direction_outputgpiod_set_value 学会"先拿 desc,再设方向,再读写"的固定套路
05_GPIO子系统层次与数据结构.md 建立对象模型 gpio_chipgpio_devicegpio_desc 的关系,以及 GPIOLIB 上下接口 以后看真实控制器驱动时,不会只盯寄存器,能看出它是在注册一个 gpio_chip
06_STM32MP157的GPIO驱动源码分析.md 看真实 SoC 的实现位置 stm32_pctl_probe 里对子 bank 的注册、stm32_gpiolib_register_bankgpiochip_add_data STM32MP157 的 GPIO 和 pinctrl 关系很近,GPIO 控制器注册就发生在 pinctrl 驱动里
source/A7/07_GPIO/03_virtual_gpio_ok/virtual_gpio_driver.c 看最小 GPIO 控制器驱动 virtual_gpio_probedirection_input/outputget/setdevm_gpiochip_add_data 一个 GPIO 控制器驱动的核心,就是填好 gpio_chip 再注册
09_GPIO子系统与Pinctrl子系统的交互.md 学会解释"GPIO 为什么会自动借道 pinctrl" gpio-rangesgpiochip_generic_requestpinctrl_request_gpiogpio_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/gpioexport/unexport 当驱动还没写完时,先验证 GPIO 号、方向和值是不是通的

至少先盯住这 4 个关键源码入口

  1. source/A7/07_GPIO/01_led/leddrv.c
    chip_demo_gpio_probe
    验证点:gpiod_get(&pdev->dev, "led", 0) 会去匹配设备树中的 led-gpios
  2. source/A7/07_GPIO/01_led/leddrv.c
    led_drv_openled_drv_write
    验证点:client 驱动真正常用的 GPIO 动作就是"设方向"和"写逻辑值"。
  3. source/A7/07_GPIO/03_virtual_gpio_ok/virtual_gpio_driver.c
    virtual_gpio_probe
    验证点:控制器驱动的核心不在业务逻辑,而在于组装 gpio_chipdevm_gpiochip_add_data()
  4. 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 流。

推荐实验顺序

  1. 先从 01_led 开始,把 leddrv.c 读顺。
  2. 再根据 04_在100ASK_STM32MMP157上机实验.docx 的流程,在设备树中加入 myled { led-gpios = <&gpioa 10 GPIO_ACTIVE_LOW>; }; 并注意屏蔽默认 LED 节点,避免引脚冲突。
  3. 然后看 05_GPIO子系统层次与数据结构.md,把 gpio_chip/gpio_desc 的对象关系补齐。
  4. 再读 03_virtual_gpio_ok/virtual_gpio_driver.c,确认"最小 GPIO 控制器驱动"长什么样。
  5. 最后读 0910 两篇,再对照 04_gpio_use_pinctrl_ok,把 gpio-rangesgpiochip_generic_requestgpio_request_enable 连成一条线。
  6. 如果要快速排障,再用 11_GPIO子系统的sysfs接口.md 里的命令确认:
    • 当前系统有哪些 gpiochip
    • base 和 ngpio 是多少
    • 某个全局 GPIO 号能否 export、读写

初学者最容易卡住的地方

  • 卡点 1:把 GPIO 子系统理解成"寄存器封装"。
    这太低估它了。GPIO 子系统真正统一的是"设备树描述 + 控制器驱动 + 客户驱动"的整套接口。
  • 卡点 2:分不清 gpio_chipgpio_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_chipgpio_descgpio-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子系统要掌握的重要概念.docx
  • 03_基于GPIO子系统的LED驱动程序.docx
  • 04_在100ASK_STM32MMP157上机实验.docx
  • source/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_domainirq_descirq_chiprequest_irq() 怎样配合
  • 驱动实现层:一级中断控制器、链式下级中断控制器、层级中断控制器分别怎么写

这章和前后章节的关系可以这样看:

  • 往前承接 07_GPIO:GPIO 章节解决"怎么拿到某根线",这一章解决"这根线怎么变成中断并最终调用 ISR"。
  • 往后服务 09_UART 以及几乎所有真实驱动:串口接收、I2C 触摸屏、SPI 设备、网卡、DMA,最终都离不开中断框架。
  • 也回扣 02_同步与互斥:中断上下文是什么、为什么中断处理要快、为什么后半部不能乱睡眠,这些都和并发模型直接相关。

一句话概括:这章是在解释 Linux 驱动里最常见的"事件驱动"基础设施。

学习主线建议

这一章如果直接从 irq_domain_add_hierarchy() 开始看,几乎必定迷路。建议强制按下面顺序学:

  1. 先把"异常"和"中断"分清
    先看 01_异常与中断的概念及处理流程.docx
    只回答三个问题:什么是异常、什么是中断、CPU 处理异常时为什么要先跳异常向量表。
  2. 再看 Linux 为什么要把中断处理拆得这么复杂
    02_07文档合集.docx08_中断相关的其他驱动程序.md
    重点理解:上半部/下半部、softirq/tasklet/workqueue、threaded irq 这些机制是为了解决"中断处理必须快"。
  3. 再看硬件路径
    09_中断的硬件框架.md10_GIC介绍与编程.md
    把 STM32MP157 上的链路记成:
    GPIO/外设 -> EXTI(如果适用) -> GIC -> CPU
  4. 再看 Linux 内核里怎么表示这条链
    12_GIC驱动程序对中断的处理流程.md13_GIC驱动程序分析.md
    这一步要把 hwirqvirqirq_domainirq_descirq_chip 这些对象理顺。
  5. 最后再学两类下级中断控制器
    先看 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. hwirqvirqirq_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_descirq_chip

对象 它保存什么 你要抓住什么
irq_desc 某个 virq 的总体描述 包括 handler、action 链表、irq_data 等
irq_chip 一组中断控制操作 mask、unmask、ack、eoi 等

最简单的理解方式是:

  • irq_desc 像"这个 virq 的总档案"
  • irq_chip 像"怎么操作这个中断控制器硬件"

handle_level_irqhandle_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_flagsgpio_to_irqrequest_irq 最基础的驱动用法是:先拿 GPIO,再转成 irq,再注册 handler
source/A7/08_Interrupt/02_gic/gic.c 直接理解 GIC 寄存器语义 gic_initgic_enable_irqget_gic_irqclear_gic_irq GIC 的软件本质就是:配置 Distributor/CPU interface,读 IAR,写 EOIR
12_GIC驱动程序对中断的处理流程.md + 13_GIC驱动程序分析.md 建立 Linux 中断核心对象模型 一级控制器下的 irq_domainirq_descirq_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_datairq_domain_add_legacyirq_set_chip_and_handler 链式控制器的核心是先挂父级 handler,再在子控制器里分辨 hwirq
source/A7/08_Interrupt/05_virtual_int_controller_linear_ok/virtual_int_controller.c 看链式控制器按需分配版 irq_domain_add_linear linearlegacy 的关键区别不是流程,而是 irq_desc 的分配方式
source/A7/08_Interrupt/07_virtual_int_controller_hierarchy_ok/virtual_int_controller.c 看层级控制器最关键的递归关系 irq_domain_add_hierarchyirq_domain_set_hwirq_and_chipirq_domain_alloc_irqs_parentirq_chip_unmask_parent 层级控制器的难点在于"一个 virq 同时被父子 domain 共同组织"
16_legacy方式代码的上机实验.md19_层级中断控制器驱动程序上机实验.md 看可执行实验入口 upper_hwirq_basedevmem 触发 GIC pending 位、设备树片段 课程提供的虚拟控制器实验,不依赖真实按键,而是直接往 GIC pending 位写值触发

至少先盯住这 5 个关键源码入口

  1. source/A7/08_Interrupt/01_simple/gpio_key_drv.c
    gpio_key_probe
    验证点:最小驱动路径就是 GPIO -> gpio_to_irq -> request_irq
  2. source/A7/08_Interrupt/02_gic/gic.c
    gic_initget_gic_irqclear_gic_irq
    验证点:GIC 最核心的动作就是初始化、取当前中断 ID、写 EOIR 结束处理。
  3. source/A7/08_Interrupt/04_virtual_int_controller_legacy_ok/virtual_int_controller.c
    virtual_intc_irq_handler
    验证点:链式控制器一定有一个"父级进来后再细分 hwirq"的步骤。
  4. source/A7/08_Interrupt/04_virtual_int_controller_legacy_ok/virtual_int_controller.c
    virtual_intc_irq_map
    验证点:给 virq 绑定 irq_chiphandle_level_irq,是把子中断真正接进 Linux 分发框架的关键一步。
  5. source/A7/08_Interrupt/07_virtual_int_controller_hierarchy_ok/virtual_int_controller.c
    virtual_intc_domain_alloc
    验证点:层级控制器不是自己单干,它要一边设置本级 irq_chip,一边递归向父 domain 申请和关联中断。

推荐实验顺序

  1. 先通读 01_异常与中断的概念及处理流程.docx,只把异常向量表和"保存现场"搞懂。
  2. 再看 09_中断的硬件框架.md,把 STM32MP157 上 GPIO -> EXTI -> GIC -> CPU 的路径画出来。
  3. 然后读 01_simple/gpio_key_drv.c,确认一个普通驱动到底怎样申请中断。
  4. 再读 02_gic/gic.c,把 IAR/EOIR/ISENABLER 这些名字和动作对上。
  5. 接着看 1213 两篇,把 irq_domainirq_descirq_chip 这三个对象真正建立起来。
  6. 最后按 14 -> 15 -> 16 -> 17 -> 18 -> 19 的顺序学:
    • 先理解链式和层级差异
    • 再看 legacy
    • 再看 linear
    • 最后看 hierarchy

STM32MP157 上机实验最小观察点

如果你要跑课程里的虚拟控制器实验,最少先确认这几件事:

  1. 设备树里父中断控制器写对了,像:
    interrupt-parent = <&intc>;
  2. 虚拟控制器子节点确实是 interrupt-controller,并声明了 #interrupt-cells = <2>;
  3. 对于 STM32MP157,课程示例里选的是保留的 SPI 210,对应直接写 GIC pending 时会落到 GIC 中断号 242 的寄存器位计算。
  4. 触发实验不是等真实按键,而是按文档里的 devmem 0xa002121c 32 0x40000 之类的命令直接置位 pending bit。

初学者最容易卡住的地方

  • 卡点 1:分不清异常和中断。
    中断只是异常的一种。CPU 先按异常机制进入处理流程,再进一步进入中断分发框架。
  • 卡点 2:把设备树里的中断号、GIC 的硬件中断号、驱动里 request_irq() 的参数当成同一个数字。
    它们经常不是同一个概念。一定要区分 hwirqvirq
  • 卡点 3:不知道 irq_domain 到底干什么。
    它的本质就是"解析中断描述,并建立 (hwirq, virq) 的映射和初始化规则"。
  • 卡点 4:以为 request_irq() 之后内核就会直接调用你的 handler。
    中间还隔着 irq_desc.handle_irqirq_chip 的 mask/ack/unmask/EOI 等流程。
  • 卡点 5:把链式和层级中断控制器混成一种。
    链式要先"分辨是谁触发的";层级不需要单独分辨,但要递归处理父子 domain 和 irq_chip。
  • 卡点 6:忽略 STM32MP157 上 EXTI 的限制。
    EXTI 线路是有限的,同号位的 PAx/PBx/... 不是都能同时独立占用同一条 EXTI 线。
  • 卡点 7:把 legacylinear 看成两种完全不同的中断模型。
    它们都属于链式模型,主要差在 irq_desc 的分配方式。
  • 卡点 8:实验里只看 request_irq() 是否成功,不看触发链路。
    这章真正要验证的是:设备树 -> domain -> virq -> handler 这整条链是不是通了。

学完后的迁移方向

学完本章后,你至少应该能做到什么

  • 能用自己的话解释异常向量表、中断控制器、CPU 三者的关系。
  • 能说清 GIC 在 Linux 中断体系里的位置,以及 irq_domain 为什么存在。
  • 能分清 hwirqvirq,不再把设备树中断号和 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_异常与中断的概念及处理流程.docx
  • 02_07文档合集.docx
  • source/A7/08_Interrupt 下的简单按键、GIC、虚拟 legacy/linear/hierarchy 控制器源码

其中两份 docx 能提取出"异常向量表、保存现场、Linux 中断演进"等关键正文,但原始图示和部分流程图无法完整还原。所以这份总结里关于 irq_domain、链式与层级的关系、以及实验触发路径的表述,是结合 Markdown 讲义和课程源码交叉补全的,没有虚构未读到的图形细节。

相关推荐
сокол1 小时前
【网安-应急响应-基础记录】Linux入侵排查
linux·网络安全·系统安全
Z文的博客1 小时前
FLASHDB实战详解 - 嵌入式KV/TSD数据库开发全攻略
stm32·单片机·嵌入式·flash·flashdb·w25q256
xieliyu.2 小时前
Java手搓数据结构:从零模拟实现单向无头非循环链表
java·数据结构·学习·链表
一个小浪吴啊2 小时前
MacOS/Linux/Windows 跨平台一键安装OpenCode指南
linux·windows·macos·opencode
圆山猫2 小时前
[AI] [Linux] 教我用rust写一个GPIO驱动
linux·rust
y = xⁿ2 小时前
MySQL学习日记:关于MVCC及一些八股总结
数据库·学习·mysql
~无忧花开~2 小时前
CSS全攻略:从基础到实战技巧
开发语言·前端·css·学习·css3
江公望2 小时前
Linux kernel devm_of_platform_populate()函数浅谈
linux
其实防守也摸鱼2 小时前
AWVS下载和安装保姆级教程
linux·服务器·git