- Android板级支持与硬件相关子系统
- 韩超等
- 2716字
- 2020-08-28 12:21:34
5.4 用户输入BSP的结构
移植Android的用户输入系统,主要的工作分成以下两个部分。
·输入(Input)驱动程序
·用户空间中动态配置的kl和kcm文件
Android用户输入部分的硬件适配层就是libui库中的EventHub,这部分是系统标准的部分。因此,在实现特定硬件平台的Android系统的时候,用户输入的硬件抽象层通常情况下不做改变。EventHub使用Linux标准的input设备作为输入设备,其中又以实用Event设备居多。在这种情况下,为了实现Android系统的输入,也必须使用Linux标准Input驱动程序作为标准的输入。
由于标准化程度比较高,实现用户输入系统,在用户空间一般不需要更改代码。唯一的情况是使用不同的kl和kcm文件,使用按键的布局和按键字符映射关系。
↘5.4.1 Input驱动程序
Input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)和事件设备(Event queue)3种驱动程序。目前以事件驱动程序为主,作为通用的输入驱动,可以支持键盘、鼠标、触摸屏等多种输入设备。
Input驱动程序的主设备号是13,驱动程序的设备号分配如下所示。
·joystick游戏杆:0~31
·mouse鼠标:32~62
·mice鼠标:63
·事件(Event)设备:64~95
实际上,每一种Input设备占用5位,因此每种设备包含的个数是32个。
Event设备在用户空间大多使用read、ioctl、poll等文件系统的接口进行操作,read用于读取输入信息,ioctl用于获得和设置信息,poll调用可以进行用户空间的阻塞。当内核有按键等中断时,通过在中断中唤醒poll的内核实现,这样在用户空间的poll调用也可以返回。
Event设备在文件系统中的设备节点为:/dev/input/eventX。主设备号为13,次设备号递增生成,为64~95,各个具体的设备在misc、touchscreen、keyboard等目录中。
输入设备驱动程序的头文件:include/linux/input.h。
输入设备驱动程序的核心和Event代码分别是:drivers/input/input.c和drivers/input/evdev.c。Event输入驱动的架构如图5-3所示。
图5-3 Event设备驱动的架构
input.h中定义了input_dev结构,它表示Input驱动程序的各种信息,对于Event设备分为同步设备、键盘、相对设备(鼠标)、绝对设备(触摸屏)等。
input_dev中定义并归纳了各种设备的信息,例如按键、相对设备、绝对设备、杂项设备、LED、声音设备、强制反馈设备、开关设备等。
struct input_dev { const char *name; // 设备名称 const char *phys; // 设备在系统的物理路径 const char *uniq; // 统一的ID struct input_id 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)]; // 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 keycodemax; // 按键码的最大值 unsigned int keycodesize; // 按键码的大小 void *keycode; // 按键码 int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); struct ff_device *ff; unsigned int repeat_key; struct timer_list timer; int sync; int abs[ABS_MAX + l]; int rep[REP_MAX + l]; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; int absmax[ABS_MAX + l]; // 绝对设备相关内容 int absmin[ABS_MAX + l]; int absfuzz[ABS_MAX + l]; int absflat[ABS_MAX + l]; int (*open)(struct input_dev *dev); // Input设备相关的操作的函数指针 void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; int going_away; struct device dev; // 表示设备的结构和链表 struct list_head h_list; struct list_head node; };
在具体的Event驱动程序的实现中,如果得到按键的事件,通常需要通过以下的接口向上进行通知,这些内容也在input.h中定义,如下所示:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value); static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_KEY, code, !!value); } static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value) {input_event(dev, EV_REL, code, value); } static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value) {input_event(dev, EV_ABS, code, value); } static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value) {input_event(dev, EV_FF_STATUS, code, value); } static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value) {input_event(dev, EV_SW, code, !!value); } static inline void input_sync(struct input_dev *dev) {input_event(dev, EV_SYN, SYN_REPORT, 0); }
对不同设备内容的报告均是通过input_event()函数来完成的,但使用了不同参数。
在手机系统中经常使用的键盘(keyboard)和小键盘(kaypad)属于按键设备EV_KEY,轨迹球属于相对设备EV_REL,触摸屏属于绝对设备EV_ABS。
关于按键数值的定义片段如下所示:
#define KEY_RESERVED 0 #define KEY_ESC l #define KEY_l 2 #define KEY_2 3 #define KEY_0 ll #define KEY_MINUS l2 #define KEY_EQUAL l3 #define KEY_BACKSPACE l4 #define KEY_TAB l5 #define KEY_Q l6 #define KEY_W l7 #define KEY_E l8 #define KEY_R l9 #define KEY_T 20
这里用整数标识各个按键,这种数值通常又被称为“扫描码”。
getevent命令可以对Event设备进行调试,执行命令行,所有使用Input设备的输入情况将打出信息。在Android的模拟器环境中,使用getevent的情况如下所示:
# getevent add device l: /dev/input/event0 name: "qwerty2" could not get driver version for /dev/input/mouse0, Not a typewriter could not get driver version for /dev/input/mice, Not a typewriter /dev/input/event0: 000l 00020000000l /dev/input/event0: 000l 000200000000
点击数字按键1,出现了上面的信息,0002是按键的扫描码,00000001和00000000分别是按下和抬起的附加信息。最前面的0001实际上是输入设备的类型。
使用getevent()可以最直接地获得按键的扫描码,从硬件的源头确定底层输入设备传递上来的信息。
↘5.4.2 输入配置文件
Android输入系统在用户空间的内容,可以通过配置文件进行配置。
·kl(Keycode Layout)文件:后缀名为kl的配置文件。
·kcm(KeyCharacterMap)文件:后缀名为kcm的配置文件。
系统默认配置文件的路径为:sdk/emulator/keymaps/。
经过处理后,kl和kcm文件被分别放置在目标文件系统的/system/usr/keylayout/目录和/system/usr/keychars/目录中。kl文件将被直接复制到目标文件系统中;由于尺寸较大,kcm文件放置在目标文件系统之前,需要经过压缩处理。
kl和kcm的文件名的含义为<设备>.kl和<设备>.kcm。其中<设备>的含义是输入设备的设备名,对于某一个输入设备,如果具有相应设备名的kl或kcm文件,将优先使用;如果没有,将使用默认的配置文件。
1.kl:按键布局文件
kl文件将被直接放在目标系统的/system/usr/keylayout/目录中。Android默认提供的按键布局文件主要包括qwerty.kl。另一个按键布局文件AVRCP.kl用于多媒体的控制,ACRCP的含义为Audio/Video Remote Control Profile。
qwerty.kl为全键盘的布局文件,是系统中主要按键使用的布局文件,也是被默认使用的,其片段如下所示:
key 399 GRAVE key 2 l key 3 2 key 4 3 key 5 4 key 6 5 key 7 6 key 8 7 key 9 8 key l0 9 key ll 0 key l58 BACK WAKE_DROPPED key 230 SOFT_RIGHT WAKE key 60 SOFT_RIGHT WAKE key l07 ENDCALL WAKE_DROPPED key 62 ENDCALL WAKE_DROPPED key 229 MENU WAKE_DROPPED # 省略部分按键的对应内容 key l6 Q key l7 W key l8 E key l9 R key 20 T key ll5 VOLUME_UP key ll4 VOLUME_DOWN
在按键布局文件中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串,完成了按键信息的第1次转化,将整型的扫描码转换成字符串类型的按键标签。第3列表示按键的Flag,带有WAKE字符,表示这个按键可以唤醒系统。
扫描码来自驱动程序,显然,不同的扫描码可以对应一个按键标签。表示物理上的两个按键可以对应同一个功能按键。
例如,当驱动程序上传扫描码为158的时候,对应的标签为BACK,再经过第二次转换,根据KeycodeLabels.h的KEYCODES数组,其对应的按键码为4。
按键布局文件同时兼顾了input驱动程序的定义和Android中按键的定义。例如,Input驱动程序中定义的数字扫描码KEY_1的数值为2,这里2对应的按键标签为“1”;Input驱动程序中定义字母扫描码KEY_Q的数值为16,这里对应的按键标签为“Q”。然而各种Android设备中使用到的全键盘毕竟有不同之处,因此有一些按键与input驱动程序的定义没有对应关系。
2.kcm:按键字符映射文件
kcm表示按键字符的映射关系,主要功能是将整数类型按键码(keycode)转换成可以显示的字符。kcm文件将被makekcharmap工具转换成二进制的格式,放在目标系统的/system/usr/keychars/目录中。
qwerty.kcm是默认的按键字符映射文件,表示全键盘字符映射,其片段如下所示:
[type=QWERTY] # keycode display number base caps fn caps_fn A 'A' '2' 'a' 'A' '#' 0x00 B 'B' '2' 'b' 'B' '<' 0x00 C 'C' '2' 'c' 'C' '9' 0x00E7 D 'D' '3' 'd' 'D' '5' 0x00 E 'E' '3' 'e' 'E' '2' 0x030l F 'F' '3' 'f' 'F' '6' 0x00A5 G 'G' '4' 'g' 'G' '-' '_' H 'H' '4' 'h' 'H' '[' '{' I 'I' '4' 'i' 'I' '$' 0x0302 J 'J' '5' 'j' 'J' ']' '}' K 'K' '5' 'k' 'K' '"' '~' L 'L' '5' 'l' 'L' ''' '`' M 'M' '6' 'm' 'M' '!' 0x00 N 'N' '6' 'n' 'N' '>' 0x0303
第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display)、数字(number)等。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel()、getNumber()等函数相对应。
这里的类型,除了QWERTY之外,还可以是Q14(单键多字符对应的键盘)、NUMERIC(12键的数字键盘)等。