Linux内核Input输入子系统浅解

2024-03-26 07:10

本文主要是介绍Linux内核Input输入子系统浅解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 L inux输入 设备 总类 繁杂 ,常见 的包括有 按键 、键盘、触摸屏、 鼠标 、摇杆 等等,他们 本身就是 字符设备 linux内核将 这些 设备 共同性抽象出来 简化驱动开发 建立 了一个input子系统。 子系统共分为 三层,如图 1所示
图1   input 输入 子系统
       驱动 层和 硬件 相关,直接 捕捉和 获取硬件设备 数据信息等 (包括触摸屏 被按下、按下位置、 鼠标移动 键盘 按下等等), 然后 数据 信息报告到核心层 。核心层负责 连接 驱动层 事件处理层 设备驱动( 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 = kzalloc(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函数 (开辟 内存 空间) 这里的kzalloc也 类似 于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中数据的下标位置
      struct fasync_struct *fasync;
      struct evdev *evdev;
      struct list_head node;
};
注意里面的input_event结构体在/linux/input.h有定义:
struct input_event {
      struct timeval time;
      __u16 type;
      __u16 code;
      __s32 value;
};
input_event结构体就是存放着用户层所需的数据信息,这里想必我们都明白,则在struct evdev_list结构体中的struct input_event buffer[EVDEV_BUFFER_SIZE];就是存放着用户所需的数据信息从硬件设备(触摸屏)获取得到的数据信息最终会存放到该数组中,而struct evdev_list结构体是在什么时候定义的呢?
当然是在open的时候,查看open函数:
static int evdev_open(struct inode * inode, struct file * file)
{
      struct evdev_list *list;      //定义一个evdev_list结构体
      int i = iminor(inode) - EVDEV_MINOR_BASE;
      int accept_err;
      if (i >= EVDEV_MINORS || !evdev_table[ i] || !evdev_table[ i]->exist)
            return -ENODEV;
      if ((accept_err = input_accept_process(&(evdev_table[ i]->handle), file)))
            return accept_err;
      if (!(list = kzalloc(sizeof(struct evdev_list), GFP_KERNEL)))     //开辟evdev_list结构体内存空间
            return -ENOMEM;
      list->evdev = evdev_table[ i];            //结构体中的evdev指针指向evdec_table[ i]
      list_add_tail(&list->node, &evdev_table[ i]->list);      //加入链表
      file->private_data = list;
      if (!list->evdev->open++)            //如果设备没有被open,则继续if操作
            if (list->evdev->exist)
                  input_open_device(&list->evdev->handle);      //打开设备
      return 0;
}
注意函数中代码加粗的部分,有注释说明,在最后以一个操作input_open_device(&list->evdev->handle);继续跟踪进入该函数
int input_open_device(struct input_handle *handle)
{
      struct input_dev *dev = handle->dev;
      int err;
      err = mutex_lock_interruptible(&dev->mutex);
      if (err)
            return err;
      handle->open++;      //handle的内部成员open++
      if (!dev->users++ && dev->open)
            err = dev->open(dev);
      if (err)
            handle->open--;
      mutex_unlock(&dev->mutex);
      return err;
}
函数加粗的部分,为什么要让open++呢因为数据要从device传到handler过程中有一个环节需要判断handle->open是否为真,为真表示设备已经打开数据可以传递,这点在下一章将会介绍到。
第四章数据传递过程
      图1所示,从硬件设备(触摸屏)中获得的数据需要经过input.c选择相应的handler进行处理,最后上报到用户空间。如何硬件设备(触摸屏)获得数据,就得看xxx_ts.c中的代码了,在xxx_ts.c中的源代码是直接和硬件设备相关的。xxx_ts.c中我们一方面要完成触摸屏设备相关的寄存器配置,另一方面要完成将获得的数据上报。
      至于设备初始化配置方面,根据每个人使用的ARM芯片的Datasheet去初始化配置寄存器,这里也不需要多说了不管是通过查询法还是中断(没见过用查询的),当触摸屏按下时候我们会得到触摸屏被按下的相关数据(主要是被按下的XY坐标值)然后需要将数据信息上报,在触摸屏被按下的时候需要上报:
            input_report_key(tsdev, BTN_TOUCH, 1);      //报告按键被按下事件
            input_report_abs(tsdev, ABS_X, x);            //报告触摸屏被按下的x坐标值
            input_report_abs(tsdev, ABS_Y, y);                  //报告触摸屏被按下的y坐标值
            input_report_abs(tsdev, ABS_PRESSURE, 1);      //报告触摸屏被按下的压力值(0或者1)
input_sync(tsdev);            //报告同步事件,表示一次事件结束
触笔从触摸屏上抬起时需要上报:
            input_report_key(tsdev, BTN_TOUCH, 0);      //报告按键被松开事件
            input_report_abs(tsdev, ABS_PRESSURE, 0);      //报告触摸屏被按下的压力值(0或者1)
            input_sync(tsdev);            //报告同步事件,表示一次事件结束
先不管input_sync()函数,先来看input_report_key()input_report_abs()这两个函数在/linux/input.h中有定义:
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
      input_event(dev, EV_KEY, code, !!value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
      input_event(dev, EV_ABS, code, value);
}
太坑爹了,这两个函数里面也就值调用了一个函数(我也不明白linux内核开发者为什么要这么做),继续跟踪查看input_event()函数,在input.c中有定义:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
      struct input_handle *handle;
      if (type > EV_MAX || !test_bit(type, dev->evbit))
            return;
      add_input_randomness(type, code, value);
      switch (type) {
                  …
            case EV_KEY:            //判断为按键事件
                  if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
                        return;
                  if (value == 2)
                        break;
                  change_bit(code, dev->key);
                  if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
                        dev->repeat_key = code;
                        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
                  }
            case EV_ABS:      //判断为绝对坐标事件
                  if (code > ABS_MAX || !test_bit(code, dev->absbit))
                        return;
                  if (dev->absfuzz[code]) {
                        if ((value > dev->abs[code] - (dev->absfuzz[code] >> 1)) &&
                            (value < dev->abs[code] + (dev->absfuzz[code] >> 1)))
                              return;
                        if ((value > dev->abs[code] - dev->absfuzz[code]) &&
                            (value < dev->abs[code] + dev->absfuzz[code]))
                              value = (dev->abs[code] * 3 + value) >> 2;
                        if ((value > dev->abs[code] - (dev->absfuzz[code] << 1)) &&
                            (value < dev->abs[code] + (dev->absfuzz[code] << 1)))
                              value = (dev->abs[code] + value) >> 1;
                  }
                  if (dev->abs[code] == value)
                        return;
                  dev->abs[code] = value;
                  break;
                  …
      }
      if (type != EV_SYN)
            dev->sync = 0;
      if (dev->grab)      //判断有没有声明自定义的处理函数(初始化过程中显然没有定义)
            dev->grab->handler->event(dev->grab, type, code, value);
      else
            list_for_each_entry(handle, &dev->h_list, d_node)           //遍历handle链表
                  if (handle->open)                  //如果某节点上的处理程序被打开了
                        handle->handler->event(handle, type, code, value);      //调用处理函数
}
先看该函数中的一个switch结构,这里只列出了EV_KEY和EV_ABS两个事件的处理,经过switch选择处理后看最后代码加粗的部分list_for_each_entry(handle, &dev->h_list, d_node)      这句类似的前面已经介绍过,这里不再说明,接下来if (handle->open)判断处理程序是否被打开,还记得是什么时候打开的吗?前面有介绍过在evdev.c中的open函数里面调用了input_open_device(&list->evdev->handle)而在input_open_device()有这么一句handle->open++;没有错,只有在经过evdev_open之后才能这里的条件才能为真,接着看下面的handle->handler->event(handle, type, code, value);继续深入跟踪:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
      struct evdev *evdev = handle->private;
      struct evdev_list *list;
      if (evdev->grab) {                  //显然grab并没有被设置,该条件为
            list = evdev->grab;
            do_gettimeofday(&list->buffer[list->head].time);
            list->buffer[list->head].type = type;
            list->buffer[list->head].code = code;
            list->buffer[list->head].value = value;
            list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1);
            kill_fasync(&list->fasync, SIGIO, POLL_IN);
      } else
            list_for_each_entry(list, &evdev->list, node) {
                  do_gettimeofday(&list->buffer[list->head].time);     //buffer成员赋值
                  list->buffer[list->head].type = type;
                  list->buffer[list->head].code = code;
                  list->buffer[list->head].value = value;
                  list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1);
                  kill_fasync(&list->fasync, SIGIO, POLL_IN);
            }
      wake_up_interruptible(&evdev->wait);      //用来唤醒一个等待队列(我也不懂)
}
先看前面struct evdev *evdev = handle->private还记得handler->private吗,在input_handle结构体中它是一个空指针,在前面有讲到在执行static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)函数的时候就被赋值了(evdev->handle.private = evdev;)。
看最后代码加粗的部分,显然是给evdev_list结构体中的buffer成员赋从设备中传过来的数据,数据存放好了之后,head加1,当head的值达到EVDEV_BUFFER_SIZE又回到0;
至此数据的传递就算是结束了,接下来就是等着读走,只要在用户空间进行read操作即可。
第五章、数据读取过程
      读取就变得很简单了,做过linux编程的都能知道,只要在应用中定义了个input_event结构体,通过open打开设备,然后进行read即可再来看该设备的read函数:
