關於正點原子input子系統,驅動中按鍵中斷只檢測了上升或下降沿卻可以實現連按(EV_REP)的原因

Tyler77發表於2024-08-31

問題

在學習到Linux核心input子系統時,產生了一個疑惑。可以看到,我們改造按鍵中斷驅動程式(請見keyinputdriver.c(核心驅動程式碼)),透過檢測按鍵的上升沿和下降沿,在中斷處理函式(上半部內)透過mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20))函式啟動定時器。在定時器處理函式中上報和同步按鍵事件(EV_KEY和EV_REP)。那麼問題來了,驅動中,按鍵中斷是上升沿和下降沿觸發,當連按的時候,中斷只觸發了一次,為什麼會一直上報的呢?

keyinputdriver.c(核心驅動程式碼)點選檢視程式碼
#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編號 */
    int irqnum;                                 /* 中斷號 */
    unsigned char value;                        /* 鍵值 */
    char name[10];                              /* 名字 */
    irqreturn_t (*handler)(int, void*);         /* 中斷處理函式 */

};


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;


/* 按鍵處理函式 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct keyinput_dev *dev = dev_id;

    dev->timer.data = (volatile unsigned long)dev;
    /* 開始定時 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));
    
    return IRQ_HANDLED;
}

/* 定時器處理函式 */
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;
        }
    }

    /* 定時器初始化 */
    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、註冊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);    //設定按鍵事件
    __set_bit(EV_REP, keyinput.inputdev->evbit);    //設定重複事件
    __set_bit(KEY_0, keyinput.inputdev->keybit);    //設定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、刪除定時器 */
    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、登出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(測試APP程式碼)點選檢視程式碼
#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) /* 資料讀取成功 */
        {
            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輸入系統實現按鍵重複的方式

簡單來說,在input的子系統中,實現按鍵重複的方式是啟動了一個核心定時器,透過儲存按鍵值和上一次的值進行比較來檢測按鍵是否按下。如果沒檢測到釋放按鍵,那麼就會不斷地利用mod_timer來啟動定時器,進行按鍵重複上報。當釋放了按鍵,那麼就不會重複啟動定時器,即停止了按鍵重複事件。
那就清晰了。我們的驅動程式中,檢測到了按鍵上升沿中斷後,EV_REP事件在沒用檢測到按鍵鍵值改變(也就是下降沿中斷),就預設開啟核心定時器,便會一直上報重複事件EV_REP了。

參考資料:https://blog.51cto.com/assassinwu/1080111 (input輸入系統中是如何實現按鍵重複 )

相關文章