示例#1
0
asynStatus mar345::erase()
{
    int numErase;
    int i;
    asynStatus status=asynSuccess;
    //const char *functionName = "erase";

    getIntegerParam(mar345NumErase, &numErase);
    if (numErase < 1) numErase=1;
    setIntegerParam(ADStatus, mar345StatusErase);
    setIntegerParam(mar345NumErased, 0);
    callParamCallbacks();
    for (i=0; i<numErase; i++) {
        if (epicsEventTryWait(this->abortEventId) == epicsEventWaitOK) {
            status = asynError;
            break;
        }
        epicsSnprintf(this->toServer, sizeof(this->toServer), "COMMAND ERASE");
        writeServer(this->toServer);
        status = waitForCompletion("SCAN_DATA    Ended o.k.", MAR345_COMMAND_TIMEOUT);
        if (status) break;
        setIntegerParam(mar345NumErased, i+1);
        callParamCallbacks();
    }
    setIntegerParam(ADStatus, mar345StatusIdle);
    setIntegerParam(mar345Erase, 0);
    callParamCallbacks();
    return(status);
}
示例#2
0
static void cancelTest(asynUser *pasynUser)
{
    cmdInfo    *pcmdInfo = (cmdInfo *)pasynUser->userPvt;
    threadInfo *pthreadInfo = pcmdInfo->pthreadInfo;
    asynStatus status;
    int        wasQueued;

    fprintf(pcmdInfo->file,"%s %s  cancelRequest should remove\n",
        pthreadInfo->threadName,pcmdInfo->message);
    startBusy(pthreadInfo);
    epicsEventTryWait(pcmdInfo->callbackDone);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow,0.0);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    epicsThreadSleep(.01);
    status = pasynManager->cancelRequest(pasynUser,&wasQueued);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    fprintf(pcmdInfo->file,"%s %s cancelRequest wasQueued %d\n",
        pthreadInfo->threadName,pcmdInfo->message,wasQueued);
    if(!wasQueued) epicsEventMustWait(pcmdInfo->callbackDone);
    epicsThreadSleep(.04); /*wait for busy to complete*/

    fprintf(pcmdInfo->file,"%s %s should find callback active\n",
        pthreadInfo->threadName,pcmdInfo->message);
    epicsEventTryWait(pcmdInfo->callbackDone);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow,0.05);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    epicsThreadSleep(.01);
    status = pasynManager->cancelRequest(pasynUser,&wasQueued);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    fprintf(pcmdInfo->file,"%s %s cancelRequest wasQueued %d\n",
        pthreadInfo->threadName,pcmdInfo->message,wasQueued);
    if(!wasQueued) epicsEventMustWait(pcmdInfo->callbackDone);

    fprintf(pcmdInfo->file,"%s %s should find timeout active\n",
        pthreadInfo->threadName,pcmdInfo->message);
    startBusy(pthreadInfo);
    epicsEventTryWait(pcmdInfo->callbackDone);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow,0.02);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    epicsThreadSleep(.03);
    status = pasynManager->cancelRequest(pasynUser,&wasQueued);
    if(checkStatus(status,pthreadInfo,"testCancelRequest")) return;
    fprintf(pcmdInfo->file,"%s %s cancelRequest wasQueued %d\n",
        pthreadInfo->threadName,pcmdInfo->message,wasQueued);
    if(!wasQueued) epicsEventMustWait(pcmdInfo->callbackDone);
}
示例#3
0
static void connectTest(asynUser *pasynUser)
{
    cmdInfo    *pcmdInfo = (cmdInfo *)pasynUser->userPvt;
    threadInfo *pthreadInfo = pcmdInfo->pthreadInfo;
    asynStatus status;

    fprintf(pcmdInfo->file,"%s connect queueRequest\n", pthreadInfo->threadName);
    epicsEventTryWait(pcmdInfo->callbackDone);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityConnect,0.0);
    if(checkStatus(status,pthreadInfo,"connect")) return;
    epicsEventMustWait(pcmdInfo->callbackDone);
}
示例#4
0
/*
 * Return whether the last get completed. In safe mode, as a
 * side effect, copy value from shared buffer to state set local buffer.
 */
