Linux输入子系统(1):总体框架

转载:https://blog.csdn.net/qq_695538007/article/details/9153099

一、input输入子系统总体框架

Linux输入子系统将输入驱动抽象为三层:设备驱动层、核心层、事件处理层。


设备驱动层:将底层的硬件输入事件转化为统一事件形式,向输入核心(Input Core)汇报。
核心层:承上启下:为驱动函数接口;转发事件给事件处理层。
事件处理层:和用户层交互,提供设备的read和write等函数。

二、输入子系统的分层分析

2.1 设备驱动层(用户编写)

    从输入子系统的结构框图,可以看出设备驱动层和硬件联系得很紧密。因为硬件的多样性,导致设备驱动层要适应这种多样性,因此设备驱动层是易于变化的部分。编写输入子系统驱动的主要工作就在设备驱动层。input子系统的设备驱动层为了向上屏蔽硬件设备的差异,将硬件产生的事件统一用struct input_event来表示。
struct input_event {
        struct timeval time;//事件发生的时间
        unsigned short type;//事件的类型
        unsigned short code;//事件的代码
        unsigned int value; //事件的值

}

设备驱动程序的数据结构
struct input_dev {
 const char *name;//设备名
 const char *phys;
 const char *uniq;
 struct input_id id;//用于匹配事件处理层handler
 unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//用于记录支持的事件类型的位图
 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//记录支持的按键值的位图
 unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//记录支持的相对坐标的位图
 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//记录支持的绝对坐标的位图
 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
 unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

 unsigned int keycodemax;//支持的按键值的个数
 unsigned int keycodesize;//每个键值的字节数
 void *keycode;//存储按键值的数组首地址
 int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
 int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);

 struct ff_device *ff;
 unsigned int repeat_key;//最近一次按键值,用于连击
 struct timer_list timer;//自动连击计时器
 int sync;//最后一次同步后没有新的事件置1


 int abs[ABS_MAX + 1];
 int rep[REP_MAX + 1];

 unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反映当前按键状态的位图
 unsigned long led[BITS_TO_LONGS(LED_CNT)];//反映当前led状态的位图
 unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反映当前beep状态的位图
 unsigned long sw[BITS_TO_LONGS(SW_CNT)];

 int absmax[ABS_MAX + 1];
 int absmin[ABS_MAX + 1];
 int absfuzz[ABS_MAX + 1];
 int absflat[ABS_MAX + 1];

 int (*open)(struct input_dev *dev);//打开函数
 void (*close)(struct input_dev *dev);//关闭函数
 int (*flush)(struct input_dev *dev, struct file *file);//断开连接时冲洗数据
 int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);//回调函数,可选
 struct input_handle *grab;

 spinlock_t event_lock;
 struct mutex mutex;
 unsigned int users;
 int going_away;

 struct device dev;//设备模型相关
 /*static LIST_HEAD(input_dev_list);input子系统维护的设备链表*/
 struct list_head h_list;//每个input_dev都维护一个input_handle链表,它指向处理该设备的input_handler方法
 struct list_head node;//将该设备挂靠到输入子系统维护的全局input_dev_list设备链表上

};

struct input_id {(将在2.3中讲到)
       __u16 bustype;/*总线类型*/
       __u16 vendor; /*生产商编号*/
       __u16 product; /*产品编号*/
       __u16 version;/* 版本号 */
};


设备驱动层编写主要内容:

       1.为硬件设备分配一个input_dev结构体(使用函数input_allocate_device)
       2.设置 input_dev结构体支持的事件
              set_bit(EV_KEY, input_dev->evbit)
              set_bit(KEY_ENTER, buttons_dev->keybit)(支持按键类型下的KEY_ENTER事件)
       3.注册input_dev结构体
              调用input_register_device(input_dev)
       4.上报事件(一般在中断函数内)
              input_event(input_dev ,type, code, value)
              input_sync(input_dev)(上报事件结束);

2.2 核心层(内核自带)
核心层它主要的就是起过渡作用,这主要体现在input_open_file中;我们的应用程序使用open函数打开input设备文件,首先调用的就是核心层input.c中的input_open_file函数。

然后通过设备次设备号找到与该设备相关联的设备操作函数集。

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

