天天看點

Linux驅動之Input子系統一、引言二、Input子系統三、Input驅動編寫方法

一、引言

在Linux驅動開發的學習過程中,Input子系統絕對是你繞不開的一道關卡。

在Linux系統中,不論是按鍵、滑鼠、鍵盤,亦或者是觸摸屏,統統都使用Input子系統來處理輸入事件。

二、Input子系統

1、Input子系統概述

Input就是輸入的意思,是以Input子系統就是管理輸入的系統,和Pinctrl、Gpio子系統一樣,都是Linux核心針對某一類裝置而建立的架構。

不同的輸入裝置在Input子系統所代表的含義不同,比如按鍵、鍵盤就是代表按鍵資訊,滑鼠和觸摸屏則是代表坐标資訊,是以對于不同的輸入裝置,在中間層或者應用層的處理方式就不一樣。對于驅動開發者,并不需要關心他們的處理方式,我們隻需要按照要求上報相應資訊即可。

2、Input子系統架構

根據Linux核心設計子系統的尿性(驅動分層模型),Input子系統被分為驅動層、核心層、事件處理層。

如下圖:

Linux驅動之Input子系統一、引言二、Input子系統三、Input驅動編寫方法

圖中左邊就是最底層的具體裝置,中間部分屬于核心空間,分為驅動層、核心層和事件處理層,最右邊則是使用者空間,所有的輸入裝置都以檔案的形式供使用者空間應用程式使用。

驅動層:輸入裝置的具體程式,比如按鍵驅動程式。

核心層:承上啟下,為驅動層提供輸入裝置注冊和操作接口,通知事件層對輸入事件進行處理。

事件處理層:主要和使用者空間進行互動。

三、Input驅動編寫方法

輸入裝置本質上就是一個字元裝置,隻是在此基礎上套上了Input子系統的架構。

是以編寫Input驅動就是通過調用Input子系統核心層提供的接口向Linux核心去注冊一個字元裝置驅動。

struct class input_class{
    .name = "input",
    .devnode = input_devnode,
};
……
static int __init input_init(void){
    int err;
    err = class_register(&input_class);
    if(err){
        pr_err(unable to register input_dev clsaa!\n);
        return err;
    }

    err = input_proc_init();
    if(err)
        goto fail1;

    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");
    if (err) {
        pr_err("unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

fail2: input_proc_exit();
fail1: class_unregister(&input_class);
    return err;
}
           

第8行,注冊了一個Input類,這樣系統啟動以後就會在/sys/class下有一個Input子目錄。

第18行,注冊了一個字元裝置,主裝置号為INPUT_MAJOR,INPUT_MAJOR在include/uapi/linux/major.h中定義,定義如下:

#define INPUT_MAJOR        13
           

是以,Inpur子系統的所有裝置主裝置号都為13,我們在使用Input子系統時去處理輸入裝置時就不需要去注冊字元裝置了,我們隻需要向系統注冊一個Input_device即可。

1、注冊Input_dev

在使用Input子系統時,我們需要注冊一個Input子裝置,Linux用Input_dev結構體來表示一個Input子裝置,Input_dev定義在include/linux/input.h中,如下:

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;
};
           

第9行,evbit表示輸入事件類型。前面說過,Input子系統是管理輸入的子系統,而在日常生活中,輸入裝置都很多,為了友善驅動開發者使用,Linux核心開發者定義了很多通用的輸入裝置類型。

他們被定義在include/uapi/linux/input.h中,如下:

#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 /* 壓力狀态事件 */
           

如果輸入裝置時鍵盤,則選擇按鍵事件;如果輸入裝置時顯示屏,則選擇絕對坐标事件。

繼續看到Input_dev結構體。第10行~17行,keybit、relbit等都是存放不同僚件的值。比如接下來用來舉例的代碼(用按鍵作為輸入事件)所用到的按鍵鍵值。

我們可以随便定義一個鍵值,也可以使用Linux定義的标準鍵值,标準鍵值定義在include/uapi/linux/input.h中。

接下來介紹注冊Input_dev需要用到的Input子系統核心層提供的接口。

1.1、input_allocate_device

input_allocate_device用來申請一個Input_dev結構體。

函數原型如下:

struct input_dev *input_allocate_device(void)
參數:無。
傳回值:申請到的 input_dev。
           

1.2、input_free_device

有申請自然就有釋放。

函數原型如下:

void input_free_device(struct input_dev *dev)
dev:需要釋放的 input_dev。
傳回值:無。
           

1.3、input_register_device

申請之後,需要注冊,否則核心,或者說Input子系統是不知道有新的Input子裝置存在的。

函數原型如下:

int input_register_device(struct input_dev *dev)
dev:要注冊的 input_dev 。
傳回值:0,input_dev 注冊成功;負值,input_dev 注冊失敗。
           

1.4、input_unregister_device

該接口用來告訴Input子系統,你要登出一個Input子裝置。

函數原型如下:

void input_unregister_device(struct input_dev *dev)
dev:要登出的 input_dev 。
傳回值:無。
           

綜上,注冊一個Input_dev的過程為:

①、使用input_allocate_device申請一個Input_dev結構體;

②、初始化Input_dev的事件類型和對應鍵值;

③、使用input_register_device向Linux注冊之前初始化好的Input_dev;

④、解除安裝驅動時,先調用input_unregister_device登出,再調用input_free_device釋放掉Input_dev。(注意,兩者順序不能弄反,負責會造成核心崩潰)

以下是注冊Input_dev的示例代碼:

struct input_dev *inputdev; /* input 結構體變量 */
 
/* 驅動入口函數 */
static int __init xxx_init(void)
{   ......
    inputdev = input_allocate_device(); /* 申請 input_dev */
    inputdev->name = "test_inputdev"; /* 設定 input_dev 名字 */
 
    /*********第一種設定事件和事件值的方法***********/
    __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);
    /************************************************/

    /*********第三種設定事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
    BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
    /************************************************/

    /* 注冊 input_dev */
    input_register_device(inputdev);
    ......
    return 0;
}

