Kernel驱动-input子系统

****在用户层的角度看input子系统:
一、输入子系统的作用和框架
1、什么是输入设备????
1.1 按键
1.2 mouse
1.3 touchscreen
1.4 游戏杆
有多个输入设备需要驱动的时候,假设不考虑输入子系统
a、gt81
设备号 创建文件,硬件初始化,实现fop,阻塞
b、ft56xx
设备号、创建文件、硬件初始化、实现fop,阻塞
多个设备输入有共同点:
获取数据 (操作硬件) 上报给用户,差异化就在硬件这个部分
多个输入设备,有部分差异也有部分通用,内核就会考虑,将通用代码编写好,,
将差异化的代码留给驱动工程师

	设计成输入子系统:是的应用编程人员和驱动编程人员上面变得简单统一
		1、兼容所有的输入设备
		2、统一编程驱动方法(实现差异化一年操作)
		3、统一的应用操作接口/dev/input/event0,event1
		
	框架:驱动分成三层
					应用层
			----------------------------------------------
			input  handler层 数据处理者
				完成fop,实现 xxx_open(),xxx_read()
				将数据交给用户没数据从就input device层
				不知道具体数据,只知道把数据给用户
			-----------------------------------------------
			input 核心层  管理层 (过度)
			------------------------------------------------
			input device设备层 
					抽象出来一个对象,描述输入设备信息
					初始化输入设备硬件,获取到数据(上报)
					:知道数据具体,但是不知道数据怎么给用户
			--------------------------------------------------
			硬件层:mouse  ts keybaord joystick
	
	代码实现方式:
					应用层
			-------------------------------
			input handler 层
					3、创建文件(向用户提供/dev1...)
			-------------------------------
			input core 层(主要开发的层)
					1、申请设备号
					2、创建类
			--------------------------------
			input device 层
					4、硬件初始化,读数据
			----------------------------------

二、输入子系统的编程方式

	输入子系统的编程方式---学会最简单的输入子系统的开发方式
		前提:input 核心层代码和input handler层需要在内核中必须有:
			drivers/input/evdev.c
			drivers/input/input.c	//核心层
			make menuconfig 
				device Dricers-->
						Input device suppot-->
							*GENRIC INPUT LAYER //(通用层设置)
							<*>eVENT INTERFACE  //(handler 层 evdev.c)
	编写步骤
	1、分配一个input device对象
	2、初始化input device 对象
	3、注册input device对象
	4、初始化按键,申请中断资源
	5、在中断里面要进行上报数据:	
			void input_event(struct input_dev *dev,
				unsigned int type, unsigned int code, int value);
			参数1:当前的input device上报数据
			参数2:上报的数据类型是什么 (EV_KEY,EV_ABS)
			参数3:具体数据是什么:KEY_POWER
			参数4:值是什么(自己写进去0或者1)
			上报数据之后一定要:上报数据结束
			input_sync(); 
	 6、在上层调用API,其实放回来的就是驱动调用的input_dev数据包
		struct input_dev event;
		 main{
			open("/dev/event1",O_RDWR);
			read(fd,&event,sizeof(struct input_event));
			event.value;
			XXXX.....
		 }	
      7、初始化input_device(这个结构体是什么,代表什么)
		//表示的是一个具体的输入设备,描述设备能够产生什么数据
	struct input_dev{
		const char *name:	//在系统的sys/calls里面的给文件的name
		canst char *phys:
		const char *uniq:
		//evbit实际是一个位表,描述输入设备能够产生什么类型数据	
		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)];
		unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
		unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
		unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
		
		struct device dev;   //继承device对象

		struct list_head	h_list;  //链表
		struct list_head	node;   //节点
		
	}

不同输入设备能够产生不同的数据:
	1、按键/keyboard:产生键值,实际是一个数字
			#define key_VOLUMEDOW 114
			#define KEY_POER      116
	2、ts\gsensor:产生左边,绝对坐标,有个明确坐标系,原点(0.0)
			#define ABS_X			0x00
			#define ABS_Y			0x01
			#define ABS_Z			0x02
			#define ABS_RX			0x03
			#define ABS_RY			0x04
			#define ABS_RZ			0x05
		......
	3、mouse:产生坐标,相对坐标,坐标值是相对于之前一个点坐标
			#define REL_X			0x00
			#define REL_Y			0x01
			#define REL_Z			0x02
	如何表示不同数据类型:
			#define EV_SYN			0x00 //表示同步数据类型
			#define EV_KEY			0x01 //表示按键数据类型
			#define EV_REL			0x02 //表示相对坐标数据类型
			#define EV_ABS			0x03 //表示绝对坐标数据类型
			#define EV_MSC			0x04 //表示杂项

