/* #define XYCOM_DO_RESET_AND_OFFLINE */
/* #define PEP_DO_RESET_AND_OFFLINE */


/*
  changes to UI:

  XYCOM_BB_MAX_OUTSTAND_MSGS -> XycomMaxOutstandMsgs



  TODO:

  1) Synthetic delays after 91s.
  2) Rebuild the link kill stuff so that it does not mess with the WDtask.
  3) Verify race conditions on the slave reset function.
  4) Write a callable bug-syncer that uses the RAC get function ID command.
  5) Merge the watchdog tasks into one.
  6) Try merging the TX and RX tasks.

*/

/*
 *	Original Author: Ned Arnold
 *      Author: John Winans
 *      Date:   09-10-91
 *	XVME-402 and PB-bit BitBus driver
 *
 *      Experimental Physics and Industrial Control System (EPICS)
 *
 *      Copyright 1991, the Regents of the University of California,
 *      and the University of Chicago Board of Governors.
 *
 *      This software was produced under  U.S. Government contracts:
 *      (W-7405-ENG-36) at the Los Alamos National Laboratory,
 *      and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *      Initial development by:
 *              The Controls and Automation Group (AT-8)
 *              Ground Test Accelerator
 *              Accelerator Technology Division
 *              Los Alamos National Laboratory
 *
 *      Co-developed with
 *              The Controls and Computing Group
 *              Accelerator Systems Division
 *              Advanced Photon Source
 *              Argonne National Laboratory
 *
 * Modification Log:
 * -----------------
 * .01  09-30-91        jrw     Completely redesigned and rewritten
 * .02	12-02-91	jrw	Changed priority info to arrays
 * .03	12-16-91	jrw	Made the data portion of the message a pointer
 * .04	01-21-91	jrw	moved the task parameters into task_params.h
 * .05	02-12-92	jrw	removed IRQ based transmission.
 * .06	04-08-92	jrw	moved the device configs into module_types.h
 * .07	09-28-92	jrw	upped the reset delay time to 1/2 second
 * .08	08-02-93	mrk	Added call to taskwdInsert
 * .09  06-28-94	jrw	Major work on RAC_RESET and NODE_OFFLINE stuff.
 * .10  07-01-94	jrw	Merged PEP and Xycom versions together.
 *				ANSIficated this code
 *
 * NOTES:
 * This driver currently needs work on error message generation.
 *
 * $Log$
 * Revision 1.35  1994/12/12  16:02:57  winans
 * Rewrote the init code so that it always returns a zero (don't kill the
 * startup.cmd file.)  It is possible that this could cause some confusion
 * to the database, should it decide to then use a link that did not init
 * properly.
 *
 * Revision 1.34  1994/10/19  18:31:22  winans
 * ANSIfied the bitbus driver so that the compiler stopped warning about
 * exery third line of code.
 *
 * Revision 1.33  1994/10/04  18:42:42  winans
 * Added an extensive debugging facility.
 *
 *
 */

#include <vxWorks.h>
#include <types.h>
#include <iosLib.h>
#include <taskLib.h>
#include <memLib.h>
#include <semLib.h>
#include <rngLib.h>
#include <wdLib.h>
#include <tickLib.h>
#include <vme.h>
#include <iv.h>
#include <rebootLib.h>
#include <logLib.h>
#include <sysLib.h>
#include <intLib.h>
#include <stdio.h>
#include <vxLib.h>
#include <string.h>

#include <task_params.h>
#include <module_types.h>
#include <drvSup.h>
#include <dbDefs.h>
#include <link.h>
#include <callback.h>
#include <taskwd.h>
#include <devLib.h>

#include <drvBitBusInterface.h>
#include "drvBitBus.h"

#define STATIC	static

#define BB_DEFAULT_RETIRE_TIME 5	/* Seconds to wait for a response */

STATIC long	reportBB(void);
STATIC long	initBB(void);
STATIC long	qBBReq(struct  dpvtBitBusHead *pdpvt, int prio);

STATIC int	xvmeReset(int link);
STATIC int	xvmeTmoHandler(int link);
STATIC int	xvmeRxTask(int link);
STATIC int	xvmeTxTask(int link);
STATIC int	xvmeWdTask(int link);
STATIC int	xvmeIrqRdav(int link);
STATIC int	xvmeIrqRcmd(int link);

STATIC int	pepReset(int link);
STATIC int	pepTmoHandler(int link);
STATIC int	pepRxTask(int link);
STATIC int	pepTxTask(int link);
STATIC int	pepWdTask(int link);
STATIC int	pepIrqRdav(int link);

STATIC int	bbKill(int link);
STATIC void	BBrebootFunc(void);

STATIC int	txStuck(int link);
int BBConfig(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long);
int __BBConfig(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long);

#ifdef BB_SUPER_DEBUG
int BBHistDump(int link);
STATIC int BBDumpXactHistory(XactHistStruct *pXact);
#endif


/*****************************************************************************
 *
 * Used to limit the TOTAL number of simultaneous messages that can be
 * outstanding on a single Xycom link.  (Shell settable.)
 *
 *****************************************************************************/
int	XycomMaxOutstandMsgs = XYCOM_BB_MAX_OUTSTAND_MSGS;

/*****************************************************************************
 *
 * Debugging flags that may be set from the shell.
 *
 * bbDebug	Used to get status information from the driver's internals.
 *		May be set with a value from 0 to 40.  the higher the number 
 *		the more information.  0 will provide no internal debugging.
 *
 * bbDebugLink	Used to get general status information about a single slave
 * bbDebugNode	node.
 *		To disable this feature, set one of them to -1.
 *
 *****************************************************************************/
int	bbDebug = 1;
int	bbDebugLink=-1;
int	bbDebugNode=-1;

#ifdef BB_SUPER_DEBUG
static int BBSetHistEvent(int link, struct dpvtBitBusHead *pXact, int State);
#endif

/******************************************************************************
 *
 * This structure contains a list of the outside-callable functions.
 *
 ******************************************************************************/
struct drvBitBusEt drvBitBus = {
  3,
  reportBB,
  initBB,
  qBBReq
};

#if 0
/* JRW --> get rid of this crap */
STATIC	char	BitbusInitCalled = 0;	/* to insure that init is done first */
STATIC	void	*short_base;		/* base of short address space */
#endif


STATIC BitbusLinkStruct	*pBBLink[BB_NUM_LINKS];	/* NULL if link not config'd */
/***************************************************************************
 *
 * User-callable configuration function.
 *
 * This function is used to configure the type of link as well as to 
 * initialize it for operation.
 *
 *****************************************************************************/
int BBConfig(unsigned long Link, 
	unsigned long LinkType,
	unsigned long BaseAddr,
	unsigned long IrqVector,
	unsigned long IrqLevel)
{
  __BBConfig(Link, LinkType, BaseAddr, IrqVector, IrqLevel);
  return(0);
}

