static void pv_call_failure(DBCHAN *dbch, PVMETA *meta, pvStat status) { meta->status = status; meta->severity = pvSevrERROR; meta->message = pvVarGetMess(dbch->pvid); }
/* * Assign/Connect to a channel. * Assign to a zero-length string ("") disconnects/de-assigns, * in safe mode, creates an anonymous PV. */ epicsShareFunc pvStat seq_pvAssign(SS_ID ss, CH_ID chId, const char *pvName) { PROG *sp = ss->prog; CHAN *ch = sp->chan + chId; pvStat status = pvStatOK; DBCHAN *dbch; if (!pvName) pvName = ""; DEBUG("Assign %s to \"%s\"\n", ch->varName, pvName); epicsMutexMustLock(sp->lock); dbch = ch->dbch; if (dbch) /* was assigned to a named PV */ { ch->dbch = 0; epicsMutexUnlock(sp->lock); status = pvVarDestroy(&dbch->pvid); epicsMutexMustLock(sp->lock); sp->assignCount--; if (dbch->connected) /* see connection handler */ { dbch->connected = FALSE; sp->connectCount--; /* Must not call seq_camonitor(ch, FALSE), it would give an error because channel is already dead. pvVarDestroy takes care that the monid inside the pvid gets invalidated. */ /* Note ch->monitored remains on because it is a configuration value that belongs to the variable and newly created channels for the same variable should inherit this configuration. */ } if (status != pvStatOK) { errlogSevPrintf(errlogFatal, "pvAssign(var %s, pv %s): pvVarDestroy() failure: " "%s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); } free(dbch->dbName); } if (pvName[0] == 0) /* new name is empty -> free resources */ { if (dbch) { free(dbch); } } else /* new name is non-empty -> create resources */ { if (!dbch) { dbch = new(DBCHAN); if (!dbch) { errlogSevPrintf(errlogFatal, "pvAssign: calloc failed\n"); epicsMutexUnlock(sp->lock); return pvStatERROR; } } dbch->dbName = epicsStrDup(pvName); if (!dbch->dbName) { errlogSevPrintf(errlogFatal, "pvAssign: epicsStrDup failed\n"); free(dbch); epicsMutexUnlock(sp->lock); return pvStatERROR; } ch->dbch = dbch; status = pvVarCreate( sp->pvSys, /* PV system context */ dbch->dbName, /* DB channel name */ seq_conn_handler, /* connection handler routine */ seq_event_handler, /* event handler routine */ ch, /* user ptr is CHAN structure */ &dbch->pvid); /* ptr to pvid */ if (status != pvStatOK) { errlogSevPrintf(errlogFatal, "pvAssign(var %s, pv %s): pvVarCreate() failure: " "%s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); free(ch->dbch->dbName); free(ch->dbch); } else { sp->assignCount++; } } epicsMutexUnlock(sp->lock); return status; }
/* * Put a variable's value to a PV, with timeout. */ epicsShareFunc pvStat seq_pvPutTmo(SS_ID ss, CH_ID chId, enum compType compType, double tmo) { PROG *sp = ss->prog; CHAN *ch = sp->chan + chId; pvStat status; unsigned count; char *var = valPtr(ch,ss); /* ptr to value */ PVREQ *req; DBCHAN *dbch = ch->dbch; PVMETA *meta = metaPtr(ch,ss); DEBUG("pvPut: pv name=%s, var=%p\n", dbch ? dbch->dbName : "<anonymous>", var); /* First handle anonymous PV (safe mode only) */ if (optTest(sp, OPT_SAFE) && !dbch) { anonymous_put(ss, ch); return pvStatOK; } if (!dbch) { errlogSevPrintf(errlogMajor, "pvPut(%s): user error (not assigned to a PV)\n", ch->varName ); return pvStatERROR; } /* Check for channel connected */ status = check_connected(dbch, meta); if (status != pvStatOK) return status; /* Determine whether to perform synchronous, asynchronous, or plain put ((+a) option was never honored for put, so DEFAULT means fire-and-forget) */ status = check_pending(pvEventPut, ss, ss->putReq + chId, ch->varName, dbch, meta, compType, tmo); if (status != pvStatOK) return status; /* 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) { pv_call_failure(dbch, meta, status); 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[chId] == NULL); ss->putReq[chId] = req; status = pvVarPutCallback( &dbch->pvid, /* PV id */ ch->type->putType, /* data type */ count, /* element count */ (pvValue *)var, /* data value */ req); /* user arg */ if (status != pvStatOK) { pv_call_failure(dbch, meta, status); errlogSevPrintf(errlogFatal, "pvPut(var %s, pv %s): pvVarPutCallback() failure: %s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); ss->putReq[chId] = NULL; /* cancel the request */ freeListFree(sp->pvReqPool, req); check_connected(dbch, meta); return status; } if (compType == SYNC) /* wait for completion */ { pvSysFlush(sp->pvSys); status = wait_complete(pvEventPut, ss, ss->putReq + chId, dbch, meta, tmo); if (status != pvStatOK) return status; } } return pvStatOK; }
/* * Get value from a channel, with timeout. */ epicsShareFunc pvStat seq_pvGetTmo(SS_ID ss, CH_ID chId, enum compType compType, double tmo) { PROG *sp = ss->prog; CHAN *ch = sp->chan + chId; pvStat status; PVREQ *req; DBCHAN *dbch = ch->dbch; PVMETA *meta = metaPtr(ch,ss); /* 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 (optTest(sp, 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 (not assigned to a PV)\n", ch->varName ); return pvStatERROR; } if (compType == DEFAULT) { compType = optTest(sp, OPT_ASYNC) ? ASYNC : SYNC; } status = check_pending(pvEventGet, ss, ss->getReq + chId, ch->varName, dbch, meta, compType, tmo); if (status != pvStatOK) return status; /* Allocate and initialize a pv request */ req = (PVREQ *)freeListMalloc(sp->pvReqPool); req->ss = ss; req->ch = ch; assert(ss->getReq[chId] == NULL); ss->getReq[chId] = 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 */ req); /* user arg */ if (status != pvStatOK) { pv_call_failure(dbch, meta, status); errlogSevPrintf(errlogFatal, "pvGet(var %s, pv %s): pvVarGetCallback() failure: %s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); ss->getReq[chId] = NULL; /* cancel the request */ freeListFree(sp->pvReqPool, req); check_connected(dbch, meta); return status; } /* Synchronous: wait for completion */ if (compType == SYNC) { pvSysFlush(sp->pvSys); status = wait_complete(pvEventGet, ss, ss->getReq + chId, dbch, meta, tmo); if (status != pvStatOK) return status; if (optTest(sp, OPT_SAFE)) /* Copy regardless of whether dirty flag is set or not */ ss_read_buffer(ss, ch, FALSE); } return pvStatOK; }
/* * Assign/Connect to a channel. * Assign to a zero-length string ("") disconnects/de-assigns, * in safe mode, creates an anonymous PV. */ epicsShareFunc pvStat epicsShareAPI seq_pvAssign(SS_ID ss, VAR_ID varId, const char *pvName) { SPROG *sp = ss->sprog; CHAN *ch = sp->chan + varId; pvStat status = pvStatOK; DBCHAN *dbch = ch->dbch; if (!pvName) pvName = ""; DEBUG("Assign %s to \"%s\"\n", ch->varName, pvName); epicsMutexMustLock(sp->programLock); if (dbch) /* was assigned to a named PV */ { status = pvVarDestroy(dbch->pvid); dbch->pvid = NULL; sp->assignCount -= 1; if (dbch->connected) /* see connection handler */ { dbch->connected = FALSE; sp->connectCount--; /* Must not call seq_monitor(ch, FALSE), it would give an error because channel is already dead. Instead mark that no subscriptions are active so when seq_monitor gets called when we re-create the channel, it actually does something. */ dbch->monid = NULL; /* Note ch->monitored remains on because it is a configuration value that belongs to the variable and newly created channels for the same variable should inherit this configuration. */ } if (status != pvStatOK) { errlogSevPrintf(errlogFatal, "pvAssign(var %s, pv %s): pvVarDestroy() failure: " "%s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); } free(dbch->dbName); } if (pvName == NULL || pvName[0] == 0) /* new name is empty -> free resources */ { if (dbch) { free(ch->dbch->ssMetaData); free(ch->dbch); } } else /* new name is non-empty -> create resources */ { if (!dbch) { dbch = new(DBCHAN); if (!dbch) { errlogSevPrintf(errlogFatal, "pvAssign: calloc failed\n"); return pvStatERROR; } } dbch->dbName = epicsStrDup(pvName); if (!dbch->dbName) { errlogSevPrintf(errlogFatal, "pvAssign: epicsStrDup failed\n"); free(dbch); return pvStatERROR; } if ((sp->options & OPT_SAFE) && sp->numSS > 0) { dbch->ssMetaData = newArray(PVMETA, sp->numSS); if (!dbch->ssMetaData) { errlogSevPrintf(errlogFatal, "pvAssign: calloc failed\n"); free(dbch->dbName); free(dbch); return pvStatERROR; } } ch->dbch = dbch; sp->assignCount++; status = pvVarCreate( sp->pvSys, /* PV system context */ dbch->dbName, /* DB channel name */ seq_conn_handler, /* connection handler routine */ ch, /* user ptr is CHAN structure */ sp->debug, /* debug level (inherited) */ &dbch->pvid); /* ptr to pvid */ if (status != pvStatOK) { errlogSevPrintf(errlogFatal, "pvAssign(var %s, pv %s): pvVarCreate() failure: " "%s\n", ch->varName, dbch->dbName, pvVarGetMess(dbch->pvid)); if (ch->dbch->ssMetaData) free(ch->dbch->ssMetaData); free(ch->dbch->dbName); free(ch->dbch); } } epicsMutexUnlock(sp->programLock); return status; }
/* * 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; }
/* * 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; }