歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux按鍵驅動程序設計詳解---從簡單到不簡單

Linux按鍵驅動程序設計詳解---從簡單到不簡單

日期:2017/3/1 11:51:51   编辑:關於Linux

混雜設備驅動模型:

1. 混雜設備描述

在Linux系統中,存在一類字符設備,它們擁有相同的主設備號(10),單次設備號不同,我們稱這類設備為混 雜設備(miscdevice).所有的混雜設備形成一個鏈表,對設備訪問時內核根據次設備號查到相應的混雜設備。

混雜設備也是字符設備!

linux中使用struct miscdevice來描述一個混雜設備。

\

2. 混雜驅動注冊

Linux中使用misc_register函數來注冊一個混雜設備驅動。

int misc_register(struct miscdev *misc)

3. 范例驅動分析

3.1 初始化miscdevice(minor、name、fops)

3.2 注冊miscdevice (通過misc_register函數實現)

這裡安照上面的分析,先來搭建一個最簡單只有一個open操作的混雜按鍵設備驅動模型,後邊逐步深入分析逐步完善代碼。

key.c

#include
#include
#inlcude /* for struct miscdevice*/

int key_open(struct inode *node, struct file *filp)
{
	
	
	return 0;
}

struct file_operations key_fops = 
{
	.open = key_open,
};

struct miscdevice key_miscdev  //定義一個misdevice結構
{
	.minor = 200;
	.name = "key";
	.fops = &key_fops;//這裡key_fops是一個struct file_operations結構
};

static int key_init()
{
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	return 0;
}

static void key_exit()
{
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
}


module_init(key_init);
module_exit(key_exit);

2. Linux 中斷處理流程分析

下面先來分析寫好按鍵驅動的一些准備工作!按鍵一般用中斷的模式來處理,這裡先分析linux中斷處理程序:

1. 裸機中斷處理流程分析

1.1 中斷有一個統一的入口 irq:

......

第一步: 保護現場(中斷部分執行完畢後要恢復之前的狀態繼續執行)

第二步: 跳轉到hand_ini處執行中斷程序

先事先注冊中斷程序,然後根據相應的中斷找到對應的中斷處理程序

第三步:恢復現場,

在Linux操作系統中,irq中斷的統一入口其實也是這樣的(entry-armv.S文件中)

\

這裡的irq_hander其實是一個宏定義:

\

而arch_irq_hander_default這個宏是在entry-macro-multi.S這個文件中

\

拿到中斷號,然後設置相關寄存器並且調到asm_do_IRQ處理中斷

\

看看generic_handle_irq(irq)這個函數:

\

然後函數又跳到這裡了:

\

最後調到了handle_irq這個結構中。

這裡總結一下上面函數跳轉的分析過程:

第一步:根據中斷產生的統一入口進入中斷處理程序,拿到產生中斷源的中斷號

第二步:根據這個中斷號irq找到irq_desc結構, 在這個irq結構中就會有一個action選項,在這個action結構中就是用戶事先填寫的中斷處理程序handler,這裡用一張圖來說明:

\

上面分析了那麼多,其實就是為了說明在驅動中如果要用中斷,驅動程序該干嘛?

第一點:實現中斷處理程序

第二點:當我們的中斷產生了,能夠被linux操作系統調用到用戶事先定義好的中斷處理程序,還需要把中斷處理程序 注冊到Linux操作系統中來,簡單的來說就是注冊中斷

3. Linux 中斷處理程序設計

3.1 注冊中斷

\

參數說明:

unsigned int irq :中斷號

void(*handler)(int , void *) :中斷處理函數

unsigned long flags:與中斷管理有關的各種選項

const char *devname:設備名

void *dev_id:共享中斷時使用

在flags參數中, 可以選擇一些與中斷管理有關的選項,如:

. IRQF_DISABLED(SA_INTERRUPT) 快速中斷

如果設置該位,表示是一個“快速”中斷處理程序;如果沒有設置該位,那麼就是一個“慢速”中斷處理程序。