int __BBConfig(unsigned long Link, 
	unsigned long LinkType,
	unsigned long BaseAddr,
	unsigned long IrqVector,
	unsigned long IrqLevel)
{
  void		*pVoid;
  int		j;
  static int	FirstTime = 1;
  char		nameTemp[30];
  int		taskId;

  if (FirstTime)
  {
    rebootHookAdd(BBrebootFunc);
    FirstTime = 0;
  }
  if (Link >= BB_NUM_LINKS)
  {
    logMsg("Error: Invalid link (%d) specified in BBConfig()\n", Link);
    return(-1);
  }
  if ((LinkType != BB_CONF_TYPE_XYCOM) && (LinkType != BB_CONF_TYPE_PEP))
  {
    logMsg("Error: Invalid link type (%d) specified in BBConfig()\n", LinkType);
    return(-1);
  }
  if (pBBLink[Link] != NULL)
  {
    logMsg("Error: BBConfig() Attempt to reconfigure link %d!\n", Link);
    return(-1);
  }

  if ((pBBLink[Link] = (BitbusLinkStruct *) malloc(sizeof(BitbusLinkStruct))) == NULL)
  {
    logMsg("Error: BBConfig() cannot malloc\n");
    return(-1);
  }

  pBBLink[Link]->LinkType = BB_CONF_HOSED;	/* Changed if all goes well */
  pBBLink[Link]->BaseAddr = BaseAddr;
  pBBLink[Link]->IrqVector = IrqVector;
  pBBLink[Link]->IrqLevel = IrqLevel;

#ifdef BB_SUPER_DEBUG
  /* Init the history FIFO to empty */
  pBBLink[Link]->History.sem = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
  pBBLink[Link]->History.Next = 0;
  pBBLink[Link]->History.Num = 0;
#endif

  pBBLink[Link]->linkEventSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);

  if (sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO , BaseAddr, &pVoid) != OK)
  {
    logMsg("Error: BBConfig() can not translate requested A16 address(0x%8.8X\n", BaseAddr);
    pBBLink[Link] = NULL;
    return(-1);
  }

  /* 
   * Interface specific I/O mapping and registration 
   */

  switch (LinkType)
  {
  case BB_CONF_TYPE_XYCOM:
    pBBLink[Link]->l.XycomLink.rxInt = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
    pBBLink[Link]->l.XycomLink.bbRegs = (XycomBBRegsStruct *)pVoid;
    if (devRegisterAddress("Xycom Bitbus", atVMEA16, (void*)BaseAddr, sizeof(XycomBBRegsStruct), NULL) != 0)
    {
      logMsg("Error: BBConfig() can not register address %8.8X\n", pVoid);
      pBBLink[Link] = NULL;
      return(-1); /* BUG */
    }
    break;
  case BB_CONF_TYPE_PEP:
    pBBLink[Link]->l.PepLink.rxInt = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
    pBBLink[Link]->l.PepLink.bbRegs = (PepBBRegsStruct *)pVoid;
    if (devRegisterAddress("PEP Bitbus", atVMEA16, (void*)BaseAddr, sizeof(PepBBRegsStruct), NULL) != 0)
    {
      logMsg("Error: BBConfig() can not register address %8.8X\n", pVoid);
      pBBLink[Link] = NULL;
      return(-1); /* BUG */
    }
    break;
  }

  /*
   * Common data structure initialization.
   */

  /* Init the prioritized queue lists */
  for (j=0; j<BB_NUM_PRIO; j++)
  {
    pBBLink[Link]->queue[j].head = NULL;
    pBBLink[Link]->queue[j].tail = NULL;
    pBBLink[Link]->queue[j].sem = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
    pBBLink[Link]->queue[j].elements = 0;
  }

  /* Init the busy message list */
  pBBLink[Link]->busyList.sem = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
  pBBLink[Link]->busyList.head = NULL;
  pBBLink[Link]->busyList.tail = NULL;
  pBBLink[Link]->busyList.elements = 0;

  for (j=0; j<BB_APERLINK; j++)
  { /* Assume all nodes are IDLE with no delay imposed */
    pBBLink[Link]->deviceStatus[j] = BB_IDLE;
    pBBLink[Link]->syntheticDelay[j] = 0;
  }
  pBBLink[Link]->DelayCount = 0;

  pBBLink[Link]->watchDogId = wdCreate();
  pBBLink[Link]->watchDogSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);

  /* Clear the link abort status */
  pBBLink[Link]->abortFlag = 0;
  pBBLink[Link]->txAbortAck = 0;
  pBBLink[Link]->rxAbortAck = 0;

  /* 
   * Interface type specific initialization of management tasks.
   */

  if (LinkType == BB_CONF_TYPE_XYCOM)
  {
    /* BOARD SPECIFIC INIT CODE for XYCOM */
    if (xvmeReset(Link) != 0)
    {
      pBBLink[Link] = NULL;
      return(-1);
    }
  
    pBBLink[Link]->LinkType = LinkType;
  
    /* attach the interrupt handler routines */
    intConnect((BB_IVEC_BASE + 1 + (Link*4)) * 4, xvmeIrqRcmd, Link);
    intConnect((BB_IVEC_BASE + 3 + (Link*4)) * 4, xvmeIrqRdav, Link);
  
    /* Start a task to manage the TX link */
    sprintf(nameTemp, "%s%d-xy", BBTXLINK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBTXLINK_PRI, BBTXLINK_OPT, BBTXLINK_STACK, xvmeTxTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start TX link task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
    /* Start a task to manage the RX link */
    sprintf(nameTemp, "%s%d-xy", BBRXLINK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBRXLINK_PRI, BBRXLINK_OPT, BBRXLINK_STACK, xvmeRxTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start RX link task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
    /* Start a task to keep an eye on the busy list */
    sprintf(nameTemp, "%s%d-xy", BBWDTASK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBWDTASK_PRI, BBWDTASK_OPT, BBWDTASK_STACK, xvmeWdTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start watchdog task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
  }
  else if (LinkType == BB_CONF_TYPE_PEP)
  {
    /* PEP specific init code */
    if (pepReset(Link) != 0)
    {
      pBBLink[Link] = NULL;
      return(-1);
    }
  
    pBBLink[Link]->LinkType = LinkType;
  
    /* attach the interrupt handler routines */
    intConnect(INUM_TO_IVEC(PEP_BB_IVEC_BASE + (Link*2)), pepIrqRdav, Link);
  
    /* Start a task to manage the TX link */
    sprintf(nameTemp, "%s%d-pep", BBTXLINK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBTXLINK_PRI, BBTXLINK_OPT, BBTXLINK_STACK, pepTxTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start TX link task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
    /* Start a task to manage the RX link */
    sprintf(nameTemp, "%s%d-pep", BBRXLINK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBRXLINK_PRI, BBRXLINK_OPT, BBRXLINK_STACK, pepRxTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start RX link task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
    /* Start a task to keep an eye on the busy list */
    sprintf(nameTemp, "%s%d-pep", BBWDTASK_NAME, Link);
    if ((taskId=taskSpawn(nameTemp, BBWDTASK_PRI, BBWDTASK_OPT, BBWDTASK_STACK, pepWdTask, Link)) == ERROR)
    {
      logMsg("BBConfig(): failed to start watchdog task for link %d\n", Link);
    }
    taskwdInsert(taskId,NULL,NULL);
  }
  sysIntEnable(IrqLevel);
  return(0);
}
/******************************************************************
 *
 * The EPICS init routine is not needed.
 *
 ******************************************************************/
STATIC long initBB(void)
{
  return(0);
}

/******************************************************************
 * FUNCTION:    reportBB()
 * PURPOSE :    Prints a message indicating the presence of each
 *              bitbus module found in system.
 * ARGS IN :    none
 * ARGS OUT:    none
 * GLOBALS:     checks pBBLink[i] for NULL
 ******************************************************************/

STATIC long reportBB(void)
{
  int	i;

  if (bbDebug>1)
    printf("Bitbus debugging flag is set to %d\n", bbDebug);

  for (i=0; i< BB_NUM_LINKS; i++)
  {
    if (pBBLink[i] != NULL)
    {
      if (pBBLink[i]->LinkType == BB_CONF_TYPE_XYCOM)
        printf("Bitbus link %d present at %p IV=0x%2.2X IL=%d (XYCOM)\n", i, pBBLink[i]->l.XycomLink.bbRegs, pBBLink[i]->IrqVector, pBBLink[i]->IrqLevel);
      else if (pBBLink[i]->LinkType == BB_CONF_TYPE_PEP)
        printf("Bitbus link %d present at %p IV=0x%2.2X IL=%d (PEP)\n", i, pBBLink[i]->l.PepLink.bbRegs, pBBLink[i]->IrqVector, pBBLink[i]->IrqLevel);
    }
  }
  return(OK);
}
/******************************************************************
 * FUNCTION:    pepReset()
 * PURPOSE :    Performs firmware reset of PB-BIT module corresponding
 *              to link. Attempts to empty any data sitting in receive
 *              FIFO. Interrupts are disabled and Rx task is unblocked.
 * ARGS IN :    xvmeRegs ptr to register structure of PB-BIT module
 *              link link number serviced by PB-BIT module
 * ARGS OUT:    none
 * GLOBALS:     twiddles board
 ******************************************************************/
STATIC int
pepReset(int link)
{
  char  trash;
  int   j;
  int   lockKey;
  int	probeValue;

  if (bbDebug)
    printf("pepReset(%d): Resetting pep module\n", link);

  probeValue = 0;
  if (vxMemProbe(&(pBBLink[link]->l.PepLink.bbRegs->int_vec), WRITE, 1, &probeValue) < OK)
  { /* no BB board present here */
    logMsg("ERROR: PEP Bitbus link %d not present at 0x%8.8X\n", link, pBBLink[link]->l.PepLink.bbRegs);
    return(-1);
  }

  /* Write firmware reset package (2 bytes) to board */
  pBBLink[link]->l.PepLink.bbRegs->data = 0x83;
  pBBLink[link]->l.PepLink.bbRegs->stat_ctl = 0x01;

  taskDelay(20);                   /* give the 80152 time to self check */

  if ((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & 0x10) != 0x0) 
  {
    if (bbDebug)
      printf("pepReset(%d): PB-BIT firmware reset failed!\n", link);
    return(ERROR);
  }

  j = 1026;		/* 1K deep receive fifo */
			/* flush receive fifo if junk in it */
  while ((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & PEP_BB_RFNE) && --j)
    trash = pBBLink[link]->l.PepLink.bbRegs->data;

  if (!j) 
  {
    if (bbDebug)
      printf("pepReset(%d): receive fifo will not clear after reset!\n", link);
    return(ERROR);
  }

  /* Disable interrupts */
  lockKey = intLock();
  pBBLink[link]->l.PepLink.bbRegs->int_vec = 0;
  intUnlock(lockKey);

  /* Tell pepRxTask to Re-enable ints */
  semGive(pBBLink[link]->l.PepLink.rxInt);  

  return(OK);
}
/****************************************************************************
 *
 * Reset an xvme-402 BitBus card by cycling the reset bit in the fifo
 * status register.
 *
 ****************************************************************************/
STATIC int xvmeReset(int link)
{
  char	trash;
  int	j;
  int	lockKey;
  int	probeValue;


  if (bbDebug)
    printf("xvmeReset(%d): Resetting xvme module\n", link);

  probeValue = XVME_RESET;
  if (vxMemProbe(&(pBBLink[link]->l.XycomLink.bbRegs->fifo_stat), WRITE, 1, &probeValue) < OK)
  { /* no BB board present here */
    logMsg("ERROR: Xycom Bitbus link %d not present at 0x%8.8X\n", link, pBBLink[link]->l.XycomLink.bbRegs);
    return(-1);
  }

#ifdef BB_SUPER_DEBUG
  BBSetHistEvent(link, NULL, XACT_HIST_STATE_RESET);
#endif

  taskDelay(2);
  pBBLink[link]->l.XycomLink.bbRegs->fifo_stat = 0;	/* clear reset pulse */
  taskDelay(30);			/* give the 8044 time to self check */

  j = 100;				/* give up after this */
  while ((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_RCMD) && --j)
    trash = pBBLink[link]->l.XycomLink.bbRegs->cmnd;		/* flush command buffer if junk in it */

  if (!j)
  {
    if (bbDebug)
      printf("xvmeReset(%d): Command buffer will not clear after reset!\n", link);
    return(ERROR);
  }

  j = 100;
  while ((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_RFNE) && --j)
    trash = pBBLink[link]->l.XycomLink.bbRegs->data;	/* flush data buffer if junk in it */

  if (!j)
  {
    if (bbDebug)
      printf("xvmeReset(%d): Data buffer will not clear after reset!\n", link);
    return(ERROR);
  }

  if ((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_FSVALID) != XVME_FSIDLE)
  {
    if (bbDebug)
      printf("xvmeReset(%d): XVME board not returning to idle status after reset!\n", link);
    return(ERROR);
  }

  /* set the interrupt vector */
  lockKey = intLock();
  pBBLink[link]->l.XycomLink.bbRegs->stat_ctl = 0;          /* disable all interupts */
  pBBLink[link]->l.XycomLink.bbRegs->int_vec = BB_IVEC_BASE + (link*4);/* set the int vector */
  intUnlock(lockKey);

  semGive(pBBLink[link]->l.XycomLink.rxInt);	/* Tell xvmeRxTask to Re-enable interrupts */

  return(OK);
}
/****************************************************************************
 *
 * This function is used to add a node to the HEAD of a list.
 *
 ****************************************************************************/
static int
listAddHead(struct bbList *plist, struct dpvtBitBusHead *pnode)
{
  pnode->prev = NULL;
  pnode->next = plist->head;

  if (plist->head != NULL)
    plist->head->prev = pnode;

  if (plist->tail == NULL)
    plist->tail = pnode;

  plist->head = pnode;

  plist->elements++;

  return(0);
}
/******************************************************************************
 *
 * This function is used to add a node to the TAIL of a list.
 *
 ******************************************************************************/
static int
listAddTail(struct bbList *plist, struct dpvtBitBusHead *pnode)
{
  pnode->next = NULL;		/* No next node if this is the TAIL */
  pnode->prev = plist->tail;	/* previous node is the 'old' TAIL node */

  if (plist->tail != NULL)
    plist->tail->next = pnode;	/* link the 'old' tail to the 'new' tail node */

  if (plist->head == NULL)
    plist->head = pnode;

  plist->tail = pnode;		/* this is the 'new' tail node */

  plist->elements++;

  return(0);
}
/***************************************************************************
 *
 * This function is used to delete a node from a list.
 *
 ***************************************************************************/
static int
listDel(struct bbList *plist, struct dpvtBitBusHead *pnode)
{
  if (pnode->next != NULL)
    pnode->next->prev = pnode->prev;

  if (pnode->prev != NULL)
    pnode->prev->next = pnode->next;

  if (plist->head == pnode)
    plist->head = pnode->next;

  if (plist->tail == pnode)
    plist->tail = pnode->prev;

  plist->elements--;

  return(0);
}
/****************************************************************************
 * 
 * xycom IRQ handler for receiver data bytes.
 *
 ****************************************************************************/
static int
xvmeIrqRdav(int link)
{
  if (bbDebug > 30)
    logMsg("bitbus rx IRQ on link %d\n", link);
  semGive(pBBLink[link]->l.XycomLink.rxInt);	/* deliver the groceries */
  return(0);
}

/****************************************************************************
 *
 * xycom IRQ handler invoked when the BitBus controller has completed the 
 * transfer of a RECEIVED message.  
 *
 ****************************************************************************/
static int
xvmeIrqRcmd(int link)
{
  if (bbDebug > 30)
    logMsg("bitbus rcmd IRQ on link %d\n", link);
  semGive(pBBLink[link]->l.XycomLink.rxInt);      /* deliver the groceries */
  return(0);
}
/******************************************************************
 * FUNCTION:    pepIrqRdav()
 * PURPOSE :    Invoked when PB-BIT module has received a complete
 *              bitbus message (ie. not on a byte-by-byte basis).
 * ARGS IN :    link link number upon which message has arrived
 * ARGS OUT:    none
 * GLOBALS:     unblocks Rx task (if it is not already running)
 ******************************************************************/
STATIC int
pepIrqRdav(int link)
{
  pBBLink[link]->l.PepLink.bbRegs->int_vec = 0;	/* disable IRQs */

  if (bbDebug > 30)
    logMsg("PEP bitbus rx IRQ on link %d\n", link);

  semGive(pBBLink[link]->l.PepLink.rxInt);       /* unblock it */
  return(0);
}
/******************************************************************
 * FUNCTION:    pepTmoHandler()
 * PURPOSE :    Invoked whenever a watchdog timer times out. Watchdogs
 *              are running whenever the busyList has any elements on
 *              it. The idea here is that the watchdog handler scans
 *              through the busyList, looking for old requests that have
 *              not been replied to in too long a time. If there are
 *              any old ones around, they are removed from the list,
 *              and the associated node is reset and marked offline.
 *
 * ARGS IN :    link
 * ARGS OUT:    none
 * GLOBALS:     none
 ******************************************************************/
STATIC int
pepTmoHandler(int link)
{
  if (bbDebug > 25)
    logMsg("pepTmoHandler(%d): Watch dog interrupt\n", link);

  semGive(pBBLink[link]->watchDogSem); /* unblock Wd task */
  return(0);
}

/******************************************************************************
 *
 * Watchdogs are running when ever the busy list has any elements in it.
 * The idea here is that the watchdog handler scans thru the busy list,
 * looking for old requests that have not been replied to in too long
 * a time.  If there are any old ones around, they are removed from the
 * list and marked as un-replied to.
 *
 ******************************************************************************/
static int
xvmeTmoHandler(int link)
{
  if (bbDebug > 25)
    logMsg("xvmeTmoHandler(%d): Watch dog interrupt\n", link);

  semGive(pBBLink[link]->watchDogSem);
  return(0);
}
/****************************************************************************
 *
 * Given a link number, make sure it is valid.
 *
 ****************************************************************************/
static int
checkLink(int link)
{
  if ((link<0) || (link>BB_NUM_LINKS))
    return(ERROR);		/* link number out of range */

  if (pBBLink[link] == NULL)
    return(ERROR);		/* link number has no card installed */

  return(OK);
}
 /***************************************************************************
 *
 * This function is started as a task during driver init time.  It's purpose
 * is to read messages that are received from the bitbus link.  After a message
 * is received, the soliciting message's completion routines are started by
 * the use of semaphores and/or a callbackRequest().
 *
 ***************************************************************************/
static int
xvmeRxTask(int link)
{
  int           rxState;       /* current state of the receiver */
#define BBRX_HEAD	1
#define	BBRX_DATA	2
#define BBRX_RCMD	3
#define	BBRX_IGN	4

  unsigned char rxHead[5];      /* room for header of current rx msg */
  unsigned char *rxMsg;         /* where to place next byte (after header) */
  int           rxTCount;       /* byte counter for data in rxHead */
  unsigned char	ch;
  struct dpvtBitBusHead *rxDpvtHead;	/* for message currently receiving */

  struct dpvtBitBusHead	UselessMsg;	/* to hold unsolicited responses */
  unsigned char         UselessData[BB_MAX_MSG_LENGTH];

  int		lockKey;		/* used for intLock calls */

  rxMsg = (unsigned char *) NULL;
  rxState = BBRX_HEAD;
  rxTCount = 0;
  rxDpvtHead = (struct dpvtBitBusHead *) NULL;

  /* Dummy up the UselessMsg fields so we can use it */
  UselessMsg.rxMaxLen = BB_MAX_MSG_LENGTH;
  UselessMsg.rxMsg.data = UselessData;
  UselessMsg.link = link;
  UselessMsg.ageLimit = 0;
  UselessMsg.retire = 0;

  while (1)
  {
    /* Wait for RX interrupts, but only if no chars are ready */
    if ((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_RFNE) == 0)
    {
      /* Enable interrupts and check again because xycom blew it */
      lockKey = intLock();
      pBBLink[link]->l.XycomLink.bbRegs->stat_ctl = XVME_ENABLE_INT | XVME_RX_INT;
      intUnlock(lockKey);
      
      while (((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_RFNE) == 0) && (pBBLink[link]->abortFlag == 0))
      { 
        /* Re-enable ints here each time in case board got reset */
	lockKey = intLock();
        pBBLink[link]->l.XycomLink.bbRegs->stat_ctl = XVME_ENABLE_INT | XVME_RX_INT;
	intUnlock(lockKey);
  
	/* Wait for groceries */
        semTake(pBBLink[link]->l.XycomLink.rxInt, WAIT_FOREVER);
      }
      /* Disable RX Interrupts (prevents unnecessary context switching) */
      lockKey = intLock();
      pBBLink[link]->l.XycomLink.bbRegs->stat_ctl = 0;
      intUnlock(lockKey);
    }
    if (pBBLink[link]->abortFlag == 0)
    {
    /* check to see if we got a data byte or a command byte */
    if ((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_RCMD) == XVME_RCMD)
      rxState = BBRX_RCMD;

    switch (rxState) {
    case BBRX_HEAD:	/* getting the head of a new message */
      rxHead[rxTCount] = pBBLink[link]->l.XycomLink.bbRegs->data;
      if (bbDebug>21)
        printf("xvmeRxTask(%d): >%2.2X< (Header)\n", link, rxHead[rxTCount]);

      if (++rxTCount == 5)
      { /* find the message this is a reply to */
	rxTCount += 2;		/* adjust for the link field space */

	/* Lock the busy list */
	semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);

        rxDpvtHead = pBBLink[link]->busyList.head;
        while (rxDpvtHead != NULL)
        {
          if (bbDebug>19)
            printf("xvmeRxTask(%d): checking reply against %p\n", link, rxDpvtHead);

          /* see if node's match */
          if (rxDpvtHead->txMsg.node == rxHead[2])
          { /* see if the tasks match */
            if (rxDpvtHead->txMsg.tasks == rxHead[3])
            { /* They match, finish putting response into the rxMsg buffer */
              if (bbDebug>4)
                printf("xvmeRxTask(%d): reply to %p\n", link, rxDpvtHead);
  
              /* Delete the node from the list */
	      listDel(&(pBBLink[link]->busyList), rxDpvtHead);

	      /* If busy list is empty, stop the dog */
              if (pBBLink[link]->busyList.head == NULL) 
                wdCancel(pBBLink[link]->watchDogId);
          
              if (rxHead[4] == 0x91)
	      { /* something bad happened... inject a delay to the */
		/* requested timeout duration. */

		if (bbDebug)
		  printf("xvmeRxTask(%d): 0x91 from node %d, invoking synthetic delay\n", link, rxHead[2]);
		(pBBLink[link]->syntheticDelay[rxDpvtHead->txMsg.node]) = rxDpvtHead->retire;
		pBBLink[link]->DelayCount++;
              }
              else
              { /* decrement the number of outstanding messages to the node */
                (pBBLink[link]->deviceStatus[rxDpvtHead->txMsg.node])--;
	      }

	      /* Wake up Link Task in case was waiting on "this" node */
              semGive(pBBLink[link]->linkEventSem); 

#if 0
	      semGive(pXvmeLink[link]->pbbLink->busyList.sem);

              rxDpvtHead->rxMsg.length = rxHead[0];
              rxDpvtHead->rxMsg.route = rxHead[1];
              rxDpvtHead->rxMsg.node = rxHead[2];
              rxDpvtHead->rxMsg.tasks = rxHead[3];
	      rxDpvtHead->rxMsg.cmd = rxHead[4];
              rxMsg = rxDpvtHead->rxMsg.data;

              rxDpvtHead->status = BB_OK;	/* OK, unless BB_LENGTH */
              rxState = BBRX_DATA;		/* finish reading till RCMD */
#endif
              break;				/* get out of the while() */
            }
          }
          rxDpvtHead = rxDpvtHead->next; /* Keep looking */
        }

	semGive(pBBLink[link]->busyList.sem);

	/* Couldn't find a match... fake one so can print the bad message */
        if (rxDpvtHead == NULL)
	  rxDpvtHead = &UselessMsg;

        rxDpvtHead->rxMsg.length = rxHead[0];
        rxDpvtHead->rxMsg.route = rxHead[1];
        rxDpvtHead->rxMsg.node = rxHead[2];
        rxDpvtHead->rxMsg.tasks = rxHead[3];
	rxDpvtHead->rxMsg.cmd = rxHead[4];
        rxMsg = rxDpvtHead->rxMsg.data;

        rxDpvtHead->status = BB_OK;	/* OK, unless BB_LENGTH */
        rxState = BBRX_DATA;		/* finish reading till RCMD */
      }
      break;

    case BBRX_DATA:	/* finish reading data portion of message */
      ch = pBBLink[link]->l.XycomLink.bbRegs->data;
      if (rxTCount >= rxDpvtHead->rxMaxLen)
      {
        rxState = BBRX_IGN;	/* toss the rest of the data */
        rxDpvtHead->status = BB_LENGTH; /* set driver status */
	if (bbDebug>22)
	  printf("xvmeRxTask(%d): %2.2X (Ignored)\n", link, ch);
      }
      else
      {
        *rxMsg = ch;
        if (bbDebug>22)
          printf("xvmeRxTask(%d): %2.2X (Data)\n", link, ch);
        rxMsg++;
        rxTCount++;
      }
      break;

    case BBRX_IGN:
      ch = pBBLink[link]->l.XycomLink.bbRegs->data;
      if (bbDebug>22)
        printf("xvmeRxTask(%d): %2.2X (Ignored)\n", link, ch);
      break;

    case BBRX_RCMD:
      if (rxDpvtHead == NULL)
      {
        ch = pBBLink[link]->l.XycomLink.bbRegs->cmnd;
	if (bbDebug)
          printf("xvmeRxTask(%d): got unexpected XVME_RCMD\n", link);
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, NULL, XACT_HIST_STATE_RX_URCMD);
#endif
      }
      else if (rxDpvtHead == &UselessMsg)
      {
	rxDpvtHead->status = BB_OK;
	rxDpvtHead->rxCmd = pBBLink[link]->l.XycomLink.bbRegs->cmnd;

	if (bbDebug)
	{
          printf("xvmeRxTask(%d): msg from node %d unsolicited:", link, rxDpvtHead->rxMsg.node);
	  drvBitBusDumpMsg(&(rxDpvtHead->rxMsg));
	}
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX_UNSOLICITED);
#endif
      }
      else
      {
        rxDpvtHead->status = BB_OK;
        rxDpvtHead->rxCmd = pBBLink[link]->l.XycomLink.bbRegs->cmnd;

        if (bbDebug>24)
          printf("xvmeRxTask(%d):RX command byte = %2.2X\n", link, rxDpvtHead->rxCmd);
    
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX);
#endif
        if (rxDpvtHead->finishProc != NULL)
        {
          if (bbDebug>8)
            printf("xvmeRxTask(%d): invoking the callbackRequest\n", link);
          callbackRequest(rxDpvtHead); /* schedule completion processing */
        }
	else
	{
          /* If there is a semaphore for synchronous I/O, unlock it */
          if (rxDpvtHead->psyncSem != NULL)
            semGive(*(rxDpvtHead->psyncSem));
	}
      }
      /* Reset the state of the RxTask to expect a new message */
      rxMsg = (unsigned char *) NULL;
      rxState = BBRX_HEAD;
      rxTCount = 0;
      rxDpvtHead = (struct dpvtBitBusHead *) NULL;

      break;
    }
  }
  else
  { /* Link abort state is active reset receiver link status now */
    if (rxDpvtHead != NULL)
    { /* This xact is not on the busy list, put it back on */
      semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
      (pBBLink[link]->deviceStatus[rxDpvtHead->txMsg.node])++;
      listAddTail(&(pBBLink[link]->busyList), rxDpvtHead);
      semGive(pBBLink[link]->busyList.sem);
    }

#ifdef BB_SUPER_DEBUG
    BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX_ABORT);
#endif
    rxMsg = (unsigned char *) NULL;
    rxState = BBRX_HEAD;
    rxTCount = 0;
    rxDpvtHead = (struct dpvtBitBusHead *) NULL;

    /* Tell the watch dog I am ready for the reset (reset in the dog task) */
    pBBLink[link]->rxAbortAck = 1;

    if (bbDebug)
      printf("xvmeRxTask(%d): resetting due to abort status\n", link);

    /* wait for link state to become active again */
    while (pBBLink[link]->abortFlag != 0)
      taskDelay(RESET_POLL_TIME);

    if (bbDebug)
      printf("xvmeRxTask(%d): restarting after abort\n", link);
  }
  }
}

