本文主要介紹外部中斷驅動模塊的編寫,包括:1.linux模塊的框架及混雜設備的注冊、卸載、操作函數集。2.中斷的申請及釋放。3.等待隊列的使用。4.工作隊列的使用。5.定時器的使用。6.向linux內核中添加外部中斷驅動模塊。7.完整驅動程序代碼。linux的內核版本為linux2.6.32.2。
一、linux模塊的框架以及混雜設備相關知識
1.內核模塊的框架如下圖所示,其中module_init()(圖中有誤,不是modules_init)只有在使用insmod命令手動加載模塊時才會被調用,將模塊靜態編譯到內核時該函數不會運行,但是會將入口調用函數的地址傳遞到內核中,啟動內核時入口函數將會被執行。(出口調用函數同理)。
外部中斷的驅動使用混雜設備的模型,混雜設備是一種特殊的字符設備,其主設備號固定為“10”。
描述混雜設備的結構體定義為:
struct miscdevice
{
int minor; /*次設備號*/ (加粗需初始化)
const char *name; /*設備名*/
const struct file_operations *fops; /*文件操作*/
struct list_head list;
struct device *parent;
struct device *this_device;
}
其中,*fops指向描述該設備的操作函數集,包括open(),read(),close()等函數。用戶在應用程序中調用open()等操作將會連接到內核模塊中定義的操作函數。
混雜設備的注冊使用函數:
misc_register(struct miscdevice *misc);
混雜設備的注銷使用函數:
misc_deregister(struct miscdevice *misc);
二、中斷的申請與釋放
中斷注冊函數的原型是:
int request_irq(unsigned int irq, irqreturn_t (handler*)(int irq, void *dev_id ), unsigned long flags, const char *devname, void *dev_id);
其中,irq為所申請的中斷,如IRQ_EINT0,該宏在irqs.h文件中定義。第二個參數為中斷處理函數名。第三個參數為該中斷的一些屬性參數,如IRQ_TYPE_EDGE_BOTH(表示該外部中斷由雙邊沿觸發)、IRQF_DISABLED(SA_INTERRUPT)(快速中斷)、IRQF_SHARED(SA_SHIQR)(共享中斷)等。*devname為設備文件名(一般同混雜設備名相同)。*dev_id為共享中斷id(中斷觸發時,具有相同共享id的中斷均進入中斷響應,注意格式)。
中斷注銷函數的原型是:
void free_irq(unsigned int irq, void *dev_id);
參數意義同上。
在中斷處理函數之中不能使用可能引起阻塞或者調度的操作,如kmalloc、ioremap等函數,否則有可能引起內核崩潰。
另外,為了使中斷處理函數盡可能地短小快速,會將中斷處理分為上下兩個部分。上半部用於處理比較緊急的部分,如寄存器相關的操作等,下半部用於處理不那么緊急的操作,下半部的操作一般在上半部返回之前提交內核,工作隊列是一種常用於實現下半部操作的方法。在上半部完成之后,中斷函數直接返回以便繼續響應外部中斷的觸發。
三、等待隊列的使用
為了提高處理器效率,避免處理器的輪詢操作,當條件不滿足時可使用等待隊列來阻塞進程直到條件成立時喚醒。
1.定義和初始化等待隊列
定義:wait_queue_head_t my_queue;
初始化:init_waitqueue_head(&my_queue);
或者定義的同時初始化:DECLARE_WAIT_QUEUE_HEAD(my_queue);
2.進入等待隊列,睡眠
wait_event(queue,condition)
當condition(布爾表達式)為真時,立即返回;否則讓進程進入TASK_UNINTERRUPTIBLE模式的睡眠,並掛在queue參數所指定的等待隊列上
wait_event_interruptible(queue,condition)
當condition(布爾表達式)為真時,立即返回;否則讓進程進入TASK_INTERRUPTIBLE的睡眠,並掛在queue參數所指定的等待隊列上。
int wait_event_killable(queue, condition)
當condition(一個布爾表達式)為真時,立即返回;否則讓進程進入TASK_KILLABLE的睡眠,並掛在queue參數所指定的等待隊列上。
3.從等待隊列中喚醒進程
wake_up(wait_queue_t *q)
從等待隊列q中喚醒狀態為TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有進程。
wake_up_interruptible(wait_queue_t *q)
從等待隊列q中喚醒狀態為TASK_INTERRUPTIBLE 的進程
可中斷的睡眠狀態的進程會睡眠直到某個條件變為真,比如說產生一個硬件中斷、釋放進程正在等待的系統資源或是傳遞一個信號都可以是喚醒進程的條件。不可中斷睡眠狀態與可中斷睡眠狀態類似,但是它有一個例外,那就是把信號傳遞到這種睡眠狀態的進程不能改變它的狀態,也就是說它不響應信號的喚醒。不可中斷睡眠狀態一般較少用到,但在一些特定情況下這種狀態還是很有用的,比如說:進程必須等待,不能被中斷,直到某個特定的事件發生。
四、工作隊列的使用
工作隊列:是一種將任務推后執行的形式,它把推后的任務交由一個內核線程去執行。這些工作組成的隊列叫做工作隊列,這些工作允許重新調度甚至睡眠。中斷進行上半部分的程序處理時處理器會屏蔽外部中斷,而在下半部分開始工作之前會打開處理器對外部中斷的響應。
Linux內核使用struct workqueue_struct來描述一個工作隊列:
struct workqueue_struct{
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name; /*workqueue name*/
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
};
Linux內核使用struct work_struct來描述一個工作項:
struct work_struct{
atomic_long_t data;
struct list_headentry;
work_func_t func;
};
typedef void (*work_func_t)(struct work_struct *work);
工作的創建過程:
1.創建工作隊列
struct workqueue_struct *create_workqueue(const char *name);
如:my_wq = create_workqueue("my_wq");
2.創建工作
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
如:work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(work1,work1_fun);
3.提交工作
int queue_work(struct workqueue_struct *,struct work_struct *work);
如:queue_work(my_wq,work1);
掛載工作后工作並不一定會立刻運行,只有在線程覺得cpu比較空閑時才會運行。另外。在大多數情況下, 驅動並不需要己建立工作隊列,只需定義工作, 然后將工作提交到內核已經定義好的工作隊列keventd_wq。
1.提交工作到默認隊列
schedule_work(struct work_struct * );
如:schedule_work(work1);
五、內核定時器的使用
linux中使用struct timer_list來描述一個定時器結構體變量
struct timer_list{
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base;
};
2.初始化定時器
init_timer初始化
如:init_timer(&buttons_timer);
設置超時函數
如:buttons_timer.function = buttons_timer_function;
3.add_timer注冊定時器
如:add_timer(&buttons_timer);
4.mod_timer啟動定時器
如:mod_timer(&buttons_timer, jiffies + (HZ /10));
(HZ代表1個滴答,jiffies代表的是系統最近一次啟動以來的滴答數,以秒計)。
定時器只是阻塞當前進程。
六、向linux內核中添加外部中斷驅動模塊
由於混雜設備是一種特殊的字符設備,所以混雜設備的驅動也存放於/drivers/char下,具體的步驟為:
1.將mini2440_remote.c放到/drivers/char目錄下
2.修改/drivers/char/kconfig文件,添加:
1 config MINI2440_REMOTE 2 tristate "Remote Driver for FriendlyARM Mini2440 development boards" 3 depends on MACH_MINI2440 4 default y if MACH_MINI2440 5 help 6 this is remote driver for "Navigation Boat Project",written by luo jie at JiangSu University.
其中,tristate表示“三態”,即Y、N、M。
3.修改/drivers/char/Makefile文件,添加:
obj-$(CONFIG_MINI2440_REMOTE) +=mini2440_remote.o
修改成功后使用make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-命令可以看到配置選項。
七、完整驅動程序代碼:
1 /****************************************************************************** 2 *文件名: Remote_Driver.c 3 *文件功能 遙控外部中斷內核驅動程序 4 *作者: 羅傑(E-Mail:1454760043@qq.com),2015年10月19日於江蘇大學 5 *修改記錄: 6 ******************************************************************************/ 7 #include"linux/module.h" 8 #include"linux/init.h" 9 #include <linux/kernel.h> 10 #include <linux/fs.h> 11 #include "linux/io.h" 12 #include <linux/init.h> 13 #include <linux/delay.h> 14 #include <linux/poll.h> 15 #include <linux/irq.h> 16 #include <asm/irq.h> 17 #include <linux/interrupt.h> 18 #include <asm/uaccess.h> 19 #include <mach/regs-gpio.h> 20 #include <mach/hardware.h> 21 #include <linux/platform_device.h> 22 #include <linux/cdev.h> 23 #include <linux/miscdevice.h> 24 #include <linux/sched.h> 25 #include <linux/gpio.h> 26 27 #define GPFCON 0x56000050 28 #define GPFDAT 0x56000054 29 #define GPGCON 0x56000060 30 #define GPGDAT 0x56000064 31 32 unsigned int *gpio_config; 33 unsigned int w_data; 34 unsigned char b_data; 35 36 static volatile int flag = 0; 37 //等待隊列 38 wait_queue_head_t wait_for_interrupt; 39 //工作隊列 40 static struct work_struct *judge_interrupt; 41 //定時器 42 static struct timer_list wipe_shaking_timer; 43 44 45 //中斷函數中提交的工作隊列,中斷處理函數下半部分 46 void My_Work(void ) 47 { 48 mod_timer(&wipe_shaking_timer,jiffies + (HZ / 10)); 49 } 50 51 //中斷消抖函數,中斷處理函數的下半部分 52 void Wipe_Shaking() 53 { 54 w_data = readw(gpio_config); 55 56 if((w_data & 0x1) == 0) 57 { 58 printk("In Remote_Driver:外部中斷0下降沿觸發!\n"); 59 wake_up_interruptible(&wait_for_interrupt); 60 flag = 1; 61 } 62 else 63 { 64 printk("In Remote_Driver:外部中斷0未發生中斷!\n"); 65 flag = 0; 66 } 67 68 69 } 70 71 //初始化I/O端口 72 void Io_Init() 73 { 74 75 //設置GPF0為中斷工作方式,設置GPF1-GPF6,GPG0-GPG1為I/O輸出引腳,所有引腳輸出低電平 76 gpio_config = ioremap(GPFCON,4); 77 w_data = readw(gpio_config); 78 w_data &= ~(0x3fff); 79 w_data |= 0x1556; 80 writew(w_data,gpio_config); 81 82 gpio_config = ioremap(GPFDAT,4); 83 b_data = readb(gpio_config); 84 b_data &= ~(0x7f); 85 b_data |= 0x0; 86 writeb(b_data,gpio_config); 87 88 gpio_config = ioremap(GPGCON,4); 89 w_data = readw(gpio_config); 90 w_data &= ~(0xf); 91 w_data |= 0x5; 92 writew(w_data,gpio_config); 93 94 gpio_config = ioremap(GPGDAT,4); 95 w_data = readw(gpio_config); 96 w_data &= ~(0x3); 97 w_data |= 0x0; 98 writew(w_data,gpio_config); 99 100 } 101 102 //遙控端口中斷處理函數 103 irqreturn_t Remote_irq(int irq,void *dev_id) 104 { 105 //提交中斷下半部工作 106 schedule_work(judge_interrupt); 107 108 //中斷返回 109 return IRQ_HANDLED; 110 } 111 112 //設備文件打開函數 113 static int Remote_Open(struct inode *inode,struct file *file) 114 { 115 int ret = 0; 116 117 flag = 0; 118 119 //初始化I/O端口 120 Io_Init(); 121 122 gpio_config = ioremap(GPFDAT,4); 123 //注冊中斷處理函數 124 ret = request_irq(IRQ_EINT0,Remote_irq,IRQ_TYPE_EDGE_BOTH,"Remote_Driver",(void *)0); 125 if(ret == 0) 126 { 127 printk("In Remote_Driver:注冊中斷服務程序成功!\n"); 128 129 } 130 else 131 { 132 printk("In Remote_Driver:無法注冊中斷服務程序!\n"); 133 return -1; 134 } 135 136 //工作隊列初始化,由中斷處理函數提交 137 judge_interrupt = kmalloc(sizeof(struct work_struct),GFP_KERNEL); 138 INIT_WORK(judge_interrupt,My_Work); 139 140 //定時器初始化及注冊,進行中斷的消抖處理 141 init_timer(&wipe_shaking_timer); 142 wipe_shaking_timer.function = Wipe_Shaking; 143 add_timer(&wipe_shaking_timer); 144 145 //等待隊列,用來對read()操作進行阻塞 146 init_waitqueue_head(&wait_for_interrupt); 147 148 return 0; 149 } 150 151 //設備文件的讀取函數 152 ssize_t Remote_Read(struct file *filp, char __user *buf, size_t size, loff_t *pos) 153 { 154 155 printk("阻塞read進程!\n"); 156 wait_event_interruptible(wait_for_interrupt,flag); 157 copy_to_user(buf, &flag, 4); 158 printk("讀取數據成功!\n"); 159 160 return 4; 161 } 162 163 //設備文件的關閉函數 164 int Remote_Close(struct inode* inode,struct file* file) 165 { 166 //注銷中斷函數 167 free_irq(IRQ_EINT0,(void *)0); 168 return 0; 169 } 170 171 //定義並初始化設備文件操作函數集 172 static struct file_operations remote_fops = 173 { 174 .open = Remote_Open, 175 .read = Remote_Read, 176 .release = Remote_Close, 177 }; 178 179 //定義一個混雜設備結構並初始化 180 static struct miscdevice remote_miscdev = 181 { 182 183 .minor = 200, 184 //名稱可以使用特殊字符 185 .name = "Remote_Driver", 186 .fops = &remote_fops, 187 }; 188 189 //驅動設備初始化函數 190 static int __init Remote_Init() 191 { 192 int ret; 193 //注冊混雜設備 194 ret = misc_register(&remote_miscdev); 195 if(ret != 0) 196 { 197 printk("In Remote_Driver:無法注冊混雜設備!\n"); 198 return -1; 199 } 200 else 201 { 202 printk("In Remote_Driver:成功注冊混雜設備!\n"); 203 } 204 205 //若初始化成功則必須返回0 206 return 0; 207 208 } 209 210 //驅動設備退出函數 211 static void __exit Remote_Exit() 212 { 213 //注銷混雜設備 214 misc_deregister(&remote_miscdev); 215 } 216 217 //模塊初始化,僅當使用insmod/podprobe命令加載時有用,如果設備不是通過模塊方式加載則此語句不會被執行 218 module_init(Remote_Init); 219 //卸載模塊,僅當使用insmod/podprobe命令加載時有用,如果設備不是通過模塊方式加載則此語句不會被執行 220 module_exit(Remote_Exit); 221 222 MODULE_LICENSE("GPL"); 223 MODULE_AUTHOR("羅傑(E-mail:1454760043@qq.com)");