一:前言
最近在研究android的sensor driver,主要是E-compass,其中用到了Linux input子系統.在網上也看了很多這方面的資料,感覺還是這篇分析的比較細緻透徹,是以轉載一下以便自己學習,同時和大家分享!
(這篇部落客要是以鍵盤驅動為例的,不過講解的是Linux Input Subsystem,可以仔細的研究一下!)
鍵盤驅動将檢測到的所有按鍵都上報給了input子系統。Input子系統是所有I/O裝置驅動的中間層,為上層提供了一個統一的界面。例如,在終 端系統中,我們不需要去管有多少個鍵盤,多少個滑鼠。它隻要從input子系統中去取對應的事件(按鍵,滑鼠移位等)就可以了。
二:使用input device的例子
下面的代碼是基于linux kernel 2.6.25.分析的代碼主要位于kernel2.6.25/drivers/input下面.
在核心自帶的文檔Documentation/input/input-programming.txt中。有一個使用input子系統的例子,并附帶相應的說明。以此為例分析如下:
#include <linux/input.h> #include <linux/module.h> #include <linux/init.h>
#include <asm/irq.h> #include <asm/io.h> static void button_interrupt(int irq, void *dummy, struct pt_regs *fp) { input_report_key(&button_dev, BTN_1, inb(BUTTON_PORT) & 1); input_sync(&button_dev); }
static int __init button_init(void) { if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) { printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq); return -EBUSY; } button_dev.evbit[0] = BIT(EV_KEY); button_dev.keybit[LONG(BTN_0)] = BIT(BTN_0); input_register_device(&button_dev); }
static void __exit button_exit(void) { input_unregister_device(&button_dev); free_irq(BUTTON_IRQ, button_interrupt); }
module_init(button_init); module_exit(button_exit);
這個示例module代碼還是比較簡單,在初始化函數裡注冊了一個中斷處理例程。然後注冊了一個input device.在中斷處理程式裡,将接收到的按鍵上報給input子系統。文檔的作者在之後的分析裡又對這個module作了優化。主要是在注冊中斷處理 的時序上。在修改過後的代碼裡,為input device定義了open函數,在open的時候再去注冊中斷處理例程。具體的資訊請自行參考這篇文檔。在資料缺乏的情況下,kernel自帶的文檔就 是剖析kernel相關知識的最好資料.文檔的作者還分析了幾個api函數。列舉如下: 1): set_bit(EV_KEY, button_dev.evbit); set_bit(BTN_0, button_dev.keybit); 分别用來設定裝置所産生的事件以及上報的按鍵值。Struct iput_dev中有兩個成員,一個是evbit.一個是keybit.分别用表示裝置所支援的事件和按鍵類型。
2): input_register_device(&button_dev); 用來注冊一個input device.
3): input_report_key() 用于給上層上報一個按鍵動作
4): input_sync() 用來告訴上層,本次的事件已經完成了.
5): NBITS(x) - returns the length of a bitfield array in longs for x bits LONG(x) - returns the index in the array in longs for bit x BIT(x) - returns the index in a long for bit x 這幾個宏在input子系統中經常用到。上面的英文解釋已經很清楚了。
三:input裝置注冊分析 Input裝置注冊的接口為:input_register_device()。代碼如下: int input_register_device(struct input_dev *dev) { static atomic_t input_no = ATOMIC_INIT(0); struct input_handler *handler; const char *path; int error; __set_bit(EV_SYN, dev->evbit); init_timer(&dev->timer); if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { dev->timer.da ta = (long) dev; dev->timer.function = input_repeat_key; dev->rep[REP_DELAY] = 250; dev->rep[REP_PERIOD] = 33; } Input_device的evbit表示該裝置所支援的事件。在這裡将其EV_SYN置位,即所有裝置都支援這個事件.如果 dev->rep[REP_DELAY]和dev->rep[REP_PERIOD]沒有設值,則将其賦預設值。這主要是處理重複按鍵的.( 這個地方還沒有仔細研究過,有點疑問 ) if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode; snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id), "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); error = device_add(&dev->dev); if (error) return error; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); printk(KERN_INFO "input: %s as %s\n", dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); kfree(path); error = mutex_lock_interruptible(&input_mutex); if (error) { device_del(&dev->dev); return error; } 如果input device沒有定義getkeycode和setkeycode.則将其賦預設值。然後調用device_add()将input_dev中封裝的device注冊到sysfs。 list_add_tail(&dev->node, &input_dev_list); list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return 0; } 這裡就是重點了。将input device 挂到input_dev_list連結清單上.然後,對每一個挂在input_handler_list的handler調用 input_attach_handler().這裡的情況好比裝置模型中的device和driver的比對。所有的input device都挂在input_dev_list鍊上。所有的handle都挂在input_handler_list上。
看一下這個比對的詳細過程。比對是在input_attach_handler()中完成的。代碼如下: static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id; int error; if (handler->blacklist && input_match_device(handler->blacklist, dev)) return -ENODEV; id = input_match_device(handler->id_table, dev); if (!id) return -ENODEV; error = handler->connect(handler, dev, id); if (error && error != -ENODEV) printk(KERN_ERR "input: failed to attach handler %s to device %s, " "error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error); return error; } 如果handle的blacklist被指派。要先比對blacklist中的資料跟dev->id的資料是否比對。比對成功過後再來匹 配handle->id和dev->id中的資料。如果比對成功,則調用handler->connect().
來看一下具體的資料比對過程,這是在input_match_device()中完成的。代碼如下: static const struct input_device_id *input_match_device(const struct input_device_id *id, struct input_dev *dev) { int i; for (; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) if (id->vendor != dev->id.vendor) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) if (id->product != dev->id.product) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) if (id->version != dev->id.version) continue; MATCH_BIT(evbit, EV_MAX); MATCH_BIT(,, KEY_MAX); MATCH_BIT(relbit, REL_MAX); MATCH_BIT(absbit, ABS_MAX); MATCH_BIT(mscbit, MSC_MAX); MATCH_BIT(ledbit, LED_MAX); MATCH_BIT(sndbit, SND_MAX); MATCH_BIT(ffbit, FF_MAX); MATCH_BIT(swbit, SW_MAX); return id; } return NULL; }
MATCH_BIT宏的定義如下: #define MATCH_BIT(bit, max) \ for (i = 0; i < BITS_TO_LONGS(max); i++) \ if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \ break; \ if (i != BITS_TO_LONGS(max)) \ continue; 由此看到。在id->flags中定義了要比對的項。定義INPUT_DEVICE_ID_MATCH_BUS。則是要比較input device和input handler的總線類型。 INPUT_DEVICE_ID_MATCH_VENDOR,INPUT_DEVICE_ID_MATCH_PRODUCT,INPUT_DEVICE_ID_MATCH_VERSION 分别要求裝置廠商。裝置号和裝置版本.
如果id->flags定義的類型比對成功。或者是id->flags沒有定義,就會進入到MATCH_BIT的比對項了.從 MATCH_BIT宏的定義可以看出。隻有當iput device和input handler的id成員在evbit, keybit,… swbit項相同才會比對成功。而且比對的順序是從evbit, keybit到swbit.隻要有一項不同,就會循環到id中的下一項進行比較.
簡而言之,注冊input device的過程就是為input device設定預設值(初始化),并将其挂到input_dev_list連結清單中.然後與挂載在input_handler_list中的 handler相比對。如果比對成功,就會調用handler的connect函數.
四:handler注冊分析 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; } list_add_tail(&handler->node, &input_handler_list); 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; } handler->minor表示對應input裝置節點的次裝置号.以handler->minor右移五位做為索引值插入到input_table[ ]中..之後再來分析input_talbe[ ]的作用.
然後将handler挂到input_handler_list中.然後将其與挂在input_dev_list中的input device比對.這個過程和input device的注冊有相似的地方.都是注冊到各自的連結清單,.然後與另外一條連結清單的對象相比對. 五:handle的注冊 int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; int error; error = mutex_lock_interruptible(&dev->mutex); if (error) return error; list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); synchronize_rcu(); list_add_tail(&handle->h_node, &handler->h_list); if (handler->start) handler->start(handle); return 0; } 在這個函數裡所做的處理其實很簡單.将handle挂到所對應input device的h_list連結清單上. 再将handle挂到對應的handler的h_list連結清單上.如果handler定義了start函數,将調用之.
到這裡,我們已經看到了input device, handler和handle是怎麼關聯起來的了.以圖的方式總結如下:

但這個圖上顯示: 在Handler->event中調用input_register_handle(),但是我在co de裡面看到 建議在connect中調用input_register_handle(),這裡也是有點疑問的。