异常和中断处理过程是每个体系结构和OS都要面对的重要问题,本文从硬件角度以及软件角度来分析在PowerPC处理器上的这个过程。

PowerPC定义了十几种异常,其中常见的如DataTLB miss,Instruction TLB miss,external input....每一个都有固定的地址(ivorn),从物理地址0x100开始,0x500是中断入口,0xe00是DTLBmiss,0xf00是itlbmiss入口。首先整体了解一下中断发生时硬件/软件处理流程,硬件会做什么,OS软件要做什么。

PowerPC 中断的硬件处理流程:

1、把正在执行的指令序列下一条指令地址保存到中断寄存器SRR0(Save/RestoreRegiser 0)。

2、把当前 MSR 的内容保存到 SRR1。

3、把 MSR 某些比特置为 0,如MSR[SPE,WE,EE,PR,FP,FE0,FE1,IS,DS]are 0 by all interrupts。

[注]:

PowerPC有两种执行模式,分别为用户模式(UserMode) 和特权模式(SupervisorMode),MSR[PR]=0 表示特权模式。Linux内核运行在特权模式,而普通程序运行在用户模式。VxWorks全部运行在特权模式。

1、在新的 MSR 状态下,从中断向量偏移处开始指令读取和执行。

2、外部中断处理结束时,必须通过 rfi 指令返回。

3、rfi 的执行,会把 SRR1 的内容恢复到 MSR, 并从 SRR0 所保存的地址处继续执行。

PowerPC Linux (软件)中断处理程序

异常向量ExternalInput处的处理程序主要分为以下几个步骤:

1、NORMAL_EXCEPTION_PROLOG宏

建立用户中断处理程序的栈帧,并把一些寄存器的值保存在栈帧中,它们在栈帧中布局由structpt_regs 定义。

2、执行 do_IRQ

在irq_enter()函数中更新preempt_count。 读MPIC的IACK寄存器,通知MPIC开始处理该中断,同时获取MPIC中断号,映射为软件中断号,找到并运行注册在该软件中断号上的用户中断处理程序,最后写MPIC的EOI寄存器,通知MPIC中断处理结束。

在irq_exit()中更新preempt_count,调用invoke_softirq()处理pending的软中断。

3、ret_from_except

某些条件满足时,进行进程调度和信号处理,最后调用rfi指令返回。

PowerPC vxWorks(软件)中断处理程序

VxWorks的处理流程大体上与linux一样,只是在代码实现细节上存在些区别。

代码的调用路径:usrInit ->excVecInit ->excVecConnectCommon

代码实现过程:

把一段机器码excConnectCode[]拷贝到每个异常vector的入口地址处(excVecConnectCommon里实现),再重新计算异常处理函数的地址并覆盖通用处理函数地址。

LOCAL INSTR excConnectCode[]=
    {
    /*  data                  word   byte    opcode  operands                              */
    0x7c7343a6,       /* 0     0x00    mtspr  SPRG3, p0                 */
    0x7c6000a6,       /* 1     0x04    mfmsr  p0                              */
    0x546303da,       /* 2     0x08    rlwinm p0,p0,0,15,13  clear MSR[CE] */
    0x7c600124,       /* 3     0x0c    mtmsr  p0                               */
    0x60000000,       /* 4     0x10    nop                                             */
    /* If either ofthe above, add 4 words/0x10bytes to following offsets */
    0x7c6802a6,       /* 1     0x04    mflr   p0                                  */
    0x48000001,       /* 2(6)  0x08/18 bl      xxxEnt                        */
    0x38610000,       /* 3     0x0c    addi   r3, sp, 0         */
    0x9421fff0,       /* 4     0x10    stwu   sp, -FRAMEBASESZ(sp)   */
    0x48000001,       /* 5(9)  0x14/24 bl      xxxHandler              */
    0x38210010,       /* 6     0x18    addi   sp, sp, FRAMEBASESZ    */
    0x48000001        /* 7(11) 0x1c/2c  bl     xxxExit                       */
    };

整个过程中最关键的就是这段机器码数组,一共12条指令。

    0x7c7343a6,       /* 0     0x00    mtspr  SPRG3, p0                 */
    0x7c6000a6,       /* 1     0x04    mfmsr  p0                              */
    0x546303da,       /* 2     0x08    rlwinm p0,p0,0,15,13  clear MSR[CE] */
    0x7c600124,       /* 3     0x0c    mtmsr  p0                               */
    0x60000000,       /* 4     0x10    nop 

首先清MSR[CE],防止在保存中断context时被CE中断打断

    0x7c6802a6,       /* 1    0x04     mflr   p0                */
    0x48000001,      /* 2(6)  0x08/18 bl      xxxEnt            */
    0x38610000,      /* 3     0x0c    addi   r3, sp, 0         */
    0x9421fff0,      /* 4     0x10    stwu   sp, -FRAMEBASESZ(sp)   */
    0x48000001,      /* 5(9)  0x14/24 bl      xxxHandler              */
    0x38210010,      /* 6     0x18    addi   sp, sp, FRAMEBASESZ    */
    0x48000001       /* 7(11) 0x1c/2c  bl     xxxExit                       */
  • xxxEnt是intEntorexcEnt.
  • xxxHandler是 excIntHandle or excExcHandle
  • xxxExit是intExitor excExit.

其中intEnt/intExit,excEnt/excExit是通用中断/异常入口和出口函数,中断进前者,异常进后者,主要作用是保存context以及恢复context,设置中断栈,保存易失的寄存器变量。

excIntHandle/excExcHandle也是通用处理函数,主要作用是打印下异常信息,对于关键异常或者中断的话,都有自己单独的处理函数,会在接下来的初始化过程中逐个把xxxHandler换成专用的处理函数,实现的方法是直接修改机器码后16bit。

MMU初始化的时候替换excExcHandle为mmuPpcDataTlbMissHandler/mmuPpcInstTlbMissHandler

MPIC初始化的时候会用vxbMpicIntHandler替换excIntHandle来调用ISR

WD/Timer初始化的时候也会替换成自己的Handler

至此,真正中断/异常来的时候会执行intEnt->vxbMpicIntHandler->intExit

下面的代码实现是intEnt,可以看看里面都做了哪些事情

/*
 *ThePowerPC Family doesn't support the notion of an interrupt
 * stackinhardware.  To avoid having to allowspacefor interrupt
 *handlingin each task stack, this stub switches from the task
 * stacktothe interrupt stack by changing the value of the SP(R1).
 * Iftheinterrupt is nested, we don't need to switch stacks but
 * wedoneed to check for overflow.
 */
 _PPC_PER_CPU_VALUE_GET(p0,vxIntStackBase)
 stwu    sp, -_PPC_ESF_STK_SIZE(p0)      /* carve stack */
 mr      sp, p0                          /* switch tointerruptstack */

 * The critical status are saved atthisstage.  The interrupt should
 *beenabled as soon as possible to reduce the interrupt latency.
 *However,there is only one mask bit on PowerPC. It is at the
 *interruptcontroller level to set mask for each individual
 *interrupt.
 * Thus,wesave task's register first, then call interrupt controller
 *routineto decide if the interrupt should be re-enabled or not.
 */
 mfspr   p0, XER                         /* load XER to P0 */
 stw     p0, _PPC_ESF_XER(sp)            /* save XER to the stack */
 mfspr   p0, CTR                         /* load CTR to P0 */
 stw     p0, _PPC_ESF_CTR(sp)            /* save CTR to the stack */

 #if   (CPU==PPC601)
     mfspr   p0, MQ                          /* load MQ to P0 */
     stw     p0, _PPC_ESF_MQ(sp)             /* save MQ to the stack */
 #endif  /*(CPU==PPC601)*/
 #if    (defined(_WRS_CONFIG_PPC_E500))
 #ifdef _WRS_SPE_SUPPORT
     mfspr   p0, SPEFSCR                     /* load SPEFSCR to P0 */
     stw     p0, _PPC_ESF_SPEFSCR(sp)        /* save SPEFSCR to the stack */
 #endif /*_WRS_SPE_SUPPORT */
 #endif  /*(defined(_WRS_CONFIG_PPC_E500)) */
 
 stw     r0, _PPC_ESF_R0(sp)             /* save general register 0 */
 stw     r2, _PPC_ESF_R2(sp)             /* save general register 2 */
 
 stw     p2, _PPC_ESF_P2(sp)             /* save general register 5 */
 stw     p3, _PPC_ESF_P3(sp)             /* save general register 6 */
 stw     p4, _PPC_ESF_P4(sp)             /* save general register 7 */
 stw     p5, _PPC_ESF_P5(sp)             /* save general register 8 */
 stw     p6, _PPC_ESF_P6(sp)             /* save general register 9 */
 stw     p7, _PPC_ESF_P7(sp)             /* save general register 10 */
 
 stw     r11,_PPC_ESF_R11(sp)           /* savegeneral register 11 */
 stw     r12, _PPC_ESF_R12(sp)          /* save general register 12 */
 stw     r13, _PPC_ESF_R13(sp)          /* save general register 13 */
 beqlr+  cr4                            /* return to vector unlessstackoverflow */