浅析 Linux input 输入子系统

1. input输入子系统介绍

input子系统就是管理输入的子系统,和pinctrl、gpio子系统一样,是Linux内核针对某一类设备而创建的框架。比如按键、键盘、鼠标、触摸屏等,都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层,只要按照要求上报这些输入事件即可

input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点,其框架如下图示
浅析 Linux input 输入子系统_第1张图片

可见在Linux内核空间,分为驱动层、核心层和事件层。编写驱动程序时只需要关注这三个层,它们的分工如下:

  • 驱动层:输入设备的具体驱动程序,向核心层报告输入内容
  • 核心层:为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理
  • 事件层:和用户空间进行交互

浅析 Linux input 输入子系统_第2张图片

2. input驱动编写流程

input核心层会向Linux内核注册一个字符设备,drivers/input/input.c文件就是input输入子系统的核心层,其内容如下:

struct class input_class = {
 .name = "input",
 .devnode = input_devnode,
};
......
......
static int __init input_init(void) {
 int err;
 err = class_register(&input_class);
 if (err) {
  pr_err("unable to register input_dev class\n");
  return err;
 }

 err = input_proc_init();
 if (err)
  goto fail1;

 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES,"input");
 if (err) {
  pr_err("unable to register char major %d", INPUT_MAJOR);
  goto fail2;
 }

 return 0;

fail2: input_proc_exit();
fail1: class_unregister(&input_class);
 return err;
}

从以上代码可以看出,核心层向内核注册了一个input类(在/sys/class下),并且注册了主设备号为INPUT_MAJOR(13)的字符设备。因此,input子系统的所有设备主设备号都为13,在使用input子系统处理输入设备时就不需要去注册字符设备了,只需要向系统注册一个input_device即可

2.1 注册input_dev

使用input子系统时,需要注册input_device设备,input_device由input_dev结构体表示

/***** input_dev 结构体定义 *****/
struct input_dev {
 const char *name;
 const char *phys;
 const char *uniq;
 struct input_id id;
 
 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
 
 unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    /* 事件类型的位图 */
 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  /* 按键值的位图 */
 unsigned long relbit[BITS_TO_LONGS(REL_CNT)];  /* 相对坐标的位图 */
 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];  /* 绝对坐标的位图 */
 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];  /* 杂项事件的位图 */
 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];  /* LED相关的位图 */
 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];  /* sound有关的位图 */
 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    /* 压力反馈的位图 */
 unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    /*开关状态的位图 */
 ......
 bool devres_managed;
};
/***** input_dev 结构体中的evbit 表示输入事件类型,可选的事件类型
定义在 include/uapi/linux/input.h 文件中,事件类型如下。比如要使用
按键,那么久需要注册EV_KEY事件,若要连按功能,还需要注册EV_REP事件 ***/
#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            /* sound(声音) */
#define EV_REP 0x14            /* 重复事件 */
#define EV_FF 0x15             /* 压力事件 */
#define EV_PWR 0x16            /* 电源事件 */
#define EV_FF_STATUS 0x17      /* 压力状态事件 */
/***** input_dev 结构体中的keybit、 relbit 等都是存放不同事件对应
的值。比如要使用按键事件,因此要用到keybit按键值位图,其按键值定义
在 include/uapi/linux/input.h 文件中 **************************/
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7

⏩ 注册input设备之前,先要申请内存;注销input设备之后,要释放内存

/* 申请 input_dev 结构体变量 */
struct input_dev *input_allocate_device(void)
//返回值:申请到的input_dev
/* 释放 input_dev 结构体变量 */
void input_free_device(struct input_dev *dev)
//dev:需要释放的input_dev

⏩ 注册input设备和注销input设备,使用如下函数

/* 注册 input_dev */
int input_register_device(struct input_dev *dev)
//dev:要注册的input_dev
//返回值:0,注册成功;负值,注册失败
/* 注销 input_dev */
void input_unregister_device(struct input_dev *dev)
//dev:要注销的 input_dev

综上所述, input_dev注册过程如下

  • 定义一个 input_dev 结构体变量,并申请内存
  • 初始化 input_dev 的事件类型及事件值
  • 向 Linux 内核注册上面初始化好的 input_dev
  • 卸载时,先注销 input_dev,再释放内存
/* input_dev 注册流程示例代码 */
struct input_dev *inputdev;   /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void) {
 ......
 inputdev = input_allocate_device();  /* 申请 input_dev */
 inputdev->name = "test_inputdev";   /* 设置 input_dev 名字 */
 /*********第一种设置事件和事件值的方法***********/
 __set_bit(EV_KEY, inputdev->evbit);  /* 设置产生按键事件 */
 __set_bit(EV_REP, inputdev->evbit);  /* 重复事件 */
 __set_bit(KEY_0, inputdev->keybit);  /*设置产生哪些按键值 */
 /************************************************/
 /*********第二种设置事件和事件值的方法***********/
 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
 /************************************************/
 /*********第三种设置事件和事件值的方法***********/
 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
 /************************************************/
 /* 注册 input_dev */
 input_register_device(inputdev);
 ......
 return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void) {
 input_unregister_device(inputdev);  /* 注销 input_dev */
 input_free_device(inputdev);   /* 删除 input_dev */
}

2.2 上报输入事件

获取输入事件及输入值,并上报给Linux内核,上报函数input_event()原型如下

void input_event(struct input_dev *dev,
                 unsigned int type,
                 unsigned int code,
                 int value)
//dev: 需要上报的 input_dev
//type: 上报的事件类型,比如 EV_KEY
//code: 事件码,也就是我们注册的按键值,比如 KEY_0、 KEY_1 等等
//value: 事件值,比如1表示按键按下,0表示按键松开

⏩ input_event函数可以上报所有的事件类型和事件值,内核也提供了其他针对具体事件的上报函数,不过本质上还是input_event函数

/* 上报按键所使用的 input_report_key 函数 */
static inline void input_report_key(struct input_dev *dev,unsigned int code,int value){
 input_event(dev, EV_KEY, code, !!value);
}
/* 其他事件上报函数 */
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

⏩ 上报事件以后还需要使用input_sync函数来告诉内核input子系统上报结束

void input_sync(struct input_dev *dev)
//dev:需要上报同步事件的 input_dev
综上所述,事件上报的过程如下(以按键上报为例)

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg) {
 unsigned char value;
 value = gpio_get_value(keydesc->gpio);   //读取IO值
 if(value == 0){                          //按下按键
  input_report_key(inputdev, KEY_0, 1);   //最后一个参数1,按下
  input_sync(inputdev);                   //同步事件
 } else {                                 //按键松开
  input_report_key(inputdev, KEY_0, 0);   //最后一个参数0,松开
  input_sync(inputdev);                   //同步事件
 }
}

2.3 输入事件结构体

Linux内核使用input_event结构体来表示所有的输入事件,用户应用程序可以通过input_event来获取到具体的输入事件或相关的值

/* input_envent 结构体定义在include/uapi/linux/input.h 文件中 */
struct input_event {
 struct timeval time;
 __u16 type;
 __u16 code;
 __s32 value;
};
//time:时间,也就是此事件发生的时间,为 timeval 结构体类型
//type:事件类型,比如EV_KEY,表示此次事件为按键事件
//code:事件码,比如在EV_KEY事件中就表示具体的按键码,KEY_0/KEY_1等按键
//value:值,比如EV_KEY事件中就表示按键值,表示按键有没有被按下

你可能感兴趣的:(#,Linux驱动开发,驱动开发,linux,运维,input子系统)