首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

input子系统中读取流程解析

  • 25-03-04 15:21
  • 2967
  • 7902
blog.csdn.net

往期内容

本专栏往期内容:

  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,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

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);

    // 成功返回 0
    return 0;

    // 如果打开设备失败,进行清理
 err_free_client:
    // 将客户端从 evdev 设备上移除
    evdev_detach_client(evdev, client);

    // 释放分配的客户端内存
    kvfree(client);

    // 返回错误代码
    return error;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

里面提到了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[];  // 环形缓冲区,用于存储输入事件
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 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);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

其中(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())。
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

(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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 以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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

其中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中有提到没数据读了就休眠
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

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_events 和 evdev_event 函数,将多个事件批量上报到用户空间的 APP。
文章知识点与官方知识档案匹配,可进一步学习相关知识
C技能树首页概览220410 人正在系统学习中
注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/143245549"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top