How PendSV is used for Context Switching in FreeRTOS ??
Free RTOS
In this article let me explain, line by line, the
PendSV handler which carry out the Context switch form one task to another task. That is nothing but saving the context of current task (Context is nothing but few processor registers, that’s it) and retrieving the context of the next potential task.
So, here is the handler code, I copied from the port.c file of ARM Cortex M3.
If you want to locate this file then, you have to go to,
<FreeRTOs installation path>\FreeRTOS\FreeRTOSv9.0.0\FreeRTOS\Source\portable\GCC\ARM_CM3\
you can download and install the FreeRTOS from here:
https://sourceforge.net/projects/freertos/
void xPortPendSVHandler( void )
{
/* This is a naked function. */
__asm volatile
(
“ mrs r0, psp \n”
“ isb \n”
“ \n”
“ ldr r3, pxCurrentTCBConst \n” /* Get the location of the current TCB. */
“ ldr r2, [r3] \n”
“ \n”
“ stmdb r0!, {r4-r11} \n” /* Save the remaining registers. */
“ str r0, [r2] \n” /* Save the new top of stack into the first member of the TCB. */
“ \n”
“ 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”
“ \n” /* Restore the context, including the critical nesting count. */
“ ldr r1, [r3] \n”
“ ldr r0, [r1] \n” /* The first item in pxCurrentTCB is the task top of stack. */
“ ldmia r0!, {r4-r11} \n” /* Pop the registers. */
“ msr psp, r0 \n”
“ isb \n”
“ bx r14 \n”
“ \n”
“ .align 4 \n”
“pxCurrentTCBConst: .word pxCurrentTCB \n”
::”i”(configMAX_SYSCALL_INTERRUPT_PRIORITY)
);
}
Now, let’s explore this line by line.
First, i will write the code and below that i will give the explanation !
” mrs R0, PSP \n”
Here, they are just saving the PSP value in to R0. Why? Let’s see.
” ldr R3, pxCurrentTCBConst \n”
Now, in this line, the address of the global variable “pxCurrentTCB” is stored in R3 register.
“pxCurrentTCB” is a global variable defined in tasks.c (go ahead and check tasks.c) which holds the address of the Currently executing task’s TCB.
Now, let’s just assume that the address of the pxCurrentTCB is 0x20000000. And it is holding the address 0x20001108 as shown below.
So, now R3 = 0x20000000
” ldr R2, [R3] \n”
(De-reference 0x20000000 and store the value into R2)
So, R2 = 0x20001108 (Address of the Current TCB)
” stmdb R0!, {R4-R11} \n”
Now , R0 has PSP value right ?? So, using that PSP, we are saving the registers contents R4 to R11 on to the tasks stack memory. So, currently R4 to R11 are safe.
” str R0, [R2] \n”
(De-reference 0x20001108 and store the value in to R0)
What is 0x20001108? It’s the address of the current TCB stored in pxCurrentTCB pointer.
That is, pxCurrentTCB is nothing but a pointer of type “tskTCB”.
tskTCB is the TCB structure which is defined in tasks.c. Go Go Go check tasks.c !!! . You will find the below structure.
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task’s context
* (the task’s run time environment, including register values)
*/
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t *pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task – used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
Note Newlib support has been included by popular demand, but is not
used by the FreeRTOS maintainers themselves. FreeRTOS is not
responsible for resulting newlib operation. User must be familiar with
newlib and must provide system-wide implementations of the necessary
stubs. Be warned that (at the time of writing) the current newlib design
implements a system-wide malloc() that must be provided with locks. */
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
/* See the comments above the definition of
tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
In this structure take look in to the first member. What’s that??
It’s another pointer isn’t? Or you can say a place holder, which holds address of the stack where the last item is placed by this task and remember that every task will have its own stack space.
So, the first member just holds the address of the last item present in the stack.
After “stmdb R0!, {R4-R11} , R0″ will be pointing to the new top of the stack isn’t it ??
So, save that new top of the stack in to the first member of the TCB.
Now, the first member of the TCB is updated with new top of the stack. i.e. value in the register R0.
This value will be used to restore the stack pointer, when this task switches in later point in time.
Ok. let’s move ahead.
“ stmdb sp!, {R3, R14}
We are going to branch out after this, that’s why, save R3 and R14 for a time being.
R0 is already saved, no worries!
R1 is not required because we didn’t used it so far,
R2 was just a place holder, now it’s not required, so only save R3 and R14.
Note that here SP being used, since we are in the handler mode of the processor, SP is nothing but MSP (Main Stack Pointer).
So, we just pushed to kernels stack memory.
“ mov R0, %0 \n”
“ msr basepri, R0 \n”
/* disable the interrupts, */
“ bl vTaskSwitchContext \n”
branch out to vTaskSwitchContext.
vTaskSwitchContext is defined in tasks.c , which selects the next potential task based on priority .
So, the below figure explains what happens when call is made to vTaskSwitchContext.
- Initially the global pointer pxCurrentTCB was holding the address of the Current TCB .i.e 0x20001108
- After that call is made to vTaskSwitchContext
- Inside vTaskSwitchContext , there is a call to taskSELECT_HIGHEST_PRIORITY_TASK()
- Which changes the pxCurrentTCB value to the address of the next potential TCB
“ mov R0, #0 \n”
” msr basepri, R0 \n”
Disables the interrupt again, who knows what happened to basepri when we branched out above */
“ ldmia sp!, {R3, R14} \n”
POP R3 , R14.
Remember that R3 = 0X20000000
” ldr R1, [R3] \n”
Dereference R3, and store the value in to R1.
“ ldr R0, [R1] \n”
Dereference 0x20000580 and store the value in to R0 .
That means, store the value of the first member of the new TCB.
As you already know, first member is nothing but top of the stack.
So, R0 = Top of the stack of new Task, which is selected for switching in.
“ ldmia R0!, {R4-R11} \n”
So, we know the top of the stack;
Let’s use that to retrieve the context of new Task.
This is the Current Stack state of the New Task which is about to switch in!
This is the Current Stack state of the New Task after “ldmia R0!, {R4-R11}”
So, context of the new task is retrieved to register R4 to R11.
“ msr PSP, R0 \n”
Now, let’s initialize the PSP with the R0 value.
“ bx R14 \n”
Return from the handler!
Before executing “bx R14”
So, the task will resume to the instruction during which it was switched out of the running state.
Note: I have omitted explaining of “ISB” instruction. Please, read from here
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/CHDEBIEG.html
Get the Full Course on FreeRTOS Porting and programming Here.
If you like this article, please consider sharing it in linkedin by clicking below.
Thanks.