; ***************************************************************
; * Copyright (C) 2004, Embed Inc (http://www.embedinc.com) *
; * *
; * Permission to copy this file is granted as long as this *
; * copyright notice is included in its entirety at the *
; * beginning of the file, whether the file is copied in whole *
; * or in part and regardless of whether other information is *
; * added to the copy. *
; * *
; * The contents of this file may be used in any way, *
; * commercial or otherwise. This file is provided "as is", *
; * and Embed Inc makes no claims of suitability for a *
; * particular purpose nor assumes any liability resulting from *
; * its use. *
; ***************************************************************
;
; Task management.
;
; The routines in this module implement a cooperative multi-tasking
; system. For context switching speed, each task has its own stack.
;
; The task scheduler is of the "round robin" type. Each call to
; TASK_YIELD switches the context to the next task in the list
; in a circular fashion.
;
/include "qq1.ins.dspic"
;
;***********************************************************************
;
; Configuration constants.
;
.equiv maxtasks, 4 ;maximum number of tasks supported (limit 0-31)
.equiv tsksize, 2 ;size of each task table entry
;
; Derived constants.
;
.equiv maxtask, maxtasks - 1 ;maximum possible 0-N task number
.set nsave, 0 ;init number of registers saved accross TASK_YIELD
.irp ii, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ;once for each reg
.if tsksave & (1 << \ii) ;this register will be saved ?
.set nsave, nsave + 1 ;count one more register to save
.endif
.endr
.equiv istacksz, 4 + (2 * nsave) + 4 ;size of data on task initial stack
;
;***********************************************************************
;
; Variables.
;
;*******************
;
; Global state.
;
;
;*******************
;
; Local state.
;
.bss tasks tsksize * maxtasks, 2 ;saved stack pointer for each tasks
.bss after_p 0, 2 ;first address after tasks table
.bss task_p 2, 2 ;adr of task table entry for current task
.bss last_p 2, 2 ;pointer to last used task table entry
.text
;
;***********************************************************************
;
; Subroutine TASK_INIT
;
; Initialize the hardware and software state managed by this module.
;
glbsub task_init, regf0
.if debug ;make place for ICD2 breakpoint if debugging
nop
nop
nop
.endif
mov #tasks, w0
mov w0, task_p ;init pointer to table entry for current task
mov w0, last_p ;init pointer to last valid table entry
leaverest
;
;***********************************************************************
;
; Subroutine TASK_NEW
;
; Create a new task. Nothing is done if the maximum number of tasks
; already exist. The current values of the registers indicated by
; TSKSAVE will be the intial values of those registers in the task.
; These registers can therefore be used to pass parameters to the
; task.
;
; The call parameters are:
;
; W14 - Stack start address for the new task.
;
; The new task start address will be immediately after the call
; to TASK_NEW. TASK_NEW will return 2 instruction words (4 addresses)
; after the call. This provides an opportunity to put a GOTO
; immediately after the TASK_NEW call. The first instruction
; executed by the new task will be the GOTO, whereas TASK_NEW
; will return to immediately after the GOTO.
;
; The existing task will continue to run until TASK_YIELD is called.
;
glbsub task_new, regf0
;
; Update LAST_P to the next unused task table entry. Abort if
; LAST_P is already at the last allocated task table entry.
;
mov last_p, w0 ;get pointer to current last table entry
add #tsksize, w0 ;make pointer to next table entry
push w1 ;temp save W1
mov #after_p, w1 ;get first address past end of table
cp w0, w1 ;compare new table entry address to past table adr
pop w1 ;restore w1
bra geu, tkn_abort ;task table is already full ?
mov w0, last_p ;update pointer to last used task table entry
;
; Initialize the stack of the new task. The stack will be set up so
; that to restart the task, the registers indicated by TSKSAVE are
; restored, then a RETURN is performed. This means the task start address
; is pushed first, then the TSKSAVE registers, then any application
; specific data.
;
; Our stack currently looks like this:
;
; [W15 - 6] --> return address low word
; [W15 - 4] --> return address high word
; [W15 - 2] --> saved W0
; [W15] --> empty
;
;
; Push the task start address onto the task stack.
;
mov [w15 - 6], w0 ;get task start address low word
mov w0, [w14++] ;push it onto new task stack
mov [w15 - 4], w0 ;get task start address high word
mov w0, [w14++] ;push it onto new task stack
;
; Push the registers saved accross task switches onto the stack
; of the new task.
;
mov [w15 - 2], w0 ;restore original W0 value
.irp ii, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ;once for each reg
.if tsksave & (1 << \ii) ;this register will be saved ?
mov w\ii, [w14++] ;push this register onto task stack
.endif
.endr
;
; Push additional data that is saved per task in this application.
;
;
; Write the task table entry for the new task.
;
mov last_p, w0 ;point W0 to task table entry for this task
mov w14, [w0] ;save top of stack address in task table
sub w14, #istacksz, w14 ;restore original W14
;
; Return to 4 addresses after the call.
;
tkn_abort: ;skip to here if already at max tasks
mov [w15 - 6], w0 ;get return address low word
add #4, w0 ;make new return address low word
mov w0, [w15 - 6] ;replace it on the stack
mov [w15 - 4], w0 ;get return address high word
addc #0, w0 ;make new return address high word
mov w0, [w15 - 4] ;replace it on the stack
leaverest
;
;***********************************************************************
;
; Subroutine TASK_YIELD
;
; End the time slice of the current task, run all other tasks for a
; time slice, then resume the caller's task and return. Only those
; registers indicated by TSKSAVE will be preserved.
;
glbsub task_yield
;
; Save the registers indicated by TSKSAVE and other per task saved
; state onto the stack.
;
.irp ii, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ;once for each reg
.if tsksave & (1 << \ii) ;this register will be saved ?
push w\ii ;push this register onto the stack
.endif
.endr
;
; Push data that is saved per task in this application.
;
;
; Save the stack pointer in the task table entry for this task.
;
mov task_p, w0 ;point W0 to table entry for the current task
mov w15, [w0] ;write the stack pointer into the table entry
;
; Advance to the task pointer to the next task. W0 is the task pointer
; for the current task.
;
mov last_p, w1 ;get pointer to last entry in the table
cp w0, w1 ;compare current pointer to last pointer
bra geu, ylast ;currently at the last task ?
;
; Not at last task in table, advance to next.
;
add #tsksize, w0 ;make pointer to next table entry
bra ynew ;now at next task
;
; At last task in table, wrap back to first.
;
ylast:
mov #tasks, w0 ;point to first task in the table
ynew: ;W0 points to task table entry of the new task
mov w0, task_p ;update pointer to task table entry for current task
mov [w0], w15 ;switch to the stack of the new task
;
; Restore application data that is saved per task in this application.
;
;
; Restore the registers indicated by TSKSAVE and return from the
; TASK_YIELD call made by this task.
;
.irp ii, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ;once for each reg
.if tsksave & (1 << \ii) ;this register will be saved ?
pop w\ii ;pop this register from the stack
.endif
.endr
return ;return from TASK_YIELD of the resumed task
.end