轉自:http://blog.csdn.net/lxl584685501/article/details/46379453html
[-]java
1.前提摘要node
(1)Sysfs文件系統 linux
內核設備模型主要的模塊和用戶之間能看到的相關部分就是sysfs文件系統了。內核在啓動的時候會註冊sysfs文件系統,而且在啓動系統的初期。經過mount命令掛載sysfs文件系統到/sys掛載點。android
Mount -t sysfs sysfs /syswindows
那麼sysfs文件系統的做用是什麼呢。歸納的說有三點:數組
1)、創建系統中總線、驅動、設備三者之間的橋樑網絡
2)、像用戶空間展現內核中各類設備的拓撲圖併發
3)、提供給用戶空間對設備獲取信息和操做的接口,部分取代ioctl功能。
(2)Kobject:Sysfs文件系統中最基本的結構就是kobject,kobject能夠表明一個設備,一條總線等。在sys目錄下直觀的以一個目錄表示出來。框架
(3)Uevent機制
上面的分析其實只是對Linux設備模型作了一些基礎性的瞭解。也就是一個穿針引線的做用,若是要細緻瞭解,須要仔細閱讀代碼。有了上面對於sysfs的基礎。接下來咱們來比較詳細的瞭解一下uevent機制。
什麼是uevent機制。這個得從熱插拔設備開始提及。最簡單的一個例子就是U盤了。當咱們在計算機上插上一個U盤的時候,系統的USB hub會檢測到U盤設備接入,而且完成設備枚舉過程(從設備上讀出相應的設備信息),並在內核中建立相應的設備結構體。可是,usb設備千奇百態,內核不可能預先將全部usb設備驅動都增長到內存中來。也就是當插入U盤設備的時候,內核中不必定存在對應這個設備的usb驅動。這個時候USB驅動也許以模塊的形式保存在硬盤上。載入驅動必然只能從用戶態來進行,那這時候應該怎麼辦呢?
看到這裏的時候,有人必定會想,人工敲入命令載入驅動,呵呵。這必然是一種方法,可是是一種很古老的方法。Linux對相似的狀況設計了一種uevent的機制。當有新的設備加入的時候,將設備的信息發送消息到用戶態。而用戶態有一個udev的進程監聽這個信息。當收到信息後作必定的解析,根據解析到的結果和用戶程序的配置作一些處理,也包括加載驅動程序。
2.具體介紹(http://www.wowotech.net/linux_kenrel/uevent.html)
Uevent是Kobject的一部分,用於在Kobject狀態發生改變時,例如增長、移除等,通知用戶空間程序。
用戶空間程序收到這樣的事件後,會作相應的處理。
該機制一般是用來支持熱拔插設備的,例如U盤插入後,USB相關的驅動軟件會動態建立用於表示該U盤的device結構(相應的也包括其中的kobject),並告知用戶空間程序,爲該U盤動態的建立/dev/目錄下的設備節點,
更進一步,能夠通知其它的應用程序,將該U盤設備mount到系統中,從而動態的支持該設備。
由此可知,Uevent的機制是比較簡單的,設備模型中任何設備有事件須要上報時,會觸發Uevent提供的接口。Uevent模塊準備好上報事件的格式後。
能夠經過兩個途徑把事件上報到用戶空間:一種是經過kmod模塊,直接調用用戶空間的可執行文件;
另外一種是經過netlink通訊機制,將事件從內核空間傳遞給用戶空間。
注1:有關kmod和netlink,會在其它文章中描述,所以本文就再也不詳細說明了。
Uevent的代碼比較簡單,主要涉及kobject.h和kobject_uevent.c兩個文件,以下:
前面有提到過,在利用Kmod向用戶空間上報event事件時,會直接執行用戶空間的可執行文件。而在Linux系統,可執行文件的執行,依賴於環境變量,所以kobj_uevent_env用於組織這次事件上報時的環境變量。
說明:怎麼指定處理uevent的用戶空間程序(簡稱uevent helper)?
上面介紹kobject_uevent_env的內部動做時,有提到,Uevent模塊經過Kmod上報Uevent時,會經過call_usermodehelper函數,調用用戶空間的可執行文件(或者腳本,簡稱uevent helper )處理該event。而該uevent helper的路徑保存在uevent_helper數組中。
能夠在編譯內核時,經過CONFIG_UEVENT_HELPER_PATH配置項,靜態指定uevent helper。但這種方式會爲每一個event fork一個進程,隨着內核支持的設備數量的增多,這種方式在系統啓動時將會是致命的(能夠致使內存溢出等)。所以只有在早期的內核版本中會使用這種方式,如今內核再也不推薦使用該方式。所以內核編譯時,須要把該配置項留空。
在系統啓動後,大部分的設備已經ready,能夠根據須要,從新指定一個uevent helper,以便檢測系統運行過程當中的熱拔插事件。這能夠經過把helper的路徑寫入到"/sys/kernel/uevent_helper」文件中實現。實際上,內核經過sysfs文件系統的形式,將uevent_helper數組開放到用戶空間,供用戶空間程序修改訪問,具體可參考"./kernel/ksysfs.c」中相應的代碼,這裏再也不詳細描述。
本文章講解插入headphone的時候,向上層上報event函數的整個過程
#ifdef CONFIG_I_LOVE_PBJ30
void headphone_event(int state)
{
switch_set_state(&wired_switch_dev, state);
}
EXPORT_SYMBOL_GPL(headphone_event);
#endif
headphone_event 函數會調用switch_set_state函數進行上報事件
接下來會調用kobject_uevent_env函數進行上報事件。
最終調用add_uevent_var()將用戶空間須要的參數添加到環境變量中去,如
retval = add_uevent_var(env,"ACTION=%s", action_string);
if (retval)
goto exit;
retval = add_uevent_var(env,"DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env,"SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
4.實例分析2
Uevent 是內核通知Android有狀態變化的一種方法,好比USB線插入、拔出,電池電量變化等等。其本質是內核發送(能夠經過socket)一個字符串,應用層(android)接收並解釋該字符串,獲取相應信息。
(一)、Kernel側:UEVENT的發起在Kernel端,主要是經過函數
intkobject_uevent_env(struct kobject*kobj, enum kobject_action action,char*envp_ext[])
該函數的主要功能是根據參數組合一個字符串併發送。一個典型的字符串以下:change@/devices/platform/msm-battery/power_supply/usb紘ACTION=change紘DEVPATH=/devices/platform/msm-battery/power_supply/usb紘SUBSYSTEM=power_supply紘POWER_SUPPLY_NAME=usb紘POWER_SUPPLY_ONLINE=0紘SEQNUM=1486紘
上面這塊來自網上,這段內容是否有問題,待考究。
下面看這個函數: int kobject_uevent_env(structkobject *kobj, enum kobject_action action,char *envp_ext[])
static const char *kobject_actions[] ={
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
};
//以上爲kobject標準的動做,調用時須要傳入相應的enum值
///如下是獲取subsystem信息
if (uevent_ops&&uevent_ops->name)
subsystem =uevent_ops->name(kset, kobj);
else
subsystem =kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p):%s: unset subsystem caused the "
"event to drop!\n",kobject_name(kobj), kobj,
__func__);
return 0;
}
//下面準備要傳遞的信息數據
retval = add_uevent_var(env, "ACTION=%s",action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s",devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s",subsystem);
if (retval)
goto exit;
//envp_ext[i]是傳進來的參數,爲該event時攜帶的一些自定義的信息
if (envp_ext) {
for (i = 0; envp_ext[i]; i++){
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
//下面經過網絡socket將數據發送出去
mutex_lock(&uevent_sock_mutex);
list_for_each_entry(ue_sk,&uevent_sock_list, list) {
struct sock *uevent_sock =ue_sk->sk;
struct sk_buff*skb;
size_t len;
NETLINK_CB(skb).dst_group =1;//下面開始發送數據
retval =netlink_broadcast_filtered(uevent_sock, skb,
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
}
(二)、Android側:
private finalUEventObserver mUEventObserver = newUEventObserver(){
@Override
public void onUEvent(UEventObserver.UEventevent) {
if(DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
String state =event.get("USB_STATE");
String accessory =event.get("ACCESSORY");
//Added for USB Develpment debug, more logfor more debuging help
if(DEBUG) Log.w(TAG, "mUEventObserver:onUEvent: state = " + state);
//Added for USB Develpment debug, more logfor more debuging help
if(state != null) {
mHandler.updateState(state);
}else if ("START".equals(accessory)) {
if(DEBUG) Slog.d(TAG, "got accessory start");
setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY,false);
}
}
};
/////在類初始化時會調用下面的動做,啓動監聽動做。
mUEventObserver.startObserving(USB_STATE_MATCH);
//////最終會調用到UEventObserver的addObserver:
privateArrayList<Object> mObservers = newArrayList<Object>();
public voidaddObserver(String match, UEventObserver observer) {
synchronized(mObservers){
mObservers.add(match);
mObservers.add(observer);
}
}
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
該函數最終會將」DEVPATH=/devices/virtual/android_usb/android0」增長到匹配序列中,當kernel發送具備該字符串的數據時,就返回匹配成功,而後調用mUEventObserver的onUEvent函數;
UeventObserver.Java
private static class UEventThread extendsThread {
privateArrayList<Object> mObservers = newArrayList<Object>();
UEventThread() {
super("UEventObserver");
}
public void run() {
native_setup();
byte[] buffer = new byte[1024];
int len;
while (true) {
len = next_event(buffer);
if(len > 0) {
String bufferStr = new String(buffer, 0,len); // easier to search a String
synchronized (mObservers) {
for (int i = 0; i <mObservers.size(); i += 2) {
if(bufferStr.indexOf((String)mObservers.get(i)) != -1) {
((UEventObserver)mObservers.get(i+1))
.onUEvent(newUEvent(bufferStr));
}
}
refer: http://www.cnblogs.com/myblesh/articles/2367648.html
http://blog.csdn.NET/bingqingsuimeng/article/details/7950543
//1. Input 子系統--概述
Android、X windows、qt等衆多應用對於linux系統中鍵盤、鼠標、觸摸屏等輸入設備的支持都經過、或愈來愈傾向於標準的input輸入子系統。
由於input子系統已經完成了字符驅動的文件操做接口,因此編寫驅動的核心工做是完成input系統留出的接口,工做量不大。但若是你想更靈活的應用它,就須要好好的分析下input子系統了。
1. 輸入子系統由驅動層、輸入子系統核心、事件處理層三部分組成:
一個輸入事件,如鼠標移動、鍵盤按下等經過Driver->Inputcore->Event handler->userspace的順序到達用戶控件的應用程序。
2. input子系統仍然是字符設備驅動程序,可是代碼量減小不少,input子系統只須要完成兩個工做:初始化和事件報告。
3. (1)上報的大體過程:設備驅動層->核心層->事件處理層->應用層
(2)具體調用的函數(以evdev爲例):
input_event()->input_handle_event() ->input_pass_event() ->handle->handler->event(handle,type, code, value)
->evdev_event() ->evdev_pass_event() ,
而後經過client->buffer[client->head++]= *event賦值給上層client(是struct evdev_client)//2. Input 子系統之一--框架結構(初始化)
1. 第一層
===============================================================================
用戶空間訪問 <User space> 設備節點訪問(略)
@@@@@@xx_test.c
2. 第二層
===============================================================================
事件處理層<Event Handler>
/*主要是和用戶空間交互。
(Linux中在用戶空間將全部的設備都當初文件來處理,因爲在通常的驅動程序中都有提供fops接口,以及在/dev下生成相應的設備文件nod,這些操做在輸入子系統中由事件處理層完成)*/
@@@@@@evdev.c等。(以evdev_handler爲例)
(1)===>>module_init(evdev_init); module_exit(evdev_exit);
(2)===>>static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);///
/*static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops, ////evdev的文件操做方法
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,*/
};
}
(3)===>>int input_register_handler(struct input_handler *handler)//註冊一個新的event handler
{
struct input_dev *dev;
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node);
input_attach_handler(dev, handler);////將這個新的event handler 與其兼容的input dev綁定在一塊兒
}
(4)===>>static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
id = input_match_device(handler, dev);//匹配規則:Input_dev和input_handler匹配後調用input_handler的connect。
error = handler->connect(handler, dev, id);//
}
(5)===>>建立新的evdev字符設備節點
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
for (minor = 0; minor < EVDEV_MINORS; minor++)//尋找未使用的minor
if (!evdev_table[minor])
break;
dev_set_name(&evdev->dev, "event%d", minor);
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//建立字符設備節點event%d
}
3. 第三層
===============================================================================
核心層<Input Core>
/*承上啓下。爲驅動層提供輸入設備註冊與操做接口,如:input_register_device;通知事件處理層對事件進行處理;在/Proc下產生相應的設備信息*/
@@@@@@input.c
(1)===>>subsys_initcall(input_init); module_exit(input_exit);
(2)===>>static int __init input_init(void)
{
//建立 sysfs 系統文件/proc/bus/input:devices && handlers
err = class_register(&input_class);/// struct class input_class = {
.name = "input",
.devnode = input_devnode,//kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));
};
//建立proc系統文件/proc/bus/input:devices && handlers
err = input_proc_init();
if (err)
goto fail1;
//註冊字符設備的文件操做input_fops
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,///
.llseek = noop_llseek,}
}
(3)===>>static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5];
if (handler)
new_fops = fops_get(handler->fops);
file->f_op = new_fops;/////根據傳入的inode節點(即:event handler),設置相應的文件操做方法
err = new_fops->open(inode, file);////打開文件操做方法
}
4. 第四層
===============================================================================
設備驅動層<Input driver>(略)
/*將底層的硬件輸入轉化爲統一事件形式,想輸入核心(Input Core)彙報。*/
/*實現設備驅動核心工做是:向系統報告按鍵、觸摸屏等輸入事件(event,經過input_event結構描述),再也不須要關心文件操做接口。
驅動報告事件通過inputCore和Eventhandler到達用戶空間。*/
@@@@@@mtk_tpd.c && ft5206_driver.c
===============================================================================
/////3. Input 子系統之二--使用方式1:來自驅動層(硬件出發,如觸屏等)向上層彙報事件
1. 在第四層:Input driver中須要創建一個input設備以供使用(分配、註冊、註銷input設備)
@@@mtk_tpd.c
/* global variable definitions */----------必加內容1
struct tpd_device *tpd = 0;
static int tpd_probe(struct platform_device *pdev)
{
if((tpd=(struct tpd_device*)kmalloc(sizeof(struct tpd_device), GFP_KERNEL))==NULL) return -ENOMEM;
memset(tpd, 0, sizeof(struct tpd_device));
/* allocate input device */
if((tpd->dev=input_allocate_device())==NULL) { kfree(tpd); return -ENOMEM; }----------必加內容2
/*設置input設備支持的事件類型、事件碼、事件值的範圍、input_id等信息*/
/*一個設備能夠支持一個或多個事件類型。每一個事件類型下面還須要設置具體的觸發事件碼。好比:EV_KEY事件,須要定義其支持哪些按鍵事件碼。*/
/*事件類型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
而當事件類型爲EV_KEY時,按鍵類型keybit包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等
*/
set_bit(ABS_MT_TRACKING_ID, tpd->dev->absbit);
set_bit(ABS_MT_TOUCH_MAJOR, tpd->dev->absbit);
set_bit(ABS_MT_TOUCH_MINOR, tpd->dev->absbit);
set_bit(ABS_MT_POSITION_X, tpd->dev->absbit);
set_bit(ABS_MT_POSITION_Y, tpd->dev->absbit);
input_set_abs_params(tpd->dev, ABS_MT_POSITION_X, 0, TPD_RES_X, 0, 0);
input_set_abs_params(tpd->dev, ABS_MT_POSITION_Y, 0, TPD_RES_Y, 0, 0);
input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0);
input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MINOR, 0, 100, 0, 0);
if(input_register_device(tpd->dev))----------必加內容3
TPD_DMESG("input_register_device failed.(tpd)\n");
else
tpd_register_flag = 1;
初始化函數定義了input設備struct tpd_device *tpd結構體,它用於描述一個輸入子系統設備。
任何驅動設備若是想標明本身是輸入設備,都應該經過初始化這樣的結構體,而且調用input_allocate_device()函數進行註冊。這個函數的功能是爲新添加的輸入設備分配內存,若是成功,將返回input_dev *的指針結構,所以在寫驅動的時候應該接受返回值,做爲驅動層得到了一個新的輸入設備操做的接口。經過input_allocate_device()函數,咱們設備驅動如今持有的input_dev裏面就被賦予了input的「形象」,可是還須要咱們去充實一下「內在」,所以,設備驅動程序,還須要爲本身的設備增長本身的特性,才能創造獨有的設備「形象」。
input_allocate_device這部分完成了輸入設備的初始化工做。可是這僅是初始化本身的「特色」,還須要通知輸入子系統有這樣一個新設備誕生了,這就須要調用輸入子系統的註冊函數input_register_device(來完成。input_register_device()用於註冊一個輸入設備。那麼註冊過程是怎樣的呢?這是一個重點,在下面的代碼中進行註釋分析:tpd)
1 int input_register_device(struct input_dev *dev) 2 { 3 /* 用於記錄輸入設備名稱的索引值 */ 4 static atomic_t input_no = ATOMIC_INIT(0); 5 /* 輸入事件的處理接口指針,用於和設備的事件類型進行匹配 */ 6 struct input_handler *handler; 7 const char *path; 8 int error; 9 10 /* 默認全部的輸入設備都支持EV_SYN同步事件 */ 11 set_bit(EV_SYN, dev->evbit); 12 13 /* 14 * 若是設備驅動沒有指定重複按鍵(連擊),系統默認提供如下的支持 15 * 其中init_timer爲連擊產生的定時器,時間到調用input_repeat_key函數 16 * 上報,REP_DELAY用於設置重複按鍵的鍵值,REP_PERIOD設置延時時間 17 */ 18 init_timer(&dev->timer); 19 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { 20 dev->timer.data = (long) dev; 21 dev->timer.function = input_repeat_key; 22 dev->rep[REP_DELAY] = 250; 23 dev->rep[REP_PERIOD] = 33; 24 } 25 26 /* 若是設備驅動沒有設置本身的獲取鍵值的函數,系統默認 */ 27 if (!dev->getkeycode) 28 dev->getkeycode = input_default_getkeycode; 29 30 /* 若是設備驅動沒有指定按鍵重置函數,系統默認 */ 31 if (!dev->setkeycode) 32 dev->setkeycode = input_default_setkeycode; 33 34 /* 重要,把設備掛到全局的input子系統設備鏈表input_dev_list上 */ 35 list_add_tail(&dev->node, &input_dev_list); 36 37 /* 動態獲取input設備的ID號,名稱爲input*,其中後面的「*」動態得到,惟一的 */ 38 snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id), 39 "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); 40 41 /* 若是這個值沒有設置,系統把輸入設備掛入設備鏈表 */ 42 if (!dev->cdev.dev) 43 dev->cdev.dev = dev->dev.parent; 44 45 /* 在/sys目錄下建立設備目錄和文件 */ 46 error = class_device_add(&dev->cdev); 47 if (error) 48 return error; 49 50 /* 獲取並打印設備的絕對路徑名稱 */ 51 path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL); 52 printk(KERN_INFO "input: %s as %s\n", 53 dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); 54 kfree(path); 55 56 /* 核心重點,input設備在增長到input_dev_list鏈表上以後,會查找 57 * input_handler_list事件處理鏈表上的handler進行匹配,這裏的匹配 58 * 方式與設備模型的device和driver匹配過程很類似,全部的input 59 * 都掛在input_dev_list上,全部類型的事件都掛在input_handler_list 60 * 上,進行「匹配相親」*/ 61 list_for_each_entry(handler, &input_handler_list, node) 62 input_attach_handler(dev, handler); 63 64 input_wakeup_procfs_readers(); 65 66 return 0; 67 }上面的代碼主要的功能有如下幾個功能,也是設備驅動註冊爲輸入設備委託內核作的事情:
咱們須要再分析下這個匹配的過程,可是須要注意的是下面分析的代碼是咱們暫時沒法分析的,由於那樣會使得狀況變得更加複雜,當咱們從應用層往下分析的時候一切都會明白。input_attach_handler匹配過程以下:
1 const struct input_device_id *id; 2 int error; 3 4 /* 若是handler的blacklist被賦值了而且則優先匹配 */ 5 if (handler->blacklist && input_match_device(handler->blacklist, dev)) 6 return -ENODEV; 7 8 /* 不然利用handler->id_table和dev進行匹配,後面講述匹配什麼和過程 */ 9 id = input_match_device(handler->id_table, dev); 10 if (!id) 11 return -ENODEV; 12 13 /* 這是一根「紅線」,雖然你可能覺的是黑色的,但不能否認,他們真的匹配上了 14 * 調用handler->connnect函數進行匹配,匹配詳細過程後面講述 15 */ 16 error = handler->connect(handler, dev, id); 17 if (error && error != -ENODEV) 18 printk(KERN_ERR 19 "input: failed to attach handler %s to device %s, " 20 "error: %d\n", 21 handler->name, kobject_name(&dev->cdev.kobj), error); 22 23 return error;
先來看下input_match_device()函數,看一下這個匹配的條件是什麼,如何匹配的過程是怎樣的,匹配的結果會是什麼?
1 /* 事件處理層中的對應flags若是設置或者driver_info被設置則進行匹配 */ 2 for (; id->flags || id->driver_info; id++) { 3 /* 如下經過flags中設置的位來匹配設備的總線類型、經銷商、生產ID和版本ID 4 若是沒有匹配上將進行MATCH_BIT匹配 */ 5 if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) 6 if (id->bustype != dev->id.bustype) 7 continue; 8 9 if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) 10 if (id->vendor != dev->id.vendor) 11 continue; 12 13 if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) 14 if (id->product != dev->id.product) 15 continue; 16 17 if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) 18 if (id->version != dev->id.version) 19 continue; 20 21 /* MATCH_BIT用於匹配設備驅動中是否設置了這些爲,MATCH_BIT的宏 22 * 被定義在input.c中,咱們在設備驅動中設置的事件類型會與事件鏈表中的 23 * 全部事件類型進行比較,匹配成功了將返回id,證實真的很合適,不然NULL 24 */ 25 MATCH_BIT(evbit, EV_MAX); 26 MATCH_BIT(keybit, KEY_MAX); 27 MATCH_BIT(relbit, REL_MAX); 28 MATCH_BIT(absbit, ABS_MAX); 29 MATCH_BIT(mscbit, MSC_MAX); 30 MATCH_BIT(ledbit, LED_MAX); 31 MATCH_BIT(sndbit, SND_MAX); 32 MATCH_BIT(ffbit, FF_MAX); 33 MATCH_BIT(swbit, SW_MAX); 34 35 return id; 36 } 37 38 return NULL;
既然證實是合適的,接下來就應該登記註冊,並公證了。還記得handler->connect(handler, dev, id)函數吧。
當input_match_device()找到最合適的事件處理層驅動時,便執行handler->connect函數進行公證了,看下面這部分代碼(假如說找到了evdev類型的驅動,在input/evdev.c中):
1 struct evdev *evdev; 2 struct class_device *cdev; 3 dev_t devt; 4 int minor; 5 int error; 6 7 /* EVDEV_MINORS爲32,表明共能容納32個evdev事件層設備,下面代碼在找到空的地方,用於保存evdev事件層的數據,即上面定義的evdev */ 8 for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); 9 /* 這說明內核已經沒辦法再分配這種類型的設備了 */ 10 if (minor == EVDEV_MINORS) { 11 printk(KERN_ERR "evdev: no more free evdev devices\n"); 12 return -ENFILE; 13 } 14 /* 開始給evdev事件層驅動分配空間了 */ 15 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); 16 if (!evdev) 17 return -ENOMEM; 18 19 /* 初始化client_list列表和evdev_wait隊列,後面介紹 */ 20 INIT_LIST_HEAD(&evdev->client_list); 21 init_waitqueue_head(&evdev->wait); 22 23 /* 初始化evdev結構體,其中handle爲輸入設備和事件處理的關聯接口 */ 24 evdev->exist = 1; 25 evdev->minor = minor; 26 evdev->handle.dev = dev; 27 evdev->handle.name = evdev->name; 28 evdev->handle.handler = handler; 29 evdev->handle.private = evdev; 30 sprintf(evdev->name, "event%d", minor); 31 32 /* 重要,上層訪問時經過次設備號找到事件處理的接口 */ 33 evdev_table[minor] = evdev; 34 35 /* evdev事件設備的此設備號的基準值INPUT_MAJOR, EVDEV_MINOR_BASE */ 36 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), 37 38 /* 建立用戶事件驅動層設備訪問接口/dev/input/event* */ 39 cdev = class_device_create(&input_class, &dev->cdev, devt, 40 dev->cdev.dev, evdev->name); 41 if (IS_ERR(cdev)) { 42 error = PTR_ERR(cdev); 43 goto err_free_evdev; 44 } 45 46 /* 提供/sys目錄的用戶空間接口 */ 47 error = sysfs_create_link(&input_class.subsys.kobj, 48 &cdev->kobj, evdev->name); 49 if (error) 50 goto err_cdev_destroy; 51 52 /* input_dev設備驅動和handler事件處理層的關聯,由handle完成 */ 53 error = input_register_handle(&evdev->handle);
經過上述代碼的執行,最終,輸入設備在input_register_handle()的關聯下與已經匹配上的handler結合
1 struct input_handler *handler = handle->handler; 2 /* 將d_node連接到輸入設備的h_list,h_node連接到事件層的h_list鏈表上 3 * 所以,在handle中是輸入設備和事件層的關聯結構體,經過輸入設備能夠 4 * 找到對應的事件處理層接口,經過事件處理層也可找到匹配的輸入設備 5 */ 6 list_add_tail(&handle->d_node, &handle->dev->h_list); 7 list_add_tail(&handle->h_node, &handler->h_list); 8 9 /* 若是start函數有定義則調用,可是evdev結構體中並未初始化這個函數 */ 10 if (handler->start) 11 handler->start(handle);
以上是輸入設備驅動註冊的全過程,牽涉的代碼比較多,須要從宏觀上理順。
縱觀整個過程:
輸入設備驅動最終的目的就是可以與事件處理層的事件驅動相互匹配,可是在drivers/input目錄下有evdev.c事件驅動、mousedev.c事件驅動、joydev.c事件驅動等等,咱們的輸入設備產生的事件應該最終上報給誰,而後讓事件驅動再去處理呢?
知道了這麼個緣由再看上面代碼就會明白,其實evdev.c、mousedev.c等根據硬件輸入設備的處理方式的不一樣抽象出了不一樣的事件處理接口幫助上層去調用,而咱們寫的設備驅動程序只不過是完成了硬件寄存器中數據的讀寫,但提交給用戶的事件必須是通過事件處理層的封裝和同步纔可以完成的,事件處理層提供給用戶一個統一的界面來操做。
因爲以上的這些緣由,纔有了上述代碼的關聯過程,看一下整個關聯註冊的過程:
經過上圖咱們能夠看到input輸入設備匹配關聯的關鍵過程以及涉及到的關鍵函數和數據。
以上主要是從input設備驅動程序的角度去看輸入子系統的註冊過程和三層之間的關聯。
2. 在第四層:在發生輸入事件時,向子系統報告事件
@@@ft5206_driver.c
static void tpd_down(int x, int y, int p,int finger_id) {
// input_report_abs(tpd->dev, ABS_PRESSURE, p);
input_report_key(tpd->dev, BTN_TOUCH, 1);
input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 1);
input_report_abs(tpd->dev, ABS_MT_POSITION_X, x);
input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y);
input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, finger_id);
//printk("D[%4d %4d %4d] ", x, y, p);
input_mt_sync(tpd->dev);
}
用於報告EV_KEY、EV_REL、EV_ABS等事件的函數有:
void input_report_key(struct input_dev *dev, unsigned int code, int value)
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)
@@@第三層:input.c
注意:若是你以爲麻煩,你也能夠只記住1個函數(由於上述函數都是經過它實現的):input core中的以下函數:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
最終調用到以下函數:
/*
* Pass event first through all filters and then, if event has not been
* filtered out, through all open handles. This function is called with
* dev->event_lock held and interrupts disabled.
*/
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 = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else {
bool filtered = false;
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue;
handler = handle->handler;
if (!handler->filter) {
if (filtered)
break;
handler->event(handle, type, code, value);///////
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
}
rcu_read_unlock();
}
@@@第二層:evdev.c
static struct input_handler evdev_handler = {
.event = evdev_event,///
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
/*
* Pass incoming event to all connected clients.
*/
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
rcu_read_lock();
client = rcu_dereference(evdev->grab);///獲得上層的client
if (client)
evdev_pass_event(client, &event, time_mono, time_real);////
else
list_for_each_entry_rcu(client, &evdev->client_list, node);
evdev_pass_event(client, &event, time_mono, time_real);////
rcu_read_unlock();
}
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event,
ktime_t mono, ktime_t real)
{
event->time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
mono : real);
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
client->buffer[client->head++] = *event;////將事件賦值給上層client(是struct evdev_client)
}/////4. Input 子系統之二--使用方式2:來自用戶空間 對 設備節點的訪問(/dev/input/event%d)
(1)關於設備節點的查詢方式
讀取並顯示/dev/input/eventX事件
$getevent -l
查看設備的major:/proc/devices/* && 訪問設備節點:/dev/*
/proc/devices/中的設備是經過insmod加載到內核的,它可產生一個major(主設備號)供mknod做爲 參數。
這個文件列出字符和塊設備的主設備號,以及分配到這些設備號的設備名稱。
/dev/* 是經過mknod加上去的(使用major),格式:mknod device1 c/b major minor 如:mknod /dev/ttyS0 c 4 64,用戶經過此設備名來訪問你的驅動。
/*在每一行均可以看到設備文件、設備編號(主、次);
對於每種硬件設備,系統內核有相應的設備驅動程序負責對它的處理。而在Unix 中,使用設備文件的方式來表示硬件設備,每種設備驅動程序都被抽象 爲設備文件的形式,這樣就給應用程序一個一致的文件界面,方便應用程序和操做系統之間的通訊。
習慣上,全部的設備文件 都放置在/dev 目錄下*/
查看input設備節點(1):/dev/input/*
( /dev/input目錄下的事件都是在驅動中調用input_register_device(struct input_dev *dev)產生的。
每一個event將上報指定的事件,如G-Sensor、觸摸屏、Mouse、按鍵等。)
查看input設備節點(2):與/dev/input/*的event對應的相關設備信息:/proc/bus/input/devices
(2)下面將從應用層的角度分析事件的接受過程和處理過程以及三層之間是如何配合處理輸入事件的。
以上部分已經藉助input子系統把input設備驅動層與事件驅動層進行了關聯,以s3c2440_ts.c(輸入設備層驅動)和evdev.c(事件處理層驅動)爲例,來分析這一過程。
因爲s3c2440_ts.c中上報的事件類型爲按鍵、絕對值座標,而evdev事件驅動程序是全匹配的,所以早在s3c2440_ts.c註冊的過程當中,就會建立設備節點/dev/input/event0(假設內核中沒有其餘的event類型的輸入設備,這裏就是event0)
咱們知道,應用層使用設備的第一步,是open(「/dev/event0」),所以這裏event0的主設備號成爲關鍵,由於主設備號將代表你是什麼設備,咱們ls -l查看/dev/event0發現:
crw-r-----1 root root 13, 64 2012-07-26 14:32 /dev/input/event0
因而可知主設備是13,輸入命令cat /proc/devices查看主設備爲13的是input設備,所以能夠肯定當咱們執行open函數打開event0設備的時候,會調用input設備的open驅動函數,這個函數在input.c中,爲了說明這一問題,須要從input驅動註冊過程開始,仍是input.c文件:
1 /* 輸入設備初始化函數 */ 2 static int __init input_init(void) 3 { 4 class_register(&input_class); 5 input_proc_init(); 6 register_chrdev(INPUT_MAJOR,"input", &input_fops); 7 }
能夠看到,輸入設備初始化的過程首先創建了input類,初始化input在proc下的節點,而後註冊input設備,設備名稱爲input,操做接口是input_fops,主設備號是INPUT_MAJOR=13。
由以上可知,只要是主設備號爲13的設備驅動程序,都是用input_fops接口,即當event0設備使用open函數打開時,會調用到input_fops接口中的open驅動函數,這個結構體的初始化爲:
1 static const struct file_operations input_fops = { 2 .owner = THIS_MODULE, 3 .open = input_open_file, 4 };
能夠看到,只實現了一個open功能字段,再看input_open_file的實現:
1 static int input_open_file(struct inode *inode, struct file *file) 2 { 3 struct input_handler *handler =input_table[iminor(inode) >> 5]; 4 const struct file_operations *old_fops,*new_fops = NULL; 5 if (!handler || !(new_fops =fops_get(handler->fops))) 6 return -ENODEV; 7 old_fops = file->f_op; 8 file->f_op = new_fops; 9 new_fops->open(inode, file); 10 }
以上代碼的功能爲找到對應事件驅動層的fops,即進行fops的接口轉換,指向對應設備的事件處理接口。
其中input_table[iminor(inode)]>>5的input_table是一個全局的input_handler類型的數組,iminor(inode)取得次設備號,而且右移5位索引input_table表中對應的位置,爲何這樣作呢?這是由於這個表格中填寫的就是事件處理的指針,待會分析。
繼續查看下面的代碼。if中將判斷是否爲空而且事件處理層中的fops有沒有初始化,若是沒有就不能進行接口轉換,報出設備不存在的錯誤,若是設備存在則把input設備的f_op驅動接口指向input_table表中存在的接口,並調用其open函數。
那麼這個input_table裏面到底存放了什麼呢?咱們仍是拿觸摸屏驅動來說解。因爲觸摸屏驅動已經完成了和evdev.c事件處理層的匹配,且次設備號爲64,設備名稱爲/dev/event0,這是咱們經過分析驅動註冊中得到的內容,既然input核心設備註冊了,s3c2440觸摸屏驅動也註冊了,那會不會evdev設備也會註冊了呢?答案是確定的,要想知道input_table裏面放了什麼,必需要去查看evdev設備的註冊過程,打開input/evdev.c查看它的註冊過程:
1 static struct input_handler evdev_handler = { 2 .event = evdev_event, //事件處理 3 .connect = evdev_connect, //設備鏈接 4 .disconnect = evdev_disconnect, //註銷鏈接 5 .fops = &evdev_fops, //驅動功能接口 6 .minor = EVDEV_MINOR_BASE, //evdev的值爲64 7 .name = "evdev", //設備名稱 8 .id_table = evdev_ids, //用於匹配設備驅動的數組 9 }; 10 11 static int __init evdev_init(void) 12 { 13 return input_register_handler(&evdev_handler); //evdev設備驅動註冊 14 }
由以上的內容能夠知道evdev_handler也被做爲一個設備來操做,可是它屬於input handler事件處理設備,然而咱們在evdev_handler結構體的.fops字段又發現它的驅動接口爲字符設備類型,在input中,若是input_table匹配到了evdev_handler,將會把file->f_op=&evdev_fops,那麼若是使用read、write等函數操做,將會調用到evdev_fops中的read、write。
爲了進一步查看input_table表中的內容是如何填充的,還須要查看這個註冊的過程:
1 int input_register_handler(struct input_handler *handler) 2 { 3 …… 4 input_table[handler->minor>> 5] = handler; 5 …… 6 }
固然這個註冊過程並非只有這麼一句話,看到這條語句,相信應該知道什麼意思了。
在input的open函數執行以前,即咱們的open代碼打開以前,input_table中的字段已經被事件處理層填充了。
因爲evdev的次設備號在初始化的時候就設置成了64,所以這裏至關於:
input_table[2]=&evdev_handler;
回到input_open_file函數查看new_fops->open(inode, file)便知道了調用的是:
evdev_handler.evdev_fops.open(inode, file);
在分析open函數以前,解釋一下爲何要右移5位?
這說明一個問題,次設備號的低5位被忽略,這說明evdev的最大支持的輸入設備驅動個數爲2^5次方等於32個,你可能會看到你的/dev目錄下面有event0、event一、event2等設備,他們的次設備號分別爲6四、6五、66等等。但最大是64+32-1,所以input_table爲這些輸入設備增長的一個統一接口,經過上層打開設備時,只要次設備號在64+32-1之間的設備都會從新定位到evdev_handler中,即event*設備打開後執行的底層函數將被從新定義到evdev_handler中。
相信上面的問題已經描述清楚,若是仍是不明白,最起碼應該知道的是,input設備中的open函數只是一個接口,經過次設備號才找到了真正的事件處理接口。接下來要看新的open接口的實現了,evdev_handler-> fops->open實現以下:
1 /*evdev字符設備驅動接口 */ 2 static const struct file_operations evdev_fops = { 3 .owner = THIS_MODULE, 4 .read = evdev_read, 5 .write = evdev_write, 6 .poll = evdev_poll, 7 .open = evdev_open, 8 .release = evdev_release, 9 .unlocked_ioctl = evdev_ioctl, 10 #ifdef CONFIG_COMPAT 11 .compat_ioctl = evdev_ioctl_compat, 12 #endif 13 .fasync = evdev_fasync, 14 .flush = evdev_flush 15 }; 16 /*evdev設備open函數的實現過程 */ 17 static int evdev_open(struct inode *inode, struct file *file) 18 { 19 struct evdev_client *client; 20 struct evdev *evdev; 21 /* 若是是event0,對於evdev設備來講,次設備號固然是0 */ 22 int i = iminor(inode) - EVDEV_MINOR_BASE; 23 int error; 24 /* 若是大於32,說明超出了evdev可以容納的最大輸入設備個數 */ 25 if (i >= EVDEV_MINORS) 26 return -ENODEV; 27 /* 因爲evdev中能容納32個輸入設備,所以經過設備號event0中的0定位到是要處理的是哪個輸入設備,evdev_table中的內容在輸入設備驅動註冊時經過evdev_connect填充 */ 28 evdev = evdev_table[i]; 29 /* 判斷是否設備接口存在,evdev_exist也是在evdev_connect填充爲1 */ 30 if (!evdev || !evdev->exist) 31 return -ENODEV; 32 /* 存在則分配evdev中的client來處理event* */ 33 client = kzalloc(sizeof(struct evdev_client),GFP_KERNEL); 34 if (!client) 35 return -ENOMEM; 36 37 /* 把event*中的接口指向evdev_table中對應項 */ 38 client->evdev = evdev; 39 /* 把client->node連接到evdev子集中 */ 40 list_add_tail(&client->node,&evdev->client_list); 41 /* 若是open是第一個打開,則會執行input_open_device*/ 42 if (!evdev->open++ &&evdev->exist) { 43 error =input_open_device(&evdev->handle); 44 if (error) { 45 list_del(&client->node); 46 kfree(client); 47 return error; 48 } 49 } 50 /* 將file私有指針指向client*/ 51 file->private_data = client; 52 return 0; 53 } 54 //由上的代碼能夠看出,最終是要執行input_open_device去執行設備驅動程序中的代碼,然而咱們在定義設備驅動的時候並無給input_dev中的open字段填充內容,所以能夠看到input_open_device函數的執行過程: 55 if(!dev->users++ && dev->open) 56 err = dev->open(dev); 57 58 if (err) 59 handle->open--;
上面截取了片斷,並無執行到open函數,open進行自減操做,表示沒有調用過open,這個值主要是爲了close中判斷open爲0時釋放資源使用。
不只如此,咱們在觸摸屏驅動中也沒有定義read、write,那當觸摸屏上報事件時,是如何處理的呢?
咱們須要先到觸摸屏驅動程序中找到上報事件的函數再作進一步分析。
觸摸屏驅動程序上報事件的函數爲:
1 input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp); 2 input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp); 3 input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure); 4 input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown); 5 input_sync(dev);
然而他們實際上是input_event函數的封裝,調用的都是input_event函數,這一函數在input.c中實現以下:
1 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, intvalue) 2 { 3 struct input_handle *handle; 4 5 if (type > EV_MAX || !test_bit(type,dev->evbit)) 6 return; 7 switch (type) { 8 case EV_SYN: 9 switch (code) { 10 case SYN_CONFIG: 11 if(dev->event) 12 dev->event(dev,type, code, value); 13 break; 14 case SYN_REPORT: 15 if(dev->sync) 16 return; 17 dev->sync= 1; 18 break; 19 } 20 break; 21 case EV_KEY: 22 case EV_SW: 23 case EV_ABS: 24 case EV_REL: 25 case EV_MSC: 26 case EV_LED: 27 case EV_SND: 28 case EV_REP: 29 case EV_FF: 30 } 31 32 if (type != EV_SYN) 33 dev->sync = 0; 34 35 if (dev->grab) 36 dev->grab->handler->event(dev->grab,type, code, value); 37 else 38 list_for_each_entry(handle,&dev->h_list, d_node) 39 if (handle->open) 40 handle->handler->event(handle,type, code, value); 41 }
代碼被作了精簡,其中就是在匹配上報的事件,並根據事件的類型調用驅動程序中相應的函數來完成,可是因爲咱們並無定義過這些函數,所以執行最後的handle_handler_event函數,由事件處理層evdev_event函數來完成事件的保存工做,具體過程以下:
1 list_for_each_entry(client,&evdev->client_list, node) { 2 client->buffer[client->head].type= type; 3 client->buffer[client->head].code= code; 4 client->buffer[client->head].value= value; 5 client->head= (client->head + 1) & (EVDEV_BUFFER_SIZE - 1); 6 }
這裏列舉了關鍵代碼,即上報的事件被保存到了client_buffer中,其中client_buffer是一個循環緩衝區,client->head表示當前數據的位置,所以每次都寫到client->head的位置,而讀數據時須要到client_tail中讀取。由於在open的時候,client已經被鏈入到了evdev->client_list中,所以經過能夠經過list_for_each_entry重evdev->client_list中找到對應的client。
事件的上報都會把數據保存到client->buffer中,以便上層經過read和write進行讀去和寫入。
事件的上報都會把數據保存到client->buffer中,以便上層經過read和write進行讀去和寫入。
仍是以觸摸屏驅動程序和evdev事件處理層驅動來分析:
1 static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t*ppos) 2 { 3 struct evdev_client *client =file->private_data; 4 struct evdev *evdev = client->evdev; 5 int retval; 6 7 /* 判斷用戶給的count是否可以容納事件數據的大小*/ 8 if (count < evdev_event_size()) 9 return -EINVAL; 10 11 /* 若是數據不爲空而且設備存在而且是阻塞訪問方式才能繼續執行 */ 12 if (client->head == client->tail&& evdev->exist && (file->f_flags & O_NONBLOCK)) 13 return -EAGAIN; 14 15 /* 若是數據爲空,設置進程等待底層驅動層上報事件到client->buffer中 */ 16 retval =wait_event_interruptible(evdev->wait, 17 client->head != client->tail|| !evdev->exist); 18 if (retval) 19 return retval; 20 21 if (!evdev->exist) 22 return -ENODEV; 23 24 /* 循環讀取數據 */ 25 while (client->head != client->tail&& retval + evdev_event_size() <= count) { 26 27 struct input_event *event =(struct input_event *) client->buffer + client->tail; 28 29 if (evdev_event_to_user(buffer +retval, event)) 30 return -EFAULT; 31 32 client->tail = (client->tail+ 1) & (EVDEV_BUFFER_SIZE - 1); 33 retval += evdev_event_size(); 34 } 35 36 return retval; 37 }
這裏若是沒有數據,進程會睡眠,那由誰來喚醒呢?細心的話能夠發現,當設備驅動層調用input_event上報事件調用相應的event函數進行事件寫入時,是會喚醒阻塞等待的進程的。
事件的上報都會把數據保存到client->buffer中,以便上層經過read和write進行讀去和寫入。
寫入過程:
1 static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count,loff_t *ppos) 2 { 3 /* 循環寫入,調用input_inject_event函數 */ 4 while (retval < count) { 5 6 if (evdev_event_from_user(buffer +retval, &event)) 7 return -EFAULT; 8 input_inject_event(&evdev->handle,event.type, event.code, event.value); 9 retval += evdev_event_size(); 10 } 11 12 return retval; 13 }
上述代碼中的event是input_event數組,包含了事件的類型、鍵值,經過input_inject_event把數據寫入循環數組client->buffer中,input_inject_event調用的是input_event函數。
對input子系統的整個過程作了分析,並從兩個角度進行考慮.
對於寫輸入設備驅動程序的來講,須要掌握的是設備應該上報事件的類型,這樣才能匹配到對應的事件層驅動幫助你保存對應的數據.
而對於設備上層開發者來講,應該先使用cat /proc/bus/input/devices查看你操做的設備類型和處理接口,以幫助你更好的對設備操做。