当前位置:首页>Linux>Linux 中 Input 子系统完整梳理

Linux 中 Input 子系统完整梳理

  • 2026-10-11 06:06:43
Linux 中 Input 子系统完整梳理

一、核心定位

Linux input 子系统是统一管理所有输入设备的内核框架:键盘、鼠标、触摸屏、编码器、游戏手柄、红外遥控、电容按键等。

作用:

  1. 屏蔽底层硬件差异;
  2. 提供标准事件上报接口;
  3. 用户空间通过 /dev/input/eventX 统一读取事件。
分层结构:
硬件寄存器 → 底层驱动 → input核心层(input.c) → evdev处理层 → /dev/input/eventX → 应用程序
二、核心组件

1. struct input_dev(设备描述符)

每一个输入设备对应一个 input_dev,驱动最核心结构体。

关键成员:

struct input_dev {    const char *name;        //设备名字    const char *phys;        //物理路径    const char *uniq;        //唯一ID    unsigned long evbit[NBITS(EV_MAX)];    //支持事件类型    unsigned long keybit[NBITS(KEY_MAX)];  //支持按键码    unsigned long relbit[NBITS(REL_MAX)];  //相对坐标(鼠标)    unsigned long absbit[NBITS(ABS_MAX)];  //绝对坐标(触摸屏)    // ...    int (*open)(struct input_dev *dev);    void (*close)(struct input_dev *dev);};
2. 事件类型 EV_*
evbit标记设备支持哪类事件:
#define EV_SYN      0x00    //同步事件(数据包结束标记,必须跟随一组数据发送)#define EV_KEY      0x01    //按键事件(键盘、按键、编码器按下/松开)#define EV_REL      0x02    //相对位移(鼠标、旋转编码器增量)#define EV_ABS      0x03    //绝对坐标(触摸屏、摇杆)#define EV_MSC      0x04    //杂项事件#define EV_SW       0x05    //开关事件#define EV_LED      0x11    //LED控制#define EV_REP      0x14    //键盘自动重复

3. 上报 API(驱动层最重要)

(1)按键上报 EV_KEY

//code: KEY_xxx;value:1按下,0松开voidinput_report_key(struct input_dev *dev, unsignedint code, int value);
(2)相对增量上报 EV_REL(旋转编码器首选)
//code: REL_X / REL_Y,value = +1 / -1voidinput_report_rel(struct input_dev *dev, unsignedint code, int value);
(3)绝对坐标 EV_ABS
voidinput_report_abs(struct input_dev *dev, unsignedint code, int value);
(4)必须调用!同步事件,结束一组上报
input_sync(dev);
⚠️ 不调用 input_sync,用户态收不到完整事件包!
三、驱动标准开发流程(极简模板)
//分配 input_devstruct input_dev *idev = input_allocate_device();
//设置支持的事件类型set_bit(EV_KEY, idev->evbit);set_bit(KEY_ENTER, idev->keybit);// 编码器例子:相对位移set_bit(EV_REL, idev->evbit);set_bit(REL_X, idev->relbit);
//注册设备input_register_device(idev);
//硬件中断 / 轮询中上报事件input_report_rel(idev, REL_X, dir);input_sync(idev);
//卸载input_unregister_device(idev);input_free_device(idev);
旋转编码器 Input 子系统内核驱动 Demo

特性:

  1. 使用 input_report_rel() 上报 REL_X(顺时针 + 1,逆时针 - 1),符合鼠标增量规范
  2. GPIO 中断方式采集 A/B 相正交信号
  3. 标准 input_dev,上层可用 evtest 直接读取
  4. 兼容 4 倍频正交解码
  5. 适合嵌入式 Linux(RK3566、IMX6ULL、STM32MP1 等平台)

⚠️ 注意:硬件接法

  • ENC_A / ENC_B 接 GPIO,外部带上拉或开启内部上拉
  • 编码器旋转产生正交相位 A/B
#include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/gpio/consumer.h>#include<linux/interrupt.h>#include<linux/input.h>#include<linux/platform_device.h>/* ===================== 可配置参数 ===================== */#define ENC_GPIO_A      12      // A相GPIO#define ENC_GPIO_B      13      // B相GPIO/* ===================================================== */struct encoder_data {    struct input_dev *idev;    struct gpio_desc *gpiod_a;    struct gpio_desc *gpiod_b;    int last_state;     // 上一次AB组合状态 (A<<1 | B)};// 正交编码器4倍频状态转移表// state = (A << 1) | B// 00,01,11,10static const int enc_table[16] = {    0, -1, 1, 0,    1, 0, 0, -1,    -1, 0, 0, 1,    0, 1, -1, 0};staticirqreturn_tencoder_isr(int irq, void *dev_id){    struct encoder_data *ed = dev_id;    int a = gpiod_get_value(ed->gpiod_a);    int b = gpiod_get_value(ed->gpiod_b);    int cur_state = (a << 1) | b;    int delta = enc_table[(ed->last_state << 2) | cur_state];    if (delta != 0) {        input_report_rel(ed->idev, REL_X, delta);        input_sync(ed->idev);    }    ed->last_state = cur_state;    return IRQ_HANDLED;}staticintencoder_probe(struct platform_device *pdev){    struct encoder_data *ed;    int irq_a, irq_b;    int ret;    ed = devm_kzalloc(&pdev->dev, sizeof(*ed), GFP_KERNEL);    if (!ed)        return -ENOMEM;    platform_set_drvdata(pdev, ed);    /* 获取GPIO */    ed->gpiod_a = devm_gpiod_get(&pdev->dev, "enc-a", GPIOD_IN);    ed->gpiod_b = devm_gpiod_get(&pdev->dev, "enc-b", GPIOD_IN);    if (IS_ERR(ed->gpiod_a) || IS_ERR(ed->gpiod_b)) {        dev_err(&pdev->dev, "Failed get gpio\n");        return PTR_ERR(IS_ERR(ed->gpiod_a) ? ed->gpiod_a : ed->gpiod_b);    }    /* 创建 input 设备 */    ed->idev = devm_input_allocate_device(&pdev->dev);    if (!ed->idev)        return -ENOMEM;    ed->idev->name = "rotary-encoder";    set_bit(EV_REL, ed->idev->evbit);    set_bit(REL_X, ed->idev->relbit);    ret = input_register_device(ed->idev);    if (ret) {        dev_err(&pdev->dev, "input register fail\n");        return ret;    }    /* 申请双边沿中断(A、B相任意跳变都进中断,实现4倍频) */    irq_a = gpiod_to_irq(ed->gpiod_a);    irq_b = gpiod_to_irq(ed->gpiod_b);    ret = devm_request_irq(&pdev->dev, irq_a, encoder_isr,                            IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                            "enc-a", ed);    if (ret)        return ret;    ret = devm_request_irq(&pdev->dev, irq_b, encoder_isr,                            IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                            "enc-b", ed);    if (ret)        return ret;    /* 初始化当前AB状态 */    ed->last_state = (gpiod_get_value(ed->gpiod_a) << 1) | gpiod_get_value(ed->gpiod_b);    dev_info(&pdev->dev, "rotary encoder input driver probed\n");    return 0;}staticintencoder_remove(struct platform_device *pdev){    dev_info(&pdev->dev, "encoder driver removed\n");    return 0;}static struct platform_driver encoder_driver = {    .probe = encoder_probe,    .remove = encoder_remove,    .driver = {        .name = "rotary-encoder-input",    },};module_platform_driver(encoder_driver);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Rotary Encoder Linux Input Subsystem Demo");MODULE_AUTHOR("RED");

最新文章

随机文章