当前位置: 首页 > news >正文

input子系统中读取流程解析

往期内容

本专栏往期内容:

  1. input子系统的框架和重要数据结构详解-CSDN博客
  2. input device和input handler的注册以及匹配过程解析-CSDN博客

I2C子系统专栏:

  1. 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 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,
};

同时,还是牢记下面这张图:

img

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);// 成功返回 0return 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_eventif (!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);//根据设备的状态和输入事件的类型,返回事件的处理方式,称为 dispositionif (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);elseinput_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)
{// 获取处理当前事件的 handlerstruct input_handler *handler = handle->handler;// 定义一个指向事件值数组的指针,初始指向 valsstruct 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;}// 如果没有任何剩余事件,则返回 0if (!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.总结

img

内容涉及了 Linux 的 input 子系统,包括 input_handler、evdev、evdev_client 等结构体的实现细节,尤其是 evdev 设备驱动的接口和数据传递的工作机制。

  1. 输入子系统的整体框架和接口:

    • evdev 设备驱动程序通过 struct file_operations evdev_fops 向用户层提供接口,包括 .open.read.write 等。evdev_open 用于在 APP 调用时打开 /dev/input/event 设备,创建并关联一个 evdev_client 客户端实例,实现事件设备的多客户端支持。
  2. 事件读取和事件处理:

    • evdev_read 负责从内核读取输入事件,遇到没有事件时阻塞等待;通过 wait_event_interruptible 来等待事件,利用 input_handle_event 进行数据传输。
    • gpio_keys_irq_isr 实现了 GPIO 按键输入,依靠 input_event 产生事件并调用 input_handle_event 分发事件,经过 input_to_handler 传递给上层的 input_handler
  3. input_event 和 input_handler 层的细化调用流程:

    • input_event 负责从硬件读取事件,将其封装为 input_value 对象,并通过 input_pass_values 传输至上层 input_handler
    • input_to_handler 将事件传递给实际的事件处理程序,支持过滤(filter)、批量处理(events)和逐个处理(event)。
  4. evdev 驱动的事件批量处理函数:

    • 通过 evdev_eventsevdev_event 函数,将多个事件批量上报到用户空间的 APP。

http://www.mrgr.cn/news/58736.html

相关文章:

  • OpenIPC开源FPV之Ardupilot配置
  • 抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
  • 代码随想录:二叉树层序遍历系列
  • 重磅!又1老牌TOP刊被On Hold!这次期刊选择发文直怼科睿唯安...
  • Linux巡检利器xsos的安装和使用
  • 协议 HTTP
  • windows DLL技术-动态链接库搜索
  • LeetCode904.水果成篮
  • uniapp 发起post和get请求!uni.request(OBJECT)
  • Typora 、 Minio and PicGo 图床搭建
  • 【高级IO】IO多路转接之select
  • 代码随想录第九天|151.翻转字符串里的单词、卡码网:55.右旋转字符串、28. 实现 strStr() 、459.重复的子字符串
  • 《西安科技大学学报》
  • 我谈Canny算子
  • windows中git无法通过ssh连接github
  • 【Git】将本地代码提交到github仓库
  • electron 监听窗口高端变化
  • 基础知识总结-因果分析-dayone-辛普森悖论 因果关系
  • Spring Boot 中应用单元测试(UT):结合 Mock 和 H2 讲解和案例示范
  • Openlayers高级交互(8/20):选取feature,平移feature
  • Linux中安装配置SQLite3,并实现C语言与SQLite3的交互。
  • 活着就好20241026
  • Nature Communications|一种3D打印和激光诱导协同策略用于定制功能化器件(3D打印/激光直写/柔性电子/人机交互/柔性电路)
  • react项目因eslint检测未通过而Failed to compile编译失败
  • Go操作Redis
  • 智创 AI 新视界 -- 探秘 AIGC 中的生成对抗网络(GAN)应用