重读uclinux-2008r1-rc8(bf561)内核的中断处理(3):中断处理流程

重读uclinux-2008r1-rc8(bf561)内核的中断处理(3):中断处理流程1.3中断处理流程1.3.1evt_nmi这个代码是用汇编写的,其实现在arch/blackfin/mach-common/interrupt.s中,用于处理nmi中断:/*Interruptroutineforev

 

这个代码是用汇编写的,其实现在arch/blackfin/mach-common/interrupt.s中,用于处理nmi中断:
/* Interrupt routine for evt2 (NMI).
 * We don’t actually use this, so just return.
 * For inner circle type details, please see:
 * http://docs.blackfin.uclinux.org/doku.php?id=linux:nmi
 */
ENTRY(_evt_nmi)
.weak _evt_nmi
       rtn;
ENDPROC(_evt_nmi)
空语句,没什么可说的。
这部分代码用于处理Exception中断,对于内核而言是相当重要的,其实现在
arch/blackfin/mach-common/entry.S
文件中
ENTRY(_trap)
/* Exception: 4th entry into system event table(supervisor mode)*/
       /* Since the kernel stack can be anywhere, it’s not guaranteed to be
        * covered by a CPLB. Switch to an exception stack; use RETN as a
        * scratch register (for want of a better option).
        */
       EX_SCRATCH_REG = sp;
       sp.l = _exception_stack_top;
       sp.h = _exception_stack_top;
       /* Try to deal with syscalls quickly. */
       [–sp] = ASTAT;
       [–sp] = (R7:6, P5:4);
       DEBUG_STOP_HWTRACE(p5, r7)
       r7 = SEQSTAT;            
/* reason code is in bit 5:0 */
       r6.l = lo(SEQSTAT_EXCAUSE);
       r6.h = hi(SEQSTAT_EXCAUSE);
       r7 = r7 & r6;
       p5.h = _ex_table;
       p5.l = _ex_table;
       p4 = r7;
       p5 = p5 + (p4 << 2);
       p4 = [p5];
       jump (p4);
 
.Lbadsys:
       r7 = -ENOSYS;           
/* signextending enough */
       [sp + PT_R0] = r7;      
/* return value from system call */
       jump .Lsyscall_really_exit;
ENDPROC(_trap)
 
在这里EX_SCRATCH_REG定义成RETN这个寄存器。
而_exception_stack_top是在本文件中定义的缓冲区的最高位置。
_exception_stack:
//      .rept 1024
//      .long 0;
//      .endr
       .byte4 _exception_stack_buffer[1024];
_exception_stack_top:
_ex_table是在本文件中定义的函数指针的数组,它指明了所有可能的64种异常的入口:
ENTRY(_ex_table)
       /* entry for each EXCAUSE[5:0]
        * This table must be in sync with the table in ./kernel/traps.c
        * EXCPT instruction can provide 4 bits of EXCAUSE, allowing 16 to be user defined
        */
       .long _ex_syscall       /* 0x00 – User Defined – Linux Syscall */
       .long _ex_soft_bp       /* 0x01 – User Defined – Software breakpoint */
_ex_table.end:
因而,这一段程序就是根据不同的EXCEPTION的类型,调用相应的入口函数进行处理。
这部分代码在
arch/blackfin/mach-common/interrupt.S中,用于处理Hardware Error Interrupt,VDSP文档对此的解释是:
The Hardware Error Interrupt is generated by:
 
Bus parity errors
Internal error conditions within the core, such as Performance Monitor overflow
Peripheral errors
Bus timeout errors
看看uclinux的处理:
/* interrupt routine for ivhw – 5 */
ENTRY(_evt_ivhw)
     SAVE_ALL_SYS
 
     //# We are going to dump something out, so make sure we print IPEND properly
     p2.l = lo(IPEND);
     p2.h = hi(IPEND);
     r0 = [p2];
     [sp + PT_IPEND] = r0;
 
     /* set the EXCAUSE to HWERR for trap_c */
     r0 = [sp + PT_SEQSTAT];
     R1.L = LO(VEC_HWERR);
     R1.H = HI(VEC_HWERR);
     R0 = R0 | R1;
     [sp + PT_SEQSTAT] = R0;
 
     r0 = sp;        /* stack frame pt_regs pointer argument ==> r0 */
     SP += -12;
     call _trap_c;
     SP += 12;
 
     call _ret_from_exception;
.Lcommon_restore_all_sys:
     RESTORE_ALL_SYS
     rti;
 
