Skip to main content
 首页 » 编程设计

Android输入系统(2)——input驱动分析(Multi-Touch,配置文件)

2022年07月19日20bluestorm

一、Linux输入子系统框架

1. 查看系统中输入设备的信息
# cat /proc/bus/input/devices 查看系统中所有的输入设备节点的详细信息
# cat /proc/bus/input/handlers 查看系统中所有注册的handler的信息
# ls /sys/class/input/ 查看系统中所有的输入设备节点在/sysfs中的信息。ls -l可以看出sysfs文件路径名,有的包含外设接口。注意权限可读可写。
# ls /dev/input/ 查看系统中所有的输入设备的设备文件

2. Linux内核中输入子系统框架

输入子系统的核心层是input.c,它里面 register_chrdev(INPUT_MAJOR, "input", &input_fops); 是系统调用访问输入设备驱动的入口,因为对驱动的查找是通过主设备号进行的。在 input_fops.open() 中它会根据次设备号的分布来决定 struct file->f_op 分派为对应 handler 中的 file_operations 结构体,从而将对应的输入设备测操作交由对应的 handler 去处理。注:5.10内核中注册没有 input_fops 成员了。

handler通过 input_register_handler() 来注册,evdev.c 注册的设备的次设备号 [64, 93],对应的设备节点名为 eventX; mousedev.c 注册的设备的次设备号为 [32, 63],其中 [32, 62] 对应的设备节点名为 mouseX,63对应的设备节点名为 mice; joydev.c 注册的设备的次设备号为[0, 15],对应的设备节点名为 jsX。

输入设备的驱动使用 input_register_device(struct input_dev *dev) 来注册一个 input_dev 结构,上面的文件通过 input_register_handler(struct input_handler *handler)
来注册一个 input_handler 结构。任意一端的注册都会触发匹配,匹配上后就会触发 handler->connect() 被调用,connect() 中传的参数id是device和handler匹配上的那个id。在这个函数中一般会创建/dev/下的设备节点,因为此时设备和驱动匹配上了才能支持上层的读写,才需要创建设备文件。初始化一个 input_handle 结构(注意不是 input_handler),并调用 input_register_handle(struct input_handle *handle) 进行注册,一个 handle 表示一对匹配上的 input_dev 和 input_handler,保存有指定他两个的指针(input_handle 的存在并没有太大意义)。

shell@tiny4412:/dev/input # ls -l 
total 0 
crw-rw----    1 0        1004       13,  64 Jan  1 12:00 event0 
crw-rw----    1 0        1004       13,  65 Jan  1 12:00 event1 
crw-rw----    1 0        1004       13,  66 Jan  1 12:00 event2 
crw-rw----    1 0        1004       13,  67 Jan  1 12:00 event3 
crw-rw----    1 0        1004       13,  68 Jan  1 12:00 event4 
crw-rw----    1 0        1004       13,  63 Jan  1 12:00 mice 
crw-rw----    1 0        1004       13,  32 Jan  1 12:00 mouse0

由上可知,所有的输入设备的主设备号为相同,次设备号来区分对应的是哪个handler(匹配的id_table中的id也不同,也就是是不同的device),eventX对应的handler是evdev.c(也不一定,cat /proc/bus/input/handlers 不一定有evdev),mice和mouse0对应的是mousedev.c,靠次设备号区分的。

3. 对于eventX设备节点来说,对应的是evdev.c, 设备驱动中使用input_sync()时,evdev.c中阻塞的App会被唤醒,然后App从缓冲区中读取数据。

input_sync 
    input_event(dev, EV_SYN, SYN_REPORT, 0); 
        input_handle_event(dev, type, code, value); 
            input_pass_event(dev, type, code, value); 
                handler->event(handle, type, code, value); 
                    evdev_event //evdev.c 
                        wake_up_interruptible(&evdev->wait); //唤醒阻塞在读的App

4. evdev.c提供的是原始的数据的读写接口,原始的数据Android中只使用了它。mousedev.c、keyboard.c是加工后的数据,可以使用/dev/mouseX来获取鼠标加工后的数据。

二、单点触摸模拟器驱动

1. 驱动上报事件到 evtest.c 中的缓冲中,当缓冲中有数据的时候就会唤醒读进程。由于是模拟的驱动,不会上报事件,因此我我们是用App向缓冲中直接写入按键事件数据来模拟事件的上报,App可用Android自带的 sendevent 程序向 eventX 中写入数据,触发 evdev_write() 调用来向缓存区写数据。

2. 输入设备驱动中需要为Android构造一些VID/PID和name信息,因为Linux上报的扫描码转换为 AKEYCODE 码时需要根据name来加载配置文件,配置文件分为.idc .ly .kcm文件。其中后两个与按键KEY事件有关,第一个与Touch事件有关。详情见:https://source.android.com/devices/input/key-layout-files

3. 驱动Demo

/* 参考: drivers\input\keyboard\gpio_keys.c */ 
 
