【驱动】input子系统全面分析


初识linux输入子系统

 

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

 

 


 

 

input输入子系统框架分析

 

  输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。

  一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。

 

1.系统核心层

  

 

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

 

4:input_handler_list和input_device_list

  对于handler和device,分别用链表input_handler_list和input_device_list进行维护,

  当handler或者device增加或减少的时候,分别往这两链表增加或删除节点。

 

5.input子系统框架图

 

 

 


 

 

input子系统调用过程分析

 

  1.当外部应用程序需要调用输入子系统的函数时,会先通过主设备号进入到核心层,然后

  2.当外部应用程序需要调用输入子系统的函数时,

  3.当外部中断到达的时候,会先确定中断事件,然后,然后在read中返回(也就是当device有多个对应的handler的时候,input_event会向所有的handler上报事件)

  4.

  input_register_handler的内部实现:往input_handler_list加入新增的handler节点,然后对input_device_list的所有结点(也就是所有的device)进行遍历,通过.id_table查看该device是否支持该handler,

  5.当需要加入新的device时,需要先构建input_dev结构体,然后调用input_register_device对该input_dev进行注册

  

  :往input_device_list加入新增的device节点,然后对input_handler_list的所有结点(也就是所有的handler)进行遍历,通过handler 的.id_table查看该handler是否支持该device,对支持的device调用该handler的.connect,一一地构建input_handle结构体,连接handler跟device

  在输入子系统框架下,我们一般的编写驱动也就是对device部分进行编写(分配input_dev并配置,驱动入口,出口,中断时进行中断判断,然后上报事件等),然后对该device的input_dev进行注册

 


 

 

Input输入子系统数据结构分析

 

input_dev

  input_dev 这是input设备基本的设备结构,每个input驱动程序中都必须分配初始化这样一个结构,成员比较多 


  (1)有以下几个数组:

    unsigned  evbit[BITS_TO_LONGS(EV_CNT)];   
    unsigned  keybit[BITS_TO_LONGS(KEY_CNT)];   
    unsigned  absbit[BITS_TO_LONGS(ABS_CNT)];   
    unsigned  swbit[BITS_TO_LONGS(SW_CNT)];  

  evbit[BITS_TO_LONGS(EV_CNT)]; 这个数组以位掩码的形式,代表了这个

  设置方式:
  dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS)
  absbit[BITS_TO_LONGS(ABS_CNT)]; 这个数组也是以位掩码的形式,代表这个类型的
  触摸屏驱动支持EV_ABS,所以要设置这个数组, 有一个专门设置这个数组的函数

 inline  input_set_abs_params( input_dev *dev,  axis,  min,  max,  fuzz, ->absmin[axis] =->absmax[axis] =->absfuzz[axis] =->absflat[axis] =->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);  
}  

  触摸屏驱动中是这样调用的

  input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);   //这个是设置ad转换的x坐标
  input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);   //这个是设置ad转换的y坐标
  input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); //这个是设置触摸屏是否按下的标志
  设置ABS_X编码值范围为0-0x3ff,因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff。

 

 (2) struct input_id id 成员
  这个是


    __u16 vendor;    
    __u16 product;   
    __u16 version;   
 }; 

  如果需要特定的事件处理器来处理这个设备的话,这几个就非常重要,因为子系统核心是通过他们,将设备驱动与事件处理层联系起来的。但是因为触摸屏驱动所用的事件处理器为evdev,匹配所有,所有这个初始化也无关紧要。

 

input_handler

  input_handler 这是


  (1)几个操作函数

     (*)( input_handle *handle, unsigned  type, unsigned  code,  (*connect)( input_handler *handler,  input_dev *dev,   input_device_id * (*disconnect)( input_handle * (*start)( input_handle *handle);

  event 函数是当事件处理器接收到了来自input设备传来的事件时调用的处理函数,负责处理事件,
  connect 函数是当一个input设备模块注册到内核的时候调用的,将事件处理器与输入设备联系起来的函数,也就是
  disconnect 函数实现connect相反的功能。

 

  (2) 两个id

    input_device_id *id_table; 
    input_device_id *blacklist; 

  这两个数组都会用在connect函数中,input_device_id结构与input_id结构类似,但是input_device_id有一个flag,用来让程序选择比较哪项,如:busytype,vendor还是其他。

 

  (3) 两个链表

     list_headh_list;  
     list_headnode;    

 

input_handle

  input_handle 结构体代表一个成功配对的input_dev和input_handler

 *;   
     open;        
      * input_dev *dev;  
     input_handler *handler; 
     list_head    d_node;  
     list_head    h_node;  
};  

 

三个数据结构之间的关系

   是硬件驱动层,代表一个input设备
   是事件处理层,代表一个事件处理器
   属于核心层,代表一个配对的input设备与input事件处理器
  input_dev 通过全局的链接在一起。设备注册的时候实现这个操作。
  input_handler 通过全局的链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)

   没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。

  通过input_dev 和input_handler就可以找到input_handle 在设备注册和事件处理器, 注册的时候都要进行配对工作,配对后就会实现链接。

  通过input_handle也可以找到input_dev和input_handler。

 

补充两个结构体

 

  (1) evdev设备结构

 open;           
     minor;          
     input_handle handle;  
    wait_queue_head_t wait;      
     evdev_client *grab;   
     list_head client_list;  
    spinlock_t client_lock;   
     device dev;       
};  

  evdev结构体在配对成功的时候生成,由handler->connect生成,对应设备文件为/class/input/event(n)。

  如触摸屏驱动的event0,这个设备是用户空间要访问的设备,可以理解它是一个虚拟设备,因为没有对应的硬件,但是通过handle->dev 就可以找到input_dev结构,而它对应着触摸屏,设备文件为/class/input/input0。这个设备结构生成之后保存在evdev_table中,索引值是minor

 

  (2) evdev用户端结构


     head;              
     tail;              
    spinlock_t buffer_lock;   
     fasync_struct *fasync;  
     evdev *evdev;           
     list_head node;         
};  

  这个结构在进程打开event0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构。

 

 

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

 

 

 


 

 

