/*************************************************************************
 *
 * drvGpib.c,v (This file is best viewed with a tabwidth of 4)
 *
 ******************************************************************************/
/***********************************************************************
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory, and the Regents of the University of
* California, as Operator of Los Alamos National Laboratory, and
* Berliner Elektronenspeicherring-Gesellschaft m.b.H. (BESSY).
* gpibCore is distributed subject to a Software License Agreement
* found in file LICENSE that is included with this distribution.
***********************************************************************/
/*
 * Generic GPIB driver (protocol layer)
 *
 * Current Author: Benjamin Franksen
 * Original Author: John Winans
 */
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <dbDefs.h>
#include <taskwd.h>
#include <epicsInterrupt.h>
#include <epicsRingBytes.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <cantProceed.h>
#include <epicsAssert.h>
#include <epicsExport.h>
#include <drvSup.h>

#include "drvHwGpibInterface.h"
#include "drvGpibInterface.h"

#define ibRelease "1.0"
#define false 0
#define true 1
#define bool int

/* dubug message stuff */
#ifdef DEBUG
#define debug(level,arg) if(ibDebug >= level) printf arg ;
#define srqdebug(level,arg) if(ibDebug >= level) printf arg ;
#else
#define debug(level,arg)
#define srqdebug(level,arg)
#endif
#define addrFromPrimarySecondary(primary,secondary) \
  (((secondary)==IBNOSECONDARY) ? (primary) : ((primary)*100 + (secondary)))

/* srqStatus represents the items that are placed on the ibLink->srqRing.*/
typedef struct srqStatus {
    int device;	         /* device number */
    int secondary;	 /* device number */
    unsigned char status;/* data from the srq poll */
}srqStatus;

/* ibLinkDevice is for a device on a link*/
struct ibLinkDevice {
    unsigned long timeStamp;	/* for srq timeout handling */
    srqHandlerFunc srqHandler;	/* registered srq handlers for link */
    void *srqParm;	        /* parms to pass to the srq handlers */
    char deviceStatus;	        /* mark device as idle or busy */
    signed char pollInhibit;	/* mark device as 0=pollable, 1=inhibited */
    char logdata;	        /* mark device for logging */
    /* papsecondaryDevice pointer to array of pointers to struct ibLinkDevice */
    /* storage only allocated if secondary device is addressed */
    struct ibLinkDevice **papsecondaryDevice;
};

/* workRequest for registerSrqCallback */
typedef struct registerSrqReq {
    dpvtGpibHead head;
    epicsEventId syncEvent;
    srqHandlerFunc handler;
    void *parm;
}registerSrqReq;

/* structure to provide mutexes for multiple reader / single writer access. */
typedef struct RWAccessRec {
    epicsMutexId dataMtx;
    epicsMutexId ctrMtx;
    int nReaders;
}RWAccessRec;

/* macros to operate on an RWAccess structure */
#define reader_in(acc) {\
	/* lock counter of readers */\
	epicsMutexLock((acc)->ctrMtx);\
	/* if we are the first reader, lock the data */\
	if( 0 == (acc)->nReaders ) epicsMutexLock((acc)->dataMtx);\
	/* one more reader */\
	(acc)->nReaders++;\
	epicsMutexUnlock((acc)->ctrMtx);\
	}
#define reader_out(acc) {\
	/* lock counter of readers and decrement it */\
	epicsMutexLock((acc)->ctrMtx);\
	(acc)->nReaders--;\
	/* if we are the last reader, unlock data; writers may now lock it */\
	if( 0 == (acc)->nReaders ) epicsMutexUnlock((acc)->dataMtx);\
	epicsMutexUnlock((acc)->ctrMtx);\
	}
#define writer_in(acc)  epicsMutexLock((acc)->dataMtx)
#define writer_out(acc) epicsMutexUnlock((acc)->dataMtx)
/* tstraumann:  NOTE: the linked list of tasks 'ibLinks' allows now for
 *	single writer / multiple reader access.
 *	Note that NO ITEMS MUST BE DELETED from this list, because locking
 *	the items is not implemented - only locking the list of items.
 */
static struct {
    ibLink *root;
    epicsMutexId newLnkMtx;
    RWAccessRec access;
}ibLinks;

/* global variables */

/* debug flags */
int ibDebug = 0;	/* turns on debug messages from this driver */
int ibSrqDebug = 0;	/* turns on ONLY srq related debug messages */
/* configuration flags (1 or 0) */
int ibSrqLock = 0;	/* stop ALL srq checking & polling */
int ibTimeoutSquelch = 0;	/* if true suppress timeout messages */

/* configuration parameters */
int ibSrqTimeout = 2;	/* # of seconds to wait for (solicited) SRQs */
int ibSrqRingSize = 50;	/* max # of events stored in the SRQ event ring */
int ibSrqPollTimeout = 100;	/* # of msec to wait for poll response */
double ibSrqPollRate = 1.0;	/* seconds between srqStatus requests */

/* internal variables */
/* NOTE: NOT SMP SAFE!!!!! */
static int initDone = 0;	/* tstraumann:  support lazy init */
static int defaultTimeout = 0;	/* in ms, for gpib io */

/* drvGpibEt methods */
static long drvGpibReport(int level);
static long drvGpibInit(void);
static ibLink *drvGpibGetLink(int link, int gpibAddr, int secondary);
static long drvGpibQReq(dpvtGpibHead * pdpvt, int prio);
static long drvGpibRegSrqHCB(registerSrqReq * preq);
static long drvGpibRegSrqHandler(ibLink * pibLink, int gpibAddr, int secondary,
    srqHandlerFunc handler, void *parm);
/*eos is end-of-string character, -1 if none */
static long drvGpibRead(ibLink * pibLink, int gpibAddr, int secondary,
	 char *data, int length, int timeout, int eos);
static long drvGpibWrite(ibLink * pibLink, int gpibAddr, int secondary,
     char *data, int length, int timeout);
