天天看點

Linux裝置模型之input子系統詳解(一)

一:前言

最近在研究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是怎麼關聯起來的了.以圖的方式總結如下:

Linux裝置模型之input子系統詳解(一)

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

繼續閱讀