一、引言
在Linux驅動開發的學習過程中,Input子系統絕對是你繞不開的一道關卡。
在Linux系統中,不論是按鍵、滑鼠、鍵盤,亦或者是觸摸屏,統統都使用Input子系統來處理輸入事件。
二、Input子系統
1、Input子系統概述
Input就是輸入的意思,是以Input子系統就是管理輸入的系統,和Pinctrl、Gpio子系統一樣,都是Linux核心針對某一類裝置而建立的架構。
不同的輸入裝置在Input子系統所代表的含義不同,比如按鍵、鍵盤就是代表按鍵資訊,滑鼠和觸摸屏則是代表坐标資訊,是以對于不同的輸入裝置,在中間層或者應用層的處理方式就不一樣。對于驅動開發者,并不需要關心他們的處理方式,我們隻需要按照要求上報相應資訊即可。
2、Input子系統架構
根據Linux核心設計子系統的尿性(驅動分層模型),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); /* 同步事件 */
}
}