/******************************************************************************
 *
 * A user callable link resetter.  This sets a flag and releases the dog
 * task to reset the link.
 *
 ******************************************************************************/
int
bbReset(int link)
{
  if (checkLink(link) != ERROR)
  {
    pBBLink[link]->nukeEm = 1;
    semGive(pBBLink[link]->watchDogSem);
  }
  else
    printf("Link %d not installed.\n", link);

  return(0);
}

/******************************************************************************
 *
 * BUG -- this does not do the nuke=2 reset and kill link operations like
 * the PEP does.
 *
 ******************************************************************************/
static int
xvmeWdTask(int link)
{
  struct dpvtBitBusHead *pnode;
  struct dpvtBitBusHead *npnode;
  unsigned      long    now;

#ifdef XYCOM_DO_RESET_AND_OFFLINE
  SEM_ID		syncSem;

  struct dpvtBitBusHead resetNode;
  unsigned char         resetNodeData;  /* 1-byte data field for RAC_OFFLINE */

  /* init the SEM used when sending the reset message */
  syncSem = semBCreate(SEM_EMPTY, SEM_Q_PRIORITY);

  /*
   * Hand-craft a RAC_OFFLINE  message to use when a message times out.
   * NOTE that having only one copy is OK provided that the dog waits for
   * a response before sending it again!
   */
  resetNode.finishProc = NULL;	/* no callback routine used */
  resetNode.psyncSem = &syncSem;/* do a semGive on this SEM when done sending */
  resetNode.link = link;	/* which bitbus link to send message out on */
  resetNode.rxMaxLen = 7;       /* Chop off the response... we don't care */
  resetNode.ageLimit = sysClkRateGet()*100; /* make sure this never times out */

  resetNode.txMsg.length = 8;
  resetNode.txMsg.route = BB_STANDARD_TX_ROUTE;
  resetNode.txMsg.node = 0xff;
  resetNode.txMsg.tasks = 0x0;
  resetNode.txMsg.cmd = RAC_OFFLINE;
  resetNode.txMsg.data = &resetNodeData;
#endif

  pBBLink[link]->nukeEm = 0;		/* Make sure the nuke status is clear */
  while(1)
  {
    semTake(pBBLink[link]->watchDogSem, WAIT_FOREVER);
    now = tickGet();		/* what time is it? */

    if (pBBLink[link]->nukeEm != 0)
      printf("Bitbus manual reset being issued on link %d\n", link);

    if (bbDebug>4)
      printf("xvmeWdTask(%d): (Watchdog) checking busy list\n", link);

    pBBLink[link]->rxAbortAck = 0;      /* In case we need to use them */
    pBBLink[link]->txAbortAck = 0;

    if (pBBLink[link]->nukeEm != 0)
    { /* set abort status and wait for the abort acks */
      pBBLink[link]->abortFlag = 1;

      /* wake up the Tx task so it can observe the abort status */
      semGive(pBBLink[link]->linkEventSem);

      /* wake up the Rx task so it can observe the abort ststus */
      semGive(pBBLink[link]->l.XycomLink.rxInt);

      /* sleep until abort ack from Tx & Rx tasks */
      while ((pBBLink[link]->rxAbortAck == 0) && (pBBLink[link]->txAbortAck == 0))
	taskDelay(RESET_POLL_TIME);

#ifdef BB_SUPER_DEBUG
      if (pBBLink[link]->nukeEm < 2)
      { /* Do a history dump since this is not supposed to happen */
        BBHistDump(link);
      }
#endif
    }
    /*
     * Run thru entire busy list to see if there are any transactions
     * that have been waiting on a response for too long a period.
     */
    semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
    pnode = pBBLink[link]->busyList.head;

    while (pnode != NULL)
    {
      npnode = pnode->next;   /* remember where we were in the list */

      if ((pBBLink[link]->nukeEm != 0) || (pnode->retire <= now))
      {
	/* Get rid of the request and set error status etc... */
	listDel(&(pBBLink[link]->busyList), pnode);

	if (bbDebug)
	{
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, pnode, XACT_HIST_STATE_WD_TIMEOUT);
#endif
	  printf("xvmeWdTask(%d): TIMEOUT on bitbus message:\n", link);
	  drvBitBusDumpMsg(&pnode->txMsg);
	}

	/* BUG -- do this here or defer until RX gets a response? */
        (pBBLink[link]->deviceStatus[pnode->txMsg.node])--; /* fix device status */
	pnode->status = BB_TIMEOUT;

#ifdef XYCOM_DO_RESET_AND_OFFLINE
	/* BUG ----- wait for the response from the last one first? */

	/* Do now in case we need it later */
	resetNodeData = pnode->txMsg.node;  /* mark the node number */
#endif

	/* Make the callbackRequest if one was spec'd */
	if(pnode->finishProc != NULL)
	{
	  callbackRequest(pnode);     /* schedule completion processing */
	}
	else
	{
	  /* Release a completion lock if one was spec'd */
	  if (pnode->psyncSem != NULL)
	    semGive(*(pnode->psyncSem));
	}

#ifdef XYCOM_DO_RESET_AND_OFFLINE
        /* If we are not going to reboot the link... */
        if (pBBLink[link]->nukeEm == 0)
	{ /* Send out a RAC_NODE_OFFLINE to the controller */
	  semGive(pBBLink[link]->busyList.sem);

	  if (bbDebug)
	    printf("issuing a node offline for link %d node %d\n", link, resetNodeData);

	  semTake(pBBLink[link]->queue[BB_Q_HIGH].sem, WAIT_FOREVER);
	  listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), &resetNode);
	  semGive(pBBLink[link]->queue[BB_Q_HIGH].sem);

          semGive(pBBLink[link]->linkEventSem);	/* Tell TxTask to send the message */

	  if (semTake(syncSem, sysClkRateGet()/4) == ERROR)
          {
	    if (bbDebug)
	      printf("xvmeWdTask(%d): link dead, trying manual reboot\n", link);
	    pBBLink[link]->nukeEm = 1;

            pBBLink[link]->abortFlag = 1; /* Start the abort sequence */
            semGive(pBBLink[link]->linkEventSem); /* Let Tx task observe abort status */
            semGive(pBBLink[link]->l.XycomLink.rxInt); /* Let Rx task observe abort ststus */

            /* sleep until abort ack from Tx & Rx tasks */
            while ((pBBLink[link]->rxAbortAck == 0) && (pBBLink[link]->txAbortAck == 0))
	      taskDelay(RESET_POLL_TIME);
	  }
	  /* Start over since released the busy list */

	  semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
          npnode = pBBLink[link]->busyList.head;
	}
#else
	semGive(pBBLink[link]->linkEventSem);
#endif
      }
      pnode = npnode;
    }
    if (pBBLink[link]->busyList.head != NULL)
    { /* Restart the dog timer */

      if (bbDebug>5)
	printf("xvmeWdTask(%d): restarting watch dog timer\n", link);

      wdStart(pBBLink[link]->watchDogId, pBBLink[link]->busyList.head->retire - now, xvmeTmoHandler, link);
    }
    semGive(pBBLink[link]->busyList.sem);

    /* Finish the link reboot if necessary */
    if (pBBLink[link]->nukeEm != 0)
    {
      xvmeReset(link);

      /* clear the abort_flag */
      pBBLink[link]->abortFlag = 0;

      pBBLink[link]->nukeEm = 0;
    }
  }
}

