Linux学习笔记之input子系统

一、input简介

1.1 input系统的分层结构

输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系统也支持自动创建设备文件,这些文件采用阻塞的IO读写方式,被创建在“/dev/input/”下。如下图所示。内核中的输入子系统自底向上分为设备驱动层、输入核心层、事件处理层。由于每种输入的设备上报的事件都各有不同,所以为了应用层能够很好识别上报的事件,内核中也为应用层封装了标准的接口来描述一个事件,这些接口在“/include/upai/linux/input”中。

Ø 设备驱动层是具体硬件相关的实现,也是驱动开发中主要完成的部分

Ø 输入核心层主要提供一些API供设备驱动层调用,通过这些API设备驱动层上报的数据就可以传递到事件处理层

Ø 事件处理层负责创建设备文件以及将上报的事件传递到用户空间

clip_image002

Linux内核为了处理各种不同类型的输入设备,比如说鼠标、键盘、操纵杆、触摸屏,设计并实现了一个对上层应用统一视图的抽象层,即Linux输入子系统。

clip_image003

从底层到上层,input子系统由设备驱动层、核心层以及事件处理层3个部分组成

当一个鼠标移动,一个按键按下或弹起,它都需要从底层设备驱动à核心层à事件处理层à用户空间,层层上报,一直到应用程序。

应用input子系统有如下优点:

Ø 统一了各种各样的输入设备的处理方法

Ø 提供了用于分布给用户使用的简单接口

Ø 提炼了输入驱动程序的通用部分,简化了驱动程序的开发和移植工作。

Input子系统从底层往上层的一个流程是这样的:

Ø 底层驱动硬件初始化,并指定要上报的事件的类型以及值

Ø 通过input.c配对

Ø 配对到相应的事件上报处理层,然后给上层发布接口

1.2 input子系统的主要数据结构

Input_dev:

struct input_dev {

const char *name; //设备名字

const char *phys; //设备在系统中的物理路径

const char *uniq; //设备的唯一标识码

struct input_id id; //设备id,包含总线id ,厂商id , 与input_handler匹配的时会用到

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; /* 如果设备支持力反馈,则该成员将指向力反馈的设备描述

结构 */

unsigned int repeat_key; /*保存上一个键值,实现软件自动重复按键 */

struct timer_list timer; /*用于软件自动重复按键的定时器 */

int rep[REP_CNT];

struct input_mt_slot *mt;

int mtsize;

int slot;

int trkid;

struct input_absinfo *absinfo;

unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反映设备按键按钮的当前状态

unsigned long led[BITS_TO_LONGS(LED_CNT)];//反映设备led灯的当前状态

unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反映设备音效的当前状态

unsigned long sw[BITS_TO_LONGS(SW_CNT)];//反映设备开关的当前状态

/* 提供以下4个设备驱动层的操作接口,根据具体的设备需求实现他们 */

int (*open)(struct input_dev *dev); //将在input_open_device()中调用

void (*close)(struct input_dev *dev);//将在input_close_device()中调用

int (*flush)(struct input_dev *dev, struct file *file);

/*(event)用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有很多事件仍需

要送到设备驱动中,如led指示灯事件和音效事件,因为这些操作通常需要设备驱动执行*/

int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

struct input_handle __rcu *grab;

spinlock_t event_lock;

struct mutex mutex;

/* 记录输入事件处理程序(input handlers)调用设备open()方法的次数,保证设备open()是在第一个调用input_open_device()中被调用, 设备close()方法是在最后一次调用input_close_device()中被调用 */

unsigned int users;

bool going_away;

bool sync; //上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为1*/

struct device dev; //内嵌设备device结构

struct list_head h_list; //与该设备相关的输入句柄(input handles)列表

struct list_head node;//通过该成员,系统中的所有的input_dev对象被管理

};

struct input_event

/*

* The event structure itself

*/

struct input_event {

struct timeval time; //事件发生的时间

__u16 type; //事件类型:按键和移动鼠标就是不同类型

__u16 code;

__s32 value; //事件值:按键a和按键b就对应不同值

};

Code:

