<輸入子系統簡介>
a:背景
內核的輸入子系統是對“分散的”,“多種不同類別”的輸入設備(鍵盤,鼠標,跟蹤桿,觸摸屏,加速度計等)進行“統一處理”的驅動程序。具有如下特點:
a-1:統一各種形態各異的相似的輸入設備的處理功能(鼠標,不論是PS/2形的鼠標,還是usb形式的鼠標,還是藍牙形式的鼠標),都做一樣的處理。
a-2:提供用於分發“輸入報告”給用戶應用程序的簡單事件(event)接口。(驅動程序
不必創建和管理/dev節點,以及相關的
訪問方法(fops))。因此能過很方便的調用API發送鼠標移動,鍵盤按鍵或觸摸屏事件給用戶空間。
a-3:抽取出輸入驅動的通用部分,簡化了驅動程序,並引入了一致性。(比如,輸入子系統提供了一個底層驅動程序(serio)的集合,支持對串口和鍵盤控制器等硬件輸入設備的訪問)
b:輸入子系統的組成示意圖

c:輸入子系統的事件處理機制示意圖

d:輸入子系統剖析

c-1:input 子系統調用過程分析
C-1-1.當外部應用程序需要調用輸入子系統的open函數時,會先通過主設備號進入到核心層,然后通過次設備號進入handler層,再調用.fops內的open函數返回fd;
C-1-
2.當外部應用程序需要調用輸入子系統的read函數時,會通過返回的fd調用.fop內的read函數,然后休眠,等待被.event函數喚醒
C-1-
3.當外部中斷到達的時候,會先確定中斷事件,然后用input_event上報事件,再通過h_list里面的所有handle調用對應的handler中的.event函數,對read進行喚醒,然后在read中返回(也就是當device有多個對應的handler的時候,input_event會向所有的handler上報事件)
C-1-
4.當需要加入新的handler時,需要先構建handler結構體,然后調用input_register_handler對該handler進行注冊
input_register_handler的內部實現:往input_handler_list加入新增的handler節點,然后對input_device_list的所有結點(也就是所有的device)進行遍歷,通過.id_table查看該device是否支持該handler,對支持的device調用.connect,一一地構建input_handle結構體,連接handler跟device
C-1-
5.當需要加入新的device時,需要先構建input_dev結構體,然后調用input_register_device對該input_dev進行注冊
input_register_dev的內部實現:往input_device_list加入新增的device節點,然后對input_handler_list的所有結點(也就是所有的handler)進行遍歷,通過handler 的.id_table查看該handler是否支持該device,對支持的device調用該handler的.connect,一一地構建input_handle結構體,連接handler跟device
在輸入子系統框架下,我們一般的編寫驅動也就是對device部分進行編寫(分配input_dev並配
<input輸入子系統數據結構分析一>
a:struct input_dev{}
struct input_dev { const char *name;//設備名稱 const char *phys;//設備在系統中的物理路徑 const char *uniq;//設備唯一識別符 struct input_id id;//設備ID,包含總線ID(PCI、USB)、廠商ID,與input_handler匹配的時會用到 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)];//支持的聲效事件 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//支持的力反饋事件 unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//支持的開關事件 unsigned int hint_events_per_packet;//事件生成的平均數量 unsigned int keycodemax;//keycode表的大小 unsigned int keycodesize;//keycode表中元素個數 void *keycode;//設備的鍵盤表 int (*setkeycode)(struct input_dev *dev,const struct input_keymap_entry *ke, unsigned int *old_keycode);//配置keycode表 int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke);//獲取keycode表 struct ff_device *ff;//力反饋設備結構 unsigned int repeat_key;//保存上一個鍵值 struct timer_list timer;//軟件計時器 int rep[REP_CNT];//autorepeat參數當前值 struct input_mt_slot *mt; int mtsize; int slot; int trkid; struct input_absinfo *absinfo;//絕對坐標軸的信息 unsigned long key[BITS_TO_LONGS(KEY_CNT)];//按鍵有兩種狀態,按下和抬起,這個字段就是記錄這兩個狀態。 unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //操作接口 int (*open)(struct input_dev *dev); void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab;//當前使用的handle spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; bool sync; struct device dev;//這個設備的驅動程序模型的視圖 struct list_head h_list;//h_list是一個鏈表頭,用來把handle掛載在這個上 struct list_head node;//這個node是用來連到input_dev_list上的 };
b:struct input_handler{}
struct input_handler { void *private;//驅動特有的數據 //當事件處理器接收到了來自input設備傳來的事件時調用的處理函數,負責處理事件 void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//該函數將被輸入子系統調用區處理發送給“設備”的事件。例如,發送一個事件命令led燈點亮,實際控制硬件的操作可以放在event()函數中實現 bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//事件過濾 bool (*match)(struct input_handler *handler, struct input_dev *dev); //當一個input設備模塊注冊到內核的時候調用的,將事件處理器與輸入設備聯系起來的函數, //也就是將input_dev和input_handler配對的函數 int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle);//實現connect相反的功能 void (*start)(struct input_handle *handle); const struct file_operations *fops;//文件操作函數集合 int minor;//次設備號 const char *name; const struct input_device_id *id_table;//事件處理器所支持的input設備 //這個鏈表用來鏈接他所支持的input_handle結構,input_dev與input_handler配對之后就會生成一個input_handle結構 struct list_head h_list; //鏈接到input_handler_list,這個鏈表鏈接了所有注冊到內核的事件處理器 struct list_head node; };
c:struct input_handle{}
struct input_handle { //每個配對的事件處理器都會分配一個對應的設備結構,如evdev事件處理器的evdev結構,注意這個結構與設備 //驅動層的input_dev不同,初始化handle時,保存到這里。 void *private; int open;//打開標志,每個input_handle打開后才能操作,這個一般通過事件處理器的open方法間接設置 const char *name; struct input_dev *dev;//關聯的input_dev結構 struct input_handler *handler;//關聯的input_handler結構 struct list_head d_node;//input_handle通過d_node連接到了input_dev上的h_list鏈表上 struct list_head h_node;//input_handle通過h_node連接到了input_handler的h_list鏈表上 };
c:三者之間的關系
input_dev,input_handler,input_handle,3者之間的關系
input_dev是硬件驅動層,代表一個input設備
input_handler是事件處理層,代表一個事件處理器
input_handle代表一個配對的input設備與input事件處理器input_dev通過全局的input_dev_list鏈接在一起。
設備注冊的時候實現這個操作。
input_handler通過全局的input_handler_list鏈接在一起。事件處理器注冊的時候實現這個操作.
input_hande沒有一個全局的鏈表,它注冊的時候將自己分別掛在了input_dev和input_handler的h_list上了。
通過input_dev和input_handler就可以找到input_handle在設備注冊和事件處理器,注冊的時候都要進行配對工作,
配對后就會實現鏈接。通過input_handle也可以找到input_dev和input_handler
<Input輸入子系統數據結構分析二>
input_dev
input_dev 這是input設備基本的設備結構,每個input驅動程序中都必須分配初始化這樣一個結構,成員比較多
A:有以下幾個數組:
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //事件支持的類型
A-1:下面是每種類型支持的編碼
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)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; evbit[BITS_TO_LONGS(EV_CNT)]; 這個數組以位掩碼的形式,代表了這個設備支持的事件的類型。
A-1-2:設置方式:
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS) absbit[BITS_TO_LONGS(ABS_CNT)]; 這個數組也是以位掩碼的形式,代表這個類型的事件支持的編碼觸摸屏驅動支持EV_ABS,所以要設置這個數組。
A-1-2-1: 有一個專門設置這個數組的函數input_set_abs_params()
static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat) { dev->absmin[axis] = min; dev->absmax[axis] = max; dev->absfuzz[axis] = fuzz; dev->absflat[axis] = flat; dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis); //填充了absbit這個數組 }
A-1-2-2:觸摸屏驅動中是這樣調用的
input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); //這個是設置ad轉換的x坐標 input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); //這個是設置ad轉換的y坐標input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); //這個是設置觸摸屏是否按下的標志 設置ABS_X編碼值范圍為0-0x3ff,因為mini2440的AD轉換出的數據最大為10位,所以不會超過0x3ff。
B:struct input_id id 成員
這個是標識設備驅動特征的
struct input_id { __u16 bustype; //總線類型 __u16 vendor; //生產廠商 __u16 product; //產品類型 __u16 version; //版本 };
如果需要特定的事件處理器來處理這個設備的話,這幾個就非常重要,因為子系統核心是通過他們,將設備驅動與事件處理層聯系起來的。但是因為觸摸屏驅動所用的事件處理器為evdev,匹配所有,所有這個初始化也無關緊要。
C:input_handler
input_handler 這是事件處理器的數據結構,代表一個事件處理器
C-1:幾個操作函數
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); void (*start)(struct input_handle *handle);
C-1-1:event 函數是當事件處理器接收到了來自input設備傳來的事件時調用的處理函數,負責處理事件,非常重要。
C-1-2:connect 函數是當一個input設備模塊注冊到內核的時候調用的,將事件處理器與輸入設備聯系起來的函數,也就是將input_dev和input_handler配對的函數。
C-1-3:disconnect 函數實現connect相反的功能。
D:兩個id
const struct input_device_id *id_table; //這個是事件處理器所支持的input設備 const struct input_device_id *blacklist; //這個是事件處理器應該忽略的input設備
D-1:這兩個數組都會用在connect函數中,input_device_id結構與input_id結構類似,但是input_device_id有一個flag,用來讓程序選擇比較哪項,如:busytype,vendor還是其他。
E:兩個鏈表
struct list_headh_list; //這個鏈表用來鏈接他所支持的input_handle結構,input_dev與input_handler配對之后就會生成一個input_handle結構 struct list_headnode; //鏈接到input_handler_list,這個鏈表鏈接了所有注冊到內核的事件處理器
F:input_handle
input_handle 結構體代表一個成功配對的input_dev和input_handler
struct input_handle { void *private; //每個配對的事件處理器都會分配一個對應的設備結構,如evdev事件處理器的evdev結構,注意這個結構與設備驅動層的input_dev不同,初始化handle時,保存到這里。 int open; //打開標志,每個input_handle 打開后才能操作,這個一般通過事件處理器的open方法間接設置 const char *name; struct input_dev *dev; //關聯的input_dev結構 struct input_handler *handler; //關聯的input_handler結構 struct list_head d_node; //input_handle通過d_node連接到了input_dev上的h_list鏈表上 struct list_head h_node; //input_handle通過h_node連接到了input_handler的h_list鏈表上 };
三個數據結構之間的關系
input_dev 是硬件驅動層,代表一個input設備
input_handler 是事件處理層,代表一個事件處理器
input_handle 屬於核心層,代表一個配對的input設備與input事件處理器
input_dev 通過全局的input_dev_list鏈接在一起。設備注冊的時候實現這個操作。
input_handler 通過全局的input_handler_list鏈接在一起。事件處理器注冊的時候實現這個操作(事件處理器一般內核自帶,一般不需要我們來寫)
input_hande 沒有一個全局的鏈表,它注冊的時候將自己分別掛在了input_dev 和 input_handler 的h_list上了。
通過input_dev 和input_handler就可以找到input_handle 在設備注冊和事件處理器, 注冊的時候都要進行配對工作,配對后就會實現鏈接。
通過input_handle也可以找到input_dev和input_handler。
G:補充兩個結構體
G-1:evdev設備結構
evdev結構體在配對成功的時候生成,由handler->connect生成,對應設備文件為/class/input/event(n)。
struct evdev { int exist; int open; //打開標志 int minor; //次設備號 struct input_handle handle; //關聯的input_handle wait_queue_head_t wait; //等待隊列,當進程讀取設備,而沒有事件產生的時候,進程就會睡在其上面 struct evdev_client *grab; //強制綁定的evdev_client結構,這個結構后面再分析 struct list_head client_list; //evdev_client 鏈表,這說明一個evdev設備可以處理多個evdev_client,可以有多個進程訪問evdev設備 spinlock_t client_lock; /* protects client_list */ struct mutex mutex; struct device dev; //device結構,說明這是一個設備結構 };
如觸摸屏驅動的event0,這個設備是用戶空間要訪問的設備,可以理解它是一個虛擬設備,因為沒有對應的硬件,但是通過handle->dev 就可以找到input_dev結構,而它對應着觸摸屏,設備文件為/class/input/input0。這個設備結構生成之后保存在evdev_table中,索引值是minor
G-2:evdev用戶端結構
struct evdev_client { struct input_event buffer[EVDEV_BUFFER_SIZE]; //這個是一個input_event數據結構的數組,input_event代表一個事件,基本成員:類型(type),編碼(code),值(value) int head; //針對buffer數組的索引 int tail; //針對buffer數組的索引,當head與tail相等的時候,說明沒有事件 spinlock_t buffer_lock; /* protects access to buffer, head and tail */ struct fasync_struct *fasync; //異步通知函數 struct evdev *evdev; //evdev設備 struct list_head node; // evdev_client 鏈表項 };
這個結構在進程打開event0設備的時候調用evdev的open方法,在open中創建這個結構,並初始化。在關閉設備文件的時候釋放這個結構。
H:各數據結構之間的關系

<輸入設備簡單實例>
a:代碼詳情


b:代碼分析
b-1:函數struct input_dev *input_allocate_device()


b-2:函數input_register_device()


input_register_device()函數是輸入子系統核心(input_core)提供的函數,用來將input_device注冊進入輸入子系統核心。
b-2-1:函數__set_bit()
第"07"行的該函數用來設置input_dev所支持的事件類型,事件類型由input_dev中的evbit成員表示。這里將EV_SYN置位(設備支持所有的事件)
b-2-2:一個input_dev可以支持許多事件,常用的如下:

b-2-3:
第"21"使用device_add()將內嵌的在input_dev中的device注冊到Linux的設備管理模型中
b-2-4:
第"33"調用list_add_tail()將input_dev加入到intput_handle中的input_dev_list鏈表中
b-2-5:
第"36"行input_attach_handler()用來匹配input_dev和handler,代碼詳情:


b-2-5-1:
第"3"行定義了input_device_id指針,該結構體在內核中的定義如下:

b-2-5-2:
第"5"行首先判斷handler->blacklist是否被賦值(blacklist是一個input_device_id類型的指針,其中存放了handler應該忽略的設備)
b-2-5-3:第"7"行調用函數input_match_device()函數用來對handler->id_table和dev->id進行匹配,如果成功就會調用函數handler->connect()將handler和input_dev連接起來。


總結:input_reigister_dev()函數的目的就是為了將輸入設備加進input_handle中的設備鏈表中,並和在input_handle中的handler鏈表進行匹配,如果匹配成功就通過connect()將兩者結合起來。
b-3:函數static inline void input_report_key(struct input_dev*dev,usigned int code,int value)

b-3-1:參數分析
dev:產生事件的設備
code:產生的事件(在input_dev初始化的時候,初始化相應的事件)
value:事件的值(針對不同的事件,這個值也有相應的變化)
b-3-2:函數input_event()

b-3-2-1:參數分析
dev:產生事件的設備
type:產生的事件類型(用來和支持的類型進行匹配)
code:產生的事件(在input_dev初始化的時候,初始化相應的事件)
value:事件的值(針對不同的事件,這個值也有相應的變化)
b-3-2-2:函數static inline int is_event_supported(unsignd int code ,unsigned long *bm,unsigned int max)

b-3-2-2-1:該函數檢查input_dev.evbit中相應的位是否設置,如果設置返回1,否者返回0.
b-3-2-2-2:evbit位圖
input_dev可以支持很多事件,通過evbit中的位圖來表示(evbit是一個long型變量,每一位表示一種事件,為1表示支持,反之不支持)

b-3-2-3:函數input_handle_event()




b-3-2-3-1:參數分析
dev:產生事件的設備
type:產生事件的類型
code:鍵碼
value:鍵值
b-3-2-3-2:主要關注"19-29"行,首先調用函數is_event_supported()函數判斷是否支持該按鍵,如果支持,則將變量disposition設置成INPUT_PASS_TO_HANDLERS,表示事件需要交給handler來進一步處理。dispositon取值有以下幾種:

b-3-2-3-3:函數input_pass_event()

b-3-2-3-3-1:第"4"行,分配一個input_handle結構指針
b-3-2-3-3-2:第"6"行,grab是強制為input device的handler
b-3-2-3-3-3:第"10-13"行,如果沒有為input_device強制指定handler,這將遍歷handle所有的handler,然后為其指定handler
-----------------------------------------------------------------------------------------------
input子系統剖析
<handler注冊分析>
a:簡介
input_handler用來對輸入的事件進行具體的處理(input_handler為輸入設備的功能實現接口,handler根據一定的規則,然后對事件進行處理)
b:輸入子系統的組成
輸入子系統由驅動層,輸入子系統核心層,和事件處理層.
c:handler數據結構
詳情見前文
d:注冊
handle,函數int input_register_handle(struct input_handler *handler)

d-1:第"3-4"行,從handle中取出一個指向input_handler和input_dev的指針
d_2:第"9,12"行,將handle分別加入到dev->h-list和handler->h_list鏈表中
<input子系統>
a:背景
到目前為止,系統已經做好了所有的與硬件相關的工作,但是作為一個驅動需要提供相應的設備操作接口,輸入子系統本身就是為了統一所有的輸入設備,所以相應的用戶空間接口統一在輸入子系統進行注冊。(注意:輸入子系統是一個
字符設備)
b:子系統初始化函數input_init()

b-1:"4"行,class_register()函數先注冊一個名為input的類,所有的input設備都屬於這個類,在文件系統中的表現形式就是所有的input_dev都在/dev/class/input 目錄下。
b-2:第"12"行,調用字符設備注冊函數register_chrdev()向Linux系統核心注冊"輸入字符設備",所有的輸入設備的主設備號為13.
input_fosp:

其中就一個input_open_file函數。該函數將控制轉到input_handler中定義的fops文件指針的open()函數。
b-2-1:input_handler中的input_open_file()

b-2-1-1:第"8"行,在分析open函數之前,解釋一下為什么要右移5位,這說明一個問題,次設備號的低5位被忽略,這說明evdev的最大支持的輸入設備驅動個數為2^5次方等於32個,你可能會看到你的/dev目錄下面有event0、event1、event2等設備,他們的次設備號分別為64、65、66等等。但最大是64+32-1,因此input_table為這些輸入設備增加的一個統一接口,通過上層打開設備時,只要次設備號在64+32-1之間的設備都會重新定位到evdev_handler中,即event*設備打開后執行的底層函數將被重新定義到evdev_handler中。
相信上面的問題已經描述清楚,如果還是不明白,你最起碼應該知道的是,input設備中的open函數只是一個接口,通過次設備號才找到了真正的事件處理接口。接下來要看新的open接口的實現了,evdev_handler-> fops->open
實現如下:
/*evdev字符設備驅動接口 */
static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, .poll = evdev_poll, .open = evdev_open, .release = evdev_release, .unlocked_ioctl = evdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = evdev_ioctl_compat, #endif .fasync = evdev_fasync, .flush = evdev_flush }; /*evdev設備open函數的實現過程 */ static int evdev_open(struct inode *inode, struct file *file) { struct evdev_client *client; struct evdev *evdev; /* 如果是event0,對於evdev設備來說,次設備號當然是0 */ int i = iminor(inode) - EVDEV_MINOR_BASE; int error; /* 如果大於32,說明超出了evdev能夠容納的最大輸入設備個數 */ if (i >= EVDEV_MINORS) return -ENODEV; /* 由於evdev中能容納32個輸入設備,因此通過設備號event0中的0定位到是要處理的是哪一個輸入設備,evdev_table中的內容在輸入設備驅動注冊時通過evdev_connect填充 */ evdev = evdev_table[i]; /* 判斷是否設備接口存在,evdev_exist也是在evdev_connect填充為1 */ if (!evdev || !evdev->exist) return -ENODEV; /* 存在則分配evdev中的client來處理event* */ client = kzalloc(sizeof(struct evdev_client),GFP_KERNEL); if (!client) return -ENOMEM; /* 把event*中的接口指向evdev_table中對應項 */ client->evdev = evdev; /* 把client->node鏈接到evdev子集中 */ list_add_tail(&client->node,&evdev->client_list); /* 如果open是第一個打開,則會執行input_open_device*/ if (!evdev->open++ &&evdev->exist) { error =input_open_device(&evdev->handle); if (error) { list_del(&client->node); kfree(client); return error; } } /* 將file私有指針指向client*/ file->private_data = client; return 0; }
由上的代碼可以看出,最終是要執行input_open_device去執行設備驅動程序中的代碼,然而我們在定義設備驅動的時候並沒有給input_dev中的open字段填充內容,因此可以看到input_open_device函數的執行過程:
if(!dev->users++ && dev->open)
err = dev->open(dev);
if (err)
handle->open--;
因為input子系統支持很多輸入設備,但是針對不同的輸入設備,用戶空間接口的具體操作應該不應.這樣索引到不同的handler,做不同的處理。
<其他>
input_register_handle()函數注冊一個input_handle
這個函數基本沒做什么事,就是把一個handle結構體通過d_node鏈表項,分別鏈接到input_dev的h_list,
input_handler的h_list上。以后通過這個h_list就可以遍歷相關的input_handle了
3、input_register_handler()函數注冊一個input_handler
這個函數其實和input_register_device類似,都是要注冊、配對
四、input輸入子系統核心層
1、input輸入子系統初始化定義在driver/input/input.c中,如下
這個函數主要是注冊了字符設備,這里和雜項設備的原理是一樣,所以input設備也是一類字符設備,只不過操作
方法交給了輸入子系統。從這里可以看出無論linux設備驅動這塊有多復雜,他們都是由一些基本的組件構成的
2.輸入子系統的核心其他部分都是提供的接口,向上連接事件處理層,向下連接驅動層。
向下對驅動層的接口主要有:
input_allocate_device這個函數主要是分配一個input_dev接口,並初始化一些基本的成員
input_unregister_device注冊一個input設備input_event這個函數很重要,是驅動層向input子系統核心報告事件
的函數。
input_allocate_device分配並初始化一個input_dev結構
向上對事件處理層接口主要有:
input_register_handler注冊一個事件處理器
input_register_handle注冊一個input_handle結構
五、事件處理層
事件處理層與用戶程序和輸入子系統核心打交道,是他們兩層的橋梁。一般內核有好幾個事件處理器,
像evdev、mousedev、jotdev。evdev事件處理器可以處理所有的事件,觸摸屏驅動就是用的這個。
1、evdev_init()事件處理層初始化
調用一個注冊handler函數,將evdev_handler注冊到系統中
2、主要的數據結構
1)evdev設備結構
int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; int error; /* * We take dev->mutex here to prevent race with * input_release_device(). */ error = mutex_lock_interruptible(&dev->mutex); if (error) return error; /* * Filters go to the head of the list, normal handlers * to the tail. */ if (handler->filter) //將handle的d_node,鏈接到其相關的input_dev的h_list鏈表中 list_add_rcu(&handle->d_node, &dev->h_list); else list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); /* * Since we are supposed to be called from ->connect() * which is mutually exclusive with ->disconnect() * we can't be racing with input_unregister_handle() * and so separate lock is not needed here. */ //將handle的d_node,鏈接到其相關的input_handler的h_list鏈表中 list_add_tail_rcu(&handle->h_node, &handler->h_list); if (handler->start) handler->start(handle); return 0; }
這個函數基本沒做什么事,就是把一個handle結構體通過d_node鏈表項,分別鏈接到input_dev的h_list,
input_handler的h_list上。以后通過這個h_list就可以遍歷相關的input_handle了
3、input_register_handler()函數注冊一個input_handler
int input_register_handler(struct input_handler *handler) { struct input_dev *dev; int retval; retval = mutex_lock_interruptible(&input_mutex); if (retval) return retval; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) { retval = -EBUSY; goto out; } input_table[handler->minor >> 5] = handler; } //連接到input_handler_list鏈表中 list_add_tail(&handler->node, &input_handler_list); //配對,遍歷input_dev,和注冊input_dev過程一樣的 list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); out: mutex_unlock(&input_mutex); return retval; }
這個函數其實和input_register_device類似,都是要注冊、配對
四、input輸入子系統核心層
1、input輸入子系統初始化定義在driver/input/input.c中,如下
static int __init input_init(void) { int err; //向內核注冊一個類,用於linux設備模型。注冊后會在/sys/class下面出現input目錄 err = class_register(&input_class); if (err) { pr_err("unable to register input_dev class\n"); return err; } //在/proc下創建入口項 err = input_proc_init(); if (err) goto fail1; //注冊字符設備,設備號INPUT_MAJOR為13,設備名為input err = register_chrdev(INPUT_MAJOR, "input", &input_fops); 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; } //子系統初始化時調用 subsys_initcall(input_init);
這個函數主要是注冊了字符設備,這里和雜項設備的原理是一樣,所以input設備也是一類字符設備,只不過操作
方法交給了輸入子系統。從這里可以看出無論linux設備驅動這塊有多復雜,他們都是由一些基本的組件構成的
2.輸入子系統的核心其他部分都是提供的接口,向上連接事件處理層,向下連接驅動層。
向下對驅動層的接口主要有:
input_allocate_device這個函數主要是分配一個input_dev接口,並初始化一些基本的成員
input_unregister_device注冊一個input設備input_event這個函數很重要,是驅動層向input子系統核心報告事件
的函數。
input_allocate_device分配並初始化一個input_dev結構
向上對事件處理層接口主要有:
input_register_handler注冊一個事件處理器
input_register_handle注冊一個input_handle結構
五、事件處理層
事件處理層與用戶程序和輸入子系統核心打交道,是他們兩層的橋梁。一般內核有好幾個事件處理器,
像evdev、mousedev、jotdev。evdev事件處理器可以處理所有的事件,觸摸屏驅動就是用的這個。
1、evdev_init()事件處理層初始化
static int __init evdev_init(void) { return input_register_handler(&evdev_handler); }
調用一個注冊handler函數,將evdev_handler注冊到系統中
2、主要的數據結構
1)evdev設備結構
struct evdev { int open;//打開標志 int minor;//次設備號 struct input_handle handle;//關聯的input_handle wait_queue_head_t wait;//等待隊列,當進程讀取設備,而沒有事件產生的時候,進程就會睡在其上面 struct evdev_client __rcu *grab;//強制綁定的evdev_client結構 struct list_head client_list;//evdev_client鏈表,這說明一個evdev設備可以處理多個evdev_client,可以有多個進程訪問evdev設備 spinlock_t client_lock; /* protects client_list */ struct mutex mutex; struct device dev;//device結構,說明這是一個設備結構 bool exist; };
如觸摸屏驅動的event0,這個設備是用戶空間要訪問的設備,可以理解它是一個虛擬設備,因為沒有對應的硬件,
但是通過handle->dev就可以找到input_dev結構,而它對應着觸摸屏,設備文件為/class/input/input0。這個設備
結構生成之后保存在evdev_table中,索引值是minor
2)evdev用戶端結構
struct evdev_client { unsigned int head;//針對buffer數組的索引 unsigned int tail;//針對buffer數組的索引,當head與tail相等的時候,說明沒有事件 unsigned int packet_head; /* [future] position of the first element of next packet */ spinlock_t buffer_lock; /* protects access to buffer, head and tail */ struct fasync_struct *fasync;//異步通知函數 struct evdev *evdev;//evdev設備 struct list_head node;//evdev_client鏈表項 unsigned int bufsize; struct input_event buffer[];//這個是一個input_event數據結構的數組,input_event代表一個事件,基本成員:類型(type),編碼(code),值(value) };
這個結構在進程打開event0設備的時候調用evdev的open方法,在open中創建這個結構,並初始化。在關閉設備文
件的時候釋放這個結構
3、主要的函數
1)evdev設備打開函數
static int evdev_open(struct inode *inode, struct file *file) { struct evdev *evdev; struct evdev_client *client; int i = iminor(inode) - EVDEV_MINOR_BASE; unsigned int bufsize; int error; if (i >= EVDEV_MINORS) return -ENODEV; error = mutex_lock_interruptible(&evdev_table_mutex); if (error) return error; //得到evdev設備結構,每次調用evdev_connect配對成功后都會把分配的evdev結構以minor為索引,保存在evdev_table數組中 evdev = evdev_table[i]; if (evdev) get_device(&evdev->dev);//增加device引用計數 mutex_unlock(&evdev_table_mutex); if (!evdev) return -ENODEV; bufsize = evdev_compute_buffer_size(evdev->handle.dev); //分配用戶端結構 client = kzalloc(sizeof(struct evdev_client) + bufsize * sizeof(struct input_event), GFP_KERNEL); if (!client) { error = -ENOMEM; goto err_put_evdev; } client->bufsize = bufsize; spin_lock_init(&client->buffer_lock); client->evdev = evdev;//使用戶端與evdev設備結構聯系起來 evdev_attach_client(evdev, client);//把client連接到evdev的client鏈表中 //打開設備 error = evdev_open_device(evdev); if (error) goto err_free_client; file->private_data = client; nonseekable_open(inode, file); return 0; err_free_client: evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); return error; }
2)evdev_open_device()函數
static int evdev_open_device(struct evdev *evdev) { int retval; retval = mutex_lock_interruptible(&evdev->mutex); if (retval) return retval; //判斷設備結構是否存在,在evdev_connect中初始話此成員為1 if (!evdev->exist) retval = -ENODEV; else if (!evdev->open++) {//evdev->open分配結構的時候沒有初始化,默認為0,也就是沒有打開,每次打開都會加1 retval = input_open_device(&evdev->handle); if (retval) evdev->open--; } mutex_unlock(&evdev->mutex); return retval; }
int input_open_device(struct input_handle *handle) { struct input_dev *dev = handle->dev; int retval; retval = mutex_lock_interruptible(&dev->mutex); if (retval) return retval; if (dev->going_away) { retval = -ENODEV; goto out; } //將handle的打開計數加1,注意和evdev的open的區別 handle->open++; //如果此input_dev沒有進程在引用,並且定義了open方法,就調用open方法 if (!dev->users++ && dev->open) retval = dev->open(dev); if (retval) {//retval=1說明沒有打開成功 dev->users--; if (!--handle->open) {//說明有其他的進程已經打開了這個handle /* * Make sure we are not delivering any more events * through this handle */ synchronize_rcu(); } } out: mutex_unlock(&dev->mutex); return retval; }
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { //這個客戶端結構在打開的時候分配並保存在file->private_data中 struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; int retval; //用戶進程每次讀取設備的字節數,不要少於input_event結構的大小 if (count < input_event_size()) return -EINVAL; //head等於tail說明目前還沒有事件傳回來,如果設置了非阻塞操作,則會立刻返回 if (client->packet_head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; //沒有事件就會睡在evdev的等待隊列上了,等待條件是有事件到來或者設備不存在了 retval = wait_event_interruptible(evdev->wait, client->packet_head != client->tail || !evdev->exist); if (retval) //如果能執行上面這條語句說明有事件傳來或者,設備被關閉了,或者內核發過來終止信號 return retval; if (!evdev->exist) return -ENODEV; while (retval + input_event_size() <= count && evdev_fetch_next_event(client, &event)) { //evdev_fetch_next_event這個函數遍歷client里面的input_eventbuffer數組 if (input_event_to_user(buffer + retval, &event))//將事件復制到用戶空間 return -EFAULT; retval += input_event_size(); } return retval; }
六、事件傳遞過程
1.事件產生
當按下觸摸屏時,進入觸摸屏按下中斷,開始ad轉換,ad轉換完成進入ad完成中斷,在這個中端中將事件發送出去,調用
input_report_abs(dev,ABS_X,xp);
input_report_abs(dev,ABS_Y,yp);
這兩個函數調用了input_event(dev,EV_ABS,code,value)
所有的事件報告函數都調用這個函數。
2、事件報告
1)input_event()函數
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { unsigned long flags; //判斷是否支持此種事件類型和事件類型中的編碼類型 if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); //對系統隨機熵池有貢獻,因為這個也是一個隨機過程 add_input_randomness(type, code, value); //事件處理函數 input_handle_event(dev, type, code, value); spin_unlock_irqrestore(&dev->event_lock, flags); } }
2)input_handle_event()函數
static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN: switch (code) { case SYN_CONFIG: disposition = INPUT_PASS_TO_ALL; break; case SYN_REPORT: if (!dev->sync) { dev->sync = true; disposition = INPUT_PASS_TO_HANDLERS; } break; case SYN_MT_REPORT: dev->sync = false; disposition = INPUT_PASS_TO_HANDLERS; break; } break; case EV_KEY: if (is_event_supported(code, dev->keybit, KEY_MAX) && !!test_bit(code, dev->key) != value) { if (value != 2) { __change_bit(code, dev->key); if (value) input_start_autorepeat(dev, code); else input_stop_autorepeat(dev); } disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_SW: if (is_event_supported(code, dev->swbit, SW_MAX) && !!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw); disposition = INPUT_PASS_TO_HANDLERS; } break; case EV_ABS: if (is_event_supported(code, dev->absbit, ABS_MAX)) disposition = input_handle_abs_event(dev, code, &value); break; case EV_REL: if (is_event_supported(code, dev->relbit, REL_MAX) && value) disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC: if (is_event_supported(code, dev->mscbit, MSC_MAX)) disposition = INPUT_PASS_TO_ALL; break; case EV_LED: if (is_event_supported(code, dev->ledbit, LED_MAX) && !!test_bit(code, dev->led) != value) { __change_bit(code, dev->led); disposition = INPUT_PASS_TO_ALL; } break; case EV_SND: if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) __change_bit(code, dev->snd); disposition = INPUT_PASS_TO_ALL; } break; case EV_REP: if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { dev->rep[code] = value; disposition = INPUT_PASS_TO_ALL; } break; case EV_FF: if (value >= 0) disposition = INPUT_PASS_TO_ALL; break; case EV_PWR: disposition = INPUT_PASS_TO_ALL; break; } if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) dev->sync = false; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS) input_pass_event(dev, type, code, value); }
這個函數主要是根據事件類型的不同,做相應的處理。disposition這個是事件處理的方式,默認的是
INPUT_IGNORE_EVENT,忽略這個事件,如果是INPUT_PASS_TO_HANDLERS則是傳遞給事件處理器,如果是
INPUT_PASS_TO_DEVICE,則是傳遞給設備處理。
3)input_pass_event()函數
static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handler *handler; struct input_handle *handle; rcu_read_lock(); //如果是綁定的handle,則調用綁定的handler->event函數 handle = rcu_dereference(dev->grab); if (handle) handle->handler->event(handle, type, code, value); else { bool filtered = false; //如果沒有綁定,則遍歷dev的h_list鏈表,尋找handle,如果handle已經打開,說明有進程讀取設備關聯的evdev list_for_each_entry_rcu(handle, &dev->h_list, d_node) { if (!handle->open) continue; handler = handle->handler; if (!handler->filter) { if (filtered) break; //調用相關的事件處理器的event函數,進行事件的處理 handler->event(handle, type, code, value); } else if (handler->filter(handle, type, code, value)) filtered = true; } } rcu_read_unlock(); }
4)evdev_event()函數
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { struct evdev *evdev = handle->private; struct evdev_client *client; struct input_event event; //將傳過來的事件,賦值給input_event結構 do_gettimeofday(&event.time); event.type = type; event.code = code; event.value = value; rcu_read_lock(); //如果evdev綁定了client那么,處理這個客戶端 client = rcu_dereference(evdev->grab); if (client) evdev_pass_event(client, &event); else //遍歷client鏈表,調用evdev_pass_event函數 list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_event(client, &event); rcu_read_unlock(); if (type == EV_SYN && code == SYN_REPORT) {//喚醒等待的進程 wake_up_interruptible(&evdev->wait); } }
static void evdev_pass_event(struct evdev_client *client, struct input_event *event) { /* Interrupts are disabled, just acquire the lock. */ spin_lock(&client->buffer_lock); //將事件賦值給客戶端的input_event數組 client->buffer[client->head++] = *event; client->head &= client->bufsize - 1; if (unlikely(client->head == client->tail)) { /* * This effectively "drops" all unconsumed events, leaving * EV_SYN/SYN_DROPPED plus the newest event in the queue. */ client->tail = (client->head - 2) & (client->bufsize - 1); client->buffer[client->tail].time = event->time; client->buffer[client->tail].type = EV_SYN; client->buffer[client->tail].code = SYN_DROPPED; client->buffer[client->tail].value = 0; client->packet_head = client->tail; } if (event->type == EV_SYN && event->code == SYN_REPORT) { client->packet_head = client->head; kill_fasync(&client->fasync, SIGIO, POLL_IN); } spin_unlock(&client->buffer_lock); }
可以看出,evdev_pass_event函數最終將事件傳遞給了用戶端的client結構中的input_event數組中,
只需將這個input_event數組復制給用戶空間,進程就能收到觸摸屏按下的信息了。具體處理由具體的應用程序來完成。