/**************************************************************************
type code vale (对应上报数据的形参)
|—>EV_KEY–>|KEY_POWER---->|抬起0
| |按下1
inputdev |
| | |ABS_X-------->|333 x轴
| |—>EV_ABS–>|ABS_y-------->|232 y轴
| |ABS_PRESSURE->|233 压力值
|
|------>__set_bit(EV_KET,inputdev->evbit)
你要上报数据之前,第一步是我们要将上报的数据类型的阀门打开,也就是实现__set_bit()函数
形参1、EV_KEY (按键数据)
形参2、inputdev->evbit (表示当前按键能够产生数据)

或者列二:
	__set_bit(KEY_POWER,inputdev->keybit)
形参1、power按键类型
形参2、表示能够产生按键的类型

/*****************************************************************************
在这里插入图片描述
三、输入子系统和平台总线的结合方式------驱动所有的按键
在我们的输入设备上面的,发现会有大量的event0 等等, 但是里面究竟是什么。去到
sys/class/input/event0/device : cat name
这样的话就可以知道我们对应的event* 究竟是一个什么设备的。
我们在input_dev结构体里面初始化的东西,name phys uniq id 等等这些东西,内核都会通过
文件系统(FS)上报到我们的用户空间路径的sys/class/input/event0/device

1、设置位表和上报数据的另外一种方式
所谓的位表就是一个long型的数组,这个数据对应的长度是116.。使用到的是数组的内存连 续分配一个特性。里面有着大量的按键表示位里面的每一个位都代表着一个按键。 我们要上报过去的键值。根据对应的键值来判断,将所谓的按键对应的到那种类型的去, 电源按键,home按键等等。

四、输入子系统的工作原理和代码分析-----学习内核的设计思想

下层:设备驱动层
步骤:

input_allocate_device()--> input_register_device(button_dev)-->      
  input_set_capability()


input_allocate_device() :

首先是input_allocate_device() 函数,这个函数是向系统申请一个input_Dev 的内存。
解析整一个input_allocate_device(),里面不仅仅是我们向内核申请一块内存那么简单,在里面还进行初始化设备的类型,初始化互斥锁,初始化自旋锁,初始化设备链表,初始化节点

struct input_dev *input_allocate_device(void)  {
        struct input_dev *dev;
          /*分配一个 input_dev 结构体,并初始化为 0*/
        dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
       if (dev) {
              dev->dev.type = &input_dev_type;   /*初始化设备的类型*/
             dev->dev.class = &input_class;  
             device_initialize(&dev->dev); 
             mutex_init(&dev->mutex);   // 初始话互斥锁
             spin_lock_init(&dev->event_lock);  // 初始化自旋锁
             INIT_LIST_HEAD(&dev->h_list);   //初始化链表
             INIT_LIST_HEAD(&dev->node);  //初始化节点 
             __module_get(THIS_MODULE);
      }
      return dev;  // 将微初始化的结构体指针返回来
}

在上面我们的到了结构体指针,里面要初始化一些特定的数据,需要的数据类型,input设备的类型。下面分析整一个返回来的结构体指针

  struct input_dev {	
		const char *name;      	   // 设备名
		const char *phys;		 //设备节点文件名
		const char *uniq;		 //唯一的ID
		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;
		.......
	};

input_register_device(button_dev):
上面就是内核申请的一块内存,这块内存以一个结构体指针的方式抛出来,下一步要做到步骤就是想内核注册对应的input-dev。那么下面要看看这个函数

input_register_device(button_dev)

里面的操作就是向内核里面注册一个input设备。

int input_register_device(struct input_dev *dev)  {
	__set_bit(EV_SYN, dev->evbit);
	__clear_bit(KEY_RESERVED, dev->keybit);
	input_cleanse_bitmasks(dev);
	packet_size = input_estimate_events_per_packet(dev);
	init_timer(&dev->timer);
}

简化之后再注册函数里面进行一些所谓初始化和设置对应的为。和字符设备申请函数内容差不多,但是搭载在上面的
总线还有内容稍微不太一样。