static long drvGpibAddressedCmd(ibLink * pibLink, int gpibAddr, int secondary,
    char *data, int length, int timeout);
static long drvGpibUniversalCmd(ibLink * pibLink, int cmd);
static long drvGpibIfc(ibLink * pibLink);
static long drvGpibRen(ibLink * pibLink, int onOff);
static long drvGpibReset(ibLink * pibLink);

/* internal methods */
static ibLinkDevice *findIbLinkDevice(
    ibLink * pibLink, int gpibAddr, int secondary);
static bool dbGpibPollDevice(ibLink * pibLink, ibLinkDevice * pibLinkDevice,
    srqStatus * psrqStatus, int gpibAddr, int secondary,
    int verbose, /* set to 1 if should log any errors */
    int timeout);
static void drvGpibLinkThread(ibLink * pibLink);

/* Beginning of routines that implement drvGpibEt */
static long drvGpibReport(int level)
{
    ibLink *pibLink;
    int links = 0, active = 0, disabled = 0;
    assert(initDone);
    /* tstraumann: protect the linked list of ibLinks from being tampered */
    reader_in(&ibLinks.access);
    pibLink = ibLinks.root;
    /* tstraumann:	end of added code section */
    for (pibLink = ibLinks.root; pibLink; pibLink = pibLink->next) {
	links++;
	if (pibLink->disabled) {
	    disabled++;
	    if (level > 0)
		printf("link %d is disabled\n", pibLink->link);
	    continue;
	}
	active++;
	if (level > 0) {
	    printf("link %d is active, controller's gpib address: %d\n",
		   pibLink->link, pibLink->ctrlAddr);
	    pibLink->pdrvHwGpibEt->reportLink(pibLink, level);
	}
	if (level > 1) {
	    int gpibAddr;
	    printf("  registered devices:\n");
	    for (gpibAddr = 0; gpibAddr < IBAPERLINK; gpibAddr++) {
		ibLinkDevice *pprimary = pibLink->pprimaryDevice[gpibAddr];
		int secondary;
		if (!pprimary)
		    continue;
		printf("    address=%d, status=%s\n", gpibAddr,
		       pprimary->deviceStatus ? "BUSY" : "IDLE");
		if (!pprimary->papsecondaryDevice)
		    continue;
		for (secondary = 0; secondary < IBAPERLINK; secondary++) {
		    ibLinkDevice *psecondary;
		    psecondary = pprimary->papsecondaryDevice[secondary];
		    if (!psecondary)
			continue;
		    printf("    secondary=%d, status=%s\n", secondary,
			   psecondary->deviceStatus ? "BUSY" : "IDLE");
		}
	    }
	}
    }
    if (!ibLinks.root) {
	printf("*NO* GPIB links found\n");
    } else {
	printf("Summary: %d links (%d active, %d disabled)\n",
	       links, active, disabled);
    }
    /* tstraumann: */
    reader_out(&ibLinks.access);
    /* tstraumann:	end of added code section */
    return (0);
}

/* Global driver initialization. Called either by iocInit or (indirectly via
 * ibLinkInit) by low level drivers. NOT SMP SAFE. MUST FIX.
 */
static long drvGpibInit(void)
{
    /* tstraumann: return immediately, if already initialized */
    if (initDone)
	return 0;
    /* Force registration */
    drvGpibRegister();
    /* tstraumann: initialize the ibLinks structure */
    ibLinks.root = 0;
    ibLinks.access.nReaders = 0;
    ibLinks.access.dataMtx = epicsMutexCreate();
    ibLinks.access.ctrMtx = epicsMutexCreate();
    ibLinks.newLnkMtx = epicsMutexCreate();
    if (!ibLinks.access.dataMtx || !ibLinks.access.ctrMtx || !ibLinks.newLnkMtx) {
	errlogPrintf("%s(): can't create semaphores\n", "drvGpibInit");
	/* fatal error, don't bother cleaning up */
	return -1;
    }
    defaultTimeout = ibSrqPollTimeout;
    initDone = true;
    return 0;
}

/* Get a link structure given a link number. Allocate if first reference. */
static ibLink *drvGpibGetLink(int link, int gpibAddr, int secondary)
{
    ibLink *pibLink;
    if (!checkGpibAddr(gpibAddr)) {
	errlogPrintf("drvGpibGetLink illegal gpibAddr %d\n", gpibAddr);
	return (0);
    }
    if (!checkSecondary(secondary)) {
	errlogPrintf("drvGpibGetLink illegal secondary %d\n", secondary);
	return (0);
    }
    assert(initDone);
    writer_in(&ibLinks.access);
    for (pibLink = ibLinks.root; pibLink; pibLink = pibLink->next) {
	if (pibLink->link == link) {
	    ibLinkDevice *pprimaryDevice = pibLink->pprimaryDevice[gpibAddr];
	    if (pprimaryDevice == 0) {
		pprimaryDevice = callocMustSucceed(
				   1, sizeof(ibLinkDevice), "drvGpibGetLink");
		pprimaryDevice->pollInhibit =
		    (secondary == IBNOSECONDARY) ? false : true;
		pibLink->pprimaryDevice[gpibAddr] = pprimaryDevice;
	    }
	    if (secondary == IBNOSECONDARY) {
		pprimaryDevice->pollInhibit = false;
	    } else {	/* it is extended address */
		ibLinkDevice **papsecondaryDevice;
		ibLinkDevice *psecondaryDevice;
		papsecondaryDevice = pprimaryDevice->papsecondaryDevice;
		if (!papsecondaryDevice) {
		    papsecondaryDevice = callocMustSucceed(
			IBAPERLINK, sizeof(ibLinkDevice *), "drvGpibGetLink");
		    pprimaryDevice->papsecondaryDevice = papsecondaryDevice;
		}
		psecondaryDevice = papsecondaryDevice[secondary];
		if (!psecondaryDevice) {
		    psecondaryDevice = callocMustSucceed(
				   1, sizeof(ibLinkDevice), "drvGpibGetLink");
		    psecondaryDevice->pollInhibit = false;
		    papsecondaryDevice[secondary] = psecondaryDevice;
		}
	    }
	    break;
	}
    }
    writer_out(&ibLinks.access);
    return pibLink;
}

