From c1ae5064b8199fc21f4d9779b6eb58e9fabea75b Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Fri, 14 Feb 2020 10:10:58 +0000 Subject: [PATCH 001/323] Added OOPT to longout record --- modules/database/src/std/rec/longoutRecord.c | 62 ++++++++++++++++++- .../src/std/rec/longoutRecord.dbd.pod | 25 ++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index f0e2252bd..f5bb4a292 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -85,6 +85,7 @@ static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); static long writeValue(longoutRecord *prec); static void convert(longoutRecord *prec, epicsInt32 value); +static long conditional_write(longoutRecord *prec); static long init_record(struct dbCommon *pcommon, int pass) { @@ -119,6 +120,7 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->mlst = prec->val; prec->alst = prec->val; prec->lalm = prec->val; + prec->oval = prec->val; return 0; } @@ -210,6 +212,15 @@ static long special(DBADDR *paddr, int after) recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); return(0); } + + /* If OOPT is "on change" we force a write operation */ + if (dbGetFieldIndex(paddr) == longoutRecordOUT) { + if ((!after) && (prec->oopt == longoutOOPT_On_Change)) + prec->oopt = longoutOOPT_Write_Once_Then_On_Change; + return 0; + } + + default: recGblDbaddrError(S_db_badChoice, paddr, "longout: special"); return(S_db_badChoice); @@ -381,7 +392,10 @@ static void monitor(longoutRecord *prec) static long writeValue(longoutRecord *prec) { +<<<<<<< HEAD longoutdset *pdset = (longoutdset *) prec->dset; +======= +>>>>>>> 2b7ca9598 (Added OOPT to longout record) long status = 0; if (!prec->pact) { @@ -391,7 +405,7 @@ static long writeValue(longoutRecord *prec) switch (prec->simm) { case menuYesNoNO: - status = pdset->write_longout(prec); + status = conditional_write(prec); break; case menuYesNoYES: { @@ -428,3 +442,49 @@ static void convert(longoutRecord *prec, epicsInt32 value) } prec->val = value; } + +/* Evaluate OOPT field to perform the write operation */ +static long conditional_write(longoutRecord *prec) +{ + struct longoutdset *pdset = (struct longoutdset *) prec->dset; + long status = 0; + int doDevSupWrite = 0; + + switch (prec->oopt) + { + case longoutOOPT_On_Change: + doDevSupWrite = (prec->val != prec->oval); + break; + + case longoutOOPT_Write_Once_Then_On_Change: + prec->oopt = longoutOOPT_On_Change; + case longoutOOPT_Every_Time: + doDevSupWrite = 1; + break; + + case longoutOOPT_When_Zero: + doDevSupWrite = (prec->val == 0); + break; + + case longoutOOPT_When_Non_zero: + doDevSupWrite = (prec->val != 0); + break; + + case longoutOOPT_Transition_To_Zero: + doDevSupWrite = ((prec->val == 0)&&(prec->oval != 0)); + break; + + case longoutOOPT_Transition_To_Non_zero: + doDevSupWrite = ((prec->val != 0)&&(prec->oval == 0)); + break; + + default: + break; + } + + if (doDevSupWrite) + status = pdset->write_longout(prec); + + prec->oval = prec->val; + return status; +} \ No newline at end of file diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 60037a62f..3f11769ba 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -20,6 +20,17 @@ limits. =cut +menu(longoutOOPT) { + choice(longoutOOPT_Every_Time,"Every Time") + choice(longoutOOPT_On_Change,"On Change") + choice(longoutOOPT_When_Zero,"When Zero") + choice(longoutOOPT_When_Non_zero,"When Non-zero") + choice(longoutOOPT_Transition_To_Zero,"Transition To Zero") + choice(longoutOOPT_Transition_To_Non_zero,"Transition To Non-zero") + choice(longoutOOPT_Write_Once_Then_On_Change, "Write Once Then On Change") +} + + recordtype(longout) { =head2 Parameter Fields @@ -93,6 +104,7 @@ and database links. } field(OUT,DBF_OUTLINK) { prompt("Output Specification") + special(SPC_MOD) promptgroup("50 - Output") interest(1) } @@ -373,6 +385,19 @@ for more information on simulation mode and its fields. promptgroup("50 - Output") interest(2) } + field(OVAL,DBF_LONG) { + prompt("Last Value Written") + promptgroup("50 - Output") + asl(ASL1) + special(SPC_NOMOD) + } + field(OOPT,DBF_MENU) { + prompt("Output Execute Opt") + promptgroup("50 - Output") + interest(1) + menu(longoutOOPT) + initial("0") + } =begin html From 4e7a18bfb40be89b848cc00ff3fdc00335b450a6 Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Mon, 17 Feb 2020 22:14:46 +0000 Subject: [PATCH 002/323] Adding test routines for longout record with OOPT field --- modules/database/test/std/rec/Makefile | 7 + modules/database/test/std/rec/longoutTest.c | 214 +++++++++++++++++++ modules/database/test/std/rec/longoutTest.db | 17 ++ 3 files changed, 238 insertions(+) create mode 100644 modules/database/test/std/rec/longoutTest.c create mode 100644 modules/database/test/std/rec/longoutTest.db diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index e8c546442..7fd6c9dfc 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -87,6 +87,13 @@ testHarness_SRCS += seqTest.c TESTFILES += ../seqTest.db TESTS += seqTest +TESTPROD_HOST += longoutTest +longoutTest_SRCS += longoutTest.c +longoutTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += longoutTest.c +TESTFILES += ../longoutTest.db +TESTS += longoutTest + TARGETS += $(COMMON_DIR)/asTestIoc.dbd DBDDEPENDS_FILES += asTestIoc.dbd$(DEP) asTestIoc_DBD += base.dbd diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c new file mode 100644 index 000000000..60da091b2 --- /dev/null +++ b/modules/database/test/std/rec/longoutTest.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2020 Joao Paulo Martins +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "dbLock.h" +#include "errlog.h" +#include "dbAccess.h" +#include "epicsMath.h" + +#include "longoutRecord.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_oopt_everytime(void){ + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write the same value two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* write two times with different values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed 4 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 4.0); + + // number of tests = 6 +} + +static void test_oopt_onchange(void){ + /* change OOPT to On Change */ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write the same value two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was processed only once */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + /* write two times with different values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed 1 + 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 3.0); + + //number of tests 8 +} + +static void test_oopt_whenzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write zero two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was processed twice */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + /* write two times with non-zero values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was still processed 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 8 +} + +static void test_oopt_whennonzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Non_zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write zero two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* write two times with non-zero values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was still processed 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 8 +} + +static void test_oopt_when_transition_zero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write non-zero then zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + /* write another transition to zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + + /* Test if the counter was processed once more */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 9 +} + +static void test_oopt_when_transition_nonzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Non_zero); + + /* write non-zero to start fresh */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write non-zero then zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* write a transition to non-zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + //number of tests 8 +} + +static void test_changing_out_field(void){ + /* change OOPT to On Change */ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); + + /* write an initial value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter2.VAL", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* change the OUT link to another counter */ + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter2.B PP"); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); +} + +MAIN(longoutTest) { + + testPlan(6+8+8+8+9+8+11); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("longoutTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_oopt_everytime(); + test_oopt_onchange(); + test_oopt_whenzero(); + test_oopt_whennonzero(); + test_oopt_when_transition_zero(); + test_oopt_when_transition_nonzero(); + test_changing_out_field(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db new file mode 100644 index 000000000..93428a070 --- /dev/null +++ b/modules/database/test/std/rec/longoutTest.db @@ -0,0 +1,17 @@ +record(calc, "counter") { + field(INPA, "counter") + field(CALC, "A+1") + field(SCAN, "Passive") +} + +record(calc, "counter2") { + field(INPA, "counter2") + field(CALC, "A+1") + field(SCAN, "Passive") +} + +record(longout, "longout_rec") { + field(VAL, "0") + field(OUT, "counter.B PP") + field(PINI, "YES") +} From f4d94b9725278f21bcfb0d6a38f7ab670330992c Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Tue, 9 Mar 2021 16:26:53 +0100 Subject: [PATCH 003/323] Longout OOPT field refactoring and updated documentation; Release notes additions --- documentation/RELEASE_NOTES.md | 7 + modules/database/src/std/rec/longoutRecord.c | 37 +++--- .../src/std/rec/longoutRecord.dbd.pod | 62 +++++++-- modules/database/test/std/rec/longoutTest.c | 124 ++++++++++++------ modules/database/test/std/rec/longoutTest.db | 10 +- 5 files changed, 169 insertions(+), 71 deletions(-) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 587b52448..213370363 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -16,6 +16,13 @@ should also be read to understand what has changed since earlier releases. ## Changes made on the 7.0 branch since 7.0.5 +### Extend longout conditions to write the OUT link (OOPT field) + +The longout record has now the capacity to condition its output write operation to +different options, using the OOPT field (similar to calcout record). This is +the first output record to have such feature as a result of the +[Make output records only write on change](https://bugs.launchpad.net/epics-base/+bug/1398215) +issue on Launchpad. ----- diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index f5bb4a292..e60e01c79 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -81,6 +81,9 @@ rset longoutRSET={ }; epicsExportAddress(rset,longoutRSET); +#define OUT_LINK_UNCHANGED 0 +#define OUT_LINK_CHANGED 1 + static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); static long writeValue(longoutRecord *prec); @@ -120,7 +123,8 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->mlst = prec->val; prec->alst = prec->val; prec->lalm = prec->val; - prec->oval = prec->val; + prec->pval = prec->val; + prec->outpvt = OUT_LINK_UNCHANGED; return 0; } @@ -213,14 +217,13 @@ static long special(DBADDR *paddr, int after) return(0); } - /* If OOPT is "on change" we force a write operation */ + /* Detect an output link re-direction (change)*/ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if ((!after) && (prec->oopt == longoutOOPT_On_Change)) - prec->oopt = longoutOOPT_Write_Once_Then_On_Change; - return 0; + if (!after) + prec->outpvt = OUT_LINK_CHANGED; + return(0); } - default: recGblDbaddrError(S_db_badChoice, paddr, "longout: special"); return(S_db_badChoice); @@ -392,10 +395,7 @@ static void monitor(longoutRecord *prec) static long writeValue(longoutRecord *prec) { -<<<<<<< HEAD longoutdset *pdset = (longoutdset *) prec->dset; -======= ->>>>>>> 2b7ca9598 (Added OOPT to longout record) long status = 0; if (!prec->pact) { @@ -435,7 +435,7 @@ static long writeValue(longoutRecord *prec) static void convert(longoutRecord *prec, epicsInt32 value) { - /* check drive limits */ + /* check drive limits */ if(prec->drvh > prec->drvl) { if (value > prec->drvh) value = prec->drvh; else if (value < prec->drvl) value = prec->drvl; @@ -453,11 +453,15 @@ static long conditional_write(longoutRecord *prec) switch (prec->oopt) { case longoutOOPT_On_Change: - doDevSupWrite = (prec->val != prec->oval); + /* Forces a write op if a change in the OUT field is detected */ + if ((prec->ooch == menuYesNoYES) && (prec->outpvt == OUT_LINK_CHANGED)) { + doDevSupWrite = 1; + } else { + /* Only write if value is different from the previous one */ + doDevSupWrite = (prec->val != prec->pval); + } break; - case longoutOOPT_Write_Once_Then_On_Change: - prec->oopt = longoutOOPT_On_Change; case longoutOOPT_Every_Time: doDevSupWrite = 1; break; @@ -471,11 +475,11 @@ static long conditional_write(longoutRecord *prec) break; case longoutOOPT_Transition_To_Zero: - doDevSupWrite = ((prec->val == 0)&&(prec->oval != 0)); + doDevSupWrite = ((prec->val == 0)&&(prec->pval != 0)); break; case longoutOOPT_Transition_To_Non_zero: - doDevSupWrite = ((prec->val != 0)&&(prec->oval == 0)); + doDevSupWrite = ((prec->val != 0)&&(prec->pval == 0)); break; default: @@ -485,6 +489,7 @@ static long conditional_write(longoutRecord *prec) if (doDevSupWrite) status = pdset->write_longout(prec); - prec->oval = prec->val; + prec->pval = prec->val; + prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ return status; } \ No newline at end of file diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 3f11769ba..fe596c118 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -27,10 +27,8 @@ menu(longoutOOPT) { choice(longoutOOPT_When_Non_zero,"When Non-zero") choice(longoutOOPT_Transition_To_Zero,"Transition To Zero") choice(longoutOOPT_Transition_To_Non_zero,"Transition To Non-zero") - choice(longoutOOPT_Write_Once_Then_On_Change, "Write Once Then On Change") } - recordtype(longout) { =head2 Parameter Fields @@ -82,7 +80,45 @@ DTYP field must then specify the C<<< Soft Channel >>> device support routine. See L
for information on the format of hardware addresses and database links. -=fields OUT, DTYP +=fields OUT, DTYP, OOPT, OOCH + +=head4 Menu longoutOOPT + +The OOPT field determines the condition that causes the output link to be +written to. It's a menu field that has six choices: + +=menu longoutOOPT + +=over + +=item * +C -- write output every time record is processed. (DEFAULT) + +=item * +C -- write output every time VAL changes, i.e., every time the +result of the expression changes. + +=item * +C -- when record is processed, write output if VAL is zero. + +=item * +C -- when record is processed, write output if VAL is +non-zero. + +=item * +C -- when record is processed, write output only if VAL +is zero and the last value was non-zero. + +=item * +C -- when record is processed, write output only if +VAL is non-zero and last value was zero. + +=back + +=head4 Changes in OUT field when OOPT = On Change + +The OOCH field determines if a change in OUT field should cause a write operation +even when the value is the same and OOPT = On Change. By default, OOCH is set to YES. =cut @@ -372,7 +408,7 @@ for more information on simulation mode and its fields. prompt("Sim. Mode Private") special(SPC_NOMOD) interest(4) - extra("epicsCallback *simpvt") + extra("epicsCallback *simpvt") } field(IVOA,DBF_MENU) { prompt("INVALID output action") @@ -385,11 +421,21 @@ for more information on simulation mode and its fields. promptgroup("50 - Output") interest(2) } - field(OVAL,DBF_LONG) { - prompt("Last Value Written") - promptgroup("50 - Output") - asl(ASL1) + field(PVAL,DBF_LONG) { + prompt("Previous Value") + } + field(OUTPVT,DBF_NOACCESS) { + prompt("Output Link Changed Private") special(SPC_NOMOD) + interest(4) + extra("epicsEnum16 outpvt") + } + field(OOCH,DBF_MENU) { + prompt("Output Execute On Change") + promptgroup("50 - Output") + interest(1) + menu(menuYesNo) + initial("1") } field(OOPT,DBF_MENU) { prompt("Output Execute Opt") diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c index 60da091b2..c69a6b386 100644 --- a/modules/database/test/std/rec/longoutTest.c +++ b/modules/database/test/std/rec/longoutTest.c @@ -10,14 +10,15 @@ #include "errlog.h" #include "dbAccess.h" #include "epicsMath.h" +#include "menuYesNo.h" #include "longoutRecord.h" void recTestIoc_registerRecordDeviceDriver(struct dbBase *); static void test_oopt_everytime(void){ - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write the same value two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); @@ -27,8 +28,8 @@ static void test_oopt_everytime(void){ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed 4 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 4.0); + /* Test if the counter_a was processed 4 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 4.0); // number of tests = 6 } @@ -37,22 +38,22 @@ static void test_oopt_onchange(void){ /* change OOPT to On Change */ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write the same value two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* Test if the counter was processed only once */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed only once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); /* write two times with different values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed 1 + 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 3.0); + /* Test if the counter_a was processed 1 + 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 3.0); //number of tests 8 } @@ -60,22 +61,22 @@ static void test_oopt_onchange(void){ static void test_oopt_whenzero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write zero two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was processed twice */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was processed twice */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); /* write two times with non-zero values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was still processed 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was still processed 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 8 } @@ -83,22 +84,22 @@ static void test_oopt_whenzero(void){ static void test_oopt_whennonzero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Non_zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write zero two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + /* Test if the counter_a was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* write two times with non-zero values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was still processed 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was still processed 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 8 } @@ -106,23 +107,23 @@ static void test_oopt_whennonzero(void){ static void test_oopt_when_transition_zero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write non-zero then zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); /* write another transition to zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); - /* Test if the counter was processed once more */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was processed once more */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 9 } @@ -133,21 +134,21 @@ static void test_oopt_when_transition_nonzero(void){ /* write non-zero to start fresh */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write non-zero then zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + /* Test if the counter_a was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* write a transition to non-zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); //number of tests 8 } @@ -159,35 +160,74 @@ static void test_changing_out_field(void){ /* write an initial value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); - testdbPutFieldOk("counter2.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* change the OUT link to another counter */ - testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter2.B PP"); + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_b.B PP"); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was processed once */ - testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was not processed again */ - testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0); + + /* Set option to write ON CHANGE even when the OUT link was changed */ + testdbPutFieldOk("longout_rec.OOCH", DBF_ENUM, menuYesNoNO); + + /* write an initial value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter_b was never processed */ + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 0.0); + + /* change back the OUT link to counter_a */ + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_a.B PP"); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); + + /* write new value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + //number of tests 24 } MAIN(longoutTest) { - testPlan(6+8+8+8+9+8+11); + testPlan(6+8+8+8+9+8+24); testdbPrepare(); testdbReadDatabase("recTestIoc.dbd", NULL, NULL); diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db index 93428a070..b988792cd 100644 --- a/modules/database/test/std/rec/longoutTest.db +++ b/modules/database/test/std/rec/longoutTest.db @@ -1,17 +1,17 @@ -record(calc, "counter") { - field(INPA, "counter") +record(calc, "counter_a") { + field(INPA, "counter_a") field(CALC, "A+1") field(SCAN, "Passive") } -record(calc, "counter2") { - field(INPA, "counter2") +record(calc, "counter_b") { + field(INPA, "counter_b") field(CALC, "A+1") field(SCAN, "Passive") } record(longout, "longout_rec") { field(VAL, "0") - field(OUT, "counter.B PP") + field(OUT, "counter_a.B PP") field(PINI, "YES") } From 1d85bc7424576fb1f7eb6c890d6a42c8d54254f4 Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Wed, 10 Mar 2021 11:07:30 +0100 Subject: [PATCH 004/323] longout record: detect OUT link change using special function AFTER put, better documentation --- modules/database/src/std/rec/longoutRecord.c | 6 +++--- modules/database/src/std/rec/longoutRecord.dbd.pod | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index e60e01c79..6ceeff1a7 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -217,9 +217,9 @@ static long special(DBADDR *paddr, int after) return(0); } - /* Detect an output link re-direction (change)*/ + /* Detect an output link re-direction (change) */ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if (!after) + if (after) prec->outpvt = OUT_LINK_CHANGED; return(0); } @@ -492,4 +492,4 @@ static long conditional_write(longoutRecord *prec) prec->pval = prec->val; prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ return status; -} \ No newline at end of file +} diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index fe596c118..1a85cf533 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -95,8 +95,7 @@ written to. It's a menu field that has six choices: C -- write output every time record is processed. (DEFAULT) =item * -C -- write output every time VAL changes, i.e., every time the -result of the expression changes. +C -- write output every time VAL changes. =item * C -- when record is processed, write output if VAL is zero. @@ -117,8 +116,10 @@ VAL is non-zero and last value was zero. =head4 Changes in OUT field when OOPT = On Change -The OOCH field determines if a change in OUT field should cause a write operation -even when the value is the same and OOPT = On Change. By default, OOCH is set to YES. +If OOCH is C (its default value) and the OOPT field is C, +the record will write to the device support the first time the record gets +processed after its OUT link is modified, even when the output value has +not actually changed. =cut From 6c573b496a2d387583e6ef603530a504b68aaa0a Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Wed, 10 Mar 2021 18:50:42 +0100 Subject: [PATCH 005/323] longout rec: fix behaviour when record is processed for the first time and OOPT is On Change --- modules/database/src/std/rec/longoutRecord.c | 17 ++++++++-------- .../src/std/rec/longoutRecord.dbd.pod | 4 ++-- modules/database/test/std/rec/longoutTest.c | 20 +++++++++++++++++-- modules/database/test/std/rec/longoutTest.db | 8 ++++++++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index 6ceeff1a7..9f29d15fa 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -81,8 +81,8 @@ rset longoutRSET={ }; epicsExportAddress(rset,longoutRSET); -#define OUT_LINK_UNCHANGED 0 -#define OUT_LINK_CHANGED 1 +#define DONT_EXEC_OUTPUT 0 +#define EXEC_OUTPUT 1 static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); @@ -124,7 +124,8 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->alst = prec->val; prec->lalm = prec->val; prec->pval = prec->val; - prec->outpvt = OUT_LINK_UNCHANGED; + prec->outpvt = EXEC_OUTPUT; + return 0; } @@ -219,8 +220,8 @@ static long special(DBADDR *paddr, int after) /* Detect an output link re-direction (change) */ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if (after) - prec->outpvt = OUT_LINK_CHANGED; + if ((after) && (prec->ooch == menuYesNoYES)) + prec->outpvt = EXEC_OUTPUT; return(0); } @@ -453,8 +454,8 @@ static long conditional_write(longoutRecord *prec) switch (prec->oopt) { case longoutOOPT_On_Change: - /* Forces a write op if a change in the OUT field is detected */ - if ((prec->ooch == menuYesNoYES) && (prec->outpvt == OUT_LINK_CHANGED)) { + /* Forces a write op if a change in the OUT field is detected OR is first process */ + if (prec->outpvt == EXEC_OUTPUT) { doDevSupWrite = 1; } else { /* Only write if value is different from the previous one */ @@ -490,6 +491,6 @@ static long conditional_write(longoutRecord *prec) status = pdset->write_longout(prec); prec->pval = prec->val; - prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ + prec->outpvt = DONT_EXEC_OUTPUT; /* reset status */ return status; } diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 1a85cf533..131ba8ff6 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -426,13 +426,13 @@ for more information on simulation mode and its fields. prompt("Previous Value") } field(OUTPVT,DBF_NOACCESS) { - prompt("Output Link Changed Private") + prompt("Output Write Control Private") special(SPC_NOMOD) interest(4) extra("epicsEnum16 outpvt") } field(OOCH,DBF_MENU) { - prompt("Output Execute On Change") + prompt("Output Exec. On Change (Opt)") promptgroup("50 - Output") interest(1) menu(menuYesNo) diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c index c69a6b386..35f161341 100644 --- a/modules/database/test/std/rec/longoutTest.c +++ b/modules/database/test/std/rec/longoutTest.c @@ -222,12 +222,28 @@ static void test_changing_out_field(void){ /* Test if the counter was processed once */ testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); - //number of tests 24 + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + + /* test if record with OOPT == On Change will + write to output at its first process */ + testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + //number of tests 29 } MAIN(longoutTest) { - testPlan(6+8+8+8+9+8+24); + testPlan(6+8+8+8+9+8+29); testdbPrepare(); testdbReadDatabase("recTestIoc.dbd", NULL, NULL); diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db index b988792cd..6b41b9883 100644 --- a/modules/database/test/std/rec/longoutTest.db +++ b/modules/database/test/std/rec/longoutTest.db @@ -15,3 +15,11 @@ record(longout, "longout_rec") { field(OUT, "counter_a.B PP") field(PINI, "YES") } + +record(longout, "longout_rec2") { + field(VAL, "16") + field(OUT, "counter_a.B PP") + field(PINI, "NO") + field(OOPT, "On Change") +} + From 4f0cc20e2b5f8abcd27d1609be99703ea59d58dc Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Tue, 9 Mar 2021 12:41:34 -0500 Subject: [PATCH 006/323] Feature: add SIMM=RAW to ao records --- modules/database/src/std/rec/aoRecord.c | 21 +++++++++++++------ modules/database/src/std/rec/aoRecord.dbd.pod | 4 ++-- modules/database/test/std/rec/simmTest.c | 18 ++++++++++++++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/modules/database/src/std/rec/aoRecord.c b/modules/database/src/std/rec/aoRecord.c index 022424054..11206277b 100644 --- a/modules/database/src/std/rec/aoRecord.c +++ b/modules/database/src/std/rec/aoRecord.c @@ -37,7 +37,7 @@ #include "recGbl.h" #include "menuConvert.h" #include "menuOmsl.h" -#include "menuYesNo.h" +#include "menuSimm.h" #include "menuIvoa.h" #define GEN_SIZE_OFFSET @@ -561,15 +561,24 @@ static long writeValue(aoRecord *prec) } switch (prec->simm) { - case menuYesNoNO: + case menuSimmNO: status = pdset->write_ao(prec); break; - case menuYesNoYES: { + case menuSimmYES: + case menuSimmRAW: + { recGblSetSevr(prec, SIMM_ALARM, prec->sims); - if (prec->pact || (prec->sdly < 0.)) { - status = dbPutLink(&prec->siol, DBR_DOUBLE, &prec->oval, 1); - prec->pact = FALSE; + if (prec->pact || (prec->sdly < 0.)) { + if(prec->simm == menuSimmYES){ + /* don't convert */ + status = dbPutLink(&prec->siol, DBR_DOUBLE, &prec->oval, 1); + } + else { /* prec->simm == menuSimmRAW*/ + /* convert */ + status = dbPutLink(&prec->siol, DBR_LONG, &prec->rval, 1); + } + prec->pact = FALSE; } else { /* !prec->pact && delay >= 0. */ epicsCallback *pvt = prec->simpvt; if (!pvt) { diff --git a/modules/database/src/std/rec/aoRecord.dbd.pod b/modules/database/src/std/rec/aoRecord.dbd.pod index 5fef0d09e..3789d1bef 100644 --- a/modules/database/src/std/rec/aoRecord.dbd.pod +++ b/modules/database/src/std/rec/aoRecord.dbd.pod @@ -263,7 +263,7 @@ processing. The following fields are used to operate the record in simulation mode. -If SIMM (fetched through SIML) is YES, the record is put in SIMS +If SIMM (fetched through SIML, if populated) is YES, the record is put in SIMS severity and the value is written through SIOL, without conversion. SSCN sets a different SCAN mechanism to use in simulation mode. SDLY sets a delay (in sec) that is used for asynchronous simulation @@ -557,7 +557,7 @@ for more information on simulation mode and its fields. prompt("Simulation Mode") special(SPC_MOD) interest(1) - menu(menuYesNo) + menu(menuSimm) } field(SIMS,DBF_MENU) { prompt("Simulation Mode Severity") diff --git a/modules/database/test/std/rec/simmTest.c b/modules/database/test/std/rec/simmTest.c index 17e4d7fe4..a253b791c 100644 --- a/modules/database/test/std/rec/simmTest.c +++ b/modules/database/test/std/rec/simmTest.c @@ -63,6 +63,7 @@ static char *rawSupp[] = { "bi", "mbbi", "mbbiDirect", + "ao" }; static @@ -77,6 +78,7 @@ int hasRawSimmSupport(const char *rectype) { static char nameVAL[PVNAMELENGTH]; static char nameB0[PVNAMELENGTH]; static char nameRVAL[PVNAMELENGTH]; +static char nameROFF[PVNAMELENGTH]; static char nameSGNL[PVNAMELENGTH]; static char nameSIMM[PVNAMELENGTH]; static char nameSIML[PVNAMELENGTH]; @@ -98,7 +100,7 @@ static char nameSimvalLEN[PVNAMELENGTH]; static void setNames(const char *name) { - SETNAME(VAL); SETNAME(B0); SETNAME(RVAL); SETNAME(SGNL); + SETNAME(VAL); SETNAME(B0); SETNAME(RVAL); SETNAME(ROFF); SETNAME(SGNL); SETNAME(SVAL); SETNAME(SIMM); SETNAME(SIML); SETNAME(SIOL); SETNAME(SIMS); SETNAME(SCAN); SETNAME(PROC); SETNAME(PACT); SETNAME(STAT); SETNAME(SEVR); SETNAME(TSE); @@ -399,6 +401,18 @@ void testSiolWrite(const char *name, testdbPutFieldOk(nameVAL, DBR_LONG, 1); testdbGetFieldEqual(nameSimval, DBR_USHORT, 1); + if(hasRawSimmSupport(name)){ + testDiag("in simmRAW, RVAL should be written to SIOL"); + testDiag("SIML overrides SIMM, disable it here"); + testdbPutFieldOk(nameSIML, DBR_STRING, ""); + testdbPutFieldOk(nameSIMM, DBR_STRING, "RAW"); + testdbPutFieldOk(nameROFF, DBR_ULONG, 2); + testdbPutFieldOk(nameVAL, DBR_DOUBLE, 5.); + testdbGetFieldEqual(nameRVAL, DBR_LONG, 3); + testdbGetFieldEqual(nameSimval, DBR_DOUBLE, 3.); + testdbPutFieldOk(nameSIML, DBR_STRING, nameSimmode); + } + /* Set TSE to -2 (from device) and reprocess: timestamp is taken from IOC */ epicsTimeGetCurrent(&now); testdbPutFieldOk(nameTSE, DBR_SHORT, -2); @@ -502,7 +516,7 @@ void testAllRecTypes(void) MAIN(simmTest) { - testPlan(1176); + testPlan(1219); startSimmTestIoc("simmTest.db"); testSimmSetup(); From edb9208b0121b4d03e55a25e7aca10c9b7cccd65 Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Fri, 12 Mar 2021 17:06:35 -0500 Subject: [PATCH 007/323] correct number of simm tests --- modules/database/test/std/rec/simmTest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/test/std/rec/simmTest.c b/modules/database/test/std/rec/simmTest.c index a253b791c..f755196de 100644 --- a/modules/database/test/std/rec/simmTest.c +++ b/modules/database/test/std/rec/simmTest.c @@ -516,7 +516,7 @@ void testAllRecTypes(void) MAIN(simmTest) { - testPlan(1219); + testPlan(1199); startSimmTestIoc("simmTest.db"); testSimmSetup(); From f5cb3cf8f60948d45263f24382c8d949155cdda3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 3 Jul 2021 20:56:32 -0500 Subject: [PATCH 008/323] Update version numbers after tagging --- configure/CONFIG_BASE_VERSION | 4 +- configure/CONFIG_CA_VERSION | 4 +- configure/CONFIG_DATABASE_VERSION | 4 +- configure/CONFIG_LIBCOM_VERSION | 4 +- documentation/RELEASE_NOTES.md | 6 + documentation/ReleaseChecklist.html | 177 ++++++++-------------------- 6 files changed, 60 insertions(+), 139 deletions(-) diff --git a/configure/CONFIG_BASE_VERSION b/configure/CONFIG_BASE_VERSION index efa0afee7..fda0f13f3 100644 --- a/configure/CONFIG_BASE_VERSION +++ b/configure/CONFIG_BASE_VERSION @@ -52,11 +52,11 @@ EPICS_MODIFICATION = 6 # EPICS_PATCH_LEVEL must be a number (win32 resource file requirement) # Not included in the official EPICS version number if zero -EPICS_PATCH_LEVEL = 0 +EPICS_PATCH_LEVEL = 1 # Immediately after an official release the EPICS_PATCH_LEVEL is incremented # and the -DEV suffix is added (similar to the Maven -SNAPSHOT versions) -EPICS_DEV_SNAPSHOT= +EPICS_DEV_SNAPSHOT=-DEV # No changes should be needed below here diff --git a/configure/CONFIG_CA_VERSION b/configure/CONFIG_CA_VERSION index ce95a0ad3..5c3127d4c 100644 --- a/configure/CONFIG_CA_VERSION +++ b/configure/CONFIG_CA_VERSION @@ -2,11 +2,11 @@ EPICS_CA_MAJOR_VERSION = 4 EPICS_CA_MINOR_VERSION = 14 -EPICS_CA_MAINTENANCE_VERSION = 0 +EPICS_CA_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions -EPICS_CA_DEVELOPMENT_FLAG = 0 +EPICS_CA_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/configure/CONFIG_DATABASE_VERSION b/configure/CONFIG_DATABASE_VERSION index 348a69b11..4e3d22b71 100644 --- a/configure/CONFIG_DATABASE_VERSION +++ b/configure/CONFIG_DATABASE_VERSION @@ -2,11 +2,11 @@ EPICS_DATABASE_MAJOR_VERSION = 3 EPICS_DATABASE_MINOR_VERSION = 20 -EPICS_DATABASE_MAINTENANCE_VERSION = 0 +EPICS_DATABASE_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions -EPICS_DATABASE_DEVELOPMENT_FLAG = 0 +EPICS_DATABASE_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/configure/CONFIG_LIBCOM_VERSION b/configure/CONFIG_LIBCOM_VERSION index 76a5a5fcf..42d5f086c 100644 --- a/configure/CONFIG_LIBCOM_VERSION +++ b/configure/CONFIG_LIBCOM_VERSION @@ -2,11 +2,11 @@ EPICS_LIBCOM_MAJOR_VERSION = 3 EPICS_LIBCOM_MINOR_VERSION = 20 -EPICS_LIBCOM_MAINTENANCE_VERSION = 0 +EPICS_LIBCOM_MAINTENANCE_VERSION = 1 # Development flag, set to zero for release versions -EPICS_LIBCOM_DEVELOPMENT_FLAG = 0 +EPICS_LIBCOM_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index c406b6506..df60dff63 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -10,6 +10,12 @@ everything that has changed in each release. The PVA submodules each have their own individual sets of release notes which should also be read to understand what has changed since earlier releases. +**This version of EPICS has not been released yet.** + +## Changes made on the 7.0 branch since 7.0.6 + + + ## EPICS Release 7.0.6 diff --git a/documentation/ReleaseChecklist.html b/documentation/ReleaseChecklist.html index 036f56a65..05b76ece9 100644 --- a/documentation/ReleaseChecklist.html +++ b/documentation/ReleaseChecklist.html @@ -37,26 +37,11 @@ that should be performed when creating production releases of EPICS Base.

The Release Process

-

Full Process

- -

The version released on the Feature Freeze date is designated the first -pre-release, -pre1. The first release candidate -rc1 is the -first version that has undergone testing by the developers and has shown no -problems that must be fixed before release. New versions should be made at about -2-weekly intervals after the -pre1 release, and designated as either -pre-release or release candidate versions by the Release Manager. Release -candidates are announced to the whole community via the tech-talk mailing list, -pre-releases are announced to to the developers via the core-talk list. After a -release candidate has been available for 2 weeks without any new problems being -reported or major changes having to be committed, the final release can be -made.

- -

Short Process for Patch Releases

- -

The Patch Release date and its scope are agreed upon a few weeks ahead of the -release. If no blocking issues are raised, the release is made by the Release -Manager on or as soon as possible after that date, following the steps below -starting at Release Approval.

+

We used to have one written down here, but we weren't following it very +closely so now the decision to make a new release is taken during the Core +Developers bi-weekly meetings in an informal manner. The steps detailed below +were written to remind Andrew (or anyone else who does the release) about +everything that has to be done since it's so easy to miss steps.

Roles

@@ -65,11 +50,11 @@ starting at Release Approval.

Release Manager ()
Responsible for managing and tagging the release
-
Platform Developers (optional)
+
Platform Developers (informal)
Responsible for individual operating system platforms
Application Developers
Responsible for support modules that depend on EPICS Base.
-
Website Manager (Andrew Johnson)
+
Website Editor (Andrew Johnson)
Responsible for the EPICS website
@@ -111,9 +96,7 @@ starting at Release Approval.

& all developers Ensure that documentation will be updated before the release date:
    -
  • Application Developers Guide
  • Release Notes
  • -
  • Known Problems
  • Other documents
@@ -125,87 +108,9 @@ starting at Release Approval.

  - Website Manager + Release Manager Create a release milestone on Launchpad. If a target release date is - known set "Date Targeted" to the expected release date. Note that - pre-release and release-candidate versions should not get Launchpad - milestones, only the final release. - - - Creating pre-release and release-candidate versions - - - - Release Manager - - Edit and commit changes to the EPICS version number file - configure/CONFIG_BASE_VERSION. - - - - Release Manager - Tag the module in Git, using these tag conventions: -
    -
  • - R7.0.5-pren - — pre-release tag -
  • -
  • - R7.0.5-rcn - — release candidate tag -
  • -
-
- cd base-7.0
- git tag -m 'ANJ: Tagged for 7.0.5-rc1' R7.0.5-rc1 -
- Note that submodules must not be tagged with the version used - for the top-level, they each have their own separate version numbers - that are only tagged at the final release. - - - - Release Manager - Export the tagged version into a tarfile. The make-tar.sh - script generates a gzipped tarfile directly from the tag, excluding the - files and directories that are only used for continuous integration: -
- cd base-7.0
- ./.tools/make-tar.sh R7.0.5-rc1 base-7.0.5-rc1.tar.gz base-7.0.5-rc1/ -
- Create a GPG signature file of the tarfile as follows: -
- gpg --armor --sign --detach-sig base-7.0.5-rc1.tar.gz -
- - - - - Release Manager - Test the tarfile by extracting its contents and building it on at - least one supported platform. - - - - Website Manager - Copy the tarfile and its signature to the Base download area of the - website and add the new files to the website Base download index - page. - - - - Website Manager - Create or update a website subdirectory to hold the release - documentation, and copy in selected files from the base/documentation - and base/html directories of the tarfile. - - - - Website Manager - Create or modify the webpage for the new release with links to the - release documents and tar file. Pre-release and release-candidate - versions should use the page and URL for the final release version - number. + known set "Date Targeted" to the expected release date. Testing @@ -250,11 +155,8 @@ starting at Release Approval.

Release Manager Check that documentation has been updated: @@ -266,9 +168,7 @@ starting at Release Approval.

Release Manager - Obtain a positive Ok to release from all platform developers - once a release candidate version has gone for 2 weeks without any major - new issues being reported. + Obtain a positive Ok to release from developers. Creating the final release version @@ -277,8 +177,8 @@ starting at Release Approval.

Release Manager -

For each external submodule in turn (assuming it has not been tagged - yet):

+

For each external submodule in turn (assuming it has not been + tagged yet):

  1. Check that the module's Release Notes have been updated to cover all changes; add items as necessary, and set the module version @@ -298,7 +198,7 @@ starting at Release Approval.

  2. Tag the module:
    - git tag -m 'ANJ: Tag for EPICS 7.0.5' <module-version> + git tag -m 'ANJ: Tag for EPICS 7.0.6.1' <module-version>
  3. @@ -326,15 +226,24 @@ starting at Release Approval.

-

Commit all the submodule updates to the 7.0 branch.

+

After all submodules complete commit the submodule updates + which were added for each submodule in step 4 above to the 7.0 branch + (don't push). After committing, make sure that the output from + git submodule status --cached only shows the appropriate + version tags in the right-most parenthesized column with no + -n-gxxxxxxx suffix.

Release Manager - Edit the main EPICS Base version file and the built-in module version - files: + +

git grep UNRELEASED and insert the release version to any + doxygen annotations that have a @since UNRELEASED comment. + Commit (don't push).

+

Edit the main EPICS Base version file and the built-in module version + files:

  • configure/CONFIG_BASE_VERSION
  • configure/CONFIG_LIBCOM_VERSION
  • @@ -346,6 +255,9 @@ starting at Release Approval.

    PATCH_LEVEL value should have been incremented after the previous release tag was applied. Set all DEVELOPMENT_FLAG values to 0 and EPICS_DEV_SNAPSHOT to the empty string.

    +

    Edit the headings in the Release Notes to show the appropriate + version number and remove the warning about this being an unreleased + version of EPICS.

    Commit these changes (don't push).

    @@ -355,9 +267,9 @@ starting at Release Approval.

    Tag the epics-base module in Git:
    cd base-7.0
    - git tag -m 'ANJ: Tagged for release' R7.0.5 + git tag -m 'ANJ: Tagged for release' R7.0.6.1
    -

    Don't push these commits or the new tag to the Launchpad repository +

    Don't push anything to the Launchpad repository yet.

    @@ -376,6 +288,9 @@ starting at Release Approval.

    release by incrementing the MAINTENANCE_VERSION or PATCH_LEVEL value in each file. Set all DEVELOPMENT_FLAG values to 1 and EPICS_DEV_SNAPSHOT to "-DEV".

    +

    Set up the headings in the Release Notes for the next release + version number and restore the warning about this being an unreleased + version of EPICS.

    Commit these changes (don't push).

    @@ -387,12 +302,12 @@ starting at Release Approval.

    files and directories that are only used for continuous integration:
    cd base-7.0
    - ./.tools/make-tar.sh R7.0.5 ../base-7.0.5.tar.gz base-7.0.5/ + ./.tools/make-tar.sh R7.0.6.1 ../base-7.0.6.1.tar.gz base-7.0.6.1/
    Create a GPG signature file of the tarfile as follows:
    cd ..
    - gpg --armor --sign --detach-sig base-7.0.5.tar.gz + gpg --armor --sign --detach-sig base-7.0.6.1.tar.gz
    @@ -412,38 +327,38 @@ starting at Release Approval.

    - Release Manager + Website Editor Copy the tarfile and its signature to the Base download area of the website. - Website Manager + Website Editor Update the website subdirectory that holds the release documentation, and copy in the files from the base/documentation directory of the tarfile. - Website Manager + Website Editor Update the webpage for the new release with links to the release documents and tar file. - Website Manager + Website Editor Add the new release tar file to the website Base download index page. - Website Manager + Website Editor Link to the release webpage from other relevent areas of the website - update front page and sidebars. - Website Manager + Website Editor Add an entry to the website News page, linking to the new version webpage. @@ -453,17 +368,17 @@ starting at Release Approval.

    - Website Manager + Website Editor Upload the tar file and its .asc signature file to the epics-controls web-server.
    - scp base-7.0.5.tar.gz base-7.0.5.tar.gz.asc epics-controls:download/base
    + scp base-7.0.6.1.tar.gz base-7.0.6.1.tar.gz.asc epics-controls:download/base
    - Website Manager + Website Editor Follow instructions on Add a page for a new release to create a new release webpage (not @@ -478,7 +393,7 @@ starting at Release Approval.

    - Website Manager + Release Manager Go to the Launchpad milestone for this release. Click the Create release button and add the release date. Put a URL for the release page in the Release notes box, and click the Create release button. Upload From 9363052956fdf63b88f787371ab97c9eedcfd7b3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 3 Jul 2021 20:56:53 -0500 Subject: [PATCH 009/323] Update submodules after release --- modules/normativeTypes | 2 +- modules/pvAccess | 2 +- modules/pvData | 2 +- modules/pvDatabase | 2 +- modules/pva2pva | 2 +- modules/pvaClient | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/normativeTypes b/modules/normativeTypes index 1250a3c23..7a2d264f2 160000 --- a/modules/normativeTypes +++ b/modules/normativeTypes @@ -1 +1 @@ -Subproject commit 1250a3c236f0aa92e0b5bd73647fd71d8a09360d +Subproject commit 7a2d264f2cb107bfd10adb23bc2b73d8323a79e4 diff --git a/modules/pvAccess b/modules/pvAccess index 3e6e6ae74..a5cae7ad9 160000 --- a/modules/pvAccess +++ b/modules/pvAccess @@ -1 +1 @@ -Subproject commit 3e6e6ae74bf9e21cf36dcbd2165888560d35d82b +Subproject commit a5cae7ad9242d099ee0602cb3061987e1ff95ef4 diff --git a/modules/pvData b/modules/pvData index b1c830387..d3b4976ea 160000 --- a/modules/pvData +++ b/modules/pvData @@ -1 +1 @@ -Subproject commit b1c8303870a04f1c3ee5a01a84aad2b2596e918c +Subproject commit d3b4976ea2b0d78075511f14d7f7bf9620dd4d14 diff --git a/modules/pvDatabase b/modules/pvDatabase index 8cac3975c..0cf706511 160000 --- a/modules/pvDatabase +++ b/modules/pvDatabase @@ -1 +1 @@ -Subproject commit 8cac3975cce67828a19e600fee85dd28df52fe2c +Subproject commit 0cf706511ea4f9f0cf769c1d3a6317d826b35af4 diff --git a/modules/pva2pva b/modules/pva2pva index 466d41ebb..61ec0715b 160000 --- a/modules/pva2pva +++ b/modules/pva2pva @@ -1 +1 @@ -Subproject commit 466d41ebb95a163133e07150d7841c03abfebf58 +Subproject commit 61ec0715be3ce1acb8d9f819bfd89d788dabc21b diff --git a/modules/pvaClient b/modules/pvaClient index a34876e36..8ed07fef9 160000 --- a/modules/pvaClient +++ b/modules/pvaClient @@ -1 +1 @@ -Subproject commit a34876e36a56c9de9b172d6a83a9439bb330783d +Subproject commit 8ed07fef96e41d35d47ab61276e29eb1a81e7fec From f801ca0501b455bee5e12b836bc24ae74b010535 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 3 Jul 2021 21:26:18 -0500 Subject: [PATCH 010/323] Drop version number from README --- documentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index a5310deb4..2d4386aed 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,6 +1,6 @@ # Installation Instructions {#install} -## EPICS Base Release 7.0.5 +## EPICS Base Release 7.0.x ----- From e5aece682e9df5d3bf40ab2dc3439da076a6ecae Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 15 Jul 2021 07:59:48 -0700 Subject: [PATCH 011/323] ci: replace GHA deprecated ubuntu-16.04 with CentOS/Fedora builds Maintain coverage of older GCC on Linux --- .ci | 2 +- .github/workflows/ci-scripts-build.yml | 128 +++++++++++++++++-------- 2 files changed, 89 insertions(+), 41 deletions(-) diff --git a/.ci b/.ci index d675de24e..75bae77c1 160000 --- a/.ci +++ b/.ci @@ -1 +1 @@ -Subproject commit d675de24e6a2be018f6ff1dc35618c16dd621727 +Subproject commit 75bae77c1d20707a53e0ff57937491f6b8b557ba diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index b9b3a0c12..6aa6eefb4 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -34,7 +34,7 @@ env: EPICS_TEST_IMPRECISE_TIMING: YES jobs: - build-base: + native: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} # Set environment variables from matrix parameters @@ -69,10 +69,10 @@ jobs: extra: "CMD_CXXFLAGS=-std=c++11" name: "Ub-20 gcc-9 C++11, static" - - os: ubuntu-16.04 + - os: ubuntu-20.04 cmp: clang configuration: default - name: "Ub-16 clang-9" + name: "Ub-20 clang-10" - os: ubuntu-20.04 cmp: clang @@ -145,35 +145,6 @@ jobs: name: "Ub-20 gcc-9 + RT-4.9" rtems_target: RTEMS-pc386-qemu - - os: ubuntu-16.04 - cmp: gcc-4.8 - utoolchain: "4.8" - configuration: default - name: "Ub-16 gcc-4.8" - - - os: ubuntu-16.04 - cmp: gcc-4.9 - utoolchain: "4.9" - configuration: default - name: "Ub-16 gcc-4.9" - - - os: ubuntu-20.04 - cmp: gcc-8 - utoolchain: "8" - configuration: default - name: "Ub-20 gcc-8" - - - os: ubuntu-20.04 - cmp: gcc-9 - utoolchain: "9" - configuration: default - name: "Ub-20 gcc-9" - - - os: ubuntu-20.04 - cmp: clang - configuration: default - name: "Ub-20 clang-10" - - os: macos-latest cmp: clang configuration: default @@ -210,14 +181,91 @@ jobs: sudo apt-get update sudo apt-get -y install qemu-system-x86 g++-mingw-w64-x86-64 gdb if: runner.os == 'Linux' - - name: "apt-get install ${{ matrix.cmp }}" - run: | - sudo apt-get update - sudo apt-get -y install software-properties-common - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get -y install g++-${{ matrix.utoolchain }} - if: matrix.utoolchain + - name: Prepare and compile dependencies + run: python .ci/cue.py prepare + - name: Build main module + run: python .ci/cue.py build + - name: Run main module tests + run: python .ci/cue.py -T 20M test + - name: Upload tapfiles Artifact + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: tapfiles ${{ matrix.name }} + path: '**/O.*/*.tap' + if-no-files-found: ignore + - name: Collect and show test results + if: ${{ always() }} + run: python .ci/cue.py -T 5M test-results + + docker: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + # Set environment variables from matrix parameters + env: + CMP: ${{ matrix.cmp }} + BCFG: ${{ matrix.configuration }} + EXTRA: ${{ matrix.extra }} + TEST: ${{ matrix.test }} + strategy: + fail-fast: false + matrix: + # Job names also name artifacts, character limitations apply + include: + - name: "CentOS-7" + image: centos:7 + cmp: gcc + configuration: default + + - name: "CentOS-8" + image: centos:8 + cmp: gcc + configuration: default + + - name: "Fedora-33" + image: fedora:33 + cmp: gcc + configuration: default + + - name: "Fedora-latest" + image: fedora:latest + cmp: gcc + configuration: default + + steps: + - name: "Build newer Git" + # actions/checkout@v2 wants git >=2.18 + # centos:7 has 1.8 + if: matrix.image=='centos:7' + run: | + yum -y install curl make gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker + curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz | tar -xz + cd git-* + make -j2 prefix=/usr/local all + make prefix=/usr/local install + cd .. + rm -rf git-* + type -a git + git --version + - name: "Redhat setup" + run: | + dnfyum() { + dnf -y "$@" || yum -y "$@" + return $? + } + dnfyum install python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel perl-Test-Simple + git --version || dnfyum install git + # rather than just bite the bullet and link python3 -> python, + # people would rather just break all existing scripts... + [ -e /usr/bin/python ] || ln -sf python3 /usr/bin/python + python --version + - uses: actions/checkout@v2 + with: + submodules: true + - name: Automatic core dumper analysis + uses: mdavidsaver/ci-core-dumper@master - name: Prepare and compile dependencies run: python .ci/cue.py prepare - name: Build main module From 967846b95084388f16be9307a99214a495a25ab1 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 27 Jun 2021 07:23:53 -0700 Subject: [PATCH 012/323] tap files are PRECIOUS --- configure/RULES_BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD index 53297af52..d750548e9 100644 --- a/configure/RULES_BUILD +++ b/configure/RULES_BUILD @@ -383,6 +383,8 @@ endif tapfiles: $(TAPFILES) junitfiles: $(JUNITFILES) +# prevent deletion of partial output from failing tests +.PRECIOUS: $(TAPFILES) $(JUNITFILES) test-results: tap-results tap-results: $(TAPFILES) From 8e11406fc6a9225dfc5852430b674397ad3157b3 Mon Sep 17 00:00:00 2001 From: "J. Lewis Muir" Date: Wed, 21 Jul 2021 09:53:11 -0500 Subject: [PATCH 013/323] Fix calcout rec doc typo: s/If it met/If met/ --- modules/database/src/std/rec/calcoutRecord.dbd.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/std/rec/calcoutRecord.dbd.pod b/modules/database/src/std/rec/calcoutRecord.dbd.pod index 9302ba410..f2a203714 100644 --- a/modules/database/src/std/rec/calcoutRecord.dbd.pod +++ b/modules/database/src/std/rec/calcoutRecord.dbd.pod @@ -1218,7 +1218,7 @@ honors the alarm hysteresis factor (HYST). Thus the value must change by at least HYST before the alarm status and severity changes. =item 4. -Determine if the Output Execution Option (OOPT) is met. If it met, either +Determine if the Output Execution Option (OOPT) is met. If met, either execute the output link (and output event) immediately (if ODLY = 0), or schedule a callback after the specified interval. See the explanation for the C routine below. From 16c3202992600a6b5c0daaaf9f29715f4650c458 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Jul 2021 11:05:41 -0500 Subject: [PATCH 014/323] waveform: Add back lost PACT = TRUE Fixes GitHub Issue #187 --- modules/database/src/std/rec/waveformRecord.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/database/src/std/rec/waveformRecord.c b/modules/database/src/std/rec/waveformRecord.c index d075c01cd..72bc63efd 100644 --- a/modules/database/src/std/rec/waveformRecord.c +++ b/modules/database/src/std/rec/waveformRecord.c @@ -139,6 +139,7 @@ static long process(struct dbCommon *pcommon) if (!pact && prec->pact) return 0; + prec->pact = TRUE; prec->udf = FALSE; recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); From 7a6aa3edd11f91e754305d4d3457063b751d09f7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Jul 2021 11:06:34 -0500 Subject: [PATCH 015/323] waveform: Update POD, describe BUSY field --- .../src/std/rec/waveformRecord.dbd.pod | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/database/src/std/rec/waveformRecord.dbd.pod b/modules/database/src/std/rec/waveformRecord.dbd.pod index d787b7bbd..db4ca362f 100644 --- a/modules/database/src/std/rec/waveformRecord.dbd.pod +++ b/modules/database/src/std/rec/waveformRecord.dbd.pod @@ -120,9 +120,18 @@ at run-time. VAL references the array where the waveform stores its data. The BPTR field holds the address of the array. -The NORD field holds a counter of the number of elements that have been read -into the array. It is reset to 0 when the device is rearmed. The BUSY field -indicates if the device is armed but has not yet been digitized. +The NORD field indicates the number of elements that were read into the array. + +The BUSY field permits asynchronous device support to collect array elements +sequentially in multiple read cycles which may call the record's C +method many times before completing a read operation. Such a device would set +BUSY to TRUE along with setting PACT at the start of acquisition (it could also +set NORD to 0 and use it to keep track of how many elements have been received). +After receiving the last element the C routine would clear BUSY which +informs the record's C method that the read has finished. Note that +CA clients that perform gets of the VAL field can see partially filled arrays +when this type of device support is used, so the BUSY field is almost never used +today. =fields VAL, BPTR, NORD, BUSY @@ -367,14 +376,14 @@ Other: Error. =head3 Device Support For Soft Records The C<<< Soft Channel >>> device support module is provided to read values from -other records and store them in arrays. If INP is a constant link, then read_wf -does nothing. In this case, the record can be used to hold arrays written via -dbPuts. If INP is a database or channel access link, the new array value is read -from the link. NORD is set to the number of items in the array. +other records and store them in the VAL field. If INP is a constant link, then +C does nothing. In this case, the record can be used to hold a fixed +set of data or array values written from elsewhere. If INP is a valid link, the +new array value is read from that link. NORD is set to the number of items +received. -This module places a value directly in VAL. - -If the INP link type is constant, then NORD is set to zero. +If the INP link type is constant, VAL is set from it in the C +routine and NORD is also set at that time. =cut From ec87b2a867b3d3930d24eb1a8bfd949a08048290 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Jul 2021 11:08:06 -0500 Subject: [PATCH 016/323] recGbl: Update to using dbGetTimeStampTag() --- modules/database/src/ioc/db/recGbl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/ioc/db/recGbl.c b/modules/database/src/ioc/db/recGbl.c index 9f5b90479..95387f5de 100644 --- a/modules/database/src/ioc/db/recGbl.c +++ b/modules/database/src/ioc/db/recGbl.c @@ -318,7 +318,7 @@ void recGblGetTimeStampSimm(void *pvoid, const epicsEnum16 simm, struct link *si } else { if (simm != menuSimmNO) { if (siol && !dbLinkIsConstant(siol)) { - if (dbGetTimeStamp(siol, &prec->time)) + if (dbGetTimeStampTag(siol, &prec->time, &prec->utag)) errlogPrintf("recGblGetTimeStampSimm: dbGetTimeStamp (sim mode) failed, %s.SIOL = %s\n", prec->name, siol->value.pv_link.pvname); return; From 3091f7c56f360d2704e37a76064ade4bdd9fef4c Mon Sep 17 00:00:00 2001 From: Kay Kasemir Date: Thu, 29 Jul 2021 14:38:16 -0400 Subject: [PATCH 017/323] int64in: Fix monitor delta test Only the lower 32 bit used to be compared. https://bugs.launchpad.net/epics-base/+bug/1938459 --- modules/database/src/std/rec/int64inRecord.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/database/src/std/rec/int64inRecord.c b/modules/database/src/std/rec/int64inRecord.c index a55b1a7da..2cd804d07 100644 --- a/modules/database/src/std/rec/int64inRecord.c +++ b/modules/database/src/std/rec/int64inRecord.c @@ -359,16 +359,16 @@ static void checkAlarms(int64inRecord *prec, epicsTimeStamp *timeLast) } /* DELTA calculates the absolute difference between its arguments - * expressed as an unsigned 32-bit integer */ + * expressed as an unsigned 64-bit integer */ #define DELTA(last, val) \ - ((epicsUInt32) ((last) > (val) ? (last) - (val) : (val) - (last))) + ((epicsUInt64) ((last) > (val) ? (last) - (val) : (val) - (last))) static void monitor(int64inRecord *prec) { unsigned short monitor_mask = recGblResetAlarms(prec); if (prec->mdel < 0 || - DELTA(prec->mlst, prec->val) > (epicsUInt32) prec->mdel) { + DELTA(prec->mlst, prec->val) > (epicsUInt64) prec->mdel) { /* post events for value change */ monitor_mask |= DBE_VALUE; /* update last value monitored */ @@ -376,7 +376,7 @@ static void monitor(int64inRecord *prec) } if (prec->adel < 0 || - DELTA(prec->alst, prec->val) > (epicsUInt32) prec->adel) { + DELTA(prec->alst, prec->val) > (epicsUInt64) prec->adel) { /* post events for archive value change */ monitor_mask |= DBE_LOG; /* update last archive value monitored */ From 78d2f20fa8cbc6a357c6340f890f8d3f84e0be84 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 24 Jun 2021 10:05:42 -0700 Subject: [PATCH 018/323] Com: Adjust epicsAtomic conditionals for GCC cf. https://bugs.launchpad.net/epics-base/+bug/1932118 --- .../src/osi/compiler/gcc/epicsAtomicCD.h | 72 +++++-------------- modules/libcom/test/epicsAtomicTest.cpp | 6 +- 2 files changed, 20 insertions(+), 58 deletions(-) diff --git a/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h b/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h index 2f6a23ff2..a7bcd8f3c 100644 --- a/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h +++ b/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h @@ -23,50 +23,36 @@ #define EPICS_ATOMIC_CMPLR_NAME "GCC" +/* expands __GCC_HAVE_SYNC_COMPARE_AND_SWAP_ concatentating the numeric value __SIZEOF_*__ */ #define GCC_ATOMIC_CONCAT( A, B ) GCC_ATOMIC_CONCATR(A,B) #define GCC_ATOMIC_CONCATR( A, B ) ( A ## B ) +/* + * As of GCC 8, the __sync_synchronize() is inlined for all + * known targets (aarch64, arm, i386, powerpc, and x86_64) + * except for arm <=6. + * Note that i386 inlines __sync_synchronize() but does not + * define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_* + */ +#define GCC_ATOMIC_INTRINSICS_AVAIL_SYNC \ + defined(GCC_ATOMIC_INTRINSICS_AVAIL_INT_T) || defined(GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T) || defined(__i386) + #define GCC_ATOMIC_INTRINSICS_AVAIL_INT_T \ GCC_ATOMIC_CONCAT ( \ __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ __SIZEOF_INT__ ) +/* we assume __SIZEOF_POINTER__ == __SIZEOF_SIZE_T__ */ #define GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T \ GCC_ATOMIC_CONCAT ( \ __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ __SIZEOF_SIZE_T__ ) -#define GCC_ATOMIC_INTRINSICS_MIN_X86 \ - ( defined ( __i486 ) || defined ( __pentium ) || \ - defined ( __pentiumpro ) || defined ( __MMX__ ) ) - -#define GCC_ATOMIC_INTRINSICS_GCC4_OR_BETTER \ - ( ( __GNUC__ * 100 + __GNUC_MINOR__ ) >= 401 ) - -#define GCC_ATOMIC_INTRINSICS_AVAIL_EARLIER \ - ( GCC_ATOMIC_INTRINSICS_MIN_X86 && \ - GCC_ATOMIC_INTRINSICS_GCC4_OR_BETTER ) - #ifdef __cplusplus extern "C" { #endif -/* - * We are optimistic that __sync_synchronize is implemented - * in all version four gcc invariant of target. The gnu doc - * seems to say that when not supported by architecture a call - * to an external function is generated but in practice - * this isn`t the case for some of the atomic intrinsics, and - * so there is an undefined symbol. So far we have not seen - * that with __sync_synchronize, but we can only guess based - * on experimental evidence. - * - * For example we know that when generating object code for - * 386 most of the atomic intrinsics are not present and - * we see undefined symbols with mingw, but we don`t have - * troubles with __sync_synchronize. - */ -#if GCC_ATOMIC_INTRINSICS_GCC4_OR_BETTER +#if GCC_ATOMIC_INTRINSICS_AVAIL_SYNC #ifndef EPICS_ATOMIC_READ_MEMORY_BARRIER #define EPICS_ATOMIC_READ_MEMORY_BARRIER @@ -84,32 +70,9 @@ EPICS_ATOMIC_INLINE void epicsAtomicWriteMemoryBarrier (void) } #endif -#else - -#ifndef EPICS_ATOMIC_READ_MEMORY_BARRIER -#if GCC_ATOMIC_INTRINSICS_MIN_X86 -#define EPICS_ATOMIC_READ_MEMORY_BARRIER -EPICS_ATOMIC_INLINE void epicsAtomicReadMemoryBarrier (void) -{ - asm("mfence;"); -} -#endif #endif -#ifndef EPICS_ATOMIC_WRITE_MEMORY_BARRIER -#if GCC_ATOMIC_INTRINSICS_MIN_X86 -#define EPICS_ATOMIC_WRITE_MEMORY_BARRIER -EPICS_ATOMIC_INLINE void epicsAtomicWriteMemoryBarrier (void) -{ - asm("mfence;"); -} -#endif -#endif - -#endif /* if GCC_ATOMIC_INTRINSICS_GCC4_OR_BETTER */ - -#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T \ - || GCC_ATOMIC_INTRINSICS_AVAIL_EARLIER +#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T #define EPICS_ATOMIC_INCR_INTT EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget ) @@ -136,10 +99,9 @@ EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); } -#endif /* if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T */ +#endif -#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T \ - || GCC_ATOMIC_INTRINSICS_AVAIL_EARLIER +#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T #define EPICS_ATOMIC_INCR_SIZET EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget ) @@ -180,7 +142,7 @@ EPICS_ATOMIC_INLINE EpicsAtomicPtrT epicsAtomicCmpAndSwapPtrT ( return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); } -#endif /* if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T */ +#endif #ifdef __cplusplus } /* end of extern "C" */ diff --git a/modules/libcom/test/epicsAtomicTest.cpp b/modules/libcom/test/epicsAtomicTest.cpp index 77321af0e..32bae191e 100644 --- a/modules/libcom/test/epicsAtomicTest.cpp +++ b/modules/libcom/test/epicsAtomicTest.cpp @@ -285,15 +285,15 @@ static void testClassify() #endif #ifdef __GNUC__ -#if GCC_ATOMIC_INTRINSICS_GCC4_OR_BETTER +#if GCC_ATOMIC_INTRINSICS_AVAIL_SYNC testDiag("GCC using atomic builtin memory barrier"); #else testDiag("GCC using asm memory barrier"); #endif -#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T || GCC_ATOMIC_INTRINSICS_AVAIL_EARLIER +#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T testDiag("GCC use builtin for int"); #endif -#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T || GCC_ATOMIC_INTRINSICS_AVAIL_EARLIER +#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T testDiag("GCC use builtin for size_t"); #endif From a667cc7aa4e741f480f77711b941b2e507c60da7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 2 Aug 2021 18:26:02 -0500 Subject: [PATCH 019/323] Move GCC+Clang common headers to a new file --- modules/libcom/src/osi/Makefile | 4 + .../src/osi/compiler/clang/epicsAtomicCD.h | 10 ++ .../src/osi/compiler/gcc/epicsAtomicCD.h | 125 +------------- modules/libcom/src/osi/epicsAtomicGCC.h | 156 ++++++++++++++++++ 4 files changed, 171 insertions(+), 124 deletions(-) create mode 100644 modules/libcom/src/osi/epicsAtomicGCC.h diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile index ce9dcc692..a0835d57b 100644 --- a/modules/libcom/src/osi/Makefile +++ b/modules/libcom/src/osi/Makefile @@ -65,6 +65,10 @@ INC += osdVME.h INC += epicsMMIO.h INC += epicsMMIODef.h +INC_clang += epicsAtomicGCC.h +INC_gcc += epicsAtomicGCC.h +INC += $(INC_$(CMPLR_CLASS)) + Com_SRCS += epicsThread.cpp Com_SRCS += epicsMutex.cpp Com_SRCS += epicsEvent.cpp diff --git a/modules/libcom/src/osi/compiler/clang/epicsAtomicCD.h b/modules/libcom/src/osi/compiler/clang/epicsAtomicCD.h index 019ce244c..081828ad5 100644 --- a/modules/libcom/src/osi/compiler/clang/epicsAtomicCD.h +++ b/modules/libcom/src/osi/compiler/clang/epicsAtomicCD.h @@ -17,8 +17,18 @@ #ifndef epicsAtomicCD_h #define epicsAtomicCD_h +#ifndef __clang__ +# error this header is only for use with the Clang compiler +#endif + #define EPICS_ATOMIC_CMPLR_NAME "CLANG" +#include + +/* + * if currently unavailable as intrinsics we + * will try for an os specific inline solution + */ #include "epicsAtomicOSD.h" #endif /* epicsAtomicCD_h */ diff --git a/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h b/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h index a7bcd8f3c..c770b363e 100644 --- a/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h +++ b/modules/libcom/src/osi/compiler/gcc/epicsAtomicCD.h @@ -23,130 +23,7 @@ #define EPICS_ATOMIC_CMPLR_NAME "GCC" -/* expands __GCC_HAVE_SYNC_COMPARE_AND_SWAP_ concatentating the numeric value __SIZEOF_*__ */ -#define GCC_ATOMIC_CONCAT( A, B ) GCC_ATOMIC_CONCATR(A,B) -#define GCC_ATOMIC_CONCATR( A, B ) ( A ## B ) - -/* - * As of GCC 8, the __sync_synchronize() is inlined for all - * known targets (aarch64, arm, i386, powerpc, and x86_64) - * except for arm <=6. - * Note that i386 inlines __sync_synchronize() but does not - * define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_* - */ -#define GCC_ATOMIC_INTRINSICS_AVAIL_SYNC \ - defined(GCC_ATOMIC_INTRINSICS_AVAIL_INT_T) || defined(GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T) || defined(__i386) - -#define GCC_ATOMIC_INTRINSICS_AVAIL_INT_T \ - GCC_ATOMIC_CONCAT ( \ - __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ - __SIZEOF_INT__ ) - -/* we assume __SIZEOF_POINTER__ == __SIZEOF_SIZE_T__ */ -#define GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T \ - GCC_ATOMIC_CONCAT ( \ - __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ - __SIZEOF_SIZE_T__ ) - -#ifdef __cplusplus -extern "C" { -#endif - -#if GCC_ATOMIC_INTRINSICS_AVAIL_SYNC - -#ifndef EPICS_ATOMIC_READ_MEMORY_BARRIER -#define EPICS_ATOMIC_READ_MEMORY_BARRIER -EPICS_ATOMIC_INLINE void epicsAtomicReadMemoryBarrier (void) -{ - __sync_synchronize (); -} -#endif - -#ifndef EPICS_ATOMIC_WRITE_MEMORY_BARRIER -#define EPICS_ATOMIC_WRITE_MEMORY_BARRIER -EPICS_ATOMIC_INLINE void epicsAtomicWriteMemoryBarrier (void) -{ - __sync_synchronize (); -} -#endif - -#endif - -#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T - -#define EPICS_ATOMIC_INCR_INTT -EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget ) -{ - return __sync_add_and_fetch ( pTarget, 1 ); -} - -#define EPICS_ATOMIC_DECR_INTT -EPICS_ATOMIC_INLINE int epicsAtomicDecrIntT ( int * pTarget ) -{ - return __sync_sub_and_fetch ( pTarget, 1 ); -} - -#define EPICS_ATOMIC_ADD_INTT -EPICS_ATOMIC_INLINE int epicsAtomicAddIntT ( int * pTarget, int delta ) -{ - return __sync_add_and_fetch ( pTarget, delta ); -} - -#define EPICS_ATOMIC_CAS_INTT -EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, - int oldVal, int newVal ) -{ - return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); -} - -#endif - -#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T - -#define EPICS_ATOMIC_INCR_SIZET -EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget ) -{ - return __sync_add_and_fetch ( pTarget, 1u ); -} - -#define EPICS_ATOMIC_DECR_SIZET -EPICS_ATOMIC_INLINE size_t epicsAtomicDecrSizeT ( size_t * pTarget ) -{ - return __sync_sub_and_fetch ( pTarget, 1u ); -} - -#define EPICS_ATOMIC_ADD_SIZET -EPICS_ATOMIC_INLINE size_t epicsAtomicAddSizeT ( size_t * pTarget, size_t delta ) -{ - return __sync_add_and_fetch ( pTarget, delta ); -} - -#define EPICS_ATOMIC_SUB_SIZET -EPICS_ATOMIC_INLINE size_t epicsAtomicSubSizeT ( size_t * pTarget, size_t delta ) -{ - return __sync_sub_and_fetch ( pTarget, delta ); -} - -#define EPICS_ATOMIC_CAS_SIZET -EPICS_ATOMIC_INLINE size_t epicsAtomicCmpAndSwapSizeT ( size_t * pTarget, - size_t oldVal, size_t newVal ) -{ - return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); -} - -#define EPICS_ATOMIC_CAS_PTRT -EPICS_ATOMIC_INLINE EpicsAtomicPtrT epicsAtomicCmpAndSwapPtrT ( - EpicsAtomicPtrT * pTarget, - EpicsAtomicPtrT oldVal, EpicsAtomicPtrT newVal ) -{ - return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); -} - -#endif - -#ifdef __cplusplus -} /* end of extern "C" */ -#endif +#include /* * if currently unavailable as gcc intrinsics we diff --git a/modules/libcom/src/osi/epicsAtomicGCC.h b/modules/libcom/src/osi/epicsAtomicGCC.h new file mode 100644 index 000000000..006d6a27e --- /dev/null +++ b/modules/libcom/src/osi/epicsAtomicGCC.h @@ -0,0 +1,156 @@ +/*************************************************************************\ +* Copyright (c) 2011 LANS LLC, as Operator of +* Los Alamos National Laboratory. +* Copyright (c) 2021 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. +\*************************************************************************/ + +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +/* + * These implementations are the same for both GCC and Clang + */ + +#ifndef INC_epicsAtomicGCC_H +#define INC_epicsAtomicGCC_H + +/* expands __GCC_HAVE_SYNC_COMPARE_AND_SWAP_ concatentating + * the numeric value __SIZEOF_*__ + */ +#define GCC_ATOMIC_CONCAT( A, B ) GCC_ATOMIC_CONCATR(A,B) +#define GCC_ATOMIC_CONCATR( A, B ) ( A ## B ) + +#define GCC_ATOMIC_INTRINSICS_AVAIL_INT_T \ + GCC_ATOMIC_CONCAT ( \ + __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ + __SIZEOF_INT__ ) + +/* we assume __SIZEOF_POINTER__ == __SIZEOF_SIZE_T__ */ +#define GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T \ + GCC_ATOMIC_CONCAT ( \ + __GCC_HAVE_SYNC_COMPARE_AND_SWAP_, \ + __SIZEOF_SIZE_T__ ) + +/* + * As of GCC 8, the __sync_synchronize() is inlined for all + * known targets (aarch64, arm, i386, powerpc, and x86_64) + * except for arm <=6. + * Note that i386 inlines __sync_synchronize() but does not + * define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_* + */ +#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T || \ + GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T || \ + defined(__i386) +#define GCC_ATOMIC_INTRINSICS_AVAIL_SYNC 1 +#else +#define GCC_ATOMIC_INTRINSICS_AVAIL_SYNC 0 +#endif +/* The above macro is also used in epicsAtomicTest.cpp */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if GCC_ATOMIC_INTRINSICS_AVAIL_SYNC + +#ifndef EPICS_ATOMIC_READ_MEMORY_BARRIER +#define EPICS_ATOMIC_READ_MEMORY_BARRIER +EPICS_ATOMIC_INLINE void epicsAtomicReadMemoryBarrier (void) +{ + __sync_synchronize (); +} +#endif + +#ifndef EPICS_ATOMIC_WRITE_MEMORY_BARRIER +#define EPICS_ATOMIC_WRITE_MEMORY_BARRIER +EPICS_ATOMIC_INLINE void epicsAtomicWriteMemoryBarrier (void) +{ + __sync_synchronize (); +} +#endif + +#endif + +#if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T + +#define EPICS_ATOMIC_INCR_INTT +EPICS_ATOMIC_INLINE int epicsAtomicIncrIntT ( int * pTarget ) +{ + return __sync_add_and_fetch ( pTarget, 1 ); +} + +#define EPICS_ATOMIC_DECR_INTT +EPICS_ATOMIC_INLINE int epicsAtomicDecrIntT ( int * pTarget ) +{ + return __sync_sub_and_fetch ( pTarget, 1 ); +} + +#define EPICS_ATOMIC_ADD_INTT +EPICS_ATOMIC_INLINE int epicsAtomicAddIntT ( int * pTarget, int delta ) +{ + return __sync_add_and_fetch ( pTarget, delta ); +} + +#define EPICS_ATOMIC_CAS_INTT +EPICS_ATOMIC_INLINE int epicsAtomicCmpAndSwapIntT ( int * pTarget, + int oldVal, int newVal ) +{ + return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); +} + +#endif + +#if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T + +#define EPICS_ATOMIC_INCR_SIZET +EPICS_ATOMIC_INLINE size_t epicsAtomicIncrSizeT ( size_t * pTarget ) +{ + return __sync_add_and_fetch ( pTarget, 1u ); +} + +#define EPICS_ATOMIC_DECR_SIZET +EPICS_ATOMIC_INLINE size_t epicsAtomicDecrSizeT ( size_t * pTarget ) +{ + return __sync_sub_and_fetch ( pTarget, 1u ); +} + +#define EPICS_ATOMIC_ADD_SIZET +EPICS_ATOMIC_INLINE size_t epicsAtomicAddSizeT ( size_t * pTarget, size_t delta ) +{ + return __sync_add_and_fetch ( pTarget, delta ); +} + +#define EPICS_ATOMIC_SUB_SIZET +EPICS_ATOMIC_INLINE size_t epicsAtomicSubSizeT ( size_t * pTarget, size_t delta ) +{ + return __sync_sub_and_fetch ( pTarget, delta ); +} + +#define EPICS_ATOMIC_CAS_SIZET +EPICS_ATOMIC_INLINE size_t epicsAtomicCmpAndSwapSizeT ( size_t * pTarget, + size_t oldVal, size_t newVal ) +{ + return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); +} + +#define EPICS_ATOMIC_CAS_PTRT +EPICS_ATOMIC_INLINE EpicsAtomicPtrT epicsAtomicCmpAndSwapPtrT ( + EpicsAtomicPtrT * pTarget, + EpicsAtomicPtrT oldVal, EpicsAtomicPtrT newVal ) +{ + return __sync_val_compare_and_swap ( pTarget, oldVal, newVal); +} + +#endif + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif /* INC_epicsAtomicGCC_H */ From 540a5c87d91d3be833bfea1ec1951a8563a5ac44 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 2 Aug 2021 18:26:44 -0500 Subject: [PATCH 020/323] Adjust wording of classification descriptions --- modules/libcom/test/epicsAtomicTest.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/libcom/test/epicsAtomicTest.cpp b/modules/libcom/test/epicsAtomicTest.cpp index 32bae191e..a7bd31000 100644 --- a/modules/libcom/test/epicsAtomicTest.cpp +++ b/modules/libcom/test/epicsAtomicTest.cpp @@ -285,16 +285,17 @@ static void testClassify() #endif #ifdef __GNUC__ + /* Also applies to CLANG */ #if GCC_ATOMIC_INTRINSICS_AVAIL_SYNC - testDiag("GCC using atomic builtin memory barrier"); + testDiag("Use " EPICS_ATOMIC_CMPLR_NAME " atomic builtin memory barrier"); #else - testDiag("GCC using asm memory barrier"); + testDiag("Use default memory barrier"); #endif #if GCC_ATOMIC_INTRINSICS_AVAIL_INT_T - testDiag("GCC use builtin for int"); + testDiag("Use " EPICS_ATOMIC_CMPLR_NAME " builtins for int"); #endif #if GCC_ATOMIC_INTRINSICS_AVAIL_SIZE_T - testDiag("GCC use builtin for size_t"); + testDiag("Use " EPICS_ATOMIC_CMPLR_NAME " builtins for size_t"); #endif #ifndef EPICS_ATOMIC_INCR_INTT From 32d76623f26223b51be5f8759127f28525b53aa1 Mon Sep 17 00:00:00 2001 From: JJL772 Date: Wed, 21 Jul 2021 17:02:12 -0700 Subject: [PATCH 021/323] Fix potential memory leak on error In osdThread.c for POSIX if pthread_create_key fails In iocLogServer.c if fdmgr_init returns NULL In dbBkpt.c if semaphore creation fails while adding a bp to a lockset In devSiSoftCallback.c if linked record is not found --- modules/database/src/ioc/db/dbBkpt.c | 1 + modules/database/src/std/dev/devSiSoftCallback.c | 1 + modules/libcom/src/log/iocLogServer.c | 1 + modules/libcom/src/osi/os/posix/osdThread.c | 4 +++- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/database/src/ioc/db/dbBkpt.c b/modules/database/src/ioc/db/dbBkpt.c index 18a357ebe..a32ebab4e 100644 --- a/modules/database/src/ioc/db/dbBkpt.c +++ b/modules/database/src/ioc/db/dbBkpt.c @@ -331,6 +331,7 @@ long dbb(const char *record_name) if (pnode->ex_sem == NULL) { printf(" BKPT> Out of memory\n"); dbScanUnlock(precord); + free(pnode); epicsMutexUnlock(bkpt_stack_sem); return(1); } diff --git a/modules/database/src/std/dev/devSiSoftCallback.c b/modules/database/src/std/dev/devSiSoftCallback.c index 85ec4fcad..f52d595c1 100644 --- a/modules/database/src/std/dev/devSiSoftCallback.c +++ b/modules/database/src/std/dev/devSiSoftCallback.c @@ -108,6 +108,7 @@ static long add_record(dbCommon *pcommon) recGblRecordError(status, (void *)prec, "devSiSoftCallback (add_record) linked record not found"); + free(pdevPvt); return status; } diff --git a/modules/libcom/src/log/iocLogServer.c b/modules/libcom/src/log/iocLogServer.c index 732316196..08f9eca5f 100644 --- a/modules/libcom/src/log/iocLogServer.c +++ b/modules/libcom/src/log/iocLogServer.c @@ -113,6 +113,7 @@ int main(void) pserver->pfdctx = (void *) fdmgr_init(); if (!pserver->pfdctx) { + free(pserver); fprintf(stderr, "iocLogServer: %s\n", strerror(errno)); return IOCLS_ERROR; } diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index 61a67f62c..80c53e3e6 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -956,8 +956,10 @@ LIBCOM_API epicsThreadPrivateId epicsStdCall epicsThreadPrivateCreate(void) return NULL; status = pthread_key_create(key,0); checkStatus(status,"pthread_key_create epicsThreadPrivateCreate"); - if(status) + if(status) { + free(key); return NULL; + } return((epicsThreadPrivateId)key); } From 7c991f3f2a804e98df7ab89d83577694c8503a48 Mon Sep 17 00:00:00 2001 From: JJL772 Date: Wed, 21 Jul 2021 17:26:39 -0700 Subject: [PATCH 022/323] Fix segfault in dbtpn when value parameter is nullptr Running 'dbtpn Record' in iocsh would result in a segfault. --- modules/database/src/ioc/db/dbNotify.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/database/src/ioc/db/dbNotify.c b/modules/database/src/ioc/db/dbNotify.c index e596285c2..7613f3672 100644 --- a/modules/database/src/ioc/db/dbNotify.c +++ b/modules/database/src/ioc/db/dbNotify.c @@ -613,8 +613,10 @@ long dbtpn(char *pname, char *pvalue) ptpnInfo = dbCalloc(1, sizeof(tpnInfo)); ptpnInfo->ppn = ppn; ptpnInfo->callbackDone = epicsEventCreate(epicsEventEmpty); - strncpy(ptpnInfo->buffer, pvalue, 80); - ptpnInfo->buffer[79] = 0; + if (pvalue) { + strncpy(ptpnInfo->buffer, pvalue, sizeof(ptpnInfo->buffer)); + ptpnInfo->buffer[sizeof(ptpnInfo->buffer)-1] = 0; + } ppn->usrPvt = ptpnInfo; epicsThreadCreate("dbtpn", epicsThreadPriorityHigh, From cb8c7998b62701a849a6fa9c299cc1613f66a627 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Fri, 18 Sep 2020 18:37:25 -0700 Subject: [PATCH 023/323] epicsTime: rework Re-implement around epicsTimeStamp (C API) with class epicsTime becoming a wrapper. Prefer epicsInt64 arithmetic. Remove opaque struct l_fp (NTP time conversion) --- modules/libcom/src/osi/epicsTime.cpp | 1082 ++++--------------- modules/libcom/src/osi/epicsTime.h | 699 ++++++------ modules/libcom/src/osi/os/WIN32/osdTime.cpp | 12 +- modules/libcom/test/epicsTimeTest.cpp | 139 ++- 4 files changed, 676 insertions(+), 1256 deletions(-) diff --git a/modules/libcom/src/osi/epicsTime.cpp b/modules/libcom/src/osi/epicsTime.cpp index 044d58cfa..90d4f9810 100644 --- a/modules/libcom/src/osi/epicsTime.cpp +++ b/modules/libcom/src/osi/epicsTime.cpp @@ -12,13 +12,6 @@ /* epicsTime.cpp */ /* Author Jeffrey O. Hill */ -// Notes: -// 1) The epicsTime::nSec field is not public and so it could be -// changed to work more like the fractional seconds field in the NTP time -// stamp. That would significantly improve the precision of epicsTime on -// 64 bit architectures. -// - #include #include @@ -29,480 +22,76 @@ #include #include // vxWorks 6.0 requires this include -#include "locationException.h" +#include "errSymTbl.h" #include "epicsAssert.h" #include "epicsVersion.h" +#include "epicsStdlib.h" +#include "epicsMath.h" #include "envDefs.h" #include "epicsTime.h" #include "osiSock.h" /* pull in struct timeval */ #include "epicsStdio.h" -static const char pEpicsTimeVersion[] = - "@(#) " EPICS_VERSION_STRING ", Common Utilities Library"; - -// -// useful public constants -// -static const unsigned mSecPerSec = 1000u; -static const unsigned uSecPerMSec = 1000u; -static const unsigned uSecPerSec = uSecPerMSec * mSecPerSec; -static const unsigned nSecPerUSec = 1000u; -static const unsigned nSecPerSec = nSecPerUSec * uSecPerSec; +static const epicsUInt32 nSecPerSec = 1000000000u; static const unsigned nSecFracDigits = 9u; -// Timescale conversion data - -static const unsigned long NTP_TIME_AT_POSIX_EPOCH = 2208988800ul; -static const unsigned long NTP_TIME_AT_EPICS_EPOCH = - NTP_TIME_AT_POSIX_EPOCH + POSIX_TIME_AT_EPICS_EPOCH; - -// -// epicsTime (const unsigned long secIn, const unsigned long nSecIn) -// -inline epicsTime::epicsTime (const unsigned long secIn, const unsigned long nSecIn) : - secPastEpoch ( secIn ), nSec ( nSecIn ) +void epicsTime::throwError(int code) { - if (nSecIn >= nSecPerSec) { - this->secPastEpoch += nSecIn / nSecPerSec; - this->nSec = nSecIn % nSecPerSec; - } -} - -// -// epicsTimeLoadTimeInit -// -class epicsTimeLoadTimeInit { -public: - epicsTimeLoadTimeInit (); - double epicsEpochOffset; // seconds - double time_tSecPerTick; // seconds (both NTP and EPICS use int sec) - unsigned long epicsEpochOffsetAsAnUnsignedLong; - bool useDiffTimeOptimization; -}; - -// -// epicsTimeLoadTimeInit () -// -epicsTimeLoadTimeInit::epicsTimeLoadTimeInit () -{ - // All we know about time_t is that it is an arithmetic type. - time_t t_zero = static_cast (0); - time_t t_one = static_cast (1); - this->time_tSecPerTick = difftime (t_one, t_zero); - - /* The EPICS epoch (1/1/1990 00:00:00UTC) was 631152000 seconds after - * the ANSI epoch (1/1/1970 00:00:00UTC) - * Convert this offset into time_t units, however this must not be - * calculated using local time (i.e. using mktime() or similar), since - * in the UK the ANSI Epoch had daylight saving time in effect, and - * the value calculated would be 3600 seconds wrong.*/ - this->epicsEpochOffset = - (double) POSIX_TIME_AT_EPICS_EPOCH / this->time_tSecPerTick; - - if (this->time_tSecPerTick == 1.0 && - this->epicsEpochOffset <= ULONG_MAX && - this->epicsEpochOffset >= 0) { - // We can use simpler code on Posix-compliant systems - this->useDiffTimeOptimization = true; - this->epicsEpochOffsetAsAnUnsignedLong = - static_cast(this->epicsEpochOffset); - } else { - // Forced to use the slower but correct code - this->useDiffTimeOptimization = false; - this->epicsEpochOffsetAsAnUnsignedLong = 0; - } -} - -// -// private epicsTime::addNanoSec () -// -// Most formats keep the nSec value as an unsigned long, so are +ve. -// struct timeval's tv_usec may be -1, but I think that means error, -// so this private method never needs to handle -ve offsets. -// -void epicsTime :: addNanoSec ( long nSecAdj ) -{ - if (nSecAdj <= 0) + if(code==epicsTimeOK) return; - - if (static_cast(nSecAdj) >= nSecPerSec) { - this->secPastEpoch += nSecAdj / nSecPerSec; - nSecAdj %= nSecPerSec; - } - - this->nSec += nSecAdj; // Can't overflow - if (this->nSec >= nSecPerSec) { - this->secPastEpoch++; - this->nSec -= nSecPerSec; - } + throw std::logic_error(errSymMsg(code)); } -// -// epicsTime (const time_t_wrapper &tv) -// -epicsTime::epicsTime ( const time_t_wrapper & ansiTimeTicks ) -{ - // avoid c++ static initialization order issues - static epicsTimeLoadTimeInit & lti = * new epicsTimeLoadTimeInit (); - // - // try to directly map time_t into an unsigned long integer because this is - // faster on systems w/o hardware floating point and a simple integer type time_t. - // - if ( lti.useDiffTimeOptimization ) { - // LONG_MAX is used here and not ULONG_MAX because some systems (linux) - // still store time_t as a long. - if ( ansiTimeTicks.ts > 0 && ansiTimeTicks.ts <= LONG_MAX ) { - unsigned long ticks = static_cast < unsigned long > ( ansiTimeTicks.ts ); - if ( ticks >= lti.epicsEpochOffsetAsAnUnsignedLong ) { - this->secPastEpoch = ticks - lti.epicsEpochOffsetAsAnUnsignedLong; - } - else { - this->secPastEpoch = ( ULONG_MAX - lti.epicsEpochOffsetAsAnUnsignedLong ) + ticks; - } - this->nSec = 0; - return; - } - } - - // - // otherwise map time_t, which ANSI C and POSIX define as any arithmetic type, - // into type double - // - double sec = ansiTimeTicks.ts * lti.time_tSecPerTick - lti.epicsEpochOffset; - - // - // map into the the EPICS time stamp range (which allows rollover) - // - static double uLongMax = static_cast (ULONG_MAX); - if ( sec < 0.0 ) { - if ( sec < -uLongMax ) { - sec = sec + static_cast ( -sec / uLongMax ) * uLongMax; - } - sec += uLongMax; - } - else if ( sec > uLongMax ) { - sec = sec - static_cast ( sec / uLongMax ) * uLongMax; - } - - this->secPastEpoch = static_cast ( sec ); - this->nSec = static_cast ( ( sec-this->secPastEpoch ) * nSecPerSec ); +epicsTime::epicsTime ( const epicsTimeStamp & replace ) { + ts = replace; + if(ts.nsec >= nSecPerSec) + throw std::logic_error("epicsTimeStamp has overflow in nano-seconds field"); } -epicsTime::epicsTime (const epicsTimeStamp &ts) -{ - if ( ts.nsec < nSecPerSec ) { - this->secPastEpoch = ts.secPastEpoch; - this->nSec = ts.nsec; - } - else { - throw std::logic_error ( - "epicsTimeStamp has overflow in nano-seconds field" ); - } -} - -epicsTime::epicsTime () : - secPastEpoch(0u), nSec(0u) {} - epicsTime epicsTime::getCurrent () { epicsTimeStamp current; int status = epicsTimeGetCurrent (¤t); if (status) { - throwWithLocation ( unableToFetchCurrentTime () ); + throw unableToFetchCurrentTime ("Unable to fetch Current Time"); } return epicsTime ( current ); } -epicsTime epicsTime::getMonotonic() -{ - epicsTimeStamp current; - epicsTimeGetMonotonic (¤t); // can't fail - return epicsTime ( current ); -} - epicsTime epicsTime::getEvent (const epicsTimeEvent &event) { epicsTimeStamp current; int status = epicsTimeGetEvent (¤t, event); if (status) { - throwWithLocation ( unableToFetchCurrentTime () ); + throw unableToFetchCurrentTime ("Unable to fetch Event Time"); } return epicsTime ( current ); } -// -// operator time_t_wrapper () -// -epicsTime::operator time_t_wrapper () const -{ - // avoid c++ static initialization order issues - static epicsTimeLoadTimeInit & lti = * new epicsTimeLoadTimeInit (); - time_t_wrapper wrap; - - if ( lti.useDiffTimeOptimization ) { - if ( this->secPastEpoch < ULONG_MAX - lti.epicsEpochOffsetAsAnUnsignedLong ) { - wrap.ts = static_cast ( this->secPastEpoch + lti.epicsEpochOffsetAsAnUnsignedLong ); - return wrap; - } - } - - // - // map type double into time_t which ansi C defines as some arithmetic type - // - double tmp = (this->secPastEpoch + lti.epicsEpochOffset) / lti.time_tSecPerTick; - tmp += (this->nSec / lti.time_tSecPerTick) / nSecPerSec; - - wrap.ts = static_cast ( tmp ); - - return wrap; +epicsTime::operator struct timeval () const { + timeval ret; + epicsTimeToTimeval(&ret, &ts); + return ret; } -// -// convert to ANSI C struct tm (with nano seconds) adjusted for the local time zone -// -epicsTime::operator local_tm_nano_sec () const -{ - time_t_wrapper ansiTimeTicks = *this; - - local_tm_nano_sec tm; - - int status = epicsTime_localtime ( &ansiTimeTicks.ts, &tm.ansi_tm ); - if ( status ) { - throw std::logic_error ( "epicsTime_localtime failed" ); - } - - tm.nSec = this->nSec; - - return tm; +epicsTime::epicsTime ( const struct timeval & replace) { + throwError(epicsTimeFromTimeval(&ts, &replace)); } -// -// convert to ANSI C struct tm (with nano seconds) adjusted for UTC -// -epicsTime::operator gm_tm_nano_sec () const -{ - time_t_wrapper ansiTimeTicks = *this; - - gm_tm_nano_sec tm; - - int status = epicsTime_gmtime ( &ansiTimeTicks.ts, &tm.ansi_tm ); - if ( status ) { - throw std::logic_error ( "epicsTime_gmtime failed" ); - } - - tm.nSec = this->nSec; - - return tm; +epicsTime & epicsTime::operator = ( const struct timeval & replace) { + throwError(epicsTimeFromTimeval(&ts, &replace)); + return *this; } -// -// epicsTime (const local_tm_nano_sec &tm) -// -epicsTime::epicsTime (const local_tm_nano_sec &tm) +std::ostream& operator<<(std::ostream& strm, const epicsTime& ts) { - struct tm tmp = tm.ansi_tm; - time_t_wrapper ansiTimeTicks = { mktime (&tmp) }; + char temp[64]; - static const time_t mktimeError = static_cast (-1); - if (ansiTimeTicks.ts == mktimeError) { - throwWithLocation ( formatProblemWithStructTM () ); - } - - *this = epicsTime(ansiTimeTicks); - this->addNanoSec(tm.nSec); -} - -// -// epicsTime (const gm_tm_nano_sec &tm) -// - -// do conversion avoiding the timezone mechanism -static inline int is_leap(int year) -{ - if (year % 400 == 0) - return 1; - if (year % 100 == 0) - return 0; - if (year % 4 == 0) - return 1; - return 0; -} - -static inline int days_from_0(int year) -{ - year--; - return 365 * year + (year / 400) - (year / 100) + (year / 4); -} - -static inline int days_from_1970(int year) -{ - static const int days_from_0_to_1970 = days_from_0(1970); - return days_from_0(year) - days_from_0_to_1970; -} - -static inline int days_from_1jan(int year, int month, int day) -{ - static const int days[2][12] = - { - { 0,31,59,90,120,151,181,212,243,273,304,334}, - { 0,31,60,91,121,152,182,213,244,274,305,335} - }; - return days[is_leap(year)][month-1] + day - 1; -} - -epicsTime::epicsTime (const gm_tm_nano_sec &tm) -{ - int year = tm.ansi_tm.tm_year + 1900; - int month = tm.ansi_tm.tm_mon; - if (month > 11) { - year += month / 12; - month %= 12; - } else if (month < 0) { - int years_diff = (-month + 11) / 12; - year -= years_diff; - month += 12 * years_diff; - } - month++; - - int day = tm.ansi_tm.tm_mday; - int day_of_year = days_from_1jan(year, month, day); - int days_since_epoch = days_from_1970(year) + day_of_year; - - time_t_wrapper ansiTimeTicks; - ansiTimeTicks.ts = ((days_since_epoch - * 24 + tm.ansi_tm.tm_hour) - * 60 + tm.ansi_tm.tm_min) - * 60 + tm.ansi_tm.tm_sec; - - *this = epicsTime(ansiTimeTicks); - this->addNanoSec(tm.nSec); -} - -// -// operator struct timespec () -// -epicsTime::operator struct timespec () const -{ - struct timespec ts; - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks = *this; - ts.tv_sec = ansiTimeTicks.ts; - ts.tv_nsec = static_cast (this->nSec); - return ts; -} - -// -// epicsTime (const struct timespec &ts) -// -epicsTime::epicsTime (const struct timespec &ts) -{ - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks.ts = ts.tv_sec; - *this = epicsTime (ansiTimeTicks); - this->addNanoSec (ts.tv_nsec); -} - -// -// operator struct timeval () -// -epicsTime::operator struct timeval () const -{ - struct timeval ts; - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks = *this; - // On Posix systems timeval :: tv_sec is a time_t so this can be - // a direct assignment. On other systems I dont know that we can - // guarantee that time_t and timeval :: tv_sec will have the - // same epoch or have the same scaling factor to discrete seconds. - // For example, on windows time_t changed recently to a 64 bit - // quantity but timeval is still a long. That can cause problems - // on 32 bit systems. So technically, we should have an os - // dependent conversion between time_t and timeval :: tv_sec? - ts.tv_sec = ansiTimeTicks.ts; - ts.tv_usec = static_cast < long > ( this->nSec / nSecPerUSec ); - return ts; -} - -// -// epicsTime (const struct timeval &ts) -// -epicsTime::epicsTime (const struct timeval &ts) -{ - time_t_wrapper ansiTimeTicks; - // On Posix systems timeval :: tv_sec is a time_t so this can be - // a direct assignment. On other systems I dont know that we can - // guarantee that time_t and timeval :: tv_sec will have the - // same epoch or have the same scaling factor to discrete seconds. - // For example, on windows time_t changed recently to a 64 bit - // quantity but timeval is still a long. That can cause problems - // on 32 bit systems. So technically, we should have an os - // dependent conversion between time_t and timeval :: tv_sec? - ansiTimeTicks.ts = ts.tv_sec; - *this = epicsTime (ansiTimeTicks); - this->addNanoSec (ts.tv_usec * nSecPerUSec); -} - - -static const double NTP_FRACTION_DENOMINATOR = 1.0 + 0xffffffff; - -struct l_fp { /* NTP time stamp */ - epicsUInt32 l_ui; /* sec past NTP epoch */ - epicsUInt32 l_uf; /* fractional seconds */ -}; - -// -// epicsTime::l_fp () -// -epicsTime::operator l_fp () const -{ - l_fp ts; - ts.l_ui = this->secPastEpoch + NTP_TIME_AT_EPICS_EPOCH; - ts.l_uf = static_cast < unsigned long > - ( ( this->nSec * NTP_FRACTION_DENOMINATOR ) / nSecPerSec ); - return ts; -} - -// -// epicsTime::epicsTime ( const l_fp & ts ) -// -epicsTime::epicsTime ( const l_fp & ts ) -{ - this->secPastEpoch = ts.l_ui - NTP_TIME_AT_EPICS_EPOCH; - this->nSec = static_cast < unsigned long > - ( ( ts.l_uf / NTP_FRACTION_DENOMINATOR ) * nSecPerSec ); -} - -epicsTime::operator epicsTimeStamp () const -{ - if ( this->nSec >= nSecPerSec ) { - throw std::logic_error ( - "epicsTimeStamp has overflow in nano-seconds field?" ); - } - epicsTimeStamp ts; - // - // truncation by design - // ------------------- - // epicsTime::secPastEpoch is based on ulong and has much greater range - // on 64 bit hosts than the original epicsTimeStamp::secPastEpoch. The - // epicsTimeStamp::secPastEpoch is based on epicsUInt32 so that it will - // match the original network protocol. Of course one can anticipate - // that eventually, a epicsUInt64 based network time stamp will be - // introduced when 64 bit architectures are more ubiquitous. - // - // Truncation usually works fine here because the routines in this code - // that compute time stamp differences and compare time stamps produce - // good results when the operands are on either side of a time stamp - // rollover as long as the difference between the operands does not exceed - // 1/2 of full range. - // - ts.secPastEpoch = static_cast < epicsUInt32 > ( this->secPastEpoch ); - ts.nsec = static_cast < epicsUInt32 > ( this->nSec ); - return ts; + (void)ts.strftime(temp, sizeof(temp), "%Y-%m-%d %H:%M:%S.%09f"); + temp[sizeof(temp)-1u] = '\0'; + return strm<%0f" @@ -522,7 +111,7 @@ static const char * fracFormatFind ( unsigned long & fracFmtWidth ) { assert ( prefixBufLen > 1 ); - unsigned long width = ULONG_MAX; + unsigned long width = 0xffffffff; bool fracFound = false; const char * pAfter = pFormat; const char * pFmt = pFormat; @@ -576,15 +165,14 @@ static const char * fracFormatFind ( // // size_t epicsTime::strftime () // -size_t epicsTime::strftime ( - char * pBuff, size_t bufLength, const char * pFormat ) const +size_t epicsStdCall epicsTimeToStrftime (char *pBuff, size_t bufLength, const char *pFormat, const epicsTimeStamp *pTS) { if ( bufLength == 0u ) { return 0u; } // presume that EPOCH date is an uninitialized time stamp - if ( this->secPastEpoch == 0 && this->nSec == 0u ) { + if ( pTS->secPastEpoch == 0 && pTS->nsec == 0u ) { strncpy ( pBuff, "", bufLength ); pBuff[bufLength-1] = '\0'; return strlen ( pBuff ); @@ -609,9 +197,10 @@ size_t epicsTime::strftime ( } // all but fractional seconds use strftime formatting if ( strftimePrefixBuf[0] != '\0' ) { - local_tm_nano_sec tmns = *this; + tm tm; + (void)epicsTimeToTM(&tm, 0, pTS); size_t strftimeNumChar = :: strftime ( - pBufCur, bufLenLeft, strftimePrefixBuf, & tmns.ansi_tm ); + pBufCur, bufLenLeft, strftimePrefixBuf, &tm ); pBufCur [ strftimeNumChar ] = '\0'; pBufCur += strftimeNumChar; bufLenLeft -= strftimeNumChar; @@ -625,8 +214,9 @@ size_t epicsTime::strftime ( // verify that there are enough chars left for the fractional seconds if ( fracWid < bufLenLeft ) { - local_tm_nano_sec tmns = *this; - if ( tmns.nSec < nSecPerSec ) { + tm tm; + (void)epicsTimeToTM(&tm, 0, pTS); + if ( pTS->nsec < nSecPerSec ) { // divisors for fraction (see below) static const unsigned long div[] = { static_cast < unsigned long > ( 1e9 ), @@ -641,7 +231,7 @@ size_t epicsTime::strftime ( static_cast < unsigned long > ( 1e0 ) }; // round without overflowing into whole seconds - unsigned long frac = tmns.nSec + div[fracWid] / 2; + unsigned long frac = pTS->nsec + div[fracWid] / 2; if (frac >= nSecPerSec) frac = nSecPerSec - 1; // convert nanosecs to integer of correct range @@ -691,449 +281,199 @@ size_t epicsTime::strftime ( // // epicsTime::show (unsigned) // -void epicsTime::show ( unsigned level ) const +void epicsStdCall epicsTimeShow (const epicsTimeStamp *pTS, unsigned level) { char bigBuffer[256]; - size_t numChar = this->strftime ( bigBuffer, sizeof ( bigBuffer ), - "%a %b %d %Y %H:%M:%S.%09f" ); + size_t numChar = epicsTimeToStrftime( bigBuffer, sizeof ( bigBuffer ), + "%a %b %d %Y %H:%M:%S.%09f", pTS ); if ( numChar > 0 ) { printf ( "epicsTime: %s\n", bigBuffer ); } - - if ( level > 1 ) { - // this also suppresses the "defined, but not used" - // warning message - printf ( "epicsTime: revision \"%s\"\n", - pEpicsTimeVersion ); - } - } -// -// epicsTime::operator + (const double &rhs) -// -// rhs has units seconds -// -epicsTime epicsTime::operator + (const double &rhs) const +int epicsStdCall epicsTimeToTime_t (time_t *pDest, const epicsTimeStamp *pSrc) { - unsigned long newSec, newNSec, secOffset, nSecOffset; - double fnsec; + STATIC_ASSERT(sizeof(*pDest) >= sizeof(pSrc->secPastEpoch)); - if (rhs >= 0) { - secOffset = static_cast (rhs); - fnsec = rhs - secOffset; - nSecOffset = static_cast ( (fnsec * nSecPerSec) + 0.5 ); - - newSec = this->secPastEpoch + secOffset; // overflow expected - newNSec = this->nSec + nSecOffset; - if (newNSec >= nSecPerSec) { - newSec++; // overflow expected - newNSec -= nSecPerSec; - } - } - else { - secOffset = static_cast (-rhs); - fnsec = rhs + secOffset; - nSecOffset = static_cast ( (-fnsec * nSecPerSec) + 0.5 ); - - newSec = this->secPastEpoch - secOffset; // underflow expected - if (this->nSec>=nSecOffset) { - newNSec = this->nSec - nSecOffset; - } - else { - // borrow - newSec--; // underflow expected - newNSec = this->nSec + (nSecPerSec - nSecOffset); - } - } - return epicsTime (newSec, newNSec); + // widen to 64-bit to (eventually) accomidate 64-bit time_t + *pDest = epicsUInt64(pSrc->secPastEpoch) + POSIX_TIME_AT_EPICS_EPOCH; + return epicsTimeOK; } -// -// operator - -// -// To make this code robust during timestamp rollover events -// time stamp differences greater than one half full scale are -// interpreted as rollover situations: -// -// when RHS is greater than THIS: -// RHS-THIS > one half full scale => return THIS + (ULONG_MAX-RHS) -// RHS-THIS <= one half full scale => return -(RHS-THIS) -// -// when THIS is greater than or equal to RHS -// THIS-RHS > one half full scale => return -(RHS + (ULONG_MAX-THIS)) -// THIS-RHS <= one half full scale => return THIS-RHS -// -double epicsTime::operator - (const epicsTime &rhs) const +int epicsStdCall epicsTimeFromTime_t (epicsTimeStamp *pDest, time_t src) { - double nSecRes, secRes; - - // - // first compute the difference between the nano-seconds members - // - // nano sec member is not allowed to be greater that 1/2 full scale - // so the unsigned to signed conversion is ok - // - if (this->nSec>=rhs.nSec) { - nSecRes = this->nSec - rhs.nSec; - } - else { - nSecRes = rhs.nSec - this->nSec; - nSecRes = -nSecRes; - } - - // - // next compute the difference between the seconds members - // and invert the sign of the nano seconds result if there - // is a range violation - // - if (this->secPastEpochsecPastEpoch; - if (secRes > ULONG_MAX/2) { - // - // In this situation where the difference is more than - // 68 years assume that the seconds counter has rolled - // over and compute the "wrap around" difference - // - secRes = 1 + (ULONG_MAX-secRes); - nSecRes = -nSecRes; - } - else { - secRes = -secRes; - } - } - else { - secRes = this->secPastEpoch - rhs.secPastEpoch; - if (secRes > ULONG_MAX/2) { - // - // In this situation where the difference is more than - // 68 years assume that the seconds counter has rolled - // over and compute the "wrap around" difference - // - secRes = 1 + (ULONG_MAX-secRes); - secRes = -secRes; - nSecRes = -nSecRes; - } - } - - return secRes + nSecRes/nSecPerSec; + pDest->secPastEpoch = epicsInt64(src) - POSIX_TIME_AT_EPICS_EPOCH; + pDest->nsec = 0; + return epicsTimeOK; } -// -// operator <= -// -bool epicsTime::operator <= (const epicsTime &rhs) const +int epicsStdCall epicsTimeToTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) { - bool rc; - - if (this->secPastEpochsecPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = true; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = false; - } - } - else if (this->secPastEpoch>rhs.secPastEpoch) { - if (this->secPastEpoch-rhs.secPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = false; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = true; - } - } - else { - if (this->nSec<=rhs.nSec) { - rc = true; - } - else { - rc = false; - } - } - return rc; + time_t temp; + int err; + err = epicsTimeToTime_t(&temp, pSrc); + if(!err) + err = epicsTime_localtime(&temp, pDest); + if(!err && pNSecDest) + *pNSecDest = pSrc->nsec; + return err; } -// -// operator < -// -bool epicsTime::operator < (const epicsTime &rhs) const +int epicsStdCall epicsTimeToGMTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) { - bool rc; - - if (this->secPastEpochsecPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = true; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = false; - } - } - else if (this->secPastEpoch>rhs.secPastEpoch) { - if (this->secPastEpoch-rhs.secPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = false; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = true; - } - } - else { - if (this->nSecnsec; + return err; } -extern "C" { - // - // ANSI C interface - // - // its too bad that these cant be implemented with inline functions - // at least when running the GNU compiler - // - LIBCOM_API int epicsStdCall epicsTimeToTime_t (time_t *pDest, const epicsTimeStamp *pSrc) - { - try { - time_t_wrapper dst = epicsTime (*pSrc); - *pDest = dst.ts; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTime_t (epicsTimeStamp *pDest, time_t src) - { - try { - time_t_wrapper dst; - dst.ts = src; - *pDest = epicsTime ( dst ); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) - { - try { - local_tm_nano_sec tmns = epicsTime (*pSrc); - *pDest = tmns.ansi_tm; - if (pNSecDest) - *pNSecDest = tmns.nSec; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToGMTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) - { - try { - gm_tm_nano_sec gmtmns = epicsTime (*pSrc); - *pDest = gmtmns.ansi_tm; - if (pNSecDest) - *pNSecDest = gmtmns.nSec; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) - { - try { - local_tm_nano_sec tmns; - tmns.ansi_tm = *pSrc; - tmns.nSec = nSecSrc; - *pDest = epicsTime (tmns); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromGMTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) - { - try { - gm_tm_nano_sec tmns; - tmns.ansi_tm = *pSrc; - tmns.nSec = nSecSrc; - *pDest = epicsTime (tmns); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTimespec (struct timespec *pDest, const epicsTimeStamp *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTimespec (epicsTimeStamp *pDest, const struct timespec *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTimeval (struct timeval *pDest, const epicsTimeStamp *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTimeval (epicsTimeStamp *pDest, const struct timeval *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API double epicsStdCall epicsTimeDiffInSeconds (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) - epicsTime (*pRight); - } - catch (...) { - return - DBL_MAX; - } - } - LIBCOM_API void epicsStdCall epicsTimeAddSeconds (epicsTimeStamp *pDest, double seconds) - { - try { - *pDest = epicsTime (*pDest) + seconds; - } - catch ( ... ) { - *pDest = epicsTime (); - } - } - LIBCOM_API int epicsStdCall epicsTimeEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) == epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeNotEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) != epicsTime (*pRight); - } - catch ( ... ) { - return 1; - } - } - LIBCOM_API int epicsStdCall epicsTimeLessThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) < epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeLessThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) <= epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeGreaterThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) > epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeGreaterThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) >= epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API size_t epicsStdCall epicsTimeToStrftime (char *pBuff, size_t bufLength, const char *pFormat, const epicsTimeStamp *pTS) - { - try { - return epicsTime(*pTS).strftime (pBuff, bufLength, pFormat); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API void epicsStdCall epicsTimeShow (const epicsTimeStamp *pTS, unsigned interestLevel) - { - try { - epicsTime(*pTS).show (interestLevel); - } - catch ( ... ) { - printf ( "Invalid epicsTimeStamp\n" ); - } - } +int epicsStdCall epicsTimeFromTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) +{ + tm temp = *pSrc; // mktime() modifies (at least) tm_wday and tm_yday + time_t tsrc = mktime(&temp); + int err = epicsTimeFromTime_t(pDest, tsrc); + if(!err) + pDest->nsec = nSecSrc; + return err; +} + +#ifdef _WIN32 +# define timegm _mkgmtime + +#elif defined(__rtems__) || defined(vxWorks) + +static +time_t timegm(tm* gtm) +{ + // ugly hack for targets without timegm(tm*), but which have mktime(tm*). + // probably has issues near start/end of DST + + // translate to seconds as if a local time. off by TZ offset + time_t fakelocal = mktime(gtm); + // now use gmtime() which applies the TZ offset again, but with the wrong sign + tm wrongtm; + epicsTime_gmtime(&fakelocal, &wrongtm); + // translate this to seconds + time_t fakex2 = mktime(&wrongtm); + + // tzoffset = fakelocal - fakex2; + + return epicsInt64(fakelocal)*2 - fakex2; +} + +#endif + +int epicsStdCall epicsTimeFromGMTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) +{ + tm temp = *pSrc; // timegm() may modify (at least) tm_wday and tm_yday + time_t tsrc = timegm(&temp); + int err = epicsTimeFromTime_t(pDest, tsrc); + if(!err) + pDest->nsec = nSecSrc; + return err; +} + +int epicsStdCall epicsTimeToTimespec (struct timespec *pDest, const epicsTimeStamp *pSrc) +{ + int err = epicsTimeToTime_t(&pDest->tv_sec, pSrc); + if(!err) + pDest->tv_nsec = pSrc->nsec; + return err; +} + +int epicsStdCall epicsTimeFromTimespec (epicsTimeStamp *pDest, const struct timespec *pSrc) +{ + int err = epicsTimeFromTime_t(pDest, pSrc->tv_sec); + if(!err) + pDest->nsec = pSrc->tv_nsec; + return err; +} + +int epicsStdCall epicsTimeToTimeval (struct timeval *pDest, const epicsTimeStamp *pSrc) +{ + time_t temp; + int err = epicsTimeToTime_t(&temp, pSrc); + if(!err) { + pDest->tv_sec = temp; // tv_sec is not time_t on windows + pDest->tv_usec = pSrc->nsec/1000u; + } + return err; +} + +int epicsStdCall epicsTimeFromTimeval (epicsTimeStamp *pDest, const struct timeval *pSrc) +{ + int err = epicsTimeFromTime_t(pDest, pSrc->tv_sec); + if(!err) + pDest->nsec = pSrc->tv_usec*1000u; + return err; +} + +double epicsStdCall epicsTimeDiffInSeconds (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + /* double(*pLeft - *pRight) + * + * 0xffffffff*1000000000 < 2**62 + * 0x200000000*1000000002 < 2**63 + * so there is (just barely) space to add 2 TSs as signed 64-bit integers without overflow + */ + + // handle over/underflow as u32 when subtracting + epicsInt64 nsec = epicsInt32(pLeft->secPastEpoch - pRight->secPastEpoch); + nsec *= nSecPerSec; + nsec += epicsInt32(pLeft->nsec) - epicsInt32(pRight->nsec); + + return double(nsec)*1e-9; +} + +void epicsStdCall epicsTimeAddSeconds (epicsTimeStamp *pDest, double seconds) +{ + epicsInt64 nsec = pDest->secPastEpoch; + nsec *= nSecPerSec; + nsec += epicsInt64(pDest->nsec); + nsec += epicsInt64(seconds*1e9 + (seconds>=0.0 ? 0.5 : -0.5)); + pDest->secPastEpoch = nsec/nSecPerSec; + pDest->nsec = (nsec>=0 ? nsec : -nsec)%nSecPerSec; +} + +int epicsStdCall epicsTimeEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return pLeft->secPastEpoch == pRight->secPastEpoch && pLeft->nsec == pRight->nsec; +} + +int epicsStdCall epicsTimeNotEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return !epicsTimeEqual(pLeft, pRight); +} + +epicsInt64 epicsStdCall epicsTimeDiffInNS (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + epicsInt64 delta = epicsInt64(pLeft->secPastEpoch) - pRight->secPastEpoch; + delta *= nSecPerSec; + delta += epicsInt64(pLeft->nsec) - pRight->nsec; + return delta; +} + +int epicsStdCall epicsTimeLessThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) < 0; +} + +int epicsStdCall epicsTimeLessThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) <= 0; +} + +int epicsStdCall epicsTimeGreaterThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) > 0; +} + +int epicsStdCall epicsTimeGreaterThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) >= 0; } diff --git a/modules/libcom/src/osi/epicsTime.h b/modules/libcom/src/osi/epicsTime.h index 090fb5c48..8996c3733 100644 --- a/modules/libcom/src/osi/epicsTime.h +++ b/modules/libcom/src/osi/epicsTime.h @@ -26,6 +26,14 @@ /** \brief The EPICS Epoch is 00:00:00 Jan 1, 1990 UTC */ #define POSIX_TIME_AT_EPICS_EPOCH 631152000u +#ifdef __cplusplus + +#include +#include + +extern "C" { +#endif + /** \brief EPICS time stamp, for use from C code. * * Because it uses an unsigned 32-bit integer to hold the seconds count, an @@ -65,277 +73,6 @@ struct timespec; /* POSIX real time */ */ struct timeval; /* BSD */ -/** \struct l_fp - * \brief Network Time Protocol timestamp - * - * Network Time Protocol timestamp. The fields are: - * \li \c lui - Number of seconds since 1900 (The NTP epoch) - * \li \c luf - Fraction of a second. For example 0x800000000 represents 1/2 second. - */ -struct l_fp; /* NTP timestamp */ - -#ifdef __cplusplus - -/** \brief C++ only ANSI C struct tm with nanoseconds, local timezone - * - * Extend ANSI C "struct tm" to include nano seconds within a second - * and a struct tm that is adjusted for the local timezone. - */ -struct local_tm_nano_sec { - struct tm ansi_tm; /**< \brief ANSI C time details */ - unsigned long nSec; /**< \brief nanoseconds extension */ -}; - -/** \brief C++ only ANSI C sruct tm with nanoseconds, UTC - * - * Extend ANSI C "struct tm" to include nanoseconds within a second - * and a struct tm that is adjusted for GMT (UTC). - */ -struct gm_tm_nano_sec { - struct tm ansi_tm; /**< \brief ANSI C time details */ - unsigned long nSec; /**< \brief nanoseconds extension */ -}; - -/** \brief C++ only ANSI C time_t - * - * This is for converting to/from the ANSI C \c time_t. Since \c time_t - * is usually an elementary type providing a conversion operator from - * \c time_t to/from epicsTime could cause undesirable implicit - * conversions. Providing a conversion operator to/from the - * \c time_t_wrapper instead prevents implicit conversions. - */ -struct time_t_wrapper { - time_t ts; -}; - -/** \brief C++ Event number wrapper class - * - * Stores an event number for use by the epicsTime::getEvent() static - * class method. - */ -class LIBCOM_API epicsTimeEvent -{ -public: - epicsTimeEvent (const int &number); /**< \brief Constructor */ - operator int () const; /**< \brief Extractor */ -private: - int eventNumber; -}; - -/** \brief C++ time stamp object - * - * Holds an EPICS time stamp, and provides conversion functions for both - * input and output from/to other types. - * - * \note Time conversions: The epicsTime implementation will properly - * convert between the various formats from the beginning of the EPICS - * epoch until at least 2038. Unless the underlying architecture support - * has defective POSIX, BSD/SRV5, or standard C time support the EPICS - * implementation should be valid until 2106. - */ -class LIBCOM_API epicsTime -{ -public: - /// \brief Exception: Time provider problem - class unableToFetchCurrentTime {}; - /// \brief Exception: Bad field(s) in struct tm - class formatProblemWithStructTM {}; - - /** \brief The default constructor sets the time to the EPICS epoch. */ - epicsTime (); - - /** \brief Get time of event system event. - * - * Returns an epicsTime indicating when the associated event system - * event last occurred. - */ - static epicsTime getEvent ( const epicsTimeEvent & ); - /** \brief Get current clock time - * - * Returns an epicsTime containing the current time. For example: - * \code{.cpp} - * epicsTime now = epicsTime::getCurrent(); - * \endcode - */ - static epicsTime getCurrent (); - /** \brief Get current monotonic time - * - * Returns an epicsTime containing the current monotonic time, an - * OS clock which never going backwards or jumping forwards. - * This time is has an undefined epoch, and is only useful for - * measuring time differences. - */ - static epicsTime getMonotonic (); - - /** \name epicsTimeStamp conversions - * Convert to and from EPICS epicsTimeStamp format - * @{ */ - /** \brief Convert to epicsTimeStamp */ - operator epicsTimeStamp () const; - /** \brief Construct from epicsTimeStamp */ - epicsTime ( const epicsTimeStamp & ts ); - /** \brief Assign from epicsTimeStamp */ - epicsTime & operator = ( const epicsTimeStamp & ); - /** @} */ - - /** \name ANSI C time_t conversions - * Convert to and from ANSI C \c time_t wrapper . - * @{ */ - /** \brief Convert to ANSI C \c time_t */ - operator time_t_wrapper () const; - /** \brief Construct from ANSI C \c time_t */ - epicsTime ( const time_t_wrapper & ); - /** \brief Assign from ANSI C \c time_t */ - epicsTime & operator = ( const time_t_wrapper & ); - /** @} */ - - /** \name ANSI C struct tm local-time conversions - * Convert to and from ANSI Cs struct tm (with nano seconds), - * adjusted for the local time zone. - * @{ */ - /** \brief Convert to struct tm in local time zone */ - operator local_tm_nano_sec () const; - /** \brief Construct from struct tm in local time zone */ - epicsTime ( const local_tm_nano_sec & ); - /** \brief Assign from struct tm in local time zone */ - epicsTime & operator = ( const local_tm_nano_sec & ); - /** @} */ - - /** \name ANSI C struct tm UTC conversions - * Convert to and from ANSI Cs struct tm (with nano seconds), - * adjusted for Greenwich Mean Time (UTC). - * @{ */ - /** \brief Convert to struct tm in UTC/GMT */ - operator gm_tm_nano_sec () const; - /** \brief Construct from struct tm in UTC/GMT */ - epicsTime ( const gm_tm_nano_sec & ); - /** \brief Assign from struct tm in UTC */ - epicsTime & operator = ( const gm_tm_nano_sec & ); - /** @} */ - - /** \name POSIX RT struct timespec conversions - * Convert to and from the POSIX RealTime struct timespec - * format. - * @{ */ - /** \brief Convert to struct timespec */ - operator struct timespec () const; - /** \brief Construct from struct timespec */ - epicsTime ( const struct timespec & ); - /** \brief Assign from struct timespec */ - epicsTime & operator = ( const struct timespec & ); - /** @} */ - - /** \name BSD's struct timeval conversions - * Convert to and from the BSD struct timeval format. - * @{ */ - /** \brief Convert to struct timeval */ - operator struct timeval () const; - /** \brief Construct from struct timeval */ - epicsTime ( const struct timeval & ); - /** \brief Assign from struct timeval */ - epicsTime & operator = ( const struct timeval & ); - /** @} */ - - /** \name NTP timestamp conversions - * Convert to and from the NTP timestamp structure \c l_fp - * @{ */ - /** \brief Convert to NTP format */ - operator l_fp () const; - /** \brief Construct from NTP format */ - epicsTime ( const l_fp & ); - /** \brief Assign from NTP format */ - epicsTime & operator = ( const l_fp & ); - /** @} */ - - /** \name WIN32 FILETIME conversions - * Convert to and from WIN32s _FILETIME - * \note These are only implemented on Windows targets. - * @{ */ - /** \brief Convert to Windows struct _FILETIME */ - operator struct _FILETIME () const; - /** \brief Constuct from Windows struct _FILETIME */ - epicsTime ( const struct _FILETIME & ); - /** \brief Assign from Windows struct _FILETIME */ - epicsTime & operator = ( const struct _FILETIME & ); - /** @} */ - - /** \name Arithmetic operators - * Standard operators involving epicsTime objects and time differences - * which are always expressed as a \c double in seconds. - * @{ */ - /// \brief \p lhs minus \p rhs, in seconds - double operator- ( const epicsTime & ) const; - /// \brief \p lhs plus rhs seconds - epicsTime operator+ ( const double & ) const; - /// \brief \p lhs minus rhs seconds - epicsTime operator- ( const double & ) const; - /// \brief add rhs seconds to \p lhs - epicsTime operator+= ( const double & ); - /// \brief subtract rhs seconds from \p lhs - epicsTime operator-= ( const double & ); - /** @} */ - - /** \name Comparison operators - * Standard comparisons between epicsTime objects. - * @{ */ - /// \brief \p lhs equals \p rhs - bool operator == ( const epicsTime & ) const; - /// \brief \p lhs not equal to \p rhs - bool operator != ( const epicsTime & ) const; - /// \brief \p rhs no later than \p lhs - bool operator <= ( const epicsTime & ) const; - /// \brief \p lhs was before \p rhs - bool operator < ( const epicsTime & ) const; - /// \brief \p rhs not before \p lhs - bool operator >= ( const epicsTime & ) const; - /// \brief \p lhs was after \p rhs - bool operator > ( const epicsTime & ) const; - /** @} */ - - /** \brief Convert to string in user-specified format - * - * This method extends the standard C library routine strftime(). - * See your OS documentation for details about the standard routine. - * The epicsTime method adds support for printing the fractional - * portion of the time. It searches the format string for the - * sequence %0nf where \a n is the desired precision, - * and uses this format to convert the fractional seconds with the - * requested precision. For example: - * \code{.cpp} - * epicsTime time = epicsTime::getCurrent(); - * char buf[30]; - * time.strftime(buf, 30, "%Y-%m-%d %H:%M:%S.%06f"); - * printf("%s\n", buf); - * \endcode - * This will print the current time in the format: - * \code - * 2001-01-26 20:50:29.813505 - * \endcode - */ - size_t strftime ( char * pBuff, size_t bufLength, const char * pFormat ) const; - - /** \brief Dump current state to standard out */ - void show ( unsigned interestLevel ) const; - -private: - /* - * private because: - * a) application does not break when EPICS epoch is changed - * b) no assumptions about internal storage or internal precision - * in the application - * c) it would be easy to forget which argument is nanoseconds - * and which argument is seconds (no help from compiler) - */ - epicsTime ( const unsigned long secPastEpoch, const unsigned long nSec ); - void addNanoSec ( long nanoSecAdjust ); - - unsigned long secPastEpoch; /* seconds since O000 Jan 1, 1990 */ - unsigned long nSec; /* nanoseconds within second */ -}; - -extern "C" { -#endif /* __cplusplus */ - /** \name Return status values * epicsTime routines return \c S_time_ error status values: * @{ @@ -457,6 +194,12 @@ LIBCOM_API void epicsStdCall epicsTimeAddSeconds ( epicsTimeStamp * pDest, double secondsToAdd ); /* adds seconds to *pDest */ /** @} */ +/** \brief Return difference LHS-RHS as signed integer nanoseconds. + * @since UNRELEASED + */ +LIBCOM_API +epicsInt64 epicsStdCall epicsTimeDiffInNS (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight); + /** \name Comparison operators * Comparisons between epicsTimeStamp objects, returning 0=false, 1=true. * @{ */ @@ -515,98 +258,330 @@ LIBCOM_API void osdMonotonicInit(void); #endif #ifdef __cplusplus -} +} // extern "C" + +/** \brief C++ only ANSI C struct tm with nanoseconds, local timezone + * + * Extend ANSI C "struct tm" to include nano seconds within a second + * and a struct tm that is adjusted for the local timezone. + */ +struct local_tm_nano_sec { + struct tm ansi_tm; /**< \brief ANSI C time details */ + unsigned long nSec; /**< \brief nanoseconds extension */ +}; + +/** \brief C++ only ANSI C sruct tm with nanoseconds, UTC + * + * Extend ANSI C "struct tm" to include nanoseconds within a second + * and a struct tm that is adjusted for GMT (UTC). + */ +struct gm_tm_nano_sec { + struct tm ansi_tm; /**< \brief ANSI C time details */ + unsigned long nSec; /**< \brief nanoseconds extension */ +}; + +/** \brief C++ only ANSI C time_t + * + * This is for converting to/from the ANSI C \c time_t. Since \c time_t + * is usually an elementary type providing a conversion operator from + * \c time_t to/from epicsTime could cause undesirable implicit + * conversions. Providing a conversion operator to/from the + * \c time_t_wrapper instead prevents implicit conversions. + */ +struct time_t_wrapper { + time_t ts; +}; + +/** \brief C++ Event number wrapper class + * + * Stores an event number for use by the epicsTime::getEvent() static + * class method. + */ +class LIBCOM_API epicsTimeEvent +{ +public: + epicsTimeEvent (const int &number) :eventNumber(number) {} + operator int () const { return eventNumber; } +private: + int eventNumber; +}; + +/** \brief C++ time stamp object + * + * Holds an EPICS time stamp, and provides conversion functions for both + * input and output from/to other types. + * + * \note Time conversions: The epicsTime implementation will properly + * convert between the various formats from the beginning of the EPICS + * epoch until at least 2038. Unless the underlying architecture support + * has defective POSIX, BSD/SRV5, or standard C time support the EPICS + * implementation should be valid until 2106. + */ +class LIBCOM_API epicsTime +{ + // translate S_time_* code to exception + static void throwError(int code); +public: + /// \brief Exception: Time provider problem + typedef std::runtime_error unableToFetchCurrentTime; + /// \brief Exception: Bad field(s) in struct tm + typedef std::logic_error formatProblemWithStructTM; + + /** \brief The default constructor sets the time to the EPICS epoch. */ +#if __cplusplus>=201103L + constexpr epicsTime() :ts{} {} +#else + epicsTime () { + ts.secPastEpoch = ts.nsec = 0u; + } +#endif + + /** \brief Get time of event system event. + * + * Returns an epicsTime indicating when the associated event system + * event last occurred. + */ + static inline epicsTime getEvent ( const epicsTimeEvent & evt) ; + /** \brief Get current clock time + * + * Returns an epicsTime containing the current time. For example: + * \code{.cpp} + * epicsTime now = epicsTime::getCurrent(); + * \endcode + */ + static epicsTime getCurrent (); + /** \brief Get current monotonic time + * + * Returns an epicsTime containing the current monotonic time, an + * OS clock which never going backwards or jumping forwards. + * This time is has an undefined epoch, and is only useful for + * measuring time differences. + */ + static epicsTime getMonotonic () { + epicsTime ret; + epicsTimeGetMonotonic(&ret.ts); // can't fail + return ret; + } + + /** \name epicsTimeStamp conversions + * Convert to and from EPICS epicsTimeStamp format + * @{ */ + /** \brief Convert to epicsTimeStamp */ + operator const epicsTimeStamp& () const { return ts; } + /** \brief Construct from epicsTimeStamp */ + epicsTime ( const epicsTimeStamp & replace ); + /** \brief Assign from epicsTimeStamp */ + epicsTime & operator = ( const epicsTimeStamp & replace) { + ts = replace; + return *this; + } + /** @} */ + + /** \name ANSI C time_t conversions + * Convert to and from ANSI C \c time_t wrapper . + * @{ */ + /** \brief Convert to ANSI C \c time_t */ + operator time_t_wrapper () const { + time_t_wrapper ret; + throwError(epicsTimeToTime_t(&ret.ts, &ts)); + return ret; + } + /** \brief Construct from ANSI C \c time_t */ + epicsTime ( const time_t_wrapper & replace ) { + throwError(epicsTimeFromTime_t(&ts, replace.ts)); + } + /** \brief Assign from ANSI C \c time_t */ + epicsTime & operator = ( const time_t_wrapper & replace) { + throwError(epicsTimeFromTime_t(&ts, replace.ts)); + return *this; + } + /** @} */ + + /** \name ANSI C struct tm local-time conversions + * Convert to and from ANSI Cs struct tm (with nano seconds), + * adjusted for the local time zone. + * @{ */ + /** \brief Convert to struct tm in local time zone */ + operator local_tm_nano_sec () const { + local_tm_nano_sec ret; + throwError(epicsTimeToTM(&ret.ansi_tm, 0, &ts)); + ret.nSec = ts.nsec; + return ret; + } + /** \brief Construct from struct tm in local time zone */ + epicsTime ( const local_tm_nano_sec & replace) { + throwError(epicsTimeFromTM(&ts, &replace.ansi_tm, replace.nSec)); + } + /** \brief Assign from struct tm in local time zone */ + epicsTime & operator = ( const local_tm_nano_sec & replace) { + throwError(epicsTimeFromTM(&ts, &replace.ansi_tm, replace.nSec)); + return *this; + } + /** @} */ + + /** \name ANSI C struct tm UTC conversions + * Convert to and from ANSI Cs struct tm (with nano seconds), + * adjusted for Greenwich Mean Time (UTC). + * @{ */ + /** \brief Convert to struct tm in UTC/GMT */ + operator gm_tm_nano_sec () const { + gm_tm_nano_sec ret; + throwError(epicsTimeToGMTM(&ret.ansi_tm, 0, &ts)); + ret.nSec = ts.nsec; + return ret; + } + /** \brief Construct from struct tm in UTC/GMT */ + epicsTime ( const gm_tm_nano_sec & replace) { + throwError(epicsTimeFromGMTM(&ts, &replace.ansi_tm, replace.nSec)); + } + /** \brief Assign from struct tm in UTC */ + epicsTime & operator = ( const gm_tm_nano_sec & replace) { + throwError(epicsTimeFromGMTM(&ts, &replace.ansi_tm, replace.nSec)); + return *this; + } + /** @} */ + + /** \name POSIX RT struct timespec conversions + * Convert to and from the POSIX RealTime struct timespec + * format. + * @{ */ + /** \brief Convert to struct timespec */ + operator struct timespec () const { + timespec ret; + epicsTimeToTimespec(&ret, &ts); + return ret; + } + /** \brief Construct from struct timespec */ + epicsTime ( const struct timespec & replace) { + throwError(epicsTimeFromTimespec(&ts, &replace)); + } + /** \brief Assign from struct timespec */ + epicsTime & operator = ( const struct timespec & replace ) { + throwError(epicsTimeFromTimespec(&ts, &replace)); + return *this; + } + /** @} */ + + /** \name BSD's struct timeval conversions + * Convert to and from the BSD struct timeval format. + * @{ */ + /** \brief Convert to struct timeval */ + operator struct timeval () const ; + /** \brief Construct from struct timeval */ + epicsTime ( const struct timeval & replace); + /** \brief Assign from struct timeval */ + epicsTime & operator = ( const struct timeval & replace); + /** @} */ + +#ifdef _WIN32 + /** \name WIN32 FILETIME conversions + * Convert to and from WIN32s _FILETIME + * \note These are only implemented on Windows targets. + * @{ */ + /** \brief Convert to Windows struct _FILETIME */ + operator struct _FILETIME () const; + /** \brief Constuct from Windows struct _FILETIME */ + epicsTime ( const struct _FILETIME & ); + /** \brief Assign from Windows struct _FILETIME */ + epicsTime & operator = ( const struct _FILETIME & ); + /** @} */ +#endif /* _WIN32 */ + + /** \name Arithmetic operators + * Standard operators involving epicsTime objects and time differences + * which are always expressed as a \c double in seconds. + * @{ */ + /// \brief \p lhs minus \p rhs, in seconds + double operator- ( const epicsTime & other) const { + return epicsTimeDiffInSeconds(&ts, &other.ts); + } + /// \brief \p lhs plus rhs seconds + epicsTime operator+ (double delta) const { + epicsTime ret(*this); + epicsTimeAddSeconds(&ret.ts, delta); + return ret; + } + /// \brief \p lhs minus rhs seconds + epicsTime operator- (double delta ) const { + return (*this)+(-delta); + } + /// \brief add rhs seconds to \p lhs + epicsTime operator+= (double delta) { + epicsTimeAddSeconds(&ts, delta); + return *this; + } + /// \brief subtract rhs seconds from \p lhs + epicsTime operator-= ( double delta ) { + return (*this) += (-delta); + } + /** @} */ + + /** \name Comparison operators + * Standard comparisons between epicsTime objects. + * @{ */ + /// \brief \p lhs equals \p rhs + bool operator == ( const epicsTime & other) const { + return epicsTimeEqual(&ts, &other.ts); + } + /// \brief \p lhs not equal to \p rhs + bool operator != ( const epicsTime & other) const { + return epicsTimeNotEqual(&ts, &other.ts); + } + /// \brief \p rhs no later than \p lhs + bool operator <= ( const epicsTime & other) const { + return epicsTimeLessThanEqual(&ts, &other.ts); + } + /// \brief \p lhs was before \p rhs + bool operator < ( const epicsTime & other) const { + return epicsTimeLessThan(&ts, &other.ts); + } + /// \brief \p rhs not before \p lhs + bool operator >= ( const epicsTime & other) const { + return epicsTimeGreaterThanEqual(&ts, &other.ts); + } + /// \brief \p lhs was after \p rhs + bool operator > ( const epicsTime & other) const { + return epicsTimeGreaterThan(&ts, &other.ts); + } + /** @} */ + + /** \brief Convert to string in user-specified format + * + * This method extends the standard C library routine strftime(). + * See your OS documentation for details about the standard routine. + * The epicsTime method adds support for printing the fractional + * portion of the time. It searches the format string for the + * sequence %0nf where \a n is the desired precision, + * and uses this format to convert the fractional seconds with the + * requested precision. For example: + * \code{.cpp} + * epicsTime time = epicsTime::getCurrent(); + * char buf[30]; + * time.strftime(buf, 30, "%Y-%m-%d %H:%M:%S.%06f"); + * printf("%s\n", buf); + * \endcode + * This will print the current time in the format: + * \code + * 2001-01-26 20:50:29.813505 + * \endcode + */ + size_t strftime ( char * pBuff, size_t bufLength, const char * pFormat ) const { + return epicsTimeToStrftime(pBuff, bufLength, pFormat, &ts); + } + + /** \brief Dump current state to standard out */ + void show ( unsigned interestLevel ) const { + epicsTimeShow(&ts, interestLevel); + } + +private: + epicsTimeStamp ts; +}; + +LIBCOM_API +std::ostream& operator<<(std::ostream& strm, const epicsTime& ts); + #endif /* __cplusplus */ -/* inline member functions ,*/ -#ifdef __cplusplus - -/* epicsTimeEvent */ - -inline epicsTimeEvent::epicsTimeEvent (const int &number) : - eventNumber(number) {} - -inline epicsTimeEvent::operator int () const -{ - return this->eventNumber; -} - - -/* epicsTime */ - -inline epicsTime epicsTime::operator - ( const double & rhs ) const -{ - return epicsTime::operator + ( -rhs ); -} - -inline epicsTime epicsTime::operator += ( const double & rhs ) -{ - *this = epicsTime::operator + ( rhs ); - return *this; -} - -inline epicsTime epicsTime::operator -= ( const double & rhs ) -{ - *this = epicsTime::operator + ( -rhs ); - return *this; -} - -inline bool epicsTime::operator == ( const epicsTime & rhs ) const -{ - if ( this->secPastEpoch == rhs.secPastEpoch && this->nSec == rhs.nSec ) { - return true; - } - return false; -} - -inline bool epicsTime::operator != ( const epicsTime & rhs ) const -{ - return !epicsTime::operator == ( rhs ); -} - -inline bool epicsTime::operator >= ( const epicsTime & rhs ) const -{ - return ! ( *this < rhs ); -} - -inline bool epicsTime::operator > ( const epicsTime & rhs ) const -{ - return ! ( *this <= rhs ); -} - -inline epicsTime & epicsTime::operator = ( const local_tm_nano_sec & rhs ) -{ - return *this = epicsTime ( rhs ); -} - -inline epicsTime & epicsTime::operator = ( const gm_tm_nano_sec & rhs ) -{ - return *this = epicsTime ( rhs ); -} - -inline epicsTime & epicsTime::operator = ( const struct timespec & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const epicsTimeStamp & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const l_fp & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const time_t_wrapper & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} -#endif /* __cplusplus */ #endif /* epicsTimehInclude */ diff --git a/modules/libcom/src/osi/os/WIN32/osdTime.cpp b/modules/libcom/src/osi/os/WIN32/osdTime.cpp index fb2cfb6c0..e2c3efbfe 100644 --- a/modules/libcom/src/osi/os/WIN32/osdTime.cpp +++ b/modules/libcom/src/osi/os/WIN32/osdTime.cpp @@ -510,8 +510,8 @@ static unsigned __stdcall _pllThreadEntry ( void * pCurrentTimeIn ) epicsTime::operator FILETIME () const { LARGE_INTEGER ftTicks; - ftTicks.QuadPart = ( this->secPastEpoch * FILE_TIME_TICKS_PER_SEC ) + - ( this->nSec / ET_TICKS_PER_FT_TICK ); + ftTicks.QuadPart = ( this->ts.secPastEpoch * FILE_TIME_TICKS_PER_SEC ) + + ( this->ts.nsec / ET_TICKS_PER_FT_TICK ); ftTicks.QuadPart += epicsEpochInFileTime; FILETIME ts; ts.dwLowDateTime = ftTicks.LowPart; @@ -527,15 +527,15 @@ epicsTime::epicsTime ( const FILETIME & ts ) if ( lift.QuadPart > epicsEpochInFileTime ) { LONGLONG fileTimeTicksSinceEpochEPICS = lift.QuadPart - epicsEpochInFileTime; - this->secPastEpoch = static_cast < epicsUInt32 > + this->ts.secPastEpoch = static_cast < epicsUInt32 > ( fileTimeTicksSinceEpochEPICS / FILE_TIME_TICKS_PER_SEC ); - this->nSec = static_cast < epicsUInt32 > + this->ts.nsec = static_cast < epicsUInt32 > ( ( fileTimeTicksSinceEpochEPICS % FILE_TIME_TICKS_PER_SEC ) * ET_TICKS_PER_FT_TICK ); } else { - this->secPastEpoch = 0; - this->nSec = 0; + this->ts.secPastEpoch = 0; + this->ts.nsec = 0; } } diff --git a/modules/libcom/test/epicsTimeTest.cpp b/modules/libcom/test/epicsTimeTest.cpp index 3e10e6808..66d13e91d 100644 --- a/modules/libcom/test/epicsTimeTest.cpp +++ b/modules/libcom/test/epicsTimeTest.cpp @@ -10,6 +10,9 @@ /* * Authors: Jeff Hill, Marty Kraimer and Andrew Johnson */ +#include + +#include #include #include #include @@ -17,6 +20,7 @@ #include #include +#include "envDefs.h" #include "epicsTime.h" #include "epicsThread.h" #include "errlog.h" @@ -29,16 +33,45 @@ using namespace std; * routines is incorporated into epicsTimeTest () below. */ -struct l_fp { /* NTP time stamp */ - epicsUInt32 l_ui; /* sec past NTP epoch */ - epicsUInt32 l_uf; /* fractional seconds */ -}; - static const unsigned mSecPerSec = 1000u; static const unsigned uSecPerSec = 1000u * mSecPerSec; static const unsigned nSecPerSec = 1000u * uSecPerSec; static const double precisionEPICS = 1.0 / nSecPerSec; +static void testAdd(epicsUInt32 lhsSec, epicsUInt32 lhsNS, + double rhs, + epicsUInt32 expectSec, epicsUInt32 expectNS) +{ + epicsTimeStamp lhs = {lhsSec, lhsNS}; + epicsTimeStamp expect = {expectSec, expectNS}; + epicsTimeStamp actual = lhs; + + + epicsTimeAddSeconds(&actual, rhs); + testOk(epicsTimeEqual(&actual, &expect), + "testAdd(%u:%u + %.9f -> %u:%u == %u:%u)", + unsigned(lhs.secPastEpoch), unsigned(lhs.nsec), + rhs, + unsigned(actual.secPastEpoch), unsigned(actual.nsec), + unsigned(expect.secPastEpoch), unsigned(expect.nsec)); +} + +static void testDiff(epicsUInt32 lhsSec, epicsUInt32 lhsNS, + epicsUInt32 rhsSec, epicsUInt32 rhsNS, + double expect) +{ + epicsTimeStamp lhs = {lhsSec, lhsNS}; + epicsTimeStamp rhs = {rhsSec, rhsNS}; + double actual = epicsTimeDiffInSeconds(&lhs, &rhs); + double diff = actual - expect; + + testOk(fabs(diff) %.9f ~= %.9f (%g)", + unsigned(lhs.secPastEpoch), unsigned(lhs.nsec), + unsigned(rhs.secPastEpoch), unsigned(rhs.nsec), + actual, expect, diff); +} + static void crossCheck(double delay) { double mindelta = 2*epicsMonotonicResolution()*1e-9, @@ -80,12 +113,91 @@ static void testMonotonic() testDiag("Small Delta %u ns", (unsigned)(B-A)); } +static void testTMGames() +{ + testDiag("testTMGames()"); + + epicsTimeStamp now; + testOk1(!epicsTimeGetCurrent(&now)); + now.nsec = 0; // not relevant + + tm gtm, ltm; + epicsTimeToTM(<m, 0, &now); + epicsTimeToGMTM(>m, 0, &now); + + // we can't do any tests on the decomposed time without knowing the current TZ + testDiag("LTM mday=%u hour=%u min=%u sec=%u", ltm.tm_mday, ltm.tm_hour, ltm.tm_min, ltm.tm_sec); + testDiag("GTM mday=%u hour=%u min=%u sec=%u", gtm.tm_mday, gtm.tm_hour, gtm.tm_min, gtm.tm_sec); + + epicsTimeStamp gtime, ltime; + epicsTimeFromTM(<ime, <m, 0); + epicsTimeFromGMTM(>ime, >m, 0); + + testOk(now.secPastEpoch==ltime.secPastEpoch, "localtime %u == %u", + unsigned(now.secPastEpoch), unsigned(ltime.secPastEpoch)); + + testOk(now.secPastEpoch==gtime.secPastEpoch, "gmtime %u == %u", + unsigned(now.secPastEpoch), unsigned(ltime.secPastEpoch)); +} + MAIN(epicsTimeTest) { const int wasteTime = 100000; const int nTimes = 10; - testPlan(22 + nTimes * 19); + testPlan(52 + nTimes * 19); + + testDiag("$TZ = \"%s\"", getenv("TZ")); + testDiag("EPICS_TZ = \"%s\"", envGetConfigParamPtr(&EPICS_TZ)); + +#if !defined(_WIN32) && !defined(vxWorks) + { + // at least glibc doesn't initialize tzname[2] until some time.h function needs the time zone + time_t junk = 0; + (void)localtime(&junk); + testDiag("Local TZ names \"%s\", \"%s\"", tzname[0], tzname[1]); + } +#endif + + // sec:ns + double == sec:ns + testAdd(0,0, 0.0, 0,0); + testAdd(1,1, 0.0, 1,1); + testAdd(1,999999999, 0.000000001, 2,0); + testAdd(1,1, 2.000000002, 3,3); + testAdd(1,0, -1.0, 0,0); + testAdd(0,1, -0.000000001, 0,0); + testAdd(1,1, -1.000000001, 0,0); + testAdd(0xffffffff,0, -1.0, 0xfffffffe,0); + testAdd(0x7fffffff,0, 1.0, 0x80000000,0); + testAdd(0x7fffffff,999999999, 0.000000001, 0x80000000,0); + + // sec:ns - sec:ns == double + testDiff(0,0, 0,0, 0.0); + + testDiff(0,1, 0,1, 0.0); + testDiff(1,0, 1,0, 0.0); + testDiff(1,1, 1,1, 0.0); + + testDiff(2,0, 1,999999999, 0.000000001); + testDiff(1,999999999, 2,0, -0.000000001); + + testDiff(1,0, 0xffffffff,0, 2.0); + testDiff(0xffffffff,0, 1,0, -2.0); + + testDiff(1,999999999, 0xffffffff,999999999, 2.0); + testDiff(0xffffffff,999999999, 1,999999999, -2.0); + + testDiff(0,999999999, 0xffffffff,0, 1.999999999); // 0.99999.. - -1.0 + testDiff(0xffffffff,0, 0,999999999, -1.999999999); // -1.0 - 0.999.. + + testDiff(0x80000000,0, 0x7fffffff,0, 1.0); + testDiff(0x7fffffff,0, 0x80000000,0, -1.0); + + testDiff(0x80000000,0, 0x7fffffff,999999999, 0.000000001); + testDiff(0x7fffffff,999999999, 0x80000000,0, -0.000000001); + + testDiff(0x80000000,999999999, 0x7fffffff,0, 1.999999999); + testDiff(0x7fffffff,0, 0x80000000,999999999, -1.999999999); try { const epicsTimeStamp epochTS = {0, 0}; @@ -113,7 +225,7 @@ MAIN(epicsTimeTest) ts.strftime(buf, sizeof(buf), pFormat); testFail("nanosecond overflow returned \"%s\"", buf); } - catch ( ... ) { + catch ( std::exception& ) { testPass("nanosecond overflow throws"); } } @@ -178,20 +290,11 @@ MAIN(epicsTimeTest) now = epicsTime::getCurrent(); testPass("default time provider"); } - catch ( ... ) { + catch ( std::exception& ) { testFail("epicsTime::getCurrent() throws"); testAbort("Can't continue, check your time provider"); } - { - l_fp ntp = now; - epicsTime tsf = ntp; - const double diff = fabs(tsf - now); - // the difference in the precision of the two time formats - static const double precisionNTP = 1.0 / (1.0 + 0xffffffff); - testOk1(diff <= precisionEPICS + precisionNTP); - } - testDiag("Running %d loops", nTimes); const epicsTime begin = epicsTime::getCurrent(); @@ -225,6 +328,7 @@ MAIN(epicsTimeTest) "now - begin ~= diff"); testOk1(begin + 0 == begin); + std::cout<<"# begin + diff ("<<(begin + diff)<<") == now ("< Date: Wed, 4 Aug 2021 17:46:37 -0700 Subject: [PATCH 024/323] Com: clear IP_MULTICAST_ALL on Linux The default, non-compliant, behavior will pass all multicast packets to any socket bound to 0.0.0.0 or the mcast address, regardless of which groups, on which interfaces, that socket has joined. --- modules/libcom/src/osi/os/posix/osdSock.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/libcom/src/osi/os/posix/osdSock.c b/modules/libcom/src/osi/os/posix/osdSock.c index 4ebd0db73..f773b3357 100644 --- a/modules/libcom/src/osi/os/posix/osdSock.c +++ b/modules/libcom/src/osi/os/posix/osdSock.c @@ -99,6 +99,22 @@ LIBCOM_API SOCKET epicsStdCall epicsSocketCreate ( close ( sock ); sock = INVALID_SOCKET; } + +#ifdef __linux__ +# ifndef IP_MULTICAST_ALL +# define IP_MULTICAST_ALL 49 +# endif + /* Enable compliant filtering of multicasts on Linux. cf. 'man 7 ip' */ + if(domain==AF_INET && type==SOCK_DGRAM){ + static int logged; + int val = 0; + if(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_ALL, (char*)&val, sizeof(val)) && !logged) { + logged = 1; + errlogPrintf("Warning: Unable to clear IP_MULTICAST_ALL (err=%d). This may cause problems on multi-homed hosts.\n", + SOCKERRNO); + } + } +#endif } return sock; } From 8beb7bd2c8e3630fda3c49a1124c4495a4012080 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Aug 2021 20:29:32 -0500 Subject: [PATCH 025/323] Restore the -p flag to MKDIR which RTEMS host.cfg removes --- configure/os/CONFIG.Common.RTEMS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configure/os/CONFIG.Common.RTEMS b/configure/os/CONFIG.Common.RTEMS index b7a42d137..c8729f933 100644 --- a/configure/os/CONFIG.Common.RTEMS +++ b/configure/os/CONFIG.Common.RTEMS @@ -79,6 +79,9 @@ CPPFLAGS += $($(BUILD_CLASS)_CPPFLAGS) $(POSIX_CPPFLAGS) $(OPT_CPPFLAGS)\ ECHO = @$(if $(filter -s,$(MFLAGS)),$(NOP),echo) +# Originally set in os/CONFIG.UnixCommon.Common +MKDIR = mkdir -p + #-------------------------------------------------- # Although RTEMS uses gcc, it wants to use gcc its own way CROSS_CPPFLAGS = From 72626cd5dd9e75f0d685d9cf609eea9647a61cb2 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Aug 2021 20:31:16 -0500 Subject: [PATCH 026/323] Add newlines to a couple iocsh usage strings --- modules/database/src/ioc/db/dbIocRegister.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/ioc/db/dbIocRegister.c b/modules/database/src/ioc/db/dbIocRegister.c index 4df5ae336..ef57c5d44 100644 --- a/modules/database/src/ioc/db/dbIocRegister.c +++ b/modules/database/src/ioc/db/dbIocRegister.c @@ -234,7 +234,7 @@ static const iocshArg dbtgfArg0 = { "record name",iocshArgString}; static const iocshArg * const dbtgfArgs[1] = {&dbtgfArg0}; static const iocshFuncDef dbtgfFuncDef = {"dbtgf",1,dbtgfArgs, "Database Test Get Field.\n" - "Get field with different DBR_* types"}; + "Get field with different DBR_* types\n"}; static void dbtgfCallFunc(const iocshArgBuf *args) { dbtgf(args[0].sval);} /* dbtpf */ @@ -283,7 +283,7 @@ static const iocshArg * const dbtpnArgs[2] = {&dbtpnArg0,&dbtpnArg1}; static const iocshFuncDef dbtpnFuncDef = {"dbtpn",2,dbtpnArgs, "Database Put Notify\n" "Without value, begin async. processing and get\n" - "With value, begin put, process, and get"}; + "With value, begin put, process, and get\n"}; static void dbtpnCallFunc(const iocshArgBuf *args) { dbtpn(args[0].sval,args[1].sval);} From 8175cc8e647f215ec93db968680b7b465239e346 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Aug 2021 20:35:19 -0500 Subject: [PATCH 027/323] POD text updates to dbCommon and various record types --- modules/database/src/ioc/db/dbCommon.dbd.pod | 153 ++++++++++-------- modules/database/src/std/rec/aiRecord.dbd.pod | 14 +- modules/database/src/std/rec/biRecord.dbd.pod | 7 +- .../database/src/std/rec/calcRecord.dbd.pod | 9 +- .../src/std/rec/calcoutRecord.dbd.pod | 16 +- .../src/std/rec/dfanoutRecord.dbd.pod | 45 ++++-- .../src/std/rec/longoutRecord.dbd.pod | 16 +- .../database/src/std/rec/mbbiRecord.dbd.pod | 9 +- .../src/std/rec/mbboDirectRecord.dbd.pod | 18 ++- .../database/src/std/rec/seqRecord.dbd.pod | 46 +++--- 10 files changed, 197 insertions(+), 136 deletions(-) diff --git a/modules/database/src/ioc/db/dbCommon.dbd.pod b/modules/database/src/ioc/db/dbCommon.dbd.pod index 5ad4627c5..5033a362f 100644 --- a/modules/database/src/ioc/db/dbCommon.dbd.pod +++ b/modules/database/src/ioc/db/dbCommon.dbd.pod @@ -77,12 +77,12 @@ A set of periodic scan intervals =back Additional periodic scan rates may be defined for individual IOCs by making a -local copy of menuScan.dbd and adding more choices as required. Scan rates -should normally be defined in order, with the fastest rates appearing first. -Scan periods may now be specified in seconds, minutes, hours or Hertz/Hz, and -plural time units will also be accepted (seconds are used if no unit is -mentioned in the choice string). For example the rates given below are all -valid: +local copy of menuScan.dbd and adding more choices as required. Periodic scan +rates should normally be defined in order following the other scan types, with +the longest periods appearing first. Scan periods can be specified with a unit +string of C/C, C/C, C/C or +C/C. Seconds are used if no unit is included in the choice string. +For example these rates are all valid: 1 hour 0.5 hours @@ -97,7 +97,7 @@ initialization (before the normal scan tasks are started). The B field orders the records within a specific SCAN group. This is not meaningful for passive records. All records of a specified phase are processed -before those with higher phase number. Whenever possible it is better to use +before those with higher phase number. It is generally better practice to use linked passive records to enforce the order of processing rather than a phase number. @@ -109,23 +109,23 @@ The call to post_event is: post_event(short event_number). The B field specifies the scheduling priority for processing records with SCAN=C and asynchronous record completion tasks. -The B field specifies a "disable value". Record processing is -immediately terminated if the value of this field is equal to the value of the -DISA field, i.e. the record is disabled. Note that field values of a record -can be changed by database put or Channel Access, even if a record is +The B field specifies a "disable value". Record processing cannot +begin when the value of this field is equal to the value of the DISA +field, meaning the record is disabled. Note that field values of a record +can be changed by database or Channel Access puts, even if the record is disabled. -The B field contains the value that is compared with DISV to determine -if the record is disabled. The value of the DISA field is obtained via SDIS if -SDIS is a database or channel access link. If SDIS is not a database or -channel access link, then DISA can be set via dbPutField or dbPutLink. - -If the B field of a record is written to, the record is processed. +The B field contains the value that is compared with DISV to determine if +the record is disabled. A value is obtained for the DISA field from the B +link field before the IOC tries to process the record. If SDIS is not set, DISA +may be set by some other method to enable and disable the record. The B field defines the record's "disable severity". If this field is not NO_ALARM and the record is disabled, the record will be put into alarm with this severity and a status of DISABLE_ALARM. +If the B field of a record is written to, the record is processed. + The B field contains the lock set to which this record belongs. All records linked in any way via input, output, or forward database links belong to the same lock set. Lock sets are determined at IOC initialization time, and @@ -135,15 +135,18 @@ The B field counts the number of times dbProcess finds the record active during successive scans, i.e. PACT is TRUE. If dbProcess finds the record active MAX_LOCK times (currently set to 10) it raises a SCAN_ALARM. -The B field is TRUE while the record is being processed. For +The B field is TRUE while the record is active (being processed). For asynchronous records PACT can be TRUE from the time record processing is started until the asynchronous completion occurs. As long as PACT is TRUE, dbProcess will not call the record processing routine. See Application Developers Guide for details on usage of PACT. -The B field is a database link to another record (the "target" record). -Processing a record with a specified FLNK field will force processing of the -target record, provided the target record's SCAN field is set to C. +The B field is a link pointing to another record (the "target" record). +Processing a record with the FLNK field set will trigger processing of the +target record towards the end of processing the first record (but before PACT is +cleared), provided the target record's SCAN field is set to C. If the +FLNK field is a Channel Access link it must point to the PROC field of the +target record. The B field is for internal use by the scanning system. @@ -236,35 +239,46 @@ The B field is for internal use by the scanning system. =head3 Alarm Fields -These fields indicate the status and severity of alarms, or else determine the +Alarm fields indicate the status and severity of record alarms, or determine how and when alarms are triggered. Of course, many records have alarm-related -fields not common to all records. These fields are listed and explained in the +fields not common to all records. Those fields are listed and explained in the appropriate section on each record. The B field contains the current alarm status. The B field contains the current alarm severity. -These two fields are seen outside database access. The B and B -fields are used by the database access, record support, and device support -routines to set new alarm status and severity values. Whenever any software -component discovers an alarm condition, it uses the following macro function: -recGblSetSevr(precord,new_status,new_severity) This ensures that the current -alarm severity is set equal to the highest outstanding alarm. The file alarm.h -defines all allowed alarm status and severity values. +The B string field may contain more detailed information about the alarm. + +The STAT, SEVR and AMSG fields hold alarm information as seen outside of the +database. The B, B and B fields are used during record +processing by the database access, record support, and device support routines +to set new alarm status and severity values and message text. Whenever any +software component discovers an alarm condition, it calls one of these routines +to register the alarm: + + recGblSetSevr(precord, new_status, new_severity); + recGblSetSevrMsg(precord, new_status, new_severity, "Message", ...); + +These check the current alarm severity and update the NSTA, NSEV and NAMSG +fields if appropriate so they always relate to the highest severity alarm seen +so far during record processing. The file alarm.h defines the allowed alarm +status and severity values. Towards the end of record processing these fields +are copied into the STAT, SEVR and AMSG fields and alarm monitors triggered. The B field contains the highest unacknowledged alarm severity. -The B field specifies if it is necessary to acknowledge transient +The B field specifies whether it is necessary to acknowledge transient alarms. -The B indicates if the record's value is BnBeBined. Typically -this is caused by a failure in device support, the fact that the record has -never been processed, or that the VAL field currently contains a NaN (not a -number). UDF is initialized to TRUE at IOC initialization. Record and device -support routines which write to the VAL field are responsible for setting UDF. +The B indicates if the record's value is BnBeBined. Typically this +is caused by a failure in device support, the fact that the record has never +been processed, or that the VAL field currently contains a NaN (not a number) or +Inf (Infinite) value. UDF defaults to TRUE but can be set in a database file. +Record and device support routines which write to the VAL field are generally +responsible for setting and clearing UDF. -=fields STAT, SEVR, NSTA, NSEV, ACKS, ACKT, UDF +=fields STAT, SEVR, AMSG, NSTA, NSEV, NAMSG, ACKS, ACKT, UDF =cut @@ -422,9 +436,11 @@ The B field is is for private use of the device support modules. =head3 Debugging Fields -The B field is used for trace processing. If this field is non-zero a -message is printed whenever this record is processed, and when any other -record in the same lock-set is processed by a database link from this record. +The B field can be used to trace record processing. When this field is +non-zero and the record is processed, a trace message will be be printed for +this record and any other record in the same lock-set that is triggered by a +database link from this record. The trace message includes the name of the +thread doing the processing, and the name of the record being processed. The B field indicates if there is a breakpoint set at this record. This supports setting a debug breakpoint in the record processing. STEP through @@ -435,32 +451,26 @@ database processing can be supported using this. =head3 Miscellaneous Fields -The B field contains a character string value defining the access -security group for this record. If left empty, the record is placed in group -DEFAULT. +The B string field sets the name of the access security group used for this +record. If left empty, the record is placed in group C. -The B field is a field for private use of the access security system. +The B field is private for use by the access security system. -The B field controls dbPutFields to this record which are normally -issued by channel access. If the field is set to TRUE all dbPutFields -directed to this record are ignored except to the field DISP itself. +The B field controls whether puts from outside the IOC are allowed to +modify the fields of this record at all. If the field is set to TRUE all puts +directed to this record are ignored, except for puts to the field DISP itself. -The B field specifies the device type for the record. Each record type -has its own set of device support routines which are specified in -devSup.ASCII. If a record type does not have any associated device support, -DTYP and DSET are meaningless. +The B field specifies the device type for the record. Most record types +have their own set of device types which are specified in the IOC's database +definition file. If a record type does not call any device support routines, +the DTYP and DSET fields are not used. -The B field contains the monitor lock. The lock used by the monitor -routines when the monitor list is being used. The list is locked whenever -monitors are being scheduled, invoked, or when monitors are being added to or -removed from the list. This field is accessed only by the dbEvent routines. +The B field contains a mutex which is locked by the monitor routines in +dbEvent.c whenever the monitor list for this record is accessed. -The B field is the head of the list of monitors connected to this +The B field holds a linked list of client monitors connected to this record. Each record support module is responsible for triggering monitors for -any fields that change as a result of record processing. Monitors are present -if mlis count is greater than zero. The call to trigger monitors is: -db_post_event(precord,&data,mask), where "mask" is some combination of -DBE_ALARM, DBE_VALUE, and DBE_LOG. +any fields that change as a result of record processing. The B field contains the address of a putNotify callback. @@ -474,23 +484,24 @@ The B field contains the address of dbRecordType The B field specifies a reprocessing of the record when current processing completes. -The B