输入子系统(input)框架解析(基于Linux3.4.2)

我们自己写驱动的流程一般是:

  1. 自己确定或由系统自动分配主设备号;

  2. 建立fops结构;

  3. 使用register_chrdev在初始化函数中进行注册;

  4. 定义入口函数MODULE_INIT()和出口函数MODULE_EXIT()。

但这种我们自己写的驱动程序,只有自己可以调用。因为这种驱动不标准,只有别人知道驱动用法的情况下才能使用。当我们使用QT等标准程序时,这类标准程序不能打开像我们这样的野驱动,所以,我们应该让我们的驱动程序融入“标准”中去。这个标准就是linux提供的输入子系统框架。

1、Linux输入子系统框架

下图是input输入子系统框架:

image-20210714215436333

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

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

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

  3. 事件处理层:用户编程的接口(设备节点),并处理驱动层提交的数据处理。

image-20210712223400336
  • /dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
  • 事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
  • 输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

2、核心层:driver/input/input.c

分析一个驱动程序,首先看他的入口函数,即init初始化函数:

static const struct file_operations input_fops = {
  .owner = THIS_MODULE,
  .open = input_open_file,
 };
 
 static int __init input_init(void)
 {
  int err;
 
  err = class_register(&input_class);
  if (err) {
  printk(KERN_ERR "input: unable to register input_dev class\n");
  return err;
  }
 
  err = input_proc_init();
  if (err)
  goto fail1;
 
  err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
  if (err) {
  printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
  goto fail2;
  }
 
  return 0;
 
  fail2: input_proc_exit();
  fail1: class_unregister(&input_class);
  return err;
 }
  • 在初始化函数中我们可以看出这只是执行了一个普通的字符设备注册过程,创建了一个input类,在该类下并没有创建具体设备,其余没有什么特别。在字符设备注册时的操作函数集合input_fops中只有一个open函数,直观上看一个open函数并不能执行read等操作。那我们分析一下这个open函数究竟做了些什么:
static int input_open_file(struct inode *inode, struct file *file)
 {
  struct input_handler *handler = input_table[iminor(inode) >> 5];
  const struct file_operations *old_fops, *new_fops = NULL;
  int err;
 
  /* No load-on-demand here? */
  if (!handler || !(new_fops = fops_get(handler->fops)))
  return -ENODEV;
 
  /*
  * That's _really_ odd. Usually NULL ->open means "nothing special",
  * not "no device". Oh, well...
  */
  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;
 }
  1. 其中iminor(inode)函数调用了MINOR(inode->i_rdev)读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input_handler驱动处理函数handler中。因为输入子系统支持的设备大类就那么几项,每项支持最多32个设备,除32意味着可将在这32区段的设备都能准确定位到自己对应的大类上,这些设备都可以使用对应大类的公共fops。
  1. 例如:输入子系统的事件设备evdev,次设备号起始位置为64,之后32个设备都属于evdev设备,input_table[iminor(inode) >> 5],次设备号64~95的设备都对应input_table[2],这个数组位置指向的是evdev的handler,这个区段内设备的fops,在这个函数中都会指向evdev设备共用的fops,这样就不用驱动编写者自己编写fops,直接使用该设备类型下别人写出的fops即可。
  1. 若handler有值,说明挂载有这个驱动。就将handler结构体里的成员file_operations * fops赋到新的file_operations * new_fops里面。
  1. 再将新的file_operations *new_fops赋到file-> file_operations *f_op里, 此时input子系统的file_operations就等于新挂载的input驱动的file_operations结构体,即实现一个偷天换日的效果。
  1. 然后调用新挂载的input驱动的old_fops里面的成员.open函数,打开驱动open函数
  • 为什么除以32:linux输入子系统,作为将输入设备标准化处理的一种方式,使别人使用这类驱动时不必关心驱动细节即可使用。输入设备分为好多类型,键盘类、鼠标类、触摸屏类等等,linux将输入子系统设备的主设备号定为13,次设备号以32为间隔细分了几大类,(例如事件设备evdev,属于次设备号为64起始向后32个成员都属于事件设备分段,64-95这些设备都属于事件设备),这些设备次设备号除以32结果都是2,在调用驱动操作时他们都可以使用事件设备提供的fops,从而不用驱动编写者自己编写fops,大大提高了驱动易用性

  • 分析:struct input_handler *handler = input_table[iminor(inode) >> 5];

    • 将传入的节点的次设备号除以32(右移5位),并以其作为索引,将数组input_table[]中的某一项赋值给handle。

    • 创建文件操作结构体new_fops,并将其用刚刚的handle结构体中的fops初始化,实现复制。

  • 那么存放各类输入设备的input_handler结构体数组input_table是由谁构造,怎么初始化的呢?

    全局搜索发现其被input_register_handler调用:

