輸入設備都有共性:中斷驅動+字符IO,基於分層的思想,Linux內核將這些設備的公有的部分提取出來,基於cdev提供接口,設計了輸入子系統,全部使用輸入子系統構建的設備都使用主設備號13,同時輸入子系統也支持自動建立設備文件,這些文件採用阻塞的IO讀寫方式,被建立在"/dev/input/"下。以下圖所示。內核中的輸入子系統自底向上分爲設備驅動層,輸入核心層,事件處理層。因爲每種輸入的設備上報的事件都各有不一樣,因此爲了應用層可以很好識別上報的事件,內核中也爲應用層封裝了標準的接口來描述一個事件,這些接口在"/include/upai/linux/input"中。node
input對象描述了一個輸入設備,包括它可能上報的事件,這些事件使用位圖來描述,內核提供的相應的工具幫助咱們構建一個input對象,你們能夠參考內核文檔"Documentation/input/input-programming.txt",裏面對於input子系統的使用有詳細的描述。linux
//input設備對象 121 struct input_dev { 122 const char *name; 129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; 130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; 131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; 132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; 134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; 135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; 136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; 137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; 155 162 unsigned long key[BITS_TO_LONGS(KEY_CNT)]; 163 unsigned long led[BITS_TO_LONGS(LED_CNT)]; 164 unsigned long snd[BITS_TO_LONGS(SND_CNT)]; 165 unsigned long sw[BITS_TO_LONGS(SW_CNT)]; 166 172 struct input_handle __rcu *grab; 179 180 struct device dev; 181 182 struct list_head h_list; 183 struct list_head node; 190 };
struct input_dev
--122--> 這個name不是設備名,input子系統的設備名在子系統源碼中指定的,不是這。
--129--> 設備支持的輸入事件位圖,EV_KEY,EV_REL, etc
--130--> 對於按鍵事件,設備支持的輸入子事件位圖
--132--> 對於相對座標事件,設備支持的相對座標子事件位圖
--133--> 對於絕對座標事件,設備支持的絕對座標子事件位圖
--134--> 混雜設備的支持的子事件位圖
--180-->表示這是一個device。
--182-->h_list是用來連接相關handle的鏈表
--183-->node用來連接其餘input_dev的鏈表api
//drivers/input/input.c //建立一個input對象 struct input_dev *input_allocate_device(void); //釋放一個input對象 void input_free_device(struct input_dev *dev);
初始化一個input對象是使用input子系統編寫驅動的主要工做,內核在頭文件"include/uapi/linux/input.h"中規定了一些常見輸入設備的常見的輸入事件,這些宏和數組就是咱們初始化input對象的工具。這些宏同時用在用戶空間的事件解析和驅動的事件註冊,能夠看做是驅動和用戶空間的通訊協議,因此理解其中的意義十分重要。在input子系統中,每個事件的發生都使用事件(type)->子事件(code)->值(value)三級來描述,好比,按鍵事件->按鍵F1子事件->按鍵F1子事件觸發的值是高電平1。注意,事件和子事件和值是相輔相成的,只有註冊了事件EV_KEY,才能夠註冊子事件BTN_0,也只有這樣作纔是有意義的。
下面就是內核約定的事件類型,對應應用層的事件對象的type域
數組
下面這些是按鍵子事件的類型,能夠看到對PC鍵值的定義
緩存
除了對經常使用的事件進行描述,內核一樣提供了工具將這些事件正確的填充到input對象中描述事件的位圖中。函數
//第一種 //這種方式很是適合同時註冊多個事件 button_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); button_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |BIT_MASK(BTN_RIGHT) |BIT_MASK(BTN_MIDDLE);
//第二種 //一般用於只註冊一個事件 set_bit(EV_KEY,button_dev.evbit); set_bit(BTN_0,button_dev.keybit);
初始化好了一個input對象,接下來就須要將其註冊到內核工具
//註冊input對象到內核 int input_register_device(struct input_dev *dev); //從內核註銷一個input對象 void input_unregister_device(struct input_dev *dev);
在合適的時機(因爲輸入最終是中斷表示的,因此一般在驅動的中斷處理函數中)驅動能夠將註冊好的事件上報,且能夠同時上報多個事件,下面是內核提供的API設計
//上報指定的事件+子事件+值 void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value); //上報鍵值 void input_report_key(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_rel(struct input_dev *dev,unsigned int code,int value); //同步全部的上報 void input_sync(struct input_dev *dev);
上報事件有2點須要注意:3d
事件處理層最終會將驅動sync一次時全部report的事件組織成一個struct input_value[]的形式上報到應用層,在應用層從相應的設備文件中獲取上報的事件的時候,須要注意:指針
前文已經說過,"include/uapi/linux/input.h"中的宏是應用層和驅動層共用的通訊協議,因此應用層在解析收到的struct input_value對象的時候,只須要"include <linux/input.h>"便可使用其中的宏。
/* * The event structure itself */ struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
上文已經說過,input子系統使用三層結構來實現驅動事件到應用層的傳遞。具體的,這三個層次每個層次都由一條結構體鏈表組成,在設備驅動層,核心結構體是input_dev;在input核心層,是input_handle;在事件處理層,是input_handler。內核經過鏈表和指針將三者結合到一塊兒,最終實現了input_dev和input_handler的多對多的映射關係,這種關係可用下圖簡單描述。
下面的這個模板首先使用input子系統上報按鍵事件,而後在應用層讀取。
/{
key@26{
compatible = "xj4412,key";
interrupt-parent = <&gpx1>;
interrupts = <2 2>;
};
};
static struct input_dev *button_dev; static int button_irq; static int irqflags; static irqreturn_t button_interrupt(int irq, void *dummy) { input_report_key(button_dev, BTN_0, 0); input_report_key(button_dev, BTN_0, 1); input_sync(button_dev); return IRQ_HANDLED; } static int button_init(void) { request_irq(button_irq, button_interrupt,irqflags, "button", NULL)) ; button_dev = input_allocate_device(); button_dev->name = "button"; button_dev->evbit[0] = BIT_MASK(EV_KEY); button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); input_register_device(button_dev); return 0; } static int button_exit(void) { input_free_device(button_dev); free_irq(button_irq, button_interrupt); return 0; } static int key_probe(struct platform_device *pdev) { struct resource *irq_res; irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if(irq_res){ button_irq = irq_res->start; irqflags = irq_res->flags & IRQF_TRIGGER_MASK; }else{ return -EINVAL; } return button_init(); } static int key_remove(struct platform_device *dev) { return button_exit(); } struct of_device_id of_tbl[] = { {.compatible = "xj4412,key",}, {}, }; MODULE_DEVICE_TABLE(of, of_tbl); struct platform_driver key_drv = { .probe = key_probe, .remove = key_remove, .driver.name = "keydrv", .driver.of_match_table = of_tbl, }; module_platform_driver_register(key_drv); MODULE_LICENSE("GPL");
#include <linux/input.h> struct input_event { struct timeval time; unsigned short type; unsigned short code; int value; }; int main(int argc, char * const argv[]) { int fd = 0; struct input_event event[3] = {0}; //3!!!,驅動上傳了2個事件,第三個用來裝空元素 int ret = 0; fd = open(argv[1],O_RDONLY); while(1){ ret = read(fd,&event,sizeof(event)); printf("ret:%d,val0:%d,val1:%d,val12:%d\n",ret,event[0].value,event[1].value,event[2].value); //2!!!,最後一個是空 sleep(1); } return 0; }