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

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

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);//上报数据结束
        // 唤醒等待队列,表示输入设备上报数据完毕




目录
相关文章
|
22天前
解释一下ConditionVariable的工作原理。
解释一下ConditionVariable的工作原理。
37 6
|
22天前
|
存储 Prometheus Cloud Native
Thanos 工作原理及组件简介
Thanos 工作原理及组件简介
|
8月前
|
编解码
A/D和D/A工作原理
A/D(模数转换)和D/A(数模转换)是两种常见的信号转换技术,用于将模拟信号转换为数字信号(A/D)或将数字信号转换为模拟信号(D/A)。以下是对这两种技术的工作原理的详细介绍。 A/D转换器的工作原理: A/D转换器是一种将连续的模拟信号转换为离散的数字信号的设备。它由两个主要部分组成:采样和量化。 1. 采样: 采样是将连续的模拟信号在一定时间间隔内进行离散化的过程。A/D转换器使用一个称为采样保持电路的设备来完成这一过程。采样保持电路在一段时间内对模拟信号进行采样,并将其保持在一个电容中。采样过程中的时间间隔称为采样周期,其决定了采样率。采样率越高,转换的数字信号越接近原始模拟信号
128 0
|
8月前
|
定位技术 UED
卫星电话的工作原理简介
卫星电话的工作原理简介
377 0
|
22天前
|
存储 缓存 IDE
CAN通信的基本原理与实现方法
CAN通信的基本原理与实现方法
225 0
|
22天前
|
前端开发
iStack详解(一)——iStack基本原理
iStack详解(一)——iStack基本原理
55 4
|
22天前
|
运维 监控 NoSQL
RedisShake的基本原理
RedisShake的基本原理
86 0
|
22天前
|
存储 JavaScript 前端开发
V8工作原理(上)
V8工作原理
66 0
|
22天前
|
自然语言处理 JavaScript 前端开发
测温仪的工作原理是什么?
1、测温枪的原理是:红外测温枪由光学系统、光电探测器、信号放大器及信号处置、显现输出等部分组成。 2、光学系统汇聚其视场的方针红外辐射能量,视场的大小由测温仪的光学零件及其方位断定。当用红外辐射测温仪丈量方针的温时首先要丈量出方针在其波段范围的红外辐射量,然后由测温仪计算出被测方针的温。
测温仪的工作原理是什么?