事件的代码。如果事件的类型代码是EV_KEY,该代码code为设备键盘代码,代码值0~127为键盘上的按键代码,0x110~0x116为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_MIDDLE)为鼠标中键。其他代码含义请参看include/linux/input.h文件。如果事件的类型代码是EV_REL,code值表示轨迹的类型。如指示鼠标的X轴方向REL_X(代码为0x00),指示鼠标的Y轴方向REL_Y(代码为0x01),指示鼠标中轮子方向REL_WHEEL(代码为0x08)。

Type:

EV_KEY,键盘

EV_REL相对坐标

EV_ABS绝对坐标

Value:

事件的值。如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;如果事件的类型代码是EV_REL,value的正数值和负数值分别代表两个不同方向的值。

input_device_id 结构体的定义:

struct input_device_id {

kernel_ulong_t flags; /*标志信息*/

__u16 bustype; /*总线类型*/

__u16 vendor; /*制造商ID*/

__u16 product; /*产品ID*/

__u16 version; /*版本号*/

kernel_ulong_t driver_info; /*驱动额外的信息*/

};

1.3 input子系统的关键函数

input_allocate_device()
input_register_device()->input_attach_handler()->input_match_device()

input_allocate_device()

这个函数在内存中为输入设备结构体分配一个空间,并对其主要成员进行初始化。其代码如下

struct input_dev *input_allocate_device(void)

{

struct input_dev *dev;

dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

/*分配一个input_dev结构体,并初始化为0*/

if (dev) {

dev->dev.type = &input_dev_type; /*初始化设备的类型*/

dev->dev.class = &input_class; /*设置为输入设备类*/

device_initialize(&dev->dev); /*初始化device结构*/

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); /*模块引用技术加1*/

}

return dev;

}

其返回一个指向 input_dev 类型的指针,该结构体是一个输入设备结构体,包含了输入设备的相关信息(按键码、设备名、支持的事件)。

input_register_device()

这个函数是输入子系统核心(input core)提供的函数。它将input_dev 结构体注册到输入子系统核心中(input_dev 结构体必须由 input_allocate_device()函数来分配的)。
如果函数注册失败,必须调用 input_free_device() 函数来释放分配的空间。
如果函数注册成功,在卸载函数中应该调用 input_unregister_device() 函数来注销输入设备结构体。

我们看一下函数原型:

int input_register_device(struct input_dev *dev)

{

//定义一些函数中将用到的局部变量

static atomic_t input_no = ATOMIC_INIT(0);

struct input_handler *handler;

const char *path;

int error;

//设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。

__set_bit(EV_SYN, dev->evbit);

//初始化 timer 定时器,用来处理重复点击按键。(去抖)

init_timer(&dev->timer);

//如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。

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;

}

//检查下列两个函数是否被定义,没有被定义则赋默认值。

if (!dev->getkeycode)

dev->getkeycode = input_default_getkeycode; //得到指定位置键值

if (!dev->setkeycode)

dev->setkeycode = input_default_setkeycode; //设置指定位置键值

//设置 input_dev 中 device 的名字为 inputN

//将如 input0 input1 input2 出现在 sysfs 文件系统中

dev_set_name(&dev->dev, “input%ld”,(unsigned long) atomic_inc_return(&input_no) – 1);

//将 input->dev 包含的 device 结构注册到 Linux 设备模型中。

//并在文件系统中表现出来

error = device_add(&dev->dev);

if (error)

return error;

//打印设备的路径并输出调试信息

path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);

printk(KERN_INFO “input: %s as %s\n”,

dev->name ? dev->name :

“Unspecified device” , path ? : “N/A”);

kfree(path);

error = mutex_lock_interruptible(&input_mutex);

if (error) {

device_del(&dev->dev);

return error;

}

//将 input_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备)

list_add_tail(&dev->node, &input_dev_list);

list_for_each_entry(handler, &input_handler_list, node);

//调用 input_attatch_handler()函数匹配 handler 和 input_dev。

//这个函数很重要,在后面单独分析。

input_attach_handler(dev, handler);

input_wakeup_procfs_readers();

mutex_unlock(&input_mutex);

return 0;

}

给 evbit 设置的,input_dev所支持的事件类型:

#define EV_SYN 0x00 /*表示设备支持所有的事件*/

