设备驱动中的中断问题及实例解析
1、关于设备驱动中的中断问题
操作系统为了使得快速设备和慢速设备合适工作,需要中断来提高效率,一个外设要使用一个中断就必须注册中断号,获得跟这个中断号相关的一些资源,并且在中断发生的时候内核可以进行一些处理,例如:调用中断处理例程来真正的处理设备中断。Linux处理中断的方式很大程度上与它在用户空间处理信号的方式是一样的。
我们知道,从本质上讲,中断处理例程会和其他代码并发运行,这就会涉及到竞态和并发的问题。
接下来我们就来讲讲有关中断的实现和使用:
首先,我们需要注册一个中断,可如下注册,在
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
参数说明:
第一个参数:要申请的中断号,主要看硬件的连接方式决定;
第二个参数:中断处理例程,自己实现,在发生中断的时候调用,稍候详细说明;
第三个参数:中断管理的标志,是一个位掩码选项,例如可设置一个中断号在几个设备间共享,常见的就 是开发板上的ADC和Touch Screen共享ADC中断;
第四个参数:用来标示中断拥有者的名称,可自己设定;
第五个参数:用于共享的中断信号线,稍候详细说明。
调用request_irq的正确位置应该是设备第一次打开、硬件被告知产生中断之前。
接下来,注册后怎么注销呢?调用如下函数即可:
void free_irq(unsigned int irq, void *dev);
这里参数的意义和上面是一样的。
调用free_irq的位置应该是最后一次关闭设备、硬件被告知不用再中断处理器后。
有关中断处理例程:
首先看看irq_handler_t的定义,显然它应该是一个自定义类型,定义在:include/linux/interrupt.h中:
typedef irqreturn_t (*irq_handler_t)(int, void *);
确实是一个类型定义,是一个函数指针类型,指向的函数有两个参数,一个irqreturn_t类型的返回值,这也是一个自定义类型,定义在include/linux/irqreturn.h中:
typedef enum irqreturn irqreturn_t;
确实是一个自定义类型,看看typedef就知道了,而且是一个枚举类型,接着看看这个枚举类型/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE,
IRQ_HANDLED,
IRQ_WAKE_THREAD,
};
这个枚举类型里面的值代表着中断处理例程的处理结果,也就是中断程序的返回值。OK,这下就清除多了!
2、中断处理的示例
这里是友善之臂的按键设备驱动程序,做了一些注释,对比这上面的理论部分就比较好理解了。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <asm/io.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/hardware.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <mach/map.h> #include <mach/regs-clock.h> #include <mach/regs-gpio.h> #include <plat/gpio-cfg.h> #include <mach/gpio-bank-n.h> #include <mach/gpio-bank-l.h> #define DEVICE_NAME "buttons" /*用于描述每个按键中断的结构体*/ struct button_irq_desc { int irq; int number; char *name; }; static struct button_irq_desc button_irqs [] = { {IRQ_EINT( 0), 0, "KEY0"}, {IRQ_EINT( 1), 1, "KEY1"}, {IRQ_EINT( 2), 2, "KEY2"}, {IRQ_EINT( 3), 3, "KEY3"}, {IRQ_EINT( 4), 4, "KEY4"}, {IRQ_EINT( 5), 5, "KEY5"}, {IRQ_EINT(19), 6, "KEY6"}, {IRQ_EINT(20), 7, "KEY7"}, }; static volatile char key_values [] = {'0', '0', '0', '0', '0', '0', '0', '0'}; /*涉及到中断处理例程,所以就需要注意竟态和并发问题*/ static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /*唤醒等待队列的条件,可以是任意的布尔表达式*/ static volatile int ev_press = 0; /*中断处理例程*/ static irqreturn_t buttons_interrupt(int irq, void *dev_id) { struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id; int down; int number; unsigned tmp; udelay(0); number = button_irqs->number; switch(number) { /*key0~key5使用的是EINT0~EINT5,看芯片手册就知道是GPN口上的功能*/ case 0: case 1: case 2: case 3: case 4: case 5: tmp = readl(S3C64XX_GPNDAT); down = !(tmp & (1<<number)); break; /*key6~key7使用EINT19~EINT20,使用GPL引脚*/ case 6: case 7: tmp = readl(S3C64XX_GPLDAT); down = !(tmp & (1 << (number + 5))); break; default: down = 0; } /*判断是否有按键按下*/ if (down != (key_values[number] & 1)) { key_values[number] = '0' + down; /*唤醒等待队列,此刻实际上是唤醒读进程*/ ev_press = 1; wake_up_interruptible(&button_waitq); } /*该返回值代表中断确实真的处理了该中断*/ return IRQ_RETVAL(IRQ_HANDLED); } static int s3c64xx_buttons_open(struct inode *inode, struct file *file) { int i; int err = 0; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } /*申请中断号*/ err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, button_irqs[i].name, (void *)&button_irqs[i]); if (err) break; } /*如果在申请中断的时候发生错误,那么就释放掉已申请的中断号*/ if (err) { i--; for (; i >= 0; i--) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return -EBUSY; } ev_press = 1; return 0; } static int s3c64xx_buttons_close(struct inode *inode, struct file *file) { int i; /*释放获得的中断资源*/ for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return 0; } static int s3c64xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (!ev_press) { if (filp->f_flags & O_NONBLOCK) //非阻塞读取 return -EAGAIN; else wait_event_interruptible(button_waitq, ev_press); //阻塞读取,则进入休眠,等待发生中断时由中断例程唤醒 } ev_press = 0; err = copy_to_user((void *)buff, (const void *)(&key_values), min(sizeof(key_values), count)); return err ? -EFAULT : min(sizeof(key_values), count); } /*用于轮询操作*/ static unsigned int s3c64xx_buttons_poll( struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDNORM; /*返回标示是否可立即无阻塞执行的位掩码*/ return mask; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = s3c64xx_buttons_open, .release = s3c64xx_buttons_close, .read = s3c64xx_buttons_read, .poll = s3c64xx_buttons_poll, }; /*定义misc设备*/ static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc.");