static int __init input_init(void)
{
 int err;
 input_init_abs_bypass();
 err = class_register(&input_class);//新建类
 if (err) {
  printk(KERN_ERR "input: unable to register input_dev class\n");
  return err;
 }
 err = input_proc_init();//新建/proc入口
 if (err)
  goto fail1;
 err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//注册输入子系统,主设备号为13
 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;
      /*static struct input_handler *input_table[8];input子系统最多维护8个事件处理方法
       *每个事件处理方法可以处理32个次设备号
       */
      handler = input_table[iminor(inode)>>5];//根据次设备号,获取该设备input_handler
      if (!handler || !(new_fops = fops_get(handler->fops))) { //提取handler里面的file_operations
              err = -ENODEV;
      goto out; }
      file->f_op = new_fops; //将设备的fops赋值给它的文件描述符的f_op
      err = new_fops->open(inode, file); //调用handler->fops->open实现文件的打开
}

2.3 事件处理层(内核自带)

       事件处理层主要是为底层的设备提供读写操作的函数,用于和用户空间进行交互。input子系统使用struct input_handler 代表设备的处理方法。

struct input_handler {
       void *private;
       //当设备驱动层有事件上报的时将导致该函数被调用
       void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
       //当调用input_register_device和input_register_handler的时候将导致该函数被调用
       int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
       void (*disconnect)(struct input_handle *handle);
       void (*start)(struct input_handle *handle);
       const struct file_operations *fops;//handler提供的读写等操作函数
       int minor;//次设备号
       const char *name;
       const struct input_device_id *id_table;//能和该input_handler匹配的input_dev的设备id
       const struct input_device_id *blacklist;//禁止和该input_handler匹配的input_dev的设备id
       struct list_head h_list ;//每个input_hander都维护一个input_handle链表,它指向该方法能支持的设备input_dev
       /*static LIST_HEAD(input_handler_list);input子系统维护的事件处理链表*/
       struct list_head node;//将该input_hander挂靠在input_handler_list链表中使用
};

当input_device或input_handler注册的时候,如果设备驱动层的input_device和事件处理层的input_handler有相匹配的话,input子系统就新建一个input_handle连联系它们。其实质就是:将新建的input_handle分别挂靠在input_dev和input_handler的h_list链表上去。这样一个input_dev就可以根据它的它的input_handle链表找到它的事件处理方法

input_handler,同样input_handler也可以根据它的input_handle链表找到它能处理的设备input_dev。可以这么说input_handle是input_dev和input_handler的纽带。

struct input_handle {
     void *private;
     int open;
     const char *name;
     struct input_dev *dev; //指向它所关联的input_dev设备
     struct input_handler *handler; //指向它所关联input_handler设备处理方法
     struct list_head d_node; //用于将该input_handle加入到它关联的input_dev的h_list链表中
     struct list_head h_node; //用于将该input_handle加入到它关联的input_handler的h_list链表中
};

2.4 input子系统工作大致流程

input子系统会维护两个全局链表: struct input_dev设备链表,struct input_handler设备处理方法链表
input子系统会维护一个全局数组: static struct input_handler *input_table[8];//按次设备号存储设备处理方法

1.设备驱动层

设备驱动层注册时调用input_register_device(),该函数完成如下工作:
(1)首先将该struct input_dev设备加入到input子系统的全局设备链表中去
(2)然后再分别取出struct input_handler链表中的每个设备处理方法,调用input_match_device(handler->id_table, dev)进行设备和设备处理方法的匹配
(3)如果匹配成功那么将新建一个struct input_handle来连接struct input_dev和struct input_handler。并且将这个新建的input_handle分别挂靠在input_dev和input_handler

所管理的input_handle的链表中去。

2.事件处理层

内核其实都自带很多的input_handler设备处理方法,evdev.c,keybroad.c等,它们调用input_register_handler()注册,该函数完成的工作如下:
(1)首先会根据注册的input_handler的次设备号将它放到一个input_table数组中去,当应用程序打开某个input设备节点时,就可以根据它的次设备号在input_table数组中找到它

的处理方法。
(2)然后将该struct input_handler加入到input子系统的全局设备处理方法链表中去
(3)再分别取出struct input_dev链表中的每个设备,调用input_match_device(handler->id_table, dev)进行设备和设备处理方法的匹配,如果匹配成功那么将新建一个struct

input_handle来连接struct input_dev和struct input_handler。并且将这个新建的input_handle分别挂靠在input_dev和input_handler所管理的input_handle的链表中去。

3. 应用程序的open操作

当应用程序打开一个input设备节点的时候,会根据设备的次设备号在input_table数组里面选择它对应的input_handler,然后将该input_handler的文件操作函数赋给struct file

*file。

4.底层的事件发生时

当硬件状态发生变化时,一般会引起一个中断,在这个中断中通过设备驱动层的input_event(dev,type, code, value)向输入核心层上报事件,这将导致input_dev关联的事件处理

层input_handler的event(handle,type, code, value)将被调用。那么用户层就可以通过调read/write进行设备读写了。

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/89021087