#define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/

#define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/

#define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/

#define EV_MSC 0x04 /*其他类型*/

#define EV_LED 0x11 /*LED灯设备*/

#define EV_SND 0x12 /*蜂鸣器,输入声音*/

#define EV_REP 0x14 /*允许重复按键类型*/

#define EV_PWR 0x16 /*电源管理事件*/

input_attatch_handler()

这个函数用来匹配 input_dev 和 handler,匹配成功才进行关联。
函数代码如下

static int input_attach_handler(struct input_dev *dev,

struct input_handler *handler)

{

// input_device_id 这个结构体表示设备的标识,存储了设备信息。

const struct input_device_id *id; /*输入设备的指针*/

int error;

//先判断 handler 的 blacklist 有无赋值,然后判断是否匹配

//blacklist 是一个 input_device_id *类型,指向了一个表,表中存放的是该驱动程序应该忽略的设备

if (handler->blacklist && input_match_device(handler->blacklist, dev))

return -ENODEV;

/*** 设备和处理函数之间的匹配 ***/

//匹配 handler->id_table指向的列表中的设备 和 dev->id 数据

id = input_match_device(handler->id_table, dev);

if (!id)

return -ENODEV;

//匹配成功则调用 handler->connect,连接 handler 和 input_dev

error = handler->connect(handler, dev, id);/*连接设备和处理函数*/

if (error && error != -ENODEV)

printk(KERN_ERR

“input: failed to attach handler %s to device %s, ”

“error: %d\n”,

handler->name, kobject_name(&dev->dev.kobj), error);

return error;

}

input_match_device()

这个函数用来将 input_dev 和 handler 进行匹配。
handler->id_table 中定义了其支持 input_dev 设备。

static const struct input_device_id *input_match_device(const struct

input_device_id *id,struct input_dev *dev)

{

int i;

//匹配 id 和 dev->id 中的信息

for (; id->flags || id->driver_info; id++) {

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;

MATCH_BIT(evbit, EV_MAX);

MATCH_BIT(keybit, KEY_MAX);

MATCH_BIT(relbit, REL_MAX);

MATCH_BIT(absbit, ABS_MAX);

MATCH_BIT(mscbit, MSC_MAX);

MATCH_BIT(ledbit, LED_MAX);

MATCH_BIT(sndbit, SND_MAX);

MATCH_BIT(ffbit, FF_MAX);

MATCH_BIT(swbit, SW_MAX);

return id;

}

return NULL;

}

在上面,只有 flags 中的信息匹配成功,或者 flags 没有定义才会调用下面。

#define MATCH_BIT(bit, max) \

for (i = 0; i < BITS_TO_LONGS(max); i++) \

if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \

break; \

if (i != BITS_TO_LONGS(max)) \

continue;

从宏定义中可以看到,
只有当 input device和input handler 的 ID 成员在 evbit、keybit、… swbit 项相同才会匹配成功。而且匹配的顺序是从evbit、keybit到swbit。只要有一项不同,就会循环到ID中的下一项进行比较。

总结:

1. input_allocate_device 在内存中为输入设备结构体分配空间并进行初始化。
2. input_register_device()->input_attach_handler()->input_match_device()
input_register_device
将input_dev 结构体注册到输入子系统核心中。主要操作是 初始化 input_dev 并将其 device_add 进 Linux 设备驱动模型中(在文件系统中创建 inputN 等文件);打印其路径;调用 input_attach_handler 匹配 handler 和 input_dev。
input_attach_handler
匹配 handler 和 input_dev。主要操作是,判断 dev 在不在 devices 的黑名单中,不在就 调用 input_match_device 进行匹配,成功就调用 handler->connect 连接设备和处理函数。
input_match_device
真正的 将 input_dev 和 handler 进行匹配。主要操作是匹配 id 和 dev->id 中的信息。包括 bustype、vendor、product、version 等;再匹配 evbit 事件类型、keybit 按键类型 等

二、input的使用及分析

Input对象描述了一个输入设备,包括它可能上报的事件,这些事件使用位图来描述,内核提供的相应的工具帮助我们构建一个input对象,大家可以参考内核文档“Documentation/input/input-programming.txt”,里面对于input子系统的使用有详细的描述。

