RK3568驱动指南|第十三篇 输入子系统-第146章 通用事件处理层connect函数分析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十三篇 输入子系统_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第146章 通用事件处理层connect函数分析

在之前的实验中,我们已经编写了设备驱动层代码和用户层代码,并且分析了输入子系统的上报数据格式。对于核心层代码,我们通常不需要自己编写,而事件处理层代码也通常不需要我们自己编写,因为Linux提供了一个通用的事件处理层代码,即evdev.c。从本章节开始,我们将着重分析evdev.c,这是Linux内核中的一个模块,用于处理输入设备的事件。evdev.c提供了一组通用的函数和数据结构,使得我们可以轻松地处理输入设备上报的事件数据。

evdev_handler结构体定义了处理输入设备事件的函数指针,在Linux内核中,evdev_handler结构体定义在include/linux/input.h头文件中的evdev.h中,如下所示:

static struct input_handler evdev_handler = {
	.event		= evdev_event,          // 输入事件处理函数
	 	.events		= evdev_events,         // 批量输入事件处理函数
	.connect	= evdev_connect,        // 当input_dev和input_handler匹配成功之后执行的连接处理函数
	.disconnect	= evdev_disconnect,     // 断开连接处理函数
	    .legacy_minors	= true,                  // 设置为true,表示支持传统次设备号,如果设置为false,则使用动态的次设备号分配方式
	.minor		= EVDEV_MINOR_BASE,     // 输入设备的基础次设备号
	.name		= "evdev",               // 输入处理器的名称
	.id_table	= evdev_ids,             // 输入设备ID表
};

在evdev.c文件中,定义了一个名为 evdev_init 的静态函数,其返回类型为 int,如下所示。该函数使用 __init 属性进行标记,这通常表示它是在内核初始化期间调用的函数。函数的主要作用是注册 evdev_handler 作为输入事件处理器。具体来说,它调用了 input_register_handler 函数,并将 evdev_handler 作为参数传递给它。input_register_handler 函数是一个用于注册输入事件处理器的函数,它负责将输入事件处理器添加到系统的输入事件处理链中,以便能够处理来自输入设备的事件。整个函数的返回值会被返回给调用者。这通常是一个表示成功或失败的整数值。

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

input_register_handler 函数如下所示,作用是注册输入事件处理器。它接收一个指向input_handler结构体的指针作为参数。函数的主要流程如下所示

1 获取互斥锁input_mutex,以防止多个线程同时访问关键部分。

2 初始化输入处理器的链表头h_list。

3 将输入处理器添加到输入处理器链表的末尾,使用list_add_tail函数。

4 遍历输入设备链表input_dev_list,对于每个输入设备,将输入处理器附加到它上面,使用input_attach_handler函数。

5 唤醒正在等待读取procfs的读者,使用input_wakeup_procfs_readers 函数。

6 释放互斥锁input_mutex,允许其他线程访问关键部分。

7 返回成功的状态码0。

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;  // 输入设备指针
	int error;              // 错误码 

	error = mutex_lock_interruptible(&input_mutex);  // 获取互斥锁,以阻塞方式
	if (error)
		return error;  // 如果获取锁失败,则返回错误码

	INIT_LIST_HEAD(&handler->h_list);  // 初始化输入处理器的链表头

	list_add_tail(&handler->node, &input_handler_list);  // 将输入处理器添加到输入处理器链表的末尾

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);  // 遍历输入设备链表,并将输入处理器附加到每个输入设备上

	input_wakeup_procfs_readers();  // 唤醒正在等待读取 procfs 的读者

	mutex_unlock(&input_mutex);  // 释放互斥锁
	return 0;  // 返回成功的状态码
}

在上面的函数中输入处理器附加到每个输入设备上,也就是取出input_dev和新注册的handler进行匹配,匹配成功之后执行evdev_connect函数,如下所示:

在evdev_connect函数中,有一个新的结构体evdev,我们先来学习下这个结构体。

146.1 evdev和evdev_client结构体

evdev结构体定义了一个evdev设备的相关信息和状态,用于管理和控制输入事件的处理。通过这些成员,可以跟踪evdev设备的打开状态,当前占用设备的客户端,设备的互斥访问以及设备关联的其他信息。

