Having fun with STR9 context switch for the RealView compiler

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 sourceforge.net/.../, 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)
    {
    }
}

Parents
  • 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
    

Reply
  • 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
    

Children
More questions in this forum