input_exent()
对应的input_exent()这个 公共函数进行上报事件,上面的所谓的各种事上报事件都是经过修饰之后的函数

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value) {
	     unsigned long flags;
      	 if (is_event_supported(type, dev->evbit, EV_MAX)) {
				spin_lock_irqsave(&dev->event_lock, flags);
				input_handle_event(dev, type, code, value);
				spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}

**
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC 0x04 其它
EV_SW 0x05 开关
EV_LED 0x11 按键/设备灯
EV_SND 0x12 声音/警报
EV_REP 0x14 重复
EV_FF 0x15 力反馈
EV_PWR 0x16 电源
EV_FF_STATUS 0x17 力反馈状态
EV_MAX 0x1f 事件类型最大个数和提供位掩码支持

**

函数上报一个按键事件的函数。下面有input子系统提供上报函

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value){
	input_event(dev, EV_KEY, code, !!value);
}

有关ABS的上报函数

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value) 
	input_event(dev, EV_ABS, code, value);
}

有关触摸屏的上报函数

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)   {
	input_event(dev, EV_REL, code, value);
}

set_bit():

里面还要指出驱动实现–初始化时间 set_bit() 告诉input子系统支持哪些事件,哪些按键。

set_bit(EV_KEY,button_dev.evbit)

当有关的上报函数将事件上报的时候。 通知一下接受者,一个报告发送完毕。里面有个函数inpit_sync(struct input_dev *dev )

static inline void input_sync(struct input_dev *dev){
	input_event(dev, EV_SYN, SYN_REPORT, 0);
}

内核里面帮我们封装一个函数 input_set_capability() ,帮我们实现以上这两步了。

void input_set_capability(struct input_dev *dev, unsigned int type, 
		unsigned int code) {
			switch (type) {
					case EV_KEY:
						__set_bit(code, dev->keybit);
						break;
				   .......
			}
		__set_bit(type, dev->evbit);
	}

其实这个函数最后还是按照上面的步骤调用,不过是将它封装起来。内核函数这点就是喜欢将多个步骤的函数都一一封装起来。 有时候同样申请一个设备但是会发现有好几种不同的函数,其实本质还是一样的思想,仅仅是封装起来。
*
*
*
*

中层:有关输入核心层分析

1、输入核心层模块注册函数input_init

① 创建设备类
②proc文件系统相关的初始化
③注册字符设备驱动主设备号是13
④注册字符设备的时候,操作结构体file_operations input_fops

模块被调用的被打开得时候,调用给的.open 函数指针。

 static int __init input_init(void)
 2 {
 3     int err;
 4 
 5     input_init_abs_bypass();
 6 
 7     err = class_register(&input_class); //创建设备类 /sys/class/input
 8     if (err) {
 9         printk(KERN_ERR "input: unable to register input_dev class\n");
10         return err;
11     }
12 
13     err = input_proc_init();  // proc文件系统相关的初始化
14     if (err)
15         goto fail1;
16 
17     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);       //   注册字符设备驱动   主设备号13   input_fops 中只实现了open函数,所以他的原理其实和misc其实是一样的
18     if (err) {
19         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
20         goto fail2;
21     }
22 
23     return 0;
24 
25  fail2:    input_proc_exit();
26  fail1:    class_unregister(&input_class);
27     return err;
28 }

分析input_open_file函数:

static int input_open_file(struct inode *inode, struct file *file){

  struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
  const struct file_operations *old_fops, *new_fops = NULL;
  int err;
  if (!handler || !(new_fops = fops_get(handler->fops)))  //(2)
        return -ENODEV; 
  if (!new_fops->open) {
          fops_put(new_fops);
          return -ENODEV;
   }
   old_fops = file->f_op;
   file->f_op = new_fops;     //(3)
   err = new_fops->open(inode, file);   //(4)
   if (err) {
           fops_put(file->f_op);
           file->f_op = fops_get(old_fops);
   }
   ps_put(old_fops);
   return err;
 }

其实里面就是初始化虚拟进程文件系统(proc_sys),当启动系统之后再proc文件系统中可以到,会有proc/bus/input/目录下的文件。两个文件devices和handlers,这两个文件就是在这里被创建的。我们cat devices 和 cat handlers

