输入子系统一(输入子系统工作原理)

本文涉及的产品
文档翻译,文档翻译 1千页
文本翻译,文本翻译 100万字符
图片翻译,图片翻译 100张
简介: 输入子系统一(输入子系统工作原理)

1,输入子系统的作用和框架

什么是输入设备:
  1,按键/keyboard
  2, mouse
  3, touchscreen :gt811, ft56xx
  4, joystick 游戏杆
  有多个输入设备需要驱动的时候,假如不考虑输入子系统
    a, gt811
        设备号,创建文件,硬件初始化,实现fop,阻塞
        硬件初始化
    b, ft56xx
        设备号,创建文件,硬件初始化,实现fop,阻塞
        硬件初始化 
    多个输入设备有共同点:
        获取到数据(操作硬件),上报给用户(xxx_read, copy_to_user, 阻塞)
            差异化                           通用
    多个输入设备,有部分差异,也有部分通用
    内核就会考虑,将通用代码编写好,将差异化的代码留给驱动工程师
    设计成输入子系统:使得应用编程人员和驱动编程人员编程的时候变得简单统一
      1, 兼容所有的输入设备
      2, 统一的编程驱动方法(实现差异化硬件操作)
      3,  统一的应用操作接口:/dev/input/event0,event1
              open("/dev/input/event0"),
              read(fd, struct input_event): struct input_event buff可以认为是一个统一的数据包
框架:驱动分成三层
      应用层
  ---------------------------------
  input handler层:数据处理者
    完成fop:实现xxx_read(), xxx_open
    将数据交给用户:数据从input device层
    不知道具体数据是什么,只知道把数据给用户
  ----------------------------------------------------------
  input 核心层:管理层
  ----------------------------------------------------------
  input device设备层:
    抽象出一个对象,描述输入设备信息
    初始化输入设备硬件,获取到数据
    知道具体的数据是什么,但是不知道数据如何给用户
  ---------------------------------
  硬件层:mouse
      ts, keybaord,joystick
 编程: 主要在input device层

2,输入子系统的编程方式–学会最简单的输入子系统的开发方式

前提:input 核心层代码和input handler层需要在内核中必须有:
  drivers/input/evdev.c //  event handler
  drivers/input/input.c  // 核心层
 make menuconfig
  Device Drivers  --->
     Input device support  ---> 
        -*- Generic input layer (needed for keyboard, mouse, ...)  // input.c
         <*>   Event interface   //input handler层--evdev.c
编写步骤:
  1,分配一个input device对象
  2, 初始化input  device对象
  3,注册input device对象
上报数据:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    参数1:当前的input device上报数据
    参数2:上报的是那种数据类型 EV_KEY,EV_ABS
    参数3:具体数据是什么:KEY_POWER
    参数4:值是是什么
用户空间读到的数据:统一的数据包
• 1
struct input_event {
  struct timeval time; //时间戳
  __u16 type; //数据类型
  __u16 code;//具体数据是什么
  __s32 value;//值是是什么
};

3, 初始化input device

struct input_dev {//表示的是一个具体的输入设备,描述设备能够产生什么数据
  const char *name; // sysfs中给用户看的信息
  const char *phys;
  const char *uniq;
  struct input_id id;
  //evbit实际是一个位表,描述输入设备能够产生什么类型数据
  unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // EV_KEY,EV_ABS, EV_REL
  //表示能够产生哪种按键
  unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//KEY_POWER.. 能够表示768bit,直接用24个long来表示
               // KEY_CNT == 768   BITS_TO_LONGS== nr/32 = 768/32==24
  //表示能够产生哪种相对坐标数据
  unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// REL_X
  //表示能够产生哪种绝对坐标数据
  unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //ABS_X
  unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
  unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
  unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
  unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
  unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
  struct device dev; // 继承device对象
  struct list_head  h_list;
  struct list_head  node; //表示节点
}

不同输入设备能够产生不同的数据:

1,按键/keyboard: 产生键值,实际是一个数字

#define KEY_VOLUMEDOWN    114
      #define KEY_VOLUMEUP    115
      #define KEY_POWER   116 /* SC System Power Down */
