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 寄存器的某一位被置 1 , INTOFFSET 寄存器中记录了是哪一位( 0--31 ),中断向量调用 asm_do_IRQ 之前要把 INTOFFSET 寄存器的值确定 irq 参数。每一个实际的中断在 irq_desc 数组中都有一项与它对应,它们的数目不止 32. 当 asm_do_IRQ 函数参数 irq 表示的是“一组”中断时, irq_desc[irq].handle_irq 成员函数还需要先分辨出是哪一个中断,然后调用 irq_desc[irqno].handle_irq 来进一步处理。
以外部中断 EINT8 — EINT23 为例,它们通常是边沿触发
(1) 它们被触发里, INTOFFSET 寄存器中的值都是 5 , asm_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 中,它首先读取 EINTPEND 、 EINTMASK 寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用 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 中定义。
它需要用到两个参数: irq 和 dev_id, 它们与通过 request_irq 注册中断函数时使用的参数一样,使用中断号 irq 定位 action 链表,再使用 dev_id 在 action 链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的 dev_id 来区分,在注册共享中断时参数 dev_id 必惟一。
free_irq 函数的处理过程与 request_irq 函数相反
(1) 根据中断号 irq , dev_id 从 action 链表中找到表项,将它移除
(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