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 Reply Children
  • Do U have ne plans 4 a '51 based version plz ???

  • 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

  • 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

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

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

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

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