epicsShareFunc boolean epicsShareAPI seq_pvGetComplete(SS_ID ss, VAR_ID varId)
{
	epicsEventId	getSem = ss->getSemId[varId];
	SPROG		*sp = ss->sprog;
	CHAN		*ch = sp->chan + varId;
	pvStat		status;

	if (!ch->dbch)
	{
		/* Anonymous PVs always complete immediately */
		if (!(sp->options & OPT_SAFE))
			errlogSevPrintf(errlogMajor,
				"pvGetComplete(%s): user error (variable not assigned)\n",
				ch->varName);
		return TRUE;
	}

	if (!ss->getReq[varId])
	{
		errlogSevPrintf(errlogMinor,
			"pvGetComplete(%s): no pending get request for this variable\n",
			ch->varName);
		return TRUE;
	}

	switch (epicsEventTryWait(getSem))
	{
	case epicsEventWaitOK:
		ss->getReq[varId] = NULL;
		epicsEventSignal(getSem);
		status = check_connected(ch->dbch, metaPtr(ch,ss));
		/* TODO: returning either TRUE or FALSE here seems wrong. We return TRUE,
		   so that state sets don't hang. Still means that user code has to check
		   status by calling pvStatus and/or pvMessage. */
		if (status) return TRUE;
		/* In safe mode, copy value and meta data from shared buffer
		   to ss local buffer. */
		if (sp->options & OPT_SAFE)
			/* Copy regardless of whether dirty flag is set or not */
			ss_read_buffer(ss, ch, FALSE);
		return TRUE;
	case epicsEventWaitTimeout:
		return FALSE;
	case epicsEventWaitError:
		ss->getReq[varId] = NULL;
		epicsEventSignal(getSem);
		errlogSevPrintf(errlogFatal, "pvGetComplete: "
		  "epicsEventTryWait(getSemId[%d]) failure\n", varId);
	default: /* pacify gcc which does not understand the we checked all possibilities */
		return FALSE;
	}
}
示例#5
0
epicsEventStatus
epicsEventWaitWithTimeout(epicsEventId id, double timeOut)
{
    rtems_id sid = (rtems_id)id;
    rtems_status_code sc;
    rtems_interval delay;
    extern double rtemsTicksPerSecond_double;
    
    if (timeOut <= 0.0)
        return epicsEventTryWait(id);
    SEMSTAT(1)
    delay = timeOut * rtemsTicksPerSecond_double;
    if (delay == 0)
        delay++;
    sc = rtems_semaphore_obtain (sid, RTEMS_WAIT, delay);
    if (sc == RTEMS_SUCCESSFUL)
        return epicsEventOK;
    else if (sc == RTEMS_TIMEOUT)
        return epicsEventWaitTimeout;
    else
        return epicsEventError;
}
示例#6
0
static void blockTest(asynUser *pasynUser)
{
    cmdInfo    *pcmdInfo = (cmdInfo *)pasynUser->userPvt;
    threadInfo *pthreadInfo = pcmdInfo->pthreadInfo;
    asynStatus status;

    status = pasynManager->blockProcessCallback(pasynUser,0);
    if(checkStatus(status,pthreadInfo,"testBlock")) return;
    fprintf(pcmdInfo->file,"%s %s first queueRequest\n",
        pthreadInfo->threadName,pcmdInfo->message);
    epicsEventTryWait(pcmdInfo->callbackDone);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow,0.0);
    if(checkStatus(status,pthreadInfo,"testBlock")) return;
    epicsEventMustWait(pcmdInfo->callbackDone);
    epicsThreadSleep(.1);
    fprintf(pcmdInfo->file,"%s %s second queueRequest\n",
        pthreadInfo->threadName,pcmdInfo->message);
    status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow,0.0);
    if(checkStatus(status,pthreadInfo,"testBlock")) return;
    epicsEventMustWait(pcmdInfo->callbackDone);
    status = pasynManager->unblockProcessCallback(pasynUser,0);
    if(checkStatus(status,pthreadInfo,"testBlock")) return;
}
示例#7
0
static void errlogThread(void)
{
    listenerNode *plistenerNode;
    int noConsoleMessage;
    char *pmessage;

    epicsAtExit(errlogExitHandler,0);
    while (TRUE) {
        epicsEventMustWait(pvtData.waitForWork);
        while ((pmessage = msgbufGetSend(&noConsoleMessage))) {
            epicsMutexMustLock(pvtData.listenerLock);
            if (pvtData.toConsole && !noConsoleMessage) {
                fprintf(pvtData.console,"%s",pmessage);
                fflush(pvtData.console);
            }

            plistenerNode = (listenerNode *)ellFirst(&pvtData.listenerList);
            while (plistenerNode) {
                (*plistenerNode->listener)(plistenerNode->pPrivate, pmessage);
                plistenerNode = (listenerNode *)ellNext(&plistenerNode->node);
            }

            epicsMutexUnlock(pvtData.listenerLock);
            msgbufFreeSend();
        }

        if (pvtData.atExit)
            break;
        if (epicsEventTryWait(pvtData.flush) != epicsEventWaitOK)
            continue;

        epicsThreadSleep(.2); /*just wait an extra .2 seconds*/
        epicsEventSignal(pvtData.waitForFlush);
    }
    epicsEventSignal(pvtData.waitForExit);
}
示例#8
0
/** This thread controls handling of slow events - erase, acquire, change mode */
void mar345::mar345Task()
{
    int status = asynSuccess;
    int numImages, numImagesCounter;
    int imageMode;
    int acquire;
    double acquirePeriod;
    double elapsedTime, delayTime;
    const char *functionName = "mar345Task";

    this->lock();

    /* Loop forever */
    while (1) {
        setStringParam(ADStatusMessage, "Waiting for event");
        callParamCallbacks();
        /* Release the lock while we wait for an event that says acquire has started, then lock again */
        this->unlock();
        asynPrint(this->pasynUserSelf, ASYN_TRACE_FLOW, 
            "%s:%s: waiting for start event\n", driverName, functionName);
        status = epicsEventWait(this->startEventId);
        this->lock();

        switch(this->mode) {
            case mar345ModeErase:
                this->erase();
                this->mode = mar345ModeIdle;
                break;

            case mar345ModeAcquire:
                getIntegerParam(ADImageMode, &imageMode);
                getIntegerParam(ADNumImages, &numImages);
                if (numImages < 1) numImages = 1;
                if (imageMode == ADImageSingle) numImages=1;
                for (numImagesCounter=0;
                        numImagesCounter<numImages || (imageMode == ADImageContinuous); 
                        numImagesCounter++) {
                    if (epicsEventTryWait(this->abortEventId) == epicsEventWaitOK) break;
                    setIntegerParam(ADNumImagesCounter, numImagesCounter);
                    callParamCallbacks();
                    status = acquireFrame();
                    if (status) break;
                    /* We get out of the loop in single shot mode or if acquire was set to 0 by client */
                    if (imageMode == ADImageSingle) setIntegerParam(ADAcquire, 0);
                    getIntegerParam(ADAcquire, &acquire);
                    if (!acquire) break;
                    /* We are in continuous or multiple mode.
                     * Sleep until the acquire period expires or acquire is set to stop */
                    epicsTimeGetCurrent(&this->acqEndTime);
                    elapsedTime = epicsTimeDiffInSeconds(&this->acqEndTime, &this->acqStartTime);
                    getDoubleParam(ADAcquirePeriod, &acquirePeriod);
                    delayTime = acquirePeriod - elapsedTime;
                    if (delayTime > 0.) {
                        setIntegerParam(ADStatus, mar345StatusWaiting);
                        callParamCallbacks();
                        this->unlock();
                        status = epicsEventWaitWithTimeout(this->abortEventId, delayTime);
                        this->lock();
                        if (status == epicsEventWaitOK) break;
                    }
                }
                this->mode = mar345ModeIdle;
                setIntegerParam(ADAcquire, 0);
                setIntegerParam(ADStatus, mar345StatusIdle);
                break;

            case mar345ModeChange:
                this->changeMode();
                this->mode = mar345ModeIdle;
                break;
                
            default:
                break;
        }

        /* Call the callbacks to update any changes */
        callParamCallbacks();
    }
}
示例#9
0
asynStatus mar345::acquireFrame()
{
    asynStatus status=asynSuccess;
    epicsTimeStamp startTime, currentTime;
    int eraseMode;
    epicsEventWaitStatus waitStatus;
    int imageCounter;
    int arrayCallbacks;
    double acquireTime;
    double timeRemaining;
    int size, res;
    int shutterMode, useShutter;
    char tempFileName[MAX_FILENAME_LEN];
    char fullFileName[MAX_FILENAME_LEN];
    //const char *functionName = "acquireframe";

    /* Get current values of some parameters */
    getDoubleParam(ADAcquireTime, &acquireTime);
    getIntegerParam(ADShutterMode, &shutterMode);
    getIntegerParam(mar345Size, &size);
    getIntegerParam(mar345Res, &res);
    getIntegerParam(NDArrayCallbacks, &arrayCallbacks);
    getIntegerParam(mar345EraseMode, &eraseMode);
    if (shutterMode == ADShutterModeNone) useShutter=0; else useShutter=1;

    epicsTimeGetCurrent(&this->acqStartTime);

    createFileName(MAX_FILENAME_LEN, tempFileName);
    /* We need to append the extension */
    epicsSnprintf(fullFileName, sizeof(fullFileName), "%s.mar%d", tempFileName, imageSizes[res][size]);

    /* Erase before exposure if set */
    if (eraseMode == mar345EraseBefore) {
        status = this->erase();
        if (status) return(status);
    }
    
    /* Set the the start time for the TimeRemaining counter */
    epicsTimeGetCurrent(&startTime);
    timeRemaining = acquireTime;
    if (useShutter) setShutter(1);

    /* Wait for the exposure time using epicsEventWaitWithTimeout, 
     * so we can abort */
    epicsTimerStartDelay(this->timerId, acquireTime);
    setIntegerParam(ADStatus, mar345StatusExpose);
    callParamCallbacks();
    while(1) {
        if (epicsEventTryWait(this->abortEventId) == epicsEventWaitOK) {
            status = asynError;
            break;
        }
        this->unlock();
        waitStatus = epicsEventWaitWithTimeout(this->stopEventId, MAR345_POLL_DELAY);
        this->lock();
        if (waitStatus == epicsEventWaitOK) {
            /* The acquisition was stopped before the time was complete */
            epicsTimerCancel(this->timerId);
            break;
        }
        epicsTimeGetCurrent(&currentTime);
        timeRemaining = acquireTime - 
            epicsTimeDiffInSeconds(&currentTime, &startTime);
        if (timeRemaining < 0.) timeRemaining = 0.;
        setDoubleParam(ADTimeRemaining, timeRemaining);
        callParamCallbacks();
    }
    setDoubleParam(ADTimeRemaining, 0.0);
    if (useShutter) setShutter(0);
    setIntegerParam(ADStatus, mar345StatusIdle);
    callParamCallbacks();
    // If the exposure was aborted return error
    if (status) return asynError;
    setIntegerParam(ADStatus, mar345StatusScan);
    callParamCallbacks();
    epicsSnprintf(this->toServer, sizeof(this->toServer), "COMMAND SCAN %s", fullFileName);
    setStringParam(NDFullFileName, fullFileName);
    callParamCallbacks();
    writeServer(this->toServer);
    status = waitForCompletion("SCAN_DATA    Ended o.k.", MAR345_COMMAND_TIMEOUT);
    if (status) {
        return asynError;
    }
    getIntegerParam(NDArrayCounter, &imageCounter);
    imageCounter++;
    setIntegerParam(NDArrayCounter, imageCounter);
    /* Call the callbacks to update any changes */
    callParamCallbacks();

    /* If arrayCallbacks is set then read the file back in */
    if (arrayCallbacks) {
        getImageData();
    }

    /* Erase after scanning if set */
    if (eraseMode == mar345EraseAfter) status = this->erase();

    return status;
}
示例#10
0
/*
 * Get value from a channel.
 * TODO: add optional timeout argument.
 */
