Linux設備模型之input子系統詳解

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 #include #include #include #include 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); /* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ init_timer(&dev->timer); if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) { dev->timer.data = (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; /* * We take dev->mutex here to prevent race with * input_release_device(). */ 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(); /* * 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. */ 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(),但是我在code裏面看到 /** * input_register_handle - register a new input handle * @handle: handle to register * * This function puts a new input handle onto device's * and handler's lists so that events can flow through * it once it is opened using input_open_device(). * * This function is supposed to be called from handler's * connect() method. */ 建議在connect中調用input_register_handle(),這裏也是有點疑問的。 六:event事件的處理 在開始的時候曾以linux kernel文檔中自帶的代碼作分析.提出了幾個事件上報的API. 這些API其實都是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); } } 首先,先判斷設備產生的這個事件是否合法.如果合法,流程轉入到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 = 1; 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); } 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)) { value = input_defuzz_abs_event(value, dev->abs[code], dev->absfuzz[code]); if (dev->abs[code] != value) { dev->abs[code] = value; disposition = INPUT_PASS_TO_HANDLERS; } } 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 (type != EV_SYN) dev->sync = 0; 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); } 在這裏, 忽略掉具體事件的處理. 到最後,如果該事件需要input device來完成的,就會將disposition設置成INPUT_PASS_TO_DEVICE.如果需要handler來完成的,就將 dispostion設爲INPUT_PASS_TO_HANDLERS.如果需要兩者都參與,將disposition設置爲 INPUT_PASS_TO_ALL. 需要輸入設備參與的,回調設備的event函數.如果需要handler參與的.調用input_pass_event().代碼如下: static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; rcu_read_lock(); handle = rcu_dereference(dev->grab); if (handle) handle->handler->event(handle, type, code, value); else list_for_each_entry_rcu(handle, &dev->h_list, d_node) if (handle->open) handle->handler->event(handle, type, code, value); rcu_read_unlock(); } 如果input device被強制指定了handler,則調用該handler的event函數. 結合handle註冊的分析.我們知道.會將handle掛到input device的h_list鏈表上. 如果沒有爲input device強制指定handler.就會遍歷input device->h_list上的handle成員.如果該handle被打開,則調用與輸入設備對應的handler的event()函數.注意,只有在handle被打開的情況下才會接收到事件. 另外,輸入設備的handler強制設置一般是用帶EVIOCGRAB標誌的ioctl來完成的.如下是發圖的方示總結evnet的處理過程: 到此,已經分析了input device,handler和handle的註冊過程以及事件的上報和處理.下面以evdev爲實例做分析.來貫穿理解一下整個過程. 七:evdev概述 Evdev對應的設備節點一般位於/dev/input/event0 ~ /dev/input/event4.理論上可以對應32個設備節點.分別代表被handler匹配的32個input device. 可以用cat /dev/input/event0.然後移動鼠標或者鍵盤按鍵就會有數據輸出(兩者之間只能選一.因爲一個設備文件只能關能一個輸入設備).還可以往這個文件裏寫數據,使其產生特定的事件.這個過程之後再詳細分析. 爲了分析這一過程,必須從input子系統的初始化說起. 八:input子系統的初始化 Input子系統的初始化函數爲input_init().代碼如下: static int __init input_init(void) { int err; err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class/n"); return err; } err = input_proc_init(); if (err) goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops); if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; } 在這個初始化函數裏,先註冊了一個名爲”input”的類.所有input device都屬於這個類.在sysfs中表現就是.所有input device所代表的目錄都位於/dev/class/input下面. 然後調用input_proc_init()在/proc下面建立相關的交互文件. 最後調用register_chrdev()註冊了主設備號爲INPUT_MAJOR(13).次設備號爲0~255的字符設備.它的操作指針爲input_fops. 這裏可以看到.所有主設備號13的字符設備的操作最終都會轉入到input_fops中.在前面分析的/dev/input/event0~/dev/input/event4的主設備號爲13.操作也不例外的落在了input_fops中. Input_fops定義如下: static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, }; 打開文件所對應的操作函數爲input_open_file.代碼如下示: static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler = input_table[iminor(inode) >> 5]; const struct file_operations *old_fops, *new_fops = NULL; int err; /* No load-on-demand here? */ if (!handler || !(new_fops = fops_get(handler->fops))) return -ENODEV; iminor(inode)爲打開文件所對應的次設備號.input_table是一個struct input_handler全局數組.先將設備結點的次設備號右移5位做爲索引值到input_table中取對應項.這裏可以看到.一 個handler代表1<<5個設備節點(因爲在input_table中取值是以次備號右移5位爲索引的.即低5位相同的次備號對應的是同一 個索引).終於看到了input_talbe大顯身手的地方了.input_talbe[ ]中取值和input_talbe[ ]的賦值,這兩個過程是相對應的. 在input_table中找到對應的handler之後,就會檢驗這個handler是否存,是否帶有fops文件操作集.如果沒有.則返回一個設備不存在的錯誤. /* * That's _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops->open) { fops_put(new_fops); return -ENODEV; } old_fops = file->f_op; file->f_op = new_fops; err = new_fops->open(inode, file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); return err; } 然後將handler中的fops替換掉當前file的fops.如果handler的fops中有open()函數,則調用它.(如果以evdev爲例的話,這裏的open()函數應該就是evdev_open()函數) 九:evdev的初始化 Evdev的模塊初始化函數爲evdev_init().代碼如下: static int __init evdev_init(void) { return input_register_handler(&evdev_handler); } 它調用了input_register_handler註冊了一個handler. 注意到,在這裏evdev_handler中定義的minor爲EVDEV_MINOR_BASE(64).也就是說evdev_handler所表示的設備文件範圍爲(13,64)~(13,64+32). 從之前的分析知道.匹配成功的關鍵在於handler中的blacklist和id_talbe. Evdev_handler的id_table定義如下: static const struct input_device_id evdev_ids[] = { { .driver_info = 1 }, /* Matches all devices */ { }, /* Terminating zero entry */ }; 它沒有定義flags.也沒有定義匹配屬性值.這個handler是匹配所有input device的.匹配成功之後會調用handler->connect函數. 在Evdev_handler中,該成員函數如下所示: static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { struct evdev *evdev; int minor; int error; for (minor = 0; minor < EVDEV_MINORS; minor++) if (!evdev_table[minor]) break; if (minor == EVDEV_MINORS) { printk(KERN_ERR "evdev: no more free evdev devices/n"); return -ENFILE; } EVDEV_MINORS定義爲32.表示evdev_handler所表示的32個設備文件.evdev_talbe是一個struct evdev類型的數組.struct evdev是模塊使用的封裝結構.在接下來的代碼中可以看到這個結構的使用. 這一段代碼的在evdev_talbe找到爲空的那一項.minor就是數組中第一項爲空的序號. evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list); spin_lock_init(&evdev->client_lock); mutex_init(&evdev->mutex); init_waitqueue_head(&evdev->wait); snprintf(evdev->name, sizeof(evdev->name), "event%d", minor); evdev->exist = 1; evdev->minor = minor; evdev->handle.dev = input_get_device(dev); evdev->handle.name = evdev->name; evdev->handle.handler = handler; evdev->handle.private = evdev; 接下來,分配了一個evdev結構,並對這個結構進行初始化.這個結構封裝了一個handle結構,這結構與之前所討論的handler是不相同的.注意有一個字母的差別哦.可以把handle看成是handler和input device的信息集合體.在這個結構裏集合了匹配成功的handler和input device strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id)); evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev); 在這段代碼裏主要完成evdev封裝的device的初始化.注意在這裏,使它所屬的類指向input_class.這樣在/sysfs中創建的設備目錄就會在/sys/class/input/下面顯示. error = input_register_handle(&evdev->handle); if (error) goto err_free_evdev; error = evdev_install_chrdev(evdev); if (error) goto err_unregister_handle; error = device_add(&evdev->dev); if (error) goto err_cleanup_evdev; return 0; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); return error; } 註冊handle,如果成功,那麼調用evdev_install_chrdev將evdev_table的minor項指向evdev. 然後將evdev->device註冊到sysfs.如果失敗,則進行相關的錯誤處理. 萬事俱備了,但是要接收事件,還得要等”東風”.這個”東風”就是要打開相應的handle.這個打開過程是在文件的open()中完成的. 十:evdev設備結點的open()操作 對主設備號爲INPUT_MAJOR的設備節點進行操作,會將操作集轉換成handler的操作集.在evdev中,這個操作集就是evdev_fops.對應的open函數如下示: static int evdev_open(struct inode *inode, struct file *file) { struct evdev *evdev; struct evdev_client *client; int i = iminor(inode) - EVDEV_MINOR_BASE; int error; if (i >= EVDEV_MINORS) return -ENODEV; error = mutex_lock_interruptible(&evdev_table_mutex); if (error) return error; evdev = evdev_table[i]; if (evdev) get_device(&evdev->dev); mutex_unlock(&evdev_table_mutex); if (!evdev) return -ENODEV; client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); if (!client) { error = -ENOMEM; goto err_put_evdev; } spin_lock_init(&client->buffer_lock); client->evdev = evdev; evdev_attach_client(evdev, client); error = evdev_open_device(evdev); if (error) goto err_free_client; file->private_data = client; return 0; err_free_client: evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); return error; } iminor(inode) - EVDEV_MINOR_BASE就得到了在evdev_table[ ]中的序號.然後將數組中對應的evdev取出.遞增devdev中device的引用計數. 分配並初始化一個client.並將它和evdev關聯起來: client->evdev指向它所表示的evdev. 將client掛到evdev->client_list上. 將client賦爲file的私有區. 對應handle的打開是在此evdev_open_device()中完成的.代碼如下: static int evdev_open_device(struct evdev *evdev) { int retval; retval = mutex_lock_interruptible(&evdev->mutex); if (retval) return retval; if (!evdev->exist) retval = -ENODEV; else if (!evdev->open++) { retval = input_open_device(&evdev->handle); if (retval) evdev->open--; } mutex_unlock(&evdev->mutex); return retval; } 如果evdev是第一次打開,就會調用input_open_device()打開evdev對應的handle.跟蹤一下這個函數: 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->open++; if (!dev->users++ && dev->open) retval = dev->open(dev); if (retval) { dev->users--; if (!--handle->open) { /* * Make sure we are not delivering any more events * through this handle */ synchronize_rcu(); } } out: mutex_unlock(&dev->mutex); return retval; } 在這個函數中可以看到.遞增handle的打開計數.如果是第一次打開.則調用input device的open()函數 (如果已經賦值的話,但是我在akm8976.c中沒有看到對input device的open函數指針賦值的地方,所以這裏有個問題:open一般是用來幹嘛的? ). 十一:evdev的事件處理 經過上面的分析.每當input device上報一個事件時,會將其交給和它匹配的handler的event函數處理.在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; do_gettimeofday(&event.time); event.type = type; event.code = code; event.value = value; rcu_read_lock(); client = rcu_dereference(evdev->grab); if (client) evdev_pass_event(client, &event); else list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_event(client, &event); rcu_read_unlock(); wake_up_interruptible(&evdev->wait); } 首先構造一個struct input_event結構.並設備它的type.code,value爲處理事件的相關屬性.如果該設備被強制設置了handle.則調用與之對應的client. 在evdev_open的時候.會初始化clinet並將其鏈入到evdev->client_list. 這樣,就可以通過evdev->client_list找到這個client. 對於找到的每一個client都會調用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); client->buffer[client->head++] = *event; client->head &= EVDEV_BUFFER_SIZE - 1; spin_unlock(&client->buffer_lock); kill_fasync(&client->fasync, SIGIO, POLL_IN); } 這裏的操作很簡單.就是將event保存到client->buffer中.client->head就是當前的數據位置.注意這裏是一個環形緩存區.寫數據是從client->head寫.而讀數據則是從client->tail中讀. 十二:設備節點的read處理 對於evdev設備節點的read操作都會由evdev_read()完成.它的代碼如下: static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; int retval; if (count < evdev_event_size()) return -EINVAL; if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); if (retval) return retval; if (!evdev->exist) return -ENODEV; while (retval + evdev_event_size() <= count && evdev_fetch_next_event(client, &event)) { if (evdev_event_to_user(buffer + retval, &event)) return -EFAULT; retval += evdev_event_size(); } return retval; } 首先,它判斷緩存區大小是否足夠.在讀取數據的情況下,可能當前緩存區內沒有數據可讀.在這裏先睡眠等待緩存區中有數據.如果在睡眠的時候,.條件滿足.是不會進行睡眠狀態而直接返回的. 然後根據read()提夠的緩存區大小.將client中的數據寫入到用戶空間的緩存區中. 十三:設備節點的寫操作 同樣.對設備節點的寫操作是由evdev_write()完成的.代碼如下: static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; int retval; retval = mutex_lock_interruptible(&evdev->mutex); if (retval) return retval; if (!evdev->exist) { retval = -ENODEV; goto out; } while (retval < count) { if (evdev_event_from_user(buffer + retval, &event)) { retval = -EFAULT; goto out; } input_inject_event(&evdev->handle, event.type, event.code, event.value); retval += evdev_event_size(); } out: mutex_unlock(&evdev->mutex); return retval; } 首先取得操作設備文件所對應的evdev. 實際上,這裏寫入設備文件的是一個event結構的數組.在之前分析過,這個結構裏包含了事件的type.code和event. 將寫入設備的event數組取出.然後對每一項調用event_inject_event(). 這個函數的操作和input_event()差不多.就是將第一個參數handle轉換爲輸入設備結構.然後這個設備再產生一個事件. 代碼如下: void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { struct input_dev *dev = handle->dev; struct input_handle *grab; unsigned long flags; if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); rcu_read_lock(); grab = rcu_dereference(dev->grab); if (!grab || grab == handle) input_handle_event(dev, type, code, value); rcu_read_unlock(); spin_unlock_irqrestore(&dev->event_lock, flags); } } 這裏可以跟input_event()對比一下,這裏設備可以產生任意事件,而不需要和設備所支持的事件類型相匹配. 由此可見.對於寫操作而言.就是讓與設備文件相關的輸入設備產生一個特定的事件. 將上述設備文件的操作過程以圖的方式表示如下: 十四:小結 在這一節點,分析了整個input子系統的架構,各個環節的流程.最後還以evdev爲例.將各個流程貫穿在一起.以加深對input子系統的理解.由此也可以看出.linux設備驅動採用了分層的模式.從最下層的設備模型到設備,驅動,總線再到input子系統最後到input device.這樣的分層結構使得最上層的驅動不必關心下層是怎麼實現的.而下層驅動又爲多種型號同樣功能的驅動提供了一個統一的接口. 源自:http://ericxiao.cublog.cn/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章