Android Input系统介绍与kcm与kl文件的定制

来源:互联网 发布:菜鸟网络盈利模式 编辑:程序博客网 时间:2024/05/16 18:54

这篇文章将以TOUCH DRIVER为例子,介绍INPUT系统的DRIVER上报过程,另外会把主要精力放在KCM与KL文件的定制原理中。

PART 1 : INPUT系统输入事件处理机制

linux中input系统的主设备号是 13(可用ls –l cmd查看)

次设备号:

0-31    joystick(游戏杆)

32-62   mouse(鼠标)

63      mice(鼠标)

64-95   事件(Event)设备 (touch,keyboard…)

Kernel中架构大致如下:

设备驱动层              核心层                      事件层                                  用户空间

                                                         Mouse Handler 

Input设备<–>       Input Core   <–>     keyboard Handler    <–>          设备节点访问

                                                         TP Handler

Input 系统kernel driver中的核心文件为linux/kernel/include/linux/input.h (Android 4.0版本之前)或 linux/kernel/include/linux/uapi/input.h(Android 4.3或更新版本,其中详细定义了Input 系统上报的基本格式和数据结构。

下面我们开始了解下这些基本的格式和数据结构。

Input event 基本格式
Type code value

数据结构
  /**
   * struct input_value – input value representation
  * @type: type of value (EV_KEY, EV_ABS, etc)
  * @code: the value code
  * @value: the value
  */
          struct input_value {
        __u16 type;
        __u16 code;
        __s32 value;
          };

分析kernel中的Input上报事件,有个十分好用的工具小助手:getevent tool
具体的使用方法有兴趣的朋友可以自行百度,这里我仅介绍一个最常用的命令:

getevent -i 

这个命令会详细的将系统中所有input类别和对应的driver node详尽的列出来,是我们的好帮手!

Tips:通过cat /sys/class/input/input(X)/name也可获取对应的input name

PART 2 :TP协议及DRIVER处理过程

Touch 基本协议 –multi-touch-protocol(type A and B) The multi-touch-protocol is divided into two types, depending on the capabilities of the hardware.

For devices handling anonymous contacts (type A) :硬件无法直接区分出触摸点,故直接将得到的raw档上传给user space处理。

For devices capable of tracking identifiable contacts (type B) :硬件有能力区分出触摸点,通过slot的方式处理多点讯息。 Get detail


reference: /code dir/linux/kernel/Documentation/input/multi-touch-protocol.txt 

Protocol Example A
——————

Here is what a minimal event sequence for a two-contact touch would look
like for a type A device:

   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

Protocol Example B
——————

Here is what a minimal event sequence for a two-contact touch would look
like for a type B device:

   ABS_MT_SLOT 0                     //ABS_MT_SLOT 一个slot代表一个触摸点
   ABS_MT_TRACKING_ID 45       //一个触摸点会绑定一个TRACKING_ID
   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

Here is the sequence after moving contact 45 in the x direction:

   ABS_MT_SLOT 0 
   ABS_MT_POSITION_X x[0]
   SYN_REPORT                    //这是一种省略的写法,可简单理解为y轴,
                                         //TRACKING_ID等均和上次的报点一样,仅x轴变化

Here is the sequence after lifting the contact in slot 0:

   ABS_MT_TRACKING_ID -1  //通过将TRACKING_ID设为-1,可以移除slot和TRACKING_ID 45
                                        //间的联系,这样slot 0就可以和下一个TRACKING_ID关联了。
   SYN_REPORT                   //注意:TRACKING_ID省略说明和上次一样,即为0。

Finally, here is the sequence after lifting the second contact:

   ABS_MT_SLOT 1              //同理移除slot 1与TRACKING_ID 46间的联系做法如此
   ABS_MT_TRACKING_ID -1
   SYN_REPORT

input设备核心结构体

struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;

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)];

unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    …..

   };