#include <linux/module.h> 
#include <linux/version.h> 
 
#include <linux/init.h> 
#include <linux/fs.h> 
#include <linux/input.h> 
 
 
static struct input_dev *input_emulator_dev; 
 
static int input_emulator_init(void) 
{ 
    int i; 
 
    /* 1.分配一个设备结构体 */ 
    input_emulator_dev = input_allocate_device();; 
 
    /* 2.设置 */ 
    /* 2.1 能产生哪类事件 */ 
    set_bit(EV_KEY, input_emulator_dev->evbit); /*按键事件*/ 
    set_bit(EV_REP, input_emulator_dev->evbit); /*连续按着不松手循环产生按键事件*/ 
 
    /* 2.2 设置能产生所有按键事件(参考sysrq.c中的设置更优雅) */ 
    for (i = 0; i < BITS_TO_LONGS(KEY_CNT); i++) 
        input_emulator_dev->keybit[i] = ~0UL; 
 
    /* 
     * 2.3 为Android构造一些设备信息,通过它来寻找映射,见: 
     * https://source.android.com/devices/input/key-layout-files 
     */ 
    input_emulator_dev->name = "InputEmulator"; 
    input_emulator_dev->id.bustype = 1; 
    input_emulator_dev->id.vendor  = 0x1234; 
    input_emulator_dev->id.product = 0x5678; 
    input_emulator_dev->id.version = 1; 
 
    /* 3. 注册 */ 
    input_register_device(input_emulator_dev); 
 
    return 0; 
} 
 
static void input_emulator_exit(void) 
{ 
    input_unregister_device(input_emulator_dev); 
    input_free_device(input_emulator_dev); 
} 
 
module_init(input_emulator_init); 
module_exit(input_emulator_exit); 
MODULE_LICENSE("GPL");

Android中编译驱动模块的方法和Linux中一样,Makefile文件:

KERN_DIR = /media/ubuntu/works/tiny4412/linux-3.0.86 
 
all: 
    make -C $(KERN_DIR) M=`pwd` modules  
 
clean: 
    make -C $(KERN_DIR) M=`pwd` modules clean 
    rm -rf modules.order 
 
obj-m    += InputEmulator.o

(1) 使用sendevent测试:

# insmod InputEmulator.ko
打开浏览器的输入框,执行以下脚本,就会发现输入框中输入了12

shell@tiny4412:/system/mytest/driver # cat test.sh  
#!/system/bin/sh 
 
# write 1  
sendevent /dev/input/event5 1 2 1 # 1 2 1 : EV_KEY, KEY_1, down 
sendevent /dev/input/event5 1 2 0 # 1 2 0 : EV_KEY, KEY_1, up 
sendevent /dev/input/event5 0 0 0 # sync 
# write 2 
sendevent /dev/input/event5 1 3 1  
sendevent /dev/input/event5 1 3 0 
sendevent /dev/input/event5 0 0 0

(2)使用hexdump测试

# hexdump /dev/input/event5 &  
# ./test.sh 
//tv_sec=4021 0000,tv_usec=2168 0004,type=0001 code=0002,value=0001 0000  
4021 0000 2168 0004 0001 0002 0001 0000 //1 2 1 
4027 0000 6269 0001 0001 0002 0000 0000 //1 2 0 
4027 0000 e524 0001 0000 0000 0000 0000 //sync 
4027 0000 81e2 0002 0001 0003 0001 0000 
4027 0000 b0e1 0003 0001 0003 0000 0000 
4027 0000 b727 0004 0000 0000 0000 0000

怎样分析这些dump出来的数据呢:

应用程序读取到的是 input_event 结构的数组( copy_to_user() 的是 input_event 结构).

struct input_event { 
    struct timeval time; 
    __u16 type; 
    __u16 code; 
    __s32 value; 
}; 
 
struct timeval { 
    __kernel_time_t         tv_sec;  /* long类型,32bit机器是4B */ 
    __kernel_suseconds_t tv_usec;    /* long类型,32bit机器是4B */ 
};

4.触摸屏上可以实现很多虚拟的按键

5.映射是在Reader中做的。

6.App也可以向eventX设备节点写入数据,最终会促使 input_dev.event() 被调用。

7.功能是判断src2是否是src1的子集, 是返回1, 不是返回0
int bitmap_subset(const unsigned long *src1, const unsigned long *src2, unsigned int nbits)

三、多点触摸驱动

1.多点触摸事件上报的类型

多点触摸的驱动很简单,同一时刻有多少个点按下就上报多少个触点即可。但是若是两个手指同时滑动,两个手指之间滑动之间的关系怎么上报呢?可以参考内核文档multi-touch-protocol.rst,这里直接给出答案:上报的数据分为A B两种类型,如下:

(1) Type A:
简单粗暴型的,只上报触点之间的位置,但是触点之间有什么联系不管。