. IRQF_SHARED(SA_SHIRQ) 共享中斷該位表明該中斷號是多個設備共享的。

快/慢速中斷的主要區別在於:快速中斷保證中斷處理的原子性(不被打斷),而慢速中斷則不保證。換句話說,也就是“開啟中斷”標志位(處理器IF)在運行快速中斷處理程序時是關閉的,因此在服務該中斷時,不會被其他類型的中斷打斷;而調用慢速中斷處理時,其他類型的中斷仍可以得到服務。

3.2 中斷處理

中斷處理程序的特別之處是在中斷上下文中運行的,它的行為為受到某些限制:

1. 不能使用可能引起阻塞的函數

2. 不能使用可能引起調度的函數

處理流程:

\

3.3 注銷處理
當設備不再需要使用中斷時(通常在驅動卸載時),應當把它們注銷,使用函數:

void free_irq(unsigned int irq, void *dev_id) // 參數dev_id 可以結和上面那張圖來看,就是共享中斷中的那個中斷

結和上面的分析在之前的代碼基礎上加入下面的部分:

中斷處理函數部分:

\

\

下面來分析按鍵硬件部分的相關知識!硬件原理圖以及相關GPIO設置

這裡先貼上OK6410開發板上的按鍵硬件原理圖部分:

這裡KEYINT1是和GPN0相連,

\

\

對應的CPU引腳是GPN組,下面查看下GPN引腳datasheet的相關部分:

由下面的圖這裡可以看到將GPNCON寄存器的最後兩位設置為0b10(外部中斷模式)

\

GPN0對應的外部中斷號查芯片手冊可以看到為:XEINT0

\

這裡看看OK6410內核源碼部分關於中斷號的宏定義:

這個在Irqs.h文件中:要與自己使用的硬件平台對應,我這裡是OK6410

\

這裡對應的設備中斷號為S3C_EINT(0)或者寫出IRQ_EINT(0)都是一樣的

這個文件源碼中還有一句#define S3C_IRQ_OFFSET (32)

中斷號偏移 其中前面的32個中斷號是留給用戶程序作為軟中斷來使用,

這裡貼出在前面的基礎上加的key.c的代碼:

#include 
#include 
#include  /* for struct miscdevice*/
#include 
#include  /* for iormap */
#include 

#define GPNCON 0x7F008830

irqreturn_t key_int(int irq, void *dev_id)
{
	//1. 檢測是否發生了按鍵中斷 這裡可以暫時不做,因為這裡沒有使用共享中斷
	
	//2. 清除已經發生的按鍵中斷 這個是指硬件內部處理,按鍵CPU內部不需要做處理
	     //比如如果是網卡驅動 就要處理
	
	//3. 打印按鍵值
	
	printk(KERN_WARNING"key down!\n");
	
	return 0;
}

void key_hw_init(void) //按鍵硬件初始化部分
{
	unsigned int *gpio_config;
	unsigned short data;
	
	//第一步:設置GPNCON寄存器設置GPIO為輸入
	gpio_config = ioremap(GPNCON, 4);//將物理地址轉化為虛擬地址
	data = readw(gpio_config);
	data &= ~0b11; //先清零
	data |= 0b10;  //後兩位設置成0b10
	writew(data, gpio_config);
	printk(KERN_WARNING"init ...!\n");
	//第二步: 按鍵中斷部分相應處理 注冊中斷 注銷等等
}

int key_open(struct inode *node, struct file *filp)
{
	printk(KERN_WARNING"open ...!\n");
	
	return 0;
}

struct file_operations key_fops = 
{
	.open = key_open,
};

struct miscdevice key_miscdev = //定義一個misdevice結構
{
	.minor = 200,
	.name = "key",
	.fops = &key_fops,//這裡key_fops是一個struct file_operations結構
};

