linux内核input子系统解析

http://www.cnblogs.com/alexyuyu/articles/4115566.html

linux内核input子系统解析

linux内核input子系统解析

2010-06-18 22:58 8422人阅读 评论(16) 收藏 举报

作者:刘洪涛,华清远见嵌入式学院讲师。

    Android、X windows、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。

因为input子系统已经完成了字符驱动的文件操作接口,所以编写驱动的核心工作是完成input系统留出的接口,工作量不大。但如果你想更灵活的应用它,就需要好好的分析下input子系统了。

一、input输入子系统框架

下 图是input输入子系统框架,输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。

注意:keyboard.c不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。

二、Input driver编写要点

1、分配、注册、注销input设备

struct input_dev *input_allocate_device(void)

int input_register_device(struct input_dev *dev)

void input_unregister_device(struct input_dev *dev)

2、设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息

参见usb键盘驱动:usbkbd.c

usb_to_input_id(dev, &input_dev->id);//设置bustype、vendo、product等

input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP);//支持的事件类型

input_dev->ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL) | BIT(LED_COMPOSE) | BIT(LED_KANA);// EV_LED事件支持的事件码

for (i = 0; i < 255; i++)

set_bit(usb_kbd_keycode[i], input_dev->keybit); //EV_KEY事件支持的事件码

include/linux/input.h中定义了支持的类型(下面列出的是2.6.22内核的情况)

#define EV_SYN 0x00

#define EV_KEY 0x01

#define EV_REL 0x02

#define EV_ABS 0x03

#define EV_MSC 0x04

#define EV_SW 0x05

#define EV_LED 0x11

#define EV_SND 0x12

#define EV_REP 0x14

#define EV_FF 0x15

#define EV_PWR 0x16

#define EV_FF_STATUS 0x17

#define EV_MAX 0x1f

一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。

3、如果需要,设置input设备的打开、关闭、写入数据时的处理方法

参见usb键盘驱动:usbkbd.c

input_dev->open = usb_kbd_open;

input_dev->close = usb_kbd_close;

input_dev->event = usb_kbd_event;

4、在发生输入事件时,向子系统报告事件

用于报告EV_KEY、EV_REL、EV_ABS等事件的函数有:

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)

如果你觉得麻烦,你也可以只记住1个函数(因为上述函数都是通过它实现的)

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

三、Event Handler层解析

1、Input输入子系统数据结构关系图

2、input_handler结构体

以evdev.c中的evdev_handler为例:

static struct input_handler evdev_handler = {

.event = evdev_event, //向系统报告input事件,系统通过read方法读取

.connect = evdev_connect, //和input_dev匹配后调用connect构建

.disconnect = evdev_disconnect,

.fops = &evdev_fops, //event设备文件的操作方法

.minor = EVDEV_MINOR_BASE, //次设备号基准值

.name = "evdev",

.id_table = evdev_ids, //匹配规则

};

3、input字符设备注册过程

drivers/input/input.c中:

static int __init input_init(void)

{

int err;

err = class_register(&input_class);

……

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

……

}

input_fops定义:

static const struct file_operations input_fops = {

.owner = THIS_MODULE,

.open = input_open_file,

};

Input_dev和input_handler匹配后调用input_handler的connect。以evdev_handler为例:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,

const struct input_device_id *id)

{

struct evdev *evdev;

struct class_device *cdev;

dev_t devt;

int minor;

int error;

for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);

if (minor == EVDEV_MINORS) {

printk(KERN_ERR "evdev: no more free evdev devices/n");

return -ENFILE;

}

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//为每个匹配evdev_handler的设备创建一个evdev。

if (!evdev)

return -ENOMEM;

INIT_LIST_HEAD(&evdev->client_list);

init_waitqueue_head(&evdev->wait);

evdev->exist = 1;

evdev->minor = minor;

evdev->handle.dev = dev;

evdev->handle.name = evdev->name;

evdev->handle.handler = handler;

evdev->handle.private = evdev;

sprintf(evdev->name, "event%d", minor);

evdev_table[minor] = evdev;//记录evdev的位置,字符设备/dev/input/evnetx访问时根据次设备号及EVDEV_MINOR_BASE最终在evdev_open中找到对应的evdev

devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

