往期内容
本专栏往期内容:
- input子系统的框架和重要数据结构详解-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
I2C子系统专栏:
- 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
参考:
- \Linux-4.9.88\Linux-4.9.88\drivers\input.c📎input.c – input.c
- \Linux-4.9.88\Linux-4.9.88\drivers\evdev.c📎evdev.c – input handler
- \Linux-4.9.88\drivers\input\keyboard📎gpio_keys.c – input device
本节主要介绍input子系统中读取流程的相关实现代码的解析。
建议先看完一下input子系统的相关结构体介绍:input device和input handler的注册以及匹配过程解析-CSDN博客
1.接口
先来看一下evdev.c设备驱动程序中提供给上层的api接口:
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
同时,还是牢记下面这张图:
2.open
APP调用open函数打开/dev/input/event0
- 在驱动程序evdev_open里,创建一个evdev_client,表示一个"客户"
static int evdev_open(struct inode *inode, struct file *file)
{
// 通过 inode 获取关联的 evdev 结构体指针
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
// 计算输入设备缓冲区大小,依赖于设备的属性
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
// 确定 evdev_client 结构体的总大小,包含客户端结构体和缓冲区
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
// 用于保存新创建的 evdev_client 指针
struct evdev_client *client;
// 错误代码变量
int error;
// 尝试分配内存,用于存储 evdev 客户端和缓冲区(使用 GFP_KERNEL 分配)
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
// 如果分配失败,则尝试使用 vzalloc 分配虚拟内存
if (!client)
client = vzalloc(size);
// 如果仍然分配失败,则返回内存不足错误
if (!client)
return -ENOMEM;
// 设置客户端的缓冲区大小
client->bufsize = bufsize;
// 初始化客户端的自旋锁,保护缓冲区的访问
spin_lock_init(&client->buffer_lock);
// 将 evdev 设备指针保存到客户端结构体中
client->evdev = evdev;
// 将客户端附加到 evdev 设备上,因为一个输入设备evdev是可以被多个程序evdev_client打开的
evdev_attach_client(evdev, client);
// 尝试打开设备,并准备接受事件
error = evdev_open_device(evdev);
// 如果打开设备失败,进行错误处理
if (error)
goto err_free_client;
// 将客户端结构体指针保存到文件私有数据中,以供后续使用
file->private_data = client;
// 设置文件为非可寻址文件类型,因为输入设备通常不支持文件定位
nonseekable_open(inode, file);
// 成功返回 0
return 0;
// 如果打开设备失败,进行清理
err_free_client:
// 将客户端从 evdev 设备上移除
evdev_detach_client(evdev, client);
// 释放分配的客户端内存
kvfree(client);
// 返回错误代码
return error;
}
里面提到了evdev和evdev_client,这个是啥? 它们在 Linux 内核的输入子系统中用于实现事件设备的管理。evdev
结构体代表一个输入设备,evdev_client
结构体代表与该设备关联的客户端,通常是用户空间中的程序。通俗点就是,在input_dev下层中,用input_dev来表示一个输入设备,在input_handler上层中,用evdev来表示一个输入设备
evdev
结构体管理输入设备本身,维护与该设备的所有客户端的连接。evdev_client
结构体代表打开设备的每个客户端,负责处理设备发送的事件并存储在其环形缓冲区中。
struct evdev {
int open; // 设备是否已经被打开的计数器,如果大于0表示有客户端打开了设备
struct input_handle handle; // 用于与输入子系统交互的句柄
wait_queue_head_t wait; // 等待队列,用于同步客户端和设备之间的事件处理
struct evdev_client __rcu *grab; // 表示当前抓取设备的客户端,通常用于独占设备的客户端
struct list_head client_list; // 链表头,用于管理当前连接到设备的所有客户端
spinlock_t client_lock; // 保护 client_list 的自旋锁,确保多客户端的并发安全
struct mutex mutex; // 保护设备状态的互斥锁,防止设备的并发访问问题
struct device dev; // 设备模型中的 device 结构,表示该 evdev 设备
struct cdev cdev; // 字符设备结构,允许该设备通过字符设备文件与用户空间交互
bool exist; // 设备是否存在的标志,用于检查设备是否被移除
};
struct evdev_client {
unsigned int head; // 缓冲区的头部索引,指向最旧的未读取事件
unsigned int tail; // 缓冲区的尾部索引,指向下一个可以写入事件的位置
unsigned int packet_head; // [未来功能] 指向下一个完整数据包的起始位置
spinlock_t buffer_lock; // 自旋锁,用于保护缓冲区的并发访问
struct fasync_struct *fasync; // 异步通知的结构体,用于支持信号通知
struct evdev *evdev; // 该客户端关联的 evdev 设备指针
struct list_head node; // 链表节点,挂载到 evdev 的 `client_list` 上
unsigned int clk_type; // 客户端时钟类型
bool revoked; // 标志客户端是否被撤销(比如设备被移除时)
unsigned long *evmasks[EV_CNT]; // 用于跟踪设备事件掩码的数组
unsigned int bufsize; // 缓冲区大小
struct input_event buffer[]; // 环形缓冲区,用于存储输入事件
};
3.read/poll
-
APP调用read/poll读取、等待数据
- 没有数据时休眠:wait_event_interruptible(evdev->wait, …)
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
size_t read = 0;
int error;
if (count != 0 && count < input_event_size())
//检查用户请求的读取字节数 count 是否有效。
return -EINVAL;
for (;;) {
if (!evdev->exist || client->revoked)
//该部分检查设备是否仍然存在,或者客户端是否已被撤销(例如设备被移除)。
return -ENODEV;
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
//如果事件缓冲区为空(即 packet_head 等于 tail),并且文件被打开时设置了 O_NONBLOCK 标志,表示调用者不希望阻塞等待事件。
//不是阻塞等待,数据为空,直接返回
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event))
return -EFAULT;
read += input_event_size();
}
//通过 evdev_fetch_next_event() 从缓冲区中获取下一个输入事件,将事件复制到 event 结构体中
//并使用 input_event_to_user() 函数将事件复制到用户提供的缓冲区中。
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
//如果启用了阻塞模式(没有设置 O_NONBLOCK 标志),当缓冲区为空时,代码会调用 wait_event_interruptible(),等待新的事件到来或者设备状态发生变化。
if (error)
return error;
}
}
return read;
}
4.event
- 点击、操作输入设备,产生中断:input_dev硬件层,这里以内核自带的gpio_keys.c为例:
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned long flags;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, button->code, 1);//上报中断事件
input_sync(input);//本质也是input_event
if (!bdata->release_delay) {
input_event(input, EV_KEY, button->code, 0);
input_sync(input);
goto out;
}
bdata->key_pressed = true;
}
if (bdata->release_delay)
mod_timer(&bdata->release_timer,
jiffies + msecs_to_jiffies(bdata->release_delay));
out:
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
-
input_event(input, EV_KEY, button->code, 1);
:在中断服务程序input_event
里- 从硬件读取到数据
- 使用
input_handle_event
/input_sync
函数上报数据
\Linux-4.9.88\drivers\input\input.c:
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
input_handle_event(dev, type, code, value);//处理输入事件,并决定如何将其分发给设备和输入处理程序。
// ----------------------(1)
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
-
input_event做什么?
-
从dev->h_list中取出input_handle,从input_handle取出input_handler
-
优先调用input_handler->filter来处理
-
如果没有input_handler->filter或者没处理成功
- 调用input_handler->events
- 没有input_handler->events的话,调用input_handler->event
-
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
//获取事件处置方式
int disposition = input_get_disposition(dev, type, code, &value);
//根据设备的状态和输入事件的类型,返回事件的处理方式,称为 disposition
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
add_input_randomness(type, code, value);
//如果事件不应被忽略(INPUT_IGNORE_EVENT)且不是同步事件(EV_SYN),则调用 add_input_randomness() 增加输入随机性。
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
//如果 disposition 标记为 INPUT_PASS_TO_DEVICE,并且设备有注册事件处理函数(dev->event),则将事件传递给设备的事件处理程序。
if (!dev->vals)//检查设备是否存储事件
return;
//将事件传递给处理程序
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++]; //和input_dev关联上,对v操作就是对dev->vals操作
v->type = EV_ABS; //事件类型
v->code = ABS_MT_SLOT; //事件类型下的哪一种
v->value = dev->mt->slot; //事件的值
//这里是用input_value来表示一个事件
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
/*
如果事件需要传递给输入处理程序(INPUT_PASS_TO_HANDLERS),则事件被存储到 dev->vals 中。这里通过 dev->num_vals++ 依次递增存储事件值。
如果 disposition 包含 INPUT_SLOT,表示多点触控设备需要处理槽位信息,会首先存储槽位事件(ABS_MT_SLOT)。
然后存储当前事件的 type、code 和 value。
*/
//检查是否需要刷新或同步事件
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals); //------(1)
dev->num_vals = 0;
}
//如果 disposition 包含 INPUT_FLUSH,表示应立即传递事件。此时,如果存储的事件数量(dev->num_vals)达到 2 个或以上,调用 input_pass_values() 将事件传递给所有注册的处理程序。
//如果事件数量接近设备存储区的上限 (dev->max_vals - 2),则首先添加一个同步事件(input_value_sync),然后调用 input_pass_values() 将事件传递出去。最后重置事件计数器 dev->num_vals。
}
其中(1)input_pass_values(dev, dev->vals, dev->num_vals)
进行传输事件:
\Linux-4.9.88\drivers\input\input.c
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;
if (!count)
return;//没有需要处理的事件。
rcu_read_lock();//使用 RCU(Read-Copy-Update)锁来确保数据的一致性。
//检查设备是否有处理程序
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count); //-------------(2)
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open) {
count = input_to_handler(handle, vals, count);
if (!count)
break;
}
}
/*
首先,通过 rcu_dereference() 检查设备是否有 “grab” 的处理程序(即,独占处理事件的处理程序)
如果有,则调用 input_to_handler() 将事件传递给它。返回的 count 表示处理程序还剩下多少未处理的事件(可能部分事件已处理)。
如果设备没有 grab 处理程序,则遍历设备的处理程序链表(dev->h_list)。对于每个已打开的处理程序(handle->open 为真),
调用 input_to_handler() 将事件传递过去。如果事件被处理完毕(count == 0),则终止循环。
*/
//读取保护区退出
rcu_read_unlock();
//处理按键自动重复(auto-repeat)
if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
//首先检查设备是否支持自动重复(EV_REP)并且事件类型为按键(EV_KEY)。
for (v = vals; v != vals + count; v++) {
//然后遍历所有剩余未处理的事件,检查每个事件的类型是否为按键(EV_KEY)且其值不为 2(2 表示自动重复按键事件,不在这里处理)。
if (v->type == EV_KEY && v->value != 2) {
if (v->value)
input_start_autorepeat(dev, v->code);
else
input_stop_autorepeat(dev);
//如果按键事件值为 1(按下),则启动自动重复功能(input_start_autorepeat()),如果按键事件值为 0(松开),则停止自动重复功能(input_stop_autorepeat())。
}
}
}
}
(2)其中count = input_to_handler(handle, vals, count)
将事件传递给handler处理程序,内部会承上到input_handler层,调用input_handler中的filter/events处理事件函数:
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
// 获取处理当前事件的 handler
struct input_handler *handler = handle->handler;
// 定义一个指向事件值数组的指针,初始指向 vals
struct input_value *end = vals;
struct input_value *v;
// 如果处理程序 handler 定义了一个过滤函数
if (handler->filter) {
// 遍历所有事件
for (v = vals; v != vals + count; v++) {
// 通过过滤函数决定是否处理该事件
// 如果返回非零值,则跳过该事件(继续处理下一个)
if (handler->filter(handle, v->type, v->code, v->value))
continue;
// 如果事件通过过滤,将其复制到 end 指针位置
// 这样可以过滤掉无效的事件
if (end != v)
*end = *v;
// end 指针向前移动
end++;
}
// 更新 count 为过滤后剩余的事件数量
count = end - vals;
}
// 如果没有任何剩余事件,则返回 0
if (!count)
return 0;
// 如果处理程序 handler 定义了批量处理函数 `events`
if (handler->events)
// 调用批量处理函数处理事件
handler->events(handle, vals, count);
// 如果没有批量处理函数,但定义了单事件处理函数 `event`
else if (handler->event)
// 遍历所有事件,调用单事件处理函数处理每个事件
for (v = vals; v != vals + count; v++)
handler->event(handle, v->type, v->code, v->value);
// 返回剩余事件的数量
return count;
}
-
以evdev.c为例
-
- 它有evdev_events:用来处理多个事件
- 也有evdev_event:实质还是调用evdev_events
- 唤醒"客户":wake_up_interruptible(&evdev->wait);
\Linux-4.9.88\drivers\input\evdev.c
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,//优先级更高
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
//----------------------------------------------------------
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
// 获取与该事件设备关联的 evdev 结构
struct evdev *evdev = handle->private;
// 定义一个指针指向当前客户端
struct evdev_client *client;
// 创建一个数组来存储事件发生的时间,数组大小为 3(EV_CLK_MAX)
// 分别存储 MONO、REAL 和 BOOT 时钟时间
ktime_t ev_time[EV_CLK_MAX];
// 获取当前的单调(MONOTONIC)时间
ev_time[EV_CLK_MONO] = ktime_get();
// 将单调时间转换为实际时间(REAL),并存储在相应的数组位置
ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
// 将单调时间转换为启动时间(BOOT),并存储在相应的位置
ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
TK_OFFS_BOOT);
// 进入 RCU 读取临界区
rcu_read_lock();
// 尝试获取当前正在 "抓取" 设备的客户端
client = rcu_dereference(evdev->grab);
// 如果有客户端 "抓取" 了设备,则将事件传递给该客户端
if (client)
evdev_pass_values(client, vals, count, ev_time);//主要是看这里-----(3)
else
// 否则,将事件传递给所有连接的客户端
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count, ev_time);
// 退出 RCU 读取临界区
rcu_read_unlock();
}
其中evdev_pass_values(client, vals, count, ev_time);
:
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t *ev_time)
{
struct evdev *evdev = client->evdev;
const struct input_value *v;
struct input_event event;
bool wakeup = false;
if (client->revoked)
return;
event.time = ktime_to_timeval(ev_time[client->clk_type]);
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
for (v = vals; v != vals + count; v++) {
if (__evdev_is_filtered(client, v->type, v->code))
continue;
if (v->type == EV_SYN && v->code == SYN_REPORT) {
/* drop empty SYN_REPORT */
if (client->packet_head == client->head)
continue;
wakeup = true;
}
event.type = v->type;
event.code = v->code;
event.value = v->value; //将传来的input_value“装进”input_event
//input_event在上层用于表示一个事件,input_value主要是中层传递给上层用的
__pass_event(client, &event);
}
spin_unlock(&client->buffer_lock);
if (wakeup)
wake_up_interruptible(&evdev->wait); //唤醒休眠的客户端,在read中有提到没数据读了就休眠
}
5.总结
内容涉及了 Linux 的 input 子系统,包括 input_handler、evdev、evdev_client 等结构体的实现细节,尤其是 evdev 设备驱动的接口和数据传递的工作机制。
-
输入子系统的整体框架和接口:
- evdev 设备驱动程序通过
struct file_operations evdev_fops
向用户层提供接口,包括.open
、.read
、.write
等。evdev_open
用于在 APP 调用时打开/dev/input/event
设备,创建并关联一个evdev_client
客户端实例,实现事件设备的多客户端支持。
- evdev 设备驱动程序通过
-
事件读取和事件处理:
evdev_read
负责从内核读取输入事件,遇到没有事件时阻塞等待;通过wait_event_interruptible
来等待事件,利用input_handle_event
进行数据传输。gpio_keys_irq_isr
实现了 GPIO 按键输入,依靠input_event
产生事件并调用input_handle_event
分发事件,经过input_to_handler
传递给上层的input_handler
。
-
input_event 和 input_handler 层的细化调用流程:
input_event
负责从硬件读取事件,将其封装为input_value
对象,并通过input_pass_values
传输至上层input_handler
。input_to_handler
将事件传递给实际的事件处理程序,支持过滤(filter)、批量处理(events)和逐个处理(event)。
-
evdev 驱动的事件批量处理函数:
- 通过
evdev_events
和evdev_event
函数,将多个事件批量上报到用户空间的 APP。
- 通过