2,ts/gsensor:产生坐标,绝对坐标, 有一个明确坐标系,并且原点(0,0),最大值(800,480)
#define ABS_X     0x00
        #define ABS_Y     0x01
        #define ABS_PRESSURE    0x18
        #define ABS_MT_TOUCH_MAJOR  0x30  /* Major axis of touching ellipse */
        #define ABS_MT_TOUCH_MINOR  0x31  /* Minor axis (omit if circular) */
        #define ABS_MT_WIDTH_MAJOR  0x32  /* Major axis of approaching ellipse */
        #define ABS_MT_WIDTH_MINOR  0x33  /* Minor axis (omit if circular) */
        #define ABS_MT_ORIENTATION  0x34  /* Ellipse orientation */
        #define ABS_MT_POSITION_X 0x35  /* Center X touch position */
        #define ABS_MT_POSITION_Y 0x36  /* Center Y touch position */
3,mouse:产生坐标,相对坐标,坐标值是相对于之前一个点坐标  
• 1
#define REL_X     0x00
      #define REL_Y     0x01
      #define REL_WHEEL   0x08
如何表示不同数据类型: 
#define EV_SYN      0x00 //表示同步数据类型
    #define EV_KEY      0x01 //表示按键数据类型
    #define EV_REL      0x02 //表示相对坐标数据类型
    #define EV_ABS      0x03 //表示绝对坐标数据类型
    #define EV_MSC      0x04 //表示杂项
    #define EV_SW     0x05 //开关  
    #define EV_LED      0x11 //led指示数据
    #define EV_SND      0x12 //声音数据
另外一种设置bit的方法
inputdev->keybit[BIT_WORD(KEY_POWER)] |= BIT_MASK(KEY_POWER);// 116%32
  inputdev->keybit[116/32] |= 1 << 116%32;// 116%32
上报数据的时候:
  方法1:通用方法
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
方法2:封装:
input_report_key(struct input_dev * dev, unsigned int code, int value)
          |
          input_event(dev, EV_KEY, code, !!value); //上报按键的时候一定0或者1

4, 驱动多个按键

一个按键有多个与其相关的元素:
  a, 中断号码
  b, 按键的状态--gpio的数据寄存器获取到
  c, 按键的值--code
在设备树文件中设置这几个元素:
key_int_node{
                compatible = "test_key";
                #address-cells = <1>;
                #size-cells = <1>;
                key_int@0 {
                        key_name = "key2_power_eint";
                        key_code = <116>;
                        gpio = <&gpx1 1 0>;
                        reg = <0x11000C20 0x18>;
                        interrupt-parent = <&gpx1>;
                        interrupts = <1 0>;
                };
                key_int@1 {
                        key_name = "key3_vup_eint";
                        key_code = <115>;
                        gpio = <&gpx1 2 0>;
                        reg = <0x11000C20 0x18>;
                        interrupt-parent = <&gpx1>;
                        interrupts = <2 0>;
                };
                key_int@2 {
                        key_name = "key4_vdown_eint";
                        key_code = <114>;
                        gpio = <&gpx3 2 0>;
                        reg = <0x11000C60 0x18>;
                        interrupt-parent = <&gpx3>;
                        interrupts = <2 0>;
                };
    };  
make dtbs
    更新设备树文件:
     cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
在代码中也会设计这几个元素
在代码中获取节点:
of_get_next_child(const struct device_node * node, struct device_node * prev)
  参数1:表示节点
  参数2:之前的节点,如果是第一个节点,设置成NULL
// 通过节点去获取到中断号码
       irqno = irq_of_parse_and_map(cnp, 0);
      //获取key name
      of_property_read_string(cnp, "key_name",  &key_name);
      //获取key code
      of_property_read_u32(cnp, "key_code", &code);
      gpionum = of_get_named_gpio(cnp, "gpio", 0);
      printk("name = %s, code = %d, gpionum = %d,irqno = %d\n",
          key_name, code, gpionum,irqno); 
//设计一个对象出来
struct key_desc{
  char *name;
  int irqno;
  int key_code;
  int gpionum;
  void *reg_base;
  struct device_node *cnp;// 可以随时去获取节点各个信息
};  

5, 输入子系统的工作原理和代码分析

目的:
  a,学会如何分析内核中子系统的代码,从而可以举一反三
  b,整体把握框架思想,理解分层中各层的配合方式
  c,掌握子系统,增强排错能力

分层分析:


input handler层:evdev.c

module_init(evdev_init);

module_exit(evdev_exit);

