Linux驅動開發(十六):INPUT子系統

簡介

輸入設備是典型的字符驅動,其工作原理一般是在按鍵、觸摸等動作發生時在底層產生一箇中斷(或者驅動通過Timer定時查詢),然後CPU通過SPI、I2C或外部存儲器總線讀取鍵值、座標等數據,並將它們放到一個緩衝區,字符設備驅動管理該緩衝區,而驅動的read()接口讓用戶可以讀取它們的鍵值、座標等數據
在這些工作中只有中斷、讀取鍵值(座標值)是與設備相關的,而輸入設備的緩衝區管理以及字符設備驅動的file_operations接口對於輸入設備來說都是通用的,所以Linux內核專門做了一個INPUT子系統來處理輸入事件
框架圖如下:
在這裏插入圖片描述
上圖就展示了INPUT子系統(也就是輸入核心)在整個系統中所處的位置
系統的結構如下圖:
在這裏插入圖片描述

  • 驅動層:輸入設備的具體驅動程序,比如按鍵驅動程序,向內核層報告輸入內容。
  • 核心層:承上啓下,爲驅動層提供輸入設備註冊和操作接口。通知事件層對輸入事件進行處理。
  • 事件層:主要和用戶空間進行交互

驅動編寫流程

input核心層會向Linux內核註冊一個字符設備
input子系統的所有設備主設備號爲13,在使用input子系統處理輸入設備時就不需要去註冊字符設備了,只需要向系統註冊一個input_device即可

註冊input_dev

表示input設備

struct input_dev {
	const char *name;
	const char *phys;
	const char *uniq;
	struct input_id id;

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	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)];//sound有關的位圖
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//壓力反饋的位圖
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//開關狀態的位圖.
	bool devres_managed;
};

evbit表示輸入設備類型,可選的類型如下

#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 /* sound(聲音) */ 
#define EV_REP 0x14 /* 重複事件 */ 
#define EV_FF 0x15 /* 壓力事件 */ 
#define EV_PWR 0x16 /* 電源事件 */ 
#define EV_FF_STATUS 0x17 /* 壓力狀態事件 */

比如使用按鍵就寫EV_KEY,如果要使用連按功能的話還需要註冊EV_REP時間
input_dev中的那些位圖都是存放不同時間對應的值的,Linux內核中定義了很多的鍵值

#define KEY_RESERVED		0
#define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
#define KEY_5			6
#define KEY_6			7
#define KEY_7			8
#define KEY_8			9
#define KEY_9			10
#define KEY_0			11
#define KEY_MINUS		12
#define KEY_EQUAL		13#define BTN_TRIGGER_HAPPY39		0x2e6
#define BTN_TRIGGER_HAPPY40		0x2e7

我們編寫input設備驅動的時候需要先申請一個input_dev結構體變量,使用input_allocate_device,原型如下:

struct input_dev __must_check *input_allocate_device(void);

如果要註銷input設備的話需要使用input_free_device,原型如下:

void input_free_device(struct input_dev *dev);

申請好一個input_dev後需要初始化,需要初始化的內容主要爲事件類(evbit)和事件值(keybit)這兩種,input_dev初始化完成後就需要向Linux內核註冊input_dev,需要用到 input_register_device,原型如下:

int __must_check input_register_device(struct input_dev *);

註銷時使用input_unregister_device,原型如下:

void input_unregister_device(struct input_dev *);

設置事件和事件值

使用__set_bit函數

這個是在Linux的bitmap(位數組)數據結構中實現的,用於設置某一位,使用匯編來實現,原子性的操作

__set_bit(EV_KEY, inputdev->evbit); /* 設置產生按鍵事件 */ 
__set_bit(EV_REP, inputdev->evbit); /* 重複事件 */ 
__set_bit(KEY_0, inputdev->keybit); /*設置產生哪些按鍵值 */

直接設置相應的成員變量

keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

使用input_set_capability

實際上底層也是來調用__set_bit函數

keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

input_dev使用總結

  • 使用 input_allocate_device函數申請一個 input_dev
  • 初始化 input_dev的事件類型 以及事件值。
  • 使用 input_register_device函數向 Linux系統註冊前面初始化好的 input_dev。
  • 卸載 input驅動的時候需要先使用 input_unregister_device函數註銷掉註冊的 input_dev
  • 最後用nput_free_device註銷input設備

上報輸入事件

我們需要獲取到具體的輸入值或者說是輸入事件,然後將輸入事件上報給Linux內核
比如按鍵,我們需要在按鍵中斷處理函數或者消抖定時器中斷函數中將按鍵值上報給Linux內核,這樣Linux內核才能獲取到正確的輸入值
不同的時間其上報事件的API函數不同

input_event函數

此函數用於上報指定的事件以及對應的值

void input_event(struct input_dev *dev, 
		unsigned int type,
		 unsigned int code,
		 int value)

