/******************************************************************************
 *
 * drvVxi11.c,v 
 *
 ******************************************************************************/
/***********************************************************************
* 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.
***********************************************************************/
/*
 *		Low level GPIB driver for HP E2050A LAN-GPIB gateway
 *
 * Author: Benjamin Franksen
 *
 *****************************************************************************/
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <errno.h>
/* epics includes */
#include <dbDefs.h>
#include <taskwd.h>
#include <errlog.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <osiSock.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <cantProceed.h>
#include <epicsExport.h>
/* local includes */
#include "drvVxi11.h"
#include "drvGpibInterface.h"
#include "drvHwGpibInterface.h"
#include "vxi11.h"
#include "vxi11core.h"
#include "vxi11intr.h"

#ifdef BOOL
#undef BOOL
#endif
#define BOOL int

#ifdef DEBUG
#define STATIC 
#define debug(level,arg) if(vxi11Debug >= level) printf arg
#define srqdebug(level,arg) if(vxi11Debug >= level) printf arg
#else
#define STATIC static
#define debug(level,arg)
#define srqdebug(level,arg)
#endif

/* general defines */
#define vxiRelease "1.0"

#define DEFAULT_RPC_TIMEOUT 10
static const char *srqThreadName = "vxi11Srq";

typedef struct devLinkPrimary {
    Device_Link primary;
    BOOL        primaryConnected;
    Device_Link secondary[IBAPERLINK];
    BOOL        secondaryConnected[IBAPERLINK];
}devLinkPrimary;
/******************************************************************************
 * This structure is used to hold the hardware-specific information for a
 * single GPIB link. There is one for each gateway.
 ******************************************************************************/
typedef struct vxiLink {
    ibLink head;	/* device independent part of link structure */
    Device_Link serverAddr;
    BOOL isSingleLink; /* Is this an ethernet device */ 
    char *hostName;	/* name of gateway for ip */
    struct in_addr inAddr;	/* ip address of gateway */
    CLIENT *rpcClient;	/* returned by clnt_create() */
    char vxiName[20];	/* name of gateway for VXI-11 */
    unsigned long maxRecvSize;	/* max. # of bytes accepted by link */
    int defTimeout;	/* default timeout in msec */
    devLinkPrimary devLink[IBAPERLINK];	/* one for each possible primary device
					 * including the gateway itself */
    unsigned char recoverWithIFC;/*fire out IFC pulse on timeout (read/write)*/
    unsigned char restartActive; /* Is vxiReconnect active */
}vxiLink;

/* global variables */
int vxi11Debug;

/* local variables */
typedef struct vxiLocal {
    struct sockaddr_in iocAddr; /* inet address of this IOC */
    struct in_addr netAddr;     /* network part of ^ */
    epicsThreadId srqThreadId;  /* current SRQ task id */
    struct timeval vxiRpcTimeout;/* time to wait for RPC completion */
    epicsEventId srqTaskReady;  /* wait for srqTask to be ready*/
}vxiLocal;
STATIC vxiLocal *pvxiLocal = 0;

/* forward references */
STATIC char *vxiError(Device_ErrorCode error);
STATIC BOOL vxiCreateLink(vxiLink * pvxiLink,
    char *devName,Device_Link *pDevice_Link);
STATIC BOOL vxiSetDevLink(vxiLink * pvxiLink, int gpibAddr, int secondary,
    Device_Link *callerLink);
STATIC long vxiDestroyDevLink(vxiLink * pvxiLink, Device_Link devLink);
STATIC long vxiWriteAddressed(vxiLink * pvxiLink, Device_Link lid,
    char *buffer, int length, int timeout);
STATIC long vxiWriteCmd(ibLink * pibLink, char *buffer, int length);
STATIC enum clnt_stat clientCall(vxiLink * pvxiLink, u_long req,
    xdrproc_t proc1, caddr_t addr1,xdrproc_t proc2, caddr_t addr2);
STATIC long vxiBusStatus(vxiLink * pvxiLink, int request, int timeout);
STATIC void vxiReconnect(void *arg);
STATIC long vxiSrqTask(void);
STATIC void vxiIrq(struct svc_req * rqstp, SVCXPRT * transp);
STATIC long vxiInit(void);

/* drvHwGpibEt routines */
STATIC long vxiGenLink(ibLink * pibLink, int pass);
STATIC long vxiDestroyLink(ibLink * pibLink);
STATIC long vxiReportLink(ibLink * pibLink, int level);
STATIC long vxiProbe(ibLink * pibLink);
STATIC long vxiRead(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout, int eos);
STATIC long vxiWrite(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout);
STATIC long vxiAddressedCmd(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout);
STATIC long vxiUniversialCmd(ibLink * pibLink, int cmd);
STATIC long vxiIfc(ibLink * pibLink);
STATIC long vxiRen(ibLink * pibLink, int onOff);
STATIC long vxiSrqStatus(ibLink * pibLink);
STATIC long vxiSrqEnable(ibLink * pibLink);
STATIC long vxiSrqDisable(ibLink * pibLink);
STATIC long vxiSerialPollBegin(ibLink * pibLink);
STATIC long vxiSerialPoll(ibLink * pibLink, int gpibAddr, int secondary,
    int timeout);
STATIC long vxiSerialPollEnd(ibLink * pibLink);

/******************************************************************************
 * Convert VXI error code to a string.
 ******************************************************************************/
STATIC char *vxiError(Device_ErrorCode error)
{
    switch (error) {
    case VXI_OK:
	return ("VXI: no error");
    case VXI_SYNERR:
	return ("VXI: syntax error");
    case VXI_NOACCESS:
	return ("VXI: device not accessible");
    case VXI_INVLINK:
	return ("VXI: invalid link identifier");
    case VXI_PARAMERR:
	return ("VXI: parameter error");
    case VXI_NOCHAN:
	return ("VXI: channel not established");
    case VXI_NOTSUPP:
	return ("VXI: operation not supported");
    case VXI_NORES:
	return ("VXI: out of resources");
    case VXI_DEVLOCK:
	return ("VXI: device locked by another link");
    case VXI_NOLOCK:
	return ("VXI: no lock held by this link");
    case VXI_IOTIMEOUT:
	return ("VXI: I/O timeout");
    case VXI_IOERR:
	return ("VXI: I/O error");
    case VXI_INVADDR:
	return ("VXI: invalid address");
    case VXI_ABORT:
	return ("VXI: abort");
    case VXI_CHANEXIST:
	return ("VXI: channel already established");
    default:
	printf("vxiError error = %ld\n", error);
	return ("VXI: unknown error");
    }
}

