输入设备都有共性:中断驱动+字符io,基于分层的思想,linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系统也支持自动创建设备文件,这些文件采用阻塞的io读写方式,被创建在/dev/input/下。如下图所示。内核中的输入子系统自底向上分为设备驱动层,输入核心层,事件处理层。由于每种输入的设备上报的事件都各有不同,所以为了应用层能够很好识别上报的事件,内核中也为应用层封装了标准的接口来描述一个事件,这些接口在/include/upai/linux/input中。
设备驱动层是具体硬件相关的实现,也是驱动开发中主要完成的部分,
输入核心层主要提供一些api供设备驱动层调用,通过这些api设备驱动层上报的数据就可以传递到事件处理层,
事件处理层负责创建设备文件以及将上报的事件传递到用户空间,
input的使用
input对象描述了一个输入设备,包括它可能上报的事件,这些事件使用位图来描述,内核提供的相应的工具帮助我们构建一个input对象,大家可以参考内核文档documentation/input/input-programming.txt,里面对于input子系统的使用有详细的描述。
//input设备对象121 struct input_dev {122 const char *name;129 unsigned long evbit[bits_to_longs(ev_cnt)];130 unsigned long keybit[bits_to_longs(key_cnt)];131 unsigned long relbit[bits_to_longs(rel_cnt)];132 unsigned long absbit[bits_to_longs(abs_cnt)];133 unsigned long mscbit[bits_to_longs(msc_cnt)];134 unsigned long ledbit[bits_to_longs(led_cnt)];135 unsigned long sndbit[bits_to_longs(snd_cnt)];136 unsigned long ffbit[bits_to_longs(ff_cnt)];137 unsigned long swbit[bits_to_longs(sw_cnt)];155 162 unsigned long key[bits_to_longs(key_cnt)];163 unsigned long led[bits_to_longs(led_cnt)];164 unsigned long snd[bits_to_longs(snd_cnt)];165 unsigned long sw[bits_to_longs(sw_cnt)];166 172 struct input_handle __rcu *grab;179 180 struct device dev;181 182 struct list_head h_list;183 struct list_head node;190 };
struct input_dev
--122--> 这个name不是设备名,input子系统的设备名在子系统源码中指定的,不是这。
--129--> 设备支持的输入事件位图,ev_key,ev_rel, etc
--130--> 对于按键事件,设备支持的输入子事件位图
--132--> 对于相对坐标事件,设备支持的相对坐标子事件位图
--133--> 对于绝对坐标事件,设备支持的绝对坐标子事件位图
--134--> 混杂设备的支持的子事件位图
--180-->表示这是一个device。
--182-->h_list是用来链接相关handle的链表
--183-->node用来链接其他input_dev的链表
分配/释放
//drivers/input/input.c//创建一个input对象struct input_dev *input_allocate_device(void);//释放一个input对象void input_free_device(struct input_dev *dev);
初始化
初始化一个input对象是使用input子系统编写驱动的主要工作,内核在头文件include/uapi/linux/input.h中规定了一些常见输入设备的常见的输入事件,这些宏和数组就是我们初始化input对象的工具。这些宏同时用在用户空间的事件解析和驱动的事件注册,可以看作是驱动和用户空间的通信协议,所以理解其中的意义十分重要。在input子系统中,每一个事件的发生都使用事件(type)->子事件(code)->值(value)三级来描述,比如,按键事件->按键f1子事件->按键f1子事件触发的值是高电平1。注意,事件和子事件和值是相辅相成的,只有注册了事件ev_key,才可以注册子事件btn_0,也只有这样做才是有意义的。
下面就是内核约定的事件类型,对应应用层的事件对象的type域
下面这些是按键子事件的类型,可以看到对pc键值的定义
除了对常用的事件进行描述,内核同样提供了工具将这些事件正确的填充到input对象中描述事件的位图中。
//第一种//这种方式非常适合同时注册多个事件button_dev->evbit[0] = bit_mask(ev_key); button_dev->keybit[bit_word(btn_0|btn_1)] = bit_mask(btn_0|btn_1);
//第二种//通常用于只注册一个事件set_bit(ev_key,button_dev.evbit);set_bit(btn_0,button_dev.keybit);
注册/注销
初始化好了一个input对象,接下来就需要将其注册到内核
//注册input对象到内核int input_register_device(struct input_dev *dev);//从内核注销一个input对象void input_unregister_device(struct input_dev *dev);
驱动层报告事件
在合适的时机(由于输入最终是中断表示的,所以通常在驱动的中断处理函数中)驱动可以将注册好的事件上报,且可以同时上报多个事件,下面是内核提供的api
//上报指定的事件+子事件+值void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//上报键值void input_report_key(struct input_dev *dev,unsigned int code,int value);//上报绝对坐标void input_report_abs(struct input_dev *dev,unsigned int code,int value);//报告同步事件void input_report_rel(struct input_dev *dev,unsigned int code,int value);//同步所有的上报void input_sync(struct input_dev *dev);
上报事件有2点需要注意:
report函数们并不会真的上报,只是准备上报,sync才会真的将刚刚report的事件真的上报搭input核心
input核心会进行裁决再上报的事件处理层,所以对于按键事件,一定要先报1再报0(或者反过来),不能只report 1或0, 这样核心会认为是一个事件被误触发了多次而只上报一次,虽然我们真的按下了多次。
应用层解析
事件处理层最终会将驱动sync一次时所有report的事件组织成一个struct input_value[]的形式上报到应用层,在应用层从相应的设备文件中获取上报的事件的时候,需要注意:
收到数组元素的数量会比底层多一个空元素,类似于写of_device_id[]时最后的空元素,这点应用层在解析的时候需要注意。
事件处理层并不会缓存收到的事件,如果有新的事件到来,即使旧的事件没有被读取,也会被覆盖,所以应用程序需要及时读取。
前文已经说过,include/uapi/linux/input.h中的宏是应用层和驱动层共用的通信协议,所以应用层在解析收到的struct input_value对象的时候,只需要include 即可使用其中的宏。
/* * the event structure itself */struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value;};
input分析
上文已经说过,input子系统使用三层结构来实现驱动事件到应用层的传递。具体的,这三个层次每一个层次都由一条结构体链表组成,在设备驱动层,核心结构体是input_dev;在input核心层,是input_handle;在事件处理层,是input_handler。内核通过链表和指针将三者结合到一起,最终实现了input_dev和input_handler的多对多的映射关系,这种关系可用下图简单描述。
模板
下面的这个模板首先使用input子系统上报按键事件,然后在应用层读取。
input按键设备驱动
/{
key@26{
compatible = xj4412,key;
interrupt-parent = ;
interrupts = ;
};
};
static struct input_dev *button_dev;static int button_irq;static int irqflags;static irqreturn_t button_interrupt(int irq, void *dummy){ input_report_key(button_dev, btn_0, 0); input_report_key(button_dev, btn_0, 1); input_sync(button_dev); return irq_handled;} static int button_init(void){ request_irq(button_irq, button_interrupt,irqflags, button, null)) ; button_dev = input_allocate_device(); button_dev->name = button; button_dev->evbit[0] = bit_mask(ev_key); button_dev->keybit[bit_word(btn_0)] = bit_mask(btn_0); input_register_device(button_dev); return 0;}static int button_exit(void){ input_free_device(button_dev); free_irq(button_irq, button_interrupt); return 0; }static int key_probe(struct platform_device *pdev){ struct resource *irq_res; irq_res = platform_get_resource(pdev, ioresource_irq, 0); if(irq_res){ button_irq = irq_res->start; irqflags = irq_res->flags & irqf_trigger_mask; }else{ return -einval; } return button_init();}static int key_remove(struct platform_device *dev){ return button_exit();}struct of_device_id of_tbl[] = { {.compatible = xj4412,key,}, {},};module_device_table(of, of_tbl);struct platform_driver key_drv = { .probe = key_probe, .remove = key_remove, .driver.name = keydrv, .driver.of_match_table = of_tbl,};module_platform_driver_register(key_drv);module_license(gpl);
应用层获取键值
#include struct input_event { struct timeval time; unsigned short type; unsigned short code; int value;};int main(int argc, char * const argv[]){ int fd = 0; struct input_event event[3] = {0}; //3!!!,驱动上传了2个事件,第三个用来装空元素 int ret = 0; fd = open(argv[1],o_rdonly); while(1){ ret = read(fd,&event,sizeof(event)); printf(ret:%d,val0:%d,val1:%d,val12:%d\n,ret,event[0].value,event[1].value,event[2].value); //2!!!,最后一个是空 sleep(1); } return 0;}
便携数据库管理系统的无线解决方案
5G支持网络切片 确保工业4.0升级的安全性
NANK南卡A2降噪耳机评测:降噪、音质、体验越来越强!
iphone6splus和华为p10谁更好?两者跑分都为14万分,售价基本持平,如何选择?
全球已商用14个TD-LTE网络,多模多频是主基调
需要了解Linux input子系统编程、分析与模板的原理
一文搞懂电阻在电路中的n种用处
科技生活「源」自泰克,为工程师验证新灵感
RTC8563与32.768khz啥关系?
功率开关对电源效率的影响(飞兆案例)
屏蔽双绞线的最大传输距离是多少
无人驾驶正在飞速发展 但目前依旧存在三道阻力
黑鲨游戏手机2 Pro将于7月30日发布该机搭载了骁龙855+ SoC
大唐移动与iVReal合作:共同研究推广基于5G+MR的智慧教育应用
亚马逊无人机居然还可以理解手势和语音呼叫
新唐开发平台:进阶使用者的七大诀窍(3)使用System Viewer检查寄存器状态
威图手机售后维修部-VERTU手机官方售后维修中心
云计算厂商在2020的竞争有多大
印制电路板的设计基础
ONNX格式模型部署兼容性框架介绍