Linux输入子系统

Linux输入子系统(Input Subsystem)


1.1.input子系统概述


输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中断(或驱动通过timer定时查询),然后cpu通过SPI,I2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值,坐标等数据。


在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。

1.2. input子系统结构图


input子系统结构图如下图1所示:

图1 输入子系统结构图


1.3.linux中输入设备驱动的分层


linux中输入设备驱动的分层如下图2所示:


图2 linux中输入设备的分层

1.4. 输入子系统设备驱动层实现原理


在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
1).在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
2).将Input设备注册到input子系统中;
3).在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。

1.5.软件设计流程


软件设计流程如下图3所示

图 3 input子系统软件设计流程

1.6.与软件设计有关的API函数


1.6.1.分配一个输入设备


Struct input_dev *input_allocate_device*(void);

1.6.2.注册一个输入设备


Int input_register_device(struct input_dev *dev);


1.6.3.驱动实现-事件支持


Set_bit告诉input子系统它支持哪些事件
Set_bit(EV_KEY,button_dev.evbit)
Struct input_dev中有两个成员,一个是evbit;一个是keybit.分别用来表示设备所支持的事件类型和按键类型。


1.6.3.1事件类型


Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):

EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标
EV_ABS 0x03 绝对坐标
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈


1.6.4.驱动实现-报告事件


Void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value);//报告指定type,code的输入事件
Void input_report_key(struct input_dev *dev,unsigned int code,int value);//报告键值
Void input_report_rel(struct input_dev *dev,unsigned int code,int value);//报告相对坐标
Void input_report_abs(struct input_dev *dev,unsigned int code,int value);//报告绝对坐标
Void input_sync(struct input_dev *dev);//报告同步事件
在触摸屏驱动设计中,一次坐标及按下状态的整个报告过程如下:
Input_report_abs(input_dev,ABS_X,x);//X坐标
Input_report_abs(input_dev,ABS_Y,y);//Y坐标
Input_report_abs(input_dev,ABS_PRESSURE,pres);//压力
input_sync(struct input_dev *dev);//同步

1.6.5释放与注销设备


Void input_free_device(struct input_dev *dev);
Void input_unregister_device(struct input_dev *);