上报的数据格式:
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT //上报完一个触点上报一个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_REPORT同步信息

那么在Type A类型上报的情况下App怎么判断哪几个点在同一条划痕上呢:根据距离来推测,最近的认为是在同一条线上。


(2) Type B:
复杂一些,上报触点位置,也上报触点之间的关系。现在的触摸一般都有一个触摸IC,访问这个触摸IC就可以获取触摸点的坐标和触摸点之间的关系。例如两个手指滑动,t1和t2时刻两条划痕上的坐标分别是P1,P1'和P2,P2'

t1时刻读取到P1位置和P1'位置,P1的ID=0,P1'的ID=1
t2时刻读取到P2位置和P2'位置,P2的ID=0,P2'的ID=1
根据ID值判断出P1 P2在同一条划痕上,P1'和P2'在同一条划痕上。

Type B上报数据格式:
ABS_MT_SLOT 0 //表示第一个触点位置(/插槽),可以认为它是一条划痕
ABS_MT_TRACKING_ID 45 //表示第一个触点ID是45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1 //表示第二个触点位置
ABS_MT_TRACKING_ID 46 //表示第二个触点ID是46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

若一条划痕松开手了,上报格式:
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID -1 //-1表示这条划痕松手了。
ABS_MT_SLOT 1 //表示第二个触点位置
ABS_MT_TRACKING_ID 46 //表示第二个触点ID是46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

(3) Type B的优化上报类型:
既然有ID值了,还上报ABS_MT_SLOT就显得有点多余了,在Android5.0上就没有再上报这个SLOT值了。Type B改进型数据格式也就是在Type A数据格式的基础上加了ABS_MT_TRACKING_ID。

Type B的优化上报数据格式如下:
ABS_MT_TRACKING_ID 45 //触点ID
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT //表示一个触点上报完毕
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT

若某个时刻一根手指已经抬起了,上报格式:
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT


2. 编写多点触摸驱动程序

(1) 触摸驱动应该是i2c/spi+input的混合体,因为受到触摸中断后使用i2c或spi去读取。

(2) 将tiny4412自带的驱动改为支持多点触摸的

(3) tiny4412触摸板资料
触摸屏资料:ft5206.pdf,支持最高5点触摸。
tiny4412自带的S702屏资料:http://wiki.friendlyarm.com/wiki/index.php/LCD-S702/zh
参考原生的:drivers/input/touchscreen/ft5x06_ts.c

(4) 试验Demo

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/platform_device.h> 
#include <linux/i2c.h> 
#include <linux/err.h> 
#include <linux/slab.h> 
#include <linux/interrupt.h> 
#include <linux/input.h> 
#include <linux/irq.h> 
#include <asm/mach/irq.h> 
 
#include <linux/gpio.h> 
#include <mach/gpio.h> 
#include <plat/gpio-cfg.h> 
 
 
#define MTP_ADDR   (0x70 >> 1) 
#define MTP_MAX_X  800 
#define MTP_MAX_Y  480 
 
#define MTP_IRQ  gpio_to_irq(EXYNOS4_GPX1(6)) 
 
#define MTP_NAME "ft5x0x_ts" 
#define MTP_MAX_ID 15 /* ÓÉÓ²¼þ¾ö¶¨ */ 
 
struct input_dev *ts_dev; 
static struct work_struct mtp_work; 
static struct i2c_client *mtp_client; 
 
struct mtp_event { 
    int x; 
    int y; 
    int id; 
}; 
 
static struct mtp_event mtp_events[16]; 
static int mtp_points; 
 
 
static irqreturn_t mtp_interrupt(int irq, void *dev_id) { 
 
    /* ±¾¸Ã: 
     * »ñÈ¡´¥µãÊý¾Ý, ²¢Éϱ¨ 
     * µ«ÊÇI2CÊÇÂýËÙÉ豸, ²»¸Ã·ÅÔÚÖжϷþÎñ³ÌÐòÖвÙ×÷ 
     */ 
 
    /* ʹÓù¤×÷¶ÓÁÐ, ÈÃÄÚºËÏß³ÌÀ´²Ù×÷ */ 
    schedule_work(&mtp_work); 
 
    return IRQ_HANDLED; 
} 
 
 
static int mtp_ft5x0x_i2c_rxdata(struct i2c_client *client, char *rxdata, int length) { 
    int ret; 
    struct i2c_msg msgs[] = { 
        { 
            .addr    = client->addr, 
            .flags    = 0, 
            .len    = 1, 
            .buf    = rxdata, 
        }, 
        { 
            .addr    = client->addr, 
            .flags    = I2C_M_RD, 
            .len    = length, 
            .buf    = rxdata, 
        }, 
    }; 
 
    ret = i2c_transfer(client->adapter, msgs, 2); 
    if (ret < 0) 
        pr_err("%s: i2c read error: %d\n", __func__, ret); 
 
    return ret; 
} 
 