int input_register_handler(struct input_handler *handler)
         {
          struct input_dev *dev;
         
          INIT_LIST_HEAD(&handler->h_list);
          //判断传进来的文件操作结合不是空的进行进一步操作
          if (handler->fops != NULL) {
         
          //如果分配的位置已经有值不为空,说明此位置已经被占用,返回EBUSY
          if (input_table[handler->minor >> 5])
          return -EBUSY;
          // 将 handler 放入 input_table数组,数组序号为次设备号/32
          input_table[handler->minor >> 5] = handler;
          }
          // 将 handler 放入 input_handler_list 链表
          list_add_tail(&handler->node, &input_handler_list);
         
          // 取出 input_dev_list 链表中的每一个dev与该handler进行比对
          list_for_each_entry(dev, &input_dev_list, node)
          input_attach_handler(dev, handler);
         
          input_wakeup_procfs_readers();
          return 0;
         }
  • 全局搜索input_register_handler,发现evdev.c joydev.c mousedev.c等等都是通过input_register_handler向核心层注册自己的结构:
----input_register_handler Matches (10 in 9 files) ----
     evbug_init in evbug.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&evbug_handler);
     evdev_init in evdev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&evdev_handler);
     input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) line 1182 : int input_register_handler(struct input_handler *handler)
     input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) line 1203 : EXPORT_SYMBOL(input_register_handler);
     input.h (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\include\linux) line 1130 : int input_register_handler(struct input_handler *);
     joydev_init in joydev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :    return input_register_handler(&joydev_handler);
     kbd_init in keyboard.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\char) :  error = input_register_handler(&kbd_handler);
     mousedev_init in mousedev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :    error = input_register_handler(&mousedev_handler);
     rfkill_handler_init in rfkill-input.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\net\rfkill) :     return input_register_handler(&rfkill_handler);
     tsdev_init in tsdev.c (F:\SourceInsightProj\JZ2440_2.6\linux-2.6.22.6\drivers\input) :  return input_register_handler(&tsdev_handler);
  • 分析其中一个evdev.c,其入口函数如下:
static int __init evdev_init(void)
         {
          return input_register_handler(&evdev_handler);
         }
  • 看看evdev_handler结构体的定义:
static struct input_handler evdev_handler = {
          .event =    evdev_event,
          //.connect:连接函数,将设备input_dev和某个input_handler建立连接
          .connect =  evdev_connect,
         
          .disconnect =   evdev_disconnect,

          //.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中
          .fops =     &evdev_fops,
         
          //.minor:用来存放次设备号
          /*其中EVDEV_MINOR_BASE=64, 然后调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中,所以当open打开这个input设备,就会进入 input_open_file()函数,执行evdev_handler-> evdev_fops -> .open函数*/
          .minor =    EVDEV_MINOR_BASE,
         
          .name =     "evdev",
         
          /*.id_table : 表示能支持哪些输入设备,比如某个驱动设备的input_dev->的id和某个input_handler的id_table相匹配,就会调用.connect连接函数*/
          .id_table = evdev_ids,
         };
         
         static const struct file_operations evdev_fops = {
          .owner      = THIS_MODULE,
          .read       = evdev_read,
          .write      = evdev_write,
          .poll       = evdev_poll,
          .open       = evdev_open,
          .release    = evdev_release,
          .unlocked_ioctl = evdev_ioctl,
         #ifdef CONFIG_COMPAT
          .compat_ioctl   = evdev_ioctl_compat,
         #endif
          .fasync     = evdev_fasync,
          .flush      = evdev_flush,
          .llseek     = no_llseek,
         };
  • input_register_device()函数,如何创建驱动设备
int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
 {
  ... ...
  list_add_tail(&dev->node, &input_dev_list);   //(1)放入链表中
  ... ...
  list_for_each_entry(handler, &input_handler_list, node)  //(2)
  input_attach_handler(dev, handler); 
  ... ...
 }