/**
 * struct input_dev – represents an input device
 * @name: name of the device
 * @phys: physical path to the device in the system hierarchy
 * @uniq: unique identification code for the device (if device has it)
 * @propbit: bitmap of device properties and quirks
 * @evbit: bitmap of types of events supported by the device 
 *  (EV_KEY,EV_REL, etc.)
 * @keybit: bitmap of keys/buttons this device has
  *  (KEY_HOME, KEY_MENU, etc. )
 * @relbit: bitmap of relative axes for the device
 * @absbit: bitmap of absolute axes for the device
 * @mscbit: bitmap of miscellaneous events supported by the device
 * @sndbit: bitmap of sound effects supported by the device
 * @ffbit: bitmap of force feedback effects supported by the device
 * @swbit: bitmap of switches present on the device
 ….
 */

TP driver 实例(ftxxxx_ts.c) 核心函数分析:

TP prob函数中的关键初始化部分:
input_dev = input_allocate_device();

ftxxxx_ts->input_dev = input_dev;

set_bit(KEY_BACK, input_dev->keybit);     //注册TP上的key键
set_bit(KEY_HOME, input_dev->keybit);
set_bit(KEY_MENU, input_dev->keybit);
set_bit(KEY_POWER, input_dev->keybit);  //双击唤醒

__set_bit(EV_ABS, input_dev->evbit);        //@evbit: bitmap of types of events supported by the device (EV_KEY,EV_REL, etc.)
__set_bit(EV_KEY, input_dev->evbit);

input_mt_init_slots(input_dev, CFG_MAX_TOUCH_POINTS, 0); //#define CFG_MAX_TOUCH_POINTS 10

//*****void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);   //(Finger Size) #define PRESS_MAX     0xFF

//****补充: input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 31, 0, 0); //(Touch Size)
input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, TOUCH_MAX_X, 0, 0);  //#define TOUCH_MAX_X 480
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, TOUCH_MAX_Y, 0, 0);  //#define TOUCH_MAX_Y 854

input_dev->name = TOUCH_INPUT_NAME;   //#define TOUCH_INPUT_NAME “himax-touchscreen”
err = input_register_device(input_dev);

当有人触摸tp后,input event的上报过程:

 tp上的key部分: 
input_report_key(data->input_dev, KEY_BACK, 1);
input_sync(data->input_dev);

 tp部分
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true);
input_report_abs(data->input_dev, ABS_MT_PRESSURE, event->pres);
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->au16_x[i]);
input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->au16_y[i]); 

相对原始的格式如下:
input_report_key(ts->input_dev, BTN_TOUCH, 1);             // touch down
input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, i);     //ID of touched point
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, area); //Finger Size
input_report_abs(ts->input_dev, ABS_MT_PRESSURE, press);   // Pressure
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);     // X axis
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);     // Y axis 

input_mt_sync(ts->input_dev);
input_sync(ts->input_dev);

PART 3:IDC  KL  KCM文件介绍及流程分析

Idc (Input Device Configuration)
输入设备配置文件,它包含设备具体的配置属性,这些属性影响输入设备的行为。

Android基于输入设备驱动汇报的事件类型(绝对或者相对坐标)和属性来检测和配置大部分输入设备,然而有些分类是模棱两可的,如:多点触摸屏和touch pad都支持EV_ABS事件类型 和ABS_MT_POSITION_X和ABS_MT_POSTION_Y事件,然而这两类设备的使用是不同的,且不总是能自动判断。

所以,需要对android系统提供另外的信息来指示该设备上报event的真正含义。因此,触摸设备,特别是内嵌的touch screen,一般都需要有idc文件。

kl (KeyLayout)        kcm (KeyCharacterMap)


用来定义按键布局文件和按键字符映射需要的配置文件,这些配置文件的后缀名分别为kl和kcm。

当有按键响应时,内核传给ANDROID的是scancode,ANDROID将scancode经配置表(如qwerty.kl)找到keycode标识符,然后由内部表KEYCODES列表找到keycode的数字值,再由keycode的数字值经字符配置表(如qwerty.kcm)找到对应的字符值。


