输入子系统框架
对于输入子系统框架的分析可以参考下面这篇文章:Linux内核输入子系统框架
输入子系统驱动程序
在了解了输入子系统的框架后,就可以编写一个输入子系统的驱动程序。
我们需要写两个程序,一个是 dev 设备程序。负责与硬件进行交互;另一个是 handler 程序,负责完成与应用程序的交互。
对于 dev 设备程序:
- 我们需要先分配一个 input_dev 结构体;
- 对 input_dev 进行设置;
- 注册 input_dev 到内核中;
- 硬件相关的操作;
对于输入子系统驱动的编写,我们可以参考 gpio_keys.c 文件的实现。
对于 handler 程序:由于我们实现的是输入子系统,而内核中已经用现成的输入系统的 handler 程序。包括: evdev.c 文件中实现的事件驱动程序、keyboard.c 中实现的按键驱动程序等等,因此我们只需要实现 dev 程序即可。
实现 dev 程序
在实现 dev 驱动程序之前,我们需要对 input_dev 结构体进行分析:
struct input_dev {
/* ...省略... */
unsigned long evbit[NBITS(EV_MAX)]; /* 表示能产生哪类事件 */
unsigned long keybit[NBITS(KEY_MAX)]; /* 表示能产生哪些按键 */
unsigned long relbit[NBITS(REL_MAX)]; /* 表示能产生哪些相对位移事件 */
unsigned long absbit[NBITS(ABS_MAX)]; /* 表示能产生哪些绝对位移事件 */
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
/* ...省略... */
}
上面是设备能产生的事件类型,事件分析参考 Linux内核输入子系统事件分析_Bin Watson的博客-CSDN博客 这篇文章。
init 函数
定义全局数据结构:
struct pin_desc
{
int irq; /* 中断号 */
char *name; /* 按键的名字 */
unsigned int pin; /* 中断的引脚 */
unsigned char code; /* 按键的值 */
};
定义全局变量:
static struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "s2", S3C2410_GPF0, KEY_L}, /* 将按键s2,定义为L */
{IRQ_EINT2, "s3", S3C2410_GPF2, KEY_S}, /* 将按键s3,定义为S */
{IRQ_EINT11, "s4", S3C2410_GPG3, KEY_ENTER}, /* 将按键s4,定义为ENTER */
{IRQ_EINT19, "s5", S3C2410_GPG11, KEY_LEFTSHIFT}, /* 将按键s5,定义为LEFTSHIFT */
};
static struct input_dev *s3c2440_buttons_dev; /* 输入设备 */
static struct timer_list s3c2440_buttons_timer; /* 定时器,防抖动用 */
static struct timer_data s3c2440_timer_data; /* timer_handler函数使用的参数 */
初始化函数的实现(参考了 gpio_keys.c):
static int __init s3c2440_buttons_init(void)
{
int i = 0, error;
/* 1.分配一个input_dev结构体 */
s3c2440_buttons_dev = input_allocate_device();
if (!s3c2440_buttons_dev)
return -ENOMEM;
/* 2.设置 */
/* 2.1 能产生按键类事件 */
set_bit(EV_KEY, s3c2440_buttons_dev->evbit);
/* 2.2 这类事件里的哪些事件
* 我们定义我们四个按键为:字母L, 字母S, ENTER, LEFTSHIT
*/
set_bit(KEY_L, s3c2440_buttons_dev->keybit);
set_bit(KEY_S, s3c2440_buttons_dev->keybit);
set_bit(KEY_ENTER, s3c2440_buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, s3c2440_buttons_dev->keybit);
/* 3.注册 */
error = input_register_device(s3c2440_buttons_dev);
if (error) {
printk(KERN_ERR "Unable to register %s input device\n", DEVICE_NAME);
goto fail1;
}
/* 4.硬件相关的操作 */
/* 4.1 定时器初始化工作 */
init_timer(&s3c2440_buttons_timer);
s3c2440_buttons_timer.data = 0;
s3c2440_buttons_timer.function = s3c2440_timer_handler;
add_timer(&s3c2440_buttons_timer);
/* 4.2 设置中断引脚 */
for (i = 0; i < (sizeof(pins_desc) / sizeof(struct pin_desc)); ++i)
{
error = request_irq(pins_desc[i].irq, s3c2440_irq_handler,
IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "%s: unable to claim irq %d; error %d\n",
DEVICE_NAME, pins_desc[i].irq, error);
goto fail2;
}
}
return 0;
return 0;
fail2: /* 释放所有已经设置的引脚 */
for (i = i - 1; i >= 0; --i)
free_irq(pins_desc[i].irq, &pins_desc[i]);
del_timer(&s3c2440_buttons_timer);
fail1: /* 释放s3c2440_buttons_dev */
input_unregister_device(s3c2440_buttons_dev);
input_free_device(s3c2440_buttons_dev);
return error;
定时器的设置参考:jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客
设置中断引脚的设置参考:jz2440_字符设备按键驱动程序_Bin Watson的博客-CSDN博客
exit 函数
static void __exit s3c2440_buttons_exit(void)
{
int i = 0;
for (i = 0; i < (sizeof(pins_desc) / sizeof(struct pin_desc)); ++i)
free_irq(pins_desc[i].irq, &pins_desc[i]);
del_timer(&s3c2440_buttons_timer);
input_unregister_device(s3c2440_buttons_dev);
input_free_device(s3c2440_buttons_dev);
}
事件发生函数
当按键被按下时,会触发中断处理函数 s3c2440_irq_handler 被调用:
static irqreturn_t s3c2440_irq_handler(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
mod_timer(&s3c2440_buttons_timer, jiffies + (HZ/100));
s3c2440_timer_data.irq = irq;
s3c2440_timer_data.dev_id = dev_id;
return IRQ_HANDLED;
}
为了实现防抖的,我们将中断处理推迟到下半部,也就是在定时器的处理函数中进行处理。(定时器教程参考jz2440_按键驱动程序的防抖机制_Bin Watson的博客-CSDN博客)
s3c2440_timer_handler 是定时器的处理函数,其所需要做的就是对事件进行上报,最终由 evdev.c 实现的 evdev 驱动程序来完成。s3c2440_timer_handler 的实现:
static void s3c2440_timer_handler(unsigned long data)
{
unsigned int pinval;
struct pin_desc *pindesc = (struct pin_desc *)s3c2440_timer_data.dev_id;
if (pindesc == NULL)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 按键松开 value = 0 */
input_event(s3c2440_buttons_dev, EV_KEY, pindesc->code, 0);
input_sync(s3c2440_buttons_dev); /* 上报一个同步类事件,表示当前事件已经上报完了 */
}
else
{
/* 按键按下 value = 1 */
input_event(s3c2440_buttons_dev, EV_KEY, pindesc->code, 1);
input_sync(s3c2440_buttons_dev);
}
}
-
pindesc:对于 init 函数中的
request_irq(pins_desc[i].irq, s3c2440_irq_handler, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i])
中的pins_desc[i]
,也就是全局数组pins_desc[4]
的某一个元素。 -
pinval = s3c2410_gpio_getpin(pindesc->pin)
:用于获取引脚的值。S3C2410_GPF0、S3C2410_GPF2、S3C2410_GPF11、S3C2410_GPF19 分别连接了四个按键 “s2”、 “s3”、 “s4”、 “s5”。中断引脚的配置详细参考jz2440_字符设备按键驱动程序_Bin Watson的博客-CSDN博客
测试
加载设备之前:
linux > ls -l /dev/event* # 查看event设备
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 /dev/event0
加载设备之后:
linux > insmod input.ko
input: Unspecified device as /class/input/input1
linux > ls -l /dev/event*
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 /dev/event0
crw-rw---- 1 0 0 13, 65 Jan 1 00:26 /dev/event1
挂载之后,我们的 event* 设备就变为了2个。
使用 tty1 进行测试(用到 keyboard.c 驱动程序):
# cat /dev/tty1
LS
使用 event1 进行测试(用到 evdev.c 驱动程序):
# hexdump /dev/event1
0000000 09ca 0000 808a 0004 0001 0026 0000 0000
0000010 09ca 0000 8095 0004 0000 0000 0000 0000
0000020 09ca 0000 a37f 0006 0001 0026 0001 0000
0000030 09ca 0000 a38c 0006 0000 0000 0000 0000
event1 内的数据的数据结构类型如下:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time | type | code | value | 描述 |
---|---|---|---|---|
09ca 0000 808a 0004 | 0001 | 0026 | 0000 0000 | 按键松开 |
09ca 0000 8095 0004 | 0000 | 0000 | 0000 0000 | |
09ca 0000 a37f 0006 | 0001 | 0026 | 0001 0000 | 按键按下 |
09ca 0000 a38c 0006 | 0000 | 0000 | 0000 0000 |
重复事件
新驱动程序
添加重复事件 EV_REP 支持:
static int __init s3c2440_buttons_init(void)
{
int i = 0, error;
/* 1.分配一个input_dev结构体 */
s3c2440_buttons_dev = input_allocate_device();
if (!s3c2440_buttons_dev)
return -ENOMEM;
/* 2.设置 */
/* 2.1 能产生按键类事件 */
set_bit(EV_KEY, s3c2440_buttons_dev->evbit);
set_bit(EV_REP, s3c2440_buttons_dev->evbit); /* 当按键一直按着时,会重复生成 */
/*...省略...*/
重复事件的实现
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
/*...省略...*/
case EV_KEY:
if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
return;
if (value == 2)
break;
change_bit(code, dev->key);
/* 当按键按下,并且设置了EV_REP重复事件 */
if (test_bit(EV_REP, dev->evbit) &&
dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code; /* 记录按键的值 */
/* 会重置定时器 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
break;
/*...省略...*/
}
定时器的处理函数,如下:
static void input_repeat_key(unsigned long data)
{
struct input_dev *dev = (void *) data;
if (!test_bit(dev->repeat_key, dev->key))
return;
input_event(dev, EV_KEY, dev->repeat_key, 2); /* 上报事件 */
input_sync(dev);
/* 再次修改定时器 */
if (dev->rep[REP_PERIOD])
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD]));
}