本文主要是介绍RK3568驱动指南|第十三篇 输入子系统-第148章 通用事件处理层open函数分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
瑞芯微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主板
第148章 通用事件处理层open函数分析
148.1 open函数分析
在上个章节中,我们已经学习了通用事件处理层connect函数,在connect函数中,要创建字符设备,创建字符设备中最重要的操作是实现文件操作集中的函数,如下所示:
比如我们在应用程序中使用open函数打开设备节点,会执行文件操作集中的open函数,当我们在应用程序中使用read函数读设备节点的时候,会执行文件操作集中的read函数。本章节我们重点学习open函数,如下所示:
static int evdev_open(struct inode *inode, struct file *file)
{// 从 inode 的 i_cdev 成员中获取 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_client 结构体和输入事件缓冲区client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);if (!client)client = vzalloc(size);if (!client)return -ENOMEM;// 初始化 client 结构体的成员变量client->bufsize = bufsize;spin_lock_init(&client->buffer_lock);client->evdev = evdev;// 将 client 添加到 evdev 的客户端列表中evdev_attach_client(evdev, client);// 打开底层设备error = evdev_open_device(evdev);if (error)goto err_free_client;// 将 client 结构体设置为文件的私有数据file->private_data = client;// 使用 nonseekable_open 函数打开文件,设置文件为不可寻址的nonseekable_open(inode, file);// 返回成功的状态码return 0;err_free_client:// 打开设备失败,需要进行错误处理// 从 evdev 的客户端列表中移除 clientevdev_detach_client(evdev, client);// 释放 client 分配的内存kvfree(client);// 返回错误码return error;
}
上面这段代码是输入设备驱动程序中evdev_open函数,用于处理输入设备的打开操作,以下是对代码的解释。
1 首先,函数使用container_of宏从inode->i_cdev中获取指向struct evdev的指针。这里假设struct evdev结构体包含一个名为cdev的成员变量,该成员变量保存了指向字符设备的指针。
2 函数调用evdev_compute_buffer_size函数来计算输入设备缓冲区的大小,存储在bufsize变量中。该函数根据输入设备的属性计算出缓冲区的大小。
3 接下来,函数计算需要分配的内存大小,即struct evdev_client结构体的大小加上输入事件缓冲区的大小。这个大小存储在size变量中。
4 函数使用kzalloc函数尝试以GFP_KERNEL | __GFP_NOWARN标志分配内存空间,并将返回的指针赋给client变量。如果kzalloc分配失败,则使用vzalloc函数尝试以GFP_KERNEL标志分配内存空间。
5如果最终分配的内存空间为空,则表示内存分配失败,函数返回-ENOMEM。
6如果内存分配成功,函数根据分配的大小设置client结构体的字段,包括缓冲区大小(bufsize)、缓冲区的自旋锁(buffer_lock)、关联的输入设备指针(evdev)。
7 接下来,函数调用evdev_attach_client函数将client结构体与输入设备关联起来,表示该客户端正在访问该输入设备。
8 函数调用evdev_open_device函数打开输入设备,执行一些与设备相关的初始化操作。如果打开设备失败,函数跳转到err_free_client标签处进行错误处理。如果打开设备成功,函数将client指针存储在file结构的private_data字段中,以便在后续的文件操作中可以访问到client结构体。
9最后,函数调用nonseekable_open函数将文件标记为不可寻址,以指示该文件不支持随机访问。
10如果一切顺利,函数返回0,表示输入设备的打开操作成功。
11如果在任何步骤中出现错误,函数通过跳转到err_free_client标签处进行错误处理。在错误处理中,函数调用evdev_detach_client函数将client从输入设备中分离,然后使用kvfree函数释放client占用的内存空间,并返回相应的错误码。
函数调用evdev_open_device函数打开输入设备,我们来详细看看这个函数
在上述代码中,首先使用mutex_lock_interruptible函数获取输入设备的互斥锁,如果无法获取锁,函数将返回相应的错误码。如果成功获取了锁,函数会继续执行后续的操作。
函数首先检查输入设备的exist字段,如果该字段为假(0),表示输入设备不存在,函数将返回-ENODEV,表示设备不存在的错误码。否则,如果输入设备的open字段为0,表示设备之前没有被打开过,函数将进一步执行以下操作。
- 调用input_open_device函数打开输入设备,并将返回值存储在retval变量中。
- 如果打开设备失败,函数会将evdev->open减一,表示设备的打开计数器递减。
最后,函数释放输入设备的互斥锁,使用mutex_unlock函数,并返回retval,表示打开设备的结果。
148.2 ioctl函数分析
接下来我们继续分析下ioctl函数,如下图所示:
evdev_ioctl函数如下所示:
上述函数中调用evdev_ioctl_handler函数来处理IO控制操作,传递给它file指针,cmd和类型转换后的arg作为参数。函数将arg转换为void __user *类型,以便在用户空间和内核空间之间传递指针。函数将evdev_ioctl_handler的返回值作为自己的返回值,并将其直接返回给调用者。
evdev_ioctl_handler函数如下所示:
static long evdev_ioctl_handler(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{// 从文件结构获取指向evdev_client的指针struct evdev_client *client = file->private_data;// 从evdev_client获取指向evdev的指针struct evdev *evdev = client->evdev;int retval;// 尝试获取evdev的互斥锁,如果无法获取则返回相应的错误码retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;// 检查设备是否存在或者客户端是否已被撤销if (!evdev->exist || client->revoked) {// 如果设备不存在或客户端已被撤销,则返回设备不存在的错误码retval = -ENODEV;goto out;}// 调用evdev_do_ioctl函数来执行实际的IO控制操作,并将返回值存储在retval变量中retval = evdev_do_ioctl(file, cmd, p, compat_mode);out:// 解锁evdev的互斥锁mutex_unlock(&evdev->mutex);// 返回IO控制操作的结果return retval;
}
上面的代码是evdev_ioctl_handler的函数,用于处理输入设备的IO控制操作,以下是对代码的解释。
1 函数接收一个指向struct file结构的指针file,一个无符号整数cmd,一个指向void __user类型的指针p,以及一个整数compat_mode作为参数。
2 函数从file结构的private_data字段获取指向struct evdev_client的指针client,并从中获取指向struct evdev的指针evdev。这些结构体用于表示输入设备和客户端信息。
3函数尝试获取输入设备的互斥锁,使用mutex_lock_interruptible函数。如果无法获取锁,函数将返回相应的错误码。如果成功获取了锁,函数会继续执行后续的操作。
4 函数首先检查输入设备的exist字段和客户端的revoked字段,如果设备不存在或者客户端已被撤销,则将返回-ENODEV,表示设备不存在的错误码。否则,函数调用evdev_do_ioctl函数来执行实际的IO控制操作,传递给它file指针,cmd,p和compat_mode作为参数,并将返回值存储在retval变量中。
5 函数通过标签out跳转到mutex_unlock处,在解锁输入设备的互斥锁之前最终返回retval的值。
6 最后,函数解锁输入设备的互斥锁,使用mutex_unlock函数,并返回retval,表示IO控制操作的结果。
evdev_do_ioctl函数来执行实际的IO控制操作,我们详细来看看这个函数,如下所示:
static long evdev_do_ioctl(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_dev *dev = evdev->handle.dev;struct input_absinfo abs;struct input_mask mask;struct ff_effect effect;int __user *ip = (int __user *)p;unsigned int i, t, u, v;unsigned int size;int error;/* First we check for fixed-length commands */switch (cmd) {case EVIOCGVERSION:return put_user(EV_VERSION, ip);case EVIOCGID:if (copy_to_user(p, &dev->id, sizeof(struct input_id)))return -EFAULT;return 0;case EVIOCGREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (put_user(dev->rep[REP_DELAY], ip))return -EFAULT;if (put_user(dev->rep[REP_PERIOD], ip + 1))return -EFAULT;return 0;case EVIOCSREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (get_user(u, ip))return -EFAULT;if (get_user(v, ip + 1))return -EFAULT;input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);return 0;case EVIOCRMFF:return input_ff_erase(dev, (int)(unsigned long) p, file);case EVIOCGEFFECTS:i = test_bit(EV_FF, dev->evbit) ?dev->ff->max_effects : 0;if (put_user(i, ip))return -EFAULT;return 0;case EVIOCGRAB:if (p)return evdev_grab(evdev, client);elsereturn evdev_ungrab(evdev, client);case EVIOCREVOKE:if (p)return -EINVAL;elsereturn evdev_revoke(evdev, client, file);case EVIOCGMASK: {void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (void __user *)(unsigned long)mask.codes_ptr;return evdev_get_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSMASK: {const void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (const void __user *)(unsigned long)mask.codes_ptr;return evdev_set_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSCLOCKID:if (copy_from_user(&i, p, sizeof(unsigned int)))return -EFAULT;return evdev_set_clk_type(client, i);case EVIOCGKEYCODE:return evdev_handle_get_keycode(dev, p);case EVIOCSKEYCODE:return evdev_handle_set_keycode(dev, p);case EVIOCGKEYCODE_V2:return evdev_handle_get_keycode_v2(dev, p);case EVIOCSKEYCODE_V2:return evdev_handle_set_keycode_v2(dev, p);}size = _IOC_SIZE(cmd);/* Now check variable-length commands */
#define EVIOC_MASK_SIZE(nr) ((nr) & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT))switch (EVIOC_MASK_SIZE(cmd)) {case EVIOCGPROP(0):return bits_to_user(dev->propbit, INPUT_PROP_MAX,size, p, compat_mode);case EVIOCGMTSLOTS(0):return evdev_handle_mt_request(dev, size, ip);case EVIOCGKEY(0):return evdev_handle_get_val(client, dev, EV_KEY, dev->key,KEY_MAX, size, p, compat_mode);case EVIOCGLED(0):return evdev_handle_get_val(client, dev, EV_LED, dev->led,LED_MAX, size, p, compat_mode);case EVIOCGSND(0):return evdev_handle_get_val(client, dev, EV_SND, dev->snd,SND_MAX, size, p, compat_mode);case EVIOCGSW(0):return evdev_handle_get_val(client, dev, EV_SW, dev->sw,SW_MAX, size, p, compat_mode);case EVIOCGNAME(0):return str_to_user(dev->name, size, p);case EVIOCGPHYS(0):return str_to_user(dev->phys, size, p);case EVIOCGUNIQ(0):return str_to_user(dev->uniq, size, p);case EVIOC_MASK_SIZE(EVIOCSFF):if (input_ff_effect_from_user(p, size, &effect))return -EFAULT;error = input_ff_upload(dev, &effect, file);if (error)return error;if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))return -EFAULT;return 0;}/* Multi-number variable-length handlers */if (_IOC_TYPE(cmd) != 'E')return -EINVAL;if (_IOC_DIR(cmd) == _IOC_READ) {if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))return handle_eviocgbit(dev,_IOC_NR(cmd) & EV_MAX, size,p, compat_mode);if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;abs = dev->absinfo[t];if (copy_to_user(p, &abs, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;return 0;}}if (_IOC_DIR(cmd) == _IOC_WRITE) {if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;if (copy_from_user(&abs, p, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;if (size < sizeof(struct input_absinfo))abs.resolution = 0;/* We can't change number of reserved MT slots */if (t == ABS_MT_SLOT)return -EINVAL;/** Take event lock to ensure that we are not* changing device parameters in the middle* of event.*/spin_lock_irq(&dev->event_lock);dev->absinfo[t] = abs;spin_unlock_irq(&dev->event_lock);return 0;}}return -EINVAL;
}
上述代码中的这些命令解释如下:
EVIOCGVERSION: 获取去掉版本号
EVIOCGID:获取输入设备的ID信息
EVIOCSREP:获取按键重复设置
EVIOCGKEYCODE: 获取按键码
EVIOCGKEYCODE_V2: 获取按键映射表
EVIOCSKEYCODE:设置按键值
EVIOCSKEYCODE_V2:设置按键映射表
EVIOCGNAME(len):获取设备名称
EVIOCGPHYS(len):获取物理位置
EVIOCGUNIQ(len):获取唯一标识符
EVIOCGPROP(len):获取设备属性
EVIOCGMTSLOTS(len):获取多点触控信息
EVIOCGKEY(len):获取全局按键状态
EVIOCGLED(len):获取所有LED状态
EVIOCGSND(len):获取所有声音状态
EVIOCGSW(len):获取所有开关状态
EVIOCGBIT(ev,len):获取事件位图
EVIOCGABS(abs):获取绝对值/范围
EVIOCSABS(abs):设置绝对值/范围
EVIOCSFF:发送力反馈效果到力反馈设备
EVIOCRMFF:删除力反馈效果
EVIOCGEFFECTS:报告同时可播放的效果数量
EVIOCGRAB:占用/释放输入设备
EVIOCREVOKE:撤销设备访问权限
EVIOCGMASK:检索当前事件掩码
EVIOCSMASK:设置事件掩码
EVIOCSCLOCKID:设置用于时间戳的时钟标识
总结来说,evdev_do_ioctl函数是通用事件处理层中用于处理输入设备ioctl命令的函数。它根据传入的参数,找到对应的输入设备对象,并执行相应的操作。这使得开发者可以通过ioctl命令与输入设备进行交互,设置参数、查询状态或执行其他特定的操作,以满足特定的应用需求。
148.3 poll函数分析
接下来我们继续分析下poll函数,如下所示:
evdev_poll函数如下所示:
static __poll_t evdev_poll(struct file *file, poll_table *wait)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 获取evdev_client结构体中的evdev指针struct evdev *evdev = client->evdev;__poll_t mask;// 将当前进程加入到等待队列中,等待evdev->wait的唤醒事件poll_wait(file, &evdev->wait, wait);// 检查evdev->exist和client->revoked的值if (evdev->exist && !client->revoked)// 如果evdev存在且client未被撤销,设置mask为EPOLLOUT | EPOLLWRNORMmask = EPOLLOUT | EPOLLWRNORM;else// 否则,设置mask为EPOLLHUP | EPOLLERRmask = EPOLLHUP | EPOLLERR;// 检查client中的packet_head和tail的值if (client->packet_head != client->tail)// 如果packet_head和tail不相等,设置mask为mask | EPOLLIN | EPOLLRDNORMmask |= EPOLLIN | EPOLLRDNORM;// 返回最终的mask值return mask;
}
上述代码是用于处理evdev设备文件的poll操作。它通过检查相应的事件状态来确定是否需要等待,读取或写入。函数首先将当前进程添加到等待队列中,以等待evdev->wait的唤醒事件。然后根据evdev->exist和client->revoked的值设置mask的值。如果evdev存在且client未被撤销,则将mask设置为EPOLLOUT | EPOLLWRNORM,表示可写入。否则,将mask设置为EPOLLHUP | EPOLLERR,表示发生错误或连接已挂断。接下来,函数检查client中的packet_head和tail的值,如果它们不相等,则将mask设置为mask | EPOLLIN | EPOLLRDNORM,表示可读取。最后,函数返回最终的mask值,表示等待、读取和写入的事件状态。
148.4 fasync函数分析
接下来我们继续分析下fasync函数,如下所示:、
evdev_fasync函数如下所示:
static int evdev_fasync(int fd, struct file *file, int on)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 调用fasync_helper函数来处理进程的异步通知// 该函数会根据on的值,将进程添加到或从异步通知的列表中// 并将通知相关的数据存储在client->fasync中return fasync_helper(fd, file, on, &client->fasync);
}
上述函数用于处理evdev设备文件的异步通知注册和注销。它接收文件描述符fd,文件指针file和一个整数on作为参数。函数首先从文件的私有数据中获取evdev_client结构体指针。然后调用fasync_helper函数,该函数负责处理进程的异步通知。根据on的值,fasync_helper函数将进程添加到异步通知的列表中或从列表中移除,并将通知相关的数据存储在client->fasync中。最后函数返回fasync_helper的返回值,表示异步通知的注册和注销是否成功。
148.5 llseek函数
接下来我们继续分析下llseek函数
no_llseek函数如下所示:
在函数开始时,首先将-ESPIPE作为返回值直接返回。这个函数的作用是阻止对设备文件执行llseek操作,也就是不允许通过改变文件位置指针来随机访问设备文件。这可能是因为设备特性或驱动程序的限制所导致的。通过禁止定位操作,可确保对设备文件的访问方式按照预期方式进行,避免潜在的问题和错误。
148.6 release函数分析
接下来我们继续分析release函数,如下所示:
evdev_release函数如下所示:
static int evdev_release(struct inode *inode, struct file *file)
{// 获取文件私有数据中的evdev_client结构体指针struct evdev_client *client = file->private_data;// 获取evdev_client结构体中的evdev指针struct evdev *evdev = client->evdev;unsigned int i;// 获取evdev的互斥锁,确保对evdev的操作是原子的mutex_lock(&evdev->mutex);// 检查evdev->exist和client->revoked的值if (evdev->exist && !client->revoked)// 如果evdev存在且client未被撤销,调用input_flush_device函数刷新设备的输入缓冲区input_flush_device(&evdev->handle, file);// 释放evdev的抢占状态,将客户端从抢占列表中移除evdev_ungrab(evdev, client);// 解锁evdev的互斥锁mutex_unlock(&evdev->mutex);// 从evdev中分离并释放客户端evdev_detach_client(evdev, client);// 释放客户端的事件掩码内存for (i = 0; i < EV_CNT; ++i)bitmap_free(client->evmasks[i]);// 释放客户端的内存kvfree(client);// 关闭evdev设备evdev_close_device(evdev);// 返回0表示成功释放资源return 0;
}
该函数用于释放evdev设备文件相关的资源。它接收一个inode结构体指针和一个file结构体指针作为参数。函数首先从文件的私有数据中获取evdev_client结构体指针和evdev指针。然后获取evdev的互斥锁,确保对evdev的操作是原子的。
接下来,函数检查evdev->exist和client->revoked的值。如果evdev存在且client未被撤销,则调用input_flush_device函数刷新设备的输入缓冲区,确保所有未处理的输入事件被处理。
然后,函数释放evdev的抢占状态,通过调用evdev_ungrab函数将客户端从抢占列表中移除。接着,解锁evdev的互斥锁。
接下来,函数调用evdev_detach_client函数从evdev中分离并释放客户端。
然后,函数使用循环遍历释放客户端的事件掩码内存,通过调用bitmap_free函数进行释放。
接着,函数使用kvfree函数释放客户端的内存。
最后,函数调用evdev_close_device函数关闭evdev设备。
最终,函数返回0表示成功释放资源。
整体来说,这个函数的作用是用于释放设备文件的资源和与之关联的内存空间,以便其他客户端能够继续访问设备并避免占用过多的系统资源。该函数在设备文件关闭时自动调用,通常不需要用户手动调用。
这篇关于RK3568驱动指南|第十三篇 输入子系统-第148章 通用事件处理层open函数分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!