Key Input 子系统驱动开发 文章封面

Key Input 子系统驱动开发

一、驱动概述

本驱动是对旧版 key.c(miscdevice 方案)的重构版本,使用 Linux Input 子系统
替代自定义字符设备,实现同一硬件的两种驱动方式对比学习。

对比维度旧版 key.c新版 key_input.c
框架miscdevice + file_operationsInput 子系统
用户接口/dev/key,返回自定义 0/1/dev/input/eventX,标准 input_event
数据获取用户轮询(sleep + read 循环)内核上报,用户 read() 阻塞等待
工具兼容仅自己的 appevtest、Android InputReader 直接可用
驱动切换DTS compatible = "alientek,key"DTS compatible = "alientek,key-input"
  • 硬件平台:iMX6ULL ALPHA,GPIO1_IO18,低有效(GPIO_ACTIVE_LOW)
  • 内核版本:4.14

二、整体架构与初始化流程

2.1 软件层次

用户空间:read(/dev/input/eventX) → struct input_event
             ↓ 阻塞,有事件时内核唤醒
evdev 层:evdev_handler 接收 input_dev 上报的事件,写入环形缓冲
             ↓
Input 核心:input_handle_event 分发事件
             ↓ input_report_key + input_sync
驱动层:  防抖定时器到期 → gpiod_get_value → input_report_key
             ↑ mod_timer(防抖)
IRQ 上半部:GPIO 边沿中断
             ↑
硬件:    GPIO1_IO18 按键

2.2 Probe 初始化流程

flowchart TD
    A[key_input_probe 入口] --> B[devm_kzalloc<br/>分配 key_input_dev]
    B --> C[devm_gpiod_get<br/>获取 key GPIO 描述符]
    C -->|失败| D[return PTR_ERR]
    C -->|成功| E[devm_input_allocate_device<br/>分配 input_dev]
    E --> F[设置 idev->name<br/>idev->dev.parent]
    F --> G["input_set_capability<br/>(EV_KEY, KEY_0)<br/>声明支持的事件能力"]
    G --> H[input_register_device<br/>注册,创建 /dev/input/eventX]
    H -->|失败| I[return ret]
    H -->|成功| J["setup_timer<br/>初始化防抖定时器"]
    J --> K["gpiod_to_irq<br/>GPIO → IRQ 编号"]
    K --> L["devm_request_irq<br/>FALLING + RISING 双沿"]
    L -->|失败| M[return ret]
    L -->|成功| N["platform_set_drvdata<br/>probe ok"]

三、数据结构

classDiagram
    class key_input_dev {
        +gpio_desc *gpiod
        +input_dev *idev
        +timer_list timer
        +unsigned int irqnum
    }
    class input_dev {
        +char *name
        +unsigned long evbit[]
        +unsigned long keybit[]
        +struct device dev
    }
    class gpio_desc {
        +GPIO1_IO18
        +ACTIVE_LOW
    }
    class timer_list {
        +key_input_timer()
        +expires
    }
    class UserSpace {
        <<用户空间>>
        +/dev/input/eventX
        +struct input_event
        +read() 阻塞
    }

    key_input_dev --> input_dev : .idev
    key_input_dev --> gpio_desc : .gpiod
    key_input_dev --> timer_list : .timer
    input_dev ..> UserSpace : Input核心分发事件

四、关键代码说明

4.1 完整事件流时序图

sequenceDiagram
    participant HW as 硬件<br/>GPIO1_IO18
    participant IRQ as IRQ 上半部<br/>key_input_irq
    participant TMR as 防抖定时器<br/>key_input_timer
    participant INP as Input 核心
    participant EVD as evdev
    participant USR as 用户空间<br/>read(eventX)

    HW->>IRQ: GPIO 下降沿(按下)
    IRQ->>TMR: mod_timer(+10ms)
    Note over IRQ: 只做 mod_timer<br/>立即返回

    HW->>IRQ: 抖动边沿(忽略)
    IRQ->>TMR: mod_timer 重置

    TMR->>TMR: 10ms 到期执行
    TMR->>HW: gpiod_get_value()
    HW-->>TMR: 1(逻辑高=按下)
    TMR->>INP: input_report_key(KEY_0, 1)
    TMR->>INP: input_sync()
    INP->>EVD: 分发 EV_KEY + EV_SYN
    EVD->>USR: 唤醒阻塞的 read()
    USR-->>USR: 处理 input_event

    HW->>IRQ: GPIO 上升沿(松开)
    IRQ->>TMR: mod_timer(+10ms)
    TMR->>HW: gpiod_get_value()
    HW-->>TMR: 0(逻辑低=松开)
    TMR->>INP: input_report_key(KEY_0, 0)
    TMR->>INP: input_sync()
    INP->>EVD: 分发 EV_KEY + EV_SYN
    EVD->>USR: 唤醒阻塞的 read()

