输入子系统笔记!

学完第一期基础驱动入门后,接着开始讲input子系统。看着视频,心里有个大概对输入子系统的认识,但是说不出来。百度一下,摘抄吧。

https://blog.csdn.net/zouleideboke/article/details/70650001

1.什么是子系统?

        内核是操作系统的核心。Linux内核提供很多基本功能,如虚拟内存、多任务、共享库、需求加载、共享写时拷贝(Copy-On-Write)以及网络功能等。增加各种不同功能导致内核代码不断增加。 
          Linux内核把不同功能分成不同的子系统,通过一种整体的结构把各种功能集合在一起,提高了工作效率。同时还提供动态加载模块的方式,为动态修改内核功能提供了灵活性。

    2.linux 驱动子系统

         linux内核中自带了很多的驱动子系统,其中比较典型的有:input子系统,led子系统,framebuffer子系统(LCD),I2c子系统等,这些子系统它是通过一层一层的函数传递与封装,它实现了设备驱动的注册,以及定义了file_operations结构体里面的各种函数操作等,不需要在单独的设备驱动代码中进行注册,定义,直接调用相应的的子系统即可,

      3.linux input子系统

      以前学了单独的按键设备驱动以及led驱动,实际上,在linux中实现这些设备驱动,有一种更为推荐的方法,就是input输入子系统。平常我们的按键,触摸屏,鼠标等输入型设备都可以利用input接口(这个接口是什么呢?可以看我对按键驱动分析就一目了然)来简化驱动程序并实现设备驱动。Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互.

我对input 子系统的理解是:其实input 子系统是对linux输入设备驱动进行了高度抽象最终分成了三层:包括input设备驱动层,核心层,事件处理层,也就是说我们之前移植的按键,usb,触摸屏驱动代码也只是子系统的一部分,起初我们自己编写的那些驱动代码都是分散的,按键是按键,led是led,都没有对这些不同类别的输入设备驱动统一起来,也就是说input 子系统这种机制的出现,最大的优点我觉得就是为内核大大的简化了驱动程序!!!这个input子系统最神秘之处就是它的核心层部分,这个核心层做了什么呢?它在内核中是怎么定义的?

2.linux输入子系统原理

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

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

 核心层:       为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。

 事件处理层:事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

对于linux输入子系统的框架结构图:

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

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

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

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

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

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

1. 在驱动加载模块中,设置你的input设备支持的事件类型。(事件类型有:EV_SYN     0x00    同步事件;

                                  EV_KEY     0x01    按键事件;

                                 EV_LED     0x11    按键/设备灯等)

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

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

3.input 核心层

input核心层位于/drivers/input/input.c

3.1 入口和出口

 1 static const struct file_operations input_fops = {
 2     .owner = THIS_MODULE,
 3     .open = input_open_file,
 4 };
 5 static int __init input_init(void)
 6 {
 7     int err;
 8     err = class_register(&input_class);//注册input类,这里就是生成了sys/class/input目录,在该目录下会看到驱动链接文件
 9     if (err) {
10         printk(KERN_ERR "input: unable to register input_dev class\n");
11         return err;
12     }
13     err = input_proc_init();//在proc目录下建立input子系统目录及交互文件,即/proc/bus/input目录下的devices文件和handlers文件 
14     if (err)
15         goto fail1;
16 
17     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//注册一个主设备号为INPUT_MAJOR(13),次设备号为0~255,文件操作符为input_fops的字符设备
18     if (err) {
19         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
20         goto fail2;
21     }
22     return 0;
23  fail2:    input_proc_exit();
24  fail1:    class_unregister(&input_class);
25     return err;
26 }
27 static void __exit input_exit(void)
28 {
29     input_proc_exit();
30     unregister_chrdev(INPUT_MAJOR, "input");
31     class_unregister(&input_class);
32 }
33 subsys_initcall(input_init);
34 module_exit(input_exit);

  这个初始化函数中,有一个注册字符设备函数register_chrdev(第17行),它用到了input_fops,这个结构体为struct file_operations,但这里他只有一个input_open_file——open成员。

3.2 input_open_file函数

 1 static int input_open_file(struct inode *inode, struct file *file)
 2 {
 3     struct input_handler *handler = input_table[iminor(inode) >> 5];//根据所打开的驱动的次设备号inode,查找对应input_handler
 4     const struct file_operations *old_fops, *new_fops = NULL;
 5     int err;
 6     /* No load-on-demand here? */
 7     if (!handler || !(new_fops = fops_get(handler->fops)))//new_fops赋值为该驱动的file_operations
 8         return -ENODEV;
      ...

17 old_fops = file->f_op; 18 file->f_op = new_fops;//所打开的文件的f_op等于新的new_fops 19 err = new_fops->open(inode, file);//打开新的驱动      ...25 return err; 26 }

分析:根据inode,在input_table数组中查找的对应的input_handler,然后从中提取出.fops成员复制给new_fops,并调用其成员下的.open函数。old_fops是为了防止出错做备份用的。

3.3 input_table的分析,input_table由谁构造?

首先input_table在Input.c中定义

1 static struct input_handler *input_table[8];

2 构造

Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....的入口函数

  input_register_handler //向上注册

    ->input_table[handler->minor >> 5] = handler;

 //以Evdev.c为例
static int __init evdev_init(void)
{ 
   return input_register_handler(&evdev_handler); 
}
static struct input_handler evdev_handler = {
      .event =    evdev_event,
      .connect =    evdev_connect,
      .disconnect =    evdev_disconnect,
      .fops =        &evdev_fops,//这个结构体里有各种读写操作函数
      .minor =    EVDEV_MINOR_BASE,//次设备号 宏定义64,handler->minor >> 5后,就是2 所以它在第三项
      .name =        "evdev",
      .id_table =    evdev_ids,//用于比较是否要打开的设备是否匹配
 };
int input_register_handler(struct input_handler *handler)
{
    ...
    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5])
            return -EBUSY;
        input_table[handler->minor >> 5] = handler;//这里evdev_handler添加到input_table[2]中
    }
    ...
    return 0;
}

分析:input_table由input_register_handler函数构造。

input_register_handler

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

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

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

以上Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....向input.c注册handler,为输入设备实现一个接口

查看evdev_handler如下

static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =    evdev_connect,
    .disconnect =    evdev_disconnect,
    .fops =        &evdev_fops,
    .minor =    EVDEV_MINOR_BASE,
    .name =        "evdev",
    .id_table =    evdev_ids,//表示这个接口能够支持那些输入设备,如果能够支持就调用.connect函数,
};

3.4注册输入设备——input_register_device

 input_register_device

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

    list_for_each_entry(handler, &input_handler_list, node)//对于每一个input_handler,都调用input_attach_handler函数

      input_attach_handler(dev, handler);//根据input_handler的id_table判断是否支持这个input_dev

3.5input_attach_handler函数作用

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
   ...
    id = input_match_device(handler->id_table, dev);//根据id_table和dev判断是否匹配
    if (!id)
       return -ENODEV;
     error = handler->connect(handler, dev, id);//如果匹配,执行.conect函数
     ...
     return error;
}

总结:注册input_dev或input_handler时,会两两比较,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能够支持,则调用input_handler的connect函数建立连接。

猜你喜欢

转载自www.cnblogs.com/Rainingday/p/9048893.html
今日推荐