(1)input_proc_init函数
①在/proc/bus/目录下创建input目录
②在/proc/bus/input/创建 devices目录
③在/proc/bus/input/创建 handler目录

	 1 static int __init input_proc_init(void)
	 2 {
	 3     struct proc_dir_entry *entry;
	 4 
	 5     proc_bus_input_dir = proc_mkdir("bus/input", NULL);    /* 在/proc/bus/目录下创建input目录 */
	 6     if (!proc_bus_input_dir)
	 7         return -ENOMEM;
	 8 
	 9     entry = proc_create("devices", 0, proc_bus_input_dir,  /* 在/proc/bus/input/目录下创建devices文件 */
	10                 &input_devices_fileops);
	11     if (!entry)
	12         goto fail1;
	13 
	14     entry = proc_create("handlers", 0, proc_bus_input_dir, /* 在/proc/bus/input/目录下创建handlers文件 */
	15                 &input_handlers_fileops);
	16     if (!entry)
	17         goto fail2;
	18 
	19     return 0;
	20 
	21  fail2:    remove_proc_entry("devices", proc_bus_input_dir);
	22  fail1: remove_proc_entry("bus/input", NULL);
	23     return -ENOMEM;
	24 }

(2)那么在这里我们要考虑一下input所谓的上中下层究竟是怎么连接起来。依靠是由input内核系统提供的一个input_attach_handler函数:只有匹配成功之后就会调用上层handler中的connect函数进行连接绑定,主要做两件事情,调用input_math_device函数进行设备与handler的匹配、匹配成功调用handler的连接函数进行连接,

(3)核心层提供给事件驱动层的接口函数,在input输入核心层事件驱动层提供的接口函数主要有两个:
input_register_handler 事件驱动层向核心层注册handler
input_register_handle 事件驱动层向核心层注册handle

分析:input_register_handler函数可知道,里面内进行的操作,分析里面的源码操作

 ① 这个函数传进来的宏是向核心层注册的input_handler结构体
 ② 初始化handler-h_list链表
 ③ 判断传进来的handler-fops是否存在, 
 ④ 将handler通过handler->node链表挂接的所有的input_handler_list链表上
 ⑤ 遍历inpuT_dev_list 链表下挂接的所有的input_dev设备
 ⑥ 然后进行匹配
 ⑦ 更新proc文件系统 

通过分析上面的input_register_device和这里的input_register_handler函数可以知道,注册设备的时候,不一定是先注册handler才能够注册,当注册设备时会先将设备挂接到设备管理链表上,再去遍历input_handler_list链表匹配handler。同样对于handler注册的时候也会handler挂接到handler管理链表中。
*
*
直接表明,设备和handler都是分开的,都是注册在对应的链表上面,然后进行遍历链表进行所谓的匹配。仅仅当双方都同时在各自对应的链表上面,然后遍历之后进行配对。 而且一个input_dev是可以和多个handler匹配成功的,所以一般sysfs中创建多个设备文件,野可以在、Dev/目录下创建多个设备节点,并且他们的此设备号是不一样的。
*
*
(3)input_register_handle函数
这个函数的作用就是注册一个handle,也就是实现下图中的将各个handle连接起来构成一个环形的结构,字啊调用这个函数之前已经将handle中的dev和handler填充好的了。
下面就是说为的handler和input_dev 和handle这之间的关系。

根本上看来input_dev与handler是多对多的关系,从上图可以看来,一个input_dev可以对应多个handler ,一个handler也可以对应多个input_dev,因为在匹配的时候。一个input_dev会与所有的handler都进行匹配的,并不是匹配成功一次就退出。

那么handle的功能根本就是记录系统中一堆匹配成功的handler和device。换句话说我们可以通handle知道device和handler的信息。
回顾核心层所的做的工作就是:

一、创建设备类,注册字符设备
二、向设备驱动层提供注册接口
三、提供上层handler和下层device之间的匹配函数
四、向上层提供注册handler的接口

上层:输入事件驱动层源码分析

input输入子系统的输入事件驱动层(上层)其实就是各个handler构成,各个handler之间都属于平行关系,不存在所谓的相互调用。核心层都是实现以模块的情况进行处理。上层也是用到同样的原理/drivers/input/evdev.c 里面有关event,也是将上层的实现为模块的方式。

static int __init evdev_init(void){
    return input_register_handler(&evdev_handler);
 }

 static void __exit evdev_exit(void){
    input_unregister_handler(&evdev_handler);
}
 module_init(evdev_init);
 module_exit(evdev_exit);

注册的结构体原型是

	 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_connect()
