0.前言
正点原子Linux驱动笔记,本章不涉及具体代码示例
1.简介
Linux内核专门为输入设备做了一个input子系统框架,用来管理按键,鼠标,触摸屏等输入设备,输入设备本质上还是字符设备,input子系统在内核空间分为驱动层,核心层,事件处理层,最终给用户空间提供可访问的设备节点 ,对于驱动编写者只需要在驱动层上报输入事件即可,比如按键值,坐标值等信息
2.框架
Linux下输入子系统内核空间分为三层,驱动层,核心层,事件处理层
- 上层为事件处理层:主要与用户空间进行交互,内核已经实现
- 中间为核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理,内核已经实现,主要作用为:构造file_operation(open read ioctl),注册,入口函数,出口函数
- 驱动层:输入设备具体的驱动程序,比如按键驱动程序,向内核层报告输入内容
3.输入子系统核心层
代码位置:drivers/input/input.c
input核心层会向Linux内核注册一个字符设备,示例代码为:
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;
}
其中class_register(&input_class) 会注册一个class类,系统起来之后会在/sys/class目录下有一个input子目录,如图所示
其中register_chrdev_region 函数为注册一个字符设备,主设备号为INPUT_MAJOR
#define INPUT_MAJOR 13
INPUT_MAX_CHAR_DEVICES定义为:
#define INPUT_MAX_CHAR_DEVICES 1024
所以,input子系统所有设备主设备号都是13,最多支持1024个输入设备我们在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可
4.输入子系统驱动编写
4.1 基本变量
在输入子系统驱动编写的时候我们只需要注册一个input设备即可,input设备使用input-dev设备表示,定义为:
include/linux/input.h
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)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
...
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
...
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
-
name:输入设备名 -
id:输入设备与事件处理器的匹配信息 -
evbit:指定支持的事件类型
-
keybit:指定支持的按键值类型
-
relbit:指定支持相对坐标类型
-
absbit:指定支持绝对坐标类型
其中evbit表示输入事件的类型,可以选择的事件为:
include/uapi/linux/input.h
#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
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
如我们需要用到按键,需要注册EV_KEY事件,如果要使用连按功能的话还需要注册EV_REP事件,evbit表示不同事件的值,keybit,relbit中存放了不同事件的位图,其中keybit表示按键事件的位图,即按键值,在Linux内核中定义了很多按键值,如下
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
...
我们可以将开发板上的按键值设置为上面中的任何一个,这里我们设置开发板的KEY按键值为KEY_0
4.2 input_dev的注册
1.在编写input设备驱动的时候我们需要使用input_allocate_device 函数来分配初始化一个input_dev结构体,原型为:
struct input_dev *input_allocate_device(void)
参数:无 return:input_dev
2.第2步使用input_register_device()函数来向系统注册一个输入设备
int input_register_device(struct input_dev *dev)
dev:要注册的设备
return: 0:注册成功,负数:注册失败
3.input_unregister_device():向系统注销输入设备
void input_unregister_device(struct input_dev *dev)
dev:要注销的 input_dev
4.input_free_device()函数:释放input_dev设备
void input_free_device(struct input_dev *dev)
dev:要释放的input_dev
所以input_dev的注册流程为:
①、使用 input_allocate_device 函数申请一个 input_dev。 ②、初始化 input_dev 的事件类型以及事件值。 ③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。 ④、卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用 input_free_device 函数释放掉前面申请的 input_dev
4.2.1 设置事件值的方法代码
struct input_dev *inputdev;
static int __init xxx_init(void)
{
......
inputdev = input_allocate_device();
inputdev->name = "test_inputdev";
__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);
__set_bit(EV_KEY, inputdev->evbit);
__set_bit(EV_REP, inputdev->evbit);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
input_register_device(inputdev);
......
return 0;
}
input_set_capability()函数:设置产生哪些值
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
switch (type) {
case EV_KEY:
__set_bit(code, dev->keybit);
break;
case EV_REL:
__set_bit(code, dev->relbit);
break;
case EV_ABS:
input_alloc_absinfo(dev);
if (!dev->absinfo)
return;
__set_bit(code, dev->absbit);
break;
...
}
BIT_MASK宏
include/asm-generic/bitops/non-atomic.h
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
4.3 上报输入事件
向 Linux 内核注册好 input_dev 以后还不能使用 input 设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,需要将获取到具体的输入值,或者说是输入事件上报给 Linux 内核。比如按键,需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,依次来看一下一些常用的事件上报 API 函数
通用的事件上报接口
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
返回值:无
按键事件上报函数: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);
}
绝对坐标事件上报函数:
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_REL, code, value);
}
绝对坐标事件上报接口:
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
当上报事件以后需要使用input_sync来告诉Linux内核input子系统上报事件完毕,本质是上报一个同步事件,同步事件上报接口
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
综上来看,按键值的上报参考代码为:
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio);
if(value == 0){
input_report_key(inputdev, KEY_0, 1);
input_sync(inputdev);
} else {
input_report_key(inputdev, KEY_0, 0);
input_sync(inputdev);
}
}
4.4 input_event结构体
Linux 内核使用 input_event 这个结构体来表示所有的输入事件, input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
其中time为发生此事件的时间,为 t为 timeval 结构体类型,定义为:
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;
struct timeval {
__kernel_time_t tv_sec;
__kernel_suseconds_t tv_usec;
};
type:事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位
code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0,KEY_1 等等这些按键。此成员变量为 16 位
value:比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的 话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了
4.4.1 使用hexdump分析按键信息
比如按键设备对应/dev/input/event1,可以通过hexdump命令查看/dev/input/event1文件,输入命令为:
hexdump /dev/input/event1
上述就是input_event类型事件的原始值,采用16进制表示,这些原始数据的含义如下
0000000 0c41 0000 d7cd 000c 0001 000b 0001 0000
0000010 0c41 0000 d7cd 000c 0000 0000 0000 0000
0000020 0c42 0000 54bb 0000 0001 000b 0000 0000
0000030 0c42 0000 54bb 0000 0000 0000 0000 0000
其中type为事件类型:EV_KEY为事件1,EV_SYN为事件0,所以第1行代码EV_KEY事件,第二行代码EV_SYN事件,code为事件编码,即按键编码,KEY_0对应11,十六进制为0xb,所以,第1行代表KEY_0这个按键事件,最后的value就是按键值,1表示按下,0表示释放
#define EV_SYN 0x00
#define EV_KEY 0x01
#define KEY_0 11
所以:
第 1 行,按键(KEY_0)按下事件。 第 2 行, EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。 第 3 行,按键(KEY_0)松开事件。 第 4 行, EV_SYN 同步事件,和第 2 行一样。
5.补充
产生的设备文件
/dev/input/event0
/dev/input/event1
可以通过如下命令查看输入设备和event的对应关系
cat /proc/bus/input/devices
|