/****************************************************************************
**
**  Name:  LQUEUE.C
**
**  Description:
**      Interface to firmware for loading code.
**
**
**  Copyright (C) 1993 Microtek International.  All rights reserved.
**
*****************************************************************************/

                       /****************************
                        *                          *
                        *       INCLUDE FILES      *
                        *                          *
                        ****************************/
#include <time.h>

#ifndef _BASEWIND_
#include "basewind.h"
#endif

#ifndef __LDR__
#include "ldr.h"
#endif

#ifndef __ERR__
#include "err.h"
#endif

#ifndef _LQUEUE_
#include "lqueue.h"
#endif

#ifndef _SDPROBE_
#include "sdprobe.h"
#endif

#ifndef _SSHARED_
#include "sshared.h"
#endif

#ifndef _TBIRDMEM_
#include "tbirdmem.h"
#endif

#ifndef _PVTASK_
#include "pvtask.h"
#endif

                       /****************************
                        *                          *
                        *     LOCAL DEFINITIONS    *
                        *                          *
                        ****************************/
typedef struct {
   enum { LOAD_EMPTY, LOAD_FILLING,      LOAD_FULL       } inputState;
   enum { LOAD_IDLE,  LOAD_TRANSMITTING, LOAD_PROCESSING } outputState;
   RETCODE error;
   U16 numBytes;
} LOAD_BUFFER_STATE;

STATIC ACCESS_SIZE loadAccess = SIZE_WORD; /* write by bytes, words, longs */
STATIC BOOLEAN loadAbortFlag;
STATIC U16 currentLoadBuffer;
STATIC U8 *currentLoadBufPtr;
STATIC U16 *currentCountPtr;
STATIC LOAD_BUFFER_STATE loadBufferState[NUM_LOAD];
STATIC U16 loadBufferTransmitting;
STATIC BOOLEAN sdsBusy = FALSE;
STATIC DESCRIPTOR loadDesc[NUM_LOAD];
STATIC U32 loadSerialNumber[NUM_LOAD];

/* Index of first buffer reporting error */
U16 indexBufferError;

#define SD_BYTE_COUNT   0xF0
#define SD_REPEAT_COUNT 0xF4
#define SD_SET_ADDRESS  0xF8
#define SD_SET_SPACE    0xFE
#define SD_EOF          0xFF
#define LOADBUF_BYTES_USED   \
   (currentLoadBufPtr - SdnData(SDN_LOAD_DATA+currentLoadBuffer))
#define LOADBUF_SPACE_LEFT   \
   (SIZE_LOAD - LOADBUF_BYTES_USED - 1)   /* -1 allows space for SD_EOF */

#define SECOND (CLK_TCK)          /* from time.h */
#define LOAD_TIMEOUT (30*SECOND)  /* max wait for box to process packet */

                       /****************************
                        *                          *
                        *    EXTERNAL VARIABLES    *
                        *                          *
                        ****************************/
extern BOOL runMode;   /* BATCH or INTERACTIVE */

                       /****************************
                        *                          *
                        *     LOCAL PROTOTYPES     *
                        *                          *
                        ****************************/
RETCODE PRIVATE LdrQueueService(VOID);
RETCODE PRIVATE LdrQueueNeedBytes(U16 bytesNeeded, U32 currentLoadAddr);
RETCODE PRIVATE LdrQueueFindEmptyBuffer(U16 *index);
VOID EXPORT LoadResultCallback(DESCRIPTOR desc);
VOID EXPORT LoadAsyncCallback(VOID);

                       /****************************
                        *                          *
                        *     EXECUTABLE CODE      *
                        *                          *
                        ****************************/