/******************************************************************************
 * Queue a GPIB work request for future execution. It is the ONLY way a user
 * task can initiate a GPIB message transaction.
 *
 * A work request represents a function that the drvGpibLinkThread is to call (when
 * ready) to allow the user program access to the drvGpibRead,
 * drvGpibWrite, and drvGpibWriteCmd functions. The user programs should never
 * call these functions at any other times.
 *
 * Returns 0, or ERROR.
 ******************************************************************************/
static long drvGpibQReq(dpvtGpibHead * pdpvt, int prio)
{
    debug(3, ("%s(%p, %d): called\n", "drvGpibQReq", (void *)pdpvt, prio));
    assert(initDone);
    assert(pdpvt);
    assert(pdpvt->pibLink);
    assert(prio == IB_Q_LOW || prio == IB_Q_HIGH);
    if (!checkGpibAddr(pdpvt->device) || !checkSecondary(pdpvt->secondary)) {
	errlogPrintf("drvGpibQReq illegal address %d %d\n",
		     pdpvt->device, pdpvt->secondary);
	return -1;
    }
    if (pdpvt->pibLink->disabled)
	return (-1);
    switch (prio) {
    case IB_Q_LOW:	/* low priority transaction request */
	epicsMutexMustLock(pdpvt->pibLink->loPriLock);
	ellAdd(&(pdpvt->pibLink->loPriList), (ELLNODE *) pdpvt);
	epicsMutexUnlock(pdpvt->pibLink->loPriLock);
	epicsEventSignal(pdpvt->pibLink->linkEvent);
	break;
    case IB_Q_HIGH:	/* high priority transaction request */
	epicsMutexMustLock(pdpvt->pibLink->hiPriLock);
	ellAdd(&(pdpvt->pibLink->hiPriList), (ELLNODE *) pdpvt);
	epicsMutexUnlock(pdpvt->pibLink->hiPriLock);
	epicsEventSignal(pdpvt->pibLink->linkEvent);
	break;
    }
    debug(2, ("%s(%p, %d): transaction queued\n", "drvGpibQReq", (void *)pdpvt, prio));
    return (0);
}

/* Allows a device support module to register an SRQ event handler.
 * The actual registration is done via a callback called from LinkTask
 */
static long drvGpibRegSrqHCB(registerSrqReq * preq)
{
    ibLink *pibLink = preq->head.pibLink;
    int gpibAddr = preq->head.device;
    int secondary = preq->head.secondary;
    ibLinkDevice *pibLinkDevice;
    pibLinkDevice = findIbLinkDevice(pibLink, gpibAddr, secondary);
    if (pibLinkDevice->srqHandler) {
	errlogPrintf("%s(%p): !!WARNING!! Attempt to re-register SRQ handler\n"
		   "  for ibLink=%p addr=%d handler=%p\n", "drvGpibRegSrqHCB",
		     preq, preq->head.pibLink,
		addrFromPrimarySecondary(gpibAddr, secondary), preq->handler);
    } else {
	/* store the handler ptr */
	pibLinkDevice->srqHandler = preq->handler;
	pibLinkDevice->srqParm = preq->parm;
    }
    epicsEventSignal(preq->syncEvent);
    return IDLE;
}
static long drvGpibRegSrqHandler(ibLink * pibLink, int gpibAddr, int secondary,
				     srqHandlerFunc handler, void *parm)
{
    registerSrqReq request;
    int status = 0;
    ibLinkDevice *pibLinkDevice;
    pibLinkDevice = findIbLinkDevice(pibLink, gpibAddr, secondary);

    srqdebug(2, ("%s(%p, %d, %p, %p): called\n", "drvGpibRegSrqHandler",
		 (void *)pibLink, gpibAddr, (void *)handler, (void *)parm));
    /* schedule a callback to make the change */
    request.head.workStart = (int (*) ()) drvGpibRegSrqHCB;
    request.head.device = gpibAddr;
    request.head.secondary = secondary;
    request.head.pibLink = pibLink;
    request.syncEvent = epicsEventMustCreate(epicsEventEmpty);
    /* pass the parameters to the callback */
    request.handler = handler;
    request.parm = parm;
    drvGpibQReq((dpvtGpibHead *) & request, IB_Q_LOW);
    if (epicsEventWaitWithTimeout(request.syncEvent, 5) != epicsEventWaitOK) {
	errlogPrintf("%s(%p, %d, %p, %p): request failed (timeout)\n",
		     "drvGpibRegSrqHandler", pibLink, gpibAddr, handler, parm);
	status = -1;
    }
    epicsEventDestroy(request.syncEvent);
    return status;
}

/* A device support callable entry point used to write data to GPIB devices.
 * Returns the number of bytes written or ERROR.
 */
static long drvGpibWrite(ibLink * pibLink, int gpibAddr, int secondary,
	 char *data, int length, int timeout)
{
    ibLinkDevice *pibLinkDevice;
    pibLinkDevice = findIbLinkDevice(pibLink, gpibAddr, secondary);

    debug(3, ("%s(%p, %d \"%s\", %d, %d): called\n", "drvGpibWrite",
	      (void *)pibLink, addrFromPrimarySecondary(gpibAddr, secondary),
	      data, length, timeout));
    if (pibLinkDevice->logdata) {
	errlogPrintf("GPIB L%d A%d write >%s<\n",
	  pibLink->link, addrFromPrimarySecondary(gpibAddr, secondary), data);
    }
    return pibLink->pdrvHwGpibEt->write(
			 pibLink, gpibAddr, secondary, data, length, timeout);
}

/* A device support callable entry point used to read data from GPIB devices.
 *
 * This routine can terminate a read on receipt of the end-of-string (Eos)
 * character.
 *
 * Returns the number of bytes read from the device, or ERROR if read failed
 */
