上面两章分别讲了 Linux 输入子系统的设备驱动层和事件处理层,这两层的实现都是建立在输入核心层的基础之上的。核心层负责管理所有的资源并连接驱动层和事件处理层。
输入子系统的核心层的实现都在 driver/input/input.c 文件中, 初始化函数如 程序清单 3 .1 所示。
程序清单 3 . 1 input core 初始化
/* driver/input/input.c */
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
static int __init input_init(void)
{
int err;
err = class_register(&input_class); ⑴
if (err) {
printk(KERN_ERR "input: unable to register input_dev class/n");
return err;
}
err = input_proc_init(); ⑵
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); ⑶
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
这个函数主要做了三件事:
⑴ 注册一个 input_class 类。
⑵ 在 /proc/bus/input 下面创建两个节点 devices 和 handlers ,通过打印这两个节点的内容可以查看输入子系统的设备驱动层信息和处理层信息。
⑶ 申请注册输入子系统的主设备号 13 ,并注册文件结构体 input_fops 。
input core 申请了主设备号,那么所有打开输入设备的处理请求都会传给 input_fops 的打开函数处理 input_open_file ,这个函数的实现如 程序清单 3 .2 所示。
程序清单 3 . 2 input_open_file
/* driver/input/input.c */
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;
lock_kernel();
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5]; ⑴
if (!handler || !(new_fops = fops_get(handler->fops))) {
err = -ENODEV;
goto out;
}
if (!new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops; ⑵
err = new_fops->open(inode, file); ⑶
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
unlock_kernel();
return err;
}
这个函数做了以下工作:
⑴ 根据次设备号从 input_table 取出 handler 。
⑵ 将 handler 对应的文件结构体赋给 struct file *file 。
⑶ 调用文件结构体的 open 函数。
input core 有两个链表表头 input_dev_list 和 input_handler_list ,分别用来连接 input_dev (设备驱动)结构和 input_handler (事件处理)结构,另外还有一个 input_table[8] 数组,可以存放 8 个支持文件操作的 handler 指针。
另外从 1.4 和 2.2 两节中可以看出 input_dev 和 input_handler 注册的时候都是将自己注册进 input core 的链表,然后从对方的链表中寻找可以匹配的节点建立连接。并且可以建立一对多的连接。连接后的结构图如 图 3 .1 所示。
图中有两个输入设备, key_dev 代表一个按键设备, mice_dev 代表一个鼠标设备。根据 2.3.1 节的分析我们知道, mice_dev 和 key_dev 都可以和 evdev_handler 匹配连接,而只有 mice_dev 可以和 mousedev_handler 匹配连接。
图 3 . 1 input core 结构
图 3 . 2 evdev 和 evdev_client 的关系
连接 input_dev 和 input_handler 的结构体 input_handle 往往作为一个更大结构体的成员。对 evdev_handler 来说这个大结构体是 struct evdev 。这个结构体负责管理打开该设备节点的所有线程。每个打开设备的线程用一个 struct evdev_client 结构体表示。它们的关系如 图 3 .2 所示。
3.1 节已经分析过, input core 会在 /proc/bus/input 下建立两个文件, devices 和 handlers ,读取它们的内容可以得到设备的信息。
在开发板上运行“ cat /proc/bus/input/devices ”命令,得到以下输出:
[root@zlg /]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0001 Product=0002 Version=0100 ⑴
N: Name="LPC32xx Touchscreen" ⑵
P: Phys=lpc32xx/input0 ⑶
S: Sysfs=/class/input/input0 ⑷
U: Uniq=
H: Handlers=mouse0 event0 ⑸
B: EV=b ⑹
B: KEY=400 0 0 0 0 0 0 0 0 0 0 ⑺
B: ABS=1000003
⑻
可以看出开发板上只有一个输入设备“ LPC32xx Touchscreen ”,这是一个触摸屏。各行的含义如下:
⑴ 这一行对应 input_dev 的 struct input_id 成员的内容,参考 程序清单 1 .4 。
⑵ 这一行对应 input_dev 的名字。
⑶ 这一行对应 input_dev 的 phys 成员。
⑷ 这一行对应该设备在 /sys 下的路径。
⑸ 这一行对应和该设备建立连接的 handler 。可以看到有两个 handler 与设备建立了连接,这与我们上文的分析一致。对应的两个节点分别是 mouse0 和 event0 。
⑹ 这一行对应该设备支持的事件,和 input_dev 的 evbit 成员一致。由此看出该设备支持三类事件:按键、绝对坐标和同步事件。
⑺ 这一行对应该设备支持的按键,和 input_dev 的 keybit 成员一致。该设备只支持一个按键: BTN_TOUCH 。
⑻ 这一行对应该设备支持的绝对值事件,和 input_dev 的 absbit 成员一致。该设备支持 X 轴绝对坐标、 Y 轴绝对坐标和压力( ABS_PRESSURE )。
下面看看 handler 的信息:
[root@zlg /]# cat /proc/bus/input/handlers
N: Number=0 Name=kbd
N: Number=1 Name=mousedev Minor=32
N: Number=2 Name=evdev Minor=64
可以看到有三个已经注册的 handler ,分别处理键盘事件、鼠标事件和一般事件。其中后两个占用设备号,起始的次设备号分别是 32 和 64 。这里与上面的分析相符。
用户程序一般不从触摸屏直接读取数据,使用触摸屏一般要通过 tslib 的接口。下面看一看 qt 界面的配置文件:
export QWS_MOUSE_PROTO=Tslib:/dev/input/event0
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/mnt/tslib-lib/etc/ts.conf
export TSLIB_PLUGINDIR=/mnt/tslib-lib/lib/ts
export QTDIR=/mnt/qt-4.6-arm-lib
export LD_LIBRARY_PATH=$QTDIR/lib
export QT_QWS_FONTDIR=$QTDIR/lib/fonts
export QT_PLUGIN_PATH=$QTDIR/plugins
./hello -qws -font wenquanyi
第一行的意思是配置鼠标的驱动程序是 tslib ,使用的设备节点是 /dev/input/event0 。
现在概括一下触摸屏在输入子系统中的使用方式:系统中有三个可用的 handler ,分别对应键盘、鼠标和一般事件,触摸屏设备的 input_dev 可以和后两个建立连接,对应 event0 和 mouse0 这两个设备节点,同时 tslib 使用 event0 读取触摸屏的事件消息。
可以看出触摸屏的事件是直接传给 tslib 的,不需要象鼠标一样建立一个专门的 handler 。