Having fun with STR9 context switch for the RealView compilerNext Thread | Thread List | Previous Thread Start a Thread | Settings | Details | Message |
|---|
Read-Only Author Tamir Michael Posted 16-Jun-2008 09:13 GMT Toolset ARM |  Having fun with STR9 context switch for the RealView compiler Tamir Michael Hello all, If you are interested, I am working on a new personal project - an ARM9 scheduler ("Sand Storm", but the name is still volatile. It will inherit a lot from http://sourceforge.net/projects/eos-xc167-rtos/, however it will of course run on STR7/9 variants instead). It is still a rudimentary implementation, which I made for demonstration purposes only. we begin with this declaration:
typedef struct
{
int32u context[17] ; // buffer for all processor registers
int8u id ;
task_state status ;
int8u priority ; // task priority
} t_tcb ;
(tcb = task control block). assuming 3 tasks are run, the following definitions are made:
t_tcb g_task1tcb, g_task2tcb, g_task3tcb,
*g_tasks_tcb[] = {&g_task1tcb, &g_task2tcb, &g_task3tcb} ;
and
#define TASK_STACK_SIZE_NORMAL 0x100
int32u g_current_task_index = 0, g_current_task_index_tcb_index = 0, g_next_task_ptr = 0, g_current_task_ptr = 0,
lp_task1_stack[TASK_STACK_SIZE_NORMAL],
lp_task2_stack[TASK_STACK_SIZE_NORMAL],
lp_task3_stack[TASK_STACK_SIZE_NORMAL] ;
each task needs to be configured prior to running, like this:
void tcb_setup(t_tcb *const ap_tcb, task_ptr ap_task, int32u *ap_stack, int16u a_stack_size)
{
int32u *l_tcb_word = &ap_tcb->context[16], *lp_stack = ap_stack, l_value ;
// IRQ ISR return address (ending of tcb)
// the IRQ ISR must (according to the ARM architecture) decrement 4 from each
// return address. so the return address of each IRQ ISR must be incremented by 4.
*(l_tcb_word--) = (int32u)ap_task + 4 ;
// SPSR
*(l_tcb_word--) = (int32u)0x50 ;
// R14 - internal task return address
*(l_tcb_word--) = (int32u)ap_task ;
// R13 - stack top
*(l_tcb_word--) = (int32u)ap_stack ;
// R12
*(l_tcb_word--) = (int32u)0x12012012 ;
// R11
*(l_tcb_word--) = (int32u)0x11011011 ;
// R10
*(l_tcb_word--) = (int32u)0x10101010 ;
// R9
*(l_tcb_word--) = (int32u)0x99999999 ;
// R8
*(l_tcb_word--) = (int32u)0x88888888 ;
// R7
*(l_tcb_word--) = (int32u)0x77777777 ;
// R6
*(l_tcb_word--) = (int32u)0x66666666 ;
// R5
*(l_tcb_word--) = (int32u)0x55555555 ;
// R4
*(l_tcb_word--) = (int32u)0x44444444 ;
// R3
*(l_tcb_word--) = (int32u)0x33333333 ;
// R2
*(l_tcb_word--) = (int32u)0x22222222 ;
// R1
*(l_tcb_word--) = (int32u)0x11111111 ;
// R0 (beginning of tcb)
*(l_tcb_word--) = (int32u)0x00000000 ;
// fill user provided stack with predicted, handy from debugging
l_value = 0x1111 ;
while (a_stack_size-- > 0)
{
*(lp_stack++) = l_value ;
l_value <<= 1 ;
if (l_value >= 0x1111000)
{
l_value = 0x1111 ;
}
}
}
and
tcb_setup(&g_task1tcb, &task1, lp_task1_stack, TASK_STACK_SIZE_NORMAL) ;
tcb_setup(&g_task2tcb, &task2, lp_task2_stack, TASK_STACK_SIZE_NORMAL) ;
tcb_setup(&g_task3tcb, &task3, lp_task3_stack, TASK_STACK_SIZE_NORMAL) ;
where the task functions are defined the following way: (only task1 is demonstrated):
void task1(void)
{
while(1)
{
}
}
| | Read-Only Author Tamir Michael Posted 16-Jun-2008 09:18 GMT Toolset ARM |  Context switch - part 1 Tamir Michael The actual context switch occurs in ISR of timer 0:
__asm __irq void TIM0_IRQHandler(void)
{
EXTERN g_tasks_tcb
EXTERN g_current_task_index
EXTERN g_next_task_ptr
EXTERN g_current_task_ptr
EXTERN vic0
EXTERN tim0
EXTERN g_scheduler_started
// before switching the processor context, all necessary computations will be
// carried out so that before the actual switch is done the values of these
// registers can be restored to their original values (R0 - R12 are shared among
// all processor modes)
// Note that for the actual switch R13 is used, because the IRQ mode has its
// own copy of it and there is no dander of corrupting user mode data.
// manipulating R0 - R12 for this purpose would corrupt vital task state data.
STMDB R13!, {R0 - R3} // save the value of the registers needed for context switch preparation
// if the scheduler is disabled, return without taking any action
LDR R2, =g_scheduler_started
LDR R2, [R2]
CMP R2, #0
BEQ scheduler_disabled
// ***********************************************************
// acquire a pointer to the tcb block of the task that is left
// ***********************************************************
LDR R1, =g_current_task_index // address of index to tcb
LDR R3, [R1] // R3 is the index
// point to the tcb block representing the current task
LDR R0, =g_tasks_tcb // base address of tcb container
LDR R1, [R0, R3, LSL#2] // R1 = R0 + R3*4 (multiply by 4 becasue each address in the container is 32 bits long = 4 bytes)
LDR R2, =g_current_task_ptr // load the address of the destination variable
STR R1, [R2] // and store there the contents of R1
// ************************************************************
// select the next task to be executed
// ************************************************************
ADD R3, #1 // round robin scheduling - try to select the next task
CMP R3, #MAX_TASKS // compare with the maximum allowed task id
MOVEQ R3, #0 // reselect the first task if all tasks got a chance to run
LDR R2, =g_current_task_index // point to the address of the current task index
STR R3, [R2] // store the index of the next task
// ************************************************************
// point to the tcb block representing the next task
// ************************************************************
LDR R2, =g_next_task_ptr // point to the address of the next task index
LDR R1, [R0, R3, LSL#2] // R1 = R0 + R3*4 (multiply by 4 becasue each address in the container is 32 bits long)
STR R1, [R2] // store the address at the variable
// clearing and acknowledging the interrupt is done before the original values
// of the R0-R3 are restored. doing this here is also possible because interrupts
// are not reentrant, by default.
// acknowledge the interrupt. TIM0 is services by VIC0.
LDR R0, =vic0
LDR R0, [R0] // the address of interrupt controller 0
STR R0, [R0, #48] // store dummy value in the vector address register
// clear TIM0 interrupt flag
LDR R0, =tim0
LDR R0, [R0]
ADD R0, #28 // point to the SR field
MOV R1, #0x4000 // this is the required mask
BIC R1, R0, R1 // AND NOT
STR R1, [R0] // store at the right location
// ************************************************************
// before the context is switched, the original value of the
// registers that have been used so far must be restored
// so that any changes made are undone
// R13 is used as IRQ mode has its own copy so it cannot
// interrupt with user mode register values
// ************************************************************
LDMIA R13!, {R0 - R3}
// ************************************************************
// save the processor context
// ************************************************************
LDR R13, =g_current_task_ptr
LDR R13, [R13] // R13 now points to the current task tcb block
STMIA R13, {R0 - LR}^ // save R0 - R14 from the beginning of the block (note that
// each tcb starts with the task's processor register values)
NOP // the RealView compiler compels this
ADD R13, #56 // move further in the tcb
MRS R0, SPSR // copy SPSR into R0
STMIB R13, {R0, LR} // save SPSR and the return address. note: the return address
// saved here is the return address of the IRQ - hence, where in the task the
// processor should resume operation. the above save LR (user mode LR - LR^) represents
// the returns address local to the task itself - for example, if a function inside
// the task was preempted, the return address from it shall be stored there.
// ************************************************************
// restore the processor context
| | Read-Only Author Tamir Michael Posted 16-Jun-2008 09:19 GMT Toolset ARM |  RE: Context switch - part 1 Tamir Michael here is the code snippet to complete the above:
// ************************************************************
LDR R13, =g_next_task_ptr
LDR R13, [R13] // R13 now points to the next task tcb block
// now, the SPSR must come first in order not to spoil the user mode register R0.
// note that R0 (or any other register) is required in order to handle the
// save/restore of SPSR.
ADD R13, #60 // point to the point where R14 and SPSR meet (see tcb_setup)
LDMIA R13, {R0, LR} // load SPSR and the return address (notice the IA variant of LDM)
MSR SPSR_cxsf, R0
LDMDB R13, {R0 - LR}^ // load user mode registers (notice the DB variant of LDM)
// notice also that the user mode's R13 is updated, too, which is the key to a fast
// context switch: unlike in the C166 architecture, the contents of the stack do
// not have to be copied to a specific region in the memory which is dedicated
// to stack memory. the ARM allows to set R13 as a pointer, to different places
// in memory. also note that unlike in the C166, a simple
// "push into system stack/select next task/copy task stack to system/pop system stack"
// scheme will not work here, because of the processor modes: once in an ISR, the
// ARM9 is in IRQ mode. pushing into the stack will update the IRQ stack, not the
// user mode stack.
NOP // the RealView compiler compels this
// ARM architecture requires that 4 is subtracted from PC upon return from an
// IRQ handler
SUBS PC, LR, #4
scheduler_disabled
// acknowledge the interrupt. TIM0 is services by VIC0.
LDR R0, =vic0
LDR R0, [R0] // the address of interrupt controller 0
STR R0, [R0, #48] // store dummy value in the vector address register
// clear TIM0 interrupt flag
LDR R0, =tim0
LDR R0, [R0]
ADD R0, #28 // point to the SR field
MOV R1, #0x4000 // this is the required mask
BIC R1, R0, R1 // AND NOT
STR R1, [R0] // store at the right location
LDMIA R13!, {R0 - R3}
BX LR
}
| | Read-Only Author Tamir Michael Posted 16-Jun-2008 09:23 GMT Toolset ARM |  some missing parts Tamir Michael the following declarations are still missing above:
int32u *vic0 = (int32u*)VIC0 ;
int32u *tim0 = (int32u*)TIM0 ;
needed in order to acknowledge and clear the interrupt, and the indication whether the scheduler is running or not:
int8u g_scheduler_started ;
| | Read-Only Author Tamir Michael Posted 16-Jun-2008 09:39 GMT Toolset ARM |  RE: some missing parts Tamir Michael and of course
typedef void (*task_ptr)(void) ;
| | Read-Only Author Facram Pooltern Posted 16-Jun-2008 09:55 GMT Toolset ARM |  RE: some missing parts Facram Pooltern Do U have ne plans 4 a '51 based version plz ??? | | Read-Only Author Tamir Michael Posted 16-Jun-2008 09:57 GMT Toolset ARM |  RE: some missing parts Tamir Michael Hello Facram, I regret to inform you that I do not have such plans at the moment. I will finish my STR9 implementation first, and then - who knows. Kind regards, Tamir | | Read-Only Author Tamir Michael Posted 4-Sep-2008 07:20 GMT Toolset ARM |  An important notice Tamir Michael Hello all, I would like to express my deepest apologies for an apparent bug in the above posted code. I will post a correction in a short while (I got it fixed, I hope, and now I am testing and working on other dependencies). The problem with the above code is that R13 (ARM's stack pointer) is not restored to point to the IRQ stack once the context switch is complete. In that case, the next context switch's R13 will point into user task/stack data, overwriting its contents. I found this issue while working on a series of modifications that exposed the problem in the form of data abort exception due to corrupt return address. The correction will include these modifications as well as the bug fix. If you want to go it alone, you should: * Save the base address of the IRQ stack in your startup file. * Put that variable in a non-zero section to prevent it from being set to zero by _main. Kind regards, Tamir Michael | | Read-Only Author Tamir Michael Posted 4-Sep-2008 07:23 GMT Toolset ARM |  RE: An important notice Tamir Michael Forgotten: * Point R13 to the base address of the IRQ stack once the context switch is done. All this is correct assuming no nested IRQ interrupts are allowed. | | Read-Only Author Tamir Michael Posted 11-Sep-2008 09:06 GMT Toolset ARM |  Fix part 1 Tamir Michael As promised, here is the fix (see next post for the rest of the code and details):
__asm __irq void TIM0_IRQHandler(void)
{
PRESERVE8
IMPORT g_tasks_tcb
IMPORT g_current_task_index
IMPORT g_startup_task_index
IMPORT g_next_task_ptr
IMPORT g_current_task_ptr
IMPORT vic0
IMPORT tim0
IMPORT g_software_interrupt
IMPORT VIC_SWITCmd
IMPORT scheduler_select_next_task
IMPORT g_irq_stack_base
// before switching the processor context, all necessary computations will be
// carried out so that before the actual switch is done the values of these
// registers can be restored to their original values (R0 - R12 are shared among
// all processor modes)
// Note that for the actual switch R13 is used, because the IRQ mode has its
// own copy of it and there is no danger of corrupting user mode data.
// manipulating R0 - R12 for this purpose would corrupt vital task state data.
// save the value of the registers needed for context switch preparation
// this occurs onto the IRQ stack
STMDB R13!, {R0 - R3}
// ***********************************************************
// acquire a pointer to the tcb block of the task that is left
// ***********************************************************
LDR R1, =g_current_task_index // address of index to tcb
LDR R3, [R1] // R3 is the index
// if the scheduler is just started, there is no task running hence saving the processor's
// context is skipped - only the next stage of task selection and restoration is carried out
CMP R3, #0xFF
BEQ context_not_saved
// but if the scheduler is not starting up, first save the context, then select the next
// task to run
// point to the tcb block representing the current task
LDR R0, =g_tasks_tcb // base address of tcb container
LDR R1, [R0, R3, LSL#2] // R1 = R0 + R3*4 (multiply by 4 becasue each address in the container is 32 bits long = 4 bytes)
LDR R2, =g_current_task_ptr // load the address of the destination variable
STR R1, [R2] // and store there the contents of R1
// prepare to save context. Restore affected registers. remember: these registers
// are shared among IRQ mode and user mode
LDMIA R13!, {R0 - R3}
// ************************************************************
// save the processor context
// ************************************************************
LDR R13, =g_current_task_ptr
LDR R13, [R13] // R13 now points to the current task tcb block
STMIA R13, {R0 - LR}^ // save R0 - R14 from the beginning of the block (note that
// each tcb starts with the task's processor register values)
NOP // the RealView compiler compels this
ADD R13, #56 // move further in the tcb
MRS R0, SPSR // copy SPSR into R0
STMIB R13, {R0, LR} // save SPSR and the return address. note: the return address
// saved here is the return address of the IRQ - hence, where in the task the
// processor should resume operation. the above save LR (user mode LR - LR^) represents
// the returns address local to the task itself - for example, if a function inside
// the task was preempted, the return address from it shall be stored there.
// ************************************************************
// select the next task to be executed
// ************************************************************
// restore R13 to point to the IRQ stack before the call is done, because R13 points
// into a user stack. if the function's assembly pushes registers onto the
// stack, user data will get corrupted. notice how 'g_irq_stack_base' is initialized
// at the startup file, and the scatter file of the linker prevents it from being
// set to 0 by the program loader.
LDR R13, =g_irq_stack_base
LDR R13, [R13]
| | Read-Only Author Tamir Michael Posted 11-Sep-2008 09:07 GMT Toolset ARM |  Fix part 2 Tamir Michael
// this call sets the value of 'g_current_task_index', hence selected the next task to run
LDR R2, =scheduler_select_next_task
BLX R2
// save the new value required to place the pointer at the right offset to the next tcb.
LDR R2, =g_current_task_index
// save the index of the next task to run in R3 (see the label 'normal_context_switch'
// to see why and where it is used)
LDR R3, [R2]
B normal_context_switch
context_not_saved
// the context switch ISR was started with a backup of the registers R0-R3.
// the program flow so far did not pop these registers out of the IRQ stack.
// prevent a stack overflow. note that R3 is needed for the selection of the right tcb,
// so restoring the registers must take place first
LDMIA R13!, {R0 - R3}
LDR R1, =g_current_task_index // address of index to tcb
LDR R2, =g_startup_task_index
LDR R3, [R2] // load the index of the first task to run in R3
// save the selected startup task to the current running task to the address
// of 'g_current_task_index'. R1 points to 'g_current_task_index'.
STR R3, [R1]
normal_context_switch
// ************************************************************
// point to the tcb block representing the next task
// ************************************************************
// R3 contains the index of the next task to execute
LDR R0, =g_tasks_tcb // base address of tcb container
LDR R2, =g_next_task_ptr // point to the address of the next task index
LDR R1, [R0, R3, LSL#2] // R1 = R0 + R3*4 (multiply by 4 becasue each address in the container is 32 bits long)
STR R1, [R2] // store the address at the variable
// acknowledge the interrupt. TIM0 is serviced by VIC0.
LDR R0, =vic0
LDR R0, [R0] // the address of interrupt controller 0
STR R0, [R0, #48] // store dummy value in the vector address register
// clear TIM0 interrupt flag
LDR R0, =tim0
LDR R0, [R0]
ADD R0, #28 // point to the SR field
MOV R1, #0x4000 // this is the required mask
BIC R1, R0, R1 // AND NOT
STR R1, [R0] // store at the right location
// the 'g_software_interrupt' flag indicates whether or not a context switch was forced by
// software. if so, the flag must be cleared and 'VIC_SWITCmd' must be called again
// (the previous call happened when the context switch was forced
// in 'scheduler_reschedule') in order to disable the software interrupt.
LDR R0, =g_software_interrupt
LDR R1, [R0]
CMP R1, #0
BEQ hardware_interrupt
// software forced context switch
// clear 'g_software_interrupt' flag
MOV R1, #0
STR R1, [R0]
// prepare parameters and call 'VIC_SWITCmd' to clear the software interrupt
MOV R0, #4
MOV R1, #0
BL VIC_SWITCmd
hardware_interrupt
// ************************************************************
// restore the processor context
// ************************************************************
LDR R13, =g_next_task_ptr
LDR R13, [R13] // R13 now points to the next task tcb block
// now, the SPSR must come first in order not to spoil the user mode register R0.
// note that R0 (or any other register) is required in order to handle the
// save/restore of SPSR.
ADD R13, #60 // point to the point where R14 and SPSR meet (see tcb_setup)
LDMIA R13, {R0, LR} // load SPSR and the return address (notice the IA variant of LDM)
MSR SPSR_cxsf, R0
LDMDB R13, {R0 - LR}^ // load user mode registers (notice the DB variant of LDM)
// notice also that the user mode's R13 is updated, too, which is the key to a fast
// context switch: unlike in the C166 architecture, the contents of the stack do
// not have to be copied to a specific region in the memory which is dedicated
// to stack memory. the ARM allows to set R13 as a pointer, to different places
// in memory. also note that unlike in the C166, a simple
// "push into system stack/select next task/copy task stack to system/pop system stack"
// scheme will not work here, because of the processor modes: once in an ISR, the
// ARM9 is in IRQ mode. pushing into the stack will update the IRQ stack, not the
// user mode stack.
NOP // the RealView compiler compels this
//*********************************************************************/
// restore R13 to point to the IRQ stack.
// note: it is assumed that nested interrupts are not allowed, otherwise
// this would certainly be incorrect (because the IRQ stack might contain
// function call parameters or backuped registers etc.).
//*********************************************************************/
LDR R13, =g_irq_stack_base
LDR R13, [R13]
// ARM architecture requires that 4 is subtracted from PC upon return from an
// IRQ handler
SUBS PC, LR, #4
}
| | Read-Only Author Tamir Michael Posted 11-Sep-2008 09:12 GMT Toolset ARM |  Fix part 3 Tamir Michael
#pragma arm section zidata = "non_init_2"
__attribute__ ((zero_init)) int32u g_irq_stack_base ;
#pragma arm section zidata
The variable 'g_irq_stack_base' is placed in a section that is not initialized to zero upon startup. That is required because after the startup file sets the variable (during stack setup),
;***********************************************************
; required for restoration of IRQ stack after context switch
IMPORT g_irq_stack_base
LDR R1, =g_irq_stack_base
STR R0, [R1]
;***********************************************************
that branch to '__main' will reset it. Notice that this code is developed to be build with a RealView compiler. I have not made a GNU variant yet. Greetings, Tamir | | Read-Only Author Tamir Michael Posted 11-Sep-2008 09:15 GMT Toolset ARM |  Fix part 4 Tamir Michael Please note that 'scheduler_select_next_task' is a function that selects the next task to be run. I will not post it here as it has no value for this thread. The simplified logic at the original post can be used instead. | |
Next Thread | Thread List | Previous Thread Start a Thread | Settings |