好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Linux输入子系统框架原理解析

今天带来Linux输入子系统框架原理解析教程详解

input输入子系统框架

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

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

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

驱动层

对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。

输入子系统核心层

对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

事件处理层

对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。

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

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

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

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

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

  • 在驱动加载模块中,设置你的input设备支持的事件类型
  • 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
  • 将输入设备注册到输入子系统中

    ///////////////////////////////////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////

    输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

    struct input_dev *input_allocate_device(void);
    void input_free_device(struct input_dev *dev);

    /**
     * input_allocate_device - allocate memory for new input device
     *
     * Returns prepared struct input_dev or NULL.
     *
     * NOTE: Use input_free_device() to free devices that have not been
     * registered; input_unregister_device() should be used for already
     * registered devices.
     */
    struct input_dev *input_allocate_device(void)
    {
      struct input_dev *dev;
         /*分配一个input_dev结构体,并初始化为0*/ 
      dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
      if (dev) {
        dev->dev.type = &input_dev_type;/*初始化设备的类型*/ 
        dev->dev.class = &input_class; /*设置为输入设备类*/ 
        device_initialize(&dev->dev);/*初始化device结构*/ 
        mutex_init(&dev->mutex); /*初始化互斥锁*/ 
        spin_lock_init(&dev->event_lock); /*初始化事件自旋锁*/ 
        INIT_LIST_HEAD(&dev->h_list);/*初始化链表*/ 
        INIT_LIST_HEAD(&dev->node); /*初始化链表*/ 
    
        __module_get(THIS_MODULE);/*模块引用技术加1*/ 
      }
    
      return dev;
    }

    注册/注销输入设备用的接口如下:

    int __must_check input_register_device(struct input_dev *);
    void input_unregister_device(struct input_dev *);

    /**
     * input_register_device - register device with input core
     * @dev: device to be registered
     *
     * This function registers device with input core. The device must be
     * allocated with input_allocate_device() and all it's capabilities
     * set up before registering.
     * If function fails the device must be freed with input_free_device().
     * Once device has been successfully registered it can be unregistered
     * with input_unregister_device(); input_free_device() should not be
     * called in this case.
     */
    int input_register_device(struct input_dev *dev)
    {
        //定义一些函数中将用到的局部变量
      static atomic_t input_no = ATOMIC_INIT(0);
      struct input_handler *handler;
      const char *path;
      int error;
      //设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。
      /* Every input device generates EV_SYN/SYN_REPORT events. */
      __set_bit(EV_SYN, dev->evbit);
    
      /* KEY_RESERVED is not supposed to be transmitted to userspace. */
      __clear_bit(KEY_RESERVED, dev->keybit);
    
      /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
      input_cleanse_bitmasks(dev);
    
       //初始化 timer 定时器,用来处理重复点击按键。(去抖)
      /*
       * If delay and period are pre-set by the driver, then autorepeating
       * is handled by the driver itself and we don't do it in input.c.
       */
      init_timer(&dev->timer);
        //如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。
      if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
        dev->timer.data = (long) dev;
        dev->timer.function = input_repeat_key;
        dev->rep[REP_DELAY] = 250;
        dev->rep[REP_PERIOD] = 33;
      }
       //检查下列两个函数是否被定义,没有被定义则赋默认值。
      if (!dev->getkeycode)
        dev->getkeycode = input_default_getkeycode;//得到指定位置键值
    
      if (!dev->setkeycode)
        dev->setkeycode = input_default_setkeycode;//设置指定位置键值
        //设置 input_dev 中 device 的名字为 inputN
        //将如 input0 input1 input2 出现在 sysfs 文件系统中
      dev_set_name(&dev->dev, "input%ld",
           (unsigned long) atomic_inc_return(&input_no) - 1);
        //将 input->dev 包含的 device 结构注册到 Linux 设备模型中。
      error = device_add(&dev->dev);
      if (error)
        return error;
      //打印设备的路径并输出调试信息
      path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
      printk(KERN_INFO "input: %s as %s\n",
        dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
      kfree(path);
    
      error = mutex_lock_interruptible(&input_mutex);
      if (error) {
        device_del(&dev->dev);
        return error;
      }
        //将 input_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备)
      list_add_tail(&dev->node, &input_dev_list);
    
      list_for_each_entry(handler, &input_handler_list, node)
       //调用 input_attatch_handler()函数匹配 handler 和 input_dev。
        //这个函数很重要,在后面单独分析。
        input_attach_handler(dev, handler);
    
      input_wakeup_procfs_readers();
    
      mutex_unlock(&input_mutex);
    
      return 0;
    }

    而对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event

    /*
     * The event structure itself
     */
    
    struct input_event {
      struct timeval time; // 
    

    输入事件的类型--input_event.type

    /*
     * Event types
     */
    
    #define EV_SYN      0x00 // 
    

    Linux输入子系统提供了设备驱动层上报输入事件的函数

    报告输入事件用的接口如下:

    /* 报告指定type、code的输入事件 */
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    /* 报告键值 */
    static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
    {
      input_event(dev, EV_KEY, code, !!value);
    }
    /* 报告相对坐标 */
    static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
    {
      input_event(dev, EV_REL, code, value);
    }
    /* 报告绝对坐标 */
    static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
    {
      input_event(dev, EV_ABS, code, value);
    }
    ...

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

    void input_sync(struct input_dev *dev);

    【例子】驱动实现――报告结束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);//同步结束

    【例子】按键中断程序

    //按键初始化
    static int __init button_init(void)
    {//申请中断
      if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))
        return CEBUSY;
      set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
      set_bit(BTN_0,button_dev.keybit); //支持设备两个键
      set_bit(BTN_1,button_dev.keybit); //
      input_register_device(&button_dev);//注册input设备
    }

    /*在按键中断中报告事件*/
    Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
    {
      input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值
      input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
      input_sync(&button_dev);
    }

    【小结】input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。

    Event Handler层解析

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


    以上就是关于Linux输入子系统框架原理解析全部内容,感谢大家支持。

    查看更多关于Linux输入子系统框架原理解析的详细内容...

  阅读:59次