static int mtp_ft5x0x_read_data(void) { 
    u8 buf[32] = { 0 }; 
    int ret; 
 
    ret = mtp_ft5x0x_i2c_rxdata(mtp_client, buf, 31); 
 
    if (ret < 0) { 
        printk("%s: read touch data failed, %d\n", __func__, ret); 
        return ret; 
    } 
 
    mtp_points = buf[2] & 0x0f; 
 
    switch (mtp_points) { 
        case 5: 
            mtp_events[4].x = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c]; 
            mtp_events[4].y = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e]; 
            mtp_events[4].id = buf[0x1d]>>4; 
        case 4: 
            mtp_events[3].x = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16]; 
            mtp_events[3].y = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18]; 
            mtp_events[3].id = buf[0x17]>>4; 
        case 3: 
            mtp_events[2].x = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10]; 
            mtp_events[2].y = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12]; 
            mtp_events[2].id = buf[0x11]>>4; 
        case 2: 
            mtp_events[1].x = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a]; 
            mtp_events[1].y = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c]; 
            mtp_events[1].id = buf[0x0b]>>4; 
        case 1: 
            mtp_events[0].x = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04]; 
            mtp_events[0].y = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06]; 
            mtp_events[0].id = buf[0x05]>>4; 
            break; 
        case 0:     
            return 0; 
        default: 
            //printk("%s: invalid touch data, %d\n", __func__, event->touch_point); 
            return -1; 
    } 
 
    return 0; 
} 
 
 
static void mtp_work_func(struct work_struct *work) 
{ 
    int i; 
    int ret; 
     
    /* ¶ÁÈ¡I2CÉ豸, »ñµÃ´¥µãÊý¾Ý, ²¢Éϱ¨ */ 
    /* ¶ÁÈ¡ */ 
    ret = mtp_ft5x0x_read_data(); 
    if (ret < 0) 
        return; 
 
    /* Éϱ¨ */ 
    if (!mtp_points) { 
        input_mt_sync(ts_dev); 
        input_sync(ts_dev); 
        return; 
    } 
     
    for (i = 0; i < mtp_points; i++) { /* ÿһ¸öµã */ 
        input_report_abs(ts_dev, ABS_MT_POSITION_X, mtp_events[i].x); 
        input_report_abs(ts_dev, ABS_MT_POSITION_Y, mtp_events[i].y); 
        input_report_abs(ts_dev, ABS_MT_TRACKING_ID, mtp_events[i].id); 
        input_mt_sync(ts_dev);     
    } 
    input_sync(ts_dev); 
} 
 
static int __devinit mtp_probe(struct i2c_client *client, 
                  const struct i2c_device_id *id) 
{ 
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); 
 
    /* ÖÕÓÚ½øÈëÁËÊäÈë×Óϵͳ */ 
    mtp_client = client; 
 
    /* ·ÖÅäinput_dev */ 
    ts_dev = input_allocate_device(); 
 
    /* ÉèÖÃ */ 
    /* 2.1 ÄܲúÉúÄÄÀàʼþ */ 
    set_bit(EV_SYN, ts_dev->evbit); 
    set_bit(EV_ABS, ts_dev->evbit); 
    set_bit(INPUT_PROP_DIRECT, ts_dev->propbit); 
 
    /* 2.2 ÄܲúÉúÕâÀàʼþÖеÄÄÄЩ */ 
    set_bit(ABS_MT_TRACKING_ID, ts_dev->absbit); 
    set_bit(ABS_MT_POSITION_X,  ts_dev->absbit); 
    set_bit(ABS_MT_POSITION_Y,  ts_dev->absbit); 
 
    /* 2.3 ÕâЩʼþµÄ·¶Î§ */ 
    input_set_abs_params(ts_dev, ABS_MT_TRACKING_ID, 0, MTP_MAX_ID, 0, 0); 
    input_set_abs_params(ts_dev, ABS_MT_POSITION_X, 0, MTP_MAX_X, 0, 0); 
    input_set_abs_params(ts_dev, ABS_MT_POSITION_Y, 0, MTP_MAX_Y, 0, 0);     
 
    ts_dev->name = MTP_NAME; /* android»á¸ù¾ÝËüÕÒµ½ÅäÖÃÎļþ */ 
 
    /* ×¢²á */ 
    input_register_device(ts_dev); 
 
    /* Ó²¼þÏà¹Ø²Ù×÷ */ 
    INIT_WORK(&mtp_work, mtp_work_func); 
     
    request_irq(MTP_IRQ, mtp_interrupt, 
            IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "100ask_mtp", ts_dev); 
     
    return 0; 
} 
 
static int __devexit mtp_remove(struct i2c_client *client) 
{ 
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); 
 
    free_irq(MTP_IRQ, ts_dev); 
    cancel_work_sync(&mtp_work); 
 
    input_unregister_device(ts_dev); 
    input_free_device(ts_dev); 
     
    return 0; 
} 
 
static const struct i2c_device_id mtp_id_table[] = { 
    { "100ask_mtp", 0 },  /* Ö§³ÖÎÒÃÇ×Ô¼ºµÄmtp_driver×ÔÐÐ̽²âµ½µÄI2CÉ豸 */ 
    { "ft5x0x_ts", 0},    /* Ö§³Ömach-tiny4412.cÖÐ×¢²áµÄÃûΪ"ft5x0x_ts"µÄI2CÉ豸 */ 
    {} 
}; 
 
static int mtp_ft5x06_valid(struct i2c_client *client) 
{ 
    u8 buf[32] = { 0 }; 
    int ret; 
     
    printk("mtp_ft5x06_valid : addr = 0x%x\n", client->addr); 
 
    /* ½øÒ»²½ÅжÏÉ豸µÄºÏ·¨ÐÔ */ 
    buf[0] = 0xa3; /* chip vendor id */     
    ret = mtp_ft5x0x_i2c_rxdata(client, buf, 1); 
 
    if (ret < 0) { 
        printk("There is not real device, i2c read err\n"); 
        return ret; 
    } 
    printk("chip vendor id = 0x%x\n", buf[0]); 
     
    if (buf[0] != 0x55){ 
        printk("There is not real device, val err\n"); 
        return -1; 
    } 
    return 0; 
} 
 
static int mtp_detect(struct i2c_client *client, 
               struct i2c_board_info *info) 
{ 
    /* ÄÜÔËÐе½ÕâÀï, ±íʾ¸ÃaddrµÄÉ豸ÊÇ´æÔÚµÄ 
     * µ«ÊÇÓÐЩÉ豸µ¥Æ¾µØÖ·ÎÞ·¨·Ö±æ(AоƬµÄµØÖ·ÊÇ0x50, BоƬµÄµØÖ·Ò²ÊÇ0x50) 
     * »¹ÐèÒª½øÒ»²½¶ÁдI2CÉ豸À´·Ö±æÊÇÄÄ¿îоƬ 
     * detect¾ÍÊÇÓÃÀ´½øÒ»²½·Ö±æÕâ¸öоƬÊÇÄÄÒ»¿î£¬²¢ÇÒÉèÖÃinfo->type 
     */ 
    printk("mtp_detect : addr = 0x%x\n", client->addr); 
 
    if (mtp_ft5x06_valid(client) < 0) 
        return -1; 
     
    strlcpy(info->type, "100ask_mtp", I2C_NAME_SIZE); 
    return 0; 
 
    /* ·µ»Ø0Ö®ºó, »á´´½¨Ò»¸öеÄI2CÉ豸 
     * i2c_new_device(adapter, &info), ÆäÖеÄinfo->type = "100ask_mtp" 
     */ 
} 
 
static const unsigned short addr_list[] = { MTP_ADDR, I2C_CLIENT_END }; 
 
/* 1. ·ÖÅä/ÉèÖÃi2c_driver */ 
static struct i2c_driver mtp_driver = { 
    .class  = I2C_CLASS_HWMON, /* ±íʾȥÄÄЩÊÊÅäÆ÷ÉÏÕÒÉ豸 */ 
    .driver    = { 
        .name    = "100ask", 
        .owner    = THIS_MODULE, 
    }, 
    .probe        = mtp_probe, 
    .remove        = __devexit_p(mtp_remove), 
    .id_table    = mtp_id_table, 
    .detect     = mtp_detect,  /* ÓÃÕâ¸öº¯ÊýÀ´¼ì²âÉ豸ȷʵ´æÔÚ */ 
    .address_list    = addr_list,   /* ÕâЩÉ豸µÄµØÖ· */ 
}; 
 
static int mtp_drv_init(void) 
{ 
    /* 2. ×¢²ái2c_driver */ 
    i2c_add_driver(&mtp_driver); 
     
    return 0; 
} 
 
static void mtp_drv_exit(void) 
{ 
    i2c_del_driver(&mtp_driver); 
} 
 
 
module_init(mtp_drv_init); 
module_exit(mtp_drv_exit); 
MODULE_LICENSE("GPL");

3. 测试

自带的tiny4412驱动的设备端是mach-tiny4412.c,触摸芯片板级资源定义:

static struct ft5x0x_i2c_platform_data ft5x0x_pdata = { 
    .gpio_irq        = EXYNOS4_GPX1(6), 
    .irq_cfg        = S3C_GPIO_SFN(0xf), 
    .screen_max_x    = 800, 
    .screen_max_y    = 1280, 
    .pressure_max    = 255, 
};

选择我们multi-touch的平台驱动端:
drivers/input/touchscreen/Makefile
# obj-$(CONFIG_TOUCHSCREEN_FT5X0X) += ft5x06_ts.o
obj-$(CONFIG_TOUCHSCREEN_FT5X0X) += mtp_input.o

此时触摸,能正常触摸,会上报多点坐标。

4. 排查bug