ENDPROC(_evt_ivhw)
在这里,SAVE_ALL_SYS将系统的大部分寄存器的值都压到栈中,且入栈的顺序是和pt_regs这个结构体的成员一一对应的,因此最后可以将此结构体的指针传给函数trap_c。
PT_IPEND
PT_SEQSTAT分别定义了保存IPEND和SEQSTAT这两个寄存器值的成员相应于pt_regs这个结构体的偏移量,因而使用
[sp + PT_IPEND]这样的方式可以取得相应寄存器的值。
VEC_HWERR则是定义好的一个常数:
/* The hardware reserves (63) for future use – we use it to tell our
 * normal exception handling code we have a hardware error
 */
#define
VEC_HWERR (63)
因为出错的原因保存在SEQSTAT寄存器的低5位中,因此通过取或的方式将pt_regs->seqstat的值设置为EXCAUSE,而不是原来的32位整数。
然后调用trap_c进行处理,此函数将决定输出错误信息后返回原位置执行,或者中止执行。
最后调用了
_ret_from_exception函数,这个函数的实现在
arch/blackfin/mach-common/entry.S中,我们来看看它的实现:
ENTRY(_ret_from_exception)
     p2.l = lo(IPEND);
     p2.h = hi(IPEND);
 
     csync;
     r0 = [p2];
     [sp + PT_IPEND] = r0;
 
_ret_from_exception_1:
     r2 = LO(~0x37) (Z);
     r0 = r2 & r0;
     cc = r0 == 0;
     if !cc jump _ret_from_exception_4;   /* if not return to user mode, get out */
 
     /* Make sure any pending system call or deferred exception
      * return in ILAT for this process to get executed, otherwise
      * in case context switch happens, system call of
      * first process (i.e in ILAT) will be carried
      * forward to the switched process
      */
 
     p2.l = lo(ILAT);
     p2.h = hi(ILAT);
     r0 = [p2];
     r1 = (EVT_IVG14 | EVT_IVG15) (z);
     r0 = r0 & r1;
     cc = r0 == 0;
     if !cc jump _ret_from_exception_5;
 
     /* Set the stack for the current process */
     r7 = sp;
     r4.l = lo(ALIGN_PAGE_MASK);
     r4.h = hi(ALIGN_PAGE_MASK);
     r7 = r7 & r4;      /* thread_info->flags */
     p5 = r7;
     r7 = [p5 + TI_FLAGS];
     r4.l = lo(_TIF_WORK_MASK);
     r4.h = hi(_TIF_WORK_MASK);
     r7 = r7 & r4;
     cc = r7 == 0;
     if cc jump _ret_from_exception_4;
 
     p0.l = lo(EVT15);
     p0.h = hi(EVT15);
     p1.l = _schedule_and_signal;
     p1.h = _schedule_and_signal;
     [p0] = p1;
     csync;
     raise 15;     /* raise evt14 to do signal or reschedule */
_ret_from_exception_4:
     r0 = syscfg;
     bitclr(r0, 0);
     syscfg = r0;
_ret_from_exception_5:
     rts;
ENDPROC(_ret_from_exception)
在此入口的开始,直接取IPEND寄存器的值判断0x37指定的掩码位置的状态,如果为1则直接跳转到
_ret_from_exception_4,查一下IPEND这几位的意义:
The IPEND register keeps track of all currently nested interrupts (see Figure 4-12). Each bit in IPEND indicates that the corresponding interrupt is currently active or nested at some level. It may be read in Supervisor mode, but not written. The IPEND[4] bit is used by the Event Controller to temporarily disable interrupts on entry and exit to an interrupt service routine.
When an event is processed, the corresponding bit in IPEND is set. The least significant bit in IPEND that is currently set indicates the interrupt that is currently being serviced. At any given time, IPEND holds the current status of all nested events.
0:EMU中断
1:RST中断
2:NMI
4:Global Interrupt Disable
5:IVHW
当发生上述状况时,内核直接跳转到
_ret_from_exception_4
_ret_from_exception_4:
     r0 = syscfg;
     bitclr(r0, 0);
     syscfg = r0;
_ret_from_exception_5:
     rts;
在这里syscfg的最低位表示Supervisor Single Step,如果置1,则每执行一条指令将中断一次,似乎是用于程序调试的,但是不是很明白。
这个事件入口在arch/blackfin/mach-common/interrupt.s中实现:
/* interrupt routine for core timer – 6 */
ENTRY(_evt_timer)
     TIMER_INTERRUPT_ENTRY(EVT_IVTMR_P)
