一、Linux輸入子系統框架
1. 查看系統中輸入設備的信息
# cat /proc/bus/input/devices 查看系統中所有的輸入設備節點的詳細信息
# cat /proc/bus/input/handlers 查看系統中所有注冊的handler的信息
# ls /sys/class/input/ 查看系統中所有的輸入設備節點在/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去處理。
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()被調用,在這個函數中一般會創建/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,eventX對應的handler是evdev.c,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 設置能產生所有按鍵事件 */ 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 <需要翻牆>
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 <需要翻牆>
.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的。測試發現的確如此。
ssss