Linux驅(qū)動(dòng)學(xué)習(xí)(四) input 子系統(tǒng)實(shí)驗(yàn)
前言
- 按鍵、鼠標(biāo)、鍵盤、觸摸屏等都屬于 input 設(shè)備,內(nèi)核專門設(shè)計(jì)了 input 子系統(tǒng)的框架來管理這些輸入設(shè)備;
- 輸入設(shè)備本質(zhì)上還是字符設(shè)備,只是在字符設(shè)備的基礎(chǔ)上又套了 input 框架;
- 用戶只需上報(bào)輸入事件(比如按鍵值、坐標(biāo)等信息),由 input 核心層負(fù)責(zé)處理這些事件;
input 子系統(tǒng)講解
簡(jiǎn)介
- input 子系統(tǒng)與 pinctl、gpio 子系統(tǒng)處于同一地位,都是 Linux 內(nèi)核針對(duì)某一類設(shè)備而設(shè)計(jì)的框架;顧名思義,input 子系統(tǒng)是管理輸入設(shè)備的;
- 不同輸入設(shè)備的事件信息含義不同,例如按鍵和鍵盤表示的是按鍵信息,鼠標(biāo)和觸摸屏表示的是坐標(biāo)信息,因此在應(yīng)用層的處理就不同;
- 我們做驅(qū)動(dòng)的,暫時(shí)不用操心應(yīng)用層的事情,只需要按照要求上報(bào)輸入設(shè)備的輸入事件即可;
- input 子系統(tǒng)框架又分為如下3層:
- input 驅(qū)動(dòng)層:輸入設(shè)備的具體驅(qū)動(dòng)程序,比如按鍵驅(qū)動(dòng)程序,向內(nèi)核層報(bào)告輸入內(nèi)容
- input 核心層:承上啟下,為驅(qū)動(dòng)層提供設(shè)備的注冊(cè)和操作接口;通知事件層對(duì)輸入事件進(jìn)行
處理; - input 事件處理層:主要和用戶空間進(jìn)行交互
- 通過編寫輸入設(shè)備的驅(qū)動(dòng),給用戶空間提供可訪問的設(shè)備節(jié)點(diǎn),input 子系統(tǒng)框架結(jié)構(gòu)圖如所示:

