Linux Input子系统4

Linux Input子系统4(基于Linux6.6)---输入事件驱动层介绍


一、输入事件驱动层的工作流程

输入事件的驱动层负责捕获硬件设备产生的原始事件并将其传递给内核或用户空间。整个过程大致可以分为以下几个步骤:

1.1、设备初始化

每个输入设备都有一个与之相关的 input_device 结构,它在设备初始化时被创建。这个结构体定义了设备的属性、支持的事件类型(如按键事件、鼠标移动事件等)以及事件的处理函数。

在设备初始化过程中,驱动程序会注册设备,并通过调用 input_register_device() 将设备注册到输入子系统中。

struct input_dev *dev;
dev = input_allocate_device();
input_register_device(dev);

1.2、事件处理

当设备产生输入事件时(例如按下一个键或移动鼠标),驱动程序会将事件通过 input_event() 函数提交给输入子系统。输入事件通常是一个三元组 (type, code, value),其中:

  • type:事件类型,如键盘事件、鼠标事件等。
  • code:事件的具体标识,例如键盘按键的编号、鼠标的 X/Y 轴移动。
  • value:事件的值,例如按键的状态(按下或释放)、鼠标的移动距离。

输入事件通过 input_event() 函数提交后,内核将其排入事件队列,并将事件传递给与该设备相关联的所有事件处理程序。

input_event(dev, EV_KEY, KEY_A, 1);  // 按下 A 键
input_sync(dev);  // 同步事件

1.3、事件分发

输入子系统通过一个事件队列将事件传递给用户空间的应用程序。具体来说,Linux 使用 evdev(Event Device)接口来将事件转发给用户空间的应用程序。用户空间程序(如图形界面、终端、游戏等)可以通过打开 /dev/input/eventX 设备文件来读取输入事件。

  • 内核中的输入事件通过 /dev/input/eventX 文件传递给用户空间,应用程序通过 read() 系统调用读取事件数据。

    示例:

  • int fd = open("/dev/input/event0", O_RDONLY);
    struct input_event ev;
    read(fd, &ev, sizeof(struct input_event));
    

1.4、输入事件同步

为了确保事件的顺序和完整性,输入事件会通过 input_sync() 函数进行同步,确保每个事件被处理完毕之后,才会处理下一个事件。

input_sync(dev);  // 同步所有事件

二、input_register_handler

首先是框架性的调用输入核心层实现的input_register_handler和input_unregister_handler来注册evdev层。

drivers/input/evdev.c 

static struct input_handler evdev_handler = {
	.event		= evdev_event,            /* 打包数据,并上报事件 */
	.connect	= evdev_connect,          /* 和dev匹配后做响应连接 */
	.disconnect	= evdev_disconnect,       /* exit使用,卸载使用 */
	.fops		= &evdev_fops,            /* 提供具体的read,write,sync之类的操作 */
	.minor		= EVDEV_MINOR_BASE,       /* 64,evdev的minor base */
	.name		= "evdev",
	.id_table	= evdev_ids,              /* 匹配规则,evdev是所有都可以匹配 */
};
 
static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}
 
static void __exit evdev_exit(void)
{
	input_unregister_handler(&evdev_handler);
}
 
module_init(evdev_init);
module_exit(evdev_exit);

evdev有个重要的数据结构

drivers/input/evdev.c 

 /*evdev结构体再配对成功的时候生成,由handler->connect生成*/
struct evdev {
	int open;                      //打开引用计数
	int minor;                     //次设备号(实际的-EVDEV_MINOR_BASE)
	struct input_handle handle;    //关联的input_handle
	wait_queue_head_t wait;        //等待队列
	struct evdev_client __rcu *grab;
	struct list_head client_list;  //evdev_client链表,说明一个evdev设备可以处理多个evdev_client,可以有多个进程访问
	spinlock_t client_lock; /* protects client_list */
	struct mutex mutex;
	struct device dev;
	bool exist;
};

三、接口实现

3.1、connect

connect函数,它在注册handler和dev时,如果匹配上了就会调用。

 drivers/input/evdev.c

 /* 在endev里面定义的全局变量,它可以存放32的evdev指针 */
static struct evdev *evdev_table[EVDEV_MINORS];
 
