需求背景
全局滑鼠应用于平板应用/浏览网页/玩游戏等。基于Android的物理按键实现鼠标操作.在android视图上自绘类似鼠标的箭头,物理按键点击上下左右和确定的操作,可让鼠标箭头上下左右移动和选中操作。
可行性分析
- 物理按键操作:按下、弹起。
- 鼠标操作状态:按下、弹起、移动、双击、长按、滑动、滚动。
- 按下、弹起、移动(down、move、up)是简单的触摸屏事件,而双击、长按、滑动、滚动需要根据运动的轨迹来做识别的
方案设计
(0)按键映射表
基于按键映射表实现鼠标操作,通过组合键控制按键及鼠标切换.
(1)灵敏度:
Int move_accelerate[] = {0, 2, 2, 4, 4, 6, 8, 10, 12, 14, 16, 18}
系统灵敏度配置符合用户使用意图,若需加快光标移动速度可减少数组成员,加大偏移.
(2)算法:
REL_X/Y =1+move_accelerate[accelerate]
(3)系统轮询按键检测周期:
20ms
(4)按照当前的算法,设备检测到连续按键按下:
1次偏移1位置
2次偏移3位置
3次偏移3位置
4次偏移5位置
5次偏移5位置
6次偏移7位置
代码解析
Linux按键驱动
common/drivers/xxx/input/keyboard/gpio_keypad.c.
//内核模块化平台驱动加载接口module_platform_driver,其属module_init的一种封装.
module_platform_driver(meson_gpio_kp_driver);
static struct platform_driver meson_gpio_kp_driver = {
.probe = meson_gpio_kp_probe,
……
};
static int meson_gpio_kp_probe(struct platform_device *pdev)
{
……
//通过of接口获取设备树节点scan_period数据
ret = of_property_read_u32(pdev->dev.of_node,
"scan_period", &keypad->scan_period);
……
//初始化sysfs文件系统class属性信息
keypad->kp_class.name = "gpio_keypad";
keypad->kp_class.owner = THIS_MODULE;
//构建class子目录属性文件组,详细逻辑见sysfs文件系统章节
keypad->kp_class.class_groups = meson_gpiokey_groups;
//注册class抽象设备模式,系统启动后sysfs文件系统/sys/class目录下有gpio_keypad子目录
ret = class_register(&keypad->kp_class);
……
//为input_dev结构体申请一块内存
input_dev = input_allocate_device();
……
//设置按键事件类型及事件代码
for (i = 0; i < keypad->key_size; i++) {
//input子系统提供设备驱动层API接口,设置可上报的事件类型和事件代码:普通按键类型
input_set_capability(input_dev, keypad->key[i].key_type,
keypad->key[i].code);
dev_info(&pdev->dev, "%s key(%d) type(0x%x) registered.\n",
keypad->key[i].name, keypad->key[i].code,
keypad->key[i].key_type);
}
//设置鼠标事件类型及事件代码
for (i = BTN_MOUSE; i < BTN_SIDE; i++) {
//input子系统提供设备驱动层API接口,设置上报的事件类型和事件代码:鼠标确定键
input_set_capability(input_dev, EV_KEY, i);
}
//设置上报鼠标X轴/Y轴/滚轮事件
input_set_capability(input_dev, EV_REL, REL_X);
input_set_capability(input_dev, EV_REL, REL_Y);
input_set_capability(input_dev, EV_REL, REL_WHEEL);
……
//初始化input_dev结构体后,调用输入子系统注册函数,注册一个新的输入设备
ret = input_register_device(keypad->input_dev);
……
//通过该内核接口见keypad保存成平台总线设备的私有数据,可调用platform_get_drvdata()获取
platform_set_drvdata(pdev, keypad);
//初始化内核定时器
timer_setup(&keypad->polling_timer,polling_timer_handler, 0);
if (keypad->use_irq) {
……
} else {
//重新注册定时器到内核链表,使得定时器进入重新调度流程内,毫秒转成节拍.
mod_timer(&keypad->polling_timer,
jiffies + msecs_to_jiffies(keypad->scan_period));
}
}
static ssize_t keymode_show(struct class *cls, struct class_attribute *attr,
char *buf)
{
return sprintf( buf ,"%d\n" , key_work_mode );
}
static ssize_t keymode_store(struct class *cls, struct class_attribute *attr,
const char *buf, size_t count) {
if ( kstrtoint( buf , 10 , &key_work_mode ))
{
return -EINVAL;
}
return count;
}
static CLASS_ATTR_RW(keymode);
static struct attribute *meson_gpiokey_attrs[] = {
&class_attr_table.attr,
&class_attr_keymode.attr,
NULL
};
ATTRIBUTE_GROUPS(meson_gpiokey);
//定时器回调函数
static void polling_timer_handler(struct timer_list *t)
{
……
if (keypad->use_irq) {
……
}
} else {
//通过设备树属性可知,按键驱动采用轮询模式,非中断模式
for (i = 0; i < keypad->key_size; i++) {
gpio_val = gpiod_get_value(keypad->key[i].desc);
//按键模式下,上报按键事件
if(switch_work_mode(keypad) == KEYPAD_MODE) {
if (gpio_val != keypad->key[i].current_status) {
keypad->key[i].count++;
} else {
keypad->key[i].count = 0;
}
if (gpio_val != keypad->key[i].current_status) {
keypad->current_key = &keypad->key[i];
report_key_code(keypad, gpio_val);
}
} else {
//根据按键鼠标映射表示上报对应事件
if ( keypad->key[i].code == rel_xy_key[0] ||
keypad->key[i].code == rel_xy_key[1] ||
keypad->key[i].code == rel_xy_key[2] ||
keypad->key[i].code == rel_xy_key[3]) {
if (gpio_val == GPIO_DOWN_STATUS) {
keypad->key[i].count++;
} else {
keypad->key[i].count = 0;
}
//鼠标模式下,上报REL_X及REL_Y事件
if (gpio_val == GPIO_DOWN_STATUS) {
keypad->current_key = &keypad->key[i];
report_key_rel_xy_code(keypad, gpio_val);
}
} else {
if (gpio_val != keypad->key[i].current_status) {
keypad->current_key = &keypad->key[i];
report_key_mouse_code(keypad, gpio_val);
}
}
}
//按键模式与鼠标模式互切
if (gpio_val == GPIO_DOWN_STATUS) {
get_key_work_mode(keypad);
}
}
mod_timer (&keypad->polling_timer,
jiffies + msecs_to_jiffies(keypad->scan_period));
}
}
static int switch_work_mode(struct gpio_keypad *keypad)
{
if (old_work_mode != key_work_mode) {
old_work_mode = key_work_mode;
}
return key_work_mode;
}
#define GPIO_DOWN_STATUS 0
#define GPIO_UP_STATUS 1
#define POINTER_MOVE_ACCELERATE {0, 2, 2, 4, 4, 6, 8, 10, 12, 14, 16, 18}
enum KEY_WORK_MODE {
KEYPAD_MODE = 0,
KEYMOUSE_MODE = 1
};
static int key_work_mode = KEYPAD_MODE;
static int old_work_mode = KEYPAD_MODE;
int switch_key[4] = { KEY_LEFT,KEY_UP,KEY_RIGHT,KEY_ENTER };
int rel_xy_key[4] = { KEY_LEFT,KEY_UP,KEY_RIGHT,KEY_DOWN };
static void report_key_mouse_code(struct gpio_keypad *keypad, int gpio_val)
{
struct pin_desc *key = keypad->current_key;
key->current_status = gpio_val;
switch(key->code) {
case KEY_ENTER:
if(key->current_status){
input_event(keypad->input_dev, EV_KEY, BTN_LEFT, 0);
} else {
input_event(keypad->input_dev, EV_KEY, BTN_LEFT, 1);
}
input_sync(keypad->input_dev);
break;
default:
if (key->current_status) {
input_event(keypad->input_dev, key->key_type,key->code, 0);
} else {
input_event(keypad->input_dev, key->key_type,key->code, 1);
}
input_sync(keypad->input_dev);
break;
}
return;
}
static void report_key_rel_xy_code(struct gpio_keypad *keypad, int gpio_val)
{
struct pin_desc *key = keypad->current_key;
int move_accelerate[] = POINTER_MOVE_ACCELERATE;
int accelerate = key->count;
if (accelerate > ARRAY_SIZE(move_accelerate) - 1) {
accelerate = ARRAY_SIZE(move_accelerate) - 1;
}
switch (key->code) {
case KEY_UP:
input_event(keypad->input_dev, EV_REL,REL_Y, -(1+move_accelerate[accelerate]));
input_sync(keypad->input_dev);
dev_info(&keypad->input_dev->dev, "-REL_Y %d count:%d accelerate:%d\n", key->code,key->count,accelerate);
break;
case KEY_DOWN:
input_event(keypad->input_dev, EV_REL,REL_Y, (1+move_accelerate[accelerate]));
input_sync(keypad->input_dev);
dev_info(&keypad->input_dev->dev, "REL_Y %d count:%d accelerate:%d\n", key->code,key->count,accelerate);
break;
case KEY_LEFT:
input_event(keypad->input_dev, EV_REL,REL_X, -(1+move_accelerate[accelerate]));
input_sync(keypad->input_dev);
dev_info(&keypad->input_dev->dev, "-REL_X %d count:%d accelerate:%d\n", key->code,key->count,accelerate);
break;
case KEY_RIGHT:
input_event(keypad->input_dev, EV_REL,REL_X, (1+move_accelerate[accelerate]));
input_sync(keypad->input_dev);
dev_info(&keypad->input_dev->dev, "REL_X %d count:%d accelerate:%d\n", key->code,key->count,accelerate);
break;
default:
break;
}
return;
}
static void get_key_work_mode(struct gpio_keypad *keypad)
{
struct pin_desc *key = keypad->current_key;
//第一个按下左键,记录标志数字1
if (key->code == switch_key[0]) {
switch_flag = 1;
}
//第二个按上键,记录标志数字2
if (key->code == switch_key[1]) {
if (switch_flag == 1) {
switch_flag = 2;
}
}
//第三个按右键,记录标志数字3
if (key->code == switch_key[2]) {
if (switch_flag == 2) {
switch_flag = 3;
}
}
//第四个按确定键,记录标志数字4
if (key->code == switch_key[3]) {
if (switch_flag == 3) {
switch_flag = 4;
}
}
if (key->code != switch_key[0] &&
key->code != switch_key[1] &&
key->code != switch_key[2] &&
key->code != switch_key[3]) {
switch_flag = 0;
}
return;
}
static int switch_work_mode(struct gpio_keypad *keypad)
{
if (switch_flag == 4) {
switch_flag = 0;
key_work_mode = (key_work_mode == KEYPAD_MODE) ? KEYMOUSE_MODE : KEYPAD_MODE;
}
return key_work_mode;
}
struct pin_desc {
int current_status;
struct gpio_desc *desc;
int irq_num;
u32 code;
u32 key_type;
const char *name;
int count;
};
struct gpio_keypad {
int key_size;
int use_irq;/* 1:irq mode ; 0:polling mode */
int scan_period;
struct pin_desc *key;
struct pin_desc *current_key;
struct timer_list polling_timer;
struct input_dev *input_dev;
struct class kp_class;
};
Linux设备树
./common/arch/arm64/boot/dts/xxx/xxxx.dts
gpio_keypad {
compatible = "xxx, gpio_keypad";
status = "okay";
scan_period = <20>;
key_num = <13>;
key_name = "bluetooth", "BUT_B", "BUT_OK", "BUT_RETURN", "BUT_START", "BUT_M", "BUT_X", "BUT_Y", "BUT_A","BUT_VOL+", "BUT_OFF", "BUT_VOL-", "BUT_ESC";
key_code = <600 108 28 102 139 113 105 106 103 115 116 114 15>;
key_type = <EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY EV_KEY>;
key-gpios = <&gpio GPIOD_2 GPIO_ACTIVE_HIGH
&gpio GPIOZ_0 GPIO_ACTIVE_HIGH
&gpio GPIOZ_1 GPIO_ACTIVE_HIGH
&gpio GPIOZ_3 GPIO_ACTIVE_HIGH
&gpio GPIOH_9 GPIO_ACTIVE_HIGH
&gpio GPIOZ_7 GPIO_ACTIVE_HIGH
&gpio GPIOZ_8 GPIO_ACTIVE_HIGH
&gpio GPIOZ_9 GPIO_ACTIVE_HIGH
&gpio GPIOZ_10 GPIO_ACTIVE_HIGH
&gpio GPIOD_3 GPIO_ACTIVE_HIGH
&gpio GPIOD_5 GPIO_ACTIVE_HIGH
&gpio GPIOD_8 GPIO_ACTIVE_HIGH
&gpio GPIOD_10 GPIO_ACTIVE_HIGH>;
detect_mode = <0>;/*0:polling mode, 1:irq mode*/
};
通识类储备
module_platform_driver
common/include/linux/platform_device.h:
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
common/include/linux/device.h:
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
common/include/linux/module.h
#define module_init(x) __initcall(x);
common/include/linux/init.h
linux内核对initcall进行等级划分,总共有0~7等级,0等级最高,由start_kernel()首先执行。
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
typedef int (*initcall_t)(void)声明为函数指针类型的变量__initcall_ meson_gpio_kp_driver_6,并将__initcall_ meson_gpio_kp_driver_6赋值给该变量。即
static initcall_t __initcall_ meson_gpio_kp_driver_6 __used \
__attribute__((__section__(".initcall6.init"))) = meson_gpio_kp_driver;
out/android11-5.4/common/System.map
console:/ # lsmod | grep gpio
gpio_keypad 20480 0
console:/ #
当前gpio_keypad驱动以KO形式插入,可通过在板运行objdump -t查看相关符号表:
objdump -t ./vendor-ramdisk/lib/modules/gpio_keypad.ko
./vendor-ramdisk/lib/modules/gpio_keypad.ko: file format elf64-little
SYMBOL TABLE:
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .llvm_addrsig 0000000000000000 .llvm_addrsig
……
0000000000000020 l O .data 00000000000000c8 meson_gpio_kp_driver
……
0000000000000000 g F .init.text 0000000000000030 init_module
0000000000000000 g F .exit.text 0000000000000024 cleanup_module
如此insmod驱动过程中,在系统内部会调用sys_init_module()去找init_module函数的入口地址。init_module与meson_gpio_kp_driver地址偏移是相关联的,详细深究驱动调用流程:sys_init_module->
Kernel/module.c:load_module().->do_init_module()->
Init/main.c:do_one_initcall()->
Drivers/base/driver.c->dirver_register()->
Bus_add_driver()->buf_for_each_device()->__driver_attach()->really_probe()->platform_drv_probe()->user_xxx_probe(). 其中__driver_attach()实现驱动与设备匹配driver_match_device()->drivers/base/platform.c:platform_match()->of_driver_match_device(dev,drv)。
若非KO形式驱动,静态内核映射out/android11-5.4/common/System.map表文件中查询相关信息, 可结合vmlinux.lds.S文件了解其放置section的逻辑。符号表如下:
ffffffc01135a218 D __initcall6_start
ffffffc01135a218 d __initcall_171_427_register_kernel_offset_dumper6
ffffffc01135a220 d __initcall_148_413_cpuinfo_regs_init6
ffffffc01135a228 d __initcall_161_80_register_cpu_hwcaps_dumper6
ffffffc01135a230 d __initcall_182_1203_armv8_pmu_driver_init6
ffffffc01135a238 d __initcall_159_208_arch_init_uprobes6
……
ffffffc01135a880 d __initcall_165_325_ gpio_led_driver_init6
……
__initcall_165_325_ gpio_led_driver_init6函数指针指向gpio_led_driver_init,内核启动后就是按照System.map表文件顺序注册驱动模块。
common/init/main.c
系统启动过程中,调用start_kernel()完成内核系统初始化操作,其中包括加载驱动,第一个循环轮询驱动框架大类别,第二循环对同一个级别不同驱动顺序进行注册。
static void __init do_initcall_level(int level, char *command_line)
{
……
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
static void __init do_initcalls(void)
{
……
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
/* Parser modifies command_line, restore it each time */
strcpy(command_line, saved_command_line);
do_initcall_level(level, command_line);
}
}
static initcall_entry_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
/* Keep these in sync with initcalls in include/linux/init.h */
static const char *initcall_level_names[] __initdata = {
"pure",
"core",
"postcore",
"arch",
"subsys",
"fs",
"device",
"late",
};
Note:
Start_kernel()->arch_call_rest_init()->rest_init():分别启动两个内核线程.
- Pid=kernel_thread(kernel_init, NULL, CLONE_FS);这个进程号1即init进程
- Pid=kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 这个进程号2即内核守护进程,保证内核正常工作。
1|console:/ # ps -A
USER PID PPID VSZ RSS WCHAN ADDR S NAME
root 1 0 46052 8244 ep_poll 0 S init
root 2 0 0 0 kthreadd 0 S [kthreadd]
platform设备模型
common/include/linux/platform_device.h:
platform_driver结构和device_driver结构类似。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
drivers/base/platform.c
platform模块的初始化
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
input子系统
common/include/uapi/linux/major.h
input子系统设备的主设备号都为13
#define INPUT_MAJOR 13
console:/dev/input # ls -als event3
0 crw-rw---- 1 root input 13, 67 1970-01-01 08:00 event3
Sysfs文件系统
common/include/linux/sysfs.h
#define __ATTRIBUTE_GROUPS(_name) \
static const struct attribute_group *_name##_groups[] = { \
&_name##_group, \
NULL, \
}
common/include/linux/device.h
通过XXX_ATTR_RW宏定义可在目录/sys/devices,/sys/class,/sys/bus生成对应属性文件。用户态可以使用cat/echo命令对设备属性文件进行读写操作,实现用户态与内核态之间数据交换。
#define BUS_ATTR_RW(_name) \
struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define CLASS_ATTR_RW(_name) \
struct class_attribute class_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
定时器
common/include/linux/timer.h
#define timer_setup(timer, callback, flags) \
__init_timer((timer), (callback), (flags))
#define __init_timer(_timer, _fn, _flags) \
init_timer_key((_timer), (_fn), (_flags), NULL, NULL)
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
//定时器列表元素
struct hlist_node entry;
//定时器定时时间
unsigned long expires;
//定时器回调函数,时间到执行回调函数
void (*function)(struct timer_list *);
u32 flags;
struct lockdep_map lockdep_map;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
common/kernel/time/timer.c
内核定时器结构体timer_list,挂载在定时器链表上.
static void do_init_timer(struct timer_list *timer,
void (*func)(struct timer_list *),
unsigned int flags,
const char *name, struct lock_class_key *key)
{
timer->entry.pprev = NULL;
timer->function = func;
timer->flags = flags | raw_smp_processor_id();
lockdep_init_map(&timer->lockdep_map, name, key, 0);
}
void init_timer_key(struct timer_list *timer,
void (*func)(struct timer_list *), unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, func, flags, name, key);
}
common/include/linux/jiffies.h
假设内核时间频率HZ为100,表示每秒触发100次时钟中断,每10ms触发一次,每次中断jiffies+1,则每秒jiffies增加了100.Linux中用全局变量jiffies表示系统自启动以来的时钟节拍数码。系统运行时间以S为单位计数,就等于jiffies/100.
static inline unsigned long _msecs_to_jiffies(const unsigned int m) {
return (m + (MSEC_PER_SEC / HZ) - 1) / (MSEC_PER_SEC / HZ);
}