Linux输入子系统详解

版权声明:本文为卫伟学习总结文章,转载请注明出处!
导读:Linux输入子系统由驱动层、输入子系统核心层、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。


驱动层
将底层的硬件输入转化为统一事件形式,向输入核心层(Input Core)汇报。
输入子系统核心层

  • 为驱动层提供输入设备注册与操作接口,如:input_register_device;
  • 通知事件处理层对事件进行处理; 在/Proc下产生相应的设备信息。

事件处理层
主要是和用户空间交互(Linux在用户空间将所有的设备都当作文件处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
设备描述
input_dev结构是实现设备驱动核心工作:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。
注册输入设备函数:
int input_register_device(struct input_dev *dev);
注销输入设备函数:
void input_unregister_device(struct input_dev *dev);
驱动实现--初始化(事件支持)set_bit()告诉input输入子系统支持那些事件,那些按键。例如:set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)。
struct input_dev中有两个成员为

  • evbit事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)。
  • eybit按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)。

驱动实现——报告事件用于报告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)

驱动实现——报告结束input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:

input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束

input.c文件分析

drivers/input/input.c:
input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //入口函数

static const struct file_operations input_fops = {
   .owner = THIS_MODULE,
   .open = input_open_file,
};

怎么读按键?

input_open_file
struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops)  //  =>&evdev_fops
file->f_op = new_fops;
err = new_fops->open(inode, file);

 app: read > ... > file->f_op->read  

input_table数组由谁构造?
input_register_handler
注册input_handler:

input_register_handler
// 放入数组
input_table[handler->minor >> 5] = handler;

// 放入链表
list_add_tail(&handler->node, &input_handler_list);

// 对于每个input_dev,调用input_attach_handler
list_for_each_entry(dev, &input_dev_list, node)
    input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev

注册输入设备:

input_register_device
// 放入链表
list_add_tail(&dev->node, &input_dev_list);

// 对于每一个input_handler,都调用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler); // 根据input_handler的id_table判断能否支持这个input_dev

input_attach_handler
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,
根据input_handler的id_table判断这个input_handler能否支持这个input_dev,
如果能支持,则调用input_handler的connect函数建立"连接"。

怎么建立连接?

  • 分配一个input_handle结构体;
  • input_handle.dev = input_dev; // 指向左边的input_dev
    input_handle.handler = input_handler; // 指向右边的input_handler
  • 注册: input_handler->h_list = &input_handle;
    inpu_dev->h_list = &input_handle;
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一个input_handle

// 设置
evdev->handle.dev = dev;  // 指向左边的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;  // 指向右边的input_handler
evdev->handle.private = evdev;

// 注册
error = input_register_handle(&evdev->handle);

怎么读按键?

evdev_read
        // 无数据并且是非阻塞方式打开,则立刻返回
        if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
            return -EAGAIN;
        
        // 否则休眠 
        retval = wait_event_interruptible(evdev->wait,
            client->head != client->tail || !evdev->exist);

谁来唤醒?
evdev_event
wake_up_interruptible(&evdev->wait);
**
evdev_event被谁调用?**
猜:应该是硬件相关的代码,input_dev那层调用的
在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数

gpio_keys_isr
// 上报事件
input_event(input, type, button->code, !!state);
input_sync(input);

input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
struct input_handle *handle;

list_for_each_entry(handle, &dev->h_list, d_node)
    if (handle->open)
        handle->handler->event(handle, type, code, value);

怎么写符合输入子系统框架的驱动程序?

  • 向内核申请input_dev结构体;
  • 设置input_dev的成员;
  • 注册input_dev 驱动设备;
  • 初始化定时器和中断;
  • 写中断服务函数;
  • 写定时器超时函数;
  • 在出口函数中 释放中断函数,删除定时器,卸载释放驱动。

具体代码如下(都加了注释):

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


struct input_dev *buttons_dev;            //  定义一个input_dev结构体  
static struct ping_desc *buttons_id;          //保存dev_id,在定时器中用
static struct timer_list buttons_timer;    //定时器结构体  

struct  ping_desc{

   unsigned  char  *name;          //中断设备名称
   int            pin_irq;          //按键的外部中断标志位
   unsigned  int    pin;                //引脚
   unsigned int  irq_ctl;           //触发中断状态:   IRQ_TYPE_EDGE_BOTH
   unsigned  int    button;         //dev_id,对应键盘的 L ,  S,  空格,  enter      
};

    // KEY1 -> L
    // KEY2 -> S
    // KEY3 -> 空格
    // KEY4 -> enter
static  struct ping_desc   buttons_desc[5]=
{
   {"s1", IRQ_EINT0,   S3C2410_GPF0,  IRQ_TYPE_EDGE_BOTH,KEY_L},
   {"s2", IRQ_EINT2,   S3C2410_GPF2,  IRQ_TYPE_EDGE_BOTH,KEY_S},
   {"s3", IRQ_EINT11, S3C2410_GPG3 , IRQ_TYPE_EDGE_BOTH,KEY_SPACE},
   {"s4", IRQ_EINT19, S3C2410_GPG11,IRQ_TYPE_EDGE_BOTH,KEY_ENTER},
};



/*5. 写中断服务函数*/
static irqreturn_t  buttons_irq (int irq, void *dev_id)       //中断服务函数
{
   buttons_id=(struct ping_desc *)dev_id;             //保存当前的dev_id
   mod_timer(&buttons_timer, jiffies+HZ/100 );   //更新定时器值 10ms 
   return 0;
}



 /*6.写定时器超时函数*/
void buttons_timer_function(unsigned long i)
{
   int val;
   val=s3c2410_gpio_getpin(buttons_id->pin);             //获取是什么电平 
  if(val)         //高电平,松开
   {
     /*上报事件*/
     input_event(buttons_dev,EV_KEY,buttons_id->button, 0);  //上报EV_KEY类型,button按键,0(没按下)
     input_sync(buttons_dev);         // 上传同步事件,告诉系统有事件出现                       
  }

 else      //低电平,按下
  {
     /*上报事件*/
     input_event(buttons_dev, EV_KEY, buttons_id->button, 1);  //上报EV_KEY类型,button按键,1(按下)
     input_sync(buttons_dev);       // 上传同步事件,告诉系统有事件出现
 }
}


static int buttons_init(void)   //入口函数
{
   int i;      
   buttons_dev=input_allocate_device();  //1.向内核 申请input_dev结构体
   /*2.设置input_dev ,  */
   set_bit(EV_KEY,buttons_dev->evbit);       //支持键盘事件
   set_bit(EV_REP,buttons_dev->evbit);       //支持键盘重复按事件

   set_bit(KEY_L,buttons_dev->keybit);                  //支持按键 L
   set_bit(KEY_S,buttons_dev->keybit);                //支持按键 S
   set_bit(KEY_SPACE,buttons_dev->keybit);      //支持按键 空格
   set_bit(KEY_ENTER,buttons_dev->keybit);     //支持按键 enter

   /*3.注册input_dev */
   input_register_device(buttons_dev);

  
   /*4. 初始化硬件:初始化定时器和中断*/      
   // KEY1 -> L
   // KEY2 -> S
   // KEY3 -> 空格
   // KEY4 -> enter
   init_timer(&buttons_timer);
   buttons_timer.function=buttons_timer_function;
   add_timer(&buttons_timer);

   for(i=0;i<4;i++)
   request_irq(buttons_desc[i].pin_irq, buttons_irq, buttons_desc[i].irq_ctl, buttons_desc[i].name, &buttons_desc[i]);

   return 0;
}


static int buttons_exit(void)  //出口函数
{
   /*7.释放中断函数,删除定时器,卸载释放驱动*/
   int i;
   for(i=0;i<4;i++)
          free_irq(buttons_desc[i].pin_irq,&buttons_desc[i]);    //释放中断函数

   del_timer(&buttons_timer);   //删除定时器

   input_unregister_device(buttons_dev);     //卸载类下的驱动设备
   input_free_device(buttons_dev);                //释放驱动结构体
   return 0; 
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL v2");

测试运行
挂载键盘驱动后, 如下图,可以通过 ls -l /dev/event* 命令查看已挂载的设备节点:

输入子系统的主设备号为13,其中event驱动本身的此设备号是从64开始的,如上图,内核启动时,会加载自带触摸屏驱动,所以我们的键盘驱动的次设备号=64+1

测试运行有两种,一种是直接打开/dev/tyy1,第二种是使用exec命令

  • cat /dev/tty1 //tty1:LCD终端,就会通过tty_io.c来访问键盘驱动,然后打印在tty1终端上
  • exec 0

你可能感兴趣的:(Linux输入子系统详解)