/****************************************************************************
**
**  LdrQueueCheckLoadAbort
**
**  description:
**    If user abort requested, send load abort command to box.  Box will
**    clean up and return ER_LOAD_ABORTED for all packets in progress.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueCheckLoadAbort(VOID) {
   BOOLEAN abortFromEsc;
   RETCODE err;
   if ((err = TskCheckAbort(&abortFromEsc)) != GOOD) return(err);
   if (abortFromEsc) {
      LOAD_COMMAND command = LOAD_ABORT;
      loadAbortFlag = TRUE;
      LdrProgressDone(ER_LDR_ABORT);
      return(SdnWriteMember(SDN_LOAD_COMMAND, (U8*)&command, GOOD));
   }
   if ((err = LdrProgressInc()) != GOOD) {
      LOAD_COMMAND command = LOAD_ABORT;
      loadAbortFlag = TRUE;
      LdrProgressDone(ER_LDR_ABORT);
      return(SdnWriteMember(SDN_LOAD_COMMAND, (U8*)&command, GOOD));
   }
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueGetLoadAbort
**
**  description:
**    Return whether the load has been aborted already.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueGetLoadAbort(BOOLEAN *aborted) {
   *aborted = loadAbortFlag;
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueInit
**
**  description:
**    Initializes static variables that control enqueuing load commands
**    en route to the box.  Called once before loading begins.  Must call
**    LdrQueueWait() to clean up when loading complete or if an error occurs.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueInit(VOID) {
   RETCODE err;
   LOAD_COMMAND command;
   LOOP_VAR i;
   ADDR_SPACE space;
   BOOLEAN verify, timedOut;

   loadAbortFlag = FALSE;
   /* Set shared data members that are constant throughout load */
   if((err = MemGetVerifyWrites(&verify))!=GOOD) return(err);
   space = (LdrSpaceMode() == SUPERVISOR_MODE) ? SPACE_SP : SPACE_UP;

   /* Reset index of first buffer reporting error - all index < NUM_LOAD */
   indexBufferError = NUM_LOAD;
   
   if((err = SdnWriteMember(SDN_LOAD_ACCESS, (U8*)&loadAccess, GOOD))
      != GOOD) return(err);
   if((err = SdnWriteMemberIfChanged(SDN_LOAD_VERIFY, (U8*)&verify, GOOD))
      != GOOD) return(err);
   if((err = SdnWriteMemberIfChanged(SDN_LOAD_SPACE, (U8*)&space, GOOD))
      != GOOD) return(err);
   for (i=0; i < NUM_LOAD; i++) {
      if ((err = SdnRegister(SDN_LOAD_RESULT+i,(FARPROC)LoadResultCallback,
        &loadDesc[i])) != GOOD) return(err);
   }
   currentLoadBuffer = loadBufferTransmitting = 0;
   currentLoadBufPtr = SdnData(SDN_LOAD_DATA+currentLoadBuffer) + sizeof(U32);
   currentCountPtr = NULL;
   for (i=0; i<NUM_LOAD; i++) {
      loadBufferState[i].inputState = LOAD_EMPTY;
      loadBufferState[i].outputState = LOAD_IDLE;
      loadBufferState[i].error = GOOD;
      loadSerialNumber[i] = 1ul;
   }

   /* For no interdependence, start each packet with complete address record*/
   if ((err = LdrQueueSetAddressRecord(0ul, 4)) != GOOD) return(err);

   /* Initialize firmware load.  Must send LOAD_FINISH when done. */
   command = LOAD_START;
   if((err = SdnWriteCmdReadResponse(SDN_LOAD_COMMAND, (U8*)&command, GOOD,
      SDN_LOAD_COMMAND, 0, &timedOut)) != GOOD) return(err);

   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueSetAddressRecord
**
**  description:
**    Enqueues a set address record.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueSetAddressRecord(U32 addr, U8 bytes) {
   RETCODE err;
   LOOP_VAR i;
   if (bytes == 0) return(GOOD);
   if ((err = LdrQueueNeedBytes(1+bytes, addr)) != GOOD) return(err);
   *currentLoadBufPtr++ = SD_SET_ADDRESS + bytes;
   for (i=0; i<bytes; i++) *currentLoadBufPtr++ = ((U8*)&addr)[i];
   currentCountPtr = NULL;   /* Cannot append next write to last command */
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueSetAddressSpace
**
**  description:
**    Enqueues a set address space record.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueSetAddressSpace(U32 addr, ADDR_SPACE addrSpace) {
   RETCODE err;

   if ((err = LdrQueueNeedBytes(2, addr)) != GOOD) return(err);
   *currentLoadBufPtr++ = SD_SET_SPACE;
   *currentLoadBufPtr++ = (U8)addrSpace;
   currentCountPtr = NULL;   /* Cannot append next write to last command */
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueDataRecord
**
**  description:
**    Enqueues a data record.  If possible (no intervening set address or
**    repeat count commands), the data is appended to the end of the last
**    command and the byte count increased.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueDataRecord(VOID *info, LOAD_FUNC FillInBytes,
      U32 currentLoadAddr, U32 byteCount, U32 appendCount) {
   RETCODE err;

   /* Need enough space for byteCount+length of header */
   if ((err = LdrQueueNeedBytes(byteCount+appendCount, currentLoadAddr)) != GOOD)
       return(err);

   if (currentCountPtr == NULL) {
      *currentLoadBufPtr++ = SD_BYTE_COUNT + 2;  /* count always 2 bytes */
      currentCountPtr = (U16*)currentLoadBufPtr;
      currentLoadBufPtr += 2;   /* Leave space for count to be filled in */
      *currentCountPtr = 0;
   }
   if ((err = (*FillInBytes)(info, currentLoadBufPtr, byteCount))
      != GOOD) return(err);

   *currentCountPtr += (U16)byteCount;
   currentLoadBufPtr += (U16)byteCount;

   if(appendCount)
   {
      memset(currentLoadBufPtr,0,(U16)appendCount);
      *currentCountPtr += (U16)appendCount;
      currentLoadBufPtr += (U16)appendCount;
   }
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueNeedBytes
**
**  description:
**    Checks for the requested number of bytes in the current load buffer.
**    If too little space available, the queue is flushed.
**
*****************************************************************************/
RETCODE PRIVATE LdrQueueNeedBytes(U16 bytesNeeded, U32 currentLoadAddr) {
   RETCODE err;
   if ((err = LdrQueueService()) != GOOD) return(err);
   if (LOADBUF_SPACE_LEFT >= bytesNeeded) return(GOOD);
   if ((err = LdrQueueFlush(currentLoadAddr)) != GOOD) return(err);
   if (LOADBUF_SPACE_LEFT < bytesNeeded) return(ER_CODEBUF_SIZE);
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueFlush
**
**  description:
**    Sends current load buffer to box.  Does not wait for box to process
**    buffer, but does update currentLoadBuffer to the next buffer.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueFlush(U32 currentLoadAddr) {
   RETCODE err;
   *currentLoadBufPtr++ = SD_EOF;  /* Indicates end of command records */
   loadBufferState[currentLoadBuffer].inputState = LOAD_FULL;
   loadBufferState[currentLoadBuffer].numBytes = LOADBUF_BYTES_USED;
   
   /* Send packet if channel available */
   if ((err = LdrQueueService()) != GOOD) return(err);

   /* Select another buffer to begin filling */
   if ((err = LdrQueueFindEmptyBuffer(&currentLoadBuffer)) != GOOD)
      return(err);
   currentLoadBufPtr = SdnData(SDN_LOAD_DATA+currentLoadBuffer) + sizeof(U32);
   currentCountPtr = NULL;

   /* For no interdependence, start each packet with complete address record*/
   if ((err = LdrQueueSetAddressRecord(currentLoadAddr, 4)) != GOOD)
      return(err);

   return(GOOD);
}


/****************************************************************************
**
**  LdrQueueWait
**
**  description:
**    Waits for all load buffers to empty and propagates any errors
**    returned from the box.  Informs box that loading is completed.
**
*****************************************************************************/
RETCODE EXPORT LdrQueueWait() {
   RETCODE err=GOOD, err2=GOOD;
   LOAD_COMMAND command;
   U16 i;
   BOOLEAN timedOut;
   clock_t startTime = clock();
   for (i=0; i<NUM_LOAD; i++) {
      if (!err) err = err2;
      if ((err2 = loadBufferState[i].error) != GOOD) continue;
      while ((loadBufferState[i].outputState != LOAD_IDLE)
             || (loadBufferState[i].inputState == LOAD_FULL)) {
         if ((err2 = LdrQueueService()) != GOOD) break;
         if ((err2 = LdrQueueCheckLoadAbort()) != GOOD) break;
         if (clock() > startTime + LOAD_TIMEOUT) {
            err2 = ER_SDS_TIMEOUT;
            break;
         }
      }
   }
   if (!err) err = err2;
   for (i=0; i<NUM_LOAD; i++) {
      err2 = SdUnRegister(loadDesc[i]);
      if (!err) err = err2;
   }
   /* Tell firmware we are done with load */
   command = LOAD_FINISH;
   err2 = SdnWriteCmdReadResponse(SDN_LOAD_COMMAND, (U8*)&command, GOOD,
      SDN_LOAD_COMMAND, 0, &timedOut);

   return(err?err:err2);
}

/****************************************************************************
**
**  LdrQueueService
**
**  description:
**     Service callbacks and transmit any buffers which are ready
**
*****************************************************************************/
RETCODE PRIVATE LdrQueueService(VOID) {
   RETCODE err;
   U16 i;
   SdsInputAvailable();
   if (sdsBusy) return(GOOD);
   for (i=0; i<NUM_LOAD; i++) {
      if (loadBufferState[i].error != GOOD) return(loadBufferState[i].error);
      if ((loadBufferState[i].inputState == LOAD_FULL)
            && (loadBufferState[i].outputState == LOAD_IDLE)) {
         /* Send filled buffer */
         sdsBusy = TRUE;
         loadBufferTransmitting = i;
         loadBufferState[i].outputState = LOAD_TRANSMITTING;

         *((U32*)SdnData(SDN_LOAD_DATA+i)) = loadSerialNumber[i];
         loadSerialNumber[i]++;
         if ((err = SdnWritePartialMemberAsync(SDN_LOAD_DATA+i, 0,
               loadBufferState[i].numBytes, NULL, GOOD,
               (FARPROC)LoadAsyncCallback)) != GOOD) {
            loadBufferState[i].error = err;
            loadBufferState[i].outputState = LOAD_IDLE;
            return(err);
         }
         return(GOOD);
      }
   }
   return(GOOD);
}

/****************************************************************************
**
**  LdrQueueFindEmptyBuffer
**
**  description:
**     Return index of empty buffer.  Waits until buffer is available.
**     Search first for a buffer which is both empty and idle (not being
**     processed).  This guarantees that it will be transmittable when
**     filled.  If all buffers are in use, search for one that is being
**     processed and refill that one.
**
*****************************************************************************/
RETCODE PRIVATE LdrQueueFindEmptyBuffer(U16 *index) {
   RETCODE err;
   U16 i;
   clock_t startTime = clock();
   while (1) {
      /* Look for empty & idle buffer */
      for (i=0; i<NUM_LOAD; i++) {
         if (loadBufferState[i].error!=GOOD) return(loadBufferState[i].error);
         if ((loadBufferState[i].inputState == LOAD_EMPTY)
             && (loadBufferState[i].outputState == LOAD_IDLE)) {
            *index = i;
            loadBufferState[i].inputState = LOAD_FILLING;
            return(GOOD);
         }
      }
      /* Look for other empty buffer */
      for (i=0; i<NUM_LOAD; i++) {
         if (loadBufferState[i].error!=GOOD) return(loadBufferState[i].error);
         if (loadBufferState[i].inputState == LOAD_EMPTY) {
            *index = i;
            loadBufferState[i].inputState = LOAD_FILLING;
            return(GOOD);
         }
      }
      /* No empty buffers found...wait for one, checking for timeout, abort*/
      if ((err = LdrQueueService()) != GOOD) return(err);
      if ((err = LdrQueueCheckLoadAbort()) != GOOD) return(err);
      if (clock() > startTime + LOAD_TIMEOUT) 
         return(ER_SDS_TIMEOUT);
   }
}

/****************************************************************************
**
**  LoadResultCallback
**
**  description:
**    Called when box completes processing a buffer.  If that buffer has been
**    filled and is waiting to send, send it immediately.  If load error, set
**    loadbuffer's error code and return.
**
*****************************************************************************/
VOID EXPORT LoadResultCallback(DESCRIPTOR desc) {
   LOAD_RESULT result;
   MEMBER_INDEX index;
   
   if (SdGetMemberIndex(desc,&index) != GOOD) return;

   if (SdReadMember(desc, (U8*)&result) != GOOD) return;
     /* Ignore result if serial number is incorrect (retransmission) */
   if (result.serialNumber != loadSerialNumber[(U16)index]-1) return;
   loadBufferState[(U16)index].error = result.retcode;

   /* Record the first buffer that had ER_MEMORY_VERIFY error */
   if ((loadBufferState[(U16)index].error == ER_MEMORY_VERIFY) &&
       (indexBufferError == NUM_LOAD)) {
      indexBufferError = (U16)index;
   }
   loadBufferState[(U16)index].outputState = LOAD_IDLE;
}

/****************************************************************************
**
**  LoadAsyncCallback
**
**  description:
**    Called when shared data server completes sending the asynchronous packet.
**    If transmission error, set loadbuffer's error code.  Advance outputState
**    of loadbuffer to LOAD_PROCESSING, and the input state to LOAD_EMPTY,
**    allowing this buffer to begin refilling.
**
*****************************************************************************/
VOID EXPORT LoadAsyncCallback(VOID) {
   U16 i=loadBufferTransmitting;
   RETCODE err;
   if ((err = SdsAsyncWait()) != GOOD) {  /* Get error from transmission */
      loadBufferState[i].error = err;
      loadBufferState[i].outputState = LOAD_IDLE;
      return;
   }
   if (loadBufferState[i].outputState == LOAD_TRANSMITTING) {
      loadBufferState[i].outputState = LOAD_PROCESSING;
   }
   loadBufferState[i].inputState = LOAD_EMPTY;   /* Ready to refill */
   sdsBusy = FALSE;
}

/***************************************************************************
**
** LdrCliSetMemSize
**
** Description:
**    Cli interface to the set the global parameter for the size of data
**    when sent to the probe
**
** syntax: loadSize [byte|word|long|dword]
**
*****************************************************************************/
#define BYTE_TEXT "byte"
#define WORD_TEXT "word"
#define LONG_TEXT "long"
#define DWORD_TEXT "dword"
#define UNKNOWN_TEXT "unknown"

RETCODE EXPORT LdrCliSetMemSize(LPSTR cmdString, U32 argc, U32 argv[]) {
   RETCODE err;
   LPSTR lpParam;

   if((err = SdnReadMember(SDN_LOAD_ACCESS, (U8 FAR *)&loadAccess)) != GOOD) 
      return(err);
   if(argc > 2) return ER_CLI_SYNTAX;
   if(argc==2) {
      lpParam = &(cmdString[(U16)argv[1]]);
      if (strncmpi(lpParam, BYTE_TEXT, strlen(lpParam))==0) {
         loadAccess = BYTE_SIZE;
      } else if (strncmpi(lpParam, WORD_TEXT, strlen(lpParam))==0) {
         loadAccess = WORD_SIZE;
      } else if (strncmpi(lpParam, LONG_TEXT, strlen(lpParam))==0) {
         loadAccess = DWORD_SIZE;
      } else if (strncmpi(lpParam, DWORD_TEXT, strlen(lpParam))==0) {
         loadAccess = DWORD_SIZE;
      } else return ER_CLI_SYNTAX;
      if((err = SdnWriteMember(SDN_LOAD_ACCESS, (U8*)&loadAccess, GOOD))
         != GOOD) return(err);
   }
   runMode = BATCH;
   InfoMsg((loadAccess==BYTE_SIZE)  ? BYTE_TEXT:
           (loadAccess==WORD_SIZE)  ? WORD_TEXT:
           (loadAccess==DWORD_SIZE) ? LONG_TEXT:
                                      UNKNOWN_TEXT);
   runMode = INTERACTIVE;
   return(GOOD);
}

/******************************** E O F ***********************************/

