/** * 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