(1) i2c协议判断一个设备是否存在是非常简单的,在发出设备地址后再第九个始终时SDA是低电平(ACK)就认为设备是存在的。

(2) # cat /proc/interrupts | grep mtp 来查看中断是否产生了。

(3) 我直接参考ft5x06_ts.c在i2c_device_id table中加入{"ft5x0x_ts", 0},使用提供好的设备端,免得校验麻烦。

(4) 在驱动移除时,可能工作队列还在运行,需要 cancel_work_sync()。处理好异步的工作,以免oops.

(5) i2c_driver.address_list 中表示驱动可以支持的设备的i2c地址,当注册这个 i2c_driver 的时候,就会去尝试遍历所有的adaptor,如果找到其能支持的地址的设备,就去调用 i2c_driver.detect(),这个函数返回0时core会创建一个新的i2c设备,会触发与内核中的驱动进行匹配,会调用匹配上的驱动的probe().

(6) 对于多点触摸,没有触点也要上报:

input_mt_sync(ts_dev); 
input_sync(ts_dev);

(7) 对于上报的最大ID并不等于最大触摸点数,它只是ID的最大值而已。
input_set_abs_params(ts_dev, ABS_MT_TRACKING_ID, 0, 最大ID值, 0, 0);

(8) Android系统下的输入设备驱动的名字input_dev.name不能不赋值,因为Android根据它来加载配置文件。

5. 注意自己修改的驱动的名字也必须是input_dev->name = "ft5x0x_ts"; 因为有个配置文件/system/usr/idc/ft5x0x_ts.idc,是按名字匹配的,后面再讲解这个配置文件的作用。

6. 多点触摸不能产生按键类事件。

四、单点触摸与多点触摸对比

五、Android系统中关于输入设备的配置文件

1. 共有3中配置文件
.idc: input device configure
格式就是prop=value,eg:device.internal=1 区分是内接输入设备还是外接输入设备,外接的话对睡眠唤醒比较敏感。
还可以通过它配置使用哪个.kl文件和.kcm文件。
.kl: keylayout
.kcm: key character map
使用介绍: https://source.android.com/devices/input/index.html <需要FQ>

2. 配置文件.idc

介绍官网:https://source.android.com/devices/input/touch-devices

tiny4412触摸屏配置文件内容为:

shell@tiny4412:/system/mytest # cat /system/usr/idc/ft5x0x_ts.idc 
# 
# Input Device Calibration File for the touch screen. 
# 
 
# Basic Parameters 
touch.deviceType = touchScreen 
touch.orientationAware = 1 
 
# Size 
# Based on empirical measurements, we estimate the size of the contact 
# using size = sqrt(area) * 43 + 0. 
touch.size.calibration = area 
touch.size.scale = 6 
touch.size.bias = 0 
touch.size.isSummed = 0 
 
# Pressure 
# Driver reports signal strength as pressure. 
# 
# A normal thumb touch typically registers about 80 signal strength 
# units although we don't expect these values to be accurate. 
touch.pressure.calibration = amplitude 
touch.pressure.scale = 0.0125 
 
# Orientation 
touch.orientation.calibration = none
文件中只有touch.deviceType有意义,取值范围有: 
touchScreen:触摸屏,覆盖在LCD屏上,可以操作各种图标 
touchPad:触摸板,不是覆盖在LCD屏上,需要在LCD屏上显示一个光标以便定位。 
pointer:和touchPad类似,但是多一些手势功能 
default:由系统根据某些算法来确定类型。 
 
若删除这个配置文件,此时屏幕上会出现一个光标(白圈圈),只有将光标移动到想要触摸的地方然后再点击任意位置,行为就和之前一样了。 
此时就像一个触控板一样,Android认为没有覆盖在LCD屏上(Android把它当做pointer了),此时只能使用一个光标来提示你触控点在哪里,类 
似鼠标了。 
 
touch.deviceType用于指定eventX设备节点对应的是什么设备,最重要的一项也是“touch.deviceType = touchScreen”,其它的可以忽略。 
 
需要这个idc配置文件的原因是驱动(input_dev)没有向用户空间提供足够多的信息。在Android源码中检索"touch.deviceType"可以知道需要哪些信息。 
 
TouchInputMapper::configureParameters //InputReader.cpp 
    getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType") 
    if (deviceTypeString == "touchScreen") 
        mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; //看看这个参数在其它地方有没有被设置为这个值的可能 
 
TouchInputMapper::configureParameters 
    if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) 
        /*从这里看出,若是驱动没有给出类型且没没有再idc文件中给出类型的话,默认是pointer类型*/ 
        if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) 
            test_bit(property, device->propBitmask); //即判断INPUT_PROP_DIRECT是否在device->propBitmask中 
 
EventHub::openDeviceLocked(const char *devicePath) //EventHub.cpp     
    ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask); 
 
 