在这里有:
/* For timer interrupts, we need to save IPEND, since the user_mode
        macro accesses it to determine where to account time. */
#define TIMER_INTERRUPT_ENTRY(N)                   /
    [–sp] = SYSCFG;                               /
                                          /
    [–sp] = P0;   /*orig_p0*/                      /
    [–sp] = R0;   /*orig_r0*/                      /
    [–sp] = (R7:0,P5:0);                          /
    p0.l = lo(IPEND);                              /
    p0.h = hi(IPEND);                              /
    r1 = [p0];                                     /
    R0 = (N);                                  /
    jump __common_int_entry;
而EVT_IVTMR_P则定义为:
#define
EVT_IVTMR_P      0x00000006 /* Timer interrupt bit position */
在此之前还有一句说明:
/**************************************************
 * EVT registers (ILAT, IMASK, and IPEND).
 **************************************************/
可以猜测得到,它就是以位掩码做为参数调用__common_int_entry。
下面分析一下进入此中断入口时的系统状态。
当进入此中断时,RETI寄存器保存了中断结束时的返回地址。
从上述代码中可以看出,此中断入口没有使用新的SP,此时SP将指向内核线程或者用户线程的Stack。到
jump __common_int_entry;为止,此时堆栈中的数据如下:
SYSCFG
P0
R0
R7 – R0
P5 – P0
 
实际上,uclinux内核对中断6到中断13的处理,都是调用这个函数进行处理的,且在参数中给出中断掩码的位置。
这个函数的实现在
arch/blackfin/mach-common/interrupt.S中:
/* Common interrupt entry code. First we do CLI, then push
 * RETI, to keep interrupts disabled, but to allow this state to be changed
 * by local_bh_enable.
 * R0 contains the interrupt number, while R1 may contain the value of IPEND,
 * or garbage if IPEND won’t be needed by the ISR. */
__common_int_entry:
       [–sp] = fp;
       [–sp] = usp;
。。。// 寄存器入栈
 
       /* Switch to other method of keeping interrupts disabled. */
       cli r1;
 
       [–sp] = RETI; /* orig_pc */
       /* Clear all L registers. */
       r1 = 0 (x);
       l0 = r1;
       l1 = r1;
       l2 = r1;
       l3 = r1;
 
       r1 = sp;
       SP += -12;
       call _do_irq;
       SP += 12;
       call _return_from_int;
.Lcommon_restore_context:
       RESTORE_CONTEXT
       rti;
看起来结构比较简单,就是调用do_irq进行后继处理而已。
当从evt_timer跳转过来时,RETI寄存器保存了中断结束时的返回地址,R0中保存了IPEND的值。SP指向内核线程或者用户线程的Stack。
而当从EVT7-EVT13的中断跳转过来时,R0则保存了中断号(7-13)。
此时堆栈中的数据如下:
SYSCFG
P0
R0
R7 – R0
P5 – P0
当程序执行到
       call _do_irq;
时,此时堆栈中已经有如下数据:
SYSCFG
P0
R0
R7 – R0
P5 – P0
FP
USP
I0 – I3
M0 – M3
L0 – L3
B0 – B3
A0.x
A0.w
A1.x
A1.w
LC0
LC1
LT0
LT1
LB0
LB1
ASTAT
R0
RETS
RETI
RETX
RETN
RETE
SEQSTAT
R1
RETI
保留12个字节。
在调用call do_irq时,R0中保存了IPEND的值,再注意到
       r1 = sp;
       SP += -12;
       call _do_irq;
看看pt_regs的定义:
/* this struct defines the way the registers are stored on the
   stack during a system call. */
 