static long drvGpibRead(ibLink * pibLink, int gpibAddr, int secondary,
	 char *data, int length, int timeout, int eos)
{/* end-of-string character, -1 if none */
    ibLinkDevice *pibLinkDevice;
    long status;
    pibLinkDevice = findIbLinkDevice(pibLink, gpibAddr, secondary);
    debug(3, ("%s(%p, %d, %p, %d, %d, %d): called\n", "drvGpibReadEos",
	      (void *)pibLink, addrFromPrimarySecondary(gpibAddr, secondary),
	      data, length, timeout, eos));
    status = pibLink->pdrvHwGpibEt->read(
		    pibLink, gpibAddr, secondary, data, length, timeout, eos);
    if (pibLinkDevice->logdata) {
	errlogPrintf("GPIB L%d A%d read >%s< status %d \n",
		     pibLink->link,
		 addrFromPrimarySecondary(gpibAddr, secondary), data, status);
    }
    return status;
}

static long drvGpibAddressedCmd(ibLink * pibLink, int gpibAddr, int secondary,
    char *data, int length, int timeout)
{
    assert(initDone);
    assert(pibLink);
    return pibLink->pdrvHwGpibEt->addressedCmd(pibLink, gpibAddr, secondary,
						   data, length, timeout);
}

static long drvGpibUniversalCmd(ibLink * pibLink, int cmd)
{
    long status;
    assert(initDone);
    assert(pibLink);
    status = pibLink->pdrvHwGpibEt->universalCmd(pibLink, cmd);
    return status;
}

static long drvGpibIfc(ibLink * pibLink)
{
    assert(initDone);
    assert(pibLink);
    return pibLink->pdrvHwGpibEt->ifc(pibLink);
}

static long drvGpibRen(ibLink * pibLink, int onOff)
{
    assert(initDone);
    assert(pibLink);
    return pibLink->pdrvHwGpibEt->ren(pibLink, onOff);
}

static long drvGpibReset(ibLink * pibLink)
{
    assert(initDone);
    assert(pibLink);
    /* when linkTask finds disabled true it cleans up and restarts */
    pibLink->disabled = true;
    return (0);
}
/******************************************************************************
 *
 * driver support entry table.
 *
 ******************************************************************************/
drvGpibEt drvGpib =
{
    12,
    /* following may be called from any task */
    drvGpibReport,
    drvGpibInit,
    drvGpibGetLink,
    drvGpibQReq,
    drvGpibRegSrqHandler,
    /* following may only be called from the linkTask */
    drvGpibRead,
    drvGpibWrite,
    drvGpibAddressedCmd,
    drvGpibUniversalCmd,
    drvGpibIfc,
    drvGpibRen,
    drvGpibReset
};
epicsExportAddress(drvet,drvGpib);

static ibLinkDevice *findIbLinkDevice(
   ibLink * pibLink, int gpibAddr, int secondary)
{
    ibLinkDevice *pprimaryDevice;
    ibLinkDevice **papsecondaryDevice;
    ibLinkDevice *psecondaryDevice;
    assert(checkGpibAddr(gpibAddr));
    assert(checkSecondary(secondary));
    assert(initDone);
    pprimaryDevice = pibLink->pprimaryDevice[gpibAddr];
    assert(pprimaryDevice);
    if (secondary == IBNOSECONDARY)
	return (pprimaryDevice);
    papsecondaryDevice = pprimaryDevice->papsecondaryDevice;
    assert(papsecondaryDevice);
    psecondaryDevice = papsecondaryDevice[secondary];
    assert(psecondaryDevice);
    return (psecondaryDevice);
}

static bool dbGpibPollDevice(ibLink * pibLink, ibLinkDevice * pibLinkDevice,
    srqStatus * psrqStatus, int gpibAddr, int secondary,
    int verbose, /* set to 1 if should log any errors */
    int timeout)
{
    int tsSave, doit = false;
    int pollResult;
    assert(initDone);
    assert(pibLink);
    srqdebug(3, ("%s(%p,%d,%d,%d): called\n", "dbGpibPollDevice", (void *)pibLink, gpibAddr,
		 verbose, timeout));
    /* poll only devices with pollInhibit not set */
    epicsMutexLock(pibLink->mutex);
    doit = !pibLinkDevice->pollInhibit;
    epicsMutexUnlock(pibLink->mutex);
    if (!doit)
	return (false);
    tsSave = ibTimeoutSquelch;
    ibTimeoutSquelch = !verbose;	/* keep the I/O routines quiet if
					 * desired */
    pollResult = pibLink->pdrvHwGpibEt->serialPoll
	(pibLink, gpibAddr, secondary, ibSrqPollTimeout);
    ibTimeoutSquelch = tsSave;	/* return I/O error logging to normal */
    if (pollResult < 0)
	return false;	/* Should this log/return an error status? */
    if ((pollResult & 0x40) == 0)
	return false;
    psrqStatus->device = gpibAddr;
    psrqStatus->secondary = secondary;
    psrqStatus->status = pollResult;
    srqdebug(2, ("%s: device %d secondary %d srq status = 0x%.2x\n",
		 "dbGpibPollDevice", gpibAddr, secondary, psrqStatus->status));
    return true;
}