STATIC BOOL vxiCreateLink(vxiLink * pvxiLink,
    char *devName,Device_Link *pDevice_Link)
{
    enum clnt_stat clntStat;
    Create_LinkParms crLinkP;
    Create_LinkResp crLinkR;
    BOOL rtnVal = 0;

    crLinkP.clientId = (long) pvxiLink->rpcClient;
    crLinkP.lockDevice = 0;	/* do not try to lock the device */
    crLinkP.lock_timeout = 0;	/* if device is locked, forget it */
    crLinkP.device = devName;
    /* initialize crLinkR */
    memset((char *) &crLinkR, 0, sizeof(Create_LinkResp));
    /* RPC call */
    clntStat = clientCall(pvxiLink, create_link,
        (const xdrproc_t) xdr_Create_LinkParms,(void *) &crLinkP,
        (const xdrproc_t) xdr_Create_LinkResp,(void *) &crLinkR);
    if(clntStat != RPC_SUCCESS) {
        errlogPrintf("vxiCreateLink devName %s RPC error %s\n",
            devName,clnt_sperror(pvxiLink->rpcClient, ""));
    } else if(crLinkR.error != VXI_OK) {
        errlogPrintf("vxiCreateLink devName %s error %s\n",
            devName,vxiError(crLinkR.error));
    } else {
	*pDevice_Link = crLinkR.lid;
        rtnVal = 1;
        if(pvxiLink->maxRecvSize==0) {
            pvxiLink->maxRecvSize = crLinkR.maxRecvSize;
        }
    }
    xdr_free((const xdrproc_t) xdr_Create_LinkResp, (char *) &crLinkR);
    return(rtnVal);
}

STATIC BOOL vxiSetDevLink(vxiLink *pvxiLink, int gpibAddr,
    int secondary,Device_Link *callerLink)
{
    char devName[40];
    Device_Link link = 0;
    int status;

    assert(pvxiLink);
    if(pvxiLink->isSingleLink) {
        *callerLink = pvxiLink->serverAddr;
        return(1);
    }
    if(secondary == IBNOSECONDARY) {
        if(pvxiLink->devLink[gpibAddr].primaryConnected) {
	    *callerLink = pvxiLink->devLink[gpibAddr].primary;
            return(1);
        }
    } else {
        if(pvxiLink->devLink[gpibAddr].secondaryConnected[secondary]) {
	    *callerLink = pvxiLink->devLink[gpibAddr].secondary[secondary];
            return(1);
        }
    }

    debug(2, ("vxiCreateDevLink addr %d %d\n",gpibAddr, secondary));
    /* Make sure bus is available */
    status = vxiBusStatus(pvxiLink, VXI_BSTAT_SYSTEM_CONTROLLER,
        pvxiLink->defTimeout);
    if(status==-1) return 0;
    if(secondary == IBNOSECONDARY) {
	sprintf(devName, "%s,%d", pvxiLink->vxiName, gpibAddr);
    } else {
	sprintf(devName, "%s,%d,%d", pvxiLink->vxiName, gpibAddr, secondary);
    }
    if(!vxiCreateLink(pvxiLink,devName,&link)) return(0);
    if(secondary == IBNOSECONDARY) {
	pvxiLink->devLink[gpibAddr].primary = link;
	pvxiLink->devLink[gpibAddr].primaryConnected = 1;
    } else {
	pvxiLink->devLink[gpibAddr].secondary[secondary] = link;
	pvxiLink->devLink[gpibAddr].secondaryConnected[secondary] = 1;
    }
    *callerLink = link;
    return(1);
}

/******************************************************************************
 * Destroy a device link. Returns OK or ERROR.
 ******************************************************************************/
STATIC long vxiDestroyDevLink(vxiLink * pvxiLink, Device_Link devLink)
{
    enum clnt_stat clntStat;
    Device_Error devErr;
    int status = 0;
    assert(pvxiLink);
    debug(2, ("vxiDestroyDevLink called pvxiLink %p devLink %lx\n",
              pvxiLink,devLink));
    clntStat = clientCall(pvxiLink, destroy_link,
        (const xdrproc_t) xdr_Device_Link,(void *) &devLink,
        (const xdrproc_t) xdr_Device_Error, (void *) &devErr);
    if(clntStat != RPC_SUCCESS) {
	status = -1;
        debug(1,("vxiDestroyDevLink RPC error %s\n",
                clnt_sperror(pvxiLink->rpcClient, "")));
    } else if(devErr.error != VXI_OK) {
	status = -1;
        debug(1,("vxiDestroyDevLink error %s\n",
             vxiError(devErr.error)));
    }
    xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
    return status;
}

/*write with ATN true */
STATIC long vxiWriteAddressed(vxiLink * pvxiLink, Device_Link lid,
    char *buffer, int length, int timeout)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_DocmdParms devDocmdP;
    Device_DocmdResp devDocmdR;

    debug(2, ("%s(%p, %p, %d): called\n", "vxiWriteAddressed",
        (void *) pvxiLink, buffer, length));
    assert(pvxiLink);
    assert(buffer);
    devDocmdP.lid = lid;
    devDocmdP.flags = 0;
    devDocmdP.io_timeout = timeout;
    devDocmdP.lock_timeout = 0;
    devDocmdP.network_order = NETWORK_ORDER;
    /* This command sets ATN to TRUE before sending */
    devDocmdP.cmd = VXI_CMD_SEND;
    devDocmdP.datasize = 1;
    devDocmdP.data_in.data_in_len = length;
    devDocmdP.data_in.data_in_val = buffer;
    /* initialize devDocmdR */
    memset((char *) &devDocmdR, 0, sizeof(Device_DocmdResp));
    /* RPC call */
    clntStat = clientCall(pvxiLink, device_docmd,
        (const xdrproc_t) xdr_Device_DocmdParms,(void *) &devDocmdP,
        (const xdrproc_t) xdr_Device_DocmdResp,(void *) &devDocmdR);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p, %p, %d)%s\n", "vxiWriteAddressed",
            pvxiLink, buffer, length,clnt_sperror(pvxiLink->rpcClient, ""));
	status = -1;
    } else if(devDocmdR.error != VXI_OK) {
	if(devDocmdR.error != VXI_IOTIMEOUT) {
	    errlogPrintf("%s(%p, %p, %d): %s\n", "vxiWriteAddressed",
                pvxiLink, buffer, length,vxiError(devDocmdR.error));
        }
	status = -1;
    } else {
	status = devDocmdR.data_out.data_out_len;
    }
    xdr_free((const xdrproc_t) xdr_Device_DocmdResp, (char *) &devDocmdR);
    return status;
}

/******************************************************************************
 * Output a command string to the GPIB bus.
 * The string is sent with the ATN line held TRUE (low).
 ******************************************************************************/
STATIC long vxiWriteCmd(ibLink * pibLink, char *buffer, int length)
{
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    long status;

    status = vxiWriteAddressed(pvxiLink,
        pvxiLink->serverAddr,buffer,length,pvxiLink->defTimeout);
    return(status);
}

/******************************************************************************
 * Check the bus status. Parameter <request> can be a number from 1 to 8 to
 * indicate the information requested (see VXI_BSTAT_XXXX in drvLanGpib.h)
 * or it can be 0 meaning all (exept the bus address) which will then be
 * combined into a bitfield according to the bit numbers+1 (1 corresponds to
 * bit 0, etc.).
 *
 * Return status if successful else ERROR.
 ******************************************************************************/
