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<linux/module.h> #include<linux/init.h> #inlcude<linux/miscdevice.h> /* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#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<linux/module.h>
#include<linux/init.h>
#include <linux/slab.h> /* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h> /* for struct miscdevice*/
#include <linux/interrupt.h>
#include <linux/fs.h> /* for iormap */
#include <linux/io.h>
#include <linux/slab.h> /* for kmalloc */
#include<linux/uaccess.h> /* for copy_to_usr */
#include <linux/sched.h>
#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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
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是在驅動程式中定義的次裝置號.
執行應用程式按下按鍵效果截圖:
終於搞定了!(歷時兩天半)