输入子系统由驱动层(input_driver)、核心层(input_core)以及事件层(input_event_handle)组成,如下图所示:
Note:Input子系统中所有输入设备的主设备号都是13,核心层通过次设备号来将输入设备分类,例如如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。在使用input系统时,不需要去注册字符设备,只需要向系统申请一个input_device即可。
在Linux内核中,使用input_dev结构体来表示一个输入设备,属于驱动层,定义在include/linux/input.h
中,如下:
struct input_dev {
const char *name; /* 设备名称 */
const char *phys; /* 设备在系统中的路径 */
const char *uniq; /* 设备唯一id */
struct input_id id; /* input设备id号 */
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 设备支持的事件类型,主要有EV_SYNC,EV_KEY,EV_KEY,EV_REL,EV_ABS等*/
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)]; /* 支持声音事件 */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 支持受力事件 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /* 支持开关事件 */
unsigned int hint_events_per_packet; /* 平均事件数*/
unsigned int keycodemax; /* 支持最大按键数 */
unsigned int keycodesize; /* 每个键值字节数 */
void *keycode; /* 存储按键值的数组的首地址 */
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke, unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke);
struct ff_device *ff; /* 设备关联的反馈结构,如果设备支持 */
unsigned int repeat_key; /* 最近一次按键值,用于连击 */
struct timer_list timer; /* 自动连击计时器 */
int rep[REP_CNT]; /* 自动连击参数 */
struct input_mt *mt; /* 多点触控区域 */
struct input_absinfo *absinfo; /* 存放绝对值坐标的相关参数数组 */
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; /* 反应设备当前的案件状态 */
unsigned long led[BITS_TO_LONGS(LED_CNT)]; /* 反应设备当前的led状态 */
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; /* 反应设备当前的声音状态 */
unsigned long sw[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); /* 事件处理函数,主要是接收用户下发的命令,如点亮led */
struct input_handle __rcu *grab; /* 当前占有设备的input_handle */
spinlock_t event_lock; /* 事件锁 */
struct mutex mutex; /* 互斥体 */
unsigned int users; /* 打开该设备的用户数量(input_handle) */
bool going_away; /* 标记正在销毁的设备 */
struct device dev; /* 一般设备 */
struct list_head h_list; /* 设备所支持的input handle */
struct list_head node; /* 用于将此input_dev连接到input_dev_list */
unsigned int num_vals; /* 当前帧中排队的值数 */
unsigned int max_vals; /* 队列最大的帧数*/
struct input_value *vals; /* 当前帧中排队的数组*/
bool devres_managed; /* 表示设备被devres 框架管理,不需要明确取消和释放*/
};
input_handler结构体属于事件层,代表一种输入事件的解决方案,如键盘,鼠标、游戏手柄等等,该结构体定义在include/linux/input.h
中,如下:
/* include/linux/input.h */
struct input_handler {
void *private; /* 存放handle数据 */
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle,
const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
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);
bool legacy_minors;
int minor;
const char *name; /* 名字 */
const struct input_device_id *id_table; /* input_dev匹配用的id */
struct list_head h_list; /* 用于链接和handler相关的handle,input_dev与input_handler配对之后就会生成一个input_handle结构 */
struct list_head node; /* 用于将该handler链入input_handler_list,链接所有注册到内核的所有注册到内核的事件处理器 */
};
input_handle结构体属于核心层,代表一对已建立连接的input_dev和input_handler,该结构体定义在include/linux/input.h
中,如下:
/* include/linux/input.h */
struct input_handle {
void *private; /* 数据指针 */
int open; /* 打开标志,每个input_handle 打开后才能操作 */
const char *name; /* 设备名称 */
struct input_dev *dev; /* 指向所属的input_dev */
struct input_handler *handler; /* 指向所属的input_handler */
struct list_head d_node; /* 用于链入所指向的input_dev的handle链表 */
struct list_head h_node; /* 用于链入所指向的input_handler的handle链表 */
};
在input子系统中,使用input_event来表示一个输入事件,该结构体被定义在文件/include/uapi/linux/input.h
中,如下:
/* include/uapi/linux/input.h */
struct input_event
{
struct timeval time; /* 事件发生的时间 */
__u16 type; /* 事件类型 */
__u16 code; /* 事件码 */
__s32 value; /* 事件值 */
}
/* include/uapi/linux/time.h */
struct timeval
{
__kernel_time_t tv_sec; /* 秒 */
__kernel_suseconds_t tv_usec; /* 微秒 */
}
在设备驱动层,注册一个input_dev需要三个步骤,如下:
1. 分配一个input_dev实例(内存)。
my_dev = input_allocate_device();
2. 初始化input_dev实例(配置该输入设备支持的事件类型等)。
my_dev->name = XXX_NAME; /* 设备名称 */
my_dev->phys = XXX_PHYS; /* 设备在系统中的路径 */
my_dev->id.vendor = XXX_VENDOR_ID; /* 设备ID */
my_dev->id.product = XXX_PRODUCT_ID;
my_dev->id.version = XXX_CTL_VERSION;
set_bit(EV_KEY, my_dev->evbit); /* 设备支持的事件类型:EV_KEY(按键) */
set_bit(EV_REP, my_dev->evbit); /* 设备支持的事件类型:EV_REP(允许重复按键) */
my_dev->keycode = xxx_key_tab; /* 存储按键值的数组的首地址 */
my_dev->keycodesize = sizeof(int); /* 每个键值字节数 */
my_dev->keycodemax = ARRAY_SIZE(xxx_key_tab); /* 支持的最大按键数 */
3. 向核心层注册input_dev。
input_register_device(my_dev);
Note:函数input_register_device
解析如下:
/* drivers/input/input.c */
int input_register_device(struct input_dev *dev)
{
struct atomic_t input_no = ATOMIC_INIT(0);
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error;
if (dev->devres_managed)
{
devres = devres_alloc(devm_input_device_unregister, sizeof(*devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->input = dev;
}
/* 每个input_device都会产生EV_SYN/SYN_REPORT事件(同步事件),所以就放在一起设置 */
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* 没有设置的位,确保被清零 */
input_cleanse_bitmasks(dev);
packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size;
dev->max_vals = dev->hint_events_per_packet + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals)
{
error = -ENOMEM;
goto err_devres_free;
}
/* 如果延时周期是程序预先设定的,那么是由驱动自动处理,主要是为了处理重复按键 */
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
{
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
if (!dev->getkeycode) /* 获取按键值 */
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode) /* 设置按键值 */
dev->setkeycode = input_default_setkeycode;
error = device_add(&dev->dev); /* 将dev注册到sys */
if (error)
goto err_free_vals;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
/* 将新的dev放入链表input_dev_list尾部 */
list_add_tail(&dev->node, &input_dev_list);
/* 遍历链表input_handler_list中所有input_handler,是否支持这个input_dev,若支持,则将两者连接 */
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
if (dev->devres_managed)
{
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n", __func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0;
err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
EXPORT_SYMBOL(input_register_device);
其中,函数input_attach_handler
解析如下:
/* drivers/input/input.c */
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* blacklist是handler该忽略input设备类型 */
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
/* 比较input_dev中的id和handler支持的id,存放在handler中的id_table中 */
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
/* 配对成功调用handler的connect函数,这个函数在事件处理器中定义,主要生成一个input_handle结构,并初始化,还生成一个事件处理器相关的设备结构 */
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
/* 出错处理 */
return error;
}
在向核心层申请、注册完input_dev结构体后,input子系统知道了输入设备的存在,并可以找到该设备对应的事件解决方案input_handler,但此时还不能正常的使用input子系统,因为input设备是输入一些信息,但是linux内核还不清楚输入的信息表示什么意思,有什么作用,所以我们需要驱动硬件获取输入事件,然后将输入事件上报给linux内核。不同的输入设备获取的输入事件不同,不同事件的上报函数也不同,首先来看一下有哪些常用的API函数:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
unsigned long flags;
/* 如果支持evbit类上报 */
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags); /* 获取自旋锁 */
input_handle_event(dev, type, code, value); /* 上报事件 */
spin_unlock_irqrestore(&dev->event_lock, flags);/* 释放锁 */
}
}
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
像input_report_key这样针对具体事件类型的事件上报函数在input子系统中还有很多,它们本质上都是都是调用了input_event函数,函数如下:
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) /* force-feedback(强制反馈)事件上报 */
void input_report_switch(struct input_dev *dev, unsigned int code, int value) /* 开关事件上报 */
void input_mt_sync(struct input_dev *dev) /* 同步多点触摸(multi-touch)事件 */
void input_sync(struct input_dev *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); /* 同步事件 */
}
}
上面讲到了输入事件上报的过程,那么驱动程序将事件上报给input子系统后,应用程序又如何拿到数据呢?过程如下:
dev/input/event0
wait_event_interruptible(evdev->wait, ...)
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev); // 实质也是 input_event
wake_up_interruptible(&evdev->wait);
唤醒中断程序。