diff --git a/src/ioc/db/dbCAC.h b/src/ioc/db/dbCAC.h index a68bfcf2d..70d9e205b 100644 --- a/src/ioc/db/dbCAC.h +++ b/src/ioc/db/dbCAC.h @@ -205,6 +205,7 @@ private: cacContextNotify & notify; epics_auto_ptr < cacContext > pNetContext; char * pStateNotifyCache; + bool isolated; cacChannel & createChannel ( epicsGuard < epicsMutex > &, diff --git a/src/ioc/db/dbCa.c b/src/ioc/db/dbCa.c index b892b7e46..bf06d995a 100644 --- a/src/ioc/db/dbCa.c +++ b/src/ioc/db/dbCa.c @@ -52,8 +52,11 @@ #include "link.h" #include "recSup.h" +/* defined in dbContext.cpp + * Setup local CA access + */ extern void dbServiceIOInit(); - +extern int dbServiceIsolate; static ELLLIST workList = ELLLIST_INIT; /* Work list for dbCaTask */ static epicsMutexId workListLock; /*Mutual exclusions semaphores for workList*/ @@ -220,19 +223,16 @@ void dbCaShutdown(void) } } -void dbCaLinkInitIsolated(void) +static void dbCaLinkInitImpl(int isolate) { + dbServiceIsolate = isolate; + dbServiceIOInit(); + if (!workListLock) workListLock = epicsMutexMustCreate(); if (!workListEvent) workListEvent = epicsEventMustCreate(epicsEventEmpty); - dbCaCtl = ctlExit; -} -void dbCaLinkInit(void) -{ - dbServiceIOInit(); - dbCaLinkInitIsolated(); startStopEvent = epicsEventMustCreate(epicsEventEmpty); dbCaCtl = ctlPause; @@ -242,6 +242,16 @@ void dbCaLinkInit(void) epicsEventMustWait(startStopEvent); } +void dbCaLinkInitIsolated(void) +{ + dbCaLinkInitImpl(1); +} + +void dbCaLinkInit(void) +{ + dbCaLinkInitImpl(0); +} + void dbCaRun(void) { if (dbCaCtl == ctlPause) { diff --git a/src/ioc/db/dbChannelNOOP.h b/src/ioc/db/dbChannelNOOP.h new file mode 100644 index 000000000..d48540d88 --- /dev/null +++ b/src/ioc/db/dbChannelNOOP.h @@ -0,0 +1,118 @@ +#ifndef DBCHANNELNOOP_H +#define DBCHANNELNOOP_H + +#include +#include + +#include "cacIO.h" +#include "caerr.h" + +/** @brief A channel which never connects + * + * Used when dbCa is placed in isolated mode for unittests + */ +class dbChannelNOOP : public cacChannel +{ + std::string myname; +public: + dbChannelNOOP(const char *name, cacChannelNotify ¬ify) + :cacChannel(notify) + ,myname(name) + {} + + virtual void destroy ( + CallbackGuard & /*callbackGuard*/, + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + { + delete this; // goodbye cruel world + } + + virtual unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () + { + const char* name = myname.c_str(); + if(bufLen>myname.size()+1) { + bufLen=myname.size()+1; + } + memcpy(pBuf, name, bufLen); + pBuf[--bufLen] = '\0'; + return bufLen; + } + + // !! deprecated, avoid use !! + virtual const char * pName ( + epicsGuard < epicsMutex > & guard ) const throw () + {return myname.c_str();} + + virtual void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const + {} + + virtual void initiateConnect ( + epicsGuard < epicsMutex > & ) + {} + + virtual unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + {return 0;} + + virtual void flush ( + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + {} + + virtual ioStatus read ( + epicsGuard < epicsMutex > &mut, + unsigned type, arrayElementCount count, + cacReadNotify ¬ify, ioid * = 0 ) + { + notify.exception(mut, ECA_NORDACCESS, "dbChannelNOOP", type, count); + return iosSynch; + } + + virtual void write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue ) + {} + + virtual ioStatus write ( + epicsGuard < epicsMutex > &mut, + unsigned type, arrayElementCount count, + const void */*pValue*/, cacWriteNotify & notify, ioid * = 0 ) + { + notify.exception(mut, ECA_NOWTACCESS, "dbChannelNOOP", type, count); + return iosSynch; + } + + virtual void subscribe ( + epicsGuard < epicsMutex > &mut, unsigned type, + arrayElementCount count, unsigned /*mask*/, cacStateNotify & notify, + ioid * = 0 ) + { + // should never subscribe + notify.exception(mut, ECA_BADMASK, "dbChannelNOOP", type, count); + } + + virtual void ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & ) + {} + + virtual void ioShow ( + epicsGuard < epicsMutex > &, + const ioid &, unsigned level ) const + {} + + virtual short nativeType ( + epicsGuard < epicsMutex > & ) const + {return 0;} // DBR_STRING + + virtual arrayElementCount nativeElementCount ( + epicsGuard < epicsMutex > & ) const + {return 1;} +}; + +#endif // DBCHANNELNOOP_H diff --git a/src/ioc/db/dbContext.cpp b/src/ioc/db/dbContext.cpp index d005f084a..124a836cf 100644 --- a/src/ioc/db/dbContext.cpp +++ b/src/ioc/db/dbContext.cpp @@ -39,6 +39,7 @@ #include "dbCAC.h" #include "dbChannel.h" #include "dbChannelIO.h" +#include "dbChannelNOOP.h" #include "dbPutNotifyBlocker.h" class dbService : public cacService { @@ -61,9 +62,16 @@ cacContext & dbService::contextCreate ( mutualExclusion, notify ); } +extern "C" int dbServiceIsolate; +int dbServiceIsolate = 0; + extern "C" void dbServiceIOInit () { - caInstallDefaultService ( dbs ); + static int init=0; + if(!init) { + caInstallDefaultService ( dbs ); + init=1; + } } dbBaseIO::dbBaseIO () {} @@ -72,7 +80,8 @@ dbContext::dbContext ( epicsMutex & cbMutexIn, epicsMutex & mutexIn, cacContextNotify & notifyIn ) : readNotifyCache ( mutexIn ), ctx ( 0 ), stateNotifyCacheSize ( 0 ), mutex ( mutexIn ), cbMutex ( cbMutexIn ), - notify ( notifyIn ), pNetContext ( 0 ), pStateNotifyCache ( 0 ) + notify ( notifyIn ), pNetContext ( 0 ), pStateNotifyCache ( 0 ), + isolated(dbServiceIsolate) { } @@ -92,7 +101,10 @@ cacChannel & dbContext::createChannel ( dbChannel *dbch = dbChannel_create ( pName ); if ( ! dbch ) { - if ( ! this->pNetContext.get() ) { + if ( isolated ) { + return *new dbChannelNOOP(pName, notifyIn); + + } else if ( ! this->pNetContext.get() ) { this->pNetContext.reset ( & this->notify.createNetworkContext ( this->mutex, this->cbMutex ) ); diff --git a/src/ioc/db/dbNotify.c b/src/ioc/db/dbNotify.c index ede8dd30c..5e902ab79 100644 --- a/src/ioc/db/dbNotify.c +++ b/src/ioc/db/dbNotify.c @@ -109,6 +109,15 @@ static void notifyCallback(CALLBACK *pcallback); (listnode)->isOnList=0; \ } +static void notifyFree(void *raw) +{ + notifyPvt *pnotifyPvt = raw; + assert(pnotifyPvt->magic==MAGIC); + epicsEventDestroy(pnotifyPvt->cancelEvent); + epicsEventDestroy(pnotifyPvt->userCallbackEvent); + free(pnotifyPvt); +} + static void notifyInit(processNotify *ppn) { notifyPvt *pnotifyPvt; @@ -301,7 +310,7 @@ static void notifyCallback(CALLBACK *pcallback) void dbProcessNotifyExit(void) { - assert(ellCount(&pnotifyGlobal->freeList)==0); + ellFree2(&pnotifyGlobal->freeList, ¬ifyFree); epicsMutexDestroy(pnotifyGlobal->lock); free(pnotifyGlobal); pnotifyGlobal = NULL; diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index c51f1a119..34534d396 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -10,10 +10,15 @@ TOP=../../../.. include $(TOP)/configure/CONFIG +# Find private headers +USR_CFLAGS += -I../.. + TESTLIBRARY = dbTestIoc dbTestIoc_SRCS += xRecord.c +dbTestIoc_SRCS += arrRecord.c dbTestIoc_SRCS += dbLinkdset.c +dbTestIoc_SRCS += devx.c dbTestIoc_LIBS = dbCore ca Com TARGETS += $(COMMON_DIR)/dbTestIoc.dbd @@ -21,6 +26,8 @@ dbTestIoc_DBD += menuGlobal.dbd dbTestIoc_DBD += menuConvert.dbd dbTestIoc_DBD += menuScan.dbd dbTestIoc_DBD += xRecord.dbd +dbTestIoc_DBD += arrRecord.dbd +dbTestIoc_DBD += devx.dbd dbTestIoc_DBD += dbLinkdset.dbd TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db @@ -81,18 +88,21 @@ testHarness_SRCS += dbCaStatsTest.c TESTS += dbCaStatsTest TESTFILES += ../dbCaStatsTest.db -TARGETS += $(COMMON_DIR)/scanIoTest.dbd -scanIoTest_DBD += menuGlobal.dbd -scanIoTest_DBD += menuConvert.dbd +TESTPROD_HOST += dbCaLinkTest +dbCaLinkTest_SRCS += dbCaLinkTest.c +dbCaLinkTest_SRCS += dbCACTest.cpp +dbCaLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbCaLinkTest.c +testHarness_SRCS += dbCACTest.cpp +TESTS += dbCaLinkTest +TESTFILES += ../dbCaLinkTest1.db ../dbCaLinkTest2.db ../dbCaLinkTest3.db + scanIoTest_DBD += menuScan.dbd -scanIoTest_DBD += yRecord.dbd TESTPROD_HOST += scanIoTest scanIoTest_SRCS += scanIoTest.c -scanIoTest_REGRDDFLAGS = -l -scanIoTest_SRCS += scanIoTest_registerRecordDeviceDriver.cpp +scanIoTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp testHarness_SRCS += scanIoTest.c -testHarness_SRCS += scanIoTest_registerRecordDeviceDriver.cpp -TESTFILES += $(COMMON_DIR)/scanIoTest.dbd ../scanIoTest.db +TESTFILES += ../scanIoTest.db TESTS += scanIoTest TESTPROD_HOST += dbChannelTest @@ -139,6 +149,9 @@ TESTSCRIPTS_HOST += $(TESTS:%=%.t) include $(TOP)/configure/RULES xRecord$(DEP): $(COMMON_DIR)/xRecord.h +arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h -scanIoTest$(DEP): $(COMMON_DIR)/yRecord.h +devx$(DEP): $(COMMON_DIR)/xRecord.h +scanIoTest$(DEP): $(COMMON_DIR)/xRecord.h +dbCaLinkTest$(DEP): $(COMMON_DIR)/xRecord.h $(COMMON_DIR)/arrRecord.h diff --git a/src/ioc/db/test/arrRecord.c b/src/ioc/db/test/arrRecord.c new file mode 100644 index 000000000..7dea7caaf --- /dev/null +++ b/src/ioc/db/test/arrRecord.c @@ -0,0 +1,141 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2008 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. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* arrRecord.c - minimal array record for test purposes: no processing */ + +/* + * Author: Ralph Lange + * + * vaguely implemented like parts of recWaveform.c by Bob Dalesio + * + */ + +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "recSup.h" +#include "recGbl.h" +#include "cantProceed.h" +#define GEN_SIZE_OFFSET +#include "arrRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(arrRecord *, int); +static long process(arrRecord *); +#define special NULL +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +static long put_array_info(DBADDR *, long); +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset arrRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, arrRSET); + +static long init_record(arrRecord *prec, int pass) +{ + if (pass == 0) { + if (prec->nelm <= 0) + prec->nelm = 1; + if (prec->ftvl > DBF_ENUM) + prec->ftvl = DBF_UCHAR; + prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl), + "arr calloc failed"); + + if (prec->nelm == 1) { + prec->nord = 1; + } else { + prec->nord = 0; + } + return 0; + } + return 0; +} + +static long process(arrRecord *prec) +{ + if(prec->clbk) + (*prec->clbk)(prec); + prec->pact = TRUE; + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->nelm; + paddr->field_type = prec->ftvl; + paddr->field_size = dbValueSize(prec->ftvl); + paddr->dbr_field_type = prec->ftvl; + + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + *no_elements = prec->nord; + *offset = prec->off; + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + prec->nord = nNew; + if (prec->nord > prec->nelm) + prec->nord = prec->nelm; + + return 0; +} diff --git a/src/ioc/db/test/arrRecord.dbd b/src/ioc/db/test/arrRecord.dbd new file mode 100644 index 000000000..b504be1cb --- /dev/null +++ b/src/ioc/db/test/arrRecord.dbd @@ -0,0 +1,42 @@ +include "menuGlobal.dbd" +include "menuConvert.dbd" +include "menuScan.dbd" +recordtype(arr) { + include "dbCommon.dbd" + field(VAL, DBF_NOACCESS) { + prompt("Value") + special(SPC_DBADDR) + pp(TRUE) + extra("void *val") + } + field(NELM, DBF_ULONG) { + prompt("Number of Elements") + special(SPC_NOMOD) + initial("1") + } + field(FTVL, DBF_MENU) { + prompt("Field Type of Value") + special(SPC_NOMOD) + menu(menuFtype) + } + field(NORD, DBF_ULONG) { + prompt("Number elements read") + special(SPC_NOMOD) + } + field(OFF, DBF_ULONG) { + prompt("Offset into array") + } + field(BPTR, DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + extra("void *bptr") + } + field(INP, DBF_INLINK) { + prompt("Input Link") + } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct arrRecord*)") + } +} diff --git a/src/ioc/db/test/dbCACTest.cpp b/src/ioc/db/test/dbCACTest.cpp new file mode 100644 index 000000000..4343ce5c0 --- /dev/null +++ b/src/ioc/db/test/dbCACTest.cpp @@ -0,0 +1,84 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ +/* + * Part of dbCaLinkTest, compiled seperately to avoid + * dbAccess.h vs. db_access.h conflicts + */ + +#include + +#include +#include + +#include + +#include "epicsUnitTest.h" + +#include "cadef.h" + +#define testECA(OP) if((OP)!=ECA_NORMAL) {testAbort("%s", #OP);} else {testPass("%s", #OP);} + +void putgetarray(chid chanid, double first, size_t count) +{ + testDiag("putgetarray(%f,%u)", first, (unsigned)count); + + std::vector buf(count); + for(size_t i=0; i buf2(count); + + testECA(ca_array_get(DBR_DOUBLE, count, chanid, &buf2[0])); + + testECA(ca_pend_io(1.0)); + + for(size_t i=0; i + */ + +#include +#include +#include + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "dbAccess.h" +#include "epicsStdio.h" +#include "dbEvent.h" +/* hackish duplication since we can't include db_access.h here */ +typedef void* chid; +#define MAX_UNITS_SIZE 8 +#include "dbCaPvt.h" +#include "errlog.h" + +#include "xRecord.h" +#include "arrRecord.h" + +#include "testMain.h" + +#define testOp(FMT,A,OP,B) testOk((A)OP(B), #A " ("FMT") " #OP " " #B " ("FMT")", A,B) + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static epicsEventId waitEvent; +static unsigned waitCounter; + +static +void waitCB(void *unused) +{ + if(waitEvent) + epicsEventMustTrigger(waitEvent); + waitCounter++; //TODO: atomic +} + +static +void startWait(DBLINK *plink) +{ + caLink *pca = plink->value.pv_link.pvt; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + assert(pca); + epicsMutexMustLock(pca->lock); + assert(!pca->monitor && !pca->userPvt); + pca->monitor = &waitCB; + epicsMutexUnlock(pca->lock); + testDiag("Preparing to wait on pca=%p", pca); +} + +static +void waitForUpdate(DBLINK *plink) +{ + caLink *pca = plink->value.pv_link.pvt; + + assert(pca); + + testDiag("Waiting on pca=%p", pca); + epicsEventMustWait(waitEvent); + + epicsMutexMustLock(pca->lock); + pca->monitor = NULL; + pca->userPvt = NULL; + epicsMutexUnlock(pca->lock); + + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static +void putLink(DBLINK *plink, short dbr, const void*buf, long nReq) +{ + long ret; + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + ret = dbCaPutLinkCallback(plink, dbr, buf, nReq, + &waitCB, NULL); + if(ret) { + testFail("putLink fails %ld\n", ret); + } else { + epicsEventMustWait(waitEvent); + testPass("putLink ok\n"); + } + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static void testNativeLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 temp; + long nReq; + testDiag("Link to a scalar numeric field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + // ensure this is really a CA link + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + // local CA_LINK connects immediately + testOk1(dbCaIsLinkConnected(psrclnk)); + nReq = 422; + testOk1(dbCaGetNelements(psrclnk, &nReq)==0); + testOp("%ld",nReq,==,1l); + + nReq = 1; + temp = 0x0f0f0f0f; + testOk1(dbGetLink(psrclnk, DBR_LONG, (void*)&temp, NULL, &nReq)==0); + testOp("%d",temp,==,42); + dbScanUnlock((dbCommon*)psrc); + + temp = 1010; + nReq = 1; + putLink(psrclnk, DBR_LONG, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk1(ptarg->val==1010); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testStringLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + char temp[MAX_STRING_SIZE]; + long nReq; + testDiag("Link to a string field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target.DESC CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + // ensure this is really a CA link + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + strcpy(ptarg->desc, "hello"); + db_post_events(ptarg, &ptarg->desc, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + // local CA_LINK connects immediately + testOk1(dbCaIsLinkConnected(psrclnk)); + + nReq = 1; + memset(temp, '!', sizeof(temp)); + testOk1(dbGetLink(psrclnk, DBR_STRING, (void*)&temp, NULL, &nReq)==0); + testOk(strcmp(temp, "hello")==0, "%s == hello", temp); + dbScanUnlock((dbCommon*)psrc); + + strcpy(temp, "world"); + putLink(psrclnk, DBR_STRING, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk(strcmp(ptarg->desc, "world")==0, "%s == world", ptarg->desc); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void wasproc(xRecord *prec) +{ + waitCB(NULL); +} + +static void testCP(void) +{ + xRecord *psrc, *ptarg; + testDiag("Link CP modifier"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CP"); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &wasproc; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,1); // initial processing + dbScanUnlock((dbCommon*)psrc); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,2); // process due to monitor update + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static void fillArray(epicsInt32 *buf, unsigned count, epicsInt32 first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void fillArrayDouble(double *buf, unsigned count, double first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void checkArray(const char *msg, + epicsInt32 *buf, epicsInt32 first, + unsigned used, unsigned total) +{ + int match = 1; + unsigned i; + epicsInt32 x, *b; + for(b=buf,x=first,i=0;ivalue.pv_link.pvt; + if(lnk->type!=CA_LINK || !pca->pputNative) + return; + epicsMutexMustLock(pca->lock); + memset(pca->pputNative, '!', + pca->nelements*dbr_value_size[ca_field_type(pca->chid)]); + epicsMutexUnlock(pca->lock); +} + +static void testArrayLink(unsigned nsrc, unsigned ntarg) +{ + char buf[100]; + arrRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 *bufsrc, *buftarg; + long nReq; + unsigned num; + + testDiag("Link to a array numeric field"); + + epicsSnprintf(buf, sizeof(buf), "TARGET=target CA,FTVL=LONG,SNELM=%u,TNELM=%u", + nsrc, ntarg); + testDiag("%s", buf); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest2.db", NULL, buf); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg= (arrRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->inp; + + eltc(0); + testIocInitOk(); + eltc(1); + + bufsrc = psrc->bptr; + buftarg= ptarg->bptr; + + num=psrc->nelm; + if(num>ptarg->nelm) + num=ptarg->nelm; + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + fillArray(buftarg, ptarg->nelm, 1); + ptarg->nord = ptarg->nelm; + db_post_events(ptarg, ptarg->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + nReq = psrc->nelm; + if(dbGetLink(psrclnk, DBR_LONG, bufsrc, NULL, &nReq)==0) { + testPass("dbGetLink"); + testOp("%ld",nReq,==,(long)num); + checkArray("array update", bufsrc, 1, nReq, psrc->nelm); + } else { + testFail("dbGetLink"); + testSkip(2, "dbGetLink fails"); + } + dbScanUnlock((dbCommon*)psrc); + + fillArray(bufsrc, psrc->nelm, 2); + /* write buffer allocated on first put */ + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + /* CA links always write the full target array length */ + testOp("%ld",(long)ptarg->nord,==,(long)ptarg->nelm); + /* However, if the source length is less, then the target + * is zero filled + */ + checkArray("array update", buftarg, 2, num, ptarg->nelm); + dbScanUnlock((dbCommon*)ptarg); + + /* write again to ensure that buffer is completely updated */ + spoilputbuf(psrclnk); + fillArray(bufsrc, psrc->nelm, 3); + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + testOp("%ld",(long)ptarg->nord,==,(long)ptarg->nelm); + checkArray("array update", buftarg, 3, num, ptarg->nelm); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg); +} + + +static void softarr(arrRecord *prec) +{ + long nReq = prec->nelm; + long status = dbGetLink(&prec->inp, DBR_DOUBLE, prec->bptr, NULL, &nReq); + if(status) { + testFail("dbGetLink() -> %ld", status); + } else { + testPass("dbGetLink() succeeds"); + prec->nord = nReq; + if(nReq>0) + testDiag("%s.VAL[0] - %f", prec->name, *(double*)prec->bptr); + } + waitCB(NULL); +} + +static void testreTargetTypeChange(void) +{ + arrRecord *psrc, *ptarg1, *ptarg2; + double *bufsrc, *buftarg1; + epicsInt32 *buftarg2; + testDiag("Retarget an link to a PV with a different type DOUBLE->LONG"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest3.db", NULL, "NELM=5,TARGET=target1 CP"); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg1= (arrRecord*)testdbRecordPtr("target1"); + ptarg2= (arrRecord*)testdbRecordPtr("target2"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &softarr; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsEventMustWait(waitEvent); // wait for initial processing + + bufsrc = psrc->bptr; + buftarg1= ptarg1->bptr; + buftarg2= ptarg2->bptr; + + testDiag("Update one with original target"); + + dbScanLock((dbCommon*)ptarg2); + fillArray(buftarg2, ptarg2->nelm, 2); + ptarg2->nord = ptarg2->nelm; + dbScanUnlock((dbCommon*)ptarg2); + + /* initialize buffers */ + dbScanLock((dbCommon*)ptarg1); + fillArrayDouble(buftarg1, ptarg1->nelm, 1); + ptarg1->nord = ptarg1->nelm; + db_post_events(ptarg1, ptarg1->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg1); + + epicsEventMustWait(waitEvent); // wait for update + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 1, 5, psrc->nelm); + dbScanUnlock((dbCommon*)psrc); + + testDiag("Retarget"); + testdbPutFieldOk("source.INP", DBR_STRING, "target2 CP"); + + epicsEventMustWait(waitEvent); // wait for update + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 2, 5, psrc->nelm); + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg1); + free(buftarg2); +} + +void dbCaLinkTest_testCAC(void); + +static void testCAC(void) +{ + arrRecord *psrc, *ptarg1, *ptarg2; + double *bufsrc, *buftarg1; + epicsInt32 *buftarg2; + + testDiag("Check local CA through libca"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest3.db", NULL, "NELM=5,TARGET=target1 CP"); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg1= (arrRecord*)testdbRecordPtr("target1"); + ptarg2= (arrRecord*)testdbRecordPtr("target2"); + + eltc(0); + testIocInitOk(); + eltc(1); + + bufsrc = psrc->bptr; + buftarg1= ptarg1->bptr; + buftarg2= ptarg2->bptr; + + dbCaLinkTest_testCAC(); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg1); + free(buftarg2); +} + +MAIN(dbCaLinkTest) +{ + testPlan(91); + testNativeLink(); + testStringLink(); + testCP(); + testArrayLink(1,1); + testArrayLink(10,1); + testArrayLink(1,10); + testArrayLink(10,10); + testreTargetTypeChange(); + testCAC(); + return testDone(); +} diff --git a/src/ioc/db/test/dbCaLinkTest1.db b/src/ioc/db/test/dbCaLinkTest1.db new file mode 100644 index 000000000..aab5ceb86 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest1.db @@ -0,0 +1,5 @@ +record(x, "target") {} + +record(x, "source") { + field(LNK, "$(TARGET)") +} diff --git a/src/ioc/db/test/dbCaLinkTest2.db b/src/ioc/db/test/dbCaLinkTest2.db new file mode 100644 index 000000000..9cfa4ba00 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest2.db @@ -0,0 +1,10 @@ +record(arr, "target") { + field(FTVL, "$(TFTVL=$(FTVL=))") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "$(SFTVL=$(FTVL=))") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/src/ioc/db/test/dbCaLinkTest3.db b/src/ioc/db/test/dbCaLinkTest3.db new file mode 100644 index 000000000..f820bd554 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest3.db @@ -0,0 +1,14 @@ +record(arr, "target1") { + field(FTVL, "DOUBLE") + field(NELM, "$(TNELM=$(NELM=))") +} +record(arr, "target2") { + field(FTVL, "LONG") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "DOUBLE") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/src/ioc/db/test/dbLinkdset.c b/src/ioc/db/test/dbLinkdset.c index 5e7ea285f..6e775df7b 100644 --- a/src/ioc/db/test/dbLinkdset.c +++ b/src/ioc/db/test/dbLinkdset.c @@ -29,7 +29,6 @@ long link_test_noop(void *junk) static dset devxLTest ## LTYPE = {4, NULL, &link_test_init, &link_test_noop, &link_test_noop}; \ epicsExportAddress(dset, devxLTest ## LTYPE); -DEFDSET(Soft) DEFDSET(VME_IO) DEFDSET(CAMAC_IO) DEFDSET(AB_IO) diff --git a/src/ioc/db/test/dbLinkdset.dbd b/src/ioc/db/test/dbLinkdset.dbd index b1e070c66..84d5eefe9 100644 --- a/src/ioc/db/test/dbLinkdset.dbd +++ b/src/ioc/db/test/dbLinkdset.dbd @@ -1,5 +1,3 @@ -device(x, CONSTANT,devxLTestSoft,"Soft Channel") - device(x, VME_IO, devxLTestVME_IO, "Unit Test VME_IO") device(x, CAMAC_IO, devxLTestCAMAC_IO, "Unit Test CAMAC_IO") device(x, AB_IO, devxLTestAB_IO, "Unit Test AB_IO") diff --git a/src/ioc/db/test/devx.c b/src/ioc/db/test/devx.c new file mode 100644 index 000000000..104c64001 --- /dev/null +++ b/src/ioc/db/test/devx.c @@ -0,0 +1,163 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven +* National Lab +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "devx.h" + +/* xRecord DTYP="Scan I/O" + * + * dset to test I/O Intr scanning. + * INP="@drvname" names a "driver" which + * provides a IOSCANPVT. + * The driver also defines a callback function which + * is invoked when the record is processed. + */ + +struct ELLLIST xdrivers; + +/* Add a new "driver" with the given group id + * and processing callback + */ +xdrv* xdrv_add(int group, xdrvcb cb, void *arg) +{ + xdrv *drv=callocMustSucceed(1, sizeof(*drv), "xdrv_add"); + drv->cb = cb; + drv->arg = arg; + drv->group = group; + scanIoInit(&drv->scan); + ellAdd(&xdrivers, &drv->drvnode); + return drv; +} + +/* Trigger the named "driver" group to scan */ +xdrv *xdrv_get(int group) +{ + ELLNODE *cur; + for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) { + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + if(curd->group==group) { + return curd; + } + } + cantProceed("xdrv_get() for non-existant group"); + return NULL; +} + +/* Free all "driver" groups and record private structures. + * Call after testdbCleanup() + */ +void xdrv_reset() +{ + ELLNODE *cur; + while((cur=ellGet(&xdrivers))!=NULL) { + ELLNODE *cur2; + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + while((cur2=ellGet(&curd->privlist))!=NULL) { + xpriv *priv = CONTAINER(cur2, xpriv, privnode); + free(priv); + } + free(curd); + } +} + +static long xscanio_init_record(xRecord *prec) +{ + ELLNODE *cur; + xpriv *priv; + xdrv *drv = NULL; + int group, member; + assert(prec->inp.type==INST_IO); + + if(sscanf(prec->inp.value.instio.string, "%d %d", + &group, &member)!=2) + cantProceed("xscanio_init_record invalid INP string"); + + for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) { + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + if(curd->group==group) { + drv = curd; + break; + } + } + + assert(drv!=NULL); + priv = mallocMustSucceed(sizeof(*priv), "xscanio_init_record"); + priv->prec = prec; + priv->drv = drv; + priv->member = member; + ellAdd(&drv->privlist, &priv->privnode); + prec->dpvt = priv; + + return 0; +} + +static long xscanio_get_ioint_info(int cmd, xRecord *prec, IOSCANPVT *ppvt) +{ + xpriv *priv = prec->dpvt; + if(!priv || !priv->drv) + return 0; + *ppvt = priv->drv->scan; + return 0; +} + +static long xscanio_read(xRecord *prec) +{ + xpriv *priv = prec->dpvt; + if(!priv || !priv->drv) + return 0; + if(priv->drv->cb) + (*priv->drv->cb)(priv, priv->drv->arg); + return 0; +} + +static xdset devxScanIO = { + 5, NULL, NULL, + &xscanio_init_record, + &xscanio_get_ioint_info, + &xscanio_read +}; +epicsExportAddress(dset, devxScanIO); + +/* basic DTYP="Soft Channel" */ +static long xsoft_init_record(xRecord *prec) +{ + if(prec->inp.type==CONSTANT) + recGblInitConstantLink(&prec->inp, DBF_LONG, &prec->val); + return 0; +} + +static long xsoft_read(xRecord *prec) +{ + if(prec->inp.type==CONSTANT) + return 0; + dbGetLink(&prec->inp, DBR_DOUBLE, &prec->val, NULL, NULL); + return 0; +} + +static struct xdset devxSoft = { + 5, NULL, NULL, + &xsoft_init_record, + NULL, + &xsoft_read +}; +epicsExportAddress(dset, devxSoft); diff --git a/src/ioc/db/test/devx.dbd b/src/ioc/db/test/devx.dbd new file mode 100644 index 000000000..ee6421bc0 --- /dev/null +++ b/src/ioc/db/test/devx.dbd @@ -0,0 +1,2 @@ +device(x, CONSTANT, devxSoft, "Soft Channel") +device(x, INST_IO , devxScanIO, "Scan I/O") diff --git a/src/ioc/db/test/devx.h b/src/ioc/db/test/devx.h new file mode 100644 index 000000000..f8f417a14 --- /dev/null +++ b/src/ioc/db/test/devx.h @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven +* National Lab +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef DEVXSCANIO_H +#define DEVXSCANIO_H + +#include +#include + +#include + +struct xRecord; +struct xpriv; + +epicsShareExtern struct ELLLIST xdrivers; + +typedef void (*xdrvcb)(struct xpriv *, void *); + +typedef struct { + ELLNODE drvnode; + IOSCANPVT scan; + xdrvcb cb; + void *arg; + ELLLIST privlist; + int group; +} xdrv; + +typedef struct xpriv { + ELLNODE privnode; + xdrv *drv; + struct xRecord *prec; + int member; +} xpriv; + +epicsShareFunc xdrv *xdrv_add(int group, xdrvcb cb, void *arg); +epicsShareFunc xdrv *xdrv_get(int group); +epicsShareFunc void xdrv_reset(); + +typedef struct xdset { + long number; + long (*report)(int); + long (*init)(int); + long (*init_record)(struct xRecord *); + long (*get_ioint_info)(int, struct xRecord*, IOSCANPVT*); + long (*process)(struct xRecord *); +} xdset; + +#endif /* DEVXSCANIO_H */ diff --git a/src/ioc/db/test/epicsRunDbTests.c b/src/ioc/db/test/epicsRunDbTests.c index 0ee631686..10f10e267 100644 --- a/src/ioc/db/test/epicsRunDbTests.c +++ b/src/ioc/db/test/epicsRunDbTests.c @@ -26,6 +26,7 @@ int dbScanTest(void); int scanIoTest(void); int dbLockTest(void); int dbPutLinkTest(void); +int dbCaLinkTest(void); int testDbChannel(void); int chfPluginTest(void); int arrShorthandTest(void); @@ -45,6 +46,7 @@ void epicsRunDbTests(void) runTest(scanIoTest); runTest(dbLockTest); runTest(dbPutLinkTest); + runTest(dbCaLinkTest); runTest(testDbChannel); runTest(arrShorthandTest); runTest(recGblCheckDeadbandTest); diff --git a/src/ioc/db/test/scanIoTest.c b/src/ioc/db/test/scanIoTest.c index 9457414b7..ee6d8b462 100644 --- a/src/ioc/db/test/scanIoTest.c +++ b/src/ioc/db/test/scanIoTest.c @@ -12,6 +12,8 @@ */ #include +#include +#include #include "epicsEvent.h" #include "epicsMessageQueue.h" @@ -26,9 +28,6 @@ #include "dbLock.h" #include "dbUnitTest.h" #include "dbCommon.h" -#include "registry.h" -#include "registryRecordType.h" -#include "registryDeviceSupport.h" #include "recSup.h" #include "devSup.h" #include "iocInit.h" @@ -38,482 +37,272 @@ #include "testMain.h" #include "osiFileName.h" -#define GEN_SIZE_OFFSET -#include "yRecord.h" - #include "epicsExport.h" -#define ONE_THREAD_LOOPS 101 -#define PAR_THREAD_LOOPS 53 -#define CB_THREAD_LOOPS 13 +#include "devx.h" +#include "xRecord.h" -#define NO_OF_THREADS 7 -#define NO_OF_MEMBERS 5 -#define NO_OF_GROUPS 11 +STATIC_ASSERT(NUM_CALLBACK_PRIORITIES==3); -#define NO_OF_MID_THREADS 3 +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); -static int noOfGroups = NO_OF_GROUPS; -static int noOfIoscans = NO_OF_GROUPS; - -static IOSCANPVT *ioscanpvt; /* Soft interrupt sources */ -static ELLLIST *pvtList; /* Per group private part lists */ - -static int executionOrder; -static int orderFail; -static int testNo; -static epicsMessageQueueId *mq; /* Per group message queue */ -static epicsEventId *barrier; /* Per group barrier event */ -static int *cbCounter; - -struct pvtY { - ELLNODE node; - yRecord *prec; - int group; - int member; - int count; - int processed; - int callback; -}; - -/* test2: priority and ioscan index for each group - * used priorities are expressed in the bit pattern of (ioscan index + 1) */ -struct groupItem { - int prio; - int ioscan; -} groupTable[12] = { - { 0, 0 }, - { 1, 1 }, - { 0, 2 }, { 1, 2 }, - { 2, 3 }, - { 0, 4 }, { 2, 4 }, - { 1, 5 }, { 2, 5 }, - { 0, 6 }, { 1, 6 }, { 2, 6 } -}; -static int recsProcessed = 1; -static int noDoubleCallback = 1; - -void scanIoTest_registerRecordDeviceDriver(struct dbBase *); - -long count_bits(long n) { - unsigned int c; /* c accumulates the total bits set in v */ - for (c = 0; n; c++) - n &= n - 1; /* clear the least significant bit set */ - return c; -} - -/*************************************************************************\ -* yRecord: minimal record needed to test I/O Intr scanning -\*************************************************************************/ - -static long get_ioint_info(int cmd, yRecord *prec, IOSCANPVT *ppvt) +static void loadRecord(int group, int member, const char *prio) { - struct pvtY *pvt = (struct pvtY *)(prec->dpvt); - - if (testNo == 2) - *ppvt = ioscanpvt[groupTable[pvt->group].ioscan]; - else - *ppvt = ioscanpvt[pvt->group]; - return 0; + char buf[40]; + sprintf(buf, "GROUP=%d,MEMBER=%d,PRIO=%s", + group, member, prio); + testdbReadDatabase("scanIoTest.db", NULL, buf); } -struct ydset { - long number; - DEVSUPFUN report; - DEVSUPFUN init; - DEVSUPFUN init_record; - DEVSUPFUN get_ioint_info; - DEVSUPFUN process; -} devY = { - 5, - NULL, - NULL, - NULL, - get_ioint_info, - NULL -}; -epicsExportAddress(dset, devY); +typedef struct { + int hasprocd[NUM_CALLBACK_PRIORITIES]; + int getcomplete[NUM_CALLBACK_PRIORITIES]; + epicsEventId wait[NUM_CALLBACK_PRIORITIES]; + epicsEventId wake[NUM_CALLBACK_PRIORITIES]; +} testsingle; -static long init_record(yRecord *prec, int pass) +static void testcb(xpriv *priv, void *raw) { - struct pvtY *pvt; + testsingle *td = raw; + int prio = priv->prec->prio; - if (pass == 0) return 0; - - pvt = (struct pvtY *) calloc(1, sizeof(struct pvtY)); - prec->dpvt = pvt; - - pvt->prec = prec; - sscanf(prec->name, "g%dm%d", &pvt->group, &pvt->member); - ellAdd(&pvtList[pvt->group], &pvt->node); - - return 0; + testOk1(td->hasprocd[prio]==0); + td->hasprocd[prio] = 1; } -static long process(yRecord *prec) +static void testcomp(void *raw, IOSCANPVT scan, int prio) { - struct pvtY *pvt = (struct pvtY *)(prec->dpvt); + testsingle *td = raw; - if (testNo == 0) { - /* Single callback thread */ - if (executionOrder != pvt->member) { - orderFail = 1; - } - pvt->count++; - if (++executionOrder == NO_OF_MEMBERS) executionOrder = 0; - } else { - pvt->count++; - if (pvt->member == 0) { - epicsMessageQueueSend(mq[pvt->group], NULL, 0); - epicsEventMustWait(barrier[pvt->group]); - } - } - pvt->processed = 1; - return 0; + testOk1(td->hasprocd[prio]==1); + testOk1(td->getcomplete[prio]==0); + td->getcomplete[prio] = 1; + epicsEventMustTrigger(td->wait[prio]); + epicsEventMustWait(td->wake[prio]); } -rset yRSET={ - 4, - NULL, /* report */ - NULL, /* initialize */ - init_record, - process -}; -epicsExportAddress(rset, yRSET); - -static void startMockIoc(void) { - char substitutions[256]; - int i, j; - char *prio[] = { "LOW", "MEDIUM", "HIGH" }; - - if (testNo == 2) { - noOfGroups = 12; - noOfIoscans = 7; - } - ioscanpvt = calloc(noOfIoscans, sizeof(IOSCANPVT)); - mq = calloc(noOfGroups, sizeof(epicsMessageQueueId)); - barrier = calloc(noOfGroups, sizeof(epicsEventId)); - pvtList = calloc(noOfGroups, sizeof(ELLLIST)); - cbCounter = calloc(noOfGroups, sizeof(int)); - - if (dbReadDatabase(&pdbbase, "scanIoTest.dbd", - "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR - "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) - testAbort("Error reading database description 'scanIoTest.dbd'"); - - callbackParallelThreads(1, "Low"); - callbackParallelThreads(NO_OF_MID_THREADS, "Medium"); - callbackParallelThreads(NO_OF_THREADS, "High"); - - for (i = 0; i < noOfIoscans; i++) { - scanIoInit(&ioscanpvt[i]); - } - - for (i = 0; i < noOfGroups; i++) { - mq[i] = epicsMessageQueueCreate(NO_OF_MEMBERS, 1); - barrier[i] = epicsEventMustCreate(epicsEventEmpty); - ellInit(&pvtList[i]); - } - - scanIoTest_registerRecordDeviceDriver(pdbbase); - for (i = 0; i < noOfGroups; i++) { - for (j = 0; j < NO_OF_MEMBERS; j++) { - sprintf(substitutions, "GROUP=%d,MEMBER=%d,PRIO=%s", i, j, - testNo==0?"LOW":(testNo==1?"HIGH":prio[groupTable[i].prio])); - if (dbReadDatabase(&pdbbase, "scanIoTest.db", - "." OSI_PATH_LIST_SEPARATOR "..", substitutions)) - testAbort("Error reading test database 'scanIoTest.db'"); - } - } - - testIocInitOk(); -} - -static void stopMockIoc(void) { +static void testSingleThreading(void) +{ int i; + testsingle data[2]; + xdrv *drvs[2]; + + memset(data, 0, sizeof(data)); + + for(i=0; i<2; i++) { + int p; + for(p=0; pscan, &testcomp, &data[0]); + scanIoSetComplete(drvs[1]->scan, &testcomp, &data[1]); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Scan first list"); + scanIoRequest(drvs[0]->scan); + testDiag("Scan second list"); + scanIoRequest(drvs[1]->scan); + + testDiag("Wait for first list to complete"); + for(i=0; imember; + + testOk1(td->hasprocd==0); + td->hasprocd = 1; + epicsEventMustTrigger(td->wait); + epicsEventMustWait(td->wake); + td->getcomplete = 1; +} + +static void testcompmulti(void *raw, IOSCANPVT scan, int prio) +{ + int *mask = raw; + testOk(((*mask)&(1<node)) { - if (pvt->callback == 1) { - testDiag("callback for rec %s arrived twice\n", pvt->prec->name); - noDoubleCallback = 0; - } - if (pvt->processed == 0) { - testDiag("rec %s was not processed\n", pvt->prec->name); - recsProcessed = 0; - } - pvt->callback = 1; + testDiag("Test multi-threaded I/O Intr scanning"); + + testdbPrepare(); + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + /* create two scan lists with one record on each of three priorities */ + + /* group#, member#, priority */ + loadRecord(0, 0, "LOW"); + loadRecord(1, 1, "LOW"); + loadRecord(0, 2, "MEDIUM"); + loadRecord(1, 3, "MEDIUM"); + loadRecord(0, 4, "HIGH"); + loadRecord(1, 5, "HIGH"); + + drvs[0] = xdrv_add(0, &testcbmulti, data); + drvs[1] = xdrv_add(1, &testcbmulti, data); + scanIoSetComplete(drvs[0]->scan, &testcompmulti, &masks[0]); + scanIoSetComplete(drvs[1]->scan, &testcompmulti, &masks[1]); + + /* just enough workers to process all records concurrently */ + callbackParallelThreads(2, "LOW"); + callbackParallelThreads(2, "MEDIUM"); + callbackParallelThreads(2, "HIGH"); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Scan first list"); + testOk1(scanIoRequest(drvs[0]->scan)==0x7); + testDiag("Scan second list"); + testOk1(scanIoRequest(drvs[1]->scan)==0x7); + + testDiag("Wait for everything to start"); + for(i=0; i= number of parallel threads"); - - /**************************************\ - * Single callback thread - \**************************************/ - - testNo = 0; - startMockIoc(); - - testDiag("Testing single callback thread"); - testDiag(" using %d ioscan sources, %d records for each, and %d loops", - noOfGroups, NO_OF_MEMBERS, ONE_THREAD_LOOPS); - - for (j = 0; j < ONE_THREAD_LOOPS; j++) { - for (i = 0; i < noOfIoscans; i++) { - scanIoRequest(ioscanpvt[i]); - } - } - - epicsThreadSleep(1.0); - - testOk((orderFail==0), "No out-of-order processing"); - - result = 1; - for (i = 0; i < noOfGroups; i++) { - for (pvt = (struct pvtY *)ellFirst(&pvtList[i]); - pvt; - pvt = (struct pvtY *)ellNext(&pvt->node)) { - if (pvt->count != ONE_THREAD_LOOPS) result = 0; - } - } - - testOk(result, "All per-record process counters match number of loops"); - - stopMockIoc(); - - /**************************************\ - * Multiple parallel callback threads - \**************************************/ - - testNo = 1; - startMockIoc(); - - testDiag("Testing multiple parallel callback threads"); - testDiag(" using %d ioscan sources, %d records for each, %d loops, and %d parallel threads", - noOfIoscans, NO_OF_MEMBERS, PAR_THREAD_LOOPS, NO_OF_THREADS); - - for (j = 0; j < PAR_THREAD_LOOPS; j++) { - for (i = 0; i < noOfIoscans; i++) { - scanIoRequest(ioscanpvt[i]); - } - } - - /* With parallel cb threads, order and distribution to threads are not guaranteed. - * We have stop barrier events for each request (in the first record). - * Test schedule: - * - After the requests have been put in the queue, NO_OF_THREADS threads should have taken - * one request each. - * - Each barrier event is given PAR_THREAD_LOOPS times. - * - Whenever things stop, there should be four threads waiting, one request each. - * - After all loops, each record should have processed PAR_THREAD_LOOPS times. - */ - - max_one_all = 1; - parallel_all = 1; - - for (loop = 0; loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS + 1; loop++) { - max_one = 1; - parallel = 0; - waiting = 0; - j = 0; - do { - epicsThreadSleep(0.001); - j++; - for (i = 0; i < noOfGroups; i++) { - int l = epicsMessageQueuePending(mq[i]); - while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1); - if (l == 1) { - waiting |= 1 << i; - } else if (l > 1) { - max_one = 0; - } - } - parallel = count_bits(waiting); - } while (j < 5 && parallel < NO_OF_THREADS); - - if (!max_one) max_one_all = 0; - if (loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS) { - if (!(parallel == NO_OF_THREADS)) parallel_all = 0; - } else { - /* In the last run of the loop only the remaining requests are processed */ - if (!(parallel == PAR_THREAD_LOOPS * noOfGroups % NO_OF_THREADS)) parallel_all = 0; - } - - for (i = 0; i < noOfGroups; i++) { - if (waiting & (1 << i)) { - epicsEventTrigger(barrier[i]); - } - } - } - - testOk(max_one_all, "No thread took more than one request per loop"); - testOk(parallel_all, "Correct number of requests were being processed in parallel in each loop"); - - epicsThreadSleep(0.1); - - result = 1; - for (i = 0; i < noOfGroups; i++) { - for (pvt = (struct pvtY *)ellFirst(&pvtList[i]); - pvt; - pvt = (struct pvtY *)ellNext(&pvt->node)) { - if (pvt->count != PAR_THREAD_LOOPS) { - testDiag("Process counter for record %s (%d) does not match loop count (%d)", - pvt->prec->name, pvt->count, PAR_THREAD_LOOPS); - result = 0; - } - } - } - - testOk(result, "All per-record process counters match number of loops"); - - stopMockIoc(); - - /**************************************\ - * Scanio callback mechanism - \**************************************/ - - testNo = 2; - startMockIoc(); - - for (i = 0; i < noOfIoscans; i++) { - scanIoSetComplete(ioscanpvt[i], checkProcessed, NULL); - } - - testDiag("Testing scanio callback mechanism"); - testDiag(" using %d ioscan sources, %d records for each, %d loops, and 1 LOW / %d MEDIUM / %d HIGH parallel threads", - noOfIoscans, NO_OF_MEMBERS, CB_THREAD_LOOPS, NO_OF_MID_THREADS, NO_OF_THREADS); - - result = 1; - for (j = 0; j < CB_THREAD_LOOPS; j++) { - for (i = 0; i < noOfIoscans; i++) { - int prio_used; - prio_used = scanIoRequest(ioscanpvt[i]); - if (i+1 != prio_used) - result = 0; - } - } - testOk(result, "All requests return the correct priority callback mask (all 7 permutations covered)"); - - /* Test schedule: - * After the requests have been put in the queue, it is checked - * - that each callback arrives exactly once, - * - after all records in the group have been processed. - */ - - /* loop count times 4 since (worst case) one loop triggers 4 groups for the single LOW thread */ - for (loop = 0; loop < CB_THREAD_LOOPS * 4; loop++) { - max_one = 1; - parallel = 0; - waiting = 0; - j = 0; - do { - epicsThreadSleep(0.001); - j++; - for (i = 0; i < noOfGroups; i++) { - int l = epicsMessageQueuePending(mq[i]); - while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1); - if (l == 1) { - waiting |= 1 << i; - } else if (l > 1) { - max_one = 0; - } - } - parallel = count_bits(waiting); - } while (j < 5); -\ - for (i = 0; i < noOfGroups; i++) { - if (waiting & (1 << i)) { - for (pvt = (struct pvtY *)ellFirst(&pvtList[i]); - pvt; - pvt = (struct pvtY *)ellNext(&pvt->node)) { - pvt->processed = 0; - pvt->callback = 0; - /* record processing will set this at the end of process() */ - } - epicsEventTrigger(barrier[i]); - } - } - } - - epicsThreadSleep(0.1); - - testOk(recsProcessed, "Each callback occured after all records in the group were processed"); - testOk(noDoubleCallback, "No double callbacks occured in any loop"); - - result = 1; - cbCountOk = 1; - for (i = 0; i < noOfGroups; i++) { - if (cbCounter[i] != CB_THREAD_LOOPS) { - testDiag("Callback counter for group %d (%d) does not match loop count (%d)", - i, cbCounter[i], CB_THREAD_LOOPS); - cbCountOk = 0; - } - for (pvt = (struct pvtY *)ellFirst(&pvtList[i]); - pvt; - pvt = (struct pvtY *)ellNext(&pvt->node)) { - if (pvt->count != CB_THREAD_LOOPS) { - testDiag("Process counter for record %s (%d) does not match loop count (%d)", - pvt->prec->name, pvt->count, CB_THREAD_LOOPS); - result = 0; - } - } - } - - testOk(result, "All per-record process counters match number of loops"); - testOk(cbCountOk, "All per-group callback counters match number of loops"); - - stopMockIoc(); - + testPlan(152); + testSingleThreading(); + testDiag("run a second time to verify shutdown and restart works"); + testSingleThreading(); + testMultiThreading(); + testDiag("run a second time to verify shutdown and restart works"); + testMultiThreading(); return testDone(); } diff --git a/src/ioc/db/test/scanIoTest.db b/src/ioc/db/test/scanIoTest.db index 810a84edf..9452ffd0a 100644 --- a/src/ioc/db/test/scanIoTest.db +++ b/src/ioc/db/test/scanIoTest.db @@ -1,4 +1,6 @@ -record(y, g$(GROUP)m$(MEMBER)) { +record(x, "g$(GROUP)m$(MEMBER)") { + field(DTYP, "Scan I/O") + field(INP , "@$(GROUP) $(MEMBER)") field(SCAN, "I/O Intr") field(PRIO, "$(PRIO)") } diff --git a/src/ioc/db/test/xRecord.c b/src/ioc/db/test/xRecord.c index dcc1f776c..356af0869 100644 --- a/src/ioc/db/test/xRecord.c +++ b/src/ioc/db/test/xRecord.c @@ -16,22 +16,47 @@ #include "dbAccessDefs.h" #include "recSup.h" #include "recGbl.h" +#include "devSup.h" +#include "dbScan.h" #define GEN_SIZE_OFFSET #include "xRecord.h" #include +#include "devx.h" + +static long init_record(xRecord *prec, int pass) +{ + long ret = 0; + xdset *xset = (xdset*)prec->dset; + if(!pass) return 0; + + if(!xset) { + recGblRecordError(S_dev_noDSET, prec, "x: init_record"); + return S_dev_noDSET; + } + if(xset->init_record) + ret = (*xset->init_record)(prec); + return ret; +} + static long process(xRecord *prec) { + long ret = 0; + xdset *xset = (xdset*)prec->dset; + if(prec->clbk) + (*prec->clbk)(prec); prec->pact = TRUE; + if(xset && xset->process) + ret = (*xset->process)(prec); recGblGetTimeStamp(prec); recGblFwdLink(prec); prec->pact = FALSE; - return 0; + return ret; } static rset xRSET = { - RSETNUMBER, NULL, NULL, NULL, process + RSETNUMBER, NULL, NULL, init_record, process }; epicsExportAddress(rset,xRSET); diff --git a/src/ioc/db/test/xRecord.dbd b/src/ioc/db/test/xRecord.dbd index fb230f563..915746a25 100644 --- a/src/ioc/db/test/xRecord.dbd +++ b/src/ioc/db/test/xRecord.dbd @@ -11,4 +11,9 @@ recordtype(x) { field(INP, DBF_INLINK) { prompt("Input Link") } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct xRecord*)") + } } diff --git a/src/ioc/db/test/yRecord.dbd b/src/ioc/db/test/yRecord.dbd deleted file mode 100644 index 0aa970da5..000000000 --- a/src/ioc/db/test/yRecord.dbd +++ /dev/null @@ -1,10 +0,0 @@ -# This is a minimal I/O scanned record - -recordtype(y) { - include "dbCommon.dbd" - field(VAL, DBF_LONG) { - prompt("Value") - } -} - -device(y,CONSTANT,devY,"ScanIO Test") diff --git a/src/ioc/misc/iocInit.c b/src/ioc/misc/iocInit.c index 05d96a99e..ceec436a3 100644 --- a/src/ioc/misc/iocInit.c +++ b/src/ioc/misc/iocInit.c @@ -679,6 +679,9 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, dbFreeLinkContents(plink); } + + // may be allocated in dbNotify.c + free(precord->ppnr); } int iocShutdown(void) @@ -698,6 +701,9 @@ int iocShutdown(void) iterateRecords(doFreeRecord, NULL); dbLockCleanupRecords(pdbbase); asShutdown(); + } + dbCaShutdown(); /* must be before dbChannelExit */ + if (iocBuildMode==buildIsolated) { dbChannelExit(); dbProcessNotifyExit(); iocshFree(); diff --git a/src/std/filters/test/arrRecord.c b/src/std/filters/test/arrRecord.c index 5e9b2f02c..7dea7caaf 100644 --- a/src/std/filters/test/arrRecord.c +++ b/src/std/filters/test/arrRecord.c @@ -97,6 +97,12 @@ static long init_record(arrRecord *prec, int pass) static long process(arrRecord *prec) { + if(prec->clbk) + (*prec->clbk)(prec); + prec->pact = TRUE; + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; return 0; } diff --git a/src/std/filters/test/arrRecord.dbd b/src/std/filters/test/arrRecord.dbd index 2b115b500..b504be1cb 100644 --- a/src/std/filters/test/arrRecord.dbd +++ b/src/std/filters/test/arrRecord.dbd @@ -31,4 +31,12 @@ recordtype(arr) { special(SPC_NOMOD) extra("void *bptr") } + field(INP, DBF_INLINK) { + prompt("Input Link") + } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct arrRecord*)") + } }