按鍵驅動程序
本文學習主要包含按鍵硬件的實現、中斷分層管理、按鍵定時器去抖、阻塞性驅動程序設計。這里面需要使用到混雜設備驅動和中斷處理程序的內容。
一、創建按鍵混雜設備驅動模型
1 int key_open(struct inode *node,struct file *filp) 2 { 3 return 0; 4 } 5 struct file_operations key_fops = 6 { 7 .open = key_open, 8 }; 9 struct miscdevice key_miscdev = { 10 .minor = 200, //次設備號 11 .name = "6410key", //設備名 12 .fops = &key_fops, 13 };
二、按鍵硬件的實現
首先是按鍵的初始化,按鍵的初始化可以選擇在open函數,和模塊的初始化函數當中完成硬件的初始化。下面我們是選擇在模塊的初始化函數進行按鍵的初始化。按鍵的初始化,主要涉及對GPIO的引腳的功能進行相應的設置。我用的是ok6410A開發板上面有六個按鍵,核心板原理圖如下:
2.1硬件初始化
s3c6410芯片手冊對GPNCON的IO功能定義如下因此對按鍵硬件的初始化如下:
1 #define GPNCON 0x7f008830 2 #define GPNDAT 0x7f008834 3 4 void key_hw_init(void) //硬件初始化 5 { 6 unsigned int *gpio_config; 7 unsigned short data; 8 gpio_config = ioremap(GPNCON,4); //動態映射虛擬地址 9 data = readw(gpio_config); 10 data &= ~0xfff; 11 data |= 0xaaa; 12 writew(data,gpio_config); 13 gpio_dat = ioremap(GPNDAT,4); //將數據寄存器地址轉化為虛擬地址 14 }
2.2按鍵中斷程序(暫時注冊第一個按鍵)
來源參照:中斷處理程序
1 #include <linux/module.h> 2 #include <linux/init.h> 3 #include <linux/miscdevice.h> 4 #include <linux/interrupt.h> 5 #include <linux/io.h> 6 #include <linux/fs.h> 7 8 #define GPNCON 0x7f008830 9 #define GPNDAT 0x7f008834 10 11 MODULE_LICENSE("GPL"); 12 13 irqreturn_t key_init(int irp,void *dev_id) 14 { 15 //1.檢測是否發生中斷 16 //2.清除已經發生的按鍵中斷 17 18 //3.打印按鍵值 19 printk(KERN_EMERG"key down!\n"); 20 return 0; 21 } 22 23 void key_hw_init(void) //硬件初始化 24 { 25 unsigned int *gpio_config; 26 unsigned short data; 27 gpio_config = ioremap(GPNCON,4); 28 data = readw(gpio_config); 29 data &= ~0xfff; 30 data |= 0xaaa; 31 writew(data,gpio_config); 32 gpio_dat = ioremap(GPNDAT,4); //將數據寄存器地址轉化為虛擬地址 33 } 34 35 int key_open(struct inode *node,struct file *filp) 36 { 37 return 0; 38 } 39 struct file_operations key_fops = 40 { 41 .open = key_open, 42 }; 43 44 struct miscdevice key_miscdev = 45 { 46 .minor = 200, 47 .name = "key", 48 .fops = &key_fops, 49 }; 50 51 static int button_init(void) 52 { 53 misc_register(&key_miscdev); //注冊混雜設備 54 //按鍵硬件初始化 55 key_hw_init(); 56 request_irq(IRQ_EINT(0),key_init,IRQF_TRIGGER_FALLING,"key",0); //注冊中斷處理程序 57 return 0; 58 } 59 60 static void button_exit(void) 61 { 62 misc_deregister(&key_miscdev); //注銷混雜設備 63 64 //注銷中斷處理程序 65 free_irq(IRQ_EINT(0),0); 66 } 67 68 module_init(button_init); 69 module_exit(button_exit);
這里需要注意上面代碼特殊標記的內容:IRQ_EINT(0)中斷號、IRQF_TRIGGER_FALLING標志的來源
標志來源:
按鍵中斷的處理,對於按鍵而言,可以在按下的時候產生中斷,也可以在彈起的時候產生中斷。需要通過一個標志來指定:IRQF_TRIGGER_FALLING,這個是從高電平到低電平產生中斷。下表是其他產生中斷的方式(內核代碼中搜索IRQF_TRIGGER_FALLING):
中斷號:
就是request_irq函數的第一個參數。我們在內核代碼中搜索irqs.h,找對應的板子的:
從上面的代碼看到,IRQ_EINT0_3的中斷號是32.系統留出了S3C_IRQ_OFFSET=32個中斷號,這是給軟中斷的。所以中斷號就是等於硬件編號加上偏移量。可以查看內核代碼的entry-macro.S
三、中斷分層管理
3.1中斷嵌套
所謂的中斷嵌套就是,當一種中斷正在執行的時候,又產生了另外中斷。可以是同類型的,也可以是不同類型的。
慢速中斷:是指在進行中斷處理的時候,中斷的總開關是不關閉的。允許其他類型中斷產生。
快速中斷:當中斷產生的時候,控制位的IF為被置1,別的中斷被禁止發生。這樣就會產生我們不想看到的情況:中斷丟失。
3.2中斷分層
上半部:當中斷發生時,它進行相應地硬件讀寫,並“登記”該中斷。通常由中斷處理程序充當上半部。
下半部:在系統空閑的時候對上半部“登記”的中斷進行后續處理。
3.3工作隊列
工作隊列是一種將任務推后執行的形式,他把推后的任務交由一個內核線程去執行。這樣下半部會在進程上下文執行,它允許重新調度甚至睡眠。 每個被推后的任務叫做“工作”,由這些工作組成的隊列稱為工作隊列
下圖為3內核的處理器隊列處理圖:
3.3.1工作隊列描述
Linux內核使用struct work_struct來描述一個工作隊列:
1 struct workqueue_struct{ 2 struct cpu_workqueue_struct *cpu_wq; 3 struct list_head list; 4 const char *name; /*workqueue name*/ 5 int singlethread; 6 int freezeable; /* Freeze threads during suspend */ 7 int rt; 8 };
3.3.2工作隊列項
Linux內核使用struct work_struct來描述一個工作項:
1 struct work_struct{ 2 atomic_long_t data; 3 struct list_headentry; 4 work_func_t func; 5 }; 6 typedef void (*work_func_t)(struct work_struct *work);
3.3.3工作隊列使用
step1. 創建工作隊列
create_workqueue
step2. 創建工作
INIT_WORK
step3. 提交工作
queue_work
在大多數情況下, 驅動並不需要自己建立工作隊列,只需定義工作, 然后將工作提交到內核已經定義好的工作隊列keventd_wq中。
1. 提交工作到默認隊列
schedule_work
按照工作隊列修改按鍵程序:
1 #include <linux/module.h> 2 #include <linux/init.h> 3 #include <linux/miscdevice.h> 4 #include <linux/interrupt.h> 5 #include <linux/io.h> 6 #include <linux/fs.h> 7 #include <linux/slab.h> 8 9 #define GPNCON 0x7f008830 10 MODULE_LICENSE("GPL"); 11 struct work_struct *work1; 12 struct timer_list key_timer; 13 14 void work1_func(struct work_struct *work) 15 { 16 printk(KERN_EMERG"key down!\n"); 17 } 18 19 irqreturn_t key_int(int irp,void *dev_id) 20 { 21 //3.提交下半部 22 schedule_work(work1); 23 return 0; 24 } 25 26 void key_hw_init(void) //硬件初始化 27 { 28 unsigned int *gpio_config; 29 unsigned short data; 30 31 gpio_config = ioremap(GPNCON,4); 32 data = readw(gpio_config); 33 data &= ~0xfff; 34 data |= 0xaaa; 35 writew(data,gpio_config); 36 gpio_dat = ioremap(GPNDAT,4); //將數據寄存器地址轉化為虛擬地址 37 } 38 39 int key_open(struct inode *node,struct file *filp) 40 { 41 return 0; 42 } 43 struct file_operations key_fops = 44 { 45 .open = key_open, 46 }; 47 48 struct miscdevice key_miscdev = 49 { 50 .minor = 200, 51 .name = "key", 52 .fops = &key_fops, 53 }; 54 55 static int button_init(void) 56 { 57 misc_register(&key_miscdev); //注冊混雜設備 58 59 //按鍵硬件初始化 60 key_hw_init(); 61 request_irq(IRQ_EINT(0),key_int,IRQF_TRIGGER_FALLING,"key",0); //注冊中斷處理程序 62 //創建工作1 63 work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL); 64 INIT_WORK(work1,work1_func); 65 return 0; 66 } 67 68 static void button_exit(void) 69 { 70 misc_deregister(&key_miscdev); //注銷混雜設備 71 //注銷中斷處理程序 72 free_irq(IRQ_EINT(0),0); 73 } 74 75 module_init(button_init); 76 module_exit(button_exit);
接下來的內容在下一個文章里包括:定時器去抖和阻塞性驅動程序設計