linux 輸入設備驅動


<輸入子系統簡介>
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設備結構
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結構,說明這是一個設備結構  

};  
evdev結構體在配對成功的時候生成,由handler->connect生成,對應設備文件/class/input/event(n)
 
  如觸摸屏驅動的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
    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;
    };
evdev結構體在配對成功的時候生成,由handler->connect生成,對應設備文件為/class/input/event(n),
如觸摸屏驅動的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;
    }
3)input_open_device()函數
    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;
    }
4)evdev_read()函數
    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);    
        }
    }
5)evdev_pass_event()函數
    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數組復制給用戶空間,進程就能收到觸摸屏按下的信息了。具體處理由具體的應用程序來完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM