一、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