初始化

初始化一个input对象是使用input子系统编写驱动的主要工作,内核在头文件“include/uapi/linux/input.h”中规定了一些常见输入设备的常见的输入事件,这些宏和数组就是我们初始化input对象的工具。这些宏同时用在用户空间的事件解析和驱动的事件注册,可以看作是驱动和用户空间的通信协议,所以理解其中的意义十分重要。在input子系统中,每一个事件的发生都使用事件(type)à子事件(code)à值(value)三级来描述,比如,按键事件à按键F1子事件à按键F1子事件触发的值是高电平1。注意,事件、子事件和值是相辅相成的,只有注册了事件EV_KEY,才可以注册子事件BTN_0,也只有这样做才是有意义的。

下面就是内核约定的事件类型,对应应用层的事件对象的type域

clip_image005

下面这些是按键子事件的类型,可以看到对PC键值的定义

clip_image007

除了对常用的事件进行描述,内核同样提供了工具将这些事件正确的填充到input对象中描述事件的位图中。

//第一种

//这种方式非常适合同时注册多个事件

button_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

button_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) |BIT_MASK(BTN_MIDDLE);

//第二种

//通常用于只注册一个事件

set_bit(EV_KEY,button_dev.evbit);

set_bit(BTN_0,button_dev.keybit);

//drivers/input/input.c

//创建一个input对象

struct input_dev *input_allocate_device(void);

//释放一个input对象

void input_free_device(struct input_dev *dev);

初始化好了一个input对象,接下来就需要将其注册到内核

//注册input对象到内核

int input_register_device(struct input_dev *dev);

//从内核注销一个input对象

void input_unregister_device(struct input_dev *dev);

驱动层报告事件

在合适的时机(由于输入最终是通过中断触发的,所以通常在驱动的中断处理函数中)驱动可以将注册好的事件上报,且可以同时上报多个事件,下面是内核提供的API

//上报指定的事件+子事件+值

void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);

//上报键值

void input_report_key(struct input_dev *dev,unsigned int code,int value);

//上报绝对坐标

void input_report_abs(struct input_dev *dev,unsigned int code,int value);

//报告同步事件

void input_report_rel(struct input_dev *dev,unsigned int code,int value);

//同步所有的上报

void input_sync(struct input_dev *dev);

上报事件有2点需要注意:

Ø Report函数们并不会真的上报,只是准备上报,sync才会真的将刚刚report的事件上报给input核心。

Ø Input核心会进行裁决再上报给事件处理层,所以对于按键事件,一定要先报1再报0(或者反过来),不能只report 1或0,这样核心会认为是一个事件被误触发了多次而只上报一次,虽然我们真的按下了多次。

应用层解析

事件处理层最终会将驱动sync一次时所有report的事件组织成一个struct input_value[]的形式上报到应用层,在应用层从相应的设备文件中获取上报的事件的时候,需要注意:

Ø 收到数组元素的数量会比底层多一个空元素,类似于写of_device_id[]时最后的空元素,这点应用层在解析的时候需要注意

Ø 事件处理层并不会缓存收到的事件,如果有新的事件到来,即使旧的事件没有被读取,也会被覆盖,所以应用程序需要及时读取。

前文已经说过,“include/uapi/linux/input.h”中的宏是应用层和驱动层共用的通信协议,所以应用层在解析收到的struct input_value对象,只需要“include <linux/input.h>”即可使用其中的宏。

input分析

上文已经说过,input子系统使用三层结构来实现驱动事件到应用层的传递。具体的,这三个层次每一个层次都由一条结构体链表组成,在设备驱动层,核心结构体是input_dev;在input核心层是input_handle;在事件处理层是input_handler。内核通过链表和指针将三者结合到一起,最终实现了input_dev和input_handler的多对多的映射关系,这种关系可用下图简单描述。

clip_image009

三、驱动模板

下面的这个模板首先使用input子系统上报按键事件,然后在应用层读取。

input按键设备驱动

/{
key@26{
                      compatible = “xj4412,key”;
                      interrupt-parent = <&gpx1>;
                      interrupts = <2 2>;
           };
};

