framework一般有两个目的:
linux input驱动框架原理:
input以input_dev_list和input_handler_list为核心的两个链表:
驱动开发者通过input_register_device()将struct input_dev(一般每个设备会以这个类型的结构体变量注册到input框架里)核心结构体类型的变量挂入input_dev_list链表中。
通过input_register_handler()将struct input_handler结构体变量挂入到input_handler_list链表中。
input的input_dev_list和input_handler_list两个链表定义在drivers/input/input.c里面:
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);
我们将input_dev结构体类型的变量叫着input device,将input_handler叫着input handler。可以理解成device是面向设备,将输入设备抽象成input_dev结构体注册到input框架里面;而handler是面向上层应用提供接口,每个handler提供的是一种类型接口,不同handler提供不同的接口(linux一般默认的都是evdev(一个handler),在drivers/input/evdev.c)。
调用input_register_device()注册device到input框架时,在input_register_device()函数里面按照匹配规则(下面会单独讲述匹配规则)将本次要注册的device和链表input_handler_list的handler一一匹配,如果匹配成功,会调用handler里面的connect指向的函数,在connect调用中会向上层应用创建接口(一般都是字符设备接口,evdev创建的是字符设备,有了字符设备上层就可以通过文件节点的形式访问了)
input_register_device()函数里面匹配的部分的代码片段:
ist_add_tail(&dev->node, &input_dev_list);//device加入到链表
//匹配handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_attach_handler()调用匹配函数input_match_device(),如匹配成功就调用connect。input_attach_handler()函数定义如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
handler也是可以自定义,通过input_register_handler()将自己定义的handler注册到input框架里面(evdev就是很好的一个例子,evdev是linux默认的handler,一般我们都不自己定义handler,都是用evdev,默认匹配规则,基本上都能匹配上evdev),handler里面是带匹配函数的:
bool (*match)(struct input_handler *handler, struct input_dev *dev);
在调用input_register_handler()注册时,同样也会与input_dev_list链表里面的每一个device匹配,优先使用handler自带的匹配函数(match),如果handler的match为NULL,则默认使用input里面自带的匹配规则匹配。input_register_handler()函数里面的代码片段:
INIT_LIST_HEAD(&handler->h_list);
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
device和handler如果匹配成功,会调用handler里面的connect,connect负责向上创建接口,同时connect还需要向input框架里面(调用input_register_handle()函数)注册handle(注意与handler是不同的),evdev的connect代码片段:
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
从上面的代码,evdev的connect注册了handle(input device和handler匹配成功后,需要建立一个handle来将device和handler连接起来,当调用input_event上报时,就可以从device找到handler(调用过程input_event()->input_handle_event()->input_pass_values(),在input_pass_values里面会通过handle找到handler),再通过handler的event处理上报的输入值到上层),也创建了字符设备(字符设备,就是以文件节点的形式向上层提供接口)
当input_register_dev()注册的输入设备(input_dev)有输入值时,需要调用input框架提供的input_event()向上层上报输入值,输入值上报是通过handler的event上报,输入值类型:
struct input_value {
__u16 type;
__u16 code;
__s32 value;
};
input_event()函数定义,如下:
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
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);
}
}
其中函数调用关系:
input_event()->input_handle_event()->input_pass_values()->input_to_handler()
input_pass_values()函数定义,如下:
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;
if (!count)
return;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count);
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
count = input_to_handler(handle, vals, count);
}
rcu_read_unlock();
add_input_randomness(vals->type, vals->code, vals->value);
/* trigger auto repeat for key events */
for (v = vals; v != vals + count; v++) {
if (v->type == EV_KEY && v->value != 2) {
if (v->value)
input_start_autorepeat(dev, v->code);
else
input_stop_autorepeat(dev);
}
}
}
从这可以到从input_dev找到handle(连接input device 和handler的结构)。一般情况下,是执行的以下部分code:
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
count = input_to_handler(handle, vals, count);
在函数input_to_handler()里面,通过handle找到handler,函数定义:
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v;
for (v = vals; v != vals + count; v++) {
if (handler->filter &&
handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
}
count = end - vals;
if (!count)
return 0;
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != end; v++)
handler->event(handle, v->type, v->code, v->value);
return count;
}
调用过程看得出,handle是input device和handler的联合剂作用。也可以看到调用了handler的event,最终input device输入值到上层是通过handler的event处理。
框架原理总结:
input框架,总的来说有三个大接口:
input提供的匹配函数input_match_device()定义如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
/*输入设备所属总线类型检测*/
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
/*输入设备供应商检测*/
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
/*输入设备厂商检测*/
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
/*输入设备版本检测*/
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
/*输入设备支持的事件类型检测*/
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
/*输入设备支持按键类型具体按键的支持检测*/
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
/*输入设备支持相对坐标事件类型检测*/
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
/*输入设备支持绝对坐标事件类型检测*/
if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue;
if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
continue;
if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
continue;
if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
continue;
if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
continue;
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
可以看到handler->id_table提供了一个handler支持的设备表struct input_device_id:
struct input_device_id {
kernel_ulong_t flags;
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
kernel_ulong_t driver_info;
};
第一部份,flags代表是否需要检测设备四个属性:bustype(输入设备所属总线类型),vendor(输入设备供应商),product(输入设备厂商),version(设备的版本)。如果提供的handler需要检测这些属性,需要将flags的相应位置位,如果需要检测的属性与handler的值不匹配,则不能连接此handler来处理输入的值。位定义如下(include/linux/mod_devicetable.h):
#define INPUT_DEVICE_ID_MATCH_BUS 1
#define INPUT_DEVICE_ID_MATCH_VENDOR 2
#define INPUT_DEVICE_ID_MATCH_PRODUCT 4
#define INPUT_DEVICE_ID_MATCH_VERSION 8
另外mod_devicetable.h在里面还有些其他标志位,但目前作用不是很清楚,估计修改input_match_device()函数,可以用,是猜的。
第二部分,关于输入值事件类型匹配:
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
bitmap_subset(id->evbit, dev->evbit, EV_MAX)其中id->evbit是handler支持的事件(支持的事件相应位是1),dev->evbit输入设备支持的事件,同样,支持的位是1,EV_MAX表示支持最多事件,bitmap_subset()表示,如果id->evbit为1的位,是dev->evbit为1的位的子集(换句话说dev->evbit为1的位包含id->evbit为1的位),则返回1,否则返回0。以上代码表示的就是,handler支持的事件,input device必须全部支持,否则无法和handler匹配,而input device支持的事件,handler不一定支持(handler的id支持的事件id->evbit,id->absbit,id->keybit等全部位,为0,表示全部支持,就看device了)。另外,需要明白的是,evbit是事件类型,而像keybit,relbit,absbit等是具体某一类事件,而且一类事件又包含了很多具体这类事件下的具体事件编号,如下结构体理解:
struct input_value {
__u16 type;//事件类型
__u16 code;//具体事件类型,某个动作编码
__s32 value;//该编号动作下的值
};
type在linux下为下列宏,或者多个宏的或(include/uapi/linux/input.h):
/*
* Event types
*/
#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
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
code就要看type支持的事件,当然也可以支持一类或者多类,如EV_KEY按键事件,只列出部分,按键比较多:
/*
* Keys and buttons
*
* Most of the keys/buttons are modeled after USB HUT 1.12
* (see http://www.usb.org/developers/hidpage).
* Abbreviations in the comments:
* AC - Application Control
* AL - Application Launch Button
* SC - System Control
*/
#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 KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
#define KEY_TAB 15
#define KEY_Q 16
#define KEY_W 17
#define KEY_E 18
#define KEY_R 19
#define KEY_T 20
#define KEY_Y 21
#define KEY_U 22
value值就看具体设备了,对于按键设备来说是1或者0,表示按键按下或抬起,触摸屏value就是坐标值,code就是代表某个坐标轴。
其它具体事件,具体看,都定义在include/uapi/linux/input.h。
其实struct input_value驱动里面并不定义,而是送到上层应用软件去解析值的意义,上面描述的按键,触摸屏都已经公认的,驱动跟着上层软件解析的意义去描述,比较清晰,直观。应用软件完全可以把一个触摸屏的坐标值解析成一个按键,从而产生一个按键的动作。
第三部分,如下代码:
if (!handler->match || handler->match(handler, dev))
return id;
当input框架定义的,以上flags和事件类型,具体支持的事件,都匹配通过后。handler没有另外增加匹配规则,就直接匹配通过返回支持的id table,否则,再按照handler的匹配规则匹配。
第四部分,id->driver_info,其实个人感觉不需要对flags检测(id->flags为0)时,要保证id->driver_info为非零,才会启动匹配过程,否则将返回NULL。evdev的handler就是不检查flags,如下(drivers/input/evdev.c):
static const struct input_device_id evdev_ids[] = {
{
.driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
第五部分,input_match_device()返回值,匹配成功返回id table,不成功返回NULL。
准确说,input_dev注册,除去input框架公共部分,是需要按照handler来注册,但是,在我们用input框架时,很多情况都是默认handler是evdev,因此这里,也按照handler是evdev,来描述。
第一步,分配input_dev,可以定义input_dev类型结构体全局变量(这不单独描述),也可以用input框架提供的分配函数input_allocate_device(),声明在include/linux/input.h:
struct input_dev *da;
da = input_allocate_device();
第二部,设置input支持的事件类型,handler是evdev,都必须支持EV_SYN同步事件(原因在单独描述evdev时说明),其他事件,根据输入设备来设置,这里假定输入设备是同时支持按键和触摸屏事件:
da->evbit = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
也可以写成
da->evbit = EV_SYN | EV_KEY | BEV_ABS;
还可以写成
__set_bit(EV_SYN,da->evbit);
__set_bit(EV_KEY,da->evbit);
__set_bit(BEV_ABS,da->evbit);
或者
set_bit(EV_SYN,da->evbit);
set_bit(EV_KEY,da->evbit);
set_bit(BEV_ABS,da->evbit);
都是将da->evbit相应位置1,BIT_MASK,__set_bit(非原子性),set_bit(原子性的)可以在include/linux/bitops.h,include/asm-generic/bitops/non-bitops.h,include/linux/asm-generic/bitops/atomic.h。一般用set_bit比较好,有原子性。
第三部分,具体事件设置,这里用按键和触摸屏举例
按键, 支持那个按键,一般用:
input_set_capability(da->input_dev, EV_KEY, KEY_1);
表示支持1按键。
触摸屏,对触摸屏属性的设置,一般用:
void input_set_abs_params(struct input_dev *dev, unsigned int axis,
int min, int max, int fuzz, int flat)
axis:表示那个轴,X轴,还是Y轴,Z轴
min:表示轴上输入的坐标,最小值
max:表示轴上输入的坐标,最大值
fuzz,flat:暂时还不清楚,一般都设置成0
//表示触摸屏X轴,最小值是0,最大是1000
input_set_abs_params(da->input_dev, ABS_MT_POSITION_X, 0, 1000, 0, 0);
//表示触摸屏Y轴,最小值是0,最大是3000
input_set_abs_params(da->input_dev, ABS_MT_POSITION_Y, 0, 3000, 0, 0);
//表示触摸屏,触点面积(一般假定位椭圆),短轴最小值0,最大255
input_set_abs_params(da->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
//表示触摸屏,触点面积(一般假定位椭圆),长轴最小值0,最大255
input_set_abs_params(da->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
//表示触摸屏,触摸滑动时轨迹ID,最下值0,最大255
input_set_abs_params(da->input_dev, ABS_MT_TRACKING_ID, 0, 255, 0, 0);
其实,在关于input框架,我们不用关注具体事件表示意思,我们只关注struct input_value结构三个值,具体意思,由应用程序根据具体设备解析意义。
第四部分,设定input_dev的名子name,以及flags
da->input_dev->name = "daname";
关于如下,如果handler是evdev,是不需要的:
da->input_dev->id.bustype = BUS_I2C;//
da->input_dev->id.vendor = 0xDEAD;//
da->input_dev->id.product = 0xBEEF;//
da->input_dev->id.version = 10427;//
要检查这些属性还需设置flags标志位。
第五部,也是最后一部,注册:
int ret = 0;
ret = input_register_device(da->input_dev);
if (ret)
{
//注册失败
input_free_device(da);
}
了解evdev先看如下代码(drivers/input/evdev.c):
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
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,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}
evdev处理过程:
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,
};
if (_IOC_DIR(cmd) == _IOC_READ) {
if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))
return handle_eviocgbit(dev,
_IOC_NR(cmd) & EV_MAX, size,
p, compat_mode);
if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
if (!dev->absinfo)
return -EINVAL;
t = _IOC_NR(cmd) & ABS_MAX;
abs = dev->absinfo[t];
if (copy_to_user(p, &abs, min_t(size_t,
size, sizeof(struct input_absinfo))))
return -EFAULT;
return 0;
}
}
if (_IOC_DIR(cmd) == _IOC_WRITE) {
if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
if (!dev->absinfo)
return -EINVAL;
t = _IOC_NR(cmd) & ABS_MAX;
if (copy_from_user(&abs, p, min_t(size_t,
size, sizeof(struct input_absinfo))))
return -EFAULT;
if (size < sizeof(struct input_absinfo))
abs.resolution = 0;
/* We can't change number of reserved MT slots */
if (t == ABS_MT_SLOT)
return -EINVAL;
/*
* Take event lock to ensure that we are not
* changing device parameters in the middle
* of event.
*/
spin_lock_irq(&dev->event_lock);
dev->absinfo[t] = abs;
spin_unlock_irq(&dev->event_lock);
return 0;
}
}
absinfo像这些,就是触摸屏信息:
struct input_absinfo {
__s32 value;//那个坐标轴,X,Y等
__s32 minimum;//对应坐标轴的最小值
__s32 maximum;//对应坐标轴的最大值
__s32 fuzz;
__s32 flat;
__s32 resolution;
};
input device和input handler的注册到input框架时,进行匹配,如匹配成功,由handler的connect创建应用成学接口,同时用handle将input device和input handler连接起来。
设备驱动,有数据时,通过input_event()上报数据到匹配的handler。
应用程序,首先要open创建的文件节点(应用程序接口),然后,才能通过read,poll,fasync三种方式的其中一种方式获取数据。