让我们来做个实验:将tp 上的多功能键更换为其他按键功能

首先,我介绍下TP 的 KL文件中各项所对应的意义:

</system/usr/keylayout/himax-touchscreen.kl>
key 158     BACK                 VIRTUAL WAKE_DROPPED
key 102     HOME                VIRTUAL WAKE
key 139     APP_SWITCH     VIRTUAL WAKE_DROPPED

第一列Key: 表示一行有效的开始,注释行用#开头
第二列表示Scancode: 是键盘物理设备的矩阵扫描码值
第三列表示系统里面的按键码Keycode
第四列表示KeyCode的Flag信息,可有可无(如果是虚拟按键,可多补上一个VIRTUAL参数,这样就能让其点击时发生震动,例如virtual这个参数是在phonewindowsmanager.java中处理,确定是否需要启动按键反馈功能,其他的参数则分别会在各自的某个环节中进行处理。)
常见如下三种状态:
 * 空        没有附加信息
 * WAKE      当机器处理Sleep状态,可以唤醒,按键信息继续被处理
 * WAKE_DROPPED  当机器处于Sleep状态,可以唤醒,但是丢弃按键信息

我们只需要将第二列所对应的功能做个替换,然后push回机器里,重启后就可以发现,功能键被替换掉了。很好玩吧?哈

下面我们开始代码级剖析EventHub.cpp中的核心代码


我们将会逐步解剖核心函数:openDeviceLocked()的所作所为


1:通过ioctl去扫描input设备的详细讯息:bus、vendor、product、version、name、location、descriptor、driver、是否是KEY设备,ads设备,res设备等等 有了这些讯息,Eventhub就有能力通过这些讯息对这个设备上报的event进行针对性解析。

Ex:
 if(ioctl(fd, EVIOCGNAME(sizeof(buffer) – 1), &buffer) < 1) {
        //fprintf(stderr, “could not get device name for %s, %s\n”, devicePath, strerror(errno));
    } else {
        buffer[sizeof(buffer) – 1] = ‘\0’;
        identifier.name.setTo(buffer);
    }

2:通过loadConfigurationLocked(device)函数查找该设备对应的idc文件。

该函数中调用了函数getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier , type)来进行具体解析,注意其中的type参数说明需要查找的文件是idc,kl或kcm三者中的哪种。

在loadConfigurationLocked函数中,默认传入的type为查找idc文件。

由于后面查找kl和kcm也会用到这个函数,所以我们先分析它究竟做了什么

String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
        if (deviceIdentifier.version != 0) {
            // Try vendor product version.
            String8 versionPath(getInputDeviceConfigurationFilePathByName(
                    String8::format(“Vendor_%04x_Product_%04x_Version_%04x“,
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type));
            if (!versionPath.isEmpty()) {
                return versionPath;
            }
        }
        // Try vendor product.
        String8 productPath(getInputDeviceConfigurationFilePathByName(
                String8::format(“Vendor_%04x_Product_%04x“,
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type));
        if (!productPath.isEmpty()) {
            return productPath;
        }
    }
    // Try device name.
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}

当中函数
String8 getInputDeviceConfigurationFilePathByName(
        const String8& name, InputDeviceConfigurationFileType type) {
    // Search system repository.
    String8 path;
    path.setTo(getenv(“ANDROID_ROOT”));
    path.append(“/usr/“);
    appendInputDeviceConfigurationFileRelativePath(path, name, type); //核心函数
if (!access(path.string(), R_OK)) {
ALOGD(“Found”);
return path;
    }

    // Search user repository. // TODO Should only look here if not in safe mode.
    path.setTo(getenv(“ANDROID_DATA”));
    path.append(“/system/devices/“);         //Will never into this situation
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
if (!access(path.string(), R_OK)) {
ALOGD(“Found”);
return path;
    }
    //IF  Not found.
       ……
    return String8();
}

