瑞芯微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主板
第142章 从最简单的设备驱动代码入手分析匹配规则
在上一章中编写完成了最简单的设备驱动层代码,从实验现象可以得到,输入设备注册完成之后会自动生成相应的设备节点,在数据结构的介绍中讲解过,设备驱动层的结构体会和事件处理层的结构体进行匹配,可是从上一章编写的代码中并没有看到匹配的过程,所以在本章中将会分析设备驱动层是怎样跟事件处理层进行匹配的?
设备驱动层的input_dev结构体由input_register_device函数所注册,该函数的具体内容如下所示:
int input_register_device(struct input_dev *dev)
{
struct input_devres *devres = NULL; // 输入设备资源结构体指针
struct input_handler *handler; // 输入处理程序指针
unsigned int packet_size; // 数据包大小
const char *path; // 设备路径字符串指针
int error;
if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) {
dev_err(&dev->dev,
"Absolute device without dev->absinfo, refusing to register\n");
return -EINVAL;
}
if (dev->devres_managed) { // 如果设备资源是由管理的,则分配设备资源结构体
devres = devres_alloc(devm_input_device_unregister,
sizeof(*devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->input = dev;
}
/* 每个输入设备都会产生 EV_SYN/SYN_REPORT 事件。 */
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED 不应传递给用户空间。 */
__clear_bit(KEY_RESERVED, dev->keybit);
/* 确保未在 dev->evbit 中提及的位掩码是干净的。 */
input_cleanse_bitmasks(dev);
packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size;
dev->max_vals = dev->hint_events_per_packet + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
}
/*
* 如果延迟和周期由驱动程序预设,
* 则自动重复由驱动程序自己处理,我们不在 input.c 中处理。
*/
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
input_enable_softrepeat(dev, 250, 33);
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
error = device_add(&dev->dev);
if (error)
goto err_free_vals;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
list_add_tail(&dev->node, &input_dev_list);
// 遍历输入处理程序链表,将输入设备与每个处理程序建立关联
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0;
err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
上述内容的重点为75行的input_attach_handler 函数,该函数可以为每个输入设备建立与处理程序的连接,该函数同样定义在input.c中,具体内容如下所示:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
// 通过输入设备和处理程序的匹配函数来确定是否适用于该设备
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
// 调用处理程序的连接函数来建立设备和处理程序之间的连接
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
// 如果连接失败且错误码不是-ENODEV,则打印错误消息
return error;
}
第7行:调用 input_match_device(handler, dev)函数来确定输入设备和处理程序是否匹配。这个函数将在处理程序的输入设备ID表中查找与给定的输入设备匹配的ID,并返回匹配的ID。如果没有找到匹配的ID,则返回 NULL。
第12行:如果找到匹配的ID会调用 handler->connect(handler, dev, id)函数,这是处理程序的连接函数。该函数用于建立输入设备和处理程序之间的连接,并执行必要的初始化和配置。它将处理程序、输入设备和匹配的ID作为参数传递。
无论是第7行的input_match_device函数还是第12行的handler->connect函数都是很重要的,首先来看第7行调用的input_match_device函数,函数具体内容如下所示:
static const struct input_device_id *input_match_device(struct input_handler *handler, struct input_dev *dev)
{
const struct input_device_id *id;
// 遍历处理程序的输入设备ID表,直到找到匹配的ID或遍历完所有ID为止
for (id = handler->id_table; id->flags || id->driver_info; id++) {
// 使用输入设备ID匹配函数判断给定的输入设备是否与当前ID匹配
if (input_match_device_id(dev, id) &&
(!handler->match || handler->match(handler, dev))) {
// 如果输入设备与ID匹配,并且处理程序的匹配函数返回true(或者没有匹配函数),则返回该ID
return id;
}
}
return NULL;
}
这个函数在输入子系统中的作用是在给定的输入事件处理程序(input handler)中查找与指定输入设备匹配的输入设备ID(input device ID)。下面是对函数的详细解释:
第7-15行:函数通过一个循环遍历处理程序的输入设备ID表,直到找到匹配的ID或遍历完所有ID为止。处理程序的输入设备ID表是一个以 struct input_device_id结构为元素的数组,每个元素表示一个可能的输入设备ID。
在循环中,函数使用 input_match_device_id(dev, id)调用输入设备ID匹配函数来判断给定的输入设备是否与当前ID匹配。匹配函数的作用是比较输入设备的属性与ID中指定的属性是否一致,例如厂商ID、产品ID等。如果输入设备与当前ID匹配,并且处理程序的匹配函数(handler->match)返回 true,则表示找到了匹配的ID。处理程序的匹配函数用于进一步检查设备是否满足处理程序特定的要求。如果处理程序没有指定匹配函数(handler->match为 NULL),则默认认为匹配。如果找到匹配的ID,函数立即返回该ID。如果遍历完所有ID都没有找到匹配的ID,则函数返回 NULL,表示在处理程序的输入设备ID表中没有与给定输入设备匹配的ID。
在上面的for循环中会遍历所有事件处理层的handler,这里以drivers/input/evdev.c这一通用事件处理层中的input_handler为例进行讲解,类型的结构体中有如下内容:
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,
};
其中evdev_handler结构体中的id_table变量为evdev_ids,evdev_ids内容如下所示:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
由于driver_info为1,所以for循环的条件就会一直成立,在每一次的循环中都会判定id是不是匹配,判定的函数是input_match_device_id,该函数的内容如下所示:
bool input_match_device_id(const struct input_dev *dev,
const struct input_device_id *id)
{
// 检查设备的总线类型是否匹配
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
return false;
// 检查设备的厂商ID是否匹配
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
return false;
// 检查设备的产品ID是否匹配
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
return false;
// 检查设备的版本号是否匹配
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
return false;
// 检查设备的事件位图是否是给定ID的子集
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
!bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
!bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
!bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
!bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
!bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
return false;
}
// 所有匹配条件都满足,返回true
return true;
}
由于在编写的最简单的设备驱动层代码中并未定义id的flags参数,所以第5、10、15、20中的判断都不成立,而bitmap_subset`是一个内联函数,用于判断两个位图是否具有子集关系,id中并未定义evbit、keybit、relbit等,所以25-34行的if判断也不成立,最终该函数会返回true,当然这仅仅只是对evdev.c这一通用事件处理代码进行的分析,返回true之后然后回到input_attach_handler 函数,在第12行会调用handler->connect建立与输入设备的连接。
至此上一章节中编写的最简单设备驱动层代码的匹配就讲解完成了,这一章节中是根据drivers/input/evdev.c这一通用时间处理层的代码来进行分析的,而drivers/input/joydev.c也是一个事件处理层代码,那在上一章中编写的最简单的设备驱动层代码能跟drivers/input/joydev.c匹配上吗,会在下一个章节中进行分析。