linux-2.6.38 input子系统(用输入子系统实现按键操作)
一、設備驅動程序
在上一篇隨筆中已經分析,linux輸入子系統分為設備驅動層、核心層和事件層。要利用linux內核中自帶的輸入子系統實現一個某個設備的操作,我們一般只需要完成驅動層的程序即可,核心層和事件層內核已經幫我們做好了。因此這篇隨筆主要介紹按鍵操作設備驅動層的代碼。
1.1設備驅動入口函數
在設備驅動入口函數中我們需要做的事:(1)分配一個input_dev 結構體
(2)設置這個input_dev 結構體
(3)調用input_register_device注冊這個input_dev
(4)完成硬件相關操作:如注冊中斷處理函數,添加定時器等
static int button_init(void) {int i;int err;/* 1. 分配一個 input_dev 結構體*/button_dev = input_allocate_device();/* 2. 設置 *//* 2.1 能產生哪類事件 */set_bit(EV_KEY, button_dev->evbit);/* 2.2 能產生這類事件下的哪些操作: L S ENTER LEFTSHIT*/set_bit(KEY_L, button_dev->keybit);set_bit(KEY_S, button_dev->keybit);set_bit(KEY_ENTER, button_dev->keybit);set_bit(KEY_LEFTSHIFT, button_dev->keybit);/* 3. 注冊 */input_register_device(button_dev);/* 4. 硬件相關操作*/gpkcon = ioremap(GPKCON_PA, 4); //io口映射gpkdat = ioremap(GPKDAT_PA, 4);gpndat = ioremap(GPNDAT_PA, 4);init_timer(&button_timer); // 初始化定時器button_timer.function = button_timer_function;// 指定定時器的處理函數add_timer(&button_timer); // 添加定時器for (i=0; i<4; i++){err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,button_irqs[i].name,(void *)&button_irqs[i]);if (err)break;} return 0; }1.1.1在初始化iinput_dev結構體過程
主要對其中的如下數組做了初始化,從而來確定該設備支持哪些事件,支持哪些操作。
初始化時首先要確定設備能夠產生哪一類事件
事件的類型如下:
事件類型的設置主要對evbit[]數組中的相應位做設置:?set_bit(EV_KEY, button_dev->evbit);
? 然后再確定設備支持該類事件下的哪些操作
例如:在相對坐標事件下可以支持如下操作
在本次按鍵驅動程序中按鍵設備產生的事件自然是按鍵事件,按鍵事件支持KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT? 4個操作,分別對應 L ,S, enter,shift?
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit;
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
1.1.2 注冊輸入設備input_register_device()
這個函數在上一篇博客中已經做了簡要分析,這里在提一下input_register_device()中做了哪些事
(1)?設置同步事件、清除KEY_RESERVED、清除bitmasks中沒有提到的位
(2) 初始化定時器,確定定時器的處理函數。這里定時器與重復上報事件有關,注意在事件類型中有EV_REP事件,設置這個事件在ev_bit中的相應位,就可以重復上報事件。
(3) 設置getkeycode 和 setkeycode 函數
(4) device_add,?input_dev包含的device結構注冊到Linux設備模型中,在sysfs文件系統中可以看到增加了設備input1
(5)list_add_tail? 在上一篇博客中已經介紹了
(6)遍歷iinput_handler_list 中的事件處理器 input_handler, 與input_handler 進行匹配 連接操作。 具體的連接操作在下邊分析。
1.1.3 硬件相關操作
定時器、IO映射、注冊中斷
2. 中斷處理函數
static void button_timer_function(unsigned long data) {int num;int tmp;struct button_irq_desc* pindesc = irq_pdesc;if (! pindesc)return;num = pindesc->number; tmp = *gpndat;input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(1<<num)));input_sync(button_dev);return; } static irqreturn_t buttons_interrupt(int irq, void *dev_id) {/* 10 ms 則 hz/100 100 ms 則 hz/10*/irq_pdesc = (struct button_irq_desc *)dev_id;mod_timer(&button_timer, jiffies+HZ/100);return IRQ_RETVAL(IRQ_HANDLED); }中斷處理函數包括兩部分:定時中斷用于消除按鍵抖動
按鍵中斷處理函數主要用來調
整定時器事件
當有按鍵中斷發生時,我們需要上報事件:
input_event(button_dev, EV_KEY,button_irqs[num].key_val , !(tmp &(1<<num)));
input_sync(button_dev);
2.1.1 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);} } 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_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);elseinput_stop_autorepeat(dev);}disposition = INPUT_PASS_TO_HANDLERS;}break;if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)dev->sync = false;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_dev下的event, 要么執行input_handler下的event函數,至于執行哪一個和disposition這個變量有關。
需要注意的是如果執行的是input_dev下邊的event,那么應該只會執行一個event函數,如果要是執行的是input_handler下的event函數,那么會執行input_pass_event 函數。這個函數中通過input_dev->h_list鏈表上掛的input_handler結構體,找到與之匹配的input_handler。然后執行input_handler下的event函數。? ? ? 如果input_dev與多個input_handler事件處理器匹配了,那么當設備驅動上報事件時,并且要執行input_handler中的event函數,那么會依次執行這些匹配好的input_handler的event函數。在接下的實驗中可以看到這一點。
二、程序執行部分過程分析:
2.1 input_dev與 input_handler 的連接
因為我們的程序中已經注冊了evdev_handler事件處理器(在上一篇博客中已經分析過了input_register_handler)和 kbd_handler 事件處理器(在keyboard.c 文件中 kbd_init-->input_register_handler(&kbd_handler)) ,當在驅動程序中input_register_device后,設備驅動和這兩個事件處理器進行匹配和連接操作。
2.1.1 button_dev 和 evdev_handler 連接
匹配完成之后會執行連接操作,連接操作執行的是evdev_handler->connet 函數,這個函數已經在前一篇博客中分析了,這里只簡述函數執行過程和效果。
(1) 分配evdev 結構體
(2) 設置evdev 結構體下邊的 handle 和 dev?
? ? ?(3) 注冊evdev 下邊的handle ,在上一篇博客中已經分析了這個handle是連接input_dev 和 input_handler 的橋梁
(4) 添加設備device_add(&evdev->dev)
執行完這個函數應該可以看到在/dev/input/下出現even0 設個設備
可以看到主設備號為13, 次設備號為64, 這里次設備號=64+minor, 因為evdev_handler 還沒有和任何設備建立連接,所以在input_table[] 數組中還沒存放任何evdev結構體,因此minor的值為0, 故此設備號為64+0=64。
2.1.2 button_dev 和 kbd_handler 的匹配過程
static int kbd_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id) {struct input_handle *handle;int error;handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);if (!handle)return -ENOMEM;handle->dev = dev;handle->handler = handler;handle->name = "kbd";error = input_register_handle(handle);if (error)goto err_free_handle;error = input_open_device(handle);if (error)goto err_unregister_handle;return 0;err_unregister_handle:input_unregister_handle(handle);err_free_handle:kfree(handle);return error; }這里的連接過程比較簡單,沒有上一個連接過程那么復雜,但是可以看到主要的數據結構input_handle并沒有少,因為這個結構體是input_dev和input_handler的聯系橋梁,復雜的數據結構之間的關系可以使input_dev 可以訪問到input_handle從而訪問到input_handler。(在事件上報函數中分析過怎樣從input_dev找到input_handler)
2.2? 事件上報函數分析
前文已經提到了button_dev 與 evdev_handler 和 kbd_handler 匹配連接成功,那么在事件上報的時候,就會分別執行這兩個事件處理器中的event函數。
2.2.1 evdev->event 函數
這個函數已經在上一篇博客中提前分析過了。
2.2.2 kbd->event 函數
static void kbd_event(struct input_handle *handle, unsigned int event_type,unsigned int event_code, int value) {/* We are called with interrupts disabled, just take the lock */spin_lock(&kbd_event_lock);if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))kbd_rawcode(value);if (event_type == EV_KEY)kbd_keycode(event_code, value, HW_RAW(handle->dev));spin_unlock(&kbd_event_lock);tasklet_schedule(&keyboard_tasklet);do_poke_blanked_console = 1;schedule_console_callback(); }三、實驗效果
3.1 執行hexdump /dev/input/event0
依次按下 l? ?s? ?enter 對應的按鍵:l 對應0x26? s 對應0x1f? enter對應0x1c??
3.2? 執行cat /dev/tty1?
并且將標準輸入重定向到/dev/tty1??依次按下 l? ?s? ?enter 對應的按鍵
可以看到依次按下l s enter 后執行了ls 命令
同時需要強調的是:上邊的兩個命令的執行在不同的shell 下,但是只按下了一次l s enter后分別在兩個shell中打印了不同的結果。這就證明了之前所說的一個button_dev 與 evdev_handler 和 kbd_handler 建立了連接,上報一次事件,會分別調用這兩個事件處理器的event 函數。
?
轉載于:https://www.cnblogs.com/zf1-2/p/10859535.html
總結
以上是生活随笔為你收集整理的linux-2.6.38 input子系统(用输入子系统实现按键操作)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用C语言用指针怎么算通用定积分,C语言:
- 下一篇: c语言库函数fgets,C语言 标准I/