linux input輸入子系統分析《四》:input子系統整體流程全面分析
1 input輸入子系統整體流程
本節分析input子系統在內核中的實現,包括輸入子系統(Input Core),事件處理層(Event Handler)和設備驅動層。由於上節代碼講解了設備驅動層的寫法,因此在開頭部分會從設備驅動層做為線索,分析輸入子系統和事件處理層是如何配合的,最後從用戶角度出發,從“/dev/input/*”接口如何使用輸入子系統提供的服務。
既然需要詳細分析,有一個這樣的流程圖能夠幫助我們在被繞進代碼的過程中,找到出口,你能夠知道你現在位於代碼框架的什麽位置,不會忘記正在分析的代碼的“身份”。其實在初識輸入子系統中已經貼出了這張圖,我們再把它拿出來參考一下吧,見下圖6。
圖6 linux輸入子系統事件處理機制
1.1 設備驅動層註冊到input子系統
在上一節分析了S3C2440觸摸屏驅動的代碼,初始化函數定義了struct input_devinput結構體,它用於描述一個輸入子系統設備,任何驅動設備如果想標明自己是輸入設備,都應該通過初始化這樣的結構體,並且調用input_allocate_device()函數進行註冊。
了解這一過程,需要先看一下structinput_dev結構體的內容:
[cpp] view plaincopy
- struct input_dev {
- void *private; //輸入設備私有指針,一般指向用於描述設備驅動層的設備結構
- const char *name; //提供給用戶的輸入設備的名稱
- const char *phys; //提供給編程者的設備節點的名稱
- const char *uniq; //指定唯一的ID號,就像MAC地址一樣
- struct input_id id; //輸入設備標識ID,用於和事件處理層進行匹配
- unsigned long evbit[NBITS(EV_MAX)]; //位圖,記錄設備支持的事件類型
- unsigned long keybit[NBITS(KEY_MAX)]; //位圖,記錄設備支持的按鍵類型
- unsigned long relbit[NBITS(REL_MAX)]; //位圖,記錄設備支持的相對坐標
- unsigned long absbit[NBITS(ABS_MAX)]; //位圖,記錄設備支持的絕對坐標
- unsigned long mscbit[NBITS(MSC_MAX)]; //位圖,記錄設備支持的其他功能
- unsigned long ledbit[NBITS(LED_MAX)]; //位圖,記錄設備支持的指示燈
- unsigned long sndbit[NBITS(SND_MAX)]; //位圖,記錄設備支持的聲音或警報
- unsigned long ffbit[NBITS(FF_MAX)]; //位圖,記錄設備支持的作用力功能
- unsigned long swbit[NBITS(SW_MAX)]; //位圖,記錄設備支持的開關功能
- unsigned int keycodemax; //設備支持的最大按鍵值個數
- unsigned int keycodesize; //每個按鍵的字節大小
- void *keycode; //指向按鍵池,即指向按鍵值數組首地址
- int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); //修改按鍵值
- int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); //獲取按鍵值
- struct ff_device *ff; //用於強制更新輸入設備的部分內容
- unsigned int repeat_key; //重復按鍵的鍵值
- struct timer_list timer; //設置當有連擊時的延時定時器
- int state; //設備狀態
- int sync; //同步事件完成標識,為1說明事件同步完成
- int abs[ABS_MAX + 1]; //記錄坐標的值
- int rep[REP_MAX + 1]; //記錄重復按鍵的參數值
- unsigned long key[NBITS(KEY_MAX)]; //位圖,按鍵的狀態
- unsigned long led[NBITS(LED_MAX)]; //位圖,led的狀態
- unsigned long snd[NBITS(SND_MAX)]; //位圖,聲音的狀態
- unsigned long sw[NBITS(SW_MAX)]; //位圖,開關的狀態
- int absmax[ABS_MAX + 1]; //位圖,記錄坐標的最大值
- int absmin[ABS_MAX + 1]; //位圖,記錄坐標的最小值
- int absfuzz[ABS_MAX + 1]; //位圖,記錄坐標的分辨率
- int absflat[ABS_MAX + 1]; //位圖,記錄坐標的基準值
- 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 *grab; //類似私有指針,可以直接訪問到事件處理接口event
- struct mutex mutex; //用於open、close函數的連續訪問互斥
- unsigned int users; //設備使用計數
- struct class_device cdev; //輸入設備的類信息
- union { //設備結構體
- struct device *parent;
- } dev;
- struct list_head h_list; //handle鏈表
- struct list_head node; //input_dev鏈表
- };
也許就這樣赤裸裸的看上面的結構體,會覺得摸不著頭腦,但是有一點是確定的,我們在寫輸入設備驅動時會定義這樣一個輸入設備結構體,並調用input_allocate_device()函數,這個函數的功能是為新添加的輸入設備分配內存,如果成功,將返回input_dev *的指針結構,因此在寫驅動的時候應該接受返回值,作為驅動層獲得了一個新的輸入設備操作的接口。
那麽input_allocate_device()函數做了什麽呢?打開函數看一下(input.c中實現):
[cpp] view plaincopy
- struct input_dev *input_allocate_device(void)
- {
- struct input_dev *dev;
- //動態申請內存,使用GFP_KERNEL方式,註意GFP_KERNEL可能導致睡眠,不能在中斷中調用這個函數
- dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
- //分配成功執行的代碼,進行成員的默認填充
- if (dev) {
- dev->cdev.class = &input_class; //支持熱插拔的結構體
- dev->cdev.groups = input_dev_attr_groups; //描述設備的硬件信息和支持的事件類型
- class_device_initialize(&dev->cdev); //類設備初始化,添加進input類設備模型中
- mutex_init(&dev->mutex); //初始化互斥鎖
- INIT_LIST_HEAD(&dev->h_list); //初始化handle鏈表
- INIT_LIST_HEAD(&dev->node); //初始化輸入設備鏈表
- }
- }
通過input_allocate_device()函數,我們設備驅動現在持有的input_dev裏面就被賦予了input的“形象”,但是還需要我們去充實一下“內在”,因此,設備驅動程序,還需要為自己的設備增加自己的特性,才能創造獨有的設備“形象”。拿上一節的代碼舉例如下:
[cpp] view plaincopy
- struct input_dev *input_dev = input_allocate_device();
- input_dev->name = "s3c2410 Touchscreen";
- input_dev->phys = "s3c2410ts/input0";
- input_dev->id.bustype = BUS_HOST;
- input_dev->id.vendor = 0x0001;
- input_dev->id.product = 0x0002;
- input_dev->id.version = 0x0100;
- input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
- input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
- input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
- input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
- input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0);
以上的內容在前一節觸摸屏驅動的初始化代碼中也有分析,完成了輸入設備的初始化工作。但是這僅是初始化自己的“特點”,還需要通知輸入子系統有這樣一個新設備誕生了,這就需要調用輸入子系統的註冊函數input_register_device(input_dev)來完成。
input_register_device()用於註冊一個輸入設備。那麽註冊過程是怎樣的呢?這是一個重點,我們在下面的代碼中進行註釋分析:
[cpp] view plaincopy
- 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;
- /* 默認所有的輸入設備都支持EV_SYN同步事件 */
- set_bit(EV_SYN, dev->evbit);
- /*
- * 如果設備驅動沒有指定重復按鍵(連擊),系統默認提供以下的支持
- * 其中init_timer為連擊產生的定時器,時間到調用input_repeat_key函數
- * 上報,REP_DELAY用於設置重復按鍵的鍵值,REP_PERIOD設置延時時間
- */
- 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;
- }
- /* 如果設備驅動沒有設置自己的獲取鍵值的函數,系統默認 */
- if (!dev->getkeycode)
- dev->getkeycode = input_default_getkeycode;
- /* 如果設備驅動沒有指定按鍵重置函數,系統默認 */
- if (!dev->setkeycode)
- dev->setkeycode = input_default_setkeycode;
- /* 重要,把設備掛到全局的input子系統設備鏈表input_dev_list上 */
- list_add_tail(&dev->node, &input_dev_list);
- /* 動態獲取input設備的ID號,名稱為input*,其中後面的“*”動態獲得,唯一的 */
- snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
- "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
- /* 如果這個值沒有設置,系統把輸入設備掛入設備鏈表 */
- if (!dev->cdev.dev)
- dev->cdev.dev = dev->dev.parent;
- /* 在/sys目錄下創建設備目錄和文件 */
- error = class_device_add(&dev->cdev);
- if (error)
- return error;
- /* 獲取並打印設備的絕對路徑名稱 */
- path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
- printk(KERN_INFO "input: %s as %s\n",
- dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
- kfree(path);
- /* 核心重點,input設備在增加到input_dev_list鏈表上之後,會查找
- * input_handler_list事件處理鏈表上的handler進行匹配,這裏的匹配
- * 方式與設備模型的device和driver匹配過程很相似,所有的input
- * 都掛在input_dev_list上,所有類型的事件都掛在input_handler_list
- * 上,進行“匹配相親”*/
- list_for_each_entry(handler, &input_handler_list, node)
- input_attach_handler(dev, handler);
- input_wakeup_procfs_readers();
- return 0;
- }
上面的代碼主要的功能有以下幾個功能,也是設備驅動註冊為輸入設備委托內核做的事情:
- 進一步初始化輸入設備,例如連擊事件;
- 註冊輸入設備到input類中;
- 把輸入設備掛到輸入設備鏈表input_dev_list中;
- 查找並匹配輸入設備對應的事件處理層,通過input_handler_list鏈表
我們需要再分析下這個匹配的過程,“相親”這種事情還是很有意思的,但是需要註意的是下面分析的代碼是我們暫時無法分析的,因為那樣會使得情況變得更加復雜,當我們從應用層往下分析的時候一切都會明白。input_attach_handler匹配過程如下:
const struct input_device_id *id; int error; /* 如果handler的blacklist被賦值了並且則優先匹配 */ if (handler->blacklist && input_match_device(handler->blacklist, dev)) return -ENODEV; /* 否則利用handler->id_table和dev進行匹配,後面講述匹配什麽和過程 */ id = input_match_device(handler->id_table, dev); if (!id) return -ENODEV; /* 這是一根“紅線”,雖然你可能覺的是黑色的,但不可否認,他們真的匹配上了 * 調用handler->connnect函數進行匹配,匹配詳細過程後面講述 */ 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->cdev.kobj), error); return error;
我們先來看下input_match_device()函數,看一下這個匹配的條件是什麽,如何匹配的過程是怎樣的,匹配的結果會是什麽?
/* 事件處理層中的對應flags如果設置或者driver_info被設置則進行匹配 */ for (; id->flags || id->driver_info; id++) { /* 以下通過flags中設置的位來匹配設備的總線類型、經銷商、生產ID和版本ID 如果沒有匹配上將進行MATCH_BIT匹配 */ 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用於匹配設備驅動中是否設置了這些為,MATCH_BIT的宏 * 被定義在input.c中,我們在設備驅動中設置的事件類型會與事件鏈表中的 * 所有事件類型進行比較,匹配成功了將返回id,證明真的很合適,否則NULL */ MATCH_BIT(evbit, EV_MAX); MATCH_BIT(keybit, 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;
既然證明是合適的,接下來就應該登記註冊,並公證了。還記得handler->connect(handler, dev, id)函數吧,當input_match_device()找到最合適的事件處理層驅動時,便執行handler->connect函數進行公證了,看下面這部分代碼(假如說找到了evdev類型的驅動,在input/evdev.c中):
struct evdev *evdev; struct class_device *cdev; dev_t devt; int minor; int error; /* EVDEV_MINORS為32,代表共能容納32個evdev事件層設備,下面代碼在找到空的地方,用於保存evdev事件層的數據,即上面定義的evdev */ for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); /* 這說明內核已經沒辦法再分配這種類型的設備了 */ if (minor == EVDEV_MINORS) { printk(KERN_ERR "evdev: no more free evdev devices\n"); return -ENFILE; } /* 開始給evdev事件層驅動分配空間了 */ evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) return -ENOMEM; /* 初始化client_list列表和evdev_wait隊列,後面介紹 */ INIT_LIST_HEAD(&evdev->client_list); init_waitqueue_head(&evdev->wait); /* 初始化evdev結構體,其中handle為輸入設備和事件處理的關聯接口 */ evdev->exist = 1; evdev->minor = minor; evdev->handle.dev = dev; evdev->handle.name = evdev->name; evdev->handle.handler = handler; evdev->handle.private = evdev; sprintf(evdev->name, "event%d", minor); /* 重要,上層訪問時通過次設備號找到事件處理的接口 */ evdev_table[minor] = evdev; /* evdev事件設備的此設備號的基準值INPUT_MAJOR, EVDEV_MINOR_BASE */ devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), /* 創建用戶事件驅動層設備訪問接口/dev/input/event* */ cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name); if (IS_ERR(cdev)) { error = PTR_ERR(cdev); goto err_free_evdev; } /* 提供/sys目錄的用戶空間接口 */ error = sysfs_create_link(&input_class.subsys.kobj, &cdev->kobj, evdev->name); if (error) goto err_cdev_destroy; /* input_dev設備驅動和handler事件處理層的關聯,由handle完成 */ error = input_register_handle(&evdev->handle);
通過上述代碼的執行,最終,輸入設備在input_register_handle()的關聯下與已經匹配上的handler結合,代碼如下:
struct input_handler *handler = handle->handler; /* 將d_node鏈接到輸入設備的h_list,h_node鏈接到事件層的h_list鏈表上 * 因此,在handle中是輸入設備和事件層的關聯結構體,通過輸入設備可以 * 找到對應的事件處理層接口,通過事件處理層也可找到匹配的輸入設備 */ list_add_tail(&handle->d_node, &handle->dev->h_list); list_add_tail(&handle->h_node, &handler->h_list); /* 如果start函數有定義則調用,但是evdev結構體中並未初始化這個函數 */ if (handler->start) handler->start(handle);
以上是輸入設備驅動註冊的全過程,牽涉的代碼比較多,需要從宏觀上理順。縱觀整個過程,輸入設備驅動最終的目的就是能夠與事件處理層的事件驅動相互匹配,但是在drivers/input目錄下有evdev.c事件驅動、mousedev.c事件驅動、joydev.c事件驅動等等,我們的輸入設備產生的事件應該最終上報給誰,然後讓事件驅動再去處理呢?知道了這麽個原因再看上面代碼就會明白,其實evdev.c、mousedev.c等根據硬件輸入設備的處理方式的不同抽象出了不同的事件處理接口幫助上層去調用,而我們寫的設備驅動程序只不過是完成了硬件寄存器中數據的讀寫,但提交給用戶的事件必須是經過事件處理層的封裝和同步才能夠完成的,事件處理層提供給用戶一個統一的界面來操作。
由於以上的這些原因,才有了上述代碼的關聯過程,通過下圖7,看一下整個關聯註冊的過程:
圖7 input_dev與handler關聯圖
通過上圖我們可以看到input輸入設備匹配關聯的關鍵過程以及涉及到的關鍵函數和數據。這一節主要是從input設備驅動程序的角度去看輸入子系統的註冊過程和三層之間的關聯,下一節將從應用層的角度分析事件的接受過程和處理過程以及三層之間是如何配合處理輸入事件的。
1.2 從應用層的角度出發看input子系統
以上部分已經借助input子系統把input設備驅動層與事件驅動層進行了關聯,我們以前面幾節所介紹的s3c2440_ts.c(輸入設備層驅動)和evdev.c(事件處理層驅動)為例,來分析這一過程。
由於s3c2440_ts.c中上報的事件類型為按鍵、絕對值坐標,而evdev事件驅動程序是全匹配的,因此早在s3c2440_ts.c註冊的過程中,就會創建設備節點/dev/input/event0(假設內核中沒有其他的event類型的輸入設備,這裏就是event0),當然由於我的內核是2.6.22,這個節點的名字是/dev/event0。因此需要註意你的內核版本,以確定設備節點的位置,我們以“/dev/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文件:
[cpp] view plaincopy
- /* 輸入設備初始化函數 */
- static int __init input_init(void)
- {
- class_register(&input_class);
- input_proc_init();
- register_chrdev(INPUT_MAJOR,"input", &input_fops);
- }
可以看到,輸入設備初始化的過程首先建立了input類,初始化input在proc下的節點,然後註冊input設備,設備名稱為input,操作接口是input_fops,主設備號是INPUT_MAJOR=13。
由以上可知,只要是主設備號為13的設備驅動程序,都是用input_fops接口,即當event0設備使用open函數打開時,會調用到input_fops接口中的open驅動函數,這個結構體的初始化為:
[cpp] view plaincopy
- static const struct file_operations input_fops = {
- .owner = THIS_MODULE,
- .open = input_open_file,
- };
可以看到,只實現了一個open功能字段,再看input_open_file的實現:
[cpp] view plaincopy
- 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;
- if (!handler || !(new_fops =fops_get(handler->fops)))
- return -ENODEV;
- old_fops = file->f_op;
- file->f_op = new_fops;
- new_fops->open(inode, file);
- }
以上代碼的功能為找到對應事件驅動層的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查看它的註冊過程:
[cpp] view plaincopy
- static struct input_handler evdev_handler = {
- .event = evdev_event, //事件處理
- .connect = evdev_connect, //設備連接
- .disconnect = evdev_disconnect, //註銷連接
- .fops = &evdev_fops, //驅動功能接口
- .minor = EVDEV_MINOR_BASE, //evdev的值為64
- .name = "evdev", //設備名稱
- .id_table = evdev_ids, //用於匹配設備驅動的數組
- };
- static int __init evdev_init(void)
- {
- return input_register_handler(&evdev_handler); //evdev設備驅動註冊
- }
由以上的內容可以知道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表中的內容是如何填充的,還需要查看這個註冊的過程:
[cpp] view plaincopy
- int input_register_handler(struct input_handler *handler)
- {
- ……
- input_table[handler->minor>> 5] = handler;
- ……
- }
當然這個註冊過程並不是只有這麽一句話,看到這條語句,相信你應該知道什麽意思了。在input的open函數執行之前,即我們的open代碼打開之前,input_table中的字段已經被事件處理層填充了。由於evdev的次設備號在初始化的時候就設置成了64,因此這裏相當於:
[cpp] view plaincopy
- input_table[2]=&evdev_handler;
回到input_open_file函數查看new_fops->open(inode, file)你便知道了調用的是:
[cpp] view plaincopy
- evdev_handler.evdev_fops.open(inode, file);
在分析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--;
上面截取了片段,並沒有執行到open函數,open進行自減操作,表示沒有調用過open,這個值主要是為了close中判斷open為0時釋放資源使用。
不僅如此,我們在觸摸屏驅動中也沒有定義read、write,那當觸摸屏上報事件時,是如何處理的呢?我們需要先到觸摸屏驅動程序中找到上報事件的函數再做進一步分析。
1.3 輸入設備上報事件的處理過程
觸摸屏驅動程序上報事件的函數為:
[cpp] view plaincopy
- input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp);
- input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp);
- input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure);
- input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown);
- input_sync(dev);
然而他們其實是input_event函數的封裝,調用的都是input_event函數,這一函數在input.c中實現如下:
[cpp] view plaincopy
- void input_event(struct input_dev *dev, unsigned int type, unsigned int code, intvalue)
- {
- struct input_handle *handle;
- if (type > EV_MAX || !test_bit(type,dev->evbit))
- return;
- switch (type) {
- case EV_SYN:
- switch (code) {
- case SYN_CONFIG:
- if(dev->event)
- dev->event(dev,type, code, value);
- break;
- case SYN_REPORT:
- if(dev->sync)
- return;
- dev->sync= 1;
- break;
- }
- break;
- case EV_KEY:
- case EV_SW:
- case EV_ABS:
- case EV_REL:
- case EV_MSC:
- case EV_LED:
- case EV_SND:
- case EV_REP:
- case EV_FF:
- }
- if (type != EV_SYN)
- dev->sync = 0;
- if (dev->grab)
- dev->grab->handler->event(dev->grab,type, code, value);
- else
- list_for_each_entry(handle,&dev->h_list, d_node)
- if (handle->open)
- handle->handler->event(handle,type, code, value);
- }
代碼被做了精簡,其中就是在匹配上報的事件,並根據事件的類型調用驅動程序中相應的函數來完成,但是由於我們並,沒有定義過這些函數,因此執行最後的handle_handler_event函數,由事件處理層evdev_event函數來完成事件的保存工作,具體過程如下:
[cpp] view plaincopy
- list_for_each_entry(client,&evdev->client_list, node) {
- client->buffer[client->head].type= type;
- client->buffer[client->head].code= code;
- client->buffer[client->head].value= value;
- client->head= (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
- }
這裏列舉了關鍵代碼,即上報的事件被保存到了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進行讀去和寫入。
1.4 通過設備節點讀取輸入事件
還是以觸摸屏驅動程序和evdev事件處理層驅動來分析:
[cpp] view plaincopy
- 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;
- int retval;
- /* 判斷用戶給的count是否能夠容納事件數據的大小*/
- if (count < evdev_event_size())
- return -EINVAL;
- /* 如果數據不為空並且設備存在並且是阻塞訪問方式才能繼續執行 */
- if (client->head == client->tail&& evdev->exist && (file->f_flags & O_NONBLOCK))
- return -EAGAIN;
- /* 如果數據為空,設置進程等待底層驅動層上報事件到client->buffer中 */
- retval =wait_event_interruptible(evdev->wait,
- client->head != client->tail|| !evdev->exist);
- if (retval)
- return retval;
- if (!evdev->exist)
- return -ENODEV;
- /* 循環讀取數據 */
- while (client->head != client->tail&& retval + evdev_event_size() <= count) {
- struct input_event *event =(struct input_event *) client->buffer + client->tail;
- if (evdev_event_to_user(buffer +retval, event))
- return -EFAULT;
- client->tail = (client->tail+ 1) & (EVDEV_BUFFER_SIZE - 1);
- retval += evdev_event_size();
- }
- return retval;
- }
這裏如果沒有數據,進程會睡眠,那由誰來喚醒呢?細心的話可以發現,當設備驅動層調用input_event上報事件調用相應的event函數進行事件寫入時,是會喚醒阻塞等待的進程的。
1.5 通過設備節點寫入輸入事件
寫入過程:
[cpp] view plaincopy
- static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count,loff_t *ppos)
- {
- /* 循環寫入,調用input_inject_event函數 */
- while (retval < count) {
- if (evdev_event_from_user(buffer +retval, &event))
- return -EFAULT;
- input_inject_event(&evdev->handle,event.type, event.code, event.value);
- retval += evdev_event_size();
- }
- return retval;
- }
上述代碼中的event是input_event數組,包含了事件的類型、鍵值,通過input_inject_event把數據寫入循環數組client->buffer中,input_inject_event調用的是input_event函數。
1.6 總結
本節對input子系統的整個過程做了分析,並從兩個角度進行考慮,對於寫輸入設備驅動程序的來說,需要掌握的是設備應該上報事件的類型,這樣才能匹配到對應的事件層驅動幫助你保存對應的數據,而對於設備上層開發者來說,應該先使用cat /proc/bus/input/devices查看你操作的設備類型和處理接口,以幫助你更好的對設備操作。
linux input輸入子系統分析《四》:input子系統整體流程全面分析