input子系统分析三(input设备数据处理流程)

在《input子系统分析一(input设备注册)》中,我们遗留了一些问题没有解决,现在我们带着这些问题来梳理app是如何访问input设备的。

样例:我们以一个按键驱动程序为例,分析app是如何访问按键设备的。假设按键设备使用的事件驱动为evdev事件驱动,生成的设备节点为“/dev/input/event1”。

1 app打开按键设备

还记得上一节中我们学习到了dev和handler在连接时,新建了一个evdev设备,并将其注册到了内核中,也就是我们现在看到的“/dev/input/event1”。废话不多说,贴上evdev_connect的核心代码,回顾下dev和handler连接做了哪些事情:

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);  // 申请一个evdev设备
.....                                               // 该部分省略,主要是初始化刚刚的evdev设备
input_register_handle(&evdev->handle);              // 重点1:将此evdev的handle注册到内核                                                                                                  //handle将按键设备与evdev事件驱动连接到了一起
cdev_init(&evdev->cdev, &evdev_fops);               // 重点2: 将evdev_fops挂给了此evdev设备
cdev_device_add(&evdev->cdev, &evdev->dev);         // 将此evdev字符设备注册到内核

依据字符设备驱动开发的经验,我们可以知道调用关系如下:

open("/dev/input/event1", O_RDONLY)  -> evdev_fops.open  -> evdev_open

好,我们来重点看看evdev_open源码:

static int evdev_open(struct inode *inode, struct file *file)
{
      // 重点1. 内核常用手段,通过设备节点,找到整个evdev结构的初始化地址
      struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
      unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
      struct evdev_client *client;
      int error;
      // 重点2. 新建一个evdev_client对象,该对象就是用来处理input设备数据的
      client = kvzalloc(struct_size(client, buffer, bufsize), GFP_KERNEL);
      if (!client)
          return -ENOMEM;
  ​
      init_waitqueue_head(&client->wait);
      client->bufsize = bufsize;
      spin_lock_init(&client->buffer_lock);
      /* 重点3. 将client的evdev指向当前evdev,并且将client挂接到evdev的链表上.
      既然这里有挂接链表的操作,说明一个evdev可以对应着多个client,这一点在evdev_disconnect()
      中已经体现:evdev需要遍历自己的client_list,给所有的client发信号 */
      client->evdev = evdev;
      evdev_attach_client(evdev, client);
  ​
      error = evdev_open_device(evdev);
      if (error)
          goto err_free_client;
      // 重点4. 将file的私有数据初始化为client
      file->private_data = client;
      stream_open(inode, file);
  ​
      return 0;
  ​
   err_free_client:
      evdev_detach_client(evdev, client);
      kvfree(client);
      return error;
}

总结:通过重点4我们可以知道:在open一次input设备后,会创建一个client,后续app对设备的访问都是通过这个client。而且一个evdev可以对应着多个client,多个client之间的数据通过一系列同步机制来实现数据处理。借用大神的一张图来直观的感受下evdev和client的关系。(再回顾下evdev字符设备是input_dev和handler在connect时创建的,并注册给了内核,相当于一个字符设备驱动。)

2 app读取设备

ret = read(fd, &ev, sizeof(struct input_event));  // app读取按键设备的测试代码

从已经打开的evdev中读取输入事件,通过章节1中的分析后,我们可以知道调用关系如下:

read -> evdev_read

好,我们来分析下evdev_read:

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
	// 重点1:通过文件节点找到刚刚新建的client
	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())
		return -EINVAL;
	// 重点2:读取数据
	for (;;) {
		if (!evdev->exist || client->revoked)
		  return -ENODEV;
		// 重点3:不允许非阻塞读取
		if (client->packet_head == client->tail &&
		   (file->f_flags & 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)) {  // 重点4:取事件,下面重点分析
		  // 重点5:相当于 copy_to_user(),把取出来的事件拷贝给用户空间。
		  if (input_event_to_user(buffer + read, &event))
			  return -EFAULT;
		  read += input_event_size();
		}
		// 重点6:读到了数据则结束,没读到则等待(由等待队列实现)
		if (read)
		  break;
		if (!(file->f_flags & O_NONBLOCK)) {
		  error = wait_event_interruptible(client->wait,
				  client->packet_head != client->tail ||
				  !evdev->exist || client->revoked);
		  if (error)
			  return error;
		}
	}
	return read;
}

