RK3568驱动指南|第十三篇 输入子系统-第139章 输入子系统数据结构介绍

本文主要是介绍RK3568驱动指南|第十三篇 输入子系统-第139章 输入子系统数据结构介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

瑞芯微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主板


第139章 输入子系统数据结构介绍

事件处理层的代码位于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(或者没有匹配函数),则返回该IDreturn 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 存在标志为 truedev_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_freedevice_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);elselist_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),将输入设备添加到输入子系统中,会在后面的输入子系统驱动编写中重新讲解该函数,至此关于输入子系统数据结构的介绍就完成了,整理好的输入子系统数据结构关系框图如下所示:

这篇关于RK3568驱动指南|第十三篇 输入子系统-第139章 输入子系统数据结构介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/800422

相关文章

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

macOS怎么轻松更换App图标? Mac电脑图标更换指南

《macOS怎么轻松更换App图标?Mac电脑图标更换指南》想要给你的Mac电脑按照自己的喜好来更换App图标?其实非常简单,只需要两步就能搞定,下面我来详细讲解一下... 虽然 MACOS 的个性化定制选项已经「缩水」,不如早期版本那么丰富,www.chinasem.cn但我们仍然可以按照自己的喜好来更换

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

使用JavaScript将PDF页面中的标注扁平化的操作指南

《使用JavaScript将PDF页面中的标注扁平化的操作指南》扁平化(flatten)操作可以将标注作为矢量图形包含在PDF页面的内容中,使其不可编辑,DynamsoftDocumentViewer... 目录使用Dynamsoft Document Viewer打开一个PDF文件并启用标注添加功能扁平化

电脑显示hdmi无信号怎么办? 电脑显示器无信号的终极解决指南

《电脑显示hdmi无信号怎么办?电脑显示器无信号的终极解决指南》HDMI无信号的问题却让人头疼不已,遇到这种情况该怎么办?针对这种情况,我们可以采取一系列步骤来逐一排查并解决问题,以下是详细的方法... 无论你是试图为笔记本电脑设置多个显示器还是使用外部显示器,都可能会弹出“无HDMI信号”错误。此消息可能

如何安装 Ubuntu 24.04 LTS 桌面版或服务器? Ubuntu安装指南

《如何安装Ubuntu24.04LTS桌面版或服务器?Ubuntu安装指南》对于我们程序员来说,有一个好用的操作系统、好的编程环境也是很重要,如何安装Ubuntu24.04LTS桌面... Ubuntu 24.04 LTS,代号 Noble NumBAT,于 2024 年 4 月 25 日正式发布,引入了众