static struct input_dev *button_dev;

static int button_irq;

static int irqflags;

static irqreturn_t button_interrupt(int irq, void *dummy)

{

input_report_key(button_dev, BTN_0, 0);

input_report_key(button_dev, BTN_0, 1);

input_sync(button_dev);

return IRQ_HANDLED;

}

static int button_init(void)

{

request_irq(button_irq, button_interrupt,irqflags, “button”, NULL)) ;

button_dev = input_allocate_device();

button_dev->name = “button”;

button_dev->evbit[0] = BIT_MASK(EV_KEY);

button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

input_register_device(button_dev);

return 0;

}

static int button_exit(void)

{

input_free_device(button_dev);

free_irq(button_irq, button_interrupt);

return 0;

}

static int key_probe(struct platform_device *pdev)

{

struct resource *irq_res;

irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

if(irq_res){

button_irq = irq_res->start;

irqflags = irq_res->flags & IRQF_TRIGGER_MASK;

}else{

return -EINVAL;

}

return button_init();

}

static int key_remove(struct platform_device *dev)

{

return button_exit();

}

struct of_device_id of_tbl[] = {

{.compatible = “xj4412,key”,},

{},

};

MODULE_DEVICE_TABLE(of, of_tbl);

struct platform_driver key_drv = {

.probe = key_probe,

.remove = key_remove,

.driver.name = “keydrv”,

.driver.of_match_table = of_tbl,

};

module_platform_driver_register(key_drv);

MODULE_LICENSE(“GPL”);

应用层获取键值

#include <linux/input.h>

struct input_event {

struct timeval time;

unsigned short type;

unsigned short code;

int value;

};

int main(int argc, char * const argv[])

{

int fd = 0;

struct input_event event[3] = {0}; //3!!!,驱动上传了2个事件,第三个用来装空元素

int ret = 0;

fd = open(argv[1],O_RDONLY);

while(1){

ret = read(fd,&event,sizeof(event));

printf(“ret:%d,val0:%d,val1:%d,val12:%d\n”,ret,event[0].value,event[1].value,event[2].value); //2!!!,最后一个是空

sleep(1);

}

return 0;

}

四、多点触控协议

多点触摸协议介绍

多点触摸协议(multi-touch, MT)是内核驱动可以对多个随意数量的触控事件上报详细的数据信息协议。基于硬件的能力,该协议被分为两种类型。对于只能处理匿名接触(type A)的设备,该协议描述了如何把所有的原始触摸数据发送给接收者。对于那些有能力跟踪并识别每个触摸点的设备(type B),该协议描述了如何把每个触摸点的单独更新通过事件slots发送给接收者。

B协议又称为slot协议,slot直译为位置、槽,有两层含义,一层是位置,另一层是容器。在input子系统中,它扮演的就是这两个角色。它产生于这样一个背景:

如果从Device获取的当前数据与上一个数据相同,我们有必要再上报当前数据吗?如果我们不管两次数据是否一致都上报,那就是A协议;如果我们选择不上报,那么既然需要比较,总需要把上一次数据存起来吧,slot就是做这个事情的,显然这就是Slot(B)协议。

我们都知道,在支持MT的手机上多指滑动的时候,多条手指滑动过的轨迹彼此是不相交的,这也是我们所期望的。但这个功能究竟是如何实现的呢?看了上面的分析应该就知道,A/B两种协议方式都可以实现该功能。

A协议不会使用slot,多指处理中,它的报点序列如下(每一个序列都以input_report_***函数实现):

ABS_MT_POSITION_X x[0]

ABS_MT_POSITION_Y y[0]

SYN_MT_REPORT

ABS_MT_POSITION_X x[1]

ABS_MT_POSITION_Y y[1]

SYN_MT_REPORT

SYN_REPORT

上面的序列中需要说明的是系统以SYN_MT_REPORT为一个点的信息的结尾,以SYN_REPORT为一次事件的结尾。也就是说多指触摸的时候,android的中间件部分每收到一次SYN_MT_REPORT就形成一个点信息,收到一个点之后并不会立即处理,而是一个事件完成之后才会处理,SYN_REPORT就是这个事件的标志。A协议比较简单,我们也可以发现在上面的序列中根本没有轨迹跟踪的信息,有的只是点坐标等信息,那么系统如何去判断当前的多个点各属于哪一条线呢?

