鲸落消零派嵌入式 Linux C ARM

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

2021-07-18  本文已影响0人  Leon_Geo

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

  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

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;
 }
 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函数
 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 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);
 static int __init evdev_init(void)
         {
          return input_register_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,
         };
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设备,若两者支持便进行连接。

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是否匹配, 若两者匹配便进行连接

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函数建立连接

 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结构体        
         ... ...
         }
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;
         }

事件驱动的.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);
   ... ...           //上传数据
 }

通过搜索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函数中去处理事件。

应该就是之前分析的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事件函数。

总结:

上一篇下一篇

猜你喜欢

热点阅读