input输入子系统主要函数分析

 

各种注册函数

  每种数据结构都代表一类对象,所以每种数据结构都会对应一个注册函数,他们都定义在子系统核心的input.c文件中。

  主要有三个注册函数

     input_register_device    
     input_register_handle    
     input_register_handler   

 

 

1.input_register_device

   注册一个input输入设备,这个注册函数在三个注册函数中是驱动程序唯一调用的。

input_register_device( input_dev * atomic_t input_no = ATOMIC_INIT( input_handler * * __set_bit(EV_SYN, dev->evbit); init_timer(&dev-> (!dev->rep[REP_DELAY] && !dev-> dev->timer.data = ( dev->timer.function = dev->rep[REP_DELAY] = dev->rep[REP_PERIOD] = (!dev-> dev->getkeycode = (!dev-> dev->setkeycode = dev_set_name(&dev->dev, (unsigned ) atomic_inc_return(&input_no) - error = device_add(&dev-> path = kobject_get_path(&dev-> printk(KERN_INFO dev->name ? dev->name : , path ? path : error = mutex_lock_interruptible(& device_del(&dev-> list_add_tail(&dev->node, & list_for_each_entry(handler, & mutex_unlock(& } View Code

  input_register_device完成的就是:初始化一些默认的值,将自己的device结构添加到linux设备模型当中,将input_dev添加到input_dev_list链表中,然后寻找合适的handler与input_handler配对,配对的核心函数是input_attach_handler。

  下面看看函数

input_attach_handler( input_dev *dev, input_handler * input_device_id * (handler->blacklist && input_match_device(handler-> - id = input_match_device(handler-> (! - error = handler-> (error && error != - handler->name, kobject_name(&dev-> } View Code

  input_attach_handler的主要功能就是调用了两个函数,一个input_match_device进行配对,一个connect处理配对成功后续工作。

  下面看看函数

input_device_id *input_match_device( input_device_id * input_dev * (; id->flags || id->driver_info; id++ (id->flags & (id->bustype != dev-> } View Code

  此函数主要是比较input_dev中的id和handler支持的id,这个存放在handler的id_table中。

  首先看id->driver_info有没有设置,如果设置了说明它匹配所有的id,evdev就是这个样的handler
  然后依据id->flag来比较内容,如果都比较成功进入MATCH_BIT,这个宏是用来按位进行比较的,功能是比较所支持事件的类型,只有所有的位都匹配才成功返回,否则进行下一个id的比较。

MATCH_BIT(bit, max) \ (i = ; i < BITS_TO_LONGS(max); i++ ((id->bit[i] & dev->bit[i]) != id-> (i != ; View Code

  这个宏对于每种事件类型,以及每种事件类型支持的编码所有的位都比较一次,看handler的id是否支持,如果有一个不支持就不会比较成功,进入下一个id进行比较。
  对于connect函数,每种事件处理器的实现都有差异,但原理都相同。

  因为触摸屏用的事件处理器为evdev,下面看看evdev的connect函数

evdev_connect( input_handler *handler, input_dev * input_device_id * evdev * (minor = ; minor < EVDEV_MINORS; minor++ (! (minor == printk(KERN_ERR - evdev = kzalloc(( (! - INIT_LIST_HEAD(&evdev-> spin_lock_init(&evdev-> mutex_init(&evdev-> init_waitqueue_head(&evdev-> dev_set_name(&evdev->dev, evdev->exist = evdev->minor = evdev->handle.dev = evdev->handle.name = dev_name(&evdev-> evdev->handle.handler = evdev->handle. = evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev. = & evdev->dev.parent = &dev-> evdev->dev.release = device_initialize(&evdev-> error = input_register_handle(&evdev-> error = error = device_add(&evdev-> input_unregister_handle(&evdev-> put_device(&evdev-> } View Code

  evdev_connect函数做配对后的善后工作,分配一个evdev结构体,并初始化相关成员,evdev结构体中有input_handle结构,初始化并注册之。

 

2.input_register_handle

   注册一个input_handle结构体,比较简单

input_register_handle( input_handle * input_handler *handler = handle-> input_dev *dev = handle-> error = mutex_lock_interruptible(&dev-> list_add_tail_rcu(&handle->d_node, &dev-> mutex_unlock(&dev-> list_add_tail(&handle->h_node, &handler-> (handler-> handler-> } View Code

  这个函数基本没做什么事,就是把一个handle结构体通过d_node链表项,分别链接到input_dev的h_list,input_handler的h_list上。

  以后通过这个h_list就可以遍历相关的input_handle了。

 

 3. input_register_handler

    注册一个input_handler结构体

input_register_handler( input_handler * input_dev * retval = mutex_lock_interruptible(& INIT_LIST_HEAD(&handler-> (handler->fops != (input_table[handler->minor >> retval = - input_table[handler->minor >> ] = list_add_tail(&handler->node, & list_for_each_entry(dev, & mutex_unlock(& } View Code

  这个函数其实和input_register_device大同小异,都是注册,都要配对。

 

4.函数调用流程 

 

 

 

 


 

 

input输入子系统事件处理机制

 

 

 

  作为输入设备的,需要做以下几步:

 

 


 

 

参考文章

http://blog.chinaunix.net/uid/26620753.html

http://blog.csdn.net/ielife/article/details/7798952

 

 

相关内容