您的位置:首页 > 运维架构 > Linux

Linux2.6 input子系统分析

2010-10-26 22:29 309 查看
Linux的input子系统提供了输入设备的驱动框架,比如鼠标、键盘、触摸屏等就属于输入设备。Linux中关于input子系统的文档在Documentation/input目录,input的核心代码在input.c和input.h中。
本文没有涉及input的一些细节实现,比如input_dev->grab,以及按键的定时事件等。

1.
input_handle, input_handler, input_dev

input_handle, input_handler, input_dev是input子系统中最重要的3个数据结构。
l
input_handler用于上层应用获取输入事件。上层应用打开输入设备的设备节点,然后对节点进行读写操作以获得鼠标移动信息,或者键盘信息等等。这里对设备节点的文件操作函数就是由input_handler提供。
l
input_dev代表的是具体的设备,比如鼠标、键盘等等。
l
对于一台Linux电脑,可能会连着多个鼠标、多个键盘。每一个鼠标都能控制光标的运动,每一个键盘也都能正常使用。这在input子系统中,体现为一个input_handler关联多个input_dev,能够同时从多个input_dev获取输入消息。与此同时,linux中可能会有多个input_handler同时与一个input_dev关联,这样,应用程序通过任何一个input_handler,都可以获得例如鼠标、键盘等具体设备的输入信息。所以,input_dev和input_handler之间是多对多的关联关系,而这些关联就是由input_handle表示。

1.1
handler与dev之间关联的建立

input_handle中包含一个input_dev的指针,和一个input_handler的指针,所以能建立handler和dev之间的一个一对一的关联。在input_handler中,有一个链表h_list,指向和这个handler关联的所有input_handle,通过这些handle就可以找到与handler关联的所有dev。同样的,在input_dev中,也有一个链表h_list,指向与dev关联的所有input_handle,通过这些handle可以找到与dev相关的所有handler。通过这两个链表和input_handle,input_handler和input_dev之间建立了一个复杂的网状结构。
那么,input_handler和input_dev之间建立关联的规则是什么?即在什么情况下需要建立关联,什么时候不需要建立关联?这就需要handler和dev之间有一个匹配机制。
input_handler中有两个指针,id_table和blacklist,其中blacklist是黑名单,凡是与之匹配的dev都将被强制过滤;而与id_table中任意一项匹配的dev才能与handler建立关联。

const struct input_device_id *id_table;

const struct input_device_id *blacklist;

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中包含一些掩码:
#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
这些掩码用来匹配bus类型、厂商、产品号、版本号等。如果选择不匹配这些信息,那么driver_info需要置位,以跳过flags的匹配过程。
evbit[INPUT_DEVICE_ID_EV_MAX /
BITS_PER_LONG + 1]中包含支持的事件掩码。input子系统支持以下事件:
#define EV_SYN
0x00 // EV_SYN用于标记一系列输入的结束
#define EV_KEY
0x01 // 键盘输入
#define EV_REL
0x02 // 鼠标的相对位移
#define EV_ABS
0x03 // 触摸屏的绝对坐标
#define EV_MSC
0x04
#define EV_SW
0x05
#define EV_LED
0x11 // 比如键盘的LED控制
#define EV_SND
0x12
#define EV_REP
0x14
#define EV_FF
0x15
#define EV_PWR
0x16
#define EV_FF_STATUS
0x17
如果handler支持某一种事件,那么evbit的对应位会置位。如果evbit的某一位置位了,那么结构体中与具体事件对应的掩码数组就会有效。例如,EV_KEY置位了,那么对应的keybit数组就有效,该数组里面定义了handler支持的具体key的类型。如果handler和dev需要匹配,那么dev必须能支持所有handler支持的事件;但是,handler却不一定要处理所有dev能提供的事件。
#define MATCH_BIT(bit, max) /

for (i = 0; i < BITS_TO_LONGS(max); i++) /

if ((id->bit[i] & dev->bit[i]) != id->bit[i]) /

break; /

if (i != BITS_TO_LONGS(max)) /

continue;
关于匹配的具体过程,可以参考函数input_match_device。

2.
input_dev

