This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

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
  •    // 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
    }
    

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

Children
  • #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

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