设备驱动(device driver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应handler去处理,最终handler数据复制到用户空间。

       先了解 三个 定义 在/linux/input.h 重要的结构体 input_dev 、input_handler、input_handle。
struct input_dev {
      void *private;
 
      const char *name;
      const char *phys;
      const char *uniq;
       struct input_id id;       //与 input_handler匹配 id
 
       unsigned long evbit[NBITS(EV_MAX)];             //设备 支持的 事件 类型
      unsigned long keybit[NBITS(KEY_MAX)];       //按键 事件支持的子 事件 类型
      unsigned long relbit[NBITS(REL_MAX)];
       unsigned long absbit[NBITS(ABS_MAX)];       //绝对坐标 事件支持的子事件类型
      unsigned long mscbit[NBITS(MSC_MAX)];
      unsigned long ledbit[NBITS(LED_MAX)];
      unsigned long sndbit[NBITS(SND_MAX)];
      unsigned long ffbit[NBITS(FF_MAX)];
      unsigned long swbit[NBITS(SW_MAX)];
      int ff_effects_max;
 
      unsigned int keycodemax;
      unsigned int keycodesize;
      void *keycode;
 
      unsigned int repeat_key;
      struct timer_list timer;
 
      struct pt_regs *regs;
      int state;
      int sync;
 
      int abs[ABS_MAX + 1];
      int rep[REP_MAX + 1];
 
      unsigned long key[NBITS(KEY_MAX)];
      unsigned long led[NBITS(LED_MAX)];
      unsigned long snd[NBITS(SND_MAX)];
      unsigned long sw[NBITS(SW_MAX)];
 
       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 (*accept)(struct input_dev *dev, struct file *file);
      int (*flush)(struct input_dev *dev, struct file *file);
      int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
      int (*upload_effect)(struct input_dev *dev, struct ff_effect *effect);
      int (*erase_effect)(struct input_dev *dev, int effect_id);
 
       struct input_handle *grab;       //当前 占有该设备的handle
 
      struct mutex mutex;      /* serializes open and close operations */
      unsigned int users;            // 打开 该设备的用户量
 
      struct class_device cdev;
      struct device *dev;      /* will be removed soon */
 
      int dynalloc;      /* temporarily */
 
      struct list_head      h_list;      // 链表头用于 链接该 设备 所关联 的input_handle
      struct list_head      node;      // 链表头用于 设备 链接到input_dev_list
};
      Input_dev 一个很强大的结构体, 把所有的 input 设备 (触摸屏 、键盘、鼠标等 )的 信息都考虑到了, 对于 触摸屏来说只用到 里面的 一部分 而已, 尤其 加粗 的部分 ,注意 该结构体中 最后 两行定义的 两个 list_head 结构体 list_head 在/linux/list.h 有定义,深入跟踪
struct list_head {
      struct list_head *next, *prev;
};
结构体内部 没有 定义 数据而 定义了两个指向本身 结构体 的指针 ,预先说明 一下, 所有 input  device 在注册后 加入一个input_dev_list( 输入 设备链表) 所有的e vent handler在注册后会加入一个input_handler_list( 输入 处理程序链表) ,这里 的list_head 主要 作用 是作为 input_dev_list 和input_handler_list 一个节点来保存地址。I nput_dev_list 和input_handler_list之间的对应关系由input_handle结构体桥接,具体后面说明。
 
struct input_handler {
 
      void *private;
 
      void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
      struct input_handle* (*connect)(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id);
      void (*disconnect)(struct input_handle *handle);
 
       const struct file_operations *fops;      // 提供给 用户对设备 操作 的函数指针
      int minor;
      char *name;
 
       struct input_device_id *id_table;       //与 input_dev匹配 id
      struct input_device_id *blacklist;      // 标记 的黑名单
 
       struct list_head      h_list;             //用于 链接和该 handler相关 handle
      struct list_head      node;            // 用于 将该 handler链入 input_handler_list
};
i nput_handler顾名思义, 它是用来处理 input_dev的 一个结构体, 相关 的处理函数在结构里内部都有定义, 最后 两行定义的list_head结构体作用 input_dev所定义的一样,这里不再说明。
:input_device_id 结构体 / linux/mod_devicetable.h 有定义
 
struct input_handle {
 
      void *private;
 
      int open;      // 记录 设备打开次数
      char *name;
 
      struct input_dev *dev;      // 指向 所属的input_dev
      struct input_handler *handler;      // 指向所属的 input_handler
 
      struct list_head      d_node;             //用于 链入 指向的 input_dev的handle 链表
      struct list_head      h_node;             //用于 链入 指向的 input_ handler 的handle 链表
};
可以 看到input_handle中拥有指向input_dev和input_handler的指针, input_handle是用来关联input_dev和input_handler 。为什么 用input_handle来关联input_dev和input_handler input_dev和input_handler直接对应呢?因为 一个 device 可以 对应多个 handler 一个handler也可处理多个 device。就 如一个 触摸屏 设备可以 对应 event handler 可以对应tseve handler
      input_dev 、input_ handler 、input_handle 的关系如下图 2所示
图2  input_dev,input_handlerinput_handle关系图
 
第二章、input  device 注册
      I nput  device 的注册 实际上 仅仅 只有几行代码,因为在input.c中已经将大量的代码封装好了,主需要调用几个关键的函数就能完成对input device的注册。
       xxx_ts.c中预先 定义 全局变量struct input_dev  tsdev ;然后 进入到初始化函数
static int __init xxx_probe(struct platform_device *pdev)
{
      …
 
      if (!(tsdev = input_allocate_device()))
      {
            printk(KERN_ERR "tsdev: not enough memory\n");
            err = -ENOMEM;
            goto fail;
      }
 
      …
 
      tsdev->name = "xxx TouchScreen";            //xxx 芯片型号
      tsdev ->phys = "xxx/event0";
      tsdev ->id.bustype = BUS_HOST;             //设备 id,用于匹配handler的id
      tsdev ->id.vendor  = 0x0005;
      tsdev ->id.product = 0x0001;
      tsdev ->id.version = 0x0100;
 
      tsdev ->open    = xxx_open;
      tsdev ->close   =xxx_close;
 
      tsdev ->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_SYN);            // 设置 支持的 事件 类型
tsdev ->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);           
      input_set_abs_params ( tsdev, ABS_X, 0, 0x400, 0, 0);            // 限定 绝对坐标X的取值范围
      input_set_abs_params(tsdev, ABS_Y, 0, 0x400, 0, 0);            // 同上
      input_set_abs_params(tsdev, ABS_PRESSURE, 0, 1000, 0, 0);      // 触摸屏 压力 范围
 
      …
     
      If(input_register_device(tsdev) == error)       //注册 设备
            goto fail;
     
      …
     
