EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024  Index 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: [Merge] ~bfrk/epics-base:address-modifiers into epics-base:7.0
From: Ben Franksen via Core-talk <core-talk at aps.anl.gov>
To: mp+381628 at code.launchpad.net
Date: Fri, 03 Apr 2020 08:35:39 -0000
Ben Franksen has proposed merging ~bfrk/epics-base:address-modifiers into epics-base:7.0 with ~bfrk/epics-base:address-modifiers-prerequisites as a prerequisite.

Requested reviews:
  EPICS Core Developers (epics-core)

For more details, see:
https://code.launchpad.net/~bfrk/epics-base/+git/epics-base/+merge/381628

As discussed on core-talk, this enables interpretation of "array address modifiers" a la "PV[s:i:e]" when using dbChannelPut or dbChannelPutField. This means the feature works for all PV links (I changed DB links to call dbChannelPut) as well as for caput.

The work is based on Dirk's dbChannelForDBLink branch (merged into 7.0) and some of my previous work (see branch address-modifiers-prerequisites).

The current implementation is /very/ liberal in that it never fails if the address modifier is out-of-bounds wrt the (target) record field. In this case the put operation is truncated to the overlap between the modifier and the target array, which may be empty (i.e. no change in the target). Similarly, if the source array has too few elements or excess elements, relative to the modifier, the request gets truncated to the smaller of these numbers.

Another design decision was that a put operation with a modifier never changes the current number of elements of the target. So you cannot currently "extend" an array using a modifier by writing beyond its current size.

All of these particular behaviors are, of course, open for debate. I can easily change any aspect as the implementation is quite simple.
-- 
Your team EPICS Core Developers is requested to review the proposed merge of ~bfrk/epics-base:address-modifiers into epics-base:7.0.
diff --git a/modules/database/src/ioc/db/Makefile b/modules/database/src/ioc/db/Makefile
index 2dc4fec..7f94cbc 100644
--- a/modules/database/src/ioc/db/Makefile
+++ b/modules/database/src/ioc/db/Makefile
@@ -15,6 +15,7 @@ INC += callback.h
 INC += dbAccess.h
 INC += dbAccessDefs.h
 INC += dbAddr.h
+INC += dbAddrModifier.h
 INC += dbBkpt.h
 INC += dbCa.h
 INC += dbChannel.h
@@ -65,6 +66,7 @@ HTMLS += $(patsubst %.dbd.pod,%.html,$(dbMenusPod))
 
 dbCore_SRCS += dbLock.c
 dbCore_SRCS += dbAccess.c
+dbCore_SRCS += dbAddrModifier.c
 dbCore_SRCS += dbBkpt.c
 dbCore_SRCS += dbChannel.c
 dbCore_SRCS += dbConstLink.c
diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
index f76c8e8..22e4a00 100644
--- a/modules/database/src/ioc/db/dbAccess.c
+++ b/modules/database/src/ioc/db/dbAccess.c
@@ -39,6 +39,7 @@
 #include "callback.h"
 #include "dbAccessDefs.h"
 #include "dbAddr.h"
+#include "dbAddrModifier.h"
 #include "dbBase.h"
 #include "dbBkpt.h"
 #include "dbCommonPvt.h"
