IMX6ULL_input子系统笔记

输入驱动知识点

1> 逻辑简单,功能简单的输入的实现 --就有限个按键的输入实现
这种情况直接使用基本驱动框架来实现,正常情况下和输入相关的驱动都需要通过input子系统来实现

代码实现流程:
1. 设备树   在设备树文件中定义pinctrl+key的设备节点 --确保定义的pin/gpio信息的唯一性
2. 驱动文件
		a. 声明驱动信息(author,GPL协议)
		b. 加载驱动 (module_init)
		c. 实现驱动加载函数 
			1. 定义全局设备信息结构体变量
			2. 原子操作 (原子变量就是按键值,对读取到的按键值进行保护)
			3. 注册设备号
			4. 初始化cdev (将设备号和fops通过cdev建立关系)
			5. 创建设备类
			6. 创建设备文件
			7. 硬件初始化
					获取设备节点
					获取GPIO编号
					gpio操作 
			8. 实现fops
				open() ==>将设备结构体信息保存在私有数据中
				read() ==>从私有数据中取出设备信息结构体,读取key对应gpio的值,然后通过copy_to_user()上报给应用层
				close()==>不需要什么操作。。
			9. 错误集中处理 goto实现
		d. 实现驱动卸载函数
			直接将前面错误集中处理的代码拿过来就可以
			一句话: 后申请的先释放
3. 应用文件
		1. 打开设备文件 	open()
		2. 循环读取按键值	read()
		3. 关闭设备文件		close()

2> input子系统简介
输入设备有哪些? 按键/鼠标/键盘/触摸屏 ; 输入设备本质上还是字符设备,所以我们前面使用字符设备的基本框架实现了读取按键的值
linux专门做了一个叫做input子系统的框架来处理输入事件
input子系统框架: 用户只需要在驱动中上报读取到的输入事件即可,系统会在input_handle层自动处理这些事件,然后将按键信息组装成input_event结构体传递给用户层

什么是按键信息?
==>
不同的输入设备所代表的按键信息含义不同,对于鼠标/触摸屏来说就是按键坐标和按键释放被按下,对于键盘来说那就是什么键被按下

input子系统框架
硬件输入设备: 按键,鼠标,键盘,TS
内核空间: 驱动层,核心层,事件层 ==>这是关键
用户空间: 操作设备文件

驱动代码分层 我们关心这个就可以了
在input_device层 驱动层(自己写代码实现) —获取数据,上报数据给input_core 硬件初始化操作
a. 申请一个input_dev对象来代表这个硬件输入设备
b. 初始化这个input_dev对象(叫什么,什么输入事件,什么按键,按键的值…)
c. 在加载驱动时将input设备注册到内核中
e. 硬件初始化
获取设备节点
因为这个节点下面可能定义了非常多的按键的gpio信息,这些按键我们都要使用,故需要使用循环来处理
获取gpio编号
设置中断处理函数所需要的参数值
获取中断号
设置中断处理函数
设置触发方式
设置中断名称 (会在某个文件下记录)
设置传递该中断函数的参数
gpio子系统的api操作
初始化系统定时器
f. 完成中断处理函数(启动定时器,实现按键消逗)
g. 实现定时器的超时函数(获取按键值,上报按键值,这里不在是直接上报该用户层,而是上报给input_core层)
h. 在卸载驱动时,删除定时器,注销中断,释放input_dev
在input_core层 核心层 (系统已经帮忙搞定) —承上启下,将input_device层中上报的数据,在传递给input_handle层
申请设备号,创建设备类
知识点: 在申请设备号时,使用的是手动申请,并且主设备号设置为13,设备号名称为“input”,故input设备的主设备号一定是13
在创建设备类时,我们设置了设备类的名称为"input",故会在/sys/class目录下新增一个名为"input’的文件

在input_handle层 事件层 (系统已帮忙搞定)    ---和用户层交换,将core层中传递进来的input_event数据上报给用户层
	创建设备文件 ,实现fops来和用户层交换

