1. 程式人生 > >linux驅動由淺入深系列:輸入子系統之二(編寫一個gpio_key驅動)

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