第1章 Linux 输入子系统设备驱动
Linux 输入子系统将输入驱动抽象为三层:事件处理层、核心层、设备驱动层。应用程序只需要跟事件处理层打交道,不需要察觉设备的变化。核心层是负责管理输入设备,并将消息在事件处理层和设备驱动层之间传递。
由于事件处理和设备驱动的分离,使得应用程序读取输入信息的接口固定不变就可以适应新的同类输入设备。
表示事件处理层的数据结构是 struct input_handler ,每个 handler 代表一种处理事件的方式,允许多个 handler 共存。代表设备驱动层的数据结构是 struct input_dev 。 input_dev 和 handler 可以建立连接,连接它们的就是 struct input_handle 。核心层一般被称为 input core 。
1.1 重要的数据结构
在输入子系统的设备驱动中,最重要的数据结构是 struct input_dev ,如 程序清单 1 .1 所示。需要完成的大部分工作都是围绕着它来的,它是驱动的主体。每个 struct input_dev 代表一个输入设备。
程序清单 1 . 1 struct input_dev 成员介绍
/* include/linux/input.h */
struct input_dev {
const char *name; /* 设备名 */
const char *phys;
const char *uniq;
struct input_id id; /* 用于匹配事件处理层 handler */
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 用于记录支持的事件类型的位图 */
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 记录支持的按键值的位图 */
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)]; /* led */
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* beep */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int keycodemax; /* 支持的按键值的个数 */
unsigned int keycodesize; /* 每个键值的字节数 */
void *keycode; /* 存储按键值的数组首地址 */
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); /* 修改键值的函数,可选 */
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); /* 获取扫描码的键值,可选 */
struct ff_device *ff;
unsigned int repeat_key; /* 最近一次按键值,用于连击 */
struct timer_list timer; /* 自动连击计时器 */
int sync; /* 最后一次同步后没有新的事件置1 */
int abs[ABS_MAX + 1]; /* 当前各个坐标的值 */
int rep[REP_MAX + 1]; /* 自动连击的参数 */
unsigned long key[BITS_TO_LONGS(KEY_CNT)]; /* 反映当前按键状态的位图 */
unsigned long led[BITS_TO_LONGS(LED_CNT)]; /* 反映当前 led 状态的位图 */
unsigned long snd[BITS_TO_LONGS(SND_CNT)]; /* 反映当前 beep 状态的位图 */
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int absmax[ABS_MAX + 1]; /* 记录各个坐标的最大值 */
int absmin[ABS_MAX + 1]; /* 记录各个坐标的最小值 */
int absfuzz[ABS_MAX + 1]; /* 记录各个坐标的分辨率 */
int absflat[ABS_MAX + 1]; /* 记录各个坐标的基准值 */
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 *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
int going_away;
struct device dev;
struct list_head h_list; /* handle 链表 */
struct list_head node; /* input_dev 链表 */
};
struct input_event 是事件传送的载体,输入子系统的事件都是包装成 struct input_event 传给用户空间。各个成员如 程序清单 1 .2 所示。
程序清单 1 . 2 struct input_event 成员介绍
/* include/linux/input.h */
struct input_event {
struct timeval time; /* 时间戳 */
__u16 type; /* 事件类型 */
__u16 code; /* 事件代码 */
__s32 value; /* 事件值,如坐标的偏移值 */
};
struct input_dev 注册的时候需要跟匹配的 hanlder 建立连接,匹配的依据就是 struct input_dev 所包含的 struct input_id 。 struct input_id 的各个成员如 程序清单 1 .3 所示。
程序清单 1 . 3 struct input_id 成员描述
/* include/linux/input.h */
struct input_id {
__u16 bustype; /* 总线类型 */
__u16 vendor; /* 生产商编号 */
__u16 product; /* 产品编号 */
__u16 version; /* 版本号 */
};
1.2 基于输入子系统的键盘驱动例程
下面提供一个基于输入子系统的键盘驱动例程,这里只是介绍编写一个键盘驱动需要哪些步骤,并不解释本程序的全部细节。如 程序清单 1 .4 所示。
程序清单 1 . 4 输入子系统设备驱动例程 - 键盘驱动
#define NR_SCANCODES (4)
typedef unsigned char KEYCODE_T;
static KEYCODE_T keypad_keycode[NR_SCANCODES] = /* 按键值 */
{
KEY_BACKLIGHT, KEY_X, KEY_Y, KEY_Z
};
typedef struct { /* 自己定义的结构体 */
struct input_dev *input;
int irq;
KEYCODE_T keycode[ARRAY_SIZE(keypad_keycode)];
KEYCODE_T lastkey;
char phys[32];
}KEYPAD_T;
static int __init keypad_init(void)
{
int err;
int i;
zlgkpd = kzalloc(sizeof(KEYPAD_T), GFP_KERNEL); ①
if (unlikely(!zlgkpd))
{
return -ENOMEM;
}
strcpy(zlgkpd->phys, "keypad/input0");
zlgkpd->input = input_allocate_device(); ②
if (!zlgkpd->input)
{
kfree(zlgkpd);
return -ENOMEM;
}
zlgkpd->input->name = CHIP_NAME; ③
zlgkpd->input->phys = zlgkpd->phys;
zlgkpd->input->id.bustype = BUS_HOST; ④
zlgkpd->input->id.vendor = 0x0001;
zlgkpd->input->id.product = 0x0001;
zlgkpd->input->id.version = 0x0100;
zlgkpd->input->keycode = zlgkpd->keycode; ⑤
zlgkpd->input->keycodesize = sizeof(KEYCODE_T);
zlgkpd->input->keycodemax = ARRAY_SIZE(keypad_keycode);
zlgkpd->input->open = keypad_open; ⑥
zlgkpd->input->close = keypad_close;
zlgkpd->irq = KEYPAD_IRQ;
set_bit(EV_KEY, zlgkpd->input->evbit); ⑦
for (i = 0; i < ARRAY_SIZE(keypad_keycode); i++) { ⑧
set_bit(keypad_keycode[i], zlgkpd->input->keybit);
}
clear_bit(KEY_RESERVED, zlgkpd->input->keybit);
err = input_register_device(zlgkpd->input); ⑨
if (err)
goto err;
DPRINTK("init OK!/n");
return 0;
err:
DPRINTK("init error/n");
input_free_device(zlgkpd->input);
kfree(zlgkpd);
return err;
}
static void __exit keypad_exit(void)
{
input_unregister_device(zlgkpd->input); ⑩
input_free_device(zlgkpd->input);
kfree(zlgkpd);
}
module_init(keypad_init);
module_exit(keypad_exit);
下面来一步步解释:
① 为自己定义的类型申请空间。这一步不是典型输入子系统驱动的必须步骤。
② 申请 struct input_dev 并做初始化。
③ 初始化设备的名字。
④ 初始化设备 ID 。在注册设备之前,名字和 ID 是必须初始化的。
⑤ 初始化设备键值数组首地址、键值字节大小和按键的个数。
⑥ 初始化设备的打开和关闭函数指针。
⑦ 使设备支持按键事件。只有注册过的事件类型才能响应。
⑧ 注册支持的按键键值。只有注册过的键值才能响应。
⑨ 注册设备结构体。
⑩ 释放占用的资源。
发送事件的代码如下
程序清单 1 . 5 发送按键事件
static void keypad_do_workqueue(struct work_struct *data)
{
/* ·········· some codes ··········· */
if (pressed) {
············
input_report_key(zlgkpd->input, code, KEY_PRESSED); ①
input_sync(zlgkpd->input);
zlgkpd->lastkey = code;
DPRINTK("KEY is %d, pressed: %d, index:%d/n", code, pressed, index);
input_report_key(zlgkpd->input, code, KEY_RELEASED); ②
input_sync(zlgkpd->input);
}
enable_irq(KEYPAD_IRQ);
}
static irqreturn_t keypad_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
disable_irq(KEYPAD_IRQ);
schedule_work(&key_wq);
return IRQ_HANDLED;
}
在中断底半部中,读取硬件决定发送的按键值,首先发送按键按下的信息,然后发送按键抬起的信息。每次发送事件之后都同步一次。