函数 appendInputDeviceConfigurationFileRelativePath{

  for (size_t i = 0; i < name.length(); i++) {
        char ch = name[i];
        if (!isValidNameChar(ch)) {
            ch = ‘_’;
        }
        path.append(&ch, 1);
    }
….
}

函数
static bool isValidNameChar(char ch) {
    return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == ‘-‘ || ch == ‘_’);
}

可见,如果input设备name中功能含有空格,这个函数会把其中的空格直接补成下划线”_”。

想必到这里,大家就能明白,为什么有的input设备名称是带空格的,可对应的kl文件都是下划线,这样一来,也是能够匹配上的!

3:查找完idc文件后,下面会继续调用loadKeyMapLocked函数调来查找kl和kcm文件。

该函数中调用到了
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
        const PropertyMap* deviceConfiguration) 函数

该函数中会逐步调用如下两个函数:
 loadKeyLayout(deviceIdenfifier, keyLayoutName);
 loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);

这两个函数的目的是扫描该设备对应的kl和kcm文件。

实际上,这两个函数中同样都调用到了前面所分析的getInputDeviceConfigurationFilePathByDeviceIdentifier函数,用来进行文件匹配。区别只是传入其中的type参数不同,从而说明分别需要查找的文件是kl和kcm。

补充:
若在
  loadKeyLayout(deviceIdenfifier, keyLayoutName);
 和
  loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);

两个函数的基本查找过程中,发现找不到kcm和kl文件
则会在查询过程的最后,启动两种最终的匹配尝试方法,即:查找以Generic或者Virtual开头的kcm和kl文件。
(注意,前面介绍的对于idc文件搜索过程,是不会去做这种尝试的,如果匹配不到就直接附空)

代码截取:
    if (probeKeyMap(deviceIdenfifier, String8(“Generic”))) {
        return OK;
    }

    // Try the Virtual key map as a last resort.
    if (probeKeyMap(deviceIdenfifier, String8(“Virtual”))) {
        return OK;
    }

Tips:

特别需要留意的一点是,目前只有在EventHub初始化时,发现该设备是keyboard或Joystick设备(gpio key也算是keyboard 设备),才会调用loadKeyMapLocked函数去初始化key转化机制。(like gsensor、psensor .etc 则不会)

    // Load the key map.
    // We need to do this for joysticks too because the key layout may specify axes.
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
        // Load the keymap for the device.
        keyMapStatus = loadKeyMapLocked(device);
    }

PART 4:虚拟按键功能的实现原理

虚拟按键指的是例如nexus手机下方的三个虚拟按键(返回,home,菜单)

实现虚拟按键的方法常见的有如下两种:

1. 在TP driver中直接报键值(kernel中处理)
(前面已经讲过)

2. TP driver上报一个坐标,上层通过KL的解析来判定是哪个键。
我们主要来介绍这种情况

TP虚拟按键的kl 被存放在/sys/board_properties/virtualkeys.devicename中,pull出来后可以看看内容:

(以milestone为例)

0x01:158:32:906:63:57:

0x01:139:162:906:89:57: 

0x01:102:292:906:89:57: 

0x01:217:439:906:63:57 

 
格式解释如下:
 0x01:
 A version code. Must always be 0x01.

  <Linux key code>: The Linux key code of the virtual key. 

<centerX>: The X pixel coordinate of the center of the virtual key.

<centerY>: The Y pixel coordinate of the center of the virtual key. 

<width>: The width of the virtual key in pixels. <height>: The height of the virtual key in pixels. 


丛上面可以看出,该设备定义了back,menu,home,search四个按键,具体的区域也是一清二楚。

通过上面的流程下来,想必大家对input子系统的大概流程有了一定的了解,细节太多不太好展开。

如果有疑问,大家可以与我在交流互动。



0 0
原创粉丝点击