static int key_init(void)
{
	int err;
	
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	//按鍵初始化 硬件初始化部分一般可一放在模塊初始化部分或者open函數中 這裡放在模塊初始化部分
	key_hw_init();
	
	//由高電平變為低電平產生中斷 IRQF_TRIGGER_FALLING
	
	
	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注冊中斷處理程序 5個參數
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}
	
	return 0;
	
irq_err:
		misc_deregister(&key_miscdev);	
	return -1;
}

static void key_exit(void)
{
	free_irq(S3C_EINT(0), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
	
	printk(KERN_WARNING"key up!");
}


module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");

這裡貼一個代碼編譯後在開發板上運行,按下按鍵的效果截圖:

\

中斷分層設計:

1. 中斷嵌套

\

2. 中斷分層方式

2.1 軟中斷

2.2 tasklet

2.3 工作隊列(使用更廣泛)

\

工作隊列是一種將任務推後執行的形式,他把推後的任務交由一個內核線程去執行。這樣下半部會在進程上下文執行,它允許重新調度甚至睡眠。每個被推後的任務叫做“工作”,由這些工作組成的隊列稱為工作隊列

\

\

這裡應該是用struct workqueue_struct:

\

\

\

2.1. 從內核源碼查看create_workqueue函數的用法:

\

這是內核源碼裡面找到的這個函數用法示例,這裡可以看到create_workqueue函數只有一個參數,參數為工作隊列的名字,返回的為創建好的一個工作隊列指針,下面第三個箭頭所指向的部分就是這個指針的類型!

用法示例:

struct workqueue_struct *my_wq; //定義一個工作隊列指針

my_wq = create_workqueue("my_queue");

2.2. 下面去內核源碼中查找一下init_work這個函數的用法:

\

兩個參數:

work :要初始化的工作work指針

func :工作要執行的函數

用法示例:

struct work_struct *work1; //定義一項工作

void work1_func(struct work_struct *work)
{
printk(KERN_WARNING"this is work1>\n");
}

work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work1 , work1_func );

2.3. queue_work函數用法示例:

\

也是兩個參數:

一個是工作隊列指針 struct workqueue_struct *wq

一個是工作指針

用法示例:

queue_work(my_wq, work1);

下面根據上面的分析這裡貼出一個示例小程序:

#include
#include
#include  /* for kmalloc */

struct workqueue_struct *my_wq; //定義一個工作隊列指針
struct work_struct *work1; //定義一項工作
struct work_struct *work2; //定義一項工作

MODULE_LICENSE("GPL");

void work1_func(struct work_struct *work)
{
	printk(KERN_WARNING"this is work1>\n");
}

void work2_func(struct work_struct *work)
{
	printk(KERN_WARNING"this is work2>\n");
}

int init_que(void)
{
	//1. 創建工作隊列
	my_wq = create_workqueue("my_queue");
	
	//2. 創建工作
	//work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
	  work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
	INIT_WORK(work1 , work1_func );
	
	//3. 掛載(提交)提交工作
	queue_work(my_wq, work1);
	
	//2. 創建工作
	work2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
	INIT_WORK(work2 , work2_func );
	
	//3. 掛載(提交)提交工作
	queue_work(my_wq, work2);
	
	
	return 0;
}

void clean_que(void)
{
	
}

module_init(init_que);
module_exit(clean_que);
\

3. 使用工作隊列實現分層

在大多數情況下,驅動並不需要自己建立工作隊列,只需定義工作,然後將工作提交到內核已經定義好的工作隊列keventd_wq中。

3.1 提交工作到默認隊列

schedule_work

在上面的代碼這樣修改也是同樣的效果:

\

有了上面的基礎,然後對之前的按鍵驅動進行改進!通過中斷分層來實現按鍵驅動

按鍵中斷處理程序 硬件處理部分比較簡單,中斷上半部 硬件中斷處理基本可以不做
下半部 和硬件沒有什麼關系的部分,就是下面打印按鍵值部分 可以放到按鍵中斷以外來處理,為系統節省更多的時間出來,避免應為中斷程序處理部分耗時過長造成中斷丟失!