① 从evdev_table数组中找到一个没有被使用的最小的数组项 最大值32
② 给evdev 申请分配内存
③ 初始化evdev_cliebt_list链表
④ 初始化自旋锁 devdev->client_lock
⑤ 初始化互斥锁 evdev->mutex
⑥ 设置input设备的名字
⑦ input设备的次设备号的偏移量
⑧ 将传进来的input_dev指针存放起来
⑨ 设置evdev->dev对象名字 并且把名字赋值给evdev->handle.name
⑩ 将传进来handler指针存放在handle->handler中
(11)把evdev作为handle的私有数据
(12)设置对应的设备号
(13)将input_dev->input-class作为evdev->device的付设备
(14)设备初始化和注册handle
(15)安装evdev 其实就是将evdev结构体指针放在evdev_table数组当中,下标就是evdev-minor
(16) 添加设备到系统/sys/devices/virtual/input/input0/event0 ,这个就是建立的设备文件

注意:/sys/devices/virtual/input/input0 这个设备是在注册input_Dev时创建的,而input0/event0 就是在handler和input_dev匹配成功之后创建的,也会在/dev目录下创建设备节点

(2)input设备注册的流程:
下层通过调用核心层的函数来向子系统注册input输入设备

input_register_device
   device_add:  /sys/devices/virtual/input/input0
   链表挂接: input_dev->node    ------->  input_dev_list
   input_attach_handler      =//  进行input_dev和handler之间的匹配
   调用handler->connect进行连接
   构建evdev结构体,加入evdev_table数组
   input_register_handle
   device_add:  /sys/devices/virtual/input/input0/event0

(3)handler注册流程

    input_register_handler
    input_table[handler->minor >> 5] = handler
    链表挂接:  handler->node  ----->   input_handler_list
    input_attach_handler
    handler->connect                  // 调用handler的connect函数进行连接

(4)事件如何传递到应用层

input子系统下层通过调用input_event函数项核心层上报数据
input_event
input_handle_event
input_pass_event
handler->event() // 最终会调用到handler 中的event函数
evdev_pass_event
client->buffer[client->head++] = *event; // 会将input输入事件数据存放在evdev_client结构体中的缓冲去中

当我们的应用层通过open打开event0这个设备节点时最终会调用到input_init函数中注册的字符设备input时注册的file_operations->open() 函数

input_open_file
handler = input_table[iminor(inode) >> 5]
handler->fops->open()
evdev = evdev_table[i];
evdev_open_device
input_open_device
input_dev->open() // 最终就是执行input设备中的open函数
file->private_data = evdev_client;

所以当我们在应用层调用read函数时,最终会调用到handler->fops->read函数

evdev_read
evdev_fetch_next_event
event = client->buffer[client->tail++] // 将evdev_client->buffer中的数据取走
input_event_to_user
copy_to_user // 拷贝到用户空间
/
*************************************************************************************

问题一:下层向核心层进行注册,上层也向核心层进行注册,那么是什么将他们匹配起来,依靠?

答:在设备层(下层)的注册设备函数input_register_device()里面的有一个表示handler与input_dev设备进行匹配的函数input_attach_handler(dev, handler); 在input_attach_handler函数里面干了两件事一时匹配。二是进行连接、
一、依次遍历handler->id_table所指向的input_device_id数组中的各个元素,然后依次进行匹 配,匹配成功之后就会返回来他的ID地址

二、一旦上面获取到对应设备的ID则调用connevt函数进行连接

问题二:
proc目录下的文件什么时候创建的?

在核心层的模块input_init(void),里面,创建了虚拟进程文件,包括在proc/bus目录下的
input目录,还在里面创建了两个devices文件与handler文件。

sysfs什么时候创建的?设备类和字符设备什么时候注册?、

在核心层的模块input_init(void)里面,函数的入口处就进行创建设备类和注册字符设备。
字符设备文件什么时候创建的?
在handler层的evdev_connect这个函数里面。 也就是所谓的连接函数里面,匹配ID上之后就开始进行连接,连接成功之后添加设备到系统。

用户层查看源码由上往下走的思想结构

应用层使用设备的第一件事就是(open("/dev/event0")),首先操作的就是这个设备文件。在文件子系统的目录下看看里面的参数:
a、查看一下所谓的event0设备名是什么:(path:/sys/class/input/event0/device)

     cat  name  
      #:Power Button      

属性:电源按键
b、查看一下event0设备属性:(path:/dev/input)

ls event0  -l
#:crw-rw---- 1 root input 13, 64 Jun 28 18:32 event0

