韦东山2440-学习笔记-输入子系统

1. 框架分析

1.1 connect

input_init
  register_chrdev(INPUT_MAJOR, "input", &input_fops);

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

input_open_file 
  struct input_handler *handler = input_table[iminor(inode) >> 5]; // 从 input_table 中根据次设备号获得 handler
  new_fops = fops_get(handler->fops); // 使用handler->fops替换file->f_op,并调用 open
  file->f_op = new_fops;
  new_fops->open(inode, file);   // 调用下层驱动的open

input_table 被谁设置?
input_register_handler(struct input_handler *handler) // 被其他模块调用
  input_table[handler->minor >> 5] = handler;   // 其他模块定义的 handler将记录到 input_table
  list_add_tail(&handler->node, &input_handler_list); // 其他模块的handler会加入 input_handler_list链表
  list_for_each_entry(dev, &input_dev_list, node) // 遍历dev链表,进行dev和 handler的匹配
     input_attach_handler(dev, handler);

以 input_dev_list 被谁添加?
input_register_device(struct input_dev *dev)
  list_add_tail(&dev->node, &input_dev_list);
  list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);

所以当handler和dev各自调用 input_register后,会调用input_attach_handler
input_attach_handler(struct input_dev *dev, struct input_handler *handler)
  input_match_device(handler->id_table, dev); // 判断handler和dev是否匹配
      for (; id->flags || id->driver_info; id++) {  // 匹配方法是检查 handler是否支持 dev 的输入类型
          if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
              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);
          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;
      }
  handler->connect(handler, dev, id); // 如果匹配则调用handler->connect


查看实际handler的实际示例,查看connect函数
发现有很多模块都调用了input_register_handler,说明他们都提供handler,作为软件层
drivers/char/keyboard.c 无 fops 
drivers/input/evbug.c   无 fops
drivers/input/evdev.c   有 fops 分析
...
以 evdev.c为例
evdev_init
  input_register_handler(&evdev_handler);

evdev_handler定义
  static struct input_handler evdev_handler = {
      .event =    evdev_event,
      .connect =  evdev_connect,   // 匹配时调用
      .disconnect =   evdev_disconnect,
      .fops =     &evdev_fops,     // input被open时,替换成 evdev_fops,并调用 evdev_fops.open
      .minor =    EVDEV_MINOR_BASE,
      .name =     "evdev",
      .id_table = evdev_ids,  // 决定是否匹配哪些dev
  };

可见evdev匹配所有input dev
  static const struct input_device_id evdev_ids[] = {
      { .driver_info = 1 },   /* Matches all devices */
      { },            /* Terminating zero entry */
  };

evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) 
  for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); // 分配次设备号,主设备号为 input子系统提供
  evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);  // 创建edev,edev包含 struct input_handle
  evdev->handle.dev = dev;          // input_handle 关联 dev 和 handler
  evdev->handle.handler = handler;
  evdev->handle.private = evdev;
  devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
  cdev = class_device_create(&input_class, &dev->cdev, devt,
                     dev->cdev.dev, evdev->name);   // 创建设备节点
  input_register_handle(&evdev->handle);
    list_add_tail(&handle->d_node, &handle->dev->h_list); // handle加入 dev->h_list 和 handler->h_list
    list_add_tail(&handle->h_node, &handler->h_list);
    handler->start(handle);

所以connect后,handler ,dev,handle可以通过任意一个,找到其他,并且dev->open > 0
他们的关系
handler->hlist --> handle
dev->hlist --> handle
handle->dev = dev
handle->handler = handler

1.2 open

由于创建设备节点使用的主设备号为 INPUT_MAJOR,所以应用程序open时调用的 f_op->open 为 input_open_file.

input_open_file
  struct input_handler *handler = input_table[iminor(inode) >> 5]; // 获得 edev的handler
  new_fops = fops_get(handler->fops); // 使用handler->fops替换file->f_op,并调用 open
  file->f_op = new_fops;
  new_fops->open(inode, file);   // 调用edev的open

edev的file_operations实现了大量的IO操作,可见使用输入子系统可以减少大量开发量
  static const struct file_operations evdev_fops = {
      .owner =    THIS_MODULE,
      .read =     evdev_read,
      .write =    evdev_write,
      .poll =     evdev_poll,
      .open =     evdev_open,
      .release =  evdev_release,
      .unlocked_ioctl = evdev_ioctl,
  #ifdef CONFIG_COMPAT
      .compat_ioctl = evdev_ioctl_compat,
  #endif
      .fasync =   evdev_fasync,
      .flush =    evdev_flush
  };