#include 
#include 
#include  /* for struct miscdevice*/
#include 
#include  /* for iormap */
#include 
#include  /* for kmalloc */

#define GPNCON 0x7F008830

struct work_struct *work1;//定義一項工作

void work1_func(struct work_struct *work)
{
	printk(KERN_WARNING"key down!\n");
}

irqreturn_t key_int(int irq, void *dev_id)
{
	//1. 檢測是否發生了按鍵中斷 這裡可以暫時不做,因為這裡沒有使用共享中斷
	
	//2. 清除已經發生的按鍵中斷 這個是指硬件內部處理,按鍵CPU內部不需要做處理
		 
	//3. 提交下半部
	schedule_work(work1);
	
	return 0;
}

void key_hw_init(void) //按鍵硬件初始化部分
{
	unsigned int *gpio_config;
	unsigned short data;
	
	//第一步:設置GPNCON寄存器設置GPIO為輸入
	gpio_config = ioremap(GPNCON, 4);//將物理地址轉化為虛擬地址
	data = readw(gpio_config);
	data &= ~0b11; //先清零
	data |= 0b10;  //後兩位設置成0b10
	writew(data, gpio_config);
	printk(KERN_WARNING"init ...!\n");
	//第二步: 按鍵中斷部分相應處理 注冊中斷 注銷等等
}

int key_open(struct inode *node, struct file *filp)
{
	printk(KERN_WARNING"open ...!\n");
	
	return 0;
}

struct file_operations key_fops = 
{
	.open = key_open,
};

struct miscdevice key_miscdev = //定義一個misdevice結構
{
	.minor = 200,
	.name = "key",
	.fops = &key_fops,//這裡key_fops是一個struct file_operations結構
};

static int key_init(void)
{
	int err;
	
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	//按鍵初始化 硬件初始化部分一般可一放在模塊初始化部分或者open函數中 這裡放在模塊初始化部分
	key_hw_init();
	
	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
	INIT_WORK(work1 , work1_func );
	
	//由高電平變為低電平產生中斷 IRQF_TRIGGER_FALLING
	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注冊中斷處理程序 5個參數
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}
	
	return 0;
	
irq_err:
		misc_deregister(&key_miscdev);	
	return -1;
}

