• 10_6 input输入子系统,流程解析


    简单分层

    在这里插入图片描述

    应用层
    内核层
    ---------------------------
    input handler 数据处理层 driver/input/evdev.c
    	1.和用户空间交互,实现fops
    	2.不知道数据怎么得到的,但是可以把数据上传给用户
    
    ---------------------------
    input core层
    	1.维护上面和下面的两个链表
    	2.为上下两层提供接口
    	
    ----------------------------
    input device层---driver/input/input.c
    	1.初始化硬件,获取硬件数据
    	2.知道数据是什么样,不知道如何把数据给用户
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    具体

    第一步

    注册顺序最开始应该是 input_coer层,锁定函数 input/input.c
    核心层,得创建链表把,图中的两个链表,方便进行匹配
    class_register() //注册这个输入子系统类,同时这个类的主设备号都分配为13
    同时这里两个链表
    第一个是 input_handler_list 存放结构体 input_handler 可以理解是对这个输入dev的操作实例或方案
    第二个是 input_dev_list 这么多输入设备,需要放进来和第一个链表进行匹配

    1.input_coer应该是第一个核心层,得创建链表把,看看input_.c文件
    同时input.c中还注册了主设备号为13的类和fops
    	input_init(void)
    		class_register(&input_class);
    		err = input_proc_init();//感觉像bus总线的新玩法,注册bus总线上的input子系统
    			proc_bus_input_dir = proc_mkdir("bus/input", NULL);
    				entry = proc_create("devices", 0, proc_bus_input_dir,&input_devices_fileops);
    				//应该是bus总线里面的注册device文件夹
    				static const struct file_operations input_devices_fileops = { //对这个文件夹里面的文件增加fops
    					.owner		= THIS_MODULE,
    					.open		= input_proc_devices_open,
    					.poll		= input_proc_devices_poll,
    					.read		= seq_read,
    					.llseek		= seq_lseek,
    					.release	= seq_release,
    				};
    				entry = proc_create("handlers", 0, proc_bus_input_dir,&input_handlers_fileops);
    				//应该是bus总线里面的注册handlers文件夹
    		err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");//这里的主设备号是13
    static LIST_HEAD(input_dev_list); //全局static 初始化链表
    static LIST_HEAD(input_handler_list);//全局static 初始化链表
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    第二步

    input handler 数据处理层
    input/evdev.c

    为了构建input_handler 先看看handler结构体里面有些什么
    里面有主次设备号,还有fops操作参数,看起来就是能创建设备节点的 这里的主设备号 同时次设备号还是64
    64那就是/dev/input/event 开始的次设备号

    //如果看struct input_handler 有下面这些成员 和/dev/input/event 13 64 里面次设备一致
    struct input_handler(
    	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    	file_operation *fops = evdev_ops
    	int minor; =EVDEV_MINOR_BASE 64
    	.connect	= evdev_connect,
    	.event		= evdev_event,
    	}
    上面构造号后 就注册到 input_hadle_list 链表中
    input_handler层中 叫数据处理者  注册进core层的链表 就是看看哪些数据能被处理
    
    假如上面的handler和下面的input_dev匹配成功 就直接调用 handler中的connect()方法
    connect()方法会做以下事情,1 创建设备节点 如/dev/event0 主次设备编号13 64	
    2 创建创建input_dev对象  
    	1.input_dev里面有event clinet(描述的缓冲区对象)  这个缓冲区是个队列 每个队列都是struct input_dev结构体
    	2.input_dev里面有handle 对象 里面放了handler指针和dev指针  我也画了图了  
    		所以说 evdev对象就能有handle 就能找到input_dev* 和event_hadle*
    	connect完就等下一层上报数据了
    好开始读代码 一步一步来
    static int __init evdev_init(void) //驱动程序的函数,自动注册
    	return input_register_handler(&evdev_handler);//注册了一个 evdev_handler结构体
    
    static struct input_handler evdev_handler = {
    	.event		= evdev_event,
    	.events		= evdev_events,
    	.connect	= evdev_connect,
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    第三步

    input_coer层
    上面第二步调用了input_register_handler()函数
    这个函数其实在 input_coer层 为了把上面的 evdev_handler 注册进链表

    struct input_dev *dev;
    input_register_handler(struct input_handler *handler) //就是我们的handler
    	list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler);//遍历链表,就是把core层两个链表进行匹配
    		id = input_match_device(handler, dev);//根据id进行匹配
    		error = handler->connect(handler, dev, id); //这里调用了 handler的connect函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    第四步

    input handler 数据处理层
    input/evdev.c
    匹配成功后,注册进入链表的 evdev_handler结构体的.connect函数被调用
    好的又回去 input handler 层了
    重点看图中 这里调用了connect函数后
    1.生成了 对象 evdev
    2.创建设备节点 /dev/input/event0
    先说第一点生成了 对象 evdev

    生成了 对象 evdev
    这个evdev对象 里面会有两个对象 evdedv_client 和 input_handle 注意这个地方是handle

    input_handler evdev_handler.connect	= evdev_connect,
    evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
    	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//初始化evdev结构体
    	INIT_LIST_HEAD(&evdev->client_list); //初始化里面的client_list ,也就是后面说的buf
    	init_waitqueue_head(&evdev->wait); //初始化里面的等待队列
    	//下面是初始化evdev的handle 也就是用 handle 连接了handler层和input device层
    	evdev->handle.dev = input_get_device(dev);
    	evdev->handle.name = dev_name(&evdev->dev);
    	evdev->handle.handler = handler; //handle的作用是能指向handler
    	evdev->handle.private = evdev; //handle的作用是能指向evdev
    	//注册这个handle
    	input_register_handle(&evdev->handle);
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    第五步

    input handler 数据处理层
    input/evdev.c
    上面的connect的第二点还没说完
    2.创建设备节点 /dev/input/event0

    input_handler evdev_handler.connect	= evdev_connect,
    evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
    	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);//查找次设备号看哪个能用
    	//注意这里的dev是device  就是字符设备哪个device
    	//创建设备节点,之前我们都是用device_create(),其实就是做了下面的事情
    	dev_set_name(&evdev->dev, "event%d", dev_no);
    	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);//这里主设备号13 次设备号从65开始
    	evdev->dev.class = &input_class;
    	evdev->dev.parent = &dev->dev;
    	evdev->dev.release = evdev_free;
    	device_initialize(&evdev->dev);
    	device_add(&evdev->dev)
    	cdev_init(&evdev->cdev, &evdev_fops);  //cdev的fops在这里
    	cdev_device_add(&evdev->cdev, &evdev->dev);
    note:以前用device_create()创建设备节点
    device *device_create(struct class *class, struct device *parent,
         dev_t devt, void *drvdata, const char *fmt, ...)
    	//这个函数要的参数上面竟然都有
    	device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
    	device_initialize(dev);
    	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    	dev->devt = devt;
    	dev->class = class;
    	dev->parent = parent;
    	dev->groups = groups;
    	dev->release = device_create_release;
    	device_add(dev);
    	//所以知道了 上面就是在创建设备节点		
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    做完那不就/dev/input/event0 就出来了

    第六步

    device层
    注册自己写的函数

    module_init(simple_btn_input_init);
    static int __init simple_btn_input_init(void)
    	//a, 分配一个input device对象
    	btn_input = input_allocate_device();
    	//b, 初始化input device对象
    	//该设备能够产生哪种数据类型---EV_KEY表示产生按键数据
    	btn_input->evbit[0] |= BIT_MASK(EV_KEY);
    	//能够产生哪个按键---比如能够产生下键 KEY_DOWN, KEY_ESC
    	// btn_input->keybit[108/32] |= 1<<(108%32);
    	btn_input->keybit[BIT_WORD(KEY_DOWN)] |= BIT_MASK(KEY_DOWN);
    	//c, 注册input device对象
    	ret = input_register_device(btn_input);
    		//这个函数里面最后也是调用了
    		handler->connect(handler, dev, id);//匹配成功就是handle的connect方法,也就是 evdev_connect()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    note: 可能这里要问了,有没有和palntfrom一样的匹配规则呢 啥设备树匹配 name匹配的
    我们看到handler层的注册的结构体 input_handler evdev_handler
    evdev_handler.id_table =evdev_ids
    static const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 }, /* Matches all devices / //这里的意思是匹配所有设备,来了就匹配,我不要规则
    { }, /
    Terminating zero entry */
    };
    那为啥还有个idtable 拿来匹配呢
    是因为我们用的是公共驱动,所有都匹配 但是有其他的handler驱动,需要用支持哪些输入事件和键值对 来看是否能匹配这个设备了
    举个例子在input_hadler这一层 我们看的是evdev.c这个万能驱动 起始还有mousedevhandler mousedev.c鼠标handler 和joydey_handle游戏杆的handler
    所以鼠标设备会和 evdev.c匹配 也会和鼠标handler匹配 所以鼠标插入的时候 有个/dev/input/event0 和 /dev/input/mouse0
    起始两个是同一个设备 所以用哪个都可以

    第7步

    应用程序调用open()
    到vfsopen 根据设备号找到cdev
    到驱动的open函数
    我们之前是在core层 申请的设备号 所以找到了 input.c的代码 这个地方也要回顾为啥是到这里!!!
    有可能推理错了,应该是open直接调用evdev的open,需要回去看cdev那节
    因为这里又register_chrdev_region()
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
    INPUT_MAX_CHAR_DEVICES, “input”);
    在input_open函数中 找到input_hadle 那么也能找到input_hadler层的fops
    register_chrdev(Input_major,“input”,&input_fops)
    input_fops.open()
    new_fops = fops_get(hadler->fops)
    //把文件节点的fop全改成了 handler层的fop了
    file_fop = new_file

    第八步

    input handler 数据处理层
    input/evdev.c
    上一步找到了 input handler的open()

    //当时的fop是这样注册的
     evdev_connect() //造connect注册了fop
    	cdev_init(&evdev->cdev, &evdev_fops);
    		evdev_fops.open = evdev_open() //open函数这里 初始化client 
    			struct evdev_client *client;
    			client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
    			client->bufsize = bufsize;
    			client->evdev = evdev;
    			evdev_attach_client(evdev, client);
    			//把文件节点的fop全改成了  handler层的fop了
    			file_fop = new_file //使用file节点私有空间传输数据,那read,write都能拿到了client
    			evdev_open_device() //查看第三层 input_dev xx层有没有open函数,有的话继续调用  但这里没有
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    第九步

    app开始read
    vfs_erad
    到evdev.c 开始进行evdev_read 也就是调用到evdev_read()
    从之前的client中 拿取最新的上报数据 返回给用户层

    evdev_read()
    	evdev_client *client = file->private_data;//从fd文件的私有属性拿到client
    	evdev *evdev = client->evdev; //从clinet拿到evdev
    	struct input_event event;//构造一个要返换给用户空间的结构体 input_event
    	if (client->packet_head == client->tail &&(file->f_flags & O_NONBLOCK))
    			//如果当前用非阻塞的方式,还没有数据,那应该马上返回
    		return -EAGAIN;
    	if (!(file->f_flags & O_NONBLOCK)) //正常的阻塞形式
    			error = wait_event_interruptible(evdev->wait,client->packet_head != client->tail ||
    					!evdev->exist || client->revoked);//这个进程丢进等待队列把,等中断唤醒继续往下走
    	//下面是有中断了,阻塞解除,进程继续往下走
    	//下面的进行用户空间数据发送
    	while()
    		evdev_fetch_next_event(client, &event)
    			*event = client->buffer[client->tail++];//这里构造input_event,也就是从client里面拿一个buffer
    		input_event_to_user(buffer + read, &event)
    			copy_to_user(buffer, event, sizeof(struct input_event)//拿了buffer后给到用户空间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    第十步

    那么中断是谁发起的
    就到了我们写的程序 input_device 层

    //按下
    input_event(btn_input, EV_KEY, KEY_DOWN,  1);
    input_sync(btn_input);  //我们的中断函数执行上报数据
    
    INPUT.C  //跑到中间层进行数据封装
    	input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
    		input_pass_values(dev, dev->vals, dev->num_vals);
    				struct input_handle *handle; 
    				struct input_value *v;
    				handle = rcu_dereference(dev->grab);//从dev中拿到handle
    				list_for_each_entry_rcu(handle, &dev->h_list, d_node)//这个也是想办法拿到handle
    				handle_event(handle,type,code,value)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    第十一步

    input handler 数据处理层
    input/evdev.c
    第11步,就把中断传输来的 evdev_event数据放入了 event_client队列里面
    就等着 app被唤醒后来拿数据就行了

    调用到 input_handler evdev_handler->event = evdev_event
    	struct evdev *evdev = handle->private; //通过private找到evdev
    	struct evdev_client *client;
    	list_for_each_entry_rcu(client, &evdev->client_list, node)//也找到clinet
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    两台同一局域网下的电脑实现共享文件夹
    一种优雅的Git分支实践
    如何解决响应结果中文乱码问题
    redis学习
    BDD - SpecFlow & SpecRun Web UI 多浏览器测试
    婚恋交友系统源码-交友APP小程序H5开发-源码交付,支持二开-实名制交友更放心!
    反激变压器计算方法_笔记
    2022年《微信小程序从基础到uni-app项目实战》
    springboot AOP记录操作日志(包含遇到的问题)
    【从零开始学微服务】01.微服务的过去与现在
  • 原文地址:https://blog.csdn.net/weixin_43898067/article/details/134488686