参考内核中对INPUT_PROP_DIRECT属性的设置: 
mxt224.c 
mxt224_probe 
    set_bit(INPUT_PROP_DIRECT, input_dev->propbit); 
 
加上INPUT_PROP_DIRECT属性后,InputReader.cpp中直接判定此输入设备是触摸屏,此时删除/system/usr/idc/ft5x0x_ts.idc文件触摸也是正 
常的。

3. .kl文件

(1) 官网:https://source.android.com/devices/input/key-layout-files

(2) 这个文件的作用是将Linux上报的code值转换为AKEYCODE, 注意Linux里面使用的key code和Android里面使用的是不同的。
默认对应关系是/system/usr/keylayout/Generic.kl,但是用户也可以修改,这个配置文件是per-eventX的,使用配置文件的文件名也input_dev->name
进行匹配。用户可以添加对应输入设备名的配置文件来改默认配置。

(3) 使用InputEmulator驱动试验测试

# cp /system/usr/keylayout/Generic.kl  /data/system/devices/keylayout/InputEmulator.kl  //注意是输入设备名 
# busybox chmod 777 /data/system/devices/keylayout -R 
在InputEmulator.kl最后添加: 
key  227  STAR        //转换Linux上报的227为AKEYCODE_STAR 
key  228  POUND        //转换Linux上报的228为AKEYCODE_POUND 
 
# busybox chmod 777 /data/system/devices -R 会将此目录极其子目录文件全部改为777权限。chmod命令没有-R选项,使用的是busybox的。 
# insmod InputEmulator.ko  产生/dev/input/event5 
 
执行下面的指令显示'*''#' 
sendevent /dev/input/event5 1 227 1; 
sendevent /dev/input/event5 1 227 0; 
sendevent /dev/input/event5 0 0 0; 
 
sendevent /dev/input/event5 1 228 1; 
sendevent /dev/input/event5 1 228 0; 
sendevent /dev/input/event5 0 0 0; 
 
注意: 
.kl配置文件只是将Linux上报的code转换为AKEYCODE,至于显示什么字符那是由.kcm文件决定的,显示‘*’的原因是默认的Generic.kcm 
文件中指定了AKEYCODE_STAR对应字符'*'

4. .kcm文件

官网: https://source.android.com/devices/input/index.html <需要FQ>
.kcm配置文件的作用是将AKEYCODE转换为要显示的字符。

试验:添加对应输入设备的kcm文件,使收到AKCODE_STAR时显示‘m’,收到AKEYCODE_POUND时显示'n'

# cp /system/usr/keychars/Generic.kcm  /data/system/devices/keychars/InputEmulator.kcm 
# busybox chmod 777 /data/system/devices/keychars -R 
做如下修改: 
key STAR {                
    label:    '*'    //label仅仅是一个标签 
#    base:    '*'    //是没有组合键按下时收到AKCODE_STAR时要显示的字符。 
    base:    'm' 
}                                           
                                            
key POUND {                                 
    label:    '#' 
#    base:    '#' 
    base:    'n' 
} 
  
reboot后再执行上面指令显示的是'm''n'(但是修改.kl文件的227改为其它数值如100测试不成功)

五、Android frameworks中对配置文件的解析

1.idc文件加载过程

根据设备的信息去读取设备的配置文件 
EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) 
    EventHub::openDeviceLocked(const char *devicePath) //EventHub.cpp 
        /*之后会根据输入设备的这些信息去加载对应的配置文件。*/ 
        InputDeviceIdentifier identifier; 
        identifier.bus = inputId.bustype; 
        identifier.product = inputId.product; 
        identifier.vendor = inputId.vendor; 
        identifier.version = inputId.version; 
         /*加载一个.idc文件*/ 
        loadConfigurationLocked(device); 
 
 
openDeviceLocked()做了: 
a.open event设备节点 
b.ioctl获取设备信息 
c.创建一个new Device()实例表示这个输入设备 
d.加载idc配置文件 
e.加载kl/kcm配置文件。

2. kl文件解析

class KeyLayoutMap { 
    /*这两项存放的是扫描码*/ 
    KeyedVector<int32_t, Key> mKeysByScanCode; //int32_t表示内核上报的Code值,Key.keyCode表示的是AKEYCODE_XXX 
    KeyedVector<int32_t, Key> mKeysByUsageCode; 
} 
 
struct Key { 
    int32_t keyCode; 
    uint32_t flags; 
}; 
 
例如kl文件中的: 
key 1    ESCAPE                //Code=1, Key.keyCode=AKEYCODE_ESCAPE, Key.flags=0 
key 228  POUND              //Code=228, Key.keyCode=AKEYCODE_POUND, Key.flags=0 
key 465  ESCAPE  FUNCTION    //Code=465, Key.keyCode=AKEYCODE_ESCAPE, Key.flags=FUNCTION 
 