4.2 核心代码段

声明事件能力:

dev-&gt;idev-&gt;name = "key-input";
dev-&gt;idev-&gt;dev.parent = &amp;pdev-&gt;dev;

/* 声明支持 EV_KEY 类型中的 KEY_0 键码
 * Input 核心只会向上层传递已声明的事件,未声明的会被过滤 */
input_set_capability(dev-&gt;idev, EV_KEY, KEY_0);

ret = input_register_device(dev-&gt;idev);

防抖定时器上报事件:

static void key_input_timer(unsigned long arg)
{
    struct key_input_dev *dev = (struct key_input_dev *)arg;
    int val = gpiod_get_value(dev-&gt;gpiod);  /* 1=按下,0=松开 */

    input_report_key(dev-&gt;idev, KEY_0, val); /* 上报键值 */
    input_sync(dev-&gt;idev);                   /* 提交,触发用户 read 返回 */
}

IRQ 上半部(极简):

static irqreturn_t key_input_irq(int irq, void *data)
{
    struct key_input_dev *dev = data;

    /* 只做防抖延时,不在中断上下文读 GPIO */
    mod_timer(&amp;dev-&gt;timer, jiffies + msecs_to_jiffies(KEY_DEBOUNCE_MS));
    return IRQ_HANDLED;
}

remove 时等待定时器:

static int key_input_remove(struct platform_device *pdev)
{
    struct key_input_dev *dev = platform_get_drvdata(pdev);

    /* del_timer_sync:等待正在执行的定时器回调结束
     * 防止 remove 返回后回调访问已释放的 idev 指针 */
    del_timer_sync(&amp;dev-&gt;timer);
    return 0;
}


五、DTS 配置

key {
    compatible = "alientek,key-input";
    /* 改为 "alientek,key" 可切换回旧版 misc 驱动 */
    pinctrl-names = "default";
    pinctrl-0 = &lt;&amp;key_pins&gt;;
    key-gpio = &lt;&amp;gpio1 18 GPIO_ACTIVE_LOW&gt;;
    status = "okay";
};

两套驱动切换只需改一行 compatible,无需修改任何代码:

"alientek,key-input"  →  加载 key_input.ko  →  /dev/input/eventX
"alientek,key"        →  加载 key.ko         →  /dev/key


六、测试验证

6.1 加载驱动

insmod /lib/modules/key_input.ko
dmesg | tail -3
# 期望:key-input probe ok (irq=49)

ls /dev/input/
# 出现 event0 或 event1

cat /sys/class/input/event0/device/name
# 输出:key-input

6.2 测试 App

key_input_test            # 自动查找设备,阻塞等待
key_input_test /dev/input/event0   # 指定路径

输出示例:

Opening: /dev/input/event0
Waiting for key events (Ctrl+C to exit)...
[1234.567890] KEY_11 PRESSED
[1234.678901] KEY_11 RELEASED

6.3 使用 evtest(如果板子上有)

evtest /dev/input/event0


七、旧版 vs 新版代码对比

用户空间读取方式对比

旧版(miscdevice):

/* 必须主动轮询,浪费 CPU */
while (1) {
    read(fd, &amp;val, sizeof(val));  /* 立即返回,不管有没有事件 */
    if (val != last_val) {
        printf("key %sn", val ? "pressed" : "released");
        last_val = val;
    }
    usleep(20000);  /* 必须手动 sleep 降低 CPU 占用 */
}

新版(Input 子系统):

/* read() 阻塞,内核有事件才返回,零轮询 */
while (read(fd, &amp;ev, sizeof(ev)) == sizeof(ev)) {
    if (ev.type == EV_KEY)
        printf("key %sn", ev.value ? "pressed" : "released");
}

内核驱动代码量对比

部分旧版 key.c新版 key_input.c
数据结构miscdevice + file_operations + spinlockinput_dev(无需 lock)
open/read/release需实现不需要,Input 核心处理
上报数据存入 key_val,等 read 取走input_report_key + input_sync
代码总行数~180 行~120 行

八、文件索引

drivers/key_input/
├── key_input.c     ← Input 子系统驱动
└── Makefile

app/key_input_test/
├── key_input_test.c    ← 测试应用(自动查找设备,阻塞读取)
└── Makefile

kernel4.14/arch/arm/boot/dts/
└── imxplore.dts    ← key 节点 compatible 改为 "alientek,key-input"


参考

延伸阅读

新版 Input 按键驱动适合与子系统原理和旧版 miscdevice 实现一起比较。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部