cdev = class_device_create(&input_class, &dev->cdev, devt,

dev->cdev.dev, evdev->name);//创建了event字符设备节点

……

}

4、input字符设备的打开过程

static int input_open_file(struct inode *inode, struct file *file)

{

struct input_handler *handler = input_table[iminor(inode) >> 5];

//得到对应的input_handler

const struct file_operations *old_fops, *new_fops = NULL;

int err;

if (!handler || !(new_fops = fops_get(handler->fops)))

//取出对应input_handler的file_operations

return -ENODEV;

if (!new_fops->open) {

fops_put(new_fops);

return -ENODEV;

}

old_fops = file->f_op;

file->f_op = new_fops;//重定位打开的设备文件的操作方法

err = new_fops->open(inode, file);

if (err) {

fops_put(file->f_op);

file->f_op = fops_get(old_fops);

}

fops_put(old_fops);

return err;

}

5、input字符设备的其它操作

由于在open阶段已经把设备文件的操作操作方法重定位了到了具体的input_handler,所以其它接口操作(read、write、ioctl等),由各个input_handler的fops方法决定。如evdev.c中的:evdev_fops

 

 

 

 

linux输入子系统

本节从整体上讲解了输入子系统的框架结构。有助于读者从整体上认识linux的输入子系统。在陷入代码分析的过程中,通过本节的知识能够找准方向,明白原理。

本节重点:

 

  •          输入子系统的框架结构
  •          各层对应内核中的文件位置
  •          输入子系统的事件处理机制
  •          输入子系统的驱动层基本操作流程
  •          输入子系统的驱动层常用函数

 

本节难点:

  •          输入子系统的事件处理机制
  •          输入子系统的驱动工作流程

 

1    初识linux输入子系统

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。

对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。

对于linux输入子系统的框架结构如下图1所示:

 

图1  linux输入子系统框架结构

 

由上图所展现的内容就是linux输入子系统的分层结构。

/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

下图2简单描述了linux输入子系统的事件处理机制:

 

图2  linux输入子系统事件处理机制

 

由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

作为输入设备的驱动开发者,需要做以下几步:

?           在驱动加载模块中,设置你的input设备支持的事件类型,类型参见表1设置

?           注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)

?           将输入设备注册到输入子系统中

 

表1  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    事件类型最大个数和提供位掩码支持

由表1可知,设备所能表示的事件种类,一个设备可以选择一个或多个事件类型上报给输入子系统。

Linux输入子系统提供了设备驱动层上报输入事件的函数,在include/linux/input.h中:

voidinput_report_key(struct input_dev *dev, unsigned int code, int value);      //上报按键事件

voidinput_report_rel(struct input_dev *dev, unsigned int code, int value);       //上报相对坐标事件

voidinput_report_abs(struct input_dev *dev, unsigned int code, int value);              //上报绝对坐标事件

……

当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:

 

[cpp] view plaincopy

 

  1. void input_sync(struct input_dev *dev);  

 

2    输入设备驱动的简单案例

在Linux内核文档的documentation/input下,有一个input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。

提供的案例代码描述了一个button设备,产生的事件通过BUTTON_PORT引脚获取,当有按下/释放发生时,BUTTON_IRQ被触发,以下是驱动的源代码:

 

[cpp] view plaincopy

 

  1. #include <linux/input.h>                                                                                                          
  2.  #include <linux/module.h>  
  3.  #include <linux/init.h>  
  4.   
  5.  #include <asm/irq.h>  
  6.  #include <asm/io.h>  
  7.   
  8.  static struct input_dev *button_dev;  
  9.   
  10.  static void button_interrupt(int irq, void*dummy, struct pt_regs *fp)  
  11.  {  
  12.         input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);  
  13.         input_sync(button_dev);  
  14.  }        
  15.   
  16.  static int __init button_init(void)  
  17.  {  
  18.         int error;  
  19.           
  20.         if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) {  
  21.                  printk(KERN_ERR"button.c: Can't allocate irq %d\n", button_irq);  
  22.                  return -EBUSY;  
  23.         }        
  24.           
  25.          button_dev = input_allocate_device();  
  26.         if (!button_dev) {  
  27.                  printk(KERN_ERR"button.c: Not enough memory\n");  
  28.                  error = -ENOMEM;  
  29.                  goto err_free_irq;  
  30.         }  
  31.   
  32.         button_dev->evbit[0] = BIT(EV_KEY);  
  33.         button_dev->keybit[LONG(BTN_0)] = BIT(BTN_0);  
  34.   
  35.         error = input_register_device(button_dev);  
  36.         if (error) {  
  37.                  printk(KERN_ERR"button.c: Failed to register device\n");  
  38.                  goto err_free_dev;  
  39.         }  
  40.   
  41.         return 0;  
  42.   
  43.  err_free_dev:  
  44.         input_free_device(button_dev);  
  45.  err_free_irq:  
  46.         free_irq(BUTTON_IRQ, button_interrupt);  
  47.         return error;  
  48.  }  
  49.   
  50.  static void __exit button_exit(void)  
  51.  {  
  52.        input_unregister_device(button_dev);  
  53.         free_irq(BUTTON_IRQ, button_interrupt);  
  54. }  
  55.   
  56. module_init(button_init);  
  57. module_exit(button_exit);  

 