Key.flags取值如下:(参见: source.android.com/devices/input/key-layout-files.html) 
FUNCTION:该键应解释为同时按下了FUNCTION键。 
GESTURE:用户手势生成的密钥,例如手掌触摸屏。 
VIRTUAL:键是与主触摸屏相邻的虚拟软键(电容式按键),这会导致启用特殊的去抖动逻辑。 
 
KeyedVector<int32_t, Key> mKeysByUsageCode;的意义是 
kl文件中的还可以这样写: 
key usage 0x0c006F BRIGHTNESS_UP 
0x0c006F就是usage的代码,对于某些USB键盘,它上报的是usage的代码,它也可以把usage转换成AKEYCODE。

3. kcm文件解析

class KeyCharacterMap { 
    KeyedVector<int32_t, Key*> mKeys; 
    KeyedVector<int32_t, int32_t> mKeysByScanCode;     //<ScanCode, AKEYCODE_XXX>,不常用 
    KeyedVector<int32_t, int32_t> mKeysByUsageCode;    //不常用 
} 
对于KeyedVector<int32_t, int32_t> mKeysByScanCode成员对应kcm文件中表示为: 
map key 1 ESCAPE    //<1, AKEYCODE_ESCAPE> 
由于其相对于KeyLayoutMap少了一个flags,其对虚拟的按键支持的就不是很好,一般不在kcm文件中存放keylayout,所以这里我们主要关心mKeys。 
 
对于KeyedVector<int32_t, Key*> mKeys成员其在kcm文件中表示为: 
//表示收到AKEYCODE_V的时候的显示字符的规则: 
key V { 
    label:              'V' 
    base:                 'v' 
    shift, capslock:     'V' 
} 
 
struct Key { 
    char16_t label;        //就对应kcm文件中的label 
    char16_t number;    //在某些只能输入数字的文本框中按下按键就会输入这个数字。kcm文件中没写,不管。 
    Behavior* firstBehavior; //对Key V{}中除了label之外的每一行都会构造一个Behavior,然后串在这个单链表上。 
}; 
 
struct Behavior { 
    Behavior* next; 
    int32_t metaState;  
    char16_t character; 
    int32_t fallbackKeyCode; 
}; 
 
对"base: 'v'"这一行会创建一个Behavior结构,metaState=0,character='a', 意思是不按下其它键的话应该得到一个'a'。 
对于"shift, capslock: 'V'" 这一行会创建两个Behavior结构,metaState=shift,character='A' 和 metaState=capslock,character='A', 
表示若按下shift或capslock后再按下按键返回'A'。 
 
这些Behavior构成一个链表,Key.firstBehavior是表头。 
 
之后按下A键的时候就会根据Device类对象找到KeyMap,然后再找到里面的KeyCharacterMap,找到KeyedMap,根据里面的keycode找到这个key的值, 
然后找到firstBehavior,然后根据里面的metaState来返回对应的character。 
 
 
另一个复杂一些的例子: 
kcm文件中: 
//表示收到AKEYCODE_SPACE的时候的显示字符的规则: 
key SPACE {               
    label:       ' ' 
    base:       ' '        // 
    alt, meta:    fallback SEARCH    // 
    ctrl:          fallback LANGUAGE_SWITCH // 
}  
对于①:会创建1个Behavior结构,Behavior1:metaState=0,character=' ' 
对于②:会创建2个Behavior结构,Behavior2:metaState=alt,fallback keycode=AKCODE_SEARCH; Behavior3: metaState=meta,fallback keycode=AKCODE_SEARCH 
对于③:会创建1个Behavior结构,Behavior4:metaState=ctrl,character=AKCODE_LANGUAGE_SWITCH 
 
然后构成一个链表:Key.firstBehavior->Behavior1->Behavior2->Behavior3->Behavior4 
 
fallback keycode的意思是: 
在Android系统在收到按键后会向应用程序上报一个AKEYCODE,应用程序如果能处理这个按键值的话在处理完之后会回复一个已经处理完的信号, 
如果不能处理的话输入系统会再上报一个值,这个值就来自fallback。 
 
对于此例,按下alt+space后输入系统会向应用程序发一个按键值,若应用程序能处理,就没事。若是无法处理,应用程序就会告诉输入系统无法 
处理,此时输入系统会再次向应用程序上报一个AKEYCODE_SEARCH。

4. 总结

打开输入设备的时候会去加载一些配置文件,如.kl和.kcm文件。这些配置信息存放在Dervice.keyMap中。.kl文件使用一个KeyLayoutMap结构体表示,.kcm文件使用一个KeyCharacterMap表示。

Android的输入系统受到Linux内核发来的一个扫描码后首先根据KeyLayoutMap(.kl文件决定)将scan code转换成AKCODE_XXX,然后再根据KeyCharacterMap(mKeys域 .kcm文件决定)将AKCODE_XXX通过其firstBehavior链表转换成一个字符进行显示。

Device结构体是per输入设备的,因此,这些映射和配置文件应该也都是per输入设备eventX的。测试发现的确如此。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/10883810.html