由RFE指令引发的一串故事

前段时间,我们在bring up一块Cortex-A5主控的芯片时,发现在跑FreeRTOS时, 系统总是crash. 通过GDB定位到一旦RFE指令执行结束后,系统便进入data abort mode.

<图一> 一旦执行RFE便进入data abort


<图二>, FreeRTOS中RFE相关代码片段

RFE指令如其字面含义, return from exception. 它的主要作用是: 从栈中弹出值分别赋值给cpsr和pc. 这条指令在恢复现场时,具有不可替代的作用(如果用其他指令来代替同样的功能, 自然就污染其他寄存器了).

RFE指令在ARM v7 spec中的伪码:

<图三>,RFE指令的伪码


曾经一度怀疑是Cortex-A5的errata,但是CPU指令级别的仿真测试对于RFE指令是没有问题的。而且,之前在FPGA上做预开发的时候,在FPGA平台上也没有发现此类问题。所以,一度陷入到困局中,没有思路。

如果整个FreeRTOS来让SOC仿真,那么仿真的时间会比较久,因此我们写了下面一段比较小的代码来进行仿真测试。但是仿真下来并没有发现问题。

_start:

    ldr r0, =vectors

    mcr p15, 0, r0, c12, c0, 0  

// PLD Capablity

    mrc p15, 0, r0, c0, c2, 2

//MAIN IDR

    mrc p15, 0, r0, c0, c0, 0

// CCSIDR

    mrc p15, 1, r0, c0, c0, 0


    cps #0x17

    ldr r0, =0x30009000

    str r0, [r0]

    ldr r0, =0x10009000

    mov sp, r0

    cps #0x1f

    ldr r0, =0x10008000

    mov sp, r0


    mrs r0, cpsr

    bic r0, r0, #0x80          

    msr cpsr_c, r0


    svc #0                      // trigger one soft interrupt.


    b .

    nop

    nop

    nop


    .section .vectors, "ax"

    .align 7

vectors:

    b reset_handler             //

    b undef_handler             //

    //b svc_handler               //

    b fault_handler               //

    b prefetch_abort_handler    //

    b data_abort_handler        //

    b reserved_handler          //

    b irq_handler               //

    b fiq_handler               //


svc_handler:

    srsdb sp!, #0x13  

    ldr r0, [sp, #4]

    bic r0, r0, #0x100

    str r0, [sp, #4]


    rfeia sp!                   // rfeia return


reset_handler:

    b .


undef_handler:

    b .


prefetch_abort_handler:

    b .


data_abort_handler:

    MRC p15, 0, r0, c1, c0, 0

    // get dfsr

    mrc p15, 0, r1, c5, c0, 0

    // get dfar

    mrc p15, 0, r2, c6, c0, 0

    b .


reserved_handler:

    b .


irq_handler:

    b .


fiq_handler:

    b .


但是,有意思的是:当这段复现代码跑在DDR中,会出现此类问题;如果跑在静态内存区,就不会出问题。所以,这个可以确认排除掉CPU内部对RFE指令的支持上面。


这里有必要简单介绍一下我们SOC软件启动的顺序。我们的SOC启动过程是bootrom -> SBL -> FreeRTOS. bootROM是芯片内固化的引导程序,而SBL是跑在静态内存区的程序,它的工作主要是负责初始化DDR,并加载FreeRTOS应用到DDR并运行。上面这段复现的代码从启动顺序上可以看作是FreeRTOS应用。没有SBL,那么应用只能跑在静态内存区,而跑在静态内存区就不会出现此类问题。

在RTOS中,一个新创建的任务除了会被加入到可运行任务队列之外,还会将global的指针pxCurrentTCB指向新创建的任务。并在调度器开始工作的时候,运行该任务(vTaskStartScheduler->xPortStartScheduler->vPortRestoreTaskContext->portRESTORE_CONTEXT)。

317 pxCurrentTCBConst: .word pxCurrentTCB

1038  static void prvAddNewTaskToReadyList(TCB_t *pxNewTCB)
1039  {
1040      /* Ensure interrupts don't access the task lists while the lists are being
1041  	updated. */
1042      taskENTER_CRITICAL();
1043      {
1044          uxCurrentNumberOfTasks++;
1045          if (pxCurrentTCB == NULL) {
1046              /* There are no other tasks, or all the other tasks are in
1047  			the suspended state - make this the current task. */
1048              pxCurrentTCB = pxNewTCB;
1049  
1050              if (uxCurrentNumberOfTasks == (UBaseType_t)1) {
1051                  /* This is the first task to be created so do the preliminary
1052  				initialisation required.  We will not recover if this call
1053  				fails, but we will report the failure. */
1054                  prvInitialiseTaskLists();
1055              } else {
1056                  mtCOVERAGE_TEST_MARKER();
1057              }
1058          } 
1059              /* If the scheduler is not already running, make this task the
1060  			current task if it is the highest priority task to be created
1061  			so far. */
1062              if (xSchedulerRunning == pdFALSE) {
1063                  if (pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority) {
1064                      pxCurrentTCB = pxNewTCB;
1065                  } else {
1066                      mtCOVERAGE_TEST_MARKER();
1067                  }
1068              } else {
1069                  mtCOVERAGE_TEST_MARKER();
1070              }
1071          }

而首次运行每一个任务时,对各各寄存器的值,其实在FreeRTOS中都需要初始化的。例如:

234      *pxTopOfStack = (StackType_t)portINITIAL_SPSR;
235  
236      if (((uint32_t)pxCode & portTHUMB_MODE_ADDRESS) != 0x00UL) {
237          /* The task will start in THUMB mode. */
238          *pxTopOfStack |= portTHUMB_MODE_BIT;
239      }
240  
241      pxTopOfStack--;
242  
243      /* Next the return address, which in this case is the start of the task. */
244      *pxTopOfStack = (StackType_t)pxCode;
245      pxTopOfStack--;
246  
247      /* Next all the registers other than the stack pointer. */
248      *pxTopOfStack = (StackType_t)portTASK_RETURN_ADDRESS; /* R14 */
249      pxTopOfStack--;
250      *pxTopOfStack = (StackType_t)0x12121212; /* R12 */
251      pxTopOfStack--;
252      *pxTopOfStack = (StackType_t)0x11111111; /* R11 */
253      pxTopOfStack--;
254      *pxTopOfStack = (StackType_t)0x10101010; /* R10 */
255      pxTopOfStack--;
256      *pxTopOfStack = (StackType_t)0x09090909; /* R9 */
257      pxTopOfStack--;
258      *pxTopOfStack = (StackType_t)0x08080808; /* R8 */
259      pxTopOfStack--;
260      *pxTopOfStack = (StackType_t)0x07070707; /* R7 */
261      pxTopOfStack--;
262      *pxTopOfStack = (StackType_t)0x06060606; /* R6 */
263      pxTopOfStack--;
264      *pxTopOfStack = (StackType_t)0x05050505; /* R5 */
265      pxTopOfStack--;
266      *pxTopOfStack = (StackType_t)0x04040404; /* R4 */
267      pxTopOfStack--;
268      *pxTopOfStack = (StackType_t)0x03030303; /* R3 */
269      pxTopOfStack--;
270      *pxTopOfStack = (StackType_t)0x02020202; /* R2 */
271      pxTopOfStack--;
272      *pxTopOfStack = (StackType_t)0x01010101; /* R1 */
273      pxTopOfStack--;
274      *pxTopOfStack = (StackType_t)pvParameters; /* R0 */
275      pxTopOfStack--;
276  


CPSR也不例外:它的值被初始化为0x1f. 在CPSR中,0x1F. 即,在FreeRTOS中系统只是将当前的mode切换为system mode。

<图4>,CPSR


<图5>,Processor mode

也就是说:

如果是BootROM->SBL->App。则App运行RFE指令后,抛出异常;而如果是BootROM->App. 则App运行RFE指令,不会抛出异常。

所以推测一下:

抛出异常的原因,很有可能是SBL中发生了一些异常,导致RFE后将异常抛出来了。查找了在RFE之前的CPSR,发现其CPSR.A 都是置1的。也就是说默认形况下,对于async abort,CPU Core是将其屏蔽的。

结合FreeRTOS中,每一个task的默认CPSR。可以大概确认SBL中发生了一个async abort异常,但是一直被压制,直到RFE时才爆发。

然后我们确认了SBL中的代码,发现在初始化DDR controller时,访问了一个不存在的寄存器。


从事后诸葛亮的角度看,这个issue好像很简单。但刚碰到这个问题,确实很头疼。

这个issue给我们的一个教训就是:

在系统上电早期,尽量把CPSR中,该打开的功能都打开。比如在bootROM的开发时,尽量把CPSR.A bit打开。否则,bootROM中发生async abort,一旦流片了,就没有挽救的机会了。

评论

此博客中的热门博文

提交了30次才AC ---【附】POJ 2488解题报告

n个进程共享m个资源得死锁问题证明