/******************************************************************************
 *
 * This function is started as a task during driver init time.  It's purpose
 * is to keep the link busy.  It awaits user-calls to qBbReq() with new work
 * as well as wake-up calls from the watchdog timer.
 *
 * At the time this function is started as its own task, the linked list
 * structures will have been created and initialized.
 *
 ******************************************************************************/
static int 
xvmeTxTask(int link)
{
  struct dpvtBitBusHead	*pnode;
  int			prio;
  int			working;
  int			dogStart;
  int			stuck;

  int			txTCount;
  int			txCCount;
  unsigned char		*txMsg;
  register	int	x;
  unsigned	long	now;
  SEM_ID		resetNodeSem;
  struct dpvtBitBusHead	resetNode;
  unsigned char		resetNodeData;	/* 1-byte data field for RAC_OFFLINE */

  if (bbDebug)
    printf("xvmeTxTask started for link %d\n", link);

  /* hand-craft a RAC_OFFLINE  message for use with RAC_RESET_SLAVE commands */
  /* NOTE that having only one copy is OK provided that this message is    */
  /*      sent immediately following the RAC_RESET_SLAVE message.          */

  resetNodeSem = semBCreate(SEM_Q_PRIORITY, SEM_FULL);

  resetNode.finishProc = NULL;
  resetNode.psyncSem = &resetNodeSem;
  resetNode.link = link;
  resetNode.rxMaxLen = 7;	/* Chop it off, we don't care */
  resetNode.ageLimit = sysClkRateGet();

  resetNode.txMsg.length = 8;
  resetNode.txMsg.route = BB_STANDARD_TX_ROUTE;
  resetNode.txMsg.node = 0xff;
  resetNode.txMsg.tasks = 0x0;
  resetNode.txMsg.cmd = RAC_OFFLINE;
  resetNode.txMsg.data = &resetNodeData;

  while(1)
  {
    if (pBBLink[link]->abortFlag != 0)
    {
      pBBLink[link]->txAbortAck = 1;	/* let the dog know we are ready */
#ifdef BB_SUPER_DEBUG
      BBSetHistEvent(link, NULL, XACT_HIST_STATE_TX_ABORT);
#endif

      if (bbDebug)
        printf("xvmeTxTask(%d): resetting due to abort status\n", link);

      while (pBBLink[link]->abortFlag != 0)
        taskDelay(RESET_POLL_TIME);	/* wait for link to reset */

      if (bbDebug)
        printf("xvmeTxTask(%d): restarting after abort\n", link);
    }
    else
    {
      if (pBBLink[link]->DelayCount)
	semTake(pBBLink[link]->linkEventSem, 2 * sysClkRateGet());
      else
	semTake(pBBLink[link]->linkEventSem, WAIT_FOREVER);
    }

    if (bbDebug>5)
      printf("xvmeTxTask(%d): got an event\n", link);

    working = 1;
    while ((working != 0) && (pBBLink[link]->abortFlag == 0) && (pBBLink[link]->busyList.elements < XycomMaxOutstandMsgs))
    {
      working = 0;

      prio = BB_NUM_PRIO-1;
      while ((prio >= 0) && (pBBLink[link]->abortFlag == 0))
      {
        /* see if the queue has anything in it */
	semTake(pBBLink[link]->queue[prio].sem, WAIT_FOREVER);
  
        if ((pnode = pBBLink[link]->queue[prio].head) != NULL)
        {
	  now = tickGet();
          while (pBBLink[link]->deviceStatus[pnode->txMsg.node] == BB_BUSY)
          {
	    if ((pBBLink[link]->syntheticDelay[pnode->txMsg.node] != 0)
		&& (pBBLink[link]->syntheticDelay[pnode->txMsg.node] < now))
	    {
	      if (bbDebug)
	        printf("xvmeTxTask(%d): terminating synthetic idle on node %d\n", link, pnode->txMsg.node);
	      (pBBLink[link]->deviceStatus[pnode->txMsg.node])--;
	      pBBLink[link]->syntheticDelay[pnode->txMsg.node] = 0;
	      pBBLink[link]-> DelayCount--;
	    }
	    else
	    {
              if ((pnode = pnode->next) == NULL)
                break;
	    }
	  }
        }
        if (pnode != NULL)
        { /* have an xact to start processing */
          working = 1;			/* indicate work being done */

          /* delete the node from the inbound fifo queue */
          listDel(&(pBBLink[link]->queue[prio]), pnode);
  
	  semGive(pBBLink[link]->queue[prio].sem);
      
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX);
#endif
          if (bbDebug>3)
            printf("xvmeTxTask(%d): got xact, pnode=%p\n", link, pnode);
      
          /* Send the message in polled mode */
  
          txTCount = pnode->txMsg.length - 2;
          txCCount = 0;
          txMsg = &(pnode->txMsg.length);
 
          while ((txCCount <= txTCount) && (pBBLink[link]->abortFlag == 0))
          {
	    stuck = 1000;
            while (((pBBLink[link]->l.XycomLink.bbRegs->fifo_stat & XVME_TFNF) != XVME_TFNF) && (pBBLink[link]->abortFlag == 0) && --stuck)
              for(x=0;x<100;x++);			/* wait for TX ready */
  
	    if (!stuck)
	    {
#ifdef BB_SUPER_DEBUG
  	      BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_STUCK);
#endif
	      txStuck(link);	/* will end up setting abortFlag */
	    }
            else if (txCCount < txTCount) /* on last time, just wait, no data */
	    {
              pBBLink[link]->l.XycomLink.bbRegs->data = *txMsg;	/* send next byte */
              if (bbDebug>30)
                printf("xvmeTxTask(%d): outputting %2.2X\n", link, *txMsg);
  
	      /* On 5th byte, we are dun w/header, set start w/data buffer */
	      if (txCCount != 4)
	        txMsg++;
              else
	        txMsg = pnode->txMsg.data;	
	    }
	    txCCount++;
          }

	  if (pBBLink[link]->abortFlag == 0)
	  {

	    /* All data bytes have been sent, put on busy list and release */

	    /* Don't add to busy list if was a RAC_RESET_SLAVE */
	    if ((pnode->txMsg.cmd == RAC_RESET_SLAVE) && (pnode->txMsg.tasks == 0))
	    { /* Finish the transaction here if was a RAC_RESET_SLAVE */
#ifdef BB_SUPER_DEBUG
              BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_RESET);
#endif
              if (bbDebug)
                printf("xvmeTxTask(%d): RAC_RESET_SLAVE sent, resetting node %d\n", link, pnode->txMsg.node);
  
	      pnode->status = BB_OK;
  
    	      if (pnode->finishProc != NULL)
    	      {
      	        if (bbDebug>4)
	          printf("xvmeTxTask(%d): invoking callbackRequest\n", link);
	      
      	        callbackRequest(pnode); /* schedule completion processing */
    	      }
  	      else
	      {
    	        /* If there is a semaphore for synchronous I/O, unlock it */
    	        if (pnode->psyncSem != NULL)
      	          semGive(*(pnode->psyncSem));
  	      }

              /* Wait for last NODE_OFFLINE to finish (if still pending) */
              semTake(resetNodeSem, WAIT_FOREVER);
  
	      /* have to reset the master so it won't wait on a response */
	      resetNodeData = pnode->txMsg.node; /* mark the node number */
	      semTake(pBBLink[link]->queue[BB_Q_HIGH].sem, WAIT_FOREVER);
	      listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), &resetNode);
	      semGive(pBBLink[link]->queue[BB_Q_HIGH].sem);

	      /* taskDelay(15); */ 	/* wait while bug is resetting */
	    }
	    else
	    {
	      /* Lock the busy list */
	      semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
  
              /* set the retire time */
              pnode->retire = tickGet();
	      if (pnode->ageLimit)
	        pnode->retire += pnode->ageLimit;
	      else
		pnode->retire += BB_DEFAULT_RETIRE_TIME * sysClkRateGet();
    
	      if (pBBLink[link]->busyList.head == NULL)
	        dogStart = 1;
              else
	        dogStart = 0;
  
              /* Add pnode to the busy list */
              listAddTail(&(pBBLink[link]->busyList), pnode);
  
              /* Count the outstanding messages */
              (pBBLink[link]->deviceStatus[pnode->txMsg.node])++;
    
	      semGive(pBBLink[link]->busyList.sem);
  
              /* If just added something to an empty busy list, start the dog */
              if (dogStart)
              {
                now = tickGet();
                wdStart(pBBLink[link]->watchDogId, pBBLink[link]->busyList.head->retire - now, xvmeTmoHandler, link);
              }
	    }

	    /* Tell the 8044 to fire out the message now */
            pBBLink[link]->l.XycomLink.bbRegs->cmnd = BB_SEND_CMD; /* forward it now */
	  }
	  else
	  { /* Aborted transmission operation, re-queue the message */
	    semTake(pBBLink[link]->queue[BB_Q_HIGH].sem, WAIT_FOREVER);
	    listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), pnode);
	    semGive(pBBLink[link]->queue[BB_Q_HIGH].sem);