STATIC long vxiBusStatus(vxiLink * pvxiLink, int request, int timeout)
{
    enum clnt_stat clntStat;
    Device_DocmdParms devDocmdP;
    Device_DocmdResp devDocmdR;
    unsigned short data;	/* what to ask */
    int status = 0, start, stop;
    assert(pvxiLink);
    assert(request >= 0 && request <= VXI_BSTAT_BUS_ADDRESS);
    devDocmdP.lid = pvxiLink->serverAddr;
    devDocmdP.flags = 0;	/* no timeout on a locked gateway */
    devDocmdP.io_timeout = timeout;
    devDocmdP.lock_timeout = 0;
    devDocmdP.network_order = NETWORK_ORDER;
    devDocmdP.cmd = VXI_CMD_STAT;
    devDocmdP.datasize = 2;
    devDocmdP.data_in.data_in_len = 2;
    if(!request) {
	start = VXI_BSTAT_REN;
	stop = VXI_BSTAT_LISTENER;
    } else {
	start = stop = request;
    }
    for(data = start; data <= stop; data++) {
	unsigned short ndata = htons(data);
	unsigned short result;
	devDocmdP.data_in.data_in_val = (char *) &ndata;
	/* initialize devDocmdR */
	memset((char *) &devDocmdR, 0, sizeof(Device_DocmdResp));
	/* RPC call */
	clntStat = clientCall(pvxiLink, device_docmd,
            (const xdrproc_t) xdr_Device_DocmdParms,(void *) &devDocmdP,
            (const xdrproc_t) xdr_Device_DocmdResp, (void *) &devDocmdR);
	if(clntStat != RPC_SUCCESS) {
	    errlogPrintf("%s(%p, %d)%s\n", "vxiBusStatus",
                pvxiLink, request, clnt_sperror(pvxiLink->rpcClient, ""));
	    xdr_free((const xdrproc_t)xdr_Device_DocmdResp,(char *) &devDocmdR);
	    return -1;
	}
	if(devDocmdR.error != VXI_OK) {
	    if(devDocmdR.error != VXI_IOTIMEOUT) {
		errlogPrintf("%s(%p, %d): %s\n", "vxiBusStatus",
                    pvxiLink, request,vxiError(devDocmdR.error));
            }
	    xdr_free((const xdrproc_t)xdr_Device_DocmdResp,(char *)&devDocmdR);
	    return -1;
	}
	result = ntohs(*(unsigned short *) devDocmdR.data_out.data_out_val);
	if(request) {
	    status = result;
	} else if(result) {
            status |= (1<<data); /* bit numbers start at 0 */
	}
	xdr_free((const xdrproc_t) xdr_Device_DocmdResp,(char *) &devDocmdR);
    }
    return status;
}

/* If lan server disconnects than automatically restart link             */
/* When clientCall detects a disconnect it starts thread vxiReconnect      */
/* vxiReconnect repeatedly trys to connect to the server. When it connects */
/* it calls drvGpibResetLink and terminates.                             */
STATIC enum clnt_stat clientCall(vxiLink * pvxiLink, u_long req,
    xdrproc_t proc1, caddr_t addr1,xdrproc_t proc2, caddr_t addr2)
{
    int errnosave;
    enum clnt_stat stat;
    errno = 0;
    stat = clnt_call(pvxiLink->rpcClient,
        req, proc1, addr1, proc2, addr2, pvxiLocal->vxiRpcTimeout);
    if(stat == RPC_SUCCESS) return (stat);
    if((errno != ECONNRESET) && (errno != EPIPE)) return (stat);
    if(pvxiLink->restartActive) return (stat);
    errnosave = errno;
    pvxiLink->restartActive = 1;
    printf("epicsThreadCreate vxiReconnect\n");
    epicsThreadCreate("vxiReconnect", epicsThreadPriorityLow,
        epicsThreadGetStackSize(epicsThreadStackMedium),
        vxiReconnect, pvxiLink);
    errno = errnosave;
    return (stat);
}

STATIC void vxiReconnect(void *arg)
{
    vxiLink *pvxiLink = (vxiLink *) arg;
    int link = pvxiLink->head.link;
    struct sockaddr_in serverAddr;
    int fd, status;
    /* wait until we can connect */
    while(1) {
	errno = 0;
	fd = epicsSocketCreate(PF_INET, SOCK_STREAM, 0);
	if(fd == -1) {
	    printf("vxiReconnect failed %s\n", strerror(errno));
	    return;
	}
	memset((char *) &serverAddr, 0, sizeof(struct sockaddr_in));
	serverAddr.sin_family = PF_INET;
	/* 23 is telnet port */
	status = aToIPAddr(pvxiLink->hostName, 23, &serverAddr);
	if(status) {
	    printf("vxiReconnect aToIPAddr failed\n");
	    close(fd);
	    return;
	}
	errno = 0;
	status = connect(fd,(struct sockaddr *)&serverAddr,sizeof(serverAddr));
	printf("connect returned %d\n", status);
	if(status) {
	    epicsThreadSleep(10.0);
	    close(fd);
	    continue;
	}
	break;
    }
    close(fd);
    drvGpibResetLink(link);
    pvxiLink->restartActive = 0;
}

/******************************************************************************
 * This is the server task for srq interrupts.
 ******************************************************************************/
STATIC long vxiSrqTask()
{
    SVCXPRT *serverId;
    int status;

    srqdebug(1, ("%s: started\n", "vxiSrqTask"));
    if(rpcTaskInit() != 0) {
	errlogPrintf("%s: Can't init RPC for this task\n", "vxiSrqTask");
	taskwdRemove(epicsThreadGetIdSelf());
	return -1;
    }
    /* unmap our service numbers */
    pmap_unset(DEVICE_INTR, DEVICE_INTR_VERSION);
    errno = 0;
    serverId = svctcp_create(RPC_ANYSOCK, 0, 0);
    if(serverId == NULL) {
	errlogPrintf("%s: cannot create tcp service %s\n",
            "vxiSrqTask",strerror(errno));
	taskwdRemove(epicsThreadGetIdSelf());
	return -1;
    }
    errno = 0;
    status = svc_register(serverId,
        DEVICE_INTR, DEVICE_INTR_VERSION, vxiIrq, IPPROTO_TCP);
    if(!status) {
	errlogPrintf("%s: unable to register server function %s\n",
            "vxiSrqTask",strerror(errno));
	svc_destroy(serverId);
	taskwdRemove(epicsThreadGetIdSelf());
	return -1;
    }
    pvxiLocal->srqThreadId = epicsThreadGetIdSelf();
    taskwdInsert(pvxiLocal->srqThreadId, NULL, NULL);
    epicsEventSignal(pvxiLocal->srqTaskReady);
    svc_run();	/* server loop */
    /* unmap our service numbers */
    svc_unregister(DEVICE_INTR, DEVICE_INTR_VERSION);
    svc_destroy(serverId);
    pmap_unset(DEVICE_INTR, DEVICE_INTR_VERSION);
    errlogPrintf("%s: server loop terminated\n", "vxiSrqTask");
    taskwdRemove(epicsThreadGetIdSelf());
    return -1;
}

/******************************************************************************
 * This function is called by an interrupt server, when the gateway calls
 * the RPC device_intr_srq().
 ******************************************************************************/