/* This function is spawned as a thread each GPIB controller in the system.*/
static void drvGpibLinkThread(ibLink * pibLink)
{
    dpvtGpibHead *preq;	/* work request pointer */
    srqStatus ringData;	/* data from polled devices */
    int working;	/* do we assume there are more requests? */
    char intFlag;	/* copy of pibLink->srqIntFlag */
    int lockState;	/* for intLock/Unlock */
    TS_STAMP time, lastTime;	/* current and previous timestamp */
    TS_STAMP lastSrqCheck;	/* last time srqStatus was called */
    double timeDiff;
    const char *tName;	/* name of this task */
    ibLinkDevice *pibLinkDevice = 0;
    drvHwGpibEt *pdrvHwGpibEt = pibLink->pdrvHwGpibEt;
    int firstTime = 1;

    assert(initDone);
    assert(pibLink);
    tName = epicsThreadGetNameSelf();
    debug(1, ("%s(%s): started\n", "drvGpibLinkThread", tName));
    taskwdInsert(epicsThreadGetIdSelf(), 0, 0);

restart_task:

    if (pibLink->pdrvHwGpibEt->genLink(pibLink, 1) != 0) {
	errlogPrintf("%s(%s): genLink failed\n", "drvGpibLinkThread", tName);
	if (firstTime) {
	    firstTime = 0;
	    epicsEventSignal(pibLink->waitLinkTask);
	}
	epicsEventWait(pibLink->waitRestart);	/* block task */
	goto restart_task;
    }
    pibLink->disabled = false;	/* accept requests */
    if (firstTime) {
	firstTime = 0;
	epicsEventSignal(pibLink->waitLinkTask);
    }
    working = true;	/* check queues for work the first time */
    epicsTimeGetCurrent(&lastTime);
    epicsTimeGetCurrent(&lastSrqCheck);
    /* make a first check to see if the SRQ line is up */
    if (!ibSrqLock && pibLink->pdrvHwGpibEt->srqStatus(pibLink) == true)
	drvGpibSrqHappened(pibLink);
    do {	/* loop until disabled */
	if (!working) {
	    epicsEventWaitStatus eventStatus;
	    /* Wait for an event associated with this GPIB link. Also, check
	     * every ibSrqTimeout seconds. */
	    if (!ibSrqLock) {
		/* Enable SRQ interrupts while waiting for an event */
		pibLink->pdrvHwGpibEt->srqEnable(pibLink);
	    }
	    eventStatus = epicsEventWaitWithTimeout(
		pibLink->linkEvent, (double) ibSrqTimeout);
	    if (eventStatus == epicsEventWaitOK) {
		srqdebug(3,("drvGpibLinkThread(%s): got an event...\n", tName));
	    } else {
		srqdebug(3, ("drvGpibLinkThread(%s): linkEvent timeout...\n",
			     tName));
	    }
	    /* Disable SRQ interrupts while processing an event */
	    pibLink->pdrvHwGpibEt->srqDisable(pibLink);
	}
	epicsTimeGetCurrent(&time);
	/* call srqStatus every ibSrqPollRate seconds           */
	/* Serves two purposes:                                 */
	/* 1) some low level drivers dont trap SRQs             */
	/* Chance that device raises SRQ while poll in progress */
	timeDiff = epicsTimeDiffInSeconds(&time, &lastSrqCheck);
	if (timeDiff >= ibSrqPollRate) {
	    lastSrqCheck = time;
	    if (pibLink->pdrvHwGpibEt->srqStatus(pibLink) == true) {
		drvGpibSrqHappened(pibLink);
	    }
	}
	working = false;	/* Assume queues are empty & no SRQ */

	/* Check if an SRQ interrupt has occurred recently */
	/* lock against interrupts & task switches */
        lockState = epicsInterruptLock();
	intFlag = pibLink->srqIntFlag;
	pibLink->srqIntFlag = 0;
	epicsInterruptUnlock(lockState);
	/* Process all SRQs that happened until now. If at the same time
	 * another SRQ happens, it is possible that this (new) SRQ is processed
	 * here immediately. In this case the srqIntFlag is set when we reach
	 * this code the next time, resulting in a probably useless round of
	 * polling (no problem). But there _is_ one problem: if a device
	 * asserts the SRQ line immediately after it has been polled, but the
	 * SRQ line is already true (because another device with a higher
	 * address initiated this polling round), then the controller cannot
	 * detect this and no RPC call will be generated. This case is handled
	 * by polling every ibSrqPollRate seconds. */
	if (intFlag && !ibSrqLock) {
	    ibLinkDevice *pprimaryDevice;
	    ibLinkDevice **papsecondaryDevice;
	    ibLinkDevice *psecondaryDevice;
	    int gpibAddr, secondary;
	    srqdebug(2, ("%s(%s): ...it's an SRQ\n", "drvGpibLinkThread", tName));
	    srqdebug(3, ("%s(%s): srqIntFlag=%d\n", "drvGpibLinkThread", tName, intFlag));
	    pdrvHwGpibEt->serialPollBegin(pibLink);
	    for (gpibAddr = 0; gpibAddr < IBAPERLINK; gpibAddr++) {
		bool gotData;
		pprimaryDevice = pibLink->pprimaryDevice[gpibAddr];
		if (!pprimaryDevice) continue;
		gotData = dbGpibPollDevice(pibLink, pprimaryDevice, &ringData,
			gpibAddr, -1, true, ibSrqPollTimeout);
		/* Check for SRQ timeouts. If a device is BUSY for more than
		 * ibSrqTimeout seconds, simulate an SRQ by calling the handler
		 * with statusByte set to 0x40. */
		if (!gotData
		    && (pprimaryDevice->deviceStatus == BUSY
			&& pprimaryDevice->srqHandler
			&& (time.secPastEpoch - pprimaryDevice->timeStamp
			    >= ibSrqTimeout))) {
		    gotData = true;
		    ringData.status = 0x40;
		}
		if (gotData && (ringData.status & 0x40)) {
		    epicsRingBytesPut(pibLink->srqRing, (char *) &ringData,
				      sizeof(ringData));
		}
		papsecondaryDevice = pprimaryDevice->papsecondaryDevice;
		if (!papsecondaryDevice)
		    continue;
		for (secondary = 0; secondary < IBAPERLINK; secondary++) {
		    psecondaryDevice = papsecondaryDevice[secondary];
		    if (!psecondaryDevice)
			continue;
		    gotData = dbGpibPollDevice(pibLink, psecondaryDevice,
		      &ringData, gpibAddr, secondary, true, ibSrqPollTimeout);
		    if (!gotData
			&& (psecondaryDevice->deviceStatus == BUSY
			    && psecondaryDevice->srqHandler
			    && (time.secPastEpoch - psecondaryDevice->timeStamp
				>= ibSrqTimeout))) {
			gotData = true;
			ringData.status = 0x40;
		    }
		    if (gotData && (ringData.status & 0x40)) {
			epicsRingBytesPut(pibLink->srqRing,
                            (char *) &ringData, sizeof(ringData));
		    }
		}
	    }
	    pdrvHwGpibEt->serialPollEnd(pibLink);
	}
	/* See if there is a need to process an SRQ solicited transaction. Do
	 * all of them before going on to other transactions. */
	while(epicsRingBytesGet(pibLink->srqRing,(char *)&ringData,sizeof(ringData))){
	    pibLinkDevice = findIbLinkDevice(pibLink,
                ringData.device, ringData.secondary);
	    if (pibLinkDevice->srqHandler) {
		/* there is a registered SRQ handler for this device */
		srqdebug(1,("%s(%s):dispatching srq handler for device %d %d\n",
                    "drvGpibLinkThread", tName,
                    (int) ringData.device, (int) ringData.secondary));
		/* call the handler */
		pibLinkDevice->deviceStatus = (*pibLinkDevice->srqHandler) (
				     pibLinkDevice->srqParm, ringData.status);
		working = true;
	    } else {
		srqdebug(2, ("%s(%s): no srq handler for device %d %d\n",
			     "drvGpibLinkThread", tName,
			     (int) ringData.device, (int) ringData.secondary));
	    }
	}
	/* See if the hi priority queue has anything in it. */
	epicsMutexMustLock(pibLink->hiPriLock);
	/* Find first work request in hiPriList with IDLE device */
	preq = (dpvtGpibHead *) ellFirst(&pibLink->hiPriList);
	while (preq) {
	    pibLinkDevice = findIbLinkDevice(
				      pibLink, preq->device, preq->secondary);
	    if (pibLinkDevice->deviceStatus == IDLE)
		break;
	    preq = (dpvtGpibHead *) ellNext((ELLNODE *) preq);
	}
	if (preq)
	    ellDelete(&pibLink->hiPriList, (ELLNODE *) preq);
	epicsMutexUnlock(pibLink->hiPriLock);
	if (preq) {
	    debug(2, ("%s(%s): ... it's a hi pri request, preq = %p\n",
		      "drvGpibLinkThread", tName, (void *)preq));
	    /* tstraumann:	after calling the workStart callback NO
	     * REFERENCE TO 'preq' IS ALLOWED because the task that sent the
	     * request owns its memory (and might free it before we get another
	     * chance to run). Only one exception is allowed: The semantics of
	     * Callback/SRQ should state (also in the docs) that the requestor
	     * must not destroy the request if the workStart routine returns
	     * BUSY until the SRQ is raised. */
	    pibLinkDevice->deviceStatus = preq->workStart(preq);
	    pibLinkDevice->timeStamp = time.secPastEpoch;
	    working = true;
	} else {
	    /* See if the low priority queue has anything in it. */
	    epicsMutexMustLock(pibLink->loPriLock);
	    /* Find first work request in loPriList with IDLE device */
	    preq = (dpvtGpibHead *) ellFirst(&pibLink->loPriList);
	    while (preq) {
		pibLinkDevice = findIbLinkDevice(
				      pibLink, preq->device, preq->secondary);
		if (pibLinkDevice->deviceStatus == IDLE)
		    break;
		preq = (dpvtGpibHead *) ellNext((ELLNODE *) preq);
	    }
	    if (preq)
		ellDelete(&pibLink->loPriList, (ELLNODE *) preq);
	    epicsMutexUnlock(pibLink->loPriLock);
	    if (preq) {
		debug(2, ("%s(%s): ... it's a lo pri request, preq = %p\n",
			  "drvGpibLinkThread", tName, (void *)preq));
		pibLinkDevice->deviceStatus = preq->workStart(preq);
		pibLinkDevice->timeStamp = time.secPastEpoch;
		working = true;
	    }
	}
	/* Allow low level driver to do periodic check to see if interface
	 * device still lives. If not, stop processing requests and prepare for
	 * reinitialization. */
	if (pibLink->secToDeath
	&& time.secPastEpoch - lastTime.secPastEpoch >= pibLink->secToDeath) {
	    if (pibLink->pdrvHwGpibEt->probe(pibLink) != 0) {
		pibLink->disabled = true;	/* jump out of the loop */
		epicsEventWait(pibLink->waitRestart);	/* block task */
	    }
	    lastTime = time;
	}
    } while (!pibLink->disabled);
    errlogPrintf("%s(%s): start re-initialization\n", "drvGpibLinkThread", tName);
    /* empty work queues before reinit */
    while ((preq = (dpvtGpibHead *) ellFirst(&pibLink->loPriList)))
	ellDelete(&pibLink->loPriList, (ELLNODE *) preq);
    while ((preq = (dpvtGpibHead *) ellFirst(&pibLink->hiPriList)))
	ellDelete(&pibLink->loPriList, (ELLNODE *) preq);
    epicsRingBytesFlush(pibLink->srqRing);
    /* reinit: */
    pibLink->pdrvHwGpibEt->destroyLink(pibLink);
    goto restart_task;
}

