;   ***************************************************************
;   * Copyright (C) 2015, 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.                                                    *
;   ***************************************************************
;
;   Command processor for a byte stream protocol.  Commands are received in
;   packets starting with a opcode byte and followed by data bytes defined
;   separately for each command.  A bi-directional stream of bytes is assumed,
;   with wrapper routines and macros in this module that can be customized to
;   receive from and write to the streams actually in use.  The Embed
;   multi-tasking system is required by this module.
;
/include "qq2.ins.aspic"

         extern_flags        ;declare global flag bits EXTERN

;*******************************************************************************
;
;   Configuration constants.
;
/const   name    string = "" ;unique name added to CMD<name>_xxx entry points
/const   drain_startup bool = true ;drain and discard all input bytes at startup
/const   nnop    integer = 16 ;number of NOP responses to send on startup
stacksz  equ     32 + ntsksave ;task data stack size
lbank    equ     1           ;register bank for the local state of this module
;
;   Derived constants.
;
lbankadr equ     bankadr(lbank) ;address within local state register bank

;*******************************************************************************
;
;   Global state.  All this state is assumed to be in the GBANK register
;   bank by other modules.
;
         defram  gbankadr


;*******************************************************************************
;
;   Local state.
;
         defram  lbankadr

lock_rsp res     1           ;mutex state for writing to response stream
;
;   Task data stack.  This is in its own linker section so that it can be placed
;   separately.
;
.cmd[chars name]_stack udata
cmd_stack res    stacksz     ;CMD_TASK private data stack

/if debug then
         global  lock_rsp
  /endif


.cmd[chars name] code
;*******************************************************************************
;
;   Customizable routines and macros.  This section is the low level interface
;   to the actual command and response streams.  The rest of the command
;   processor only accesses the streams thru the code in this section.
;

;***************************************
;
;   Subroutine CMD_INIT
;
;   Initialize the state managed by this module.
;
         glbsub  cmd[chars name]_init, noregs

         mutex_init rsp      ;init single-threaded lock for the response stream

         popregs savedregs   ;restore registers saved on entry to routine
         extern  cmds[chars name]_init
         gjump   cmds[chars name]_init ;init command routines module, return to caller

;***************************************
;
;   Subroutine CMD_GET8
;
;   Get the next command stream byte into REG0.
;
         glbsub  cmd[chars name]_get8
         extern  uart_get
         goto    uart_get    ;call get-byte routine and return to caller

;***************************************
;
;   Subroutine CMD_PUT8
;
;   Send the byte in REG0 out the response stream.  Nothing is done if the
;   caller is not holding the response stream lock.
;
         glbsub  cmd[chars name]_put8

         mutex_skip_lock_us rsp ;we are holding the response stream lock ?
         jump    put8_leave  ;no, ignore the request
         gcall   uart_put    ;send the byte

put8_leave unbank
         leaverest

;***************************************
;
;   Macro SKIP_CMDBYTE
;
;   Skip the next instruction if a command stream byte is immediately available.
;
skip_cmdbyte macro
         skip_flag sin
         endm

;***************************************
;
;   Subroutine CMD_LOCK_OUT
;
;   Lock the response stream for exclusive access by this task.  This routine
;   waits indefinitely until the lock is available.
;
         glbsub  cmd[chars name]_lock_out
         mutex_lock rsp
         leaverest

;***************************************
;
;   Subroutine CMD_UNLOCK_OUT
;
;   Release the lock on the response stream, if held by this task.
;
         glbsub  cmd[chars name]_unlock_out
         mutex_unlock rsp
         leaverest

;*******************************************************************************
;
;   Subroutine CMD_START
;
;   Start the command stream processing task.  This routine is called during
;   system initialization after all the modules have been individually
;   initialized.
;
         glbsub  cmd[chars name]_start, regf0 | regf1 | regf2 | regf3 | regf4

         task_create cmd_task, cmd_stack ;create command processing task

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_GET16
;
;   Get the next two bytes from the command stream as a 16 bit integer into
;   REG1:REG0.  The bytes are assumed to be sent in most to least significant
;   order.
;
         glbsub  cmd[chars name]_get16

         mcall   cmd[chars name]_get8 ;get the high byte
         movff   reg0, reg1
         mcall   cmd[chars name]_get8 ;get the low byte

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_GET24
;
;   Get the next three bytes from the command stream as a 24 bit integer into
;   REG2:REG1:REG0.  The bytes are assumed to be sent in most to least
;   significant order.
;
         glbsub  cmd[chars name]_get24

         mcall   cmd[chars name]_get8 ;get the high byte
         movff   reg0, reg2
         mcall   cmd[chars name]_get8
         movff   reg0, reg1
         mcall   cmd[chars name]_get8 ;get the low byte

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_GET32
;
;   Get the next four bytes from the command stream as a 32 bit integer into
;   REG3:REG2:REG1:REG0.  The bytes are assumed to be sent in most to least
;   significant order.
;
         glbsub  cmd[chars name]_get32

         mcall   cmd[chars name]_get8 ;get the high byte
         movff   reg0, reg3
         mcall   cmd[chars name]_get8
         movff   reg0, reg2
         mcall   cmd[chars name]_get8
         movff   reg0, reg1
         mcall   cmd[chars name]_get8 ;get the low byte

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_PUT16
;
;   Send the 16 bits in REG1:REG0 over the response stream.  The bytes are sent
;   in most to least significant order.
;
         glbsub  cmd[chars name]_put16

         pushreg reg0        ;save REG0 (contains the low byte)

         movff   reg1, reg0
         mcall   cmd[chars name]_put8
         popreg  reg0        ;restore the low byte into REG0
         mcall   cmd[chars name]_put8

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_PUT24
;
;   Send the 24 bits in REG2:REG1:REG0 over the response stream.  The bytes are
;   sent in most to least significant order.
;
         glbsub  cmd[chars name]_put24

         pushreg reg0        ;save REG0 (contains the low byte)

         movff   reg2, reg0
         mcall   cmd[chars name]_put8
         movff   reg1, reg0
         mcall   cmd[chars name]_put8
         popreg  reg0        ;restore the low byte into REG0
         mcall   cmd[chars name]_put8

         leaverest

