linux驅動由淺入深系列:輸入子系統之二(編寫一個gpio_key驅動)
本系列導航:
在上一篇文章中我們大致瞭解了linux input subsystem的功能及應用層的使用,本文我們一起來看一看驅動程式碼的編寫。接下來一篇,計劃寫一下應用層如何模擬按鍵訊息,產生與按下實際按鍵相同的效果。
在“linux驅動由淺入深系列:驅動程式的基本結構概覽”一文中已經解釋的驅動程式的基本結構,今天我們以上一篇文章中的程式為基本結構,新增相關內容來構成一個gpio按鍵的驅動程式。
先來看看修改完後的程式碼:
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/input.h> #include <linux/workqueue.h> #include <linux/gpio.h> #include <linux/interrupt.h> #define DRIVER_NAME "hello" #define DEVICE_NAME "hello" #define GPIO_KEY 72 MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Radia"); static struct input_dev *input; static irqreturn_t button_interrupt(int irq, void *dev_id) { int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; printk("hello report event val:%d\n", state); input_report_key(input, KEY_VOLUMEUP, !!state); input_sync(input); return IRQ_HANDLED; } static int hello_probe(struct platform_device *pdv) { int irq = gpio_to_irq(GPIO_KEY); printk(KERN_EMERG "hello key probe\n"); gpio_request(GPIO_KEY, "gpio_key_test_button"); gpio_direction_input(GPIO_KEY); request_irq(irq, button_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL); input = input_allocate_device(); set_bit(EV_KEY, input->evbit); input->name = "hello_gpio_key"; input->id.bustype = BUS_HOST; set_bit(KEY_VOLUMEUP, input->keybit); input_register_device(input); //misc_register(&hello_dev); return 0; } static int hello_remove(struct platform_device *pdv) { struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdv); printk(KERN_EMERG "hello key remove\n"); input_unregister_device(input); //misc_deregister(&hello_dev); return 0; } static void hello_shutdown(struct platform_device *pdv) { } static int hello_suspend(struct platform_device *pdv, pm_message_t pmt) { return 0; } static int hello_resume(struct platform_device *pdv) { return 0; } static struct platform_driver hello_driver = { .probe = hello_probe, .remove = hello_remove, .shutdown = hello_shutdown, .suspend = hello_suspend, .resume = hello_resume, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, } }; static int hello_init(void) { int driver_state; printk(KERN_EMERG "hello module has been mount!\n"); driver_state = platform_driver_register(&hello_driver); printk(KERN_EMERG "platform_driver_register driver_state is %d\n", driver_state); platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); printk(KERN_EMERG "platform_device_register_simple end\n"); return 0; } static void hello_exit(void) { printk(KERN_EMERG "hello module has been remove!\n"); platform_driver_unregister(&hello_driver); } module_init(hello_init); module_exit(hello_exit);
使用beyond compare對比修改前後的結果,發現刪除了misc裝置的檔案操作結構體hello_fops相關的open、read、write介面,增加了probe中的相關操作。因為input子系統是使用event與應用層互動的,下一篇文章具體分析。對於新增內容展示在下圖,下面具體分析一下:
1, hello_remove函式是模組解除安裝時呼叫的,其中只是增加了input裝置的解除安裝過程,其實此處還應有gpio、irq、mem等資源的釋放動作,為了便於更為簡潔的演示,先了解輸入裝置的基本註冊過程,此處都省略了(同理,各個函式的返回值也都沒有處理)。在實際專案中是必須要寫完整的,否則驅動層的資源洩漏對整個系統的影響是十分嚴重的。
2, hello_probe函式是在驅動掛載時呼叫的。其中主要對input裝置進行初始化,註冊到input子系統。首先申請了gpio資源(我使用的系統gpio72連線了按鍵,其它平臺可能不同),是為了中斷函式中讀取gpio狀態做的準備。接著申請了該gpio對應的中斷,同時聲明瞭上升、下降沿觸發,設定了終端處理函式button_interrupt。之後配置了input_dev結構體的相關成員(這是一個最簡配置,僅供參考),向系統註冊了input裝置。
3, button_interrupt終端處理函式中,在gpio72上發生上升沿或下降沿時執行。其中首先判斷了gpio72的電平值,以確定按鍵的狀態。之後根據按鍵相應的狀態呼叫input子系統中的訊息傳送函式,嚮應用層傳送了不同鍵值的訊息。
測試:
採用與“linux驅動由淺入深系列:輸入輸出子系統之一”一文中相同的方法,檢視 cat /proc/bus/input/devices檔案發現,我們新加的hello_gpio_key對應系統的/dev/input/event7節點。使用busybox hexdump /dev/input /event7 命令檢視該節點中的二進位制訊息,當gpio72對應的按鍵按下一次後如下圖(相關解析參照“linux驅動由淺入深系列:輸入輸出子系統之一”):
因為button_interrupt中斷處理函式中加了列印,下圖為kernel對應的log