static void key_exit(void)
{
	free_irq(S3C_EINT(0), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
	
	printk(KERN_WARNING"key up!");
}


module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");
編譯並且insmod安裝這個驅動模塊,同樣可以看到按鍵打印的效果!不過本質上驅動處理效率上提高了!當然這裡只是一個很簡單的例程!

按鍵定時器去抖:

按鍵所用開關為機械彈性開關,當機械觸點斷開、閉合時,由於機械觸點的彈性作用,開關不會馬上穩定接通或斷開。因而在閉合及斷開的瞬間總是伴隨有一連串的抖動。

按鍵去抖動的方法主要有兩種,一種是硬件電路去抖動;另一種就是軟件延時去抖。而延時一般由分為兩種,一種是for循環等待,另一種是定時器延時,在操作系統中,由於效率方面的原因,一般不允許使用for循環來等待,只能使用定時器。

內核定時器:

\

上面兩個重要的成員(紅色部分)

expires: 超時也就是定時多長時間

function: 函數指針

\

這之間的函數就不細說了,還是同樣的方法不會就查看內核代碼!上面的按鍵驅動實際上是不完善的,按一下會打印好幾個按鍵按下的信息,這裡利用上面介紹到的內核定時器知識優化上面的按鍵程序:

#include 
#include 
#include  /* for struct miscdevice*/
#include 
#include  /* for iormap */
#include 
#include  /* for kmalloc */

#define GPNCON  0x7F008830
#define GPNDAT  0x7F008834

unsigned int *gpio_data;

struct work_struct *work1;//定義一項工作

struct timer_list key_timer; //定義一個定時器key_timer

void work1_func(struct work_struct *work)
{
	//啟動定時器 jiffies是全局變量,用來表示當前系統時間 1S=1000個滴答數
	mod_timer(&key_timer,jiffies + HZ/10); //設置100ms超時 1HZ=1S
}

void key_timer_func(unsigned long data)
{
	unsigned int key_val;
	
	key_val = readw(gpio_data)&0x01; //只讀取最後一位
	
	if(key_val == 0)
	{
		printk(KERN_WARNING"OK6410 key0 down!\n");
	}

}

irqreturn_t key_int(int irq, void *dev_id)
{
	//1. 檢測是否發生了按鍵中斷 這裡可以暫時不做,因為這裡沒有使用共享中斷
	
	//2. 清除已經發生的按鍵中斷 這個是指硬件內部處理,按鍵CPU內部不需要做處理
		 
	//3. 提交下半部
	schedule_work(work1);
	
	return 0;
}

void key_hw_init(void) //按鍵硬件初始化部分
{
	unsigned int *gpio_config;
	unsigned short data;
	
	gpio_config = ioremap(GPNCON, 4);//將物理地址轉化為虛擬地址
	data = readw(gpio_config);
	data &= ~0b11; //先清零
	data |= 0b10;  //後兩位設置成0b10
	writew(data, gpio_config);
	
	gpio_data = ioremap(GPNDAT, 4);//將物理地址轉化為虛擬地址
	
	printk(KERN_WARNING"init ...!\n");
}

int key_open(struct inode *node, struct file *filp)
{
	printk(KERN_WARNING"open ...!\n");
	
	return 0;
}

struct file_operations key_fops = 
{
	.open = key_open,
};

struct miscdevice key_miscdev = //定義一個misdevice結構
{
	.minor = 200,
	.name = "key",
	.fops = &key_fops,//這裡key_fops是一個struct file_operations結構
};

static int key_init(void)
{
	int err;
	
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	key_hw_init();
	
	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
	INIT_WORK(work1 , work1_func );
	
	//初始化定時器
	init_timer(&key_timer);
	key_timer.function = key_timer_func; //將定義的函數賦值給函數指針
	
	//注冊定時器
	add_timer(&key_timer);
	
	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}
	
	return 0;
	
irq_err:
		misc_deregister(&key_miscdev);	
	return -1;
}

static void key_exit(void)
{
	free_irq(S3C_EINT(0), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
	
	printk(KERN_WARNING"key up!");
}

module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");

編譯運行可以看到:按一下按鍵 只打印一個OK6410 key0 down!

\

在上面的基礎上繼續優化,實現多按鍵驅動這裡增加key5按鍵!(結合上邊的原理圖部分)

#include 
#include 
#include  /* for struct miscdevice*/
#include 
#include  /* for iormap */
#include 
#include  /* for kmalloc */

#define GPNCON  0x7F008830
#define GPNDAT  0x7F008834

unsigned int *gpio_data;

struct work_struct *work1;//定義一項工作

struct timer_list key_timer; //定義一個定時器key_timer

void work1_func(struct work_struct *work)
{
	//啟動定時器 jiffies是全局變量,用來表示當前系統時間 1S=1000個滴答數
	mod_timer(&key_timer,jiffies + HZ/10); //設置100ms超時 1HZ=1S
}

void key_timer_func(unsigned long data)
{
	unsigned int key_val;
	
	key_val = readw(gpio_data)&0x01; //只讀取最後一位
	
	if(key_val == 0)
	{
		printk(KERN_WARNING"OK6410 key0 down!\n");
	}
	
	key_val = readw(gpio_data)&0x20; //只讀取最後一位
	
	if(key_val == 0)
	{
		printk(KERN_WARNING"OK6410 key5 down!\n");
	}

}

irqreturn_t key_int(int irq, void *dev_id)
{
	//1. 檢測是否發生了按鍵中斷 這裡可以暫時不做,因為這裡沒有使用共享中斷
	
	//2. 清除已經發生的按鍵中斷 這個是指硬件內部處理,按鍵CPU內部不需要做處理
		 
	//3. 提交下半部
	schedule_work(work1);
	
	return 0;
}

void key_hw_init(void) //按鍵硬件初始化部分
{
	unsigned int *gpio_config;
	unsigned short data;
	
	gpio_config = ioremap(GPNCON, 4);//將物理地址轉化為虛擬地址
	data = readw(gpio_config);
	data &= ~0b110000000011; //先清零
	data |= 0b100000000010;  //後兩位設置成0b10
	writew(data, gpio_config);
	
	gpio_data = ioremap(GPNDAT, 4);//將物理地址轉化為虛擬地址
	
	printk(KERN_WARNING"init ...!\n");
}

int key_open(struct inode *node, struct file *filp)
{
	printk(KERN_WARNING"open ...!\n");
	
	return 0;
}

struct file_operations key_fops = 
{
	.open = key_open,
};

struct miscdevice key_miscdev = //定義一個misdevice結構
{
	.minor = 200,
	.name = "key",
	.fops = &key_fops,//這裡key_fops是一個struct file_operations結構
};

static int key_init(void)
{
	int err;
	
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	key_hw_init();
	
	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
	INIT_WORK(work1 , work1_func );
	
	//初始化定時器
	init_timer(&key_timer);
	key_timer.function = key_timer_func; //將定義的函數賦值給函數指針
	
	//注冊定時器
	add_timer(&key_timer);
	
	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}
	if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}

	
	return 0;
	
