分类: LINUX
2014-02-27 18:41:36
INPUT sub_system
Input子系统是linux内核中使用比较频繁的一个模块,因为信息的输入在交互式产品中占据了一个非常重要的地位,在各种各样的产品中如手机的按键和触摸屏,电脑的鼠标键盘等。对于各种类型的不同的输入方式的输入设备,linux设计了一整套的输入事件类型去支持。
Input子系统主要同三个部分组成,input_dev,input_handler,input_handle。
下面首先来看一下在Linux中是如何定义这几个数据结构的。
Input_dev,顾名思义,就是代表一个输入设备,这也出现在输入设备驱动程序当中频率最高的结构之一,它一般作为一个具体设备的一个成员,表示这个设备是一个输入设备,比如在一个触摸屏驱动当中会定义一个触摸屏的结构:
struct goodix_ts_data {
spinlock_t irq_lock;
s32 irq_is_disable;
int retry;
int panel_type;
uint8_t bad_data;
char phys[32];
struct i2c_client *client;
struct input_dev *input_dev;//定义输入设备
uint8_t use_irq;
uint8_t use_shutdown;
……
};
从结构中就可知这是一个输入设备。
struct input_dev {
const char *name;//设备名
const char *phys;
const char *uniq;
struct input_id id;//设备的input_id,ic的总线类型,厂商,设备等信息
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)];//设备支持的内核定义的具体的KEY/BUTTON事件
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)];
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_slot *mt;
int mtsize;
int slot;
int trkid;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反应当前KEY/BUTTON状态
unsigned long led[BITS_TO_LONGS(LED_CNT)];
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);
struct input_handle __rcu *grab;//关联该设备的input_handle
spinlock_t event_lock;//事件锁
struct mutex mutex;
unsigned int users;//打开该设备的用户数量
bool going_away;
bool sync;
struct device dev;//input_dev的设备节点
struct list_head h_list;//与该设备关联的input_handle的链表
struct list_head node;//该节点关联到input_dev_list
};
Input_handle:用于链接input_dev和input_handler,input_handle的成员不多,从成员的定义也可以看出,input_handle存在的意义在于为input_dev和input_handler搭建一条康庄大道,方便input_dev和input_handler勾搭。
struct input_handle {
void *private;//结构的私有数据域
int open;//使用设备的数量
const char *name;//
struct input_dev *dev;//指向所属的input_dev
struct input_handler *handler;//指向所属的input_handler
struct list_head d_node;//链接所属的input_dev的input_handle
struct list_head h_node;//链接所属的input_handler的input_handle
};
Input_handler,与input_handle长的很像,但它们的作用与内容差异就很大了,而它的作用也明确,就是处理输入设备产生数据。
struct input_handler {
void *private;//存储私有数据
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//处理支持的input_dev提供的事件
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);//用于判断input_handler是否支持处理某input_dev
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//建立input_dev与input_handler之间的连接关系
void (*disconnect)(struct input_handle *handle);//断开input_dev与input_handler之间的连接关系
void (*start)(struct input_handle *handle);
const struct file_operations *fops;//设备文件支持的操作集
int minor;//支持的32个设备的从设备号起始量
const char *name;//input_handler名,可从/proc/bus/input/handlers中查看
const struct input_device_id *id_table;//该input_handler支持的input_dev的id集合,在match中用来判断是否支持某input_dev
struct list_head h_list;//用于链接input_handler关联的input_handle
struct list_head node;//将input_handler加入input_handler_list
};
从数据流程的角度来看,input_dev它是一个数据的生产者,它代表着一个具体的硬件设备,并且从硬件设备中取出数据来,然后把这些数据送出来。input_handler是一个数据的消耗者,它负责把input_dev提供的数据处理掉,比如将数据从系统空间运送到用户空间,然后用户空间再有专门的线程来取这些数据,再一个个分发给各个应用进程。Input_handle呢?它不生产也不消耗数据,而在一个系统当中不仅仅存在一个input_dev,也可能不仅仅存在一个input_handler,那么指定哪些input_dev生产出来的数据由哪个input_handler去处理呢,input_handle干的就是这个勾当。由此可以看出,这三者各司其职,相互配合,干的不亦乐乎。下面用一副图来粗略的表现下它们三个之间的关系:
|
|
|
|
从上图可以看出,它们之间的关系并不复杂,但是只要知道了其中一个,就可以找到另外的两个。
下面从具体的代码来分析input子系统的使用,以一个触摸屏驱动为例。
当系统启动检测到一个触摸屏的存在的时候就会去初始化它,在触摸屏驱动的初始化代码当中会分配并且初始化好一个输入设备:
ts->input_dev = input_allocate_device();//为input_dev分配地址空间并初始化其总线类型,结构链表等。
if (ts->input_dev == NULL)
{
ret = -ENOMEM;
dev_dbg(&client->dev,"Failed to allocate input device\n");
goto err_input_dev_alloc_failed;
}
ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;//设置这个输入设置支持的事件类型,有按键和绝对坐标事件类型。
ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);//支持的是一件触摸类型的按键
input_set_capability(ts->input_dev,EV_KEY,KEY_BACK);
input_set_capability(ts->input_dev,EV_KEY,KEY_MENU);//设置支持按键的键值,有返回键和菜单键
#endif
ts->input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);//支持X,Y,按下/弹起状态的绝对坐标类型事件
input_set_abs_params(ts->input_dev, ABS_Y, 0, SCREEN_MAX_HEIGHT, 0, 0);
input_set_abs_params(ts->input_dev, ABS_X, 0, SCREEN_MAX_WIDTH, 0, 0);
input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(ts->input_dev, ABS_MT_TRACKING_ID, 0, 5, 0, 0);//分别定义了绝对坐标,按下/弹起状态,轨迹id事件的数据域。
sprintf(ts->phys, "input/goodix-ts");
ts->input_dev->name = f3x_ts_name;
ts->input_dev->phys = ts->phys;
ts->input_dev->id.bustype = BUS_I2C;
ts->input_dev->id.vendor = 0xDEAD;
ts->input_dev->id.product = 0xBEEF;
ts->input_dev->id.version = 10427;
//初始化input_dev的name,phys,总线类型,以及相关设备的硬件ID。
ret = input_register_device(ts->input_dev);//向系统注册一个input_dev。
if (ret) {
dev_err(&client->dev,"Unable to register %s input device\n", ts->input_dev->name);
goto err_input_register_device_failed;
}
初始化好了之后就向系统注册设备,注册了设备之后就等于告诉系统,我现在可以开始干活了。
来看一下注册input_dev的流程是什么样的吧:
int input_register_device(struct input_dev *dev)
{
……
list_add_tail(&dev->node, &input_dev_list);//将input_dev挂载在input_dev_list上,input_dev_list保存了所有的input_dev。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);// 遍历input_handler_list链表,通过挂载在链表上的node结构找到对应input_handler的首地址,通过结构的某成员获取该结构的首地址是内核当中使用非常频率而又非常有效率的一种用法。然后为input_dev和input_handler建立链接。
因此我们来看一下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);//查看该input_handler是否支持处理该input_dev
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);//如果支持,则为input_handler和input_dev建立链接。
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;
}
这里我们首先要了解一下,linux内核为我们实现了一个支持所有类型的输入设备的input_handler—>evdev,它的具体实现在driver/input/evdev.c文件当中。如果没有特殊的要求,一般情况下我们就使用这个evdev作为我们输入设备的input_handler。
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i;
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;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
由以上代码可知,linux的input子系统通过获取该input_handler的id_table表示其支持的input_dev,该id_table中定义了其支持的input_dev的总线类型bustype,设备厂商vendor,设备制造商product,设备版本号version。通过id_table设置的flags项来设置是判断是否支持的标准是总线类型或者设备vendor/ product/ version,在上面的input_dev的初始化当中有对input_dev的id的这几项进行初始化。而在evdev.c中有对该input_handler id_table的定义:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
其flags为0,而又没有实现match接口,因此所有和它match的input_dev都会成功,match成功之后就会调用input_handler的connect接口:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
……
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;//我们关注的重点是这几行代码,在connect过程中会创建并初始化一个evdev类型的设备,然后在这里初始化evdev的handle,将传递进来的input_dev和input_handler保存在这个input_handle上,因此从此以后就可以由handle找到其链接的input_dev和input_handler了。
……
error = input_register_handle(&evdev->handle);
/*
……
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
list_add_tail_rcu(&handle->h_node, &handler->h_list);
……
在input_register_handle当中,最主要的是上面几行代码,它分别通过handle的d_node和h_node节点将handle保存在dev和handler的h_list上面。由此,我们可以通过input_dev或者input_handler的h_list上挂载的d_node节点找到包含其的input_handle结构,进而找到其对应的input_handler或者input_dev。
*/
if (error)
goto err_free_evdev;
……
}
从这里以后,input_dev,input_handler和input_handle之间就相互连接起来了。