static struct input_handler evdev_handler = {

.event = evdev_event,

.events = evdev_events,

.connect = evdev_connect,

.disconnect = evdev_disconnect,

.legacy_minors = true,

.minor = EVDEV_MINOR_BASE,

.name = “evdev”,

.id_table = evdev_ids,

};

evdev_init(void)
    |
    input_register_handler(&evdev_handler);
        |
        //初始化h_list
        INIT_LIST_HEAD(&handler->h_list);
        //将当前的handler加入到一个input_handler_list
        list_add_tail(&handler->node, &input_handler_list);
        //遍历链表input_dev_list
        list_for_each_entry(dev, &input_dev_list, node)
          input_attach_handler(dev, handler);
            |
            // 将当前的hanler和input dev进行匹配, event handler能够匹配所有的input dev
            id = input_match_device(handler, dev);
            //匹配成功,之后要调用handler中connect方法
            // 实际就是event handler,实际调用了evdev_connect
            error = handler->connect(handler, dev, id);
        //将当前的handler加入到/proc/bus/input/handlers文件中
        input_wakeup_procfs_readers();
  总结:
    1,注册了evdev_handler
    2, 遍历input dev list,并行匹配,恒匹配成功,自动会
      调用handler中connect方法--- evdev_connect

input核心层:input.c

subsys_initcall(input_init);

module_exit(input_exit);

input_init()
  |
  //注册类,类似于class_create();
  err = class_register(&input_class);
  //在/proc创建 bus/input/devices  handlers
  err = input_proc_init();
  //申请设备号
  err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
           INPUT_MAX_CHAR_DEVICES, "input");
  总结:
    1,注册了主设备号
    2,注册input class

simple_input.c

input_register_device(inputdev);

|

//将input dev加入到链表input_dev_list

list_add_tail(&dev->node, &input_dev_list);

//遍历input handler链表,进行匹配
  list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);
        |
      //匹配成功,之后要调用handler中connect方法
      // 实际就是event handler,实际调用了evdev_connect
      error = handler->connect(handler, dev, id);

分析: evdev.c中evdev_connect()— 属于input handler层

evdev_connect(struct input_handler *handler, struct input_dev *dev,

const struct input_device_id *id)

|

//找到一个没有被使用的次设备号, 从64开始, 65,66

minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

// 实例化 一个evdev对象
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//初始化evdev对象
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
//等待队列是完成阻塞
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
dev_no = minor;
dev_no -= EVDEV_MINOR_BASE; //减去了64
// 创建设备文件/dev/event0,1,2
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// 12, 64
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev)
device_add(&evdev->dev); 
//以上代码和device_create是一样
//利用handle记录input device和input handler
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
//你中有有我,我中有你
evdev->handle.private = evdev;
//将儿子关联到父亲(input handler)和母亲(input dev)
error = input_register_handle(&evdev->handle);
        |
      list_add_tail_rcu(&handle->d_node, &dev->h_list);
      list_add_tail_rcu(&handle->h_node, &handler->h_list);
//初始化了cdev,完成了fops, 为用户提供文件io
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
总结:
  1,分配evdev,并初始化,记录input device和handler的关系
  2,创建设备节点/dev/event0
  3, 注册cdev,并实现fops
  4,关系:
    多个input device可以对应一个event handler
    一个input device对应一个 evdev,对应于一个设备节点/dev/event0,1,2
  5, 所有的设备节点调用open,read,write文件io的时候
    实际是调用cdev中fops中各个接口,最终都调用了
    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,
        .llseek   = no_llseek,
      };

device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, …)

|

dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);

|

device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, args);

|

struct device *dev = NULL;

dev = kzalloc(sizeof(*dev), GFP_KERNEL);

device_initialize(dev);
      dev->devt = devt;
      dev->class = class;
      dev->parent = parent;
      dev->groups = groups;
      dev->release = device_create_release;
      dev_set_drvdata(dev, drvdata);
      kobject_set_name_vargs(&dev->kobj, fmt, args);//设置名字
      device_add(dev);//注册到系统

应用程序中调用了输入子系统的代码,数据是如何传递给用户层的?

open(“/dev/event1”, O_RDWR);

vfs

sys_open();

struct file file->f_ops = cdev->ops;

file->f_ops->open();

设备驱动层:输入子系统

input handler 层:evdev.c

cdev;

xxx_ops = {

.open = xxx_open,

.write = xxx_write,

}