irq_err:
		misc_deregister(&key_miscdev);	
	return -1;
}

static void key_exit(void)
{
	free_irq(S3C_EINT(0), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
	
	printk(KERN_WARNING"key up!");
}

module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");

運行效果:

\

阻塞型驅動設計:

\

阻塞的必要性:

1. 當一個設備無法立即滿足用戶的讀寫請求時應當如何處理?例如: 調用read時,設備沒有數據提供,但以後可能會有:或者一個進程試圖向設備寫入數據,但是設備暫時沒有准備好接受數據。當上述情況發生的時候,驅動程序應當阻塞進程,當它進入等待(睡眠)狀態,直到請求可以得到滿足。

2. 在實現阻塞型驅動的過程中,也需要有一個“候車室”來安排被阻塞的進程“休息”,當喚醒它們的條件成熟時,則可以從“候車室”中將這些進程喚醒。而這個“候車室”就是等待隊列。

\

\

\

\

這裡結合阻塞型驅動的知識點繼續優化程序代碼!這裡順便寫個應用測試程序來測試按鍵驅動!

key.c代碼

#include 
#include 
#include  /* for struct miscdevice*/
#include 
#include  /* for iormap */
#include 
#include  /* for kmalloc */
#include /* for copy_to_usr */
#include 

#define GPNCON  0x7F008830
#define GPNDAT  0x7F008834

unsigned int *gpio_data;

struct work_struct *work1;//定義一項工作

struct timer_list key_timer; //定義一個定時器key_timer

unsigned int key_num = 0;

wait_queue_head_t key_q; //定義一個等待隊列

void work1_func(struct work_struct *work)
{
	//啟動定時器 jiffies是全局變量,用來表示當前系統時間 1S=1000個滴答數
	mod_timer(&key_timer,jiffies + HZ/10); //設置100ms超時 1HZ=1S
}

void key_timer_func(unsigned long data)
{
	unsigned int key_val;
	
	key_val = readw(gpio_data)&0x01; //只讀取最後一位
	if(key_val == 0)
	{
		//printk(KERN_WARNING"OK6410 key0 down!\n");
		key_num = 1;
	}
	
	key_val = readw(gpio_data)&0x20; //只讀取最後一位
	if(key_val == 0)
	{
		//printk(KERN_WARNING"OK6410 key5 down!\n");
		key_num = 6;
	}
	
	wake_up(&key_q);
}

irqreturn_t key_int(int irq, void *dev_id)
{
	//1. 檢測是否發生了按鍵中斷 這裡可以暫時不做,因為這裡沒有使用共享中斷
	
	//2. 清除已經發生的按鍵中斷 這個是指硬件內部處理,按鍵CPU內部不需要做處理
		 
	//3. 提交下半部
	schedule_work(work1);
	
	//return 0;
	return IRQ_HANDLED;
}

void key_hw_init(void) //按鍵硬件初始化部分
{
	unsigned int *gpio_config;
	unsigned short data;
	
	gpio_config = ioremap(GPNCON, 4);//將物理地址轉化為虛擬地址
	data = readw(gpio_config);
	data &= ~0b110000000011; //先清零
	data |= 0b100000000010;  //後兩位設置成0b10
	writew(data, gpio_config);
	
	gpio_data = ioremap(GPNDAT, 4);//將物理地址轉化為虛擬地址
	
	printk(KERN_WARNING"init ...!\n");
}

int key_open(struct inode *node, struct file *filp)
{
	printk(KERN_WARNING"open ...!\n");
	
	return 0;
}

ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
	wait_event(key_q,key_num);//休眠 沒有按下為0
	
	//將key_value值返回給用戶空間
	printk(KERN_WARNING"in kernel :key num is %d\n",key_num);
	copy_to_user(buf, &key_num, 4); //buf為用戶空間傳過來的地址
	
	key_num = 0;
	
	return 4;
}

