;   ***************************************************************
;   * 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