/* BEGINNING OF EXTERNAL PROCEDURES */
/******************************************************************************
 *
 * Completely reinitialize network stuff by restarting the link task.
 * This can be used to recover from a (hard) reset of the interface device.
 *
 * May be called from cmd line.
 *
 ******************************************************************************/
int drvGpibResetLink(int link)
{
    ibLink *pibLink;
    assert(initDone);
    for (pibLink = ibLinks.root; pibLink; pibLink = pibLink->next) {
	if (pibLink->link == link)
	    break;
    }
    if (!pibLink) {
	errlogPrintf("link %d does not exist\n", link);
	return 0;
    }
    /* When linkTask finds disabled true it cleans up and starts over */
    if (pibLink->disabled) {
	epicsEventSignal(pibLink->waitRestart);
    } else {
	pibLink->disabled = true;
    }
    epicsEventSignal(pibLink->linkEvent);
    debug(1, ("%s(%p): operation complete\n", "drvGpibResetLink", (void *)pibLink));
    return 0;
}

/* Log any I/O over this link/addr. If link or addr is -1 then set for all
 * If link is -1 then set for all links
 * If addr is -1 then set for all address on link
 */
int drvGpibLogData(int link, int addr, int onOff)
{
    ibLink *pibLink;
    int gpibAddr = 0;
    int secondary = 0;
    if (!initDone) {
	errlogPrintf("drvGpib not initialized!\n");
	return 0;
    }
    if (addr != -1) {
	if (addr >= 100) {
	    secondary = addr % 100;
	    gpibAddr = addr / 100;
	} else {
	    secondary = IBNOSECONDARY;
	    gpibAddr = addr;
	}
	if (!checkGpibAddr(gpibAddr) || !checkSecondary(secondary)) {
	    printf("addr %d does not exist\n",
		   addrFromPrimarySecondary(gpibAddr, secondary));
	    return 0;
	}
    }
    writer_in(&ibLinks.access);
    for (pibLink = ibLinks.root; pibLink; pibLink = pibLink->next) {
	if (link == -1 || pibLink->link == link) {
	    int i;
	    for (i = 0; i < IBAPERLINK; i++) {
		if (addr == -1 || i == gpibAddr) {
		    if (addr == -1) {
			int j;
			pibLink->pprimaryDevice[i]->logdata = onOff;
			for (j = 0; j < IBAPERLINK; j++) {
			    ibLinkDevice **papsecondaryDevice;
			    ibLinkDevice *pibLinkDevice;
			    papsecondaryDevice =
				pibLink->pprimaryDevice[i]->papsecondaryDevice;
			    if (!papsecondaryDevice)
				break;
			    pibLinkDevice = papsecondaryDevice[secondary];
			    if (pibLinkDevice)
				pibLinkDevice->logdata = onOff;
			}
		    } else {
			if (secondary == IBNOSECONDARY) {
			    if (pibLink->pprimaryDevice[i]) {
				pibLink->pprimaryDevice[i]->logdata = onOff;
			    }
			    break;
			} else {
			    ibLinkDevice **papsecondaryDevice;
			    if (!pibLink->pprimaryDevice[i])
				break;
			    papsecondaryDevice =
				pibLink->pprimaryDevice[i]->papsecondaryDevice;
			    if (!papsecondaryDevice)
				break;
			    if (papsecondaryDevice[secondary]) {
				papsecondaryDevice[secondary]->logdata = onOff;;
			    }
			    break;
			}
		    }
		}
	    }
	    if (link != -1) break;
	}
    }
    writer_out(&ibLinks.access);
    return 0;
}

