概述
Input子系统介绍
以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。
输入子系统引入的好处:
- (1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
- (2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
- (3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。
输入设备(按键、键盘、触摸屏、鼠标等)都是典型的字符设备,其主设备号固定为13,其一般的工作机制是在底层按键、触摸、鼠标点击等动作发生时产生一个中断(或驱动通过timer定时查询),然后CPU通过 SPI、IIC或外部存储器总线读取键值、坐标等数据,放在一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据,其过程如下图所示:
在Linux中,输入子系统 是由输入子系统设备驱动层、输入子系统核心层(input core)和输入子系统事件处理层(Event handler)组成,如下图所示。其中设备层驱动提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一的访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在关心对设备文件的操作,而只要关心对各硬件寄存器的操作和提交的输入事件。
Input子系统框架
Input子系统将所有的输入设备统称为像鼠标输入、键盘输入、joydev输入等,将输入的数据封装成统一的事件格式(struct input_event)上传到应用,而将具体的硬件设备分离出来(这才是我们要做的事),如左图,系统分层结构下,我们的应用程序就可以不用关心获取的数据来源于SPI的键盘,还是IIC的键盘,它只需要关心获取到数据的具体含义就行了,这样就保证了更换不同的硬件设备,不用修改一行应用程序代码,如下图所示:
按照如上的思想,我们可以很明确的看出input子系统的框架包含三个层次:设备驱动层(input dev)、输入子系统核心层(input core)和输入子系统事件处理层(Event handler)。这三个层次的框架及对应到Linux内核的源码关系如下图所示
Input子系统实现
1.输入子系统的注册
- input子系统的设备主设备号固定为13,并且在系统启动的时候就已经向系统注册了。
- 首先input子系统的核心input.c文件的最后有如下语句:subsys_initcall(input_init);该语句向系统注册了一个子系统,系统启动时就会调用input_init。
- 然后在input_init()函数中注册一个input_class类,创建了一个主设备号为13的字符设备,并在文件开关创建了两个链表:input_dev_list、input_handler_list,分别用于链接input_dev(输入设备)和input_handler(输入设备驱动)。
- 在这里没创建设备节点,因为创建设备节点需要知道次设备号,而这里只是知道主设备号为13.创建设备节点为的地方在具休的input_dev和input_handler匹配调用connect的时候进行创建。
2. 应用程序与驱动交互
字符设备、input_handler、input_handler和input_dev几个的关系:
- •Linux系统启动时注册输入子系统(注册主设备号为13的字符设备);
- •应用程序调用oper(),对应调用输入子系统的input_open_file();
- •Input_open_file找到对应的input_handler,并调用其中的open();
- •应用程序调用read()函数,对应调用open()中找到input_handler中的read()函数,阻塞;
- •驱动收到硬件访问需求,进入中断处理函数,对应到input_dev;
- •驱动调用input_event()上报事件,上报过程为:通过input_dev找到input_handler结构体,在通过input_handler结构体找匹配的input_handler,然后调用该input_handler的event()函数,该函数即是唤醒对read()、write()函数的实现。
从应用到底层的匹配过程是通过input_handler,具体驱动中是通过静态全局指针数组变量input_table[]实现的;而硬件到应用程序的匹配过程是通过input_handler结构体找到对应的input_handler,从而实现数据传输的,具体到代码就是通过input_handler->connect()函数将input_dev、input_handler结构体和input_handler三者进行绑定的,绑定关系如图
3. Input子系统接口
Input_dev分配接口:
struct input_dev *input_allocate_device*(void);
Void input_free_device(struct input_dev *dev);
Input_dev注册接口:
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
Input_handler注册接口:
int input_register_handle(stuct input_handle *handle);
void input_unregister_handle(stuct input_handle *handle);
事件支持:
Set_bit告诉input子系统它支持哪些事件
Set_bit(EV_KEY,button_dev.evbit)
Struct input_dev中有两个成员,一个是evbit;一个是keybit.分别用来表示设备所支持的事件类型和按键类型。
Linux输入子系统支持的事件类型:
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC 0x04 其它
EV_SW 0x05 开关
EV_LED 0x11 按键/设备灯
EV_SND 0x12 声音/警报
EV_REP 0x14 重复
EV_FF 0x15 力反馈
EV_PWR 0x16 电源
EV_FF_STATUS 0x17 力反馈状态
EV_MAX 0x1f 事件类型最大个数和提供位掩码支持
Linux输入子系统提供了设备驱动层上报输入事件的函数,在include/linux/input.h中:
void input_report_key(struct input_dev *dev, unsigned int code, int value); //上报按键事件
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); //上报绝对坐标事件
……
当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:
void input_sync(struct input_dev *dev);
Input子系统实例
在Linux内核文档的documentation/input下,有一个input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。提供的案例代码描述了一个button设备,产生的事件通过BUTTON_PORT引脚获取,当有按下/释放发生时,BUTTON_IRQ被触发,以下是驱动的源代码:
static struct input_dev *button_dev;
static void button_interrupt(int irq, void*dummy, struct pt_regs *fp)
{
input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);
input_sync(button_dev);
}
static int __init button_init(void)
{
int error;
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) {
printk(KERN_ERR"button.c: Can't allocate irq %dn", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR"button.c: Not enough memoryn");
error = -ENOMEM;
goto err_free_irq;
button_dev->evbit[0] = BIT(EV_KEY);
button_dev->keybit[LONG(BTN_0)] = BIT(BTN_0);
error = input_register_device(button_dev);
if (error) {
printk(KERN_ERR"button.c: Failed to register devicen");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void)
{
input_unregister_device(button_dev);
free_irq(BUTTON_IRQ, button_interrupt);
}
module_init(button_init);
module_exit(button_exit);
最后
以上就是沉默棒球为你收集整理的Linux 输入子系统Input子系统介绍Input子系统框架Input子系统实现Input子系统实例的全部内容,希望文章能够帮你解决Linux 输入子系统Input子系统介绍Input子系统框架Input子系统实现Input子系统实例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复