fail:
      input_free_device(tsdev);
      printk(“ts probe failed\n”);
      return err;
}
先看 该函数中的tsdev = input_allocate_device() 深入最终,该函数在input.c中有定义
struct input_dev *input_allocate_device(void)
{
      struct input_dev *dev;
 
       dev = kmalloc(sizeof(struct input_dev), GFP_KERNEL);
      if (dev) {
            dev->dynalloc = 1;
            dev->cdev.class = &input_class;
            class_device_initialize(&dev->cdev);
            INIT_LIST_HEAD(&dev->h_list);
            INIT_LIST_HEAD(&dev->node);
      }
 
      return dev;
}
学过 C语言应该都知道malloc函数 (开辟 内存 空间) 这里的kmalloc也 类似 于C语言中的malloc一样,是linux内核 空间 分配内存函数, 后面 的GFP_KERNEL 标志意为常规 的内存分配,更多的分配标志 参阅先关资料 (我 也不懂 )。再 回到前面的函数中来,接着后面的代码为对tsdev结构体中的成员进行 赋值初始化 ,赋值完成后就要开始进入主题: 注册 设备了 进入到函数input_register_device(tsdev) 函数在input.c中有定义。
int input_register_device(struct input_dev *dev)
{
      static atomic_t input_no = ATOMIC_INIT(0);      // 定义 原子变量, 禁止线程 并发访问
      struct input_handle *handle;            // 定义 一些变量 后文使用
      struct input_handler *handler;
      struct input_device_id *id;
      const char *path;
      int error;
 
      if (!dev->dynalloc) {
            printk(KERN_WARNING "input: device %s is statically allocated, will not register\n"
                  "Please convert to input_allocate_device() or contact dtor_core@ameritech.net\n",
                  dev->name ? dev->name : "<Unknown>");
            return -EINVAL;
      }
 
       mutex_init(&dev->mutex);            // 互斥锁 初始化, 防止 临界区代码 被并发访问
       set_bit(EV_SYN, dev->evbit);            // 设置 支持同步事件,input设备 全部 默认支持同步事件
 
      /*
      * 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);
      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;
      }
 
      INIT_LIST_HEAD(&dev->h_list);      // 初始化需要关联 的handle链表头
      list_add_tail(&dev->node, &input_dev_list);      // 设备添加到input_dev_list中
 
      dev->cdev.class = &input_class;
      snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
            "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
 
      error = class_device_add(&dev->cdev);
      if (error)
            return error;
 
      error = sysfs_create_group(&dev->cdev.kobj, &input_dev_attr_group);
      if (error)
            goto fail1;
 
      error = sysfs_create_group(&dev->cdev.kobj, &input_dev_id_attr_group);
      if (error)
            goto fail2;
 
      error = sysfs_create_group(&dev->cdev.kobj, &input_dev_caps_attr_group);
      if (error)
            goto fail3;
 
      __module_get(THIS_MODULE);
 
      path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
      printk(KERN_INFO "input: %s as %s\n",
            dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
      kfree(path);
 
 
/* **  遍历input_handler_list 全部 的handler,寻找与该设备匹配的handler  ***/
      list_for_each_entry(handler, &input_handler_list, node)
            if (!handler->blacklist || !input_match_device(handler->blacklist, dev))
                  if ((id = input_match_device(handler->id_table, dev)))
                        if ((handle = handler->connect(handler, dev, id)))
                              input_link_handle(handle);
 
 
 
      input_wakeup_procfs_readers();
 
      return 0;
 
 fail3:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_id_attr_group);
 fail2:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_attr_group);
 fail1:      class_device_del(&dev->cdev);
      return error;
}
先看函数 中前面代码加粗的部分 mutex_init(&dev->mutex) 与互斥锁相关的东西 (我也 不太懂 ), set_bit(EV_SYN, dev->evbit) 设置 支持同步事件,linux的input子系统默认要支持同步事件
 