dev:需要上報的input_dev
type:上報事件的類型,比如EV_KEY
code:時間碼,也就是我們註冊的按鍵值,比如KEY_0,KEY_1
value:事件值,比如1表示按鍵按下,0表示按鍵鬆開
input_event可以上報所有的事件類型和事件值,Linux內核提供的其他針對具體事件的上報函數其實都用到了input_event函數

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)

input_sync

當我們上報事件以後還需要使用input_sync函數來告訴Linux內核input子系統上報結束,input_sync本質是一個同步事件

input_event結構體

struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

time:事件發生的時間 timeval類型
type:事件類型
code:事件碼
value:值

實驗代碼與分析

實驗代碼

驅動部分

struct irqkey_dev
{
    dev_t devid;
	...
    struct input_dev *inputdev;
};

void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct irqkey_dev *dev = (struct irqkey_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];
    value = gpio_get_value(keydesc->gpio);
    printk(KERN_EMERG "GPIO is %d\r\n",value);
    if(value == 0)
    {
        printk(KERN_EMERG "gpio == 1 \r\n");
        /*report key value*/
        input_report_key(dev->inputdev, keydesc->value, 1);
        input_sync(dev->inputdev);
    }
    else
    {
        printk(KERN_EMERG "gpio == 0 \r\n");
        input_report_key(dev->inputdev, keydesc->value, 0);
        input_sync(dev->inputdev);
    }
}
static int keyio_init(void)
{
    ...
    /*get gpio*/
  	...
    /*request interrupt*/
   	...
    /*create timer*/
    ...
    /*request input_dev*/
    key.inputdev = input_allocate_device();
    key.inputdev->name = INPUTKEY_NAME;
#if 0
    /*init input_dev,set event*/
    __set_bit(EV_KEY, key.inputdev->evbit);//key event
    __set_bit(EV_REP, key.inputdev->evbit);//repeat event

    /*init input_dev,set keys*/
    __set_bit(KEY_0, key.inputdev->keybit);

#endif

#if 0 
    key.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    key.inputdev->keybit[bit_WORD(KEY_0)] |= bit_MASK(KEY_0);
#endif

    key.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(key.inputdev, EV_KEY, KEY_0);

    /*register input device*/
    ret = input_register_device(key.inputdev);
    if(ret)
    {
        printk(KERN_EMERG "register input device failed!\n");
        return ret;
    }
    return 0;
}

static int __init input_key_init(void)
{	
    printk(KERN_EMERG "input_key_init enter!\n"); 
    keyio_init();
    return 0;
	
}

static void __exit input_key_exit(void)
{ 
    unsigned int i =0;
    printk(KERN_EMERG "input_key_exit enter!\n");
    
    /*delete timer*/
    del_timer_sync(&key.timer);

    /*release interrupt*/
    for(i=0; i<KEY_NUM; i++)
    {
        free_irq(key.irqkeydesc[i].irqnum, &key);
    }

    /*release input_dev*/
    input_unregister_device(key.inputdev);
    input_free_device(key.inputdev);
}

APP部分

static struct input_event inputevent;

int main(int argc, char *argv[])
{
    int fd, retvalue;
	char *filename;
	unsigned char databuf[2];
	
	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打開led驅動 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

    while(1)
    {
        retvalue = read(fd, &inputevent, sizeof(inputevent));
        if(retvalue > 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");
                    }
                    break;
                case EV_REL:
                    break;
                case EV_ABS:
                    break;
                case EV_MSC:
                    break;
                case EV_SW:
                    break;
            }
        }
        else
        {
            printf("read error!\r\n");
        }
    }
    return 0;
}

代碼分析

驅動部分

在自定義的dev結構體中我們添加了一個input_dev類型的成員變量
驅動部分的代碼可以明顯看出與之前的字符設備驅動是有明顯的不同的,我們找不到之前的分配設備號、初始化cdev、註冊cdev、創建class、創建註冊、獲取GPIO這樣的流程
我們可以看到在Init函數中調用了keyio_init()函數,這個函數完成了與按鍵驅動相關的中斷初始化、定時器初始化以及INPUT設備的創建與初始化的過程,INPUT設備代替了我們之前的設備號、cdev、class、device、file_operations相關的操作,這就是Linux的驅動分層
在keyio_init中,我們首先完成了GPIO的獲取、中斷的申請以及定時器的創建與初始化,接着就進行INPUT設備相關的操作

  • 使用input_allocate_device();來分配一個input設備
  • 設置input設備的名字
  • 接下來就是使用了三種方法來置位evbit和keybit,表示我們的設備爲按鍵
  • 最後使用input_register_device來註冊一個input設備

當按鍵事件被觸發時,要完成上報操作,驅動要做如下的操作(在本例子中是在按鍵消抖的定時器回調函數中執行的)

  • 使用input_report_key來上報鍵值
  • 使用input_sync來進行一次同步

在exit函數中我們還要做如下的操作

  • 使用input_unregister_device來註銷input設備
  • 使用input_free_device來釋放input設備

應用部分

在應用程序的死循環中使用read函數來讀取input事件值,當input事件值的類型爲EV_KEY時進行打印

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章