#ifdef BB_SUPER_DEBUG
            BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_ABORT);
#endif
	    if (bbDebug)
	    {
	      printf("Message in progress when abort issued:\n");
	      drvBitBusDumpMsg(&pnode->txMsg);
	    }
	  }
/* BUG -- I don't really need this */
	/* break;*/			/* stop checking the fifo queues */
        }
        else
        { /* we have no xacts that can be processed at this time */
	  semGive(pBBLink[link]->queue[prio].sem);
        }
     	prio--;				/* look at the next prio queue */
      }
    }
  }
}

/******************************************************************************
 *
 * This gets called by the transmit task if it gets stuck waiting to send a 
 * byte to the transmit fifo.
 *
 ******************************************************************************/
STATIC int txStuck(int link)
{
  if (bbDebug)
    printf("bitbus transmitter task stuck, resetting link %d\n", link);

  bbReset(link);
  while (pBBLink[link]->abortFlag == 0)
    taskDelay(1);

  return(OK);
}

/******************************************************************************
 *
 * This function is called by user programs to queue an I/O transaction request
 * for the BB driver.  It is the only user-callable function provided in this
 * driver.
 *
 ******************************************************************************/
STATIC long qBBReq(struct  dpvtBitBusHead *pdpvt, int prio)
{
  static linkErrFlags[BB_NUM_LINKS];    /* Supposedly init'd to zero */
  char	message[200];

  if ((prio < 0) || (prio >= BB_NUM_PRIO))
  {
    sprintf(message, "invalid priority requested in call to qbbreq(%p, %d)\n", pdpvt, prio);
    errMessage(S_BB_badPrio, message);
    return(ERROR);
  }
  if (checkLink(pdpvt->link) == ERROR)
  {
    if (pdpvt->link >= BB_NUM_LINKS)
    {
      sprintf(message, "qbbreq(%p, %d) %d\n", pdpvt, prio, pdpvt->link);
      errMessage(S_BB_badlink, message);
    }
    else if (linkErrFlags[pdpvt->link] == 0)
    { /* Anti-message swamping check */
      linkErrFlags[pdpvt->link] = 1;
      sprintf(message, "qbbreq(%p, %d) %d... card not present\n", pdpvt, prio, pdpvt->link);
      errMessage(S_BB_badlink, message);
    }
    return(ERROR);
  }

#ifdef BB_SUPER_DEBUG
  BBSetHistEvent(pdpvt->link, pdpvt, XACT_HIST_STATE_QUEUE);
#endif

  if (bbDebug>5)
    printf("qbbreq(%p, %d): transaction queued\n", pdpvt, prio);
  if (bbDebug>6)
    drvBitBusDumpMsg(&(pdpvt->txMsg));

  semTake(pBBLink[pdpvt->link]->queue[prio].sem, WAIT_FOREVER);

  /* Add to the end of the queue of waiting transactions */
  listAddTail(&(pBBLink[pdpvt->link]->queue[prio]), pdpvt);

  semGive(pBBLink[pdpvt->link]->queue[prio].sem);

  semGive(pBBLink[pdpvt->link]->linkEventSem);

  return(OK);
}


/******************************************************************
 *
 * Reset hook function... takes down ALL the bitbus links and leaves
 * them down.
 *
 ******************************************************************/
STATIC void BBrebootFunc(void)
{
  int i;

  for (i=0; i<BB_NUM_LINKS; i++)
  {
    if (checkLink(i) == OK)
      bbKill(i);
  }
  taskDelay(sysClkRateGet());   /* Give it a second in case prints something */
  return;
}

/*******************************************************************
 *
 * Same as bbReset() but it takes the link down and leaves it down.
 *
 *******************************************************************/
int bbKill(int link)
{
  if (checkLink(link) != ERROR)
  {
    pBBLink[link]->nukeEm = 2;
    semGive(pBBLink[link]->watchDogSem);
  }
  else
    printf("Link %d not installed.\n", link);

  return(0);
}


/******************************************************************
 *
 * Super debugging facility.
 *
 ******************************************************************/
int
drvBitBusDumpMsg(struct bitBusMsg *pbbMsg)
{
   char	ascBuf[15];	/* for ascii xlation part of the dump */
   int	x;
   int	y;
   int	z;

   printf("Link 0x%4.4X, length 0x%2.2X, route 0x%2.2X, node 0x%2.2X, tasks 0x%2.2X, cmd %2.2X\n", pbbMsg->link, pbbMsg->length, pbbMsg->route, pbbMsg->node, pbbMsg->tasks, pbbMsg->cmd);

  x = BB_MSG_HEADER_SIZE;
  y = pbbMsg->length;
  z = 0;

  while (x < y)
  {
    printf("%2.2X ", pbbMsg->data[z]);
    ascBuf[z] = pbbMsg->data[z];

    if (!((ascBuf[z] >= 0x20) && (ascBuf[z] <= 0x7e)))
      ascBuf[z] = '.';

    x++;
    z++;
  }

  while (x < BB_MAX_MSG_LENGTH)
  {
    printf("   ");
    x++;
  }

  ascBuf[z] = '\0';
  printf("   *%s*\n", ascBuf);
  return(OK);
}

