****在用户层的角度看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;
}