Merged calinktest branch
This commit is contained in:
@@ -205,6 +205,7 @@ private:
|
||||
cacContextNotify & notify;
|
||||
epics_auto_ptr < cacContext > pNetContext;
|
||||
char * pStateNotifyCache;
|
||||
bool isolated;
|
||||
|
||||
cacChannel & createChannel (
|
||||
epicsGuard < epicsMutex > &,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
118
src/ioc/db/dbChannelNOOP.h
Normal file
118
src/ioc/db/dbChannelNOOP.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef DBCHANNELNOOP_H
|
||||
#define DBCHANNELNOOP_H
|
||||
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
141
src/ioc/db/test/arrRecord.c
Normal file
141
src/ioc/db/test/arrRecord.c
Normal file
@@ -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 <Ralph.Lange@bessy.de>
|
||||
*
|
||||
* vaguely implemented like parts of recWaveform.c by Bob Dalesio
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
42
src/ioc/db/test/arrRecord.dbd
Normal file
42
src/ioc/db/test/arrRecord.dbd
Normal file
@@ -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*)")
|
||||
}
|
||||
}
|
||||
84
src/ioc/db/test/dbCACTest.cpp
Normal file
84
src/ioc/db/test/dbCACTest.cpp
Normal file
@@ -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 <stdio.h>
|
||||
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <epicsEvent.h>
|
||||
|
||||
#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<double> buf(count);
|
||||
for(size_t i=0; i<count ;i++)
|
||||
buf[i] = first + i*1.0;
|
||||
|
||||
testDiag("Put");
|
||||
|
||||
testECA(ca_array_put(DBR_DOUBLE, count, chanid, &buf[0]));
|
||||
|
||||
testECA(ca_pend_io(1.0));
|
||||
|
||||
testDiag("Get");
|
||||
|
||||
std::vector<double> buf2(count);
|
||||
|
||||
testECA(ca_array_get(DBR_DOUBLE, count, chanid, &buf2[0]));
|
||||
|
||||
testECA(ca_pend_io(1.0));
|
||||
|
||||
for(size_t i=0; i<count ;i++)
|
||||
testOk(buf[i]==buf2[i], "%f == %f", buf[i], buf2[i]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct CATestContext
|
||||
{
|
||||
CATestContext()
|
||||
{
|
||||
if(ca_context_create(ca_enable_preemptive_callback)!=ECA_NORMAL)
|
||||
throw std::runtime_error("Failed to create CA context");
|
||||
}
|
||||
~CATestContext()
|
||||
{
|
||||
ca_context_destroy();
|
||||
}
|
||||
};
|
||||
|
||||
extern "C"
|
||||
void dbCaLinkTest_testCAC(void)
|
||||
{
|
||||
try {
|
||||
CATestContext ctxt;
|
||||
chid chanid = 0;
|
||||
testECA(ca_create_channel("target1", NULL, NULL, 0, &chanid));
|
||||
testECA(ca_pend_io(1.0));
|
||||
putgetarray(chanid, 1.0, 1);
|
||||
putgetarray(chanid, 2.0, 2);
|
||||
// repeat to ensure a cache hit in dbContextReadNotifyCacheAllocator
|
||||
putgetarray(chanid, 2.0, 2);
|
||||
putgetarray(chanid, 5.0, 5);
|
||||
|
||||
testECA(ca_clear_channel(chanid));
|
||||
}catch(std::exception& e){
|
||||
testAbort("Unexpected exception in testCAC: %s", e.what());
|
||||
}
|
||||
}
|
||||
597
src/ioc/db/test/dbCaLinkTest.c
Normal file
597
src/ioc/db/test/dbCaLinkTest.c
Normal file
@@ -0,0 +1,597 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
\*************************************************************************/
|
||||
|
||||
/*
|
||||
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#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;i<used;i++,x++,b++)
|
||||
match &= (*b)==x;
|
||||
for(;i<total;i++,x++,b++)
|
||||
match &= (*b)==0;
|
||||
testOk(match, msg);
|
||||
if(!match) {
|
||||
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
|
||||
if((*b)!=x)
|
||||
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
|
||||
for(;i<total;i++,x++,b++)
|
||||
if((*b)!=0)
|
||||
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkArrayDouble(const char *msg,
|
||||
double *buf, double first,
|
||||
unsigned used, unsigned total)
|
||||
{
|
||||
int match = 1;
|
||||
unsigned i;
|
||||
double x, *b;
|
||||
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
|
||||
match &= (*b)==x;
|
||||
for(;i<total;i++,x++,b++)
|
||||
match &= (*b)==0;
|
||||
testOk(match, msg);
|
||||
if(!match) {
|
||||
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
|
||||
if((*b)!=x)
|
||||
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
|
||||
for(;i<total;i++,x++,b++)
|
||||
if((*b)!=0)
|
||||
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
|
||||
}
|
||||
}
|
||||
|
||||
static void spoilputbuf(DBLINK *lnk)
|
||||
{
|
||||
extern const unsigned short dbr_value_size[];
|
||||
short epicsShareAPI ca_field_type (chid chan);
|
||||
|
||||
caLink *pca = lnk->value.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();
|
||||
}
|
||||
5
src/ioc/db/test/dbCaLinkTest1.db
Normal file
5
src/ioc/db/test/dbCaLinkTest1.db
Normal file
@@ -0,0 +1,5 @@
|
||||
record(x, "target") {}
|
||||
|
||||
record(x, "source") {
|
||||
field(LNK, "$(TARGET)")
|
||||
}
|
||||
10
src/ioc/db/test/dbCaLinkTest2.db
Normal file
10
src/ioc/db/test/dbCaLinkTest2.db
Normal file
@@ -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=))")
|
||||
}
|
||||
14
src/ioc/db/test/dbCaLinkTest3.db
Normal file
14
src/ioc/db/test/dbCaLinkTest3.db
Normal file
@@ -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=))")
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
163
src/ioc/db/test/devx.c
Normal file
163
src/ioc/db/test/devx.c
Normal file
@@ -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 <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <epicsAssert.h>
|
||||
#include <cantProceed.h>
|
||||
#include <ellLib.h>
|
||||
#include <dbDefs.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbScan.h>
|
||||
#include <devSup.h>
|
||||
#include <recGbl.h>
|
||||
#include <link.h>
|
||||
#include <dbLink.h>
|
||||
#include <xRecord.h>
|
||||
|
||||
#include <epicsExport.h>
|
||||
|
||||
#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);
|
||||
2
src/ioc/db/test/devx.dbd
Normal file
2
src/ioc/db/test/devx.dbd
Normal file
@@ -0,0 +1,2 @@
|
||||
device(x, CONSTANT, devxSoft, "Soft Channel")
|
||||
device(x, INST_IO , devxScanIO, "Scan I/O")
|
||||
52
src/ioc/db/test/devx.h
Normal file
52
src/ioc/db/test/devx.h
Normal file
@@ -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 <ellLib.h>
|
||||
#include <dbScan.h>
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
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 */
|
||||
@@ -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);
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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; p<NUM_CALLBACK_PRIORITIES; p++) {
|
||||
data[i].wake[p] = epicsEventMustCreate(epicsEventEmpty);
|
||||
data[i].wait[p] = epicsEventMustCreate(epicsEventEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
testDiag("Test single-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, 0, "LOW");
|
||||
loadRecord(0, 1, "MEDIUM");
|
||||
loadRecord(1, 1, "MEDIUM");
|
||||
loadRecord(0, 2, "HIGH");
|
||||
loadRecord(1, 2, "HIGH");
|
||||
|
||||
drvs[0] = xdrv_add(0, &testcb, &data[0]);
|
||||
drvs[1] = xdrv_add(1, &testcb, &data[1]);
|
||||
scanIoSetComplete(drvs[0]->scan, &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; i<NUM_CALLBACK_PRIORITIES; i++)
|
||||
epicsEventMustWait(data[0].wait[i]);
|
||||
|
||||
testDiag("Wait one more second");
|
||||
epicsThreadSleep(1.0);
|
||||
|
||||
testOk1(data[0].hasprocd[0]==1);
|
||||
testOk1(data[0].hasprocd[1]==1);
|
||||
testOk1(data[0].hasprocd[2]==1);
|
||||
testOk1(data[0].getcomplete[0]==1);
|
||||
testOk1(data[0].getcomplete[1]==1);
|
||||
testOk1(data[0].getcomplete[2]==1);
|
||||
testOk1(data[1].hasprocd[0]==0);
|
||||
testOk1(data[1].hasprocd[1]==0);
|
||||
testOk1(data[1].hasprocd[2]==0);
|
||||
testOk1(data[1].getcomplete[0]==0);
|
||||
testOk1(data[1].getcomplete[1]==0);
|
||||
testOk1(data[1].getcomplete[2]==0);
|
||||
|
||||
testDiag("Release the first scan and wait for the second");
|
||||
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
|
||||
epicsEventMustTrigger(data[0].wake[i]);
|
||||
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
|
||||
epicsEventMustWait(data[1].wait[i]);
|
||||
|
||||
testOk1(data[0].hasprocd[0]==1);
|
||||
testOk1(data[0].hasprocd[1]==1);
|
||||
testOk1(data[0].hasprocd[2]==1);
|
||||
testOk1(data[0].getcomplete[0]==1);
|
||||
testOk1(data[0].getcomplete[1]==1);
|
||||
testOk1(data[0].getcomplete[2]==1);
|
||||
testOk1(data[1].hasprocd[0]==1);
|
||||
testOk1(data[1].hasprocd[1]==1);
|
||||
testOk1(data[1].hasprocd[2]==1);
|
||||
testOk1(data[1].getcomplete[0]==1);
|
||||
testOk1(data[1].getcomplete[1]==1);
|
||||
testOk1(data[1].getcomplete[2]==1);
|
||||
|
||||
testDiag("Release the second scan and complete");
|
||||
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
|
||||
epicsEventMustTrigger(data[1].wake[i]);
|
||||
|
||||
testIocShutdownOk();
|
||||
epicsThreadSleep(0.1);
|
||||
for (i = 0; i < noOfGroups; i++) {
|
||||
epicsMessageQueueDestroy(mq[i]); mq[i] = NULL;
|
||||
epicsEventDestroy(barrier[i]); barrier[i] = NULL;
|
||||
ellFree(&pvtList[i]);
|
||||
}
|
||||
free(mq);
|
||||
free(barrier);
|
||||
free(pvtList);
|
||||
free(cbCounter);
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
xdrv_reset();
|
||||
|
||||
for(i=0; i<2; i++) {
|
||||
int p;
|
||||
for(p=0; p<NUM_CALLBACK_PRIORITIES; p++) {
|
||||
epicsEventDestroy(data[i].wake[p]);
|
||||
epicsEventDestroy(data[i].wait[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void checkProcessed(void *user, IOSCANPVT ioscan, int prio) {
|
||||
struct pvtY *pvt;
|
||||
int group = -1;
|
||||
typedef struct {
|
||||
int hasprocd;
|
||||
int getcomplete;
|
||||
epicsEventId wait;
|
||||
epicsEventId wake;
|
||||
} testmulti;
|
||||
|
||||
static void testcbmulti(xpriv *priv, void *raw)
|
||||
{
|
||||
testmulti *td = raw;
|
||||
td += priv->member;
|
||||
|
||||
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<<prio))==0, "(0x%x)&(0x%x)==0", *mask, 1<<prio);
|
||||
*mask |= 1<<prio;
|
||||
}
|
||||
|
||||
static void testMultiThreading(void)
|
||||
{
|
||||
int i;
|
||||
int masks[2];
|
||||
testmulti data[6];
|
||||
xdrv *drvs[2];
|
||||
|
||||
for (i = 0; i < noOfGroups; i++) {
|
||||
if (ioscanpvt[groupTable[i].ioscan] == ioscan
|
||||
&& groupTable[i].prio == prio) {
|
||||
group = i;
|
||||
break;
|
||||
}
|
||||
memset(masks, 0, sizeof(masks));
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
for(i=0; i<NELEMENTS(data); i++) {
|
||||
data[i].wake = epicsEventMustCreate(epicsEventEmpty);
|
||||
data[i].wait = epicsEventMustCreate(epicsEventEmpty);
|
||||
}
|
||||
if (group == -1)
|
||||
testAbort("invalid ioscanpvt in scanio callback");
|
||||
|
||||
cbCounter[group]++;
|
||||
for (pvt = (struct pvtY *)ellFirst(&pvtList[group]);
|
||||
pvt;
|
||||
pvt = (struct pvtY *)ellNext(&pvt->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<NELEMENTS(data); i++)
|
||||
epicsEventMustWait(data[i].wait);
|
||||
|
||||
testDiag("Wait one more second");
|
||||
epicsThreadSleep(1.0);
|
||||
|
||||
for(i=0; i<NELEMENTS(data); i++) {
|
||||
testOk(data[i].hasprocd==1, "data[%d].hasprocd==1 (%d)", i, data[i].hasprocd);
|
||||
testOk(data[i].getcomplete==0, "data[%d].getcomplete==0 (%d)", i, data[i].getcomplete);
|
||||
}
|
||||
|
||||
testDiag("Release all and complete");
|
||||
for(i=0; i<NELEMENTS(data); i++)
|
||||
epicsEventMustTrigger(data[i].wake);
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
for(i=0; i<NELEMENTS(data); i++) {
|
||||
testOk(data[i].getcomplete==1, "data[%d].getcomplete==0 (%d)", i, data[i].getcomplete);
|
||||
}
|
||||
testOk1(masks[0]==0x7);
|
||||
testOk1(masks[1]==0x7);
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
xdrv_reset();
|
||||
|
||||
for(i=0; i<NELEMENTS(data); i++) {
|
||||
epicsEventDestroy(data[i].wake);
|
||||
epicsEventDestroy(data[i].wait);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************\
|
||||
* scanIoTest: Test I/O Intr scanning
|
||||
* including parallel callback threads and scanio callbacks
|
||||
\*************************************************************************/
|
||||
|
||||
MAIN(scanIoTest)
|
||||
{
|
||||
int i, j;
|
||||
int loop;
|
||||
int max_one, max_one_all;
|
||||
int parallel, parallel_all;
|
||||
int result;
|
||||
int cbCountOk;
|
||||
long waiting;
|
||||
struct pvtY *pvt;
|
||||
|
||||
testPlan(10);
|
||||
|
||||
if (noOfGroups < NO_OF_THREADS)
|
||||
testAbort("ERROR: This test requires number of ioscan sources >= 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();
|
||||
}
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
|
||||
@@ -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 <epicsExport.h>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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*)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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*)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user