所有的input_dev都是虚拟设备,其class名为”input”,class的登记函数:

class_register(&input_class);
所以,需要先注册一个物理设备,已此物理设备为父设备注册input_dev。

2.1
input_dev的注册

input_dev的注册由input_register_device函数完成。
int input_register_device(struct input_dev *dev)

__set_bit(EV_SYN, dev->evbit); // 强制设置EV_SYN位

// 初始化定时器,REP_DELAY是第一次延时的超时值,REP_PERIOD是第一次延时
// 之后的超时值

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;

}

// 设置默认的get key和set
key函数

if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode;

if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode;

// 设置设备名称,然后注册设备

snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id),

"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

device_add(&dev->dev);

list_add_tail(&dev->node, &input_dev_list); //
将设备加入input_dev_list链表

// 对每一个已注册的handler,调用input_attach_handler

list_for_each_entry(handler, &input_handler_list, node)

input_attach_handler(dev, handler); // 见下节

// proc有关的操作

input_wakeup_procfs_readers();

3
input_handler

struct input_handler {

void *private;

void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

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);

const struct file_operations *fops;

int minor;

const char *name;

const struct input_device_id *id_table;

const struct input_device_id *blacklist;

struct list_head h_list;

struct list_head node;
};

3.1
input_handler的注册

int input_register_handler(struct input_handler *handler)

// 将fops域登记到全局变量input_table

input_table[handler->minor >> 5] = handler;

// 将handler登记到链表input_handler_list

list_add_tail(&handler->node, &input_handler_list);

// 遍历已经登记的dev,对每个dev调用input_attach_handler(chapter
2.2)

list_for_each_entry(dev, &input_dev_list, node)

input_attach_handler(dev, handler);

// proc有关的操作

input_wakeup_procfs_readers();

3.2
input_attach_handler

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

// 匹配黑名单,如果设备在handler的黑名单里,返回



// 匹配handler中的id_table,如果匹配,则需要建立handler与dev之间的关联,于
// 是调用handler->connect
handler的connect函数中,会malloc一个handle,把handler和dev填到handle中对应的域,然后调用input_register_handle函数,以及input_open_file函数。

4.
input_handle

struct input_handle {

void *private;

int open;

const char *name;
// handle的名称

struct input_dev *dev; // 关联的dev

struct input_handler *handler; // 关联的handler

struct list_head d_node;
// 通过这个域挂到dev的handle链表

struct list_head h_node;
// 通过这个域挂到handler的handle链表
};

4.1
input_handle的注册

int input_register_handle(struct input_handle *handle);
input_register_handle会在handler的connect函数中被调用,它会将handler挂到dev和handler的链表中。最后会调用handler->start函数。

5.
设备层

上层应用通过设备节点访问input子系统提供的服务,而input_handler为上层应用提供了接口,所以设备节点的文件访问函数理所当然由handler提供,这就是handler中的fops域。

input子系统本身也提供了一组文件读写操作函数,input_fops。

register_chrdev(INPUT_MAJOR, "input", &input_fops);
这里的input_fops中仅包含open函数,open函数的作用是根据子设备号重新设置设备文件的file->f_op域(这个机制在LDD的书中有提及),被更新的fops保存在全局数组input_table中。

在注册handler时,会将handler登记到一个全局数组input_table,具体代码在input_register_handler中:

input_table[handler->minor >> 5] = handler;
这个数组主要是保存了handler中的fops。另外,数组的下标是handler->minor
>> 5,也就是说,一个handler可以占有32个子设备号(1
<< 5)。

6.
事件的传递

input子系统的主要作用就是控制输入事件在handler和dev之间的传递。输入事件在input系统中的定义如下:
struct input_event {

struct timeval time;

__u16 type;

__u16 code;

__s32 value;
};
input_dev可以通过函数input_event传递消息。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

// 加锁、消息检验等



input_handle_event(dev, type, code, value);

void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

判断消息是否需要传给handler和dev

如果需要传给dev,调用dev->event(dev,
type, code, value);

如果需要传给handler,调用input_pass_event(dev,
type, code, value);

input_pass_event遍历dev中所有handle列表,对每一个handle,调用event函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: