Merge branch '7.0' (after codeathon 2023) into PSI-7.0

This commit is contained in:
2023-03-29 13:36:52 +02:00
36 changed files with 662 additions and 181 deletions

2
.ci

Submodule .ci updated: aea7906839...1e0e326f74

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
# Documentation for this file: https://EditorConfig.org
root = true
# Unix-style newlines ending every file,
# as some compilers complain about files not ending in newline
[*]
insert_final_newline = true

View File

@@ -0,0 +1,13 @@
name: Check EditorConfig
on:
push:
pull_request:
jobs:
editorconfig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: EditorConfig-Action
uses: greut/eclint-action@v0

View File

@@ -43,11 +43,8 @@ jobs:
env:
CMP: ${{ matrix.cmp }}
BCFG: ${{ matrix.configuration }}
WINE: ${{ matrix.wine }}
RTEMS: ${{ matrix.rtems }}
RTEMS_TARGET: ${{ matrix.rtems_target }}
CI_CROSS_TARGETS: ${{ matrix.cross }}
EXTRA: ${{ matrix.extra }}
EXTRA1: ${{ matrix.extra1 }}
TEST: ${{ matrix.test }}
strategy:
fail-fast: false
@@ -57,13 +54,13 @@ jobs:
- os: ubuntu-20.04
cmp: gcc
configuration: default
wine: "64"
cross: "windows-x64-mingw"
name: "Ub-20 gcc-9 + MinGW"
- os: ubuntu-20.04
cmp: gcc
configuration: static
wine: "64"
cross: "windows-x64-mingw"
name: "Ub-20 gcc-9 + MinGW, static"
- os: ubuntu-20.04
@@ -75,8 +72,7 @@ jobs:
- os: ubuntu-20.04
cmp: gcc
configuration: static
extra: "CMD_CFLAGS=-funsigned-char"
extra1: "CMD_CXXFLAGS=-funsigned-char"
extra: "CMD_CFLAGS=-funsigned-char CMD_CXXFLAGS=-funsigned-char"
name: "Ub-20 gcc-9 unsigned char"
- os: ubuntu-20.04
@@ -93,67 +89,42 @@ jobs:
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "5"
rtems_target: RTEMS-pc686-qemu
cross: "RTEMS-pc686-qemu@5"
name: "Ub-20 gcc-9 + RT-5.1 pc686"
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "5"
rtems_target: RTEMS-beatnik
cross: "RTEMS-beatnik@5"
test: NO
name: "Ub-20 gcc-9 + RT-5.1 beatnik"
# Only build one RTEMS target per CPU family
# unless it's running the tests
#
# - os: ubuntu-20.04
# cmp: gcc
# configuration: default
# rtems: "5"
# rtems_target: RTEMS-mvme3100
# test: NO
# name: "Ub-20 gcc-9 + RT-5.1 mvme3100"
#
# - os: ubuntu-20.04
# cmp: gcc
# configuration: default
# rtems: "5"
# rtems_target: RTEMS-qoriq_e500
# test: NO
# name: "Ub-20 gcc-9 + RT-5.1 qoriq_e500"
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "5"
rtems_target: RTEMS-xilinx_zynq_a9_qemu
cross: "RTEMS-xilinx_zynq_a9_qemu@5"
test: NO
name: "Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu"
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "5"
rtems_target: RTEMS-uC5282
cross: "RTEMS-uC5282@5"
test: NO
name: "Ub-20 gcc-9 + RT-5.1 uC5282"
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "4.10"
name: "Ub-20 gcc-9 + RT-4.10"
rtems_target: RTEMS-pc386-qemu
cross: "RTEMS-pc386-qemu@4.10"
test: NO
- os: ubuntu-20.04
cmp: gcc
configuration: default
rtems: "4.9"
name: "Ub-20 gcc-9 + RT-4.9"
rtems_target: RTEMS-pc386-qemu
cross: "RTEMS-pc386-qemu@4.9"
- os: macos-latest
cmp: clang

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@
/modules/Makefile.local
O.*/
/QtC-*
/.qtc_*
/.vscode/
*.orig
*.log

View File

@@ -12,6 +12,9 @@ ifneq (,$(wildcard /opt/homebrew))
else ifneq (,$(wildcard /usr/local/Homebrew))
# Default location on x86_64
HOMEBREW_DIR = /usr/local
else ifneq (,$(wildcard /opt/local/include/readline))
# MacPorts
READLINE_DIR = /opt/local
endif
# Look for Homebrew's readline

View File

@@ -45,5 +45,5 @@ COMMANDLINE_LIBRARY ?= READLINE
COMMANDLINE_LIBRARY ?= EPICS
# endif
#else
COMMANDLINE_LIBRARY ?= EPICS
COMMANDLINE_LIBRARY ?= $(strip $(if $(wildcard $(if $(GNU_DIR),$(GNU_DIR)/include/readline/readline.h)), READLINE, EPICS))
#endif

View File

@@ -15,7 +15,38 @@ should also be read to understand what has changed since earlier releases.
## Changes made on the 7.0 branch since 7.0.7
<!-- Insert new items immediately below here ... -->
### dbEvent eventsRemaining missed on cancel
In some cases, RSRV may queue a subscription update, but not flush it.
This partially addresses this issue.
### subRecord on bad INP links
Previously, if a subRecord has an invalid `INP*` link, it was silently failing
(and not running the proc function). Now the the status code returned by the
subroutine is returned from `dbProcess()`.
### COMMANDLINE_LIBRARY fallback to GNU_DIR
Fall back to the previous behavior when searching for `readline.h` with older compilers.
### Search for readline installed via HomeBrew.
Look for `/opt/local/include/readline` on OSX.
### Always stop worker threads
The SCAN and callback threads are now stopped during normal IOC shutdown.
### Allow runtime bypass of free list allocator
The environment variable `$EPICS_FREELIST_BYPASS` may be set to `YES` to cause the `freeListLib` functions to always call directly to `malloc()`/`free()`. May be useful when troubleshooting some kinds of memory allocation bugs which would otherwise be "hidden". eg. use-after-free data races. This may also improve the results of dynamic analysis tools which are not aware of this internal free list.
### `compress` record enhancement
The compress record now supports the use of partially-filled buffers when using
any of the N-to-one algorithms. This is achieved by setting the new field `PBUF`
to `YES`.
### Add conditional output (OOPT) to the longout record

View File

@@ -56,6 +56,7 @@ extern "C" void epicsStdCall ca_dump_dbr (
if ( INVALID_DB_REQ ( type ) ) {
printf ( "bad DBR type %ld\n", type );
return;
}
printf ( "%s\t", dbr_text[type] );

View File

@@ -58,6 +58,7 @@ typedef struct cbQueueSet {
int shutdown; // use atomic
int threadsConfigured;
int threadsRunning;
epicsThreadId *threads;
} cbQueueSet;
static cbQueueSet callbackQueue[NUM_CALLBACK_PRIORITIES];
@@ -242,11 +243,15 @@ void callbackStop(void)
for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) {
cbQueueSet *mySet = &callbackQueue[i];
int j;
while (epicsAtomicGetIntT(&mySet->threadsRunning)) {
epicsEventSignal(mySet->semWakeUp);
epicsEventWaitWithTimeout(startStopEvent, 0.1);
}
for(j=0; j<mySet->threadsConfigured; j++) {
epicsThreadMustJoin(mySet->threads[j]);
}
}
}
@@ -266,6 +271,8 @@ void callbackCleanup(void)
mySet->semWakeUp = NULL;
epicsRingPointerDelete(mySet->queue);
mySet->queue = NULL;
free(mySet->threads);
mySet->threads = NULL;
}
epicsTimerQueueRelease(timerQueue);
@@ -297,17 +304,25 @@ void callbackInit(void)
cantProceed("epicsRingPointerLockedCreate failed for %s\n",
threadNamePrefix[i]);
callbackQueue[i].queueOverflow = FALSE;
if (callbackQueue[i].threadsConfigured == 0)
callbackQueue[i].threadsConfigured = callbackThreadsDefault;
callbackQueue[i].threads = callocMustSucceed(callbackQueue[i].threadsConfigured,
sizeof(*callbackQueue[i].threads),
"callbackInit");
for (j = 0; j < callbackQueue[i].threadsConfigured; j++) {
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
opts.joinable = 1;
opts.priority = threadPriority[i];
opts.stackSize = epicsThreadStackBig;
if (callbackQueue[i].threadsConfigured > 1 )
sprintf(threadName, "%s-%d", threadNamePrefix[i], j);
else
strcpy(threadName, threadNamePrefix[i]);
tid = epicsThreadCreate(threadName, threadPriority[i],
epicsThreadGetStackSize(epicsThreadStackBig),
(EPICSTHREADFUNC)callbackTask, &priorityValue[i]);
callbackQueue[i].threads[j] = tid = epicsThreadCreateOpt(threadName,
(EPICSTHREADFUNC)callbackTask, &priorityValue[i], &opts);
if (tid == 0) {
cantProceed("Failed to spawn callback thread %s\n", threadName);
} else {

View File

@@ -154,7 +154,7 @@ static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer)
const char *pstr = plink->value.constantStr;
size_t len;
if (!pstr)
if (!pstr || !pstr[0])
return S_db_badField;
len = strlen(pstr);
@@ -181,7 +181,7 @@ static long dbConstLoadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
const char *pstr = plink->value.constantStr;
long status;
if (!pstr)
if (!pstr || !pstr[0])
return S_db_badField;
status = dbLSConvertJSON(pstr, pbuffer, size, plen);
@@ -197,7 +197,7 @@ static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer,
const char *pstr = plink->value.constantStr;
long status;
if (!pstr)
if (!pstr || !pstr[0])
return S_db_badField;
/* Choice values must be numeric */

View File

@@ -76,6 +76,7 @@ struct event_que {
unsigned short quota; /* the number of assigned entries*/
unsigned short nDuplicates; /* N events duplicated on this q */
unsigned short nCanceled; /* the number of canceled entries */
unsigned possibleStall;
};
struct event_user {
@@ -934,6 +935,7 @@ void db_post_single_event (dbEventSubscription event)
static int event_read ( struct event_que *ev_que )
{
db_field_log *pfl;
int notifiedRemaining = 0;
void ( *user_sub ) ( void *user_arg, struct dbChannel *chan,
int eventsRemaining, db_field_log *pfl );
@@ -955,6 +957,7 @@ static int event_read ( struct event_que *ev_que )
while ( ev_que->evque[ev_que->getix] != EVENTQEMPTY ) {
struct evSubscrip *pevent = ev_que->evque[ev_que->getix];
int eventsRemaining;
pfl = ev_que->valque[ev_que->getix];
if ( pevent == &canceledEvent ) {
@@ -977,6 +980,7 @@ static int event_read ( struct event_que *ev_que )
event_remove ( ev_que, ev_que->getix, EVENTQEMPTY );
ev_que->getix = RNGINC ( ev_que->getix );
eventsRemaining = ev_que->evque[ev_que->getix] != EVENTQEMPTY && !ev_que->nCanceled;
/*
* create a local copy of the call back parameters while
@@ -1009,7 +1013,8 @@ static int event_read ( struct event_que *ev_que )
if (pfl) {
/* Issue user callback */
( *user_sub ) ( pevent->user_arg, pevent->chan,
ev_que->evque[ev_que->getix] != EVENTQEMPTY, pfl );
eventsRemaining, pfl );
notifiedRemaining = eventsRemaining;
}
LOCKEVQUE (ev_que);
@@ -1036,6 +1041,11 @@ static int event_read ( struct event_que *ev_que )
db_delete_field_log(pfl);
}
if(notifiedRemaining && !ev_que->possibleStall) {
ev_que->possibleStall = 1;
errlogPrintf(ERL_WARNING " dbEvent possible queue stall\n");
}
UNLOCKEVQUE (ev_que);
return DB_EVENT_OK;

View File

@@ -168,9 +168,13 @@ void scanStop(void)
epicsEventSignal(ppsl->loopEvent);
epicsEventWait(startStopEvent);
}
for (i = 0; i < nPeriodic; i++) {
epicsThreadMustJoin(periodicTaskId[i]);
}
scanOnce((dbCommon *)&exitOnce);
epicsEventWait(startStopEvent);
epicsThreadMustJoin(onceTaskId);
}
void scanCleanup(void)
@@ -761,14 +765,16 @@ void scanOnceQueueShow(const int reset)
static void initOnce(void)
{
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
opts.joinable = 1;
opts.priority = epicsThreadPriorityScanLow + nPeriodic;
opts.stackSize = epicsThreadStackBig;
if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) {
cantProceed("initOnce: Ring buffer create failed\n");
}
if(!onceSem)
onceSem = epicsEventMustCreate(epicsEventEmpty);
onceTaskId = epicsThreadCreate("scanOnce",
epicsThreadPriorityScanLow + nPeriodic,
epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0);
onceTaskId = epicsThreadCreateOpt("scanOnce", onceTask, 0, &opts);
epicsEventWait(startStopEvent);
}
@@ -932,14 +938,16 @@ static void spawnPeriodic(int ind)
{
periodic_scan_list *ppsl = papPeriodic[ind];
char taskName[20];
epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT;
opts.joinable = 1;
opts.priority = epicsThreadPriorityScanLow + ind;
opts.stackSize = epicsThreadStackBig;
if (!ppsl) return;
sprintf(taskName, "scan-%g", ppsl->period);
periodicTaskId[ind] = epicsThreadCreate(
taskName, epicsThreadPriorityScanLow + ind,
epicsThreadGetStackSize(epicsThreadStackBig),
periodicTask, (void *)ppsl);
periodicTaskId[ind] = epicsThreadCreateOpt(
taskName, periodicTask, (void *)ppsl, &opts);
epicsEventWait(startStopEvent);
}

