在Linux系统中,无论是键盘敲击、鼠标移动、触摸屏滑动,还是游戏手柄摇杆操作,所有输入设备都通过同一套框架与系统交互——这就是Linux输入子系统(Input Subsystem)。作为内核中设计最精巧、最经典的子系统之一,它屏蔽了底层硬件差异,为上层应用提供了统一的交互接口,是构建整个人机交互生态的核心基石。本文将从架构原理、核心机制到驱动开发实战,带你全面拆解输入子系统的运作逻辑。
在没有统一框架的时代,每款输入设备都需要单独编写字符设备驱动:开发者既要处理底层硬件寄存器操作,还要自行实现read、write、ioctl等用户态接口,不同设备的数据格式也无法互通。这导致了两个严重问题:
驱动开发冗余:键盘、鼠标、触摸屏、游戏手柄……每种设备都要从零开始实现一套文件操作接口,大量重复劳动。
应用适配成本高:上层应用程序(如X11、GUI框架)必须针对每种设备编写不同的解析逻辑,系统扩展性极差。
输入子系统的出现一举解决了这些痛点,其核心价值体现在三个方面:
EV_KEY / KEY_A / 1就代表"A键按下",EV_ABS / ABS_X / 512代表"X轴绝对坐标为512"。
设备驱动层(最底层)
直接与物理硬件交互,负责初始化GPIO/中断控制器、配置时钟、注册中断处理函数、采集原始输入数据(如扫描码、ADC坐标值、压力值),并完成硬件去抖动等底层时序控制。它将原始硬件信号转换为标准事件,通过核心层提供的接口向上汇报。
核心层(中间层)
位于drivers/input/input.c,是整个子系统的"中枢神经"。它提供统一的设备注册/注销接口、事件分发引擎、设备与处理器的自动匹配机制,同时维护全局的设备链表和处理器链表。驱动开发者不需要直接操作这一层的大部分代码,但它是连接上下两层的桥梁。
事件处理层(最上层)
将核心层分发的标准事件流按需路由至不同的用户接口。典型的事件处理器包括:
/dev/input/eventX | ||
/dev/input/mice | ||
/dev/input/jsX | ||
/dev/ttyN | /dev/input/下的节点 |
在Linux输入子系统中,input_dev、input_handler、input_handle是三个最核心的数据结构,它们之间的关系构成了整个输入框架的骨架。
input_dev定义在include/linux/input.h中,代表一个具体的输入设备实例。无论是键盘、鼠标、触摸屏还是游戏手柄,在输入子系统中都被抽象为一个input_dev结构体。
structinput_dev {
constchar *name; /* 设备名称 */
constchar *phys; /* 设备在系统中的物理路径 */
constchar *uniq; /* 设备唯一标识符 */
structinput_idid;/* 设备ID(总线类型、厂商、产品、版本) */
/* ===== 能力位图:声明设备支持什么 ===== */
unsignedlong propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsignedlong evbit[BITS_TO_LONGS(EV_CNT)]; /* 支持的事件类型 */
unsignedlong keybit[BITS_TO_LONGS(KEY_CNT)]; /* 支持的按键码 */
unsignedlong relbit[BITS_TO_LONGS(REL_CNT)]; /* 支持的相对轴 */
unsignedlong absbit[BITS_TO_LONGS(ABS_CNT)]; /* 支持的绝对轴 */
unsignedlong mscbit[BITS_TO_LONGS(MSC_CNT)];
unsignedlong ledbit[BITS_TO_LONGS(LED_CNT)];
unsignedlong sndbit[BITS_TO_LONGS(SND_CNT)];
unsignedlong ffbit[BITS_TO_LONGS(FF_CNT)];
unsignedlong swbit[BITS_TO_LONGS(SW_CNT)];
/* ===== 状态缓存 ===== */
unsignedlong key[BITS_TO_LONGS(KEY_CNT)]; /* 当前按键状态 */
unsignedlong led[BITS_TO_LONGS(LED_CNT)];
unsignedlong snd[BITS_TO_LONGS(SND_CNT)];
unsignedlong sw[BITS_TO_LONGS(SW_CNT)];
/* ===== 操作回调 ===== */
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsignedint type,
unsignedint code, int value);
/* ===== 链表与同步 ===== */
structinput_handle __rcu *grab;/* 当前独占设备的handle */
spinlock_t event_lock;
structmutexmutex;
unsignedint users; /* 打开该设备的用户数 */
bool going_away;
structdevicedev;/* 嵌入的通用设备模型 */
structlist_headh_list;/* 关联的handle链表 */
structlist_headnode;/* 挂到全局input_dev_list */
/* ===== 事件批处理 ===== */
unsignedint num_vals;
unsignedint max_vals;
structinput_value *vals;
...
};
核心字段分类解读
name | "gpio_keys" | |
phys | "gpio-keys/input0" | |
uniq | ||
id | bustype、vendor、product、version,用于与Handler匹配 | bustype=BUS_HOST |
能力位图是设备驱动注册时最重要的配置,它告诉核心层和事件处理器"这个设备能产生哪些事件"。
位图决定了匹配结果:Handler通过检查这些位图来判断是否能处理该设备。例如evdev的id_table中driver_info = 1表示匹配所有设备,而mousedev会检查EV_REL和BTN_MOUSE等位。
状态缓存区
这些状态位图在事件上报时由核心层自动更新,驱动无需手动维护。
操作回调区
open | ||
close | ||
event | ||
flush |
链表节点区
node:将设备挂到全局input_dev_list链表上,核心层通过遍历该链表实现设备管理
h_list:挂载所有与该设备关联的input_handle,一个设备可以匹配多个Handler,因此有多个handle
生命周期管理
input_allocate_device() → 配置字段 → input_register_device()
↓
遍历input_handler_list
逐一尝试匹配(input_attach_handler)
↓
匹配成功则调用handler->connect()
创建input_handle并创建设备节点
↓
input_unregister_device() → 自动清理所有关联的handle和设备节点
input_allocate_device()内部会完成:内存分配、device_initialize()、互斥锁/自旋锁/定时器初始化、链表头初始化、设备名自动编号(input0、input1...)。
input_handler定义在include/linux/input.h中,代表一类事件处理程序。它是纯软件层,不涉及任何硬件操作,职责是将内核事件传递到用户空间或内核其他子系统。
structinput_handler {
void *private; /* 处理器私有数据 */
/* ===== 事件处理回调 ===== */
void (*event)(struct input_handle *handle, unsignedint type,
unsignedint code, int value);
unsignedint(*events)(struct input_handle *handle,
const struct input_value *vals,
unsignedint count);
/* ===== 过滤器 ===== */
bool (*filter)(struct input_handle *handle, unsignedint type,
unsignedint code, int value);
/* ===== 自定义匹配 ===== */
bool (*match)(struct input_handler *handler, struct input_dev *dev);
/* ===== 连接管理 ===== */
int (*connect)(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
bool legacy_minors;
int minor;
constchar *name; /* 处理器名称 */
conststructinput_device_id *id_table;/* 匹配规则表 */
conststructinput_device_id *blacklist;/* 黑名单 */
structlist_headh_list;/* 关联的handle链表 */
structlist_headnode;/* 全局input_handler_list链表 */
};
核心字段分类解读事件处理回调
event | {type, code, value} | |
events | event | |
filter | true则丢弃该事件 |
核心层分发事件时,优先调用events(批量),若未实现则回退到逐个调用event。
匹配机制
匹配由两部分组成:
以evdev为例,它的id_table只有一项{ .driver_info = 1 },意味着匹配所有设备——这就是为什么每个输入设备都会生成/dev/input/eventX节点。
连接管理回调
connect | input_handle、创建设备节点、初始化缓冲区等 | |
disconnect | ||
start |
链表节点
内核中常见的Handler实例
"evdev" | /dev/input/eventX | ||
"mousedev" | /dev/input/mice | EV_REL + 鼠标按键 | |
"joydev" | /dev/input/jsX | EV_ABS + 摇杆轴 | |
"kbd" | /dev/ttyN |
定义与定位
input_handle是三个结构体中最"轻量"却最关键的,它不包含任何业务逻辑,纯粹是一个连接节点。
structinput_handle {
void *private; /* Handler私有数据指针 */
int open; /* 打开计数(>0才投递事件) */
constchar *name; /* Handler给handle取的名字 */
structinput_dev *dev;/* 指向关联的输入设备 */
structinput_handler *handler;/* 指向关联的事件处理器 */
structlist_headd_node;/* 挂到设备的h_list */
structlist_headh_node;/* 挂到处理器的h_list */
};
各字段详解
private | connect时通常将自己的上下文结构体挂在这里。例如evdev_connect中handle.private = evdev,后续事件处理时通过handle->private取回evdev实例 |
open | open > 0时,核心层才会向该handle投递事件。用户调用open("/dev/input/eventX")时递增,close时递减 |
dev | input_dev |
handler | input_handler |
d_node | input_dev->h_list链表上 |
h_node | input_handler->h_list链表上 |
为什么需要input_handle?核心原因是多对多关系:
如果直接让input_dev和input_handler一对一关联,就无法表达这种多对多关系。input_handle作为中间节点,通过双向链表分别挂在设备和Handler的h_list上,优雅地解决了这个问题。
假设我们有一个嵌入式开发板,上面接了两个硬件:
例1:GPIO按键驱动注册,驱动开发者编写如下代码:
staticstructinput_dev *key_dev;
staticint __init gpio_key_init(void)
{
/* 第1步:分配 input_dev */
key_dev = input_allocate_device();
/* 第2步:填写身份信息 */
key_dev->name = "gpio_key_enter";
key_dev->id.bustype = BUS_HOST;
key_dev->id.vendor = 0x0001;
key_dev->id.product = 0x0001;
/* 第3步:声明能力位图(最关键!) */
__set_bit(EV_KEY, key_dev->evbit); /* 我能产生按键事件 */
__set_bit(KEY_ENTER, key_dev->keybit); /* 我支持Enter键 */
/* 第4步:设置回调 */
key_dev->open = gpio_key_open; /* 有人读我时才开中断 */
key_dev->close = gpio_key_close; /* 没人读我时关中断省电 */
/* 第5步:注册到核心层 */
input_register_device(key_dev);
}
注册时核心层内部发生了什么?
// drivers/input/input.c 中的简化逻辑
intinput_register_device(struct input_dev *dev)
{
// ① 把 dev 挂到全局链表 input_dev_list 上
list_add_tail(&dev->node, &input_dev_list);
// ② 遍历已注册的所有 handler,逐一尝试匹配
list_for_each_entry(handler, &input_handler_list, node) {
input_attach_handler(dev, handler);
}
}
此时系统中 input_handler_list 上已经有 evdev 和 keyboard 两个Handler,核心层会逐一匹配:
匹配 evdev: → evdev 的 id_table 是 { .driver_info = 1 },表示"来者不拒" → 匹配成功!调用 evdev->connect()
匹配 keyboard: → keyboard 检查 keybit 中是否包含键盘相关键码 → KEY_ENTER 属于键盘键码,匹配成功!调用 keyboard->connect()
evdev->connect() 内部做了什么?
staticintevdev_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
// ① 分配一个 evdev 实例(Handler的私有上下文)
structevdev *evdev = kzalloc(sizeof(*evdev), GFP_KERNEL);
// ② 分配一个 input_handle(连接器)
structinput_handle *handle = kzalloc(sizeof(*handle), GFP_KERNEL);
// ③ 建立三者之间的引用关系(最关键!)
handle->dev = dev; // handle 指向设备
handle->handler = handler; // handle 指向处理器
handle->private = evdev; // handle 携带Handler的私有数据
// ④ 注册 handle(把它挂到两端的链表上)
input_register_handle(handle);
// 内部实现:
// list_add_rcu(&handle->d_node, &dev->h_list); // 挂到设备的链表
// list_add(&handle->h_node, &handler->h_list); // 挂到Handler的链表
// ⑤ 创建用户态可见的设备节点
cdev_init(&evdev->cdev, &evdev_fops);
device_create(evdev_class, &dev->dev, devt, NULL, "event%d", minor);
// 结果:/dev/input/event0 诞生了!
}
keyboard->connect() 类似
staticintkbd_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
structinput_handle *handle = kzalloc(sizeof(*handle), GFP_KERNEL);
handle->dev = dev;
handle->handler = handler;
handle->private = kbd_data; // keyboard的私有数据
input_register_handle(handle);
// 挂到 dev->h_list 和 keyboard->h_list 上
// 注意:keyboard 不创建 /dev/input/ 下的节点,它绑定到 /dev/ttyN
}
注册完成后的内存状态:
全局 input_dev_list:
┌─────────────────────────────────────────────┐
│ input_dev "gpio_key_enter" │
│ name = "gpio_key_enter" │
│ evbit = [EV_KEY] │
│ keybit = [KEY_ENTER] │
│ open() = gpio_key_open │
│ close() = gpio_key_close │
│ h_list ──→ ┌──────────────────────┐ │
│ │ input_handle #1 │ │
│ │ dev ──→ 自己 │ │
│ │ handler ──→ evdev │ │
│ │ private ──→ evdev实例│ │
│ │ open = 0(无人读取)│ │
│ │ d_node ← 挂在h_list│ │
│ ├──────────────────────┤ │
│ │ input_handle #2 │ │
│ │ dev ──→ 自己 │ │
│ │ handler ──→ kbd │ │
│ │ private ──→ kbd_data│ │
│ │ open = 0 │ │
│ │ d_node ← 挂在h_list│ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────┘
全局 input_handler_list:
┌─────────────────────────────────────────────┐
│ input_handler "evdev" │
│ h_list ──→ [input_handle #1] ──→ ... │
├─────────────────────────────────────────────┤
│ input_handler "kbd" │
│ h_list ──→ [input_handle #2] ──→ ... │
└─────────────────────────────────────────────┘
用户态程序执行:
cat /dev/input/event0
这触发了 evdev 的文件操作:
// evdev.c
staticintevdev_open(struct inode *inode, struct file *file)
{
structevdev *evdev = container_of(inode->i_cdev, structevdev, cdev);
structevdev_client *client = kzalloc(...);// 分配客户端缓冲区
// 找到对应的 handle
structinput_handle *handle = ...;// 通过evdev找到
// 核心操作:调用 input_open_device
input_open_device(handle);
// 内部实现:
// handle->open++; // open计数 0→1
// if (handle->open == 1)
// dev->open(dev); // 第一次打开,调用设备的open回调!
// // → gpio_key_open() 被调用
// // → 申请中断、启动硬件
}
关键点:handle->open 从0变为1时,核心层自动调用 dev->open()。这就是"按需供电"机制——没人读事件时硬件可以休眠,有人读时才启动。 此时 input_handle #1 的状态变为:
input_handle #1:
dev ──→ gpio_key_enter
handler ──→ evdev
open = 1 ← 从0变为1,事件开始投递到这个handle
private ──→ evdev实例
而 input_handle #2(keyboard)的 open 仍为0,所以事件不会投递给keyboard(除非tty终端也打开了它)。
用户按下了GPIO按键,硬件产生中断。以下是事件从硬件到用户态的完整流转:第1步:设备驱动层——中断服务程序
staticirqreturn_tgpio_key_isr(int irq, void *dev_id)
{
int value = gpio_get_value(50);
// 调用输入子系统提供的上报函数
input_report_key(key_dev, KEY_ENTER, !value);
// ↑ 这个函数内部做了什么?
// input_report_key() → input_event(dev, EV_KEY, KEY_ENTER, !value)
input_sync(key_dev);
// ↑ 等价于 input_event(dev, EV_SYN, SYN_REPORT, 0)
}
第2步:核心层——事件分发引擎input_event() 内部调用链:
voidinput_event(struct input_dev *dev, unsignedint type,
unsignedint code, int value)
{
// ① 更新设备状态缓存
if (type == EV_KEY)
__set_bit(value ? code : 0, dev->key); // 更新按键状态位图
// ② 将事件暂存到设备的批量缓冲区
dev->vals[dev->num_vals++] = { type, code, value };
// ③ 调用 input_handle_event 处理
input_handle_event(dev, type, code, value);
}
// input_sync() 触发时,缓冲区中有两个事件:
// vals[0] = { EV_KEY, KEY_ENTER, 1 }
// vals[1] = { EV_SYN, SYN_REPORT, 0 }
// 然后调用:
voidinput_pass_values(struct input_dev *dev,
const struct input_value *vals,
unsignedint count)
{
structinput_handle *handle;
// ④ 检查是否有独占(grab)
handle = rcu_dereference(dev->grab);
if (handle) {
// 有独占:只投递给这一个handle
handle->handler->event(handle, type, code, value);
return;
}
// ⑤ 无独占:遍历设备的所有handle
rcu_read_lock();
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
// 关键判断:这个handle有没有被打开?
if (handle->open) {
// open > 0,投递事件!
handle->handler->events(handle, vals, count);
// 或 handle->handler->event(handle, type, code, value);
}
// open == 0 的handle直接跳过,不浪费CPU
}
rcu_read_unlock();
}
这里就是三者协作的核心! 核心层做的事非常简单:
第3步:事件处理器层——evdev处理事件input_event() 内部调用链:
// evdev.c
staticunsignedintevdev_events(struct input_handle *handle,
const struct input_value *vals,
unsignedint count)
{
// 通过 handle->private 取回自己的上下文
structevdev *evdev = handle->private;
structevdev_client *client;
// 遍历所有正在读取的客户端
rcu_read_lock();
list_for_each_entry_rcu(client, &evdev->client_list, node) {
// 将事件写入客户端的环形缓冲区
for (i = 0; i < count; i++) {
client->buffer[head].time = ...;
client->buffer[head].type = vals[i].type;
client->buffer[head].code = vals[i].code;
client->buffer[head].value = vals[i].value;
}
// 唤醒正在阻塞等待的用户进程
wake_up_interruptible(&client->wait);
}
rcu_read_unlock();
}
第4步:用户态——读取事件
// 用户态程序
structinput_eventev;
read(fd, &ev, sizeof(ev));
// 此时 ev 的内容为:
// ev.type = EV_KEY (0x01)
// ev.code = KEY_ENTER (28)
// ev.value = 1 (按下)
//
// 紧接着再 read 一次:
// ev.type = EV_SYN (0x00)
// ev.code = SYN_REPORT (0)
// ev.value = 0
五、完整流程串联图把上面的例子串起来,一次按键的完整生命周期:
时间线 ──────────────────────────────────────────────────────→
① 模块加载
gpio_key_init()
│
├─ input_allocate_device() → 创建 input_dev "gpio_key_enter"
├─ 设置 evbit=[EV_KEY], keybit=[KEY_ENTER]
└─ input_register_device()
│
├─ dev 加入 input_dev_list
└─ 遍历 handler_list,逐一匹配
│
├─ evdev 匹配成功 → connect()
│ ├─ 创建 input_handle #1
│ ├─ handle#1.dev = dev
│ ├─ handle#1.handler = evdev
│ ├─ 挂到 dev->h_list 和 evdev->h_list
│ └─ 创建 /dev/input/event0
│
└─ keyboard 匹配成功 → connect()
├─ 创建 input_handle #2
├─ handle#2.dev = dev
├─ handle#2.handler = keyboard
└─ 挂到 dev->h_list 和 keyboard->h_list
② 用户打开设备
open("/dev/input/event0")
│
└─ evdev_open()
└─ input_open_device(handle#1)
├─ handle#1.open: 0 → 1
└─ dev->open() → gpio_key_open() → 申请GPIO中断
③ 用户按下按键
GPIO中断触发
│
└─ gpio_key_isr()
├─ input_report_key(dev, KEY_ENTER, 1)
│ └─ input_event(dev, EV_KEY, KEY_ENTER, 1)
│ └─ input_pass_values()
│ │
│ ├─ 遍历 dev->h_list:
│ │
│ ├─ handle#1: open=1 ✓
│ │ └─ evdev->events()
│ │ └─ 写入客户端缓冲区
│ │ └─ wake_up() 唤醒用户进程
│ │
│ └─ handle#2: open=0 ✗ 跳过!
│
└─ input_sync(dev)
└─ input_event(dev, EV_SYN, SYN_REPORT, 0)
└─ 同上流程,发送同步事件
④ 用户态读取
read(fd, &ev, sizeof(ev))
│
└─ evdev_read()
└─ 从缓冲区取出 struct input_event 拷贝到用户空间
→ { EV_KEY, KEY_ENTER, 1 }
→ { EV_SYN, SYN_REPORT, 0 }
⑤ 用户关闭设备
close(fd)
│
└─ evdev_release()
└─ input_close_device(handle#1)
├─ handle#1.open: 1 → 0
└─ dev->close() → gpio_key_close() → 释放中断、硬件休眠
触摸屏驱动注册时,能力配置不同:
// 触摸屏驱动
__set_bit(EV_ABS, touch_dev->evbit); /* 绝对坐标事件 */
__set_bit(EV_KEY, touch_dev->evbit); /* 按键事件 */
__set_bit(BTN_TOUCH, touch_dev->keybit); /* 触摸按键 */
input_set_abs_params(touch_dev, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(touch_dev, ABS_Y, 0, 767, 0, 0);
注册时匹配结果:
evdev 匹配成功 → 创建 handle → /dev/input/event1
mousedev 匹配?→ 检查 keybit 是否有 BTN_MOUSE → BTN_TOUCH ≠ BTN_MOUSE → 不匹配!
keyboard 匹配?→ 检查 keybit 是否有键盘键码 → BTN_TOUCH 不是键盘键码 → 不匹配!
所以触摸屏只匹配了 evdev 一个Handler,它的 h_list 上只有一个 input_handle。 用户触摸屏幕时:
input_report_abs(touch_dev, ABS_X, 512); // X坐标
input_report_abs(touch_dev, ABS_Y, 384); // Y坐标
input_report_key(touch_dev, BTN_TOUCH, 1); // 触摸按下
input_sync(touch_dev); // 同步
核心层分发时只遍历到一个handle(evdev),直接投递,用户态读到:
{ EV_ABS, ABS_X, 512 }
{ EV_ABS, ABS_Y, 384 }
{ EV_KEY, BTN_TOUCH, 1 }
{ EV_SYN, SYN_REPORT, 0 }
input_dev | ||
input_handler | ||
input_handle |
以下以一个GPIO按键为例,从设备树描述、内核驱动编写到用户态应用读取,完整展示输入子系统的全链路。
一、设备树(Device Tree)
设备树负责描述硬件信息,告诉驱动"按键接在哪个GPIO上、什么电平触发"。
/ {
gpio_keys {
compatible = "gpio-keys"; /* 匹配驱动的compatible字段 */
#address-cells = <1>;
#size-cells = <0>;
autorepeat; /* 启用内核自动重复功能 */
key_enter {
label = "Enter Key"; /* 按键名称,会出现在 /proc/bus/input/devices */
gpios = <&gpio1 50 GPIO_ACTIVE_LOW>; /* GPIO1的第50号引脚,低电平有效 */
linux,code = <28>; /* 映射为KEY_ENTER(28) */
debounce-interval = <20>; /* 去抖动20ms */
wakeup-source; /* 可作为唤醒源 */
};
key_volumeup {
label = "Volume Up Key";
gpios = <&gpio1 51 GPIO_ACTIVE_LOW>;
linux,code = <115>; /* KEY_VOLUMEUP */
debounce-interval = <20>;
};
key_volumedown {
label = "Volume Down Key";
gpios = <&gpio1 52 GPIO_ACTIVE_LOW>;
linux,code = <114>; /* KEY_VOLUMEDOWN */
debounce-interval = <20>;
};
};
};
设备树解析
compatible = "gpio-keys" | |
gpios = <&gpio1 50 GPIO_ACTIVE_LOW> | |
linux,code = <28> | |
debounce-interval | |
autorepeat | |
wakeup-source |
驱动层分为两部分:平台驱动框架(负责匹配设备树、管理生命周期)和输入子系统接口(负责事件上报)。
完整驱动代码
/*
* gpio_keys_custom.c - 自定义GPIO按键输入驱动
*
* 功能:读取设备树中描述的GPIO按键,将硬件中断转换为
* Linux输入子系统的标准事件,上报到用户空间。
*/
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/input.h>
#include<linux/gpio.h>
#include<linux/interrupt.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/slab.h>
#include<linux/timer.h>
#include<linux/pm_wakeirq.h>
/* ===== 按键私有数据结构 ===== */
structgpio_key_data {
int gpio; /* GPIO编号 */
int irq; /* 对应的中断号 */
unsignedint keycode; /* Linux键码(如KEY_ENTER) */
constchar *label; /* 按键名称 */
structinput_dev *input;/* 所属的 input_dev(所有按键共享) */
structtimer_listdebounce_timer;/* 软件去抖定时器 */
int active_low; /* 是否低电平有效 */
};
/* 整个设备的全局数据 */
structgpio_keys_drvdata {
structinput_dev *input;/* 输入设备实例 */
structgpio_key_data *keys;/* 按键数组 */
int num_keys; /* 按键数量 */
};
/* ===== 软件去抖定时器回调 ===== */
staticvoidgpio_key_debounce(unsignedlong data)
{
structgpio_key_data *key = (structgpio_key_data *)data;
int level = gpio_get_value(key->gpio);
int pressed = key->active_low ? !level : level;
/* 上报按键事件到输入子系统核心层 */
input_report_key(key->input, key->keycode, pressed);
/* 发送同步事件,标记本帧结束 */
input_sync(key->input);
pr_debug("gpio_keys: %s %s (code=%d)\n",
key->label, pressed ? "pressed" : "released", key->keycode);
}
/* ===== 中断服务程序 ===== */
staticirqreturn_tgpio_key_isr(int irq, void *dev_id)
{
structgpio_key_data *key = dev_id;
/*
* 不在中断中直接上报事件,而是启动定时器延迟处理。
* 这样做有两个好处:
* 1. 实现软件去抖(20ms后电平稳定再读取)
* 2. 避免在中断上下文中执行耗时操作
*/
mod_timer(&key->debounce_timer, jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
/* ===== input_dev 的 open 回调 ===== */
staticintgpio_keys_open(struct input_dev *input)
{
structgpio_keys_drvdata *ddata = input_get_drvdata(input);
int i, ret;
/* 第一个用户打开设备时,申请所有按键的中断 */
for (i = 0; i < ddata->num_keys; i++) {
structgpio_key_data *key = &ddata->keys[i];
ret = request_any_context_irq(key->irq, gpio_key_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
key->label, key);
if (ret < 0) {
pr_err("gpio_keys: failed to request IRQ %d for %s\n",
key->irq, key->label);
goto err_free_irqs;
}
}
pr_info("gpio_keys: all interrupts enabled, device active\n");
return0;
err_free_irqs:
while (--i >= 0)
free_irq(ddata->keys[i].irq, &ddata->keys[i]);
return ret;
}
/* ===== input_dev 的 close 回调 ===== */
staticvoidgpio_keys_close(struct input_dev *input)
{
structgpio_keys_drvdata *ddata = input_get_drvdata(input);
int i;
/* 最后一个用户关闭设备时,释放所有中断,硬件进入低功耗 */
for (i = 0; i < ddata->num_keys; i++)
free_irq(ddata->keys[i].irq, &ddata->keys[i]);
pr_info("gpio_keys: all interrupts freed, device suspended\n");
}
/* ===== 平台驱动 probe 函数 ===== */
staticintgpio_keys_probe(struct platform_device *pdev)
{
structdevice *dev = &pdev->dev;
structdevice_node *np = dev->of_node;
structgpio_keys_drvdata *ddata;
structinput_dev *input;
structgpio_key_data *keys;
int num_keys, i, ret;
/* ---- 第1步:统计设备树中定义了多少个按键子节点 ---- */
num_keys = of_get_child_count(np);
if (num_keys == 0) {
dev_err(dev, "no key nodes found in device tree\n");
return -EINVAL;
}
/* ---- 第2步:分配驱动数据 ---- */
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
keys = devm_kcalloc(dev, num_keys, sizeof(*keys), GFP_KERNEL);
if (!keys)
return -ENOMEM;
ddata->keys = keys;
ddata->num_keys = num_keys;
/* ---- 第3步:分配并初始化 input_dev ---- */
input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;
input->name = "gpio_keys_custom";
input->phys = "gpio_keys_custom/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* 绑定 open/close 回调 */
input->open = gpio_keys_open;
input->close = gpio_keys_close;
/* 保存私有数据,供 open/close 中取回 */
input_set_drvdata(input, ddata);
/* 声明支持自动重复事件 */
if (of_property_read_bool(np, "autorepeat"))
__set_bit(EV_REP, input->evbit);
/* ---- 第4步:逐个解析设备树中的按键节点 ---- */
i = 0;
for_each_child_of_node(np, child) {
enum of_gpio_flags flags;
/* 读取GPIO编号和极性 */
keys[i].gpio = of_get_gpio_flags(np, i, &flags);
if (!gpio_is_valid(keys[i].gpio)) {
dev_err(dev, "invalid GPIO for key %d\n", i);
return -EINVAL;
}
keys[i].active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;
/* 读取键码 */
ret = of_property_read_u32(child, "linux,code", &keys[i].keycode);
if (ret) {
dev_err(dev, "missing linux,code for key %d\n", i);
return ret;
}
/* 读取名称 */
keys[i].label = "unknown";
of_property_read_string(child, "label", &keys[i].label);
/* 关联 input_dev */
keys[i].input = input;
/* 申请GPIO资源并设置为输入 */
ret = devm_gpio_request_one(dev, keys[i].gpio,
GPIOF_IN, keys[i].label);
if (ret) {
dev_err(dev, "failed to request GPIO %d\n", keys[i].gpio);
return ret;
}
/* 获取对应的中断号 */
keys[i].irq = gpio_to_irq(keys[i].gpio);
/* 初始化软件去抖定时器 */
setup_timer(&keys[i].debounce_timer,
gpio_key_debounce,
(unsignedlong)&keys[i]);
/* ===== 关键:声明设备能力位图 ===== */
__set_bit(EV_KEY, input->evbit); /* 支持按键事件 */
__set_bit(keys[i].keycode, input->keybit); /* 支持该键码 */
dev_info(dev, "key[%d]: gpio=%d irq=%d code=%u label=%s\n",
i, keys[i].gpio, keys[i].irq,
keys[i].keycode, keys[i].label);
i++;
}
/* ---- 第5步:注册 input_dev 到输入子系统 ---- */
/*
* input_register_device() 内部会:
* 1. 将 input_dev 加入全局 input_dev_list
* 2. 遍历 input_handler_list,逐一匹配
* 3. evdev 匹配成功后自动创建 /dev/input/eventX
*/
ret = input_register_device(input);
if (ret) {
dev_err(dev, "failed to register input device\n");
return ret;
}
platform_set_drvdata(pdev, ddata);
dev_info(dev, "gpio_keys_custom: registered %d keys\n", num_keys);
return0;
}
/* ===== 平台驱动 remove 函数 ===== */
staticintgpio_keys_remove(struct platform_device *pdev)
{
structgpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
int i;
/* 注销 input_dev(内部会自动断开所有 handler 连接) */
input_unregister_device(ddata->input);
/* 清理定时器 */
for (i = 0; i < ddata->num_keys; i++)
del_timer_sync(&ddata->keys[i].debounce_timer);
dev_info(&pdev->dev, "gpio_keys_custom: removed\n");
return0;
}
/* ===== 设备树匹配表 ===== */
staticconststructof_device_idgpio_keys_of_match[] = {
{ .compatible = "gpio-keys-custom", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);
/* ===== 平台驱动结构体 ===== */
staticstructplatform_drivergpio_keys_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys-custom",
.of_match_table = gpio_keys_of_match,
},
};
module_platform_driver(gpio_keys_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Custom GPIO Keys Input Driver");
驱动层数据流总结
设备树 驱动probe() 输入子系统
────── ────────── ──────────
gpio-keys-custom ──匹配──→ platform_driver.probe()
├─ key_enter ├─ input_allocate_device()
│ gpio=<&gpio1 50> ├─ 解析每个子节点
│ linux,code=<28> │ ├─ gpio_request()
│ │ ├─ gpio_to_irq()
│ │ └─ __set_bit(KEY_ENTER, keybit)
│ ├─ input_register_device()
│ │ ├─ 加入 input_dev_list
│ │ └─ evdev匹配 → 创建 /dev/input/event0
│ │
├─ key_volumeup │
│ gpio=<&gpio1 51> │
│ linux,code=<115> │
│ │
└─ key_volumedown │
gpio=<&gpio1 52> │
linux,code=<114> │
│
用户按下Enter键 ──→ GPIO中断 ──→ gpio_key_isr()
│
├─ mod_timer(20ms去抖)
│
└─ gpio_key_debounce()
├─ input_report_key(input, KEY_ENTER, 1)
└─ input_sync(input)
│
└─ 核心层分发到 evdev
└─ /dev/input/event0 可读
读取并打印所有事件
/*
* input_reader.c - 输入子系统事件读取工具
*
* 功能:打开 /dev/input/eventX,实时读取并打印所有输入事件
* 编译:gcc -o input_reader input_reader.c
* 运行:./input_reader /dev/input/event0
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<linux/input.h>
#include<sys/select.h>
/* 事件类型名称映射表 */
staticconstchar *ev_type_name(unsignedint type)
{
switch (type) {
case EV_SYN: return"SYN";
case EV_KEY: return"KEY";
case EV_REL: return"REL";
case EV_ABS: return"ABS";
case EV_MSC: return"MSC";
case EV_LED: return"LED";
case EV_SND: return"SND";
case EV_REP: return"REP";
case EV_FF: return"FF";
default: return"???";
}
}
/* 常见按键名称映射(部分) */
staticconstchar *key_name(unsignedint code)
{
switch (code) {
case KEY_ENTER: return"ENTER";
case KEY_ESC: return"ESC";
case KEY_SPACE: return"SPACE";
case KEY_UP: return"UP";
case KEY_DOWN: return"DOWN";
case KEY_LEFT: return"LEFT";
case KEY_RIGHT: return"RIGHT";
case KEY_VOLUMEUP: return"VOL_UP";
case KEY_VOLUMEDOWN: return"VOL_DOWN";
case KEY_POWER: return"POWER";
case BTN_TOUCH: return"TOUCH";
case BTN_LEFT: return"BTN_LEFT";
case BTN_RIGHT: return"BTN_RIGHT";
default: returnNULL;
}
}
intmain(int argc, char *argv[])
{
int fd;
structinput_eventev;
constchar *dev_path;
if (argc < 2) {
fprintf(stderr, "用法: %s <设备节点>\n", argv[0]);
fprintf(stderr, "示例: %s /dev/input/event0\n", argv[0]);
return1;
}
dev_path = argv[1];
/* 打开输入设备节点 */
fd = open(dev_path, O_RDONLY);
if (fd < 0) {
perror("open");
return1;
}
printf("已打开设备: %s (fd=%d)\n", dev_path, fd);
printf("等待事件... (Ctrl+C 退出)\n");
printf("%-12s %-8s %-8s %-8s %s\n",
"时间", "类型", "编码", "值", "描述");
printf("----------------------------------------------------\n");
/* 循环读取事件 */
while (1) {
ssize_t n = read(fd, &ev, sizeof(ev));
if (n != sizeof(ev)) {
if (n < 0)
perror("read");
break;
}
/* 打印事件信息 */
constchar *name = key_name(ev.code);
printf("%ld.%06ld %-8s %-8u %-8d %s%s%s\n",
ev.time.tv_sec, ev.time.tv_usec,
ev_type_name(ev.type),
ev.code,
ev.value,
name ? name : "",
(ev.type == EV_KEY && name) ?
(ev.value ? " [按下]" : " [释放]") : "",
(ev.type == EV_SYN) ? " --- 帧结束 ---" : "");
}
close(fd);
return0;
}
另一种版本
/*
* input_monitor.c - 生产级输入事件监控程序
*
* 功能:
* 1. 独占模式打开设备(EVIOCGRAB),阻止其他进程接收事件
* 2. 非阻塞 I/O + select 多路复用
* 3. 信号处理(Ctrl+C 优雅退出并释放独占)
* 4. 查询设备能力信息
*
* 编译:gcc -o input_monitor input_monitor.c
* 运行:sudo ./input_monitor /dev/input/event0
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
#include<sys/select.h>
#include<sys/ioctl.h>
#include<linux/input.h>
staticvolatileint running = 1;
staticint g_fd = -1;
/* 信号处理函数 */
staticvoidsignal_handler(int sig)
{
(void)sig;
running = 0;
}
/* 查询并打印设备能力 */
staticvoidprint_device_caps(int fd)
{
char name[256] = "Unknown";
char phys[256] = "Unknown";
unsignedlong evbit[EV_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong keybit[KEY_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong absbit[ABS_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
unsignedlong relbit[REL_MAX / (8 * sizeof(unsignedlong)) + 1] = {0};
structinput_idid;
int i;
/* 获取设备基本信息 */
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys);
ioctl(fd, EVIOCGID, &id);
printf("========== 设备信息 ==========\n");
printf("名称: %s\n", name);
printf("物理路径: %s\n", phys);
printf("总线类型: 0x%04x\n", id.bustype);
printf("厂商ID: 0x%04x\n", id.vendor);
printf("产品ID: 0x%04x\n", id.product);
printf("版本: 0x%04x\n", id.version);
/* 获取能力位图 */
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit);
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit);
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit);
printf("\n支持的事件类型:");
for (i = 0; i <= EV_MAX; i++) {
if (evbit[i / (8 * sizeof(unsignedlong))] &
(1UL << (i % (8 * sizeof(unsignedlong))))) {
printf(" 0x%02X", i);
}
}
printf("\n");
/* 打印支持的按键 */
printf("支持的按键:");
for (i = 0; i <= KEY_MAX; i++) {
if (keybit[i / (8 * sizeof(unsignedlong))] &
(1UL << (i % (8 * sizeof(unsignedlong))))) {
printf(" %d", i);
}
}
printf("\n");
/* 打印绝对轴信息 */
for (i = 0; i <= ABS_MAX; i++) {
if (absbit[i / (8 * sizeof(unsignedlong))] &
(1UL << (i % (8 * sizeof(unsignedlong))))) {
structinput_absinfoabsinfo;
ioctl(fd, EVIOCGABS(i), &absinfo);
printf("绝对轴 %d: min=%d max=%d fuzz=%d flat=%d resolution=%d\n",
i, absinfo.minimum, absinfo.maximum,
absinfo.fuzz, absinfo.flat, absinfo.resolution);
}
}
printf("==============================\n\n");
}
intmain(int argc, char *argv[])
{
int fd;
structinput_eventev;
constchar *dev_path;
int grab = 0; /* 是否启用独占模式 */
int opt;
/* 解析命令行参数 */
while ((opt = getopt(argc, argv, "g")) != -1) {
switch (opt) {
case'g':
grab = 1;
break;
default:
fprintf(stderr, "用法: %s [-g] <设备节点>\n", argv[0]);
fprintf(stderr, " -g 启用独占模式(EVIOCGRAB)\n");
return1;
}
}
if (optind >= argc) {
fprintf(stderr, "错误: 请指定设备节点\n");
return1;
}
dev_path = argv[optind];
/* 以非阻塞模式打开设备 */
fd = open(dev_path, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror("open");
return1;
}
g_fd = fd;
/* 注册信号处理 */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
/* 打印设备能力 */
print_device_caps(fd);
/* 独占模式:阻止其他进程接收该设备的事件 */
if (grab) {
if (ioctl(fd, EVIOCGRAB, (void *)1) < 0) {
perror("EVIOCGRAB");
fprintf(stderr, "提示: 独占模式需要 root 权限\n");
close(fd);
return1;
}
printf("[独占模式] 已独占设备,其他进程将无法接收事件\n");
}
printf("开始监控事件... (Ctrl+C 退出)\n\n");
/* 主循环:使用 select 实现非阻塞等待 */
while (running) {
fd_set readfds;
structtimevaltimeout = { .tv_sec = 1, .tv_usec = 0 };
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
int ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (ret < 0) {
if (errno == EINTR)
continue; /* 被信号中断,检查 running 标志 */
perror("select");
break;
}
if (ret == 0)
continue; /* 超时,继续循环 */
/* 有数据可读,读取所有可用事件 */
while (1) {
ssize_t n = read(fd, &ev, sizeof(ev));
if (n != sizeof(ev)) {
if (n < 0 && errno != EAGAIN)
perror("read");
break; /* 没有更多数据 */
}
/* 过滤同步事件(仅用于标记帧边界,不打印) */
if (ev.type == EV_SYN)
continue;
printf("[%ld.%06ld] type=0x%02X code=0x%03X value=%d\n",
ev.time.tv_sec, ev.time.tv_usec,
ev.type, ev.code, ev.value);
/* 按键事件特殊处理 */
if (ev.type == EV_KEY) {
printf(" → 按键 %d %s\n", ev.code,
ev.value ? "按下" : (ev.value == 0 ? "释放" : "长按重复"));
}
/* 绝对坐标事件 */
if (ev.type == EV_ABS) {
printf(" → 轴 %d = %d\n", ev.code, ev.value);
}
}
}
/* 释放独占 */
if (grab) {
ioctl(fd, EVIOCGRAB, (void *)0);
printf("\n[独占模式] 已释放设备\n");
}
close(fd);
printf("程序退出\n");
return0;
}
流程
┌──────────────────────────────────────────────────────────────┐
│ 物理硬件层 │
│ │
│ [用户手指按下按键] │
│ │ │
│ ▼ │
│ GPIO1_50 引脚电平: HIGH → LOW(低电平有效) │
│ │ │
│ ▼ │
│ GPIO控制器检测到边沿变化,触发硬件中断 │
└──────────────────────────────────────────────────────────────┘
│
▼ IRQ #138(假设)
┌──────────────────────────────────────────────────────────────┐
│ 内核驱动层 │
│ │
│ gpio_key_isr() │
│ │ │
│ ├─ mod_timer(20ms) ← 启动去抖定时器 │
│ └─ return IRQ_HANDLED │
│ │
│ ... 20ms后 ... │
│ │
│ gpio_key_debounce() │
│ │ │
│ ├─ gpio_get_value(50) → 0(低电平 = 按下) │
│ ├─ pressed = !0 = 1 │
│ │ │
│ ├─ input_report_key(input, KEY_ENTER, 1) │
│ │ └─ input_event(dev, EV_KEY, 28, 1) │
│ │ └─ 核心层:更新 dev->key 状态位图 │
│ │ 暂存到 dev->vals 缓冲区 │
│ │ │
│ └─ input_sync(input) │
│ └─ input_event(dev, EV_SYN, SYN_REPORT, 0) │
│ └─ 触发 input_pass_values() │
│ │ │
│ ├─ 遍历 dev->h_list │
│ │ │
│ ├─ handle#1 (evdev): open=1 ✓ │
│ │ └─ evdev_events() │
│ │ └─ 写入 client 环形缓冲区 │
│ │ └─ wake_up_interruptible() │
│ │ │
│ └─ handle#2 (kbd): open=0 ✗ 跳过 │
└──────────────────────────────────────────────────────────────┘
│
▼ 进程被唤醒
┌──────────────────────────────────────────────────────────────┐
│ 用户态应用层 │
│ │
│ input_monitor 程序中的 select() 返回可读 │
│ │ │
│ ▼ │
│ read(fd, &ev, sizeof(ev)) │
│ │ │
│ ├─ 第1次读取: │
│ │ ev = { time=1629.123456, │
│ │ type=EV_KEY(0x01), │
│ │ code=KEY_ENTER(28), │
│ │ value=1 } │
│ │ │
│ └─ 第2次读取: │
│ ev = { time=1629.123456, │
│ type=EV_SYN(0x00), │
│ code=SYN_REPORT(0), │
│ value=0 } │
│ │
│ 输出: │
│ [1629.123456] type=0x01 code=0x01C value=1 │
│ → 按键 28 按下 │
└──────────────────────────────────────────────────────────────┘
insmod/dev/input/eventX | modprobe evdev | |
cat /proc/interrupts | ||
chmod 666 /dev/input/event0 | ||
debounce-interval或定时器延时 | ||
autorepeat或驱动中__set_bit(EV_REP, evbit) |
