【Linux】關(guān)于正點(diǎn)原子input子系統(tǒng),驅(qū)動(dòng)中按鍵中斷只檢測(cè)了上升或下降沿卻可以實(shí)現(xiàn)連按(EV_REP)的原因
問(wèn)題
在學(xué)習(xí)到Linux內(nèi)核input子系統(tǒng)時(shí),產(chǎn)生了一個(gè)疑惑。可以看到,我們改造按鍵中斷驅(qū)動(dòng)程序(請(qǐng)見(jiàn)keyinputdriver.c(內(nèi)核驅(qū)動(dòng)代碼)),通過(guò)檢測(cè)按鍵的上升沿和下降沿,在中斷處理函數(shù)(上半部?jī)?nèi))通過(guò)mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20))函數(shù)啟動(dòng)定時(shí)器。在定時(shí)器處理函數(shù)中上報(bào)和同步按鍵事件(EV_KEY和EV_REP)。那么問(wèn)題來(lái)了,驅(qū)動(dòng)中,按鍵中斷是上升沿和下降沿觸發(fā),當(dāng)連按的時(shí)候,中斷只觸發(fā)了一次,為什么會(huì)一直上報(bào)的呢?
keyinputdriver.c(內(nèi)核驅(qū)動(dòng)代碼)點(diǎn)擊查看代碼
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/input.h>
#define KEYINPUT_CNT 1
#define KEYINPUT_NAME "keyinput"
#define KEY_NUM 1
#define KEY0_VALUE 0X01
#define INVAKEY 0XFF
struct irq_keydesc
{
int gpio; /* io編號(hào) */
int irqnum; /* 中斷號(hào) */
unsigned char value; /* 鍵值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void*); /* 中斷處理函數(shù) */
};
struct keyinput_dev
{
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer;
struct input_dev *inputdev;
};
struct keyinput_dev keyinput;
/* 按鍵處理函數(shù) */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct keyinput_dev *dev = dev_id;
dev->timer.data = (volatile unsigned long)dev;
/* 開(kāi)始定時(shí) */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
/* 定時(shí)器處理函數(shù) */
static void timer_func(unsigned long arg)
{
int value = 0;
struct keyinput_dev *dev = (struct keyinput_dev*)arg;
value = gpio_get_value(dev->irqkey[0].gpio);
if (0 == value) /* 按下 */
{
// printk("KEY0 press!\r\n");
input_event(dev->inputdev, EV_KEY, KEY_0, 1);
}
else if (1 == value) /* 釋放 */
{
// printk("KEY0 release!\r\n");
input_event(dev->inputdev, EV_KEY, KEY_0, 0);
}
input_sync(dev->inputdev);
}
/* 按鍵初始化 */
static int keyio_init(struct keyinput_dev *dev)
{
int i = 0;
int ret = 0;
/* 按鍵初始化 */
dev->nd = of_find_node_by_path("/key");
if (NULL == dev->nd)
{
ret = -EINVAL;
goto fail_findnode;
}
for (i = 0; i < KEY_NUM; i++)
{
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);
}
for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name, "KEY%d", i);
gpio_request(dev->irqkey[i].gpio, "key-gpios");
gpio_direction_input(dev->irqkey[i].gpio);
/* 按鍵中斷初始化 */
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);
#endif // 0
}
/* 按鍵中斷初始化 */
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY0_VALUE;
for (i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irqkey[i].name,
&keyinput);
if (ret)
{
printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/* 定時(shí)器初始化 */
init_timer(&dev->timer);
dev->timer.function = timer_func;
fail_irq:
for (i = 0; i < KEY_NUM; i++)
{
gpio_free(dev->irqkey[i].gpio);
}
fail_findnode:
return ret;
}
static int __init keyinput_init(void)
{
int ret = 0;
/* 1、初始化io key */
ret = keyio_init(&keyinput);
if (ret < 0)
{
goto fail_keyinit;
}
/* 2、注冊(cè)input_dev*/
keyinput.inputdev = input_allocate_device();
if (NULL == keyinput.inputdev)
{
ret = -EINVAL;
goto fail_keyinit;
}
keyinput.inputdev->name = KEYINPUT_NAME;
__set_bit(EV_KEY, keyinput.inputdev->evbit); //設(shè)置按鍵事件
__set_bit(EV_REP, keyinput.inputdev->evbit); //設(shè)置重復(fù)事件
__set_bit(KEY_0, keyinput.inputdev->keybit); //設(shè)置key0
ret = input_register_device(keyinput.inputdev);
if (ret)
{
goto fail_input_register;
}
return 0;
fail_input_register:
input_free_device(keyinput.inputdev);
fail_keyinit:
return ret;
}
static void __exit keyinput_exit(void)
{
int i = 0;
/* 1、刪除定時(shí)器 */
del_timer_sync(&keyinput.timer);
/* 2、釋放中斷 */
for (i = 0; i < KEY_NUM; i++)
{
free_irq(keyinput.irqkey[i].irqnum, &keyinput);
}
/* 3、釋放GPIO */
for (i = 0; i < KEY_NUM; i++)
{
gpio_free(keyinput.irqkey[i].gpio);
}
/* 4、注銷(xiāo)input_dev */
input_unregister_device(keyinput.inputdev);
input_free_device(keyinput.inputdev);
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_AUTHOR("tyler");
MODULE_LICENSE("GPL");
keyinputAPP.c(測(cè)試APP代碼)點(diǎn)擊查看代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>
/*
* ./keyinputAPP <filename> <0/1>
* ./keyinputAPP /dev/input/event2
*
*/
static struct input_event inputevent;
int main(int argc, char* argv[])
{
int fd, err;
char * filename;
if (argc != 2)
{
printf("args num error!!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("open error\r\n");
return -1;
}
while (1)
{
err = read(fd, &inputevent, sizeof(inputevent));
if (err > 0) /* 數(shù)據(jù)讀取成功 */
{
switch (inputevent.type)
{
case EV_KEY:
if (inputevent.code < BTN_MISC)
{
printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press":"release");
}
else
{
printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press":"release");
}
printf("EV_KEY事件\r\n");
break;
case EV_SYN:
break;
case EV_REL:
break;
case EV_ABS:
break;
};
}
else
{
printf("讀取失敗!\r\n");
}
}
close(fd);
return 0;
}
input輸入系統(tǒng)實(shí)現(xiàn)按鍵重復(fù)的方式
簡(jiǎn)單來(lái)說(shuō),在input的子系統(tǒng)中,實(shí)現(xiàn)按鍵重復(fù)的方式是啟動(dòng)了一個(gè)內(nèi)核定時(shí)器,通過(guò)保存按鍵值和上一次的值進(jìn)行比較來(lái)檢測(cè)按鍵是否按下。如果沒(méi)檢測(cè)到釋放按鍵,那么就會(huì)不斷地利用mod_timer來(lái)啟動(dòng)定時(shí)器,進(jìn)行按鍵重復(fù)上報(bào)。當(dāng)釋放了按鍵,那么就不會(huì)重復(fù)啟動(dòng)定時(shí)器,即停止了按鍵重復(fù)事件。
那就清晰了。我們的驅(qū)動(dòng)程序中,檢測(cè)到了按鍵上升沿中斷后,EV_REP事件在沒(méi)用檢測(cè)到按鍵鍵值改變(也就是下降沿中斷),就默認(rèn)開(kāi)啟內(nèi)核定時(shí)器,便會(huì)一直上報(bào)重復(fù)事件EV_REP了。
參考資料:https://blog.51cto.com/assassinwu/1080111 (input輸入系統(tǒng)中是如何實(shí)現(xiàn)按鍵重復(fù) )

浙公網(wǎng)安備 33010602011771號(hào)