零、Platform 总线结合 DTS 的核心思想
为什么要用这种组合?
传统方式(仅 Platform) :每支持一块新板子,就要在 arch/arm/mach-xxx/ 下新增一个 C 文件(少则几百行,多则上千行),里面充满 platform_device_register、resource 定义、gpio 数组等。这些代码大多重复且难以阅读,内核源码越来越臃肿。
Platform 结合 DTS 的方式:板级描述变成结构化的 .dts 文本文件,不再需要为每块板子在内核源码里增加 C 文件。主流架构的 mach-xxx 目录如今只剩少量核心代码。
结果:内核更干净,维护更容易。
与之前两种写法的区别
| 对比项 | 仅 Platform | Platform + DTS |
|---|---|---|
| 硬件信息 | led_device.c 的 resource 数组 | pt.dts 文本文件 |
| 匹配依据 | device.name == driver.name | of_match_table 里的 compatible 字段 |
| 驱动文件数 | 2个(device.c + driver.c) | 1个(driver.c,device.c 功能由 DTS 替代) |
| 内核是否需重编 | 改 device.c 需重编 | 只改 .dts,重编 DTB 即可 |
关键细节:匹配效率的提升
设备树方式的好处:匹配上了再去寻找设备节点,减少很多无用功。
匹配的是 compatible 字段,跟节点名 pt_led 和属性名 name1 没关系 。只有 compatible 字符串一致,内核才会调用 probe,probe 里才去 of_find_node_by_path 查找节点------避免了为没有匹配到的设备执行多余的初始化代码。
一、两个版本 DTS 的差异(h.dts → pt.dts)
1.1 新增了 pt_key 节点(最核心的改动)
dts
/* h.dts:没有 pt_key 节点 */
/* pt.dts:新增 pt_key 节点 */
pt_key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pt-key"; /* 驱动匹配字符串 */
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ptkey>; /* 引用下面的引脚配置 */
key-gpio = <&gpio1 18 GPIO_ACTIVE_HIGH>; /* GPIO1_IO18,按键引脚 */
interrupt-parent = <&gpio1>; /* 中断控制器:gpio1 */
interrupts = <18 IRQ_TYPE_EDGE_FALLING>; /* 引脚18,下降沿触发 */
};
1.2 新增了 pinctrl_ptkey(iomuxc 里的引脚配置)
dts
/* h.dts:没有 pinctrl_ptkey */
/* pt.dts:在 &iomuxc → imx6ul-evk 里新增 */
pinctrl_ptkey: ptkeygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
0xF080是引脚电气属性值,表示使能内部上拉、输入模式。对比 LED 用的0x10b0(推挽输出),按键用0xF080才能正确检测按键。
1.3 改动总结
| 位置 | h.dts | pt.dts |
|---|---|---|
| 根节点下 | 无 pt_key |
新增 pt_key 节点 |
&iomuxc 下 |
无 pinctrl_ptkey |
新增 pinctrl_ptkey |
pt_key 中断声明 |
无 | interrupt-parent + interrupts |
二、两种获取中断号的方式
key_irq.c 和 key_sub.c 用了不同的方式从 DTS 获取中断号,这是本节的关键区别。
2.1 方式一:gpio_to_irq(key_irq.c 用的)
流程:DTS 里的 key-gpio → of_get_named_gpio() 读出 GPIO 编号
→ gpio_to_irq() 把 GPIO 编号转成 Linux 中断号
c
/* key_irq.c 里的写法 */
key_gpio = of_get_named_gpio(pnode, "key-gpio", 0); // ① 读 GPIO 编号
gpio_request(key_gpio, "pt_key"); // ② 申请 GPIO
irq_key_num = gpio_to_irq(key_gpio); // ③ GPIO编号 → Linux中断号
// gpio_to_irq:内核根据 GPIO 控制器的配置自动计算出对应的 IRQ 号
ret = request_irq(irq_key_num, key_irq_handler,
IRQF_TRIGGER_FALLING, "key_irq", &arg);
适用场景 :DTS 里写了 key-gpio 属性,但没有写 interrupts 属性时。
2.2 方式二:irq_of_parse_and_map(key_sub.c 用的)
流程:DTS 里的 interrupts 属性 → irq_of_parse_and_map() 直接读出 Linux 中断号
不需要先获取 GPIO 编号
c
/* key_sub.c 里的写法 */
irq_key_num = irq_of_parse_and_map(pnode, 0);
// ↑ 第 0 个中断(DTS 里只定义了一个)
// irq_of_parse_and_map:解析 DTS 的 interrupts 属性,转换成 Linux 中断号
ret = request_irq(irq_key_num, key_irq_handler,
IRQF_TRIGGER_FALLING, "key_irq", &arg);
适用场景 :DTS 里明确写了 interrupt-parent + interrupts 属性(本项目 pt.dts 的 pt_key 节点就是这样写的)。
2.3 两种方式对比
| 对比项 | gpio_to_irq | irq_of_parse_and_map |
|---|---|---|
| 前提条件 | DTS 有 key-gpio 属性 |
DTS 有 interrupts 属性 |
| 需要 gpio_request | 是 | 否(不需要提前申请GPIO) |
| 代码步骤 | 多一步(先拿GPIO编号) | 直接拿中断号 |
| 代码文件 | key_irq.c | key_sub.c |
三、中断驱动(key_irq.c)完整解析
3.1 中断服务函数
c
static int arg = 100; // 用作传给 ISR 的私有数据(演示用途)
static irqreturn_t key_irq_handler(int dev_num, void *dev)
// ↑ ↑
// 中断号(内核分配) request_irq 传入的私有数据指针
{
// dev 就是 &arg,解引用得到 100
printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev);
return IRQ_HANDLED; // 告诉内核:中断已处理完毕
}
irqreturn_t 只有两个返回值:
IRQ_HANDLED:我处理了这个中断IRQ_NONE:这个中断不是我的,让别人处理
3.2 probe 函数里的完整中断注册流程
c
static int probe(struct platform_device *pdev)
{
struct device_node *pnode;
int ret;
ret = misc_register(&misc);
if (IS_ERR_VALUE(ret)) goto err_misc;
/* ① 找设备树节点 */
pnode = of_find_node_by_path("/pt_key");
if (IS_ERR(pnode)) {
ret = PTR_ERR(pnode);
goto err_find_node;
}
/* ② 获取 GPIO 编号 */
key_gpio = of_get_named_gpio(pnode, "key-gpio", 0);
if (key_gpio < 0) {
ret = key_gpio;
goto err_get_gpio;
}
/* ③ 申请 GPIO */
ret = gpio_request(key_gpio, "pt_key");
if (ret < 0) goto err_gpio_request;
/* ④ GPIO 编号转中断号 */
irq_key_num = gpio_to_irq(key_gpio);
if (irq_key_num < 0) {
ret = irq_key_num;
goto err_gpio_to_irq;
}
/* ⑤ 注册中断
* 参数1:中断号
* 参数2:中断服务函数
* 参数3:触发方式(IRQF_TRIGGER_FALLING = 下降沿)
* 参数4:中断名字(/proc/interrupts 里显示)
* 参数5:私有数据,传给 ISR 的第二个参数
*/
ret = request_irq(irq_key_num, key_irq_handler,
IRQF_TRIGGER_FALLING, "key_irq", &arg);
if (ret < 0) goto err_request_irq;
return 0;
/* ===== 错误处理(逆序释放,下节详解)===== */
err_request_irq:
disable_irq(irq_key_num);
free_irq(irq_key_num, &arg);
err_gpio_to_irq:
err_gpio_request:
err_get_gpio:
err_find_node:
err_misc:
misc_deregister(&misc);
return ret;
}
3.3 remove 函数里的中断注销
c
static int remove(struct platform_device *pdev)
{
disable_irq(irq_key_num); // ① 先禁用中断,防止注销过程中再触发
free_irq(irq_key_num, &arg); // ② 释放中断(第二个参数必须和 request_irq 一致)
gpio_free(key_gpio); // ③ 释放 GPIO
misc_deregister(&misc);
return 0;
}
为什么先 disable_irq 再 free_irq?
free_irq会等待当前正在执行的 ISR 结束。先disable_irq可以防止在free_irq执行期间新中断进来,保证注销的安全。
四、等待队列(key_sub.c)--- 让 read 阻塞到按键按下
4.1 问题背景
key_irq.c 里 read 是空的,应用层调用 read() 会立刻返回 0,没有任何意义。
真实场景需要:应用层调用 read → 没有按键就阻塞 → 按下按键 → read 返回,这就是等待队列要解决的问题。
4.2 等待队列的三个核心操作
c
/* 头文件 */
#include <linux/wait.h>
#include <linux/sched.h>
/* ① 声明等待队列头和条件变量 */
static wait_queue_head_t wq; // 等待队列头
static int condition; // 条件变量:0=没事件,1=有事件
/* ② 在 probe 里初始化 */
init_waitqueue_head(&wq);
/* ③ 在 read 里等待 */
static ssize_t read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
condition = 0; // 先重置条件
wait_event_interruptible(wq, condition); // 阻塞,直到 condition != 0
// ↑ 如果 condition == 0,进程进入睡眠
// ↑ _interruptible 表示可以被信号(Ctrl+C)打断
printk("key read...\n");
return 0;
}
/* ④ 在中断服务函数里唤醒 */
static irqreturn_t key_irq_handler(int dev_num, void *dev)
{
condition = 1; // 设置条件为真
wake_up_interruptible(&wq); // 唤醒所有在 wq 上睡眠的进程
printk("dev_num = %d dev = %d\n", dev_num, *(int *)dev);
return IRQ_HANDLED;
}
4.3 完整运行时序
应用层:read("/dev/key")
↓
内核:进入 read() 函数
↓
condition = 0(重置)
↓
wait_event_interruptible(wq, condition=0)
↓
condition 为 0 → 进程进入睡眠,让出 CPU
↓ (用户按下按键)
硬件产生下降沿 → CPU 触发中断
↓
key_irq_handler() 执行:
condition = 1
wake_up_interruptible(&wq)
↓
read() 里的 wait_event_interruptible 被唤醒
condition 为 1 → 条件满足,继续执行
↓
read() 返回 → 应用层拿到数据
4.4 wait_event 系列函数对比
| 函数 | 说明 |
|---|---|
wait_event(wq, condition) |
等待,不可被信号打断(进程变为 D 状态) |
wait_event_interruptible(wq, condition) |
等待,可被信号打断(推荐,进程变为 S 状态) |
wait_event_timeout(wq, condition, timeout) |
等待,有超时限制 |
wake_up(&wq) |
唤醒队列上的所有进程 |
wake_up_interruptible(&wq) |
只唤醒 interruptible 状态的进程 |
五、错误处理规范(goto 链模式)
5.1 为什么要用 goto 链?
probe 函数里要申请多个资源(misc、GPIO、IRQ),如果中途失败,必须逆序释放已申请的资源,否则内存泄漏或硬件被锁死。
5.2 标准 goto 链写法
c
static int probe(struct platform_device *pdev)
{
int ret;
/* 第1步 */
ret = misc_register(&misc);
if (err) goto err_misc; // ← 失败跳到最底部
/* 第2步 */
ret = gpio_request(key_gpio, "key");
if (err) goto err_gpio; // ← 失败跳到释放 misc 的位置
/* 第3步 */
ret = request_irq(...);
if (err) goto err_irq; // ← 失败跳到释放 GPIO 的位置
return 0; // 全部成功
/* 逆序释放!申请顺序是 misc→gpio→irq,释放顺序是 irq→gpio→misc */
err_irq:
gpio_free(key_gpio);
err_gpio:
misc_deregister(&misc);
err_misc:
return ret;
}
关键原则:
goto标签的排列顺序 = 申请顺序的逆序。失败点越靠后,要释放的资源越多,对应的 label 越靠前。
5.3 IS_ERR / PTR_ERR / ERR_PTR 三兄弟
Linux 内核有些函数失败时不返回 NULL,而是返回一个"编码了错误码的指针",需要用专门的宏来判断。
c
/* key_irq.c 里的写法 */
pnode = of_find_node_by_path("/pt_key");
if (IS_ERR(pnode)) { // IS_ERR:判断指针是否是错误指针
ret = PTR_ERR(pnode); // PTR_ERR:从错误指针里提取错误码(如 -ENODEV)
goto err_find_node;
}
| 宏 | 作用 | 使用场景 |
|---|---|---|
IS_ERR(ptr) |
判断 ptr 是否是错误指针 | 调用返回指针的函数后检查 |
PTR_ERR(ptr) |
从错误指针提取错误码(负数) | IS_ERR 为真后用于记录错误 |
ERR_PTR(err) |
把错误码转成错误指针 | 自己写的函数需要返回错误指针时 |
IS_ERR_VALUE(val) |
判断整数值是否是错误码(如 ret<0) | 函数返回 int 的场景 |
c
/* 普通返回 int 的函数 → 用 IS_ERR_VALUE */
ret = misc_register(&misc);
if (IS_ERR_VALUE(ret)) /* 等价于 if (ret < 0) */
goto err_misc;
/* 返回指针的函数 → 用 IS_ERR + PTR_ERR */
pnode = of_find_node_by_path("/pt_key");
if (IS_ERR(pnode)) {
ret = PTR_ERR(pnode);
goto err_find_node;
}
六、of_match_table 匹配机制详解
led_platform.c 和 led_subgpio.c 都加入了 of_match_table,这是 DTS 时代 Platform 驱动的标准匹配方式。
6.1 旧方式(纯 name 匹配)
platform_device.name == platform_driver.driver.name
这种方式只适合没有 DTS 的场景(led_device.c + led_driver.c)。
6.2 新方式(of_match_table 匹配)
有 DTS 时,内核优先用 of_match_table 里的 compatible 字段去匹配 DTS 节点里的 compatible 属性。
c
/* led_platform.c:支持两个 compatible */
static const struct of_device_id match_table[] = {
[0] = { .compatible = "pt-led" },
[1] = { .compatible = "pt-led1" },
/* 结尾哨兵可以省略,内核用数组大小判断 */
};
/* led_subgpio.c:只支持一个 compatible */
static const struct of_device_id match_table[] = {
[0] = { .compatible = "pt-gpioled" },
};
static struct platform_driver drv = {
.probe = probe,
.remove = remove,
.driver = {
.name = DEV_NAME, // 备用名字匹配(无 DTS 时用)
.of_match_table = match_table, // DTS 匹配表(有 DTS 时优先用)
}
};
匹配逻辑:
内核启动,解析 DTS → 找到 compatible = "pt-led" 的节点
↓
遍历 platform_driver 链表
↓
找到 of_match_table 里有 "pt-led" 的驱动
↓
match 成功 → 调用 probe(pdev)
七、pinctrl 引脚配置(DTS 里的重要机制)
pt_gpioled 和 pt_key 节点里都有:
dts
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>; /* 引用 iomuxc 下定义的 group */
这两行的作用是:内核在 probe 前,自动根据 pinctrl 配置完成引脚复用设置,驱动里不需要手动操作 IOMUXC 寄存器。
dts
/* &iomuxc 里的定义 */
pinctrl_gpioled: gpioledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
/* 宏名格式:MX6UL_PAD_物理引脚名__复用功能名 配置值 */
/* 0x10b0 = 推挽输出,默认速度,使能keeper */
>;
};
pinctrl_ptkey: ptkeygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
/* 0xF080 = 使能上拉,输入模式,用于检测按键 */
>;
};
这就是为什么 led_subgpio.c 里不需要
*sw_mux = 0x05这种操作 ------引脚复用已经由 DTS + pinctrl 子系统在驱动加载前自动完成了。
八、中断触发方式速查
dts
/* DTS 里 interrupts 的第二个值 */
interrupts = <18 IRQ_TYPE_EDGE_FALLING>;
// ↑
// IRQ_TYPE_EDGE_RISING = 1 上升沿
// IRQ_TYPE_EDGE_FALLING = 2 下降沿(按键按下,高→低)
// IRQ_TYPE_EDGE_BOTH = 3 双边沿(按下和松开都触发)
// IRQ_TYPE_LEVEL_HIGH = 4 高电平
// IRQ_TYPE_LEVEL_LOW = 8 低电平
c
/* request_irq 里 flags 对应关系 */
IRQF_TRIGGER_RISING // 上升沿
IRQF_TRIGGER_FALLING // 下降沿
IRQF_TRIGGER_HIGH // 高电平
IRQF_TRIGGER_LOW // 低电平
/* 组合使用(双边沿)*/
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
九、本节涉及的全部新 API 速查
c
/* ===== GPIO 子系统 ===== */
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
// 从 DTS 属性里读出 GPIO 编号
int gpio_request(unsigned gpio, const char *label);
// 申请独占使用一个 GPIO
void gpio_free(unsigned gpio);
// 释放 GPIO
int gpio_direction_output(unsigned gpio, int value);
// 设置为输出,同时设初始值
int gpio_direction_input(unsigned gpio);
// 设置为输入
void gpio_set_value(unsigned gpio, int value);
// 设置输出值(0 或 1)
int gpio_get_value(unsigned gpio);
// 读取 GPIO 当前电平
/* ===== 中断 ===== */
unsigned int gpio_to_irq(unsigned int gpio);
// GPIO 编号 → Linux 中断号
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
// 直接从 DTS interrupts 属性解析中断号
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev);
// 注册中断服务函数
void free_irq(unsigned int irq, void *dev);
// 注销中断(dev 必须和 request_irq 时一致)
void disable_irq(unsigned int irq);
// 禁用中断(会等当前 ISR 执行完)
void enable_irq(unsigned int irq);
// 重新启用中断
/* ===== 等待队列 ===== */
init_waitqueue_head(wait_queue_head_t *wq);
// 初始化等待队列头
wait_event_interruptible(wq, condition);
// 等待直到 condition 为真(可被信号打断)
wake_up_interruptible(wait_queue_head_t *wq);
// 唤醒在 wq 上等待的进程
/* ===== 错误处理 ===== */
IS_ERR(ptr) // 判断指针是否是错误指针
PTR_ERR(ptr) // 提取错误指针里的错误码
IS_ERR_VALUE(val) // 判断整数是否是错误值(val < 0)
十、完整流程图
开机:内核解析 pt.dts
├─ pt_led 节点 → compatible="pt-led" → 等待驱动
├─ pt_gpioled 节点 → compatible="pt-gpioled" → 等待驱动
└─ pt_key 节点 → compatible="pt-key" → 等待驱动
↓ 内核根据 pinctrl-0 自动配置引脚复用
insmod led_subgpio.ko
→ platform_driver_register(compatible="pt-gpioled")
→ 匹配 pt_gpioled 节点 → probe() 调用
→ misc_register() → /dev/led 出现
→ of_get_named_gpio() → 读出 GPIO1_IO03
→ gpio_request() → 申请 GPIO
→ gpio_direction_output() → 设置为输出,默认灭
insmod key_irq.ko (或 key_sub.ko)
→ platform_driver_register(compatible="pt-key")
→ 匹配 pt_key 节点 → probe() 调用
→ misc_register() → /dev/key 出现
→ of_find_node_by_path() → 找到 /pt_key 节点
→ of_get_named_gpio() → 读出 GPIO1_IO18
→ gpio_request()
→ gpio_to_irq() → 得到中断号 (key_irq.c)
或 irq_of_parse_and_map() → 直接解析 DTS 得到中断号 (key_sub.c)
→ request_irq() → 注册中断,等待按键
→ init_waitqueue_head() → 初始化等待队列 (key_sub.c)
应用层:read("/dev/key")
→ key_sub.c:wait_event_interruptible(wq, condition=0) → 阻塞睡眠
↓ (用户按下按键,GPIO1_IO18 下降沿)
→ key_irq_handler() 执行
→ condition = 1
→ wake_up_interruptible(&wq) → 唤醒 read
→ read() 继续执行,返回应用层
rmmod key_irq / key_sub:
remove() → disable_irq() → free_irq() → gpio_free() → misc_deregister()
rmmod led_subgpio:
remove() → gpio_free() → misc_deregister()
十一、三个驱动文件横向对比
| 对比项 | led_platform.c | led_subgpio.c | key_irq.c / key_sub.c |
|---|---|---|---|
| 硬件操作方式 | ioremap 操作寄存器 | GPIO 子系统 | GPIO 子系统 + 中断 |
| DTS 属性 | reg |
led-gpio |
key-gpio + interrupts |
| 获取资源函数 | of_property_read_u32_array |
of_get_named_gpio |
of_get_named_gpio / irq_of_parse_and_map |
| 中断 | 无 | 无 | request_irq |
| 等待队列 | 无 | 无 | key_sub.c 有 |
| 错误检查风格 | if (ret < 0) |
IS_ERR_VALUE / IS_ERR |
IS_ERR_VALUE / IS_ERR |
| 头文件 | <linux/of.h> |
<linux/of_gpio.h> <linux/gpio.h> |
同左 + <linux/interrupt.h> <linux/of_irq.h> <linux/wait.h> |
十二、等待队列 vs 轮询(为什么不用 while 循环)
一个常见的错误思路是用标志位 + while 循环来等待按键:
c
// ❌ 错误做法:CPU 一直空转,100% 占用
while (condition == 0) {
// 什么都不做,干等着
}
这种方式非常浪费资源,CPU 一直空转无法执行其他任务。等待队列的本质是让进程进入睡眠状态,把 CPU 让给别人,等到条件满足时再被唤醒:
while 轮询:进程一直占用 CPU → CPU 使用率 100%
等待队列:进程睡眠,让出 CPU → 中断来了再唤醒 → CPU 使用率接近 0%
十三、IRQF_ONESHOT 中断标志(补充知识点)
request_irq 的第三个参数除了触发方式,还有一个常用标志:
c
request_irq(irq, handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "name", dev);
IRQF_ONESHOT 的作用:
数据到来时触发中断,在没有把数据读空之前 ,再有数据来就不触发中断。
适用场景:某些设备(如触摸屏、UART)数据连续来到时,如果每次都触发中断会导致中断嵌套或数据混乱。加上 IRQF_ONESHOT 后,中断处理函数执行完之前,同一中断不会再次触发,保证数据处理的完整性。
| 标志 | 含义 |
|---|---|
IRQF_TRIGGER_FALLING |
下降沿触发 |
IRQF_TRIGGER_RISING |
上升沿触发 |
| `IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING` |
IRQF_ONESHOT |
中断处理完成前屏蔽同一中断 |
IRQF_SHARED |
多个设备共享同一中断号 |
十四、实际调试中遇到的问题
问题一:insmod 返回错误码 -16(-EBUSY,设备忙)
现象 :insmod key_irq.ko 失败,dmesg 显示 -16。
原因:板子默认的设备树(厂商提供的 DTB)里已经有 key 或 led 的相关节点,内核已经加载了对应的驱动并占用了 GPIO/中断资源,再次注册就冲突了。
解决:把默认设备树中与 key、led 相关的节点全部删掉或注释掉,换用自己写的 pt.dts 编译的 DTB。
dts
/* 把厂商 DTS 里类似这些节点注释掉 */
/*
key {
compatible = "atkalpha-key";
...
};
*/
问题二:insmod 返回错误码 -2(-ENOENT,找不到文件/节点)
现象 :insmod 失败,dmesg 显示 -2。
原因:驱动和设备节点没有匹配上,probe 根本没有被调用,或者 probe 里找不到节点。
排查清单:
of_match_table里的compatible是否和 DTS 节点里的compatible完全一致(区分大小写)of_find_node_by_path("/pt_key")里的路径是否和 DTS 节点名一致of_get_named_gpio(pnode, "key-gpio", 0)里的属性名是否和 DTS 里的一致- DTB 是否已经更新并重新加载(改了 DTS 但没重新编译部署)
问题三:为什么按键驱动(key_sub.c)不用 of_get_named_gpio?
key_sub.c 只关心中断事件(按键是否被按下),不需要直接读取 GPIO 电平(比如做消抖、轮询状态)。
key_irq.c:需要拿 GPIO 编号 → 再转中断号,因此显式调用of_get_named_gpiokey_sub.c:直接用irq_of_parse_and_map从 DTS 的interrupts属性拿中断号,完全跳过 GPIO 这一步
如果后续需要在中断里读取按键当前电平(判断按下还是松开),才需要加上 of_get_named_gpio。
十五、Vim 常用操作补充
在修改 DTS 和驱动文件时经常用到:
vim
# 替换当前行第一个匹配
:s/old/new/
# 替换当前行所有匹配
:s/old/new/g
# 替换全文所有匹配
:%s/old/new/g
# 替换时逐个确认
:%s/old/new/gc
# 示例:把所有 pt-led 替换为 pt-gpioled
:%s/pt-led/pt-gpioled/g