/*************************************************************************\ * Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * SPDX-License-Identifier: EPICS * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* dbDbLink.c * * Original Authors: Bob Dalesio, Marty Kraimer * Current Author: Andrew Johnson */ /* The PUTF and RPRO fields in dbCommon are flags that indicate when a record * is being processed as a result of an external put (i.e. some server process * calling dbPutField()), ensuring that the record and its successors will * eventually get processed even if they happen to be busy at the time of the * put. From Base-3.16.2 and 7.0.2 the code ensures that all records downstream * from the original are processed even if a busy asynchronous device appears * in the processing chain (this breaks the chain in older versions). * * PUTF - This field is set in dbPutField() prior to it calling dbProcess(). * It is normally cleared at the end of processing in recGblFwdLink(). * It may also be cleared in dbProcess() if DISA==DISV (scan disabled), * or by the processTarget() function below. * * If PUTF is TRUE before a call to dbProcess(prec), then after it returns * either PACT is TRUE, or PUTF will be FALSE. * * RPRO - This field is set by dbPutField() or by the processTarget() function * below when a record to be processed is found to be busy (PACT==1). * It is normally cleared in recGblFwdLink() when the record is queued * for re-processing, or in dbProcess() if DISA==DISV (scan disabled). */ #include #include #include #include #include #include "alarm.h" #include "cantProceed.h" #include "cvtFast.h" #include "dbDefs.h" #include "ellLib.h" #include "epicsTime.h" #include "errlog.h" #include "caeventmask.h" #include "dbAccessDefs.h" #include "dbAddr.h" #include "dbBase.h" #include "dbBkpt.h" #include "dbCommonPvt.h" #include "dbConvertFast.h" #include "dbConvert.h" #include "db_field_log.h" #include "db_access_routines.h" #include "dbFldTypes.h" #include "dbLink.h" #include "dbLockPvt.h" #include "dbNotify.h" #include "dbScan.h" #include "dbStaticLib.h" #include "dbServer.h" #include "devSup.h" #include "link.h" #include "recGbl.h" #include "recSup.h" #include "special.h" #include "dbDbLink.h" #include "dbChannel.h" /***************************** Database Links *****************************/ /* Forward definitions */ static lset dbDb_lset; static long processTarget(dbCommon *psrc, dbCommon *pdst); #define linkChannel(plink) ((dbChannel *) (plink)->value.pv_link.pvt) long dbDbInitLink(struct link *plink, short dbfType) { long status; dbChannel *chan; dbCommon *precord; chan = dbChannelCreate(plink->value.pv_link.pvname); if (!chan) return S_db_notFound; status = dbChannelOpen(chan); if (status) return status; precord = dbChannelRecord(chan); plink->lset = &dbDb_lset; plink->type = DB_LINK; plink->value.pv_link.pvt = chan; ellAdd(&precord->bklnk, &plink->value.pv_link.backlinknode); /* merging into the same lockset is deferred to the caller. * cf. initPVLinks() */ dbLockSetMerge(NULL, plink->precord, precord); assert(plink->precord->lset->plockSet == precord->lset->plockSet); return 0; } void dbDbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, dbChannel *chan) { plink->lset = &dbDb_lset; plink->type = DB_LINK; plink->value.pv_link.pvt = chan; ellAdd(&dbChannelRecord(chan)->bklnk, &plink->value.pv_link.backlinknode); /* target record is already locked in dbPutFieldLink() */ dbLockSetMerge(locker, plink->precord, dbChannelRecord(chan)); } static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink) { dbChannel *chan = linkChannel(plink); dbCommon *precord = dbChannelRecord(chan); plink->type = PV_LINK; /* locker is NULL when an isolated IOC is closing its links */ if (locker) { plink->value.pv_link.pvt = 0; plink->value.pv_link.getCvt = 0; plink->value.pv_link.pvlMask = 0; plink->value.pv_link.lastGetdbrType = 0; ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode); dbLockSetSplit(locker, plink->precord, precord); } dbChannelDelete(chan); } static int dbDbIsConnected(const struct link *plink) { return TRUE; } static int dbDbGetDBFtype(const struct link *plink) { dbChannel *chan = linkChannel(plink); return dbChannelFinalFieldType(chan); } static long dbDbGetElements(const struct link *plink, long *nelements) { dbChannel *chan = linkChannel(plink); *nelements = dbChannelFinalElements(chan); return 0; } static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer, long *pnRequest) { struct pv_link *ppv_link = &plink->value.pv_link; dbChannel *chan = linkChannel(plink); DBADDR *paddr = &chan->addr; dbCommon *precord = plink->precord; db_field_log *pfl = NULL; long status; /* scan passive records if link is process passive */ if (ppv_link->pvlMask & pvlOptPP) { status = dbScanPassive(precord, dbChannelRecord(chan)); if (status) return status; } if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) { /* shortcut: scalar with known conversion, no filter */ status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr); } else if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1) && dbChannelSpecial(chan) != SPC_DBADDR && dbChannelSpecial(chan) != SPC_ATTRIBUTE && ellCount(&chan->filters) == 0) { /* Simple scalar w/o filters, so *Final* type has no additional information. * Needed to correctly handle DBF_MENU fields, which become DBF_ENUM during * probe of dbChannelOpen(). */ unsigned short dbfType = dbChannelFieldType(chan); if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE) return S_db_badDbrtype; ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType]; ppv_link->lastGetdbrType = dbrType; status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr); } else { /* filter, array, or special */ ppv_link->getCvt = NULL; if (ellCount(&chan->filters)) { /* If filters are involved in a read, create field log and run filters */ pfl = db_create_read_log(chan); if (!pfl) return S_db_noMemory; pfl = dbChannelRunPreChain(chan, pfl); pfl = dbChannelRunPostChain(chan, pfl); } status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl); if (pfl) db_delete_field_log(pfl); if (status) return status; } if (!status && precord != dbChannelRecord(chan)) recGblInheritSevrMsg(plink->value.pv_link.pvlMask & pvlOptMsMode, plink->precord, dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr, dbChannelRecord(chan)->amsg); return status; } /* Some records get options (precsision, units, ...) for some fields * from an input link. We need to catch the case that this link * points back to the same field or we will end in an infinite recursion. */ static long dbDbGetOptionLoopSafe(const struct link *plink, short dbrType, void *pbuffer, long option) { /* We need to cast away const to set the flags. That's ok because we know that plink is never actually readonly. And we reset everything to its original state. */ struct link *mutable_plink = (struct link *)plink; long status = S_dbLib_badLink; dbChannel *chan = linkChannel(plink); DBADDR *paddr = &chan->addr; long number_elements = 0; dbScanLock(paddr->precord); if (!(mutable_plink->flags & DBLINK_FLAG_VISITED)) { mutable_plink->flags |= DBLINK_FLAG_VISITED; status = dbGet(paddr, dbrType, pbuffer, &option, &number_elements, NULL); mutable_plink->flags &= ~DBLINK_FLAG_VISITED; } dbScanUnlock(paddr->precord); return status; } static long dbDbGetControlLimits(const struct link *plink, double *low, double *high) { struct buffer { DBRctrlDouble double value; } buffer; long status = dbDbGetOptionLoopSafe(plink, DBR_DOUBLE, &buffer, DBR_CTRL_DOUBLE); if (status) return status; *low = buffer.lower_ctrl_limit; *high = buffer.upper_ctrl_limit; return 0; } static long dbDbGetGraphicLimits(const struct link *plink, double *low, double *high) { struct buffer { DBRgrDouble double value; } buffer; long status = dbDbGetOptionLoopSafe(plink, DBR_DOUBLE, &buffer, DBR_GR_DOUBLE); if (status) return status; *low = buffer.lower_disp_limit; *high = buffer.upper_disp_limit; return 0; } static long dbDbGetAlarmLimits(const struct link *plink, double *lolo, double *low, double *high, double *hihi) { struct buffer { DBRalDouble double value; } buffer; long status = dbDbGetOptionLoopSafe(plink, DBR_DOUBLE, &buffer, DBR_AL_DOUBLE); if (status) return status; *lolo = buffer.lower_alarm_limit; *low = buffer.lower_warning_limit; *high = buffer.upper_warning_limit; *hihi = buffer.upper_alarm_limit; return 0; } static long dbDbGetPrecision(const struct link *plink, short *precision) { struct buffer { DBRprecision double value; } buffer; long status = dbDbGetOptionLoopSafe(plink, DBR_DOUBLE, &buffer, DBR_PRECISION); if (status) return status; *precision = (short) buffer.precision.dp; return 0; } static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize) { struct buffer { DBRunits double value; } buffer; long status = dbDbGetOptionLoopSafe(plink, DBR_DOUBLE, &buffer, DBR_UNITS); if (status) return status; strncpy(units, buffer.units, unitsSize); return 0; } static long dbDbGetAlarmMsg(const struct link *plink, epicsEnum16 *status, epicsEnum16 *severity, char *msgbuf, size_t msgbuflen) { dbChannel *chan = linkChannel(plink); dbCommon *precord = dbChannelRecord(chan); if (status) *status = precord->stat; if (severity) *severity = precord->sevr; if (msgbuf && msgbuflen) { strncpy(msgbuf, precord->amsg, msgbuflen-1); msgbuf[msgbuflen-1] = '\0'; } return 0; } static long dbDbGetTimeStampTag(const struct link *plink, epicsTimeStamp *pstamp, epicsUTag *ptag) { dbChannel *chan = linkChannel(plink); dbCommon *precord = dbChannelRecord(chan); *pstamp = precord->time; if(ptag) *ptag = precord->utag; return 0; } static long dbDbPutValue(struct link *plink, short dbrType, const void *pbuffer, long nRequest) { struct pv_link *ppv_link = &plink->value.pv_link; dbChannel *chan = linkChannel(plink); struct dbCommon *psrce = plink->precord; DBADDR *paddr = &chan->addr; dbCommon *pdest = dbChannelRecord(chan); long status = dbPut(paddr, dbrType, pbuffer, nRequest); recGblInheritSevrMsg(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta, psrce->nsev, psrce->namsg); if (status) return status; if (dbChannelField(chan) == (void *) &pdest->proc || (ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) { status = processTarget(psrce, pdest); } return status; } static void dbDbScanFwdLink(struct link *plink) { dbCommon *precord = plink->precord; dbChannel *chan = linkChannel(plink); dbScanPassive(precord, dbChannelRecord(chan)); } static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) { return rtn(plink, priv); } static lset dbDb_lset = { 0, 0, /* not Constant, not Volatile */ NULL, dbDbRemoveLink, NULL, NULL, NULL, dbDbIsConnected, dbDbGetDBFtype, dbDbGetElements, dbDbGetValue, dbDbGetControlLimits, dbDbGetGraphicLimits, dbDbGetAlarmLimits, dbDbGetPrecision, dbDbGetUnits, NULL, NULL, dbDbPutValue, NULL, dbDbScanFwdLink, doLocked, dbDbGetAlarmMsg, dbDbGetTimeStampTag, }; /* * Process a record if its scan field is passive. */ long dbScanPassive(dbCommon *pfrom, dbCommon *pto) { /* if not passive we're done */ if (pto->scan != 0) return 0; return processTarget(pfrom, pto); } static long processTarget(dbCommon *psrc, dbCommon *pdst) { char context[40] = ""; int trace = dbAccessDebugPUTF && *dbLockSetAddrTrace(psrc); int claim_src = dbRec2Pvt(psrc)->procThread==NULL; int claim_dst = psrc!=pdst && dbRec2Pvt(pdst)->procThread==NULL; long status; epicsUInt8 pact = psrc->pact; epicsThreadId self = epicsThreadGetIdSelf(); #ifdef LOCKSET_DEBUG { lockSet *ls = dbLockGetRef(psrc->lset); assert(ls->owner == self); dbLockDecRef(ls); ls = dbLockGetRef(pdst->lset); assert(ls->owner == self); dbLockDecRef(ls); } #endif psrc->pact = TRUE; if (psrc->ppn) dbNotifyAdd(psrc, pdst); if (trace && dbServerClient(context, sizeof(context))) { /* No client, use thread name */ strncpy(context, epicsThreadGetNameSelf(), sizeof(context)); context[sizeof(context) - 1] = 0; } if (!pdst->pact) { /* Normal propagation of PUTF from src to dst */ if (trace) printf("%s: '%s' -> '%s' with PUTF=%u\n", context, psrc->name, pdst->name, psrc->putf); pdst->putf = psrc->putf; } else if (psrc->putf && claim_dst) { /* The dst record is busy (awaiting async reprocessing), * not being processed recursively by us, and * we were originally triggered by a call to dbPutField(), * so we mark the dst record for reprocessing once the async * completion is over. */ if (trace) printf("%s: '%s' -> Active '%s', setting RPRO=1\n", context, psrc->name, pdst->name); pdst->putf = FALSE; pdst->rpro = TRUE; } else { /* The dst record is busy, but either is being processed recursively, * or wasn't triggered by a call to dbPutField(). Do nothing. */ if (trace) printf("%s: '%s' -> Active '%s', done\n", context, psrc->name, pdst->name); } if(claim_src) { dbRec2Pvt(psrc)->procThread = self; } if(claim_dst) { dbRec2Pvt(pdst)->procThread = self; } if(dbRec2Pvt(psrc)->procThread!=self || dbRec2Pvt(pdst)->procThread!=self) { errlogPrintf("Logic Error: processTarget 1 from %p, %s(%p) -> %s(%p)\n", self, psrc->name, dbRec2Pvt(psrc), pdst->name, dbRec2Pvt(pdst)); } status = dbProcess(pdst); psrc->pact = pact; if(dbRec2Pvt(psrc)->procThread!=self || dbRec2Pvt(pdst)->procThread!=self) { errlogPrintf("Logic Error: processTarget 2 from %p, %s(%p) -> %s(%p)\n", self, psrc->name, dbRec2Pvt(psrc), pdst->name, dbRec2Pvt(pdst)); } if(claim_src) { dbRec2Pvt(psrc)->procThread = NULL; } if(claim_dst) { dbRec2Pvt(pdst)->procThread = NULL; } return status; }