/* 驅動出口函數 */
static void __exit xxx_exit(void)
{
    input_unregister_device(inputdev); /* 登出 input_dev */
    input_free_device(inputdev); /* 删除 input_dev */
}
           

EV_KEY就是輸入事件類型(按鍵輸入),KEY_0就是對應支援的按鍵值。

這裡需要注意,你有多少按鍵值,你就需要設定多少,否則你是上報不上去的,沒有設定的按鍵值屬于非法值,在Input事件處理層就會被攔下。

2、上報輸入事件

當我們向核心注冊好Input_dev并不能就此高枕無憂了,因為我們僅僅是注冊了一個輸入裝置,對于使用者層來說,他們什麼都不知道。

是以,當輸入事件來臨時,核心就需要去處理并将其上報。

而核心對于不同的輸入事件類型有不同的上報接口。

2.1、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可以上報所有的事件類型和事件值。

2.2、input_report_key

函數原型:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) {
    input_event(dev, EV_KEY, code, !!value);
}
           

從代碼可以看出,該接口由input_event封裝而成,事實上,所有類型事件的專用上報接口都是由input_event接口封裝而成,不過,對于核心開發者而言,更推薦使用專用的接口。

2.3、input_sync

當我們上報一次事件之後,還需要使用該接口告訴Input子系統上報結束,input_sync本質上是上報一個同步事件。

接下來來看示例代碼:

void func(unsigned long arg)
{
    unsigned char value;
    value = gpio_get_value(keydesc->gpio); /* 讀取 IO 值 */
    if(value == 0){ /* 按下按鍵 */
        /* 上報按鍵值 */
        input_report_key(inputdev, KEY_0, 1); /* 最後一個參數 1,按下 */
        input_sync(inputdev); /* 同步事件 */
    } else { /* 按鍵松開 */
        input_report_key(inputdev, KEY_0, 0); /* 最後一個參數 0,松開 */
        input_sync(inputdev); /* 同步事件 */
    } 
}
           

繼續閱讀