我们假设前一次事件共5个点,本次触摸也有5个点,系统会分别计算前一次5个点与本次5个点的距离,distance[prev_i, curr_j](i=0,1,…4; j=0,1,…4),这样会产生总共5×5=25个数字。然后对这个25个数字进行排序,android用的是堆排序。下面的任务就是判断哪些当前点与前一次的点最近,那么赋予它们相同的id,应用收到这个信息后,就可以知道当前点属于哪条线了。

手抬起来的时候又用什么样的序列来通知系统呢:

SYN_MT_REPORT

SYN_REPORT

只有SYNC,没有其它任何信息,系统就会认为此次事件为UP。

B协议使用slot,还有一个新面孔TRACKING_ID:

ABS_MT_SLOT 0

ABS_MT_TRACKING_ID **

ABS_MT_POSITION_X x[0]

ABS_MT_POSITION_Y y[0]

ABS_MT_SLOT 1

ABS_MT_TRACKING_ID **

ABS_MT_POSITION_X x[1]

ABS_MT_POSITION_Y y[1]

SYN_REPORT

没有SYN_MT_REPORT,那么它用什么来跟踪当前点属于哪一条线呢,用的就是ABS_MT_TRACKING_ID,当前序列中某点的ID值,如果与前一次序列中某点的ID值相等,那么他们就属于同一条线。既然如此,那么android系统中还需要排序等运算吗?当然不需要。那么手指全部抬起的时候序列又是怎样的呢?

ABS_MT_SLOT 0

ABS_MT_TRACKING_ID -1

SYN_REPORT

ABS_MT_SLOT 1

ABS_MT_TRACKING_ID -1

SYN_REPORT

这里上报的ABS_MT_TRACKING_ID为-1,也只有这里该值才可以小于零,收到该值,系统就会清除对应的ID。看似简单的两个协议内容到这里就分析完毕了。

看了上面的分析,明显可以看出B协议要优于A协议,但事实上并不如此简单。B协议需要硬件上的支持,ID值并不是随便赋值的,而是硬件上跟踪了点的轨迹;如果硬件上满足不了这个条件,那么采用B协议只能闹成笑话。另外,B协议的复杂性如果掌握不好往往会带来一些莫名其妙的问题,比如如果因为某些因素(同步等),在UP的时候少清除了一个slot的信息,那么下次单击的时候也会惊奇地发现竟然有两个点(采用了B协议,slot已经保存了点信息,除非明确清除)。

多点触控协议linux文档(multi-touch-protocol.txt)

详细的触控信息被按顺序地分割为多个ABS_MT事件数据包进行发送。只有ABS_MT事件信息被识别为触控数据包的一部分,因为这些事件在当前的单点触控(single-touch,ST)应用中是被忽略的,我们可以在现有的驱动中基于ST协议之上来实现MT协议。

对于type A设备的驱动,在每个数据包的结尾用input_mt_sync()对多个触控包进行分割,这将会产生一个SYN_MT_REPORT事件,它通知接收者接收当前的触控信息并准备接收下一个信息。

对于type B设备的驱动,在每个数据包的开始,通过调用input_mt_slot()进行分割,同时带入一个参数:slot。这会产生一个ABS_MT_SLOT事件,它通知接收者准备更新给定slot的信息。

两种类型的驱动通常都通过调用input_sync()函数来标记一个多点触摸数据传送的结束,这通知接收者对从上一个EV_SYN/SYN_REPORT以来的所有累加事件作出响应,并准备接收新的一组事件/数据包。

无状态的type A协议和有状态的type B slot协议之间的主要区别是通过识别相同接触点来减低发送到用户空间的数据量。Slot协议需要使用到ABS_MT_TRACKING_ID,该ID要么由硬件来提供、要么通过原始数据来计算出来。

对于type A设备,内核驱动应该根据设备表面上全部有效触控生成任意序列事件。数据包出现在事件流中的顺序是不重要的。事件过滤和手指跟踪的工作留给用户空间来实现。

