Kernel层又分为三层,他们分别是事件处理层、输入核心层和设备驱动层,
事件处理层主要负责和上层进行交互、
输入核心层实现承上启下的作用给事件处理层和设备驱动层提供公共的接口。
设备驱动层负责和底层输入设备打交道,获取来自输入设备的信息。
Func |
remark |
Tpd_down(A) |
|
input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, id); |
unsigned int code, int value |
input_report_key(tpd->dev, BTN_TOUCH, 1); |
BIN_TOUCH为1表示按下 |
input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 1); |
|
input_report_abs(tpd->dev, ABS_MT_POSITION_X, x); |
|
input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y); |
|
input_mt_sync(tpd->dev); |
多点触控的A协议所特有 |
input_sync(tpd->dev); |
|
|
|
Tpd_up(A) |
|
input_report_key(tpd->dev, BTN_TOUCH, 0); |
unsigned int code, int value |
input_mt_sync(tpd->dev); |
多点触控的A协议所特有 |
input_sync(tpd->dev); |
|
|
|
Report_key |
|
input_report_key(tpd->dev, KEY_F13, 1); |
|
input_sync(tpd->dev); |
|
input_report_key(tpd->dev, KEY_F13, 0); |
|
input_sync(tpd->dev); |
|
input_allocate_device是输入核心层提供给设备驱动层的接口函数,在设备驱动层首先就会调用此函数为input_dev分配内存空间,同时input_core也会给他初始化一些input_dev的基本特征。
函数定义如下:struct input_dev *input_allocate_device(void)
a) 设备原子量计数
// 定义原子量并初始化为0
static atomic_t input_no = ATOMIC_INIT(0);// 实际上是一个原子量结构体
b) 分配input_dev空间
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
c) 初始化\使他具有input_dev的基本特征
// 分配成功之后进行初始化,使他具有所有的input设备的共性特征
if (dev) {
dev->dev.type = &input_dev_type;// 设备类别
dev->dev.class = &input_class;//从属的类,所有的输入设备都属于是input_class这一个类别
device_initialize(&dev->dev);// 设备初始化
mutex_init(&dev->mutex);// 用于read和write函数的连续访问互锁
spin_lock_init(&dev->event_lock);// 新增的事件上锁
init_timer(&dev->timer); //设置当有连击时的延时定时器
INIT_LIST_HEAD(&dev->h_list);//handle链表
INIT_LIST_HEAD(&dev->node);//input_dev链表
// atomic_inc_return该函数对原子类型的变量v原子地增加1并且返回指向v的指针。
dev_set_name(&dev->dev, "input%lu",
(unsigned long)atomic_inc_return(&input_no) - 1);// 对设备的名称进行操作
printk(" enter input_allocate_devic and input%lu", (unsigned long)atomic_inc_return(&input_no));
// 在kernel4.4里面,他的初始化是-1,且每次的值都没有减去1,实际上就是依次的设备号,
从0开始一直往后。
想要输出这个值,需要这样输出。
printk(" enter input_allocate_device and input_no is input%d\n", atomic_read(&input_no));不可再在上面再做原子操作。
__module_get(THIS_MODULE);// 获取linux内核模块的具体信息
}
Ø 设备类别
dev->dev.type = &input_dev_type;// 设备类别
Ø 设备类别
dev->dev.class = &input_class;//从属的类,所有的输入设备都属于是input_class这一个类别
Ø 设备初始化
device_initialize(&dev->dev);// 设备初始化
Ø RW连续访问锁
mutex_init(&dev->mutex);// 用于read和write函数的连续访问互锁
Ø 新增事件锁
spin_lock_init(&dev->event_lock);// 新增的事件上锁
Ø 连续点击延时计时器
init_timer(&dev->timer); //设置当有连击时的延时定时器
Ø 初始化handle链表
INIT_LIST_HEAD(&dev->h_list);//handle链表
Ø 初始化input_dev链表
INIT_LIST_HEAD(&dev->node);//input_dev链表
Ø 设备名称
// atomic_inc_return该函数对原子类型的变量v原子地增加1并且返回指向v的指针。
dev_set_name(&dev->dev, "input%lu",
(unsigned long)atomic_inc_return(&input_no) - 1);// 对设备的名称进行操作
Ø 获取内核模块信息
__module_get(THIS_MODULE);// 获取linux内核模块的具体信息
d) 返回值
返回值是返回的input_dev的指针return dev;
input_register_device这个函数也是input_core提供给事件处理层的函数,在为input_dev分配完空间并注册了一系列的事件和事件码之后,就会执行这个函数将input_dev注册进input_core。
实际上基本上每个设备都是在分配空间之后再执行注册函数,具体可以看到里面的log是配套存在的。
1. 添加input_devres指针
Input_devres是和input_dev相关的一个结构体,这里我将他视为指向input_dev的二级指针
struct input_devres *devres = NULL;// input_devres成员是一个input_dev的结构体指针,也就是说他实际上是一个二级指针
/*
struct input_devres {
struct input_dev *input;
};
*/
2. 添加input_handler指针
既然需要在这个函数里面将input_dev和input_handler匹配成input_handle那就一定需要初始化一个这样的指针才行啊!
struct input_handler *handler;
3. 设置属性devres_managed
// 这个属性值一般设置的都是0
if (dev->devres_managed) {// 属性的devres_managed定义的是一个bool类型的变量,但是这个似乎在触摸屏里面不怎么被使用。
// devres_alloc先分配一个devres,然后返回的指针ptr是data指针,然后把master指针传给data,这样spi的资源就是spi_master,虽然这个没有看懂
// devm_input_device_unregister为他本身的release函数
devres = devres_alloc(devm_input_device_unregister,
sizeof(struct input_devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->input = dev;
}
4. 设置同步事件
EV_SYN/SYN_REPORT
/* Every input device generates EV_SYN/SYN_REPORT events. */
// 系统认为所有的input设备都产生EV_SYN/SYN_REPORT类事件
__set_bit(EV_SYN, dev->evbit);
5. 清除保留值
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
6. 清除位掩码
/* Make sure that bitmasks not mentioned in dev->evbit are clean. *///确保把在初始化时注册的所有事件类型清零
input_cleanse_bitmasks(dev);
7. 初始化事件平均数
// hint_events_per_packet在一个数据包里面,由input设备产生事件的平均数
packet_size = input_estimate_events_per_packet(dev);//其中input_estimate_events_per_packet函数获取的是一个设备中包含的输入事件的个数
if (dev->hint_events_per_packet < packet_size)// 尽量初始化时设置得大一点
dev->hint_events_per_packet = packet_size;
hint_events_per_packet这个一般的初始化是0,所以一般都是会执行到这个if条件这里的
packet_size是对于某个具体设备而言具有具体的大小值,log输出的平均的输入事件的个数如下所示。
8. 初始化事件最大数
最大数为平均数+2
dev->max_vals = dev->hint_events_per_packet + 2;
9. 为输入设备的事件分配空间
Dev->vals实际上是一个input_value的结构体数组。这个后面再input_event的时候会被使用到。
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);// 申请空间并初始化为0
if (!dev->vals) {// dev->vals实际上是input_value的结构体
error = -ENOMEM;
goto err_devres_free; // 直接跑到前面那个去了,那个默认是会执行吗?
// 一般都是不会到这里来的。
}
10. 定时器实现重复按键
一般情况下,这个定时器都会被初始化。
// 如果rep中的REP_DELAY和REP_PERIOD对应的字段没设置,系统就会给input设备设定一个内核默认的重复按键事件的处理过程,具体是用定时器实现的
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;//重复扫描的时间要小于33ms,input.c的input_repeat_key会隔rep[REP_PERIOD]调用一次
}
11. 初始化getkeycode
// 如果没有定义这个可选的检测映射键的值话,就按系统默认了,下面的setkeycode类似
if (!dev->getkeycode)// getkeycode表示检测映射键值
dev->getkeycode = input_default_getkeycode;
12. 初始化setkeycode
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
13. 添加Device到内核
// 将设备注册到内核
error = device_add(&dev->dev);
if (error)
goto err_free_vals;
14. 获取设备路劲
// 调用者必需使用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);
获取设备的路劲是对应的是之前分配空间时候的原子量计数
或者直接通过这个来看。
15. 上锁
// 获得内核互斥锁,防止竞争嘛
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
16. 添加到输入设备链表
// 把input设备添加到input_dev_list链表
list_add_tail(&dev->node, &input_dev_list);
其中dev->node实际上是一个input_dev的list_head,而input_dev_list是定义的一个全局变量
17. 遍历链表handler链表,attach, dev和handler
这个循环可能执行几次,输出的结果有看到输出2次或者4次的
//遍历input_handler_list链表,传入的handler循环遍历handler_list中的node子项,list_for_each_entry是内核定义的一个宏,input_handler有一个子项叫做node
list_for_each_entry(handler, &input_handler_list, node)// 是定义的一个static的handler列表
input_attach_handler(dev, handler);// 匹配handler和dev
实际上看到的是除了ACCDET这个设备执行了两次之外,其余的设备都执行了4次。
但是实际匹配的话,他就只会匹配成功两个handler一个evdev,一个是gpufreq_ib
且他们声称设备的路劲是在/devices/virtual/input/input10,且后面的数字是随机的。
a) input_attach_handler
const struct input_device_id *id;
id = input_match_device(handler, dev);// 具体的匹配函数,匹配成功之后,返回input_device_id的值
if (!id)
return -ENODEV;
匹配主要分为两个类别的匹配,一个是供应商信息等等,另外一个就是位图。
这些信息的匹配都是在下面这个循环中进行。
const struct input_device_id *id;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
// 正确返回input_device_id的指针
}
return NULL;// 错误返回NULL
其中这里匹配的两个handler的id_table均为
/*
为什么evdev、gpufreq_ib这个handler基本上和所有的输入设备匹配,关键在于他其中的这个设置
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, Matches all devices
{ }, Terminating zero entry
};
*/
1) 供应商信息匹配
// 下面这几句就是匹配一下什么总线类型了、生产厂商了,如果你看过触摸屏驱动,你会发现在写驱动时,的确定义这些参数,但是我感觉我好像没有定义这些东西耶!
// 实际上如果是可以匹配所有的输入设备的话,那这几个选项也没有什么意义啊!
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
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;
2) 位图信息匹配
位图信息主要在设备驱动设置的是这三个选项evbit、keybit、absbit
set_bit(EV_ABS, tpd->dev->evbit);
set_bit(EV_KEY, tpd->dev->evbit);
set_bit(ABS_X, tpd->dev->absbit);
set_bit(ABS_Y, tpd->dev->absbit);
set_bit(ABS_PRESSURE, tpd->dev->absbit);
#if !defined(CONFIG_MTK_S3320) && !defined(CONFIG_MTK_S3320_47)\
&& !defined(CONFIG_MTK_S3320_50) && !defined(CONFIG_MTK_MIT200) \
&& !defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) && !defined(CONFIG_MTK_S7020) \
&& !defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50)
set_bit(BTN_TOUCH, tpd->dev->keybit);
#endif /* CONFIG_MTK_S3320 */
具体到这里的匹配信息是这样的。
// 匹配位图,那就来看看这个位图是如何进行匹配的,找一个没有匹配上的项目来看看
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;
3) Handler的match函数
// handler的match函数一般是没有定义的。
if (!handler->match || handler->match(handler, dev))
return id;
实际上这个也有一点不明白的地方,比如说他的第一个device为什么一个都没有被匹配上,这些不是很重要的还是放在后面来看吧!
匹配成功之后调用handler的connect函数,将dev和handler通过id绑定起来
error = handler->connect(handler, dev, id);// 匹配成功之后就把handler和dev通过input_device_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);
匹配的handler是evdev和gpufreq_ib,所以可以直接跳转到这两个函数里面去看具体的handler的connect函数Evdev->connect以及函数mt_gpufreq_input_connect。
Normal返回0,错误返回错误码,但是也不怎么care他的返回值
18. 唤醒等待队列
这个是哪个等待队列呢?
input_wakeup_procfs_readers();
19. 解锁
mutex_unlock(&input_mutex);
20. devres加入到device的相关链表
if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev));
// 申请完struct devres就可以进行注册,devres_add就是注册函数,他把devres加入到device的相关链表中
devres_add(dev->dev.parent, devres);
}
21. 返回值
正确返回0,错误就返回错误码
和驱动层匹配
核心层提供给设备驱动层一系列的数据上报的函数,这些函数定义在input.h文件中,他们都使用的是input_event这个函数,具体的对应关系如下。
实际上就是将设备驱动层的多个接口统一为4个接口,其中事件类型是在核心层添加的,事件码、以及具体的事件的值、具体对应的input_dev是驱动层提供的。
Dev_func |
Core_func |
Tpd_down(A) |
|
input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, id); |
input_event(dev,EV_ABS,code,value); |
input_report_key(tpd->dev, BTN_TOUCH, 1); |
input_event(dev,EV_KEY,code, !!value); |
input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 1); |
|
input_report_abs(tpd->dev, ABS_MT_POSITION_X, x); |
|
input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y); |
|
input_mt_sync(tpd->dev); |
input_event(dev,EV_SYN,SYN_MT_REPORT, 0); |
input_sync(tpd->dev); |
input_event(dev, EV_SYN, SYN_REPORT, 0); |
|
|
Tpd_up(A) |
|
input_report_key(tpd->dev, BTN_TOUCH, 0); |
|
input_mt_sync(tpd->dev); |
多点触控的A协议所特有 |
input_sync(tpd->dev); |
|
|
|
Report_key |
|
input_report_key(tpd->dev, KEY_F13, 1); |
|
input_sync(tpd->dev); |
|
input_report_key(tpd->dev, KEY_F13, 0); |
|
input_sync(tpd->dev); |
|
b) 事件码明细
事件类型、事件码、事件的值和具体行为的对应关系
宏 |
type |
Code |
Value |
|
KEY_BACK |
1 |
158 |
1/dowm |
0/up |
KEY_HOMEPAGE |
1 |
172 |
1/dowm |
0/up |
KEY_APPSELECT |
1 |
580 |
1/dowm |
0/up |
KEY_POWER |
1 |
116 |
1/dowm |
0/up |
KEY_F13 |
1 |
183 |
1/dowm |
0/up |
KEY_F11 |
1 |
87 |
1/dowm |
0/up |
BIN_TOUCH |
1 |
330 |
1/dowm |
0/up |
ABS_MT_TOUCH_MAJOR |
3 |
48 |
1 |
|
ABS_MT_POSITION_X |
3 |
53 |
坐标值 |
|
ABS_MT_POSITION_Y |
3 |
54 |
坐标值 |
|
SYN_REPORT |
0 |
0 |
0 |
|
SYN_MT_REPORT |
0 |
2 |
0 |
|
DEVICE_ADDED |
0x10000000 |
|
不添加具体值 |
|
DEVICE_REMOVED |
0x20000000 |
|
不添加具体值 |
|
FINISHED_DEVICE_SCAN |
0x30000000 |
|
不添加具体值 |
|
c) 具体input_event函数
1. 判断是否支持事件类型
默认都是支持同步的事件类型的,为什么呢!因为从他的返回值的判断条件里面。
<=,因为同步事件是0,所以一定是可以支持的。
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
unsigned long flags;
// EV_MAX定义的是0x1f
// 判断是否支持这类事件,按键类和绝对位移类是之前初始化的时候添加的,但是同步类事件呢!还是需要跑到这个函数里面去看看
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);
}
}
然后来到具体的关于是否支持此类事件的函数
static inline int is_event_supported(unsigned int code,
unsigned long *bm, unsigned int max)
{
return code <= max && test_bit(code, bm);//默认的应该是都支持sync类别事件的。
}
Input_handle_event
在判断了支持此类事件之后,就可以继续往下面执行函数input_handle_event(dev, type, code, value);这个在上面这个函数里面已经说明了。
a) 获取事件处理者身份
// disposition表示事件处理者身份
disposition = input_get_disposition(dev, type, code, &value);// 返回值是0、1或者9
事件的处理者身份一般有以下几类:INPUT_PASS_TO_DEVICE、INPUT_PASS_TO_HANDLERS、INPUT_FLUSH
INPUT_PASS_TO_DEVICE表示需要交给input_device处理、INPUT_PASS_TO_HANDLERS表示交给input_handler处理,INPUT_FLUSH表示需要立刻处理。一般我们使用的是INPUT_PASS_TO_HANDLERS和INPUT_FLUSH。INPUT_PASS_TO_DEVICE一般是LED点亮事件或者是蜂鸣器鸣叫事件。
宏 |
Value |
INPUT_PASS_TO_DEVICE |
2 |
INPUT_PASS_TO_HANDLER |
1 |
INPUT_FLUSH |
8 |
1) input_get_disposition
该函数主要是对很多不同的事件类型进行分类处理,这里我们时常用到的事件只包含
按键、绝对坐标、同步事件,他们分别对应的是1、3、0,我们就只看这几个case。
同步事件,一般是最后返回值需要交给INPUT_FLUSH,返回值一般是9(sync)或者是1(A协议的mt_sync),9的二进制是1001和INPUT_FLUSH 8取&运算是大于0的。所以handler会立刻处理掉。1和INPUT_PASS_TO_HANDLER处理的,他不会立刻被处理掉。
case EV_SYN:// 同步类型
switch (code) {
case SYN_CONFIG: // 这个目前还没有用到
disposition = INPUT_PASS_TO_ALL;// (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE),前面一位是1后面是2,刚好是3
break;
case SYN_REPORT:
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH; // 前面那个定义的是1后面那个定义的是8,两个取或,刚好是9
break;
case SYN_MT_REPORT:// 使用A协议
disposition = INPUT_PASS_TO_HANDLERS; // 这个定义的是1
break;
}
break;
按键的事件这里需要注意的是他一般是传递给input_handler来处理的,还有就是连续的BIN_TOUCH会在这里被合并掉。如果要合并掉就需要存储起来上一次的状态。里面使用的是位运算来存储。判断的条件当然是他首先得支持此类按键事件
case EV_KEY:// 在这个地方过滤bin_touch事件
if (is_event_supported(code, dev->keybit, KEY_MAX)) { // 判断是否支持指定的key事件,dev->key初始化的值是0
/* auto-repeat bypasses state updates *///自动重复旁路状态更新
if (value == 2) {// 按键类型一般是down和up,现在暂时还没有遇到
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
// unsigned long key[BITS_TO_LONGS(KEY_CNT)];
if (!!test_bit(code, dev->key) != !!value) {//用于检测上次按键的状态是否为value,状态就存储在dev->key这个bitmap中
__change_bit(code, dev->key);//用于反转dev->key这个bitmap中用于表示按键状态的标志位,change_bit将0转换成1,将1转换成0
disposition = INPUT_PASS_TO_HANDLERS; // 按键事件也是传递给input_handler
}
}
break;
只有在第一次BIN_TOUCH的时候,这个事件会被传递出去,也就是传递给HANDLER,否则的话默认是
int disposition = INPUT_IGNORE_EVENT; // 默认定义的是0,这个时候事件就会被直接过滤掉。
只有在第一次BIN_TOUCH和弹起时候的BIN_TOUCH事件的时候,这个条件才会被满足。
if (!!test_bit(code, dev->key) != !!value)
首先需要他支持绝对坐标的事件类型,返回值一般是0或者是1,1就交给input_handler处理,0就直接被过滤掉。
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX))
disposition = input_handle_abs_event(dev, code, &value);// 返回值是0或者是1,0表示互阐述的事件
break;
具体的函数input_handle_abs_event,这个到后面开始研究B协议的时候再考虑。
b) 传递给input_device
这个一般情况下,我们是没有执行到这里的。
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)// 传递给设备处理器,INPUT_PASS_TO_DEVICE定义的值是2,
dev->event(dev, type, code, value);// 定义了他的event函数,。event()函数用来向输入子系统报告一个将要发送给设备的事件,例如让 LED 灯点亮事件、蜂鸣器鸣叫事件等
// 指纹、touch、按键、sensor事件都没有传递给设备的,是直接传递给handler
c) 判定dev->vals
if (!dev->vals)// struct input_value *vals;
/*
struct input_value {
__u16 type;
__u16 code;
__s32 value;
};
*/
return; // 后续需要使用,所以需要他能有分配空间。否则直接返回。
d) 传递给input_handler
一般事件都是交给handler来处理的,这里B协议先没有使用就暂时搁置,实际上这个地方就是将数据都扔到input_value这个结构体里面去。
if (disposition & INPUT_PASS_TO_HANDLERS) {// 传递给事件处理器, INPUT_PASS_TO_HANDLERS定义的值是1,tp相关的事件都是传递给handler的,1或者是9的时候就会进去里面
struct input_value *v;
if (disposition & INPUT_SLOT) {// 多点触控的B协议,也称为SLOT协议,INPUT_SLOT定义的是4
v = &dev->vals[dev->num_vals++];// vals的数目
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;// B协议需要再前面加上一个东西
}
// 就是指定的值
v = &dev->vals[dev->num_vals++];// 实际上这里就是将具体的每个value装进dev的input->value的数组,这个在注册的时候有分配过空间大小。
v->type = type;
v->code = code;
v->value = value;
}
每个数据都被仍在input_val这个结构体的成员v里面,事实上后续她应该是被弄在一个input_vale的结构体数组里面,以sync为标志来隔分,然后打包扔出去。继续看他是如何暂存的。
e) INPUT_FLUSH
Flush的意思就是一下子把数据清空掉,实际上就是将这整个数据包给扔出去,来看看他是怎么扔的吧,扔了之后是否需要把这个数据包清空呢!后续跟的时候注意一下吧!
// 重点在函数input_pass_values,同步的时候把数据以一个包的形式扔出去。,dev->max_vals定义的是17、79、10根据不同设备有所不同
if (disposition & INPUT_FLUSH) {//INPUT_FLUSH 标志设备输入事件发送dev->vals 数组将被下次事件覆盖,INPUT_FLUSH定义的值是8,所以这里只能是同步事件
if (dev->num_vals >= 2)// 上报键值,比如KEY_F13还有KEY_POWER键值,dev->num_vals一般是6或者7,一个完整的数据至少包含两个数据包。
input_pass_values(dev, dev->vals, dev->num_vals);// 把输入转换成input_value,主要是在这里吧数据扔出去了
dev->num_vals = 0;//将数据清空
}
实际上将num_vals设置成0就是清空数据包的意思。
Input_pass_value
现在的数据已经以一个包的形式压缩起来了,静待发送出去,看看他是怎么被扔出去的。
1) 判断事件的个数
if (!count)// count里面记录的是input_value数据的个数,数据都被存储在dev->val里面,vals实际上是头指针。
return;
2) RCU上锁
上锁和解锁的这块是真正涉及到数据上报的地方
rcu_read_lock();
3) 获取dev对应的handle
为什么第一个函数会获取不到他的handle呢?前面不是有给他赋值吗?仔细找了一下,前面的确是没有给他初始化的地方。
// rcu_dereference,该接口用来获取RCU protected pointer。reader要访问RCU保护的共享数据,当然要获取RCU protected pointer,然后通过该指针进行dereference的操作
handle = rcu_dereference(dev->grab);// struct input_handle __rcu *grab;,也就是通过这个函数来获取input_dev对应的handle,读者调用它来获取一个被保护的指针,前提是需要他被初始化。一般情况下都不会去初始化他。所以直接到了下面这几句
if (handle) {
count = input_to_handler(handle, vals, count); // 后续主要是这个函数里面继续往下面走,基本上不会到下面的那个里面去,好吧,实际上第一次是不会来到这里的,我已经苦瞎了
} else {// 一般应该是不会找出来为空的。
/* 如果在链表的遍历的handle的open函数有定义(如果该handle被打开,表示该设备已经被一个用户进程
使用(只有handle被打开的情况下才会接收到事件,这就是说,只有设备被用户程序使用时,才有
必要向用户空间导出信息))则调用handle->handler->event 来处理事件*/
list_for_each_entry_rcu(handle, &dev->h_list, d_node)// list_for_each_entry_rcu针对的是循环列表,//handle链表 //d_node是input_dev列表,注意后面是没有;也就是说他实际上是一个循环
if (handle->open)
count = input_to_handler(handle, vals, count);// 遍历链表,所以可能会被发送到多个handle里面去。那这样真的好吗!不是handle和input相互匹配的嘛!
}
一般这里都会打开两个handler一个为evedev、还有一个叫做gpufreq,后续可以在input_to_handler这个函数里面打印出handler->name
a) 在遍历handler链表的时候会找到对应的两个handler,我们继续往下面看input_to_handler这个函数,实际上input_to_handler这个函数就是被调用了两次
struct input_handler *handler = handle->handler;// struct input_handler *handler;实际上就是handle里面存储的那个handler
过滤这里一般我们是不会使用的。
// 首先是过滤
for (v = vals; v != vals + count; v++) {
if (handler->filter &&// 定义了他的filter函数,但是明显这个函数是没有定义的啊!
handler->filter(handle, v->type, v->code, v->value))// 这个函数是没有定义的。
continue;
if (end != v)// 不会过滤也不会执行到这里
*end = *v;
end++;
}
count = end - vals; // 过滤之后的input_value的数目
if (!count)
return 0;
每个handler都有event以及events函数,实际上evdev是两个都定义了,在两个都定义的时候会优先的进行批量的事件处理,gpugreq_ib这个只是去定义了event函数,所以他直接就调用它本身的event函数来处理。
4) RCU解锁
rcu_read_unlock();
5) 添加随机数池
// add_input_randomness()函数对事件发送没有一点用处,只是用来对随机数熵池增加一些贡献,因为按键输入是一种随机事件,所以对熵池是有贡献的。
add_input_randomness(vals->type, vals->code, vals->value);
6) 自动重复按键事件
实际上即使这个地方进去了之后也不会继续往下面走,关于输入系统实现按键重复的,这个后续再来继续作为专题研究。
/* trigger auto repeat for key events */// 触发自动重复的按键事件
for (v = vals; v != vals + count; v++) {
if (v->type == EV_KEY && v->value != 2) {// 按键事件,用于软件产生重复按键类事件 */
if (v->value)
// 实现按键重复,input_start_autorepeat函数就会启动内核定时器,接着就会执行input_repeat_key函数
input_start_autorepeat(dev, v->code);// 按下,这个不是之前在前面返回值是0的时候做了处理的嘛,实际上他是不会启动定时器去重复报点的。
else
input_stop_autorepeat(dev);// 弹起
}
}
实际上就是input_start_autorepeat和input_stop_autorepeat这两个函数都有条件被执行,但是到了具体的函数里面,input_start_autorepeat他实际上是不会被执行的。我们具体来看。
static void input_start_autorepeat(struct input_dev *dev, int code)
{
if (test_bit(EV_REP, dev->evbit) &&dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] &&dev->timer.data) {// 上面的这个条件是不满足的。
dev->repeat_key = code;// 事件码
mod_timer(&dev->timer,// mod_timer函数就会启动内核定时器
jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
// 启动了内核计时器之后会到这里,接着就会执行input_repeat_key函数
}
但是他的input_stop_autorepeat是可能被执行,实际上里面就是删除定时器。
b) 超过数据大小
当超过dev->vale的大小的时候就手动给他一个同步的数据,把这部分数据先丢出去。
else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;// static const struct input_value input_value_sync = { EV_SYN, SYN_REPORT, 1 };里面的value是1,一半情况下是0,表示对于最大的数据的打包。
//dev->num_vals is 3 and dev->max_vals is 79
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
一般情况下是不会跑到这里面来的。
这个函数是设置事件类型,以及具体的事件码,具体是在设置KEY_F13这个地方有用到。
input_set_capability(input_dev, EV_KEY, KEY_F13);
具体到input核心层提供的函数是这个样子的。
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;
case EV_REL:
__set_bit(code, dev->relbit);
break;
、、、、
}
这个函数和mtk-tpd.c里面的那个函数调用实际上是一样的。
set_bit(BTN_TOUCH, tpd->dev->keybit);
1) input_register_handle
其中不管是dev_list还是handler_list,在内核里面基本上所有使用到链表的地方都是使用了struct list_head,这个数据结构,他是内核中最常用的双向循环链表,这个函数在handler->connect函数中使用,具体在这两个地方注册handle注册handle
1. 上锁
struct input_dev *dev = handle->dev;
/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
2. 加入到dev_list
其中dev_list实际上是一个list_head的结构体。
/*
* Filters go to the head of the list, normal handlers
* to the tail.
*/
if (handler->filter) // 一般不会定义他的filter函数
list_add_rcu(&handle->d_node, &dev->h_list);
else
// 此函数将handle加入到输入设备的dev->h_list链表中。
list_add_tail_rcu(&handle->d_node, &dev->h_list);
3. 解锁
mutex_unlock(&dev->mutex);
4. 加入handler_lsit
/*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
// 此函数将handle加入input_handler的handler->h_list链表中。
list_add_tail_rcu(&handle->h_node, &handler->h_list);
5. 执行handler->start函数
if (handler->start)// 一般也不会定义start函数
handler->start(handle);
6. 返回值
正确返回0,错误返回错误码
事件处理层主要对应的是一些input_handler,这一块主要负责和上层和交互。
1. Evdev->connect
在input_dev的注册函数里面,有一个遍历handler_list匹配handler然后调用handler的connect的函数将handler和dev通过id绑定起来,因为evdev的handler匹配成功,所以evdev->connect函数将会被调用,handler和dev将会被input_device_id绑定起来。具体看看是如何实现绑定的。
a) 获取最新次设备号
获取来干什么呢?之前不是有一个设备路劲了吗?
// EVDEV_MINOR_BASE 64
// EVDEV_MINORS 32
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);// reserve 保留
return error;
}
输入设备的主设备号是确定的,次设备在一定的范围内,log看到生成的次设备号如下。
b) 为evdev分配空间
简单说明一下evdev这个数据结构。
struct evdev {
int open;
struct input_handle handle; // 将匹配好的handler和dev绑定之后仍进去
wait_queue_head_t wait;
struct evdev_client __rcu *grab; // 通过他找到evdev_client,其中evdev_client就是上报数据的容器
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
struct cdev cdev; // 字符设备和上层的接口
bool exist;// 初始化为true
};
struct evdev *evdev;
// 数据结构evdev
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
c) 初始化evdev和注册成员
// 初始化evdev这个结构体,最后这个结构体只有一个成员没有被初始化grap,也就是evdev_client这个东西
1. 初始化client_list
INIT_LIST_HEAD(&evdev->client_list);// 结构体list_head
2. 初始化client_lock
spin_lock_init(&evdev->client_lock);// /* protects client_list */
3. 初始化mutex
mutex_init(&evdev->mutex);
4. 初始化waiter
init_waitqueue_head(&evdev->wait);// wait_queue_head_t,在事件同步之后会被设置成true,此时等待数据发送过来
5. 初始化exist
evdev->exist = true; // 在open文件的基础上需要这个标志位来做判断
实际上需要这里作为true
6. 给device命名
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);// 给这个device命名
7. 初始化handle
// evdev中的handle变量的初始化,这个handle,这里面保存的就是已经匹配成功的input_dev 和 handler
evdev->handle.dev = input_get_device(dev);// 这两个是重点
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;// 这两个是重点
evdev->handle.private = evdev; // 后面应该会被用到,但是他还没有被初始化完成,这样真的OK吗?因为是指针,感觉是OK的
handle的名称实际是evevnt*
handler的名称有两个一个是evdev一个是gpufreq_ib
8. 初始化device
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// MKDEV通过主设备号和次设备号生成设备号
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
9. 注册handle
// 将一个已经匹配好的handle注册到input核心去
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
10. 初始化cdev
cdev_init(&evdev->cdev, &evdev_fops);// 初始化cdev字符设备
evdev->cdev.kobj.parent = &evdev->dev.kobj;
11. 添加cdev
// 把这个device 添加到/sys/class/input/下面,所以我们可以看到/dev/input下面看到:event0~31 字样字符设备文件
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle;
12. 添加device
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
d) 返回值
正确返回0,错误返回错误码
2. Evdev_open
1) 获取evdev结构体指针
关于这个的解释很多,简单的讲就是inode的i_cdev指针指向evdev的cdev成员,inode则是我们在模块编程的时候insmod内核的时候产生的,他会帮我们找到具体的内核模块。
还有就是container_of函数可以通过域成员的指针找到结构体的指针。
// 通过域成员指针找到evdev的结构体指针
/*
参数3是参数2这个结构体的一个成员的名字!而不是类型名! struct cdev cdev;这里不合理的是,他的成员和结构体取名称一致了。参数1是一个指针,它指向参数3这个成员
inode 中的i_cdev字段是一个指针,当我们成功insmod了一个设备驱动的时候,我们会通过mknod创建一个设备文件节点并和具体设备
(驱动)想关联,这个设备文件节点所对应的就是struct inode结构体的一个实例,这个结构体有一个字段i_cdev,是个struct cdev类型的指针
,它会指向设备结构体的cdev字段。至此你已经有了一个指向某个evdev的cdev字段的一个指针
由此container_of可以帮你计算出指向该设备结构体的指针。
当一个设备驱动对应多个设备(子设备)时,你就知道container_of发挥的作用了!当你针对每一个设备调用 open时,
因为每个设备对应的设备文件节点不一样,那么根据该节点的i_cdev字段所计算的设备结构体指针也不一样,
你就可以找到特定节点所对应的设备结构体!而不至于对不同的子设备编写大同小异的各自的设备驱动。
*/
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);// 实际这里是打开具体的设备
2) 获取input_event的数目
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);// 计算的input_dev中的input_event的数量,打开的时候能够通过inode这个来知道这个设备的input_event的数目,那后面不打开的时候呢!
// 是不是设备变化的时候又会重新去扫描文件,重新去打开文件,重新去计算input_size呢?来验证一下猜想。
根据不同设备来获取相应的input_event的size
实际上这个值和前面注册的时候那个packet_size还相关。这个在input_register_device的时候可以看到。
static unsigned int evdev_compute_buffer_size(struct input_dev *dev)// 计算出input_dev里面的input_evnt的数据大小
{
/* @ hint_events_per_packet:所产生的事件的平均数
在分组装置(ev_syn / syn_report事件)之间。用
事件处理程序估计需要保持的缓冲区的大小
事件。
*/
// EVDEV_BUF_PACKETS定义的是8
unsigned int n_events =
max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,EVDEV_MIN_BUFFER_SIZE);// EVDEV_MIN_BUFFER_SIZE定义的是64u
return roundup_pow_of_two(n_events);/* 思想很简单,就是找出当前数的二级制中最大位为1位的位置,然后用1左移位数即可。
比如数据5,它的二进制形式为101,最高位为1的位置是3,然后左移3位,等于1000,即数字8。也就是数字8是5的接近的2的整数次幂。*/
}
3) 为evedev_client分配空间大小
unsigned int size = sizeof(struct evdev_client) +bufsize * sizeof(struct input_event);// 他本身的大小加上他里面的buffer存储的input_event的大小
struct evdev_client *client;
int error;
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);// 分配用户端结构,初始化最大的空间。
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM;
4) 初始化evdev_client的成员
先来了解一下evdev_client这个结构体,实际上他也是一个很重要的结构体
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct wake_lock wake_lock;
bool use_wake_lock;
char name[28];
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
int clkid;
bool revoked;
unsigned int bufsize;
struct input_event buffer[];
};
主要初始化的成员包含bufsize、buffer_lock、client->name、evdev
client->bufsize = bufsize;// 对evdev_client进行赋值,赋值buffersize
spin_lock_init(&client->buffer_lock);
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current));// 对evdev_client进行赋值,赋值name
client->evdev = evdev;// 用户端与evdev设备结构关联起来
5) 将client添加到evdev的client链表
evdev_attach_client(evdev, client);// 把client链接到evdev的client链表上,struct list_head client_list
6) 打开设备
error = evdev_open_device(evdev); // 打开设备
if (error)
goto err_free_client;
a) 上锁
retval = mutex_lock_interruptible(&evdev->mutex);// 上锁
b) 判断evdev是否存在
if (!evdev->exist)// 判断evdev结构体是否存在,在evdev_connect中初始化成员为true
retval = -ENODEV;
else if( ){
// 标记设备打开是在这里面执行
}
这个初始化的值一般都是true,但是下面这个就不对了。
c) 标记设备打开
只有evdev->open的值为0的时候才会执行input_open_device函数
//当evdev首次打开,那么就同时打开它的handle,打开handle实际上就是将handle的open标志为非0
if (!evdev->open++) {// open实际上是一个int成员,表示已经打开的设备,
// ++的算术优先级是高于取非运算的。所以先自加再取非,里面先取出的是open的值,未打开的时候里面是0,取非是1,后面加上1
retval = input_open_device(&evdev->handle);// 此函数在input.c文件中定义,在核心层定义,在事件处理层进行使用,这就更加验证了输入核心层承上启下的作用,但是也是先去执行的是++运算,只是++运算本身在if里面就是先取出他的值
if (retval)
evdev->open--;
}
那是在哪里这个evdev->open被赋值了呢!其实除了InputReader之外还有一个就是SystemServer这个类,也可能在之前调用了对应evdev的open函数。
d) 打开handle,input_open_device
1) 上锁
int retval;
retval = mutex_lock_interruptible(&dev->mutex);// 获得锁之后会返回0
if (retval)
return retval;
2) 判断设备是否正在被注销
if (dev->going_away) {// 判断设备是否在open期间被注销
retval = -ENODEV;
goto out;
}
3) Handle的open加1
handle->open++;// handle的打开计数加1,实际上打开一个cdev就是打开一个handle
4) 调用input_dev的open函数
一般设备的open函数都不会定义,所以一般也不会进来。
调用input_dev的open函数是有条件的,需要在无其余的用户进程占用且input_dev的open函数定义了基础上来实现这个。
if (!dev->users++ && dev->open)// 如果输入设备没有进程引用并定义了open函数就调用open函数
retval = dev->open(dev);// 调用input_dev的open函数打开设备,这个是不是在具体的设备驱动里面了呢,但是发现这个函数并没有定义啊!实际上这个函数是没有定义的,但是这个函数是在第一次打开的时候被调用且是用来完成初始化使用的、
// 看来他不是每次都重新,扫描设备,只会扫描一次,那又是怎么知道需要分配的数据的大小呢!
5) 打开设备失败
这个一般都不会执行进来,因为retval的值一般是0,不会进行堵塞
if (retval) {// 没有打开成功
dev->users--;
if (!--handle->open) {// 说明有其他进程打开了这个设备
/*
* Make sure we are not delivering any more events delivering:提供
* through this handle
*/
// 不是一种新的锁,而是一种数据同步的机制,主要针对的数据对象是链表,目的是为了提高遍历链表的效率,主要适用于读取多更改少的情况。
synchronize_rcu();// 该函数由RCU写端调用,它将阻塞写者,直到所有读执行单元完成对临界区的访问后,写者才可以继续下一步操作,
// 上面这个函数可以看做是有其他进程占用这个设备,此时就堵塞。不获取任何事件。
}
}
e) 解锁
mutex_unlock(&evdev->mutex);
7) 将client设置为文件的私有数据
file->private_data = client;// 就是在这里进行的赋值,所以后续可以通过文件的私有数据找到evdv_client,在读取设备的数据的时候 evdev_read就是在这个函数里面使用
8) 将file标记为不支持定位
/*使用数据区时,可以使用 lseek 来往上往下地定位数据。但像串口或键盘一类设备,使用的是数据流,
所以定位这些设备没有意义;在这种情况下,不能简单地不声明 llseek 操作,因为默认方法是允许定位的。
*/
nonseekable_open(inode, file);
3. Evdev_events
实际他会优先跑到evdev_events函数,event函数是不会被调用的。
1) 通过handle->private找到evdev
这个成员的赋值是在evdev->connect函数的时候被初始化和赋值的。
2) 获取时间
因为最初的数据包的格式是input_value我们需要传给上层的是这个数据结构input_event
就需要在原有的基础上添加时间。
ktime_t time_mono, time_real;
time_mono = ktime_get();
time_real = ktime_mono_to_real(time_mono);
3) 上锁
rcu_read_lock();// 我记得RCU好像也是一种锁机制
4) 寻找对应的evdev_client发送数据
client = rcu_dereference(evdev->grab);// 通过evdev的grap成员是可以找到evdev_client结构指针的,但是一般我们都不会初始化他grab这个成员,在evdev的connect函数这里,我们专门提到的唯一没有初始化的成员就是他了
if (client)
// 指定了client就用指定的
evdev_pass_values(client, vals, count, time_mono, time_real);
else
// 遍历evdev->client_list,找到对应的client,client_list中的client是什么时候挂上的?(evdev_open()->evdev_attach_client()->list_add_tail_rcu(&client->node, &evdev->client_list))
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count,
time_mono, time_real);// 根据我的直觉他应该是进入到这里面来,有唯一的一个client来负责输出传输
evdev_pass_values
这个函数的实际作用就是将input_val这个数据转换成input_event
a) 判断client是否被撤销
if (client->revoked)
return;
b) 添加time字段
一个数据包的time应该是一致的。
// 涉及的是linux的时钟管理相关的东西,这里只需要了解的是在input_event里面需要添加time字段
event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
mono : real);
c) 上锁
spin_lock(&client->buffer_lock);
d) 填充input_event
填充之后又是一个一个的被扔了出去,因为是for循环来执行这个pass_value的函数
bool wakeup = false;他的初始化是0
for (v = vals; v != vals + count; v++) {
event.type = v->type;
event.code = v->code;
event.value = v->value; // 现在所有的值都被添加进去了。
__pass_event(client, &event);// 通过_pass_event传输出去,当收到SYNC标志之后,唤醒等待队列,唤醒等待队列来干什么了呢?
if (v->type == EV_SYN && v->code == SYN_REPORT)// 最后上报的那个同步事件,report_sync
wakeup = true;//wait也是connect时候初始化的
}
__pass_event
这个函数是进一步对数据进行处理,实际上就是将这个一个一个的input_event放在evdev_client的buffer里面,这样方便hal对数据进行读取。
client->buffer[client->head++] = *event;// head:动态索引,每加入一个event到buffer中,head++;一直在怀疑的是evdev_client里面的buffer数据是怎么来的,现在看来就是在这个地方,里面的数据被填充的。
client->head &= client->bufsize - 1; // 需要搞清楚的是这里的buffersize是随着什么变化的值,事实上bufsize是一个定值,是不会改变的且是2的n次方,这里head和一个全是1的定值取&运算,一旦超过立刻将值设置成0,达到一个清零的动作。head是针对bufsize的索引
Unlikely所以一般是不会进去里面的,我们暂且不看
// likely和unlikely是内核定义的两个宏,他们是表示的是进入这个函数的可能性,likely进入if的可能性大,unlikely进入if的可能性小,或者说他进入else的可能性大
if (unlikely(client->head == client->tail)) {// 不太可能发生的事件就先不管这块。
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1);// tail:也是动态索引,每取出一个buffer中的event,tail++;
// buffer:event存储器,是一个环形区域,__pass_event会把数据放到client->buffer中。
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;// packet_head:一个数据包头
if (client->use_wake_lock)
wake_unlock(&client->wake_lock);
}
同步的索引值是packect_head
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;// 当一个数据结束时,更新数据包头,实际上是链表的尾指针,实际上指向的是最后一个数据,实际上他是下一个数据包的第一个元素,本次数据的最后一个元素
if (client->use_wake_lock)// use_wake_lock表示不能休眠锁
wake_lock(&client->wake_lock);
// 发送信号SIGIO信号给fasync_struct 结构体所描述的PID,触发应用程序的SIGIO信号处理函数,这个就是基本上继续追下去的线索了
// 即向每一个登记的进程发送SIGIO信号。那哪些进程是登记的进程呢,继续来看看。
kill_fasync(&client->fasync, SIGIO, POLL_IN);// 向内核发送SIGIO,POLL_IN表示可读,数据发送出去,然后就可以去读取文件了。
}
实际上的数据包是这样的。
最后这个函数没有在上层找到对应的响应信号的函数,就先暂时放在这里。
数据包头的含义如下:
e) 解锁
spin_unlock(&client->buffer_lock);
f) 唤醒等待队列
这里的等待队列是哪来干什么的呢?继续追这个wait
if (wakeup)
// 表示数据已经写好了,那对于非阻塞形式的访问呢!他又起到了什么作用呢!
wake_up_interruptible(&evdev->wait);// 这个对应的是以阻塞的形式来访问数据的wait_event_interruptible(&evdev->wait)这一句
这个等待队列还是和后面的数据读取相关
5) 解锁
rcu_read_unlock();
4. Evdev_read
在这个函数里面我们获取上报的数据,并且将数据传递给用户空间,将数据传递到用户空间实际上都会用到的是copy_to_user函数。那获取数据呢!还是说在上一个函数evdev_events里面,数据已经被扔到了一个buffer里面,这里就是将这个buffer的数据扔出去,那这个buffer就一定是全局变量了。好吧!带着这些猜想一起去看看吧!
1) 获取evdev_client
这个是在evdev_open的时候被赋值的,那这个又是如何和evedev_events里面进行连接的呢!
// 从打开的文件的私有数据中获取evdev_client的指针,evdev_client是在evdev_open函数函数中被创建和赋值的。也就是在哪个时候被放到了file的私有数据里面
struct evdev_client *client = file->private_data;// 私有数据,感觉是个很屌的东西!
实际上在open的时候除了会将他赋值为文件的私有成员,还会将这个evdev_client添加到evdev的成员里面去,在evdev_events这个函数里面就是通过他的成员找到他的,所以里面的evdev_client实际上是相互对应的。
2) 获取evdev指针
是通过client来找到evdev的
struct evdev *evdev = client->evdev;// evdev_client里面包含了evedev的成员指针,
//这个主要是用来取下一个数据包的,如果只有一个事件呢!感觉不可能啊!至少后面都会加上一个sync啊!所以一定需要clienet
3) 检测count值
一定是读取的是整数个input_event,还有就是一个struct input_event的size是24,这里一定是24的整数倍,还有就是在hal层调用的count实际上是定义的一个value为256的值,所以count是6144,表示在getevents里面使用了read函数读取。其余情况可能在别的调用了read函数。
if (count != 0 && count < input_event_size())// 需要读取的事件小于一个事件长度
return -EINVAL;
4) 循环读取事件
接下来的都是在这for(;;)里面实现的,但是这个还不是真正的数据读取,真正的数据读取在里面又内嵌了一个while循环。
1) 检测evdev和client
正常情况下一般都不会进入到这里。
evdev->exist这个是在注册的时候被赋值为true的。Revoked只有在注销的时候才会被赋值为true
if (!evdev->exist || client->revoked)// revoked撤销,也就是说这个数据的源头一定和evedev和evdev_client相关,要是这两个结构体不存在的话,那注定是无法获取数据的。那继续看看他的必要性吧!已经找到evedev_client这个东西了
// 实际上想想evdev都退出了,还有这个read函数什么事情了?
return -ENODEV;
2) 检测是否有数据可读
正常情况下也不会到这里面来。
其中packect_head和tail都是整数值,不是指针!不是指针!不是指针!重要的事情说三遍
if (client->packet_head == client->tail &&// 当这两个数据是一致,表示没有数据可读,不应该理解成指针,没有数据就直接返回算了。
(file->f_flags & O_NONBLOCK))// 并且以非阻塞的方式来访问,那最后到底是阻塞的方式还是非阻塞的方式访问啊!
return -EAGAIN;
关于数据的访问方式,这个地方需要看里面的设置吧!
数据的访问方式是在open的时候确定的,这个在open的时候指定的是堵塞的方式来访问他,当然如果想要修改的话,需要使用ioctrl或者ftctrl,但是这里都没有使用到。所以默认是堵塞的方式来访问。
3) 检测count的值
这个也基本上不会进入到这里面来的。
这里也是我觉得奇怪的地方,进来函数这里不是就已经检测了count!=0,这里为什么还做了一次检查呢?关键就在这个for循环里面,先预留在这里。
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)// 这里为什么还需要添加这个判断条件。上面不是做了判断了吗?
break;
4) 循环读取数据并发送到用户空间
// 里面的bufsize的长度根据不同设备大小来分配。一般touch是1024,sensor是128,指纹。三个按键是64,找到对应的分配空间的地方
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {// 应该是取出数据放在需要发送到用户空间的event,其中取数据就是在evedev_client这个结构体的buffer数组里面去取东西。
if (input_event_to_user(buffer + read, &event))// copy_to_user的封装定义在文件input-compat.c中,buffer + read里面装的实际上是一个input_event的数据大小。copy_to_user,直接是一个input_event的形式被扔出去的。
return -EFAULT;
read += input_event_size();// 自加上一个input_event的数据长度。
}
a) 将buffer里面的数据扔到event里面
这里使用的是函数evdev_fetch_next_event(client, &event),我们来看看这个函数的具体定义。
1) 上锁
spin_lock_irq(&client->buffer_lock);
2) 判断是否有数据可读
这个感觉之前也做过判断了啊!
have_event = client->packet_head != client->tail;
if (have_event) {
3、4、5都是在这个条件里面执行的。
}
3) 从buffer里面取一个input_event
*event = client->buffer[client->tail++];// struct input_event buffer[];
4) 对buffer进行计数
client->tail &= client->bufsize - 1; // 这里不是更新索引值,实际上是实现重新计数
5) 不可休眠锁
这里的不可休眠,实际上是在另外一个地方上锁的。
// 如果首尾相接了,说明数据读完了。wake_unlock(&client->wake_lock);;因为_pass_event中添加_event的时候上了一把锁wake_lock(&client->wake_lock);。
if (client->use_wake_lock &&client->packet_head == client->tail)//这个地方实在是有些不明白,先暂时放这里吧!对于这个解锁的操作,也就是读取到了一个event就解锁,那之前是在哪里开始上锁的呢?
wake_unlock(&client->wake_lock);
6) 解锁
spin_unlock_irq(&client->buffer_lock);
b) 将event数据传递到用户空间
主要使用的是这个函数input_event_to_user,这个函数定义在文件input-compat.c这个文件里面去。找找哈!函数是这个。
// 实际上就是实现的用户空间到内核空间的数据拷贝
int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (copy_to_user(buffer, event, sizeof(struct input_event)))// 最后扔给用户空间的就是这个event,他的类型是input_event
return -EFAULT;
return 0;
}
5) 读取到数据返回
if (read)
break;
6) 阻塞方式访问等待唤醒
这里我感觉是以阻塞的方式来访问的。但是实际上呢!从log输出来看,inputread访问的方式是以非阻塞的方式来访问的。另外一个psensor是以阻塞的方式来访问的。
if (!(file->f_flags & O_NONBLOCK)) {// 以阻塞的形式访问,读取不到数据就需要在这里堵着。
// 在读取数据的情况下,可能当前缓存区内没有数据可读。在这里先睡眠等待缓存区中有数据
/*
这一句时,她就不走了,干嘛?睡个觉先,等待她的另一半的到来,然后把她唤醒。
这就需要我们的fun1函数执行到wake_up_interruptible(&evdev->wait);在数据上报的函数里面
*/
error = wait_event_interruptible(evdev->wait, client->packet_head != client->tail || !evdev->exist || client->revoked);// wait_event_interruptible这个函数实际上是kernel里面的那个是一样一样的。和等待队列相关
if (error)
return error;
}
来看看为什么inputreader编程了阻塞的方式来访问了。实际上在打开文件的时候指定的没有指定,默认的话就是阻塞的方式来读取,后面通过fctlio函数将他设置成了非阻塞的访问方式。那就继续往下面看。
首先说明的是gpufreq实际上是linux的动态频率调节系统,因为cpu的主频越高他的功耗就越大,但是cpu没有必要随时都工作在最高的主频上,为了较少cpu功耗和减少发热,linux于是导入了gpufreq系统。所有和gpufreq相关的接口都在这个文件下面,怎么感觉有点不对劲呢!
1. mt_gpufreq_input_connect
a) 为handle分配空间
// 为handle分配空间
struct input_handle *handle;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
b) 初始化handle
// 初始化handle
handle->dev = dev; // 实际上这两个是关键点
handle->handler = handler;// 实际上这两个是关键点
handle->name = "gpufreq_ib";
c) 注册handle
// 注册handle
error = input_register_handle(handle);
if (error)
goto err2;
d) Open_device
// 打开handle,实际上就是将handle的open自加1
error = input_open_device(handle);
if (error)
goto err1;
e) 返回值
返回值和evdev_connect一样,正确返回0,错误返回错误码
2. mt_gpufreq_input_event
这个函数在核心层的数据传递的时候传递给handler->event的。也就是到了这个函数里面去。
我们看看这个函数里面又干了什么。也就是说在bin_touch且值是1的时候开始唤醒进程,真正的这些数据只是用来动态调节gpu的频率的,和数据上报没有多大关系。看看他之前的bin_touch是否有被过滤过,根据前面的流程,他真的有被过滤过。
1. 判断gpu是否ready
printk(" enter mt_gpufreq_input_event and mt_gpufreq_ready is %d\n", mt_gpufreq_ready);
if (mt_gpufreq_ready == false) {
gpufreq_warn("@%s: GPU DVFS not ready!\n", __func__);
return;
}
2. 按键点击时唤醒进程
mt_gpufreq_input_boost_state打印出来的结果都是1
printk(" mt_gpufreq_input_boost_state is %d\n", mt_gpufreq_input_boost_state);
if ((type == EV_KEY) && (code == BTN_TOUCH) && (value == 1)&& (mt_gpufreq_input_boost_state == 1)) {
gpufreq_dbg("@%s: accept.\n", __func__);
/* if ((g_cur_gpu_freq < mt_gpufreqs[g_gpufreq_max_id].gpufreq_khz) &&
* (g_cur_gpu_freq < mt_gpufreqs[g_limited_max_id].gpufreq_khz)) */
/* { */
wake_up_process(mt_gpufreq_up_task);
/* } */
}