接着看 中间代码加粗的部分, 有关 list操作 在/linux/list.h 有定义
static inline void INIT_LIST_HEAD(struct list_head *list)
{
      list->next = list;
      list->prev = list;
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
      __list_add(new, head->prev, head);
}
static inline void __list_add(struct list_head *new,
                        struct list_head *prev,
                        struct list_head *next)
{
      next->prev = new;
      new->next = next;
      new->prev = prev;
      prev->next = new;
}
可以 看得出INIT_LIST_HEAD(struct list_head *list) 就是让 list指向结构体的成员再指向其本身完成初始化操作,list_add_tail(struct list_head *new, struct list_head *head) 将new 指向的 结构体 作为一 节点 插入到head所指向链表节点之前。 因此 函数中的 list_add_tail(&dev->node, &input_dev_list) 就是将该 设备 添加 input_dev_list 中。 input_dev_list这个双向链表在什么时候被定义了呢,且看input.c源代码开始部分 全局 部分有定义:
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);
LIST_HEAD的宏定义:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
      struct list_head name = LIST_HEAD_INIT(name)
显然 这在最开始就已经定义了input_dev_list和input_handler_list 两个链表, 且这 两个链表都只有一个节点, device 和handler注册的时候会在这两条链表中加入节点, list_add_tail(&dev->node, &input_dev_list) 就是将该 设备 添加 input_dev_list
 
注意 最后代码加粗的部分,该部分完成了input_dev和input_handler的桥接。先看 list_for_each_entry(handler, &input_handler_list, node) list_for_each_entry list.h中有定义, 作用相当于一个for循环。其 等价于:( 个人 理解)
for(handler = input_handler_list 表头所属 input_ handler 结构体 地址;
 handler != input_handler_list 表尾所属 input_ handler 结构体 地址 ;
  handler++)
handler第一次 指向 input_handler_list 头部 所在 的input_handler地址。每循环一次handler就沿着input_handler_list移动到下一个节点,得到下一节点 所属 的handler地址,直到 input_handler_list 的结尾。 而每次 的循环需要做什么 做的就是 遍历 input_handler_list上的每一个handler,看有 哪些 handler能与该设备匹配的上 。匹配过程
 
if (!handler->blacklist || !input_match_device(handler->blacklist, dev)) //判断 该handler没有被列入黑名单或者 黑名单匹配不成功 的话则 继续
       if ((id = input_match_device(handler->id_table, dev)))       // 设备id与handler的id进行匹配,成功则继续往下
if ((handle = handler->connect(handler, dev, id)))        // 链接 device与handler 成功则继续往下
      input_link_handle(handle);       //将 handle链入input_handler_list和input_dev_list
 
继续 跟踪进这些函数
static struct input_device_id *input_match_device(struct input_device_id *id, struct input_dev *dev)
{
      int i;
 
      for (; id->flags || id->driver_info; id++) {
 
            if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)      // 匹配 handler和device  id flag标志位
                  if (id->bustype != dev->id.bustype)
                        continue;
 
            if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
                  if (id->vendor != dev->id.vendor)
                        continue;
 
            if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
                  if (id->product != dev->id.product)
                        continue;
 
            if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
                  if (id->version != dev->id.version)
                        continue;
 
            MATCH_BIT(evbit,  EV_MAX);            // 匹配 id 相关 标志位
            MATCH_BIT(keybit, KEY_MAX);
            MATCH_BIT(relbit, REL_MAX);
            MATCH_BIT(absbit, ABS_MAX);
            MATCH_BIT(mscbit, MSC_MAX);
            MATCH_BIT(ledbit, LED_MAX);
            MATCH_BIT(sndbit, SND_MAX);
            MATCH_BIT(ffbit,  FF_MAX);
            MATCH_BIT(swbit,  SW_MAX);
 
            return id;
      }
 
      return NULL;
}
函数用于匹配input_dev 结构体 和input_handler 结构 体里面 定义 的input_device_id, 两者里面 任何 相关 变量不一样则匹配失败 (条件 真苛刻 )。
 
再看 handle = handler->connect(handler, dev, id) connect函数即为static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) 为什么呢会是这个呢,我们先看 evdev.c中 的input_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,
};
这里 input_handler 结构体 里边的connect函数即为evdev_connect函数
 