struct
pt_regs {
     long orig_pc;
     long ipend;
     long seqstat;
     long rete;
     long retn;
     long retx;
     long pc;      /* PC == RETI */
     long rets;
     long reserved;         /* Used as scratch during system calls */
     long astat;
     long lb1;
     long lb0;
     long lt1;
     long lt0;
     long lc1;
     long lc0;
     long a1w;
     long a1x;
     long a0w;
     long a0x;
     long b3;
     long b2;
     long b1;
     long b0;
     long l3;
     long l2;
     long l1;
     long l0;
     long m3;
     long m2;
     long m1;
     long m0;
     long i3;
     long i2;
     long i1;
     long i0;
     long usp;
     long fp;
     long p5;
     long p4;
     long p3;
     long p2;
     long p1;
     long p0;
     long r7;
     long r6;
     long r5;
     long r4;
     long r3;
     long r2;
     long r1;
     long r0;
     long orig_r0;
     long orig_p0;
     long syscfg;
};
因为堆栈空间是从高往低走的,而结构体的存储空间则是从低往高分配的,所以这里pt_regs的成员的顺序与寄存器入栈的顺序相反,从而将它们一一对应起来,这样调用do_irq函数时pt_regs就能得到正确的值。
由于do_irq是个C函数,对其调用后SP值将自动恢复为原来的位置。
最后调用了_return_from_int,这个函数位于entry.s,同样的,在调用前后SP不变。
最后调用RESTORE_CONTEXT:
#define RESTORE_CONTEXT     restore_context_with_interrupts
#define restore_context_with_interrupts                                        /
     sp += 4; /* Skip orig_pc */                                      /
     sp += 4; /* Skip IPEND */                                        /
     SEQSTAT = [sp++];                                       /
     RETE = [sp++];                                          /
     RETN = [sp++];                                          /
     RETX = [sp++];                                          /
     RETI = [sp++];                                          /
     RETS = [sp++];                                          /
                                          /
     p0.h = _irq_flags;                                      /
     p0.l = _irq_flags;                                      /
     r0 = [p0];                                         /
     sti r0;                                        /
                                          /
     sp += 4; /* Skip Reserved */                                          /
                                          /
     ASTAT = [sp++];                                         /
                                          /
     LB1 = [sp++];                                      /
     LB0 = [sp++];                                      /
     LT1 = [sp++];                                      /
     LT0 = [sp++];                                      /
     LC1 = [sp++];                                      /
     LC0 = [sp++];                                      /
                                          /
     a1.w = [sp++];                                          /
     a1.x = [sp++];                                          /
     a0.w = [sp++];                                          /
     a0.x = [sp++];                                          /
     b3 = [sp++];                                       /
     b2 = [sp++];                                       /
     b1 = [sp++];                                       /
     b0 = [sp++];                                       /
                                          /
     l3 = [sp++];                                       /
     l2 = [sp++];                                       /
     l1 = [sp++];                                       /
     l0 = [sp++];                                       /
                                          /
     m3 = [sp++];                                       /
     m2 = [sp++];                                       /
     m1 = [sp++];                                       /
     m0 = [sp++];                                       /
                                          /
     i3 = [sp++];                                       /
     i2 = [sp++];                                       /
     i1 = [sp++];                                       /
     i0 = [sp++];                                       /
                                          /
     sp += 4;                                       /
     fp = [sp++];                                       /
                                          /
     ( R7:0, P5:0) = [ SP ++ ];                                       /
     sp += 8; /* Skip orig_r0/orig_p0 */                                       /
     csync;                                         /
     SYSCFG = [sp++];                                        /
     csync;                                         /
从而完成了现场的恢复。
这7个中断入口的实现都在
arch/blackfin/mach-common/interrupt.S中:
ENTRY(_evt_evt7)
       INTERRUPT_ENTRY(EVT_IVG7_P)
ENTRY(_evt_evt8)
       INTERRUPT_ENTRY(EVT_IVG8_P)
ENTRY(_evt_evt9)
       INTERRUPT_ENTRY(EVT_IVG9_P)
ENTRY(_evt_evt10)
       INTERRUPT_ENTRY(EVT_IVG10_P)
ENTRY(_evt_evt11)
       INTERRUPT_ENTRY(EVT_IVG11_P)
ENTRY(_evt_evt12)
       INTERRUPT_ENTRY(EVT_IVG12_P)
ENTRY(_evt_evt13)
       INTERRUPT_ENTRY(EVT_IVG13_P)
在这里INTERRUPT_ENTRY的定义为:
///* This is used for all normal interrupts. It saves a minimum of registers
//   to the stack, loads the IRQ number, and jumps to common code. */
#define INTERRUPT_ENTRY(N)                         /
    [–sp] = SYSCFG;                               /
                                          /
    [–sp] = P0;   /*orig_p0*/                      /
    [–sp] = R0;   /*orig_r0*/                      /
    [–sp] = (R7:0,P5:0);                          /
    R0 = (N);                                  /
    jump __common_int_entry;
注意,这里的R0保存了中断号N,N可取7-13。在对
__common_int_entry的分析中我们可以发现,它的R0寄存器一直没有改变,直到调用do_irq。do_irq是一个用C写的函数,其声明为
void
do_irq(int vec, struct pt_regs *fp)
而我们又知道,VDSP编译器在函数参数小于3个的情况下会使用R0, R1, R2这三个寄存器来传值,这就是do_irq中vec这个参数的来源。
前面提到,当内核发生IVG6-IVG13这8个中断时,最后都会调用do_irq函数进行处理,下面看看这个函数的实现:
void
do_irq(int vec, struct pt_regs *fp)
{
     if (vec == EVT_IVTMR_P) {
         vec = IRQ_CORETMR;
     } else {
         struct ivgx *ivg = ivg7_13[vec – IVG7].ifirst;
         struct ivgx *ivg_stop = ivg7_13[vec – IVG7].istop;
         unsigned long sic_status[3];
 
         SSYNC();
         sic_status[0] = bfin_read_SIC_ISR0() & bfin_read_SIC_IMASK0();
         sic_status[1] = bfin_read_SIC_ISR1() & bfin_read_SIC_IMASK1();
         for (;; ivg++) {
              if (ivg >= ivg_stop) {
                   atomic_inc(&num_spurious);
                   return;
              }
              if (sic_status[(ivg->irqno – IVG7) / 32] & ivg->isrflag)
                   break;
         }
         vec = ivg->irqno;
     }
     asm_do_IRQ(vec, fp);
 
}
当调用此函数时,vec保存了内部中断的中断号,即6-13,然后此函数通过读取SIC_ISR和SIC_IMASK,确定外部中断号,并将之转换为内核的中断描述表的序号。最后使用此序号调用asm_do_IRQ函数。
这个函数在arch/kernel/blackfin/irqchip.c中:
/*
 * do_IRQ handles all hardware IRQs. Decoded IRQs should not
 * come via this function. Instead, they should provide their
 * own ‘handler’
 */
 
#ifdef
CONFIG_DO_IRQ_L1
__attribute__((l1_text))
#endif
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
     struct pt_regs *old_regs;
     struct irq_desc *desc = irq_desc + irq;
     unsigned short pending, other_ints;
 
     old_regs = set_irq_regs(regs);
 
     /*
      * Some hardware gives randomly wrong interrupts. Rather
      * than crashing, do something sensible.
      */
     if (irq >= NR_IRQS)
         desc = &bad_irq_desc;
 
     irq_enter();
 
     generic_handle_irq(irq);
 
     /* If we’re the only interrupt running (ignoring IRQ15 which is for
        syscalls), lower our priority to IRQ14 so that softirqs run at
        that level. If there’s another, lower-level interrupt, irq_exit
        will defer softirqs to that. */
     CSYNC();
     pending = bfin_read_IPEND() & ~0x8000;
     other_ints = pending & (pending – 1);
     if (other_ints == 0)
         lower_to_irq14();
     irq_exit();
 
     set_irq_regs(old_regs);
}
进入这个函数的时候,irq参数为irq_desc这个内核数组的序号,而regs则是中断发生时系统寄存器的状态。
这个函数主要就是两个工作,一个是调用
generic_handle_irq进行中断处理,再一个就是当没有其它中断的时候,将中断级别降为IRQ14。
/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it’s attached to an irqtype-style controller.
 */
static
inline void generic_handle_irq(unsigned int irq)
{
     struct irq_desc *desc = irq_desc + irq;
 
#ifdef
CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
     desc->handle_irq(irq, desc);
#else
     if (likely(desc->handle_irq))
         desc->handle_irq(irq, desc);
     else
         __do_IRQ(irq);
#endif
}
因为在内核中已经为每一个irq_desc都设置了处理函数,即desc->handle_irq,因此__do_IRQ将不会被调用。
lower_to_irq14的实现则在arch/blackfin/kernel/entry.s中:
ENTRY(_lower_to_irq14)
       r0 = 0x401f;
       sti r0;
       raise 14;
       rti;
此中断入口位于arch/blackfin/mach-common/entry.s:
ENTRY(_evt14_softirq)
       cli r0;
       [–sp] = RETI;
       SP += 4;
       rts;
没啥可说的。
此入口位于arch/blackfin/mach-common/interrupt.s:
/* interrupt routine for system_call – 15 */
ENTRY(_evt_system_call)
       SAVE_CONTEXT_SYSCALL
#ifdef CONFIG_FRAME_POINTER
       fp = 0;
#endif
       call _system_call;
       jump .Lcommon_restore_context;
ENDPROC(_evt_system_call)
直接就调用了system_call,这个函数位于
arch/blackfin/mach-common/entry.S
它的作用就是根据序号调用_sys_call_table中的指定函数。

今天的文章重读uclinux-2008r1-rc8(bf561)内核的中断处理(3):中断处理流程分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/65450.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注