epicsShareFunc pvStat epicsShareAPI seq_pvGet(SS_ID ss, VAR_ID varId, enum compType compType)
{
	SPROG		*sp = ss->sprog;
	CHAN		*ch = sp->chan + varId;
	pvStat		status;
	PVREQ		*req;
	epicsEventId	getSem = ss->getSemId[varId];
	DBCHAN		*dbch = ch->dbch;
	PVMETA		*meta = metaPtr(ch,ss);
	double		tmo = seq_sync_timeout;

	/* Anonymous PV and safe mode, just copy from shared buffer.
	   Note that completion is always immediate, so no distinction
	   between SYNC and ASYNC needed. See also pvGetComplete. */
	if ((sp->options & OPT_SAFE) && !dbch)
	{
		/* Copy regardless of whether dirty flag is set or not */
		ss_read_buffer(ss, ch, FALSE);
		return pvStatOK;
	}
	/* No named PV and traditional mode => user error */
	if (!dbch)
	{
		errlogSevPrintf(errlogMajor,
			"pvGet(%s): user error (variable not assigned)\n",
			ch->varName
		);
		return pvStatERROR;
	}

	if (compType == DEFAULT)
	{
		compType = (sp->options & OPT_ASYNC) ? ASYNC : SYNC;
	}

	if (compType == SYNC)
	{
		double before, after;
		pvTimeGetCurrentDouble(&before);
		switch (epicsEventWaitWithTimeout(getSem, tmo))
		{
		case epicsEventWaitOK:
			status = check_connected(dbch, meta);
			if (status) return epicsEventSignal(getSem), status;
			pvTimeGetCurrentDouble(&after);
			tmo -= (after - before);
			break;
		case epicsEventWaitTimeout:
			errlogSevPrintf(errlogMajor,
				"pvGet(ss %s, var %s, pv %s): failed (timeout "
				"waiting for other get requests to finish)\n",
				ss->ssName, ch->varName, dbch->dbName
			);
			return pvStatERROR;
		case epicsEventWaitError:
			/* try to recover */
			ss->getReq[varId] = NULL;
			epicsEventSignal(getSem);
			errlogSevPrintf(errlogFatal,
				"pvGet: epicsEventWaitWithTimeout() failure\n");
			return pvStatERROR;
		}
	}
	else if (compType == ASYNC)
	{
		switch (epicsEventTryWait(getSem))
		{
		case epicsEventWaitOK:
			if (ss->getReq[varId] != NULL)
			{
				/* previous request timed out but user
				   did not call pvGetComplete */
				ss->getReq[varId] = NULL;
			}
			status = check_connected(dbch, meta);
			if (status) return epicsEventSignal(getSem), status;
			break;
		case epicsEventWaitTimeout:
			errlogSevPrintf(errlogMajor,
				"pvGet(ss %s, var %s, pv %s): user error "
				"(there is already a get pending for this variable/"
				"state set combination)\n",
				ss->ssName, ch->varName, dbch->dbName
			);
			return pvStatERROR;
		case epicsEventWaitError:
			/* try to recover */
			ss->getReq[varId] = NULL;
			epicsEventSignal(getSem);
			errlogSevPrintf(errlogFatal,
				"pvGet: epicsEventTryWait() failure\n");
			return pvStatERROR;
		}
	}

	/* Allocate and initialize a pv request */
	req = (PVREQ *)freeListMalloc(sp->pvReqPool);
	req->ss = ss;
	req->ch = ch;

	assert(ss->getReq[varId] == NULL);
	ss->getReq[varId] = req;

	/* Perform the PV get operation with a callback routine specified.
	   Requesting more than db channel has available is ok. */
	status = pvVarGetCallback(
			dbch->pvid,		/* PV id */
			ch->type->getType,	/* request type */
			ch->count,		/* element count */
			seq_get_handler,	/* callback handler */
			req);			/* user arg */
	if (status != pvStatOK)
	{
		meta->status = pvStatERROR;
		meta->severity = pvSevrMAJOR;
		meta->message = "get failure";
		errlogSevPrintf(errlogFatal, "pvGet(var %s, pv %s): pvVarGetCallback() failure: %s\n",
			ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid));
		ss->getReq[varId] = NULL;
		freeListFree(sp->pvReqPool, req);
		epicsEventSignal(getSem);
		check_connected(dbch, meta);
		return status;
	}

	/* Synchronous: wait for completion */
	if (compType == SYNC)
	{
		epicsEventWaitStatus event_status;

		pvSysFlush(sp->pvSys);
		event_status = epicsEventWaitWithTimeout(getSem, tmo);
		ss->getReq[varId] = NULL;
		epicsEventSignal(getSem);
		switch (event_status)
		{
		case epicsEventWaitOK:
			status = check_connected(dbch, meta);
			if (status) return status;
			if (sp->options & OPT_SAFE)
				/* Copy regardless of whether dirty flag is set or not */
				ss_read_buffer(ss, ch, FALSE);
			break;
		case epicsEventWaitTimeout:
			meta->status = pvStatTIMEOUT;
			meta->severity = pvSevrMAJOR;
			meta->message = "get completion timeout";
			return meta->status;
		case epicsEventWaitError:
			meta->status = pvStatERROR;
			meta->severity = pvSevrMAJOR;
			meta->message = "get completion failure";
			return meta->status;
		}
	}

	return pvStatOK;
}
示例#11
0
/*
 * Return whether the last put completed.
 */
