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