对于type B设备,内核驱动应该把每一个识别出来的触控和一个slot相关联,并使用该slot来传播触摸状态的改变。通过修改关联slot的ABS_MT_TRACKING_ID来达到对触摸点的创建、替换和销毁。一个非负数的跟踪id被解释为有效的触摸,-1则代表一个不再使用的slot。一个之前没有出现过的跟踪id被认为是一个新的接触点,当一个跟踪id不再出现时则认为该接触点已经被移除。因为只有变化的部分被传播,每个被启动的接触点的状态信息必须驻留在接收端。每当接收到一个MT事件,只需对当前slot的相关属性进行一次简单的更新即可。

有些设备可以识别和/或跟踪比它能报告给驱动要多的接触点,对于这种设备的驱动应该使得硬件上报的每一个接触点关联一个type B的slot。一旦识别到一个关联了slot的接触点发生了变化,驱动应该通过改变他的ABS_MT_TRACKING_ID使得该slot无效。如果硬件发出通知它跟踪到了比目前上报的还要多的接触点,驱动应该使用BTN_TOOL_*TAP事件知会用户空间此刻硬件跟踪的总的接触点数目已经改变。要完成此工作,驱动应该显式地发送BTN_TOOL_*TAP事件,并在调用input_mt_report_pointer_emulation()时把use_count参数设为false。驱动应该只通告硬件所能报告的slots数量。用户空间可以通过注意到最大支持的BTN_TOOL_*TAP事件大于在ABS_MT_SLOT轴的absinfo中报告的type B的slots总数,来检测驱动是否能报告比slots数还多的触控点。

Protocol Example A

对于一个两点触控的触摸信息,type A设备的最小的事件序列看起来像下面这样:

ABS_MT_POSITION_X x[0]

ABS_MT_POSITION_Y y[0]

SYN_MT_REPORT

ABS_MT_POSITION_X x[1]

ABS_MT_POSITION_Y y[1]

SYN_MT_REPORT

SYN_REPORT

实际上,在移动其中一个触控点后的上报序列看起来是一样的、所有存在触控点的原始数据被发送,然后在它们之间用SYN_REPORT进行同步。

当第一个接触点离开后,事件序列如下:

ABS_MT_POSITION_X x[1]

ABS_MT_POSITION_Y y[1]

SYN_MT_REPORT

SYN_REPORT

当第二个接触点离开后,事件序列如下:

SYN_MT_REPORT

SYN_REPORT

假如驱动在ABS_MT事件之外上报一个BTN_TOUCH或ABS_pressure事件,最后一个SYN­_MT_REPORT可以省略掉,否则,最后的syn_report会被input核心层扔掉,结果就是一个0触控点事件被传送到用户空间中。

Protocol Example B

对于一个两点触控的触摸信息,type B设备的最小的事件序列看起来就像下面这样:

ABS_MT_SLOT 0

ABS_MT_TRACKING_ID 45

ABS_MT_POSITION_X x[0]

ABS_MT_POSITION_Y y[0]

ABS_MT_SLOT 1

ABS_MT_TRACKING_ID 46

ABS_MT_POSITION_X x[1]

ABS_MT_POSITION_Y y[1]

SYN_REPORT

Id 45的触控点在x方向移动后的事件序列如下:

ABS_MT_SLOT 0

ABS_MT_POSITION_X x[0]

SYN_REPORT

Slot 0 对应的接触点离开后,对应的事件序列如下:

ABS_MT_TRACKING_ID -1

SYN_REPORT

上一个被修改的slot也是0,所以ABS_MT_SLOT被省略掉。这一消息移除了接触点45相关联的slot 0,于是接触点45被销毁,slot 0 被释放后可以被另一个接触点重用。

最后,第二个接触点离开后的时间序列如下:

ABS_MT_SLOT 1

ABS_MT_TRACKING_ID -1

SYN_REPORT

参考:

https://www.cnblogs.com/chenfulin5/p/5703015.html

https://www.cnblogs.com/xiaojiang1025/p/6414746.html

https://www.2cto.com/kf/201310/248141.html

https://blog.csdn.net/dearsq/article/details/51381671