n1vg wrote on Wednesday, October 10, 2018:
Hi Richard,
Thanks for the quick reply. My port.c looks different - I believe this one came from Erich Styger’s PEx components at mcuoneclipse.com originally. I know this port has been modified to support Newlib’s malloc() for heap allocation, not sure what else might be different. Here’s the vPortPendSVHandler:
#if (configCOMPILER==configCOMPILER_ARM_GCC)
#if configGDB_HELPER
/* prototypes to avoid compiler warnings */
__attribute__ ((naked)) void vPortPendSVHandler_native(void);
__attribute__ ((naked)) void PendSV_Handler_jumper(void);
__attribute__ ((naked)) void vPortPendSVHandler_native(void) {
#elif !MCUC1_CONFIG_PEX_SDK_USED /* the SDK expects different interrupt handler names */
__attribute__ ((naked)) void PendSV_Handler(void) {
#else
__attribute__ ((naked)) void vPortPendSVHandler(void) {
#endif
#if configCPU_FAMILY_IS_ARM_M4_M7(configCPU_FAMILY) /* Cortex M4 or M7*/
__asm volatile (
" mrs r0, psp \n"
" ldr r3, pxCurrentTCBConst \n" /* Get the location of the current TCB. */
" ldr r2, [r3] \n"
#if configCPU_FAMILY_IS_ARM_FPU(configCPU_FAMILY)
" tst r14, #0x10 \n" /* Is the task using the FPU context? If so, push high vfp registers. */
" it eq \n"
" vstmdbeq r0!, {s16-s31} \n"
" stmdb r0!, {r4-r11, r14} \n" /* save remaining core registers */
#else
" stmdb r0!, {r4-r11} \n" /* Save the core registers. */
#endif
" str r0, [r2] \n" /* Save the new top of stack into the first member of the TCB. */
" stmdb sp!, {r3, r14} \n"
" mov r0, %0 \n"
" msr basepri, r0 \n"
" bl vTaskSwitchContext \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" ldmia sp!, {r3, r14} \n"
" ldr r1, [r3] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" ldr r0, [r1] \n"
#if configCPU_FAMILY_IS_ARM_FPU(configCPU_FAMILY)
" ldmia r0!, {r4-r11, r14} \n" /* Pop the core registers */
" tst r14, #0x10 \n" /* Is the task using the FPU context? If so, pop the high vfp registers too. */
" it eq \n"
" vldmiaeq r0!, {s16-s31} \n"
#else
" ldmia r0!, {r4-r11} \n" /* Pop the core registers. */
#endif
" msr psp, r0 \n"
" bx r14 \n"
" \n"
" .align 2 \n"
"pxCurrentTCBConst: .word pxCurrentTCB \n"
::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
);
#else /* Cortex M0+ */
__asm volatile (
" mrs r0, psp \n"
" \n"
" ldr r3, pxCurrentTCBConst \n" /* Get the location of the current TCB. */
" ldr r2, [r3] \n"
" \n"
" sub r0, r0, #32 \n" /* Make space for the remaining low registers. */
" str r0, [r2] \n" /* Save the new top of stack. */
" stmia r0!, {r4-r7} \n" /* Store the low registers that are not saved automatically. */
" mov r4, r8 \n" /* Store the high registers. */
" mov r5, r9 \n"
" mov r6, r10 \n"
" mov r7, r11 \n"
" stmia r0!, {r4-r7} \n"
" \n"
" push {r3, r14} \n"
" cpsid i \n"
" bl vTaskSwitchContext \n"
" cpsie i \n"
" pop {r2, r3} \n" /* lr goes in r3. r2 now holds tcb pointer. */
" \n"
" ldr r1, [r2] \n"
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" add r0, r0, #16 \n" /* Move to the high registers. */
" ldmia r0!, {r4-r7} \n" /* Pop the high registers. */
" mov r8, r4 \n"
" mov r9, r5 \n"
" mov r10, r6 \n"
" mov r11, r7 \n"
" \n"
" msr psp, r0 \n" /* Remember the new top of stack for the task. */
" \n"
" sub r0, r0, #32 \n" /* Go back for the low registers that are not automatically restored. */
" ldmia r0!, {r4-r7} \n" /* Pop low registers. */
" \n"
" bx r3 \n"
" \n"
".align 2 \n"
"pxCurrentTCBConst: .word pxCurrentTCB"
);
#endif
}
My ARM assembly language is really weak - what’s the %0 notation? Looks like it assembled as #0x50, which is correct. I’m assuming that must be an assembly-time substitution but I don’t know what it references.
When I catch the race condition, BASEPRI is 0. I’ve tried to step through the PendSV handler but the debugger seems determined to make sure I don’t get to see it happening - every time, it hangs before it gets to the msr BASEPRI, r0 instruction. The gdb server sits there with high CPU utilization, whether I’m using an LPC-Link2 or a P&E Cyclone ACP.
I’ve attached a screenshot so you can see where it was at. r0 is set to 0x50, so that’s right. There are other interrupts enabled, but I’ve set all of the NVIC priority registers to values in the range of 0x50 to 0x80 whether the interrupt is implemented or not, just in case. I once got the debugger to stop in vPortSwitchContext and saw BASEPRI set, but when it stepped over the stack check it came up 0 again. It also stopped reading registers entirely right after that so it’s hard to say what was actually going on.
This is where I’d normally insert a rant about the unreliability of NXP’s debugging tools, but I’ve got at least a partial workaround for the bug that’s been plauging this project for weeks, we just started shipping another new product today, and I had a really great first date last night, so I’m going to quit while I’m still in a good mood.
I’ll see what I can find out from Erich about the origin of this port.c, and fuss with the debugger some more tomorrow.
Thanks again,
Scott