STATIC void vxiIrq(struct svc_req * rqstp, SVCXPRT * transp)
{
    Device_SrqParms devSrqP;
    vxiLink *pvxiLink = NULL;
    int status;

    assert(rqstp);
    assert(transp);
    switch (rqstp->rq_proc) {
    case NULLPROC:
	break;
    case device_intr_srq:
	memset((char *) &devSrqP, 0, sizeof(Device_SrqParms));
	status = svc_getargs(transp, (const xdrproc_t) xdr_Device_SrqParms,
            (void *) &devSrqP);
        if(!status) {
	    svcerr_decode(transp);
	    break;
	}
	srqdebug(1, ("%s(%s): GPIB service request\n",
		     "vxiIrq", devSrqP.handle.handle_val));
	sscanf(devSrqP.handle.handle_val, "%p", (void **)&pvxiLink);
	if(!pvxiLink) {
	    errlogPrintf("%s(%s): unknown handle\n",
			 "vxiIrq", devSrqP.handle.handle_val);
	}
	status = svc_freeargs(transp,(const xdrproc_t) xdr_Device_SrqParms,
            (void *) &devSrqP);
        if(!status) {
	    errlogPrintf("%s(%s): unable to free arguments\n",
			 "vxiIrq", devSrqP.handle.handle_val);
	    break;
	}
	/* tell the generic driver that an SRQ happened on this link */
	if(pvxiLink) {
	    drvGpibSrqHappened(&pvxiLink->head);
        }
	break;
    default:
	svcerr_noproc(transp);
	errlogPrintf("%s(%p,%p):\n", "vxiIrq", rqstp, transp);
	return;
    }
    /* always return something */
    /* IS THIS NECESSARY. */
    if(!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
	svcerr_systemerr(transp);
    return;
}

STATIC long vxiInit()
{
    if(pvxiLocal) return(0);
    pvxiLocal = callocMustSucceed(1,sizeof(vxiLocal),"vxiInit");
    pvxiLocal->vxiRpcTimeout.tv_sec = DEFAULT_RPC_TIMEOUT;
#if defined(__rtems__)
    /* Start portmapper at fairly low priority (EPICS priority 20). */
    {
#include <rtems/error.h>
	rtems_status_code sc;
	if((sc = rtems_rpc_start_portmapper(180)) != RTEMS_SUCCESSFUL)
	    errlogPrintf("%s(): can't start RPC portmapper (%s)\n",
			 "vxiInit", rtems_status_text(sc));
    }
#endif
    if(rpcTaskInit() != 0) {	/* init RPC lib for iocInit task */
	errlogPrintf("%s(): can't init RPC library\n", "vxiInit");
	return -1;
    }
    get_myaddress(&pvxiLocal->iocAddr);	/* get our inet address */
    pvxiLocal->netAddr.s_addr = inet_netof(pvxiLocal->iocAddr.sin_addr);
    if(vxi11Debug >= 1) {
	char netName[128];
	struct in_addr dummy;
#ifdef vxWorks
	inet_makeaddr_b(pvxiLocal->netAddr.s_addr, 0, &dummy);
#else
	dummy = inet_makeaddr(pvxiLocal->netAddr.s_addr, 0);
#endif
	if(ipAddrToHostName(&dummy, netName, sizeof netName) > 0)
	    errlogPrintf("%s(): network address = %s\n", "vxiInit", netName);
    }
#if (defined(THREAD_SAFE_RPC))
    pvxiLocal->srqTaskReady = epicsEventMustCreate(epicsEventEmpty);
    epicsThreadCreate(srqThreadName, 46,
	  epicsThreadGetStackSize(epicsThreadStackMedium),
	  (EPICSTHREADFUNC) vxiSrqTask,NULL);
    epicsEventMustWait(pvxiLocal->srqTaskReady);
#else
    errlogPrintf("%s(): Can't start vxiSrqTask "
		 "-- RPC library is not thread-safe.\n", "vxiInit");
#endif
    errlogPrintf("drvVxi11 (release %s) initialized successfully\n",vxiRelease);
    return 0;
}

/******************************************************************************
 * Create an RPC client, create an interface link for the gateway, and finally
 * create device links for all registered devices.
 *
 * This function may only be called from a linkTask! (Otherwise results are
 * desastrous, i.e. system crash. This is caused by the VxWorks implementation
 * of the RPC library.)
 ******************************************************************************/
STATIC long vxiGenLink(ibLink * pibLink, int pass)
{
    enum clnt_stat clntStat;
    int isController;
    Device_Error devErr;
    Device_RemoteFunc devRemF;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    Device_Link link;

    debug(2, ("%s(%p,%d): called\n", "vxiGenLink", (void *) pvxiLink, pass));
    /* we do everything in the second pass, when the task is started */
    if(pass == 0) return drvGpibLinkStart(pibLink);
    assert(pvxiLink);
    if(rpcTaskInit() == -1) {
	errlogPrintf("%s(%p,%d): Can't init RPC for this task\n",
            "vxiGenLink", pvxiLink, pass);
        return(-1);
    }
    debug(2, ("%s(%p): rpcTaskInit done\n", "vxiGenLink", (void *) pvxiLink));
    pvxiLink->rpcClient = clnt_create(pvxiLink->hostName,
        DEVICE_CORE, DEVICE_CORE_VERSION, "tcp");
    if(!pvxiLink->rpcClient) {
	errlogPrintf("%s(%p): %s", "vxiGenLink",
            pvxiLink, clnt_spcreateerror(pvxiLink->hostName));
	return -1;
    }
    debug(2,("%s(%p): rpc client created\n", "vxiGenLink", (void *) pvxiLink));
    /* now establish a link to the gateway (for docmds etc.) */
    if(!vxiCreateLink(pvxiLink,pvxiLink->vxiName,&link)) return(-1);
    pvxiLink->serverAddr = link;
    debug(2,("%s(%p): vxi link to gateway created\n",
        "vxiGenLink",(void *) pvxiLink));
    /* Ask the controller's gpib address.*/
    memset((char *) pvxiLink->devLink, 0, IBAPERLINK * sizeof(devLinkPrimary));
    pvxiLink->head.ctrlAddr = vxiBusStatus(pvxiLink,
        VXI_BSTAT_BUS_ADDRESS,pvxiLink->defTimeout);
    if(pvxiLink->head.ctrlAddr == -1) {
	errlogPrintf("%s(%p):cannot read bus status, initialization aborted\n",
	     "vxiGenLink", pvxiLink);
	clnt_destroy(pvxiLink->rpcClient);
	return -1;
    }
    /* initialize the vxiLink structure with the data we have got so far */
    pvxiLink->devLink[pvxiLink->head.ctrlAddr].primary = link;
    pvxiLink->devLink[pvxiLink->head.ctrlAddr].primaryConnected = 1;
    /* now we can use vxiBusStatus; if we are not the controller fail */
    if(!pvxiLink->isSingleLink) {
        isController = vxiBusStatus(pvxiLink, VXI_BSTAT_SYSTEM_CONTROLLER,
            pvxiLink->defTimeout);
        if(isController <= 0) {
            errlogPrintf("vxiGenLink vxiBusStatus error %d"
                         " initialization aborted\n",isController);
            clnt_destroy(pvxiLink->rpcClient);
            return -1;
        }
    }
    /* report what we have done */
    debug(1, ("%s(%p):\n"
	      "  ctrlAddr    = %d\n"
	      "  isSingleLink = %d\n"
	      "  rpcClient   = %p\n"
	      "  maxRecvSize = %lu\n", "vxiGenLink", (void *) pvxiLink,
	      pvxiLink->head.ctrlAddr, pvxiLink->isSingleLink,
              (void *) pvxiLink->rpcClient, pvxiLink->maxRecvSize));
    /* fire out an interface clear pulse */
    vxiIfc(pibLink);
#if (defined(THREAD_SAFE_RPC))
    /* create the interrupt channel */
    devRemF.hostAddr = ntohl(pvxiLocal->iocAddr.sin_addr.s_addr);
    devRemF.hostPort = 0;
    devRemF.progNum = DEVICE_INTR;
    devRemF.progVers = DEVICE_INTR_VERSION;
    devRemF.progFamily = DEVICE_TCP;
    memset((char *) &devErr, 0, sizeof(Device_Error));
    clntStat = clientCall(pvxiLink, create_intr_chan,
        (const xdrproc_t) xdr_Device_RemoteFunc, (void *) &devRemF,
        (const xdrproc_t) xdr_Device_Error, (void *) &devErr);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p), (create_intr_chan)%s\n", "vxiGenLink",
            pvxiLink, clnt_sperror(pvxiLink->rpcClient, ""));
	xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
	clnt_destroy(pvxiLink->rpcClient);
	return -1;
    } else if(devErr.error != VXI_OK) {
	errlogPrintf("%s(%p): %s (create_intr_chan)\n", "vxiGenLink",
            pvxiLink, vxiError(devErr.error));
	xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
	clnt_destroy(pvxiLink->rpcClient);
	return -1;
    }
    vxiSrqEnable(&pvxiLink->head);
    xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
