linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。
对于linux输入子系统的框架结构如下图1所示:
以前我们的驱动程序打开了一个特定的设备文件“/dev/buttons”。而一般写的应用程序不会去打开这个
“/dev/buttons”。一般打开的都是原有的文件,如“ dev/tty* ” ,还有可能是不需要打开什么tty,
而 是直接“scanf()”就去获得了按键的输入。
以前写的那些驱动程序只能自已使用而非通用。要写一个通用的驱动程序,让其他应用
程序“无缝”的使用, 就是说不需要修改人家的应用程序。这需要使用现成的驱动,把自
已的设备相关的驱动放到内核中这种驱动架构 中去。这个现成的驱动就是“输入子系统--
input 子系统”。
输入子系统不仅可以让驱动编写者更加舒服,还可以让应用层编写者不需要特定的更改,就对应新增或者你写的驱动。
以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大牛们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统
使用内核提供的输入子系统,那么应用层的代码只要是按照输入子系统规范的接口来调用驱动,就不会出现混乱的情况。
驱动层
将底层的硬件输入转化为统一事件形式,向输入核心(Input Core)汇报。
它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。
主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
1.系统核心层
主要功能
- 注册主设备号
- 对于swi进入的open函数进行第一层处理,并通过次设备号选择handler进入第二层open,也就是真正的open所在的file_operation,并返回该file_opration的fd
- 提供input_register_device跟input_register_handler函数分别用于注册device跟handler
2.handler层(事件处理层)
handler层是纯软件层,包含不同的解决方案,如键盘,鼠标,游戏手柄等,但是没有设计到硬件方面的操作
对于不同的解决方案,都包含一个名为input_handler的结构体,该结构体内含的主要成员如下
.id_table 一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)
.fops 该handler的file_operation
.connect 连接该handler跟所支持device的函数
.disconnect 断开该连接
.event 事件处理函数,让device调用
h_list 也是一个链表,该链表保存着该handler到所支持的所有device的中间站:handle结构体的指针
3.device层(驱动层)
device是纯硬件操作层,包含不同的硬件接口处理,如gpio等
对于每种不同的具体硬件操作,都对应着不同的input_dev结构体
该结构体内部也包含着一个h_list
有了输入子系统这样的分层设计之后,我们编写驱动就只用管驱动层了,而应用层也只用管打开对应的输入子系统设备即可。这样就不会必须驱动编写者要告诉应用编写者需要打开哪个设备。
核心层
drivers/input.c 所以输入子系统的代码在这个 c 文件中。
看一个驱动程序是从“入口函数”开始查看。
这里注册了一个主设备号“INPUT_MAJOR”为 13 的字符设备,名字为“input”,它的
file_operations 结构是“input_fops”。这个结构中只有一个“.open”函数。 显然这个open函数里面是做了更多的其他事情的。
源码分析还不是时候,现在也正在进行源码的阅读,还是先把所有的这些函数当做STM32库函数一样来使用,先用起来更符合现实需要。
下面我们只用知道,使用输入子系统这一套方法,我们只需要编写硬件驱动层代码,那么,以什么样的方式编写呢?
入口函数:我相信只要是学过单片机的人都应该能模仿别人的调用方式实现自己的功能
出口函数:
就这样,比之前的操作简洁多了,就实现输入子系统。
实验现象:
在没有加载驱动的时候只要event0,加载驱动之后有event1了
cat tty1之后按键(这里需要注意,按键按下的时候,需要输入enter键才能显示,对应我们的按键S4):
tty的简单介绍:
控制台终端(/dev/ttyn, /dev/console)
在Linux系统中,电脑显示器通常被称为控制台终端 (Console)。他仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特别文档和之相关联:tty0、tty1、tty2 等。当您在控制台上登录时,使用的是tty1。使用Alt+[F1―F6]组合键时,我们就能够转换到tty2、tty3等上面去。tty1tty6等 称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上(这时也叫控制台终端)。因此不管当前正在使用哪个虚拟终 端,系统信息都会发送到控制台终端上。您能够登录到不同的虚拟终端上去,因而能够让系统同时有几个不同的会话期存在。只有系统或终极用户root能够向 /dev/tty0进行写操作 即下例:
1、# tty(查看当前TTY) /dev/tty1 2、#echo "test tty0" > /dev/tty0 test tty0
这里的标准输入和标准输出以及标准错误都是我们串口控制台/dev/console
现在把tty1重定向成标准输入:
可以看到,第一行的l^H^H是我键盘输入的,此时键盘的l(小写L)是可以被识别,但是我按键盘的退格键,就打印^H。
此时,我们按下2440上面的按键依次为l然后s最后enter键,果然,我们通过按键实现了ls的功能,列举出了现在目录文件信息。我们还有一个S5是Shift键,此时我们按住Shift键的同时,按下l和s,可以看到大写的L和S。证明我们的驱动实验成功。
完整代码Code:
/* 参考driversinputkeyboardgpio_keys.c */ #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/irq.h> #include <asm/gpio.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> struct pin_desc{ int irq; char *name; unsigned int pin; unsigned int key_val; }; struct pin_desc pins_desc[4] = { {IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L}, {IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S}, {IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER}, {IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT}, }; static struct input_dev *buttons_dev; static struct pin_desc *irq_pd; static struct timer_list buttons_timer; static irqreturn_t buttons_irq(int irq, void *dev_id) { /* 10ms后启动定时器 */ irq_pd = (struct pin_desc *)dev_id; mod_timer(&buttons_timer, jiffies+HZ/100); return IRQ_RETVAL(IRQ_HANDLED); } static void buttons_timer_function(unsigned long data) { struct pin_desc * pindesc = irq_pd; unsigned int pinval; if (!pindesc) return; pinval = s3c2410_gpio_getpin(pindesc->pin); if (pinval) { /* 松开 : 最后一个参数: 0-松开, 1-按下 */ input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);//报告一个新的输入事件 input_sync(buttons_dev);//同步事件 } else { /* 按下 */ input_event(buttons_dev, EV_KEY, pindesc->key_val, 1); input_sync(buttons_dev); } } static int buttons_init(void) { int i; /* 1. 分配一个input_dev结构体 */ buttons_dev = input_allocate_device();; /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY, buttons_dev->evbit);//按键类 set_bit(EV_REP, buttons_dev->evbit);//按键可重复,即按下不松开的时候,一直保持这个值 /* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */ set_bit(KEY_L, buttons_dev->keybit); set_bit(KEY_S, buttons_dev->keybit); set_bit(KEY_ENTER, buttons_dev->keybit); set_bit(KEY_LEFTSHIFT, buttons_dev->keybit); /* 3. 注册 */ input_register_device(buttons_dev); /* 4. 硬件相关的操作 */ init_timer(&buttons_timer); buttons_timer.function = buttons_timer_function; add_timer(&buttons_timer); for (i = 0; i < 4; i++) { request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]); } return 0; } static void buttons_exit(void) { int i; for (i = 0; i < 4; i++) { free_irq(pins_desc[i].irq, &pins_desc[i]); } del_timer(&buttons_timer); input_unregister_device(buttons_dev); input_free_device(buttons_dev); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");