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