struct evdev {
	int open;                      // 记录 evdev 设备打开的状态
	struct input_handle handle;   // 输入事件处理器的句柄
	wait_queue_head_t wait;       // 等待队列头,用于休眠和唤醒等待事件的进程
	struct evdev_client __rcu *grab;  // 指向当前占用 evdev 设备的客户端
	struct list_head client_list; // 与 evdev 设备关联的客户端链表
	spinlock_t client_lock;       // 用于保护客户端链表的自旋锁
	struct mutex mutex;           // 用于保护对 evdev 设备的互斥访问
	struct device dev;            // 与 evdev 设备关联的设备结构
	struct cdev cdev;             // evdev 设备的字符设备结构
	bool exist;                   // 表示 evdev 设备是否存在
};

接下来我们解释下每个成员的含义:

open:记录evdev设备的打开状态,可能的取值为0(关闭)或1(打开)。

handle:用于处理输入事件的句柄,包含了与事件处理器相关的信息,如打开的输入设备和事件处理函数等。

wait:等待队列头,用于休眠和唤醒等待事件的进程。当没有输入事件时,进程可以通过等待在该队列上来阻塞。

grab: 指向当前占用evdev设备的客户端。当某个客户端占用了evdev设备时,其他客户端无法访问该设备。

client_list: 与evdev设备关联的客户端链表,用于管理连接到该设备的客户端。

client_lock:用于保护客户端链表的自旋锁,确保在多线程环境下对客户端链表的操作线程是安全的。

mutex:用于保护对evdev设备的互斥访问,确保在多线程环境下对设备的操作是互斥的。

dev:与evdev设备关联的设备结构体,用于表示设备的特定信息,如设备名称,设备号等。

cdev:evdev设备的字符设备结构,用于注册和管理字符设备。

exist:表示evdev设备是否存在的标志。如果设备存在,则为true;否则为false。

   接下来我们来学习下evdev_client结构体。这个结构体定义了一个evdev客户端的相关信息和状态,用于管理与evdev设备相关联的客户端,在应用程序中每打开一遍event设备节点,就会用一个evdev_client结构体来表示,系统可以为每个evdev设备维护多个客户端,并管理每个客户端的状态和属性。在evdev.c中,也会对这个结构体进行操作,并根据客户端的状态和属性,将接收到的事件写入缓冲区或通知客户端。

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 设备关联的客户端链表节点
	enum input_clock_type clk_type;    // 输入时钟类型
	bool revoked;                      // 标志,指示客户端是否被撤销
	unsigned long *evmasks[EV_CNT];    // 用于事件掩码的数组
	unsigned int bufsize;              // 缓冲区的大小
	struct input_event buffer[];       // 输入事件缓冲区,可变长度数组
};

接下来我们解释下每个成员的含义:

head: 缓冲区的头指针,指向下一个可写入的位置。

tail:缓冲区的尾指针,指向下一个可读取的位置。

packet_head:下一个数据包的第一个元素的位置。

buffer_lock:用于保护对缓冲区,头指针和尾指针的访问的自旋锁,在多线程环境下确保对缓冲区的操作线程是安全的。

fasync:用于异步通知的结构体指针,在需要异步通知时,将其设置为相应的值。

evdev:与客户端关联的evdev设备指针,表示客户端所属的evdev设备。

node:与evdev设备关联的客户端链表节点,用于管理与设备关联的客户端。

clk_type:输入时钟类型,表示客户端使用的输入时钟类型。

revoked:标志,指示客户端是否被撤销。

evmasks:用于事件掩码的数组,存储了不同类型事件的掩码,数组的大小由EV_CNT定义。

bufsize:缓冲区的大小,表示缓冲区可以容纳的输入事件数量。

buffer[]:输入事件缓冲区,是一个可变长度数组,存储了输入事件数据。

146.2 evdev_connect函数