属性:主设备号为13 此设备为64

一旦我们执行C函数去打开event0设备的时候,那么就会调用到input子系统驱动的函数模块里面的open函数。那么具体的调用情况和代码分析都是在input.c开始:(input.c 是以模块的情况出现)(path : /linux-3.4/drivers/input --input.c )

/输入初始化函数/

 static int  __init input_init(void) {
      ....
     class_register(&input_class);   //注册一个类
     input_proc_init();		     // 初始化proc
     register_chrdev(INPUT_MAJOR, "input", &input_fops);  //注册一个字符设备
 ....
 }

可以看到代码里面首先进行的操作就是注册一个input_class类,然后初始化proc系统。之后再去注册一个字符设备,这个宏(INPUT_MAJOR = 13),接口函数input_fops。

A、class_register类的注册就不在重复讲,在字符设备里面都提及到,作为常用的函数。

B、分析input_proc_init()

 static int __init input_proc_init(void) {
 proc_mkdir("bus/input", NULL);                    
 proc_create("devices", 0, proc_bus_input_dir,&input_devices_fileops);
 proc_create("handlers", 0, proc_bus_input_dir,  &input_handlers_fileops);
   ...
 }

这个函数里面就做了三件事情
a、创建/proc/bus/input目录
b、在input目录下创建devices文件
c、在input目录下创建handlers文件

C、接口函数input_fops

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
     .open = input_open_file,
    .llseek = noop_llseek,
 };

当在用户层直接open(/dev/input/event0)就会间接打开的就是 input_open_file函数。在这个接口函数里面就是实现了open功能。

								static int input_open_file(struct inode *inode, struct file *file)   {
								 struct input_handler *handler;
								 const struct file_operations *old_fops, *new_fops = NULL;
								 int err; 
								 err = mutex_lock_interruptible(&input_mutex);	//加上互斥锁
								 if (err)
								 return err;
								
								 /* No load-on-demand here? */
								   handler = input_table[iminor(inode) >> 5];   //①
								   if (handler)
								       new_fops = fops_get(handler->fops);	//②
								
								 mutex_unlock(&input_mutex);   	                    //释放互斥锁
								/*
								     * That's _really_ odd. Usually NULL ->open means "nothing special",
								     * not "no device". Oh, well...
								 */
								  if (!new_fops || !new_fops->open) {          //③
								       fops_put(new_fops);
								       err = -ENODEV;
								       goto out;
								  }
							
								    old_fops = file->f_op;       //④
								    file->f_op = new_fops;       //④
								
								   err = new_fops->open(inode, file);      //⑤
								   if (err) {
								    	 fops_put(file->f_op);
								    	 file->f_op = fops_get(old_fops);
								    }
								   fops_put(old_fops);
								
								out: 
								      return err;
								 }

这个代码就是找到handler层(事件处理层)的fops,就是进行fops的接口转换,指向对应设备的事件处理接口。
① handler = input_table[iminor(inode) >> 5],input_table是一个全局input_handler类型的数组, iminor(inode),就是取得对应的次设备号,并且进行右移5位索引input_table表中对应的位置,handler代表32个设备结点,也就是说一个handler最多处理32个设备。这里也符合了input子系统里面的定义,input_devices次设备号都是64开始。因为这个表格中填写的就是时间处理的指针。判断指着指针是否成立。
那么获取回来的结构体究竟是什么,以evdev为例:

				static struct input_handler evdev_handler = {  
				       .event =   evdev_event,                          //事件处理  
				       .connect =      evdev_connect,                  //设备连接  
				       .disconnect =  evdev_disconnect,                //注销连接  
				       .fops =           &evdev_fops,                 //驱动功能接口  
				       .minor =  EVDEV_MINOR_BASE,                    //evdev的值为64  
				       .name =          "evdev",                      //设备名称  
				      .id_table =      evdev_ids,                      //用于匹配设备驱动的数组  
				 };

同时在handler层做成一个模块,当模块注册的时候就会evdev设备驱动注册

				   static int __init evdev_init(void)  {  
				       return input_register_handler(&evdev_handler);         
				 }  

那么input_register_handler这个函数获取到input_handler结构体之后的下步动作,继续跟踪下去才能观览全局信息。

				int input_register_handler(struct input_handler *handler)   { 
				    input_table[handler->minor>> 5] = handler;  
				    ……  
				 }  