#endif
    errlogPrintf("drvVxi11 (release %s) initialized link %d\n",
        vxiRelease, pvxiLink->head.link);
    return 0;
}

/******************************************************************************
 * Destroy a gateway link, including all device links and the RPC client.
 ******************************************************************************/
STATIC long vxiDestroyLink(ibLink * pibLink)
{
    int gpibAddr;
    int secondary;
    Device_Error devErr;
    int dummy;
    enum clnt_stat clntStat;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    assert(pvxiLink);
    debug(2, ("%s(%p): called\n", "vxiDestroyLink", (void *) pvxiLink));
    for(gpibAddr = 0; gpibAddr < IBAPERLINK; gpibAddr++) {
	int link;
	link = pvxiLink->devLink[gpibAddr].primary;
	if(link > 0) vxiDestroyDevLink(pvxiLink, link);
	for(secondary = 0; secondary < IBAPERLINK; secondary++) {
	    link = pvxiLink->devLink[gpibAddr].secondary[secondary];
	    if(link > 0) vxiDestroyDevLink(pvxiLink, link);
	}
    }
    clntStat = clientCall(pvxiLink, destroy_intr_chan,
        (const xdrproc_t) xdr_void, (void *) &dummy,
        (const xdrproc_t) xdr_Device_Error,(void *) &devErr);
    /* report errors only if debug flag set. If any errors, the next time this
     * link is initialized, make sure a new SRQ task is started.  See comments
     * in vxiGenLink. */
    if(clntStat != RPC_SUCCESS) {
	debug(1, ("%s(%p)%s\n", "vxiDestroyLink",
            (void *) pvxiLink, clnt_sperror(pvxiLink->rpcClient,"")));
    } else if(devErr.error != VXI_OK) {
	debug(1, ("%s(%p): %s\n", "vxiDestroyLink",
            (void *) pvxiLink, vxiError(devErr.error)));
    }
    xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
    clnt_destroy(pvxiLink->rpcClient);
    return 0;
}

STATIC long vxiReportLink(ibLink * pibLink, int level)
{
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    assert(pvxiLink);
    printf("  HP link, host name: %s\n", pvxiLink->hostName);
    if(level > 1) {
	char nameBuf[60];
	if(ipAddrToHostName(&pvxiLink->inAddr, nameBuf, sizeof nameBuf) > 0)
	    printf(" ip address: %s\n", nameBuf);
	printf("  vxi name: %s\n", pvxiLink->vxiName);
	printf("  maxRecvSize: %lu\n", pvxiLink->maxRecvSize);
	printf("  isSingleLink: %s\n", ((pvxiLink->isSingleLink) ? "yes" : "no"));
	printf("  srq task: %s (%p)\n", srqThreadName,
	       (void *) pvxiLocal->srqThreadId);
    }
    return 0;
}

STATIC long vxiProbe(ibLink * pibLink)
{
    int isController;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    assert(pibLink);
    isController = vxiBusStatus(pvxiLink, VXI_BSTAT_SYSTEM_CONTROLLER,
        pvxiLink->defTimeout);
    if(isController == -1) {
	errlogPrintf("%s(%d): fatal error, cannot read bus status,"
		     " assuming gateway is dead.\n"
		     "  All operations suspended until reinitialization.\n",
		     "vxiProbe", pibLink->link);
	return -1;
    }
    if(!isController) {
	errlogPrintf("%s(%d): fatal error, gateway is not bus controller.\n"
		     "  All operations suspended until reinitialization.\n",
		     "vxiProbe", pibLink->link);
	return -1;
    }
    return 0;
}

/******************************************************************************
 * Read data from a specific device into a buffer.
 * Returns number of bytes read or ERROR.
 ******************************************************************************/
STATIC long vxiRead(ibLink * pibLink, int gpibAddr, int secondary,
		        char *buffer, int length, int timeout, int eos)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_ReadParms devReadP;
    Device_ReadResp devReadR;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    debug(2, ("%s(%p, %d, %d %p, %d, %d, %d): called\n", "vxiRead",
        (void *) pvxiLink, gpibAddr, secondary, buffer, length, timeout,eos));
    assert(pvxiLink);
    assert(buffer);
    if(!vxiSetDevLink(pvxiLink, gpibAddr, secondary,&devReadP.lid)) return -1;
    /* device link is created; do the read */
    do{
	devReadP.requestSize = length;
	devReadP.io_timeout = timeout;
	devReadP.lock_timeout = 0;
	devReadP.flags = 0;
	if(eos != -1) {
	    devReadP.flags |= VXI_TERMCHRSET;
	    devReadP.termChar = eos;
	}
	/* initialize devReadR */
	memset((char *) &devReadR, 0, sizeof(Device_ReadResp));
	/* RPC call */
	clntStat = clientCall(pvxiLink, device_read,
            (const xdrproc_t) xdr_Device_ReadParms,(void *) &devReadP,
            (const xdrproc_t) xdr_Device_ReadResp,(void *) &devReadR);
	if(clntStat != RPC_SUCCESS) {
	    errlogPrintf("%s(%p, %d, %p, %d, %d, %d)%s\n", "vxiRead",
                pvxiLink, gpibAddr, buffer, length, timeout, eos,
                clnt_sperror(pvxiLink->rpcClient, ""));
	    status = -1;
	} else if(devReadR.error != VXI_OK) {
	    if(devReadR.error != VXI_IOTIMEOUT) {
		errlogPrintf("%s(%p, %d, %p, %d, %d, %d): %s\n", "vxiRead",
                    pvxiLink, gpibAddr, buffer, length, timeout,
                    eos, vxiError(devReadR.error));
            }
	    if(devReadR.error == VXI_IOTIMEOUT && pvxiLink->recoverWithIFC){
		/* try to recover */
		vxiAddressedCmd(pibLink,gpibAddr,secondary,IBSDC,1,timeout);
            }
	    status = -1;
	} else {
	    memcpy(buffer, devReadR.data.data_val, devReadR.data.data_len);
	    debug(1, ("%s(): returned \"%s\", length %d\n", "vxiRead", buffer,
                devReadR.data.data_len));
	    status += devReadR.data.data_len;
	    buffer += devReadR.data.data_len;
	    length -= devReadR.data.data_len;
	}
	xdr_free((const xdrproc_t) xdr_Device_ReadResp, (char *) &devReadR);
    }
    while(status != -1 && !devReadR.reason);
    /* send <UNT,UNL> after completion */
    /* SHOULD THIS BE DONE ???*/
    if(vxiWriteCmd(pibLink, "_?", 2) != 2) return -1;
    return status;
}