input 子系統(tǒng)結(jié)構(gòu)圖.png
- 左邊(硬件輸入設(shè)備)就是最底層的具體設(shè)備,比如按鍵、USB 鍵盤/鼠標(biāo)等;
- 中間部分屬于 Linux 內(nèi)核空間(分為驅(qū)動(dòng)層、核心層、事件處理層);
- 最右邊的就是用戶空間,所有輸入設(shè)備都會(huì)對(duì)應(yīng)一個(gè)設(shè)備訪問節(jié)點(diǎn),用戶以訪問文件的方式訪問設(shè)備節(jié)點(diǎn),進(jìn)而做一些數(shù)據(jù)處理;
- 我們編寫驅(qū)動(dòng)只需要關(guān)注驅(qū)動(dòng)層即可(核心層和事件處理層雖然不用修改,但也要理解)
驅(qū)動(dòng)編寫流程
- input 核心層會(huì)向內(nèi)核注冊(cè)一個(gè)字符設(shè)備;
- input 核心層源碼:
drivers/input/input.c
// input 子系統(tǒng)的所有設(shè)備主設(shè)備號(hào)都為 13, 已經(jīng)固定,不需要我們?cè)偃ピO(shè)置
#define INPUT_MAJOR 13 /* include/uapi/linux/major.h */
/**
* @brief 定義一個(gè) input 類
* @param name 名稱
* @param devnode 設(shè)備節(jié)點(diǎn)名
*/
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
EXPORT_SYMBOL_GPL(input_class);
/**
* @brief input 類初始化
* @note
*/
static int __init input_init(void)
{
int err;
/* 注冊(cè) input 類 */
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
/* 注冊(cè)一個(gè)字符設(shè)備,主設(shè)備號(hào)為 INPUT_MAJOR, 定義在 include/uapi/linux/major.h */
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;
}
- 如上代碼,注冊(cè)一個(gè) input 類,這樣系統(tǒng)啟動(dòng)以后就會(huì)在
/sys/class目錄下有一個(gè) input 子目錄; - 注冊(cè)一個(gè)字符設(shè)備,主設(shè)備號(hào)為 INPUT_MAJOR,固定值13,所以 input 子系統(tǒng)注冊(cè)的所有設(shè)備,其主設(shè)備號(hào)都是13,所以我們不再需要去注冊(cè)字符設(shè)備,只需注冊(cè) input_device 即可;
注冊(cè) input_dev
- 在使用 input 子系統(tǒng)框架時(shí),我們只需要注冊(cè)一個(gè) input device,非常省事兒;
- input_dev 定義如下,省略不關(guān)心的代碼:
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)]; /* 相對(duì)坐標(biāo)的位圖 */
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 絕對(duì)坐標(biāo)的位圖 */
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 雜項(xiàng)事件的位圖 */
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /* LED 相關(guān)的位圖 */
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* sound 有關(guān)的位圖 */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 壓力反饋的位圖 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /* 開關(guān)狀態(tài)的位圖 */
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
#define to_input_dev(d) container_of(d, struct input_dev, dev)
- 其中 evbit 表示輸入事件類型,可選的事件類型定義在 include/uapi/linux/input.h 文件
中,事件類型如下:
/*
* Event types
*/
#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按鍵事件 */
#define EV_REL 0x02 /* 相對(duì)坐標(biāo)事件 */
#define EV_ABS 0x03 /* 絕對(duì)坐標(biāo)事件 */
#define EV_MSC 0x04 /* 雜項(xiàng)(其他)事件 */
#define EV_SW 0x05 /* 開關(guān)事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(聲音) */
#define EV_REP 0x14 /* 重復(fù)事件 */
#define EV_FF 0x15 /* 壓力事件 */
#define EV_PWR 0x16 /* 電源事件 */
#define EV_FF_STATUS 0x17 /* 壓力狀態(tài)事件 */
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
- 比如我們使用的是按鍵,就注冊(cè) EV_KEY 事件,如果使用連按功能還需要注冊(cè) EV_REP 事件;
- evbit、keybit、relbit 等都是存放不同事件對(duì)應(yīng)的值,比如我們現(xiàn)在要使用按鍵事件,所以會(huì)用到 keybit,即按鍵事件使用的位圖;
- Linux 內(nèi)核定義了很多按鍵值,它們定義在 include/uapi/linux/input.h ,按鍵值如下所示:
/*
* Keys and buttons
*
* Most of the keys/buttons are modeled after USB HUT 1.12
* (see http://www.usb.org/developers/hidpage).
* Abbreviations in the comments:
* AC - Application Control
* AL - Application Launch Button
* SC - System Control
*/
#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 BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
- 我們可以將按鍵值設(shè)置為以上中的任意一個(gè),比如設(shè)置為 KEY_0
- 編寫 input 設(shè)備驅(qū)動(dòng)時(shí),首先需要定義 input_dev 結(jié)構(gòu)體變量,然后分配內(nèi)存(同理,使用完后,需要釋放內(nèi)存資源),函數(shù)接口如下:
struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
- 申請(qǐng)好 input_dev 之后,需要對(duì)其進(jìn)行初始化,初始化內(nèi)容主要有 事件類型(evbit)和事件值(keybit)這兩項(xiàng);
- 初始化 input_dev 之后,需要將其注冊(cè)到內(nèi)核中(同理,不使用的時(shí)候也要注銷),函數(shù)接口如下:
/**
* @brief 注冊(cè) input_dev
* @param dev 指向要進(jìn)行注冊(cè)的 input_dev
* @return 0,input_dev 注冊(cè)成功;負(fù)值,input_dev 注冊(cè)失敗
*/
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
- 綜上所述,input_dev 注冊(cè)過程如下:
①、使用 input_allocate_device 函數(shù)申請(qǐng)一個(gè) input_dev。
②、初始化 input_dev 的事件類型以及事件值。
③、使用 input_register_device 函數(shù)向 Linux 系統(tǒng)注冊(cè)前面初始化好的 input_dev。
④、卸載input驅(qū)動(dòng)的時(shí)候需要先使用 input_unregister_device 函數(shù)注銷掉注冊(cè)的 input_dev,
然后使用 input_free_device 函數(shù)釋放掉前面申請(qǐng)的 input_dev。input_dev 注冊(cè)過程示例代碼如下所示:
struct input_dev *inputdev; /* input 結(jié)構(gòu)體變量 */
/* 驅(qū)動(dòng)入口函數(shù) */
static int __init xxx_init(void)
{
......;
inputdev = input_allocate_device(); /* 申請(qǐng) input_dev */
inputdev->name = "test_inputdev"; /* 設(shè)置 input_dev 名字 */
/*********第一種設(shè)置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 設(shè)置產(chǎn)生按鍵事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重復(fù)事件 */
__set_bit(KEY_0, inputdev->keybit); /*設(shè)置產(chǎn)生哪些按鍵值 */
/************************************************/
/*********第二種設(shè)置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= T_MASK(KEY_0);
/************************************************/
/*********第三種設(shè)置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/
/* 注冊(cè) 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 */
}
上報(bào)輸入事件
- 注冊(cè)完 input_dev 后,還不能使用,因?yàn)?input 設(shè)備是具有輸入功能的,輸入值是什么還不確定;
- 我們需要獲取到具體的輸入值(或輸入事件),然后將輸入事件上報(bào)給內(nèi)核,比如按鍵,我們需要在按鍵中斷處理函數(shù)(或消抖定時(shí)器中斷函數(shù))中,將按鍵值上報(bào)給內(nèi)核,這樣內(nèi)核才能獲取到正確的輸入值;
- 不同事件,其上報(bào)事件的 API 函數(shù)不同,以下是一些常用的事件上報(bào)API函數(shù):
/**
* input_event() - report new input event
* @dev: device that generated the event -- 產(chǎn)生事件的設(shè)備
* @type: type of the event -- 事件類型,比如 EV_KEY
* @code: event code -- 事件碼,也就是我們注冊(cè)的按鍵值,比如 KEY_0
* @value: value of the event -- 事件值,比如 1 表示按鍵按下,0 表示按鍵松開
*
* This function should be used by drivers implementing various input
* devices to report input events. See also input_inject_event().
*
* NOTE: input_event() may be safely used right after input device was
* allocated with input_allocate_device(), even before it is registered
* with input_register_device(), but the event will not reach any of the
* input handlers. Such early invocation of input_event() may be used
* to 'seed' initial state of a switch or initial position of absolute
* axis, etc.
*/
void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value);
- input_event 函數(shù)可以上報(bào)所有的事件類型和事件值;
- 內(nèi)核也提供了其他針對(duì)具體事件的上報(bào)函數(shù),比如針對(duì)按鍵事件的上報(bào)函數(shù)(本質(zhì)上還是使用 input_event ):
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
- 同理,還有一些其他上報(bào)其他具體事件的函數(shù),如下:
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
void input_report_switch(struct input_dev *dev, unsigned int code, int value);
void input_mt_sync(struct input_dev *dev);
- 上報(bào)事件完成后,還需要使用 input_sync () 函數(shù)高速內(nèi)核 input 子系統(tǒng)上報(bào)結(jié)束,其本質(zhì)上是一個(gè)同步事件,函數(shù)原型:
void input_sync(struct input_dev *dev);
- 綜上所述,按鍵上報(bào)按鍵值得參考代碼如下:
/* 用于按鍵消抖的定時(shí)器服務(wù)函數(shù) */
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio); /* 讀取 IO 值 */
if (value == 0) /* 按下按鍵 */
{
/* 上報(bào)按鍵值 */
input_report_key(inputdev, KEY_0, 1); /* 最后一個(gè)參數(shù) 1 , 按下 */
input_sync(inputdev); /* 同步事件 */
}
else /* 按鍵松開 */
{
input_report_key(inputdev, KEY_0, 0); /* 最后一個(gè)參數(shù) 0 , 松開 */
input_sync(inputdev); /* 同步事件 */
}
}
input_event 結(jié)構(gòu)體
- Linux 內(nèi)核使用 input_event 這個(gè)結(jié)構(gòu)體來表示所有的輸入事件,定義在 include/uapi/linux/input.h 文件;
- tv_sec 和 tv_usec 這兩個(gè)成員變量都為 long 類型,也就是 32
位,這個(gè)一定要記住,后面我們分析 event 事件上報(bào)數(shù)據(jù)的時(shí)候要用到;
typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;
struct timeval
{
__kernel_time_t tv_sec; /* 秒 */
__kernel_suseconds_t tv_usec; /* 微秒 */
};
struct input_event
{
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
- input_evetn 這個(gè)結(jié)構(gòu)體非常重要,所有輸入設(shè)備都是按照 input_event 結(jié)構(gòu)體格式呈現(xiàn)給用戶的;用戶通過 input_event 來獲取具體的輸入事件和對(duì)應(yīng)的事件值(比如按鍵事件的按鍵值);
實(shí)驗(yàn)
待更新。。。