evdev_open
  evdev = evdev_table[i];
  client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
  client->evdev = evdev;
  input_open_device(&evdev->handle);
    dev->open(dev);
  file->private_data = client;
evdev_open只是把struct file 进行派生,并给 dev open时初始化的机会

1.3 read

evdev_read
  if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) // 环形缓存为空,且用户使用非阻塞方式打开,则返回EAGAIN
     return -EAGAIN;
  wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); // 如果环形缓存为空,则在 evdev->wait 上睡眠
  // 有数据被唤醒
  evdev_event_to_user(buffer + retval, event); // 复制数据给用户空间

1.4 event

当进程睡眠到evdev->wait时,谁来唤醒

evdev_event
  wake_up_interruptible(&evdev->wait); // 唤醒等待队列上的进程

那么谁调用 evdev_event
static struct input_handler evdev_handler = {
      .event =    evdev_event,
      ...
};
搜索后发现 input_event 中会调用handle->handler->event()

谁调用input_event,发现通常是在数据dev部分检查到数据到来,如dev的中断处理中
atkbd_interrupt
  input_event

1.5 总结

输入子系统分为三部分:
dev和 handler首先 调用 input_register_xx 将自己注册到 input的两个链表,其中handler还需要将自己注册到input_table
如果发生匹配,则会调用 handler->connect,通常connect需要创建设备节点。并创建handle结构,让handler,dev关联
当用户open时,先调用 input->f_op->open,先通过此设备号从input_table中找到handler,再让 file->f_op 改变为 handler->f_op,并调用 new_fop->open()
当用户read时,由于fop已经改变,所以会调用handler的fop->read,通常会因没有数据而阻塞,
当dev数据到来,触发中断,dev中断处理中调用 input_event,以调用 handler->event,handler->event通常会唤醒等待进程。

2. 函数和细节

int input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

其中,各参数的含义如下:
dev: 指向 input_dev 结构体的指针,表示要发送事件的输入设备;
type: 事件类型,可以是 EV_SYN(同步事件),EV_KEY(按键事件),EV_REL(相对移动事件)等;
code: 事件代码,表示具体的按键或鼠标事件,例如 BTN_LEFT(左键按下),ABS_X(X 轴相对移动)等;
value: 事件的值,表示事件发生的状态或数值,例如 0(表示按键松开),1(表示按键按下),或者鼠标的相对移动量。
调用 input_event 函数后,输入子系统会将事件传递到相应的输入设备,并触发相应的事件处理程序。

int input_sync(struct input_dev *dev);

在 Linux 输入子系统中,input_sync 函数用于发送同步事件到输入设备。同步事件是一种特殊的事件类型,它用于告知输入设备当前事件序列的结束。输入设备在接收到同步事件后,会将之前的事件序列视为一个完整的事件,然后交给事件处理程序处理。
其中,dev 参数表示要发送同步事件的输入设备。调用 input_sync 函数后,输入子系统会向输入设备发送一个 EV_SYN 类型、SYN_REPORT 代码的同步事件,以表示事件序列的结束。通常,每个事件序列都应该以一个同步事件结尾,以确保输入设备能够正确地处理事件序列。

4. input_event 类型介绍


#define NBITS(x) (((x)/BITS_PER_LONG)+1)
#define BIT(x)  (1UL<<((x)%BITS_PER_LONG))
#define LONG(x) ((x)/BITS_PER_LONG)

struct input_dev {

    void *private;

    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    // 描述此输入设备类型
    unsigned long evbit[NBITS(EV_MAX)];  // 类型 :EV_SYN(虚拟同步事件,用于将 输入事件组合序列), EV_KEY , EV_REL, EV_ABS, EV_MSC(杂项事件非标准事件,用于表示不属于 EV_KEY,EV_REL,EV_ABS的事件) ... 
    // 当 evbit 确定后,下面使用一项描述此设备支持的 code
    unsigned long keybit[NBITS(KEY_MAX)];  // 若是键盘设备,支持哪些 code
    unsigned long relbit[NBITS(REL_MAX)];  // 若是相对位移鼠标,支持哪些code
    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)];