@@ -1213,6 +1214,12 @@ cleanup:
 long dbPutField(DBADDR *paddr, short dbrType,
     const void *pbuffer, long nRequest)
 {
+    return dbPutFieldModifier(paddr, dbrType, pbuffer, nRequest, NULL);
+}
+
+long dbPutFieldModifier(DBADDR *paddr, short dbrType,
+    const void *pbuffer, long nRequest, dbAddrModifier *pmod)
+{
     long	status = 0;
     long	special  = paddr->special;
     dbFldDes	*pfldDes = paddr->pfldDes;
@@ -1230,7 +1237,7 @@ long dbPutField(DBADDR *paddr, short dbrType,
         return dbPutFieldLink(paddr, dbrType, pbuffer, nRequest);
 
     dbScanLock(precord);
-    status = dbPut(paddr, dbrType, pbuffer, nRequest);
+    status = dbPutModifier(paddr, dbrType, pbuffer, nRequest, pmod);
     if (status == 0) {
         if (paddr->pfield == &precord->proc ||
             (pfldDes->process_passive &&
@@ -1284,16 +1291,23 @@ static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest,
     return 0;
 }
 
-long dbPut(DBADDR *paddr, short dbrType,
-    const void *pbuffer, long nRequest)
+long dbPut(DBADDR *paddr, short dbrType, const void *pbuffer, long nRequest)
+{
+    return dbPutModifier(paddr, dbrType, pbuffer, nRequest, NULL);
+}
+
+long dbPutModifier(DBADDR *paddr, short dbrType,
+    const void *pbuffer, long nRequest, dbAddrModifier *pmod)
 {
     dbCommon *precord = paddr->precord;
     short field_type  = paddr->field_type;
+    short field_size  = paddr->field_size;
     long no_elements  = paddr->no_elements;
     long special      = paddr->special;
     void *pfieldsave  = paddr->pfield;
     rset *prset = dbGetRset(paddr);
     long status = 0;
+    long available_no_elements = no_elements;
     long offset;
     dbFldDes *pfldDes;
     int isValueField;
@@ -1320,27 +1334,57 @@ long dbPut(DBADDR *paddr, short dbrType,
 
     if (paddr->pfldDes->special == SPC_DBADDR &&
         prset && prset->get_array_info) {
-        long dummy;
 
-        status = prset->get_array_info(paddr, &dummy, &offset);
+        status = prset->get_array_info(paddr, &available_no_elements, &offset);
         /* paddr->pfield may be modified */
         if (status) goto done;
     } else
         offset = 0;
 
-    if (no_elements <= 1) {
-        status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
-            paddr->pfield, paddr);
-        nRequest = 1;
+    if (pmod && pmod->incr > 1) {
+        long start = pmod->start;
+        long end = pmod->end;
+        long i,j;
+        long (*putCvt) (const void *from, void *to, const dbAddr * paddr) =
+            dbFastPutConvertRoutine[dbrType][field_type];
+        short dbr_size = dbValueSize(dbrType);
+        /*
+         * With an increment > 1 it makes no sense to write beyond the
+         * current array size.
+         */
+        long n = wrapArrayIndices(&start, pmod->incr, &end, available_no_elements);
+        if (nRequest > n)
+            nRequest = n;
+        for (i=0, j=(offset+start)%no_elements; i<nRequest; i++, j=(j+pmod->incr)%no_elements) {
+            status = putCvt(pbuffer+(i*dbr_size), paddr->pfield+(j*field_size), paddr);
+            if (status)
+                break;
+        }
     } else {
-        if (no_elements < nRequest)
-            nRequest = no_elements;
-        status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
-            nRequest, no_elements, offset);
+        if (pmod) {
+            long start = pmod->start;
+            long end = pmod->end;
+            /* Note that this limits the return value to be <= no_elements */
+            long n = wrapArrayIndices(&start, pmod->incr, &end, no_elements);
+            assert(pmod->incr == 1);
+            offset = (offset + start) % no_elements;
+            if (nRequest > n)
+                nRequest = n;
+        }
+        if (no_elements <= 1) {
+            status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
+                paddr->pfield, paddr);
+            nRequest = 1;
+        } else {
+            if (no_elements < nRequest)
+                nRequest = no_elements;
+            status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
+                nRequest, no_elements, offset);
+        }
     }
 
-    /* update array info */
-    if (!status &&
+    /* update array info unless we have a modifier or writing failed */
+    if (!pmod && !status &&
         paddr->pfldDes->special == SPC_DBADDR &&
         prset && prset->put_array_info) {
         status = prset->put_array_info(paddr, nRequest);
diff --git a/modules/database/src/ioc/db/dbAccessDefs.h b/modules/database/src/ioc/db/dbAccessDefs.h
index 805dfd4..de25011 100644
--- a/modules/database/src/ioc/db/dbAccessDefs.h
+++ b/modules/database/src/ioc/db/dbAccessDefs.h
@@ -26,7 +26,9 @@
 
 #include "dbBase.h"
 #include "dbAddr.h"
+#include "dbAddrModifier.h"
 #include "recSup.h"
+#include "db_field_log.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -247,11 +249,19 @@ epicsShareFunc long dbGetField(
 epicsShareFunc long dbGet(
     struct dbAddr *,short dbrType,void *pbuffer,long *options,
     long *nRequest,void *pfl);
+
 epicsShareFunc long dbPutField(
     struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
 epicsShareFunc long dbPut(
     struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
 
+epicsShareFunc long dbPutFieldModifier(
+    struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
+    dbAddrModifier *pmod);
+epicsShareFunc long dbPutModifier(
+    struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
+    dbAddrModifier *pmod);
+
 typedef void(*SPC_ASCALLBACK)(struct dbCommon *);
 /*dbSpcAsRegisterCallback called by access security */
 epicsShareFunc void dbSpcAsRegisterCallback(SPC_ASCALLBACK func);
diff --git a/modules/database/src/ioc/db/dbAddrModifier.c b/modules/database/src/ioc/db/dbAddrModifier.c
new file mode 100644
index 0000000..4f9e951
--- /dev/null
+++ b/modules/database/src/ioc/db/dbAddrModifier.c
@@ -0,0 +1,16 @@
+long wrapArrayIndices(long *start, const long increment,
+    long *end, const long no_elements)
+{
+    if (*start < 0) *start = no_elements + *start;
+    if (*start < 0) *start = 0;
+    if (*start > no_elements) *start = no_elements;
+
+    if (*end < 0) *end = no_elements + *end;
+    if (*end < 0) *end = 0;
+    if (*end >= no_elements) *end = no_elements - 1;
+
+    if (*end - *start >= 0)
+        return 1 + (*end - *start) / increment;
+    else
+        return 0;
+}
diff --git a/modules/database/src/ioc/db/dbAddrModifier.h b/modules/database/src/ioc/db/dbAddrModifier.h
new file mode 100644
index 0000000..0df63e8
--- /dev/null
+++ b/modules/database/src/ioc/db/dbAddrModifier.h
@@ -0,0 +1,27 @@
+#ifndef DBADDRMODIFIER_H
+#define DBADDRMODIFIER_H
+
+#include "shareLib.h"
+
+typedef struct {
+    long start;
+    long incr;
+    long end;
+} dbAddrModifier;
+
+/** @brief The address modifier that modifies nothing. */
+#define defaultAddrModifier (struct dbAddrModifier){0,1,-1}
+
+/** @brief Given a number of elements, convert negative start and end indices
+ *         to non-negative ones by counting from the end of the array.
+ *
+ * @param start         Pointer to possibly negative start index.
+ * @param increment     Increment.
+ * @param end           Pointer to possibly negative end index.
+ * @param no_elements   Number of array elements.
+ * @return              Final number of elements.
+ */
+epicsShareFunc long wrapArrayIndices(long *start, const long increment,
+    long *end, const long no_elements);
+
+#endif /* DBADDRMODIFIER_H */
diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
index d0db612..60be5a5 100644
--- a/modules/database/src/ioc/db/dbChannel.c
+++ b/modules/database/src/ioc/db/dbChannel.c
@@ -50,12 +50,14 @@ typedef struct parseContext {
 
 static void *dbChannelFreeList;
 static void *chFilterFreeList;
+static void *dbAddrModifierFreeList;
 
 void dbChannelExit(void)
 {
     freeListCleanup(dbChannelFreeList);
     freeListCleanup(chFilterFreeList);
-    dbChannelFreeList = chFilterFreeList = NULL;
+    freeListCleanup(dbAddrModifierFreeList);
+    dbChannelFreeList = chFilterFreeList = dbAddrModifierFreeList = NULL;
 }
 
 void dbChannelInit (void)
@@ -65,6 +67,7 @@ void dbChannelInit (void)
 
     freeListInitPvt(&dbChannelFreeList,  sizeof(dbChannel), 128);
     freeListInitPvt(&chFilterFreeList,  sizeof(chFilter), 64);
+    freeListInitPvt(&dbAddrModifierFreeList,  sizeof(dbAddrModifier), 128);
     db_init_event_freelists();
 }
 
@@ -401,9 +404,19 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn
     pname++;
     *ppnext = pname;
 
+    if (!chan->paddrModifier) {
+        chan->paddrModifier = freeListCalloc(dbAddrModifierFreeList);
+        if (!chan->paddrModifier) {
+            status = S_db_noMemory;
+            goto finish;
+        }
+    }
+    chan->paddrModifier->start = start;
+    chan->paddrModifier->incr = incr;
+    chan->paddrModifier->end = end;
+
     plug = dbFindFilter("arr", 3);
     if (!plug) {
-        status = S_dbLib_fieldNotFound;
         goto finish;
     }
 
@@ -700,13 +713,15 @@ long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
 long dbChannelPut(dbChannel *chan, short type, const void *pbuffer,
         long nRequest)
 {
-    return dbPut(&chan->addr, type, pbuffer, nRequest);
+    return dbPutModifier(&chan->addr, type, pbuffer, nRequest,
+        chan->paddrModifier);
 }
 
 long dbChannelPutField(dbChannel *chan, short type, const void *pbuffer,
         long nRequest)
 {
-    return dbPutField(&chan->addr, type, pbuffer, nRequest);
+    return dbPutFieldModifier(&chan->addr, type, pbuffer, nRequest,
+        chan->paddrModifier);
 }
 
 void dbChannelShow(dbChannel *chan, int level, const unsigned short indent)
@@ -760,6 +775,8 @@ void dbChannelDelete(dbChannel *chan)
         freeListFree(chFilterFreeList, filter);
     }
     free((char *) chan->name);
+    if (chan->paddrModifier)
+        freeListFree(dbAddrModifierFreeList, chan->paddrModifier);
     freeListFree(dbChannelFreeList, chan);
 }
 
diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
index 2dc929b..0981c1c 100644
--- a/modules/database/src/ioc/db/dbChannel.h
+++ b/modules/database/src/ioc/db/dbChannel.h
@@ -23,6 +23,7 @@
 #include "errMdef.h"
 #include "shareLib.h"
 #include "db_field_log.h"
+#include "dbAddrModifier.h"
 #include "dbEvent.h"
 
 #ifdef __cplusplus
@@ -55,6 +56,8 @@ typedef long fastConvert(void *from, void *to, const dbAddr *paddr);
 typedef struct dbChannel {
     const char *name;
     dbAddr addr;              /* address structure for record/field */
+    dbAddrModifier *paddrModifier;
+                              /* optional: which indices are targeted */
     long  final_no_elements;  /* final number of elements (arrays) */
     short final_field_size;   /* final size of element */
     short final_type;         /* final type of database field */
diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
index ed57447..bfff51b 100644
--- a/modules/database/src/ioc/db/dbDbLink.c
+++ b/modules/database/src/ioc/db/dbDbLink.c
@@ -329,9 +329,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
     struct pv_link *ppv_link = &plink->value.pv_link;
     dbChannel *chan = linkChannel(plink);
     struct dbCommon *psrce = plink->precord;
-    DBADDR *paddr = &chan->addr;
     dbCommon *pdest = dbChannelRecord(chan);
-    long status = dbPut(paddr, dbrType, pbuffer, nRequest);
+    long status = dbChannelPut(chan, dbrType, pbuffer, nRequest);
 
     recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
         psrce->nsev);
diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c
index dc54529..14f2fb4 100644
--- a/modules/database/src/std/filters/arr.c
+++ b/modules/database/src/std/filters/arr.c
@@ -73,23 +73,6 @@ static void freeArray(db_field_log *pfl)
     }
 }
 
-static long wrapArrayIndices(long *start, const long increment, long *end,
-    const long no_elements)
-{
-    if (*start < 0) *start = no_elements + *start;
-    if (*start < 0) *start = 0;
-    if (*start > no_elements) *start = no_elements;
-
-    if (*end < 0) *end = no_elements + *end;
-    if (*end < 0) *end = 0;
-    if (*end >= no_elements) *end = no_elements - 1;
-
-    if (*end - *start >= 0)
-        return 1 + (*end - *start) / increment;
-    else
-        return 0;
-}
-
 static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
 {
     myStruct *my = (myStruct*) pvt;
diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile
index 3d7cff9..909bb4e 100644
--- a/modules/database/test/std/rec/Makefile
+++ b/modules/database/test/std/rec/Makefile
@@ -15,6 +15,7 @@ USR_CPPFLAGS += -DUSE_TYPED_DSET
 
 TESTLIBRARY = dbRecStdTest
 
+dbRecStdTest_SRCS += arroutRecord.c
 dbRecStdTest_SRCS += asTestLib.c
 dbRecStdTest_LIBS += dbRecStd dbCore ca Com
 
@@ -23,6 +24,7 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
 TARGETS += $(COMMON_DIR)/recTestIoc.dbd
 DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
 recTestIoc_DBD = base.dbd
+recTestIoc_DBD += arroutRecord.dbd
 TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
 
 testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
@@ -163,6 +165,13 @@ testHarness_SRCS += linkFilterTest.c
 TESTFILES += ../linkFilterTest.db
 TESTS += linkFilterTest
 
+TESTPROD_HOST += addrModifierTest
+addrModifierTest_SRCS += addrModifierTest.c
+addrModifierTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
+testHarness_SRCS += addrModifierTest.c
+TESTFILES += ../addrModifierTest.db
+TESTS += addrModifierTest
+
 # dbHeader* is only a compile test
 # no need to actually run
 TESTPROD += dbHeaderTest
@@ -196,5 +205,7 @@ endif
 
 include $(TOP)/configure/RULES
 
+arroutRecord$(DEP): $(COMMON_DIR)/arroutRecord.h
+
 rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
 	$(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES)
diff --git a/modules/database/test/std/rec/addrModifierTest.c b/modules/database/test/std/rec/addrModifierTest.c
new file mode 100644
index 0000000..d555b91
--- /dev/null
+++ b/modules/database/test/std/rec/addrModifierTest.c
@@ -0,0 +1,247 @@
+/*************************************************************************\
+* Copyright (c) 2020 Dirk Zimoch
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include <string.h>
+
+#include "dbAccess.h"
+#include "devSup.h"
+#include "alarm.h"
+#include "dbUnitTest.h"
+#include "errlog.h"
+#include "epicsThread.h"
+
+#include "longinRecord.h"
+
+#include "testMain.h"
+
+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
+
+static void startTestIoc(const char *dbfile)
+{
+    testdbPrepare();
+    testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
+    recTestIoc_registerRecordDeviceDriver(pdbbase);
+    testdbReadDatabase(dbfile, NULL, NULL);
+
+    eltc(0);
+    testIocInitOk();
+    eltc(1);
+}
+
+struct pv {
+    const char *name;
+    long count;
+    double values[4];
+};
+
+static void expectProcSuccess(struct pv *pv)
+{
+    char fieldname[20];
+    testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
+    testDiag("expecting success from %s", pv->name);
+    sprintf(fieldname, "%s.PROC", pv->name);
+    testdbPutFieldOk(fieldname, DBF_LONG, 1);
+    sprintf(fieldname, "%s.SEVR", pv->name);
+    testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
+    sprintf(fieldname, "%s.STAT", pv->name);
+    testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
+}
+
+#if 0
+static void expectProcFailure(struct pv *pv)
+{
+    char fieldname[20];
+    testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
+    testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, pv->name);
+    sprintf(fieldname, "%s.PROC", pv->name);
+    testdbPutFieldFail(S_db_badField, fieldname, DBF_LONG, 1);
+    sprintf(fieldname, "%s.SEVR", pv->name);
+    testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
+    sprintf(fieldname, "%s.STAT", pv->name);
+    testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
+}
+#endif
+
+static double initial[] = {0,1,2,3,4,5,6,7,8,9};
+static double buf[10];
+
+static void changeRange(struct pv *pv, long start, long incr, long end)
+{
+    char linkstring[60];
+    char pvstring[10];
+
+    if (incr)
+        sprintf(linkstring, "tgt.[%ld:%ld:%ld]", start, incr, end);
+    else if (end)
+        sprintf(linkstring, "tgt.[%ld:%ld]", start, end);
+    else
+        sprintf(linkstring, "tgt.[%ld]", start);
+    testDiag("modifying %s.OUT link: %s", pv->name, linkstring);
+    sprintf(pvstring, "%s.OUT", pv->name);
+    testdbPutFieldOk(pvstring, DBF_STRING, linkstring);
+}
+
+static void expectRange(double *values, long start, long incr, long end)
+{
+    int i,j;
+    if (!incr)
+        incr = 1;
+    for (i=0; i<10; i++)
+        buf[i] = initial[i];
+    for (i=0, j=start; j<=end; i++, j+=incr)
+        buf[j] = values[i];
+    testdbGetFieldEqual("tgt.NORD", DBF_LONG, 8);
+    testdbGetFieldEqual("tgt.SEVR", DBF_LONG, NO_ALARM);
+    testdbGetFieldEqual("tgt.STAT", DBF_LONG, NO_ALARM);
+    testdbGetArrFieldEqual("tgt.VAL", DBF_DOUBLE, 10, 8, buf);
+}
+
+#if 0
+static void expectEmptyArray(void)
+{
+    /* empty arrays are now allowed at the moment */
+    testDiag("expecting empty array");
+    testdbGetFieldEqual("tgt.NORD", DBF_LONG, 0);
+}
+#endif
+
+struct pv ao_pv = {"ao",1,{20,0,0,0}};
+struct pv src_pv = {"src",4,{30,40,50,60}};
+struct pv *ao = &ao_pv;
+struct pv *src = &src_pv;
+
+static double pini_values[] = {20,30,40,50};
+
+#define expectEmptyRange() expectRange(0,0,0,-1)
+
+MAIN(addrModifierTest)
+{
+    testPlan(205);
+    startTestIoc("addrModifierTest.db");
+
+    testdbGetFieldEqual("src.NORD", DBF_LONG, 4);
+    testDiag("PINI");
+    expectRange(pini_values,2,1,5);
+
+    testDiag("after processing");
+    testdbPutFieldOk("ao.PROC", DBF_LONG, 1);
+    testdbPutFieldOk("src.PROC", DBF_LONG, 1);
+    expectRange(pini_values,2,1,5);
+
+    testDiag("after processing target record");
+    testdbPutFieldOk("tgt.PROC", DBF_LONG, 1);
+    expectRange(pini_values,2,1,5);
+
+    testDiag("modify range");
+
+    changeRange(ao,3,0,0);
+    expectProcSuccess(ao);
+    expectRange(ao->values,3,1,3);
+
+    changeRange(src,4,0,7);
+    expectProcSuccess(src);
+    expectRange(src->values,4,1,7);
+
+    testDiag("put more than available");
+
+    changeRange(ao,3,0,6);
+    expectProcSuccess(ao);
+    expectRange(ao->values,3,1,3); /* clipped range */
+
+    changeRange(src,3,0,9);
+    expectProcSuccess(src);
+    expectRange(src->values,3,1,6); /* clipped range */
+
+    testDiag("backward range");
+
+    changeRange(ao,5,0,3);
+    expectProcSuccess(ao);
+    expectEmptyRange();
+
+    changeRange(src,5,0,3);
+    expectProcSuccess(src);
+    expectEmptyRange();
+
+    testDiag("step 2");
+
+    changeRange(ao,1,2,6);
+    expectProcSuccess(ao);
+    expectRange(ao->values,1,1,1); /* clipped range */
+
+    changeRange(src,1,2,6);
+    expectProcSuccess(src);
+    expectRange(src->values,1,2,5);
+
+    testDiag("range start beyond tgt.NORD");
+
+    changeRange(ao,8,0,0);
+    expectProcSuccess(ao);
+    expectEmptyRange();
+
+    changeRange(src,8,0,9);
+    expectProcSuccess(src);
+    expectEmptyRange();
+
+    testDiag("range end beyond tgt.NORD");
+
+    changeRange(ao,3,0,9);
+    expectProcSuccess(ao);
+    expectRange(ao->values,3,1,3); /* clipped range */
+
+    changeRange(src,3,0,9);
+    expectProcSuccess(src);
+    expectRange(src->values,3,1,6); /* clipped range */
+
+    testDiag("range start beyond tgt.NELM");
+
+    changeRange(ao,11,0,12);
+    expectProcSuccess(ao);
+    expectEmptyRange();
+
+    changeRange(src,11,0,12);
+    expectProcSuccess(src);
+    expectEmptyRange();
+
+    testDiag("range end beyond tgt.NELM");
+
+    changeRange(src,4,0,12);
+    expectProcSuccess(src);
+    expectRange(src->values,4,1,7); /* clipped range */
+
+    testDiag("single value beyond tgt.NORD");
+
+    changeRange(ao,8,0,0);
+    expectProcSuccess(ao);
+    expectEmptyRange();
+
+    changeRange(src,8,0,0);
+    expectProcSuccess(src);
+    expectEmptyRange();
+
+    testDiag("single value");
+
+    changeRange(ao,5,0,0);
+    expectProcSuccess(ao);
+    expectRange(ao->values,5,1,5);
+
+    changeRange(src,5,0,0);
+    expectProcSuccess(src);
+    expectRange(src->values,5,1,5);
+
+    testDiag("single values beyond tgt.NELM");
+
+    changeRange(ao,12,0,0);
+    expectProcSuccess(ao);
+    expectEmptyRange();
+
+    changeRange(src,12,0,0);
+    expectProcSuccess(src);
+    expectEmptyRange();
+
+    testIocShutdownOk();
+    testdbCleanup();
+    return testDone();
+}
diff --git a/modules/database/test/std/rec/addrModifierTest.db b/modules/database/test/std/rec/addrModifierTest.db
new file mode 100644
index 0000000..ed28a80
--- /dev/null
+++ b/modules/database/test/std/rec/addrModifierTest.db
@@ -0,0 +1,24 @@
+record(arrout, "tgt") {
+    field(NELM, "10")
+    field(FTVL, "SHORT")
+    field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
+    field(PINI, "YES")
+}
+record(ao, "ao") {
+    field(OUT, "tgt.[2]")
+    field(DOL, 20)
+    field(PINI, "YES")
+}
+record(arrout, "src") {
+    field(NELM, "5")
+    field(FTVL, "DOUBLE")
+    field(DOL, [30.0, 40.0, 50.0, 60.0])
+    field(OUT, "tgt.[3:5]")
+    field(PINI, "YES")
+}
+record(arrout, "reset") {
+    field(NELM, "10")
+    field(FTVL, "SHORT")
+    field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
+    field(OUT, "tgt PP")
+}
diff --git a/modules/database/test/std/rec/arroutRecord.c b/modules/database/test/std/rec/arroutRecord.c
new file mode 100644
index 0000000..61c7680
--- /dev/null
+++ b/modules/database/test/std/rec/arroutRecord.c
@@ -0,0 +1,169 @@
+/*************************************************************************\
+* 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.
+\*************************************************************************/
+
+/* arroutRecord.c - minimal array output record for test purposes: no processing */
+
+/* adapted from: arrRecord.c
+ *
+ *  Author: Ralph Lange <Ralph.Lange at bessy.de>
+ *
+ * vaguely implemented like parts of recWaveform.c by Bob Dalesio
+ *
+ */
+
+#include <stdio.h>
+
+#include "alarm.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 "arroutRecord.h"
+#undef  GEN_SIZE_OFFSET
+#include "epicsExport.h"
+
+/* Create RSET - Record Support Entry Table*/
+#define report NULL
+#define initialize NULL
+static long init_record(struct dbCommon *, int);
+static long process(struct dbCommon *);
+#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 arroutRSET = {
+        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, arroutRSET);
+
+static long init_record(struct dbCommon *pcommon, int pass)
+{
+    struct arroutRecord *prec = (struct arroutRecord *)pcommon;
+
+    if (pass == 0) {
+        if (prec->nelm <= 0)
+            prec->nelm = 1;
+        if (prec->ftvl > DBF_ENUM)
+            prec->ftvl = DBF_UCHAR;
+        prec->val = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl),
+            "arr calloc failed");
+        return 0;
+    } else {
+        /* copied from devWfSoft.c */
+        long nelm = prec->nelm;
+        long status = dbLoadLinkArray(&prec->dol, prec->ftvl, prec->val, &nelm);
+
+        if (!status && nelm > 0) {
+            prec->nord = nelm;
+            prec->udf = FALSE;
+        }
+        else
+            prec->nord = 0;
+        return status;
+    }
+}
+
+static long process(struct dbCommon *pcommon)
+{
+    struct arroutRecord *prec = (struct arroutRecord *)pcommon;
+    long status = 0;
+
+    prec->pact = TRUE;
+    /* read DOL */
+    if (!dbLinkIsConstant(&prec->dol)) {
+        long nReq = prec->nelm;
+
+        status = dbGetLink(&prec->dol, prec->ftvl, prec->val, 0, &nReq);
+        if (status) {
+            recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
+        } else {
+            prec->nord = nReq;
+        }
+    }
+    /* soft "device support": write OUT */
+    status = dbPutLink(&prec->out, prec->ftvl, prec->val, prec->nord);
+    if (status) {
+        recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
+    }
+    recGblGetTimeStamp(prec);
+    recGblResetAlarms(prec);
+    recGblFwdLink(prec);
+    prec->pact = FALSE;
+    return status;
+}
+
+static long cvt_dbaddr(DBADDR *paddr)
+{
+    arroutRecord *prec = (arroutRecord *) paddr->precord;
+    int fieldIndex = dbGetFieldIndex(paddr);
+
+    if (fieldIndex == arroutRecordVAL) {
+        paddr->pfield = prec->val;
+        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)
+{
+    arroutRecord *prec = (arroutRecord *) paddr->precord;
+
+    *no_elements = prec->nord;
+    *offset = prec->off;
+
+    return 0;
+}
+
+static long put_array_info(DBADDR *paddr, long nNew)
+{
+    arroutRecord *prec = (arroutRecord *) paddr->precord;
+
+    prec->nord = nNew;
+    if (prec->nord > prec->nelm)
+        prec->nord = prec->nelm;
+
+    return 0;
+}
diff --git a/modules/database/test/std/rec/arroutRecord.dbd b/modules/database/test/std/rec/arroutRecord.dbd
new file mode 100644
index 0000000..2c99e64
--- /dev/null
+++ b/modules/database/test/std/rec/arroutRecord.dbd
@@ -0,0 +1,35 @@
+include "menuGlobal.dbd"
+include "menuConvert.dbd"
+include "menuScan.dbd"
+recordtype(arrout) {
+  include "dbCommon.dbd"
+  field(VAL, DBF_NOACCESS) {
+    prompt("Value")
+    special(SPC_DBADDR)
+    pp(TRUE)
+    extra("void *val")
+  }
+  field(OUT, DBF_OUTLINK) {
+    prompt("Output Link")
+  }
+  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(DOL, DBF_INLINK) {
+    prompt("Desired Output Link")
+  }
+}

Replies:
Re: [Merge] ~bfrk/epics-base:address-modifiers into epics-base:7.0 Ben Franksen via Core-talk

Navigate by Date:
Prev: Re: [Merge] ~bfrk/epics-base:remove-dbfl_type_rec into epics-base:7.0 Ben Franksen via Core-talk
Next: Build completed: EPICS Base base-3.15-577 AppVeyor via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
Navigate by Thread:
Prev: Jenkins build is back to stable : epics-base-3.15-win32-test #287 APS Jenkins via Core-talk
Next: Re: [Merge] ~bfrk/epics-base:address-modifiers into epics-base:7.0 Ben Franksen via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <20202021  2022  2023  2024 
ANJ, 16 Apr 2020 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·