ARM-05-Platform + DTS + GPIO子系统 + 中断 + 等待队列 + 错误处理

零、Platform 总线结合 DTS 的核心思想

为什么要用这种组合?

传统方式(仅 Platform) :每支持一块新板子,就要在 arch/arm/mach-xxx/ 下新增一个 C 文件(少则几百行,多则上千行),里面充满 platform_device_registerresource 定义、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.ckey_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.cread 是空的,应用层调用 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.cled_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_gpioledpt_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_gpio
  • key_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
相关推荐
满天星83035772 小时前
【MySQL】索引
linux·服务器·数据库·mysql
T0uken2 小时前
【Linux】耗时任务执行并邮件通知
linux·运维·服务器
c++逐梦人2 小时前
Linux生产者消费者模型
linux
Yolo566Q3 小时前
从机理到实践告别“黑箱”模拟:OpenGeoSys(OGS6)多物理场THMC 全耦合建模与Python自动化分析
运维·自动化
网络安全许木3 小时前
自学渗透测试第16天(Linux文本处理进阶)
linux·运维·服务器·网络安全·渗透测试
Lugas Luo3 小时前
车载录像存储性能模拟测试工具设计
linux·嵌入式硬件·测试工具
铅笔小新z3 小时前
【Linux】进程控制(下)
linux·运维·chrome
企鹅的蚂蚁3 小时前
Ubuntu 22.04 终端进阶:Terminator 安装与快捷键完全手册
linux·运维·ubuntu
不会写程序的未来程序员3 小时前
nvm 安装教程:Node.js 版本管理全攻略 (Win/Mac/Linux) + .nvmrc 实战
linux·macos·node.js·前端开发·环境配置·nvm