分析下 unsigned long evbit[NBITS(EV_MAX)]; 的位操作

#define NBITS(x) (((x)/BITS_PER_LONG)+1)
#define BIT(x)  (1UL<<((x)%BITS_PER_LONG))
#define LONG(x) ((x)/BITS_PER_LONG)

   unsigned long evbit[NBITS(EV_MAX)];  // 定义一段内存,内存大小支持 EV_MAX个bit 的位操作

 // 设置位
  static __inline__ void set_bit(int nr, volatile unsigned long *addr)
  {
      int *a = (int *)addr;
      int mask;
      unsigned long flags;

      a += nr >> 5; // a是int *类型,所以以32位移动, nr >> 5 也就是 nr / 32,得到a应该移动的单位
      mask = 1 << (nr & 0x1f);  // nr & 0x1f 就是 nr % 32 
      local_irq_save(flags);
      *a |= mask;
      local_irq_restore(flags);
  }

3. 实现按键设备

1.分配并设置input_device,注册
2.当有可读时用 input_event 发送给 handler

#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/input.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm-arm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static void button_timeout(unsigned long);

static struct input_dev *button_dev;
static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0);

struct button_desc {
        unsigned int pin;
        unsigned int val;
        unsigned int irq;
        const char *name;
        unsigned int code;
};

static struct button_desc button_desc_arr[3] = {
        { .pin = S3C2410_GPF0, .val = 0x01, .irq = IRQ_EINT0 , .name = "s1", .code = KEY_L},
        { .pin = S3C2410_GPF2, .val = 0x02, .irq = IRQ_EINT2 , .name = "s2", .code = KEY_S},
        { .pin = S3C2410_GPG3, .val = 0x03, .irq = IRQ_EINT11 , .name = "s3", .code = KEY_ENTER},
};

static void
button_timeout(unsigned long data)
{
        struct button_desc *bd = (struct button_desc *)data;
        int up;

        del_timer(&button_timer);
        button_timer.data = 0;

        up = s3c2410_gpio_getpin(bd->pin);
        if (up) {
                input_event(button_dev, EV_KEY, bd->code, 0);
                input_sync(button_dev);
        }
        else {
                input_event(button_dev, EV_KEY, bd->code, 1);
                input_sync(button_dev);
        }

}

static irqreturn_t
buttons_irq_handler(int irq, void *dev_id)
{
        if (button_timer.data != 0)
                return IRQ_HANDLED;

        button_timer.data = (unsigned int)dev_id;
        mod_timer(&button_timer, jiffies + msecs_to_jiffies(10));

        return IRQ_HANDLED;
}

static int __init
button_dev_init(void)
{
        int i, ret;

        /*  注册input_dev */
        button_dev = input_allocate_device();
        /* 设置此设备能产生的事件 */
        set_bit(EV_KEY, button_dev->evbit); // 产生按键事件
        set_bit(EV_REP, button_dev->evbit); // 产生重复事件
        set_bit(KEY_L, button_dev->keybit); // 键入 'l'
        set_bit(KEY_S, button_dev->keybit); // 键入 's'
        set_bit(KEY_ENTER, button_dev->keybit); // 键入 'enter'
        button_dev->name = "buttons";
        input_register_device(button_dev);

        /* 硬件操作 */
        /* 设置gpio为中断模式 */
        s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
        s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);
        s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);

        /* 注册中断, IRQT_BOTHEDGE : 双边缘触发 */
        for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
                ret = request_irq(button_desc_arr[i].irq, buttons_irq_handler,
                                IRQT_BOTHEDGE, button_desc_arr[i].name, button_desc_arr + i);
        }

        init_timer(&button_timer);

        return 0;
}

static void __exit
button_dev_exit(void)
{
        int i;

        // 删除设备节点
        input_unregister_device(button_dev);
        // 释放input_device 内存
        input_free_device(button_dev);
        // 释放中断号
        for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
                free_irq(button_desc_arr[i].irq, button_desc_arr + i);
        }
        // 删除定时器
        del_timer(&button_timer);
}

module_init(button_dev_init);
module_exit(button_dev_exit);

MODULE_LICENSE("GPL");

posted on 2023-02-22 16:07  开心种树  阅读(70)  评论(0编辑  收藏  举报