Input子系统与TP驱动
对于众多的输入设备的驱动问题,linux提供了一套非常灵活的机制:input子系统。通过它我们只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。input输入子系统由输入子系统驱动层,核心层(Input Core),和事件处理层(Event Handler)三部分组成。

驱动层:负责和具体的硬件设备交互,采集输入设备的数据信息,通过核心层提供的API上报数据;
核心层:为事件处理层和设备驱动层提供接口API,起到一个中间层的作用;
事件处理层:通过核心层的API获取输入事件上报的数据,定义API与应用层交互。

主要是三大结构体所建立的联系,沟通了input子系统,他们在
kernel-4.4/include/linux/input.h中有定义:
struct input_dev:会在具体设备驱动层中被填充
struct input_handle:会在事件处理层和设备驱动层注册设备时通过input_dev或input_handler间接调用
struct input_handler:会在事件处理层如evdev.c中被实例化

浅析三大结构体关系

input_handle是连接input_dev和input_handler的桥梁,input_dev可以通过input_handle找到input_handler,同样的input_handler可以通过input_handle找到input_dev

一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应evdev_handler,也可以对应mouse_handler,因此当其注册时与系统中的handler进行匹配,

就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle,至于以何种方式来传递事件,就由用户程序打开哪个实例来决定

后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备

在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个。

以MTK的TP驱动为例贯穿讲解输入子系统:

MTK平台的TP驱动是分为两个部分组合在一起的,全平台的共享驱动mtk_tpd.c(抽象),以及各个型号TP的独立驱动(真实),mtk_tpd.c负责将TP注册到platform总线,以及利用input子系统核心层提供的API向事件处理层上报键值,各个型号的独立驱动负责I2C总线挂接,读取键值提交给mtk_tpd.c。mtk_tpd.c做的重要的一件事就是注册platform平台总线,对设备的申请tpd->dev = input_allocate_device();
==> input_register_device(tpd->dev)注册输入设备,一些事件的属性设置,以及对各型号TP的兼容遍历,都是在其probe函数(mtk_touch_driver函数的.of_match_table = touch_of_match的compatible = "mediatek,mt6739-touch"与在mt6739.dts注册的设备device touch: touch compatible = “mediatek,mt6739-touch”;相同,就执行tpd_probe函数)中完成的。

注册input device的过程就是为input device设置默认值,

==> list_add_tail(&dev->node, &input_dev_list)将新分配的input设备连接到input_dev_list链表上并与挂在input_handler_list链表中的handler相匹配

==> 调用input_attach_handler(dev, handler);去匹配

==> 调用input_match_device匹配(关于input_match_device函数,它是通过匹配id来确认匹配的,看handler的id是否支持),

所有的input_dev挂载到input_dev_list 链表上,所有的handler挂载到input_handler_list上),如果匹配成功就会调用handler的connnect函数,

==> connnect函数是在事件处理层定义并实现的,

以evdev.c为例,则connect函数就是 ==> evdev_connect。evdev_connect()函数主要用来连接input_dev和input_handler,这样事件的流通链才能建立,流通链建立后,事件才知道被谁处理,或者处理后将向谁返回结果。

总结:
TP的操作就是底层将信息储存在 /sys/class/input/eventn 中,然后上层对其进行读取识别,然后根据其中的信息进行事件处理。

--------------------------------------------- kernel层 --------------------------------------------------

驱动层:(在具体的设备驱动文件中注册driver/input/touchscreen)
1、注册input_dev,进入input_register_device()
(1)把input_dev添加到input_dev_list链表中
list_add_tail(&dev->node, &input_dev_list);

(2)判断input_handler的id,是否有支持这个设备的驱动
list_for_each_entry(handler, &input_handler_list, node); //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。

事件处理层:(./kernel-3.18/drivers/input/evdev.c)
2、注册input_handler,进入input_register_handler()
(1)把input_handler添加到input_handler_list链表中
list_add_tail(&handler->node, &input_handler_list);

(2)判断input_dev的id,是否有支持这个驱动的设备
list_for_each_entry(dev, &input_dev_list, node); //遍历查找input_dev_list链表里所有input_dev
input_attach_handler(dev, handler);//判断两者id,若两者支持便进行连接。