;*******************************************************************************
;
;   Subroutine CMD_PUT32
;
;   Send the 32 bits in REG3:REG2:REG1:REG0 over the response stream.  The bytes
;   are sent in most to least significant order.
;
         glbsub  cmd[chars name]_put32

         pushreg reg0        ;save REG0 (contains the low byte)

         movff   reg3, reg0
         mcall   cmd[chars name]_put8
         movff   reg2, reg0
         mcall   cmd[chars name]_put8
         movff   reg1, reg0
         mcall   cmd[chars name]_put8
         popreg  reg0        ;restore the low byte into REG0
         mcall   cmd[chars name]_put8

         leaverest

;*******************************************************************************
;
;   Routine CMD_TASK
;
;   This routines runs in a separate task.  It receives bytes from the command
;   stream and processes them accordingly.  A task swap is performed explicitly
;   when waiting on external conditions.  Most task swaps are done implicitly
;   from CMD_GET8 and CMD_PUT8.
;
cmd_task unbank              ;task initial start point
;
;   Drain the input FIFO.  Startup glitches may have looked like valid data.
;   This is only done if DRAIN_STARTUP is set to TRUE, which is a configuration
;   constant defined at the start of this module.
;
/if drain_startup then

loop_drain unbank
         dbankif gbankadr
         skip_cmdbyte        ;a input byte is immediately available ?
         jump    done_drain  ;no, all buffered input bytes have been drained
         mcall   cmd[chars name]_get8 ;read the buffered input byte
         jump    loop_drain  ;back to check the buffer again
done_drain dbankis gbankadr  ;input buffer has been completely drained

  /endif
;
;   Send a bunch of NOP responses.  This is to ensure that the host is in sync
;   with our reponse stream.  The number of NOP responses to send is set by the
;   configuration constant NNOP at the top of this module.  This code is removed
;   completely when NNOP is 0 or less.
;
/if [> nnop 0] then          ;configured to send at least one NOP ?
         mcall   cmd[chars name]_lock_out ;get exclusive lock on the response stream
         loadk8  reg1, [v nnop] ;init number of NOPs left to send
         loadk8  reg0, rsp_nop ;get the opcode to send
loop_nop unbank
         mcall   cmd[chars name]_put8 ;send this NOP response
         decfsz  reg1        ;count one less NOP left to do
         jump    loop_nop    ;back to send next NOP
         mcall   cmd[chars name]_unlock_out ;release lock on the response stream

         gcall   task_yield  ;give other tasks a chance to run
  /endif
;
;   Send a initial FWINFO response.  This calls the FWINFO command routine
;   directly, which may trash all the REGn general registers.
;
         gcall   cm[chars name]_fwinfo ;call FINFO command routine directly
;
;   Get the next command and process it.  The call and data stacks are empty.
;
cmd_next unbank
         mcall   cmd[chars name]_get8 ;get the opcode byte into REG0
         ;
         ;   Push the address of CMD_DONE onto the call stack.  This allows
         ;   command routines to be implemented as subroutines.
         ;
         push                ;create new call stack level
         movlw   low cmd[chars name]_done ;set the new entry to address of CMD_DONE
         movwf   tosl
         movlw   high cmd[chars name]_done
         movwf   tosh
         movlw   upper cmd[chars name]_done
         movwf   tosu

         dispatch cmd_table  ;jump to address for this command in dispatch table
;
;   Execution ends up here after done processing each command or if the command
;   opcode was invalid.
;
cmd[chars name]_done unbank
         clrf    stkptr      ;reset the call stack to empty
         stack_set cmd_stack ;reset the data stack to empty
         mcall   cmd[chars name]_unlock_out ;make sure we are not holding response stream lock
         jump    cmd_next    ;back to get and process the next command

////////////////////////////////////////////////////////////////////////////////
//
//   Macro CMD_ENTRY n, command
//
//   Defines one dispatch table entry.  N is the 0-255 command opcode of this
//   entry.  COMMAND defines the name of the external routine that executes the
//   command.  The actual name of the external routine is CM<name>_<command>.
//   Dispatch table entries must be defined in ascending opcode order.
//
/macro cmd_entry
         extern  cm[chars name]_[arg 2]
         dsp_entry [arg 1], cm[chars name]_[arg 2]
  /endmac

;*******************************************************************************
;
;   Commands dispatch table.
;
.cmd_table code_pack
         dsp_start cmd_table
         dsp_entry 0, cmd[chars name]_done ;NOP

         cmd_entry 1, ping   ;send PONG
         cmd_entry 2, fwinfo ;send FWINFO response

         dsp_end cmd_table

         end