static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)
{
1      struct evdev *evdev;             //定义 一个evdev结构体指针
      struct class_device *cdev;
      int minor;
 
2      for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
      if (minor == EVDEV_MINORS) {
            printk(KERN_ERR "evdev: no more free evdev devices\n");
            return NULL;
      }
 
3       if (!(evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)))
            return NULL;
 
      INIT_LIST_HEAD(&evdev->list);
      init_waitqueue_head(&evdev->wait);
 
      evdev->exist = 1;
      evdev->minor = minor;
      evdev->handle.dev = dev;
      evdev->handle.name = evdev->name;
      evdev->handle.handler = handler;
      evdev->handle.private = evdev;
      sprintf(evdev->name, "event%d", minor);
 
      evdev_table[minor] = evdev;
 
      cdev = class_device_create(&input_class, &dev->cdev,
                  MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
                  dev->cdev.dev, evdev->name);
 
      /* temporary symlink to keep userspace happy */
      sysfs_create_link(&input_class.subsys.kset.kobj, &cdev->kobj,
                    evdev->name);
 
      return &evdev->handle;
}
1处 ,这里有个定义在evdev.c里边的新面孔
struct evdev {
      int exist;
      int open;
      int minor;
      char name[16];
       struct input_handle handle;      // 关联 input_handler和input_dev的input_handle
      wait_queue_head_t wait;
       struct evdev_list *grab;
      struct list_head list;
};
evdev 这个 结构体就是 拿来应用 开发操作的 ,在 这里就是触摸屏 对应 的设备文件实体, 结构体 前边定义了记录设备的一些信息 (设备号 ,打开状态、设备名字等 ), 这里还定义了一个input_handle的实体handle,没错这个handle就是要用来 关联 input_dev和input_handler的 后面还有一 加粗的部分后面再做介绍。
2处 evdev_table [] 一个全局变量的数组 在evdev.c中有定义
#define EVDEV_MINORS            32
static struct evdev *evdev_table[EVDEV_MINORS];
前面 已经 说明了, 一个 device 可以 对应多个 handler 一个handler也可处理多个 device, 这里体现 出了 后者 。既然 evdev这个结构体是 对应 的设备文件实体, 因为这个 handler可能会处理多个device,因此 handler要处理 n 个device就会应该有 n evdev 实体,而这些实体的地址存放在evdev_table[] 这个指针 数组中,也就是说该handler最多只能处理EVDEV_MINORS device 2处 的这几句代码就是要遍历evdev_table[] 这个 数组 还有没有 空着 的位置,有 话才会继续 进行 下面的程序。
3处 ,开辟 一个evdev结构体的内存空间,前面有说明过kzalloc 函数 ,这里不再说明。
后面 的代码就是为 evdev 结构体变量赋 初始值 了, 其中 init_waitqueue_head(&evdev->wait) 初始化 等待队列,具体介绍 结合/linux/wait.h和查看 相关资料 (本人 不懂 )。
函数 最后 得到 evdev结构体内的hanlde地址并返回, 此时返回到 我们的int input_register_device(struct input_dev *dev) 函数 里面,最后 一句
input_link_handle(handle) ,进入 到该函数中发现
static void input_link_handle(struct input_handle *handle)
{
      list_add_tail(&handle->d_node, &handle->dev->h_list);
      list_add_tail(&handle->h_node, &handle->handler->h_list);
}
函数中也 只是 将handle中的d_node和h_node 分别 接入到input_dev和input_handler的h_list中 此时input_dev、input_handler、input_handle 三者 形成了如图 2所示 的关系。
 