继续分析输入事件从何而来,且看evdev_fetch_next_event函数:

static int evdev_fetch_next_event(struct evdev_client *client,
				struct input_event *event)
{
	int have_event;
	​
	spin_lock_irq(&client->buffer_lock);
	// 重点1:妥妥的缓冲队列,事实上evdev_read就是到client中的buffer取数据
	have_event = client->packet_head != client->tail;
	if (have_event) {
	  *event = client->buffer[client->tail++];
	  client->tail &= client->bufsize - 1;
	}
	​
	spin_unlock_irq(&client->buffer_lock);
	​
	return have_event;
}

事实上evdev_read就是到对应的client->buffer中取数据。

现在我们把读操作分析完了,那有没有问题了呢,显然有个很大的问题:client中的buffer数据从何而来?当read操作没数据时睡眠,谁来唤醒它,谁在调用次client的wake_up_interruptible?

带着问题,我们继续探索.....

3 输入事件上报

到目前为止,我们还没看到按键设备到底发挥了什么作用?这可是我们的初心啊,就是要让这个按键设备工作起来。现在是时候让它发光发热了!

答:我们在编写设备驱动的时候是不是调用过input_event,就是这个函数将输入设备产生的数据上报到了evdev字符设备的所有client中,并存入了每个client->buffer中。

先看下input_event函数:

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)) {  // step1. 判断设备是否支持该时间类型

		spin_lock_irqsave(&dev->event_lock, flags);
		input_handle_event(dev, type, code, value);      // step2. 若支持,继续调用
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}

input_handle_event

        -> input_pass_values

                -> input_pass_values

我们重点来看input_pass_value函数:

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();
    // 重点1
	handle = rcu_dereference(dev->grab);
	if (handle) {
		count = input_to_handler(handle, vals, count);
	} else {
        // 重点2
		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_read_unlock();

	/* trigger auto repeat for key events */
	if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
		for (v = vals; v != vals + count; v++) {
			if (v->type == EV_KEY && v->value != 2) {
				if (v->value)
					input_start_autorepeat(dev, v->code);
				else
					input_stop_autorepeat(dev);
			}
		}
	}
}

先看注释中的重点1:这里的grab的意思是设备被抓或者设备被独占的意思,这是通过EVIOCGRAB ioctl设置的,设置后当前设备变成唯一的来自设备的所有输入事件的接收者。我们之前说过,当多个应用打开这个eventx的时候,都会接收到事件,但是当某一个应用通过EVIOCGRAB ioctl设置后,就只有当前应用可以接收到事件了。

来看重点2:如果设备没有被独占,那我们要遍历内核中所有的handle,因为每个是input_dev可以匹配多个handler。

现在我们通过dev->h_list找了handle,再通过handle找到了handler,如果handle被打开,那就开始调用input_to_handler。

input_to_handler函数:

static unsigned int input_to_handler(struct input_handle *handle,
			struct input_value *vals, unsigned int count)
{
	struct input_handler *handler = handle->handler;
	struct input_value *end = vals;
	struct input_value *v;

	if (handler->filter) {
		for (v = vals; v != vals + count; v++) {
			if (handler->filter(handle, v->type, v->code, v->value))
				continue;
			if (end != v)
				*end = *v;
			end++;
		}
		count = end - vals;
	}

	if (!count)
		return 0;

	if (handler->events)
		handler->events(handle, vals, count);
	else if (handler->event)
		for (v = vals; v != vals + count; v++)
			handler->event(handle, v->type, v->code, v->value);