struct file_operations key_fops = 
{
	.open = key_open,
	.read = key_read,
};

struct miscdevice key_miscdev = //定義一個misdevice結構
{
	.minor = 200,
	.name = "6410key",
	.fops = &key_fops,//這裡key_fops是一個struct file_operations結構
};

static int key_init11(void)
{
	int err;
	
	misc_register(&key_miscdev);//注冊一個混雜設備驅動設備
	
	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}
	if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
	{
		 printk(KERN_WARNING"err = %d\n", err);
		 goto irq_err;
	}

	key_hw_init();
	
	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
	INIT_WORK(work1 , work1_func );
	
	//初始化定時器
	init_timer(&key_timer);
	key_timer.function = key_timer_func; //將定義的函數賦值給函數指針
	
	//注冊定時器
	add_timer(&key_timer);
	
	//初始化一個等待隊列
	init_waitqueue_head(&key_q);
	
	return 0;
	
irq_err:
		misc_deregister(&key_miscdev);	
	return -1;
}

static void key_exit(void)
{
	free_irq(S3C_EINT(0), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	free_irq(S3C_EINT(5), 0);//注銷中斷 這裡irqnumber參數暫時用一個變量來表示(中斷號)
	
	misc_deregister(&key_miscdev);//注銷一個混雜設備驅動
	
	printk(KERN_WARNING"key up!");
}

module_init(key_init11);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("key driver");

key_app.c

#include
#include
#include
#include
#include
#include

int main(void)
{
	int fd;
	int key_num;
	int ret;
	
	//1. 打開設備
	fd = open("/dev/ok6410key", 0);
	if(fd < 0)
	{
		printf("open key_device fail!\n");
	}
	

	//2. 讀取設備
	ret = read(fd, &key_num, 4);
	if(ret == -1)
	{
		printf("read fail\n");
	}
	printf("key is %d\n", key_num);

	//3. 關閉設備
	close(fd);
	
	return 0;
}

Makefile

obj-m := key.o
KDIR := /home/kernel/linux-ok6410
all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order

編譯:

\

同步到開發上,安裝驅動模塊 insmod key.ko

然後mknod /dev/ok6410key c 10 200

這一行的命令作用是產生設備結點供應用程序訪問 ,ok6410key為設備名字 c表示這個是字符設備 混雜設備也是字符設備 10 是混雜字符設備的統一設備號 200是在驅動程序中定義的次設備號.

運行應用程序按下按鍵效果截圖:

\

Copyright © Linux教程網 All Rights Reserved