编写基于输入子系统的设备驱动程序需要包含<linux/input.h>,因为它包含了输入子系统的接口和所有的宏定义,这些内容在编写输入设备驱动程序时需要用到。

button_init函数说明:

当模块加载(insmod)或内核引导过程中,button_init函数会被调用。首先做的工作是获取能够正确控制硬件设备的硬件资源(例如内存、IO 内存、中断和DMA),在代码中BUTTON_IRQ作为BUTTON设备的中断资源,通过request_irq()函数被申请注册。当有按键按下/释 放时,调用button_interrupt()中断处理函数获取按键值BUTTON_PORT(BUTTON设备的I/O资源)。

那么输入子系统怎么能够知道这个设备为输入设备呢?通过第8行为设备定义一个用于描述一个输入设备对象。

 

[cpp] view plaincopy

 

  1. static struct input_dev *button_dev;  

 

定义了button_dev之后,如何通知输入子系统有新的输入设备了呢?或者说如何把一个新的输入设备加入到输入子系统中呢?可以通过输入子系统核心层input.c中提供的函数分配一个输入设备,在代码的第25行。

 

[cpp] view plaincopy

 

  1. button_dev= input_allocate_device();  

 

有了输入设备的描述,当事件产生时,输入子系统怎么能够知道设备产生的事件类型呢?通过32和33行的代码。

 

[cpp] view plaincopy

 

  1. button_dev->evbit[0]= BIT(EV_KEY);  
  2. button_dev->keybit[LONG(BTN_0)]= BIT(BTN_0);  

 

其中evbit和keybit成员分别代表设备产生的事件类型和上报的按键值。其中输入子系统的一些位操作NBITS、BIT、LONG经常被用到:

 

[cpp] view plaincopy

 

  1. #defineNBITS(x) (((x)/BITS_PER_LONG)+1)                 //通过位x获取数组的长度  
  2. #defineBIT(x)       (1UL<<((x)%BITS_PER_LONG))       //返回位x在数组中的位域  
  3. #defineLONG(x) ((x)/BITS_PER_LONG)                        //返回位x的索引  

 

以上的工作做完之后,即可注册为输入设备了,代码的35行。

 

[cpp] view plaincopy

 

  1. input_register_device(button_dev);  

 

这个函数把button_dev输入设备挂入输入设备链表中,并且通知事件处理层调用connect函数完成设备和事件处理的绑定,当用户打开设备时,便 能够调用到相应的事件处理接口获得硬件上报的数据了。input_register_device()函数是会睡眠的函数,因此不能够在中断上下文和持有 自旋锁的代码中调用。

当我们把上面的工作做完之后,设备驱动中唯一值得关注的就是button_interrupt()中断处理函数了。当按键动作发生,button_interrupt()函数被调用,完成事件的上报由其中的两条语句完成。

 

[cpp] view plaincopy

 

  1. input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);  
  2. input_sync(button_dev);  

 

其中input_report_key上报了这是一个按键事件,且它的值为inb(BUTTON_PORT) & 1,由于案例代码只产生一个按键的值,因此input_sync()在这里不起关键作用。但如果是一个触摸屏,即有x坐标和y坐标,则需要通过 input_sync()函数把x和y坐标完整地传递给输入子系统。

用于测试的应用层代码:
test_key_write.rar
test_key_read.rar

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值