步骤如下:

(1)将要注册的input_dev驱动设备链入input_dev_list链表中,其中input_handler_list在前面讲过,就是存放每个input_handle驱动处理结构体。
(2)然后list_for_each_entry()函数会将每个input_handle从链表中取出并放到handler中,然后再调用input_attach_handler()函数,判断每个input_handle的id_table能否支持当前dev设备,若两者支持便进行连接。

  • 回过头来看注册input_handlerinput_register_handler()函数:
int input_register_handler(struct input_handler *handler)
     {
      struct input_dev *dev;
      int retval;
     
      retval = mutex_lock_interruptible(&input_mutex);
      if (retval)
      return retval;
     
      INIT_LIST_HEAD(&handler->h_list);
     
      if (handler->fops != NULL) {
      if (input_table[handler->minor >> 5]) {
      retval = -EBUSY;
      goto out;
      }
      input_table[handler->minor >> 5] = handler;
      }
     
      list_add_tail(&handler->node, &input_handler_list);
     
      list_for_each_entry(dev, &input_dev_list, node)//在设备列表中找出设备结构体
      input_attach_handler(dev, handler);//设备结构体中的id与handler中的id进行匹配
     
      input_wakeup_procfs_readers();
     
      out:
      mutex_unlock(&input_mutex);
      return retval;
     }

发现:不管新添加input_dev还是input_handler,都会调用input_attach_handler()判断两者id是否匹配, 若两者匹配便进行连接

  • input_attach_handler()如何实现匹配两者id
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
         {
         ... ...
         id = input_match_device(handler->id_table, dev);  //匹配两者
         
         if (!id)                                     //若不匹配,return退出
         return -ENODEV; 
         
         error = handler->connect(handler, dev, id);  //调用input_handler->connect函数建立连接
         ... ...
         }

发现:若两者匹配成功,就会自动进入input_handler 的connect函数建立连接

  • evdev.c(事件驱动) 的evdev_handler->connect函数来分析是怎样建立连接的

    • evdev_handler.connect函数是evdev_connect(),代码如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) 
         {
         ... ... 
         for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
          if (minor == EVDEV_MINORS) {  // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
          printk(KERN_ERR "evdev: no more free evdev devices\n");
          return -ENFILE;                //没找到驱动设备
          }
          ... ...
          evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //分配一个input_handle全局结构体(没有r)
          ... ...
          evdev->handle.dev = dev;              //指向参数input_dev驱动设备
         evdev->handle.name = evdev->name;
         evdev->handle.handler = handler;    //指向参数 input_handler驱动处理结构体
         evdev->handle.private = evdev;
         sprintf(evdev->name, "event%d", minor);    //(1)保存驱动设备名字, event%d
         ... ...
         devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  //(2) 将主设备号和次设备号转换成dev_t类型
         cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); 
          // (3)在input类下创建驱动设备
         ... ...
         error = input_register_handle(&evdev->handle); //(4)注册这个input_handle结构体        
         ... ...
         }
  • 是在保存驱动设备名字,名为event%d, 因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1
  • 是在保存驱动设备的主次设备号,其中主设备号INPUT_MAJOR=13,因为EVDEV_MINOR_BASE=64,所以此设备号=64+驱动程序本事子设备号
  • 在之前在2小结里就分析了input_class类结构,会在/sys/class/input类下创建驱动设备event%d
  • 最终会进入input_register_handle()函数来注册,代码在下面
int input_register_handle(struct input_handle *handle)
         {
          struct input_handler *handler = handle->handler; //handler= input_handler驱动处理结构体 
         
          list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
          list_add_tail(&handle->h_node, &handler->h_list);    // (2)
         
          if (handler->start)
          handler->start(handle);
          return 0;
         }
  • 因为handle->dev指向input_dev驱动设备,所以就是将handle->d_node放入到input_dev驱动设备的h_list链表中,即input_dev驱动设备的h_list链表就指向handle->d_node

  • 同样, input_handler驱动处理结构体的h_list也指向了handle->h_node,两者的.h_list都指向了同一个handle结构体,然后通过.h_list 来找到handle的成员.devhandler,便能找到对方建立连接了。

  • 建立了连接后,如何读取evdev.c(事件驱动) 的evdev_handler->.fops->.read函数呢?

