/*************************************************************************\ * 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. \*************************************************************************/ /* dbLink.c */ /* * Original Authors: Bob Dalesio, Marty Kraimer * Current Author: Andrew Johnson */ #include #include #include #include #include #include "alarm.h" #include "cvtFast.h" #include "dbDefs.h" #include "ellLib.h" #include "epicsTime.h" #include "errlog.h" #include "caeventmask.h" #define epicsExportSharedSymbols #include "dbAccessDefs.h" #include "dbAddr.h" #include "dbBase.h" #include "dbCa.h" #include "dbCommon.h" #include "dbConstLink.h" #include "dbDbLink.h" #include "db_field_log.h" #include "dbFldTypes.h" #include "dbJLink.h" #include "dbLink.h" #include "dbLock.h" #include "dbScan.h" #include "dbStaticLib.h" #include "devSup.h" #include "link.h" #include "recGbl.h" #include "recSup.h" #include "special.h" /* How to identify links in error messages */ const char * dbLinkFieldName(const struct link *plink) { const struct dbCommon *precord = plink->precord; const dbRecordType *pdbRecordType = precord->rdes; dbFldDes * const *papFldDes = pdbRecordType->papFldDes; const short *link_ind = pdbRecordType->link_ind; int i; for (i = 0; i < pdbRecordType->no_links; i++) { const dbFldDes *pdbFldDes = papFldDes[link_ind[i]]; if (plink == (DBLINK *)((char *)precord + pdbFldDes->offset)) return pdbFldDes->name; } return "????"; } /* Special TSEL handler for PV links */ /* FIXME: Generalize for new link types... */ static void TSEL_modified(struct link *plink) { struct pv_link *ppv_link; char *pfieldname; if (plink->type != PV_LINK) { errlogPrintf("dbLink::TSEL_modified called for non PV_LINK\n"); return; } /* If pvname contains .TIME truncate it to point to VAL instead */ ppv_link = &plink->value.pv_link; pfieldname = strstr(ppv_link->pvname, ".TIME"); if (pfieldname) { *pfieldname = 0; plink->flags |= DBLINK_FLAG_TSELisTIME; } } /***************************** Generic Link API *****************************/ void dbInitLink(struct link *plink, short dbfType) { struct dbCommon *precord = plink->precord; /* Only initialize link once */ if (plink->flags & DBLINK_FLAG_INITIALIZED) return; else plink->flags |= DBLINK_FLAG_INITIALIZED; if (plink->type == CONSTANT) { dbConstInitLink(plink); return; } if (plink->type == JSON_LINK) { dbJLinkInit(plink); return; } if (plink->type != PV_LINK) return; if (plink == &precord->tsel) TSEL_modified(plink); if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) { /* Make it a DB link if possible */ if (!dbDbInitLink(plink, dbfType)) return; } /* Make it a CA link */ if (dbfType == DBF_INLINK) plink->value.pv_link.pvlMask |= pvlOptInpNative; dbCaAddLink(NULL, plink, dbfType); if (dbfType == DBF_FWDLINK) { char *pperiod = strrchr(plink->value.pv_link.pvname, '.'); if (pperiod && strstr(pperiod, "PROC")) { plink->value.pv_link.pvlMask |= pvlOptFWD; } else { errlogPrintf("Forward-link uses Channel Access " "without pointing to PROC field\n" " %s.%s => %s\n", precord->name, dbLinkFieldName(plink), plink->value.pv_link.pvname); } } } void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, dbChannel *ptarget) { struct dbCommon *precord = plink->precord; /* Clear old TSELisTIME flag */ plink->flags &= ~DBLINK_FLAG_TSELisTIME; if (plink->type == CONSTANT) { dbConstAddLink(plink); return; } if (plink->type == JSON_LINK) { /* * FIXME: Can't create DB links as dbJLink types yet, * dbLock.c doesn't have any way to find/track them. */ dbJLinkInit(plink); return; } if (plink->type != PV_LINK) return; if (plink == &precord->tsel) TSEL_modified(plink); if (ptarget) { /* It's a DB link */ dbDbAddLink(locker, plink, dbfType, ptarget); return; } /* Make it a CA link */ if (dbfType == DBF_INLINK) plink->value.pv_link.pvlMask |= pvlOptInpNative; dbCaAddLink(locker, plink, dbfType); if (dbfType == DBF_FWDLINK) { char *pperiod = strrchr(plink->value.pv_link.pvname, '.'); if (pperiod && strstr(pperiod, "PROC")) plink->value.pv_link.pvlMask |= pvlOptFWD; } } void dbLinkOpen(struct link *plink) { lset *plset = plink->lset; if (plset && plset->openLink) plset->openLink(plink); } void dbRemoveLink(struct dbLocker *locker, struct link *plink) { lset *plset = plink->lset; if (plset) { if (plset->removeLink) plset->removeLink(locker, plink); plink->lset = NULL; } if (plink->type == JSON_LINK) plink->value.json.jlink = NULL; } int dbLinkIsDefined(const struct link *plink) { return (plink->lset != 0); } int dbLinkIsConstant(const struct link *plink) { lset *plset = plink->lset; return !plset || plset->isConstant; } int dbLinkIsVolatile(const struct link *plink) { lset *plset = plink->lset; return plset && plset->isVolatile; } long dbLoadLink(struct link *plink, short dbrType, void *pbuffer) { lset *plset = plink->lset; if (plset && plset->loadScalar) return plset->loadScalar(plink, dbrType, pbuffer); return S_db_noLSET; } long dbLoadLinkLS(struct link *plink, char *pbuffer, epicsUInt32 size, epicsUInt32 *plen) { lset *plset = plink->lset; if (plset && plset->loadLS) return plset->loadLS(plink, pbuffer, size, plen); return S_db_noLSET; } long dbLoadLinkArray(struct link *plink, short dbrType, void *pbuffer, long *pnRequest) { lset *plset = plink->lset; if (plset && plset->loadArray) return plset->loadArray(plink, dbrType, pbuffer, pnRequest); return S_db_noLSET; } int dbIsLinkConnected(const struct link *plink) { lset *plset = plink->lset; if (!plset) return FALSE; if (!plset->isVolatile) return TRUE; if (!plset->isConnected) { struct dbCommon *precord = plink->precord; errlogPrintf("dbLink: Link type for '%s.%s' is volatile but has no" " lset::isConnected() method\n", precord->name, dbLinkFieldName(plink)); return FALSE; } return plset->isConnected(plink); } int dbGetLinkDBFtype(const struct link *plink) { lset *plset = plink->lset; if (!plset || !plset->getDBFtype) return -1; return plset->getDBFtype(plink); } long dbGetNelements(const struct link *plink, long *nelements) { lset *plset = plink->lset; if (!plset || !plset->getElements) return S_db_noLSET; return plset->getElements(plink, nelements); } long dbTryGetLink(struct link *plink, short dbrType, void *pbuffer, long *pnRequest) { lset *plset = plink->lset; if (!plset || !plset->getValue) return S_db_noLSET; return plset->getValue(plink, dbrType, pbuffer, pnRequest); } long dbGetLink(struct link *plink, short dbrType, void *pbuffer, long *poptions, long *pnRequest) { struct dbCommon *precord = plink->precord; long status; if (poptions && *poptions) { printf("dbGetLink: Use of poptions no longer supported\n"); *poptions = 0; } status = dbTryGetLink(plink, dbrType, pbuffer, pnRequest); if (status == S_db_noLSET) return -1; if (status) recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); return status; } long dbGetControlLimits(const struct link *plink, double *low, double *high) { lset *plset = plink->lset; if (!plset || !plset->getControlLimits) return S_db_noLSET; return plset->getControlLimits(plink, low, high); } long dbGetGraphicLimits(const struct link *plink, double *low, double *high) { lset *plset = plink->lset; if (!plset || !plset->getGraphicLimits) return S_db_noLSET; return plset->getGraphicLimits(plink, low, high); } long dbGetAlarmLimits(const struct link *plink, double *lolo, double *low, double *high, double *hihi) { lset *plset = plink->lset; if (!plset || !plset->getAlarmLimits) return S_db_noLSET; return plset->getAlarmLimits(plink, lolo, low, high, hihi); } long dbGetPrecision(const struct link *plink, short *precision) { lset *plset = plink->lset; if (!plset || !plset->getPrecision) return S_db_noLSET; return plset->getPrecision(plink, precision); } long dbGetUnits(const struct link *plink, char *units, int unitsSize) { lset *plset = plink->lset; if (!plset || !plset->getUnits) return S_db_noLSET; return plset->getUnits(plink, units, unitsSize); } long dbGetAlarm(const struct link *plink, epicsEnum16 *status, epicsEnum16 *severity) { lset *plset = plink->lset; if (!plset || !plset->getAlarm) return S_db_noLSET; return plset->getAlarm(plink, status, severity); } long dbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp) { lset *plset = plink->lset; if (!plset || !plset->getTimeStamp) return S_db_noLSET; return plset->getTimeStamp(plink, pstamp); } long dbPutLink(struct link *plink, short dbrType, const void *pbuffer, long nRequest) { lset *plset = plink->lset; long status; if (!plset || !plset->putValue) return S_db_noLSET; status = plset->putValue(plink, dbrType, pbuffer, nRequest); if (status) { struct dbCommon *precord = plink->precord; recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); } return status; } void dbLinkAsyncComplete(struct link *plink) { dbCommon *pdbCommon = plink->precord; dbScanLock(pdbCommon); pdbCommon->rset->process(pdbCommon); dbScanUnlock(pdbCommon); } long dbPutLinkAsync(struct link *plink, short dbrType, const void *pbuffer, long nRequest) { lset *plset = plink->lset; long status; if (!plset || !plset->putAsync) return S_db_noLSET; status = plset->putAsync(plink, dbrType, pbuffer, nRequest); if (status) { struct dbCommon *precord = plink->precord; recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); } return status; } void dbScanFwdLink(struct link *plink) { lset *plset = plink->lset; if (plset && plset->scanForward) plset->scanForward(plink); } long dbLinkDoLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) { lset *plset = plink->lset; if (!rtn || !plset || !plset->doLocked) return S_db_noLSET; return plset->doLocked(plink, rtn, priv); } /* Helper functions for long string support */ long dbGetLinkLS(struct link *plink, char *pbuffer, epicsUInt32 size, epicsUInt32 *plen) { int dtyp = dbGetLinkDBFtype(plink); long len = size; long status; if (dtyp < 0) /* Not connected */ return 0; if (dtyp == DBR_CHAR || dtyp == DBF_UCHAR) { status = dbGetLink(plink, dtyp, pbuffer, 0, &len); } else if (size >= MAX_STRING_SIZE) status = dbGetLink(plink, DBR_STRING, pbuffer, 0, 0); else { /* pbuffer is too small to fetch using DBR_STRING */ char tmp[MAX_STRING_SIZE]; status = dbGetLink(plink, DBR_STRING, tmp, 0, 0); if (!status) strncpy(pbuffer, tmp, len - 1); } if (!status) { pbuffer[--len] = 0; *plen = (epicsUInt32) strlen(pbuffer) + 1; } return status; } long dbPutLinkLS(struct link *plink, char *pbuffer, epicsUInt32 len) { int dtyp = dbGetLinkDBFtype(plink); if (dtyp < 0) return 0; /* Not connected */ if (dtyp == DBR_CHAR || dtyp == DBF_UCHAR) return dbPutLink(plink, dtyp, pbuffer, len); return dbPutLink(plink, DBR_STRING, pbuffer, 1); }