/******************************************************************************
 *
 * Request that the GPIB driver NEVER poll a given device on a given link.
 *
 * Devices are polled when an SRQ event is present on the GPIB link. Some
 * devices are too dumb to deal with being polled.
 *
 * This is NOT a static function because it may be called from the VxWorks
 * command line.
 *
 ******************************************************************************/
long drvGpibPoll(int link, int addr, int onOff)
{
    ibLink *pibLink;
    ibLinkDevice *pibLinkDevice = 0;
    int gpibAddr = 0;
    int secondary = 0;
    assert(initDone);
    if (addr >= 100) {
	secondary = addr % 100;
	gpibAddr = addr / 100;
    } else {
	secondary = IBNOSECONDARY;
	gpibAddr = addr;
    }
    if (!checkGpibAddr(gpibAddr) || !checkSecondary(secondary)) {
	printf("addr %d is illegal\n",
	       addrFromPrimarySecondary(gpibAddr, secondary));
	return 0;
    }
    pibLink = drvGpibGetLink(link, gpibAddr, secondary);
    if (!pibLink) {
	printf("drvGpibGetLink failed\n");
	return 0;
    }
    pibLinkDevice = findIbLinkDevice(pibLink, gpibAddr, secondary);
    if (!pibLinkDevice) {
	errlogPrintf("link or device does not exist\n");
	return 0;
    }
    epicsMutexLock(pibLink->mutex);
    pibLinkDevice->pollInhibit = onOff;
    epicsMutexUnlock(pibLink->mutex);
    return 0;
}
long drvGpibPollInhibit(int link, int addr)
{
    return (drvGpibPoll(link, addr, 0));
}

/******************************************************************************
 * Registers a newly allocated ibLink structure with the generic driver.
 * To be called by the low level gpib drivers.
 *
 * The caller must
 * - allocate the link structure (ibLink)
 * - set the link number (ibLink.link)
 * - set the low level driver interface (ibLink.pdrvHwGpibEt)
 * before calling ibLinkInit.
 ******************************************************************************/