/******************************************************************************
 *
 * Write the contents of a buffer to a specific device.
 * Returns number of bytes written or ERROR.
 *
 ******************************************************************************/
STATIC long vxiWrite(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_WriteParms devWriteP;
    Device_WriteResp devWriteR;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    int rtnlen = 0;
    int lennow;

    debug(1, ("%s(%p, %d, %d \"%s\", %d, %d): called\n", "vxiWrite",
        (void *) pvxiLink, gpibAddr, secondary, buffer, length,timeout));
    assert(pvxiLink);
    assert(buffer);
    if(!vxiSetDevLink(pvxiLink, gpibAddr, secondary,&devWriteP.lid)) return -1;
    devWriteP.io_timeout = timeout;
    devWriteP.lock_timeout = 0;
    /*write at most maxRecvSize bytes at a time */
    do {
        if(length<=pvxiLink->maxRecvSize) {
            devWriteP.flags = VXI_ENDW;
            lennow = length;
        } else {
            devWriteP.flags = 0;
            lennow = pvxiLink->maxRecvSize;
        }
        devWriteP.data.data_len = lennow;
        devWriteP.data.data_val = buffer;
        /* initialize devWriteR */
        memset((char *) &devWriteR, 0, sizeof(Device_WriteResp));
        /* RPC call */
        clntStat = clientCall(pvxiLink, device_write,
            (const xdrproc_t) xdr_Device_WriteParms,(void *) &devWriteP,
            (const xdrproc_t) xdr_Device_WriteResp,(void *) &devWriteR);
        if(clntStat != RPC_SUCCESS) {
    	    errlogPrintf("%s(%p, %d, \"%s\", %d, %d)%s\n", "vxiWrite", pvxiLink,
                gpibAddr, buffer, length, timeout,
                clnt_sperror(pvxiLink->rpcClient, ""));
    	    status = -1;
        } else if(devWriteR.error != VXI_OK) {
    	    if(devWriteR.error != VXI_IOTIMEOUT) {
    	        errlogPrintf("%s(%p, %d, \"%s\", %d, %d): %s\n", "vxiWrite",
                    pvxiLink, gpibAddr, buffer, length, timeout,
                    vxiError(devWriteR.error));
            }
    	    if(devWriteR.error == VXI_IOTIMEOUT && pvxiLink->recoverWithIFC) 
    	        vxiAddressedCmd(pibLink,gpibAddr,secondary,IBSDC,1,timeout);
    	    status = -1;
        } else {
    	    status = devWriteR.size;
        }
        xdr_free((const xdrproc_t) xdr_Device_WriteResp, (char *) &devWriteR);
        buffer += lennow;
        length -= lennow;
        rtnlen += lennow;
    } while(status==lennow && length>0);
    /* send <UNT,UNL> after completion */
    /* SHOULD THIS BE DONE ???*/
    if(vxiWriteCmd(pibLink, "_?", 2) != 2) return -1;
    return rtnlen;
}

STATIC long vxiAddressedCmd(ibLink * pibLink, int gpibAddr, int secondary,
    char *buffer, int length, int timeout)
{
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    Device_Link lid;
    long status;

    debug(2, ("%s(%p, %p, %d): called\n", "vxiAddressedCmd",
        (void *) pvxiLink, buffer, length));
    assert(pvxiLink);
    assert(buffer);
    if(!vxiSetDevLink(pvxiLink, gpibAddr, secondary,&lid)) return -1;
    status = vxiWriteAddressed(pvxiLink,lid,buffer,length,timeout);
    return(status);
}

STATIC long vxiUniversialCmd(ibLink * pibLink, int cmd)
{
    long nout;
    char buffer[2];
    buffer[0] = cmd;
    buffer[1] = 0;
    nout = vxiWriteCmd(pibLink, buffer, 1);
    return( nout==1 ? 0 : -1 );
}

STATIC long vxiIfc(ibLink * pibLink)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_DocmdParms devDocmdP;
    Device_DocmdResp devDocmdR;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    debug(2, ("%s(%p): called\n", "vxiIfc", (void *) pvxiLink));
    assert(pvxiLink);
    /* initialize devDocmdP */
    devDocmdP.lid = pvxiLink->serverAddr;
    devDocmdP.flags = 0;	/* no timeout on a locked gateway */
    devDocmdP.io_timeout = pvxiLink->defTimeout;
    devDocmdP.lock_timeout = 0;
    devDocmdP.network_order = NETWORK_ORDER;
    devDocmdP.cmd = VXI_CMD_IFC;
    devDocmdP.datasize = 0;
    devDocmdP.data_in.data_in_len = 0;
    devDocmdP.data_in.data_in_val = NULL;
    /* initialize devDocmdR */
    memset((char *) &devDocmdR, 0, sizeof(Device_DocmdResp));
    /* RPC call */
    clntStat = clientCall(pvxiLink, device_docmd,
        (const xdrproc_t) xdr_Device_DocmdParms,(void *) &devDocmdP,
        (const xdrproc_t) xdr_Device_DocmdResp, (void *) &devDocmdR);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p)\n", "vxiIfc", pvxiLink);
	status = -1;
    } else if(devDocmdR.error != VXI_OK) {
	if(devDocmdR.error != VXI_IOTIMEOUT) {
	    errlogPrintf("%s(%p): %s\n", "vxiIfc",
                pvxiLink, vxiError(devDocmdR.error));
        }
	status = -1;
    }
    xdr_free((const xdrproc_t) xdr_Device_DocmdResp, (char *) &devDocmdR);
    return status;
}