我们继续来学习下evdev_connect函数。这个函数用于在输入子系统中连接一个evdev设备。

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;       // evdev 结构体指针,表示与设备关联的 evdev 实例
	int minor;                 // 设备的次设备号
	int dev_no;                // 设备号
	int error;                 // 错误码

	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
	if (minor < 0) {
		error = minor;
		pr_err("failed to reserve new minor: %d\n", error);
		return error;
	}

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);  // 分配 evdev 结构体的内存空间
	if (!evdev) {
		error = -ENOMEM;
		goto err_free_minor;
	}

	INIT_LIST_HEAD(&evdev->client_list);         // 初始化 evdev 的客户端链表头
	spin_lock_init(&evdev->client_lock);         // 初始化 evdev 的客户端自旋锁
	mutex_init(&evdev->mutex);                    // 初始化 evdev 的互斥锁
	init_waitqueue_head(&evdev->wait);           // 初始化 evdev 的等待队列头
	evdev->exist = true;

	dev_no = minor;
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;
	dev_set_name(&evdev->dev, "event%d", dev_no); // 设置 evdev 的设备名称

	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);  // 设置 evdev 的设备号
	evdev->dev.class = &input_class;              // 设置 evdev 的设备类
	evdev->dev.parent = &dev->dev;                // 设置 evdev 的父设备
	evdev->dev.release = evdev_free;              // 设置 evdev 的释放函数
	device_initialize(&evdev->dev);               // 初始化 evdev 的设备结构体

	error = input_register_handle(&evdev->handle);   // 注册输入事件处理器
	if (error)
		goto err_free_evdev;

	cdev_init(&evdev->cdev, &evdev_fops);         // 初始化 evdev 的字符设备结构体

	error = cdev_device_add(&evdev->cdev, &evdev->dev);   // 将字符设备添加到系统
	if (error)
		goto err_cleanup_evdev;

	return 0;

 err_cleanup_evdev:
	evdev_cleanup(evdev);                      // 清理 evdev
	input_unregister_handle(&evdev->handle);   // 反注册输入事件处理器
 err_free_evdev:
	put_device(&evdev->dev);                    // 释放 evdev 设备
 err_free_minor:
	input_free_minor(minor);                    // 释放次设备号
	return error;
}

我们来对上面的代码解释一下。

1 通过input_get_new_minor函数获取一个新的次设备号minor,如果获取次设备号失败,则返回错误码。

2 使用kzalloc函数为evdev分配内存空间,evdev是一个指向struct evdev结构体的指针。

如果内存分配失败,则返回错误码并释放次设备号。

3 使用INIT_LIST_HEAD,spin_locspin_lock_init、mutex_init 和 init_waitqueue_headk_init、mutex_init 和 init_waitqueue_head初始化evdev的客户端链表头,客户端自旋锁,互斥锁和等待队列头。设置evdev->exist为true,表示evdev存在。根据次设备号计算设备号dev_no,并根据情况将其归一化为传统范围内的设备号。

4 使用dev_set_name为evdev->dev设置设备名称。

设置evdev->handle结构体的成员变量,包括dev(输入的设备指针),name(设备名称),handler(输入事件处理器)和private(指向evdev的指针)。

5 使用MKDEV宏设置evdev->dev的设备号。使用evdev->dev的设备类为input_class。设置evdev->dev的父设备为dev->dev。设置evdev->dev的释放函数为evdev_free。

6 使用device_initialize函数初始化evdev->dev的设备结构体。使用input_register_handle函数注册输入事件处理器。如果注册失败,则跳转到err_free_evdev进行清理。

7 使用cdev_init函数初始化evdev->cdev的字符设备结构体。使用cdev_device_add函数将字符设备添加到系统。如果添加失败,则跳转到err_cleanup_evdev进行清理。如果一切顺利,则返回0表示连接成功。在错误处理的路径中,调用evdev_cleanup_evdev进行清理evdev。

使用input_unregister_handle函数反注册输入事件处理器。调用put_device释放evdev的设备。调用input_free_minor释放次设备号。返回错误码表示连接失败。

综上所述,connect函数的主要任务是将输入设备与事件处理器关联起来,以便在事件发生时调用相应的处理函数。它通过注册输入处理器和设置回调函数来实现这一关联,并确保正确的事件处理器被调用。这种关联机制允许开发者根据需要自定义处理函数,以便根据输入设备上报的事件进行相应的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值