3、判断input_handler和input_dev的id,进入input_attach_handler()
(1)匹配两者id
id = input_match_device(handler, dev); //匹配input_handler和dev的id,匹配不成功退出函数

(2)匹配成功调用input_handler ->connect
error = handler->connect(handler, dev, id); //匹配成功建立连接

核心层:(kernel-4.4/drivers/input/input.c)
4、建立input_handler和input_dev的连接,进入input_handler->connect()
(1)input_register_handle函数,将handle通过d_node挂到input device的h_list,通过h_node挂到handler的h_list上,两者的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.dev和handler,便能找到对方,便建立了连接connect
list_add_tail_rcu(&handle->d_node, &dev->h_list); //连接input_dev->h_list
list_add_tail_rcu(&handle->h_node, &handler->h_list); //连接input_handler->h_list

5、有事件发生时,比如触摸中断,在中断函数中需要进入input_event()上报事件,TP上报流程如下
驱动中调用input_report_abs上报绝对坐标

 ->input_event ------ input.h->input_handle_event ------ input.c->dev->event(dev, type, code, value); ------input.c->evdev.c/evdev_event() ------ evdev.c->evdev_events ------ evdev.c-> evdev_pass_values ------ evdev.c

然后将 type、value、code 存储在 evdev_client 的 struct input_event buffer[] 中,input_event buffer存放在一个设备节点文件,在evdev_connect中注册生成了 /sys/class/input/event%d ,这个字符设备文件就是连接kernel与framework的桥梁了。

----------------------------------------接下来到framework层---------------------------------------------

6、再看 framework 上层怎么读取这个文件中的 buffer 的,我们从 InputReader.cpp 来分析
在frameworks/native/services/inputflinger/InputReader.cpp中

