We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
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 ; } } }
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) { } }
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.