Hi there,
I’m using FreeRTOS on a project of my own with a custom board and an AT90USB647 uController. At some point I need to jump to the USB boot loader code and before I do it I stop the scheduler with
vTaskEndScheduler
function.
Problem is:
- in the documentation it is stated that execution will return to the instruction after the call to
vTaskStartScheduler
just as if it had returned (see http://www.freertos.org/a00133.html). The code does not behave that way. After some time trying to make it work I checked the source code and
vTaskEndScheduler
has only 3 lines of code that I pasted below.
void vTaskEndScheduler( void )
{
/* Stop the scheduler interrupts and call the portable scheduler end
routine so the original ISRs can be restored if necessary. The port
layer must ensure interrupts enable bit is left in the correct state. */
portDISABLE_INTERRUPTS();
xSchedulerRunning = pdFALSE;
vPortEndScheduler();
}
this means that unless somehow i make code on
vPortEndScheduler
to return to the instruction after
vTaskStartScheduler
the return will be to the instruction after
vTaskEndScheduler
.
Can someone please tell me how to make it return to the statement after
vTaskStartScheduler
??
Or does the documentation need to be changed?
Anyway, this is a either a bug or an unimplemented feature on FreeRTOS isn’t it?
This is the same question but readable, i did not now that the ‘code’ tag would do that and i do not know how to edit the post.
Hi there,
I’m using FreeRTOS on a project of my own with a custom board and an AT90USB647 uController. At some point I need to jump to the USB boot loader code and before I do it I stop the scheduler with vTaskEndScheduler function.
Problem is:
- in the documentation it is stated that execution will return to the instruction after the call to vTaskStartScheduler just as if it had returned (see http://www.freertos.org/a00133.html).
- The code does not behave that way. After some time trying to make it work I checked the source code and vTaskEndScheduler has only 3 lines of code that I pasted below.
void vTaskEndScheduler( void )
{
/* Stop the scheduler interrupts and call the portable scheduler end
routine so the original ISRs can be restored if necessary. The port
layer must ensure interrupts enable bit is left in the correct state. */
portDISABLE_INTERRUPTS();
xSchedulerRunning = pdFALSE;
vPortEndScheduler();
}
this means that unless somehow i make code on vPortEndScheduler to return to the instruction after vTaskStartScheduler the return will be to the instruction after vTaskEndScheduler.
Can someone please tell me how to make it return to the statement after vTaskStartScheduler??
Or does the documentation need to be changed?
Anyway, this is a either a bug or an unimplemented feature on FreeRTOS isn’t it?
I think that function is only implemented for the PC port, where ending the scheduler allows you to return to DOS. The implementation uses setjmp(), take a look at the OpenWatcom PC port implementation of the function.
Shouldn’t the documentation be changed to say that the function returns as expected unless using the PC port where it’ll return to the instruction after vTaskStartScheduler?
Rather than putting in the documentation differing behavior baed on the port, it might be better to document that this function is only fully implemented on the PC port, thus opening the door to implementing it on other ports later if desired without “breaking” the documentation.
I would also think a different “default implementation” for the ports which don’t really implement the function, maybe vPortEndScheduler() should do something like a while(1); stall loop which is at least closer to documented behavior and puts up a nice big flag when debugging that makes it clear what wasn’t working.
I updated the online documentation page to say it was only implemented on the PC port a couple of hours ago - when the last page came in. The reference manual already has the text “At the time of writing, vTaskEndScheduler() is only implemented for the real mode x86 OpenRTOS port.”.
Oops. It’s wrong solution. I found that prvRestoreContextOfFirstTask() unwinds main stack which corrupts saved “context” needed to return back to vTaskStartScheduler(). Why ???
P.S. It’s very strange that it worked for me very first time.
I didn’t found any reason to unwind stack and removed few assembler instructions as useless (and destructive). Now it works perfectly. A final patch as follows:
diff -u -r FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c FreeRTOSV7.1.0_improved/Source/portable/GCC/ARM_CM3_MPU/port.c
--- FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c 2011-12-13 22:38:23.906250000 +0700
+++ FreeRTOSV7.1.0_improved/Source/portable/GCC/ARM_CM3_MPU/port.c 2012-08-02 19:20:05.640625000 +0700
@@ -288,10 +288,6 @@
{
__asm volatile
(
- " ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */
- " ldr r0, [r0] \n"
- " ldr r0, [r0] \n"
- " msr msp, r0 \n" /* Set the msp back to the start of the stack. */
" ldr r3, pxCurrentTCBConst2 \n" /* Restore the context. */
" ldr r1, [r3] \n"
" ldr r0, [r1] \n" /* The first item in the TCB is the task top of stack. */
@@ -316,8 +312,11 @@
/*
* See header file for description.
*/
+static volatile char sh_stop_request = 0; // indicates whether xPortStartScheduler() called from vPortEndScheduler() to stop sheduler
+static volatile unsigned long sh_context[4]; // stores context of vTaskEndScheduler() call to restore it when sheduler being requested to stop
portBASE_TYPE xPortStartScheduler( void )
{
+ if (!sh_stop_request) {
/* Make PendSV and SysTick the same priroity as the kernel. */
*(portNVIC_SYSPRI2) |= portNVIC_PENDSV_PRI;
*(portNVIC_SYSPRI2) |= portNVIC_SYSTICK_PRI;
@@ -332,19 +331,49 @@
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;
+ /* Save context to return from vTaskEndScheduler() */
+ __asm volatile
+ (
+ " push {r0-r12,lr} \n"
+ " mrs %[basepri_val], basepri \n"
+ " mrs %[control_val], control \n"
+ " mrs %[psp_val], psp \n"
+ " mrs %[msp_val], msp \n"
+ : [basepri_val]"=r" (sh_context[0]), [psp_val]"=r" (sh_context[1]), [msp_val]"=r" (sh_context[2]), [control_val]"=r" (sh_context[3])
+ );
+
/* Start the first task. */
__asm volatile( " svc %0 \n"
:: "i" (portSVC_START_SCHEDULER) );
+ } else {
+ sh_stop_request = 0;
- /* Should not get here! */
- return 0;
+ /* Cancel most important system changes made when sheduler started */
+ *(portNVIC_SYSTICK_CTRL) &= ~(portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE);
+ *portMPU_CTRL &= ~portMPU_ENABLE;
+ /* Restore context to return back to vTaskStartScheduler() */
+ __asm volatile
+ (
+ " msr basepri, %[basepri_val] \n"
+ " msr control, %[control_val] \n"
+ " msr psp, %[psp_val] \n"
+ " msr msp, %[msp_val] \n"
+ " isb \n"
+ " pop {r0-r12,lr} \n"
+ :: [basepri_val]"r" (sh_context[0]), [psp_val]"r" (sh_context[1]), [msp_val]"r" (sh_context[2]), [control_val]"r" (sh_context[3])
+ : "sp"
+ );
+ }
+
+ return pdFALSE;
}
/*-----------------------------------------------------------*/
void vPortEndScheduler( void )
{
- /* It is unlikely that the CM3 port will require this function as there
- is nothing to return to. */
+ /* Restore point where sheduler started with tricky change of control flow */
+ sh_stop_request = 1;
+ xPortStartScheduler();
}
/*-----------------------------------------------------------*/