赞
踩
瑞芯微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主板
事件处理层的代码位于drivers/input/evdev.c文件中,为上层的应用程序提供了统一的事件处理机制。它定义了处理输入设备事件的函数,并提供了读取事件、控制设备等功能的接口。
当evdev设备注册时,首先会调用input_register_handler函数来注册input handler结构体,具体内容如下所示:
- static int __init evdev_init(void)
- {
- return input_register_handler(&evdev_handler);
- }
这里的input_register_handler函数会将evdev handler添加到输入子系统的handler列表中,并分配一个唯一的handler编号,evdev handler结构体内容如下所示:
- static struct input_handler evdev_handler = {
- .event = evdev_event,// 事件处理函数指针,指向名为 evdev_event 的函数,用于处理输入事件
- .events = evdev_events,// 批量事件处理函数指针,指向名为 evdev_events 的函数,用于处理输入设备的多个事件
- .filter = evdev_filter,// 事件过滤函数指针,指向名为 evdev_filter 的函数,用于确定是否接收和处理特定类型和代码的事件
- .match = evdev_match,// 匹配函数指针,指向名为 evdev_match 的函数,用于确定处理程序是否适用于给定的输入设备
- .name = "evdev",// 设备名称,设置为字符串 "evdev"
- .id_table = evdev_ids,// 输入设备ID表,指向名为 evdev_ids 的表,用于匹配输入设备的ID
- };
evdev_handler是一个struct input_handler 类型的结构体变量,它的目的是定义和管理输入处理程序的行为,处理程序可以注册到输入子系统中,以接收和处理特定输入设备产生的事件。通过使用不同的处理程序,输入子系统可以支持各种类型的输入设备,并提供定制的事件处理逻辑。input_handler结构体内容如下所示:
- struct input_handler {
- void *private; // 私有数据指针,用于存储特定处理程序的私有数据
-
- // 事件处理函数指针,当输入事件发生时调用,参数包括输入句柄、事件类型、事件代码和事件值
- void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
-
- // 批量事件处理函数指针,当输入设备有多个事件同时发生时调用,参数包括输入句柄、事件值数组和事件数量
- void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);
-
- // 事件过滤函数指针,用于确定是否接收和处理特定类型和代码的事件,返回值为布尔类型,表示是否接受该事件
- bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
-
- // 匹配函数指针,用于确定处理程序是否适用于给定的输入设备,返回值为布尔类型,表示是否适用
- bool (*match)(struct input_handler *handler, struct input_dev *dev);
-
- // 连接函数指针,用于建立输入设备和处理程序之间的连接,返回值为整数类型,表示连接的结果
- int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
-
- // 断开连接函数指针,用于断开输入设备和处理程序之间的连接
- void (*disconnect)(struct input_handle *handle);
-
- // 启动函数指针,用于启动输入设备的数据传输或处理过程
- void (*start)(struct input_handle *handle);
-
- bool legacy_minors; // 是否使用旧版次设备号
- int minor; // 设备次设备号
- const char *name; // 设备名称
-
- const struct input_device_id *id_table; // 输入设备ID表
-
- struct list_head h_list; // 处理程序链表头
- struct list_head node; // 处理程序链表节点
- };
然后继续回到drivers/input/evdev.c文件中,驱动入口函数中的input_register_handler函数定义在kernel/drivers/input/input.c文件中,该函数的具体内容如下所示:
- 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);
-
- // 唤醒procfs读取器,通知其有新的处理程序注册
- input_wakeup_procfs_readers();
-
- // 释放输入互斥锁
- mutex_unlock(&input_mutex);
-
- return 0;
- }
函数的详细解释如下:
第7行:函数尝试获取输入互斥锁,通过调用 mutex_lock_interruptible(&input_mutex)函数,确保在注册处理程序时不会被中断,以保护关键操作的完整性。
第12行:函数初始化处理程序链表头,通过调用 INIT_LIST_HEAD(&handler->h_list)函数创建一个空的链表头,用于存储与此处理程序相关的其他结构体。
第15行:函数将处理程序添加到全局处理程序链表的末尾,通过调用 list_add_tail(&handler->node, &input_handler_list)函数将处理程序加入到全局链表中,使其能够与输入子系统的其他组件进行交互。
第18-19行:函数使用 list_for_each_entry 循环遍历输入设备链表(input_dev_list),为每个输入设备建立与处理程序的连接。在每次迭代中,它调用 input_attach_handler(dev, handler),将当前设备和处理程序作为参数传递给 input_attach_handler 函数。这样可以为每个输入设备建立与处理程序的连接,以便处理设备发送的输入事件。
第22行:在为所有输入设备附加处理程序后,函数调用 input_wakeup_procfs_readers(),唤醒正在阻塞的 procfs 读取器。这是为了通知 procfs 读取器有新的处理程序已经注册,以便读取器可以及时获取新的输入事件信息。
第25行,函数释放输入互斥锁,通过调用 mutex_unlock(&input_mutex),以允许其他线程继续访问输入子系统。
在上述程序中最重要的就是18-19行的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-14行:函数通过一个循环遍历处理程序的输入设备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。
由于input_handler结构体属于事件处理层,所以与它相对应的input_dev结构体自然就属于设备驱动层,input_dev结构体定义在input.h中,该结构体内容具体如下所示:
- struct input_dev {
- const char *name; // 设备的名称
- const char *phys; // 设备的物理位置
- const char *uniq; // 设备的唯一标识符
- struct input_id id; // 输入设备的标识信息
-
- unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; // 设备的属性位图
- unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 设备支持的事件类型位图
- unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; // 设备支持的按键位图
- unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; // 设备支持的相对坐标位图
- unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; // 设备支持的绝对坐标位图
- unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; // 设备支持的杂项事件位图
- unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; // 设备支持的LED位图
- unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; // 设备支持的声音位图
- unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; // 设备支持的力反馈位图
- unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; // 设备支持的开关位图
-
- unsigned int hint_events_per_packet; // 每个输入事件报告中的事件数量提示
-
- unsigned int keycodemax; // 支持的按键编码的最大值
- unsigned int keycodesize; // 按键编码的字节大小
- void *keycode; // 按键编码数据的指针
-
- int (*setkeycode)(struct input_dev *dev,
- const struct input_keymap_entry *ke,
- unsigned int *old_keycode); // 设置按键编码的回调函数
- int (*getkeycode)(struct input_dev *dev,
- struct input_keymap_entry *ke); // 获取按键编码的回调函数
-
- struct ff_device *ff; // 力反馈设备
-
- unsigned int repeat_key; // 重复按键的编码
- struct timer_list timer; // 定时器用于处理重复按键
-
- int rep[REP_CNT]; // 按键重复设置
-
- struct input_mt *mt; // 多点触摸信息
-
- struct input_absinfo *absinfo; // 绝对坐标信息
-
- unsigned long key[BITS_TO_LONGS(KEY_CNT)]; // 当前按键状态位图
- unsigned long led[BITS_TO_LONGS(LED_CNT)]; // 当前LED状态位图
- unsigned long snd[BITS_TO_LONGS(SND_CNT)]; // 当前声音状态位图
- unsigned long sw[BITS_TO_LONGS(SW_CNT)]; // 当前开关状态位图
-
- int (*open)(struct input_dev *dev); // 打开设备的回调函数
- void (*close)(struct input_dev *dev); // 关闭设备的回调函数
- int (*flush)(struct input_dev *dev, struct file *file); // 刷新设备的回调函数
- int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); // 处理输入事件的回调函数
-
- struct input_handle __rcu *grab; // 设备的当前占用者
-
- spinlock_t event_lock; // 事件锁,用于保护事件队列
- struct mutex mutex; // 互斥锁,用于保护设备状态
-
- unsigned int users; // 设备的使用者数量
- bool going_away; // 设备是否即将被移除
-
- struct device dev; // 设备结构体
-
- struct list_head h_list; // 用于设备管理的链表
- struct list_head node; // 用于设备管理的链表
-
- unsigned int num_vals; // 输入值的数量
- unsigned int max_vals; // 最大输入值的数量
- struct input_value *vals; // 输入值的数组
-
- bool devres_managed; // 是否由设备资源管理
-
- ktime_t timestamp[INPUTCLK_MAX]; // 输入事件的时间戳数组
- };
关于input_dev结构体在后面编写实际驱动程序的时候会进行讲解,然后继续回到input_attach_handler函数,在input_attach_handler函数第12行调用的handler->connect函数,建立了输入设备和处理程序之间的连接,在讲解该函数之前首先引入input_handle结构体,在调用connect函数之后,会创建一个input_handle结构体,用于记录匹配成功的输入处理程序(input_handler)和输入设备(input_dev),并建立它们之间的关系。下面是input_handle结构体的定义(位于include/linux/input.h):
- struct input_handle {
- void *private; // 私有数据指针
- int open; // 打开计数
- const char *name; // 名称
- struct input_dev *dev; // 输入设备
- struct input_handler *handler; // 输入处理程序
- struct list_head d_node; // 指向输入设备链表的节点
- struct list_head h_node; // 指向输入处理程序链表的节点
- };
connect函数定义在kernel/drivers/input/evdev.c文件中,具体内容如下所示:
- 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;
-
- // 获取一个新的次设备号
- 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 结构体
- evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
- if (!evdev) {
- error = -ENOMEM;
- goto err_free_minor;
- }
-
- // 初始化 evdev 结构体中的成员
- INIT_LIST_HEAD(&evdev->client_list); // 初始化客户端链表
- spin_lock_init(&evdev->client_lock); // 初始化客户端链表的自旋锁
- mutex_init(&evdev->mutex); // 初始化互斥锁
- init_waitqueue_head(&evdev->wait); // 初始化等待队列头
- evdev->exist = true; // 设置 evdev 存在标志为 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->handle.dev = input_get_device(dev); // 设置输入句柄的输入设备
- evdev->handle.name = dev_name(&evdev->dev); // 设置输入句柄的名称为设备名称
- evdev->handle.handler = handler; // 设置输入句柄的处理程序为传入的处理程序
- evdev->handle.private = evdev; // 设置输入句柄的私有数据指针为 evdev 结构体的指针
-
- evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); // 设置设备号
- evdev->dev.class = &input_class; // 设置设备的类
- evdev->dev.parent = &dev->dev; // 设置设备的父设备
- evdev->dev.release = evdev_free; // 设置设备的释放函数为 evdev_free
- device_initialize(&evdev->dev); // 初始化设备
-
- error = input_register_handle(&evdev->handle); // 注册输入句柄
- if (error)
- goto err_free_evdev;
-
- cdev_init(&evdev->cdev, &evdev_fops); // 初始化字符设备结构体
-
- 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); // 释放设备
- err_free_minor:
- input_free_minor(minor); // 释放次设备号
- return error;
- }
该函数的主要功能是建立与输入设备的连接,初始化并注册输入句柄,设置设备属性,并添加字符设备到系统中 。
第9-15行:函数调用 input_get_new_minor 获取一个新的次设备号,以便将其分配给新的输入设备。
第17-22行:使用 kzalloc函数为 evdev 分配了一块内存,大小为 sizeof(struct evdev),并将返回的指针赋值给 evdev。evdev 是一个结构体指针,用于管理与输入设备相关的数据和状态。
第24-28行,对 evdev 结构体进行初始化。它调用了几个初始化函数,包括 INIT_LIST_HEAD、spin_lock_init、mutex_init 和 init_waitqueue_head,用于初始化 evdev 结构体中的成员。
第29行:函数将 evdev->exist 设置为 true,表示 evdev 结构体表示的设备存在。
第31-35行:计算设备号 dev_no。
第36行:使用 dev_set_name 函数来设置设备的名称,格式为 "event%d",其中 %d 是设备号。
第38-41行:设置 evdev 结构体中的输入句柄的属性。它将输入句柄的 dev 成员设置为输入设备 dev,将 name 成员设置为设备名称的字符串表示,将 handler 成员设置为传入的输入处理程序 handler,将 private 成员设置为 evdev 结构体的指针。
第43-46行:函数设置设备的属性。它将设备号设置为 MKDEV(INPUT_MAJOR, minor),这是一个宏,用于创建设备号。它将设备的类设置为 input_class,这是一个表示输入设备的类结构体指针。它将设备的父设备设置为输入设备 dev 的父设备。最后,它将设备的释放函数设置为 evdev_free,这是在设备被释放时调用的函数。
第47行:调用 device_initialize 初始化设备。
第49行:调用 input_register_handle 注册输入句柄。这将输入句柄添加到输入子系统中,以便处理输入事件。
第53行:使用 cdev_init 函数初始化字符设备结构体 evdev->cdev,并将字符设备的操作函数指针设置为 evdev_fops。
第55行:调用 cdev_device_add 将字符设备添加到系统中。这将使得字符设备可以被用户空间的程序打开和访问。
input_register_handle函数用来注册input_handle句柄,该函数定义在drivers/input/input.c文件中,具体内容如下所示:
- int input_register_handle(struct input_handle *handle)
- {
- struct input_handler *handler = handle->handler; // 获取输入处理程序
- struct input_dev *dev = handle->dev; // 获取输入设备
- int error;
-
- /*
- * 在这里获取 dev->mutex 锁,以防止与 input_release_device() 的竞争。
- */
- error = mutex_lock_interruptible(&dev->mutex);
- if (error)
- return error;
-
- /*
- * 将过滤器添加到链表头部,普通处理程序添加到链表尾部。
- */
- if (handler->filter)
- list_add_rcu(&handle->d_node, &dev->h_list);
- else
- list_add_tail_rcu(&handle->d_node, &dev->h_list);
-
- mutex_unlock(&dev->mutex);
-
- /*
- * 由于我们假设被从 ->connect() 调用,这与 ->disconnect() 是互斥的,
- * 所以我们不能与 input_unregister_handle() 竞争,因此此处不需要额外的锁定。
- */
- list_add_tail_rcu(&handle->h_node, &handler->h_list);
-
- if (handler->start)
- handler->start(handle);
-
- return 0;
- }
该函数的主要作用是将输入处理程序(input_handler)和输入设备(input_dev)建立关联。对于输入设备来说,可以通过遍历handler->h_list链表来查找与之匹配的输入处理程序。这意味着输入设备可以通过遍历与之相关联的输入处理程序链表来找到相应的处理程序。对于输入处理程序来说,可以通过遍历dev->h_list链表来查找与之匹配的输入设备。这意味着输入处理程序可以通过遍历与之相关联的输入设备链表来找到相应的设备。这样,通过建立输入处理程序和输入设备之间的关联关系,可以实现输入处理程序对特定输入设备的处理和控制。
第3-4行:获取输入句柄中的输入处理程序和输入设备分别赋值给handler和dev。
第10行:函数获取输入设备的互斥锁dev->mutex,以防止与input_release_device()函数的竞争。如果获取锁失败,函数返回相应的错误代码。
第17-20行:根据输入处理程序的类型,将输入句柄添加到输入设备的h_list链表中。如果输入处理程序是过滤器类型(handler->filter为真),则将输入句柄添加到链表头部(使用list_add_rcu函数),否则将输入句柄添加到链表尾部(使用list_add_tail_rcu函数)。通过将输入句柄添加到链表中,建立了输入设备与输入句柄之间的关联关系。
第22行:解锁输入设备的互斥锁dev->mutex。
第28行:将输入句柄添加到输入处理程序的h_list链表中,使用list_add_tail_rcu函数将输入句柄添加到链表尾部。通过将输入句柄添加到链表中,建立了输入处理程序与输入句柄之间的关联关系。
第30-31行:如果输入处理程序的start函数存在,则调用handler->start(handle)来启动输入处理程序的相关操作。
input_handler结构体要使用input_register_handler来注册,而输入处理程序(input_handler)和输入设备(input_dev)链接的结构体input_handle也需要input_register_handle函数来注册,那输入设备input_dev结构体肯定也需要一个函数来注册,input_dev结构体的注册函数为input_register_device,定义在drivers/input/input.c文件中,函数的具体内容如下所示
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; } |
input_register_device函数用于注册输入设备(input_dev),将输入设备添加到输入子系统中,会在后面的输入子系统驱动编写中重新讲解该函数,至此关于输入子系统数据结构的介绍就完成了,整理好的输入子系统数据结构关系框图如下所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。