STATIC long vxiRen(ibLink * pibLink, int onOff)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_DocmdParms devDocmdP;
    Device_DocmdResp devDocmdR;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    unsigned short data,netdata;

    debug(2, ("%s(%p): called\n", "vxiIfc", (void *) pvxiLink));
    assert(pvxiLink);
    /* initialize devDocmdP */
    devDocmdP.lid = pvxiLink->serverAddr;
    devDocmdP.flags = 0;	/* no timeout on a locked gateway */
    devDocmdP.io_timeout = pvxiLink->defTimeout;
    devDocmdP.lock_timeout = 0;
    devDocmdP.network_order = NETWORK_ORDER;
    devDocmdP.cmd = VXI_CMD_REN;
    devDocmdP.datasize = 2;
    devDocmdP.data_in.data_in_len = 2;
    data = onOff ? 1 : 0;
    netdata = htons(data);
    devDocmdP.data_in.data_in_val = (char *)&netdata;
    /* initialize devDocmdR */
    memset((char *) &devDocmdR, 0, sizeof(Device_DocmdResp));
    /* RPC call */
    clntStat = clientCall(pvxiLink, device_docmd,
        (const xdrproc_t) xdr_Device_DocmdParms,(void *) &devDocmdP,
        (const xdrproc_t) xdr_Device_DocmdResp, (void *) &devDocmdR);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p)\n", "vxiIfc", pvxiLink);
	status = -1;
    } else if(devDocmdR.error != VXI_OK) {
	if(devDocmdR.error != VXI_IOTIMEOUT) {
	    errlogPrintf("%s(%p): %s\n", "vxiIfc",
                pvxiLink, vxiError(devDocmdR.error));
        }
	status = -1;
    }
    xdr_free((const xdrproc_t) xdr_Device_DocmdResp, (char *) &devDocmdR);
    return status;
}

STATIC long vxiSrqStatus(ibLink * pibLink)
{
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    long status;
    assert(pibLink);
    status = vxiBusStatus(pvxiLink, VXI_BSTAT_SRQ, pvxiLink->defTimeout);
    srqdebug(2, ("%s(%p): vxiBusStatus %x\n",
        "vxiSrqStatus", (void *) pibLink, (int)status));
    return(status);
}

STATIC long vxiSrqEnable(ibLink * pibLink)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_EnableSrqParms devEnSrqP;
    Device_Error devErr;
    char handle[16];
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    debug(3, ("%s(%p): called\n", "vxiSrqEnable", (void *) pvxiLink));
    assert(pvxiLink);
    devEnSrqP.lid = pvxiLink->serverAddr;
    devEnSrqP.enable = TRUE;
    sprintf(handle, "%p", (void *) pvxiLink);
    devEnSrqP.handle.handle_val = handle;
    devEnSrqP.handle.handle_len = strlen(handle) + 1;	/* include the '\0' */
    /* initialize devErr */
    memset((char *) &devErr, 0, sizeof(Device_Error));
    /* RPC call */
    clntStat = clientCall(pvxiLink, device_enable_srq,
        (const xdrproc_t) xdr_Device_EnableSrqParms,(void *) &devEnSrqP,
        (const xdrproc_t) xdr_Device_Error, (void *) &devErr);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p)%s\n", "vxiSrqEnable",
		     pvxiLink, clnt_sperror(pvxiLink->rpcClient, ""));
	status = -1;
    } else if(devErr.error != VXI_OK) {
	errlogPrintf("%s(%p): %s\n", "vxiSrqEnable",
		     pvxiLink, vxiError(devErr.error));
	status = -1;
    }
    xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
    return status;
}

STATIC long vxiSrqDisable(ibLink * pibLink)
{
    int status = 0;
    enum clnt_stat clntStat;
    Device_EnableSrqParms devEnSrqP;
    Device_Error devErr;
    vxiLink *pvxiLink = (vxiLink *) pibLink;
    debug(3, ("%s(%p): called\n", "vxiSrqDisable", (void *) pvxiLink));
    assert(pvxiLink);
    devEnSrqP.lid = pvxiLink->serverAddr;
    devEnSrqP.enable = FALSE;
    devEnSrqP.handle.handle_val = "";
    devEnSrqP.handle.handle_len = 0;
    /* initialize devErr */
    memset((char *) &devErr, 0, sizeof(Device_Error));
    /* RPC call */
    clntStat = clientCall(pvxiLink, device_enable_srq,
        (const xdrproc_t) xdr_Device_EnableSrqParms, (void *)&devEnSrqP,
        (const xdrproc_t) xdr_Device_Error,(void *)&devErr);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p)%s\n", "vxiSrqDisable",
		     pvxiLink, clnt_sperror(pvxiLink->rpcClient, ""));
	status = -1;
    } else if(devErr.error != VXI_OK) {
	errlogPrintf("%s(%p): %s\n", "vxiSrqDisable",
		     pvxiLink, vxiError(devErr.error));
	status = -1;
    }
    xdr_free((const xdrproc_t) xdr_Device_Error, (char *) &devErr);
    return status;
}

STATIC long vxiSerialPollBegin(ibLink * pibLink)
{
    return 0;
}

STATIC long vxiSerialPoll(ibLink * pibLink, int gpibAddr, int secondary,
			      int timeout)
{
    enum clnt_stat clntStat;
    Device_GenericParms devGenP;
    Device_ReadStbResp devGenR;
    unsigned char stb;
    vxiLink *pvxiLink = (vxiLink *) pibLink;

    debug(2, ("%s(%p, %d,%d): called\n", "vxiSerialPoll",
        (void *) pibLink, gpibAddr, secondary));
    assert(pvxiLink);
    if(!vxiSetDevLink(pvxiLink, gpibAddr, secondary,&devGenP.lid)) return -1;
    devGenP.flags = 0;	/* no timeout on a locked gateway */
    devGenP.io_timeout = timeout;
    devGenP.lock_timeout = 0;
    /* initialize devGenR */
    memset((char *) &devGenR, 0, sizeof(Device_ReadStbResp));
    clntStat = clientCall(pvxiLink, device_readstb,
        (const xdrproc_t) xdr_Device_GenericParms, (void *) &devGenP,
        (const xdrproc_t) xdr_Device_ReadStbResp, (void *) &devGenR);
    if(clntStat != RPC_SUCCESS) {
	errlogPrintf("%s(%p, %d)%s\n", "vxiSerialPoll",
            pvxiLink, gpibAddr, clnt_sperror(pvxiLink->rpcClient,""));
	return -1;
    } else if(devGenR.error != VXI_OK) {
	if(devGenR.error != VXI_IOTIMEOUT) {
	    errlogPrintf("%s(%p, %d): %s\n", "vxiSerialPoll",
                pvxiLink, gpibAddr, vxiError(devGenR.error));
        }
	return -1;
    } else {
	stb = devGenR.stb;
    }
    xdr_free((const xdrproc_t) xdr_Device_ReadStbResp, (char *) &devGenR);
    return stb;
}

STATIC long vxiSerialPollEnd(ibLink * pibLink)
{
    return 0;
}

/******************************************************************************
 *
 * The entry table to this driver.
 *
 ******************************************************************************/
drvHwGpibEt drvVxi11 = {
    vxiGenLink,
    vxiDestroyLink,
    vxiReportLink,
    vxiProbe,
    vxiRead,
    vxiWrite,
    vxiAddressedCmd,
    vxiUniversialCmd,
    vxiIfc,
    vxiRen,
    vxiSrqStatus,
    vxiSrqEnable,
    vxiSrqDisable,
    vxiSerialPollBegin,
    vxiSerialPoll,
    vxiSerialPollEnd
};