3> 代码流程分析
1. 驱动层 代码见 keyinput.c文件 这个文件需要我们自己实现,目的就是将硬件设备抽象为一个input_dev对象,然后将他注册到input_core
例如我们要实现:实时监视一个按键的状态,当这个按键的状态发生改变就上报当前按键的状态
大致思路: 按键状态发生变化,产生中断,在中断中开启定时器,定时器定时超时,产生超时函数,在超时函数中读取按键的状态,并将按键状态上报给input_core层

   a. 创建一个input_dev对象
   b. 初始化input_dev对象(该设备的名称,要上报的事件类型,按键键码,按键的值)
   c. 将input_dev对象注册到input_core中   
   d. 硬件初始化 
			获取设备树中定义的那些信息,要监视的按键的设备节点,GPIO编号,设置按键值,中断名称,中断参数,注册中断处理函数,gpio操作)  关键点:如果要监视多个按键,这里直接循环处理即可
			初始化定时器  使用定时器是为了实现按键消抖 
   e. 错误处理
   
    知识点:
		1. a,b,c 3点是input子系统中的专属步骤,只要使用了input子系统这3个步骤就必须要有
		2. 什么是input_dev对象  成员的含义直接将将前面的注释翻译即可
			struct input_dev {
				const char *name;		//表示输入设备的名称   名称用于区别设备
				
				/*
					evbit:表示输入事件类型
					输入事件类型包括如下
					/*
					 * Event types
					 */

					#define EV_SYN			0x00		//表示设备支持所有的事件
					#define EV_KEY			0x01		//按键事件   就表示一个按键的状态的事件 键盘/按键 表示一个键
					#define EV_REL			0x02		//相对坐标事件 鼠标设备 
					#define EV_ABS			0x03		//绝对坐标事件 TS
					#define EV_MSC			0x04		//其他类型
					#define EV_SW			0x05		
					#define EV_LED			0x11		//LED灯设备
					#define EV_SND			0x12		//蜂鸣器,输入声音
					#define EV_REP			0x14		//重复事件,当你需要使用按键的连按功能的话就需要注册这个功能
					#define EV_FF			0x15
					... ...
				*/
				unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
				
				/*
					下面的这些刚好就是前面那些输入事件对应的值存放的地方 ,注意这里不是值,而只是存放值的地方
					以keybit为例: 它只是存放了按键的状态值,并不就是按键的状态值  类似于数组和成员的关系,按键状态的值是成员,它是数组
				*/
				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)];
				unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
				unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
				unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

				... ...
			}; 
		3. 注册input_dev对象到哪里去?   //其实这里就进入了input_core层 因为input_register_device函数是在input_core中实现的   ********** 这里开始就是input_core层了
			/*
				input_register_device -注册设备到输入核心
				这个函数用于输入核心注册设备,使用这个函数的前提:设备必须是通过input_allocate_device()产生和它的所有功能(设置上报的事件类型,键码)注册前已经做好准备。
				如果函数失败,必须使用input_free_device()释放设备。
				一旦设备已成功注册,它可以取消注册,取消注册使用input_unregister_device();input_free_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;

				... ...

				/* Every input device generates EV_SYN/SYN_REPORT events.  每个输入设备都会生成EV_SYN/SYN_REPORT事件。*/
				/*
					个人理解:
					将evbit的EV_SYN置1: 表示支持所有的事件
				*/
				__set_bit(EV_SYN, dev->evbit);

				/* KEY_RESERVED is not supposed to be transmitted to userspace. KEY_RESERVED不应该被传输到用户空间*/
				/*
					个人理解:
					就是不管你设不设置上报KEY_RESERVED按键的值,反正系统是不允许你上报的
				*/
				__clear_bit(KEY_RESERVED, dev->keybit);

				/* Make sure that bitmasks not mentioned in dev->evbit are clean.  确保dev->evbit中没有提到的位掩码是干净的*/
				/*
					个人理解:
					根据前面设置的需要上报的事件类型信息,将那些没有设置的事件类型清0,因为在前面通过EV_SYN=1,所有时=事件类型都打开了
						
				*/
				input_cleanse_bitmasks(dev);
						|
				static void input_cleanse_bitmasks(struct input_dev *dev)
				{
					INPUT_CLEANSE_BITMASK(dev, KEY, key);
								|
						#define INPUT_CLEANSE_BITMASK(dev, type, bits)				\
							do {								\
								/*
									判断参数dev(要注册的input_dev对象)中的每个事件类型的值是否为0,如果为0就将dev中的对应事件类型清0
									##表示连接符
									EV_##type == EV_type变量 == EV_KEY
								*/
								if (!test_bit(EV_##type, dev->evbit))			\
									memset(dev->bits##bit, 0,			\
										sizeof(dev->bits##bit));		\
							} while (0)
					
					INPUT_CLEANSE_BITMASK(dev, REL, rel);
					INPUT_CLEANSE_BITMASK(dev, ABS, abs);
					INPUT_CLEANSE_BITMASK(dev, MSC, msc);
					INPUT_CLEANSE_BITMASK(dev, LED, led);
					INPUT_CLEANSE_BITMASK(dev, SND, snd);
					INPUT_CLEANSE_BITMASK(dev, FF, ff);
					INPUT_CLEANSE_BITMASK(dev, SW, sw);
				}
		
				/*
					个人理解;
					设置每个包最大包含的event数量
					dev->max_vals == struct input_dev下的max_vals成员  unsigned int max_vals; ==>在帧排队中的最大值  驱动上报给input_core的一次数据看作为一帧数据,在一帧数据中可以包含多个event(一帧数据以上报SYN结束)
				*/
				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内存空间 struct input_dev下的struct input_value *vals成员
				*/
				dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
				if (!dev->vals) {
					error = -ENOMEM;
					goto err_devres_free;
				}

				/*
				 * If delay and period are pre-set by the driver, then autorepeating
				 * is handled by the driver itself and we don't do it in input.c.
				 如果延时和周期由驱动器预先设定,则自动重复
				*是由驱动本身处理的,我们没有在input.c中这样做。
				 */
				if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
					dev->timer.data = (long) dev;
					dev->timer.function = input_repeat_key;
					dev->rep[REP_DELAY] = 250;
					dev->rep[REP_PERIOD] = 33;
				}
				
				/*
					个人理解:
					获取按键值的方法为:input_default_getkeycode函数
					设置按键值的方法为:input_default_setkeycode函数
				*/
				if (!dev->getkeycode)
					dev->getkeycode = input_default_getkeycode;

				if (!dev->setkeycode)
					dev->setkeycode = input_default_setkeycode;
				
				/*
					个人理解:   非常重要 ****************
					将input_dev包含的device结构注册到linux设备模型中,并可以在sysfs文件系统中表现出来
					驱动分层理念: device bus driver 
					这里就是将input_dev这个device注册到对应的input总线中的device链表中,后面会匹配bus中的另外一个driver链表,匹配成功就会调用驱动
					思路: 
						注册设备: 先将设备添加到对应bus(input总线)中的device链表中,然后去bus中的driver链表中寻找匹配的驱动,如果匹配成功那设备就驱动起来了,如果没有匹配成功的driver那就继续等待  input子系统
						注册驱动: 先将驱动添加到对应的bus(platform总线)中driver链表中,然后去bus的device链表中寻找匹配的设备,匹配成功设备就驱动起来了
					学习网站: https://blog.csdn.net/yiyeguzhou100/article/details/72637948
							  https://blog.csdn.net/leesagacious/article/details/50221227 
				*/
				error = device_add(&dev->dev);
				if (error)
					goto err_free_vals;
				
				/*
					个人理解:
					生成并返回设备的路径,调用者必须使用kfree()来释放结果
					因为它的代码中有path = kzalloc(len,gfp_mask)
				*/
				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;
				
				/*
					个人理解:
					在input_bus中维护了两条链表
						device链表: input_dev_list链表,该链表中包含了系统中所有的input_dev设备
						driver链表: input_handle_list链表,该链表中包含了系统中所有的input_handle驱动
					这里就是将要注册的input_dev对象添加到device链表中
				*/
				list_add_tail(&dev->node, &input_dev_list);
·				
·				/*
					个人理解:  ******* 非常重要
					遍历input_handler_list链表,看看有没有与我们要注册的input_dev匹配的input_handle
					input_attach_handler: 匹配input_dev和input_handle
				*/
				list_for_each_entry(handler, &input_handler_list, node)
					input_attach_handler(dev, handler);
				
				/*
					唤醒等待队列input_devices_poll_wait()
				*/
				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注册到input总线中的input_dev_list链表(device链表)中,然后遍历input总线的driver链表,寻找匹配的input_handler驱动
			
		4. input_attach_handler函数分析 input中设备和驱动的匹配过程,已经匹配成功之后干什么
		    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
			{
				const struct input_device_id *id;
				int error;
				
				/*
					个人理解:
					input_match_device函数在判断这两个东西是否匹配
				*/
				id = input_match_device(handler, dev);
				if (!id)
					return -ENODEV;
				
				/*
					个人理解:
					这是匹配成功之后要执行的代码,connect表示匹配成功就会调用input_handler下的connect方法  
					这个过程和platform平台的匹配流程非常相似  connect函数类似于platform中的probe函数
				*/
				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);

				return error;
			}
		
		5.  input_match_device函数分析  了解input_dev是如何匹配input_handler的
			static const struct input_device_id *input_match_device(struct input_handler *handler,
						struct input_dev *dev)
			{
				const struct input_device_id *id;

				for (id = handler->id_table; id->flags || id->driver_info; id++) {
					
					if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
						if (id->bustype != dev->id.bustype)
							continue;
					
					//代码分析   后面有结合例子分析
					dev->id.bustype //dev->id = struct input_id id 
														|
												struct input_id {
													__u16 bustype;  //总线类型
													__u16 vendor;
													__u16 product;
													__u16 version;
												};
						
							
					
					//先确保struct input_id中的4个成员是否相同
					if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
						if (id->vendor != dev->id.vendor)
							continue;

					if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
						if (id->product != dev->id.product)
							continue;

					if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
						if (id->version != dev->id.version)
							continue;

					if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
						continue;

					if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
						continue;

					if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
						continue;

					if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
						continue;

					if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
						continue;

					if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
						continue;

					if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
						continue;

					if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
						continue;

					if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
						continue;

					if (!handler->match || handler->match(handler, dev))
						return id;
				}

				return NULL;
			}
		
		6. 实际开发工程中 input_device层的代码通常上由platform来实现的  *******重点中的重点
		   下面以内核下 input/keyboard/gpio_keys.c这个代码为input_device层驱动
						input/input.c 为input_core层驱动
						input/evdev.c 为input_handle层驱动为例来分析input子系统
						
						input_device层 gpio_keys.c文件分析
						late_initcall(gpio_keys_init);
						==>
							static int __init gpio_keys_init(void)
							{
								return platform_driver_register(&gpio_keys_device_driver);
							}
							==> 将platform_driver驱动对象注册到平台总线中
								static struct platform_driver gpio_keys_device_driver = {
									.probe		= gpio_keys_probe,
									.remove		= gpio_keys_remove,
									.driver		= {
										.name	= "gpio-keys",
										.pm	= &gpio_keys_pm_ops,
										.of_match_table = of_match_ptr(gpio_keys_of_match),
									}
								};
								
								static const struct of_device_id gpio_keys_of_match[] = {
									{ .compatible = "gpio-keys", },
									{ },
								};
								==> 故只需要在设备树文件中定义要驱动key的compatible属性值为"gpio-keys"即可,然后将gpio_keys.c这个文件添加到内核即可
								设备树文件:
								gpio-keys {
									compatible = "gpio-keys";
									#address-cells = <1>;
									#size-cells = <0>;
									autorepeat;
									key0 {
										label = "GPIO Key Enter";
										linux,code = <KEY_ENTER>;
										/* gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; */
									};
								};
								操作Kconfig文件将gpio_keys.c文件添加到内核中
								make menuconfig
								选中该文件对应的配置 [*]
								确认是否添加成功: 查看.config文件中是否有CONFIG_KEYBOARD_GPIO=y 这一行代码
								==>	匹配成功调用probe函数
									static int gpio_keys_probe(struct platform_device *pdev)
									{
										struct device *dev = &pdev->dev;
										const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
										struct gpio_keys_drvdata *ddata;
										struct input_dev *input;						//声明一个input_dev变量
										size_t size;
										int i, error;
										int wakeup = 0;

										... ...
										input = devm_input_allocate_device(dev);		//申请一个input_dev对象的内存空间 +devm前缀说明用完后不需要我们手动去释放该内存,系统会帮忙自动是否
										if (!input) {
											dev_err(dev, "failed to allocate input device\n");
											return -ENOMEM;
										}
										... ...
										platform_set_drvdata(pdev, ddata);				//将pdev信息保存在ddate中 该函数通常在probe中执行,在remove中执行platform_get_drvdata函数
										input_set_drvdata(input, ddata);

										input->name = pdata->name ? : pdev->name;		//这里就开始初始化input_dev对象中的成员
										... ...

										input->id.bustype = BUS_HOST;					//这里就是input_dev与input_handle匹配时需要匹配的4个选项
										input->id.vendor = 0x0001;
										input->id.product = 0x0001;
										input->id.version = 0x0100;
										
										/* Enable auto repeat feature of Linux input subsystem */
										if (pdata->rep)									//设置事件类型为重复事件
											__set_bit(EV_REP, input->evbit);
										... ...
														
										error = input_register_device(input);			//这里就是将input_dev注册到input_bus中的device链表中
										if (error) {
											dev_err(dev, "Unable to register input device, error: %d\n",
												error);
											goto err_remove_group;
										}

										device_init_wakeup(&pdev->dev, wakeup);

										return 0;

									err_remove_group:
										sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
										return error;
									}
						
						input_core层input.c文件分析  其实在前面分析input_register_device函数时就是在分析input_core层
						subsys_initcall(input_init);	//加载函数
						==>
							static int __init input_init(void)
							{
								int err;

								err = class_register(&input_class);									//******* 创建设备类
									==> class类 class_create函数 == 先定义一个class类然后在用class_register函数注册它
										struct class input_class = {
											.name		= "input",
											.devnode	= input_devnode,
										};
								if (err) {
									pr_err("unable to register input_dev class\n");
									return err;
								}

							

								err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),					//******* 注册设备号,INPUT_MAJOR=13,故input设备的主设备号都是13
												 INPUT_MAX_CHAR_DEVICES, "input");
								... ...
							}
						==> 然后就是在input_device层中调用input_register_device函数时
							我将要注册的input_dev添加到device链表中,然后去driver链表中寻找匹配的input_handler对象
							list_for_each_entry(handler, &input_handler_list, node)
									input_attach_handler(dev, handler);
							==> 这时候就开始和input_handler层开始有联系了
						
						==>input_dev和input_handler开始匹配,前面已经分析过了,匹配函数为input_match_device,匹配成功之后调用执行input_handler中的connect函数, **** 这里就过度到input_handler层了
						学习网站: https://blog.csdn.net/lickylin/article/details/106449162
						input_handle层 evdev.c文件 是linux系统中input_handler层的通用文件
						module_init(evdev_init);
						==>
							static int __init evdev_init(void)
							{
								return input_register_handler(&evdev_handler);		//将input_handler对象注册到input_core层中的driver链表中 
							}
							==>
								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,
								};
								
								//匹配表
								static const struct input_device_id evdev_ids[] = {
									{ .driver_info = 1 },	/* Matches all devices */		//这样设置,就会匹配任意的input_device(即任何注册到input_bus中的input_device都会与之匹配),这就是为什么它是通用input_handle文件了
									{ },			/* Terminating zero entry */
								};
						==> 故前面注册的input_dev会与evdev.c中注册的input_handler匹配从而调用了 input_handler中的connect()
							connect函数的作用是: 类似于pribe函数,这里是在connect函数中进行字符设备注册的操作,和就行input_dev和input_handler的绑定操作
							/*
							 * Create new evdev device. Note that input core serializes calls
							 * to connect and disconnect.
							 创建新的evdev设备。注意input core序列化调用 连接和断开连接。
							 */
							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;
								
								/*
									非常关键 获取一个未被使用的evdev设备的次设备号
									input子系统创建的字符设备文件一定为: /dev/input/eventX (X=0,1,...,实际上就是次设备号的值)
									input子系统创建的设备号的主设备号都是13(input_core层中定义的),故这里只需要分配一个未被使用的次设备号即可
								*/
								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;
								}

								INIT_LIST_HEAD(&evdev->client_list);
								spin_lock_init(&evdev->client_lock);
								mutex_init(&evdev->mutex);
								init_waitqueue_head(&evdev->wait);
								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);		//这里就设置了设备文件的名称格式为 "eventX类型",具体体现就是会在/dev/input目录下创建一个eventX文件来作为该设备的字符设备文件  ***创建设备文件
								... ...
								evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);		//在这里定义了匹配成功之后,该input设备的设备号 *****  非常关键						
								... ...

								error = input_register_handle(&evdev->handle);		//注册input_handle
								if (error)
									goto err_free_evdev;
								
								/*
									熟悉的cdev操作,将设备号和fops建立联系   ***实现fops操作那应该也在input_handler实现
								*/
								cdev_init(&evdev->cdev, &evdev_fops); 
								evdev->cdev.kobj.parent = &evdev->dev.kobj;
								error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
								... ...
							}
							总结一下connect函数的操作:
							创建input_handle,并设置input_handle与input_handler、input_dev的关联,同时完成input_handle的注册;然后创建cdev,并完成字符设备的注册(设备号,cdev,fops的联系)
							
							static const struct file_operations evdev_fops = {
								.owner		= THIS_MODULE,
								.read		= evdev_read,
								... ...
							};
							
							以read为例
							static ssize_t evdev_read(struct file *file, char __user *buffer,
										  size_t count, loff_t *ppos)
							{
									... ...

									while (read + input_event_size() <= count &&
										   evdev_fetch_next_event(client, &event)) {

										if (input_event_to_user(buffer + read, &event)) //*** 这里就是通过内核调用将数据上报给应用层了
											return -EFAULT;

										read += input_event_size();
									}								
								}

								return read;
							}
		
		7.  应用层是如何获取到上报数据的?  大概分析  
			1>打开设备文件  
			fd = open(argv[1],O_RDWR);	//argv[1] == /dev/input/event1 (值如何确认? 观察在我们加载了驱动之后/dev/input下新增了那个文件,那个文件就是驱动对应的设备文件)
			==>  反正经过一系列调用最终会调用input_handler层中定义的fops下的open函数 (至于调用)
			static int evdev_open(struct inode *inode, struct file *file)
			{
				struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
				unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
				unsigned int size = sizeof(struct evdev_client) +
								bufsize * sizeof(struct input_event);
				struct evdev_client *client;
				int error;

				client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);		//每当一个应用程序打开evdev对应的字符设备文件(/dev/input/eventX),就会创建一个evdev_client类型的变量
				if (!client)
					client = vzalloc(size);
				if (!client)
					return -ENOMEM;

				client->bufsize = bufsize;								//evdev_client变量中存在一个buffer缓冲区来存储底层上报的event数据
				... ...

				file->private_data = client;							//将evdev_client和文件描述符的私有数据建立关系
				nonseekable_open(inode, file);

				return 0;
				
			}
			当input_device层有事件上报时,则将该事件广播到evdev->client_list链表上所有evdev_client的buffer中,这样每一个打开的文件描述符均可以读取到该事件(这种事件分发是广播的机制)
	
			还是看这个网站中的信息吧: https://blog.csdn.net/lickylin/article/details/106449162

		学习网站:https://blog.csdn.net/lickylin/article/details/105851233  (好,顶)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值