#ifdef BB_SUPER_DEBUG
/*
 * Place an event into the history buffer.
 */
static int BBSetHistEvent(int link, struct dpvtBitBusHead *pXact, int State)
{
  semTake(pBBLink[link]->History.sem, WAIT_FOREVER);


  pBBLink[link]->History.Num++;

  pBBLink[link]->History.Xact[pBBLink[link]->History.Next].Time = tickGet();
  pBBLink[link]->History.Xact[pBBLink[link]->History.Next].State = State;
  if (pXact != NULL)
    memcpy (&pBBLink[link]->History.Xact[pBBLink[link]->History.Next].Xact, pXact,  sizeof(struct dpvtBitBusHead));

  if (link==bbDebugLink && pBBLink[link]->History.Xact[pBBLink[link]->History.Next].Xact.txMsg.node==bbDebugNode)
    BBDumpXactHistory(&pBBLink[link]->History.Xact[pBBLink[link]->History.Next]);

  if (++pBBLink[link]->History.Next == BB_SUPER_DEBUG_HIST_SIZ)
    pBBLink[link]->History.Next = 0;

  semGive(pBBLink[link]->History.sem);
  return(0);
}

int BBHistDump(int link)
{
  int	count;
  int	ix;

  if (checkLink(link) == ERROR)
  {
    printf(" Link %d is not valid\n", link);
    return(-1);
  }
  printf("Dumping bitbus history for link %d\n", link);
  semTake(pBBLink[link]->History.sem, WAIT_FOREVER);

  if (pBBLink[link]->History.Num < BB_SUPER_DEBUG_HIST_SIZ)
  {
    count = pBBLink[link]->History.Num;
    ix = 0;
  }
  else
  {
    count = BB_SUPER_DEBUG_HIST_SIZ;
    ix = pBBLink[link]->History.Next;
  }

  while(count)
  {
    printf("%8.8d:", pBBLink[link]->History.Xact[ix].Time);

    if (BBDumpXactHistory(&pBBLink[link]->History.Xact[ix]) != 0)
      count = 0;
    else
      --count;

    if (++ix >= BB_SUPER_DEBUG_HIST_SIZ)
      ix = 0;
  }

  semGive(pBBLink[link]->History.sem);
  return(0);
}
STATIC int BBDumpXactHistory(XactHistStruct *pXact)
{
    switch (pXact->State)
    {
    /* No BB message present */
    case XACT_HIST_STATE_RESET:		
      printf("Link Reset:\n");
      break;
    case XACT_HIST_STATE_RX_ABORT:
      printf("Link RX aborted\n");
      break;
    case XACT_HIST_STATE_TX_ABORT:
      printf("Link TX aborted\n");
      break;
    case XACT_HIST_STATE_RX_URCMD:
      printf("Link RX got unexpected RCMD\n");
      break;

    /* TX message only */
    case XACT_HIST_STATE_QUEUE:
      printf("Message Queued:\n");
      goto do_tx_only;
    case XACT_HIST_STATE_TX:
      printf("Message Transmitted:\n");
      goto do_tx_only;
    case XACT_HIST_STATE_WD_TIMEOUT:
      printf("Watchdog Timeout:\n");
      goto do_tx_only;
    case XACT_HIST_STATE_TX_STUCK:
      printf("Transmitter stuck:\n");
      goto do_tx_only;
    case XACT_HIST_STATE_TX_RESET:
      printf("TX just sent a RAC RESET:\n");

    do_tx_only:
      drvBitBusDumpMsg(&pXact->Xact.txMsg);
      break;

    /* RX message only */
    case XACT_HIST_STATE_RX_UNSOLICITED:
      printf("RX unsolicited message:\n");
      drvBitBusDumpMsg(&pXact->Xact.rxMsg);
      break;

    /* TX and RX messages present */
    case XACT_HIST_STATE_RX:
      printf("RX response... complete transaction:\n");
      drvBitBusDumpMsg(&pXact->Xact.txMsg);
      drvBitBusDumpMsg(&pXact->Xact.rxMsg);
      break;

    default:
      printf("Corrupt bitbus debug state!\n");
      return(-1);
    }
  return(0);
}
#endif

/******************************************************************************
 *
 * A user callable function that may be used to reset the specified slave
 * node.
 *
 * This routine is NOT reenterant but contains a monitor so that it always
 * operates properly.
 *
 ******************************************************************************/
int ResetBug(int node, int link)
{
  static int            FirstTime = 1;
  static SEM_ID         monLock;
  static SEM_ID         syncSem;        /* Semaphore to use for completion */
  struct dpvtBitBusHead resetNode;      /* actual message to send out */
  unsigned char         resetNodeData;  /* 1-byte data field for RAC_RESET */

  if (FirstTime)
  {
    monLock = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
    syncSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
    FirstTime = 0;
  }

  semTake(monLock, WAIT_FOREVER);

  resetNode.finishProc = NULL;  /* no callback routine used */
  resetNode.psyncSem = &syncSem;/* do a semGive on this SEM when done sending */
  resetNode.link = link;        /* which bitbus link to send message out on */
  resetNode.rxMaxLen = 7;       /* Chop off the response... we don't care */
  resetNode.ageLimit = sysClkRateGet()*2;

  resetNode.txMsg.length = 8;
  resetNode.txMsg.route = BB_STANDARD_TX_ROUTE;
  resetNode.txMsg.node = node;
  resetNode.txMsg.tasks = 0x0;
  resetNode.txMsg.cmd = RAC_RESET_SLAVE;
  resetNode.txMsg.data = &resetNodeData;

  qBBReq(&resetNode, 0);
  semTake(syncSem, WAIT_FOREVER);
  semGive(monLock);

  return(0);
}
/******************************************************************
 * FUNCTION:    pepRxTask()
 * PURPOSE :    This function is started as a task during driver init
 *              time. It's purpose is to read messages off the receive
 *              FIFO. After a complete message is read, the soliciting
 *              message's completion routines are started by the use
 *              of semaphores and/or a callbackRequest().
 * ARGS IN :    link
 * ARGS OUT:    none
 * GLOBALS:     none
 ******************************************************************/
STATIC int
pepRxTask(int link)
{
  int           rxState;       /* current state of the receiver */
#define BBRX_HEAD	1
#define	BBRX_DATA	2
#define BBRX_RCMD	3
#define	BBRX_IGN	4

  unsigned char rxHead[7];      /* room for header of current rx msg */
  unsigned char *rxMsg;         /* where to place next byte (after header) */
  int           rxTCount;       /* byte counter for data in rxHead */
  unsigned char	ch;
  struct dpvtBitBusHead *rxDpvtHead;  /* for message currently receiving */

  struct dpvtBitBusHead UselessMsg;   /* to hold unsolicited responses */
  unsigned char		UselessData[BB_MAX_MSG_LENGTH];

  int		lockKey;		/* used for intLock calls */
  int		packageComplete;	/* indicates end of package read */

  rxMsg = (unsigned char *) NULL;
  rxState = BBRX_HEAD;
  rxTCount = 0;
  rxDpvtHead = (struct dpvtBitBusHead *) NULL;
  packageComplete = 0;

  /* Dummy up the UselessMsg fields so we can use it */
  UselessMsg.rxMaxLen = BB_MAX_MSG_LENGTH;
  UselessMsg.rxMsg.data = UselessData;
  UselessMsg.link = link;
  UselessMsg.ageLimit = 0;
  UselessMsg.retire = 0;

  while (1) 
  {
    /* Wait for RX interrupts, but only if no chars are ready */
    if ((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & PEP_BB_RFNE) == 0) 
    {
      /* Enable interrupts and check again in case PB-BIT blew it */
      lockKey = intLock();
      pBBLink[link]->l.PepLink.bbRegs->int_vec = PEP_BB_IVEC_BASE +(link*2);
      intUnlock(lockKey);
      
      while (((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & PEP_BB_RFNE) == 0) 
	     && (pBBLink[link]->abortFlag == 0)) 
      {
        /* Re-enable ints here each time in case board got reset */
	lockKey = intLock();
        pBBLink[link]->l.PepLink.bbRegs->int_vec = PEP_BB_IVEC_BASE +(link*2);
	intUnlock(lockKey);
	
	if (bbDebug>35)
	  printf("pepRxTask(%d): waiting on IRQ\n", link);

	/* wait for data bytes */
        semTake(pBBLink[link]->l.PepLink.rxInt, WAIT_FOREVER); 
      }
      /* Disable RX Interrupts (prevents unnecessary context switching) */
      lockKey = intLock();
      pBBLink[link]->l.PepLink.bbRegs->int_vec = 0;
      intUnlock(lockKey);
    }

    if (pBBLink[link]->abortFlag == 0) 
    {
      /* READ ONE CHAR FROM RECEIVE FIFO */
      ch = pBBLink[link]->l.PepLink.bbRegs->data;

      /* check to see if we got a data byte or a command byte */
      if ((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & PEP_BB_RCMD) == PEP_BB_RCMD)
	packageComplete = 1;
      
      switch (rxState) {
      case BBRX_HEAD:	/* getting the head of a new message */
	if (rxTCount > 1)  /* Toss the 2 PEP specific header bytes */
	  rxHead[rxTCount] = ch;
	if (bbDebug>21)
	  printf("pepRxTask(%d): >%2.2X< (Header)\n", link, ch);
	
	if (++rxTCount == 7) 
	{
	  /* find the message this is a reply to */
	  /* rxTCount += 2; PEP header bytes already messed up the count (jrw)*/
	  
	  /* Lock the busy list */
	  semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
	  
	  rxDpvtHead = pBBLink[link]->busyList.head;
	  while (rxDpvtHead != NULL) {
	    if (bbDebug>19)
	      printf("pepRxTask(%d): checking reply against %p\n",
		     link, rxDpvtHead);
	    
	    /* see if node's match */
	    if (rxDpvtHead->txMsg.node == rxHead[4]) {
	      /* see if the tasks match */
	      /* if (rxDpvtHead->txMsg.tasks == rxHead[5]) */
	      {
		/* They match, finish putting response into the rxMsg buffer */
		if (bbDebug>4)
		  printf("pepRxTask(%d): reply to %p\n", 
			 link, rxDpvtHead);
		
		/* Delete the node from the list */
		listDel(&(pBBLink[link]->busyList), rxDpvtHead);
		
		/* If busy list is empty, stop the dog */
		if (pBBLink[link]->busyList.head == NULL)
		  wdCancel(pBBLink[link]->watchDogId);
		
		if (rxHead[6] == 0x91)
		{ /* something bad happened... inject a delay to the */
		  /* requested timeout duration. */
		  if (bbDebug)
		    printf("pepRxTask(%d): 0x91 from node %d, invoking synthetic delay\n", link, rxHead[4]);
		  (pBBLink[link]->syntheticDelay[rxDpvtHead->txMsg.node]) = rxDpvtHead->retire;
		  pBBLink[link]->DelayCount++;
		}
		else
		{ /* decrement the number of outstanding messages to the node */
		  (pBBLink[link]->deviceStatus[rxDpvtHead->txMsg.node])--;
		}

		/* Wake up Link Task in case was waiting on "this" node */
		semGive(pBBLink[link]->linkEventSem); 
#if 0
		semGive(pXvmeLink[link]->pbbLink->busyList.sem);
		
		rxDpvtHead->rxMsg.length = rxHead[2];
		rxDpvtHead->rxMsg.route = rxHead[3];
		rxDpvtHead->rxMsg.node = rxHead[4];
		rxDpvtHead->rxMsg.tasks = rxHead[5];
		rxDpvtHead->rxMsg.cmd = rxHead[6];
		rxMsg = rxDpvtHead->rxMsg.data;
		
		rxDpvtHead->status = BB_OK;	/* OK, unless BB_LENGTH */
		rxState = BBRX_DATA;		/* finish reading till RCMD */
#endif
		break;                          /* get out of the while() */
	      }
	    }
	    rxDpvtHead = rxDpvtHead->next; /* Keep looking */
	  }
	  
	  semGive(pBBLink[link]->busyList.sem);

	  /* Couldn't find a match... fake one so can print the bad message */
	  if (rxDpvtHead == NULL)
	    rxDpvtHead = &UselessMsg;

	  rxDpvtHead->rxMsg.length = rxHead[2];
	  rxDpvtHead->rxMsg.route = rxHead[3];
	  rxDpvtHead->rxMsg.node = rxHead[4];
	  rxDpvtHead->rxMsg.tasks = rxHead[5];
	  rxDpvtHead->rxMsg.cmd = rxHead[6];
	  rxMsg = rxDpvtHead->rxMsg.data;

	  rxDpvtHead->status = BB_OK;     /* OK, unless BB_LENGTH */
	  rxState = BBRX_DATA;            /* finish reading till RCMD */
	}
	break;
	
      case BBRX_DATA:	/* finish reading data portion of message */
	if (rxTCount >= rxDpvtHead->rxMaxLen) {
	  rxState = BBRX_IGN;	/* toss the rest of the data */
	  rxDpvtHead->status = BB_LENGTH; /* set driver status */
	  if (bbDebug>22)
	    printf("pepRxTask(%d): %2.2X (Ignored)\n", link, ch);
	  
	}
	else {
	  *rxMsg = ch;
	  if (bbDebug>22)
	    printf("pepRxTask(%d): %2.2X (Data)\n", link, ch);
	  rxMsg++;
	  rxTCount++;
	}
	break;
	
      case BBRX_IGN:
	if (bbDebug>22)
	  printf("pepRxTask(%d): %2.2X (Ignored)\n", link, ch);
	break;
      }

      if (packageComplete) 
      {
	if (rxDpvtHead == NULL) 
	{
	  if (bbDebug > 22)
	    printf("pepRxTask(%d): got unexpected completion status\n", link);
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, NULL, XACT_HIST_STATE_RX_URCMD);
#endif
	}
	else if (rxDpvtHead == &UselessMsg)
	{
          rxDpvtHead->status = BB_OK;
          if (bbDebug)
          {
            printf("pepRxTask(%d): msg from node %d unsolicited:\n", link, rxDpvtHead->rxMsg.node);
            drvBitBusDumpMsg(&(rxDpvtHead->rxMsg));
          }
#ifdef BB_SUPER_DEBUG
            BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX_UNSOLICITED);
#endif
	}
	else 
	{
	  rxDpvtHead->status = BB_OK;
	  if (bbDebug>24)
	    printf("pepRxTask(%d): RX command byte = %2.2X\n", link, ch);

#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX);
#endif
	  
	  if (rxDpvtHead->finishProc != NULL) 
	  {
	    if (bbDebug>8)
	      printf("pepRxTask(%d): invoking the callbackRequest\n", link);
	    callbackRequest(rxDpvtHead); /* schedule completion processing */
	  }
	  else
	  {
	    /* If there is a semaphore for synchronous I/O, unlock it */
	    if (rxDpvtHead->psyncSem != NULL)
	      semGive(*(rxDpvtHead->psyncSem));
	  }
	}
	/* Reset the state of the RxTask to expect a new message */
	rxMsg = (unsigned char *) NULL;
	rxState = BBRX_HEAD;
	rxTCount = 0;
	rxDpvtHead = (struct dpvtBitBusHead *) NULL;
	packageComplete = 0;
      }
    }
    else 
    {
      /* Link abort state is active reset receiver link status now */
      if (rxDpvtHead != NULL) 
      {
	/* This xact is not on the busy list, put it back on */
	semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
	(pBBLink[link]->deviceStatus[rxDpvtHead->txMsg.node])++;
	listAddTail(&(pBBLink[link]->busyList), rxDpvtHead);
	semGive(pBBLink[link]->busyList.sem);
      }
#ifdef BB_SUPER_DEBUG
      BBSetHistEvent(link, rxDpvtHead, XACT_HIST_STATE_RX_ABORT);
#endif

      rxMsg = (unsigned char *) NULL;
      rxState = BBRX_HEAD;
      rxTCount = 0;
      rxDpvtHead = (struct dpvtBitBusHead *) NULL;
      
      /* Tell the watch dog I am ready for the reset (reset in the dog task) */
      pBBLink[link]->rxAbortAck = 1;
      
      if (bbDebug)
        printf("pepRxTask(%d): resetting due to abort status\n", link);
      
      /* wait for link state to become active again */
      while (pBBLink[link]->abortFlag != 0)
	taskDelay(RESET_POLL_TIME);
      
      if (bbDebug)
        printf("pepRxTask(%d): restarting after abort\n", link);
    }
  }
}