View File

@@ -339,7 +339,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
break;
}
#define OP(DBR,Type,pat) case DBR: {Type expect = *(Type*)pbuf, actual = *(Type*)gbuf; assert(vSize==sizeof(Type)); match &= expect==actual; \
if(expect!=actual) testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual); break;}
if(expect!=actual) {testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual);} break;}
OP(DBR_CHAR, char, "%c");
OP(DBR_UCHAR, unsigned char, "%u");

View File

@@ -111,7 +111,8 @@ typedef struct inputFile{
static ELLLIST inputFileList = ELLLIST_INIT;
static inputFile *pinputFileNow = NULL;
static DBBASE *pdbbase = NULL;
/* The DBBASE most recently allocated/used by dbReadCOM() */
static DBBASE *savedPdbbase = NULL;
typedef struct tempListNode {
ELLNODE node;
@@ -233,15 +234,15 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
}
if(*ppdbbase == 0) *ppdbbase = dbAllocBase();
pdbbase = *ppdbbase;
savedPdbbase = *ppdbbase;
if(path && strlen(path)>0) {
dbPath(pdbbase,path);
dbPath(savedPdbbase,path);
} else {
penv = getenv("EPICS_DB_INCLUDE_PATH");
if(penv) {
dbPath(pdbbase,penv);
dbPath(savedPdbbase,penv);
} else {
dbPath(pdbbase,".");
dbPath(savedPdbbase,".");
}
}
my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char));
@@ -271,7 +272,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
FILE *fp1 = 0;
if (pinputFile->filename)
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp1);
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp1);
if (!pinputFile->filename || !fp1) {
errPrintf(0, __FILE__, __LINE__,
"dbRead opening file %s\n",pinputFile->filename);
@@ -297,13 +298,13 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
while (ellCount(&tempList))
popFirstTemp(); /* Memory leak on parser failure */
dbFreePath(pdbbase);
dbFreePath(savedPdbbase);
if(!status) { /*add RTYP and VERS as an attribute */
DBENTRY dbEntry;
DBENTRY *pdbEntry = &dbEntry;
long localStatus;
dbInitEntry(pdbbase,pdbEntry);
dbInitEntry(savedPdbbase,pdbEntry);
localStatus = dbFirstRecordType(pdbEntry);
while(!localStatus) {
localStatus = dbPutRecordAttribute(pdbEntry,"RTYP",
@@ -323,7 +324,7 @@ static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp,
cleanup:
if(dbRecordsAbcSorted) {
ELLNODE *cur;
for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur))
for(cur = ellFirst(&savedPdbbase->recordTypeList); cur; cur=ellNext(cur))
{
dbRecordType *rtype = CONTAINER(cur, dbRecordType, node);
@@ -416,12 +417,12 @@ static void dbIncludePrint(void)
static void dbPathCmd(char *path)
{
dbPath(pdbbase,path);
dbPath(savedPdbbase,path);
}
static void dbAddPathCmd(char *path)
{
dbAddPath(pdbbase,path);
dbAddPath(savedPdbbase,path);
}
static void dbIncludeNew(char *filename)
@@ -431,7 +432,7 @@ static void dbIncludeNew(char *filename)
pinputFile = dbCalloc(1,sizeof(inputFile));
pinputFile->filename = macEnvExpand(filename);
pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp);
pinputFile->path = dbOpenFile(savedPdbbase, pinputFile->filename, &fp);
if (!fp) {
epicsPrintf("Can't open include file \"%s\"\n", filename);
yyerror(NULL);
@@ -453,7 +454,7 @@ static void dbMenuHead(char *name)
yyerrorAbort("dbMenuHead: Menu name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->menuList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->menuList);
if(pgphentry) {
duplicate = TRUE;
return;
@@ -501,14 +502,14 @@ static void dbMenuBody(void)
}
if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty");
/* Add menu in sorted order */
pMenu = (dbMenu *)ellFirst(&pdbbase->menuList);
pMenu = (dbMenu *)ellFirst(&savedPdbbase->menuList);
while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 )
pMenu = (dbMenu *)ellNext(&pMenu->node);
if(pMenu)
ellInsert(&pdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
ellInsert(&savedPdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node);
else
ellAdd(&pdbbase->menuList,&pnewMenu->node);
pgphentry = gphAdd(pdbbase->pgpHash,pnewMenu->name,&pdbbase->menuList);
ellAdd(&savedPdbbase->menuList,&pnewMenu->node);
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewMenu->name,&savedPdbbase->menuList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
} else {
@@ -525,14 +526,14 @@ static void dbRecordtypeHead(char *name)
yyerrorAbort("dbRecordtypeHead: Recordtype name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->recordTypeList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->recordTypeList);
if(pgphentry) {
duplicate = TRUE;
return;
}
pdbRecordType = dbCalloc(1,sizeof(dbRecordType));
pdbRecordType->name = epicsStrDup(name);
if (pdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
if (savedPdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList);
if(ellCount(&tempList))
yyerrorAbort("dbRecordtypeHead tempList not empty");
allocTemp(pdbRecordType);
@@ -564,13 +565,13 @@ static short findOrAddGuiGroup(const char *name)
{
dbGuiGroup *pdbGuiGroup;
GPHENTRY *pgphentry;
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList);
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->guiGroupList);
if (!pgphentry) {
pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup));
pdbGuiGroup->name = epicsStrDup(name);
ellAdd(&pdbbase->guiGroupList, &pdbGuiGroup->node);
pdbGuiGroup->key = ellCount(&pdbbase->guiGroupList);
pgphentry = gphAdd(pdbbase->pgpHash, pdbGuiGroup->name, &pdbbase->guiGroupList);
ellAdd(&savedPdbbase->guiGroupList, &pdbGuiGroup->node);
pdbGuiGroup->key = ellCount(&savedPdbbase->guiGroupList);
pgphentry = gphAdd(savedPdbbase->pgpHash, pdbGuiGroup->name, &savedPdbbase->guiGroupList);
pgphentry->userPvt = pdbGuiGroup;
}
return ((dbGuiGroup *)pgphentry->userPvt)->key;
@@ -653,8 +654,8 @@ static void dbRecordtypeFieldItem(char *name,char *value)
return;
}
if(strcmp(name,"menu")==0) {
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(pdbbase,value);
if(!pdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(savedPdbbase,value);
if(!savedPdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt)
yyerrorAbort("menu not found");
return;
}
@@ -672,7 +673,7 @@ static void dbRecordtypeCdef(char *text) {
tempListNode *ptempListNode;
dbRecordType *pdbRecordType;
if (!pdbbase->loadCdefs || duplicate) return;
if (!savedPdbbase->loadCdefs || duplicate) return;
ptempListNode = (tempListNode *)ellFirst(&tempList);
pdbRecordType = ptempListNode->item;
@@ -781,14 +782,14 @@ static void dbRecordtypeBody(void)
ellInit(&pdbRecordType->attributeList);
ellInit(&pdbRecordType->recList);
ellInit(&pdbRecordType->devList);
pgphentry = gphAdd(pdbbase->pgpHash,pdbRecordType->name,
&pdbbase->recordTypeList);
pgphentry = gphAdd(savedPdbbase->pgpHash,pdbRecordType->name,
&savedPdbbase->recordTypeList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
} else {
pgphentry->userPvt = pdbRecordType;
}
ellAdd(&pdbbase->recordTypeList,&pdbRecordType->node);
ellAdd(&savedPdbbase->recordTypeList,&pdbRecordType->node);
}
static void dbDevice(char *recordtype,char *linktype,
@@ -798,7 +799,7 @@ static void dbDevice(char *recordtype,char *linktype,
dbRecordType *pdbRecordType;
GPHENTRY *pgphentry;
int i,link_type;
pgphentry = gphFind(pdbbase->pgpHash,recordtype,&pdbbase->recordTypeList);
pgphentry = gphFind(savedPdbbase->pgpHash,recordtype,&savedPdbbase->recordTypeList);
if(!pgphentry) {
epicsPrintf("Record type \"%s\" not found for device \"%s\"\n",
recordtype, choicestring);
@@ -819,7 +820,7 @@ static void dbDevice(char *recordtype,char *linktype,
return;
}
pdbRecordType = (dbRecordType *)pgphentry->userPvt;
pgphentry = gphFind(pdbbase->pgpHash,choicestring,&pdbRecordType->devList);
pgphentry = gphFind(savedPdbbase->pgpHash,choicestring,&pdbRecordType->devList);
if(pgphentry) {
return;
}
@@ -827,7 +828,7 @@ static void dbDevice(char *recordtype,char *linktype,
pdevSup->name = epicsStrDup(dsetname);
pdevSup->choice = epicsStrDup(choicestring);
pdevSup->link_type = link_type;
pgphentry = gphAdd(pdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
pgphentry = gphAdd(savedPdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
} else {
@@ -845,18 +846,18 @@ static void dbDriver(char *name)
yyerrorAbort("dbDriver: Driver name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->drvList);
if(pgphentry) {
return;
}
pdrvSup = dbCalloc(1,sizeof(drvSup));
pdrvSup->name = epicsStrDup(name);
pgphentry = gphAdd(pdbbase->pgpHash,pdrvSup->name,&pdbbase->drvList);
pgphentry = gphAdd(savedPdbbase->pgpHash,pdrvSup->name,&savedPdbbase->drvList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
}
pgphentry->userPvt = pdrvSup;
ellAdd(&pdbbase->drvList,&pdrvSup->node);
ellAdd(&savedPdbbase->drvList,&pdrvSup->node);
}
static void dbLinkType(char *name, char *jlif_name)
@@ -864,19 +865,19 @@ static void dbLinkType(char *name, char *jlif_name)
linkSup *pLinkSup;
GPHENTRY *pgphentry;
pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->linkList);
pgphentry = gphFind(savedPdbbase->pgpHash, name, &savedPdbbase->linkList);
if (pgphentry) {
return;
}
pLinkSup = dbCalloc(1,sizeof(linkSup));
pLinkSup->name = epicsStrDup(name);
pLinkSup->jlif_name = epicsStrDup(jlif_name);
pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList);
pgphentry = gphAdd(savedPdbbase->pgpHash, pLinkSup->name, &savedPdbbase->linkList);
if (!pgphentry) {
yyerrorAbort("gphAdd failed");
}
pgphentry->userPvt = pLinkSup;
ellAdd(&pdbbase->linkList, &pLinkSup->node);
ellAdd(&savedPdbbase->linkList, &pLinkSup->node);
}
static void dbRegistrar(char *name)
@@ -888,18 +889,18 @@ static void dbRegistrar(char *name)
yyerrorAbort("dbRegistrar: Registrar name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->registrarList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->registrarList);
if(pgphentry) {
return;
}
ptext = dbCalloc(1,sizeof(dbText));
ptext->text = epicsStrDup(name);
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->registrarList);
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->registrarList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
}
pgphentry->userPvt = ptext;
ellAdd(&pdbbase->registrarList,&ptext->node);
ellAdd(&savedPdbbase->registrarList,&ptext->node);
}
static void dbFunction(char *name)
@@ -911,18 +912,18 @@ static void dbFunction(char *name)
yyerrorAbort("dbFunction: Function name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->functionList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->functionList);
if(pgphentry) {
return;
}
ptext = dbCalloc(1,sizeof(dbText));
ptext->text = epicsStrDup(name);
pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->functionList);
pgphentry = gphAdd(savedPdbbase->pgpHash,ptext->text,&savedPdbbase->functionList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
}
pgphentry->userPvt = ptext;
ellAdd(&pdbbase->functionList,&ptext->node);
ellAdd(&savedPdbbase->functionList,&ptext->node);
}
static void dbVariable(char *name, char *type)
@@ -934,19 +935,19 @@ static void dbVariable(char *name, char *type)
yyerrorAbort("dbVariable: Variable name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->variableList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->variableList);
if(pgphentry) {
return;
}
pvar = dbCalloc(1,sizeof(dbVariableDef));
pvar->name = epicsStrDup(name);
pvar->type = epicsStrDup(type);
pgphentry = gphAdd(pdbbase->pgpHash,pvar->name,&pdbbase->variableList);
pgphentry = gphAdd(savedPdbbase->pgpHash,pvar->name,&savedPdbbase->variableList);
if(!pgphentry) {
yyerrorAbort("gphAdd failed");
}
pgphentry->userPvt = pvar;
ellAdd(&pdbbase->variableList,&pvar->node);
ellAdd(&savedPdbbase->variableList,&pvar->node);
}
static void dbBreakHead(char *name)
@@ -958,7 +959,7 @@ static void dbBreakHead(char *name)
yyerrorAbort("dbBreakHead: Breaktable name can't be empty");
return;
}
pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->bptList);
pgphentry = gphFind(savedPdbbase->pgpHash,name,&savedPdbbase->bptList);
if(pgphentry) {
duplicate = TRUE;
return;
@@ -1042,17 +1043,17 @@ static void dbBreakBody(void)
/* Continue with last slope beyond the final point */
paBrkInt[number-1].slope = paBrkInt[number-2].slope;
/* Add brkTable in sorted order */
pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList);
pbrkTable = (brkTable *)ellFirst(&savedPdbbase->bptList);
while (pbrkTable) {
if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) {
ellInsert(&pdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
ellInsert(&savedPdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable),
(ELLNODE *)pnewbrkTable);
break;
}
pbrkTable = (brkTable *)ellNext(&pbrkTable->node);
}
if (!pbrkTable) ellAdd(&pdbbase->bptList, &pnewbrkTable->node);
pgphentry = gphAdd(pdbbase->pgpHash,pnewbrkTable->name,&pdbbase->bptList);
if (!pbrkTable) ellAdd(&savedPdbbase->bptList, &pnewbrkTable->node);
pgphentry = gphAdd(savedPdbbase->pgpHash,pnewbrkTable->name,&savedPdbbase->bptList);
if (!pgphentry) {
yyerrorAbort("dbBreakBody: gphAdd failed");
return;
@@ -1103,7 +1104,7 @@ static void dbRecordHead(char *recordType, char *name, int visible)
if(dbRecordNameValidate(name))
return;
pdbentry = dbAllocEntry(pdbbase);
pdbentry = dbAllocEntry(savedPdbbase);
if (ellCount(&tempList))
yyerrorAbort("dbRecordHead: tempList not empty");
allocTemp(pdbentry);
@@ -1260,7 +1261,7 @@ static void dbAlias(char *name, char *alias)
if(dbRecordNameValidate(alias))
return;
dbInitEntry(pdbbase, pdbEntry);
dbInitEntry(savedPdbbase, pdbEntry);
if (dbFindRecord(pdbEntry, name)) {
epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n",
alias, name);

View File

@@ -716,13 +716,13 @@ int iocShutdown(void)
iterateRecords(doCloseLinks, NULL);
initHookAnnounce(initHookAfterCloseLinks);
if (iocBuildMode == buildIsolated) {
/* stop and "join" threads */
scanStop();
initHookAnnounce(initHookAfterStopScan);
callbackStop();
initHookAnnounce(initHookAfterStopCallback);
} else {
/* stop and "join" threads */
scanStop();
initHookAnnounce(initHookAfterStopScan);
callbackStop();
initHookAnnounce(initHookAfterStopCallback);
if (iocBuildMode != buildIsolated) {
dbStopServers();
}

View File

@@ -28,6 +28,7 @@
#include "dbEvent.h"
#include "dbFldTypes.h"
#include "errMdef.h"
#include "menuYesNo.h"
#include "special.h"
#include "recSup.h"
#include "recGbl.h"
@@ -166,9 +167,9 @@ static int compress_array(compressRecord *prec,
}
if (prec->n <= 0)
prec->n = 1;
n = prec->n;
if (no_elements < n)
if (no_elements < prec->n && prec->pbuf != menuYesNoYES)
return 1; /*dont do anything*/
n = no_elements;
/* determine number of samples to take */
if (no_elements < nsam * n)
@@ -272,7 +273,7 @@ static int array_average(compressRecord *prec,
prec->inx = 0;
return 0;
}
static int compress_scalar(struct compressRecord *prec,double *psource)
{
double value = *psource;
@@ -292,19 +293,13 @@ static int compress_scalar(struct compressRecord *prec,double *psource)
/* for scalars, Median not implemented => use average */
case (compressALG_N_to_1_Average):
case (compressALG_N_to_1_Median):
if (inx == 0)
*pdest = value;
else {
*pdest += value;
if (inx + 1 >= prec->n)
*pdest = *pdest / (inx + 1);
}
*pdest = (inx * (*pdest) + value) / (inx + 1);
break;
}
inx++;
if (inx >= prec->n) {
if ((inx >= prec->n) || (prec->pbuf == menuYesNoYES)) {
put_value(prec,pdest,1);
prec->inx = 0;
prec->inx = (inx >= prec->n) ? 0 : inx;
return 0;
} else {
prec->inx = inx;

View File

@@ -40,7 +40,7 @@ the beginning or the end of the VAL array.
=head2 Parameter Fields
The record-specific fields are described below.
The record-specific fields are described below, grouped by functionality.
=recordtype compress
@@ -60,10 +60,6 @@ menu(bufferingALG) {
}
recordtype(compress) {
=head2 Parameter Fields
The record-specific fields are described below, grouped by functionality.
=head3 Scanning Parameters
The compression record has the standard fields for specifying under what
@@ -85,7 +81,7 @@ algorithms which can be specified as follows:
The following fields determine what channel to read and how to compress the data:
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES
=fields ALG, INP, NSAM, N, ILIL, IHIL, OFF, RES, PBUF
As stated above, the ALG field specifies which algorithm to be performed on the data.
@@ -167,6 +163,23 @@ Compress N to 1 samples, taking the median value.
=back
The behaviour of the record for partially filled buffers depends on the field PBUF.
If PBUF is set to NO, then the record will wait until the buffer is completely full
before processing. If PBUF is set to YES, then it will start processing immediately.
For example, if ALG is set to C<<< N to 1 Average >>> with NSAM equal to 4, N equal
to 1, and PBUF set to NO, then the first three times that the compress record is
processed it will remain in an undefined state. On the fourth process, the average
of all four records will be calculated and placed into the VAL field.
If PBUF is set to YES, then after each process the average of the first several
elements will be calculated.
Note that PBUF has no impact on the C<<< Average >>> method. If one wishes to have a
rolling average computed, then the best way to achieve that is with two compress
records: a C<<< Circular buffer >>> which is linked to an C<<< N to 1 Average >>>
record with PBUF set to YES.
The compression record keeps NSAM data samples.
The field N determines the number of elements to compress into each result.
@@ -393,7 +406,15 @@ Scan forward link if necessary, set PACT FALSE, and return.
interest(1)
menu(compressALG)
}
field(BALG,DBF_MENU) {
field(PBUF,DBF_MENU) {
prompt("Use Partial buffers")
promptgroup("30 - Action")
special(SPC_RESET)
interest(1)
menu(menuYesNo)
initial("NO")
}
field(BALG,DBF_MENU) {
prompt("Buffering Algorithm")
promptgroup("30 - Action")
special(SPC_RESET)

View File

@@ -271,7 +271,7 @@ static long special(DBADDR *paddr, int after)
} else if(after==1 && fieldIndex >= mbboDirectRecordB0 && fieldIndex <= mbboDirectRecordB1F) {
/* Adjust VAL corresponding to the bit changed */
epicsUInt8 *pBn = (epicsUInt8 *) paddr->pfield;
epicsUInt32 bit = 1 << (pBn - &prec->b0);
epicsUInt32 bit = 1u << (pBn - &prec->b0);
/* Because this is !(VAL and PP), dbPut() will always post a monitor on this B* field
* after we return. We must keep track of this change separately from MLST to handle

View File

@@ -162,7 +162,7 @@ static long process(struct dbCommon *pcommon)
recGblFwdLink(prec);
prec->pact = FALSE;
return 0;
return status;
}
static long special(DBADDR *paddr, int after)

View File

@@ -146,7 +146,7 @@ __EOF__
" prt->papFldDes[${rn}Record${fn}]->size = " .
"sizeof(prec->${cn});\n" .
" prt->papFldDes[${rn}Record${fn}]->offset = " .
"(unsigned short)((char *)&prec->${cn} - (char *)prec);"
"(unsigned short)offsetof(${rn}Record, ${cn});"
} @fields), << "__EOF__";
prt->rec_size = sizeof(*prec);

View File

@@ -167,6 +167,16 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
TESTS += asyncproctest
TARGETS += $(COMMON_DIR)/subproctest.dbd
DBDDEPENDS_FILES += subproctest.dbd$(DEP)
subproctest_DBD += base.dbd
TESTPROD_HOST += subproctest
subproctest_SRCS += subproctest.c
subproctest_SRCS += subproctest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/subproctest.dbd ../subproctest.db
TESTS += subproctest
TESTPROD_HOST += linkFilterTest
linkFilterTest_SRCS += linkFilterTest.c
linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp

View File

@@ -5,17 +5,24 @@
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <stdlib.h>
#include "cantProceed.h"
#include "dbUnitTest.h"
#include "testMain.h"
#include "dbLock.h"
#include "errlog.h"
#include "dbAccess.h"
#include "epicsMath.h"
#include "menuYesNo.h"
#include "aiRecord.h"
#include "waveformRecord.h"
#include "compressRecord.h"
#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B)
#define fetchRecordOrDie(recname, addr) if (dbNameToAddr(recname, &addr)) {testAbort("Unknown PV '%s'", recname);}
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
@@ -33,8 +40,7 @@ void checkArrD(const char *pv, long elen, double a, double b, double c, double d
expect[2] = c;
expect[3] = d;
if (dbNameToAddr(pv, &addr))
testAbort("Unknown PV '%s'", pv);
fetchRecordOrDie(pv, addr);
if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL))
testAbort("Failed to get '%s'", pv);
@@ -66,8 +72,7 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
expect[2] = c;
expect[3] = d;
if (dbNameToAddr(pv, &addr))
testAbort("Unknown PV '%s'", pv);
fetchRecordOrDie(pv, addr);
if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL))
testAbort("Failed to get '%s'", pv);
@@ -85,6 +90,24 @@ void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32
}
}
void
writeToWaveform(DBADDR *addr, long count, ...) {
va_list args;
long i;
double *values = (double *)callocMustSucceed(count, sizeof(double), "writeToWaveform");
va_start(args, count);
for (i=0; i< count; i++) {
values[i] = va_arg(args, double);
}
va_end(args);
dbScanLock(addr->precord);
dbPut(addr, DBR_DOUBLE, values, count);
dbScanUnlock(addr->precord);
free(values);
}
static
void testFIFOCirc(void)
{
@@ -100,9 +123,9 @@ void testFIFOCirc(void)
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
vrec = (aiRecord*)testdbRecordPtr("val");
vrec = (aiRecord*)testdbRecordPtr("ai");
crec = (compressRecord*)testdbRecordPtr("comp");
eltc(0);
@@ -230,9 +253,9 @@ void testLIFOCirc(void)
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL,
"ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
"INP=ai,ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
vrec = (aiRecord*)testdbRecordPtr("val");
vrec = (aiRecord*)testdbRecordPtr("ai");
crec = (compressRecord*)testdbRecordPtr("comp");
eltc(0);
@@ -346,10 +369,278 @@ void testLIFOCirc(void)
testdbCleanup();
}
void
testArrayAverage(void) {
DBADDR wfaddr, caddr;
testDiag("Test Array Average");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=Average,BALG=FIFO Buffer,NSAM=4,N=2");
eltc(0);
testIocInitOk();
eltc(1);
fetchRecordOrDie("wf", wfaddr);
fetchRecordOrDie("comp", caddr);
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
writeToWaveform(&wfaddr, 4, 2., 4., 6., 8.);
dbProcess(caddr.precord);
checkArrD("comp", 4, 1.5, 3., 4.5, 6.);
dbScanUnlock(caddr.precord);
testIocShutdownOk();
testdbCleanup();
}
void
testNto1Average(void) {
double buf = 0.0;
long nReq = 1;
DBADDR wfaddr, caddr;
testDiag("Test Average");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4");
eltc(0);
testIocInitOk();
eltc(1);
fetchRecordOrDie("wf", wfaddr);
fetchRecordOrDie("comp", caddr);
testDiag("Test incomplete input data");
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testOk1(nReq == 0);
testDEq(buf, 0., 0.01);
dbScanUnlock(caddr.precord);
testDiag("Test complete input data");
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
nReq = 1;
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 2.5, 0.01);
dbScanUnlock(caddr.precord);
testDiag("Test single input data");
writeToWaveform(&wfaddr, 1, 5.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
nReq = 1;
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
// Assert that nothing has changed from before
testDEq(buf, 2.5, 0.01);
dbScanUnlock(caddr.precord);
testIocShutdownOk();
testdbCleanup();
}
void
testNto1AveragePartial(void) {
double buf = 0.0;
long nReq = 1;
DBADDR wfaddr, caddr;
testDiag("Test Average, Partial");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
eltc(0);
testIocInitOk();
eltc(1);
testDiag("Test incomplete input data");
fetchRecordOrDie("wf", wfaddr);
fetchRecordOrDie("comp", caddr);
writeToWaveform(&wfaddr, 3, 1., 2., 3.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 2.0, 0.01);
dbScanUnlock(caddr.precord);
testDiag("Test single entry from wf record");
writeToWaveform(&wfaddr, 1, 6.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 6.0, 0.01);
dbScanUnlock(caddr.precord);
testIocShutdownOk();
testdbCleanup();
}
void
testNto1LowValue(void) {
double buf = 0.0;
long nReq = 1;
DBADDR wfaddr, caddr;
testDiag("Test 'N to 1 Low Value'");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "INP=wf,ALG=N to 1 Low Value,BALG=FIFO Buffer,NSAM=1,N=4");
eltc(0);
testIocInitOk();
eltc(1);
fetchRecordOrDie("wf", wfaddr);
fetchRecordOrDie("comp", caddr);
testDiag("Test full array");
writeToWaveform(&wfaddr, 4, 1., 2., 3., 4.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 1.0, 0.01);
dbScanUnlock(caddr.precord);
writeToWaveform(&wfaddr, 4, 4., 3., 2., 1.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 1.0, 0.01);
dbScanUnlock(caddr.precord);
testDiag("Test partial data with PBUF set to NO");
writeToWaveform(&wfaddr, 3, 5., 6., 7.);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
// We confirm that this hasn't changed i.e. the dbProcess above did nothing
testDEq(buf, 1.0, 0.01);
testDiag("Test partial data with PBUF set to YES");
((compressRecord *)caddr.precord)->pbuf = menuYesNoYES;
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
testDEq(buf, 5.0, 0.01);
dbScanUnlock(caddr.precord);
testIocShutdownOk();
testdbCleanup();
}
void
testAIAveragePartial(void) {
double buf = 0.;
double data[5] = {1., 2., 3., 4., 5.};
/*
* Note that the fifth dbPut essentially resets the circular buffer, so the
* average is once again the average of the _first_ entry alone.
*/
double expected[5] = {1., 1.5, 2., 2.5, 5.};
long nReq = 1;
int i;
DBADDR aiaddr, caddr;
testDiag("Test 'N to 1 Average' with analog in, PBUF=YES");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "INP=ai,ALG=N to 1 Average,BALG=FIFO Buffer,NSAM=1,N=4,PBUF=YES");
eltc(0);
testIocInitOk();
eltc(1);
fetchRecordOrDie("ai", aiaddr);
fetchRecordOrDie("comp", caddr);
for (i = 0; i < 5; i++) {
dbScanLock(aiaddr.precord);
dbPut(&aiaddr, DBR_DOUBLE, &data[i], 1);
dbScanUnlock(aiaddr.precord);
dbScanLock(caddr.precord);
dbProcess(caddr.precord);
if (dbGet(&caddr, DBR_DOUBLE, &buf, NULL, &nReq, NULL))
testAbort("dbGet failed on compress record");
dbScanUnlock(caddr.precord);
testDEq(buf, expected[i], 0.01);
}
testIocShutdownOk();
testdbCleanup();
}
MAIN(compressTest)
{
testPlan(116);
testPlan(132);
testFIFOCirc();
testLIFOCirc();
testArrayAverage();
testNto1Average();
testNto1AveragePartial();
testAIAveragePartial();
testNto1LowValue();
return testDone();
}

View File

@@ -1,7 +1,13 @@
record(ai, "val") {}
record(ai, "ai") {}
record(waveform, "wf") {
field(FTVL, "DOUBLE")
field(NELM, "4")
}
record(compress, "comp") {
field(INP, "val NPP")
field(INP, "$(INP) NPP")
field(ALG, "$(ALG)")
field(PBUF,"$(PBUF=NO)")
field(BALG,"$(BALG)")
field(NSAM,"$(NSAM)")
field(N, "$(N=1)")
}

View File

@@ -123,7 +123,7 @@ MAIN(mbbioDirectTest)
testDiag("##### clear bit 31 (0x1f) #####");
putN("do%u.B1F", N, 0);
value &= ~(1<<31);
value &= ~(1u<<31u);
testN("val%d", N, value);
testmbbioRecords(N, value);

View File

@@ -0,0 +1,55 @@
/*************************************************************************\
* Copyright (c) 2022 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* This test covers tests related to invoking subrecords
*/
#include <testMain.h>
#include <dbUnitTest.h>
#include <dbAccess.h>
#include <iocsh.h>
#include "registryFunction.h"
#include <subRecord.h>
static
long subproc(subRecord *prec)
{
prec->proc = 77;
return 0;
}
void subproctest_registerRecordDeviceDriver(struct dbBase *);
MAIN(subproctest)
{
testPlan(2);
testdbPrepare();
testdbReadDatabase("subproctest.dbd", NULL, NULL);
subproctest_registerRecordDeviceDriver(pdbbase);
registryFunctionAdd("subproc", (REGISTRYFUNCTION) subproc);
testdbReadDatabase("subproctest.db", NULL, "TPRO=0");
testIocInitOk();
testDiag("===== Test that invalid link in INPA field fails a put request ======");
testdbPutFieldFail(-1, "InvalidINPARec.PROC", DBF_LONG, 1);
/* Since the put to PROC above fails, subproc() never runs
* and the value of PROC will not be set by subproc(). However,
* the testdbPutField call above goes through, so we get a partial
* result of the PROC field being left as 1. */
testdbGetFieldEqual("InvalidINPARec.PROC", DBF_LONG, 1);
testIocShutdownOk();
testdbCleanup();
return testDone();
}

View File

@@ -0,0 +1,4 @@
record(sub, "InvalidINPARec") {
field(SNAM, "subproc")
field(INPA, "nonexistent")
}

View File

@@ -54,10 +54,10 @@ static struct rtems_bsdnet_ifconfig loopback_config = {
*/
#if defined(__i386__)
extern int
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config, int attach);
rtems_ne2kpci_driver_attach (struct rtems_bsdnet_ifconfig *config);
static struct rtems_bsdnet_ifconfig ne2k_driver_config = {
"ne2", /* name */
rtems_ne2kpci_driver_attach, /* attach function */
(void*)&rtems_ne2kpci_driver_attach, /* attach function */
#if RTEMS_VERSION_INT<VERSION_INT(4,11,0,0)
&loopback_config, /* link to next interface */
#else

View File

@@ -663,7 +663,7 @@ int otoi(Char *str)
char *readable_form(int c)
{
static char rform[10];
static char rform[16];
if ( (c >= 0 && c < 32) || c >= 127 )
{

View File

@@ -29,6 +29,8 @@
extern "C" {
#endif
LIBCOM_API extern int freeListBypass;
LIBCOM_API void epicsStdCall freeListInitPvt(void **ppvt, int size, int malloc);
LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt);
LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt);

View File

@@ -26,6 +26,20 @@
#include "epicsMutex.h"
#include "freeList.h"
#include "adjustment.h"
#include "errlog.h"
#include "epicsString.h"
#include "epicsAtomic.h"
#include "epicsExport.h"
/* Bypass free list and directly call malloc() every time? */
int freeListBypass
#ifdef EPICS_FREELIST_DEBUG
= 1;
#else
= 2; /* checks environment $EPICS_FREELIST_BYPASS */
#endif
epicsExportAddress(int, freeListBypass);
typedef struct allocMem {
struct allocMem *next;
@@ -44,10 +58,25 @@ LIBCOM_API void epicsStdCall
freeListInitPvt(void **ppvt,int size,int nmalloc)
{
FREELISTPVT *pfl;
int bypass = epicsAtomicGetIntT(&freeListBypass);
if(bypass==2) {
const char *str = getenv("EPICS_FREELIST_BYPASS");
if(str && epicsStrCaseCmp(str, "YES")==0) {
bypass = 1;
} else if(!str || str[0]=='\0' || epicsStrCaseCmp(str, "NO")==0) {
bypass = 0;
} else {
errlogPrintf(ERL_WARNING " EPICS_FREELIST_BYPASS expected to be YES, NO, or empty. Not \"%s\"\n", str);
}
epicsAtomicSetIntT(&freeListBypass, bypass);
}
pfl = callocMustSucceed(1,sizeof(FREELISTPVT), "freeListInitPvt");
pfl->size = adjustToWorstCaseAlignment(size);
pfl->nmalloc = nmalloc;
if(!bypass)
pfl->nmalloc = nmalloc; /* nmalloc==0 to bypass */
pfl->head = NULL;
pfl->mallochead = NULL;
pfl->nBlocksAvailable = 0u;
@@ -60,28 +89,26 @@ LIBCOM_API void epicsStdCall
LIBCOM_API void * epicsStdCall freeListCalloc(void *pvt)
{
FREELISTPVT *pfl = pvt;
# ifdef EPICS_FREELIST_DEBUG
return callocMustSucceed(1,pfl->size,"freeList Debug Calloc");
# else
void *ptemp;
ptemp = freeListMalloc(pvt);
if(ptemp) memset((char *)ptemp,0,pfl->size);
if(!pfl->nmalloc)
ptemp = calloc(1u, pfl->size);
else if(!!(ptemp = freeListMalloc(pvt)))
memset((char *)ptemp,0,pfl->size);
return(ptemp);
# endif
}
LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt)
{
FREELISTPVT *pfl = pvt;
# ifdef EPICS_FREELIST_DEBUG
return callocMustSucceed(1,pfl->size,"freeList Debug Malloc");
# else
void *ptemp;
void **ppnext;
allocMem *pallocmem;
int i;
if(!pfl->nmalloc)
return malloc(pfl->size);
epicsMutexMustLock(pfl->lock);
ptemp = pfl->head;
if(ptemp==0) {
@@ -125,18 +152,18 @@ LIBCOM_API void * epicsStdCall freeListMalloc(void *pvt)
VALGRIND_MEMPOOL_FREE(pfl, ptemp);
VALGRIND_MEMPOOL_ALLOC(pfl, ptemp, pfl->size);
return(ptemp);
# endif
}
LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem)
{
FREELISTPVT *pfl = pvt;
# ifdef EPICS_FREELIST_DEBUG
memset ( pmem, 0xdd, pfl->size );
free(pmem);
# else
void **ppnext;
if(!pfl->nmalloc) {
free(pmem);
return;
}
VALGRIND_MEMPOOL_FREE(pvt, pmem);
VALGRIND_MEMPOOL_ALLOC(pvt, pmem, sizeof(void*));
@@ -146,7 +173,6 @@ LIBCOM_API void epicsStdCall freeListFree(void *pvt,void*pmem)
pfl->head = pmem;
pfl->nBlocksAvailable++;
epicsMutexUnlock(pfl->lock);
# endif
}
LIBCOM_API void epicsStdCall freeListCleanup(void *pvt)

View File

@@ -95,9 +95,9 @@ typedef enum {
initHookAtShutdown, /**< Start of iocShutdown() (unit tests only) */
initHookAfterCloseLinks, /**< Links disabled/deleted */
initHookAfterStopScan, /**< Scan tasks stopped */
initHookAfterStopScan, /**< Scan tasks stopped. Prior to UNRELEASED, triggered only by unittest code. */
initHookAfterStopCallback, /**< Callback tasks stopped */
initHookAfterStopLinks, /**< CA links stopped */
initHookAfterStopLinks, /**< CA links stopped. Prior to UNRELEASED, triggered only by unittest code. */
initHookBeforeFree, /**< Resource cleanup about to happen */
initHookAfterShutdown, /**< End of iocShutdown() */

View File

@@ -25,6 +25,7 @@
#include "taskwd.h"
#include "registry.h"
#include "epicsGeneralTime.h"
#include "freeList.h"
#include "libComRegister.h"
/* Register the PWD environment variable when the cd IOC shell function is
@@ -447,7 +448,7 @@ static void epicsThreadResumeCallFunc(const iocshArgBuf *args)
}
/* generalTimeReport */
static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgArgv};
static const iocshArg generalTimeReportArg0 = { "interest_level", iocshArgInt};
static const iocshArg * const generalTimeReportArgs[1] = { &generalTimeReportArg0 };
static const iocshFuncDef generalTimeReportFuncDef = {"generalTimeReport",1,generalTimeReportArgs,
"Display time providers and their priority levels"
@@ -467,7 +468,11 @@ static void installLastResortEventProviderCallFunc(const iocshArgBuf *args)
installLastResortEventProvider();
}
static iocshVarDef asCheckClientIPDef[] = { { "asCheckClientIP", iocshArgInt, 0 }, { NULL, iocshArgInt, NULL } };
static iocshVarDef comDefs[] = {
{ "asCheckClientIP", iocshArgInt, 0 },
{ "freeListBypass", iocshArgInt, 0 },
{ NULL, iocshArgInt, NULL }
};
void epicsStdCall libComRegister(void)
{
@@ -504,6 +509,7 @@ void epicsStdCall libComRegister(void)
iocshRegister(&generalTimeReportFuncDef,generalTimeReportCallFunc);
iocshRegister(&installLastResortEventProviderFuncDef, installLastResortEventProviderCallFunc);
asCheckClientIPDef[0].pval = &asCheckClientIP;
iocshRegisterVariable(asCheckClientIPDef);
comDefs[0].pval = &asCheckClientIP;
comDefs[1].pval = &freeListBypass;
iocshRegisterVariable(comDefs);
}

View File

@@ -371,6 +371,9 @@ void epicsThreadMustJoin(epicsThreadId id)
rtems_id target_tid = (rtems_id)id, self_tid;
struct taskVar *v = 0;
if(!id)
return;
rtems_task_ident (RTEMS_SELF, 0, &self_tid);
{

View File

@@ -85,7 +85,7 @@ set_first_derives(void)
k = 0;
}
if (cword & (1 << k))
if (cword & (1u << k))
{
rp = derives[j];
while ((rule = *rp++) >= 0)
@@ -152,7 +152,7 @@ closure(short int *nucleus, int n)
{
for (i = 0; i < BITS_PER_WORD; ++i)
{
if (word & (1 << i))
if (word & (1u << i))
{
itemno = rrhs[ruleno+i];
while (csp < csend && *csp < itemno)

View File

@@ -26,7 +26,7 @@ transitive_closure(unsigned int *R, int n)
while (rowj < relend)
{
if (*ccol & (1 << i))
if (*ccol & (1u << i))
{
rp = rowi;
rend = rowj + rowsize;
@@ -68,7 +68,7 @@ reflexive_transitive_closure(unsigned int *R, int n)
rp = R;
while (rp < relend)
{
*rp |= (1 << i);
*rp |= (1u << i);
if (++i >= BITS_PER_WORD)
{
i = 0;