嵌入式linux之输入子系统

来源:互联网 发布:linux下复制粘贴命令 编辑:程序博客网 时间:2024/05/21 09:06

1、为何引入input system?

   以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。   输入子系统引入的好处:

(1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。

(2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。

(3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问

过去的框架:

1.确定主设备号 major
2.构造file_operations结构体(open,read,write)
3.register.chrdev 注册
4.入口.init
5.出口.exit
缺点:只适合内部知道接口的人员使用。不够通用。

输入子系统:内核现成的框架

1.最上层,input.c中的内容
2.下层, input_device , input_handler。

概念:输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。

这里写图片描述

1.驱动层:将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。

2.输入子系统核心:承上启下。为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息

3.事件处理层:主要是和用户空间交互。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)

二、drivers/input/input.c:入口函数input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static int __init input_init(void)  {      int err;      ...      /* 创建类 */      err = class_register(&input_class);      ...      /* 注册一个字符驱动,主设备号为13 */      err = register_chrdev(INPUT_MAJOR, "input", &input_fops);      ...      return 0;  }</span>  只有一个open函数,其他read,write函数呢?[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static const struct file_operations input_fops = {      .owner = THIS_MODULE,      .open = input_open_file,  };</span>  input_open_file函数[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static int input_open_file(struct inode *inode, struct file *file)  {      struct input_handler *handler;      const struct file_operations *old_fops, *new_fops = NULL;      int err;      ...      /* 以次设备号为下标,在input_table数组找到一项handler */      handler = input_table[iminor(inode) >> 5];      /* 通过handler找到一个新的fops */      new_fops = fops_get(handler->fops);      ...      old_fops = file->f_op;      /* 从此file->f_op = new_fops */      file->f_op = new_fops;      ...      /* 用新的new_fops的打开函数 */      err = new_fops->open(inode, file);      ...      return err;  }</span>  input_handlerj结构体成员[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">struct input_handler {      void *private;      void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);      int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);      void (*disconnect)(struct input_handle *handle);      void (*start)(struct input_handle *handle);      const struct file_operations *fops;      int minor;      const char *name;      const struct input_device_id *id_table;      const struct input_device_id *blacklist;      struct list_head    h_list;      struct list_head    node;  };</span>  问:怎么读按键?APP:read > ... > file->f_op->read 问:input_table数组由谁构造?答:input_register_handler ,由设备驱动程序的init初始化中调用注册三、input_register_handler函数(注册input_handler)[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">int input_register_handler(struct input_handler *handler)  {      struct input_dev *dev;      ...      INIT_LIST_HEAD(&handler->h_list);      ...      /* 将handler放入input_table数组 */      input_table[handler->minor >> 5] = handler;      ...      /* 将handler放入input_handler_list链表 */      list_add_tail(&handler->node, &input_handler_list);      ...      /* 对于每个input_dev,调用input_attach_handler      * 根据input_handler的id_table判断能否支持这个input_dev      */      list_for_each_entry(dev, &input_dev_list, node)          input_attach_handler(dev, handler);      ...  }</span>  四、input_register_device函数(注册inout_dev)[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">int input_register_device(struct input_dev *dev)  {      ...      struct input_handler *handler;      ...      device_add(&dev->dev);      ...      /* 把input_dev放入input_dev_list链表 */      list_add_tail(&dev->node, &input_dev_list);      ...      /* 对于每一个input_handler,都调用input_attach_handler      * 根据input_handler的id_table判断能否支持这个input_dev      */      list_for_each_entry(handler, &input_handler_list, node)          input_attach_handler(dev, handler);      ...  }</span>  五、input_attach_handler函数[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)  {      const struct input_device_id *id;      ...      /* 根据input_handler的id_table判断能否支持这个input_dev */      input_match_device(handler->id_table, dev);      ...      /* 若支持,则调用handler的connect函数,建立连接 */      handler->connect(handler, dev, id);      ...  }</span>  小总结: 注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立"连接"。问:如何建立连接connect?答:举例,evdev_connect函数[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static int evdev_connect(struct input_handler *handler, struct input_dev *dev,               const struct input_device_id *id)  {      struct evdev *evdev;      ...      /* 分配一个input_handle */      evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);       ...      snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);      evdev->exist = 1;      evdev->minor = minor;      evdev->handle.dev = input_get_device(dev); // 指向左边的input_dev      evdev->handle.name = evdev->name;      evdev->handle.handler = handler; // 指向右边的input_handler      evdev->handle.private = evdev;      /* 设置dev结构体成员 */      dev_set_name(&evdev->dev, evdev->name);      evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);      evdev->dev.class = &input_class;      evdev->dev.parent = &dev->dev;      evdev->dev.release = evdev_free;      device_initialize(&evdev->dev);      /* 注册 */      input_register_handle(&evdev->handle);      ...  }</span>  input_handle结构体成员[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">struct input_handle {      void *private;      int open;      const char *name;      struct input_dev *dev;      struct input_handler *handler;      struct list_head    d_node;      struct list_head    h_node;  };</span>  问:input_register_handle如何注册?[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">int input_register_handle(struct input_handle *handle)  {      struct input_handler *handler = handle->handler;      struct input_dev *dev = handle->dev;      ...      /* 把handle->d_node添加到dev->h_list      * 这样,就可以从dev->h_list找到handle,进而找到handler      */      list_add_tail_rcu(&handle->d_node, &dev->h_list);      ...      /* 把handle->h_node添加到handler->h_list       * 这样,就可以从handler->h_list找到handle,进而找到dev      */      list_add_tail(&handle->h_node, &handler->h_list);      ...      return 0;  }</span>  小总结: 怎么建立连接connect?1. 分配一个input_handle结构体2. input_handle.dev = input_dev;  // 指向左边的input_devinput_handle.handler = input_handler;  // 指向右边的input_handler3. 注册:   input_handler->h_list = &input_handle;   inpu_dev->h_list      = &input_handle;六、怎么读按键?答:举例,evdev_read[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static ssize_t evdev_read(struct file *file, char __user *buffer,                size_t count, loff_t *ppos)  {      struct evdev_client *client = file->private_data;      struct evdev *evdev = client->evdev;      struct input_event event;      ...      /* 无数据并且是非阻塞方式打开,则立刻返回 */      if (client->head == client->tail && evdev->exist &&          (file->f_flags & O_NONBLOCK))          return -EAGAIN;      /* 否则休眠 */      retval = wait_event_interruptible(evdev->wait,          client->head != client->tail || !evdev->exist);      ...  }</span>  问:谁来唤醒?搜索evdev->wait发现是evdev_event唤醒的[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static void evdev_event(struct input_handle *handle,              unsigned int type, unsigned int code, int value)  {      struct evdev *evdev = handle->private;      struct evdev_client *client;      struct input_event event;      ...      /* 唤醒 */      wake_up_interruptible(&evdev->wait);  }</span>  问:evdev_event被谁调用? 答:应该是硬件相关的代码,input_dev那层调用的在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数。举例,在drivers/input/keyboard/gpio_keys.c里的gpio_keys_isr函数[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static irqreturn_t gpio_keys_isr(int irq, void *dev_id)  {      struct gpio_button_data *bdata = dev_id;      struct gpio_keys_button *button = bdata->button;      ...      /* 上报事件 */      gpio_keys_report_event(bdata);      return IRQ_HANDLED;  }</span>  gpio_keys_report_event函数[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片<span style="font-size:14px;">static void gpio_keys_report_event(struct gpio_button_data *bdata)  {      struct gpio_keys_button *button = bdata->button;      struct input_dev *input = bdata->input;      unsigned int type = button->type ?: EV_KEY;      int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;      /* 上报事件 */      input_event(input, type, button->code, !!state);      input_sync(input);  }</span>  问:input_event函数如何上报事件答:[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片input_event-->input_handle_event-->input_pass_event  list_for_each_entry_rcu(handle, &dev->h_list, d_node)  if (handle->open)  handle->handler->event(handle,  type, code, value);  怎么写符合输入子系统框架的驱动程序?1. 分配一个input_dev结构体2. 设置3. 注册4. 硬件相关的代码,比如在中断服务程序里上报事件
怎么建立连接?1. 分配一个input_handle结构体2.     input_handle.dev = input_dev;  // 指向左边的input_dev    input_handle.handler = input_handler;  // 指向右边的input_handler3. 注册:   input_handler->h_list = &input_handle;   inpu_dev->h_list      = &input_handle;

修改指针的指向,形参要用二级指针来接收~

怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件

struct input_dev {

void *private;const char *name;const char *phys;const char *uniq;struct input_id id;unsigned long evbit[NBITS(EV_MAX)];   // 表示能产生哪类事件unsigned long keybit[NBITS(KEY_MAX)]; // 表示能产生哪些按键unsigned long relbit[NBITS(REL_MAX)]; // 表示能产生哪些相对位移事件, x,y,滚轮unsigned long absbit[NBITS(ABS_MAX)]; // 表示能产生哪些绝对位移事件, x,yunsigned 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)];

测试:
1.
hexdump /dev/event1 (open(/dev/event1), read(), )
秒 微秒 类 code value
0000000 0bb2 0000 0e48 000c 0001 0026 0001 0000
0000010 0bb2 0000 0e54 000c 0000 0000 0000 0000
0000020 0bb2 0000 5815 000e 0001 0026 0000 0000
0000030 0bb2 0000 581f 000e 0000 0000 0000 0000

  1. 如果没有启动QT:
    cat /dev/tty1
    按:s2,s3,s4
    就可以得到ls

或者:
exec 0然后可以使用按键来输入

  1. 如果已经启动了QT:
    可以点开记事本
    然后按:s2,s3,s4
0 0
原创粉丝点击