/******************************************************************
 * FUNCTION:    pepWdTask()
 * PURPOSE :    
 *              
 * ARGS IN :    none
 * ARGS OUT:    none
 * GLOBALS:     none
 ******************************************************************/
STATIC int pepWdTask(int link)
{
  struct dpvtBitBusHead *pnode;
  struct dpvtBitBusHead *npnode;
  unsigned      long    now;

#ifdef PEP_DO_RESET_AND_OFFLINE
  SEM_ID		syncSem;
  
  struct dpvtBitBusHead resetNode;
  unsigned char         resetNodeData;   /* 1-byte data field for RAC_RESET */
  unsigned char         responseData;

  struct dpvtBitBusHead offlnNode;
  unsigned char         offlnNodeData;   /* 1-byte data field for RAC_OFFLN */
  unsigned char         response1Data;   /* 1-byte response data field */
  
  /* init the SEM used when sending the RAC_OFFLNE message */
  syncSem = semBCreate(SEM_Q_PRIORITY,SEM_EMPTY);

  /*
   * Hand-craft a RAC_RESET and RAC_OFFLINE message to use when 
   * a message times out.
   * NOTE that having only one copy is OK provided that the dog waits for
   * a response before sending it again!
   */
  resetNode.finishProc = NULL;	/* no callback routine used */
  resetNode.psyncSem = NULL;
  resetNode.link = link;	/* which bitbus link to send message out on */
  resetNode.rxMaxLen = 10; 
  resetNode.ageLimit = sysClkRateGet()*100; /* make sure this never times out */

  resetNode.txMsg.length = 8;
  resetNode.txMsg.route = BB_STANDARD_TX_ROUTE;
  resetNode.txMsg.tasks = 0x0;
  resetNode.txMsg.data = &resetNodeData;
  resetNode.rxMsg.data = &responseData;

  offlnNode.finishProc = NULL;	/* no callback routine used */
  offlnNode.psyncSem = &syncSem;/* do a semGive on this SEM when responded to*/
  offlnNode.link = link;	/* which bitbus link to send message out on */
  offlnNode.rxMaxLen = 10; 
  offlnNode.ageLimit = sysClkRateGet()*100; /* make sure this never times out */

  offlnNode.txMsg.length = 8;
  offlnNode.txMsg.route = BB_STANDARD_TX_ROUTE;
  offlnNode.txMsg.tasks = 0x0;
  offlnNode.txMsg.data = &offlnNodeData;
  offlnNode.rxMsg.data = &response1Data;
#endif

  pBBLink[link]->nukeEm = 0;		/* Make sure the nuke status is clear */
  while(1) {
    /* SLEEP UNTIL WATCHDOG TIMER ROUTINE GIVES SEMAPHORE */
    semTake(pBBLink[link]->watchDogSem, WAIT_FOREVER);
    now = tickGet();		/* what time is it? */
    
    if (pBBLink[link]->nukeEm != 0)
      printf("Bitbus manual reset being issued on link %d\n", link);
    
    if (bbDebug>4)
      printf("pepWdTask(%d): (Watchdog) checking busy list\n", link);
    
    pBBLink[link]->rxAbortAck = 0;
    pBBLink[link]->txAbortAck = 0;
    
    if (pBBLink[link]->nukeEm != 0) {
      /* set abort status and wait for the abort acks */
      pBBLink[link]->abortFlag = 1;
      
      /* wake up the Tx task so it can observe the abort status */
      semGive(pBBLink[link]->linkEventSem);
      
      /* wake up the Rx task so it can observe the abort status */
      semGive(pBBLink[link]->l.PepLink.rxInt);
      
      /* sleep until abort ack from Tx & Rx tasks */
      while ((pBBLink[link]->rxAbortAck == 0) && 
	     (pBBLink[link]->txAbortAck == 0))
	taskDelay(RESET_POLL_TIME);

#ifdef BB_SUPER_DEBUG
      if (pBBLink[link]->nukeEm < 2)
      { /* Do a history dump since this is not supposed to happen */
        BBHistDump(link);
      }
#endif
    }
    /*
     * Run thru entire busy list to see if there are any transactions
     * that have been waiting on a response for too long a period.
     */
    semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
    pnode = pBBLink[link]->busyList.head;
    
    while (pnode != NULL) {
      npnode = pnode->next;   /* remember where we were in the list */
      
      if ((pBBLink[link]->nukeEm != 0) || (pnode->retire <= now)) {
	/* Get rid of the request and set error status etc... */
	listDel(&(pBBLink[link]->busyList), pnode);
	
#ifdef BB_SUPER_DEBUG
        BBSetHistEvent(link, pnode, XACT_HIST_STATE_WD_TIMEOUT);
#endif
	if (bbDebug)
	{
	  printf("pepWdTask(%d): TIMEOUT on bitbus message:\n", link);
	  drvBitBusDumpMsg(&pnode->txMsg);
	}
	
	/* BUG -- do this here or defer until RX gets a response? */
        (pBBLink[link]->deviceStatus[pnode->txMsg.node])--; /* fix device status */
	pnode->status = BB_TIMEOUT;
	
#ifdef PEP_DO_RESET_AND_OFFLINE
	/* Gotta do this now in case we need the info after the completion */
	resetNode.txMsg.node = pnode->txMsg.node;
	offlnNodeData = pnode->txMsg.node;  /* mark the node number */
#endif
	/* Make the callbackRequest if one was spec'd */
	if(pnode->finishProc != NULL)
	{
	    if (bbDebug>2)
	    {
	      printf("pepWdTask(%d): invoking the callbackRequest %p %d\n", link, pnode->finishProc, pnode->priority);
	    }
	  callbackRequest(pnode);     /* schedule completion processing */
	}
	else
	{
	  /* Release a completion lock if one was spec'd */
	  if (pnode->psyncSem != NULL && pBBLink[link]->nukeEm == 0)
	    semGive(*(pnode->psyncSem));
	}

#ifdef PEP_DO_RESET_AND_OFFLINE
        /* If we are not going to reboot the link... */
        if ( pBBLink[link]->nukeEm == 0 ) {
	  /* Send out a RAC_RESET/RAC_OFFLINE pair */
	  semGive(pBBLink[link]->busyList.sem);

	  /* Configure message for a RAC_RESET */
	  resetNode.txMsg.cmd = 0x00;

	  /* Configure message for a RAC_OFFLINE */
	  offlnNode.txMsg.cmd = RAC_OFFLINE;
	  offlnNode.txMsg.node = 0xff;

	  /* Queue the messages (high priority, reset first) */

	  semTake(pBBLink[link]->queue[BB_Q_HIGH].sem, WAIT_FOREVER);
	  listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), &offlnNode); 
/*	  listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), &resetNode);  */
	  semGive(pBBLink[link]->queue[BB_Q_HIGH].sem);

	  semGive(pBBLink[link]->linkEventSem); /* Tell TxTask to send the messages */

	  if (semTake(syncSem, (sysClkRateGet()) ) == ERROR) 
	  {
	    if (bbDebug)
	      printf("pepWdTask(%d): link dead, trying manual reboot\n", link);

	    pBBLink[link]->nukeEm = 1;
            pBBLink[link]->abortFlag = 1;
            semGive(pBBLink[link]->linkEventSem);
            semGive(pBBLink[link]->rxInt);
	    
            /* sleep until abort ack from Tx & Rx tasks */
            while ((pBBLink[link]->rxAbortAck == 0) && 
		   (pBBLink[link]->txAbortAck == 0))
	      taskDelay(RESET_POLL_TIME);
	  }

	  /* Start over since released the busy list */
	  semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
          npnode = pBBLink[link]->busyList.head;
	}
#endif

      }
      pnode = npnode;
    }
    
    /* Finish the link reboot if necessary */
    if (pBBLink[link]->nukeEm != 0) 
    {
      /* shut down the bitbus card. */
      pepReset(link);

      if (pBBLink[link]->nukeEm == 2)
      {
	/* 
	 * Stop the watchdog task so the link stays dead.
	 * Since the busy list is locked and the nukeEm flag is still set,
	 * this link can not do any more work.
	 */
	abort();
      }

      /* clear the abort_flag */
      pBBLink[link]->abortFlag = 0;
      
      pBBLink[link]->nukeEm = 0;
    }

    if (pBBLink[link]->busyList.head != NULL) {
      /* Restart the dog timer */
      if (bbDebug>5)
	printf("pepWdTask(%d): restarting watch dog timer\n", link);
      
      wdStart(pBBLink[link]->watchDogId, pBBLink[link]->busyList.head->retire - now,
	      pepTmoHandler, link);
    }

    semGive(pBBLink[link]->busyList.sem);

    /* In case the TX is waiting on a node that just timed out */
    semGive(pBBLink[link]->linkEventSem);
  }
}

