vTaskEndScheduler not working as stated

bernardomarques wrote on Wednesday, January 06, 2010:

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?

Regards,
Bernardo Marques.

bernardomarques wrote on Wednesday, January 06, 2010:

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?

Regards,
Bernardo Marques.

rtel wrote on Thursday, January 07, 2010:

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.

Regards.

bernardomarques wrote on Thursday, January 07, 2010:

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?

Thanks for your help Richard.

Regards,
Bernardo Marques.

anonymous wrote on Wednesday, August 01, 2012:

I fallen into the same trap. Agree. Documentation should be corrected (it’s still unclear).

richard_damon wrote on Wednesday, August 01, 2012:

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.

rtel wrote on Wednesday, August 01, 2012:

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.”.

Regards.

anonymous wrote on Thursday, August 02, 2012:

Thank you, Richard.
I’ve implemented this behavior for GCC ARM Cortex-M3 MPU port. Following is diff applied to original FreeRTOS V7.1.0 port:

diff --git a/FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c b/FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c
--- a/FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c
+++ b/FreeRTOSV7.1.0/Source/portable/GCC/ARM_CM3_MPU/port.c
@@ -316,8 +316,11 @@ static void prvRestoreContextOfFirstTask( void )
 /*
  * See header file for description.
  */
+volatile char sh_stop_request = 0; // indicates whether xPortStartScheduler() called from vPortEndScheduler() to stop sheduler
+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 +335,49 @@ portBASE_TYPE xPortStartScheduler( void )
 	/* 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;
+
+		/* 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"
+		);
+	}
 
-	/* Should not get here! */
-	return 0;
+	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();
 }
 /*-----------------------------------------------------------*/

It works as expected but I didn’t tested heavily.
It must work also on non-MPU port if you remove

*portMPU_CTRL &= ~portMPU_ENABLE;

line.

anonymous wrote on Thursday, August 02, 2012:

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.

anonymous wrote on Thursday, August 02, 2012:

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();
 }
 /*-----------------------------------------------------------*/