一、核心定位
Linux input 子系统是统一管理所有输入设备的内核框架:键盘、鼠标、触摸屏、编码器、游戏手柄、红外遥控、电容按键等。
作用:
- 用户空间通过
/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);};
#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);
voidinput_report_abs(struct input_dev *dev, unsignedint code, int value);
⚠️ 不调用 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_report_rel() 上报 REL_X(顺时针 + 1,逆时针 - 1),符合鼠标增量规范 - 标准 input_dev,上层可用
evtest 直接读取 - 适合嵌入式 Linux(RK3566、IMX6ULL、STM32MP1 等平台)
⚠️ 注意:硬件接法
- ENC_A / ENC_B 接 GPIO,外部带上拉或开启内部上拉
#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");