diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index cc2ddd940..819b1e6f5 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -17,6 +17,95 @@ should also be read to understand what has changed since earlier releases. +### Filters in database input links + +Input database links can now use channel filters, it is not necessary to +make them CA links for the filters to work. + +### ai Soft Channel support + +The Soft Channel device support for ai records now returns failure when +fetching the INP link fails. + +### Support for zero-length arrays + +Several modifications have been made to properly support zero-length +array values inside the IOC and over Channel Access. Some of these changes +may affect external code that interfaces with the IOC, either directly or +over the CA client API so we recommend thorough testing of any external +code that handles array fields when upgrading to this release. + +Since these changes affect the Channel Access client-side API they will +require rebuilding any CA Gateways against this version or Base to +properly handle zero-length arrays. The `caget`, `caput` and `camonitor` +client programs are known to work with empty arrays as long as they were +built with this or a later version of EPICS. + +#### Change to the db_access.h `dbr_size_n(TYPE, COUNT)` macro + +When called with COUNT=0 this macro no longer returns the number of bytes +required for a scalar (1 element) but for an empty array (0 elements). +Make sure code that uses this doesn't call it with COUNT=0 when it really +means COUNT=1. + +Note that the db_access.h header file is included by cadef.h so the change +can impact Channel Access client programs that use this macro. + +#### Channel Access support for zero-length arrays + +The `ca_array_put()` and `ca_array_put_callback()` routines now accept an +element count of zero, and will write a zero-length array to the PV if +possible. No error will be raised if the target is a scalar field though, +and the field's value will not be changed. + +The `ca_array_get_callback()` and `ca_create_subscription()` routines +still accept a count of zero to mean fetch as many elements as the PV +currently holds. + +Client programs should be prepared for the `count` fields of any +`struct event_handler_args` or `struct exception_handler_args` passed to +their callback routines to be zero. + +#### Array records + +The soft device support for the array records aai, waveform, and subArray +as well as the aSub record type now correctly report reading 0 elements +when getting an empty array from an input link. + +#### Array support for dbpf + +The dbpf command now accepts array values, including empty arrays, when +provided as a JSON string. This must be enclosed in quotes so the iocsh +argument parser sees the JSON as a single argument: + +``` +epics> dbpf wf10:i32 '[1, 2, 3, 4, 5]' +DBF_LONG[5]: 1 = 0x1 2 = 0x2 3 = 0x3 4 = 0x4 5 = 0x5 +``` + +#### Reading empty arrays as scalar values + +Record links that get a scalar value from an array that is currently +empty will cause the record that has the link field to be set to an +`INVALID/LINK` alarm status. +The record code must call `dbGetLink()` with `pnRequest=NULL` for it to +be recognized as a request for a scalar value though. + +This changes the semantics of passing `pnRequest=NULL` to `dbGetLink()`, +which now behaves differently than passing it a pointer to a long integer +containing the value 1, which was previously equivalent. +The latter can successfully fetch a zero-element array without triggering +a LINK alarm. + +#### Writing empty arrays to scalar fields + +Record links that put a zero-element array into a scalar field will now set +the target record to `INVALID/LINK` alarm without changing the field's value. +Previously the field was set to 0 in this case (with no alarm). +The target field must be marked as `special(SPC_DBADDR)` to be recognized +as an array field, and its record support must define a `put_array_info()` +routine. + ### Timestamp before processing output links The record processing code for records with output links has been modified to update the timestamp via recGblGetTimeStamp() before processing the diff --git a/modules/ca/src/client/db_access.h b/modules/ca/src/client/db_access.h index 9123b5b0a..c13977e51 100644 --- a/modules/ca/src/client/db_access.h +++ b/modules/ca/src/client/db_access.h @@ -517,7 +517,7 @@ struct dbr_ctrl_double{ }; #define dbr_size_n(TYPE,COUNT)\ -((unsigned)((COUNT)<=0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE])) +((unsigned)((COUNT)<0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE])) /* size for each type - array indexed by the DBR_ type code */ LIBCA_API extern const unsigned short dbr_size[]; diff --git a/modules/ca/src/client/nciu.cpp b/modules/ca/src/client/nciu.cpp index eed2bce11..61ccebafc 100644 --- a/modules/ca/src/client/nciu.cpp +++ b/modules/ca/src/client/nciu.cpp @@ -329,7 +329,7 @@ void nciu::write ( if ( ! this->accessRightState.writePermit() ) { throw cacChannel::noWriteAccess(); } - if ( countIn > this->count || countIn == 0 ) { + if ( countIn > this->count) { throw cacChannel::outOfBounds(); } if ( type == DBR_STRING ) { @@ -350,7 +350,7 @@ cacChannel::ioStatus nciu::write ( if ( ! this->accessRightState.writePermit() ) { throw cacChannel::noWriteAccess(); } - if ( countIn > this->count || countIn == 0 ) { + if ( countIn > this->count) { throw cacChannel::outOfBounds(); } if ( type == DBR_STRING ) { diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index f1e9fc8a2..d7e5d0890 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -946,13 +946,18 @@ long dbGet(DBADDR *paddr, short dbrType, if (offset == 0 && (!nRequest || no_elements == 1)) { if (nRequest) *nRequest = 1; + else if (no_elements < 1) { + status = S_db_onlyOne; + goto done; + } + if (!pfl || pfl->type == dbfl_type_rec) { status = dbFastGetConvertRoutine[field_type][dbrType] (paddr->pfield, pbuf, paddr); } else { DBADDR localAddr = *paddr; /* Structure copy */ - if (pfl->no_elements < 1) { + if (no_elements < 1) { status = S_db_badField; goto done; } @@ -996,6 +1001,11 @@ long dbGet(DBADDR *paddr, short dbrType, } else { DBADDR localAddr = *paddr; /* Structure copy */ + if (pfl->no_elements < 1) { + status = S_db_badField; + goto done; + } + localAddr.field_type = pfl->field_type; localAddr.field_size = pfl->field_size; localAddr.no_elements = pfl->no_elements; @@ -1037,7 +1047,7 @@ static long dbPutFieldLink(DBADDR *paddr, short dbrType, const void *pbuffer, long nRequest) { dbLinkInfo link_info; - DBADDR *pdbaddr = NULL; + dbChannel *chan = NULL; dbCommon *precord = paddr->precord; dbCommon *lockrecs[2]; dbLocker locker; @@ -1075,16 +1085,11 @@ static long dbPutFieldLink(DBADDR *paddr, if (link_info.ltype == PV_LINK && (link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) { - DBADDR tempaddr; - - if (dbNameToAddr(link_info.target, &tempaddr)==0) { - /* This will become a DB link. */ - pdbaddr = malloc(sizeof(*pdbaddr)); - if (!pdbaddr) { - status = S_db_noMemory; - goto cleanup; - } - *pdbaddr = tempaddr; /* struct copy */ + chan = dbChannelCreate(link_info.target); + if (chan && dbChannelOpen(chan) != 0) { + errlogPrintf("ERROR: dbPutFieldLink %s.%s=%s: dbChannelOpen() failed\n", + precord->name, pfldDes->name, link_info.target); + goto cleanup; } } @@ -1093,7 +1098,7 @@ static long dbPutFieldLink(DBADDR *paddr, memset(&locker, 0, sizeof(locker)); lockrecs[0] = precord; - lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL; + lockrecs[1] = chan ? dbChannelRecord(chan) : NULL; dbLockerPrepare(&locker, lockrecs, 2); dbScanLockMany(&locker); @@ -1181,7 +1186,8 @@ static long dbPutFieldLink(DBADDR *paddr, case PV_LINK: case CONSTANT: case JSON_LINK: - dbAddLink(&locker, plink, pfldDes->field_type, pdbaddr); + dbAddLink(&locker, plink, pfldDes->field_type, chan); + chan = NULL; /* don't clean it up */ break; case DB_LINK: @@ -1211,6 +1217,8 @@ unlock: dbScanUnlockMany(&locker); dbLockerFinalize(&locker); cleanup: + if (chan) + dbChannelDelete(chan); free(link_info.target); return status; } @@ -1330,25 +1338,21 @@ long dbPut(DBADDR *paddr, short dbrType, status = prset->get_array_info(paddr, &dummy, &offset); /* paddr->pfield may be modified */ if (status) goto done; - } else - offset = 0; - - if (no_elements <= 1) { - status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer, - paddr->pfield, paddr); - nRequest = 1; - } else { if (no_elements < nRequest) nRequest = no_elements; status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer, nRequest, no_elements, offset); - } - - /* update array info */ - if (!status && - paddr->pfldDes->special == SPC_DBADDR && - prset && prset->put_array_info) { - status = prset->put_array_info(paddr, nRequest); + /* update array info */ + if (!status && prset->put_array_info) + status = prset->put_array_info(paddr, nRequest); + } else { + if (nRequest < 1) { + recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); + } else { + status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer, + paddr->pfield, paddr); + nRequest = 1; + } } /* Always do special processing if needed */ diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c index c076561c9..0397064d7 100644 --- a/modules/database/src/ioc/db/dbCa.c +++ b/modules/database/src/ioc/db/dbCa.c @@ -411,9 +411,15 @@ long dbCaGetLink(struct link *plink, short dbrType, void *pdest, goto done; } newType = dbDBRoldToDBFnew[pca->dbrType]; - if (!nelements || *nelements == 1) { + if (!nelements) { long (*fConvert)(const void *from, void *to, struct dbAddr *paddr); + if (pca->usedelements < 1) { + pca->sevr = INVALID_ALARM; + pca->stat = LINK_ALARM; + status = -1; + goto done; + } fConvert = dbFastGetConvertRoutine[newType][dbrType]; assert(pca->pgetNative); status = fConvert(pca->pgetNative, pdest, 0); diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c index 032293bc5..b6f53f797 100644 --- a/modules/database/src/ioc/db/dbChannel.c +++ b/modules/database/src/ioc/db/dbChannel.c @@ -18,6 +18,8 @@ #include #include +#define EPICS_PRIVATE_API + #include "cantProceed.h" #include "epicsAssert.h" #include "epicsString.h" @@ -68,6 +70,7 @@ void dbChannelInit (void) freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); + db_init_event_freelists(); } static void chf_value(parseContext *parser, parse_result *presult) diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c index e89759a90..b2813145c 100644 --- a/modules/database/src/ioc/db/dbDbLink.c +++ b/modules/database/src/ioc/db/dbDbLink.c @@ -61,6 +61,7 @@ #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" @@ -74,7 +75,7 @@ #include "recSup.h" #include "special.h" #include "dbDbLink.h" - +#include "dbChannel.h" /***************************** Database Links *****************************/ @@ -83,45 +84,51 @@ 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) { - DBADDR dbaddr; long status; - DBADDR *pdbAddr; + dbChannel *chan; + dbCommon *precord; - status = dbNameToAddr(plink->value.pv_link.pvname, &dbaddr); + 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; - pdbAddr = dbCalloc(1, sizeof(struct dbAddr)); - *pdbAddr = dbaddr; /* structure copy */ - plink->value.pv_link.pvt = pdbAddr; - ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode); + 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, dbaddr.precord); - assert(plink->precord->lset->plockSet == dbaddr.precord->lset->plockSet); + 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, - DBADDR *ptarget) + dbChannel *chan) { plink->lset = &dbDb_lset; plink->type = DB_LINK; - plink->value.pv_link.pvt = ptarget; - ellAdd(&ptarget->precord->bklnk, &plink->value.pv_link.backlinknode); + 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, ptarget->precord); + dbLockSetMerge(locker, plink->precord, dbChannelRecord(chan)); } static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink) { - DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + dbCommon *precord = dbChannelRecord(chan); plink->type = PV_LINK; @@ -131,10 +138,10 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink) plink->value.pv_link.getCvt = 0; plink->value.pv_link.pvlMask = 0; plink->value.pv_link.lastGetdbrType = 0; - ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode); - dbLockSetSplit(locker, plink->precord, pdbAddr->precord); + ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode); + dbLockSetSplit(locker, plink->precord, precord); } - free(pdbAddr); + dbChannelDelete(chan); } static int dbDbIsConnected(const struct link *plink) @@ -144,16 +151,14 @@ static int dbDbIsConnected(const struct link *plink) static int dbDbGetDBFtype(const struct link *plink) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; - - return paddr->field_type; + dbChannel *chan = linkChannel(plink); + return dbChannelFinalFieldType(chan); } static long dbDbGetElements(const struct link *plink, long *nelements) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; - - *nelements = paddr->no_elements; + dbChannel *chan = linkChannel(plink); + *nelements = dbChannelFinalElements(chan); return 0; } @@ -161,47 +166,75 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer, long *pnRequest) { struct pv_link *ppv_link = &plink->value.pv_link; - DBADDR *paddr = ppv_link->pvt; + 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, paddr->precord); + status = dbScanPassive(precord, dbChannelRecord(chan)); if (status) return status; } - if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) { - status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr); - } else { - unsigned short dbfType = paddr->field_type; + 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: set up shortcut */ + unsigned short dbfType = dbChannelFinalFieldType(chan); if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE) return S_db_badDbrtype; - if (paddr->no_elements == 1 && (!pnRequest || *pnRequest == 1) - && paddr->special != SPC_DBADDR - && paddr->special != SPC_ATTRIBUTE) { - ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType]; - status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr); - } else { - ppv_link->getCvt = NULL; - status = dbGet(paddr, dbrType, pbuffer, NULL, pnRequest, NULL); - } + 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 != paddr->precord) + if (!status && precord != dbChannelRecord(chan)) recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode, - plink->precord, paddr->precord->stat, paddr->precord->sevr); + plink->precord, + dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr); return status; } static long dbDbGetControlLimits(const struct link *plink, double *low, double *high) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + DBADDR *paddr = &chan->addr; struct buffer { DBRctrlDouble double value; @@ -222,7 +255,8 @@ static long dbDbGetControlLimits(const struct link *plink, double *low, static long dbDbGetGraphicLimits(const struct link *plink, double *low, double *high) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + DBADDR *paddr = &chan->addr; struct buffer { DBRgrDouble double value; @@ -243,7 +277,8 @@ static long dbDbGetGraphicLimits(const struct link *plink, double *low, static long dbDbGetAlarmLimits(const struct link *plink, double *lolo, double *low, double *high, double *hihi) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + DBADDR *paddr = &chan->addr; struct buffer { DBRalDouble double value; @@ -265,7 +300,8 @@ static long dbDbGetAlarmLimits(const struct link *plink, double *lolo, static long dbDbGetPrecision(const struct link *plink, short *precision) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + DBADDR *paddr = &chan->addr; struct buffer { DBRprecision double value; @@ -284,7 +320,8 @@ static long dbDbGetPrecision(const struct link *plink, short *precision) static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + dbChannel *chan = linkChannel(plink); + DBADDR *paddr = &chan->addr; struct buffer { DBRunits double value; @@ -304,20 +341,20 @@ static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize) static long dbDbGetAlarm(const struct link *plink, epicsEnum16 *status, epicsEnum16 *severity) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; - + dbChannel *chan = linkChannel(plink); + dbCommon *precord = dbChannelRecord(chan); if (status) - *status = paddr->precord->stat; + *status = precord->stat; if (severity) - *severity = paddr->precord->sevr; + *severity = precord->sevr; return 0; } static long dbDbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp) { - DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; - - *pstamp = paddr->precord->time; + dbChannel *chan = linkChannel(plink); + dbCommon *precord = dbChannelRecord(chan); + *pstamp = precord->time; return 0; } @@ -325,9 +362,10 @@ 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 = (DBADDR *) ppv_link->pvt; - dbCommon *pdest = paddr->precord; + DBADDR *paddr = &chan->addr; + dbCommon *pdest = dbChannelRecord(chan); long status = dbPut(paddr, dbrType, pbuffer, nRequest); recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta, @@ -335,7 +373,7 @@ static long dbDbPutValue(struct link *plink, short dbrType, if (status) return status; - if (paddr->pfield == (void *) &pdest->proc || + if (dbChannelField(chan) == (void *) &pdest->proc || (ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) { status = processTarget(psrce, pdest); } @@ -346,9 +384,8 @@ static long dbDbPutValue(struct link *plink, short dbrType, static void dbDbScanFwdLink(struct link *plink) { dbCommon *precord = plink->precord; - dbAddr *paddr = (dbAddr *) plink->value.pv_link.pvt; - - dbScanPassive(precord, paddr->precord); + dbChannel *chan = linkChannel(plink); + dbScanPassive(precord, dbChannelRecord(chan)); } static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) diff --git a/modules/database/src/ioc/db/dbDbLink.h b/modules/database/src/ioc/db/dbDbLink.h index ce99c0345..66bd452ee 100644 --- a/modules/database/src/ioc/db/dbDbLink.h +++ b/modules/database/src/ioc/db/dbDbLink.h @@ -27,7 +27,7 @@ struct dbLocker; epicsShareFunc long dbDbInitLink(struct link *plink, short dbfType); epicsShareFunc void dbDbAddLink(struct dbLocker *locker, struct link *plink, - short dbfType, DBADDR *ptarget); + short dbfType, dbChannel *ptarget); #ifdef __cplusplus } diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c index 4b3caac9d..437cb6d5d 100644 --- a/modules/database/src/ioc/db/dbEvent.c +++ b/modules/database/src/ioc/db/dbEvent.c @@ -252,18 +252,15 @@ int dbel ( const char *pname, unsigned level ) } /* - * DB_INIT_EVENTS() + * DB_INIT_EVENT_FREELISTS() * * - * Initialize the event facility for this task. Must be called at least once - * by each task which uses the db event facility + * Initialize the free lists used by the event facility. + * Safe to be called multiple times. * - * returns: ptr to event user block or NULL if memory can't be allocated */ -dbEventCtx db_init_events (void) +void db_init_event_freelists (void) { - struct event_user * evUser; - if (!dbevEventUserFreeList) { freeListInitPvt(&dbevEventUserFreeList, sizeof(struct event_user),8); @@ -280,6 +277,22 @@ dbEventCtx db_init_events (void) freeListInitPvt(&dbevFieldLogFreeList, sizeof(struct db_field_log),2048); } +} + +/* + * DB_INIT_EVENTS() + * + * + * Initialize the event facility for this task. Must be called at least once + * by each task which uses the db event facility + * + * returns: ptr to event user block or NULL if memory can't be allocated + */ +dbEventCtx db_init_events (void) +{ + struct event_user * evUser; + + db_init_event_freelists(); evUser = (struct event_user *) freeListCalloc(dbevEventUserFreeList); diff --git a/modules/database/src/ioc/db/dbEvent.h b/modules/database/src/ioc/db/dbEvent.h index c8254800b..2c6feca11 100644 --- a/modules/database/src/ioc/db/dbEvent.h +++ b/modules/database/src/ioc/db/dbEvent.h @@ -66,6 +66,7 @@ epicsShareFunc void db_event_change_priority ( dbEventCtx ctx, unsigned epicsPri #ifdef EPICS_PRIVATE_API epicsShareFunc void db_cleanup_events(void); +epicsShareFunc void db_init_event_freelists (void); #endif typedef void EVENTFUNC (void *user_arg, struct dbChannel *chan, diff --git a/modules/database/src/ioc/db/dbLink.c b/modules/database/src/ioc/db/dbLink.c index 421da5068..ef8336818 100644 --- a/modules/database/src/ioc/db/dbLink.c +++ b/modules/database/src/ioc/db/dbLink.c @@ -144,7 +144,7 @@ void dbInitLink(struct link *plink, short dbfType) } void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, - DBADDR *ptarget) + dbChannel *ptarget) { struct dbCommon *precord = plink->precord; diff --git a/modules/database/src/ioc/db/dbLink.h b/modules/database/src/ioc/db/dbLink.h index 89a0aaf9f..f7c11c748 100644 --- a/modules/database/src/ioc/db/dbLink.h +++ b/modules/database/src/ioc/db/dbLink.h @@ -21,6 +21,7 @@ #include "epicsTypes.h" #include "epicsTime.h" #include "dbAddr.h" +#include "dbChannel.h" #ifdef __cplusplus extern "C" { @@ -369,7 +370,7 @@ epicsShareFunc const char * dbLinkFieldName(const struct link *plink); epicsShareFunc void dbInitLink(struct link *plink, short dbfType); epicsShareFunc void dbAddLink(struct dbLocker *locker, struct link *plink, - short dbfType, DBADDR *ptarget); + short dbfType, dbChannel *ptarget); epicsShareFunc void dbLinkOpen(struct link *plink); epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct link *plink); diff --git a/modules/database/src/ioc/db/dbLock.c b/modules/database/src/ioc/db/dbLock.c index cac17fb41..002b5cfc2 100644 --- a/modules/database/src/ioc/db/dbLock.c +++ b/modules/database/src/ioc/db/dbLock.c @@ -743,14 +743,14 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) for(i=0; ino_links; i++) { dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); - DBADDR *ptarget; + dbChannel *chan; lockRecord *lr; if(plink->type!=DB_LINK) continue; - ptarget = plink->value.pv_link.pvt; - lr = ptarget->precord->lset; + chan = plink->value.pv_link.pvt; + lr = dbChannelRecord(chan)->lset; assert(lr); if(lr->precord==pfirst) { diff --git a/modules/database/src/ioc/db/dbTest.c b/modules/database/src/ioc/db/dbTest.c index 8baed5aea..6106027a8 100644 --- a/modules/database/src/ioc/db/dbTest.c +++ b/modules/database/src/ioc/db/dbTest.c @@ -41,6 +41,7 @@ #include "recGbl.h" #include "recSup.h" #include "special.h" +#include "dbConvertJSON.h" #define MAXLINE 80 #define MAXMESS 128 @@ -364,8 +365,9 @@ long dbpf(const char *pname,const char *pvalue) { DBADDR addr; long status; - short dbrType; - size_t n = 1; + short dbrType = DBR_STRING; + long n = 1; + char *array = NULL; if (!pname || !*pname || !pvalue) { printf("Usage: dbpf \"pv name\", \"value\"\n"); @@ -380,16 +382,25 @@ long dbpf(const char *pname,const char *pvalue) return -1; } - if (addr.no_elements > 1 && - (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR)) { + if (addr.no_elements > 1) { dbrType = addr.dbr_field_type; - n = strlen(pvalue) + 1; + if (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR) { + n = (long)strlen(pvalue) + 1; + } else { + n = addr.no_elements; + array = calloc(n, dbValueSize(dbrType)); + if (!array) { + printf("Out of memory\n"); + return -1; + } + status = dbPutConvertJSON(pvalue, dbrType, array, &n); + if (status) + return status; + pvalue = array; + } } - else { - dbrType = DBR_STRING; - } - - status = dbPutField(&addr, dbrType, pvalue, (long) n); + status = dbPutField(&addr, dbrType, pvalue, n); + free(array); dbgf(pname); return status; } @@ -943,13 +954,13 @@ static void printBuffer( } /* Now print values */ - if (no_elements == 0) - return; - if (no_elements == 1) sprintf(pmsg, "DBF_%s: ", dbr[dbr_type]); - else + else { sprintf(pmsg, "DBF_%s[%ld]: ", dbr[dbr_type], no_elements); + if (no_elements == 0) + strcat(pmsg, "(empty)"); + } dbpr_msgOut(pMsgBuff, tab_size); if (status != 0) { diff --git a/modules/database/src/std/dev/devAaiSoft.c b/modules/database/src/std/dev/devAaiSoft.c index e8fa05f44..55975bae0 100644 --- a/modules/database/src/std/dev/devAaiSoft.c +++ b/modules/database/src/std/dev/devAaiSoft.c @@ -61,9 +61,10 @@ static long init_record(dbCommon *pcommon) } status = dbLoadLinkArray(plink, prec->ftvl, prec->bptr, &nRequest); - if (!status && nRequest > 0) { + if (!status) { prec->nord = nRequest; prec->udf = FALSE; + return status; } } return 0; @@ -75,7 +76,7 @@ static long readLocked(struct link *pinp, void *dummy) long nRequest = prec->nelm; long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &nRequest); - if (!status && nRequest > 0) { + if (!status) { prec->nord = nRequest; prec->udf = FALSE; @@ -90,8 +91,12 @@ static long read_aai(aaiRecord *prec) { epicsUInt32 nord = prec->nord; struct link *pinp = prec->simm == menuYesNoYES ? &prec->siol : &prec->inp; - long status = dbLinkDoLocked(pinp, readLocked, NULL); + long status; + if (dbLinkIsConstant(pinp)) + return 0; + + status = dbLinkDoLocked(pinp, readLocked, NULL); if (status == S_db_noLSET) status = readLocked(pinp, NULL); diff --git a/modules/database/src/std/dev/devAiSoft.c b/modules/database/src/std/dev/devAiSoft.c index fe0ccec56..bd0ef9c76 100644 --- a/modules/database/src/std/dev/devAiSoft.c +++ b/modules/database/src/std/dev/devAiSoft.c @@ -86,9 +86,10 @@ static long read_ai(aiRecord *prec) prec->udf = FALSE; prec->dpvt = &devAiSoft; /* Any non-zero value */ + return 2; } else prec->dpvt = NULL; - return 2; + return status; } diff --git a/modules/database/src/std/dev/devSASoft.c b/modules/database/src/std/dev/devSASoft.c index 55e55e290..d8e6e5c62 100644 --- a/modules/database/src/std/dev/devSASoft.c +++ b/modules/database/src/std/dev/devSASoft.c @@ -66,7 +66,7 @@ static long init_record(dbCommon *pcommon) status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nRequest); - if (!status && nRequest > 0) + if (!status) subset(prec, nRequest); return status; @@ -116,7 +116,7 @@ static long read_sa(subArrayRecord *prec) status = readLocked(&prec->inp, &rt); } - if (!status && rt.nRequest > 0) { + if (!status) { subset(prec, rt.nRequest); if (nord != prec->nord) diff --git a/modules/database/src/std/dev/devWfSoft.c b/modules/database/src/std/dev/devWfSoft.c index 3d455a29e..ccc984352 100644 --- a/modules/database/src/std/dev/devWfSoft.c +++ b/modules/database/src/std/dev/devWfSoft.c @@ -42,7 +42,7 @@ static long init_record(dbCommon *pcommon) long nelm = prec->nelm; long status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nelm); - if (!status && nelm > 0) { + if (!status) { prec->nord = nelm; prec->udf = FALSE; } @@ -78,11 +78,14 @@ static long read_wf(waveformRecord *prec) rt.ptime = (dbLinkIsConstant(&prec->tsel) && prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL; + if (dbLinkIsConstant(&prec->inp)) + return 0; + status = dbLinkDoLocked(&prec->inp, readLocked, &rt); if (status == S_db_noLSET) status = readLocked(&prec->inp, &rt); - if (!status && rt.nRequest > 0) { + if (!status) { prec->nord = rt.nRequest; prec->udf = FALSE; if (nord != prec->nord) diff --git a/modules/database/src/std/rec/aSubRecord.c b/modules/database/src/std/rec/aSubRecord.c index ebb37f5b0..5d380d725 100644 --- a/modules/database/src/std/rec/aSubRecord.c +++ b/modules/database/src/std/rec/aSubRecord.c @@ -278,10 +278,9 @@ static long fetch_values(aSubRecord *prec) long nRequest = (&prec->noa)[i]; status = dbGetLink(&(&prec->inpa)[i], (&prec->fta)[i], (&prec->a)[i], 0, &nRequest); - if (nRequest > 0) - (&prec->nea)[i] = nRequest; if (status) return status; + (&prec->nea)[i] = nRequest; } return 0; } diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index d48424f65..e8c546442 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -159,6 +159,13 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db TESTS += asyncproctest +TESTPROD_HOST += linkFilterTest +linkFilterTest_SRCS += linkFilterTest.c +linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += linkFilterTest.c +TESTFILES += ../linkFilterTest.db +TESTS += linkFilterTest + # dbHeader* is only a compile test # no need to actually run TESTPROD += dbHeaderTest diff --git a/modules/database/test/std/rec/linkFilterTest.c b/modules/database/test/std/rec/linkFilterTest.c new file mode 100644 index 000000000..1996c8c11 --- /dev/null +++ b/modules/database/test/std/rec/linkFilterTest.c @@ -0,0 +1,158 @@ +/*************************************************************************\ +* Copyright (c) 2020 Dirk Zimoch +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "devSup.h" +#include "alarm.h" +#include "dbUnitTest.h" +#include "errlog.h" +#include "epicsThread.h" + +#include "longinRecord.h" + +#include "testMain.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void startTestIoc(const char *dbfile) +{ + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase(dbfile, NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); +} + +static void expectProcSuccess(const char *rec) +{ + char fieldname[20]; + testDiag("expecting success from %s", rec); + sprintf(fieldname, "%s.PROC", rec); + testdbPutFieldOk(fieldname, DBF_LONG, 1); + sprintf(fieldname, "%s.SEVR", rec); + testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM); + sprintf(fieldname, "%s.STAT", rec); + testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM); +} + +static void expectProcFailure(const char *rec) +{ + char fieldname[20]; + testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, rec); + sprintf(fieldname, "%s.PROC", rec); + testdbPutFieldFail(S_db_onlyOne, fieldname, DBF_LONG, 1); + sprintf(fieldname, "%s.SEVR", rec); + testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM); + sprintf(fieldname, "%s.STAT", rec); + testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM); +} + +static void changeRange(long start, long stop, long step) +{ + char linkstring[60]; + if (step) + sprintf(linkstring, "src.[%ld:%ld:%ld]", start, step, stop); + else if (stop) + sprintf(linkstring, "src.[%ld:%ld]", start, stop); + else + sprintf(linkstring, "src.[%ld]", start); + testDiag("modifying link: %s", linkstring); + testdbPutFieldOk("ai.INP", DBF_STRING, linkstring); + testdbPutFieldOk("wf.INP", DBF_STRING, linkstring); +} + +static const double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6}; + +static void expectRange(long start, long end) +{ + long n = end-start+1; + testdbGetFieldEqual("ai.VAL", DBF_DOUBLE, buf[start]); + testdbGetFieldEqual("wf.NORD", DBF_LONG, n); + testdbGetArrFieldEqual("wf.VAL", DBF_DOUBLE, n+2, n, buf+start); +} + +static void expectEmptyArray(void) +{ + testDiag("expecting empty array"); + testdbGetFieldEqual("wf.NORD", DBF_LONG, 0); +} + +MAIN(linkFilterTest) +{ + testPlan(102); + startTestIoc("linkFilterTest.db"); + + testDiag("PINI"); + expectRange(2,4); + + testDiag("modify range"); + changeRange(3,6,0); + expectProcSuccess("ai"); + expectProcSuccess("wf"); + expectRange(3,6); + + testDiag("backward range"); + changeRange(5,3,0); + expectProcFailure("ai"); + expectProcSuccess("wf"); + expectEmptyArray(); + + testDiag("step 2"); + changeRange(1,6,2); + expectProcSuccess("ai"); + expectProcSuccess("wf"); + expectRange(10,12); + + testDiag("range start beyond src.NORD"); + changeRange(8,9,0); + expectProcFailure("ai"); + expectProcSuccess("wf"); + expectEmptyArray(); + + testDiag("range end beyond src.NORD"); + changeRange(3,9,0); + expectProcSuccess("ai"); + expectProcSuccess("wf"); + expectRange(3,7); /* clipped range */ + + testDiag("range start beyond src.NELM"); + changeRange(11,12,0); + expectProcFailure("ai"); + expectProcSuccess("wf"); + + testDiag("range end beyond src.NELM"); + changeRange(4,12,0); + expectProcSuccess("ai"); + expectProcSuccess("wf"); + expectRange(4,7); /* clipped range */ + + testDiag("single value beyond src.NORD"); + changeRange(8,0,0); + expectProcFailure("ai"); + expectProcSuccess("wf"); + expectEmptyArray(); + + testDiag("single value"); + changeRange(5,0,0); + expectProcSuccess("ai"); + expectProcSuccess("wf"); + expectRange(5,5); + + testDiag("single beyond rec.NELM"); + changeRange(12,0,0); + expectProcFailure("ai"); + expectProcSuccess("wf"); + expectEmptyArray(); + + testIocShutdownOk(); + testdbCleanup(); + return testDone(); +} diff --git a/modules/database/test/std/rec/linkFilterTest.db b/modules/database/test/std/rec/linkFilterTest.db new file mode 100644 index 000000000..5ee371e51 --- /dev/null +++ b/modules/database/test/std/rec/linkFilterTest.db @@ -0,0 +1,16 @@ +record(waveform, "src") { + field(NELM, "10") + field(FTVL, "SHORT") + field(INP, [1, 2, 3, 4, 5, 6, 7, 8]) +} +record(ai, "ai") { + field(INP, "src.[2]") # expect 3 + field(PINI, "YES") +} +record(waveform, "wf") { + field(NELM, "5") + field(FTVL, "DOUBLE") + field(INP, "src.[2:4]") # expect 3,4,5 + field(PINI, "YES") +} + diff --git a/modules/database/test/std/rec/regressTest.c b/modules/database/test/std/rec/regressTest.c index 7e9cb2f73..182b6bc8b 100644 --- a/modules/database/test/std/rec/regressTest.c +++ b/modules/database/test/std/rec/regressTest.c @@ -133,7 +133,7 @@ void testCADisconn(void) startRegressTestIoc("badCaLink.db"); - testdbPutFieldOk("ai:disconn.PROC", DBF_LONG, 1); + testdbPutFieldFail(-1, "ai:disconn.PROC", DBF_LONG, 1); testdbGetFieldEqual("ai:disconn.SEVR", DBF_LONG, INVALID_ALARM); testdbGetFieldEqual("ai:disconn.STAT", DBF_LONG, LINK_ALARM); }