向linux内核中添加外部中断驱动模块
本文主要介绍外部中断驱动模块的编写,包括: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)");