输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,全部使用输入子系统构建的设备都使用主设备号13,同时输入子系统也支持自动建立设备文件,这些文件采用阻塞的IO读写方式,被建立在"/dev/input/"下。以下图所示。内核中的输入子系统自底向上分为设备驱动层,输入核心层,事件处理层。因为每种输入的设备上报的事件都各有不一样,因此为了应用层可以很好识别上报的事件,内核中也为应用层封装了标准的接口来描述一个事件,这些接口在"/include/upai/linux/input"中。node
input对象描述了一个输入设备,包括它可能上报的事件,这些事件使用位图来描述,内核提供的相应的工具帮助咱们构建一个input对象,你们能够参考内核文档"Documentation/input/input-programming.txt",里面对于input子系统的使用有详细的描述。linux
//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的链表api
//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) | BIT_MASK(EV_REL); button_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) |BIT_MASK(BTN_MIDDLE);
//第二种 //一般用于只注册一个事件 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点须要注意:3d
事件处理层最终会将驱动sync一次时全部report的事件组织成一个struct input_value[]的形式上报到应用层,在应用层从相应的设备文件中获取上报的事件的时候,须要注意:指针
前文已经说过,"include/uapi/linux/input.h"中的宏是应用层和驱动层共用的通讯协议,因此应用层在解析收到的struct input_value对象的时候,只须要"include <linux/input.h>"便可使用其中的宏。
/* * The event structure itself */ struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
上文已经说过,input子系统使用三层结构来实现驱动事件到应用层的传递。具体的,这三个层次每个层次都由一条结构体链表组成,在设备驱动层,核心结构体是input_dev;在input核心层,是input_handle;在事件处理层,是input_handler。内核经过链表和指针将三者结合到一块儿,最终实现了input_dev和input_handler的多对多的映射关系,这种关系可用下图简单描述。
下面的这个模板首先使用input子系统上报按键事件,而后在应用层读取。
/{
key@26{
compatible = "xj4412,key";
interrupt-parent = <&gpx1>;
interrupts = <2 2>;
};
};
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 <linux/input.h> 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; }