long drvGpibLinkInit(ibLink * pibLink)
{
    int saveLink;
    drvHwGpibEt *savedrvHwGpib;
    ibLink *pexisting;
    assert(pibLink);
    assert(pibLink->pdrvHwGpibEt);
    assert(pibLink->link != NONE);
    if (drvGpibInit() != 0)
	return -1;
    /* prevent two callers from creating the same link */
    epicsMutexLock(ibLinks.newLnkMtx);
    /* check if link already exists */
    reader_in(&ibLinks.access);
    for (pexisting = ibLinks.root; pexisting; pexisting = pexisting->next) {
	if (pexisting->link == pibLink->link)
	    break;
    }
    reader_out(&ibLinks.access);
    if (pexisting) {
	errlogPrintf("drvGpibLinkInit(%p): "
		     "Sorry, link number %d already in use\n",
		     pibLink, pibLink->link);
	epicsMutexUnlock(ibLinks.newLnkMtx);
	return -1;
    }
    saveLink = pibLink->link;
    savedrvHwGpib = pibLink->pdrvHwGpibEt;
    memset(pibLink, 0, sizeof(ibLink));
    pibLink->link = saveLink;
    pibLink->disabled = true;	/* leave disabled until linkTask ready */
    pibLink->pdrvHwGpibEt = savedrvHwGpib;
    pibLink->waitLinkTask = epicsEventMustCreate(epicsEventEmpty);
    pibLink->waitRestart = epicsEventMustCreate(epicsEventEmpty);
    pibLink->linkEvent = epicsEventMustCreate(epicsEventEmpty);
    pibLink->mutex = epicsMutexMustCreate();
    ellInit(&pibLink->hiPriList);
    pibLink->hiPriLock = epicsMutexMustCreate();
    ellInit(&pibLink->loPriList);
    pibLink->loPriLock = epicsMutexMustCreate();
    pibLink->srqRing = epicsRingBytesCreate(ibSrqRingSize * sizeof(srqStatus));
    if (!pibLink->srqRing) {
	errlogPrintf("%s(%p): can't create ring buffer\n",
		     "drvGpibLinkFill", pibLink);
	return -1;
    }
    pibLink->pdrvHwGpibEt->genLink(pibLink, 0);
    /* link the new one into the list */
    /* make sure the link task doesn't read the list right now */
    writer_in(&ibLinks.access);
    pibLink->next = ibLinks.root;
    ibLinks.root = pibLink;
    writer_out(&ibLinks.access);
    epicsMutexUnlock(ibLinks.newLnkMtx);
    return 0;
}

/******************************************************************************
 * Init and start an drvGpibLinkThread.
 *
 * May be called from low level drivers during initialization.
 ******************************************************************************/
long drvGpibLinkStart(ibLink * pibLink)
{
    char tName[20];
    epicsThreadId threadId;

    assert(initDone);
    assert(pibLink);
    debug(2, ("%s(%p): called\n", "drvGpibLinkStart", (void *)pibLink));
    sprintf(tName, "gpib%.5d", pibLink->link);
    /* Start a task to manage the link */
    threadId = epicsThreadCreate(tName, 47,
	epicsThreadGetStackSize(epicsThreadStackMedium),
	(EPICSTHREADFUNC) drvGpibLinkThread, pibLink);
    if (!threadId) {
	errlogPrintf("drvGpibLinkStart failed to create %s\n", tName);
	pibLink->disabled = true;
	return (-1);
    }
    /* wait for task init */
    if (epicsEventWaitWithTimeout(pibLink->waitLinkTask, 80.0)) {
	errlogPrintf("%s(%p): linkTask init failure / timeout\n",
           "drvGpibLinkStart",pibLink);
	return -1;
    }
    debug(2, ("%s(%p): task \"%s\" created\n",
        "drvGpibLinkStart", (void *)pibLink, tName));
    return 0;
}

/* Tell us that an SRQ happened. May be called from interrupt context. */
void drvGpibSrqHappened(ibLink * pibLink)
{
    int lockState;

    assert(initDone);
    assert(pibLink);
    assert(pibLink->pdrvHwGpibEt);
    /* set the flag; intLock may be unnecessary, it certainly does no harm */
    lockState = epicsInterruptLock();
    pibLink->srqIntFlag = 1;
    epicsInterruptUnlock(lockState);
    /* wake up the linkTask */
    epicsEventSignal(pibLink->linkEvent);
}

/*
 * drvGpib.c,v
 * Revision 1.9  2003/04/10 15:17:03  mrk
 * open source license
 *
 * Revision 1.8  2003/04/02 14:08:16  mrk
 * changes for R3.14.2 build rules
 *
 * Revision 1.7  2003/03/14 16:46:53  mrk
 * Changes for March 2003 version
 *
 * Revision 1.6  2003/02/14 14:31:50  mrk
 * changes contributed by Stephanie Allison
 *
 * Revision 1.5  2003/02/13 14:13:28  mrk
 * changes for automatic registration
 *
 * Revision 1.4  2002/11/05 20:41:08  norume
 * Change the drvHwGpibEt serialPoll function to return a long instead of
 * an unsigned char.  This allows for the possibility of returning an
 * error status (-1) should the device not respond to the serial poll request.
 *
 * Revision 1.3  2002/11/01 19:29:56  mrk
 * More changes for ioctl replacement
 *
 * Revision 1.2  2002/10/24 18:31:09  mrk
 * support IBRESETLNK
 *
 * Revision 1.1  2002/10/18 17:21:06  mrk
 * moved to here
 *
 * Revision 1.5  2002/08/23 20:58:54  norume
 * The E2050A can take a very long time to get a BOOTP/DHCP reply on the
 * very large bridged network here at the U of S.  Allow more time for
 * driver to connect.
 *
 * Revision 1.4  2001/04/17 18:40:07  mrk
 * Changes to get ready for bitBus support which will be implemented with bitBus
 *
 * Revision 1.3  2001/03/05 21:54:26  mrk
 * hiPriEvent=>hiPriLock, loPriEvent=>loPriLock
 * moved shell callable routines to the end of drvGpib.c
 * Added files drvGpibRegister.c and gpibInteract.c
 *
 * Revision 1.2  2001/02/22 19:57:18  norume
 * Many, many R3.14 changes.  Driver tested on RTEMS-gen68360 and Linux.
 *
 * Revision 1.1.1.1  2001/02/15 14:52:51  mrk
 * Import sources
 *
 * Revision 1.3  2000/05/19 20:33:32  franksen
 * restructured; -= drvGpibSpx
 *
 * Revision 1.2  2000/05/19 12:57:42  franksen
 * use errlogPrintf; fixed bug in drvGpibGetLink;
 * changed type of link/gpibAddr from short to int
 *
 * Revision 1.1.1.1  2000/03/21 18:06:35  franksen
 * unbundled gpib first version
 *
 *****************************************************************************/
