摘要:
- input framework
- input_dev与handler匹配
- input_dev注册
- handler之evdev
- 总结
input framework
framework一般有两个目的:
- 一方面向开发者提供统一的接口(API);
- 另一方面是向实现功能的模块提供接口,将功能挂接框架里。最终实现通过API来使用系统功能。
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框架,总的来说有三个大接口:- 第一个,向设备提供device(struct input_dev),以及注册函数input_register_device(),同时还有些对input_dev结构体变量操作的函数(后面会单独描述)。
- 第二个,将应用层接口挂入到input框架里面的handler,提供了注册handler的input_register_handler()函数,同时也提供了input device和handler的联合剂handle,以及handle的注册函数input_register_handle(),这个注册函数一般在connect里面调用,当然也可以在其他地方调用来注册,只要时机选择正确。handler的connect(一个开发者自己实现的函数)创建应用层接口,event(开发者自己实现的接口)负责处理上报。
- 第三个,提供了input_event()上报函数,在注册设备的device有数据时,需要此函数来上报。当然,input将input_event封装出了其他接口函数,使用更方便,下面会单独描述。
input_dev和handler匹配
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_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); }
handler之evdev:
了解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处理过程:
- 1、以驱动模块加载的形式,调用input_register_handler()注册handler到input框架里面。
- 2、当有input_dev注册到input框架时,与evdev_ids匹配(基本上都能匹配成功)。
- 3、匹配成功后evdev_connect()函数被调用,这个函数创建了/dev/input/event*文件节点(上层用用程序接口)。创建的字符文件绑定的file_operations如下:
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, };
- 4、当上层应用软件需要这个input_dev提供的数据时,首先需要open下/dev/input/event*,获取文件句柄,只有这样open下后,evdev才会为其分配struct evdev_client类型的结构体变量内存空间(client将成为file->private_data = client)。
- 5、应用程序,可以直接调用evdev_read读取,如果此时input_dev没有数据将会阻塞在这里,等有数据时将解除阻塞返回;也可以用evdev_poll先探测有无数据(参考linux的poll),有数据再调用evdev_read来读取;也可以用evdev_fasync异步方法获取(参考linux的异步机制fasync)。
- 6、当input_dev有数据,调用input_event()上报时,evdev_events()或者evdev_event()将被调用,具体是那个暂时还每弄清楚,功能都一样,此时会解除evdev_read的阻塞,以及evdev_poll,采用fasync的,应用程序将接收到kill_fasync()发出的通知。
- 7、一些特殊信息,控制或者获取,通过evdev_ioctl来设置或获取,如触摸屏一些信息就是通过ioctl方式,在evdev_ioctl里面会调用到的部分代码如下:
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三种方式的其中一种方式获取数据。