INPUT子系统
一:什么是Input子系统?
(应用场景,用途)
二:怎么设计Input子系统的程序?
(分配一个输入设备——注册一个输入设备——上报输入事件——注销一个输入设备——释放一个输入设备)
三:Input子系统需要知道哪些?
(涉及的重要数据结构(input_dev,input_handle,input_handler),中断的相关知识)
四:一个自带按键驱动分析
(linux-4.5/drivers/input/keyboard下的amikbd.c )
五:模拟实现案例
(通过echo sysfs接口值,上传到core层)
六:总结
一:什么是Input子系统?
输入设备(如按键,键盘,触摸屏,鼠标,蜂鸣器等)是典型的字符设备,其一般的工作机制是底层在按键,触摸等动作发生时产生一个中断(或驱动通过
timer定时查询),然后cpu通过SPI,I2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的
read()接口让用户可以读取键值,坐标等数据。
在
Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input
Core)和输入子系统事件处理层(Event
Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理
层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱
动层提交来的事件处理。这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。
在
Linux中,输入子系统作为一个模块存在,向上,为用户层提供接口函数,向下,为驱动层程序提供统一的接口函数。其构建非常灵活,只需要调用一些简单的
函数,就可以将一个输入设备的功能呈现给应用程序。这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系
统通知驱动程序完成某项功能。
Input子系统结构图
二:怎么设计Input子系统的程序?
2.1:分配一个输入设备
struct
input_dev *input_allocate_device*(void);
这个函数的作用就是初始化input_dev结构体,input_dev是非常关键的数据结构,将在下面重点分析。
代码如下表:
struct input_dev *input_allocate_device(void) { struct input_dev *dev; dev = if (dev) { dev->dev.type = dev->dev.class = device_initialize(&dev->dev); mutex_init(&dev->mutex); spin_lock_init(&dev->event_lock); INIT_LIST_HEAD(&dev->h_list); INIT_LIST_HEAD(&dev->node); __module_get(THIS_MODULE); } return dev; } |
分析如下:
该函数返回一个指向
input_dev
类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如设备支持的按键码、设备的名称、设备支持的事件等。
2.2:注册一个输入设备
int
input_register_device(struct input_dev *dev);
input_register_device()函数注册输入设备结构体,
input_register_device()函数是输入子系统核心(input
core)提供的函数。该函数将input_dev结构体注册到输入子系统核心中,input_dev结构体必须由前面讲的
input_allocate_device()函数来分配。input_register_device()函数如果注册失败,必须调input_free_device()函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用
input_unregister_device()函数来注销输入设备结构体。
代码如下:
[cpp] view plain copy /** * input_register_device - * @dev: device to be * * This function registers * allocated with * set up before registering. * If function fails the * Once device has been * with * called in this case. */ int { static atomic_t input_no = struct input_handler const char *path; int error; /* Every input device __set_bit(EV_SYN, /* __set_bit()函数设置 常用的事件类型如下: 2. #define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/ 3. #define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/ 4. #define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/ 5. #define EV_MSC 0x04/*其他类型*/ 6. #define EV_LED 0x11 /*LED 灯设备*/ 7. #define EV_SND 0x12 /*蜂鸣器,输入声音*/ 8. #define EV_REP 0x14 /*允许重复按键类型*/ 9. #define EV_PWR 0x16 /*电源管理事件*/ */ /* KEY_RESERVED is not __clear_bit(KEY_RESERVED, /* Make sure that bitmasks input_cleanse_bitmasks(dev); /* * If delay and period are * is handled by the */ init_timer(&dev->timer); //初始化一个 if (!dev->rep[REP_DELAY] dev->timer.data = dev->timer.function dev->rep[REP_DELAY] dev->rep[REP_PERIOD] } //如果dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]没有设值,则将其赋默认值。这主要是处理重复按键的. if (!dev->getkeycode) dev->getkeycode = if (!dev->setkeycode) dev->setkeycode = dev_set_name(&dev->dev, (unsigned long) /* //检查 //input_default_getkeycode()和 //值。input_default_setkeycode()函数用来设置键值。 */ error = if (error) return error; //使用 //文件系统中表现出来。 path = printk(KERN_INFO "input: dev->name ? kfree(path); error = if (error) { device_del(&dev->dev); return error; } list_add_tail(&dev->node, //调用 //表中包含了系统中所有的 list_for_each_entry(handler, input_attach_handler(dev, //将input //input_attach_handler().在这里的情况有好比设备模型中的device和driver的匹配。所有的input //input_dev_list链上。所有的handler都挂在input_handler_list上。 input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0; } |
分析如下:
第03~06行,定义了一些函数中将要用到的局部变量。
第07行,调用__set_bit()函数设置input_dev所支持的事件类型。事件类型由input_dev的evbit成员来表示,在这里将其EV_SYN置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:
#define EV_SYN 0x00 /*表示设备支持所有的事件*/ #define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/ #define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/ #define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/ #define EV_MSC 0x04 /*其他类型*/ #define EV_LED 0x11 /*LED灯设备*/ #define EV_SND 0x12 /*蜂鸣器,输入声音*/ #define EV_REP 0x14 /*允许重复按键类型*/ #define EV_PWR 0x16 /*电源管理事件*/
dev_set_name设置input_dev中的device的名字,名字以input0、input1、input2、input3、input4等的形式出现在sysfs文件系统中。
使用device_add()函数将input_dev包含的device结构注册到Linux设备模型中,并可以在sysfs文件系统中表现出来。
打印设备的路径,输出调试信息。
调用list_add_tail()函数将input_dev加入input_dev_list链表中,input_dev_list链表中包含了系统中所有的input_dev设备。
设计到的两个重要函数分析如下表:
/* static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id;/*输入设备的指针,该结构体表示设备的标识,标识中存储了设备的信息*/ int error; if (handler->blacklist && input_match_device(handler->blacklist, dev))/*首先判断 handle 的 blacklist 是否被赋值,如果被赋值,则匹配 blacklist 中的数据跟 dev->id 的数据是否匹配。blacklist 是一个 input_device_id*的类型,其指向 input_device_id的一个表,这个表中存放了驱动程序应该忽略的设备。即使在 id_table 中找到支持的项,也应该忽略这种设备。*/ return -ENODEV; id = input_match_device(handler, dev); if (!id) return -ENODEV; error = handler->connect(handler, dev, id);/*连接设备和处理函数*/ if (error && error != -ENODEV) printk(KERN_ERR "input: failed to attach handler %s to device %s, " "error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error); return error; } */ /* static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev) { const struct input_device_id *id; int i;//声明一个局部变量 i,用于循环。 /*是一个 for 循环,用来匹配 id 和 dev->id 中的信息,只要有一项相同则返回。*/ for (id = handler->id_table; id->flags || id->driver_info; id++) { /*用 来 匹 配 总 线 类 型 。 id->flags 中 定 义 了 要 匹 配 的 项 , 其 中INPUT_DEVICE_ID_MATCH_BUS 如果没有设置,则比较 input device 和 input handler 的总线类型。*/ if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)//匹配设备厂商的信息。 if (id->vendor != dev->id.vendor) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)//分别匹配设备号的信息。 if (id->product != dev->id.product) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) if (id->version != dev->id.version) continue; /*使用 MATCH_BIT 匹配项。如果 id->flags 定义的类型匹配成功,或者 id->flags没有定义,才会进入到 MATCH_BIT 的匹配项。*/ MATCH_BIT(evbit, EV_MAX); MATCH_BIT(keybit, KEY_MAX); MATCH_BIT(relbit, REL_MAX); MATCH_BIT(absbit, ABS_MAX); MATCH_BIT(mscbit, MSC_MAX); MATCH_BIT(ledbit, LED_MAX); MATCH_BIT(sndbit, SND_MAX); MATCH_BIT(ffbit, FF_MAX); MATCH_BIT(swbit, SW_MAX); if (!handler->match || handler->match(handler, dev)) return id; } return NULL; } */ |
2.3:驱动实现-事件支持
Set_bit(EV_KEY,button_dev.evbit)
//Set_bit告诉inout子系统它支持哪些事件
//Struct
input_dev中有两个成员,一个是evbit;一个是keybit;分别用来表示设备所支持的事件类型和按键类型。
事件类型
Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
EV_SYN
0x00 同步事件
EV_KEY
0x01 按键事件
EV_REL
0x02 相对坐标
EV_ABS
0x03 绝对坐标
EV_MSC
0x04 其它
EV_LED
0x11 LED
EV_SND
0x12 声音
EV_REP
0x14 Repeat
EV_FF
0x15 Force feedback事件
按键类型
当事件类型为EV_KEY时,还需指明按键类型:
BTN_LEFT
鼠标左键
BTN_RIGHT
鼠标右键
BTN_MIDDLE
鼠标中键
BTN_0
数字0键
BTN_1
数字1键
上述set_bit函数实则完成了把EV_KEY赋值到button_dev.evbit
驱动实现-报告事件
//报告指定type,code的输入事件
Void
input_event(struct input_dev
*dev,unsigned int type,unsigned int code,int value);
/*报告键值,code
:
事件的代码,如果事件是ev_key,该代码则为设备的键盘代码。例如鼠标按键代码为0x110~0x116,其中0x110(BTN_LEFT),0x111(BTN_RIGHT),0x112(BTN_MIDDLE)。其它带按摩含义参考include/linux/input.h文件*/
Void
input_report_key(struct
input_dev *dev,unsigned int code,int value);
value
: 事件的值,如果事件的类型是EV_KEY,当按键按下时值为1,松开时为0。
//报告相对坐标
Void
input_report_rel(struct
input_dev *dev,unsigned int code,int value);
//报告绝对坐标
Void
input_report_abs(struct
input_dev *dev,unsigned int code,int value);
/*报告同步事件,input_sync()用于高速input
core 此次报告已经结束,能够根据上报的信息往后面处理了*/
Void
input_sync(struct input_dev
*dev);
在触摸屏驱动设计中,一次坐标及按下状态的整个报告过程如下:
Input_report_abs(input_dev,ABS_X,x);//X坐标
Input_report_abs(input_dev,ABS_Y,y);//Y坐标
Input_report_abs(input_dev,ABS_PRESSURE,pres);//压力
input_sync(struct
input_dev *dev);//同步
2.4:释放与注销设备、
Void
input_free_device(struct input_dev *dev);
Void
input_unregister_device(struct input_dev *);
三:Input子系统需要知道哪些?
3.1:input_dev(代表一个输入设备)
结构剖析:
/* input_dev 成员说明: char //设备名字,如键盘名字。 char //设备文件节点名,如input/kbd0。 char //全球唯一的ID号。 struct //后文作详细介绍。用于匹配事件处理层handler unsigned //该设备驱动所能支持的事件。 //EV_SYN //EV_KEY //EV_REL //EV_ABS //EV_MSC //EV_LED //EV_SND //EV_REP //EV_FF //EV_PWR //EV_FF_STATUS unsigned unsigned //键值存放表(位图) unsigned //用于存放相对坐标值等 unsigned //用于存放绝对坐标值等 unsigned //存放其他事件类型 unsigned //存放表示各种状态的LED值 unsigned //存放各种事件的声音 unsigned //存放受力设备的属性 int //显然与受力效果有关,具体作用还不大清楚。 unsigned unsigned void * unsigned //存放重复按键时的键值(最近一次的按键值,可用于连击) struct //定时器 struct //考虑到有些设备可能有电源管理 struct //不清楚 int //显然是表示一个状态,但不清楚具体是谁的状态 int sync; //最后一次同步后没有新的事件置1 int //显然是与绝对坐标有关的,但具体的作用不清楚。 int //存放重复按键时的延时,系统依靠这个延时时间来判断重复按键 //rep[0]表示开始要重复按键时的延时时间,即第1个键与第2个键(开始重复按键)之间的延时 //rep[1]此后重复按键之前的延时时间,直到按键抬起 //通俗解释就是,假如我按了一个“a”,并且一直按着,那么在显示出来的第一个a与第二个a之间的时间延时为rep[0],而此后的相邻两个a之间的延时为rep[1] unsigned unsigned unsigned int int int int int void int int int //回调函数 int int //底层与硬件相关的一组操作,若有具体定义,则会在input struct //该结构会在后文做具体介绍,这个指针用于占用输入设备用,如键盘 struct struct //h_list链表用于与input_handler相联系 //node链表:设备向输入子系统(input */ |
3.2:input_handler(事件驱动的主体,每一种处理方式对应一个handler结构体)
结构剖析:
事件处理层(event handler)的核心结构。 头文件:include/linux/input.h 成员说明: void //私有数据指针 void //事件处理函数指针。设备驱动报告的事件最终由这个函数来处理。 struct void //event struct //给应用程序所调用的一组接口 int //这个handler可以使用的32个次设备号的最小值 char //input_handler的名字 struct struct //该结构体是对input_id结构体的扩展,从表面上看blacklist为被系统列为黑名单的输入设备列表 struct //用来存放全局handler链表的节点 struct //h_list链表用于与input_dev相联系 //node链表:事件处理程序向输入子系统(input |
3.3:
input_handle(用来连接input_dev和input_handler)
结构剖析:
说明:是一个用于关联驱动层input_dev和事件处理层input_handler的中间结构。 头文件:include/linux/input.h 成员说明: void //私有数据指针 int open; //记录本设备被打开的次数 char //input_handle的名字 struct struct //这两个就不解释了,前面都有具体介绍 struct struct //d_node链表用于input_dev链,h_node链表用于input_handler链,有了input_handle,就把相关dev和handler联系起来,相互能容易找到。 |
3.4:input_event(事件传送的载体,输入子系统的事件通过这个结构体包装传送给用户空间)
结构剖析:
(这个结构体是事件传送的载体,输入子系统的事件都是包装成struct input_event传给用户空间) 说明:应用程序可通过此结构体获取输入设备事件信息,也就是说,比如在写键盘测试程序时,我们可用这个结构体,再结合ioctl系统调用来获取来自键盘的信息。 头文件:include/linux/input.h 成员说明: struct //time是一个时间戳(timestamp),储存着事件发生时的时间记录 __u16 //事件的类型,如EV_KEY,则表示输入事件为键盘事件 __u16 //事件的代码,如果为KEY_1,则表示键盘输入为“1” __s32 //用于键盘时,value为0表示按键松开,value为1表示按键按下,value为2表示重复按键 #define #define #define #define #define #define #define #define #define #define #define #define #define */ |
3.5:input_id
结构剖析:
说明:输入设备的一些属性。 头文件:include/linux/input.h 成员说明: __u16 //总线类型,如BUS_PCI、BUS_USB等 __u16 //设备生产商 __u16 //产品名字 __u16 //版本号 |
3.6:中断(事件产生事件通知处理器的方式)
详细了解参照LINUX设备驱动程序第十章节介绍。
四:一个自带按键驱动分析
/* * * * Based * Hamish */ /* * Amiga */ /* * This * it * the * (at * * This * but * * GNU * * You * along * * * Should * e-mail * Vojtech */ #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("Vojtech MODULE_DESCRIPTION("Amiga MODULE_LICENSE("GPL"); #ifdef static [0] = [1] = [2] = [3] = [4] = [5] = [6] = [7] = [8] = [9] = [10] = [11] = [12] = [13] = [15] = [16] = [17] = [18] = [19] = [20] = [21] = [22] = [23] = [24] = [25] = [26] = [27] = [29] = [30] = [31] = [32] = [33] = [34] = [35] = [36] = [37] = [38] = [39] = [40] = [41] = [42] = [43] = [45] = [46] = [47] = [48] = [49] = [50] = [51] = [52] = [53] = [54] = [55] = [56] = [57] = [58] = [60] = [61] = [62] = [63] = [64] = [65] = [66] = [67] = [68] = [69] = [70] = [74] = [76] = [77] = [78] = [79] = [80] = [81] = [82] = [83] = [84] = [85] = [86] = [87] = [88] = [89] = [90] = [91] = [92] = [93] = [94] = [95] = [96] = [97] = [98] = [99] = [100] = [101] = [102] = [103] = }; static { /* We can unsigned int i, for (i = if continue; memset(temp_map, for (j = if continue; temp_map[j] } for (j = if temp_map[j] } memcpy(key_maps[i], } } #else /* static #endif /* static [0] = [1] = [2] = [3] = [4] = [5] = [6] = [7] = }; static { struct unsigned scancode ciaa.cra udelay(85); /* ciaa.cra down = scancode if if input_report_key(dev, input_report_key(dev, } else input_report_key(dev, } input_sync(dev); } printk(amikbd_messages[scancode return } static int { struct int i, dev = if (!dev) dev_err(&pdev->dev, return } dev->name dev->phys dev->id.bustype dev->id.vendor dev->id.product dev->id.version dev->dev.parent dev->evbit[0] for (i = set_bit(i, amikbd_init_console_keymaps(); ciaa.cra err = dev); if (err) goto err = if (err) goto platform_set_drvdata(pdev, return fail3: free_irq(IRQ_AMIGA_CIAA_SP, fail2: input_free_device(dev); return } static int { struct free_irq(IRQ_AMIGA_CIAA_SP, input_unregister_device(dev); return } static .remove = .driver .name = }, }; module_platform_driver_probe(amikbd_driver, MODULE_ALIAS("platform:amiga-keyboard"); |
五:模拟实现案例
代码实例:
#include <linux/module.h> #include #include #include #include #include #include<linux/delay.h> struct struct struct int struct struct }; struct static const { int sscanf(buf,"%d",&ch); vinput_dev->ch //post up(&vinput_dev->sem); return } static char return } DEVICE_ATTR(push,0666,show_ch,push_ch); static { int struct struct printk(KERN_INFO while(1){ //等待信号量 while((down_interruptible(sema)) ch mdelay(3000); if(ch<0x78) { input_report_key(vinput_dev->input,ch, input_report_key(vinput_dev->input,ch, } if(ch==100) input_report_key(vinput_dev->input,ch, // // //for(i;i<0x78;i++) //input_report_key(vinput_dev->input,i, // // // // //i=0; //for(i;i<0x78;i++) // // // input_sync(vinput_dev->input); printk("vinput } return } static { int printk("%s if(vinput_dev->p_dev printk("platform } vinput_dev->input if(!(vinput_dev->input)){ printk("%s goto } vinput_dev->input->name vinput_dev->input->evbit[0] // int for set_bit(i,vinput_dev->input->keybit); ret if(ret printk("%s goto } device_create_file(&pdev->dev,&dev_attr_push); //初始化信号量,在线程中要用到 sema_init(&(vinput_dev->sem),0); vinput_dev->run_thread return input_register: input_free_device(vinput_dev->input); alloc_input: kfree(vinput_dev); return } static .probe .driver .owner .name }, }; static { int printk("%s\n", vinput_dev if(vinput_dev printk("%s return } vinput_dev->p_dev= if(!(vinput_dev->p_dev)){ printk("%s return } ret if(ret printk("%s return } return } static { printk("%s\n", if(vinput_dev->input input_unregister_device(vinput_dev->input); } printk("%s if(vinput_dev platform_device_unregister(vinput_dev->p_dev); } printk("%s platform_driver_unregister(&vinput_driver); printk("%s kfree(vinput_dev); printk("%s } module_init(vinput_init); module_exit(vinput_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("arch"); |