static const struct file_operations evdev_fops = {
        .owner    = THIS_MODULE,
        .read   = evdev_read,
        .write    = evdev_write,
        .poll   = evdev_poll,
        .open   = evdev_open,
 }
实际最终调用了evdev_open();
  |
  // 实际cdev是谁,就是evdev_connect注册的那个
  struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
  // 通过儿子,找到老母input device
  unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
  // size就包含了很多input_event
  unsigned int size = sizeof(struct evdev_client) +
          bufsize * sizeof(struct input_event);
  struct evdev_client *client;
  // 分配一个client对像,描述一个缓冲队列,存放的就是input_event
  client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
  // client中有一个缓冲区
  client->bufsize = bufsize;
  spin_lock_init(&client->buffer_lock);
  //在client中记录evdev
  client->evdev = evdev;
  // 将client加入到evdev中一个小链表
  evdev_attach_client(evdev, client);
      |
      list_add_tail_rcu(&client->node, &evdev->client_list);
  // 将client记录到file,方面其他的接口使用
  file->private_data = client;
  总结:
    1,为输入设备分配一个缓冲区evdev_client,用户存放input device层上报的数据
    2,evdev_client记录到evdev中
    3,evdev_client记录到file中,方面其他的接口使用

应用程序中read,是如何获取到数据的

read(fd, &event, sizeof(struct input_event));

---------------------------------------------

vfs

sys_read();

file->f_ops->read();

-------------------------------------------

evdev.c