那么可以得到所谓的input_table[handler->minor>>5] = input_table[2] = handler.可以知道的是
在input的open函数执行之前的话,input_table中的字段已经被填充了。那么可以得到的情况就是一下这样了。

				 input_table[handler->minor>> 5] = &evdev_handler;  

可以提前去到⑤看看,这个回调函数。便知道new_fops->open(inode, file);将④那一步的指针转换 过来就会发现他其实就是下面的情况。

				evdev_handler.evdev_fops.open(inode, file);  

② new_fops = fops_get(handler->fops) 通过上面的索引然后获得到handler结构体,然后通过fops_get()函数获取到handler的fops函数接口。

③ if (!new_fops || !new_fops->open),其实就是判断一下new_fops指针是否为NULL和new_fops指针指向fops结构体里面的open对象是否存在。一旦new_fops指针为空或者open对象不存在的话将执行判断条件成立之后的内容。

④ old_fops = file->f_op; file->f_op = new_fops; 其实这两步操作就是将之前的指针保存起来进行一个备份,然后将handler->fops指针给赋值到file->f_op这个里面。

⑤ new_fops->open(inode, file); 执行handler层(事件处理层)的fops的open函数。

经过上面可以知道当我们open("/dev/input/event0")的时候打开驱动里面的open函数接口只是一个接口,最终会转到evdev_handler->fops->open才是实现。

			static const struct file_operations evdev_fops = {
								.owner		= THIS_MODULE,
								.read		= evdev_read,
								.write		= evdev_write,
								.poll		= evdev_poll,
								.open		= evdev_open,
								.release	= evdev_release,
								.unlocked_ioctl	= evdev_ioctl,
							#ifdef CONFIG_COMPAT
								.compat_ioctl	= evdev_ioctl_compat,
							#endif
								.fasync		= evdev_fasync,
								.flush		= evdev_flush,
								.llseek		= no_llseek,
							};

里面男的open函数都会转到 static int evdev_open(struct inode *inode, struct file *file)函数里面,然后还对其两个形参进行赋值。

		    	static int evdev_open(struct inode *inode, struct file *file)   {
						struct evdev *evdev;
						struct evdev_client *client;
						
						//这里是相对evdev设备来说的   evdev类型的设备的次设备号开始是0
					    int i = iminor(inode) - EVDEV_MINOR_BASE;
						unsigned int bufsize;
						int error;
						
		                //次设备大于32就返回最大input_dev个数,换句话说就是input_Dev最多有32个
						if (i >= EVDEV_MINORS)
							return -ENODEV;
						error = mutex_lock_interruptible(&evdev_table_mutex); //上锁
						if (error)
							return error;

					//通过设备号0定位到要处理的是哪一个输入设备,evdev_table里面的成员内容在输入设备注册时通过connect填充了
					evdev = evdev_table[i];
					if (evdev)
						get_device(&evdev->dev);
					mutex_unlock(&evdev_table_mutex);  //解锁
					if (!evdev)
						return -ENODEV;
	
			/* 这个函数接口是用于计算每个数据包的大小。好准备好大小合适的数据包buffer.
			   如果大于数据包*8大于64 那么这个bufsize就是64  */
				bufsize = evdev_compute_buffer_size(evdev->handle.dev);
				
			/* 分配对应的数据包内存大小 就是所谓的client指针了  client指针作用大了*/
				client = kzalloc(sizeof(struct evdev_client) +
							bufsize * sizeof(struct input_event),
						 GFP_KERNEL);
				if (!client) {
					error = -ENOMEM;
					goto err_put_evdev;
				}
				
		/*填充一些数据 */
			client->clkid = CLOCK_MONOTONIC;
			client->bufsize = bufsize;
			spin_lock_init(&client->buffer_lock);   //初始化自旋锁
			snprintf(client->name, sizeof(client->name), "%s-%d",
					dev_name(&evdev->dev), task_tgid_vnr(current));
					
		/*下面的两行代码就是一个相互保存联系方式操作, 就是我保存你的指针   你也保存我的指针  */
			client->evdev = evdev;
			evdev_attach_client(evdev, client);
			
		/* 其实这个函数里面做了 判断evdev->exist 是否存在  然后再去判断open是否是第一个打开,则会执行 input_open_device */
			error = evdev_open_device(evdev);
			if (error)
				goto err_free_client;
		/* 将ffile私有指针指向client*/
			file->private_data = client;
			return 0;
		}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值