本文是摘自《Linux驱动开发入门与实战》第17章输入子系统设计的内容,在这里备份一下,以后查找方便。
第17章 输入子系统设计
本章将介绍Linux输入子系统的驱动开发。Linux的输入子系统不仅支持鼠标、键盘等常规输入设备,而且还支持蜂鸣器、触摸屏等设备。本章将对Linux输入子系统进行详细的分析。
17.1 input子系统入门
输入子系统又叫input子系统。其构建非常灵活,只需要调用一些简单的函数,就可以将一个输入设备的功能呈现给应用程序。本节将从一个实例开始,介绍编写输入子系统驱动程序的方法。17.1.1 简单的实例
本节将讲述一个简单的输入设备驱动实例。这个输入设备只有一个按键,按键被连接到一条中断线上,当按键被按下时,将产生一个中断,内核将检测到这个中断,并对其进行处理。该实例的代码如下:
02 #include <asm/io.h>
03 static struct input_dev *button_dev; /*输入设备结构体*/
04 static irqreturn_t button_interrupt(int irq, void *dummy) /*中断处理函数*/
05 {
06 input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
/*向输入子系统报告产生按键事件*/
07 input_sync(button_dev); /*通知接收者,一个报告发送完毕*/
08 return IRQ_HANDLED;
09 }
10 static int __init button_init(void) /*加载函数*/
11 {
12 int error;
13 if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申请中断处理函数*/
14 {
15 /*申请失败,则打印出错信息*/
16 printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_
irq);
17 return -EBUSY;
18 }
19 button_dev = input_allocate_device(); /*分配一个设备结构体*/
20 if (!button_dev) /*判断分配是否成功*/
21 {
22 printk(KERN_ERR "button.c: Not enough memory\n");
23 error = -ENOMEM;
24 goto err_free_irq;
25 }
26 button_dev->evbit[0] = BIT_MASK(EV_KEY); /*设置按键信息*/
27 button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
28 error = input_register_device(button_dev); /*注册一个输入设备*/
29 if (error)
30 {
31 printk(KERN_ERR "button.c: Failed to register device\n");
32 goto err_free_dev;
33 }
34 return 0;
35 err_free_dev: /*以下是错误处理*/
36 input_free_device(button_dev);
37 err_free_irq:
38 free_irq(BUTTON_IRQ, button_interrupt);
39 return error;
40 }
41 static void __exit button_exit(void) /*卸载函数*/
42 {
43 input_unregister_device(button_dev); /*注销按键设备*/
44 free_irq(BUTTON_IRQ, button_interrupt); /*释放按键占用的中断线*/
45 }
46 module_init(button_init);
47 module_exit(button_exit);
这个实例程序代码比较简单,在初始化函数button_init()中注册了一个中断处理函数,然后调用 input_allocate_device()函数分配了一个input_dev结构体,并调用input_register_device()函数对 其进行了注册。在中断处理函数button_interrupt()中,实例将接收到的按键信息上报给input子系统。从而通过input子系统,向用 户态程序提供按键输入信息。
本实例采用了中断方式,除了中断相关的代码外,实例中包含了一些input子系统提供的函数,现对其中一些重要的函数进行分析。
第19行的input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化。驱动开发人员为了更深入的了解input子系统,应该对其代码有一点的认识,该函数的代码如下:
{
struct input_dev *dev;
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
/*分配一个input_dev结构体,并初始化为0*/
if (dev) {
dev->dev.type = &input_dev_type; /*初始化设备的类型*/
dev->dev.class = &input_class; /*设置为输入设备类*/
device_initialize(&dev->dev); /*初始化device结构*/
mutex_init(&dev->mutex); /*初始化互斥锁*/
spin_lock_init(&dev->event_lock); /*初始化事件自旋锁*/
INIT_LIST_HEAD(&dev->h_list); /*初始化链表*/
INIT_LIST_HEAD(&dev->node); /*初始化链表*/
__module_get(THIS_MODULE); /*模块引用技术加1*/
}
return dev;
}
17.1.2 注册函数input_register_device()(1)
button_init()函数中的28行调用了input_register_device()函数注册输入设备结构体。 input_register_device()函数是输入子系统核心(input core)提供的函数。该函数将input_dev结构体注册到输入子系统核心中,input_dev结构体必须由前面讲的 input_allocate_device()函数来分配。input_register_device()函数如果注册失败,必须调用 input_free_device()函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用 input_unregister_device()函数来注销输入设备结构体。
1.input_register_device()函数
input_register_device()函数的代码如下:
02 {
03 static atomic_t input_no = ATOMIC_INIT(0);
04 struct input_handler *handler;
05 const char *path;
06 int error;
07 __set_bit(EV_SYN, dev->evbit);
08 init_timer(&dev->timer);
09 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
10 dev->timer.data = (long) dev;
11 dev->timer.function = input_repeat_key;
12 dev->rep[REP_DELAY] = 250;
13 dev->rep[REP_PERIOD] = 33;
14 }
15 if (!dev->getkeycode)
16 dev->getkeycode = input_default_getkeycode;
17 if (!dev->setkeycode)
18 dev->setkeycode = input_default_setkeycode;
19 dev_set_name(&dev->dev, "input%ld",
20 (unsigned long) atomic_inc_return(&input_no) - 1);
21 error = device_add(&dev->dev);
22 if (error)
23 return error;
24 path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
25 printk(KERN_INFO "input: %s as %s\n",
26 dev->name ? dev->name : "Unspecified device", path ? path:"N/A");
27 kfree(path);
28 error = mutex_lock_interruptible(&input_mutex);
29 if (error) {
30 device_del(&dev->dev);
31 return error;
32 }
33 list_add_tail(&dev->node, &input_dev_list);
34 list_for_each_entry(handler, &input_handler_list, node)
35 input_attach_handler(dev, handler);
36 input_wakeup_procfs_readers();
37 mutex_unlock(&input_mutex);
38 return 0;
39 }
下面对该函数的主要代码进行分析。
第03~06行,定义了一些函数中将要用到的局部变量。
第07行,调用__set_bit()函数设置input_dev所支持的事件类型。事件类型由input_dev的evbit成员来表示,在这里将其EV_SYN置位,表示设备支持所有的事件。注意,一个设备可以支持一种或者多种事件类型。常用的事件类型如下:
#define EV_KEY 0x01 /*键盘或者按键,表示一个键码*/
#define EV_REL 0x02 /*鼠标设备,表示一个相对的光标位置结果*/
#define EV_ABS 0x03 /*手写板产生的值,其是一个绝对整数值*/
#define EV_MSC 0x04 /*其他类型*/
#define EV_LED 0x11 /*LED灯设备*/
#define EV_SND 0x12 /*蜂鸣器,输入声音*/
#define EV_REP 0x14 /*允许重复按键类型*/
#define EV_PWR 0x16 /*电源管理事件*/
第08行,初始化一个timer定时器,这个定时器是为处理重复击键而定义的。
第09~14行,如果dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]没有设值,则将其赋默认值,这主要是为自动处理重复按键定义的。
第15~18行,检查getkeycode()函数和setkeycode()函数是否被定义,如果没定义,则使用默认的处理函数,这两个函数为 input_default_getkeycode()和input_default_setkeycode()。 input_default_getkeycode()函数用来得到指定位置的键值。input_default_setkeycode()函数用来设置 键值。
第19行,设置input_dev中的device的名字,名字以input0、input1、input2、input3、input4等的形式出现在sysfs文件系统中。
第21行,使用device_add()函数将input_dev包含的device结构注册到Linux设备模型中,并可以在sysfs文件系统中表现出来。
第24~27行,打印设备的路径,输出调试信息。
第33行,调用list_add_tail()函数将input_dev加入input_dev_list链表中,input_dev_list链表中包含了系统中所有的input_dev设备。
第34~35行,调用了input_attach_handler()函数,该函数将在下面单独解释。
17.1.2 注册函数input_register_device()(2)
2.input_attach_handler()函数
input_attach_handler()函数用来匹配input_dev和handler,只有匹配成功,才能进行下一步的关联操作。input_attach_handler()函数的代码如下:
struct input_handler *handler)
02 {
03 const struct input_device_id *id; /*输入设备的指针*/
04 int error;
05 if (handler->blacklist && input_match_device(handler->blacklist,dev))
06 return -ENODEV; /*设备和处理函数之间的匹配*/
07 id = input_match_device(handler->id_table, dev);
08 if (!id)
09 return -ENODEV;
10 error = handler->connect(handler, dev, id);/*连接设备和处理函数*/
11 if (error && error != -ENODEV)
12 printk(KERN_ERR
13 "input: failed to attach handler %s to device %s, "
14 "error: %d\n",
15 handler->name, kobject_name(&dev->dev.kobj), error);
16 return error;
17 }
下面对该函数进行简要的分析。
第03行,定义了一个input_device_id的指针。该结构体表示设备的标识,标识中存储了设备的信息,其定义如下:
kernel_ulong_t flags; /*标志信息*/
__u16 bustype; /*总线类型*/
__u16 vendor; /*制造商ID*/
__u16 product; /*产品ID*/
__u16 version; /*版本号*/
...
kernel_ulong_t driver_info; /*驱动额外的信息*/
};
第05行,首先判断handle的blacklist是否被赋值,如果被赋值,则匹配blacklist中的数据跟dev->id的数据是否 匹配。blacklist是一个input_device_id*的类型,其指向input_device_ids的一个表,这个表中存放了驱动程序应该 忽略的设备。即使在id_table中找到支持的项,也应该忽略这种设备。
第07~09行,调用input_match_device()函数匹配handle->>id_table和dev->id中 的数据。如果不成功则返回。handle->id_table也是一个input_device_id类型的指针,其表示驱动支持的设备列表。
第10行,如果匹配成功,则调用handler->connect()函数将handler与input_dev连接起来。
3.input_match_device ()函数
input_match_device ()函数用来与input_dev和handler进行匹配。handler的id_table表中定义了其支持的input_dev设备。该函数的代码如下:
input_device_id *id,
02 struct input_dev *dev)
03 {
04 int i;
05 for (; id->flags || id->driver_info; id++) {
06 if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
07 if (id->bustype != dev->id.bustype)
08 continue;
09 if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
10 if (id->vendor != dev->id.vendor)
11 continue;
12 if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
13 if (id->product != dev->id.product)
14 continue;
15 if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
16 if (id->version != dev->id.version)
17 continue;
18 MATCH_BIT(evbit, EV_MAX);
19 MATCH_BIT(keybit, KEY_MAX);
20 MATCH_BIT(relbit, REL_MAX);
21 MATCH_BIT(absbit, ABS_MAX);
22 MATCH_BIT(mscbit, MSC_MAX);
23 MATCH_BIT(ledbit, LED_MAX);
24 MATCH_BIT(sndbit, SND_MAX);
25 MATCH_BIT(ffbit, FF_MAX);
26 MATCH_BIT(swbit, SW_MAX);
27 return id;
28 }
29 return NULL;
30 }
下面对该函数进行简要的解释。
第04行声明一个局部变量i,用于循环。
第05行,是一个for循环,用来匹配id和dev->id中的信息,只要有一项相同则返回。
第06~08行,用来匹配总线类型。id->flags中定义了要匹配的项,其中INPUT_DEVICE_ID_MATCH_BUS如果没有设置,则比较input device和input handler的总线类型。
第09~11行,匹配设备厂商的信息。
第12~14行,分别匹配设备号的信息。
第18~26行,使用MATCH_BIT匹配项。如果id->flags定义的类型匹配成功,或者id->flags没有定义,才会进入到MATCH_BIT的匹配项。MATCH_BIT宏的定义如下:
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;
从MATCH_BIT宏的定义可以看出。只有当iput device和input handler的ID成员在evbit、keybit、… swbit项相同才会匹配成功。而且匹配的顺序是从evbit、keybit到swbit。只要有一项不同,就会循环到ID中的下一项进行比较。
简而言之,注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list。与挂载在input_handler_list中的handler相匹配。如果匹配成功,就会调用handler的connect函数。
17.1.3 向子系统报告事件(1)
在17.1.1节button_interrupt()函数的06行,调用了input_report_key()函数向输入子系统报告发生的事 件,这里就是一个按键事件。在button_interrupt()中断函数中,不需要考虑重复按键的重复点击情 况,input_report_key()函数会自动检查这个问题,并报告一次事件给输入子系统。该函数的代码如下:
unsigned int code, int value)
02 {
03 input_event(dev, EV_KEY, code, !!value);
04 }
该函数的第1个参数是产生事件的输入设备,第2个参数是产生的事件,第3个参数是事件的值。需要注意的是,第2个参数可以取类似BTN_0、 BTN_1、BTN_LEFT、BTN_RIGHT等值,这些键值被定义在include/linux/input.h文件中。当第2个参数为按键时,第 3个参数表示按键的状态,value值为0表示按键释放,非0表示按键按下。
1.input_report_key()函数
在input_report_key()函数中正在起作用的函数是input_event()函数,该函数用来向输入子系统报告输入设备产生的事件,这个函数非常重要,它的代码如下:
02 unsigned int type, unsigned int code, int value)
03 {
04 unsigned long flags;
05 if (is_event_supported(type, dev->evbit, EV_MAX)) {
06 spin_lock_irqsave(&dev->event_lock, flags);
07 add_input_randomness(type, code, value);
08 input_handle_event(dev, type, code, value);
09 spin_unlock_irqrestore(&dev->event_lock, flags);
10 }
11 }
该函数第1个参数是input_device设备,第2个参数是事件的类型,可以取EV_KEY、EV_REL、EV_ABS等值,在上面的按键时 间报告函数input_report_key()中传递的就是EV_KEY值,表示发生一个按键事件。第3、4个函数与 input_report_key()函数的参数相同,下面对这个函数进行简要的分析。
第04行,调用is_event_supported()函数检查输入设备是否支持该事件。该函数的代码如下:
02 unsigned long *bm, unsigned int max)
03 {
04 return code <= max && test_bit(code, bm);
05 }
该函数检查input_dev.evbit中的相应位是否设置,如果设置返回1,否则返回0。每一种类型的事件都在input_dev.evbit 中用一个位来表示,构成一个位图,如果某位为1,表示该输入设备支持这类事件,如果为0,表示输入设备不支持这类事件。如图17.1所示,表示各位支持的 事件,其中省略了一些事件类型,目前Linux支持十多种事件类型,所以用一个long型变量就可以全部表示了。
(点击查看大图)图17.1 input_dev.evbit支持的事件表示方法 |
该行就是设置输入设备button_dev所支持的事件类型,BIT_MASK是用来构造input_dev.evbit这个位图的宏,宏代码如下:
回到17.1.1节input_event()函数的第06行,调用spin_lock_irqsave()函数对将事件锁锁定。
第07行,add_input_randomness()函数对事件发送没有一点用处,只是用来对随机数熵池增加一些贡献,因为按键输入是一种随机事件,所以对熵池是有贡献的。
第08行,调用input_handle_event()函数来继续输入子系统的相关模块发送数据。该函数较为复杂,下面单独进行分析。
2.input_handle_event()函数
input_handle_event()函数向输入子系统传送事件信息。第1个参数是输入设备input_dev,第2个参数是事件的类型,第3个参数是键码,第4个参数是键值。该函数的代码如下:
02 unsigned int type, unsigned int code, int value)
03 {
04 int disposition = INPUT_IGNORE_EVENT;
05 switch (type) {
06 case EV_SYN:
07 switch (code) {
08 case SYN_CONFIG:
09 disposition = INPUT_PASS_TO_ALL;
10 break;
11 case SYN_REPORT:
12 if (!dev->sync) {
13 dev->sync = 1;
14 disposition = INPUT_PASS_TO_HANDLERS;
15 }
16 break;
17 }
18 break;
19 case EV_KEY:
20 if (is_event_supported(code, dev->keybit, KEY_MAX) &&
21 !!test_bit(code, dev->key) != value) {
22 if (value != 2) {
23 __change_bit(code, dev->key);
24 if (value)
25 input_start_autorepeat(dev, code);
26 }
27 disposition = INPUT_PASS_TO_HANDLERS;
28 }
29 break;
30 case EV_SW:
31 if (is_event_supported(code, dev->swbit, SW_MAX) &&
32 !!test_bit(code, dev->sw) != value) {
33 __change_bit(code, dev->sw);
34 disposition = INPUT_PASS_TO_HANDLERS;
35 }
36 break;
37 case EV_ABS:
38 if (is_event_supported(code, dev->absbit, ABS_MAX)) {
39 value = input_defuzz_abs_event(value,
40 dev->abs[code], dev->absfuzz[code]);
41 if (dev->abs[code] != value) {
42 dev->abs[code] = value;
43 disposition = INPUT_PASS_TO_HANDLERS;
44 }
45 }
46 break;
47 case EV_REL:
48 if (is_event_supported(code, dev->relbit, REL_MAX) && value)
49 disposition = INPUT_PASS_TO_HANDLERS;
50 break;
51 case EV_MSC:
52 if (is_event_supported(code, dev->mscbit, MSC_MAX))
53 disposition = INPUT_PASS_TO_ALL;
54 break;
55 case EV_LED:
56 if (is_event_supported(code, dev->ledbit, LED_MAX) &&
57 !!test_bit(code, dev->led) != value) {
58 __change_bit(code, dev->led);
59 disposition = INPUT_PASS_TO_ALL;
60 }
61 break;
62 case EV_SND:
63 if (is_event_supported(code, dev->sndbit, SND_MAX)) {
64 if (!!test_bit(code, dev->snd) != !!value)
65 __change_bit(code, dev->snd);
66 disposition = INPUT_PASS_TO_ALL;
67 }
68 break;
69 case EV_REP:
70 if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
71 dev->rep[code] = value;
72 disposition = INPUT_PASS_TO_ALL;
73 }
74 break;
75 case EV_FF:
76 if (value >= 0)
77 disposition = INPUT_PASS_TO_ALL;
78 break;
79 case EV_PWR:
80 disposition = INPUT_PASS_TO_ALL;
81 break;
82 }
83 if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
84 dev->sync = 0;
85 if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
86 dev->event(dev, type, code, value);
87 if (disposition & INPUT_PASS_TO_HANDLERS)
88 input_pass_event(dev, type, code, value);
89 }
17.1.3 向子系统报告事件(2)
浏览一下该函数的大部分代码,主要由一个switch结构组成。该结构用来对不同的事件类型,分别处理。其中case语句包含了EV_SYN、 EV_KEY、EV_SW、EV_SW、EV_SND等事件类型。在这么多事件中,本例只要关注EV_KEY事件,因为本节的实例发送的是键盘事件。其 实,只要对一个事件的处理过程了解后,对其他事件的处理过程也就清楚了。下面对input_handle_event()函数进行简要的介绍:
第04行,定义了一个disposition变量,该变量表示使用什么样的方式处理事件。此处初始化为INPUT_IGNORE_EVENT,表示如果后面没有对该变量重新赋值,则忽略这个事件。
第05~82行是一个重要的switch结构,该结构中对各种事件进行了一些必要的检查,并设置了相应的disposition变量的值。其中只需要关心第19~29行的代码 即可。
第19~29行,对EV_KEY事件进行处理。第20行,调用is_event_supported()函数判断是否支持该按键。第21行,调用 test_bit()函数来测试按键状态是否改变。第23行,调用__change_bit()函数改变键的状态。第25行,处理重复按键的情况。第27 行,将disposition变量设置为INPUT_PASS_TO_HANDLERS,表示事件需要handler来处理。disposition的取 值有如下几种:
#define INPUT_PASS_TO_HANDLERS 1
#define INPUT_PASS_TO_DEVICE 2
#define INPUT_PASS_TO_ALL(INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
INPUT_IGNORE_EVENT表示忽略事件,不对其进行处理。INPUT_PASS_ TO_HANDLERS表示将事件交给handler处理。INPUT_PASS_TO_DEVICE表示将事件 交给input_dev处理。INPUT_PASS_TO_ALL表示将事件交给handler和input_dev共同处理。
第83、84行,处理EV_SYN事件,这里并不对其进行关心。
第85、86行,首先判断disposition等于INPUT_PASS_TO_DEVICE,然后判断dev->event是否对其指定 了一个处理函数,如果这些条件都满足,则调用自定义的dev->event()函数处理事件。有些事件是发送给设备,而不是发送给handler处 理的。event()函数用来向输入子系统报告一个将要发送给设备的事件,例如让LED灯点亮事件、蜂鸣器鸣叫事件等。当事件报告给输入子系统后,就要求 设备处理这个事件。
第87、88行,如果事件需要handler处理,则调用input_pass_event()函数,该函数将在下面详细解释。
3.input_pass_event()函数
input_pass_event()函数将事件传递到合适的函数,然后对其进行处理,该函数的代码如下:
02 unsigned int type, unsigned int code, int value)
03 {
04 struct input_handle *handle;
05 rcu_read_lock();
06 handle = rcu_dereference(dev->grab);
07 if (handle)
08 handle->handler->event(handle, type, code, value);
09 else
10 list_for_each_entry_rcu(handle, &dev->h_list, d_node)
11 if (handle->open)
12 handle->handler->event(handle,
13 type, code, value);
14 rcu_read_unlock();
15 }
下面对该函数进行简要的分析。
第04行,分配一个input_handle结构的指针。
第06行,得到dev->grab的指针。grab是强制为input device的handler,这时要调用handler的event函数。
第10~13行,表示如果没有为input device强制指定handler,即为grab赋值,就会遍历input device->h_list上的handle成员。如果该handle被打开,表示该设备已经被一个用户进程使用。就会调用与输入设备对应的 handler的event()函数。注意,只有在handle被打开的情况下才会接收到事件,这就是说,只有设备被用户程序使用时,才有必要向用户空间 导出信息。事件的处理过程如图17.2所示。