由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,一旦流片了,就没有挽救的机会了。
评论
发表评论