事件驱动的.read函数是evdev_read()函数,我们来分析下:

static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
 {
  ... ...
 /*判断应用层要读取的数据是否正确*/
 if (count < evdev_event_size())
 return -EINVAL;
 
 /*在无数据且是非阻塞操作情况下打开,则立刻返回*/
  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);
   ... ...           //上传数据
 }
  • 若read函数进入了休眠状态,又是谁来唤醒

通过搜索evdev->wait这个等待队列变量,找到evdev_event被谁唤醒:

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
 {
 ... ...
  wake_up_interruptible(&evdev->wait);   //有事件触发,便唤醒等待中断
 }

其中evdev_event()evdev.c(事件驱动)的evdev_handler->.event成员。当有事件发生了(比如对于按键驱动,当有按键按下时),就会进入.event函数中去处理事件。

  • 是谁调用evdev_event()这个.event事件驱动函数?

应该就是之前分析的input_dev那层调用的,我们来看看内核 gpio_keys_isr()函数代码例子就知道了(driver/input/keyboard/gpio_key.c)

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
 {
  /*获取按键值,赋到state里*/
  ... ...
 /*上报事件*/
 input_event(input, type, button->code, !!state);  
 input_sync(input);                        //同步信号通知,表示事件发送完毕
 }

显然就是通过input_event()来调用.event事件函数,我们来看看:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 {
 struct input_handle *handle;
 ... ...
 
 /* 通过input_dev->h_list链表找到input_handle驱动处理结构体*/
 list_for_each_entry(handle, &dev->h_list, d_node)    
 if (handle->open)  //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
  handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数 
 }

若之前驱动input_dev和处理input_handler已经通过input_handler.connect函数建立起了连接,那么就调用evdev_event().event事件函数。

总结:

  • 1.注册输入子系统,进入input_init():

    • 1)创建主设备号为13的input字符设备:register_chrdev(INPUT_MAJOR, "input", &input_fops);
  • 2.open打开驱动,进入input_open_file():

    • 1)更新设备的file_oprations:file->f_op=fops_get(handler->fops);

    • 2)执行file_oprations->open函数:new_fops->open(inode, file);

  • 3.注册input_handler,进入input_register_handler():

    • 1)添加到input_table[]处理数组中:input_table[handler->minor >> 5] = handler;

    • 2)添加到input_handler_list链表中:list_add_tail(&handler->node, &input_handler_list);

    • 3)判断input_dev的id,看是否有支持这个驱动的设备:

      • 遍历查找input_dev_list链表里所有input_dev:list_for_each_entry(dev, &input_dev_list, node)

      • 判断两者id,若两者支持便进行连接:input_attach_handler(dev, handler);

  • 4.注册input_dev,进入input_register_device():

    • 1)放在input_dev_list链表中:list_add_tail(&dev->node, &input_dev_list);

    • 2)判断input_handler的id,是否有支持这个设备的驱动:

      • 遍历查找input_handler_list链表里所有input_handler:list_for_each_entry(handler, &input_handler_list, node)

      • 判断两者id,若两者支持便进行连接:input_attach_handler(dev, handler);

  • 5.判断input_handler和input_dev的id,进入input_attach_handler():

    • 1)匹配两者id:input_match_device(handler->id_table, dev);

    • 2)匹配成功,则建立连接。调用:input_handler ->connec(handler, dev, id);

  • 6.建立input_handler和input_dev的连接,进入input_handler->connect():

    • 1)创建全局结构体,通过input_handle结构体连接双方

      • 创建两者连接的input_handle全局结构体:evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

      • 连接input_dev->h_list:list_add_tail(&handle->d_node, &handle->dev->h_list);

      • 连接input_handle->h_list:list_add_tail(&handle->h_node, &handler->h_list);

  • 7.有事件发生时(比如按键中断),在中断函数中需要进入input_event()上报事件:

    • 1)找到驱动处理结构体,然后执行input_handler->event():

      • 通过input_dev ->h_list链表找到input_handle驱动处理结构体:list_for_each_entry(handle, &dev->h_list, d_node)

      • 如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式):if (handle->open)

      • 调用evdev_event()的.event事件函数:handle->handler->event(handle, type, code, value);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值