/******************************************************************
 * FUNCTION:    pepTxTask()
 * PURPOSE :    This function is started as a task during driver init
 *              time. It's purpose is to keep the link busy with 
 *              outgoing messages. It awaits user-calls to qBBReq()
 *              (new message to send). It also awaits wake up calls
 *              from the Wd timer (a timed out message to a node,
 *              once handled, means Tx is free to send again to that
 *              node).
 *              
 * ARGS IN :    link
 * ARGS OUT:    none
 * GLOBALS:     none
 ******************************************************************/
STATIC int pepTxTask(int link)
{
  struct dpvtBitBusHead	*pnode;
  int			prio;
  int			working;
  int			dogStart;
  int                   stuck;
  
  int			txTCount;
  int			txCCount;
  unsigned char		*txMsg;
  register	int	x;
  unsigned	long	now;
  
  if (bbDebug>1)
    printf("pepTxTask started for link %d\n", link);

  while(1) {
    if (pBBLink[link]->abortFlag != 0) {
      /* let the dog know we are ready */
      pBBLink[link]->txAbortAck = 1;

#ifdef BB_SUPER_DEBUG
      BBSetHistEvent(link, NULL, XACT_HIST_STATE_TX_ABORT);
#endif
      
      if (bbDebug)
        printf("pepTxTask(%d): resetting due to abort status\n", link);
      
      while (pBBLink[link]->abortFlag != 0)
	taskDelay(RESET_POLL_TIME);	/* wait for link to reset */
      
      if (bbDebug)
        printf("pepTxTask(%d): restarting after abort\n", link);
    }
    else
    {
      if (pBBLink[link]->DelayCount)
	semTake(pBBLink[link]->linkEventSem, 2 * sysClkRateGet());
      else
	semTake(pBBLink[link]->linkEventSem, WAIT_FOREVER);
    }

    if (bbDebug>5)
      printf("pepTxTask(%d): got an event\n", link);
    
    working = 1;
    while ((working != 0) && (pBBLink[link]->abortFlag == 0)) {
      working = 0;
      
      prio = BB_NUM_PRIO-1;
      while ((prio >= 0) && (pBBLink[link]->abortFlag == 0)) {
	/* see if the queue has anything in it */
	semTake(pBBLink[link]->queue[prio].sem, WAIT_FOREVER);
	
	if ((pnode = pBBLink[link]->queue[prio].head) != NULL) 
	{
	  now = tickGet();
	  while (pBBLink[link]->deviceStatus[pnode->txMsg.node] == BB_BUSY) 
	  {
	    if ((pBBLink[link]->syntheticDelay[pnode->txMsg.node] != 0)
		&& (pBBLink[link]->syntheticDelay[pnode->txMsg.node] < now))
	    {
	      if (bbDebug)
	        printf("pepTxTask(%d): terminating synthetic idle on node %d\n", link, pnode->txMsg.node);
	      (pBBLink[link]->deviceStatus[pnode->txMsg.node])--;
	      pBBLink[link]->syntheticDelay[pnode->txMsg.node] = 0;
	      pBBLink[link]->DelayCount--;
	    }
	    else
	    {
	      if ((pnode = pnode->next) == NULL)
	        break;
	    }
	  }
	}

	if (pnode != NULL) {  /* have an xact to start processing */
	  working = 1;
	  
	  /* delete the node from the inbound fifo queue */
	  listDel(&(pBBLink[link]->queue[prio]), pnode);
	  
	  semGive(pBBLink[link]->queue[prio].sem);
#ifdef BB_SUPER_DEBUG
          BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX);
#endif

	  
	  if (bbDebug>3)
	    printf("pepTxTask(%d): got xact, pnode=%p\n", link, pnode);
	  
	  /* Send the message in polled mode */
	  txTCount = pnode->txMsg.length;
	  txCCount = 0;
	  txMsg = &(pnode->txMsg.length);

	  while ((txCCount < txTCount) && (pBBLink[link]->abortFlag == 0)) {

	    stuck = 1000;
	    while (((pBBLink[link]->l.PepLink.bbRegs->stat_ctl & PEP_BB_TFNF) 
		    != PEP_BB_TFNF) && 
		   (pBBLink[link]->abortFlag == 0) && 
		   --stuck)
	      for(x=0;x<100;x++);			/* wait for TX ready */

	    if (!stuck)
	    {
#ifdef BB_SUPER_DEBUG
              BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_STUCK);
#endif
	      txStuck(link);
	    }
	    else if (txCCount == 0) {  /* first byte is package type */
	      if (bbDebug>30)
		printf("pepTxTask(%d): outputting %2.2X\n",link,0x81);
	      pBBLink[link]->l.PepLink.bbRegs->data = 0x81;
	    }
	    else if (txCCount == 1) {  /* unused 2nd byte of package */
	      if (bbDebug>30)
		printf("pepTxTask(%d): outputting %2.2X\n",link,0x00);
	      pBBLink[link]->l.PepLink.bbRegs->data = 0x00;
	    }
	    else if (txCCount == (txTCount -1)) { /* last byte of package */
	      pBBLink[link]->l.PepLink.bbRegs->stat_ctl = *txMsg;
	      if (bbDebug>30)
		printf("pepTxTask(%d): outputting last byte %2.2X\n",
		       link,*txMsg);
	    } else {                 /* regular ol' message byte */
	      pBBLink[link]->l.PepLink.bbRegs->data = *txMsg;
	      if (bbDebug>30)
		printf("pepTxTask(%d): outputting %2.2X\n",link,*txMsg);
	      if (txCCount != 6)
		txMsg++;
	      else
		txMsg = pnode->txMsg.data;
	    }
	    txCCount++;
	  }
	  
	  if (pBBLink[link]->abortFlag == 0) {

	    /* don't add to busy list if was a RAC_RESET_SLAVE */
	    if ((pnode->txMsg.cmd == RAC_RESET_SLAVE) && (pnode->txMsg.tasks == 0))
	    {
#ifdef BB_SUPER_DEBUG
              BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_RESET);
#endif
	      if (bbDebug)
	        printf("pepTxTask(%d): RAC_RESET_SLAVE sent\n", link);
	      
	      pnode->status = BB_OK;
	      
	      if (pnode->finishProc != NULL) {
		if (bbDebug>4)
		  printf("pepTxTask(%d): invoking the callbackRequest\n", 
			 link);
		callbackRequest(pnode); /* schedule completion processing */
	      }
    	      else
	      {
	        /* If there is a semaphore for synchronous I/O, unlock it */
	        if (pnode->psyncSem != NULL)
		  semGive(*(pnode->psyncSem));
	      }
	      taskDelay(15);
	    }
	    else
	    {
	      /* Lock the busy list */
	      semTake(pBBLink[link]->busyList.sem, WAIT_FOREVER);
	      
	      /* set the retire time */
	      pnode->retire = tickGet();
	      if (pnode->ageLimit)
	        pnode->retire += pnode->ageLimit;
	      else
		pnode->retire += BB_DEFAULT_RETIRE_TIME * sysClkRateGet();
	      
	      if (pBBLink[link]->busyList.head == NULL)
		dogStart = 1;
	      else
		dogStart = 0;
	      
	      /* Add pnode to the busy list */
	      listAddTail(&(pBBLink[link]->busyList), pnode);
	      
	      /* Count the outstanding messages */
	      (pBBLink[link]->deviceStatus[pnode->txMsg.node])++;
	      
	      semGive(pBBLink[link]->busyList.sem);
	      
	      /* If something just added to empty busy list, start the dog */
	      if (dogStart) {
		now = tickGet();
		wdStart(pBBLink[link]->watchDogId, 
			pBBLink[link]->busyList.head->retire - now, 
			pepTmoHandler, link);
	      }
	    }
	  } else {  /* if abortFlag != 0 */
	    /* Aborted transmission operation, re-queue the message */
	    semTake(pBBLink[link]->queue[BB_Q_HIGH].sem, WAIT_FOREVER);
	    listAddHead(&(pBBLink[link]->queue[BB_Q_HIGH]), pnode);
	    semGive(pBBLink[link]->queue[BB_Q_HIGH].sem);
#ifdef BB_SUPER_DEBUG
            BBSetHistEvent(link, pnode, XACT_HIST_STATE_TX_ABORT);
#endif
	  }

	} else {  /* if pnode == NULL */
	  /* we have no xacts that can be processed at this time */
	  semGive(pBBLink[link]->queue[prio].sem);
	}
	prio--;				/* look at the next prio queue */
      }
    }
  }
}
/******************************************************************
 * FUNCTION:    dumpStat()
 * PURPOSE :    Temporary function. Shows contents of status
 *              register on PB-BIT module corresponding to link.
 * ARGS IN :    link
 * ARGS OUT:    none
 * GLOBALS:     none
 ******************************************************************/
int pepDumpStat(int link)
{
  unsigned char stat_ctl;

  stat_ctl = pBBLink[link]->l.PepLink.bbRegs->stat_ctl;

  printf("stat_ctl reg: %2X\n",stat_ctl);
  return(OK);
}
