分享

Linux中断处理体系结构分析 第3页

 mzsm 2015-06-02
 2.3 中断的处理过程

asm_do_IRQ是中断的C语言总入口函数,它在/arch/arm/kernel/irq.c中定义,


106 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
107 {
108 struct pt_regs *old_regs = set_irq_regs(regs);
109
110 irq_enter();
111
112
/*
113 * Some hardware gives randomly wrong interrupts. Rather
114 * than crashing, do something sensible.
115 */

116 if (unlikely(irq >= NR_IRQS)) {
117 if (printk_ratelimit())
118 printk(KERN_WARNING "Bad IRQ%u\n", irq);
119 ack_bad_irq(irq);
120 } else {
121 generic_handle_irq(irq);
122 }
123
124 /* AT91 specific workaround */
125 irq_finish(irq);
126
127 irq_exit();
128 set_irq_regs(old_regs);
129 }

desc_hand_irq函数直接调用desc结构中的hand_irq成员函数,它就是irq_desc[irq].handle.irq

asm_do_IRQ函数中参数irq的取值范围为IRQ_EINT0~IRQ_EINT0 + 31,只有32个取值。它可能是一个实际的中断号,也可能是一组中断的中断号。这里有S3C2440的芯片特性决定的:发生中断时,INTPND寄存器的某一位被置1INTOFFSET寄存器中记录了是哪一位(0--31),中断向量调用asm_do_IRQ之前要把INTOFFSET寄存器的值确定irq参数。每一个实际的中断在irq_desc数组中都有一项与它对应,它们的数目不止32.asm_do_IRQ函数参数irq表示的是“一组”中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出是哪一个中断,然后调用irq_desc[irqno].handle_irq来进一步处理。

以外部中断EINT8EINT23为例,它们通常是边沿触发

(1) 它们被触发里,INTOFFSET寄存器中的值都是5asm_do_IRQ函数中参数irq的值为(IRQ_EINTO+5,IRQ_EINT8t23

(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irq_demux_extint8.

(3)s3c_irq_demux_extint8函数的代码在arch/arm/plat-s3c24xx/irq.c中,它首先读取EINTPENDEINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用irq_desc数组项中的handle_irq成员函数


453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
//EINT8-EINT23 发生时,相应位被置1

457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
//屏蔽寄存器

458
459 eintpnd &= ~eintmsk;
//清除被屏蔽的位

460 eintpnd &= ~0xff; /* 清除低8位(EINT8对应位8)ignore lower irqs */
461
462 /* 循环处理所有子中断*/
463
464 while (eintpnd) {
465 irq = __ffs(eintpnd);
//确定eintpnd中为1的最高位

466 eintpnd &= ~(1<<irq);
//将此们清0

467
468 irq += (IRQ_EINT4 - 4);
//重新计算中断号,前面计算出irq等于8时,中断号为

                            IRQ_EINT8
469 generic_handle_irq(irq);
//调用这中断的真正的处理函数

470 }
471
472 }
void

(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq/chip.c中定义,它用来处理边沿触发的中断,

中断发生的次数统计

531 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
532 {
533 spin_lock(&desc->lock);
534
535 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
536
537
/*
538 * If we're currently running this IRQ, or its disabled,
539 * we shouldn't process the IRQ. Mark it pending, handle
540 * the necessary masking and go out
541 */

542 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
543 !desc->action)) {
544 desc->status |= (IRQ_PENDING | IRQ_MASKED);
545 mask_ack_irq(desc, irq);
546 goto out_unlock;
547 }
548 kstat_incr_irqs_this_cpu(irq, desc);
549
550 /* Start handling the irq */
551 if (desc->chip->ack)
552 desc->chip->ack(irq);
553
554 /* Mark the IRQ currently in progress.*/
555 desc->status |= IRQ_INPROGRESS;
556
557 do {
558 struct irqaction *action = desc->action;
559 irqreturn_t action_ret;
560
561 if (unlikely(!action)) {
562 desc->chip->mask(irq);
563 goto out_unlock;
564 }
565
566
/*
567 * When another irq arrived while we were handling
568 * one, we could have masked the irq.
569 * Renable it, if it was not disabled in meantime.
570 */

571 if (unlikely((desc->status &
572 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
573 (IRQ_PENDING | IRQ_MASKED))) {
574 desc->chip->unmask(irq);
575 desc->status &= ~IRQ_MASKED;
576 }
577
578 desc->status &= ~IRQ_PENDING;
579 spin_unlock(&desc->lock);
580 action_ret = handle_IRQ_event(irq, action);
581 if (!noirqdebug)
582 note_interrupt(irq, desc, action_ret);
583 spin_lock(&desc->lock);
584
585 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
586
587 desc->status &= ~IRQ_INPROGRESS;
588 out_unlock:
589 spin_unlock(&desc->lock);
590 }
591

响应中断,通常是清除当前中断使得可以接收下一个中断,对于IRQ_EINT8~IRQ_EINT23这几个中断,desc->chip在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irqext_chip.desc->chip->ack就是s3c_irqext_ack函数,(arch/armplat-s3c24xx/irq.c)它用来清除中断

handle_IRQ_event函数来逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义。


do {
379 trace_irq_handler_entry(irq, action);
380 ret = action->handler(irq, action->dev_id);
//执行用户注册的中断处理函数

381 trace_irq_handler_exit(irq, action, ret);
382
383 switch (ret) {
384 case IRQ_WAKE_THREAD:
385
/*
386 * Set result to handled so the spurious check
387 * does not trigger.
388 */

389 ret = IRQ_HANDLED;
390
391
/*
392 * Catch drivers which return WAKE_THREAD but
393 * did not set up a thread function
394 */

395 if (unlikely(!action->thread_fn)) {
396 warn_no_thread(irq, action);
397 break;
398 }
399
400 /*
408 if (likely(!test_bit(IRQTF_DIED,
409 &action->thread_flags))) {
410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
411 wake_up_process(action->thread);
412 }
413
414 /* Fall through to add to randomness */
415 case IRQ_HANDLED:
416 status |= action->flags;
417 break;
418
419 default:
420 break;
421 }
422
423 retval |= ret;
424 action = action->next;
//下一个

425 } while (action);

用户注册的中断处理函数的参数为中断号irq,action->dev_id。后一个参数是通过request_irq函数注册中断时传入的dev_id参数,它由用户自己指定、自己使用,可以为空,当这个中断是“共享中断”时除外。

对于电平触发的中断,它们的irq_desc[irq].handle_irq通常是handle_level_irq函数。它也是在kernel/irq/chip.c中定义,其功能与上述handle_edge_irq函数相似,

 

对于handle_level_irq函数已经清除了中断,但是它只限于清除SoC内部的的信号,如果外设输入到SoC的中断信号仍然有效,这就会导致当前中断处理完成后,会误认为再次发生了中断,对于这种情况,需要用户注册的中断处理函数中清除中断,先清除外设的中断,然后再清除SoC内部的中断号。

中断的处理流程可以总结如下

(1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq

(2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq

(3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断

(4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数

(5) 入口函数是handle_level_irq时还要重新开启中断

  卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。

它需要用到两个参数:irqdev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,再使用dev_idaction链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数dev_id必惟一。

free_irq函数的处理过程与request_irq函数相反

(1)根据中断号irqdev_idaction链表中找到表项,将它移除

(2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。

在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP.产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》下载见http://www./Linux/2011-05/35647.htm

linux

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多