	return count;
}

input_to_handler -> handler->events -> evdev_events

static void evdev_events(struct input_handle *handle,
			 const struct input_value *vals, unsigned int count)
{
    // step1. 通过handle找到了evdev设备
	struct evdev *evdev = handle->private;
	struct evdev_client *client;
	ktime_t *ev_time = input_get_timestamp(handle->dev);

	rcu_read_lock();
    // step2. 判断evdev有没有指定特定的client
	client = rcu_dereference(evdev->grab);

	if (client)
		evdev_pass_values(client, vals, count, ev_time);
	else
		list_for_each_entry_rcu(client, &evdev->client_list, node)
			evdev_pass_values(client, vals, count, ev_time);

	rcu_read_unlock();
}

step2中:又看到了grab,EVIOCGRAB ioctl中会设置handle和client,这里才是真正的把当前设备变成唯一的来自设备的所有输入事件的接收者。

如果grab没有指定client,会把事件发送给所有的接收者

evdev_pass_values函数:

static void evdev_pass_values(struct evdev_client *client,
			const struct input_value *vals, unsigned int count,
			ktime_t *ev_time)
{
	const struct input_value *v;
	struct input_event event;
	struct timespec64 ts;
	bool wakeup = false;

	if (client->revoked)
		return;

	ts = ktime_to_timespec64(ev_time[client->clk_type]);
	event.input_event_sec = ts.tv_sec;
	event.input_event_usec = ts.tv_nsec / NSEC_PER_USEC;

	/* 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;
		__pass_event(client, &event);
	}

	spin_unlock(&client->buffer_lock);

	if (wakeup)
		wake_up_interruptible_poll(&client->wait,
			EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);
}

step1:调用了__pass_event函数,这个下面继续分析。

step2:和本文第二章末尾呼应上了,当有数据上报时,唤醒client的wake_up_interruptible。

wake_up_interruptible_poll(&client->wait, EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);

__pass_event函数:将数据写入client->buffer缓冲区

static void __pass_event(struct evdev_client *client, const struct input_event *event)
{
	client->buffer[client->head++] = *event;
	client->head &= client->bufsize - 1;

	if (unlikely(client->head == client->tail)) {
		/*
		 * This effectively "drops" all unconsumed events, leaving
		 * EV_SYN/SYN_DROPPED plus the newest event in the queue.
		 */
		client->tail = (client->head - 2) & (client->bufsize - 1);

		client->buffer[client->tail] = (struct input_event) {
			.input_event_sec = event->input_event_sec,
			.input_event_usec = event->input_event_usec,
			.type = EV_SYN,
			.code = SYN_DROPPED,
			.value = 0,
		};

		client->packet_head = client->tail;
	}

	if (event->type == EV_SYN && event->code == SYN_REPORT) {
		client->packet_head = client->head;
		kill_fasync(&client->fasync, SIGIO, POLL_IN);
	}
}

4 总结:

1 按键设备key_dev与evdev->handler匹配后,调用evdev_connect函数进行连接,evdev_connect时为其创建了一个全局的evdev字符设备,并生成“/dev/input/eventX”设备节点供用户访问。

2 用户在访问/dev/input/eventX设备节点时,每次open设备,都会创建一个client对象,该对象用来做数据设备的数据处理。而且一个evdev可以对应着多个client。

3 1)当设备产生数据时,设备一般通过中断方式调用input_event,input_event在上报时先通过input_dev的h_list找到handle,再通过handle找到handler,调用hanler的events接口,比如evdev_events.

   2)然后evdev_events中通过handle找到evdev字符设备,evdev会找到其挂接的所有client,将数据写入每个client.buffer中。当然如果用户在访问设备时指定了 client,那么设备只会把事件发送给这个client。

4 每个client都通过等待队列实现了数据访问的阻塞操作。

5 附上网上找来的input子系统框架图:

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值