bool InputReaderThread::threadLoop() {
mReader->loopOnce();
->void InputReader::loopOnce() {  int32_t oldGeneration;  int32_t timeoutMillis;  ...  size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

跟踪到在构造函数里,mEventHub 是 eventHub 的实例,那么就是调用 eventHub 的 getEvents 方法。

在frameworks/native/services/inputflinger/EventHub.cpp中

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {  ...  for (;;) {  ...  scanDevicesLocked(); //这个往里走就是通过EventHub::openDeviceLocked  //打开*DEVICE_PATH = "/dev/input" 这个设备 ,//最终用的open,实际到kernel层就是input设备注册的open
...  int32_t readSize = read(device->fd, readBuffer, sizeof(struct input_event) * capacity); //这里的device->fd就是/dev/input/eventn这个设备文件,就是从这里读取出event的buffer

再往上就是对这些数据的处理了。

下图就是对应的流程框架图:

事件上报流程:

一旦上层打开设备文件就会调用==> evdev_open函数,evdev_open分配并初始化一个client结构体,并将它和evdev关联起来,关联的内容是,将client->evdev指向它所表示的evdev,
==> 调用evdev_attach_client()将client挂到evdev->client_list上,我们驱动层上报的输入事件
的键值,就是存放在evdev->buffer中的。

==> 调用evdev_open_device()函数,通过调用核心层input.c中的input_open_device函数实现的,打开输入设备使设备准备好接收或者发送数据。

==> 调用evdev_read函数实现事件处理层对上层的读操作,我们的事件处理层对上层的读操作,用一个等待队列wake_up_interruptible(&evdev->wait)实现阻塞,这样就能保证,我们只有在触摸按键事件发生,中断到来,我们才去上报按键事件,并唤醒阻塞,让事件处理层的evdev_read将键值最终通过copy_to_user送到用户空间。

mtk的mtk_tpd.c怎么做兼容多个TP:

关键代码:遍历mtk的tpd_driver_list里面的所有的驱动,判断名字是否为NULL,每一个module touch IC驱动都会添加到这个静态数组里面对于if (tpd_load_status == 1)这个条件,
会判断我们所遍历的每一个module IC驱动的初始化函数,probe成功即i2c通信成功的话就会将tpd_load_status置1(具体驱动的probe函数中),所以我们就是通过这个值判断哪一个驱动的。

具体TP ic驱动里面,主要进行IC的上电、申请中断号注册中断处理函数、Update FW等动作。

重点有两个函数:事件处理线程、中断处理函数

中断处理函数:

一旦触摸事件发生,则触发中断,此中断处理函数唤醒等待队列。

static irqreturn_t tpd_eint_interrupt_handler(void)
{TPD_DEBUG_PRINT_INT;tpd_flag = 1;wake_up_interruptible(&waiter);//唤醒等待队列return IRQ_HANDLED;
}

事件处理线程:

static int touch_event_handler(void *unused)
{...sched_setscheduler(current, SCHED_RR, &param);do {set_current_state(TASK_INTERRUPTIBLE);//设置Task 的状态为可中断的等待状态if (tpd_eint_mode) {wait_event_interruptible(waiter, tpd_flag != 0);//满足tpd_flag!=0 就唤醒队列tpd_flag = 0;//改变条件} else {msleep(tpd_polling_time);}set_current_state(TASK_RUNNING);//设置Task 的状态为执行态...#if defined(CONFIG_GTP_SLIDE_WAKEUP)if (DOZE_ENABLED == doze_status) {ret = gtp_i2c_read(i2c_client_point, doze_buf, 3);GTP_DEBUG("0x814B = 0x%02X", doze_buf[2]);if (ret > 0) {if (0xAA == doze_buf[2]) {GTP_INFO("Forward slide up screen!");doze_status = DOZE_WAKEUP;input_report_key(tpd->dev,KEY_POWER, 1);input_sync(tpd->dev);input_report_key(tpd->dev,KEY_POWER, 0);input_sync(tpd->dev);/* clear 0x814B */doze_buf[2] = 0x00;gtp_i2c_write(i2c_client_point,doze_buf, 3);} else if (0xBB == doze_buf[2]) {GTP_INFO("Back slide up screen!");doze_status = DOZE_WAKEUP;input_report_key(tpd->dev,KEY_POWER, 1);input_sync(tpd->dev);...}

开启这个线程的目的是读取坐标,上报坐位给上层使用;它会在循环内轮询触摸事件,但触摸事件是随机的,所以用等待队列实现阻塞。

只有当触摸事件的中断到来,才唤醒队列,通过I2C通信gtp_i2c_read读取数据,之后通过input_report_xx和input_sync函数上报坐标。

补充:TP input_report_xx上报的内容一般有(从TP驱动ft6336s/focaltech_core.c截取部分代码):

static void tpd_down(int x, int y,int press, int id)
{if ((!press) && (!id)){input_report_abs(tpd->dev, ABS_MT_PRESSURE, 100);input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 100);}else{input_report_abs(tpd->dev, ABS_MT_PRESSURE, press); //上报手指按下还是抬起的状态input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, press);/* track id Start 0 */input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, id);//id可以不用上报,上层可以自动匹配}input_report_key(tpd->dev, BTN_TOUCH, 1); //上报按键状态input_report_abs(tpd->dev, ABS_MT_POSITION_X, x); //上报x轴坐标input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y);//上报y轴坐标input_mt_sync(tpd->dev);//同步上报事件,通知上层完成一次上报TPD_DEBUG_SET_TIME;TPD_EM_PRINT(x, y, x, y, id, 1);tpd_history_x=x;tpd_history_y=y;
#ifdef TPD_HAVE_BUTTONif (FACTORY_BOOT == get_boot_mode()|| RECOVERY_BOOT == get_boot_mode()){tpd_button(x, y, 1);//虚拟按键的处理,x和y的数据还有按键状态:按下和释放}
#endifTPD_DOWN_DEBUG_TRACK(x,y);}

窗体顶端
1.tp driver的tpd_down()和tpd_up()函数中不需要上报id号,上层会自动进行匹配;
2.tpd_up()函数中只需要上报BTN_TOUCH和mt_sync信息,其他信息不用上报,如下:
窗体顶端

static  void tpd_up(int x, int y,int *count)
{input_report_key(tpd->dev, BTN_TOUCH, 0);//printk("U[%4d %4d %4d] ", x, y, 0);input_mt_sync(tpd->dev);TPD_EM_PRINT(x, y, x, y, 0, 0);if (FACTORY_BOOT == get_boot_mode()|| RECOVERY_BOOT == get_boot_mode()){   tpd_button(x, y, 0); }
}

---------- 爱生活,爱安卓,爱Linux ----------

Linux输入子系统浅析相关推荐

  1. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons 1 .. ...

  2. [arm 驱动]Linux输入子系统分析

    首先说明一下,本文是基于Linux-2.6.38版本内核来分析Linux输入子系统架构和原理的.这阵子本来没有打算花时间来分析Linux input system的,然而当在研究S3C6410触摸屏驱 ...

  3. Linux 输入子系统原理理解(原创)

    linux    输入子系统原理理解(原创) 以前学了单独的按键设备驱动以及鼠标驱动,实际上,在linux中实现这些设备驱动,有一种更为推荐的方法,就是input输入子系统.平常我们的按键,触摸屏,鼠 ...

  4. Linux输入子系统框架

    输入子系统 自己写的驱动程序,自己可以调用,我们自己写驱动的流程一般是,建立fops结构,使用register_chrdev在初始化函数中进行注册,在应用中使用open函数打开该设备.这种驱动不标准只 ...

  5. linux abs函数怎么用,Linux输入子系统详解

    原标题:Linux输入子系统详解 纯手工打造每一篇开源资讯与技术干货,数十万程序员和Linuxer已经关注. 导读 linux输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如 ...

  6. 4. Linux - 输入子系统框架详解

    输入子系统概述 Linux内核为了能够处理各种不同类型的输入设备,比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ,设计并实现了为驱动层程序的实现提供统一接口函数:为上层应用提供试图统一的抽象层 , 即是 ...

  7. linux驱动通过地址配置按键,linux输入子系统之多个按键

    文章目录 linux输入子系统:驱动多个按键 驱动多个按键 一个按键有多个与其相关的元素: 中断号码 按键状态 按键的值 input_dev的详细描述如下图 在设备树文件中增加以下信息: 按键定义配置 ...

  8. linux输入子系统

    输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如鼠标移动.键盘按下等通过Driver->Inputcore->Event handler->userspac ...

  9. Linux输入子系统:输入设备编程指南 -- input-programming.txt

    输入设备编程指南(Programming input drivers) ~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 新建一个输入设备驱动程序 ~~~~~~~~~~~~~~~~~~~~~~ ...

  10. 137.Linux输入子系统基本概念

    文章目录 0.前言 1.简介 2.框架 3.输入子系统核心层 4.输入子系统驱动编写 4.1 基本变量 4.2 input_dev的注册 4.2.1 设置事件值的方法代码 4.3 上报输入事件 4.4 ...

最新文章

  1. CSS的四种引入方式
  2. VLAN与trunk配置
  3. 自定义ui_如何允许用户自定义UI
  4. java 语音匹配,java-语音识别,是否可以通过正则表达式确定用户所说的内容?
  5. skywalking使用方法_skywalking 6.2配置相关和使用
  6. 一些c中常用的功能函数
  7. C语言学习笔记---嵌套结构体
  8. OpenCasCade中一个V3d_Viewer对应多个AIS_InteractiveContext的使用
  9. 寿司大厨Sushiswap挥刀Uniswap背后:或将消灭传统VC
  10. 化繁从简,别让思维打了结
  11. 国家一级职业资格证书 计算机类有哪些
  12. 文件排版(文本文件读写)
  13. 如何找到共享的打印机
  14. Qt中文乱码原因及解决方案
  15. 第3.1章:StarRocks数据导入--Insert into
  16. 【笔记】ARM指令系统
  17. hdu 5418 Victor and World (floyd+状压dp)
  18. 电路中的过压保护和过流保护的区别
  19. 1411: 喜闻乐见的a+b(20进制)
  20. 自己动手实现主题搜索引擎

热门文章

  1. JavaScript ECMAScript版本介绍
  2. 第四次作业(胡明浩)
  3. Ubuntu server搭建vsftpd小记
  4. 拒绝访问(Access Denied)错误的快捷诊断方法
  5. Python动态数据展示
  6. 【每日算法Day 87】今天我脱单了,所以大家不用做题了!
  7. 数据结构与算法python—13.堆及python实现与leetcode总结
  8. 机器学习—XGBoost实战与调参
  9. 吴恩达深度学习——提高算法运行效率
  10. Java网络编程之IP地址