1.3 输入子系统设备驱动讲解
1.3.1 打开和关闭函数
struct input_dev 中有 open 和 close 两个函数指针。在与 handler 第一次连接之后会调用 open 函数,断开连接会调用 close 。 open 中应该完成硬件初始化的相关工作,并且申请用到的其他资源,如中断号。 close 函数做相反的工作。
1.3.2 事件类型
Linux 输入子系统支持的事件类型如 程序清单 1 .6 所示。
程序清单 1 . 6 输入子系统事件类型
/* include/linux/input.h */
#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 /* led */
#define EV_SND 0x12 /* beep */
#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)
按键事件 (EV_KEY) 是最简单的事件类型,用来描述按键或者按钮。报告按键事件使用以下函数:
input_report_key(struct input_dev *dev, int code, int value)
code 的值在 < 内核 >include/linux/input.h 中定义,大小 从 0 到 KEY_MAX 。 value 为 0 时表示按键抬起,非 0 时代表按键按下。对同一个按键来说,只有当 value 的值和上次不同才会产生一次事件。
除了按键事件,相对坐标事件 (EV_REL) 和绝对坐标事件 (EV_ABS) 也是常用的事件。 为了使设备支持这两类事件,需要在初始化时对 struct input_dev 的 evbit 相应比特置 1 ,并且还要分别在 relbit 和 absbit 位图中为支持的坐标轴置 1 。参考 1.2 节的 7 、 8 两步。
相对坐标事件用来描述类似鼠标移动的消息。报告相对坐标的函数如下:
input_report_rel(struct input_dev *dev, int code, int value)
code 描述坐标轴, value 代表相对移动(可正可负)。只有当 value 的值非零时才能产生一个有效的事件。
绝对坐标需要额外的工作。初始化时需要填充 input_dev 中的 一些数据域。对于支持的每个坐标轴调用如下函数:
input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);
函数参数从右往左依次代表输入设备指针、坐标轴、最小值、最大值、分辨率、基准值。最后两个参数也可以填为 0 ,代表设备非常精确并且总能精确的回到中心位置。
input_set_abs_params 函数的代码如 程序清单 1 .7 所示。
程序清单 1 . 7 input_set_abs_params
static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)
{
dev->absmin[axis] = min;
dev->absmax[axis] = max;
dev->absfuzz[axis] = fuzz;
dev->absflat[axis] = flat;
dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis); /* 这一行已经注册了坐标 */
}
另外输入设备驱动中经常用到同步事件 (EV_SYN) ,输入子系统会默认支持此事件,驱动无需注册。鼠标、触摸板之类的设备需要用它来提示上层已经发送完了一个完整的事件报告。同步事件的报告形式如下:
input_sync(zlgkpd->input);
1.3.3 keycode 、 keycodemax 和 keycodesize
首先说明扫描码和键值的区别。如 程序清单 1 .4 所示,例子中包含四个按键的值,那么扫描码的范围是 0~3 ,键值就是 keypad_keycode 中的四个值。
keycode 、 keycodemax 和 keycodesize 这三个数据域保存的值分别是键值数组的首地址、键值的个数和每个键值的字节大小。有了这三个变量,就可以在运行是改变键盘的键值映射。比如原来数组中的第四个键值为 KEY_Z ,可以根据需要改为 KEY_D 或者其他的值。这样同样的扫描码就对应的不同的键值。
这三个域正确填充之后,内核可以使用 input_dev 的成员函数来修改键值映射。修改映射的函数就是 input_dev 中的 setkeycode 、 getkeycode 这两个函数指针对应的函数,如果注册之前不初始化它们,则初始化函数把系统默认的函数赋给它们。
EVIOCGKEYCODE 和 EVIOCSKEYCODE 这两个 ioctl 命令分别用来查看和修改键值。
1.3.4 按键的自动连击
我们都有这样的经历:按住方向键不松开,一直把文档往某个方向拉。这个功能就是自动连击,也就是在按键抬起之前连续发送按键事件。
按键连击事件 (EV_REP) 的开启非常简单,在 input_devd 的 evbit 相应比特置 1 即可。 dev->rep[REP_DELAY] 和 dev->rep[REP_PERIOD] 分别存储连击的延时和周期,如果驱动不对它们赋值,则系统为他们分别赋为 250 和 33 。按键延时即按键到第一次连击的间隔,按键周期即两次连击之间的间隔。它们的单位都是毫秒。
1.3.5 总线类型
input_dev 中 input_id 用到了总线类型。输入子系统支持的总线类型如 程序清单 1 .8 所示。
程序清单 1 . 8 输入子系统总线类型
/* include/linux/input.h */
#define BUS_PCI 0x01
#define BUS_ISAPNP 0x02
#define BUS_USB 0x03
#define BUS_HIL 0x04
#define BUS_BLUETOOTH 0x05
#define BUS_VIRTUAL 0x06
#define BUS_ISA 0x10
#define BUS_I8042 0x11
#define BUS_XTKBD 0x12
#define BUS_RS232 0x13
#define BUS_GAMEPORT 0x14
#define BUS_PARPORT 0x15
#define BUS_AMIGA 0x16
#define BUS_ADB 0x17
#define BUS_I2C 0x18
#define BUS_HOST 0x19
#define BUS_GSC 0x1A
#define BUS_ATARI 0x1B
1.3.6 其他的事件类型
EV_LED 和 EV_SND 事件是针对键盘上的 led 和蜂鸣器。这两类事件是由输入子系统内核发送给驱动的。
如果驱动支持这两类事件的话,应该填充 input_dev 中的 event 函数指针,其实这是一个回调函数。另外需要填充 input_dev 中 evbit 对应的比特位。
这个回调函数可能在中断或者中断的底半部调用,因此 event 函数不能睡眠且必须尽快结束。