539 lines
13 KiB
C
539 lines
13 KiB
C
/*************************************************************************\
|
|
* 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 <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "alarm.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 "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);
|
|
}
|
|
|
|
static
|
|
void setLinkAlarm(struct link* plink)
|
|
{
|
|
struct dbCommon *precord = plink->precord;
|
|
dbRecordType *rdes = precord->rdes;
|
|
const char* amsg = NULL;
|
|
short i;
|
|
|
|
for(i=0; i<rdes->no_links; i++) {
|
|
dbFldDes *fdes = rdes->papFldDes[rdes->link_ind[i]];
|
|
if((char*)plink - (char*)precord == fdes->offset) {
|
|
amsg = fdes->name;
|
|
}
|
|
}
|
|
|
|
recGblSetSevrMsg(precord, LINK_ALARM, INVALID_ALARM, "field %s", amsg);
|
|
}
|
|
|
|
long dbGetLink(struct link *plink, short dbrType, void *pbuffer,
|
|
long *poptions, long *pnRequest)
|
|
{
|
|
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)
|
|
setLinkAlarm(plink);
|
|
|
|
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)
|
|
{
|
|
return dbGetAlarmMsg(plink, status, severity, NULL, 0);
|
|
}
|
|
|
|
long dbGetAlarmMsg(const struct link *plink, epicsEnum16 *status,
|
|
epicsEnum16 *severity, char *msgbuf, size_t msgbuflen)
|
|
{
|
|
lset *plset = plink->lset;
|
|
|
|
if (plset && plset->getAlarmMsg) {
|
|
return plset->getAlarmMsg(plink, status, severity, msgbuf, msgbuflen);
|
|
} else if(plset && plset->getAlarm) {
|
|
return plset->getAlarm(plink, status, severity);
|
|
} else {
|
|
return S_db_noLSET;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
setLinkAlarm(plink);
|
|
}
|
|
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) {
|
|
setLinkAlarm(plink);
|
|
}
|
|
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);
|
|
}
|