/*
 * Create new evdev device. Note that input core serializes calls
 * to connect and disconnect so we don't need to lock evdev_table here.
 */
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int dev_no;
	int error;
 
	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])        /* 从小到大找到一个新的未使用的minor */
			break;
    /* 最多只有32个evdev设备,这里面minor是相对EVDEV_MINOR_BASE来说的,实际的次设备号应该加上EVDEV_MINOR_BASE */
	if (minor == EVDEV_MINORS) {
		printk(KERN_ERR "evdev: no more free evdev devices\n");
		return -ENFILE;
	}
    /* 申请空间 */
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;
    /* 初始化新申请的evdev */
	INIT_LIST_HEAD(&evdev->client_list);
	spin_lock_init(&evdev->client_lock);
	mutex_init(&evdev->mutex);
	init_waitqueue_head(&evdev->wait);    /* 注意这里初始化了一个等待队列,将来肯定会有evdev被放入等待队列和唤醒等待队列操作 */
 
    /*evdev的名字是从0开始增加  */
	dev_set_name(&evdev->dev, "event%d", minor);
	evdev->exist = 1;
	evdev->minor = minor;
 
	evdev->handle.dev = input_get_device(dev);	/* input_dev绑定到 evdev里面的handle里 */
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;      /* 绑定handler到handle里 */
	evdev->handle.private = evdev;        /* handle的私有数据就是evdev本身 */
 
	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
	evdev->dev.class = &input_class;      /* evdev里面的设备属于input_class,这样endev所有设备才会出现在sysfs下的input文件夹下面 */
	evdev->dev.parent = &dev->dev;        /* 父节点是设备的dev */
	evdev->dev.release = evdev_free;     
	device_initialize(&evdev->dev);       /* 初始化evdev里面的dev通用数据 */
 
	error = input_register_handle(&evdev->handle);    /* 注册handle,核心层有分析过,把handler和dev通过链表增加到handler里面的handle里面的链表节点 */
	if (error)
		goto err_free_evdev;
 
	error = evdev_install_chrdev(evdev);    /* 把该evdev添加到前面定义的evdev_table表中 */
	if (error)
		goto err_unregister_handle;
 
	error = device_add(&evdev->dev);        /* 真正的创建设备层次和attribute在sysfs中,学习完驱动学习sysfs的时候统一分析这个 */
	if (error)
		goto err_cleanup_evdev;
 
	return 0;
 
 err_cleanup_evdev:
	evdev_cleanup(evdev);
 err_unregister_handle:
	input_unregister_handle(&evdev->handle);
 err_free_evdev:
	put_device(&evdev->dev);
	return error;
}

3.2、disconnect

disconnect,和connect相反

drivers/input/evdev.c 

static void evdev_disconnect(struct input_handle *handle)
{
	struct evdev *evdev = handle->private;

	cdev_device_del(&evdev->cdev, &evdev->dev);
	evdev_cleanup(evdev);
	input_free_minor(MINOR(evdev->dev.devt));
	input_unregister_handle(handle);
	put_device(&evdev->dev);
}

 3.3、事件打包

对一个设备驱动层发送过来的事件打包

 drivers/input/evdev.c

/*
 * Pass incoming events to all connected clients.
 */
static void evdev_events(struct input_handle *handle,
			 const struct input_value *vals, unsigned int count)
{
	struct evdev *evdev = handle->private;
	struct evdev_client *client;
	ktime_t *ev_time = input_get_timestamp(handle->dev);

	rcu_read_lock();

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

/*
 * Pass incoming event to all connected clients.
 */
static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	struct input_value vals[] = { { type, code, value } };

	evdev_events(handle, vals, 1);
}

发送数据给client函数

 drivers/input/evdev.c

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

3.4、键值的缓存

方便read,ioctl函数操作。

drivers/input/evdev.c 

 static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

3.5、file_operation

应用层的调用接口file_operation

 drivers/input/evdev.c

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

分析open和read函数:

drivers/input/evdev.c 

static int evdev_open(struct inode *inode, struct file *file)
{
	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;

    /* 存在则分配evdev中的client来处理event*,同一个文件打开n次要分配n个client */
	client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
	if (!client) {
		error = -ENOMEM;
		goto err_put_evdev;
	}
    /* 初始化上面新分配的client */
	spin_lock_init(&client->buffer_lock);
	snprintf(client->name, sizeof(client->name), "%s-%d",
			dev_name(&evdev->dev), task_tgid_vnr(current));
	wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
	client->evdev = evdev;                /* client和具体的event绑定 */
	evdev_attach_client(evdev, client);   /* 把clinet添加到evdev的client_list链表末尾 */
 
	error = evdev_open_device(evdev);    /* 将evdev设置为打开状态,下面分析  */
	if (error)
		goto err_free_client;
 
	file->private_data = client;        /* 将文件的私有数据指针指向该client */
	nonseekable_open(inode, file);      /* 给该inode设置不能使用seek的属性(指定位置读写) */
 
	return 0;
 
 err_free_client:
	evdev_detach_client(evdev, client);
	kvfree(client);
	return error;
}