int vxi11SetRpcTimeout(double timeout)
{
    int seconds,microseconds;

    seconds = (int)timeout;
    microseconds = (int)(((timeout - (double)seconds))/1e6);
    pvxiLocal->vxiRpcTimeout.tv_sec = seconds;
    pvxiLocal->vxiRpcTimeout.tv_usec = microseconds;
    return(0);
}

int drvVxi11Config(int link, char *hostName, int recoverWithIFC,
    int defTimeout, /* in msec */
    char *vxiName,
    int isSingleLink)
{
    vxiLink *pvxiLink;
    struct in_addr inAddr;

    /* Force registration */
    if(vxiInit() != 0) return -1;
    if(hostName) {
	struct sockaddr_in ip;
	if(aToIPAddr(hostName, 0, &ip) < 0)
	    return errlogPrintf("Unknown host: \"%s\"\n", hostName), -1;
	inAddr.s_addr = ip.sin_addr.s_addr;
    } else {
	inAddr.s_addr = link;	/* default if no name or address is given */
    }
    /* allocate vxiLink structure */
    pvxiLink = (vxiLink *)callocMustSucceed(1,sizeof(vxiLink),"drvVxi11Config");
    if(!vxiName) {
	strcpy(pvxiLink->vxiName, "gpib0");
    } else {
	strncpy(pvxiLink->vxiName, vxiName, sizeof(pvxiLink->vxiName) - 1);
    }
    if(defTimeout)
	pvxiLink->defTimeout = defTimeout;
    if(recoverWithIFC)
	pvxiLink->recoverWithIFC = TRUE;
    if(inet_netof(inAddr) == 0) {	/* they gave us only the local part */
#ifdef vxWorks
	inet_makeaddr_b(pvxiLocal->netAddr.s_addr, inAddr.s_addr, &pvxiLink->inAddr);
#else
	pvxiLink->inAddr = inet_makeaddr(pvxiLocal->netAddr.s_addr, inAddr.s_addr);
#endif
    } else {
	pvxiLink->inAddr = inAddr;
    }
    pvxiLink->hostName = (char *) malloc(strlen(hostName) + 1);
    if(!pvxiLink->hostName) {
	errlogPrintf("Can't allocate hostname string\n");
	free(pvxiLink);
	return -1;
    }
    pvxiLink->isSingleLink = (isSingleLink ? 1 : 0 );
    strcpy(pvxiLink->hostName, hostName);
    pvxiLink->head.link = link;
    pvxiLink->head.pdrvHwGpibEt = &drvVxi11;
    if(drvGpibLinkInit(&pvxiLink->head) != 0) {
	free(pvxiLink);
	return -1;
    }
    return 0;
}

/*
 * IOC shell command registration
 */
#include <iocsh.h>
static const iocshArg drvVxi11ConfigArg0 = { "link",iocshArgInt};
static const iocshArg drvVxi11ConfigArg1 = { "host name",iocshArgString};
static const iocshArg drvVxi11ConfigArg2 = { "recover with IFC?",iocshArgInt};
static const iocshArg drvVxi11ConfigArg3 = { "default timeout (msec)",iocshArgInt};
static const iocshArg drvVxi11ConfigArg4 = { "vxiName",iocshArgString};
static const iocshArg drvVxi11ConfigArg5 = { "isSingleLink",iocshArgInt};
static const iocshArg *drvVxi11ConfigArgs[] = {&drvVxi11ConfigArg0,
    &drvVxi11ConfigArg1, &drvVxi11ConfigArg2, &drvVxi11ConfigArg3,
    &drvVxi11ConfigArg4,&drvVxi11ConfigArg5};
static const iocshFuncDef drvVxi11ConfigFuncDef = {"drvVxi11Config",6,drvVxi11ConfigArgs};
static void drvVxi11ConfigCallFunc(const iocshArgBuf *args)
{
    int link = args[0].ival;
    char *hostName = args[1].sval;
    int recoverWithIFC = args[2].ival;
    int defTimeout = args[3].ival;
    char *vxiName = args[4].sval;
    int isSingleLink = args[5].ival;

    drvVxi11Config (link, hostName, recoverWithIFC,
        defTimeout, vxiName, isSingleLink);
}

extern int E5810Reboot(char * inetAddr,char *password);
extern int E2050Reboot(char * inetAddr);

static const iocshArg E5810RebootArg0 = { "inetAddr",iocshArgString};
static const iocshArg E5810RebootArg1 = { "password",iocshArgString};
static const iocshArg *E5810RebootArgs[2] = {&E5810RebootArg0,&E5810RebootArg1};
static const iocshFuncDef E5810RebootFuncDef = {"E5810Reboot",2,E5810RebootArgs};
static void E5810RebootCallFunc(const iocshArgBuf *args)
{
    E5810Reboot(args[0].sval,args[1].sval);
}

static const iocshArg E2050RebootArg0 = { "inetAddr",iocshArgString};
static const iocshArg *E2050RebootArgs[1] = {&E2050RebootArg0};
static const iocshFuncDef E2050RebootFuncDef = {"E2050Reboot",1,E2050RebootArgs};
static void E2050RebootCallFunc(const iocshArgBuf *args)
{
    E2050Reboot(args[0].sval);
}

static const iocshArg vxi11DebugArg0 = {"level",iocshArgInt};
static const iocshArg *vxi11DebugArgs[1] = {&vxi11DebugArg0};
static const iocshFuncDef vxi11DebugFuncDef = {"setVxi11Debug",1,vxi11DebugArgs};
static void vxi11DebugCallFunc(const iocshArgBuf *args)
{
    int level = args[0].ival;
    vxi11Debug = level;
}

static const iocshArg vxi11SetRpcTimeoutArg0 = {"double",iocshArgDouble};
static const iocshArg *vxi11SetRpcTimeoutArgs[1] = {&vxi11SetRpcTimeoutArg0};
static const iocshFuncDef vxi11SetRpcTimeoutFuncDef =
    {"vxi11SetRpcTimeout",1,vxi11SetRpcTimeoutArgs};
static void vxi11SetRpcTimeoutCallFunc(const iocshArgBuf *args)
{ 
    vxi11SetRpcTimeout(args[0].dval);
}

/*
 * This routine is called before multitasking has started, so there's
 * no race condition in the test/set of firstTime.
 */
static void drvVxi11RegisterCommands (void)
{
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        iocshRegister(&drvVxi11ConfigFuncDef,drvVxi11ConfigCallFunc);
        iocshRegister(&E2050RebootFuncDef,E2050RebootCallFunc);
        iocshRegister(&E5810RebootFuncDef,E5810RebootCallFunc);
        iocshRegister(&vxi11DebugFuncDef,vxi11DebugCallFunc);
        iocshRegister(&vxi11SetRpcTimeoutFuncDef,vxi11SetRpcTimeoutCallFunc);
    }
}
epicsExportRegistrar(drvVxi11RegisterCommands);