epicsShareFunc boolean epicsShareAPI seq_pvPutComplete(
	SS_ID		ss,
	VAR_ID		varId,
	unsigned	length,
	boolean		any,
	boolean		*complete)
{
	SPROG		*sp = ss->sprog;
	boolean		anyDone = FALSE, allDone = TRUE;
	unsigned	n;

	for (n = 0; n < length; n++)
	{
		epicsEventId	putSem = ss->putSemId[varId+n];
		boolean		done = FALSE;
		CHAN		*ch = sp->chan + varId + n;

		if (!ch->dbch)
		{
		        /* Anonymous PVs always complete immediately */
			if (!(sp->options & OPT_SAFE))
			        errlogSevPrintf(errlogMajor,
				        "pvPutComplete(%s): user error (variable not assigned)\n",
				        ch->varName);
			done = TRUE;
		}
		else if (!ss->putReq[varId])
		{
		        errlogSevPrintf(errlogMinor,
			        "pvPutComplete(%s): no pending put request for this variable\n",
			        ch->varName);
			done = TRUE;
		}
		else
		{
			switch (epicsEventTryWait(putSem))
			{
			case epicsEventWaitOK:
				ss->putReq[varId] = NULL;
				epicsEventSignal(putSem);
				check_connected(ch->dbch, metaPtr(ch,ss));
				/* TODO: returning either TRUE or FALSE here seems wrong. We return TRUE,
				   so that state sets don't hang. Still means that user code has to check
				   status by calling pvStatus and/or pvMessage. */
				done = TRUE;
				break;
			case epicsEventWaitTimeout:
				break;
			case epicsEventWaitError:
				ss->putReq[varId] = NULL;
				epicsEventSignal(putSem);
				errlogSevPrintf(errlogFatal, "pvPutComplete(%s): "
				  "epicsEventTryWait(putSem[%d]) failure\n", ch->varName, varId);
				break;
			}
		}

		anyDone = anyDone || done;
		allDone = allDone && done;

		if (complete)
		{
			complete[n] = done;
		}
		else if (any && done)
		{
			break;
		}
	}

	DEBUG("pvPutComplete: varId=%u, length=%u, anyDone=%u, allDone=%u\n",
		varId, length, anyDone, allDone);

	return any?anyDone:allDone;
}
示例#12
0
/*
 * Put a variable's value to a PV.
 */
epicsShareFunc pvStat epicsShareAPI seq_pvPut(SS_ID ss, VAR_ID varId, enum compType compType)
{
	SPROG	*sp = ss->sprog;
	CHAN	*ch = sp->chan + varId;
	pvStat	status;
	unsigned count;
	char	*var = valPtr(ch,ss);	/* ptr to value */
	PVREQ	*req;
	DBCHAN	*dbch = ch->dbch;
	PVMETA	*meta = metaPtr(ch,ss);
	epicsEventId	putSem = ss->putSemId[varId];
	double	tmo = seq_sync_timeout;

	DEBUG("pvPut: pv name=%s, var=%p\n", dbch ? dbch->dbName : "<anonymous>", var);

	/* First handle anonymous PV (safe mode only) */
	if ((sp->options & OPT_SAFE) && !dbch)
	{
		anonymous_put(ss, ch);
		return pvStatOK;
	}
	if (!dbch)
	{
		errlogSevPrintf(errlogMajor,
			"pvPut(%s): user error (variable not assigned)\n",
			ch->varName
		);
		return pvStatERROR;
	}

	/* Check for channel connected */
	status = check_connected(dbch, meta);
	if (status) return status;

	/* Determine whether to perform synchronous, asynchronous, or
	   plain put ((+a) option was never honored for put, so DEFAULT
	   means non-blocking and therefore implicitly asynchronous) */
	if (compType == SYNC)
	{
		double before, after;
		pvTimeGetCurrentDouble(&before);
		switch (epicsEventWaitWithTimeout(putSem, tmo))
		{
		case epicsEventWaitOK:
			pvTimeGetCurrentDouble(&after);
			tmo -= (after - before);
			break;
		case epicsEventWaitTimeout:
			errlogSevPrintf(errlogMajor,
				"pvPut(ss %s, var %s, pv %s): failed (timeout "
				"waiting for other put requests to finish)\n",
				ss->ssName, ch->varName, dbch->dbName
			);
			return pvStatERROR;
		case epicsEventWaitError:
			/* try to recover */
			ss->putReq[varId] = NULL;
			epicsEventSignal(putSem);
			errlogSevPrintf(errlogFatal,
				"pvPut: epicsEventWaitWithTimeout() failure\n");
			return pvStatERROR;
		}
	}
	else if (compType == ASYNC)
	{
		switch (epicsEventTryWait(putSem))
		{
		case epicsEventWaitOK:
			if (ss->putReq[varId] != NULL)
			{
				/* previous request timed out but user
				   did not call pvPutComplete */
				ss->putReq[varId] = NULL;
			}
			break;
		case epicsEventWaitTimeout:
			meta->status = pvStatERROR;
			meta->severity = pvSevrMAJOR;
			meta->message = "already one put pending";
			status = meta->status;
			errlogSevPrintf(errlogMajor,
				"pvPut(ss %s, var %s, pv %s): user error "
				"(there is already a put pending for this variable/"
				"state set combination)\n",
				ss->ssName, ch->varName, dbch->dbName
			);
			return pvStatERROR;
		case epicsEventWaitError:
			/* try to recover */
			ss->putReq[varId] = NULL;
			epicsEventSignal(putSem);
			errlogSevPrintf(errlogFatal,
				"pvPut: epicsEventTryWait() failure\n");
			return pvStatERROR;
		}
	}

	/* Determine number of elements to put (don't try to put more
	   than db count) */
	count = dbch->dbCount;

	/* Perform the PV put operation (either non-blocking or with a
	   callback routine specified) */
	if (compType == DEFAULT)
	{
		status = pvVarPutNoBlock(
				dbch->pvid,		/* PV id */
				ch->type->putType,	/* data type */
				count,			/* element count */
				(pvValue *)var);	/* data value */
		if (status != pvStatOK)
		{
			errlogSevPrintf(errlogFatal, "pvPut(var %s, pv %s): pvVarPutNoBlock() failure: %s\n",
				ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid));
			return status;
		}
	}
	else
	{
		/* Allocate and initialize a pv request */
		req = (PVREQ *)freeListMalloc(sp->pvReqPool);
		req->ss = ss;
		req->ch = ch;

		assert(ss->putReq[varId] == NULL);
		ss->putReq[varId] = req;

		status = pvVarPutCallback(
				dbch->pvid,		/* PV id */
				ch->type->putType,	/* data type */
				count,			/* element count */
				(pvValue *)var,		/* data value */
				seq_put_handler,	/* callback handler */
				req);			/* user arg */
		if (status != pvStatOK)
		{
			ss->putReq[varId] = NULL;
			errlogSevPrintf(errlogFatal, "pvPut(var %s, pv %s): pvVarPutCallback() failure: %s\n",
				ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid));
			freeListFree(sp->pvReqPool, req);
			epicsEventSignal(putSem);
			check_connected(dbch, meta);
			return status;
		}
	}

	/* Synchronous: wait for completion (10s timeout) */
	if (compType == SYNC)
	{
		epicsEventWaitStatus event_status;

		pvSysFlush(sp->pvSys);
		event_status = epicsEventWaitWithTimeout(putSem, tmo);
		ss->putReq[varId] = NULL;
		epicsEventSignal(putSem);
		switch (event_status)
		{
		case epicsEventWaitOK:
			status = check_connected(dbch, meta);
			if (status) return status;
			break;
		case epicsEventWaitTimeout:
			meta->status = pvStatTIMEOUT;
			meta->severity = pvSevrMAJOR;
			meta->message = "put completion timeout";
			return meta->status;
			break;
		case epicsEventWaitError:
			meta->status = pvStatERROR;
			meta->severity = pvSevrMAJOR;
			meta->message = "put completion failure";
			return meta->status;
			break;
		}
	}

	return pvStatOK;
}