/** * Author:hasen * 参考 :《linux设备驱动开发详解》 * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:中断 * Date:2014-11-13 */
一、中断和定时器
所谓中断是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,
转而去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。
下图是中断的分类
嵌入式系统以及X86 PC中大多包含可编程中断控制器(PIC),许多MCU内部就集成了PIC。如在80386中,
PIC是两片i8259A芯片的级联。通过读写PIC的寄存器,程序员可以屏蔽/使能某中断及获得中断状态,前者一般
通过中断MASK寄存器完成,后者一般通过中断PEND寄存器完成。
定时器在硬件上也是依赖中断实现的。
二、Linux中断处理架构
设备的中断会打断内核的正常调度和运行,系统对更高吞吐量的追求势必要求中断服务程序尽可能的
短小精悍。大多数系统中,中断到来时,工作往往不是短小的,它可能要进行大量的耗时操作。
下图描述了Linux的内核中断机制。为了在中断时间尽可能短和中断处理需完成大工作量之间找到平衡
点,Linux将中断处理程序分为两部分:顶半部(top half)和底半部(bottom half) 。
顶半部完成可能少的比较紧急的功能,往往只是:
(1)简单地读取寄存器中的中断状态并清除中断标志
(2)进行“登记中断的”工作,这意味着将底半部处理程序挂到该设备的底半部执行队列中去。
这样,顶半部执行速度回很快,可以服务更多的中断请求。
中断工作的重心落在底半部,
(1)它来完成中断事件的绝大多数任务。
(2)可以被新的中断打断,这是和顶半部最大不同,顶半部往往不可中断。
(3)底半部相对来说不是非常紧急的,比较耗时,不在硬件中断服务程序中执行。
如果中断要处理的工作本身很少,完全可以在顶半部完成。
在linux中,查看/proc/interrupts文件可以获得系统中中断的统计信息。
三、Linux中断编程
1、申请和释放中断
在linux设备驱动中,使用中断的设备需要申请和释放相应的中断,分别使用内核提供的request_irq()
和free_irq()函数。
申请IRQ
int request_irq(unsigned int irq,irq_handler_t handler, unsigned long irqflags,const char *devname,void *dev_id)==>irq是要申请的中断号。
typedef irqreturn_t (*irq_handler_t)(int,void *) ; typedef int irqreturn_t ;释放IRQ
void free_irq(unsigned int irq,void *dev_id) ;free_irq()中的参数的定义域request_irq()函数相同。
void disable_irq(int irq) ; void disable_irq_nosync(int irq) ; void enable_irq(int irq) ;disable_irq_nosync()和disable_irq()的区别在于前者立即返回,而后者等待目前的中断处理完成。由
#define local_irq_save(flags)... void local_irq_disable(void) ;前者会将目前的中断状态保留在flags中(注意flags为unsigned long类型,被直接传递,而不是通过指针
#define local_irq_restore(flags) ... void local_irq_enable(void) ;以上个local开头的方法的作用范围是本CPU内。
void my_tasklet_func(unsigned long) ;/*定义一个处理函数*/ DECLARE_TASKLET(my_tasklet,my_tasklet_func,data) ; /*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联*/在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:
tasklet_schedule(&my_tasklet) ;示例:使用tasklet作为底半部处理中断的设备驱动程序模板代码
/*定义tasklet和底半部函数相关联*/
void xxx_do_tasklet(unsigned long) ;
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0) ;
/*中断处理底半部*/
void xxx_do_tasklet(unsigned long)
{
...
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void* dev_id)
{
...
tasklet_schedule(&xxx_tasklet) ;/*调度定义的tasklet函数xxx_do_tasklet适当时候执行*/
...
}
/*设备驱动模块加载函数*/
int __init xxx_init(void)
{
...
/*申请中断*/
result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLE,"xxx",NULL) ;
...
return IRQ_HANDLED ;
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit()
{
...
free_irq(xxx_irq,xxx_interrupt) ;
...
} (2)工作队列struct work_struct my_wq ;/*定义一个工作队列*/ void my_wq_func(unsigned long) ;/*定义一个处理函数*/通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func ,NULL) ; /*初始化工作队列并将其与处理函数绑定*/与tasklet_schedule()对应的用于调度工作队列执行的函数为schedule_work(),如:
schedule_work(&mt_wq) ;/*调度工作队列执行*/示例:使用工作队列处理中断底半部的设备驱动程序模板
/*定义工作队列和关联函数*/
struct work_struct xxx_wq ;
void xxx_do_work(struct long) ;
/*中断处理底半部*/
void xxx_do_work(unsigned long)
{
...
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
schedule_work(&xxx_wq) ;
...
return IRQ_HANDLED ;
}
/*设备驱动模块加载函数*/
int xxx_init(void)
{
...
/*申请中断*/
result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL) ;
...
/*初始化工作队列*/
INIT_WORK(&xxx_wq,(void (*)(void *))xxx_do_work,NULL) ;
...
}
/*设备驱动模块卸载函数*/
void xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt) ;
...
} (3)软中断/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
int status = read_int_status() ;/*获知中断源*/
if(!is_myint(dev_id,status)) /*判断是否是本设备中断*/
return IRQ_NONE ; /*不是本设备中断,立即返回*/
/*是本设备中断,进行处理*/
...
return IRQ_HANDLED ;/*返回IRQ_HANDLED表明中断已被处理*/
}
/*设备驱动模块加载函数*/
int xxx_init(void)
{
...
/*申请共享中断*/
result = request_irq(sh_irq,xxx_interrupt,IRQF_SHARED,"xxx",xxx_dev) ;
...
}
/*设备驱动模块卸载函数*/
void xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt) ;
...
}实例:S3C6410时钟中断static int s3c_rtc_open(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev) ;
struct rtc_device *rtc_dev = platform_get_drvdata(pdev) ;
int ret ;
/*申请alarm中断*/
ret = request_irq(s3c_rtc_alarmno,s3c_rtc_alarmirq,
IRQF_DISABLE,"s3c2410-rtc alarm",rtc_dev) ;
if(ret){
dev_err(dev,"IRQ%d error %d\n",s3c_rtc_alarmno,ret);
return ret ;
}
/*申请tick中断*/
ret = request_irq(s3c_rtc_tickno,s3c_rtc_tickirq,
IRQF_DISABLE,"s3c2410-rtc tick",rtc_dev) ;
if(ret){
dev_err(dev,"IRQ%d error %d\n",s3c_rtc_tickno,ret);
goto tick_err ;
}
return ret ;
tick_err :
free_irq(s3c_rtc_alarmno,rtc_dev) ;
return ret ;
} S3C6410实时钟设备驱动的release()函数中,会释放它将要使用的中断。static void s3c_rtc_release(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev) ;
struct rtc_device *rtc_dev = platform_get_drvdata(pdev) ;
s3c_rtc_setpie(dev,0) ;
/*释放中断*/
free_irq(s3c_rtc_alarmno,rtc_dev) ;
free_irq(s3c_rtc_tickno,rtc_dev) ;
} S3C6410实时钟驱动的中断处理比较简单,没有明确地分为上下两个半部,只有顶半部。static irqreturn_t s3c_rtc_alarmirq(int irq,void *id)
{
struct rtc_device *rdev = id ;
rtc_update_irq(rdev,1,RTC_AF|RTC_IRQF) ;
s3c_rtc_set_bit_byte(s3c_rtc_base,S3C2410_INTP,S3C2410_INTP_ALM) ;
return IRQ_HANDLED ;
}
static irqreturn_t s3c_rtc_tickirq(int irq,void *id)
{
struct rtc_device *rdev = id ;
rtc_update_irq(rdev,1,RTC_PF|RTC_IRQF) ;
s3c_rtc_set_bit_byte(s3c_rtc_base,S3C2410_INTP,S3C2410_INTP_TIC) ;
return IRQ_HANDLED ;
} 代码中调用的rtc_update_irq()函数定义于drivers/rtc/interface.c文件中,被各种实时钟驱动共享。void rtc_update_irq(struct rtc_device *rtc,unsigned long num,unsigned long events)
{
spin_lock(&rtc->rtc_lock) ;
rtc->irq_data = (rtc->irq_data + (num << 8)) | events ;
spin_unlock(&rtc->irq_lock) ;
spin_lock(&rtc->irq_task_lock) ;
if(rtc->irq_task)
rtc->irq_task->func(rtc->irq_task->private_data) ;
spin_unlock(&rtc->irq_task_lock) ;
wake_up_interuptible(&rtc->irq_queue) ;
kill_fasync(&rtc->async_queue,SIGIO,POLL_IN) ;
} 上述中断处理程序中没有底半部(没有严格意义上的tasklet,工作队列或者软中断底半部),实际上,它只
原文地址:http://blog.csdn.net/android_hasen/article/details/41010943