至此 设备注册 过程 算是全部完成了,但是 貌似 还有点乱 整个 设备的注册过程中函数的 嵌套一个 接着一个, 不仅 函数嵌套,结构体也使用了结构体和函数嵌套等,在 各个 函数内也用了大量的结构体指针等等 在整个 过程中 input_dev、input_handler、input_handle 三个结构体变量的实体也 只有一个。其中 input_dev 结构体实体在xxx_ts.c中直接定义了一个 input_dev 指针 全局变量
struct input_dev *tsdev;
在初始化 函数 开辟 了一个input_dev的内存空间并让tsdev指针指向它:
w55fa95_dev = input_allocate_device()
input_handler结构体 在evdev.c中也直接被定义了 初始化了
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,
};
关联input_dev和input_handler的input_handle则是在调用 链接连接 函数static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id) 时候,在该函数的内部 调用 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL) 开辟 了一个evdev结构体的内存空间 创建了 input_handle就是使用了evdev结构体内部定义的input_handle
      一个完整input设备系统不仅要有设备,还需要有处理程序input_handler, 上文中 主要 介绍的是设备注册、生成 以及和handler搭配的一个过程,而handler在 何时生成
 
第三章 、input_handler 注册。
      Input_handler是要和用户层打交道的 在evdev.c中直接定义了一个input_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,            // 指向 一个evedev的指针数组
};
先看 第一 加粗的代码,evedev_fops结构体的定义 如下
static 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
};
相信 做过 linux 设备驱动编程的对这 都很熟悉 了,就是一大堆的用户接口函数,包括对设备的 open 、close、read、write、ioctl等函数
看第二行代码加粗的部分 .id_table =      evdev_ids,  id.table 就是 前面所说过要和input_dev的id匹配的这么一个结构体,这里让它初始化为evdev_ids,在看evdev_ids的定义
static struct input_device_id evdev_ids[] = {
      { .driver_info = 1 },      /* Matches all devices */
      { },                  /* Terminating zero entry */
};
MODULE_DEVICE_TABLE(input, evdev_ids);
这里是 一个结构体数组, 数组中 第一个 结构体的该成员变量driver_info的值为 1, 其他成员变量均 定义,说明 这个 handler对 所有 device的id都能匹配得上 。数组 中的第二个结构体为空 表示 结束 ,用来 标识结束配合下面的MODULE_DEVICE_TABLE(input, evdev_ids) 使用 ,关于MODULE_DEVICE_TABLE 宏定义 介绍自行查看 相关 文献 (我也 不懂 )。
     
       接下来 进入正题,input_handler的注册
      Input_handler的注册和input_dev的注册很相似,大同小异罢了,在evdev.c源码中 显示 定义并初始化了一个input_handler结构体并直接给相关的 成员 变量赋值了,就是本章开始所将的部分,然后再初始化函数中注册一个input_handler:
static int __init evdev_init(void)
{
      input_register_handler(&evdev_handler);
      return 0;
}
这里 只调用了一个input_register_handler() 函数, 看起来应该是很简单的样子 (相比 input_dev的注册 ),继续 跟踪进入到该函数中:
void input_register_handler(struct input_handler *handler)
{
      struct input_dev *dev;
      struct input_handle *handle;
      struct input_device_id *id;
 
      if (!handler) return;
 
       INIT_LIST_HEAD(&handler->h_list);
 
      if (handler->fops != NULL)
            input_table[handler->minor >> 5] = handler;
 
       list_add_tail(&handler->node, &input_handler_list);
 
      list_for_each_entry(dev, &input_dev_list, node)
            if (!handler->blacklist || !input_match_device(handler->blacklist, dev))
                  if ((id = input_match_device(handler->id_table, dev)))
                        if ((handle = handler->connect(handler, dev, id)))
                              input_link_handle(handle);
 
      input_wakeup_procfs_readers();
}
该函数 中代码加粗的部分,与input_register_device() 函数 里的如出一辙,这里不再做说明。
 
至此input_handler 的注册已经 结束
 
第四章 、input 子系统数据 结构
       3是以 触摸屏设备为例子的input子系统 数据 结构图。
图3  input子系统数据结构图
     
进行 到这里,又多冒出来了一个struct evdev_list的结构体, 在之前并没有提到过,因为在注册input_dev和input_handler过程中并没有用到过, 查看 该结构体:
struct evdev_list {
       struct input_event buffer[EVDEV_BUFFER_SIZE];      // 存放 设备数据信息
      int head;      // buffer 下标 ,标识从设备中过来 存放到buffer的 数据 的位置
      int tail;            //buffer的下标,标识用户读取 buffer中数据的下标位置
};
};
{
 
 
 
 
 
 
}
{
 
 
 
}
 
{
}
{
}
{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
{
 
 
 
 
 
 
}
 
 
{
 
}
{
 
}
 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值