static ssize_t evdev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos)
{
      struct evdev_list *list = file->private_data;
      int retval;
if (count < evdev_event_size())//每次读取的字节数至少是input_event的大小
      return -EINVAL;
      if (list->head == list->tail && list->evdev->exist && (file->f_flags & O_NONBLOCK))      //是否满足读取条件
            return -EAGAIN;
      retval = wait_event_interruptible(list->evdev->wait,
            list->head != list->tail || (!list->evdev->exist));      //等待唤醒前面等待队列对应(我也不懂)
      if (retval)
            return retval;
      if (!list->evdev->exist)
            return -ENODEV;
      while (list->head != list->tail && retval + evdev_event_size() <= count) {
            struct input_event *event = (struct input_event *) list->buffer + list->tail;
            if (evdev_event_to_user(buffer + retval, event))      //复制数据到用户空间
                  return -EFAULT;
            list->tail = (list->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
            retval += evdev_event_size();
      }
      return retval;
}
函数前面基本上都是在做一系列的条件判断,判断是否符合读取的条件,evdev_event_to_user(buffer + retval, event)这句是真正的把数据复制到用户空间,进入查看
static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
{
      if (copy_to_user(buffer, event, sizeof(struct input_event)))
            return -EFAULT;
      return 0;
}
函里面也只是调用了copy_to_user()函数而已,该函数相比我们都不会陌生了,就是把event所指向地址空间的数据复制到buffer指向的地址空间中而已。
回到read函数中来,函数在末尾返回读取到的字节数,至于要把这些数据拿去干什么用,那就是应用层的事情了
      可以看到接口函数中除了read以外还有write、ioctl函数因为对于触摸屏来说我们主要就是要获取触笔在触摸屏上点击的坐标位置而已,所以write、ioctl等函数会很少用到,这里就不再做介绍。
      本文只在一定的层面(本人的理解)linux下的input子系统介绍,里边还有很多要点没有办法去涉及,只是一个入门级的学习过程,紧限于在完善的input子系统下进行做驱动修改,而不具备做驱动开发的能力。 - - 触摸屏驱动为例
第一章、了解 linux input子系统
      L inux输入 设备 总类 繁杂 ,常见 的包括有 按键 、键盘、触摸屏、 鼠标 、摇杆 等等,他们 本身就是 字符设备 linux内核将 这些 设备 共同性抽象出来 简化驱动开发 建立 了一个input子系统。 子系统共分为 三层,如图 1所示
图1   input 输入 子系统
       驱动 层和 硬件 相关,直接 捕捉和 获取硬件设备 数据信息等 (包括触摸屏 被按下、按下位置、 鼠标移动 键盘 按下等等), 然后 数据 信息报告到核心层 。核心层负责 连接 驱动层 事件处理层 设备驱动( 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 = kzalloc(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函数 (开辟 内存 空间) 这里的kzalloc也 类似 于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中数据的下标位置
      struct fasync_struct *fasync;
      struct evdev *evdev;
      struct list_head node;
};
注意里面的input_event结构体在/linux/input.h有定义:
struct input_event {
      struct timeval time;
      __u16 type;
      __u16 code;
      __s32 value;
};
input_event结构体就是存放着用户层所需的数据信息,这里想必我们都明白,则在struct evdev_list结构体中的struct input_event buffer[EVDEV_BUFFER_SIZE];就是存放着用户所需的数据信息从硬件设备(触摸屏)获取得到的数据信息最终会存放到该数组中,而struct evdev_list结构体是在什么时候定义的呢?
当然是在open的时候,查看open函数:
static int evdev_open(struct inode * inode, struct file * file)
{
      struct evdev_list *list;      //定义一个evdev_list结构体
      int i = iminor(inode) - EVDEV_MINOR_BASE;
      int accept_err;
      if (i >= EVDEV_MINORS || !evdev_table || !evdev_table->exist)
            return -ENODEV;
      if ((accept_err = input_accept_process(&(evdev_table->handle), file)))
            return accept_err;
      if (!(list = kzalloc(sizeof(struct evdev_list), GFP_KERNEL)))     //开辟evdev_list结构体内存空间
            return -ENOMEM;
      list->evdev = evdev_table;            //结构体中的evdev指针指向evdec_table
      list_add_tail(&list->node, &evdev_table->list);      //加入链表
      file->private_data = list;
      if (!list->evdev->open++)            //如果设备没有被open,则继续if操作
            if (list->evdev->exist)
                  input_open_device(&list->evdev->handle);      //打开设备
      return 0;
}
注意函数中代码加粗的部分,有注释说明,在最后以一个操作input_open_device(&list->evdev->handle);继续跟踪进入该函数
int input_open_device(struct input_handle *handle)
{
      struct input_dev *dev = handle->dev;
      int err;
      err = mutex_lock_interruptible(&dev->mutex);
      if (err)
            return err;
      handle->open++;      //handle的内部成员open++
      if (!dev->users++ && dev->open)
            err = dev->open(dev);
      if (err)
            handle->open--;
      mutex_unlock(&dev->mutex);
      return err;
}
函数加粗的部分,为什么要让open++呢因为数据要从device传到handler过程中有一个环节需要判断handle->open是否为真,为真表示设备已经打开数据可以传递,这点在下一章将会介绍到。
第四章数据传递过程
      图1所示,从硬件设备(触摸屏)中获得的数据需要经过input.c选择相应的handler进行处理,最后上报到用户空间。如何硬件设备(触摸屏)获得数据,就得看xxx_ts.c中的代码了,在xxx_ts.c中的源代码是直接和硬件设备相关的。xxx_ts.c中我们一方面要完成触摸屏设备相关的寄存器配置,另一方面要完成将获得的数据上报。
      至于设备初始化配置方面,根据每个人使用的ARM芯片的Datasheet去初始化配置寄存器,这里也不需要多说了不管是通过查询法还是中断(没见过用查询的),当触摸屏按下时候我们会得到触摸屏被按下的相关数据(主要是被按下的XY坐标值)然后需要将数据信息上报,在触摸屏被按下的时候需要上报:
            input_report_key(tsdev, BTN_TOUCH, 1);      //报告按键被按下事件
            input_report_abs(tsdev, ABS_X, x);            //报告触摸屏被按下的x坐标值
            input_report_abs(tsdev, ABS_Y, y);                  //报告触摸屏被按下的y坐标值
            input_report_abs(tsdev, ABS_PRESSURE, 1);      //报告触摸屏被按下的压力值(0或者1)
input_sync(tsdev);            //报告同步事件,表示一次事件结束
触笔从触摸屏上抬起时需要上报:
            input_report_key(tsdev, BTN_TOUCH, 0);      //报告按键被松开事件
            input_report_abs(tsdev, ABS_PRESSURE, 0);      //报告触摸屏被按下的压力值(0或者1)
            input_sync(tsdev);            //报告同步事件,表示一次事件结束
先不管input_sync()函数,先来看input_report_key()input_report_abs()这两个函数在/linux/input.h中有定义:
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
      input_event(dev, EV_KEY, code, !!value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
      input_event(dev, EV_ABS, code, value);
}
太坑爹了,这两个函数里面也就值调用了一个函数(我也不明白linux内核开发者为什么要这么做),继续跟踪查看input_event()函数,在input.c中有定义:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
      struct input_handle *handle;
      if (type > EV_MAX || !test_bit(type, dev->evbit))
            return;
      add_input_randomness(type, code, value);
      switch (type) {
                  …
            case EV_KEY:            //判断为按键事件
                  if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
                        return;
                  if (value == 2)
                        break;
                  change_bit(code, dev->key);
                  if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
                        dev->repeat_key = code;
                        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
                  }
            case EV_ABS:      //判断为绝对坐标事件
                  if (code > ABS_MAX || !test_bit(code, dev->absbit))
                        return;
                  if (dev->absfuzz[code]) {
                        if ((value > dev->abs[code] - (dev->absfuzz[code] >> 1)) &&
                            (value < dev->abs[code] + (dev->absfuzz[code] >> 1)))
                              return;
                        if ((value > dev->abs[code] - dev->absfuzz[code]) &&
                            (value < dev->abs[code] + dev->absfuzz[code]))
                              value = (dev->abs[code] * 3 + value) >> 2;
                        if ((value > dev->abs[code] - (dev->absfuzz[code] << 1)) &&
                            (value < dev->abs[code] + (dev->absfuzz[code] << 1)))
                              value = (dev->abs[code] + value) >> 1;
                  }
                  if (dev->abs[code] == value)
                        return;
                  dev->abs[code] = value;
                  break;
                  …
      }
      if (type != EV_SYN)
            dev->sync = 0;
      if (dev->grab)      //判断有没有声明自定义的处理函数(初始化过程中显然没有定义)
            dev->grab->handler->event(dev->grab, type, code, value);
      else
            list_for_each_entry(handle, &dev->h_list, d_node)           //遍历handle链表
                  if (handle->open)                  //如果某节点上的处理程序被打开了
                        handle->handler->event(handle, type, code, value);      //调用处理函数
}
先看该函数中的一个switch结构,这里只列出了EV_KEY和EV_ABS两个事件的处理,经过switch选择处理后看最后代码加粗的部分list_for_each_entry(handle, &dev->h_list, d_node)      这句类似的前面已经介绍过,这里不再说明,接下来if (handle->open)判断处理程序是否被打开,还记得是什么时候打开的吗?前面有介绍过在evdev.c中的open函数里面调用了input_open_device(&list->evdev->handle)而在input_open_device()有这么一句handle->open++;没有错,只有在经过evdev_open之后才能这里的条件才能为真,接着看下面的handle->handler->event(handle, type, code, value);继续深入跟踪:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
      struct evdev *evdev = handle->private;
      struct evdev_list *list;
      if (evdev->grab) {                  //显然grab并没有被设置,该条件为
            list = evdev->grab;
            do_gettimeofday(&list->buffer[list->head].time);
            list->buffer[list->head].type = type;
            list->buffer[list->head].code = code;
            list->buffer[list->head].value = value;
            list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1);
            kill_fasync(&list->fasync, SIGIO, POLL_IN);
      } else
            list_for_each_entry(list, &evdev->list, node) {
                  do_gettimeofday(&list->buffer[list->head].time);     //buffer成员赋值
                  list->buffer[list->head].type = type;
                  list->buffer[list->head].code = code;
                  list->buffer[list->head].value = value;
                  list->head = (list->head + 1) & (EVDEV_BUFFER_SIZE - 1);
                  kill_fasync(&list->fasync, SIGIO, POLL_IN);
            }
      wake_up_interruptible(&evdev->wait);      //用来唤醒一个等待队列(我也不懂)
}
先看前面struct evdev *evdev = handle->private还记得handler->private吗,在input_handle结构体中它是一个空指针,在前面有讲到在执行static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)函数的时候就被赋值了(evdev->handle.private = evdev;)。
看最后代码加粗的部分,显然是给evdev_list结构体中的buffer成员赋从设备中传过来的数据,数据存放好了之后,head加1,当head的值达到EVDEV_BUFFER_SIZE又回到0;
至此数据的传递就算是结束了,接下来就是等着读走,只要在用户空间进行read操作即可。
第五章、数据读取过程
      读取就变得很简单了,做过linux编程的都能知道,只要在应用中定义了个input_event结构体,通过open打开设备,然后进行read即可再来看该设备的read函数:
static ssize_t evdev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos)
{
      struct evdev_list *list = file->private_data;
      int retval;
if (count < evdev_event_size())//每次读取的字节数至少是input_event的大小
      return -EINVAL;
      if (list->head == list->tail && list->evdev->exist && (file->f_flags & O_NONBLOCK))      //是否满足读取条件
            return -EAGAIN;
      retval = wait_event_interruptible(list->evdev->wait,
            list->head != list->tail || (!list->evdev->exist));      //等待唤醒前面等待队列对应(我也不懂)
      if (retval)
            return retval;
      if (!list->evdev->exist)
            return -ENODEV;
      while (list->head != list->tail && retval + evdev_event_size() <= count) {
            struct input_event *event = (struct input_event *) list->buffer + list->tail;
            if (evdev_event_to_user(buffer + retval, event))      //复制数据到用户空间
                  return -EFAULT;
            list->tail = (list->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
            retval += evdev_event_size();
      }
      return retval;
}
函数前面基本上都是在做一系列的条件判断,判断是否符合读取的条件,evdev_event_to_user(buffer + retval, event)这句是真正的把数据复制到用户空间,进入查看
static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
{
      if (copy_to_user(buffer, event, sizeof(struct input_event)))
            return -EFAULT;
      return 0;
}
函里面也只是调用了copy_to_user()函数而已,该函数相比我们都不会陌生了,就是把event所指向地址空间的数据复制到buffer指向的地址空间中而已。
回到read函数中来,函数在末尾返回读取到的字节数,至于要把这些数据拿去干什么用,那就是应用层的事情了
      可以看到接口函数中除了read以外还有write、ioctl函数因为对于触摸屏来说我们主要就是要获取触笔在触摸屏上点击的坐标位置而已,所以write、ioctl等函数会很少用到,这里就不再做介绍。
      本文只在一定的层面(本人的理解)linux下的input子系统介绍,里边还有很多要点没有办法去涉及,只是一个入门级的学习过程,紧限于在完善的input子系统下进行做驱动修改,而不具备做驱动开发的能力。

这篇关于Linux内核Input输入子系统浅解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/847670

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级