drivers/input/evdev.c

static int evdev_open_device(struct evdev *evdev)
{
	int retval;
 
	retval = mutex_lock_interruptible(&evdev->mutex);
	if (retval)
		return retval;
 
        /* evdev->exist在connevt里面职位1的 */
	if (!evdev->exist)		
		retval = -ENODEV;
        /* 第一次打开的话要打开设备文件,其它时候open做打开计数 */
	else if (!evdev->open++) {		
                /* 这个打开设备的是再核心层定义的,其实它也是一个假的函数,又要调用设备驱动层的open函数 */
		retval = input_open_device(&evdev->handle);
		if (retval)
			evdev->open--;
	}
 
	mutex_unlock(&evdev->mutex);
	return retval;
} 

核心层的open函数

drivers/input/input.c

 
/**
 * input_open_device - open input device
 * @handle: handle through which device is being accessed
 *
 * This function should be called by input handlers when they
 * want to start receive events from given input device.
 */
int input_open_device(struct input_handle *handle)
{
	struct input_dev *dev = handle->dev;
	int retval;
 
	retval = mutex_lock_interruptible(&dev->mutex);
	if (retval)
		return retval;
 
	if (dev->going_away) {
		retval = -ENODEV;
		goto out;
	}
 
	handle->open++;        /* 统计打开次数 */
 
	if (!dev->users++ && dev->open)
		retval = dev->open(dev);    /* 真正的open函数在设备驱动层,当然设备驱动层也可以不实现这个函数 */
    /* 如果没有在设备驱动层定义open函数,不会执行执行到open函数,open进行自减操作,表示没有调用过open,这个值主要是为了close中判断open为0时释放资源使用。 */
	if (retval) {
		dev->users--;
		if (!--handle->open) {
			/*
			 * Make sure we are not delivering any more events
			 * through this handle
			 */
			synchronize_rcu();
		}
	}
 
 out:
	mutex_unlock(&dev->mutex);
	return retval;
}

对应应用层的read函数

 drivers/input/evdev.c

 
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;
	int retval;
 
	if (count < input_event_size())        /* 熟读的数据长度至少要满足一个input_event的大小 */
		return -EINVAL;
    
     /* 当client缓冲区无数据;evdev不存在;文件非阻塞打开,那个read直接返回错误 */
	if (client->head == client->tail && evdev->exist &&
	    (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
    /* 当client里面没有数据时,将应用程序请到evdev->wait等待队列休息,还记得在哪唤醒的吗(evdev_event的最后一行) */  
	retval = wait_event_interruptible(evdev->wait,
		client->head != client->tail || !evdev->exist);
	if (retval)
		return retval;
 
	if (!evdev->exist)
		return -ENODEV;
 
    /* 当要读取的数据大于struct input_event且client里面的buffer有数据,则把数据拷贝到用户空间*/
	while (retval + input_event_size() <= count &&
	       evdev_fetch_next_event(client, &event)) {
 
		if (input_event_to_user(buffer + retval, &event))    /* 对copy_to_user的封装 */
			return -EFAULT;
 
		retval += input_event_size();
	}
 
	return retval;
}

查看有没有数据,有的话返回1,且*event参数会把数据带出来一包

drivers/input/evdev.c 

 
static int evdev_fetch_next_event(struct evdev_client *client,
				  struct input_event *event)
{
	int have_event;
 
	spin_lock_irq(&client->buffer_lock);
 
	have_event = client->head != client->tail;    /* 头不等于尾说明有数据 */
	if (have_event) {
		*event = client->buffer[client->tail++];    /* 读数据到*event,每次只能读一个包 */
		client->tail &= EVDEV_BUFFER_SIZE - 1;      /* 么次读完一个包,尾指针都要移动 */
		if (client->head == client->tail)
			wake_unlock(&client->wake_lock);
	}
 
	spin_unlock_irq(&client->buffer_lock);
 
	return have_event;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值