static const struct file_operations evdev_fops = {

.read = evdev_read,

evdev_read(struct file *file, char __user *buffer,

size_t count, loff_t *ppos)

|

// 获取到open中分配的缓冲区对象

struct evdev_client *client = file->private_data;

//获取到evdev

struct evdev *evdev = client->evdev;

//表示一个数据包,要给用户

struct input_event event;

for (;;) {
    // 实现非阻塞
    if (client->packet_head == client->tail &&
      (file->f_flags & O_NONBLOCK))
      return -EAGAIN;
      while (read + input_event_size() <= count &&
      // 1从缓冲区获取数据,存放在 input_event数据包
         evdev_fetch_next_event(client, &event)) {
            |
            *event = client->buffer[client->tail++];
      // 2, 将数据上报给用户
      if (input_event_to_user(buffer + read, &event))
            |
            copy_to_user(buffer, event, sizeof(struct input_event)
      // 3,统计上报多少数据
      read += input_event_size();
    }
    // 如果当前是阻塞模式
    if (!(file->f_flags & O_NONBLOCK)) {
      //等待---休眠,需要被唤醒,有数据时候唤醒
      error = wait_event_interruptible(evdev->wait,
          client->packet_head != client->tail ||
          !evdev->exist || client->revoked);
  }
  总结:
    1,如果没有数据,就会休眠等待
    2,如果有数据,就从缓冲区client->buffer[client->tail++]拿数据
      通过copy_to_user上报给用户
    疑问:
      数据到底是如何存放在缓冲区的
      等待队列是谁唤醒的
      input_report_key(inputdev, pdesc->key_code, 0);
      input_sync(inputdev);//上报数据结束
input_report_key(inputdev, pdesc->key_code, 0);
input_sync(inputdev);//上报数据结束
  |//input_event(dev, EV_KEY, code, !!value);
  input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);// 上报数据
      |
      input_handle_event(dev, type, code, value);
          |
          // 如果将数据交给input handler去处理
          if (disposition & INPUT_PASS_TO_HANDLERS) {
            struct input_value *v;
            //将input device获取到数据暂存一下input value
            v = &dev->vals[dev->num_vals++];
            v->type = type;
            v->code = code;
            v->value = value;
            input_pass_values(dev, dev->vals, dev->num_vals)
              |
              // 从input device中获取到input handle
            else {
            list_for_each_entry_rcu(handle, &dev->h_list, d_node)
              if (handle->open)
                count = input_to_handler(handle, vals, count);
                    |
                  // 通过handle儿子找到handler父亲
                  struct input_handler *handler = handle->handler;
                  // 如果有events函数指针,那就调用
                  if (handler->events)
                    handler->events(handle, vals, count);
                  else if (handler->event)//否则就调用event();
                    for (v = vals; v != end; v++)
                      handler->event(handle, v->type, v->code, v->value);
          }

static struct input_handler evdev_handler = {

.event = evdev_event,

.events = evdev_events,

.connect = evdev_connect,

.disconnect = evdev_disconnect,

.legacy_minors = true,

.minor = EVDEV_MINOR_BASE,

.name = “evdev”,

.id_table = evdev_ids,

};

总结: 如果将数据上报,最终是调用handler中events()或者event()

实际是evdev.c

.event = evdev_event,

.events = evdev_events,

|

// 拿到evdev,肯定要拿到缓冲区

struct evdev *evdev = handle->private;

struct evdev_client *client;

// 获取到缓冲evdev_client
          else
          list_for_each_entry_rcu(client, &evdev->client_list, node)
            evdev_pass_values(client, vals, count,
                  time_mono, time_real);
              |
              // 通过client获取到evdev
              struct evdev *evdev = client->evdev;
              const struct input_value *v;
              struct input_event event;
              event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real);
              for (v = vals; v != vals + count; v++) {
                // 将input device上报的数据获取到,并且封装成input event对象
                event.type = v->type;
                event.code = v->code;
                event.value = v->value;
                // 将input event数据存放到缓冲区中
                __pass_event(client, &event);
                    |
                    client->buffer[client->head++] = *event;
                    client->head &= client->bufsize - 1;
                for (v = vals; v != vals + count; v++) {
                  // 将input device上报的数据获取到,并且封装成input event对象
                  event.type = v->type;
                  event.code = v->code;
                  event.value = v->value;
                  // 将input event数据存放到缓冲区中
                  __pass_event(client, &event);
                      client->buffer[client->head++] = *event;
                      client->head &= client->bufsize - 1;
                  // 如果调用 input_sync()
                  if (v->type == EV_SYN && v->code == SYN_REPORT)
                    wakeup = true;
                }
                  // 唤醒等待队列
                  if (wakeup)
                    wake_up_interruptible(&evdev->wait);
    总结:
      input_report_key(inputdev, pdesc->key_code, 0);
        //将输入设备产生数据交给input handler,调用events();将数据存放在
        // 缓冲区client->buffer[client->head++] = *event;
      input_sync(inputdev);//上报数据结束
        // 唤醒等待队列,表示输入设备上报数据完毕




目录
相关文章
|
6月前
解释一下ConditionVariable的工作原理。
解释一下ConditionVariable的工作原理。
97 6
|
6月前
|
存储 Prometheus Cloud Native
Thanos 工作原理及组件简介
Thanos 工作原理及组件简介
|
编解码
A/D和D/A工作原理
A/D(模数转换)和D/A(数模转换)是两种常见的信号转换技术,用于将模拟信号转换为数字信号(A/D)或将数字信号转换为模拟信号(D/A)。以下是对这两种技术的工作原理的详细介绍。 A/D转换器的工作原理: A/D转换器是一种将连续的模拟信号转换为离散的数字信号的设备。它由两个主要部分组成:采样和量化。 1. 采样: 采样是将连续的模拟信号在一定时间间隔内进行离散化的过程。A/D转换器使用一个称为采样保持电路的设备来完成这一过程。采样保持电路在一段时间内对模拟信号进行采样,并将其保持在一个电容中。采样过程中的时间间隔称为采样周期,其决定了采样率。采样率越高,转换的数字信号越接近原始模拟信号
194 0
|
vr&ar 芯片
三级管集电极开路电路工作原理详细分析
三级管集电极开路电路工作原理详细分析
117 0
|
芯片 SoC
FinFET工作原理、结构和应用特性介绍
FinFET的全称是Fin Field-Effect Transistor。它是一种新型互补金属氧化物半导体晶体管。FinFET 的名称是基于晶体管和鳍片形状的相似性。
11812 0
FinFET工作原理、结构和应用特性介绍
|
3月前
|
安全 网络协议 Shell
Telnet:简介、工作原理及其优缺点
【8月更文挑战第19天】
570 0
|
5月前
|
Java 开发者 Spring
深入解析这两种扩展机制的工作原理和用法
深入解析这两种扩展机制的工作原理和用法
|
6月前
|
存储 缓存 IDE
CAN通信的基本原理与实现方法
CAN通信的基本原理与实现方法
468 0
|
6月前
|
自然语言处理 JavaScript 前端开发
|
6月前
|
存储 JavaScript 前端开发
V8工作原理(上)
V8工作原理
83 0