diff --git a/modules/database/.ci/travis-build.sh b/modules/database/.ci/travis-build.sh new file mode 100755 index 000000000..622979b9d --- /dev/null +++ b/modules/database/.ci/travis-build.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -e -x + +# set RTEMS to eg. "4.9" or "4.10" +# requires qemu, bison, flex, texinfo, install-info +if [ -n "$RTEMS" ] +then + # find local qemu-system-i386 + export PATH="$HOME/.cache/qemu/usr/bin:$PATH" + echo -n "Using QEMU: " + type qemu-system-i386 || echo "Missing qemu" + EXTRA=RTEMS_QEMU_FIXUPS=YES +fi + +make -j2 $EXTRA + +if [ "$TEST" != "NO" ] +then + make -j2 tapfiles + make -s test-results +fi diff --git a/modules/database/.ci/travis-prepare.sh b/modules/database/.ci/travis-prepare.sh new file mode 100755 index 000000000..ce2c18b5d --- /dev/null +++ b/modules/database/.ci/travis-prepare.sh @@ -0,0 +1,133 @@ +#!/bin/sh +set -e -x + +CURDIR="$PWD" + +QDIR="$HOME/.cache/qemu" + +if [ -n "$RTEMS" -a "$TEST" = "YES" ] +then + git clone --quiet --branch vme --depth 10 https://github.com/mdavidsaver/qemu.git "$HOME/.build/qemu" + cd "$HOME/.build/qemu" + + HEAD=`git log -n1 --pretty=format:%H` + echo "HEAD revision $HEAD" + + [ -e "$HOME/.cache/qemu/built" ] && BUILT=`cat "$HOME/.cache/qemu/built"` + echo "Cached revision $BUILT" + + if [ "$HEAD" != "$BUILT" ] + then + echo "Building QEMU" + git submodule --quiet update --init + + install -d "$HOME/.build/qemu/build" + cd "$HOME/.build/qemu/build" + + "$HOME/.build/qemu/configure" --prefix="$HOME/.cache/qemu/usr" --target-list=i386-softmmu --disable-werror + make -j2 + make install + + echo "$HEAD" > "$HOME/.cache/qemu/built" + fi +fi + +cd "$CURDIR" + +cat << EOF > configure/RELEASE.local +EPICS_BASE=$HOME/.source/epics-base +EOF + +install -d "$HOME/.source" +cd "$HOME/.source" + +add_base_module() { + MODULE=$1 + BRANCH=$2 + ( cd epics-base/modules && \ + git clone --quiet --depth 5 --branch "$MODULE"/"$BRANCH" https://github.com/${REPOBASE:-epics-base}/epics-base.git "$MODULE" && \ + cd "$MODULE" && git log -n1 ) +} + +git clone --quiet --depth 5 --branch core/"${BRCORE:-master}" https://github.com/${REPOBASE:-epics-base}/epics-base.git epics-base +( cd epics-base && git log -n1 ) +add_base_module libcom "${BRLIBCOM:-master}" +add_base_module ca "${BRCA:-master}" + +EPICS_HOST_ARCH=`sh epics-base/startup/EpicsHostArch` + +# requires wine and g++-mingw-w64-i686 +if [ "$WINE" = "32" ] +then + echo "Cross mingw32" + sed -i -e '/CMPLR_PREFIX/d' epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw + cat << EOF >> epics-base/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw +CMPLR_PREFIX=i686-w64-mingw32- +EOF + cat << EOF >> epics-base/configure/CONFIG_SITE +CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw +EOF +fi + +if [ "$STATIC" = "YES" ] +then + echo "Build static libraries/executables" + cat << EOF >> epics-base/configure/CONFIG_SITE +SHARED_LIBRARIES=NO +STATIC_BUILD=YES +EOF +fi + +case "$CMPLR" in +clang) + echo "Host compiler is clang" + cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH +GNU = NO +CMPLR_CLASS = clang +CC = clang +CCC = clang++ +EOF + + # hack + sed -i -e 's/CMPLR_CLASS = gcc/CMPLR_CLASS = clang/' epics-base/configure/CONFIG.gnuCommon + + clang --version + ;; +*) + echo "Host compiler is default" + gcc --version + ;; +esac + +cat <> epics-base/configure/CONFIG_SITE +USR_CPPFLAGS += $USR_CPPFLAGS +USR_CFLAGS += $USR_CFLAGS +USR_CXXFLAGS += $USR_CXXFLAGS +EOF + +# set RTEMS to eg. "4.9" or "4.10" +# requires qemu, bison, flex, texinfo, install-info +if [ -n "$RTEMS" ] +then + echo "Cross RTEMS${RTEMS} for pc386" + install -d /home/travis/.cache + curl -L "https://github.com/mdavidsaver/rsb/releases/download/travis-20160306-2/rtems${RTEMS}-i386-trusty-20190306-2.tar.gz" \ + | tar -C /home/travis/.cache -xj + + sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' epics-base/configure/os/CONFIG_SITE.Common.RTEMS + cat << EOF >> epics-base/configure/os/CONFIG_SITE.Common.RTEMS +RTEMS_VERSION=$RTEMS +RTEMS_BASE=/home/travis/.cache/rtems${RTEMS}-i386 +EOF + cat << EOF >> epics-base/configure/CONFIG_SITE +CROSS_COMPILER_TARGET_ARCHS+=RTEMS-pc386 +EOF + + # find local qemu-system-i386 + export PATH="$HOME/.cache/qemu/usr/bin:$PATH" + echo -n "Using QEMU: " + type qemu-system-i386 || echo "Missing qemu" + EXTRA=RTEMS_QEMU_FIXUPS=YES +fi + +make -j2 -C epics-base $EXTRA diff --git a/modules/database/.travis.yml b/modules/database/.travis.yml new file mode 100644 index 000000000..127ae7bc9 --- /dev/null +++ b/modules/database/.travis.yml @@ -0,0 +1,26 @@ +sudo: false +dist: trusty +language: c +compiler: + - gcc +addons: + apt: + packages: + - libreadline6-dev + - libncurses5-dev + - perl + - clang + - g++-mingw-w64-i686 +install: + - ./.ci/travis-prepare.sh +script: + - ./.ci/travis-build.sh +env: + - BRCORE=master BRLIBCOM=master BRCA=master + - CMPLR=clang + - USR_CXXFLAGS=-std=c++11 + - CMPLR=clang USR_CXXFLAGS=-std=c++11 + - WINE=32 TEST=NO STATIC=YES + - WINE=32 TEST=NO STATIC=NO + - RTEMS=4.10 TEST=NO + - RTEMS=4.9 TEST=NO diff --git a/modules/database/Makefile b/modules/database/Makefile new file mode 100644 index 000000000..9998e6cf5 --- /dev/null +++ b/modules/database/Makefile @@ -0,0 +1,19 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = . +include $(TOP)/configure/CONFIG + +DIRS += configure src +src_DEPEND_DIRS = configure + +DIRS += test +test_DEPEND_DIRS = src + +include $(TOP)/configure/RULES_TOP diff --git a/modules/database/configure/CONFIG b/modules/database/configure/CONFIG new file mode 100644 index 000000000..9ebdbeb20 --- /dev/null +++ b/modules/database/configure/CONFIG @@ -0,0 +1,36 @@ +# CONFIG - Load build configuration data +# +# Do not make changes to this file! + +# Allow user to override where the build rules come from +RULES = $(EPICS_BASE) + +# RELEASE files point to other application tops +include $(TOP)/configure/RELEASE +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common +ifdef T_A +-include $(TOP)/configure/RELEASE.Common.$(T_A) +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A) +endif + +BUILDING_DATABASE = DEFINED + +CONFIG = $(RULES)/configure +include $(CONFIG)/CONFIG + +# Override the Base definition: +INSTALL_LOCATION = $(TOP) + +# Use new RSET definition +BASE_CPPFLAGS += -DUSE_TYPED_RSET + +# Shared library ABI version. +SHRLIB_VERSION = 3.17.0 + +# CONFIG_SITE files contain other build configuration settings +include $(TOP)/configure/CONFIG_SITE +-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common +ifdef T_A + -include $(TOP)/configure/CONFIG_SITE.Common.$(T_A) + -include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A) +endif diff --git a/modules/database/configure/CONFIG_DATABASE_MODULE b/modules/database/configure/CONFIG_DATABASE_MODULE new file mode 100644 index 000000000..1ef9b475b --- /dev/null +++ b/modules/database/configure/CONFIG_DATABASE_MODULE @@ -0,0 +1,26 @@ +#************************************************************************* +# Copyright (c) 2017 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# Set EPICS_DATABASE if necessary +ifndef EPICS_DATABASE + EPICS_DATABASE = $(if $(BUILDING_DATABASE),$(INSTALL_LOCATION),$(EPICS_BASE)) + + # Paths to tools built here + EPICS_DATABASE_HOST_BIN = $(EPICS_DATABASE)/bin/$(EPICS_HOST_ARCH) +endif + +# Set location of locally-built tools +MAKEBPT = $(EPICS_DATABASE_HOST_BIN)/makeBpt$(HOSTEXE) +DBEXPAND = $(PERL) $(EPICS_DATABASE_HOST_BIN)/dbdExpand.pl +DBTORECORDTYPEH = $(PERL) $(EPICS_DATABASE_HOST_BIN)/dbdToRecordtypeH.pl +DBTOMENUH = $(PERL) $(EPICS_DATABASE_HOST_BIN)/dbdToMenuH.pl +DBDTOHTML = $(PERL) $(EPICS_DATABASE_HOST_BIN)/dbdToHtml.pl +REGISTERRECORDDEVICEDRIVER = $(PERL) $(EPICS_DATABASE_HOST_BIN)/registerRecordDeviceDriver.pl +MSI3_15 = $(EPICS_DATABASE_HOST_BIN)/msi$(HOSTEXE) + +# Libraries needed to link a basic IOC +EPICS_BASE_IOC_LIBS = dbRecStd dbCore ca Com diff --git a/modules/database/configure/CONFIG_DATABASE_VERSION b/modules/database/configure/CONFIG_DATABASE_VERSION new file mode 100644 index 000000000..a42eab01e --- /dev/null +++ b/modules/database/configure/CONFIG_DATABASE_VERSION @@ -0,0 +1,4 @@ +EPICS_DATABASE_MAJOR_VERSION = 3 +EPICS_DATABASE_MINOR_VERSION = 17 +EPICS_DATABASE_MAINTENANCE_VERSION = 1 +EPICS_DATABASE_DEVELOPMENT_FLAG = 1 diff --git a/modules/database/configure/CONFIG_SITE b/modules/database/configure/CONFIG_SITE new file mode 100644 index 000000000..d78c7f514 --- /dev/null +++ b/modules/database/configure/CONFIG_SITE @@ -0,0 +1,42 @@ +# CONFIG_SITE + +# Make any application-specific changes to the EPICS build +# configuration variables in this file. +# +# Host/target specific settings can be specified in files named +# CONFIG_SITE.$(EPICS_HOST_ARCH).Common +# CONFIG_SITE.Common.$(T_A) +# CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A) + +# CHECK_RELEASE controls the consistency checking of the support +# applications pointed to by the RELEASE* files. +# Normally CHECK_RELEASE should be set to YES. +# Set CHECK_RELEASE to NO to disable checking completely. +# Set CHECK_RELEASE to WARN to perform consistency checking but +# continue building even if conflicts are found. +CHECK_RELEASE = YES + +# Set this when you only want to compile this application +# for a subset of the cross-compiled target architectures +# that Base is built for. +#CROSS_COMPILER_TARGET_ARCHS = vxWorks-ppc32 + +# To install files into a location other than $(TOP) define +# INSTALL_LOCATION here. +#INSTALL_LOCATION= + +# Set this when the IOC and build host use different paths +# to the install location. This may be needed to boot from +# a Microsoft FTP server say, or on some NFS configurations. +#IOCS_APPL_TOP = + +# For application debugging purposes, override the HOST_OPT and/ +# or CROSS_OPT settings from base/configure/CONFIG_SITE +#HOST_OPT = NO +#CROSS_OPT = NO + +# These allow developers to override the CONFIG_SITE variable +# settings without having to modify the configure/CONFIG_SITE +# file itself. +-include $(TOP)/../CONFIG_SITE.local +-include $(TOP)/configure/CONFIG_SITE.local diff --git a/modules/database/configure/Makefile b/modules/database/configure/Makefile new file mode 100644 index 000000000..dd292091c --- /dev/null +++ b/modules/database/configure/Makefile @@ -0,0 +1,15 @@ +#************************************************************************* +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +TOP = .. + +include $(TOP)/configure/CONFIG + +TARGETS = $(CONFIG_TARGETS) +CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS))) + +CFG += CONFIG_DATABASE_MODULE +CFG += CONFIG_DATABASE_VERSION + +include $(TOP)/configure/RULES diff --git a/modules/database/configure/RELEASE b/modules/database/configure/RELEASE new file mode 100644 index 000000000..9b685a13e --- /dev/null +++ b/modules/database/configure/RELEASE @@ -0,0 +1,40 @@ +# RELEASE - Location of external support modules +# +# IF YOU CHANGE ANY PATHS in this file or make API changes to +# any modules it refers to, you should do a "make rebuild" in +# this application's top level directory. +# +# The EPICS build process does not check dependencies against +# any files from outside the application, so it is safest to +# rebuild it completely if any modules it depends on change. +# +# Host- or target-specific settings can be given in files named +# RELEASE.$(EPICS_HOST_ARCH).Common +# RELEASE.Common.$(T_A) +# RELEASE.$(EPICS_HOST_ARCH).$(T_A) +# +# This file is parsed by both GNUmake and an EPICS Perl script, +# so it may ONLY contain definititions of paths to other support +# modules, variable definitions that are used in module paths, +# and include statements that pull in other RELEASE files. +# Variables may be used before their values have been set. +# Build variables that are NOT used in paths should be set in +# the CONFIG_SITE file. + +# Variables and paths to dependent modules: +#MODULES = /path/to/modules +#MYMODULE = $(MODULES)/my-module + +# If building the EPICS modules individually, set these: +#EPICS_CA = $(MODULES)/ca-4.13.1 +#EPICS_LIBCOM = $(MODULES)/libcom-3.17.0 +#EPICS_BASE = $(MODULES)/core-7.0.1 + +# Set RULES here if you want to use build rules from elsewhere: +#RULES = $(MODULES)/build-rules + +# These lines allow developers to override these RELEASE settings +# without having to modify this file directly. +-include $(TOP)/../RELEASE.local +-include $(TOP)/../RELEASE.$(EPICS_HOST_ARCH).local +-include $(TOP)/configure/RELEASE.local diff --git a/modules/database/configure/RULES b/modules/database/configure/RULES new file mode 100644 index 000000000..6d56e14e8 --- /dev/null +++ b/modules/database/configure/RULES @@ -0,0 +1,6 @@ +# RULES + +include $(CONFIG)/RULES + +# Library should be rebuilt because LIBOBJS may have changed. +$(LIBNAME): ../Makefile diff --git a/modules/database/configure/RULES.ioc b/modules/database/configure/RULES.ioc new file mode 100644 index 000000000..901987c6c --- /dev/null +++ b/modules/database/configure/RULES.ioc @@ -0,0 +1,2 @@ +#RULES.ioc +include $(CONFIG)/RULES.ioc diff --git a/modules/database/configure/RULES_DIRS b/modules/database/configure/RULES_DIRS new file mode 100644 index 000000000..3ba269dcc --- /dev/null +++ b/modules/database/configure/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(CONFIG)/RULES_DIRS diff --git a/modules/database/configure/RULES_TOP b/modules/database/configure/RULES_TOP new file mode 100644 index 000000000..2b8cbc6da --- /dev/null +++ b/modules/database/configure/RULES_TOP @@ -0,0 +1,2 @@ +#RULES_TOP +include $(CONFIG)/RULES_TOP diff --git a/modules/database/src/Makefile b/modules/database/src/Makefile new file mode 100644 index 000000000..dfd1d3be9 --- /dev/null +++ b/modules/database/src/Makefile @@ -0,0 +1,31 @@ +#************************************************************************* +# Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = .. +include $(TOP)/configure/CONFIG + +# PDB Tools + +DIRS += tools + +# PDB Core + +DIRS += ioc +ioc_DEPEND_DIRS = tools + +# PDB Standard Record Definitions + +DIRS += std +std_DEPEND_DIRS = ioc + +# Templates + +DIRS += template + +include $(TOP)/configure/RULES_DIRS diff --git a/modules/database/src/ioc/Makefile b/modules/database/src/ioc/Makefile new file mode 100644 index 000000000..00851867e --- /dev/null +++ b/modules/database/src/ioc/Makefile @@ -0,0 +1,60 @@ +#************************************************************************* +# Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +TOP=../.. + +include $(TOP)/configure/CONFIG + +SRC = $(TOP)/src +IOCDIR = $(SRC)/ioc + +LIBRARY_IOC += dbCore +dbCore_LIBS += ca Com +dbCore_SYS_LIBS_WIN32 += ws2_32 + +dbCore_RCS += dbCore.rc +dbStaticHost_RCS = dbStaticHost.rc + +INC += databaseVersion.h +INC += databaseVersionNum.h + +PROD_LIBS = Com + +EPICS_DATABASE_MAJOR_VERSION = 3 +EPICS_DATABASE_MINOR_VERSION = 17 +EPICS_DATABASE_MAINTENANCE_VERSION = 0 +EPICS_DATABASE_DEVELOPMENT_FLAG = 1 + +include $(IOCDIR)/as/Makefile +include $(IOCDIR)/bpt/Makefile +include $(IOCDIR)/db/Makefile +include $(IOCDIR)/dbStatic/Makefile +include $(IOCDIR)/dbtemplate/Makefile +include $(IOCDIR)/misc/Makefile +include $(IOCDIR)/registry/Makefile +include $(IOCDIR)/rsrv/Makefile + +EXPANDVARS += EPICS_DATABASE_MAJOR_VERSION +EXPANDVARS += EPICS_DATABASE_MINOR_VERSION +EXPANDVARS += EPICS_DATABASE_MAINTENANCE_VERSION +EXPANDVARS += EPICS_DATABASE_DEVELOPMENT_FLAG + +EXPANDFLAGS += $(foreach var,$(EXPANDVARS),-D$(var)="$(strip $($(var)))") + +include $(TOP)/configure/RULES + +include $(IOCDIR)/dbStatic/RULES +include $(IOCDIR)/bpt/RULES +include $(IOCDIR)/db/RULES +include $(IOCDIR)/dbtemplate/RULES + +# Can't use EXPAND as generated headers must appear +# in O.Common, but EXPAND emits rules for O.$(T_A) +../O.Common/databaseVersionNum.h: ../databaseVersionNum.h@ + $(MKDIR) $(COMMON_DIR) + $(EXPAND_TOOL) $(EXPANDFLAGS) $($@_EXPANDFLAGS) $< $@ diff --git a/modules/database/src/ioc/as/Makefile b/modules/database/src/ioc/as/Makefile new file mode 100644 index 000000000..bef8637e5 --- /dev/null +++ b/modules/database/src/ioc/as/Makefile @@ -0,0 +1,26 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2010 Brookhaven Science Associates, as Operator of +# Brookhaven National Lab. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/as + +INC += asDbLib.h +INC += asCa.h +INC += asIocRegister.h + +dbCore_SRCS += asDbLib.c +dbCore_SRCS += asCa.c +dbCore_SRCS += asIocRegister.c + +PROD_HOST += ascheck +ascheck_SRCS = ascheck.c +ascheck_LIBS = dbCore ca Com diff --git a/modules/database/src/ioc/as/asCa.c b/modules/database/src/ioc/as/asCa.c new file mode 100644 index 000000000..d0180448b --- /dev/null +++ b/modules/database/src/ioc/as/asCa.c @@ -0,0 +1,335 @@ +/*asCa.c*/ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* Author: Marty Kraimer Date: 10-15-93 */ + +/*This module is separate from asDbLib because CA uses old database access*/ +#include +#include +#include +#include + +#include "alarm.h" +#include "asLib.h" +#include "cantProceed.h" +#include "db_access.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsStdio.h" +#include "epicsThread.h" +#include "errlog.h" +#include "taskwd.h" + +#include "cadef.h" +#include "caerr.h" +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "asCa.h" +#include "asDbLib.h" +#include "callback.h" +#include "epicsExport.h" + +int asCaDebug = 0; +epicsExportAddress(int,asCaDebug); +static int firstTime = TRUE; +static epicsThreadId threadid=0; +static int caInitializing=FALSE; +static epicsMutexId asCaTaskLock; /*lock access to task */ +static epicsEventId asCaTaskWait; /*Wait for task to respond*/ +static epicsEventId asCaTaskAddChannels; /*Tell asCaTask to add channels*/ +static epicsEventId asCaTaskClearChannels;/*Tell asCaTask to clear channels*/ + +typedef struct { + struct dbr_sts_double rtndata; + chid chid; +} CAPVT; + +static void exceptionCallback(struct exception_handler_args args) +{ + chid chid = args.chid; + long stat = args.stat; /* Channel access status code*/ + const char *channel; + const char *context; + static char *unknown = "unknown"; + const char *nativeType; + const char *requestType; + long nativeCount; + long requestCount; + int readAccess; + int writeAccess; + + channel = (chid ? ca_name(chid) : unknown); + context = (args.ctx ? args.ctx : unknown); + nativeType = dbr_type_to_text((chid ? ca_field_type(chid) : -1)); + requestType = dbr_type_to_text(args.type); + nativeCount = (chid ? ca_element_count(chid) : 0); + requestCount = args.count; + readAccess = (chid ? ca_read_access(chid) : 0); + writeAccess = (chid ? ca_write_access(chid) : 0); + + errlogPrintf("dbCa:exceptionCallback stat \"%s\" channel \"%s\"" + " context \"%s\"\n" + " nativeType %s requestType %s" + " nativeCount %ld requestCount %ld %s %s\n", + ca_message(stat),channel,context, + nativeType,requestType, + nativeCount,requestCount, + (readAccess ? "readAccess" : "noReadAccess"), + (writeAccess ? "writeAccess" : "noWriteAccess")); +} + +/*connectCallback only handles disconnects*/ +static void connectCallback(struct connection_handler_args arg) +{ + chid chid = arg.chid; + ASGINP *pasginp = (ASGINP *)ca_puser(chid); + ASG *pasg = pasginp->pasg; + + if(ca_state(chid)!=cs_conn) { + if(!(pasg->inpBad & (1<inpIndex))) { + /*was good so lets make it bad*/ + pasg->inpBad |= (1<inpIndex); + if(!caInitializing) asComputeAsg(pasg); + if(asCaDebug) printf("as connectCallback disconnect %s\n", + ca_name(chid)); + } + } +} + +static void eventCallback(struct event_handler_args arg) +{ + int caStatus = arg.status; + chid chid = arg.chid; + ASGINP *pasginp = (ASGINP *)arg.usr; + ASG *pasg; + CAPVT *pcapvt; + const struct dbr_sts_double *pdata; + + if(caStatus!=ECA_NORMAL) { + if(chid) { + epicsPrintf("asCa: eventCallback error %s channel %s\n", + ca_message(caStatus),ca_name(chid)); + } else { + epicsPrintf("asCa: eventCallback error %s chid is null\n", + ca_message(caStatus)); + } + return; + } + pasg = pasginp->pasg; + pcapvt = (CAPVT *)pasginp->capvt; + if(chid!=pcapvt->chid) { + epicsPrintf("asCa: eventCallback error pcapvt->chid != arg.chid\n"); + return; + } + if(ca_state(chid)!=cs_conn || !ca_read_access(chid)) { + if(!(pasg->inpBad & (1<inpIndex))) { + /*was good so lets make it bad*/ + pasg->inpBad |= (1<inpIndex); + if(!caInitializing) asComputeAsg(pasg); + if(asCaDebug) { + printf("as eventCallback %s inpBad ca_state %d" + " ca_read_access %d\n", + ca_name(chid),ca_state(chid),ca_read_access(chid)); + } + } + return; + } + pdata = arg.dbr; + pcapvt->rtndata = *pdata; /*structure copy*/ + if(pdata->severity==INVALID_ALARM) { + pasg->inpBad |= (1<inpIndex); + if(asCaDebug) + printf("as eventCallback %s inpBad because INVALID_ALARM\n", + ca_name(chid)); + } else { + pasg->inpBad &= ~((1<inpIndex)); + pasg->pavalue[pasginp->inpIndex] = pdata->value; + if(asCaDebug) + printf("as eventCallback %s inpGood data %f\n", + ca_name(chid),pdata->value); + } + pasg->inpChanged |= (1<inpIndex); + if(!caInitializing) asComputeAsg(pasg); +} + +static void asCaTask(void) +{ + ASG *pasg; + ASGINP *pasginp; + CAPVT *pcapvt; + int status; + + taskwdInsert(epicsThreadGetIdSelf(),NULL,NULL); + SEVCHK(ca_context_create(ca_enable_preemptive_callback), + "asCaTask calling ca_context_create"); + SEVCHK(ca_add_exception_event(exceptionCallback,NULL), + "ca_add_exception_event"); + while(TRUE) { + epicsEventMustWait(asCaTaskAddChannels); + caInitializing = TRUE; + pasg = (ASG *)ellFirst(&pasbase->asgList); + while(pasg) { + pasginp = (ASGINP *)ellFirst(&pasg->inpList); + while(pasginp) { + pasg->inpBad |= (1<inpIndex); + pcapvt = pasginp->capvt = asCalloc(1,sizeof(CAPVT)); + /*Note calls connectCallback immediately for local Pvs*/ + status = ca_search_and_connect(pasginp->inp,&pcapvt->chid, + connectCallback,pasginp); + if(status!=ECA_NORMAL) { + epicsPrintf("asCa ca_search_and_connect error %s\n", + ca_message(status)); + } + /*Note calls eventCallback immediately for local Pvs*/ + status = ca_add_event(DBR_STS_DOUBLE,pcapvt->chid, + eventCallback,pasginp,0); + if(status!=ECA_NORMAL) { + epicsPrintf("asCa ca_add_event error %s\n", + ca_message(status)); + } + pasginp = (ASGINP *)ellNext((ELLNODE *)pasginp); + } + pasg = (ASG *)ellNext((ELLNODE *)pasg); + } + SEVCHK(ca_flush_io(),"asCaTask"); + caInitializing = FALSE; + asComputeAllAsg(); + if(asCaDebug) printf("asCaTask initialized\n"); + epicsEventSignal(asCaTaskWait); + epicsEventMustWait(asCaTaskClearChannels); + pasg = (ASG *)ellFirst(&pasbase->asgList); + while(pasg) { + pasginp = (ASGINP *)ellFirst(&pasg->inpList); + while(pasginp) { + pcapvt = (CAPVT *)pasginp->capvt; + status = ca_clear_channel(pcapvt->chid); + if(status!=ECA_NORMAL) { + epicsPrintf("asCa ca_clear_channel error %s\n", + ca_message(status)); + } + free(pasginp->capvt); + pasginp->capvt = 0; + pasginp = (ASGINP *)ellNext((ELLNODE *)pasginp); + } + pasg = (ASG *)ellNext((ELLNODE *)pasg); + } + if(asCaDebug) printf("asCaTask has cleared all channels\n"); + epicsEventSignal(asCaTaskWait); + } +} + +void asCaStart(void) +{ + if(asCaDebug) printf("asCaStart called\n"); + if(firstTime) { + firstTime = FALSE; + asCaTaskLock=epicsMutexMustCreate(); + asCaTaskWait=epicsEventMustCreate(epicsEventEmpty); + asCaTaskAddChannels=epicsEventMustCreate(epicsEventEmpty); + asCaTaskClearChannels=epicsEventMustCreate(epicsEventEmpty); + threadid = epicsThreadCreate("asCaTask", + (epicsThreadPriorityScanLow - 3), + epicsThreadGetStackSize(epicsThreadStackBig), + (EPICSTHREADFUNC)asCaTask,0); + if(threadid==0) { + errMessage(0,"asCaStart: taskSpawn Failure\n"); + } + } + epicsMutexMustLock(asCaTaskLock); + epicsEventSignal(asCaTaskAddChannels); + epicsEventMustWait(asCaTaskWait); + if(asCaDebug) printf("asCaStart done\n"); + epicsMutexUnlock(asCaTaskLock); +} + +void asCaStop(void) +{ + if(threadid==0) return; + if(asCaDebug) printf("asCaStop called\n"); + epicsMutexMustLock(asCaTaskLock); + epicsEventSignal(asCaTaskClearChannels); + epicsEventMustWait(asCaTaskWait); + if(asCaDebug) printf("asCaStop done\n"); + epicsMutexUnlock(asCaTaskLock); +} + +int ascar(int level) { return ascarFP(stdout,level);} + +int ascarFP(FILE *fp,int level) +{ + ASG *pasg; + int n=0,nbad=0; + enum channel_state state; + + if(!pasbase) { + fprintf(fp,"access security not started\n"); + return(0); + } + pasg = (ASG *)ellFirst(&pasbase->asgList); + while(pasg) { + ASGINP *pasginp; + pasginp = (ASGINP *)ellFirst(&pasg->inpList); + while(pasginp) { + CAPVT *pcapvt = (CAPVT *)pasginp->capvt; + chid chid = pcapvt->chid; + pcapvt = pasginp->capvt; + ++n; + state = ca_state(chid); + if(state!=cs_conn) ++nbad; + if(level>1 || (level==1 && state!=cs_conn)) { + fprintf(fp,"connected:"); + if(state==cs_never_conn) fprintf(fp,"never "); + else if(state==cs_prev_conn) fprintf(fp,"prev "); + else if(state==cs_conn) fprintf(fp,"yes "); + else if(state==cs_closed) fprintf(fp,"closed"); + else fprintf(fp,"unknown"); + fprintf(fp," read:%s write:%s", + (ca_read_access(chid) ? "yes" : "no "), + (ca_write_access(chid) ? "yes" : "no ")); + fprintf(fp," %s %s\n", ca_name(chid),ca_host_name(chid)); + } + pasginp = (ASGINP *)ellNext((ELLNODE *)pasginp); + } + pasg = (ASG *)ellNext((ELLNODE *)pasg); + } + fprintf(fp,"%d channels %d not connected\n",n,nbad); + return(0); +} + +void ascaStats(int *pchans, int *pdiscon) +{ + ASG *pasg; + int n = 0; + int nbad = 0; + + if(!pasbase) { + if (pchans) *pchans = n; + if (pdiscon) *pdiscon = nbad; + return; + } + pasg = (ASG *)ellFirst(&pasbase->asgList); + while (pasg) { + ASGINP *pasginp; + pasginp = (ASGINP *)ellFirst(&pasg->inpList); + while (pasginp) { + CAPVT *pcapvt = (CAPVT *)pasginp->capvt; + chid chid = pcapvt->chid; + ++n; + if (ca_state(chid) != cs_conn) ++nbad; + pasginp = (ASGINP *)ellNext((ELLNODE *)pasginp); + } + pasg = (ASG *)ellNext((ELLNODE *)pasg); + } + if (pchans) *pchans = n; + if (pdiscon) *pdiscon = nbad; +} + diff --git a/modules/database/src/ioc/as/asCa.h b/modules/database/src/ioc/as/asCa.h new file mode 100644 index 000000000..360296fe2 --- /dev/null +++ b/modules/database/src/ioc/as/asCa.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* asCa.h */ + +#ifndef INCasCah +#define INCasCah + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void asCaStart(void); +epicsShareFunc void asCaStop(void); +epicsShareFunc int ascar(int level); +epicsShareFunc int ascarFP(FILE *fp, int level); +epicsShareFunc void ascaStats(int *pchans, int *pdiscon); + +#ifdef __cplusplus +} +#endif + +#endif /*INCasCah*/ diff --git a/modules/database/src/ioc/as/asDbLib.c b/modules/database/src/ioc/as/asDbLib.c new file mode 100644 index 000000000..c0fe192b0 --- /dev/null +++ b/modules/database/src/ioc/as/asDbLib.c @@ -0,0 +1,336 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* Author: Marty Kraimer Date: 02-11-94*/ + +#include +#include +#include +#include + +#include "alarm.h" +#include "asLib.h" +#include "cantProceed.h" +#include "dbDefs.h" +#include "epicsStdio.h" +#include "epicsThread.h" +#include "errlog.h" +#include "taskwd.h" + +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "asCa.h" +#include "asDbLib.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbStaticLib.h" +#include "recSup.h" + +static char *pacf=NULL; +static char *psubstitutions=NULL; +static epicsThreadId asInitTheadId=0; +static int firstTime = TRUE; + +static long asDbAddRecords(void) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + dbCommon *precord; + + dbInitEntry(pdbbase,pdbentry); + status = dbFirstRecordType(pdbentry); + while(!status) { + status = dbFirstRecord(pdbentry); + while(!status) { + precord = pdbentry->precnode->precord; + if(!precord->asp) { + status = asAddMember(&precord->asp, precord->asg); + if(status) errMessage(status,"asDbAddRecords:asAddMember"); + asPutMemberPvt(precord->asp,precord); + } + status = dbNextRecord(pdbentry); + } + status = dbNextRecordType(pdbentry); + } + dbFinishEntry(pdbentry); + return(0); +} + +int asSetFilename(const char *acf) +{ + if (pacf) + free (pacf); + if (acf) { + pacf = calloc(1, strlen(acf)+1); + if (!pacf) { + errMessage(0, "asSetFilename calloc failure"); + } else { + strcpy(pacf, acf); + if (*pacf != '/' && !strchr(pacf, ':')) { + printf("asSetFilename: Warning - relative paths won't usually " + "work\n"); + } + } + } else { + pacf = NULL; + } + return 0; +} + +int asSetSubstitutions(const char *substitutions) +{ + if(psubstitutions) free ((void *)psubstitutions); + if(substitutions) { + psubstitutions = calloc(1,strlen(substitutions)+1); + if(!psubstitutions) { + errMessage(0,"asSetSubstitutions calloc failure"); + } else { + strcpy(psubstitutions,substitutions); + } + } else { + psubstitutions = NULL; + } + return(0); +} + +static void asSpcAsCallback(struct dbCommon *precord) +{ + asChangeGroup(&precord->asp, precord->asg); +} + +static void asInitCommonOnce(void *arg) +{ + int *firstTime = (int *)arg; + *firstTime = FALSE; +} + +static long asInitCommon(void) +{ + long status; + int asWasActive = asActive; + int wasFirstTime = firstTime; + static epicsThreadOnceId asInitCommonOnceFlag = EPICS_THREAD_ONCE_INIT; + + + epicsThreadOnce(&asInitCommonOnceFlag,asInitCommonOnce,(void *)&firstTime); + if(wasFirstTime) { + if(!pacf) return(0); /*access security will NEVER be turned on*/ + } else { + if(!asActive) { + printf("Access security is NOT enabled." + " Was asSetFilename specified before iocInit?\n"); + return(S_asLib_asNotActive); + } + if(pacf) { + asCaStop(); + } else { /*Just leave everything as is */ + return(S_asLib_badConfig); + } + } + status = asInitFile(pacf,psubstitutions); + if(asActive) { + if(!asWasActive) { + dbSpcAsRegisterCallback(asSpcAsCallback); + asDbAddRecords(); + } + asCaStart(); + } + return(status); +} + +int asInit(void) +{ + return(asInitCommon()); +} + +int asShutdown(void) { + volatile ASBASE *pbase = pasbase; + pasbase = NULL; + firstTime = TRUE; + if(pbase) + asFreeAll((ASBASE*)pbase); + return 0; +} + +static void wdCallback(void *arg) +{ + ASDBCALLBACK *pcallback = (ASDBCALLBACK *)arg; + pcallback->status = S_asLib_InitFailed; + callbackRequest(&pcallback->callback); +} + +static void asInitTask(ASDBCALLBACK *pcallback) +{ + long status; + + taskwdInsert(epicsThreadGetIdSelf(), wdCallback, (void *)pcallback); + status = asInitCommon(); + taskwdRemove(epicsThreadGetIdSelf()); + asInitTheadId = 0; + if(pcallback) { + pcallback->status = status; + callbackRequest(&pcallback->callback); + } +} + +int asInitAsyn(ASDBCALLBACK *pcallback) +{ + if(!pacf) return(0); + if(asInitTheadId) { + errMessage(-1,"asInit: asInitTask already active"); + if(pcallback) { + pcallback->status = S_asLib_InitFailed; + callbackRequest(&pcallback->callback); + } + return(-1); + } + asInitTheadId = epicsThreadCreate("asInitTask", + (epicsThreadPriorityCAServerHigh + 1), + epicsThreadGetStackSize(epicsThreadStackBig), + (EPICSTHREADFUNC)asInitTask,(void *)pcallback); + if(asInitTheadId==0) { + errMessage(0,"asInit: epicsThreadCreate Error"); + if(pcallback) { + pcallback->status = S_asLib_InitFailed; + callbackRequest(&pcallback->callback); + } + asInitTheadId = 0; + } + return(0); +} + +int asDbGetAsl(struct dbChannel *chan) +{ + return dbChannelFldDes(chan)->as_level; +} + +void * asDbGetMemberPvt(struct dbChannel *chan) +{ + return dbChannelRecord(chan)->asp; +} + +static void astacCallback(ASCLIENTPVT clientPvt,asClientStatus status) +{ + char *recordname; + + recordname = (char *)asGetClientPvt(clientPvt); + printf("astac callback %s: status=%d",recordname,status); + printf(" get %s put %s\n",(asCheckGet(clientPvt) ? "Yes" : "No"), + (asCheckPut(clientPvt) ? "Yes" : "No")); +} + +int astac(const char *pname,const char *user,const char *location) +{ + DBADDR *paddr; + long status; + ASCLIENTPVT *pasclientpvt=NULL; + dbCommon *precord; + dbFldDes *pflddes; + char *puser; + char *plocation; + + paddr = dbCalloc(1,sizeof(DBADDR) + sizeof(ASCLIENTPVT)); + pasclientpvt = (ASCLIENTPVT *)(paddr + 1); + status=dbNameToAddr(pname,paddr); + if(status) { + errMessage(status,"dbNameToAddr error"); + return(1); + } + precord = paddr->precord; + pflddes = paddr->pfldDes; + puser = asCalloc(1,strlen(user)+1); + strcpy(puser,user); + plocation = asCalloc(1,strlen(location)+1); + strcpy(plocation,location); + + status = asAddClient(pasclientpvt,precord->asp, + (int)pflddes->as_level,puser,plocation); + if(status) { + errMessage(status,"asAddClient error"); + return(1); + } else { + asPutClientPvt(*pasclientpvt,(void *)precord->name); + asRegisterClientCallback(*pasclientpvt,astacCallback); + } + return(0); +} + +static void myMemberCallback(ASMEMBERPVT memPvt,FILE *fp) +{ + dbCommon *precord; + + precord = asGetMemberPvt(memPvt); + if(precord) fprintf(fp," Record:%s",precord->name); +} + +int asdbdump(void) +{ + asDumpFP(stdout,myMemberCallback,NULL,1); + return(0); +} + +int asdbdumpFP(FILE *fp) +{ + asDumpFP(fp,myMemberCallback,NULL,1); + return(0); +} + +int aspuag(const char *uagname) +{ + asDumpUagFP(stdout,uagname); + return(0); +} + +int aspuagFP(FILE *fp,const char *uagname) +{ + + asDumpUagFP(fp,uagname); + return(0); +} + +int asphag(const char *hagname) +{ + asDumpHagFP(stdout,hagname); + return(0); +} + +int asphagFP(FILE *fp,const char *hagname) +{ + asDumpHagFP(fp,hagname); + return(0); +} + +int asprules(const char *asgname) +{ + asDumpRulesFP(stdout,asgname); + return(0); +} + +int asprulesFP(FILE *fp,const char *asgname) +{ + asDumpRulesFP(fp,asgname); + return(0); +} + +int aspmem(const char *asgname,int clients) +{ + asDumpMemFP(stdout,asgname,myMemberCallback,clients); + return(0); +} + +int aspmemFP(FILE *fp,const char *asgname,int clients) +{ + asDumpMemFP(fp,asgname,myMemberCallback,clients); + return(0); +} diff --git a/modules/database/src/ioc/as/asDbLib.h b/modules/database/src/ioc/as/asDbLib.h new file mode 100644 index 000000000..65c4c6f59 --- /dev/null +++ b/modules/database/src/ioc/as/asDbLib.h @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* Author: Marty Kraimer Date: 02-23-94*/ + +#ifndef INCdbAsLibh +#define INCdbAsLibh + +#include "callback.h" +#include "shareLib.h" + +typedef struct { + CALLBACK callback; + long status; +} ASDBCALLBACK; + +struct dbChannel; + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int asSetFilename(const char *acf); +epicsShareFunc int asSetSubstitutions(const char *substitutions); +epicsShareFunc int asInit(void); +epicsShareFunc int asInitAsyn(ASDBCALLBACK *pcallback); +epicsShareFunc int asShutdown(void); +epicsShareFunc int asDbGetAsl(struct dbChannel *chan); +epicsShareFunc void * asDbGetMemberPvt(struct dbChannel *chan); +epicsShareFunc int asdbdump(void); +epicsShareFunc int asdbdumpFP(FILE *fp); +epicsShareFunc int aspuag(const char *uagname); +epicsShareFunc int aspuagFP(FILE *fp,const char *uagname); +epicsShareFunc int asphag(const char *hagname); +epicsShareFunc int asphagFP(FILE *fp,const char *hagname); +epicsShareFunc int asprules(const char *asgname); +epicsShareFunc int asprulesFP(FILE *fp,const char *asgname); +epicsShareFunc int aspmem(const char *asgname,int clients); +epicsShareFunc int aspmemFP( + FILE *fp,const char *asgname,int clients); +epicsShareFunc int astac( + const char *recordname,const char *user,const char *location); + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbAsLibh*/ diff --git a/modules/database/src/ioc/as/asIocRegister.c b/modules/database/src/ioc/as/asIocRegister.c new file mode 100644 index 000000000..16cba90c6 --- /dev/null +++ b/modules/database/src/ioc/as/asIocRegister.c @@ -0,0 +1,129 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "asLib.h" +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "asCa.h" +#include "asDbLib.h" +#include "asIocRegister.h" + +/* asSetFilename */ +static const iocshArg asSetFilenameArg0 = { "ascf",iocshArgString}; +static const iocshArg * const asSetFilenameArgs[] = {&asSetFilenameArg0}; +static const iocshFuncDef asSetFilenameFuncDef = + {"asSetFilename",1,asSetFilenameArgs}; +static void asSetFilenameCallFunc(const iocshArgBuf *args) +{ + asSetFilename(args[0].sval); +} + +/* asSetSubstitutions */ +static const iocshArg asSetSubstitutionsArg0 = { "substitutions",iocshArgString}; +static const iocshArg * const asSetSubstitutionsArgs[] = {&asSetSubstitutionsArg0}; +static const iocshFuncDef asSetSubstitutionsFuncDef = + {"asSetSubstitutions",1,asSetSubstitutionsArgs}; +static void asSetSubstitutionsCallFunc(const iocshArgBuf *args) +{ + asSetSubstitutions(args[0].sval); +} + +/* asInit */ +static const iocshFuncDef asInitFuncDef = {"asInit",0}; +static void asInitCallFunc(const iocshArgBuf *args) +{ + asInit(); +} + +/* asdbdump */ +static const iocshFuncDef asdbdumpFuncDef = {"asdbdump",0}; +static void asdbdumpCallFunc(const iocshArgBuf *args) +{ + asdbdump(); +} + +/* aspuag */ +static const iocshArg aspuagArg0 = { "uagname",iocshArgString}; +static const iocshArg * const aspuagArgs[] = {&aspuagArg0}; +static const iocshFuncDef aspuagFuncDef = {"aspuag",1,aspuagArgs}; +static void aspuagCallFunc(const iocshArgBuf *args) +{ + aspuag(args[0].sval); +} + +/* asphag */ +static const iocshArg asphagArg0 = { "hagname",iocshArgString}; +static const iocshArg * const asphagArgs[] = {&asphagArg0}; +static const iocshFuncDef asphagFuncDef = {"asphag",1,asphagArgs}; +static void asphagCallFunc(const iocshArgBuf *args) +{ + asphag(args[0].sval); +} + +/* asprules */ +static const iocshArg asprulesArg0 = { "asgname",iocshArgString}; +static const iocshArg * const asprulesArgs[] = {&asprulesArg0}; +static const iocshFuncDef asprulesFuncDef = {"asprules",1,asprulesArgs}; +static void asprulesCallFunc(const iocshArgBuf *args) +{ + asprules(args[0].sval); +} + +/* aspmem */ +static const iocshArg aspmemArg0 = { "asgname",iocshArgString}; +static const iocshArg aspmemArg1 = { "clients",iocshArgInt}; +static const iocshArg * const aspmemArgs[] = {&aspmemArg0,&aspmemArg1}; +static const iocshFuncDef aspmemFuncDef = {"aspmem",2,aspmemArgs}; +static void aspmemCallFunc(const iocshArgBuf *args) +{ + aspmem(args[0].sval,args[1].ival); +} + +/* astac */ +static const iocshArg astacArg0 = { "recordname",iocshArgString}; +static const iocshArg astacArg1 = { "user",iocshArgString}; +static const iocshArg astacArg2 = { "location",iocshArgString}; +static const iocshArg * const astacArgs[] = {&astacArg0,&astacArg1,&astacArg2}; +static const iocshFuncDef astacFuncDef = {"astac",3,astacArgs}; +static void astacCallFunc(const iocshArgBuf *args) +{ + astac(args[0].sval,args[1].sval,args[2].sval); +} + +/* ascar */ +static const iocshArg ascarArg0 = { "level",iocshArgInt}; +static const iocshArg * const ascarArgs[] = {&ascarArg0}; +static const iocshFuncDef ascarFuncDef = {"ascar",1,ascarArgs}; +static void ascarCallFunc(const iocshArgBuf *args) +{ + ascar(args[0].ival); +} + +/* asDumpHash */ +static const iocshFuncDef asDumpHashFuncDef = {"asDumpHash",0,0}; +static void asDumpHashCallFunc(const iocshArgBuf *args) +{ + asDumpHash(); +} + +void asIocRegister(void) +{ + iocshRegister(&asSetFilenameFuncDef,asSetFilenameCallFunc); + iocshRegister(&asSetSubstitutionsFuncDef,asSetSubstitutionsCallFunc); + iocshRegister(&asInitFuncDef,asInitCallFunc); + iocshRegister(&asdbdumpFuncDef,asdbdumpCallFunc); + iocshRegister(&aspuagFuncDef,aspuagCallFunc); + iocshRegister(&asphagFuncDef,asphagCallFunc); + iocshRegister(&asprulesFuncDef,asprulesCallFunc); + iocshRegister(&aspmemFuncDef,aspmemCallFunc); + iocshRegister(&astacFuncDef,astacCallFunc); + iocshRegister(&ascarFuncDef,ascarCallFunc); + iocshRegister(&asDumpHashFuncDef,asDumpHashCallFunc); +} diff --git a/modules/database/src/ioc/as/asIocRegister.h b/modules/database/src/ioc/as/asIocRegister.h new file mode 100644 index 000000000..a7421cdac --- /dev/null +++ b/modules/database/src/ioc/as/asIocRegister.h @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_asIocRegister_H +#define INC_asIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void asIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_asIocRegister_H */ diff --git a/modules/database/src/ioc/as/ascheck.c b/modules/database/src/ioc/as/ascheck.c new file mode 100644 index 000000000..7272ef4ae --- /dev/null +++ b/modules/database/src/ioc/as/ascheck.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Author: Marty Kraimer Date: 03-24-94 */ + +#include +#include +#include +#include + +#include "asLib.h" +#include "dbStaticLib.h" +#include "errlog.h" + +int main(int argc,char **argv) +{ + int argn = 1; + char *sub = NULL; + int subLength = 0; + char **pstr; + char *psep; + int *len; + long status = 0; + static char *subSep = ","; + + /* Look for -Smacro=value options */ + while (argc>argn && (strncmp(argv[argn], "-S", 2)==0)) { + pstr = ⊂ + psep = subSep; + len = &subLength; + if (strlen(argv[argn])==2) { + dbCatString(pstr, len, argv[++argn], psep); + } else { + dbCatString(pstr, len, argv[argn]+2, psep); + } + argn++; + } + if (argc == argn) { + status = asInitFP(stdin, sub); + if(status) errlogPrintf("ascheck: Access Security File failed.\n"); + } else if (argc == argn+1) { + status = asInitFile(argv[argn], sub); + if(status) errlogPrintf("ascheck: Access Security File failed.\n"); + } else { + printf("usage: ascheck [-Smac=sub ...] [<] file\n"); + status = -1; + } + errlogFlush(); + return status; +} diff --git a/modules/database/src/ioc/bpt/Makefile b/modules/database/src/ioc/bpt/Makefile new file mode 100644 index 000000000..ea1db1f14 --- /dev/null +++ b/modules/database/src/ioc/bpt/Makefile @@ -0,0 +1,29 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/bpt + +INC += cvtTable.h + +DBDINC += menuConvert + +BPT_DBD += bptTypeJdegC.dbd +BPT_DBD += bptTypeJdegF.dbd +BPT_DBD += bptTypeKdegC.dbd +BPT_DBD += bptTypeKdegF.dbd +DBD += $(BPT_DBD) + +PROD_HOST += makeBpt + +makeBpt_SRCS = makeBpt + +HTMLS += menuConvert.html + diff --git a/modules/database/src/ioc/bpt/RULES b/modules/database/src/ioc/bpt/RULES new file mode 100644 index 000000000..cf6232bc6 --- /dev/null +++ b/modules/database/src/ioc/bpt/RULES @@ -0,0 +1,15 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2010 Brookhaven Science Associates, as Operator of +# Brookhaven National Lab. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +$(patsubst %,$(COMMON_DIR)/%,$(BPT_DBD)) : \ + $(COMMON_DIR)/bpt%.dbd : $(MAKEBPT) diff --git a/modules/database/src/ioc/bpt/bptTypeJdegC.data b/modules/database/src/ioc/bpt/bptTypeJdegC.data new file mode 100644 index 000000000..a2ac1928f --- /dev/null +++ b/modules/database/src/ioc/bpt/bptTypeJdegC.data @@ -0,0 +1,142 @@ +!header +"typeJdegC" 0 0 700 4095 .5 -210 760 1 +!data + +-8.096 -8.076 -8.057 -8.037 -8.017 -7.996 -7.976 -7.955 -7.934 -7.912 + + +-7.890 -7.868 -7.846 -7.824 -7.801 -7.778 -7.755 -7.731 -7.707 -7.683 +-7.659 -7.634 -7.609 -7.584 -7.559 -7.533 -7.508 -7.482 -7.455 -7.429 +-7.402 -7.375 -7.348 -7.321 -7.293 -7.265 -7.237 -7.209 -7.180 -7.151 +-7.122 -7.093 -7.064 -7.034 -7.004 -6.974 -6.944 -6.914 -6.883 -6.852 +-6.821 -6.790 -6.758 -6.727 -6.695 -6.663 -6.630 -6.598 -6.565 -6.532 + + +-6.499 -6.466 -6.433 -6.399 -6.365 -6.331 -6.297 -6.263 -6.228 -6.194 +-6.159 -6.124 -6.089 -6.053 -6.018 -5.982 -5.946 -5.910 -5.874 -5.837 +-5.801 -5.764 -5.727 -5.690 -5.653 -5.615 -5.578 -5.540 -5.502 -5.464 +-5.426 -5.388 -5.349 -5.311 -5.272 -5.233 -5.194 -5.155 -5.115 -5.076 +-5.036 -4.996 -4.956 -4.916 -4.876 -4.836 -4.795 -4.755 -4.714 -4.673 + + +-4.632 -4.591 -4.550 -4.508 -4.467 -4.425 -4.383 -4.341 -4.299 -4.257 +-4.215 -4.172 -4.130 -4.087 -4.044 -4.001 -3.958 -3.915 -3.872 -3.829 +-3.785 -3.742 -3.698 -3.654 -3.610 -3.566 -3.522 -3.478 -3.433 -3.389 +-3.344 -3.299 -3.255 -3.210 -3.165 -3.120 -3.074 -3.029 -2.984 -2.938 +-2.892 -2.847 -2.801 -2.755 -2.709 -2.663 -2.617 -2.570 -2.524 -2.478 + + +-2.431 -2.384 -2.338 -2.291 -2.244 -2.197 -2.150 -2.102 -2.055 -2.008 +-1.960 -1.913 -1.865 -1.818 -1.770 -1.722 -1.674 -1.626 -1.578 -1.530 +-1.481 -1.433 -1.385 -1.336 -1.288 -1.239 -1.190 -1.141 -1.093 -1.044 +-0.995 -0.945 -0.896 -0.847 -0.798 -0.748 -0.699 -0.650 -0.600 -0.550 +-0.501 -0.451 -0.401 -0.351 -0.301 -0.251 -0.201 -0.151 -0.101 -0.050 + + +0.0 0.050 0.101 0.151 0.202 0.253 0.303 0.354 0.405 0.456 +0.507 0.558 0.609 0.660 0.711 0.762 0.813 0.865 0.916 0.967 +1.019 1.070 1.122 1.174 1.225 1.277 1.329 1.381 1.432 1.484 +1.536 1.588 1.640 1.693 1.745 1.797 1.849 1.901 1.954 2.006 +2.058 2.111 2.163 2.216 2.268 2.321 2.374 2.426 2.479 2.532 + + +2.585 2.638 2.691 2.743 2.796 2.849 2.902 2.956 3.009 3.062 +3.115 3.168 3.221 3.275 3.328 3.381 3.435 3.488 3.542 3.595 +3.649 3.702 3.756 3.809 3.863 3.917 3.971 4.024 4.078 4.132 +4.186 4.239 4.293 4.347 4.401 4.455 4.509 4.563 4.617 4.671 +4.725 4.780 4.834 4.888 4.942 4.996 5.050 5.105 5.159 5.213 + + +5.268 5.322 5.376 5.431 5.485 5.540 5.594 5.649 5.703 5.758 +5.812 5.867 5.921 5.976 6.031 6.085 6.140 6.195 6.249 6.304 +6.359 6.414 6.468 6.523 6.578 6.633 6.688 6.742 6.797 6.852 +6.907 6.962 7.017 7.072 7.127 7.182 7.237 7.292 7.347 7.402 +7.457 7.512 7.567 7.622 7.677 7.732 7.787 7.843 7.898 7.953 + + +8.008 8.063 8.118 8.174 8.229 8.284 8.339 8.394 8.450 8.505 +8.560 8.616 8.671 8.726 8.781 8.837 8.892 8.947 9.003 9.058 +9.113 9.169 9.224 9.279 9.335 9.390 9.446 9.501 9.556 9.612 +9.667 9.723 9.778 9.834 9.889 9.944 10.000 10.055 10.111 10.166 +10.222 10.277 10.333 10.388 10.444 10.499 10.555 10.610 10.666 10.721 + + +10.777 10.832 10.888 10.943 10.999 11.054 11.110 11.165 11.221 11.276 +11.332 11.387 11.443 11.498 11.554 11.609 11.665 11.720 11.776 11.831 +11.887 11.943 11.998 12.054 12.109 12.165 12.220 12.276 12.331 12.387 +12.442 12.498 12.553 12.609 12.664 12.720 12.776 12.831 12.887 12.942 +12.998 13.053 13.109 13.164 13.220 13.275 13.331 13.386 13.442 13.497 + + +13.553 13.608 13.664 13.719 13.775 13.830 13.886 13.941 13.997 14.052 +14.108 14.163 14.219 14.274 14.330 14.385 14.441 14.496 14.552 14.607 +14.663 14.718 14.774 14.829 14.885 14.940 14.995 15.051 15.106 15.162 +15.217 15.273 15.328 15.383 15.439 15.494 15.550 15.605 15.661 15.716 +15.771 15.827 15.882 15.938 15.993 16.048 16.104 16.159 16.214 16.270 + + +16.325 16.380 16.436 16.491 16.547 16.602 16.657 16.713 16.768 16.823 +16.879 16.934 16.989 17.044 17.100 17.155 17.210 17.266 17.321 17.376 +17.432 17.487 17.542 17.597 17.653 17.708 17.763 17.818 17.874 17.929 +17.984 18.039 18.095 18.150 18.205 18.260 18.316 18.371 18.426 18.481 +18.537 18.592 18.647 18.702 18.757 18.813 18.868 18.923 18.978 19.033 + + +19.089 19.144 19.199 19.254 19.309 19.364 19.420 19.475 19.530 19.585 +19.640 19.695 19.751 19.806 19.861 19.916 19.971 20.026 20.081 20.137 +20.192 20.247 20.302 20.357 20.412 20.467 20.523 20.578 20.633 20.688 +20.743 20.798 20.853 20.909 20.964 21.019 21.074 21.129 21.184 21.239 +21.295 21.350 21.405 21.460 21.515 21.570 21.625 21.680 21.736 21.791 + + +21.846 21.901 21.956 22.011 22.066 22.122 22.177 22.232 22.287 22.342 +22.397 22.453 22.508 22.563 22.618 22.673 22.728 22.784 22.839 22.894 +22.949 23.004 23.060 23.115 23.170 23.225 23.280 23.336 23.391 23.446 +23.501 23.556 23.612 23.667 23.722 23.777 23.833 23.888 23.943 23.999 +24.054 24.109 24.164 24.220 24.275 24.330 24.386 24.441 24.496 24.552 + + +24.607 24.662 24.718 24.773 24.829 24.884 24.939 24.995 25.050 25.106 +25.161 25.217 25.272 25.327 25.383 25.438 25.494 25.549 25.605 25.661 +25.716 25.772 25.827 25.883 25.938 25.994 26.050 26.105 26.161 26.216 +26.272 26.328 26.383 26.439 26.495 26.551 26.606 26.662 26.718 26.774 +26.829 26.885 26.941 26.997 27.053 27.109 27.165 27.220 27.276 27.332 + + +27.388 27.444 27.500 27.556 27.612 27.668 27.724 27.780 27.836 27.893 +27.949 28.005 28.061 28.117 28.173 28.230 28.286 28.342 28.398 28.455 +28.511 28.567 28.624 28.680 28.736 28.793 28.849 28.906 28.962 29.019 +29.075 29.132 29.188 29.245 29.301 29.358 29.415 29.471 29.528 29.585 +29.642 29.698 29.755 29.812 29.869 29.926 29.983 30.039 30.096 30.153 + + +30.210 30.267 30.324 30.381 30.439 30.496 30.553 30.610 30.667 30.724 +30.782 30.839 30.896 30.954 31.011 31.068 31.126 31.183 31.241 31.298 +31.356 31.413 31.471 31.528 31.586 31.644 31.702 31.759 31.817 31.875 +31.933 31.991 32.048 32.106 32.164 32.222 32.280 32.338 32.396 32.455 +32.513 32.571 32.629 32.687 32.746 32.804 32.862 32.921 32.979 33.038 + + +33.096 33.155 33.213 33.272 33.330 33.389 33.448 33.506 33.565 33.624 +33.683 33.742 33.800 33.859 33.918 33.977 34.036 34.095 34.155 34.214 +34.273 34.332 34.391 34.451 34.510 34.569 34.629 34.688 34.748 34.807 +34.867 34.926 34.986 35.046 35.105 35.165 35.225 35.285 35.344 35.404 +35.464 35.524 35.584 35.644 35.704 35.764 35.825 35.885 35.945 36.005 + + +36.066 36.126 36.186 36.247 36.307 36.368 36.428 36.489 36.549 36.610 +36.671 36.732 36.792 36.853 36.914 36.975 37.036 37.097 37.158 37.219 +37.280 37.341 37.402 37.463 37.525 37.586 37.647 37.709 37.770 37.831 +37.893 37.954 38.016 38.078 38.139 38.201 38.262 38.324 38.386 38.448 +38.510 38.572 38.633 38.695 38.757 38.819 38.882 38.944 39.006 39.068 + + +39.130 39.192 39.255 39.317 39.379 39.442 39.504 39.567 39.629 39.692 +39.754 39.817 39.880 39.942 40.005 40.068 40.131 40.193 40.256 40.319 +40.382 40.445 40.508 40.571 40.634 40.697 40.760 40.823 40.886 40.950 +41.013 41.076 41.139 41.203 41.266 41.329 41.393 41.456 41.520 41.583 +41.647 41.710 41.774 41.837 41.901 41.965 42.028 42.092 42.156 42.219 + + +42.283 42.347 42.411 42.475 42.538 42.602 42.666 42.730 42.794 42.858 +42.922 diff --git a/modules/database/src/ioc/bpt/bptTypeJdegF.data b/modules/database/src/ioc/bpt/bptTypeJdegF.data new file mode 100644 index 000000000..f912cb87e --- /dev/null +++ b/modules/database/src/ioc/bpt/bptTypeJdegF.data @@ -0,0 +1,213 @@ +! cvtTypeJdegF.data +"typeJdegF" 32 0 1200 4095 1.0 -350 1400 1 +! +-8.137 -8.127 -8.117 -8.106 -8.096 -8.085 -8.074 -8.063 -8.052 -8.041 +-8.030 -8.019 -8.008 -7.996 -7.985 -7.973 -7.962 -7.950 -7.938 -7.927 +-7.915 -7.903 -7.890 -7.878 -7.866 -7.854 -7.841 -7.829 -7.816 -7.803 +-7.791 -7.778 -7.765 -7.752 -7.739 -7.726 -7.712 -7.699 -7.686 -7.672 +-7.659 -7.645 -7.631 -7.618 -7.604 -7.590 -7.576 -7.562 -7.548 -7.533 + +-7.519 -7.505 -7.490 -7.476 -7.461 -7.447 -7.432 -7.417 -7.402 -7.387 +-7.372 -7.357 -7.342 -7.327 -7.311 -7.296 -7.281 -7.265 -7.250 -7.234 +-7.218 -7.202 -7.187 -7.171 -7.155 -7.139 -7.122 -7.106 -7.090 -7.074 +-7.057 -7.041 -7.024 -7.008 -6.991 -6.974 -6.958 -6.941 -6.924 -6.907 +-6.890 -6.873 -6.856 -6.838 -6.821 -6.804 -6.786 -6.769 -6.751 -6.734 + +-6.716 -6.698 -6.680 -6.663 -6.645 -6.627 -6.609 -6.591 -6.572 -6.554 +-6.536 -6.518 -6.499 -6.481 -6.462 -6.444 -6.425 -6.407 -6.388 -6.369 +-6.350 -6.331 -6.312 -6.293 -6.274 -6.255 -6.236 -6.217 -6.198 -6.178 +-6.159 -6.139 -6.120 -6.100 -6.081 -6.061 -6.041 -6.022 -6.002 -5.982 +-5.962 -5.942 -5.922 -5.902 -5.882 -5.861 -5.841 -5.821 -5.801 -5.780 + +-5.760 -5.739 -5.719 -5.698 -5.678 -5.657 -5.636 -5.615 -5.594 -5.574 +-5.553 -5.532 -5.511 -5.490 -5.468 -5.447 -5.426 -5.405 -5.383 -5.362 +-5.341 -5.319 -5.298 -5.276 -5.255 -5.233 -5.211 -5.190 -5.168 -5.146 +-5.124 -5.102 -5.080 -5.058 -5.036 -5.014 -4.992 -4.970 -4.948 -4.925 +-4.903 -4.881 -4.858 -4.836 -4.813 -4.791 -4.768 -4.746 -4.723 -4.700 + +-4.678 -4.655 -4.632 -4.609 -4.586 -4.563 -4.540 -4.517 -4.494 -4.471 +-4.448 -4.425 -4.402 -4.379 -4.355 -4.332 -4.309 -4.285 -4.262 -4.238 +-4.215 -4.191 -4.168 -4.144 -4.120 -4.097 -4.073 -4.049 -4.025 -4.001 +-3.978 -3.954 -3.930 -3.906 -3.882 -3.858 -3.833 -3.809 -3.785 -3.761 +-3.737 -3.712 -3.688 -3.664 -3.639 -3.615 -3.590 -3.566 -3.541 -3.517 + +-3.492 -3.468 -3.443 -3.418 -3.394 -3.369 -3.344 -3.319 -3.294 -3.270 +-3.245 -3.220 -3.195 -3.170 -3.145 -3.120 -3.094 -3.069 -3.044 -3.019 +-2.994 -2.968 -2.943 -2.918 -2.892 -2.867 -2.842 -2.816 -2.791 -2.765 +-2.740 -2.714 -2.689 -2.663 -2.637 -2.612 -2.586 -2.560 -2.534 -2.509 +-2.483 -2.457 -2.431 -2.405 -2.379 -2.353 -2.327 -2.301 -2.275 -2.249 + +-2.223 -2.197 -2.171 -2.144 -2.118 -2.092 -2.066 -2.039 -2.013 -1.987 +-1.960 -1.934 -1.908 -1.881 -1.855 -1.828 -1.802 -1.775 -1.748 -1.722 +-1.695 -1.669 -1.642 -1.615 -1.589 -1.562 -1.535 -1.508 -1.481 -1.455 +-1.428 -1.401 -1.374 -1.347 -1.320 -1.293 -1.266 -1.239 -1.212 -1.185 +-1.158 -1.131 -1.103 -1.076 -1.049 -1.022 -0.995 -0.967 -0.940 -0.913 + +-0.885 -0.858 -0.831 -0.803 -0.776 -0.748 -0.721 -0.694 -0.666 -0.639 +-0.611 -0.583 -0.556 -0.528 -0.501 -0.473 -0.445 -0.418 -0.390 -0.362 +-0.334 -0.307 -0.279 -0.251 -0.223 -0.195 -0.168 -0.140 -0.112 -0.084 +-0.056 -0.028 0.000 0.028 0.056 0.084 0.112 0.140 0.168 0.196 +0.224 0.253 0.281 0.309 0.337 0.365 0.394 0.422 0.450 0.478 + +0.507 0.535 0.563 0.592 0.620 0.648 0.677 0.705 0.734 0.762 +0.791 0.819 0.848 0.876 0.905 0.933 0.962 0.990 1.019 1.048 +1.076 1.105 1.134 1.162 1.191 1.220 1.248 1.277 1.306 1.335 +1.363 1.392 1.421 1.450 1.479 1.507 1.536 1.565 1.594 1.623 +1.652 1.681 1.710 1.739 1.768 1.797 1.826 1.855 1.884 1.913 + +1.942 1.971 2.000 2.029 2.058 2.088 2.117 2.146 2.175 2.204 +2.233 2.263 2.292 2.321 2.350 2.380 2.409 2.438 2.467 2.497 +2.526 2.555 2.585 2.614 2.644 2.673 2.702 2.732 2.761 2.791 +2.820 2.849 2.879 2.908 2.938 2.967 2.997 3.026 3.056 3.085 +3.115 3.145 3.174 3.204 3.233 3.263 3.293 3.322 3.352 3.381 + +3.411 3.441 3.470 3.500 3.530 3.560 3.589 3.619 3.649 3.678 +3.708 3.738 3.768 3.798 3.827 3.857 3.887 3.917 3.947 3.976 +4.006 4.036 4.066 4.096 4.126 4.156 4.186 4.216 4.245 4.275 +4.305 4.335 4.365 4.395 4.425 4.455 4.485 4.515 4.545 4.575 +4.605 4.635 4.665 4.695 4.725 4.755 4.786 4.816 4.846 4.876 + +4.906 4.936 4.966 4.996 5.026 5.057 5.087 5.117 5.147 5.177 +5.207 5.238 5.268 5.298 5.328 5.358 5.389 5.419 5.449 5.479 +5.509 5.540 5.570 5.600 5.630 5.661 5.691 5.721 5.752 5.782 +5.812 5.843 5.873 5.903 5.934 5.964 5.994 6.025 6.055 6.085 +6.116 6.146 6.176 6.207 6.237 6.268 6.298 6.328 6.359 6.389 + +6.420 6.450 6.481 6.511 6.541 6.572 6.602 6.633 6.663 6.694 +6.724 6.755 6.785 6.816 6.846 6.877 6.907 6.938 6.968 6.999 +7.029 7.060 7.090 7.121 7.151 7.182 7.212 7.243 7.274 7.304 +7.335 7.365 7.396 7.426 7.457 7.488 7.518 7.549 7.579 7.610 +7.641 7.671 7.702 7.732 7.763 7.794 7.824 7.855 7.885 7.916 + +7.947 7.977 8.008 8.039 8.069 8.100 8.131 8.161 8.192 8.223 +8.253 8.284 8.315 8.345 8.376 8.407 8.437 8.468 8.499 8.530 +8.560 8.591 8.622 8.652 8.683 8.714 8.745 8.775 8.806 8.837 +8.867 8.898 8.929 8.960 8.990 9.021 9.052 9.083 9.113 9.144 +9.175 9.206 9.236 9.267 9.298 9.329 9.359 9.390 9.421 9.452 + +9.483 9.513 9.544 9.575 9.606 9.636 9.667 9.698 9.729 9.760 +9.790 9.821 9.852 9.883 9.914 9.944 9.975 10.006 10.037 10.068 +10.098 10.129 10.160 10.191 10.222 10.252 10.283 10.314 10.345 10.376 +10.407 10.437 10.468 10.499 10.530 10.561 10.592 10.622 10.653 10.684 +10.715 10.746 10.777 10.807 10.838 10.869 10.900 10.931 10.962 10.992 + +11.023 11.054 11.085 11.116 11.147 11.177 11.208 11.239 11.270 11.301 +11.332 11.363 11.393 11.424 11.455 11.486 11.517 11.548 11.578 11.609 +11.640 11.671 11.702 11.733 11.764 11.794 11.825 11.856 11.887 11.918 +11.949 11.980 12.010 12.041 12.072 12.103 12.134 12.165 12.196 12.226 +12.257 12.288 12.319 12.350 12.381 12.411 12.442 12.473 12.504 12.535 + +12.566 12.597 12.627 12.658 12.689 12.720 12.751 12.782 12.813 12.843 +12.874 12.905 12.936 12.967 12.998 13.029 13.059 13.090 13.121 13.152 +13.183 13.214 13.244 13.275 13.306 13.337 13.368 13.399 13.430 13.460 +13.491 13.522 13.553 13.584 13.615 13.645 13.676 13.707 13.738 13.769 +13.800 13.830 13.861 13.892 13.923 13.954 13.985 14.015 14.046 14.077 + +14.108 14.139 14.170 14.200 14.231 14.262 14.293 14.324 14.355 14.385 +14.416 14.447 14.478 14.509 14.539 14.570 14.601 14.632 14.663 14.694 +14.724 14.755 14.786 14.817 14.848 14.878 14.909 14.940 14.971 15.002 +15.032 15.063 15.094 15.125 15.156 15.186 15.217 15.248 15.279 15.310 +15.340 15.371 15.402 15.433 15.464 15.494 15.525 15.556 15.587 15.617 + +15.648 15.679 15.710 15.741 15.771 15.802 15.833 15.864 15.894 15.925 +15.956 15.987 16.018 16.048 16.079 16.110 16.141 16.171 16.202 16.233 +16.264 16.294 16.325 16.356 16.387 16.417 16.448 16.479 16.510 16.540 +16.571 16.602 16.633 16.663 16.694 16.725 16.756 16.786 16.817 16.848 +16.879 16.909 16.940 16.971 17.001 17.032 17.063 17.094 17.124 17.155 + +17.186 17.217 17.247 17.278 17.309 17.339 17.370 17.401 17.432 17.462 +17.493 17.524 17.554 17.585 17.616 17.646 17.677 17.708 17.739 17.769 +17.800 17.831 17.861 17.892 17.923 17.953 17.984 18.015 18.046 18.076 +18.107 18.138 18.168 18.199 18.230 18.260 18.291 18.322 18.352 18.383 +18.414 18.444 18.475 18.506 18.537 18.567 18.598 18.629 18.659 18.690 + +18.721 18.751 18.782 18.813 18.843 18.874 18.905 18.935 18.966 18.997 +19.027 19.058 19.089 19.119 19.150 19.180 19.211 19.242 19.272 19.303 +19.334 19.364 19.395 19.426 19.456 19.487 19.518 19.548 19.579 19.610 +19.640 19.671 19.702 19.732 19.763 19.793 19.824 19.855 19.885 19.916 +19.947 19.977 20.008 20.039 20.069 20.100 20.131 20.161 20.192 20.222 + +20.253 20.284 20.314 20.345 20.376 20.406 20.437 20.467 20.498 20.529 +20.559 20.590 20.621 20.651 20.682 20.713 20.743 20.774 20.804 20.835 +20.866 20.896 20.927 20.958 20.988 21.019 21.049 21.080 21.111 21.141 +21.172 21.203 21.233 21.264 21.295 21.325 21.356 21.386 21.417 21.448 +21.478 21.509 21.540 21.570 21.601 21.631 21.662 21.693 21.723 21.754 + +21.785 21.815 21.846 21.877 21.907 21.938 21.968 21.999 22.030 22.060 +22.091 22.122 22.152 22.183 22.214 22.244 22.275 22.305 22.336 22.367 +22.397 22.428 22.459 22.489 22.520 22.551 22.581 22.612 22.643 22.673 +22.704 22.735 22.765 22.796 22.826 22.857 22.888 22.918 22.949 22.980 +23.010 23.041 23.072 23.102 23.133 23.164 23.194 23.225 23.256 23.286 + +23.317 23.348 23.378 23.409 23.440 23.471 23.501 23.532 23.563 23.593 +23.624 23.655 23.685 23.716 23.747 23.777 23.808 23.839 23.870 23.900 +23.931 23.962 23.992 24.023 24.054 24.085 24.115 24.146 24.177 24.207 +24.238 24.269 24.300 24.330 24.361 24.392 24.423 24.453 24.484 24.515 +24.546 24.576 24.607 24.638 24.669 24.699 24.730 24.761 24.792 24.822 + +24.853 24.854 24.915 24.946 24.976 25.007 25.038 25.069 25.099 25.130 +25.161 25.192 25.223 25.254 25.284 25.315 25.346 25.377 25.408 25.438 +25.469 25.500 25.531 25.562 25.593 25.623 25.654 25.685 25.716 25.747 +25.778 25.809 25.840 25.870 25.901 25.932 25.963 25.994 26.025 26.056 +26.087 26.118 26.148 26.179 26.210 26.241 26.272 26.303 26.334 26.365 + +26.396 26.427 26.458 26.489 26.520 26.551 26.582 26.613 26.644 26.675 +26.705 26.736 26.767 26.798 26.829 26.860 26.891 26.922 26.954 26.985 +27.016 27.047 27.078 27.109 27.140 27.171 27.202 27.233 27.264 27.295 +27.326 27.357 27.388 27.419 27.450 27.482 27.513 27.544 27.575 27.606 +27.637 27.668 27.699 27.731 27.762 27.793 27.824 27.855 27.886 27.917 + +27.949 27.980 28.011 28.042 28.073 28.105 28.136 28.167 28.198 28.230 +28.261 28.292 28.323 28.355 28.386 28.417 28.448 28.480 28.511 28.542 +28.573 28.605 28.636 28.667 28.699 28.730 28.761 28.793 28.824 28.855 +28.887 28.918 28.950 28.981 29.012 29.044 29.075 29.107 29.138 29.169 +29.201 29.232 29.264 29.295 29.327 29.358 29.390 29.421 29.452 29.484 + +29.515 29.547 29.578 29.610 29.642 29.673 29.705 29.736 29.768 29.799 +29.831 29.862 29.894 29.926 29.957 29.989 30.020 30.052 30.084 30.115 +30.147 30.179 30.210 30.242 30.274 30.305 30.337 30.369 30.400 30.432 +30.464 30.496 30.527 30.559 30.591 30.623 30.654 30.686 30.718 30.750 +30.782 30.813 30.845 30.877 30.909 30.941 30.973 31.005 31.036 31.068 + +31.100 31.132 31.164 31.196 31.228 31.260 31.292 31.324 31.356 31.388 +31.420 31.452 31.484 31.516 31.548 31.580 31.612 31.644 31.676 31.708 +31.740 31.772 31.804 31.836 31.868 31.901 31.933 31.965 31.997 32.029 +32.061 32.094 32.126 32.158 32.190 32.222 32.255 32.287 32.319 32.351 +32.384 32.416 32.448 32.480 32.513 32.545 32.577 32.610 32.642 32.674 + +32.707 32.739 32.772 32.804 32.836 32.869 32.901 32.934 32.966 32.999 +33.031 33.064 33.096 33.129 33.161 33.194 33.226 33.259 33.291 33.324 +33.356 33.389 33.422 33.454 33.487 33.519 33.552 33.585 33.617 33.650 +33.683 33.715 33.748 33.781 33.814 33.846 33.879 33.912 33.945 33.977 +34.010 34.043 34.076 34.109 34.141 34.174 34.207 34.240 34.273 34.306 + +34.339 34.372 34.405 34.437 34.470 34.503 34.536 34.569 34.602 34.635 +34.668 34.701 34.734 34.767 34.801 34.834 34.867 34.900 34.933 34.966 +34.999 35.032 35.065 35.099 35.132 35.165 35.198 35.231 35.265 35.298 +35.331 35.364 35.398 35.431 35.464 35.498 35.531 35.564 35.598 35.631 +35.664 35.698 35.731 35.764 35.798 35.831 35.865 35.898 35.932 35.965 + +35.999 36.032 36.066 36.099 36.133 36.166 36.200 36.233 36.267 36.301 +36.334 36.368 36.401 36.435 36.469 36.502 36.536 36.570 36.603 36.637 +36.671 36.705 36.738 36.772 36.806 36.840 36.873 36.907 36.941 36.975 +37.009 37.043 37.076 37.110 37.144 37.178 37.212 37.246 37.280 37.314 +37.348 37.382 37.416 37.450 37.484 37.518 37.552 37.586 37.620 37.654 + +37.688 37.722 37.756 37.790 37.825 37.859 37.893 37.927 37.961 37.995 +38.030 38.064 38.098 38.132 38.167 38.201 38.235 38.269 38.304 38.338 +38.372 38.407 38.441 38.475 38.510 38.544 38.578 38.613 38.647 38.682 +38.716 38.751 38.785 38.819 38.854 38.888 38.923 38.957 38.992 39.027 +39.061 39.096 39.130 39.165 39.199 39.234 39.269 39.303 39.338 39.373 + +39.407 39.442 39.477 39.511 39.546 39.581 39.615 39.650 39.685 39.720 +39.754 39.789 39.824 39.859 39.894 39.928 39.963 39.998 40.033 40.068 +40.103 40.138 40.172 40.207 40.242 40.277 40.312 40.347 40.382 40.417 +40.452 40.487 40.522 40.557 40.592 40.627 40.662 40.697 40.732 40.767 +40.802 40.837 40.872 40.908 40.943 40.978 41.013 41.048 41.083 41.118 + +41.154 41.189 41.224 41.259 41.294 41.329 41.365 41.400 41.435 41.470 +41.506 41.541 41.576 41.611 41.647 41.682 41.717 41.753 41.788 41.823 +41.859 41.894 41.929 41.965 42.000 42.035 42.071 42.106 42.142 42.177 +42.212 42.248 42.283 42.319 42.354 42.390 42.425 42.460 42.496 42.531 +42.567 42.602 42.638 42.673 42.709 42.744 42.780 42.815 42.851 42.886 +42.922 diff --git a/modules/database/src/ioc/bpt/bptTypeKdegC.data b/modules/database/src/ioc/bpt/bptTypeKdegC.data new file mode 100644 index 000000000..4136fa9b5 --- /dev/null +++ b/modules/database/src/ioc/bpt/bptTypeKdegC.data @@ -0,0 +1,201 @@ +! cvtTypeKdegC.data +"typeKdegC" 0 0 1000 4095 .5 -270 1372 1 +! +-6.458 -6.457 -6.456 -6.455 -6.453 -6.452 -6.450 -6.448 -6.446 -6.444 +-6.441 -6.438 -6.435 -6.432 -6.429 -6.425 -6.421 -6.417 -6.413 -6.408 + +-6.404 -6.399 -6.394 -6.388 -6.382 -6.377 -6.371 -6.364 -6.358 -6.351 +-6.344 -6.337 -6.329 -6.322 -6.314 -6.306 -6.297 -6.289 -6.280 -6.271 +-6.262 -6.253 -6.243 -6.233 -6.223 -6.213 -6.202 -6.192 -6.181 -6.170 +-6.158 -6.147 -6.135 -6.123 -6.111 -6.099 -6.087 -6.074 -6.061 -6.048 +-6.035 -6.021 -6.007 -5.994 -5.980 -5.965 -5.951 -5.936 -5.922 -5.907 + +-5.891 -5.876 -5.860 -5.845 -5.829 -5.813 -5.796 -5.780 -5.763 -5.747 +-5.730 -5.712 -5.695 -5.678 -5.660 -5.642 -5.624 -5.606 -5.587 -5.569 +-5.550 -5.531 -5.512 -5.493 -5.474 -5.454 -5.434 -5.414 -5.394 -5.374 +-5.354 -5.333 -5.313 -5.292 -5.271 -5.249 -5.228 -5.207 -5.185 -5.163 +-5.141 -5.119 -5.097 -5.074 -5.051 -5.029 -5.006 -4.983 -4.959 -4.936 + +-4.912 -4.889 -4.865 -4.841 -4.817 -4.792 -4.768 -4.743 -4.719 -4.694 +-4.669 -4.644 -4.618 -4.593 -4.567 -4.541 -4.515 -4.489 -4.463 -4.437 +-4.410 -4.384 -4.357 -4.330 -4.303 -4.276 -4.248 -4.221 -4.193 -4.166 +-4.138 -4.110 -4.082 -4.053 -4.025 -3.997 -3.968 -3.939 -3.910 -3.881 +-3.852 -3.823 -3.793 -3.764 -3.734 -3.704 -3.674 -3.644 -3.614 -3.584 + +-3.553 -3.523 -3.492 -3.461 -3.430 -3.399 -3.368 -3.337 -3.305 -3.274 +-3.242 -3.211 -3.179 -3.147 -3.115 -3.082 -3.050 -3.018 -2.985 -2.953 +-2.920 -2.887 -2.854 -2.821 -2.788 -2.754 -2.721 -2.687 -2.654 -2.620 +-2.586 -2.552 -2.518 -2.484 -2.450 -2.416 -2.381 -2.347 -2.312 -2.277 +-2.243 -2.208 -2.173 -2.137 -2.102 -2.067 -2.032 -1.996 -1.961 -1.925 + +-1.889 -1.853 -1.817 -1.781 -1.745 -1.709 -1.673 -1.636 -1.600 -1.563 +-1.527 -1.490 -1.453 -1.416 -1.379 -1.342 -1.305 -1.268 -1.231 -1.193 +-1.156 -1.118 -1.081 -1.043 -1.005 -0.968 -0.930 -0.892 -0.854 -0.816 +-0.777 -0.739 -0.701 -0.662 -0.624 -0.585 -0.547 -0.508 -0.469 -0.431 +-0.392 -0.353 -0.314 -0.275 -0.236 -0.197 -0.157 -0.118 -0.079 -0.039 + +0.000 0.039 0.079 0.119 0.158 0.198 0.238 0.277 0.317 0.357 +0.397 0.437 0.477 0.517 0.557 0.597 0.637 0.677 0.718 0.758 +0.798 0.838 0.879 0.919 0.960 1.000 1.041 1.081 1.122 1.162 +1.203 1.244 1.285 1.325 1.366 1.407 1.448 1.489 1.529 1.570 +1.611 1.652 1.693 1.734 1.776 1.817 1.858 1.899 1.940 1.981 + +2.022 2.064 2.105 2.146 2.188 2.229 2.270 2.312 2.353 2.394 +2.436 2.477 2.519 2.560 2.601 2.643 2.684 2.726 2.767 2.809 +2.850 2.892 2.933 2.975 3.016 3.058 3.100 3.141 3.183 3.224 +3.266 3.307 3.349 3.390 3.432 3.473 3.515 3.556 3.598 3.639 +3.681 3.722 3.764 3.805 3.847 3.888 3.930 3.971 4.012 4.054 + +4.095 4.137 4.178 4.219 4.261 4.302 4.343 4.384 4.426 4.467 +4.508 4.549 4.590 4.632 4.673 4.714 4.755 4.796 4.837 4.878 +4.919 4.960 5.001 5.042 5.083 5.124 5.164 5.205 5.246 5.287 +5.327 5.368 5.409 5.450 5.490 5.531 5.571 5.612 5.652 5.693 +5.733 5.774 5.814 5.855 5.895 5.936 5.976 6.016 6.057 6.097 + +6.137 6.177 6.218 6.258 6.298 6.338 6.378 6.419 6.459 6.499 +6.539 6.579 6.619 6.659 6.699 6.739 6.779 6.819 6.859 6.899 +6.939 6.979 7.019 7.059 7.099 7.139 7.179 7.219 7.259 7.299 +7.338 7.378 7.418 7.458 7.498 7.538 7.578 7.618 7.658 7.697 +7.737 7.777 7.817 7.857 7.897 7.937 7.977 8.017 8.057 8.097 + +8.137 8.177 8.216 8.256 8.296 8.336 8.376 8.416 8.456 8.497 +8.537 8.577 8.617 8.657 8.697 8.737 8.777 8.817 8.857 8.898 +8.938 8.978 9.018 9.058 9.099 9.139 9.179 9.220 9.260 9.300 +9.341 9.381 9.421 9.462 9.502 9.543 9.583 9.624 9.664 9.705 +9.745 9.786 9.826 9.867 9.907 9.948 9.989 10.029 10.070 10.111 + +10.151 10.192 10.233 10.274 10.315 10.355 10.396 10.437 10.478 10.519 +10.560 10.600 10.641 10.682 10.723 10.764 10.805 10.846 10.887 10.928 +10.969 11.010 11.051 11.093 11.134 11.175 11.216 11.257 11.298 11.339 +11.381 11.422 11.463 11.504 11.546 11.587 11.628 11.669 11.711 11.752 +11.793 11.835 11.876 11.918 11.959 12.000 12.042 12.083 12.125 12.166 + +12.207 12.249 12.290 12.332 12.373 12.415 12.456 12.498 12.539 12.581 +12.623 12.664 12.706 12.747 12.789 12.831 12.872 12.914 12.955 12.997 +13.039 13.080 13.122 13.164 13.205 13.247 13.289 13.331 13.372 13.414 +13.456 13.497 13.539 13.581 13.623 13.665 13.706 13.748 13.790 13.832 +13.874 13.915 13.957 13.999 14.041 14.083 14.125 14.167 14.208 14.250 + +14.292 14.334 14.376 14.418 14.460 14.502 14.544 14.586 14.628 14.670 +14.712 14.754 14.796 14.838 14.880 14.922 14.964 15.006 15.048 15.090 +15.132 15.174 15.216 15.258 15.300 15.342 15.384 15.426 15.468 15.510 +15.552 15.594 15.636 15.679 15.721 15.763 15.805 15.847 15.889 15.931 +15.974 16.016 16.058 16.100 16.142 16.184 16.227 16.269 16.311 16.353 + +16.395 16.438 16.480 16.522 16.564 16.607 16.649 16.691 16.733 16.776 +16.818 16.860 16.902 16.945 16.987 17.029 17.072 17.114 17.156 17.199 +17.241 17.283 17.326 17.368 17.410 17.453 17.495 17.537 17.580 17.622 +17.664 17.707 17.749 17.792 17.834 17.876 17.919 17.961 18.004 18.046 +18.088 18.131 18.173 18.216 18.258 18.301 18.343 18.385 18.428 18.470 + +18.513 18.555 18.598 18.640 18.683 18.725 18.768 18.810 18.853 18.895 +18.938 18.980 19.023 19.065 19.108 19.150 19.193 19.235 19.278 19.320 +19.363 19.405 19.448 19.490 19.533 19.576 19.618 19.661 19.703 19.746 +19.788 19.831 19.873 19.916 19.959 20.001 20.044 20.086 20.129 20.172 +20.214 20.257 20.299 20.342 20.385 20.427 20.470 20.512 20.555 20.598 + +20.640 20.683 20.725 20.768 20.811 20.853 20.896 20.938 20.981 21.024 +21.066 21.109 21.152 21.194 21.237 21.280 21.322 21.365 21.407 21.450 +21.493 21.535 21.578 21.621 21.663 21.706 21.749 21.791 21.834 21.876 +21.919 21.962 22.004 22.047 22.090 22.132 22.175 22.218 22.260 22.303 +22.346 22.388 22.431 22.473 22.516 22.559 22.601 22.644 22.687 22.729 + +22.772 22.815 22.857 22.900 22.942 22.985 23.028 23.070 23.113 23.156 +23.198 23.241 23.284 23.326 23.369 23.411 23.454 23.497 23.539 23.582 +23.624 23.667 23.710 23.752 23.795 23.837 23.880 23.923 23.965 24.008 +24.050 24.093 24.136 24.178 24.221 24.263 24.306 24.348 24.391 24.434 +24.476 24.519 24.561 24.604 24.646 24.689 24.731 24.774 24.817 24.859 + +24.902 24.944 24.987 25.029 25.072 25.114 25.157 25.199 25.242 25.284 +25.327 25.369 25.412 25.454 25.497 25.539 25.582 25.624 25.666 25.709 +25.751 25.794 25.836 25.879 25.921 25.964 26.006 26.048 26.091 26.133 +26.176 26.218 26.260 26.303 26.345 26.387 26.430 26.472 26.515 26.557 +26.599 26.642 26.684 26.726 26.769 26.811 26.853 26.896 26.938 26.980 + +27.022 27.065 27.107 27.149 27.192 27.234 27.276 27.318 27.361 27.403 +27.445 27.487 27.529 27.572 27.614 27.656 27.698 27.740 27.783 27.825 +27.867 27.909 27.951 27.993 28.035 28.078 28.120 28.162 28.204 28.246 +28.288 28.330 28.372 28.414 28.456 28.498 28.540 28.583 28.625 28.667 +28.709 28.751 28.793 28.835 28.877 28.919 28.961 29.002 29.044 29.086 + +29.128 29.170 29.212 29.254 29.296 29.338 29.380 29.422 29.464 29.505 +29.547 29.589 29.631 29.673 29.715 29.756 29.798 29.840 29.882 29.924 +29.965 30.007 30.049 30.091 30.132 30.174 30.216 30.257 30.299 30.341 +30.383 30.424 30.466 30.508 30.549 30.591 30.632 30.674 30.716 30.757 +30.799 30.840 30.882 30.924 30.965 31.007 31.048 31.090 31.131 31.173 + +31.214 31.256 31.297 31.339 31.380 31.422 31.463 31.504 31.546 31.587 +31.629 31.670 31.712 31.753 31.794 31.836 31.877 31.918 31.960 32.001 +32.042 32.084 32.125 32.166 32.207 32.249 32.290 32.331 32.372 32.414 +32.455 32.496 32.537 32.578 32.619 32.661 32.702 32.743 32.784 32.825 +32.866 32.907 32.948 32.990 33.031 33.072 33.113 33.154 33.195 33.236 + +33.277 33.318 33.359 33.400 33.441 33.482 33.523 33.564 33.604 33.645 +33.686 33.727 33.768 33.809 33.850 33.891 33.931 33.972 34.013 34.054 +34.095 34.136 34.176 34.217 34.258 34.299 34.339 34.380 34.421 34.461 +34.502 34.543 34.583 34.624 34.665 34.705 34.746 34.787 34.827 34.868 +34.909 34.949 34.990 35.030 35.071 35.111 35.152 35.192 35.233 35.273 + +35.314 35.354 35.395 35.435 35.476 35.516 35.557 35.597 35.637 35.678 +35.718 35.758 35.799 35.839 35.880 35.920 35.960 36.000 36.041 36.081 +36.121 36.162 36.202 36.242 36.282 36.323 36.363 36.403 36.443 36.483 +36.524 36.564 36.604 36.644 36.684 36.724 36.764 36.804 36.844 36.885 +36.925 36.965 37.005 37.045 37.085 37.125 37.165 37.205 37.245 37.285 + +37.325 37.365 37.405 37.445 37.484 37.524 37.564 37.604 37.644 37.684 +37.724 37.764 37.803 37.843 37.883 37.923 37.963 38.002 38.042 38.082 +38.122 38.162 38.201 38.241 38.281 38.320 38.360 38.400 38.439 38.479 +38.519 38.558 38.598 38.638 38.677 38.717 38.756 38.796 38.836 38.875 +38.915 38.954 38.994 39.033 39.073 39.112 39.152 39.191 39.231 39.270 + +39.310 39.349 39.388 39.428 39.467 39.507 39.546 39.585 39.625 39.644 +39.703 39.743 39.782 39.821 39.861 39.900 39.939 39.979 40.018 40.057 +40.096 40.136 40.175 40.214 40.253 40.292 40.332 40.371 40.410 40.449 +40.488 40.527 40.566 40.605 40.645 40.684 40.723 40.762 40.801 40.840 +40.879 40.918 40.957 40.996 41.035 41.074 41.113 41.152 41.191 41.230 + +41.269 41.308 41.347 41.385 41.424 41.463 41.502 41.541 41.580 41.619 +41.657 41.696 41.735 41.774 41.813 41.851 41.890 41.929 41.968 42.006 +42.045 42.084 42.123 42.161 42.200 42.239 42.277 42.316 42.355 42.393 +42.432 42.470 42.509 42.548 42.586 42.625 42.663 42.702 42.740 42.779 +42.817 42.856 42.894 42.933 42.971 43.010 43.048 43.087 43.125 43.164 + +43.202 43.240 43.279 43.317 43.356 43.394 43.432 43.471 43.509 43.547 +43.585 43.624 43.662 43.700 43.739 43.777 43.815 43.853 43.891 43.930 +43.968 44.006 44.044 44.082 44.121 44.159 44.197 44.235 44.273 44.311 +44.349 44.387 44.425 44.463 44.501 44.539 44.577 44.615 44.653 44.691 +44.729 44.767 44.805 44.843 44.881 44.919 44.957 44.995 45.033 45.070 + +45.108 45.146 45.184 45.222 45.260 45.297 45.335 45.373 45.411 45.448 +45.486 45.524 45.561 45.599 45.637 45.675 45.712 45.750 45.787 45.825 +45.863 45.900 45.938 45.975 46.013 46.051 46.088 46.126 46.163 46.201 +46.238 46.275 46.313 46.350 46.388 46.425 46.463 46.500 46.537 46.575 +46.612 46.649 46.687 46.724 46.761 46.799 46.836 46.873 46.910 46.948 + +46.985 47.022 47.059 47.096 47.134 47.171 47.208 47.245 47.282 47.319 +47.356 47.393 47.430 47.468 47.505 47.542 47.579 47.616 47.653 47.689 +47.726 47.763 47.800 47.837 47.874 47.911 47.948 47.985 48.021 48.058 +48.095 48.132 48.169 48.205 48.242 48.279 48.316 48.352 48.389 48.426 +48.462 48.499 48.536 48.572 48.609 48.645 48.682 48.718 48.755 48.792 + +48.828 48.865 48.901 48.937 48.974 49.010 49.047 49.083 49.120 49.156 +49.192 49.229 49.265 49.301 49.338 49.374 49.410 49.446 49.483 49.519 +49.555 49.591 49.627 49.663 49.700 49.736 49.772 49.808 49.844 49.880 +49.916 49.952 49.988 50.024 50.060 50.096 50.132 50.168 50.204 50.240 +50.276 50.311 50.347 50.383 50.419 50.455 50.491 50.526 50.562 50.598 + +50.633 50.669 50.705 50.741 50.776 50.812 50.847 50.883 50.919 50.954 +50.990 51.025 51.061 51.096 51.132 51.167 51.203 51.238 51.274 51.309 +51.344 51.380 51.415 51.450 51.486 51.521 51.556 51.592 51.627 51.662 +51.697 51.733 51.768 51.803 51.838 51.873 51.908 51.943 51.979 52.014 +52.049 52.084 52.119 52.154 52.189 52.224 52.259 52.294 52.329 52.364 + +52.398 52.433 52.468 52.503 52.538 52.573 52.608 52.642 52.677 52.712 +52.747 52.781 52.816 52.851 52.886 52.920 52.955 52.989 53.024 53.059 +53.093 53.128 53.162 53.197 53.232 53.266 53.301 53.335 53.370 53.404 +53.439 53.473 53.507 53.542 53.576 53.611 53.645 53.679 53.714 53.748 +53.782 53.817 53.851 53.885 53.920 53.954 53.988 54.022 54.057 54.091 + +54.125 54.159 54.193 54.228 54.262 54.296 54.330 54.364 54.398 54.432 +54.466 54.501 54.535 54.569 54.603 54.637 54.671 54.705 54.739 54.773 +54.807 54.841 54.875 diff --git a/modules/database/src/ioc/bpt/bptTypeKdegF.data b/modules/database/src/ioc/bpt/bptTypeKdegF.data new file mode 100644 index 000000000..20210cc5a --- /dev/null +++ b/modules/database/src/ioc/bpt/bptTypeKdegF.data @@ -0,0 +1,360 @@ +! cvtTypeKdegF.data +"typeKdegF" 32 0 1832 4095 1.0 -454 2500 1 +! + -6.458 -6.457 -6.457 -6.456 + +-6.456 -6.455 -6.454 -6.454 -6.453 -6.452 -6.451 -6.450 -6.449 -6.448 +-6.447 -6.445 -6.444 -6.443 -6.441 -6.440 -6.438 -6.436 -6.435 -6.433 +-6.431 -6.429 -6.427 -6.425 -6.423 -6.421 -6.419 -6.416 -6.414 -6.411 +-6.409 -6.406 -6.404 -6.401 -6.398 -6.395 -6.392 -6.389 -6.386 -6.383 +-6.380 -6.377 -6.373 -6.370 -6.366 -6.363 -6.359 -6.355 -6.352 -6.348 + +-6.344 -6.340 -6.336 -6.332 -6.328 -6.323 -6.319 -6.315 -6.310 -6.306 +-6.301 -6.296 -6.292 -6.287 -6.282 -6.277 -6.272 -6.267 -6.262 -6.257 +-6.251 -6.246 -6.241 -6.235 -6.230 -6.224 -6.219 -6.213 -6.207 -6.201 +-6.195 -6.189 -6.183 -6.177 -6.171 -6.165 -6.158 -6.152 -6.146 -6.139 +-6.133 -6.126 -6.119 -6.113 -6.106 -6.099 -6.092 -6.085 -6.078 -6.071 + +-6.064 -6.057 -6.049 -6.042 -6.035 -6.027 -6.020 -6.012 -6.004 -5.997 +-5.989 -5.981 -5.973 -5.965 -5.957 -5.949 -5.941 -5.933 -5.925 -5.917 +-5.908 -5.900 -5.891 -5.883 -5.874 -5.866 -5.857 -5.848 -5.839 -5.831 +-5.822 -5.813 -5.804 -5.795 -5.786 -5.776 -5.767 -5.758 -5.748 -5.739 +-5.730 -5.720 -5.711 -5.701 -5.691 -5.682 -5.672 -5.662 -5.652 -5.642 + +-5.632 -5.622 -5.612 -5.602 -5.592 -5.581 -5.571 -5.561 -5.550 -5.540 +-5.529 -5.519 -5.508 -5.497 -5.487 -5.476 -5.465 -5.454 -5.443 -5.432 +-5.421 -5.410 -5.399 -5.388 -5.376 -5.365 -5.354 -5.342 -5.331 -5.319 +-5.308 -5.296 -5.285 -5.273 -5.261 -5.249 -5.238 -5.226 -5.214 -5.202 +-5.190 -5.178 -5.165 -5.153 -5.141 -5.129 -5.116 -5.104 -5.092 -5.079 + +-5.067 -5.054 -5.041 -5.029 -5.016 -5.003 -4.990 -4.978 -4.965 -4.952 +-4.939 -4.926 -4.912 -4.899 -4.886 -4.873 -4.860 -4.846 -4.833 -4.819 +-4.806 -4.792 -4.779 -4.765 -4.752 -4.738 -4.724 -4.710 -4.697 -4.683 +-4.669 -4.655 -4.641 -4.627 -4.613 -4.598 -4.584 -4.570 -4.556 -4.541 +-4.527 -4.512 -4.498 -4.484 -4.469 -4.454 -4.440 -4.425 -4.410 -4.396 + +-4.381 -4.366 -4.351 -4.336 -4.321 -4.306 -4.291 -4.276 -4.261 -4.245 +-4.230 -4.215 -4.200 -4.184 -4.169 -4.153 -4.138 -4.122 -4.107 -4.091 +-4.075 -4.060 -4.044 -4.028 -4.012 -3.997 -3.981 -3.965 -3.949 -3.933 +-3.917 -3.901 -3.884 -3.868 -3.852 -3.836 -3.819 -3.803 -3.787 -3.770 +-3.754 -3.737 -3.721 -3.704 -3.688 -3.671 -3.654 -3.637 -3.621 -3.604 + +-3.587 -3.570 -3.553 -3.536 -3.519 -3.502 -3.485 -3.468 -3.451 -3.434 +-3.417 -3.399 -3.382 -3.365 -3.347 -3.330 -3.312 -3.295 -3.277 -3.260 +-3.242 -3.225 -3.207 -3.189 -3.172 -3.154 -3.136 -3.118 -3.100 -3.082 +-3.065 -3.047 -3.029 -3.010 -2.992 -2.974 -2.956 -2.938 -2.920 -2.902 +-2.883 -2.865 -2.847 -2.828 -2.810 -2.791 -2.773 -2.754 -2.736 -2.717 + +-2.699 -2.680 -2.661 -2.643 -2.624 -2.605 -2.586 -2.567 -2.549 -2.530 +-2.511 -2.492 -2.473 -2.454 -2.435 -2.416 -2.397 -2.377 -2.358 -2.339 +-2.320 -2.300 -2.281 -2.262 -2.243 -2.223 -2.204 -2.184 -2.165 -2.145 +-2.126 -2.106 -2.087 -2.067 -2.047 -2.028 -2.008 -1.988 -1.968 -1.949 +-1.929 -1.909 -1.889 -1.869 -1.849 -1.829 -1.809 -1.789 -1.769 -1.749 + +-1.729 -1.709 -1.689 -1.669 -1.648 -1.628 -1.608 -1.588 -1.567 -1.547 +-1.527 -1.506 -1.486 -1.465 -1.445 -1.424 -1.404 -1.383 -1.363 -1.342 +-1.322 -1.301 -1.280 -1.260 -1.239 -1.218 -1.197 -1.177 -1.156 -1.135 +-1.114 -1.093 -1.072 -1.051 -1.031 -1.010 -0.989 -0.968 -0.946 -0.925 +-0.904 -0.883 -0.862 -0.841 -0.820 -0.799 -0.777 -0.756 -0.735 -0.714 + +-0.692 -0.671 -0.650 -0.628 -0.607 -0.585 -0.564 -0.543 -0.521 -0.500 +-0.478 -0.457 -0.435 -0.413 -0.392 -0.370 -0.349 -0.327 -0.305 -0.284 +-0.262 -0.240 -0.218 -0.197 -0.175 -0.153 -0.131 -0.109 -0.088 -0.066 +-0.044 -0.022 0.000 0.022 0.044 0.066 0.088 0.110 0.132 0.154 +0.176 0.198 0.220 0.242 0.264 0.286 0.308 0.331 0.353 0.375 + +0.397 0.419 0.441 0.464 0.486 0.508 0.530 0.553 0.575 0.597 +0.619 0.642 0.664 0.686 0.709 0.731 0.753 0.776 0.798 0.821 +0.843 0.865 0.888 0.910 0.933 0.955 0.978 1.000 1.023 1.045 +1.068 1.090 1.113 1.135 1.158 1.181 1.203 1.226 1.248 1.271 +1.294 1.316 1.339 1.362 1.384 1.407 1.430 1.452 1.475 1.498 + +1.520 1.543 1.566 1.589 1.611 1.634 1.657 1.680 1.703 1.725 +1.748 1.771 1.794 1.817 1.839 1.862 1.885 1.908 1.931 1.954 +1.977 2.000 2.022 2.045 2.068 2.091 2.114 2.137 2.160 2.183 +2.206 2.229 2.252 2.275 2.298 2.321 2.344 2.367 2.390 2.413 +2.436 2.459 2.482 2.505 2.528 2.551 2.574 2.597 2.620 2.643 + +2.666 2.689 2.712 2.735 2.758 2.781 2.804 2.827 2.850 2.873 +2.896 2.920 2.943 2.966 2.989 3.012 3.035 3.058 3.081 3.104 +3.127 3.150 3.173 3.196 3.220 3.243 3.266 3.289 3.312 3.335 +3.358 3.381 3.404 3.427 3.450 3.473 3.496 3.519 3.543 3.566 +3.589 3.612 3.635 3.658 3.681 3.704 3.727 3.750 3.773 3.796 + +3.819 3.842 3.865 3.888 3.911 3.934 3.957 3.980 4.003 4.026 +4.049 4.072 4.095 4.118 4.141 4.164 4.187 4.210 4.233 4.256 +4.279 4.302 4.325 4.348 4.371 4.394 4.417 4.439 4.462 4.485 +4.508 4.531 4.554 4.577 4.600 4.622 4.645 4.668 4.691 4.714 +4.737 4.759 4.782 4.805 4.828 4.851 4.873 4.896 4.919 4.942 + +4.964 4.987 5.010 5.033 5.055 5.078 5.101 5.124 5.146 5.169 +5.192 5.214 5.237 5.260 5.282 5.305 5.327 5.350 5.373 5.395 +5.418 5.440 5.463 5.486 5.508 5.531 5.553 5.576 5.598 5.621 +5.643 5.666 5.688 5.711 5.733 5.756 5.778 5.801 5.823 5.846 +5.868 5.891 5.913 5.936 5.958 5.980 6.003 6.025 6.048 6.070 + +6.092 6.115 6.137 6.160 6.182 6.204 6.227 6.249 6.271 6.294 +6.316 6.338 6.361 6.383 6.405 6.428 6.450 6.472 6.494 6.517 +6.539 6.561 6.583 6.606 6.628 6.650 6.672 6.695 6.717 6.739 +6.761 6.784 6.806 6.828 6.850 6.873 6.895 6.917 6.939 6.961 +6.984 7.006 7.028 7.050 7.072 7.094 7.117 7.139 7.161 7.183 + +7.205 7.228 7.250 7.272 7.294 7.316 7.338 7.361 7.383 7.405 +7.427 7.449 7.471 7.494 7.516 7.538 7.560 7.582 7.604 7.627 +7.649 7.671 7.693 7.715 7.737 7.760 7.782 7.804 7.826 7.848 +7.870 7.893 7.915 7.937 7.959 7.981 8.003 8.026 8.048 8.070 +8.092 8.114 8.137 8.159 8.181 8.203 8.225 8.248 8.270 8.292 + +8.314 8.336 8.359 8.381 8.403 8.425 8.448 8.470 8.492 8.514 +8.537 8.559 8.581 8.603 8.626 8.648 8.670 8.692 8.715 8.737 +8.759 8.782 8.804 8.826 8.849 8.871 8.893 8.916 8.938 8.960 +8.983 9.005 9.027 9.050 9.072 9.094 9.117 9.139 9.161 9.184 +9.206 9.229 9.251 9.273 9.296 9.318 9.341 9.363 9.385 9.408 + +9.430 9.453 9.475 9.498 9.520 9.543 9.565 9.588 9.610 9.633 +9.655 9.678 9.700 9.723 9.745 9.768 9.790 9.813 9.835 9.858 +9.880 9.903 9.926 9.948 9.971 9.993 10.016 10.038 10.061 10.084 +10.106 10.129 10.151 10.174 10.197 10.219 10.242 10.265 10.287 10.310 +10.333 10.355 10.378 10.401 10.423 10.446 10.469 10.491 10.514 10.537 + +10.560 10.582 10.605 10.628 10.650 10.673 10.696 10.719 10.741 10.764 +10.787 10.810 10.833 10.855 10.878 10.901 10.924 10.947 10.969 10.992 +11.015 11.038 11.061 11.083 11.106 11.129 11.152 11.175 11.198 11.221 +11.243 11.266 11.289 11.312 11.335 11.358 11.381 11.404 11.426 11.449 +11.472 11.495 11.518 11.541 11.564 11.587 11.610 11.633 11.656 11.679 + +11.702 11.725 11.748 11.770 11.793 11.816 11.839 11.862 11.885 11.908 +11.931 11.954 11.977 12.000 12.023 12.046 12.069 12.092 12.115 12.138 +12.161 12.184 12.207 12.230 12.254 12.277 12.300 12.323 12.346 12.369 +12.392 12.415 12.438 12.461 12.484 12.507 12.530 12.553 12.576 12.599 +12.623 12.646 12.669 12.692 12.715 12.738 12.761 12.784 12.807 12.831 + +12.854 12.877 12.900 12.923 12.946 12.969 12.992 13.016 13.039 13.062 +13.085 13.108 13.131 13.154 13.178 13.201 13.224 13.247 13.270 13.293 +13.317 13.340 13.363 13.386 13.409 13.433 13.456 13.479 13.502 13.525 +13.549 13.572 13.595 13.618 13.641 13.665 13.688 13.711 13.734 13.757 +13.781 13.804 13.827 13.850 13.874 13.897 13.920 13.943 13.967 13.990 + +14.013 14.036 14.060 14.083 14.106 14.129 14.153 14.176 14.199 14.222 +14.246 14.269 14.292 14.316 14.339 14.362 14.385 14.409 14.432 14.455 +14.479 14.502 14.525 14.548 14.572 14.595 14.618 14.642 14.665 14.688 +14.712 14.735 14.758 14.782 14.805 14.828 14.852 14.875 14.898 14.922 +14.945 14.968 14.992 15.015 15.038 15.062 15.085 15.108 15.132 15.155 + +15.178 15.202 15.225 15.248 15.272 15.295 15.318 15.342 15.365 15.389 +15.412 15.435 15.459 15.482 15.505 15.529 15.552 15.576 15.599 15.622 +15.646 15.669 15.693 15.716 15.739 15.763 15.786 15.810 15.833 15.856 +15.880 15.903 15.927 15.950 15.974 15.997 16.020 16.044 16.067 16.091 +16.114 16.138 16.161 16.184 16.208 16.231 16.255 16.278 16.302 16.325 + +16.349 16.372 16.395 16.419 16.442 16.466 16.489 16.513 16.536 16.560 +16.583 16.607 16.630 16.654 16.677 16.700 16.724 16.747 16.771 16.794 +16.818 16.841 16.865 16.888 16.912 16.935 16.959 16.982 17.006 17.029 +17.053 17.076 17.100 17.123 17.147 17.170 17.194 17.217 17.241 17.264 +17.288 17.311 17.335 17.358 17.382 17.406 17.429 17.453 17.476 17.500 + +17.523 17.547 17.570 17.594 17.617 17.641 17.664 17.688 17.711 17.735 +17.759 17.782 17.806 17.829 17.853 17.876 17.900 17.923 17.947 17.971 +17.994 18.018 18.041 18.065 18.088 18.112 18.136 18.159 18.183 18.206 +18.230 18.253 18.277 18.301 18.324 18.348 18.371 18.395 18.418 18.442 +18.466 18.489 18.513 18.536 18.560 18.584 18.607 18.631 18.654 18.678 + +18.702 18.725 18.749 18.772 18.796 18.820 18.843 18.867 18.890 18.914 +18.938 18.961 18.985 19.008 19.032 19.056 19.079 19.103 19.127 19.150 +19.174 19.197 19.221 19.245 19.268 19.292 19.316 19.339 19.363 19.386 +19.410 19.434 19.457 19.481 19.505 19.528 19.552 19.576 19.599 19.623 +19.646 19.670 19.694 19.717 19.741 19.765 19.788 19.812 19.836 19.859 + +19.883 19.907 19.930 19.954 19.978 20.001 20.025 20.049 20.072 20.096 +20.120 20.143 20.167 20.190 20.214 20.238 20.261 20.285 20.309 20.332 +20.356 20.380 20.403 20.427 20.451 20.474 20.498 20.522 20.545 20.569 +20.593 20.616 20.640 20.664 20.688 20.711 20.735 20.759 20.782 20.806 +20.830 20.853 20.877 20.901 20.924 20.948 20.972 20.995 21.019 21.043 + +21.066 21.090 21.114 21.137 21.161 21.185 21.208 21.232 21.256 21.280 +21.303 21.327 21.351 21.374 21.398 21.422 21.445 21.469 21.493 21.516 +21.540 21.564 21.587 21.611 21.635 21.659 21.682 21.706 21.730 21.753 +21.777 21.801 21.824 21.848 21.872 21.895 21.919 21.943 21.966 21.990 +22.014 22.038 22.061 22.085 22.109 22.132 22.156 22.180 22.203 22.227 + +22.251 22.274 22.298 22.322 22.346 22.369 22.393 22.417 22.440 22.464 +22.488 22.511 22.535 22.559 22.582 22.606 22.630 22.654 22.677 22.701 +22.725 22.748 22.772 22.796 22.819 22.843 22.867 22.890 22.914 22.938 +22.961 22.985 23.009 23.032 23.056 23.080 23.104 23.127 23.151 23.175 +23.198 23.222 23.246 23.269 23.293 23.317 23.340 23.364 23.388 23.411 + +23.435 23.459 23.482 23.506 23.530 23.553 23.577 23.601 23.624 23.648 +23.672 23.695 23.719 23.743 23.766 23.790 23.814 23.837 23.861 23.885 +23.908 23.932 23.956 23.979 24.003 24.027 24.050 24.074 24.098 24.121 +24.145 24.169 24.192 24.216 24.240 24.263 24.287 24.311 24.334 24.358 +24.382 24.405 24.429 24.453 24.476 24.500 24.523 24.547 24.571 24.594 + +24.618 24.642 24.665 24.689 24.713 24.736 24.760 24.783 24.807 24.831 +24.854 24.878 24.902 24.925 24.949 24.972 24.996 25.020 25.043 25.067 +25.091 25.114 25.138 25.161 25.185 25.209 25.232 25.256 25.279 25.303 +25.327 25.350 25.374 25.397 25.421 25.445 25.468 25.492 25.515 25.539 +25.563 25.586 25.610 25.633 25.657 25.681 25.704 25.728 25.751 25.775 + +25.799 25.822 25.846 25.869 25.893 25.916 25.940 25.964 25.987 26.011 +26.034 26.058 26.081 26.105 26.128 26.152 26.176 26.199 26.223 26.246 +26.270 26.293 26.317 26.340 26.364 26.387 26.411 26.435 26.458 26.482 +26.505 26.529 26.552 26.576 26.599 26.623 26.646 26.670 26.693 26.717 +26.740 26.764 26.787 26.811 26.834 26.858 26.881 26.905 26.928 26.952 + +26.975 26.999 27.022 27.046 27.069 27.093 27.116 27.140 27.163 27.187 +27.210 27.234 27.257 27.281 27.304 27.328 27.351 27.375 27.398 27.422 +27.445 27.468 27.492 27.515 27.539 27.562 27.586 27.609 27.633 27.656 +27.679 27.703 27.726 27.750 27.773 27.797 27.820 27.843 27.867 27.890 +27.914 27.937 27.961 27.984 28.007 28.031 28.054 28.078 28.101 28.124 + +28.148 28.171 28.195 28.218 28.241 28.265 28.288 28.311 28.335 28.358 +28.382 28.405 28.428 28.452 28.475 28.498 28.522 28.545 28.569 28.592 +28.615 28.639 28.662 28.685 28.709 28.732 28.755 28.779 28.802 28.825 +28.849 28.872 28.895 28.919 28.942 28.965 28.988 29.012 29.035 29.058 +29.082 29.105 29.128 29.152 29.175 29.198 29.221 29.245 29.268 29.291 + +29.315 29.338 29.361 29.384 29.408 29.431 29.454 29.477 29.501 29.524 +29.547 29.570 29.594 29.617 29.640 29.663 29.687 29.710 29.733 29.756 +29.780 29.803 29.826 29.849 29.872 29.896 29.919 29.942 29.965 29.989 +30.012 30.035 30.058 30.081 30.104 30.128 30.151 30.174 30.197 30.220 +30.244 30.267 30.290 30.313 30.336 30.359 30.383 30.406 30.429 30.452 + +30.475 30.498 30.521 30.545 30.568 30.591 30.614 30.637 30.660 30.683 +30.706 30.730 30.753 30.776 30.799 30.822 30.845 30.868 30.891 30.914 +30.937 30.961 30.984 31.007 31.030 31.053 31.076 31.099 31.122 31.145 +31.168 31.191 31.214 31.237 31.260 31.283 31.306 31.329 31.353 31.376 +31.399 31.422 31.445 31.468 31.491 31.514 31.537 31.560 31.583 31.606 + +31.629 31.652 31.675 31.698 31.721 31.744 31.767 31.790 31.813 31.836 +31.859 31.882 31.905 31.927 31.950 31.973 31.996 32.019 32.042 32.065 +32.088 32.111 32.134 32.157 32.180 32.203 32.226 32.249 32.272 32.294 +32.317 32.340 32.363 32.386 32.409 32.432 32.455 32.478 32.501 32.523 +32.546 32.569 32.592 32.615 32.638 32.661 32.683 32.706 32.729 32.752 + +32.775 32.798 32.821 32.843 32.866 32.889 32.912 32.935 32.958 32.980 +33.003 33.026 33.049 33.072 33.094 33.117 33.140 33.163 33.186 33.208 +33.231 33.254 33.277 33.300 33.322 33.345 33.368 33.391 33.413 33.436 +33.459 33.482 33.504 33.527 33.550 33.573 33.595 33.618 33.641 33.664 +33.686 33.709 33.732 33.754 33.777 33.800 33.823 33.845 33.868 33.891 + +33.913 33.936 33.959 33.981 34.004 34.027 34.049 34.072 34.095 34.117 +34.140 34.163 34.185 34.208 34.231 34.253 34.276 34.299 34.321 34.344 +34.366 34.389 34.412 34.434 34.457 34.480 34.502 34.525 34.547 34.570 +34.593 34.615 34.638 34.660 34.683 34.705 34.728 34.751 34.773 34.796 +34.818 34.841 34.863 34.886 34.909 34.931 34.954 34.976 34.999 35.021 + +35.044 35.066 35.089 35.111 35.134 35.156 35.179 35.201 35.224 35.246 +35.269 35.291 35.314 35.336 35.359 35.381 35.404 35.426 35.449 35.471 +35.494 35.516 35.539 35.561 35.583 35.606 35.628 35.651 35.673 35.696 +35.718 35.741 35.763 35.785 35.808 35.830 35.853 35.875 35.897 35.920 +35.942 35.965 35.987 36.009 36.032 36.054 36.077 36.099 36.121 36.144 + +36.166 36.188 36.211 36.233 36.256 36.278 36.300 36.323 36.345 36.367 +36.390 36.412 36.434 36.457 36.479 36.501 36.524 36.546 36.568 36.590 +36.613 36.635 36.657 36.680 36.702 36.724 36.746 36.769 36.791 36.813 +36.836 36.858 36.880 36.902 36.925 36.947 36.969 36.991 37.014 37.036 +37.058 37.080 37.103 37.125 37.147 37.169 37.191 37.214 37.236 37.258 + +37.280 37.303 37.325 37.347 37.369 37.391 37.413 37.436 37.458 37.480 +37.502 37.524 37.547 37.569 37.591 37.613 37.635 37.657 37.679 37.702 +37.724 37.746 37.768 37.790 37.812 37.834 37.857 37.879 37.901 37.923 +37.945 37.967 37.989 38.011 38.033 38.055 38.078 38.100 38.122 38.144 +38.166 38.188 38.210 38.232 38.254 38.276 38.298 38.320 38.342 38.364 + +38.387 38.409 38.431 38.453 38.475 38.497 38.519 38.541 38.563 38.585 +38.607 38.629 38.651 38.673 38.695 38.717 38.739 38.761 38.783 38.805 +38.827 38.849 38.871 38.893 38.915 38.937 38.959 38.981 39.003 39.024 +39.046 39.068 39.090 39.112 39.134 39.156 39.178 39.200 39.222 39.244 +39.266 39.288 39.310 39.331 39.353 39.375 39.397 39.419 39.441 39.463 + +39.485 39.507 39.529 39.550 39.572 39.594 39.616 39.638 39.660 39.682 +39.703 39.725 39.747 39.769 39.791 39.813 39.835 39.856 39.878 39.900 +39.922 39.944 39.965 39.987 40.009 40.031 40.053 40.075 40.096 40.118 +40.140 40.162 40.183 40.205 40.227 40.249 40.271 40.292 40.314 40.336 +40.358 40.379 40.401 40.423 40.445 40.466 40.488 40.510 40.532 40.553 + +40.575 40.597 40.619 40.640 40.662 40.684 40.705 40.727 40.749 40.770 +40.792 40.814 40.836 40.857 40.879 40.901 40.922 40.944 40.966 40.987 +41.009 41.031 41.052 41.074 41.096 41.117 41.139 41.161 41.182 41.204 +41.225 41.247 41.269 41.290 41.312 41.334 41.355 41.377 41.398 41.420 +41.442 41.463 41.485 41.506 41.528 41.550 41.571 41.593 41.614 41.636 + +41.657 41.679 41.701 41.722 41.744 41.765 41.787 41.808 41.830 41.851 +41.873 41.895 41.916 41.938 41.959 41.981 42.002 42.024 42.045 42.067 +42.088 42.110 42.131 42.153 42.174 42.196 42.217 42.239 42.260 42.282 +42.303 42.325 42.346 42.367 42.389 42.410 42.432 42.453 42.475 42.496 +42.518 42.539 42.560 42.582 42.603 42.625 42.646 42.668 42.689 42.710 + +42.732 42.753 42.775 42.796 42.817 42.839 42.860 42.882 42.903 42.924 +42.946 42.967 42.989 43.010 43.031 43.053 43.074 43.095 43.117 43.138 +43.159 43.181 43.202 43.223 43.245 43.266 43.287 43.309 43.330 43.351 +43.373 43.394 43.415 43.436 43.458 43.479 43.500 43.522 43.543 43.564 +43.585 43.607 43.628 43.649 43.671 43.692 43.713 43.734 43.756 43.777 + +43.798 43.819 43.841 43.862 43.883 43.904 43.925 43.947 43.968 43.989 +44.010 44.031 44.053 44.074 44.095 44.116 44.137 44.159 44.180 44.201 +44.222 44.243 44.265 44.286 44.307 44.328 44.349 44.370 44.391 44.413 +44.434 44.455 44.476 44.497 44.518 44.539 44.560 44.582 44.603 44.624 +44.645 44.666 44.687 44.708 44.729 44.750 44.771 44.793 44.814 44.835 + +44.856 44.877 44.898 44.919 44.940 44.961 44.982 45.003 45.024 45.045 +45.066 45.087 45.108 45.129 45.150 45.171 45.192 45.213 45.234 45.255 +45.276 45.297 45.318 45.339 45.360 45.381 45.402 45.423 45.444 45.465 +45.486 45.507 45.528 45.549 45.570 45.591 45.612 45.633 45.654 45.675 +45.695 45.716 45.737 45.758 45.779 45.800 45.821 45.842 45.863 45.884 + +45.904 45.925 45.946 45.967 45.988 46.009 46.030 46.051 46.071 46.092 +46.113 46.134 46.155 46.176 46.196 46.217 46.238 46.259 46.280 46.300 +46.321 46.342 46.363 46.384 46.404 46.425 46.446 46.467 46.488 46.508 +46.529 46.550 46.571 46.591 46.612 46.633 46.654 46.674 46.695 46.716 +46.737 46.757 46.778 46.799 46.819 46.840 46.861 46.881 46.902 46.923 + +46.944 46.964 46.985 47.006 47.026 47.047 47.068 47.088 47.109 47.130 +47.150 47.171 47.191 47.212 47.233 47.253 47.274 47.295 47.315 47.336 +47.356 47.377 47.398 47.418 47.439 47.459 47.480 47.500 47.521 47.542 +47.562 47.583 47.603 47.624 47.644 47.665 47.685 47.706 47.726 47.747 +47.767 47.788 47.808 47.829 47.849 47.870 47.890 47.911 47.931 47.952 + +47.972 47.993 48.013 48.034 48.054 48.075 48.095 48.116 48.136 48.156 +48.177 48.197 48.218 48.238 48.258 48.279 48.299 48.320 48.340 48.360 +48.381 48.401 48.422 48.442 48.462 48.483 48.503 48.523 48.544 48.564 +48.584 48.605 48.625 48.645 48.666 48.686 48.706 48.727 48.747 48.767 +48.787 48.808 48.828 48.848 48.869 48.889 48.909 48.929 48.950 48.970 + +48.990 49.010 49.031 49.051 49.071 49.091 49.111 49.132 49.152 49.172 +49.192 49.212 49.233 49.253 49.273 49.293 49.313 49.333 49.354 49.374 +49.394 49.414 49.434 49.454 49.474 49.495 49.515 49.535 49.555 49.575 +49.595 49.615 49.635 49.655 49.675 49.696 49.716 49.736 49.756 49.776 +49.796 49.816 49.836 49.856 49.876 49.896 49.916 49.936 49.956 49.976 + +49.996 50.016 50.036 50.056 50.076 50.096 50.116 50.136 50.156 50.176 +50.196 50.216 50.236 50.256 50.276 50.296 50.315 50.335 50.355 50.375 +50.395 50.415 50.435 50.455 50.475 50.494 50.514 50.534 50.554 50.574 +50.594 50.614 50.633 50.653 50.673 50.693 50.713 50.733 50.752 50.772 +50.792 50.812 50.832 50.851 50.871 50.891 50.911 50.930 50.950 50.970 + +50.990 51.009 51.029 51.049 51.069 51.088 51.108 51.128 51.148 51.167 +51.187 51.207 51.226 51.246 51.266 51.285 51.305 51.325 51.344 51.364 +51.384 51.403 51.423 51.443 51.462 51.482 51.501 51.521 51.541 51.560 +51.580 51.599 51.619 51.639 51.658 51.678 51.697 51.717 51.736 51.756 +51.776 51.795 51.815 51.834 51.854 51.873 51.893 51.912 51.932 51.951 + +51.971 51.990 52.010 52.029 52.049 52.068 52.088 52.107 52.127 52.146 +52.165 52.185 52.204 52.224 52.243 52.263 52.282 52.301 52.321 52.340 +52.360 52.379 52.398 52.418 52.437 52.457 52.476 52.495 52.515 52.534 +52.553 52.573 52.592 52.611 52.631 52.650 52.669 52.689 52.708 52.727 +52.747 52.766 52.785 52.805 52.824 52.843 52.862 52.882 52.901 52.920 + +52.939 52.959 52.978 52.997 53.016 53.036 53.055 53.074 53.093 53.113 +53.132 53.151 53.170 53.189 53.209 53.228 53.247 53.266 53.285 53.304 +53.324 53.343 53.362 53.381 53.400 53.419 53.439 53.458 53.477 53.496 +53.515 53.534 53.553 53.572 53.592 53.611 53.630 53.649 53.668 53.687 +53.706 53.725 53.744 53.763 53.782 53.801 53.821 53.840 53.859 53.878 + +53.897 53.916 53.935 53.954 53.973 53.992 54.011 54.030 54.049 54.068 +54.087 54.106 54.125 54.144 54.163 54.182 54.201 54.220 54.239 54.258 +54.277 54.296 54.315 54.334 54.353 54.372 54.391 54.410 54.429 54.447 +54.466 54.485 54.504 54.523 54.542 54.561 54.580 54.599 54.618 54.637 +54.656 54.675 54.694 54.712 54.731 54.750 54.769 54.788 54.807 54.826 + +54.845 diff --git a/modules/database/src/ioc/bpt/cvtTable.h b/modules/database/src/ioc/bpt/cvtTable.h new file mode 100644 index 000000000..374666450 --- /dev/null +++ b/modules/database/src/ioc/bpt/cvtTable.h @@ -0,0 +1,37 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Breakpoint Tables + * + * Author: Marty Kraimer + * Date: 11-7-90 + */ + +#ifndef INCcvtTableh +#define INCcvtTableh 1 + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Global Routines*/ +epicsShareFunc long cvtEngToRawBpt( + double *pval,short linr,short init,void **ppbrk,short *plbrk); + +epicsShareFunc long cvtRawToEngBpt( + double *pval,short linr,short init,void **ppbrk, short *plbrk); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/database/src/ioc/bpt/makeBpt.c b/modules/database/src/ioc/bpt/makeBpt.c new file mode 100644 index 000000000..93095f1c2 --- /dev/null +++ b/modules/database/src/ioc/bpt/makeBpt.c @@ -0,0 +1,427 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Marty Kraimer + * Date: 9/28/95 + * Replacement for old bldCvtTable + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "ellLib.h" +#include "cvtTable.h" + +#define MAX_LINE_SIZE 160 +#define MAX_BREAKS 100 +struct brkCreateInfo { + double engLow; /* Lowest value desired: engineering units */ + double engHigh; /* Highest value desired: engineering units */ + double rawLow; /* Raw value for EngLow */ + double rawHigh; /* Raw value for EngHigh */ + double accuracy; /* accuracy desired in engineering units */ + double tblEngFirst;/* First table value: engineering units */ + double tblEngLast; /* Last table value: engineering units */ + double tblEngDelta;/* Change per table entry: eng units */ + long nTable; /* number of table entries */ + /* (last-first)/delta + 1 */ + double *pTable; /* addr of data table */ +} brkCreateInfo; + +typedef struct brkInt { /* breakpoint interval */ + double raw; /* raw value for beginning of interval */ + double slope; /* slope for interval */ + double eng; /* converted value for beginning of interval */ +} brkInt; + +brkInt brkint[MAX_BREAKS]; + +static int create_break(struct brkCreateInfo *pbci, brkInt *pabrkInt, + int max_breaks, int *n_breaks); +static char inbuf[MAX_LINE_SIZE]; +static int linenum=0; + +typedef struct dataList{ + struct dataList *next; + double value; +}dataList; + +static int getNumber(char **pbeg, double *value) +{ + int nchars=0; + + while(isspace((int)**pbeg) && **pbeg!= '\0') (*pbeg)++; + if(**pbeg == '!' || **pbeg == '\0') return(-1); + if(sscanf(*pbeg,"%lf%n",value,&nchars)!=1) return(-1); + *pbeg += nchars; + return(0); +} + +static void errExit(char *pmessage) +{ + fprintf(stderr, "%s\n", pmessage); + fflush(stderr); + exit(-1); +} + +int main(int argc, char **argv) +{ + char *pbeg; + char *pend; + double value; + char *pname = NULL; + dataList *phead; + dataList *pdataList; + dataList *pnext; + double *pdata; + long ndata; + int nBreak,n; + size_t len; + char *outFilename; + char *pext; + FILE *outFile; + FILE *inFile; + char *plastSlash; + + + if(argc<2) { + fprintf(stderr,"usage: makeBpt file.data [outfile]\n"); + exit(-1); + } + if (argc==2) { + plastSlash = strrchr(argv[1],'/'); + plastSlash = (plastSlash ? plastSlash+1 : argv[1]); + outFilename = calloc(1,strlen(plastSlash)+2); + if(!outFilename) { + fprintf(stderr,"calloc failed\n"); + exit(-1); + } + strcpy(outFilename,plastSlash); + pext = strstr(outFilename,".data"); + if(!pext) { + fprintf(stderr,"Input file MUST have .data extension\n"); + exit(-1); + } + strcpy(pext,".dbd"); + } else { + outFilename = calloc(1,strlen(argv[2])+1); + if(!outFilename) { + fprintf(stderr,"calloc failed\n"); + exit(-1); + } + strcpy(outFilename,argv[2]); + } + inFile = fopen(argv[1],"r"); + if(!inFile) { + fprintf(stderr,"Error opening %s\n",argv[1]); + exit(-1); + } + outFile = fopen(outFilename,"w"); + if(!outFile) { + fprintf(stderr,"Error opening %s\n",outFilename); + exit(-1); + } + while(fgets(inbuf,MAX_LINE_SIZE,inFile)) { + linenum++; + pbeg = inbuf; + while(isspace((int)*pbeg) && *pbeg!= '\0') pbeg++; + if(*pbeg == '!' || *pbeg == '\0') continue; + while(*pbeg!='"' && *pbeg!= '\0') pbeg++; + if(*pbeg!='"' ) errExit("Illegal Header"); + pbeg++; pend = pbeg; + while(*pend!='"' && *pend!= '\0') pend++; + if(*pend!='"') errExit("Illegal Header"); + len = pend - pbeg; + if(len<=1) errExit("Illegal Header"); + pname = calloc(len+1,sizeof(char)); + if(!pname) { + fprintf(stderr,"calloc failed while processing line %d\n",linenum); + exit(-1); + } + strncpy(pname,pbeg,len); + pname[len]='\0'; + pbeg = pend + 1; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.engLow = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.rawLow = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.engHigh = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.rawHigh = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.accuracy = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.tblEngFirst = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.tblEngLast = value; + if(getNumber(&pbeg,&value)) errExit("Illegal Header"); + brkCreateInfo.tblEngDelta = value; + goto got_header; + } + errExit("Illegal Header"); +got_header: + phead = pnext = 0; + ndata = 0; + errno = 0; + while(fgets(inbuf,MAX_LINE_SIZE,inFile)) { + double value; + + linenum++; + pbeg = inbuf; + while(!getNumber(&pbeg,&value)) { + ndata++; + pdataList = (dataList *)calloc(1,sizeof(dataList)); + if(!pdataList) { + fprintf(stderr,"calloc failed (after header)" + " while processing line %d\n",linenum); + exit(-1); + } + if(!phead) + phead = pdataList; + else + pnext->next = pdataList; + pdataList->value = value; + pnext = pdataList; + } + } + if(!pname) { + errExit("create_break failed: no name specified\n"); + } + brkCreateInfo.nTable = ndata; + pdata = (double *)calloc(brkCreateInfo.nTable,sizeof(double)); + if(!pdata) { + fprintf(stderr,"calloc failed for table length %ld\n",brkCreateInfo.nTable); + exit(-1); + } + pnext = phead; + for(n=0; nvalue; + pdataList = pnext; + pnext = pnext->next; + free((void *)pdataList); + } + brkCreateInfo.pTable = pdata; + if(create_break(&brkCreateInfo,&brkint[0],MAX_BREAKS,&nBreak)) + errExit("create_break failed\n"); + fprintf(outFile,"breaktable(%s) {\n",pname); + for(n=0; npTable; + long ntable = pbci->nTable; + double ilow, + ihigh, + tbllow, + tblhigh, + slope, + offset; + int ibeg, + iend, + i, + inc, + imax, + n; + double rawBeg, + engBeg, + rawEnd, + engEnd, + engCalc, + engActual, + error; + int valid, + all_ok, + expanding; + /* make checks to ensure that brkCreateInfo makes sense */ + if (pbci->engLow >= pbci->engHigh) { + errExit("create_break: engLow >= engHigh"); + return (-1); + } + if ((pbci->engLow < pbci->tblEngFirst) + || (pbci->engHigh > pbci->tblEngLast)) { + errExit("create_break: engLow > engHigh"); + return (-1); + } + if (pbci->tblEngDelta <= 0.0) { + errExit("create_break: tblEngDelta <= 0.0"); + return (-1); + } + if (ntable < 3) { + errExit("raw data must have at least 3 elements"); + return (-1); + } +/*************************************************************************** + Convert Table to raw values + * + * raw and table values are assumed to be related by an equation of the form: + * + * raw = slope*table + offset + * + * The following algorithm converts each table value to raw units + * + * 1) Finds the locations in Table corresponding to engLow and engHigh + * Note that these locations need not be exact integers + * 2) Interpolates to obtain table values corresponding to engLow and enghigh + * we now have the equations: + * rawLow = slope*tblLow + offset + * rawHigh = slope*tblHigh + offset + * 4) Solving these equations for slope and offset gives: + * slope=(rawHigh-rawLow)/(tblHigh-tblLow) + * offset=rawHigh-slope*tblHigh + * 5) for each table value set table[i]=table[i]*slope+offset + *************************************************************************/ + /* Find engLow in Table and then compute tblLow */ + ilow = (pbci->engLow - pbci->tblEngFirst) / (pbci->tblEngDelta); + i = (int) ilow; + if (i >= ntable - 1) + i = ntable - 2; + tbllow = table[i] + (table[i + 1] - table[i]) * (ilow - (double) i); + /* Find engHigh in Table and then compute tblHigh */ + ihigh = (pbci->engHigh - pbci->tblEngFirst) / (pbci->tblEngDelta); + i = (int) ihigh; + if (i >= ntable - 1) + i = ntable - 2; + tblhigh = table[i] + (table[i + 1] - table[i]) * (ihigh - (double) i); + /* compute slope and offset */ + slope = (pbci->rawHigh - pbci->rawLow) / (tblhigh - tbllow); + offset = pbci->rawHigh - slope * tblhigh; + /* convert table to raw units */ + for (i = 0; i < ntable; i++) + table[i] = table[i] * slope + offset; + +/***************************************************************************** + * Now create break point table + * + * The algorithm does the following: + * + * It finds one breakpoint interval at a time. For each it does the following: + * + * 1) Use a relatively large portion of the remaining table as an interval + * 2) It attempts to use the entire interval as a breakpoint interval + * Success is determined by the following algorithm: + * a) compute the slope using the entire interval + * b) for each table entry in the interval determine the eng value + * using the slope just determined. + * c) compare the computed value with eng value associated with table + * d) if all table entries are within the accuracy desired then success. + * 3) If successful then attempt to expand the interval and try again. + * Note that it is expanded by up to 1/10 of the table size. + * 4) If not successful reduce the interval by 1 and try again. + * Once the interval is being decreased it will never be increased again. + * 5) The algorithm will ultimately fail or will have determined the optimum + * breakpoint interval + *************************************************************************/ + + /* Must start with table entry corresponding to engLow; */ + i = (int) ilow; + if (i >= ntable - 1) + i = ntable - 2; + rawBeg = table[i] + (table[i + 1] - table[i]) * (ilow - (double) i); + engBeg = pbci->engLow; + ibeg = (int) (ilow); /* Make sure that ibeg > ilow */ + if( ibeg < ilow ) ibeg = ibeg + 1; + /* start first breakpoint interval */ + n = 1; + pbrkInt = pabrkInt; + pbrkInt->raw = rawBeg; + pbrkInt->eng = engBeg; + /* determine next breakpoint interval */ + while ((engBeg <= pbci->engHigh) && (ibeg < ntable - 1)) { + /* determine next interval to try. Up to 1/10 full range */ + rawEnd = rawBeg; + engEnd = engBeg; + iend = ibeg; + inc = (int) ((ihigh - ilow) / 10.0); + if (inc < 1) + inc = 1; + valid = TRUE; + /* keep trying intervals until cant do better */ + expanding = TRUE; /* originally we are trying larger and larger + * intervals */ + while (valid) { + imax = iend + inc; + if (imax >= ntable) { + /* don't go past end of table */ + imax = ntable - 1; + inc = ntable - iend - 1; + expanding = FALSE; + } + if (imax > (int) (ihigh + 1.0)) { /* Don't go to far past + * engHigh */ + imax = (int) (ihigh + 1.0); + inc = (int) (ihigh + 1.0) - iend; + expanding = FALSE; + } + if (imax <= ibeg) + break; /* failure */ + rawEnd = table[imax]; + engEnd = pbci->tblEngFirst + (double) imax *(pbci->tblEngDelta); + slope = (engEnd - engBeg) / (rawEnd - rawBeg); + all_ok = TRUE; + for (i = ibeg + 1; i <= imax; i++) { + engCalc = engBeg + slope * (table[i] - rawBeg); + engActual = pbci->tblEngFirst + ((double) i) * (pbci->tblEngDelta); + error = engCalc - engActual; + if (error < 0.0) + error = -error; + if (error >= pbci->accuracy) { + /* we will be trying smaller intervals */ + expanding = FALSE; + /* just decrease inc and let while(valid) try again */ + inc--; + all_ok = FALSE; + break; + } + } /* end for */ + if (all_ok) { + iend = imax; + /* if not expanding we found interval */ + if (!expanding) + break; + /* will automatically try larger interval */ + } + } /* end while(valid) */ + /* either we failed or optimal interval has been found */ + if ((iend <= ibeg) && (iend < (int) ihigh)) { + errExit("Could not meet accuracy criteria"); + return (-1); + } + pbrkInt->slope = slope; + /* get ready for next breakpoint interval */ + if (n++ >= max_breaks) { + errExit("Break point table too large"); + return (-1); + } + ibeg = iend; + pbrkInt++; + rawBeg = rawEnd; + engBeg = engEnd; + pbrkInt->raw = rawBeg; + pbrkInt->eng = engBeg + (pbrkInt->raw - rawBeg) * slope; + } + pbrkInt->slope = 0.0; + *n_breaks = n; + return (0); +} diff --git a/modules/database/src/ioc/bpt/menuConvert.dbd.pod b/modules/database/src/ioc/bpt/menuConvert.dbd.pod new file mode 100644 index 000000000..e81d4e6f2 --- /dev/null +++ b/modules/database/src/ioc/bpt/menuConvert.dbd.pod @@ -0,0 +1,38 @@ +#************************************************************************* +# Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=head1 Menu menuConvert + +This menu defines the standard analog conversions which are included with Base. +IOC applications may add choices or replace the later choices in this menu, +although the first three choices must not be renamed or moved to different +positions. The breakpoint table name must exactly match the choice string +listed here. + +=menu menuConvert + +=cut + +menu(menuConvert) { + choice(menuConvertNO_CONVERSION,"NO CONVERSION") + choice(menuConvertSLOPE,"SLOPE") + choice(menuConvertLINEAR,"LINEAR") + choice(menuConverttypeKdegF,"typeKdegF") + choice(menuConverttypeKdegC,"typeKdegC") + choice(menuConverttypeJdegF,"typeJdegF") + choice(menuConverttypeJdegC,"typeJdegC") + choice(menuConverttypeEdegF,"typeEdegF(ixe only)") + choice(menuConverttypeEdegC,"typeEdegC(ixe only)") + choice(menuConverttypeTdegF,"typeTdegF") + choice(menuConverttypeTdegC,"typeTdegC") + choice(menuConverttypeRdegF,"typeRdegF") + choice(menuConverttypeRdegC,"typeRdegC") + choice(menuConverttypeSdegF,"typeSdegF") + choice(menuConverttypeSdegC,"typeSdegC") +} diff --git a/modules/database/src/ioc/databaseVersion.h b/modules/database/src/ioc/databaseVersion.h new file mode 100644 index 000000000..eb5e4d732 --- /dev/null +++ b/modules/database/src/ioc/databaseVersion.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (c) 2017 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef DATABASEVERSION_H +#define DATABASEVERSION_H + +#include + +#ifndef VERSION_INT +# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P)) +#endif + +/* include generated headers with: + * EPICS_DATABASE_MAJOR_VERSION + * EPICS_DATABASE_MINOR_VERSION + * EPICS_DATABASE_MAINTENANCE_VERSION + * EPICS_DATABASE_DEVELOPMENT_FLAG + */ +#include "databaseVersionNum.h" + +#define DATABASE_VERSION_INT VERSION_INT(EPICS_DATABASE_MAJOR_VERSION, EPICS_DATABASE_MINOR_VERSION, EPICS_DATABASE_MAINTENANCE_VERSION, 0) + +#endif // DATABASEVERSION_H diff --git a/modules/database/src/ioc/databaseVersionNum.h@ b/modules/database/src/ioc/databaseVersionNum.h@ new file mode 100644 index 000000000..02184a81b --- /dev/null +++ b/modules/database/src/ioc/databaseVersionNum.h@ @@ -0,0 +1,7 @@ +#ifndef DATABASEVERSION_H +# error include databaseVersion.h, not this header +#endif +#define EPICS_DATABASE_MAJOR_VERSION @EPICS_DATABASE_MAJOR_VERSION@ +#define EPICS_DATABASE_MINOR_VERSION @EPICS_DATABASE_MINOR_VERSION@ +#define EPICS_DATABASE_MAINTENANCE_VERSION @EPICS_DATABASE_MAINTENANCE_VERSION@ +#define EPICS_DATABASE_DEVELOPMENT_FLAG @EPICS_DATABASE_DEVELOPMENT_FLAG@ diff --git a/modules/database/src/ioc/db/Makefile b/modules/database/src/ioc/db/Makefile new file mode 100644 index 000000000..d28119ff7 --- /dev/null +++ b/modules/database/src/ioc/db/Makefile @@ -0,0 +1,101 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/db + +INC += callback.h +INC += dbAccess.h +INC += dbAccessDefs.h +INC += dbAddr.h +INC += dbBkpt.h +INC += dbCa.h +INC += dbChannel.h +INC += dbConstLink.h +INC += dbConvert.h +INC += dbConvertFast.h +INC += dbConvertJSON.h +INC += dbDbLink.h +INC += dbExtractArray.h +INC += dbEvent.h +INC += dbJLink.h +INC += dbLink.h +INC += dbLock.h +INC += dbNotify.h +INC += dbScan.h +INC += dbServer.h +INC += dbTest.h +INC += dbCaTest.h +INC += db_test.h +INC += db_field_log.h +INC += initHooks.h +INC += recGbl.h +INC += dbIocRegister.h +INC += chfPlugin.h +INC += dbState.h +INC += db_access_routines.h +INC += db_convert.h +INC += dbUnitTest.h + +# Generate menuGlobal.dbd, not really by concatenation, see RULES +DBDCAT += menuGlobal.dbd +menuGlobal_DBD += menuAlarmSevr.dbd +menuGlobal_DBD += menuAlarmStat.dbd +menuGlobal_DBD += menuFtype.dbd +menuGlobal_DBD += menuIvoa.dbd +menuGlobal_DBD += menuOmsl.dbd +menuGlobal_DBD += menuPini.dbd +menuGlobal_DBD += menuPost.dbd +menuGlobal_DBD += menuPriority.dbd +menuGlobal_DBD += menuYesNo.dbd +menuGlobal_DBD += menuSimm.dbd + +DBDINC += $(basename $(menuGlobal_DBD)) +DBDINC += menuScan +DBDINC += dbCommon + +dbMenusPod = $(notdir $(wildcard ../db/menu*.dbd.pod)) +HTMLS += $(patsubst %.dbd.pod,%.html,$(menusPod)) + +dbCore_SRCS += dbLock.c +dbCore_SRCS += dbAccess.c +dbCore_SRCS += dbBkpt.c +dbCore_SRCS += dbChannel.c +dbCore_SRCS += dbConstLink.c +dbCore_SRCS += dbConvert.c +dbCore_SRCS += dbConvertJSON.c +dbCore_SRCS += dbDbLink.c +dbCore_SRCS += dbFastLinkConv.c +dbCore_SRCS += dbExtractArray.c +dbCore_SRCS += dbJLink.c +dbCore_SRCS += dbLink.c +dbCore_SRCS += dbNotify.c +dbCore_SRCS += dbScan.c +dbCore_SRCS += dbEvent.c +dbCore_SRCS += dbTest.c +dbCore_SRCS += db_access.c +dbCore_SRCS += db_test.c +dbCore_SRCS += recGbl.c +dbCore_SRCS += callback.c +dbCore_SRCS += dbCa.c +dbCore_SRCS += dbCaTest.c +dbCore_SRCS += initHooks.c +dbCore_SRCS += cvtBpt.c +dbCore_SRCS += dbContext.cpp +dbCore_SRCS += dbChannelIO.cpp +dbCore_SRCS += dbSubscriptionIO.cpp +dbCore_SRCS += dbPutNotifyBlocker.cpp +dbCore_SRCS += dbContextReadNotifyCache.cpp +dbCore_SRCS += dbIocRegister.c +dbCore_SRCS += chfPlugin.c +dbCore_SRCS += dbState.c +dbCore_SRCS += dbUnitTest.c +dbCore_SRCS += dbServer.c + diff --git a/modules/database/src/ioc/db/RULES b/modules/database/src/ioc/db/RULES new file mode 100644 index 000000000..5f11a4cc6 --- /dev/null +++ b/modules/database/src/ioc/db/RULES @@ -0,0 +1,27 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2010 Brookhaven Science Associates, as Operator of +# Brookhaven National Lab. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +dbCommon.h$(DEP): $(IOCDIR)/db/dbCommonRecord.dbd $(IOCDIR)/db/RULES + @$(RM) $@ + @$(DBTORECORDTYPEH) -D -I ../db -o $(COMMONDEP_TARGET) $< > $@ + +$(COMMON_DIR)/dbCommon.h: $(IOCDIR)/db/dbCommonRecord.dbd $(IOCDIR)/db/RULES + @$(RM) $(notdir $@) + $(DBTORECORDTYPEH) -I ../db -o $(notdir $@) $< + @$(MV) $(notdir $@) $@ + +$(COMMON_DIR)/menuGlobal.dbd: $(IOCDIR)/db/Makefile $(IOCDIR)/db/RULES + +# This is a target-specific variable +$(COMMON_DIR)/menuGlobal.dbd: DBDCAT_COMMAND = \ + $(PERL) $(INSTALL_HOST_BIN)/makeIncludeDbd.pl $(menuGlobal_DBD) $(@F) diff --git a/modules/database/src/ioc/db/callback.c b/modules/database/src/ioc/db/callback.c new file mode 100644 index 000000000..058dc8d8c --- /dev/null +++ b/modules/database/src/ioc/db/callback.c @@ -0,0 +1,354 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* callback.c */ + +/* general purpose callback tasks */ +/* + * Original Author: Marty Kraimer + * Date: 07-18-91 +*/ + +#include +#include +#include +#include + +#include "cantProceed.h" +#include "dbDefs.h" +#include "epicsAtomic.h" +#include "epicsEvent.h" +#include "epicsInterrupt.h" +#include "epicsRingPointer.h" +#include "epicsString.h" +#include "epicsThread.h" +#include "epicsTimer.h" +#include "errlog.h" +#include "errMdef.h" +#include "taskwd.h" + +#define epicsExportSharedSymbols +#include "callback.h" +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCommon.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "dbStaticLib.h" +#include "epicsExport.h" +#include "link.h" +#include "recSup.h" + + +static int callbackQueueSize = 2000; + +typedef struct cbQueueSet { + epicsEventId semWakeUp; + epicsRingPointerId queue; + int queueOverflow; + int shutdown; + int threadsConfigured; + int threadsRunning; +} cbQueueSet; + +static cbQueueSet callbackQueue[NUM_CALLBACK_PRIORITIES]; + +int callbackThreadsDefault = 1; +/* Don't know what a reasonable default is (yet). + * For the time being: parallel means 2 if not explicitly specified */ +epicsShareDef int callbackParallelThreadsDefault = 2; +epicsExportAddress(int,callbackParallelThreadsDefault); + +/* Timer for Delayed Requests */ +static epicsTimerQueueId timerQueue; + +/* Shutdown handling */ +enum ctl {ctlInit, ctlRun, ctlPause, ctlExit}; +static volatile enum ctl cbCtl; +static epicsEventId startStopEvent; + +static int callbackIsInit; + +/* Static data */ +static char *threadNamePrefix[NUM_CALLBACK_PRIORITIES] = { + "cbLow", "cbMedium", "cbHigh" +}; +#define FULL_MSG(name) "callbackRequest: " name " ring buffer full\n" +static char *fullMessage[NUM_CALLBACK_PRIORITIES] = { + FULL_MSG("cbLow"), FULL_MSG("cbMedium"), FULL_MSG("cbHigh") +}; +static unsigned int threadPriority[NUM_CALLBACK_PRIORITIES] = { + epicsThreadPriorityScanLow - 1, + epicsThreadPriorityScanLow + 4, + epicsThreadPriorityScanHigh + 1 +}; +static int priorityValue[NUM_CALLBACK_PRIORITIES] = {0, 1, 2}; + + +int callbackSetQueueSize(int size) +{ + if (callbackIsInit) { + fprintf(stderr, "Callback system already initialized\n"); + return -1; + } + callbackQueueSize = size; + return 0; +} + +int callbackParallelThreads(int count, const char *prio) +{ + if (callbackIsInit) { + fprintf(stderr, "Callback system already initialized\n"); + return -1; + } + + if (count < 0) + count = epicsThreadGetCPUs() + count; + else if (count == 0) + count = callbackParallelThreadsDefault; + if (count < 1) count = 1; + + if (!prio || *prio == 0 || strcmp(prio, "*") == 0) { + int i; + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { + callbackQueue[i].threadsConfigured = count; + } + } + else { + dbMenu *pdbMenu; + int i; + + if (!pdbbase) { + fprintf(stderr, "callbackParallelThreads: pdbbase not set\n"); + return -1; + } + + /* Find prio in menuPriority */ + pdbMenu = dbFindMenu(pdbbase, "menuPriority"); + if (!pdbMenu) { + fprintf(stderr, "callbackParallelThreads: No Priority menu\n"); + return -1; + } + + for (i = 0; i < pdbMenu->nChoice; i++) { + if (epicsStrCaseCmp(prio, pdbMenu->papChoiceValue[i]) == 0) + goto found; + } + fprintf(stderr, "callbackParallelThreads: " + "Unknown priority \"%s\"\n", prio); + return -1; + +found: + callbackQueue[i].threadsConfigured = count; + } + return 0; +} + +static void callbackTask(void *arg) +{ + int prio = *(int*)arg; + cbQueueSet *mySet = &callbackQueue[prio]; + + taskwdInsert(0, NULL, NULL); + epicsEventSignal(startStopEvent); + + while(!mySet->shutdown) { + void *ptr; + if (epicsRingPointerIsEmpty(mySet->queue)) + epicsEventMustWait(mySet->semWakeUp); + + while ((ptr = epicsRingPointerPop(mySet->queue))) { + CALLBACK *pcallback = (CALLBACK *)ptr; + if(!epicsRingPointerIsEmpty(mySet->queue)) + epicsEventMustTrigger(mySet->semWakeUp); + mySet->queueOverflow = FALSE; + (*pcallback->callback)(pcallback); + } + } + + if(!epicsAtomicDecrIntT(&mySet->threadsRunning)) + epicsEventSignal(startStopEvent); + taskwdRemove(0); +} + +void callbackStop(void) +{ + int i; + + if (cbCtl == ctlExit) return; + cbCtl = ctlExit; + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { + callbackQueue[i].shutdown = 1; + epicsEventSignal(callbackQueue[i].semWakeUp); + } + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { + cbQueueSet *mySet = &callbackQueue[i]; + + while (epicsAtomicGetIntT(&mySet->threadsRunning)) { + epicsEventSignal(mySet->semWakeUp); + epicsEventWaitWithTimeout(startStopEvent, 0.1); + } + } +} + +void callbackCleanup(void) +{ + int i; + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { + cbQueueSet *mySet = &callbackQueue[i]; + + assert(epicsAtomicGetIntT(&mySet->threadsRunning)==0); + epicsEventDestroy(mySet->semWakeUp); + epicsRingPointerDelete(mySet->queue); + } + + epicsTimerQueueRelease(timerQueue); + callbackIsInit = 0; + memset(callbackQueue, 0, sizeof(callbackQueue)); +} + +void callbackInit(void) +{ + int i; + int j; + char threadName[32]; + + if (callbackIsInit) { + errlogMessage("Warning: callbackInit called again before callbackCleanup\n"); + return; + } + callbackIsInit = 1; + + if(!startStopEvent) + startStopEvent = epicsEventMustCreate(epicsEventEmpty); + cbCtl = ctlRun; + timerQueue = epicsTimerQueueAllocate(0, epicsThreadPriorityScanHigh); + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) { + epicsThreadId tid; + + callbackQueue[i].semWakeUp = epicsEventMustCreate(epicsEventEmpty); + callbackQueue[i].queue = epicsRingPointerLockedCreate(callbackQueueSize); + if (callbackQueue[i].queue == 0) + cantProceed("epicsRingPointerLockedCreate failed for %s\n", + threadNamePrefix[i]); + callbackQueue[i].queueOverflow = FALSE; + if (callbackQueue[i].threadsConfigured == 0) + callbackQueue[i].threadsConfigured = callbackThreadsDefault; + + for (j = 0; j < callbackQueue[i].threadsConfigured; j++) { + if (callbackQueue[i].threadsConfigured > 1 ) + sprintf(threadName, "%s-%d", threadNamePrefix[i], j); + else + strcpy(threadName, threadNamePrefix[i]); + tid = epicsThreadCreate(threadName, threadPriority[i], + epicsThreadGetStackSize(epicsThreadStackBig), + (EPICSTHREADFUNC)callbackTask, &priorityValue[i]); + if (tid == 0) { + cantProceed("Failed to spawn callback thread %s\n", threadName); + } else { + epicsEventWait(startStopEvent); + epicsAtomicIncrIntT(&callbackQueue[i].threadsRunning); + } + } + } +} + +/* This routine can be called from interrupt context */ +int callbackRequest(CALLBACK *pcallback) +{ + int priority; + int pushOK; + cbQueueSet *mySet; + + if (!pcallback) { + epicsInterruptContextMessage("callbackRequest: pcallback was NULL\n"); + return S_db_notInit; + } + priority = pcallback->priority; + if (priority < 0 || priority >= NUM_CALLBACK_PRIORITIES) { + epicsInterruptContextMessage("callbackRequest: Bad priority\n"); + return S_db_badChoice; + } + mySet = &callbackQueue[priority]; + if (mySet->queueOverflow) return S_db_bufFull; + + pushOK = epicsRingPointerPush(mySet->queue, pcallback); + + if (!pushOK) { + epicsInterruptContextMessage(fullMessage[priority]); + mySet->queueOverflow = TRUE; + return S_db_bufFull; + } + epicsEventSignal(mySet->semWakeUp); + return 0; +} + +static void ProcessCallback(CALLBACK *pcallback) +{ + dbCommon *pRec; + + callbackGetUser(pRec, pcallback); + if (!pRec) return; + dbScanLock(pRec); + (*pRec->rset->process)(pRec); + dbScanUnlock(pRec); +} + +void callbackSetProcess(CALLBACK *pcallback, int Priority, void *pRec) +{ + callbackSetCallback(ProcessCallback, pcallback); + callbackSetPriority(Priority, pcallback); + callbackSetUser(pRec, pcallback); +} + +int callbackRequestProcessCallback(CALLBACK *pcallback, + int Priority, void *pRec) +{ + callbackSetProcess(pcallback, Priority, pRec); + return callbackRequest(pcallback); +} + +static void notify(void *pPrivate) +{ + CALLBACK *pcallback = (CALLBACK *)pPrivate; + callbackRequest(pcallback); +} + +void callbackRequestDelayed(CALLBACK *pcallback, double seconds) +{ + epicsTimerId timer = (epicsTimerId)pcallback->timer; + + if (timer == 0) { + timer = epicsTimerQueueCreateTimer(timerQueue, notify, pcallback); + pcallback->timer = timer; + } + epicsTimerStartDelay(timer, seconds); +} + +void callbackCancelDelayed(CALLBACK *pcallback) +{ + epicsTimerId timer = (epicsTimerId)pcallback->timer; + + if (timer != 0) { + epicsTimerCancel(timer); + } +} + +void callbackRequestProcessCallbackDelayed(CALLBACK *pcallback, + int Priority, void *pRec, double seconds) +{ + callbackSetProcess(pcallback, Priority, pRec); + callbackRequestDelayed(pcallback, seconds); +} diff --git a/modules/database/src/ioc/db/callback.h b/modules/database/src/ioc/db/callback.h new file mode 100644 index 000000000..d25b782c9 --- /dev/null +++ b/modules/database/src/ioc/db/callback.h @@ -0,0 +1,80 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* includes for general purpose callback tasks */ +/* + * Original Author: Marty Kraimer + * Date: 07-18-91 +*/ + +#ifndef INCcallbackh +#define INCcallbackh 1 + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * WINDOWS also has a "CALLBACK" type def + */ +#ifdef _WIN32 +# ifdef CALLBACK +# undef CALLBACK +# endif /*CALLBACK*/ +#endif /*_WIN32*/ + +#define NUM_CALLBACK_PRIORITIES 3 +#define priorityLow 0 +#define priorityMedium 1 +#define priorityHigh 2 + +typedef struct callbackPvt { + void (*callback)(struct callbackPvt*); + int priority; + void *user; /*for use by callback user*/ + void *timer; /*for use by callback itself*/ +}CALLBACK; + +typedef void (*CALLBACKFUNC)(struct callbackPvt*); + +#define callbackSetCallback(PFUN, PCALLBACK) \ + ( (PCALLBACK)->callback = (PFUN) ) +#define callbackSetPriority(PRIORITY, PCALLBACK) \ + ( (PCALLBACK)->priority = (PRIORITY) ) +#define callbackGetPriority(PRIORITY, PCALLBACK) \ + ( (PRIORITY) = (PCALLBACK)->priority ) +#define callbackSetUser(USER, PCALLBACK) \ + ( (PCALLBACK)->user = (void *) (USER) ) +#define callbackGetUser(USER, PCALLBACK) \ + ( (USER) = (PCALLBACK)->user ) + +epicsShareFunc void callbackInit(void); +epicsShareFunc void callbackStop(void); +epicsShareFunc void callbackCleanup(void); +epicsShareFunc int callbackRequest(CALLBACK *pCallback); +epicsShareFunc void callbackSetProcess( + CALLBACK *pcallback, int Priority, void *pRec); +epicsShareFunc int callbackRequestProcessCallback( + CALLBACK *pCallback,int Priority, void *pRec); +epicsShareFunc void callbackRequestDelayed( + CALLBACK *pCallback,double seconds); +epicsShareFunc void callbackCancelDelayed(CALLBACK *pcallback); +epicsShareFunc void callbackRequestProcessCallbackDelayed( + CALLBACK *pCallback, int Priority, void *pRec, double seconds); +epicsShareFunc int callbackSetQueueSize(int size); +epicsShareFunc int callbackParallelThreads(int count, const char *prio); + +#ifdef __cplusplus +} +#endif + +#endif /*INCcallbackh*/ diff --git a/modules/database/src/ioc/db/chfPlugin.c b/modules/database/src/ioc/db/chfPlugin.c new file mode 100644 index 000000000..5a86d3a26 --- /dev/null +++ b/modules/database/src/ioc/db/chfPlugin.c @@ -0,0 +1,681 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +/* Based on the linkoptions utility by Michael Davidsaver (BNL) */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsStdio.h" +#include "epicsStdlib.h" +#include "epicsString.h" +#include "epicsTypes.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "chfPlugin.h" +#include "dbStaticLib.h" + +/* + * Data for a chfPlugin + */ +typedef struct chfPlugin { + const chfPluginArgDef *opts; + size_t nopts; + epicsUInt32 *required; + const chfPluginIf *pif; +} chfPlugin; + +/* + * Parser state data for a chfFilter (chfPlugin instance) + */ +typedef struct chfFilter { + const chfPlugin *plugin; + epicsUInt32 *found; + void *puser; + epicsInt16 nextParam; +} chfFilter; + +/* Data types we get from the parser */ +typedef enum chfPluginType { + chfPluginTypeBool, + chfPluginTypeInt, + chfPluginTypeDouble, + chfPluginTypeString +} chfPluginType; + +/* + * Convert the (epicsInt32) integer value 'val' to the type named in + * 'opt->optType' and store the result at 'user + opt->offset'. + */ +static int +store_integer_value(const chfPluginArgDef *opt, char *user, epicsInt32 val) +{ + epicsInt32 *ival; + int *eval; + const chfPluginEnumType *emap; + double *dval; + char *sval; + int ret; + char buff[22]; /* 2^64 = 1.8e+19, so 20 digits plus sign max */ + +#ifdef DEBUG_CHF + printf("Got an integer for %s (type %d): %ld\n", + opt->name, opt->optType, (long) val); +#endif + + if (!opt->convert && opt->optType != chfPluginArgInt32) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (epicsInt32 *) ((char *)user + opt->dataOffset); + *ival = val; + break; + case chfPluginArgBoolean: + sval = user + opt->dataOffset; + *sval = !!val; + break; + case chfPluginArgDouble: + dval = (double*) (user + opt->dataOffset); + *dval = val; + break; + case chfPluginArgString: + sval = user + opt->dataOffset; + ret = sprintf(buff, "%ld", (long)val); + if (ret < 0 || (unsigned) ret > opt->size - 1) { + return -1; + } + strncpy(sval, buff, opt->size-1); + sval[opt->size-1]='\0'; + break; + case chfPluginArgEnum: + eval = (int*) (user + opt->dataOffset); + for (emap = opt->enums; emap && emap->name; emap++) { + if (val == emap->value) { + *eval = val; + break; + } + } + if (!emap || !emap->name) { + return -1; + } + break; + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the (int) boolean value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static int store_boolean_value(const chfPluginArgDef *opt, char *user, int val) +{ + epicsInt32 *ival; + double *dval; + char *sval; + +#ifdef DEBUG_CHF + printf("Got a boolean for %s (type %d): %d\n", + opt->name, opt->optType, val); +#endif + + if (!opt->convert && opt->optType != chfPluginArgBoolean) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (epicsInt32 *) (user + opt->dataOffset); + *ival = val; + break; + case chfPluginArgBoolean: + sval = user + opt->dataOffset; + *sval = val; + break; + case chfPluginArgDouble: + dval = (double*) (user + opt->dataOffset); + *dval = !!val; + break; + case chfPluginArgString: + sval = user + opt->dataOffset; + if ((unsigned) (val ? 4 : 5) > opt->size - 1) { + return -1; + } + strncpy(sval, val ? "true" : "false", opt->size - 1); + sval[opt->size - 1] = '\0'; + break; + case chfPluginArgEnum: + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the double value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static int +store_double_value(const chfPluginArgDef *opt, void *vuser, double val) +{ + char *user = vuser; + epicsInt32 *ival; + double *dval; + char *sval; + int i; + +#ifdef DEBUG_CHF + printf("Got a double for %s (type %d, convert: %s): %g\n", + opt->name, opt->optType, opt->convert ? "yes" : "no", val); +#endif + + if (!opt->convert && opt->optType != chfPluginArgDouble) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + if (val < INT_MIN || val > INT_MAX) { + return -1; + } + ival = (epicsInt32 *) (user + opt->dataOffset); + *ival = (epicsInt32) val; + break; + case chfPluginArgBoolean: + sval = user + opt->dataOffset; + *sval = !!val; + break; + case chfPluginArgDouble: + dval = (double*) (user + opt->dataOffset); + *dval = val; + break; + case chfPluginArgString: + sval = user + opt->dataOffset; + if (opt->size <= 8) { /* Play it safe: 3 exp + 2 sign + 'e' + '.' */ + return -1; + } + i = epicsSnprintf(sval, opt->size, "%.*g", (int) opt->size - 7, val); + if (i < 0 || (unsigned) i >= opt->size) { + return -1; + } + break; + case chfPluginArgEnum: + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the (char*) string value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static int +store_string_value(const chfPluginArgDef *opt, char *user, const char *val, + size_t len) +{ + epicsInt32 *ival; + int *eval; + const chfPluginEnumType *emap; + double *dval; + char *sval; + char *end; + size_t i; + +#ifdef DEBUG_CHF + printf("Got a string for %s (type %d): %.*s\n", + opt->name, opt->optType, (int) len, val); +#endif + + if (!opt->convert && opt->optType != chfPluginArgString && + opt->optType != chfPluginArgEnum) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (epicsInt32 *) (user + opt->dataOffset); + return epicsParseInt32(val, ival, 0, &end); + + case chfPluginArgBoolean: + sval = user + opt->dataOffset; + if (epicsStrnCaseCmp(val, "true", len) == 0) { + *sval = 1; + } else if (epicsStrnCaseCmp(val, "false", len) == 0) { + *sval = 0; + } else { + epicsInt8 i8; + + if (epicsParseInt8(val, &i8, 0, &end)) + return -1; + *sval = !!i8; + } + break; + case chfPluginArgDouble: + dval = (double*) (user + opt->dataOffset); + return epicsParseDouble(val, dval, &end); + + case chfPluginArgString: + i = opt->size-1 < len ? opt->size-1 : (int) len; + sval = user + opt->dataOffset; + strncpy(sval, val, i); + sval[i] = '\0'; + break; + case chfPluginArgEnum: + eval = (int*) (user + opt->dataOffset); + for (emap = opt->enums; emap && emap->name; emap++) { + if (strncmp(emap->name, val, len) == 0) { + *eval = emap->value; + break; + } + } + if( !emap || !emap->name ) { + return -1; + } + break; + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +static void freeInstanceData(chfFilter *f) +{ + free(f->found); + free(f); /* FIXME: Use a free-list */ +} + +/* + * chFilterIf callbacks + */ + +/* First entry point when a new filter instance is created. + * All per-instance allocations happen here. + */ +static parse_result parse_start(chFilter *filter) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f; + + /* Filter context */ + /* FIXME: Use a free-list */ + f = calloc(1, sizeof(chfFilter)); + if (!f) { + errlogPrintf("chfFilterCtx calloc failed\n"); + goto errfctx; + } + f->nextParam = -1; + + /* Bit array to find missing required keys */ + f->found = calloc( (p->nopts/32)+1, sizeof(epicsUInt32) ); + if (!f->found) { + errlogPrintf("chfConfigParseStart: bit array calloc failed\n"); + goto errbitarray; + } + + /* Call the plugin to allocate its structure, it returns NULL on error */ + if (p->pif->allocPvt) { + if ((f->puser = p->pif->allocPvt()) == NULL) { + errlogPrintf("chfConfigParseStart: plugin pvt alloc failed\n"); + goto errplugin; + } + } + + filter->puser = (void*) f; + + return parse_continue; + + errplugin: + free(f->found); + errbitarray: + free(f); /* FIXME: Use a free-list */ + errfctx: + return parse_stop; +} + +static void parse_abort(chFilter *filter) { + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + /* Call the plugin to tell it we're aborting */ + if (p->pif->parse_error) p->pif->parse_error(f->puser); + if (p->pif->freePvt) p->pif->freePvt(f->puser); + freeInstanceData(f); +} + +static parse_result parse_end(chFilter *filter) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + int i; + + /* Check if all required arguments were supplied */ + for(i = 0; i < (p->nopts/32)+1; i++) { + if ((f->found[i] & p->required[i]) != p->required[i]) { + if (p->pif->parse_error) p->pif->parse_error(f->puser); + if (p->pif->freePvt) p->pif->freePvt(f->puser); + freeInstanceData(f); + return parse_stop; + } + } + + /* Call the plugin to tell it we're done */ + if (p->pif->parse_ok) { + if (p->pif->parse_ok(f->puser)) { + if (p->pif->freePvt) p->pif->freePvt(f->puser); + freeInstanceData(f); + return parse_stop; + } + } + + return parse_continue; +} + +static parse_result parse_boolean(chFilter *filter, int boolVal) +{ + const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts; + chfFilter *f = (chfFilter*)filter->puser; + + if (f->nextParam < 0 || + store_boolean_value(&opts[f->nextParam], f->puser, boolVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_integer(chFilter *filter, long integerVal) +{ + const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts; + chfFilter *f = (chfFilter*)filter->puser; + + if(sizeof(long)>sizeof(epicsInt32)) { + epicsInt32 temp=integerVal; + if(integerVal !=temp) + return parse_stop; + } + if (f->nextParam < 0 || + store_integer_value(&opts[f->nextParam], f->puser, integerVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_double(chFilter *filter, double doubleVal) +{ + const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts; + chfFilter *f = (chfFilter*)filter->puser; + + if (f->nextParam < 0 || + store_double_value(&opts[f->nextParam], f->puser, doubleVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result +parse_string(chFilter *filter, const char *stringVal, size_t stringLen) +{ + const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts; + chfFilter *f = (chfFilter*)filter->puser; + + if (f->nextParam < 0 || + store_string_value(&opts[f->nextParam], f->puser, stringVal, stringLen)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_start_map(chFilter *filter) +{ + return parse_continue; +} + +static parse_result +parse_map_key(chFilter *filter, const char *key, size_t stringLen) +{ + const chfPluginArgDef *cur; + const chfPluginArgDef *opts = ((chfPlugin*)filter->plug->puser)->opts; + chfFilter *f = (chfFilter*)filter->puser; + int *tag; + int i; + int j; + + f->nextParam = -1; + for (cur = opts, i = 0; cur && cur->name; cur++, i++) { + if (strncmp(key, cur->name, stringLen) == 0) { + f->nextParam = i; + break; + } + } + if (f->nextParam == -1) { + return parse_stop; + } + + if (opts[i].tagged) { + tag = (int*) ((char*) f->puser + opts[i].tagOffset); + *tag = opts[i].choice; + } + + f->found[i/32] |= 1<<(i%32); + /* Mark tag and all other options pointing to the same data as found */ + for (cur = opts, j = 0; cur && cur->name; cur++, j++) { + if ((opts[i].tagged && cur->dataOffset == opts[i].tagOffset) + || cur->dataOffset == opts[i].dataOffset) + f->found[j/32] |= 1<<(j%32); + } + + return parse_continue; +} + +static parse_result parse_end_map(chFilter *filter) +{ + return parse_continue; +} + +static long channel_open(chFilter *filter) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + if (p->pif->channel_open) + return p->pif->channel_open(filter->chan, f->puser); + else + return 0; +} + +static void +channel_register_pre(chFilter *filter, chPostEventFunc **cb_out, + void **arg_out, db_field_log *probe) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + if (p->pif->channelRegisterPre) + p->pif->channelRegisterPre(filter->chan, f->puser, cb_out, arg_out, + probe); +} + +static void +channel_register_post(chFilter *filter, chPostEventFunc **cb_out, + void **arg_out, db_field_log *probe) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + if (p->pif->channelRegisterPost) + p->pif->channelRegisterPost(filter->chan, f->puser, cb_out, arg_out, + probe); +} + +static void channel_report(chFilter *filter, int level, + const unsigned short indent) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + if (p->pif->channel_report) + p->pif->channel_report(filter->chan, f->puser, level, indent); +} + +static void channel_close(chFilter *filter) +{ + chfPlugin *p = (chfPlugin*) filter->plug->puser; + chfFilter *f = (chfFilter*) filter->puser; + + if (p->pif->channel_close) p->pif->channel_close(filter->chan, f->puser); + if (p->pif->freePvt) p->pif->freePvt(f->puser); + free(f->found); + free(f); /* FIXME: Use a free-list */ +} + +static void plugin_free(void* puser) +{ + chfPlugin *p=puser; + free(p->required); + free(p); +} + +/* + * chFilterIf for the wrapper + * we just support a simple one-level map, and no arrays + */ +static chFilterIf wrapper_fif = { + plugin_free, + + parse_start, + parse_abort, + parse_end, + + NULL, /* parse_null, */ + parse_boolean, + parse_integer, + parse_double, + parse_string, + + parse_start_map, + parse_map_key, + parse_end_map, + + NULL, /* parse_start_array, */ + NULL, /* parse_end_array, */ + + channel_open, + channel_register_pre, + channel_register_post, + channel_report, + channel_close +}; + +const char* +chfPluginEnumString(const chfPluginEnumType *emap, int i, const char* def) +{ + for(; emap && emap->name; emap++) { + if ( i == emap->value ) { + return emap->name; + } + } + return def; +} + +int +chfPluginRegister(const char* key, const chfPluginIf *pif, + const chfPluginArgDef* opts) +{ + chfPlugin *p; + size_t i; + const chfPluginArgDef *cur; + epicsUInt32 *reqd; + + /* Check and count options */ + for (i = 0, cur = opts; cur && cur->name; i++, cur++) { + switch(cur->optType) { + case chfPluginArgInt32: + if (cur->size < sizeof(epicsInt32)) { + errlogPrintf("Plugin %s: %d bytes too small for epicsInt32 %s\n", + key, cur->size, cur->name); + return -1; + } + break; + case chfPluginArgBoolean: + if (cur->size < 1) { + errlogPrintf("Plugin %s: %d bytes too small for boolean %s\n", + key, cur->size, cur->name); + return -1; + } + break; + case chfPluginArgDouble: + if (cur->size < sizeof(double)) { + errlogPrintf("Plugin %s: %d bytes too small for double %s\n", + key, cur->size, cur->name); + return -1; + } + break; + case chfPluginArgString: + if (cur->size < sizeof(char*)) { + /* Catch if someone has given us a char* instead of a char[] + * Also means that char buffers must be >=4. + */ + errlogPrintf("Plugin %s: %d bytes too small for string %s\n", + key, cur->size, cur->name); + return -1; + } + break; + case chfPluginArgEnum: + if (cur->size < sizeof(int)) { + errlogPrintf("Plugin %s: %d bytes too small for enum %s\n", + key, cur->size, cur->name); + return -1; + } + break; + case chfPluginArgInvalid: + errlogPrintf("Plugin %s: storage type for %s is not defined\n", + key, cur->name); + return -1; + break; + } + } + + /* Bit array used to find missing required keys */ + reqd = dbCalloc((i/32)+1, sizeof(epicsUInt32)); + if (!reqd) { + errlogPrintf("Plugin %s: bit array calloc failed\n", key); + return -1; + } + + for (i = 0, cur = opts; cur && cur->name; i++, cur++) { + if (cur->required) reqd[i/32] |= 1 << (i%32); + } + + /* Plugin data */ + p = dbCalloc(1, sizeof(chfPlugin)); + p->pif = pif; + p->opts = opts; + p->nopts = i; + p->required = reqd; + + dbRegisterFilter(key, &wrapper_fif, p); + + return 0; +} diff --git a/modules/database/src/ioc/db/chfPlugin.h b/modules/database/src/ioc/db/chfPlugin.h new file mode 100644 index 000000000..eabaa7ac3 --- /dev/null +++ b/modules/database/src/ioc/db/chfPlugin.h @@ -0,0 +1,318 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +/* Based on the linkoptions utility by Michael Davidsaver (BNL) */ + +#ifndef CHFPLUGIN_H +#define CHFPLUGIN_H + +#include +#include +#include +#include + +struct db_field_log; + +/** @file chfPlugin.h + * @brief Channel filter simplified plugins. + * + * Utility layer to allow an easier (reduced) interface for + * channel filter plugins. + * + * Parsing the configuration arguments of a channel filter plugin + * is done according to an argument description table provided by the plugin. + * The parser stores the results directly into a user supplied structure + * after appropriate type conversion. + * + * To specify the arguments, a chfPluginArgDef table must be defined + * for the user structure. This table has to be specified when the plugin registers. + * + * The plugin is responsible to register an init function using + * epicsExportRegistrar() and the accompanying registrar() directive in the dbd, + * and call chfPluginRegister() from within the init function. + * + * For example: + * + * typedef struct myStruct { + * ... other stuff + * char mode; + * epicsInt32 ival; + * double dval; + * epicsInt32 ival2; + * int enumval; + * char strval[20]; + * char boolval; + * } myStruct; + * + * static const + * chfPluginEnumType colorEnum[] = { {"Red",1}, {"Green",2}, {"Blue",3}, {NULL,0} }; + * + * static const + * chfPluginDef myStructDef[] = { + * chfTagInt32(myStruct, ival, "Integer" , ival2, 3, 0, 0), + * chfInt32 (myStruct, ival2, "Second" , 1, 0), + * chfDouble (myStruct, dval, "Double" , 1, 0), + * chfString (myStruct, strval , "String" , 1, 0), + * chfEnum (myStruct, enumval, "Color" , 1, 0, colorEnum), + * chfBoolean (myStruct, boolval, "Bool" , 1, 0), + * chfPluginEnd + * }; + * + * Note: The 4th argument specifies the parameter to be required (1) or optional (0), + * the 5th whether converting to the required type is allowed (1), or + * type mismatches are an error (0). + * Note: The "Tag" version has two additional arguments. the 4th arg specifies the tag + * field (integer type) inside the structure to be set, the 5th arg specifies the + * value to set the tag field to. Arguments 6 and 7 specify "required" and + * "conversion" as described above. + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Channel filter simplified plugin interface. + * + * The routines in this structure must be implemented by each filter plugin. + */ +typedef struct chfPluginIf { + + /* Memory management */ + /** @brief Allocate private resources. + * + * Called before parsing starts. + * The plugin should allocate its per-instance structures, + * returning a pointer to them or NULL requesting an abort of the operation. + * + * allocPvt may be set to NULL, if no resource allocation is needed. + * + * @return Pointer to private structure, NULL if operation is to be aborted. + */ + void * (* allocPvt) (void); + + /** @brief Free private resources. + * + * Called as part of abort or shutdown. + * The plugin should release any resources allocated for this filter; + * no further calls through this interface will be made. + * + * freePvt may be set to NULL, if no resources need to be released. + * + * @param pvt Pointer to private structure. + */ + void (* freePvt) (void *pvt); + + /* Parameter parsing results */ + /** @brief A parsing error occurred. + * + * Called after parsing failed with an error. + * + * @param pvt Pointer to private structure. + */ + void (* parse_error) (void *pvt); + + /** @brief Configuration has been parsed successfully. + * + * Called after parsing has finished ok. + * The plugin may check the validity of the parsed data, + * returning -1 to request an abort of the operation. + * + * @param pvt Pointer to private structure. + * @return 0 for success, -1 if operation is to be aborted. + */ + int (* parse_ok) (void *pvt); + + /* Channel operations */ + /** @brief Open channel. + * + * Called as part of the channel connection setup. + * + * @param chan dbChannel for which the connection is being made. + * @param pvt Pointer to private structure. + * @return 0 for success, -1 if operation is to be aborted. + */ + long (* channel_open) (dbChannel *chan, void *pvt); + + /** @brief Register callbacks for pre-event-queue operation. + * + * Called as part of the channel connection setup. + * + * This function is called to establish the stack of plugins that an event + * is passed through between the database and the event queue. + * + * The plugin must set pe_out to point to its own post-event callback in order + * to be called when a data update is sent from the database towards the + * event queue. + * + * The plugin may find out the type of data it will receive by looking at 'probe'. + * If the plugin will change the data type and/or size, it must update 'probe' + * accordingly. + * + * @param chan dbChannel for which the connection is being made. + * @param pvt Pointer to private structure. + * @param cb_out Pointer to this plugin's post-event callback (NULL to bypass + * this plugin). + * @param arg_out Argument that must be supplied when calling + * this plugin's post-event callback. + */ + void (* channelRegisterPre) (dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, + db_field_log *probe); + + /** @brief Register callbacks for post-event-queue operation. + * + * Called as part of the channel connection setup. + * + * This function is called to establish the stack of plugins that an event + * is passed through between the event queue and the final user (CA server or + * database access). + * + * The plugin must set pe_out to point to its own post-event callback in order + * to be called when a data update is sent from the event queue towards the + * final user. + * + * The plugin may find out the type of data it will receive by looking at 'probe'. + * If the plugin will change the data type and/or size, it must update 'probe' + * accordingly. + * + * @param chan dbChannel for which the connection is being made. + * @param pvt Pointer to private structure. + * @param cb_out Pointer to this plugin's post-event callback (NULL to bypass + * this plugin). + * @param arg_out Argument that must be supplied when calling + * this plugin's post-event callback. + */ + void (* channelRegisterPost) (dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, + db_field_log *probe); + + /** @brief Channel report request. + * + * Called as part of show... routines. + * + * @param chan dbChannel for which the report is requested. + * @param pvt Pointer to private structure. + * @param level Interest level. + * @param indent Number of spaces to print before each output line. + */ + void (* channel_report) (dbChannel *chan, void *pvt, int level, const unsigned short indent); + + /** @brief Channel close request. + * + * Called as part of connection shutdown. + * @param chan dbChannel for which the connection is being shut down. + * @param pvt Pointer to private structure. + */ + void (* channel_close) (dbChannel *chan, void *pvt); + +} chfPluginIf; + +typedef enum chfPluginArg { + chfPluginArgInvalid=0, + chfPluginArgBoolean, + chfPluginArgInt32, + chfPluginArgDouble, + chfPluginArgString, + chfPluginArgEnum +} chfPluginArg; + +typedef struct chfPluginEnumType { + const char *name; + const int value; +} chfPluginEnumType; + +typedef struct chfPluginArgDef { + const char * name; + chfPluginArg optType; + unsigned int required:1; + unsigned int convert:1; + unsigned int tagged:1; + epicsUInt32 tagOffset; + epicsUInt32 choice; + epicsUInt32 dataOffset; + epicsUInt32 size; + const chfPluginEnumType *enums; +} chfPluginArgDef; + +/* Simple arguments */ + +#define chfInt32(Struct, Member, Name, Req, Conv) \ + {Name, chfPluginArgInt32, Req, Conv, 0, 0, 0, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfBoolean(Struct, Member, Name, Req, Conv) \ + {Name, chfPluginArgBoolean, Req, Conv, 0, 0, 0, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfDouble(Struct, Member, Name, Req, Conv) \ + {Name, chfPluginArgDouble, Req, Conv, 0, 0, 0, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfString(Struct, Member, Name, Req, Conv) \ + {Name, chfPluginArgString, Req, Conv, 0, 0, 0, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfEnum(Struct, Member, Name, Req, Conv, Enums) \ + {Name, chfPluginArgEnum, Req, Conv, 0, 0, 0, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums} + +/* Tagged arguments */ + +#define chfTagInt32(Struct, Member, Name, Tag, Choice, Req, Conv) \ + {Name, chfPluginArgInt32, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfTagBoolean(Struct, Member, Name, Tag, Choice, Req, Conv) \ + {Name, chfPluginArgBoolean, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfTagDouble(Struct, Member, Name, Tag, Choice, Req, Conv) \ + {Name, chfPluginArgDouble, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfTagString(Struct, Member, Name, Tag, Choice, Req, Conv) \ + {Name, chfPluginArgString, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfTagEnum(Struct, Member, Name, Tag, Choice, Req, Conv, Enums) \ + {Name, chfPluginArgEnum, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \ + OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums} + +#define chfPluginArgEnd {0} + +/* Extra output when parsing and converting */ +#define CHFPLUGINDEBUG 1 + +/** @brief Return the string associated with Enum index 'i'. + * + * @param Enums A null-terminated array of string/integer pairs. + * @param i An Enum index. + * @param def String to be returned when 'i' isn't a valid Enum index. + * @return The string associated with 'i'. + */ +epicsShareFunc const char* chfPluginEnumString(const chfPluginEnumType *Enums, int i, const char* def); + +/** @brief Register a plugin. + * + * @param key The plugin name key that clients will use. + * @param pif Pointer to the plugin's interface. + * @param opts Pointer to the configuration argument description table. + */ +epicsShareFunc int chfPluginRegister(const char* key, const chfPluginIf *pif, const chfPluginArgDef* opts); + +#ifdef __cplusplus +} +#endif + +#endif // CHFPLUGIN_H diff --git a/modules/database/src/ioc/db/cvtBpt.c b/modules/database/src/ioc/db/cvtBpt.c new file mode 100644 index 000000000..2689483ee --- /dev/null +++ b/modules/database/src/ioc/db/cvtBpt.c @@ -0,0 +1,202 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* cvtBpt.c - Convert using breakpoint table + * + * Author: Marty Kraimer + * Date: 04OCT95 + * This is adaptation of old bldCvtTable + */ + +#include "epicsPrint.h" + +#define epicsExportSharedSymbols +#include "cvtTable.h" +#include "dbAccess.h" +#include "dbBase.h" +#include "dbStaticLib.h" + +static brkTable *findBrkTable(short linr) +{ + dbMenu *pdbMenu; + + pdbMenu = dbFindMenu(pdbbase,"menuConvert"); + if (!pdbMenu) { + epicsPrintf("findBrkTable: menuConvert not loaded!\n"); + return NULL; + } + if (linr < 0 || linr >= pdbMenu->nChoice) { + epicsPrintf("findBrkTable: linr=%d but menuConvert only has %d choices\n", + linr,pdbMenu->nChoice); + return NULL; + } + return dbFindBrkTable(pdbbase,pdbMenu->papChoiceValue[linr]); +} + +/* Used by both ao and ai record types */ +long cvtRawToEngBpt(double *pval, short linr, short init, + void **ppbrk, short *plbrk) +{ + double val = *pval; + long status = 0; + brkTable *pbrkTable; + brkInt *pInt, *nInt; + short lbrk; + int number; + + if (linr < 2) + return -1; + + if (init || *ppbrk == NULL) { + pbrkTable = findBrkTable(linr); + if (!pbrkTable) + return S_dbLib_badField; + + *ppbrk = (void *)pbrkTable; + *plbrk = 0; + } else + pbrkTable = (brkTable *)*ppbrk; + + number = pbrkTable->number; + lbrk = *plbrk; + + /* Limit index to the size of the table */ + if (lbrk < 0) + lbrk = 0; + else if (lbrk > number-2) + lbrk = number-2; + + pInt = & pbrkTable->paBrkInt[lbrk]; + nInt = pInt + 1; + + if (nInt->raw > pInt->raw) { + /* raw values increase down the table */ + while (val > nInt->raw) { + lbrk++; + pInt = nInt++; + if (lbrk > number-2) { + status = 1; + break; + } + } + while (val < pInt->raw) { + if (lbrk <= 0) { + status = 1; + break; + } + lbrk--; + nInt = pInt--; + } + } else { + /* raw values decrease down the table */ + while (val <= nInt->raw) { + lbrk++; + pInt = nInt++; + if (lbrk > number-2) { + status = 1; + break; + } + } + while(val > pInt->raw) { + if (lbrk <= 0) { + status = 1; + break; + } + lbrk--; + nInt = pInt--; + } + } + + *plbrk = lbrk; + *pval = pInt->eng + (val - pInt->raw) * pInt->slope; + + return status; +} + +/* Used by the ao record type */ +long cvtEngToRawBpt(double *pval, short linr, short init, + void **ppbrk, short *plbrk) +{ + double val = *pval; + long status = 0; + brkTable *pbrkTable; + brkInt *pInt, *nInt; + short lbrk; + int number; + + if (linr < 2) + return -1; + + if (init || *ppbrk == NULL) { /*must find breakpoint table*/ + pbrkTable = findBrkTable(linr); + if (!pbrkTable) + return S_dbLib_badField; + + *ppbrk = (void *)pbrkTable; + /* start at the beginning */ + *plbrk = 0; + } else + pbrkTable = (brkTable *)*ppbrk; + + number = pbrkTable->number; + lbrk = *plbrk; + + /* Limit index to the size of the table */ + if (lbrk < 0) + lbrk = 0; + else if (lbrk > number-2) + lbrk = number-2; + + pInt = & pbrkTable->paBrkInt[lbrk]; + nInt = pInt + 1; + + if (nInt->eng > pInt->eng) { + /* eng values increase down the table */ + while (val > nInt->eng) { + lbrk++; + pInt = nInt++; + if (lbrk > number-2) { + status = 1; + break; + } + } + while (val < pInt->eng) { + if (lbrk <= 0) { + status = 1; + break; + } + lbrk--; + nInt = pInt--; + } + } else { + /* eng values decrease down the table */ + while (val <= nInt->eng) { + lbrk++; + pInt = nInt++; + if (lbrk > number-2) { + status = 1; + break; + } + } + while (val > pInt->eng) { + if (lbrk <= 0) { + status = 1; + break; + } + lbrk--; + nInt = pInt--; + } + } + + *plbrk = lbrk; + *pval = pInt->raw + (val - pInt->eng) / pInt->slope; + + return status; +} diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c new file mode 100644 index 000000000..973a66836 --- /dev/null +++ b/modules/database/src/ioc/db/dbAccess.c @@ -0,0 +1,1346 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbAccess.c */ +/* + * Original Author: Bob Dalesio + * Current Author: Marty Kraimer + * Andrew Johnson + * Ralph Lange + */ + +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "cantProceed.h" +#include "cvtFast.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsMath.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "errlog.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "caeventmask.h" +#include "callback.h" +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbBkpt.h" +#include "dbCommonPvt.h" +#include "dbConvertFast.h" +#include "dbConvert.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbFldTypes.h" +#include "dbLink.h" +#include "dbLockPvt.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "dbServer.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "devSup.h" +#include "epicsEvent.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + +epicsShareDef struct dbBase *pdbbase = 0; +epicsShareDef volatile int interruptAccept=FALSE; + +/* Hook Routines */ + +epicsShareDef DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL; + +static short mapDBFToDBR[DBF_NTYPES] = { + /* DBF_STRING => */ DBR_STRING, + /* DBF_CHAR => */ DBR_CHAR, + /* DBF_UCHAR => */ DBR_UCHAR, + /* DBF_SHORT => */ DBR_SHORT, + /* DBF_USHORT => */ DBR_USHORT, + /* DBF_LONG => */ DBR_LONG, + /* DBF_ULONG => */ DBR_ULONG, + /* DBF_INT64 => */ DBR_INT64, + /* DBF_UINT64 => */ DBR_UINT64, + /* DBF_FLOAT => */ DBR_FLOAT, + /* DBF_DOUBLE => */ DBR_DOUBLE, + /* DBF_ENUM, => */ DBR_ENUM, + /* DBF_MENU, => */ DBR_ENUM, + /* DBF_DEVICE => */ DBR_ENUM, + /* DBF_INLINK => */ DBR_STRING, + /* DBF_OUTLINK => */ DBR_STRING, + /* DBF_FWDLINK => */ DBR_STRING, + /* DBF_NOACCESS => */ DBR_NOACCESS +}; + +/* + * The number of consecutive attempts that can be made to process an + * active record before a SCAN_ALARM is raised. Active records + * (records with the pact flag set) cannot be processed until + * that flag becomes unset. + */ +#define MAX_LOCK 10 + +/* The following is to handle SPC_AS */ +static SPC_ASCALLBACK spcAsCallback = 0; + +void dbSpcAsRegisterCallback(SPC_ASCALLBACK func) +{ + spcAsCallback = func; +} + +long dbPutSpecial(DBADDR *paddr,int pass) +{ + long int (*pspecial)()=NULL; + rset *prset; + dbCommon *precord = paddr->precord; + long status=0; + long special=paddr->special; + + prset = dbGetRset(paddr); + if(special<100) { /*global processing*/ + if((special==SPC_NOMOD) && (pass==0)) { + status = S_db_noMod; + recGblDbaddrError(status,paddr,"dbPut"); + return(status); + }else if(special==SPC_SCAN){ + if(pass==0) + scanDelete(precord); + else + scanAdd(precord); + }else if((special==SPC_AS) && (pass==1)) { + if(spcAsCallback) (*spcAsCallback)(precord); + } + }else { + if( prset && (pspecial = (prset->special))) { + status=(*pspecial)(paddr,pass); + if(status) return(status); + } else if(pass==0){ + recGblRecSupError(S_db_noSupport,paddr,"dbPut", "special"); + return(S_db_noSupport); + } + } + return(0); +} + +static void get_enum_strs(DBADDR *paddr, char **ppbuffer, + rset *prset,long *options) +{ + short field_type=paddr->field_type; + dbFldDes *pdbFldDes = paddr->pfldDes; + dbMenu *pdbMenu; + dbDeviceMenu *pdbDeviceMenu; + char **papChoice; + unsigned long no_str; + char *ptemp; + struct dbr_enumStrs *pdbr_enumStrs=(struct dbr_enumStrs*)(*ppbuffer); + unsigned int i; + + memset(pdbr_enumStrs,'\0',dbr_enumStrs_size); + switch(field_type) { + case DBF_ENUM: + if( prset && prset->get_enum_strs ) { + (*prset->get_enum_strs)(paddr,pdbr_enumStrs); + } else { + *options = (*options)^DBR_ENUM_STRS;/*Turn off option*/ + } + break; + case DBF_MENU: + pdbMenu = (dbMenu *)pdbFldDes->ftPvt; + no_str = pdbMenu->nChoice; + papChoice= pdbMenu->papChoiceValue; + goto choice_common; + case DBF_DEVICE: + pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt; + if(!pdbDeviceMenu) { + *options = (*options)^DBR_ENUM_STRS;/*Turn off option*/ + break; + } + no_str = pdbDeviceMenu->nChoice; + papChoice = pdbDeviceMenu->papChoice; + goto choice_common; +choice_common: + i = sizeof(pdbr_enumStrs->strs)/ + sizeof(pdbr_enumStrs->strs[0]); + if(ino_str = no_str; + ptemp = &(pdbr_enumStrs->strs[0][0]); + for (i=0; istrs[0])); + *(ptemp+sizeof(pdbr_enumStrs->strs[0])-1) = 0; + } + ptemp += sizeof(pdbr_enumStrs->strs[0]); + } + break; + default: + *options = (*options)^DBR_ENUM_STRS;/*Turn off option*/ + break; + } + *ppbuffer = ((char *)*ppbuffer) + dbr_enumStrs_size; + return; +} + +static void get_graphics(DBADDR *paddr, char **ppbuffer, + rset *prset,long *options) +{ + struct dbr_grDouble grd; + int got_data=FALSE; + + grd.upper_disp_limit = grd.lower_disp_limit = 0.0; + if( prset && prset->get_graphic_double ) { + (*prset->get_graphic_double)(paddr,&grd); + got_data=TRUE; + } + if( (*options) & (DBR_GR_LONG) ) { + char *pbuffer=*ppbuffer; + + if(got_data) { + struct dbr_grLong *pgr=(struct dbr_grLong*)pbuffer; + pgr->upper_disp_limit = (epicsInt32)grd.upper_disp_limit; + pgr->lower_disp_limit = (epicsInt32)grd.lower_disp_limit; + } else { + memset(pbuffer,'\0',dbr_grLong_size); + *options = (*options) ^ DBR_GR_LONG; /*Turn off option*/ + } + *ppbuffer = ((char *)*ppbuffer) + dbr_grLong_size; + } + if( (*options) & (DBR_GR_DOUBLE) ) { + char *pbuffer=*ppbuffer; + + if(got_data) { + struct dbr_grDouble *pgr=(struct dbr_grDouble*)pbuffer; + pgr->upper_disp_limit = grd.upper_disp_limit; + pgr->lower_disp_limit = grd.lower_disp_limit; + } else { + memset(pbuffer,'\0',dbr_grDouble_size); + *options = (*options) ^ DBR_GR_DOUBLE; /*Turn off option*/ + } + *ppbuffer = ((char *)*ppbuffer) + dbr_grDouble_size; + } + return; +} + +static void get_control(DBADDR *paddr, char **ppbuffer, + rset *prset,long *options) +{ + struct dbr_ctrlDouble ctrld; + int got_data=FALSE; + + ctrld.upper_ctrl_limit = ctrld.lower_ctrl_limit = 0.0; + if( prset && prset->get_control_double ) { + (*prset->get_control_double)(paddr,&ctrld); + got_data=TRUE; + } + if( (*options) & (DBR_CTRL_LONG) ) { + char *pbuffer=*ppbuffer; + + if(got_data) { + struct dbr_ctrlLong *pctrl=(struct dbr_ctrlLong*)pbuffer; + pctrl->upper_ctrl_limit = (epicsInt32)ctrld.upper_ctrl_limit; + pctrl->lower_ctrl_limit = (epicsInt32)ctrld.lower_ctrl_limit; + } else { + memset(pbuffer,'\0',dbr_ctrlLong_size); + *options = (*options) ^ DBR_CTRL_LONG; /*Turn off option*/ + } + *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlLong_size; + } + if( (*options) & (DBR_CTRL_DOUBLE) ) { + char *pbuffer=*ppbuffer; + + if(got_data) { + struct dbr_ctrlDouble *pctrl=(struct dbr_ctrlDouble*)pbuffer; + pctrl->upper_ctrl_limit = ctrld.upper_ctrl_limit; + pctrl->lower_ctrl_limit = ctrld.lower_ctrl_limit; + } else { + memset(pbuffer,'\0',dbr_ctrlDouble_size); + *options = (*options) ^ DBR_CTRL_DOUBLE; /*Turn off option*/ + } + *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlDouble_size; + } + return; +} + +static void get_alarm(DBADDR *paddr, char **ppbuffer, + rset *prset, long *options) +{ + char *pbuffer = *ppbuffer; + struct dbr_alDouble ald = {epicsNAN, epicsNAN, epicsNAN, epicsNAN}; + long no_data = TRUE; + + if (prset && prset->get_alarm_double) + no_data = prset->get_alarm_double(paddr, &ald); + + if (*options & DBR_AL_LONG) { + struct dbr_alLong *pal = (struct dbr_alLong*) pbuffer; + + pal->upper_alarm_limit = (epicsInt32) ald.upper_alarm_limit; + pal->upper_warning_limit = (epicsInt32) ald.upper_warning_limit; + pal->lower_warning_limit = (epicsInt32) ald.lower_warning_limit; + pal->lower_alarm_limit = (epicsInt32) ald.lower_alarm_limit; + + if (no_data) + *options ^= DBR_AL_LONG; /*Turn off option*/ + + *ppbuffer += dbr_alLong_size; + } + if (*options & DBR_AL_DOUBLE) { + struct dbr_alDouble *pal = (struct dbr_alDouble*) pbuffer; + + pal->upper_alarm_limit = ald.upper_alarm_limit; + pal->upper_warning_limit = ald.upper_warning_limit; + pal->lower_warning_limit = ald.lower_warning_limit; + pal->lower_alarm_limit = ald.lower_alarm_limit; + + if (no_data) + *options ^= DBR_AL_DOUBLE; /*Turn off option*/ + + *ppbuffer += dbr_alDouble_size; + } +} + +/* + * This code relies on *poriginal being aligned and all increments done by the + * blocks only changing the buffer pointer in a way that does not break alignment. + */ +static void getOptions(DBADDR *paddr, char **poriginal, long *options, + void *pflin) +{ + db_field_log *pfl= (db_field_log *)pflin; + rset *prset; + short field_type; + dbCommon *pcommon; + char *pbuffer = *poriginal; + + if (!pfl || pfl->type == dbfl_type_rec) + field_type = paddr->field_type; + else + field_type = pfl->field_type; + prset=dbGetRset(paddr); + /* Process options */ + pcommon = paddr->precord; + if( (*options) & DBR_STATUS ) { + unsigned short *pushort = (unsigned short *)pbuffer; + + if (!pfl || pfl->type == dbfl_type_rec) { + *pushort++ = pcommon->stat; + *pushort++ = pcommon->sevr; + } else { + *pushort++ = pfl->stat; + *pushort++ = pfl->sevr; + } + *pushort++ = pcommon->acks; + *pushort++ = pcommon->ackt; + pbuffer = (char *)pushort; + } + if( (*options) & DBR_UNITS ) { + memset(pbuffer,'\0',dbr_units_size); + if( prset && prset->get_units ){ + (*prset->get_units)(paddr, pbuffer); + pbuffer[DB_UNITS_SIZE-1] = '\0'; + } else { + *options ^= DBR_UNITS; /*Turn off DBR_UNITS*/ + } + pbuffer += dbr_units_size; + } + if( (*options) & DBR_PRECISION ) { + memset(pbuffer, '\0', dbr_precision_size); + if((field_type==DBF_FLOAT || field_type==DBF_DOUBLE) + && prset && prset->get_precision ){ + (*prset->get_precision)(paddr,(long *)pbuffer); + } else { + *options ^= DBR_PRECISION; /*Turn off DBR_PRECISION*/ + } + pbuffer += dbr_precision_size; + } + if( (*options) & DBR_TIME ) { + epicsUInt32 *ptime = (epicsUInt32 *)pbuffer; + + if (!pfl || pfl->type == dbfl_type_rec) { + *ptime++ = pcommon->time.secPastEpoch; + *ptime++ = pcommon->time.nsec; + } else { + *ptime++ = pfl->time.secPastEpoch; + *ptime++ = pfl->time.nsec; + } + pbuffer = (char *)ptime; + } + if( (*options) & DBR_ENUM_STRS ) + get_enum_strs(paddr, &pbuffer, prset, options); + if( (*options) & (DBR_GR_LONG|DBR_GR_DOUBLE )) + get_graphics(paddr, &pbuffer, prset, options); + if((*options) & (DBR_CTRL_LONG | DBR_CTRL_DOUBLE )) + get_control(paddr, &pbuffer, prset, options); + if((*options) & (DBR_AL_LONG | DBR_AL_DOUBLE )) + get_alarm(paddr, &pbuffer, prset, options); + *poriginal = pbuffer; +} + +rset * dbGetRset(const struct dbAddr *paddr) +{ + struct dbFldDes *pfldDes = paddr->pfldDes; + + if(!pfldDes) return(0); + return(pfldDes->pdbRecordType->prset); +} + +long dbPutAttribute( + const char *recordTypename, const char *name, const char *value) +{ + DBENTRY dbEntry; + DBENTRY *pdbEntry = &dbEntry; + long status = 0; + + if (!pdbbase) + return S_db_notFound; + if (!name) { + status = S_db_badField; + goto done; + } + if (!value) + value = ""; + dbInitEntry(pdbbase, pdbEntry); + status = dbFindRecordType(pdbEntry, recordTypename); + if (!status) + status = dbPutRecordAttribute(pdbEntry, name, value); + dbFinishEntry(pdbEntry); +done: + if (status) + errMessage(status, "dbPutAttribute failure"); + return status; +} + +int dbIsValueField(const struct dbFldDes *pdbFldDes) +{ + if (pdbFldDes->pdbRecordType->indvalFlddes == pdbFldDes->indRecordType) + return TRUE; + else + return FALSE; +} + +int dbGetFieldIndex(const struct dbAddr *paddr) +{ + return paddr->pfldDes->indRecordType; +} + +/* + * Process a record if its scan field is passive. + * Will notify if processing is complete by callback. + * (only if you are interested in completion) + */ +long dbScanPassive(dbCommon *pfrom, dbCommon *pto) +{ + /* if not passive just return success */ + if (pto->scan != 0) + return 0; + + if (pfrom && pfrom->ppn) + dbNotifyAdd(pfrom,pto); + return dbProcess(pto); +} + +/* + * Process the record. + * 1. Check for breakpoints. + * 2. Check the process active flag (PACT). + * 3. Check the disable link. + * 4. Check the RSET (record support entry table) exists. + * 5. Run the process routine specific to the record type. + * 6. Check to see if record contents should be automatically printed. + */ +long dbProcess(dbCommon *precord) +{ + rset *prset = precord->rset; + dbRecordType *pdbRecordType = precord->rdes; + unsigned char tpro = precord->tpro; + char context[40] = ""; + long status = 0; + int *ptrace; + int set_trace = FALSE; + dbFldDes *pdbFldDes; + int callNotifyCompletion = FALSE; + + ptrace = dbLockSetAddrTrace(precord); + /* + * Note that it is likely that if any changes are made + * to dbProcess() corresponding changes will have to + * be made in the breakpoint handler. + */ + + /* see if there are any stopped records or breakpoints */ + if (lset_stack_count != 0) { + /* + * Check to see if the record should be processed + * and activate breakpoint accordingly. If this + * function call returns non-zero, skip record + * support and fall out of dbProcess(). This is + * done so that a dbContTask() can be spawned to + * take over record processing for the lock set + * containing a breakpoint. + */ + if (dbBkpt(precord)) + goto all_done; + } + + /* check for trace processing*/ + if (tpro) { + if (!*ptrace) { + *ptrace = 1; + set_trace = TRUE; + } + } + + if (*ptrace) { + /* Identify this thread's client from server layer */ + if (dbServerClient(context, sizeof(context))) { + /* No client, use thread name */ + strncpy(context, epicsThreadGetNameSelf(), sizeof(context)); + context[sizeof(context) - 1] = 0; + } + } + + /* If already active dont process */ + if (precord->pact) { + unsigned short monitor_mask; + + if (*ptrace) + printf("%s: Active %s\n", context, precord->name); + + /* raise scan alarm after MAX_LOCK times */ + if ((precord->stat == SCAN_ALARM) || + (precord->lcnt++ < MAX_LOCK) || + (precord->sevr >= INVALID_ALARM)) goto all_done; + + recGblSetSevr(precord, SCAN_ALARM, INVALID_ALARM); + monitor_mask = recGblResetAlarms(precord); + monitor_mask |= DBE_VALUE|DBE_LOG; + pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes]; + db_post_events(precord, + (void *)(((char *)precord) + pdbFldDes->offset), + monitor_mask); + goto all_done; + } + else + precord->lcnt = 0; + + /* + * Check the record disable link. A record will not be + * processed if the value retrieved through this link + * is equal to constant set in the record's disv field. + */ + status = dbGetLink(&precord->sdis, DBR_SHORT, &precord->disa, 0, 0); + + /* if disabled check disable alarm severity and return success */ + if (precord->disa == precord->disv) { + if (*ptrace) + printf("%s: Disabled %s\n", context, precord->name); + + /*take care of caching and notifyCompletion*/ + precord->rpro = FALSE; + precord->putf = FALSE; + callNotifyCompletion = TRUE; + + /* raise disable alarm */ + if (precord->stat == DISABLE_ALARM) + goto all_done; + + precord->sevr = precord->diss; + precord->stat = DISABLE_ALARM; + precord->nsev = 0; + precord->nsta = 0; + db_post_events(precord, &precord->stat, DBE_VALUE); + db_post_events(precord, &precord->sevr, DBE_VALUE); + pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes]; + db_post_events(precord, + (void *)(((char *)precord) + pdbFldDes->offset), + DBE_VALUE|DBE_ALARM); + goto all_done; + } + + /* locate record processing routine */ + /* FIXME: put this in iocInit() !!! */ + if (!prset || !prset->process) { + callNotifyCompletion = TRUE; + precord->pact = 1;/*set pact so error is issued only once*/ + recGblRecordError(S_db_noRSET, (void *)precord, "dbProcess"); + status = S_db_noRSET; + if (*ptrace) + printf("%s: No RSET for %s\n", context, precord->name); + goto all_done; + } + + if (*ptrace) + printf("%s: Process %s\n", context, precord->name); + + /* process record */ + status = prset->process(precord); + + /* Print record's fields if PRINT_MASK set in breakpoint field */ + if (lset_stack_count != 0) { + dbPrint(precord); + } + +all_done: + if (set_trace) + *ptrace = 0; + if (callNotifyCompletion && precord->ppn) + dbNotifyCompletion(precord); + + return status; +} + +/* + * Fill out a database structure (*paddr) for + * a record given by the name "pname." + * + * Returns error codes from StaticLib module, not + * from dbAccess. + */ +long dbNameToAddr(const char *pname, DBADDR *paddr) +{ + DBENTRY dbEntry; + dbFldDes *pflddes; + long status = 0; + short dbfType; + + if (!pname || !*pname || !pdbbase) + return S_db_notFound; + + dbInitEntry(pdbbase, &dbEntry); + status = dbFindRecordPart(&dbEntry, &pname); + if (status) goto finish; + + if (*pname == '.') ++pname; + status = dbFindFieldPart(&dbEntry, &pname); + if (status == S_dbLib_fieldNotFound) + status = dbGetAttributePart(&dbEntry, &pname); + if (status) goto finish; + + pflddes = dbEntry.pflddes; + dbfType = pflddes->field_type; + + paddr->precord = dbEntry.precnode->precord; + paddr->pfield = dbEntry.pfield; + paddr->pfldDes = pflddes; + paddr->no_elements = 1; + paddr->field_type = dbfType; + paddr->field_size = pflddes->size; + paddr->special = pflddes->special; + paddr->dbr_field_type = mapDBFToDBR[dbfType]; + + if (paddr->special == SPC_DBADDR) { + rset *prset = dbGetRset(paddr); + + /* Let record type modify paddr */ + if (prset && prset->cvt_dbaddr) { + status = prset->cvt_dbaddr(paddr); + if (status) + goto finish; + dbfType = paddr->field_type; + } + } + + /* Handle field modifiers */ + if (*pname++ == '$') { + /* Some field types can be accessed as char arrays */ + if (dbfType == DBF_STRING) { + paddr->no_elements = paddr->field_size; + paddr->field_type = DBF_CHAR; + paddr->field_size = 1; + paddr->dbr_field_type = DBR_CHAR; + } else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) { + /* Clients see a char array, but keep original dbfType */ + paddr->no_elements = PVLINK_STRINGSZ; + paddr->field_size = 1; + paddr->dbr_field_type = DBR_CHAR; + } else { + status = S_dbLib_fieldNotFound; + goto finish; + } + } + +finish: + dbFinishEntry(&dbEntry); + return status; +} + +void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry) +{ + struct dbCommon *prec = paddr->precord; + dbCommonPvt *ppvt = CONTAINER(prec, dbCommonPvt, common); + + memset((char *)pdbentry,'\0',sizeof(DBENTRY)); + + pdbentry->pdbbase = pdbbase; + pdbentry->precordType = prec->rdes; + pdbentry->precnode = ppvt->recnode; + pdbentry->pflddes = paddr->pfldDes; + pdbentry->pfield = paddr->pfield; + pdbentry->indfield = paddr->pfldDes->indRecordType; +} + +void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY *pdbentry) +{ + dbCommonPvt *ppvt = CONTAINER(prec, dbCommonPvt, common); + + memset((char *)pdbentry,'\0',sizeof(DBENTRY)); + + pdbentry->pdbbase = pdbbase; + pdbentry->precordType = prec->rdes; + pdbentry->precnode = ppvt->recnode; +} + +long dbValueSize(short dbr_type) +{ + /* sizes for value associated with each DBR request type */ + static long size[] = { + MAX_STRING_SIZE, /* STRING */ + sizeof(epicsInt8), /* CHAR */ + sizeof(epicsUInt8), /* UCHAR */ + sizeof(epicsInt16), /* SHORT */ + sizeof(epicsUInt16), /* USHORT */ + sizeof(epicsInt32), /* LONG */ + sizeof(epicsUInt32), /* ULONG */ + sizeof(epicsInt64), /* INT64 */ + sizeof(epicsUInt64), /* UINT64 */ + sizeof(epicsFloat32), /* FLOAT */ + sizeof(epicsFloat64), /* DOUBLE */ + sizeof(epicsEnum16)}; /* ENUM */ + + return(size[dbr_type]); +} + + +long dbBufferSize(short dbr_type, long options, long no_elements) +{ + long nbytes=0; + + nbytes += dbValueSize(dbr_type) * no_elements; + if (options & DBR_STATUS) nbytes += dbr_status_size; + if (options & DBR_UNITS) nbytes += dbr_units_size; + if (options & DBR_PRECISION) nbytes += dbr_precision_size; + if (options & DBR_TIME) nbytes += dbr_time_size; + if (options & DBR_ENUM_STRS) nbytes += dbr_enumStrs_size; + if (options & DBR_GR_LONG) nbytes += dbr_grLong_size; + if (options & DBR_GR_DOUBLE) nbytes += dbr_grDouble_size; + if (options & DBR_CTRL_LONG) nbytes += dbr_ctrlLong_size; + if (options & DBR_CTRL_DOUBLE) nbytes += dbr_ctrlDouble_size; + if (options & DBR_AL_LONG) nbytes += dbr_alLong_size; + if (options & DBR_AL_DOUBLE) nbytes += dbr_alDouble_size; + return(nbytes); +} +int dbLoadDatabase(const char *file, const char *path, const char *subs) +{ + return dbReadDatabase(&pdbbase, file, path, subs); +} + +int dbLoadRecords(const char* file, const char* subs) +{ + int status = dbReadDatabase(&pdbbase, file, 0, subs); + + if (!status && dbLoadRecordsHook) + dbLoadRecordsHook(file, subs); + return status; +} + + +static long getLinkValue(DBADDR *paddr, short dbrType, + char *pbuf, long *nRequest) +{ + dbCommon *precord = paddr->precord; + dbFldDes *pfldDes = paddr->pfldDes; + /* size of pbuf storage in bytes, including space for trailing nil */ + int maxlen; + DBENTRY dbEntry; + long status; + long nReq = nRequest ? *nRequest : 1; + + /* dbFindRecord() below will always succeed as we have a + * valid DBADDR, so no point to check again. + * Request for zero elements always succeeds + */ + if(!nReq) + return 0; + + switch (dbrType) { + case DBR_STRING: + maxlen = MAX_STRING_SIZE; + nReq = 1; + break; + + case DBR_DOUBLE: /* Needed for dbCa links */ + if (nRequest) *nRequest = 1; + *(double *)pbuf = epicsNAN; + return 0; + + case DBR_CHAR: + case DBR_UCHAR: + maxlen = nReq; + break; + default: + return S_db_badDbrtype; + } + + dbInitEntry(pdbbase, &dbEntry); + status = dbFindRecord(&dbEntry, precord->name); + if (!status) status = dbFindField(&dbEntry, pfldDes->name); + if (!status) { + const char *rtnString = dbGetString(&dbEntry); + + strncpy(pbuf, rtnString, maxlen-1); + pbuf[maxlen-1] = 0; + if(dbrType!=DBR_STRING) + nReq = strlen(pbuf)+1; + if(nRequest) *nRequest = nReq; + } + dbFinishEntry(&dbEntry); + return status; +} + +static long getAttrValue(DBADDR *paddr, short dbrType, + char *pbuf, long *nRequest) +{ + int maxlen; + long nReq = nRequest ? *nRequest : 1; + + if (!paddr->pfield) return S_db_badField; + + switch (dbrType) { + case DBR_STRING: + maxlen = MAX_STRING_SIZE; + nReq = 1; + break; + + case DBR_CHAR: + case DBR_UCHAR: + maxlen = nReq; + break; + + /* else fall through ... */ + default: + return S_db_badDbrtype; + } + + strncpy(pbuf, paddr->pfield, maxlen-1); + pbuf[maxlen-1] = 0; + if(dbrType!=DBR_STRING) + nReq = strlen(pbuf)+1; + if(nRequest) *nRequest = nReq; + return 0; +} + +long dbGetField(DBADDR *paddr,short dbrType, + void *pbuffer, long *options, long *nRequest, void *pflin) +{ + dbCommon *precord = paddr->precord; + long status = 0; + + dbScanLock(precord); + status = dbGet(paddr, dbrType, pbuffer, options, nRequest, pflin); + dbScanUnlock(precord); + return status; +} + +long dbGet(DBADDR *paddr, short dbrType, + void *pbuffer, long *options, long *nRequest, void *pflin) +{ + char *pbuf = pbuffer; + void *pfieldsave = paddr->pfield; + db_field_log *pfl = (db_field_log *)pflin; + short field_type; + long capacity, no_elements, offset; + rset *prset; + long status = 0; + + if (options && *options) + getOptions(paddr, &pbuf, options, pflin); + if (nRequest && *nRequest == 0) + return 0; + + if (!pfl || pfl->type == dbfl_type_rec) { + field_type = paddr->field_type; + no_elements = capacity = paddr->no_elements; + + /* Update field info from record + * may modify paddr->pfield + */ + if (paddr->pfldDes->special == SPC_DBADDR && + (prset = dbGetRset(paddr)) && + prset->get_array_info) { + status = prset->get_array_info(paddr, &no_elements, &offset); + } else + offset = 0; + } else { + field_type = pfl->field_type; + no_elements = capacity = pfl->no_elements; + offset = 0; + } + + if (field_type >= DBF_INLINK && field_type <= DBF_FWDLINK) { + status = getLinkValue(paddr, dbrType, pbuf, nRequest); + goto done; + } + + if (paddr->special == SPC_ATTRIBUTE) { + status = getAttrValue(paddr, dbrType, pbuf, nRequest); + goto done; + } + + /* Check for valid request */ + if (INVALID_DB_REQ(dbrType) || field_type > DBF_DEVICE) { + char message[80]; + + sprintf(message, "dbGet: Request type is %d\n", dbrType); + recGblDbaddrError(S_db_badDbrtype, paddr, message); + status = S_db_badDbrtype; + goto done; + } + + if (offset == 0 && (!nRequest || no_elements == 1)) { + if (nRequest) + *nRequest = 1; + if (!pfl || pfl->type == dbfl_type_rec) { + status = dbFastGetConvertRoutine[field_type][dbrType] + (paddr->pfield, pbuf, paddr); + } else { + DBADDR localAddr = *paddr; /* Structure copy */ + + localAddr.field_type = pfl->field_type; + localAddr.field_size = pfl->field_size; + localAddr.no_elements = pfl->no_elements; + if (pfl->type == dbfl_type_val) + localAddr.pfield = (char *) &pfl->u.v.field; + else + localAddr.pfield = (char *) pfl->u.r.field; + status = dbFastGetConvertRoutine[field_type][dbrType] + (localAddr.pfield, pbuf, &localAddr); + } + } else { + long n; + GETCONVERTFUNC convert; + + if (nRequest) { + if (no_elements < *nRequest) + *nRequest = no_elements; + n = *nRequest; + } else { + n = 1; + } + convert = dbGetConvertRoutine[field_type][dbrType]; + if (!convert) { + char message[80]; + + sprintf(message, "dbGet: Missing conversion for [%d][%d]\n", + field_type, dbrType); + recGblDbaddrError(S_db_badDbrtype, paddr, message); + status = S_db_badDbrtype; + goto done; + } + /* convert data into the caller's buffer */ + if (n <= 0) { + ;/*do nothing*/ + } else if (!pfl || pfl->type == dbfl_type_rec) { + status = convert(paddr, pbuf, n, capacity, offset); + } else { + DBADDR localAddr = *paddr; /* Structure copy */ + + localAddr.field_type = pfl->field_type; + localAddr.field_size = pfl->field_size; + localAddr.no_elements = pfl->no_elements; + if (pfl->type == dbfl_type_val) + localAddr.pfield = (char *) &pfl->u.v.field; + else + localAddr.pfield = (char *) pfl->u.r.field; + status = convert(&localAddr, pbuf, n, capacity, offset); + } + + if(!status && dbrType==DBF_CHAR && nRequest && + paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING) + { + /* long string ensure nil and truncate to actual length */ + long nReq = *nRequest; + pbuf[nReq-1] = '\0'; + *nRequest = strlen(pbuf)+1; + } + } +done: + paddr->pfield = pfieldsave; + return status; +} + +devSup* dbDTYPtoDevSup(dbRecordType *prdes, int dtyp) { + return (devSup *)ellNth(&prdes->devList, dtyp+1); +} + +devSup* dbDSETtoDevSup(dbRecordType *prdes, struct dset *pdset) { + devSup *pdevSup = (devSup *)ellFirst(&prdes->devList); + while (pdevSup) { + if (pdset == pdevSup->pdset) return pdevSup; + pdevSup = (devSup *)ellNext(&pdevSup->node); + } + return NULL; +} + +static long dbPutFieldLink(DBADDR *paddr, + short dbrType, const void *pbuffer, long nRequest) +{ + dbLinkInfo link_info; + DBADDR *pdbaddr = NULL; + dbCommon *precord = paddr->precord; + dbCommon *lockrecs[2]; + dbLocker locker; + dbFldDes *pfldDes = paddr->pfldDes; + long special = paddr->special; + struct link *plink = (struct link *)paddr->pfield; + const char *pstring = (const char *)pbuffer; + struct dsxt *old_dsxt = NULL; + struct dset *new_dset = NULL; + struct dsxt *new_dsxt = NULL; + devSup *new_devsup = NULL; + long status; + int isDevLink; + short scan; + + STATIC_ASSERT(DBLOCKER_NALLOC>=2); + + switch (dbrType) { + case DBR_CHAR: + case DBR_UCHAR: + if (pstring[nRequest - 1] != '\0') + return S_db_badDbrtype; + break; + + case DBR_STRING: + break; + + default: + return S_db_badDbrtype; + } + + status = dbParseLink(pstring, pfldDes->field_type, &link_info, 0); + if (status) + return status; + + if (link_info.ltype == PV_LINK && + (link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) { + DBADDR tempaddr; + + if (dbNameToAddr(link_info.target, &tempaddr)==0) { + /* This will become a DB link. */ + pdbaddr = malloc(sizeof(*pdbaddr)); + if (!pdbaddr) { + status = S_db_noMemory; + goto cleanup; + } + *pdbaddr = tempaddr; /* struct copy */ + } + } + + isDevLink = ellCount(&precord->rdes->devList) > 0 && + pfldDes->isDevLink; + + memset(&locker, 0, sizeof(locker)); + lockrecs[0] = precord; + lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL; + dbLockerPrepare(&locker, lockrecs, 2); + + dbScanLockMany(&locker); + + scan = precord->scan; + + if (isDevLink) { + new_devsup = dbDTYPtoDevSup(precord->rdes, precord->dtyp); + if (new_devsup) { + new_dset = new_devsup->pdset; + new_dsxt = new_devsup->pdsxt; + } + } + + if (dbCanSetLink(plink, &link_info, new_devsup)) { + /* link type mis-match prevents assignment */ + status = S_dbLib_badField; + goto unlock; + } + + if (isDevLink) { + if (precord->dset) { + devSup *old_devsup = dbDSETtoDevSup(precord->rdes, precord->dset); + + if (old_devsup) + old_dsxt = old_devsup->pdsxt; + } + + if (new_dsxt == NULL || + new_dsxt->add_record == NULL || + (precord->dset && old_dsxt == NULL) || + (old_dsxt && old_dsxt->del_record == NULL)) { + status = S_db_noSupport; + goto unlock; + } + + if (scan == menuScanI_O_Intr) { + scanDelete(precord); + precord->scan = menuScanPassive; + } + + if (old_dsxt) { + status = old_dsxt->del_record(precord); + if (status) + goto restoreScan; + } + } + + if (dbLinkIsDefined(plink)) { + dbRemoveLink(&locker, plink); /* Clear out old link */ + } + else if (!isDevLink) { + status = S_db_badHWaddr; + goto restoreScan; + } + + if (special) status = dbPutSpecial(paddr, 0); + + if (!status) status = dbSetLink(plink, &link_info, new_devsup); + + if (!status && special) status = dbPutSpecial(paddr, 1); + + if (status) { + if (isDevLink) { + precord->dset = NULL; + precord->pact = TRUE; + } + goto postScanEvent; + } + + if (isDevLink) { + precord->dpvt = NULL; + precord->dset = new_dset; + precord->pact = FALSE; + + status = new_dsxt->add_record(precord); + if (status) { + precord->dset = NULL; + precord->pact = TRUE; + goto postScanEvent; + } + } + + switch (plink->type) { /* New link type */ + case PV_LINK: + case CONSTANT: + case JSON_LINK: + dbAddLink(&locker, plink, pfldDes->field_type, pdbaddr); + break; + + case DB_LINK: + case CA_LINK: + case MACRO_LINK: + break; /* should never get here */ + + default: /* Hardware address */ + if (!isDevLink) { + status = S_db_badHWaddr; + goto postScanEvent; + } + break; + } + db_post_events(precord, plink, DBE_VALUE | DBE_LOG); + +restoreScan: + if (isDevLink && + scan == menuScanI_O_Intr) { /* undo scanDelete() */ + precord->scan = scan; + scanAdd(precord); + } +postScanEvent: + if (scan != precord->scan) + db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG); +unlock: + dbScanUnlockMany(&locker); + dbLockerFinalize(&locker); +cleanup: + free(link_info.target); + return status; +} + +long dbPutField(DBADDR *paddr, short dbrType, + const void *pbuffer, long nRequest) +{ + long status = 0; + long special = paddr->special; + dbFldDes *pfldDes = paddr->pfldDes; + dbCommon *precord = paddr->precord; + short dbfType = paddr->field_type; + + if (special == SPC_ATTRIBUTE) + return S_db_noMod; + + /*check for putField disabled*/ + if (precord->disp && paddr->pfield != &precord->disp) + return S_db_putDisabled; + + if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) + return dbPutFieldLink(paddr, dbrType, pbuffer, nRequest); + + dbScanLock(precord); + status = dbPut(paddr, dbrType, pbuffer, nRequest); + if (status == 0) { + if (paddr->pfield == &precord->proc || + (pfldDes->process_passive && + precord->scan == 0 && + dbrType < DBR_PUT_ACKT)) { + if (precord->pact) { + if (precord->tpro) + printf("%s: Active %s\n", + epicsThreadGetNameSelf(), precord->name); + precord->rpro = TRUE; + } else { + /* indicate that dbPutField called dbProcess */ + precord->putf = TRUE; + status = dbProcess(precord); + } + } + } + dbScanUnlock(precord); + return status; +} + +static long putAckt(DBADDR *paddr, const void *pbuffer, long nRequest, + long no_elements, long offset) +{ + dbCommon *precord = paddr->precord; + const unsigned short *ptrans = pbuffer; + + if (*ptrans == precord->ackt) return 0; + precord->ackt = *ptrans; + db_post_events(precord, &precord->ackt, DBE_VALUE | DBE_ALARM); + if (!precord->ackt && + precord->acks > precord->sevr) { + precord->acks = precord->sevr; + db_post_events(precord, &precord->acks, DBE_VALUE | DBE_ALARM); + } + db_post_events(precord, NULL, DBE_ALARM); + return 0; +} + +static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest, + long no_elements, long offset) +{ + dbCommon *precord = paddr->precord; + const unsigned short *psev = pbuffer; + + if (*psev >= precord->acks) { + precord->acks = 0; + db_post_events(precord, &precord->acks, DBE_VALUE | DBE_ALARM); + db_post_events(precord, NULL, DBE_ALARM); + } + return 0; +} + +long dbPut(DBADDR *paddr, short dbrType, + const void *pbuffer, long nRequest) +{ + dbCommon *precord = paddr->precord; + short field_type = paddr->field_type; + long no_elements = paddr->no_elements; + long special = paddr->special; + void *pfieldsave = paddr->pfield; + rset *prset = dbGetRset(paddr); + long status = 0; + long offset; + dbFldDes *pfldDes; + int isValueField; + + if (special == SPC_ATTRIBUTE) + return S_db_noMod; + + if (dbrType == DBR_PUT_ACKT && field_type <= DBF_DEVICE) { + return putAckt(paddr, pbuffer, 1, 1, 0); + } else if (dbrType == DBR_PUT_ACKS && field_type <= DBF_DEVICE) { + return putAcks(paddr, pbuffer, 1, 1, 0); + } else if (INVALID_DB_REQ(dbrType) || field_type > DBF_DEVICE) { + char message[80]; + + sprintf(message, "dbPut: Request type is %d", dbrType); + recGblDbaddrError(S_db_badDbrtype, paddr, message); + return S_db_badDbrtype; + } + + if (special) { + status = dbPutSpecial(paddr, 0); + if (status) return status; + } + + if (paddr->pfldDes->special == SPC_DBADDR && + prset && prset->get_array_info) { + long dummy; + + status = prset->get_array_info(paddr, &dummy, &offset); + /* paddr->pfield may be modified */ + if (status) goto done; + } else + offset = 0; + + if (no_elements <= 1) { + status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer, + paddr->pfield, paddr); + nRequest = 1; + } else { + if (no_elements < nRequest) + nRequest = no_elements; + status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer, + nRequest, no_elements, offset); + } + + /* update array info */ + if (!status && + paddr->pfldDes->special == SPC_DBADDR && + prset && prset->put_array_info) { + status = prset->put_array_info(paddr, nRequest); + } + + /* Always do special processing if needed */ + if (special) { + long status2 = dbPutSpecial(paddr, 1); + if (status2) goto done; + } + if (status) goto done; + + /* Propagate monitor events for this field, */ + /* unless the field is VAL and PP is true. */ + pfldDes = paddr->pfldDes; + isValueField = dbIsValueField(pfldDes); + if (isValueField) precord->udf = FALSE; + if (precord->mlis.count && + !(isValueField && pfldDes->process_passive)) + db_post_events(precord, pfieldsave, DBE_VALUE | DBE_LOG); + /* If this field is a property (metadata) field, + * then post a property change event (even if the field + * didn't change). + */ + if (precord->mlis.count && pfldDes->prop) + db_post_events(precord, NULL, DBE_PROPERTY); +done: + paddr->pfield = pfieldsave; + return status; +} + diff --git a/modules/database/src/ioc/db/dbAccess.h b/modules/database/src/ioc/db/dbAccess.h new file mode 100644 index 000000000..96246b302 --- /dev/null +++ b/modules/database/src/ioc/db/dbAccess.h @@ -0,0 +1,29 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbAccess.h */ + +#ifndef INCdbAccessh +#define INCdbAccessh + +#include "dbDefs.h" +#include "epicsTime.h" +#include "caeventmask.h" +#include "dbFldTypes.h" +#include "link.h" +#include "dbBase.h" +#include "shareLib.h" +#include "dbAddr.h" +#include "dbLock.h" +#include "dbAccessDefs.h" +#include "dbLink.h" +#include "dbCa.h" +#include "dbCommon.h" +#include "db_field_log.h" + +#endif /*INCdbAccessh*/ diff --git a/modules/database/src/ioc/db/dbAccessDefs.h b/modules/database/src/ioc/db/dbAccessDefs.h new file mode 100644 index 000000000..cc45b17fe --- /dev/null +++ b/modules/database/src/ioc/db/dbAccessDefs.h @@ -0,0 +1,252 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbAccessDefs.h */ + +#ifndef INCdbAccessDefsh +#define INCdbAccessDefsh + +#ifdef epicsExportSharedSymbols +# define INCLdb_accessh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsTypes.h" +#include "epicsTime.h" +#include "dbBase.h" +#include "dbAddr.h" +#include "recSup.h" + +#ifdef INCLdb_accessh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +epicsShareExtern struct dbBase *pdbbase; +epicsShareExtern volatile int interruptAccept; + +/* The database field and request types are defined in dbFldTypes.h*/ +/* Data Base Request Options */ +#define DBR_STATUS 0x00000001 +#define DBR_UNITS 0x00000002 +#define DBR_PRECISION 0x00000004 +#define DBR_TIME 0x00000008 +#define DBR_ENUM_STRS 0x00000010 +#define DBR_GR_LONG 0x00000020 +#define DBR_GR_DOUBLE 0x00000040 +#define DBR_CTRL_LONG 0x00000080 +#define DBR_CTRL_DOUBLE 0x00000100 +#define DBR_AL_LONG 0x00000200 +#define DBR_AL_DOUBLE 0x00000400 + +/********************************************************************** + * The next page contains macros for defining requests. + * As an example the following defines a buffer to accept an array + * of 10 float values + DBR_STATUS and DBR_TIME options + * + * struct { + * DBRstatus + * DBRtime + * epicsFloat32 value[10] + * } buffer; + * + * IMPORTANT!! The DBRoptions must be given in the order that they + * appear in the Data Base Request Options #defines + * + * The associated dbGetField call is: + * + * long options,number_elements; + * ... + * options = DBR_STATUS|DBR_TIME; + * number_elements = 10; + * rtnval=dbGetField(paddr,DBR_FLOAT,&buffer,&options,&number_elements); + * + * When dbGetField returns: + * rtnval is error status (0 means success) + * options has a bit set for each option that was accepted + * number_elements is actual number of elements obtained + * + * The individual items can be refered to by the expressions:: + * + * buffer.status + * buffer.severity + * buffer.err_status + * buffer.epoch_seconds + * buffer.nano_seconds + * buffer.value[i] + * + * The following is also a valid declaration: + * + * typedef struct { + * DBRstatus + * DBRtime + * epicsFloat32 value[10] + * } MYBUFFER; + * + * With this definition you can give definitions such as the following: + * + * MYBUFFER *pbuf1; + * MYBUFFER buf; + *************************************************************************/ + +/* Macros for defining each option */ +#define DBRstatus \ + epicsUInt16 status; /* alarm status */\ + epicsUInt16 severity; /* alarm severity*/\ + epicsUInt16 acks; /* alarm ack severity*/\ + epicsUInt16 ackt; /* Acknowledge transient alarms?*/ +#define DB_UNITS_SIZE 16 +#define DBRunits \ + char units[DB_UNITS_SIZE]; /* units */ +#define DBRprecision union { \ + long dp; /* number of decimal places*/\ + double unused; /* for alignment */\ + } precision; + /* precision.dp must be long to match the pointer arguments to + * RSET->get_precision() and recGblGetPrec(), which it's + * too late to change now. DBRprecision must be padded to + * maintain 8-byte alignment. */ +#define DBRtime \ + epicsTimeStamp time; /* time stamp*/ +#define DBRenumStrs \ + epicsUInt32 no_str; /* number of strings*/\ + epicsInt32 padenumStrs; /*padding to force 8 byte align*/\ + char strs[DB_MAX_CHOICES][MAX_STRING_SIZE]; /* string values */ +#define DBRgrLong \ + epicsInt32 upper_disp_limit; /*upper limit of graph*/\ + epicsInt32 lower_disp_limit; /*lower limit of graph*/ +#define DBRgrDouble \ + epicsFloat64 upper_disp_limit; /*upper limit of graph*/\ + epicsFloat64 lower_disp_limit; /*lower limit of graph*/ +#define DBRctrlLong \ + epicsInt32 upper_ctrl_limit; /*upper limit of graph*/\ + epicsInt32 lower_ctrl_limit; /*lower limit of graph*/ +#define DBRctrlDouble \ + epicsFloat64 upper_ctrl_limit; /*upper limit of graph*/\ + epicsFloat64 lower_ctrl_limit; /*lower limit of graph*/ +#define DBRalLong \ + epicsInt32 upper_alarm_limit;\ + epicsInt32 upper_warning_limit;\ + epicsInt32 lower_warning_limit;\ + epicsInt32 lower_alarm_limit; +#define DBRalDouble \ + epicsFloat64 upper_alarm_limit;\ + epicsFloat64 upper_warning_limit;\ + epicsFloat64 lower_warning_limit;\ + epicsFloat64 lower_alarm_limit; + +/* structures for each option type */ +struct dbr_status {DBRstatus}; +struct dbr_units {DBRunits}; +struct dbr_precision {DBRprecision}; +struct dbr_time {DBRtime}; +struct dbr_enumStrs {DBRenumStrs}; +struct dbr_grLong {DBRgrLong}; +struct dbr_grDouble {DBRgrDouble}; +struct dbr_ctrlLong {DBRctrlLong}; +struct dbr_ctrlDouble {DBRctrlDouble}; +struct dbr_alLong {DBRalLong}; +struct dbr_alDouble {DBRalDouble}; +/* sizes for each option structure */ +#define dbr_status_size sizeof(struct dbr_status) +#define dbr_units_size sizeof(struct dbr_units) +#define dbr_precision_size sizeof(struct dbr_precision) +#define dbr_time_size sizeof(struct dbr_time) +#define dbr_enumStrs_size sizeof(struct dbr_enumStrs) +#define dbr_grLong_size sizeof(struct dbr_grLong) +#define dbr_grDouble_size sizeof(struct dbr_grDouble) +#define dbr_ctrlLong_size sizeof(struct dbr_ctrlLong) +#define dbr_ctrlDouble_size sizeof(struct dbr_ctrlDouble) +#define dbr_alLong_size sizeof(struct dbr_alLong) +#define dbr_alDouble_size sizeof(struct dbr_alDouble) + +#ifndef INCerrMdefh +#include "errMdef.h" +#endif +#define S_db_notFound (M_dbAccess| 1) /*Process Variable Not Found*/ +#define S_db_badDbrtype (M_dbAccess| 3) /*Illegal Database Request Type*/ +#define S_db_noMod (M_dbAccess| 5) /*Attempt to modify noMod field*/ +#define S_db_badLset (M_dbAccess| 7) /*Illegal Lock Set*/ +#define S_db_precision (M_dbAccess| 9) /*get precision failed */ +#define S_db_onlyOne (M_dbAccess|11) /*Only one element allowed*/ +#define S_db_badChoice (M_dbAccess|13) /*Illegal choice*/ +#define S_db_badField (M_dbAccess|15) /*Illegal field value*/ +#define S_db_lsetLogic (M_dbAccess|17) /*Logic error generating lock sets*/ +#define S_db_noLSET (M_dbAccess|21) /*No link support table or entry*/ +#define S_db_noRSET (M_dbAccess|31) /*missing record support entry table*/ +#define S_db_noSupport (M_dbAccess|33) /*RSET or DSXT routine not defined*/ +#define S_db_BadSub (M_dbAccess|35) /*Subroutine not found*/ +/*!!!! Do not change next line without changing src/rsrv/server.h!!!!!!!!*/ +#define S_db_Pending (M_dbAccess|37) /*Request is pending*/ + +#define S_db_Blocked (M_dbAccess|39) /*Request is Blocked*/ +#define S_db_putDisabled (M_dbAccess|41) /*putFields are disabled*/ +#define S_db_badHWaddr (M_dbAccess|43) /*Hardware link type not on INP/OUT*/ +#define S_db_bkptSet (M_dbAccess|53) /*Breakpoint already set*/ +#define S_db_bkptNotSet (M_dbAccess|55) /*No breakpoint set in record*/ +#define S_db_notStopped (M_dbAccess|57) /*Record not stopped*/ +#define S_db_errArg (M_dbAccess|59) /*Error in argument*/ +#define S_db_bkptLogic (M_dbAccess|61) /*Logic error in breakpoint routine*/ +#define S_db_cntSpwn (M_dbAccess|63) /*Cannot spawn dbContTask*/ +#define S_db_cntCont (M_dbAccess|65) /*Cannot resume dbContTask*/ +#define S_db_noMemory (M_dbAccess|66) /*unable to allocate data structure from pool*/ +#define S_db_notInit (M_dbAccess|67) /*Not initialized*/ +#define S_db_bufFull (M_dbAccess|68) /*Buffer full*/ + +epicsShareFunc long dbPutSpecial(struct dbAddr *paddr,int pass); +epicsShareFunc rset * dbGetRset(const struct dbAddr *paddr); +epicsShareFunc long dbPutAttribute( + const char *recordTypename,const char *name,const char*value); +epicsShareFunc int dbIsValueField(const struct dbFldDes *pdbFldDes); +epicsShareFunc int dbGetFieldIndex(const struct dbAddr *paddr); +epicsShareFunc long dbScanPassive( + struct dbCommon *pfrom,struct dbCommon *pto); +epicsShareFunc long dbProcess(struct dbCommon *precord); +epicsShareFunc long dbNameToAddr( + const char *pname,struct dbAddr *); +epicsShareFunc devSup* dbDTYPtoDevSup(dbRecordType *prdes, int dtyp); +epicsShareFunc devSup* dbDSETtoDevSup(dbRecordType *prdes, struct dset *pdset); +epicsShareFunc long dbGetField( + struct dbAddr *,short dbrType,void *pbuffer,long *options, + long *nRequest,void *pfl); +epicsShareFunc long dbGet( + struct dbAddr *,short dbrType,void *pbuffer,long *options, + long *nRequest,void *pfl); +epicsShareFunc long dbPutField( + struct dbAddr *,short dbrType,const void *pbuffer,long nRequest); +epicsShareFunc long dbPut( + struct dbAddr *,short dbrType,const void *pbuffer,long nRequest); + +typedef void(*SPC_ASCALLBACK)(struct dbCommon *); +/*dbSpcAsRegisterCallback called by access security */ +epicsShareFunc void dbSpcAsRegisterCallback(SPC_ASCALLBACK func); +epicsShareFunc long dbBufferSize( + short dbrType,long options,long nRequest); +epicsShareFunc long dbValueSize(short dbrType); + +/* Hook Routine */ + +typedef void (*DB_LOAD_RECORDS_HOOK_ROUTINE)(const char* filename, + const char* substitutions); +epicsShareExtern DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook; + +epicsShareFunc int dbLoadDatabase( + const char *filename, const char *path, const char *substitutions); +epicsShareFunc int dbLoadRecords( + const char* filename, const char* substitutions); + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbAccessDefsh*/ diff --git a/modules/database/src/ioc/db/dbAddr.h b/modules/database/src/ioc/db/dbAddr.h new file mode 100644 index 000000000..2a6e4c0e2 --- /dev/null +++ b/modules/database/src/ioc/db/dbAddr.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef dbAddrh +#define dbAddrh + +struct dbCommon; +struct dbFldDes; + +typedef struct dbAddr { + struct dbCommon *precord; /* address of record */ + void *pfield; /* address of field */ + struct dbFldDes *pfldDes; /* address of struct fldDes */ + long no_elements; /* number of elements (arrays) */ + short field_type; /* type of database field */ + short field_size; /* size of the field being accessed */ + short special; /* special processing */ + short dbr_field_type; /* field type as seen by database request*/ + /* DBR_STRING,...,DBR_ENUM,DBR_NOACCESS */ +} dbAddr; + +typedef dbAddr DBADDR; + +#endif /* dbAddrh */ diff --git a/modules/database/src/ioc/db/dbBkpt.c b/modules/database/src/ioc/db/dbBkpt.c new file mode 100644 index 000000000..8e7bf3a2b --- /dev/null +++ b/modules/database/src/ioc/db/dbBkpt.c @@ -0,0 +1,970 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbBkpt.c */ +/* + * Author: Matthew Needes + * Date: 8-30-93 +*/ + +/* + * Database Breakpoint Manipulation and User Interface + * + * USER COMMANDS + * dbb(record_name) Set a breakpoint in a record + * dbd(record_name) Delete a record's breakpoint + * dbc(record_name) Resume record processing + * dbs(record_name) Step through record processing through + * IO links, forward process links, etc. + * dbstat() Display status of stopped records in lock sets. + * dbap(record_name) Toggle automatic print after processing. + * dbp(record_name) Print out fields from record currently stopped. + * dbprc(record_name) Processes a record once without printing it. + * (Unless autoprint is on) + * + * INTERNAL FUNCTIONS + * dbBkpt() Process breakpoints, called by dbProcess(). + * dbPrint() Prints record if autoprint enabled. + * dbBkptCont() The task that continues and steps through + * records that are stopped at a breakpoint. + */ + +/* #define BKPT_DIAG */ + +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "errlog.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbBkpt.h" +#include "dbCommon.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbFldTypes.h" +#include "dbLink.h" +#include "dbLock.h" +#include "dbScan.h" +#include "dbTest.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + +/* private routines */ +static void dbBkptCont(dbCommon *precord); +static long FIND_CONT_NODE( + const char *record_name, + struct LS_LIST **ppnode, + struct dbCommon **pprecord); + +/* + * Breakpoints are used as a debugging instrument to suspend the + * processing of database records. Once suspended, record + * processing may continue if either a continue (dbc()) or a + * step (dbs()) command is then issued. The current record's + * contents may be printed either with dbp(), or immediately + * after processing (use dbap() to toggle the BKPT_PRINT bit). + * + * dbb() and dbd() add a breakpoint to a record or delete one + * from a record. dbstat() prints out comprehensive breakpoint + * status information. + * + * Breakpoints may be set on a per lockset basis. When a + * breakpoint is set in a lockset, a new task is created. A + * separate task gets created for _every_ lockset containing + * a breakpoint. Thus multiple locksets may be debugged + * simultaneously. The breakpoint handler then schedules future + * processing in that lockset to this task. The separate task is + * used so that locksets that do not have breakpoints are isolated + * from locksets that do. This allows the processing of other + * locksets to continue uninterupted, even if they exist on the same + * scan list as a lockset containing a breakpoint. + * + * An entrypoint is the first record that gets processed in a lockset. + * This type of record is the basis for subsequent recursive executions + * of dbProcess(). The breakpoint handler monitors and schedules + * these entrypoints to the breakpoint tasks. + * + * Two hooks have been inserted in dbProcess() to manage breakpoints, + * dbBkpt() and dbPrint(). The former does two things: + * + * 1. Schedule entrypoints with the breakpoint task. + * 2. Suspend record processing when a breakpoint is detected. + * + * 1 occurs only if dbProcess() is called outside of the breakpoint + * task. Number 2 only occurs when dbProcess() is called from + * _within_ the breakpoint task's context. Number 1 is used for + * detection and scheduling, while 2 is used for suspending the task. + * + * The dbPrint() hook is used to print out a record's contents immediately + * _after_ a record has been processed. + * + * The dbBkptCont, or breakpoint task, pends on a semaphore that gets + * released whenever new entrypoints are scheduled for it. When + * released, this task then runs down its entrypoint queue and + * processes each entrypoint in turn. In this context, dbProcess + * will execute the dbBkpt() hook in mode 2, allowing this task to + * be suspended whenever a breakpoint is detected. + * + * NOTE: This is not a very "real-time" implementation (even for those + * locksets not containing a breakpoint). I may fix this later. + * + * Final comment: The scary thing is, I don't think this can be done + * more simply... + * + */ + +/* + * Flag used by dbProcess() to determine if there are + * any breakpoints. This is so that there is only + * a single comparison in the critical path during + * normal record execution, i.e. when there aren't + * any breakpoints set. + */ +long lset_stack_count = 0; + +/* + * Stack--in which each entry represents a different + * lock set with either breakpoints and/or stopped + * execution. (Breakpoints may be disabled even + * though execution is stopped). The order of the + * list is maintained so that the entry on the top + * of stack is used as a default for dbc() and dbs(). + * The semaphore is used to prevent conflicts while + * operating with this stack. + */ +static ELLLIST lset_stack = ELLLIST_INIT; +static epicsMutexId bkpt_stack_sem = 0; + +/* + * Stores the last lockset continued or stepped from. + * dbs() and dbc() will print a message if the current + * lockset to be continued from differs from this + * variable. + */ +static unsigned long last_lset = 0; + +/* + * FIND_LOCKSET() finds the stack entry + * whose l_num field matches precord's + * lset field. The node that is found + * is returned in "pnode." + */ +#define FIND_LOCKSET(precord, pnode) \ + pnode = (struct LS_LIST *) ellFirst(&lset_stack); \ + while ((pnode) != NULL) { \ + if (pnode->l_num == dbLockGetLockId(precord)) break; \ + pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); \ + } \ + +/* + * FIND_QUEUE_ENTRY() matches entries in an + * entry point queue. pep_queue is the queue + * being searched, pqe is the pointer to the + * queue entry found, and precord is the record + * being searched for in *pep_queue. + */ +#define FIND_QUEUE_ENTRY(pep_queue, pqe, precord) \ + pqe = (struct EP_LIST *) ellFirst(pep_queue); \ + while ((pqe) != NULL) { \ + if ((pqe)->entrypoint == (precord)) break; \ + pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); \ + } \ + +/* + * Fills out pnode and precord structures for dbc() and dbs() + * MUST LOCK OUT STACK BEFORE ENTRY + */ +static long FIND_CONT_NODE( + const char *record_name, + struct LS_LIST **ppnode, + struct dbCommon **pprecord) +{ + struct dbAddr addr; + struct LS_LIST *pnode; + struct dbCommon *precord = NULL; + long status = 0; + + if (record_name == NULL) { + /* + * Search through stack, taking the first entry that + * is currently stopped at a breakpoint. + */ + pnode = (struct LS_LIST *) ellFirst(&lset_stack); + while (pnode != NULL) { + if (pnode->precord != NULL) { + precord = pnode->precord; + break; + } + pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); + } + + if (pnode == NULL) { + printf(" BKPT> No records are currently stopped\n"); + return(S_db_notStopped); + } + } + else { + /* + * Convert name to address + */ + status = dbNameToAddr(record_name, &addr); + if (status == S_db_notFound) + printf(" BKPT> Record %s not found\n", record_name); + if (status != 0) + return(status); + + precord = addr.precord; + + FIND_LOCKSET(precord, pnode); + + if (pnode == NULL || pnode->precord == NULL) { + printf(" BKPT> Currently not stopped in this lockset\n"); + return(S_db_notStopped); + } + } + + *pprecord = precord; + *ppnode = pnode; + return(0); +} + +/* + * Initialise the breakpoint stack + */ +void dbBkptInit(void) +{ + if (! bkpt_stack_sem) { + bkpt_stack_sem = epicsMutexMustCreate(); + lset_stack_count = 0; + } +} + +/* + * Add breakpoint to a lock set + * 1. Convert name to address and check breakpoint mask. + * 2. Lock database. + * 3. If empty, initialize lock set stack and its semaphore. + * 4. Take that semaphore. + * 5. Find lockset in the list. If it doesn't exist, create it. + * 6. Turn on breakpoint field in record. + * 7. Add breakpoint to list of breakpoints in structure. + * 8. Spawn continuation task if it isn't already running. + */ +long dbb(const char *record_name) +{ + struct dbAddr addr; + struct LS_LIST *pnode; + struct BP_LIST *pbl; + struct dbCommon *precord; + long status; + + /* + * Convert name to address + */ + status = dbNameToAddr(record_name, &addr); + if (status == S_db_notFound) + printf(" BKPT> Record %s not found\n", record_name); + if (status != 0) return(status); + + precord = addr.precord; + + if (precord->bkpt & BKPT_ON_MASK) { + printf(" BKPT> Breakpoint already set in this record\n"); + return(S_db_bkptSet); + } + + dbScanLock(precord); + + /* + * Add lock set to the stack of lock sets that + * contain breakpoints and/or stopped records. + */ + + epicsMutexMustLock(bkpt_stack_sem); + + FIND_LOCKSET(precord, pnode); + + if (pnode == NULL) { + /* lockset not found, create node, add to end of list */ + pnode = (struct LS_LIST *) malloc(sizeof(struct LS_LIST)); + if (pnode == NULL) { + printf(" BKPT> Out of memory\n"); + dbScanUnlock(precord); + epicsMutexUnlock(bkpt_stack_sem); + return(1); + } + pnode->precord = NULL; + + /* initialize breakpoint list */ + ellInit(&pnode->bp_list); + + /* initialize entry point queue */ + ellInit(&pnode->ep_queue); + + /* create execution semaphore */ + pnode->ex_sem = epicsEventCreate(epicsEventEmpty); + if (pnode->ex_sem == NULL) { + printf(" BKPT> Out of memory\n"); + dbScanUnlock(precord); + epicsMutexUnlock(bkpt_stack_sem); + return(1); + } + + pnode->taskid = 0; + pnode->step = 0; + pnode->l_num = dbLockGetLockId(precord); + + ellAdd(&lset_stack, (ELLNODE *)pnode); + ++lset_stack_count; + } + + /* + * Add record to breakpoint list + */ + pbl = (struct BP_LIST *) malloc(sizeof(struct BP_LIST)); + if (pbl == NULL) { + printf(" BKPT> Out of memory\n"); + dbScanUnlock(precord); + epicsMutexUnlock(bkpt_stack_sem); + return(1); + } + pbl->precord = precord; + ellAdd(&pnode->bp_list, (ELLNODE *)pbl); + + /* + * Turn on breakpoint field in record + */ + precord->bkpt |= BKPT_ON_MASK; + + if (! pnode->taskid) { + +#ifdef BKPT_DIAG + printf(" BKPT> Spawning task: %s\n", precord->name); +#endif + /* + * Spawn continuation task + */ + pnode->taskid = epicsThreadCreate("bkptCont",epicsThreadPriorityScanLow-1, + epicsThreadGetStackSize(epicsThreadStackBig), + (EPICSTHREADFUNC)dbBkptCont,precord); + if (pnode->taskid == 0) { + printf(" BKPT> Cannot spawn task to process record\n"); + pnode->taskid = 0; + dbScanUnlock(precord); + epicsMutexUnlock(bkpt_stack_sem); + return(1); + } + } + + epicsMutexUnlock(bkpt_stack_sem); + dbScanUnlock(precord); + return(0); +} + +/* + * Remove breakpoint from a record + * 1. Convert name to address and check breakpoint mask. + * 2. Lock database and take stack semaphore. + * 3. Find structure for record's lockset (in stack). + * 4. Find and delete record from breakpoint list. + * 5. Turn off break point field. + * 6. Give up semaphore to "signal" bkptCont task to quit. + */ +long dbd(const char *record_name) +{ + struct dbAddr addr; + struct LS_LIST *pnode; + struct BP_LIST *pbl; + struct dbCommon *precord; + long status; + + /* + * Convert name to address + */ + status = dbNameToAddr(record_name, &addr); + if (status == S_db_notFound) + printf(" BKPT> Record %s not found\n", record_name); + if (status != 0) return(status); + + precord = addr.precord; + + if (!(precord->bkpt & BKPT_ON_MASK)) { + printf(" BKPT> No breakpoint set in this record\n"); + return(S_db_bkptNotSet); + } + + dbScanLock(precord); + + epicsMutexMustLock(bkpt_stack_sem); + + FIND_LOCKSET(precord, pnode); + + if (pnode == NULL) { + /* not found, error ! */ + printf(" BKPT> Logic Error in dbd()\n"); + precord->bkpt &= BKPT_OFF_MASK; + + epicsMutexUnlock(bkpt_stack_sem); + dbScanUnlock(precord); + return(S_db_bkptLogic); + } + + /* + * Remove record from breakpoint list + */ + + /* find record in list */ + pbl = (struct BP_LIST *) ellFirst(&pnode->bp_list); + while (pbl != NULL) { + if (pbl->precord == precord) { + ellDelete(&pnode->bp_list, (ELLNODE *)pbl); + free(pbl); + break; + } + pbl = (struct BP_LIST *) ellNext((ELLNODE *)pbl); + } + + if (pbl == NULL) { + printf(" BKPT> Logic Error in dbd()\n"); + precord->bkpt &= BKPT_OFF_MASK; + epicsMutexUnlock(bkpt_stack_sem); + dbScanUnlock(precord); + return(S_db_bkptLogic); + } + + /* + * Turn off breakpoint field in record + */ + precord->bkpt &= BKPT_OFF_MASK; + + /* + * If there are no more breakpoints, give up semaphore + * to cause the bkptCont task to quit. + */ + if (ellCount(&pnode->bp_list) == 0) + epicsEventSignal(pnode->ex_sem); + + epicsMutexUnlock(bkpt_stack_sem); + + dbScanUnlock(precord); + return(0); +} + +/* + * Continue processing in a lock set + * 1. Find top node in the lockset stack. + * 2. Turn off stepping mode. + * 2. Resume dbBkptCont. + */ +long dbc(const char *record_name) +{ + struct LS_LIST *pnode; + struct dbCommon *precord = NULL; + long status = 0; + + epicsMutexMustLock(bkpt_stack_sem); + + status = FIND_CONT_NODE(record_name, &pnode, &precord); + if (status) { + epicsMutexUnlock(bkpt_stack_sem); + return(status); + } + + if (record_name == NULL && last_lset != pnode->l_num) + printf(" BKPT> Continuing: %s\n", pnode->precord->name); + + last_lset = pnode->l_num; + + /* + * Turn off stepping mode + */ + pnode->step = 0; + + /* + * Resume dbBkptCont() until dbProcess() is executed + * for a record with a breakpoint. This occurs + * because stepping mode has been switched off. + */ + epicsThreadResume(pnode->taskid); + epicsMutexUnlock(bkpt_stack_sem); + return(0); +} + +/* + * Step through record processing + * 1. Find top node in lockset stack. + * 2. Resume dbBkptCont. + */ +long dbs(const char *record_name) +{ + struct LS_LIST *pnode; + struct dbCommon *precord = NULL; + long status = 0; + + epicsMutexMustLock(bkpt_stack_sem); + + status = FIND_CONT_NODE(record_name, &pnode, &precord); + if (status) { + epicsMutexUnlock(bkpt_stack_sem); + return(status); + } + + if (last_lset != pnode->l_num && record_name == NULL) + printf(" BKPT> Stepping: %s\n", pnode->precord->name); + + last_lset = pnode->l_num; + + epicsThreadResume(pnode->taskid); + epicsMutexUnlock(bkpt_stack_sem); + return(0); +} + +/* + * Task for continuing record processing + * 1. Find lockset in stack for precord. + * DO 2-3 while breakpoints exist in the lockset. + * 2. Wait on execution semaphore ... + * 3. Run through every entrypoint in queue, processing + * those that are scheduled. + * 4. Free resources for lockset, and exit task. + */ +static void dbBkptCont(dbCommon *precord) +{ + struct LS_LIST *pnode; + struct EP_LIST *pqe = NULL; + + /* + * Reset breakpoint, process record, and + * reset bkpt field in record + */ + epicsMutexMustLock(bkpt_stack_sem); + + FIND_LOCKSET(precord, pnode); + + if (pnode == NULL) { + printf(" BKPT> Logic error in dbBkptCont()\n"); + return; + } + + /* + * For every entrypoint scheduled, process. Run process + * until there are no more breakpoints remaining in a + * lock set. + */ + do { + /* Give up semaphore before waiting to run ... */ + epicsMutexUnlock(bkpt_stack_sem); + + /* Wait to run */ + epicsEventMustWait(pnode->ex_sem); + + /* Bkpt stack must still be stable ! */ + epicsMutexMustLock(bkpt_stack_sem); + + pqe = (struct EP_LIST *) ellFirst(&pnode->ep_queue); + + /* Run through entrypoint queue */ + while (pqe != NULL) { + /* check if entrypoint is currently scheduled */ + if (pqe->sched) { + /* save current entrypoint */ + pnode->current_ep = pqe->entrypoint; + + /* lock the lockset, process record, unlock */ + dbScanLock(precord); + dbProcess(pqe->entrypoint); + dbScanUnlock(precord); + + /* reset schedule and stepping flag - Do this AFTER processing */ + pqe->sched = 0; + pnode->step = 0; + } + pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); + } + + /* Reset precord. (Since no records are at a breakpoint) */ + pnode->precord = NULL; + } while (ellCount(&pnode->bp_list) != 0); + + /* remove node from lockset stack */ + ellDelete(&lset_stack, (ELLNODE *)pnode); + --lset_stack_count; + + /* free entrypoint queue */ + ellFree(&pnode->ep_queue); + + /* remove execution semaphore */ + epicsEventDestroy(pnode->ex_sem); + + printf("\n BKPT> End debug of lockset %lu\n-> ", pnode->l_num); + + /* free list node */ + free(pnode); + + epicsMutexUnlock(bkpt_stack_sem); +} + +/* + * Process breakpoint + * Returns a zero if dbProcess() is to execute + * record support, a one if dbProcess() is to + * skip over record support. See dbProcess(). + * + * 1. See if there is at least a breakpoint set somewhere + * in precord's lockset. If not, return immediately. + * 2. Check the disable flag. + * 3. Add entry points to the queue for future stepping and + * schedule new entrypoints for the continuation task. + * 4. Check the pact flag. + * 5. Check to see if there is a breakpoint set in a record, and + * if so, turn on stepping mode. + * 6. If stepping mode is set, stop and report the breakpoint. + */ +int dbBkpt(dbCommon *precord) +{ + struct LS_LIST *pnode; + struct EP_LIST *pqe; + + /* + * It is crucial that operations in dbBkpt() execute + * in the correct order or certain features in the + * breakpoint handler will not work as expected. + */ + + /* + * Take and give a semaphore to check for breakpoints + * every time a record is processed. Slow. Thank + * goodness breakpoint checking is turned off during + * normal operation. + */ + epicsMutexMustLock(bkpt_stack_sem); + FIND_LOCKSET(precord, pnode); + epicsMutexUnlock(bkpt_stack_sem); + + if (pnode == NULL) { + /* no breakpoints in precord's lockset */ + return(0); + } + + /* Check disable flag */ + dbGetLink(&(precord->sdis),DBR_SHORT,&(precord->disa),0,0); + if (precord->disa == precord->disv) { + /* + * Do not process breakpoints if the record is disabled, + * but allow disable alarms. Alarms will be raised + * in dbProcess() because returning 0 allows dbProcess() + * to continue. However processing will be prevented + * because disa and disv will be examined again in + * dbProcess(). Note that checking for pact will occur + * before checking for disa and disv in dbProcess(). + */ + return(0); + } + + /* + * Queue entry points for future stepping. The taskid comparison + * is used to determine if the source of processing is the + * continuation task or an external source. If it is an external + * source, queue its execution, but dump out of dbProcess without + * calling record support. + */ + if (pnode->taskid && (epicsThreadGetIdSelf() != pnode->taskid)) { + /* CONTINUE TASK CANNOT ENTER HERE */ + + /* + * Add an entry point to queue, if it does + * not already exist. + */ + FIND_QUEUE_ENTRY(&pnode->ep_queue, pqe, precord); + + if (pqe == NULL) { + + pqe = (struct EP_LIST *) malloc(sizeof(struct EP_LIST)); + if (pqe == NULL) + return(1); + + + pqe->entrypoint = precord; + pqe->count = 1; + epicsTimeGetCurrent(&pqe->time); + pqe->sched = 0; + +#ifdef BKPT_DIAG + printf(" BKPT> Adding entrypoint %s to queue\n", precord->name); +#endif + + /* + * Take semaphore, wait on continuation task + */ + epicsMutexMustLock(bkpt_stack_sem); + + /* Add entry to queue */ + ellAdd(&pnode->ep_queue, (ELLNODE *)pqe); + + epicsMutexUnlock(bkpt_stack_sem); + } + else { + if (pqe->count < MAX_EP_COUNT) + pqe->count++; + } + + /* check pact */ + if (! precord->pact) { + /* schedule if pact not set */ + pqe->sched = 1; + + /* + * Release the semaphore, letting the continuation + * task begin execution of the new entrypoint. + */ + epicsEventSignal(pnode->ex_sem); + } + return(1); + } + + /* + * Don't mess with breakpoints if pact set! Skip + * over rest of dbProcess() since we don't want + * alarms going off. The pact flag is checked + * AFTER entry point queuing so that the record + * timing feature will work properly. + */ + if (precord->pact) + return(1); + + /* Turn on stepping mode if a breakpoint is found */ + if (precord->bkpt & BKPT_ON_MASK) { + pnode->step = 1; + +#ifdef BKPT_DIAG + printf(" BKPT> Bkpt detected: %s\n", precord->name); +#endif + } + + /* + * If we are currently stepping through the lockset, + * suspend task. + */ + if (pnode->step) { + printf("\n BKPT> Stopped at: %s within Entrypoint: %s\n-> ", + precord->name, pnode->current_ep->name); + + pnode->precord = precord; + + /* Move current lockset to top of stack */ + ellDelete(&lset_stack, (ELLNODE *)pnode); + ellInsert(&lset_stack, NULL, (ELLNODE *)pnode); + /* + * Unlock database while the task suspends itself. This + * is done so that dbb() dbd() dbc() dbs() may be used + * when the task is suspended. Scan tasks that also + * use the scan lock feature will not be hung during + * a breakpoint, so that records in other locksets will + * continue to be processed. Cross your fingers, this + * might actually work ! + */ + epicsMutexUnlock(bkpt_stack_sem); + dbScanUnlock(precord); + epicsThreadSuspendSelf(); + dbScanLock(precord); + epicsMutexMustLock(bkpt_stack_sem); + } + return(0); +} + +/* print record after processing */ +void dbPrint(dbCommon *precord ) +{ + struct LS_LIST *pnode; + + if (! (precord->bkpt & BKPT_PRINT_MASK)) + return; + + FIND_LOCKSET(precord, pnode); + + /* do not print if lockset does not currently contain breakpoints */ + if (pnode == NULL) + return; + + printf("\n"); + dbpr(precord->name, 2); + printf("-> "); +} + +/* print stopped record */ +long dbp(const char *record_name, int interest_level) +{ + struct LS_LIST *pnode; + struct dbCommon *precord = NULL; + int status; + + epicsMutexMustLock(bkpt_stack_sem); + + /* find pnode and precord pointers */ + status = FIND_CONT_NODE(record_name, &pnode, &precord); + if (status) { + epicsMutexUnlock(bkpt_stack_sem); + return(status); + } + + /* print out record's fields */ + dbpr(precord->name, (interest_level == 0) ? 2 : interest_level); + + epicsMutexUnlock(bkpt_stack_sem); + return(0); +} + +/* toggle printing after processing a certain record */ +long dbap(const char *record_name) +{ + struct dbAddr addr; + struct dbCommon *precord; + long status; + + /* + * Convert name to address + */ + status = dbNameToAddr(record_name, &addr); + if (status == S_db_notFound) + printf(" BKPT> Record %s not found\n", record_name); + if (status != 0) return(status); + + precord = addr.precord; + + /* + * Toggle print after process field in record + */ + if (precord->bkpt & BKPT_PRINT_MASK) { + printf(" BKPT> Auto print off for record %s\n", precord->name); + precord->bkpt &= BKPT_PRINT_OFF_MASK; + } + else { + printf(" BKPT> Auto print on for record %s\n", precord->name); + precord->bkpt |= BKPT_PRINT_MASK; + } + + return(0); +} + +/* print list of stopped records, and breakpoints set in locksets */ +long dbstat(void) +{ + struct LS_LIST *pnode; + struct BP_LIST *pbl; + struct EP_LIST *pqe; + epicsTimeStamp time; + + epicsMutexMustLock(bkpt_stack_sem); + + epicsTimeGetCurrent(&time); + + /* + * Traverse list, reporting stopped records + */ + pnode = (struct LS_LIST *) ellFirst(&lset_stack); + while (pnode != NULL) { + if (pnode->precord != NULL) { + + printf("LSet: %lu Stopped at: %-28.28s #B: %5.5d T: %p\n", + pnode->l_num, pnode->precord->name, ellCount(&pnode->bp_list), pnode->taskid); + + /* for each entrypoint detected, print out entrypoint statistics */ + pqe = (struct EP_LIST *) ellFirst(&pnode->ep_queue); + while (pqe != NULL) { + double diff = epicsTimeDiffInSeconds(&time,&pqe->time); + if (diff) { + printf(" Entrypoint: %-28.28s #C: %5.5lu C/S: %7.1f\n", + pqe->entrypoint->name, pqe->count,diff); + } + pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); + } + } + else { + printf("LSet: %lu #B: %5.5d T: %p\n", + pnode->l_num, ellCount(&pnode->bp_list), pnode->taskid); + } + + /* + * Print out breakpoints set in the lock set + */ + pbl = (struct BP_LIST *) ellFirst(&pnode->bp_list); + while (pbl != NULL) { + printf(" Breakpoint: %-28.28s", pbl->precord->name); + + /* display auto print flag */ + if (pbl->precord->bkpt & BKPT_PRINT_MASK) + printf(" (ap)\n"); + else + printf("\n"); + + pbl = (struct BP_LIST *) ellNext((ELLNODE *)pbl); + } + + pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); + } + + epicsMutexUnlock(bkpt_stack_sem); + return(0); +} + +/* + * Process a record without printing it. + */ +long dbprc(char *record_name) +{ + struct dbAddr addr; + struct dbCommon *precord; + long status; + + /* + * Convert name to address + */ + status = dbNameToAddr(record_name, &addr); + if (status == S_db_notFound) + printf(" BKPT> Record %s not found\n", record_name); + if (status != 0) return(status); + + precord = addr.precord; + + /* lock lockset, process record, unlock lockset */ + dbScanLock(precord); + status = dbProcess(precord); + dbScanUnlock(precord); + + return(status); +} + +#ifdef BKPT_DIAG + +/* Reset breakpoints */ +int dbreset() +{ + epicsMutexUnlock(bkpt_stack_sem); + + return(0); +} + +#endif + diff --git a/modules/database/src/ioc/db/dbBkpt.h b/modules/database/src/ioc/db/dbBkpt.h new file mode 100644 index 000000000..7240b701c --- /dev/null +++ b/modules/database/src/ioc/db/dbBkpt.h @@ -0,0 +1,103 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbBkpt.h */ +/* + * Author: Matthew Needes + * Date: 8-30-93 + */ + +#ifndef INCdbBkptsh +#define INCdbBkptsh 1 + +#include "ellLib.h" +#include "epicsEvent.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "shareLib.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Structure containing a list of set breakpoints + * in a lockset + */ + +struct BP_LIST { + ELLNODE *next_list; + ELLNODE *prev_list; + struct dbCommon *precord; +}; + +/* + * Structure containing queue of entrypoints + * detected for a lockset. + */ +struct EP_LIST { + ELLNODE *next_list; + ELLNODE *prev_list; + struct dbCommon *entrypoint; /* pointer to entry point in lockset */ + unsigned long count; /* number of times record processed */ + epicsTimeStamp time; /* time record first logged */ + char sched; /* schedule record for next dbContTask() pass */ +}; + +/* + * Structure for stack of lock sets that + * currently contain breakpoints. (uses ellLib) + */ +struct LS_LIST { + ELLNODE *next_list; + ELLNODE *prev_list; + struct dbCommon *precord;/* points to where execution is currently stopped */ + struct dbCommon *current_ep; /* current entrypoint */ + ELLLIST bp_list; /* list of records containing breakpoints in a lockset */ + ELLLIST ep_queue; /* queue of entrypoints found so far */ + epicsEventId ex_sem; /* semaphore for execution queue */ + epicsThreadId taskid; /* saved taskid for the task in stepping mode */ + int step; /* one if currently "stepping," else zero */ + unsigned long l_num; /* lockset number */ +}; + +/* Values for BKPT (breakpoint) field in record */ + +/* 1st bit = 0 if breakpoint is not set, */ +/* 1 if breakpoint set */ +/* 2nd bit = 0 if no printing after processing */ +/* 1 if print after processing set */ + +/* Breakpoint Masks */ +#define BKPT_ON_MASK 0x001 +#define BKPT_OFF_MASK 0x0FE +#define BKPT_PRINT_MASK 0x002 +#define BKPT_PRINT_OFF_MASK 0x0FD + +#define MAX_EP_COUNT 99999 + +epicsShareFunc void dbBkptInit(void); +epicsShareFunc long dbb(const char *recordname); +epicsShareFunc long dbd(const char *recordname); +epicsShareFunc long dbc(const char *recordname); +epicsShareFunc long dbs(const char *recordname); +epicsShareFunc long dbstat(void); +epicsShareFunc long dbp( + const char *record_name, int interest_level); +epicsShareFunc long dbap(const char *record_name); +epicsShareFunc int dbBkpt(struct dbCommon *precord); +epicsShareFunc void dbPrint(struct dbCommon *precord); +epicsShareFunc long dbprc(char *record_name); + +extern long lset_stack_count; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/database/src/ioc/db/dbCAC.h b/modules/database/src/ioc/db/dbCAC.h new file mode 100644 index 000000000..2d97fac35 --- /dev/null +++ b/modules/database/src/ioc/db/dbCAC.h @@ -0,0 +1,239 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + * + * NOTES: + * 1) This interface is preliminary and will change in the future + */ + +#ifndef dbCACh +#define dbCACh + +#ifdef epicsExportSharedSymbols +# define dbCACh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "stdlib.h" + +#include // std::auto_ptr + +#include "tsDLList.h" +#include "tsFreeList.h" +#include "resourceLib.h" +#include "cacIO.h" +#include "compilerDependencies.h" + +#ifdef dbCACh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "db_access.h" +#include "dbNotify.h" +#include "dbEvent.h" +#include "dbChannel.h" +#include "dbLock.h" +#include "dbCommon.h" +#include "db_convert.h" +#include "resourceLib.h" + +extern "C" int putNotifyPut ( processNotify *ppn, notifyPutType notifyPutType ); +extern "C" void putNotifyCompletion ( processNotify *ppn ); + +class dbContext; +class dbChannelIO; +class dbPutNotifyBlocker; +class dbSubscriptionIO; + +class dbBaseIO + : public chronIntIdRes < dbBaseIO > { +public: + virtual dbSubscriptionIO * isSubscription () = 0; + virtual void show ( epicsGuard < epicsMutex > &, unsigned level ) const = 0; + virtual void show ( unsigned level ) const = 0; + dbBaseIO (); + dbBaseIO ( const dbBaseIO & ); + dbBaseIO & operator = ( const dbBaseIO & ); +protected: + virtual ~dbBaseIO() {} +}; + +extern "C" void dbSubscriptionEventCallback ( void *pPrivate, struct dbChannel *dbch, + int eventsRemaining, struct db_field_log *pfl ); + +class dbSubscriptionIO : + public tsDLNode < dbSubscriptionIO >, + public dbBaseIO { +public: + dbSubscriptionIO ( + epicsGuard < epicsMutex > &, epicsMutex &, + dbContext &, dbChannelIO &, struct dbChannel *, cacStateNotify &, + unsigned type, unsigned long count, unsigned mask, dbEventCtx ); + void destructor ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void unsubscribe ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void channelDeleteException ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; + void show ( unsigned level ) const; + void * operator new ( size_t size, + tsFreeList < dbSubscriptionIO, 256, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < dbSubscriptionIO, 256, epicsMutexNOOP > & )) +private: + epicsMutex & mutex; + unsigned long count; + cacStateNotify & notify; + dbChannelIO & chan; + dbEventSubscription es; + unsigned type; + unsigned id; + dbSubscriptionIO * isSubscription (); + friend void dbSubscriptionEventCallback ( + void * pPrivate, struct dbChannel * dbch, + int eventsRemaining, struct db_field_log * pfl ); + dbSubscriptionIO ( const dbSubscriptionIO & ); + dbSubscriptionIO & operator = ( const dbSubscriptionIO & ); + virtual ~dbSubscriptionIO (); + void operator delete ( void * ); +}; + +class dbContext; + +class dbContextPrivateListOfIO { +public: + dbContextPrivateListOfIO (); + ~dbContextPrivateListOfIO (); +private: + tsDLList < dbSubscriptionIO > eventq; + dbPutNotifyBlocker * pBlocker; + friend class dbContext; + dbContextPrivateListOfIO ( const dbContextPrivateListOfIO & ); + dbContextPrivateListOfIO & operator = ( const dbContextPrivateListOfIO & ); +}; + +class dbContextReadNotifyCacheAllocator { +public: + dbContextReadNotifyCacheAllocator (); + ~dbContextReadNotifyCacheAllocator (); + char * alloc ( unsigned long size ); + void free ( char * pFree ); + void show ( unsigned level ) const; +private: + struct cacheElem_t { + size_t size; + struct cacheElem_t * pNext; + char buf[1]; + }; + unsigned long _readNotifyCacheSize; + cacheElem_t * _pReadNotifyCache; + void reclaimAllCacheEntries (); + dbContextReadNotifyCacheAllocator ( const dbContextReadNotifyCacheAllocator & ); + dbContextReadNotifyCacheAllocator & operator = ( const dbContextReadNotifyCacheAllocator & ); +}; + +class dbContextReadNotifyCache { +public: + dbContextReadNotifyCache ( epicsMutex & ); + void callReadNotify ( epicsGuard < epicsMutex > &, + struct dbChannel * dbch, unsigned type, unsigned long count, + cacReadNotify & notify ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; +private: + dbContextReadNotifyCacheAllocator _allocator; + epicsMutex & _mutex; + dbContextReadNotifyCache ( const dbContextReadNotifyCache & ); + dbContextReadNotifyCache & operator = ( const dbContextReadNotifyCache & ); +}; + +class dbContext : public cacContext { +public: + dbContext ( epicsMutex & cbMutex, epicsMutex & mutex, + cacContextNotify & notify ); + virtual ~dbContext (); + void destroyChannel ( CallbackGuard &,epicsGuard < epicsMutex > &, dbChannelIO & ); + void callReadNotify ( epicsGuard < epicsMutex > &, + struct dbChannel * dbch, unsigned type, unsigned long count, + cacReadNotify & notify ); + void callStateNotify ( struct dbChannel * dbch, unsigned type, unsigned long count, + const struct db_field_log * pfl, cacStateNotify & notify ); + void subscribe ( + epicsGuard < epicsMutex > &, + struct dbChannel * dbch, dbChannelIO & chan, + unsigned type, unsigned long count, unsigned mask, + cacStateNotify & notify, cacChannel::ioid * pId ); + void initiatePutNotify ( + epicsGuard < epicsMutex > &, dbChannelIO &, struct dbChannel *, + unsigned type, unsigned long count, const void * pValue, + cacWriteNotify & notify, cacChannel::ioid * pId ); + void show ( unsigned level ) const; + void showAllIO ( const dbChannelIO & chan, unsigned level ) const; + void destroyAllIO ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > &, dbChannelIO & chan ); + void ioCancel ( CallbackGuard &, epicsGuard < epicsMutex > &, + dbChannelIO & chan, const cacChannel::ioid &id ); + void ioShow ( epicsGuard < epicsMutex > &, + const cacChannel::ioid & id, unsigned level ) const; +private: + tsFreeList < dbPutNotifyBlocker, 64, epicsMutexNOOP > dbPutNotifyBlockerFreeList; + tsFreeList < dbSubscriptionIO, 256, epicsMutexNOOP > dbSubscriptionIOFreeList; + tsFreeList < dbChannelIO, 256, epicsMutexNOOP > dbChannelIOFreeList; + chronIntIdResTable < dbBaseIO > ioTable; + dbContextReadNotifyCache readNotifyCache; + dbEventCtx ctx; + unsigned long stateNotifyCacheSize; + epicsMutex & mutex; + epicsMutex & cbMutex; + cacContextNotify & notify; + std::auto_ptr < cacContext > pNetContext; + char * pStateNotifyCache; + bool isolated; + + cacChannel & createChannel ( + epicsGuard < epicsMutex > &, + const char * pChannelName, cacChannelNotify &, + cacChannel::priLev ); + void flush ( + epicsGuard < epicsMutex > & ); + unsigned circuitCount ( + epicsGuard < epicsMutex > & ) const; + void selfTest ( + epicsGuard < epicsMutex > & ) const; + unsigned beaconAnomaliesSinceProgramStart ( + epicsGuard < epicsMutex > & ) const; + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; + + dbContext ( const dbContext & ); + dbContext & operator = ( const dbContext & ); +}; + +inline dbContextPrivateListOfIO::dbContextPrivateListOfIO () : + pBlocker ( 0 ) +{ +} + +inline dbContextPrivateListOfIO::~dbContextPrivateListOfIO () +{ + assert ( ! this->pBlocker ); +} + +inline void dbContext::callReadNotify ( + epicsGuard < epicsMutex > & guard, struct dbChannel * dbch, + unsigned type, unsigned long count, cacReadNotify & notifyIn ) +{ + guard.assertIdenticalMutex ( this-> mutex ); + this->readNotifyCache.callReadNotify ( guard, dbch, type, count, notifyIn ); +} + +#endif // dbCACh + diff --git a/modules/database/src/ioc/db/dbCa.c b/modules/database/src/ioc/db/dbCa.c new file mode 100644 index 000000000..72ad6cd24 --- /dev/null +++ b/modules/database/src/ioc/db/dbCa.c @@ -0,0 +1,1181 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 26MAR96 + */ +#define EPICS_DBCA_PRIVATE_API +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "cantProceed.h" +#include "dbDefs.h" +#include "epicsAssert.h" +#include "epicsEvent.h" +#include "epicsExit.h" +#include "epicsMutex.h" +#include "epicsPrint.h" +#include "epicsString.h" +#include "epicsThread.h" +#include "epicsAtomic.h" +#include "epicsTime.h" +#include "errlog.h" +#include "errMdef.h" +#include "taskwd.h" + +#include "cadef.h" + +/* We can't include dbStaticLib.h here */ +#define dbCalloc(nobj,size) callocMustSucceed(nobj,size,"dbCalloc") + +#define epicsExportSharedSymbols +#include "db_access_routines.h" +#include "dbCa.h" +#include "dbCaPvt.h" +#include "dbCommon.h" +#include "db_convert.h" +#include "dbLink.h" +#include "dbLock.h" +#include "dbScan.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" + +/* defined in dbContext.cpp + * Setup local CA access + */ +extern void dbServiceIOInit(); +extern int dbServiceIsolate; + +static ELLLIST workList = ELLLIST_INIT; /* Work list for dbCaTask */ +static epicsMutexId workListLock; /*Mutual exclusions semaphores for workList*/ +static epicsEventId workListEvent; /*wakeup event for dbCaTask*/ +static int removesOutstanding = 0; +#define removesOutstandingWarning 10000 + +static volatile enum dbCaCtl_t { + ctlInit, ctlRun, ctlPause, ctlExit +} dbCaCtl; +static epicsEventId startStopEvent; + +struct ca_client_context * dbCaClientContext; + +/* Forward declarations */ +static void dbCaTask(void *); + +static lset dbCa_lset; + +#define printLinks(pcaLink) \ + errlogPrintf("%s has DB CA link to %s\n",\ + pcaLink->plink->precord->name, pcaLink->pvname) + +static int dbca_chan_count; + +/* caLink locking + * + * Lock ordering: + * dbScanLock -> caLink.lock -> workListLock + * + * workListLock: + * Guards access to workList. + * + * dbScanLock: + * All dbCa* functions operating on a single link may only be called when + * the record containing the DBLINK is locked. Including: + * dbCaGet*() + * isConnected() + * dbCaPutLink() + * scanForward() + * dbCaAddLinkCallback() + * dbCaRemoveLink() + * + * Guard the pointer plink.value.pv_link.pvt, but not the struct caLink + * which is pointed to. + * + * caLink.lock: + * Guards the caLink structure (but not the struct DBLINK) + * + * The dbCaTask only locks caLink, and must not lock the record (a violation of lock order). + * + * During link modification or IOC shutdown the pca->plink pointer (guarded by caLink.lock) + * is used as a flag to indicate that a link is no longer active. + * + * References to the struct caLink are owned by the dbCaTask, and any scanOnceCallback() + * which is in progress. + * + * The libca and scanOnceCallback callbacks take no action if pca->plink==NULL. + * + * dbCaPutLinkCallback causes an additional complication because + * when dbCaRemoveLink is called the callback may not have occured. + * If putComplete sees plink==0 it will not call the user's code. + * If pca->putCallback is non-zero, dbCaTask will call the + * user's callback AFTER it has called ca_clear_channel. + * Thus the user's callback will get called exactly once. + */ + +static void addAction(caLink *pca, short link_action) +{ + int callAdd; + + epicsMutexMustLock(workListLock); + callAdd = (pca->link_action == 0); + if (pca->link_action & CA_CLEAR_CHANNEL) { + errlogPrintf("dbCa::addAction %d with CA_CLEAR_CHANNEL set\n", + link_action); + printLinks(pca); + link_action = 0; + } + if (link_action & CA_CLEAR_CHANNEL) { + if (++removesOutstanding >= removesOutstandingWarning) { + errlogPrintf("dbCa::addAction pausing, %d channels to clear\n", + removesOutstanding); + } + while (removesOutstanding >= removesOutstandingWarning) { + epicsMutexUnlock(workListLock); + epicsThreadSleep(1.0); + epicsMutexMustLock(workListLock); + } + } + pca->link_action |= link_action; + if (callAdd) + ellAdd(&workList, &pca->node); + epicsMutexUnlock(workListLock); + if (callAdd) + epicsEventSignal(workListEvent); +} + +static void caLinkInc(caLink *pca) +{ + assert(epicsAtomicGetIntT(&pca->refcount)>0); + epicsAtomicIncrIntT(&pca->refcount); +} + +static void caLinkDec(caLink *pca) +{ + int cnt; + dbCaCallback callback; + void *userPvt = 0; + + cnt = epicsAtomicDecrIntT(&pca->refcount); + assert(cnt>=0); + if(cnt>0) + return; + + if (pca->chid) { + ca_clear_channel(pca->chid); + --dbca_chan_count; + } + callback = pca->putCallback; + if (callback) { + userPvt = pca->putUserPvt; + pca->putCallback = 0; + pca->putType = 0; + } + free(pca->pgetNative); + free(pca->pputNative); + free(pca->pgetString); + free(pca->pputString); + free(pca->pvname); + epicsMutexDestroy(pca->lock); + free(pca); + if (callback) callback(userPvt); +} + +/* Block until worker thread has processed all previously queued actions. + * Does not prevent additional actions from being queued. + */ +void dbCaSync(void) +{ + epicsEventId wake; + caLink templink; + + /* we only partially initialize templink. + * It has no link field and no subscription + * so the worker must handle it early + */ + memset(&templink, 0, sizeof(templink)); + templink.refcount = 1; + + wake = epicsEventMustCreate(epicsEventEmpty); + templink.lock = epicsMutexMustCreate(); + + templink.userPvt = wake; + + addAction(&templink, CA_SYNC); + + epicsEventMustWait(wake); + /* Worker holds workListLock when calling epicsEventMustTrigger() + * we cycle through workListLock to ensure worker call to + * epicsEventMustTrigger() returns before we destroy the event. + */ + epicsMutexMustLock(workListLock); + epicsMutexUnlock(workListLock); + + assert(templink.refcount==1); + + epicsMutexDestroy(templink.lock); + epicsEventDestroy(wake); +} + +epicsShareFunc unsigned long dbCaGetUpdateCount(struct link *plink) +{ + caLink *pca = (caLink *)plink->value.pv_link.pvt; + unsigned long ret; + + if (!pca) return (unsigned long)-1; + + epicsMutexMustLock(pca->lock); + + ret = pca->nUpdate; + + epicsMutexUnlock(pca->lock); + + return ret; +} + +void dbCaCallbackProcess(void *userPvt) +{ + struct link *plink = (struct link *)userPvt; + + dbLinkAsyncComplete(plink); +} + +void dbCaShutdown(void) +{ + enum dbCaCtl_t cur = dbCaCtl; + assert(cur == ctlRun || cur == ctlPause); + dbCaCtl = ctlExit; + epicsEventSignal(workListEvent); + epicsEventMustWait(startStopEvent); +} + +static void dbCaLinkInitImpl(int isolate) +{ + dbServiceIsolate = isolate; + dbServiceIOInit(); + + if (!workListLock) + workListLock = epicsMutexMustCreate(); + if (!workListEvent) + workListEvent = epicsEventMustCreate(epicsEventEmpty); + + if(!startStopEvent) + startStopEvent = epicsEventMustCreate(epicsEventEmpty); + dbCaCtl = ctlPause; + + epicsThreadCreate("dbCaLink", epicsThreadPriorityMedium, + epicsThreadGetStackSize(epicsThreadStackBig), + dbCaTask, NULL); + epicsEventMustWait(startStopEvent); +} + +void dbCaLinkInitIsolated(void) +{ + dbCaLinkInitImpl(1); +} + +void dbCaLinkInit(void) +{ + dbCaLinkInitImpl(0); +} + +void dbCaRun(void) +{ + if (dbCaCtl == ctlPause) { + dbCaCtl = ctlRun; + epicsEventSignal(workListEvent); + } +} + +void dbCaPause(void) +{ + if (dbCaCtl == ctlRun) { + dbCaCtl = ctlPause; + epicsEventSignal(workListEvent); + } +} + +void dbCaAddLinkCallback(struct link *plink, + dbCaCallback connect, dbCaCallback monitor, void *userPvt) +{ + caLink *pca; + + assert(!plink->value.pv_link.pvt); + + pca = (caLink *)dbCalloc(1, sizeof(caLink)); + pca->refcount = 1; + pca->lock = epicsMutexMustCreate(); + pca->plink = plink; + pca->pvname = epicsStrDup(plink->value.pv_link.pvname); + pca->connect = connect; + pca->monitor = monitor; + pca->userPvt = userPvt; + + epicsMutexMustLock(pca->lock); + plink->lset = &dbCa_lset; + plink->type = CA_LINK; + plink->value.pv_link.pvt = pca; + addAction(pca, CA_CONNECT); + epicsMutexUnlock(pca->lock); +} + +long dbCaAddLink(struct dbLocker *locker, struct link *plink, short dbfType) +{ + dbCaAddLinkCallback(plink, 0, 0, NULL); + return 0; +} + +void dbCaRemoveLink(struct dbLocker *locker, struct link *plink) +{ + caLink *pca = (caLink *)plink->value.pv_link.pvt; + + if (!pca) return; + epicsMutexMustLock(pca->lock); + pca->plink = 0; + plink->value.pv_link.pvt = 0; + plink->value.pv_link.pvlMask = 0; + plink->type = PV_LINK; + plink->lset = NULL; + /* Unlock before addAction or dbCaTask might free first */ + epicsMutexUnlock(pca->lock); + addAction(pca, CA_CLEAR_CHANNEL); +} + +long dbCaGetLink(struct link *plink, short dbrType, void *pdest, + long *nelements) +{ + caLink *pca = (caLink *)plink->value.pv_link.pvt; + long status = 0; + short link_action = 0; + int newType; + + assert(pca); + epicsMutexMustLock(pca->lock); + assert(pca->plink); + if (!pca->isConnected || !pca->hasReadAccess) { + pca->sevr = INVALID_ALARM; + pca->stat = LINK_ALARM; + status = -1; + goto done; + } + if (pca->dbrType == DBR_ENUM && dbDBRnewToDBRold[dbrType] == DBR_STRING){ + long (*fConvert)(const void *from, void *to, struct dbAddr *paddr); + + /* Subscribe as DBR_STRING */ + if (!pca->pgetString) { + plink->value.pv_link.pvlMask |= pvlOptInpString; + link_action |= CA_MONITOR_STRING; + } + if (!pca->gotInString) { + pca->sevr = INVALID_ALARM; + pca->stat = LINK_ALARM; + status = -1; + goto done; + } + if (nelements) *nelements = 1; + fConvert = dbFastGetConvertRoutine[dbDBRoldToDBFnew[DBR_STRING]][dbrType]; + status = fConvert(pca->pgetString, pdest, 0); + goto done; + } + if (!pca->pgetNative) { + plink->value.pv_link.pvlMask |= pvlOptInpNative; + link_action |= CA_MONITOR_NATIVE; + } + if (!pca->gotInNative){ + pca->sevr = INVALID_ALARM; + pca->stat = LINK_ALARM; + status = -1; + goto done; + } + newType = dbDBRoldToDBFnew[pca->dbrType]; + if (!nelements || *nelements == 1) { + long (*fConvert)(const void *from, void *to, struct dbAddr *paddr); + + fConvert = dbFastGetConvertRoutine[newType][dbrType]; + assert(pca->pgetNative); + status = fConvert(pca->pgetNative, pdest, 0); + } else { + unsigned long ntoget = *nelements; + struct dbAddr dbAddr; + long (*aConvert)(struct dbAddr *paddr, void *to, long nreq, long nto, long off); + + aConvert = dbGetConvertRoutine[newType][dbrType]; + assert(pca->pgetNative); + + if (ntoget > pca->usedelements) + ntoget = pca->usedelements; + *nelements = ntoget; + + memset((void *)&dbAddr, 0, sizeof(dbAddr)); + dbAddr.pfield = pca->pgetNative; + /*Following will only be used for pca->dbrType == DBR_STRING*/ + dbAddr.field_size = MAX_STRING_SIZE; + /*Ignore error return*/ + aConvert(&dbAddr, pdest, ntoget, ntoget, 0); + } +done: + if (link_action) + addAction(pca, link_action); + if (!status) + recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode, + plink->precord, pca->stat, pca->sevr); + epicsMutexUnlock(pca->lock); + + return status; +} + +static long dbCaPutAsync(struct link *plink,short dbrType, + const void *pbuffer,long nRequest) +{ + return dbCaPutLinkCallback(plink, dbrType, pbuffer, nRequest, + dbCaCallbackProcess, plink); +} + +long dbCaPutLinkCallback(struct link *plink,short dbrType, + const void *pbuffer,long nRequest,dbCaCallback callback,void *userPvt) +{ + caLink *pca = (caLink *)plink->value.pv_link.pvt; + long status = 0; + short link_action = 0; + + assert(pca); + /* put the new value in */ + epicsMutexMustLock(pca->lock); + assert(pca->plink); + if (!pca->isConnected || !pca->hasWriteAccess) { + epicsMutexUnlock(pca->lock); + return -1; + } + if (pca->dbrType == DBR_ENUM && dbDBRnewToDBRold[dbrType] == DBR_STRING) { + long (*fConvert)(const void *from, void *to, struct dbAddr *paddr); + + /* Send as DBR_STRING */ + if (!pca->pputString) { + pca->pputString = dbCalloc(1, MAX_STRING_SIZE); +/* Disabled by ANJ, needs a link flag to allow user to control this. + * Setting these makes the reconnect callback re-do the last CA put. + plink->value.pv_link.pvlMask |= pvlOptOutString; + */ + } + fConvert = dbFastPutConvertRoutine[dbrType][dbDBRoldToDBFnew[DBR_STRING]]; + status = fConvert(pbuffer, pca->pputString, 0); + link_action |= CA_WRITE_STRING; + pca->gotOutString = TRUE; + if (pca->newOutString) pca->nNoWrite++; + pca->newOutString = TRUE; + } else { + int newType = dbDBRoldToDBFnew[pca->dbrType]; + if (!pca->pputNative) { + pca->pputNative = dbCalloc(pca->nelements, + dbr_value_size[ca_field_type(pca->chid)]); + pca->putnelements = 0; +/* Fixed and disabled by ANJ, see comment above. + plink->value.pv_link.pvlMask |= pvlOptOutNative; + */ + } + if (nRequest == 1 && pca->nelements==1){ + long (*fConvert)(const void *from, void *to, struct dbAddr *paddr); + + fConvert = dbFastPutConvertRoutine[dbrType][newType]; + status = fConvert(pbuffer, pca->pputNative, 0); + pca->putnelements = 1; + } else { + struct dbAddr dbAddr; + long (*aConvert)(struct dbAddr *paddr, const void *from, long nreq, long nfrom, long off); + + aConvert = dbPutConvertRoutine[dbrType][newType]; + memset((void *)&dbAddr, 0, sizeof(dbAddr)); + dbAddr.pfield = pca->pputNative; + /*Following only used for DBF_STRING*/ + dbAddr.field_size = MAX_STRING_SIZE; + if(nRequest>pca->nelements) + nRequest = pca->nelements; + status = aConvert(&dbAddr, pbuffer, nRequest, pca->nelements, 0); + pca->putnelements = nRequest; + } + link_action |= CA_WRITE_NATIVE; + pca->gotOutNative = TRUE; + if (pca->newOutNative) pca->nNoWrite++; + pca->newOutNative = TRUE; + } + if (callback) { + pca->putType = CA_PUT_CALLBACK; + pca->putCallback = callback; + pca->putUserPvt = userPvt; + } else { + pca->putType = CA_PUT; + pca->putCallback = 0; + } + addAction(pca, link_action); + epicsMutexUnlock(pca->lock); + return status; +} + +long dbCaPutLink(struct link *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + return dbCaPutLinkCallback(plink, dbrType, pbuffer, nRequest, 0, NULL); +} + +static int isConnected(const struct link *plink) +{ + caLink *pca; + + if (!plink || plink->type != CA_LINK) return FALSE; + pca = (caLink *)plink->value.pv_link.pvt; + if (!pca || !pca->chid) return FALSE; + return pca->isConnected; +} + +static void scanForward(struct link *plink) { + short fwdLinkValue = 1; + + if (plink->value.pv_link.pvlMask & pvlOptFWD) + dbCaPutLink(plink, DBR_SHORT, &fwdLinkValue, 1); +} + +#define pcaGetCheck \ + assert(plink); \ + if (plink->type != CA_LINK) return -1; \ + pca = (caLink *)plink->value.pv_link.pvt; \ + assert(pca); \ + epicsMutexMustLock(pca->lock); \ + assert(pca->plink); \ + if (!pca->isConnected) { \ + epicsMutexUnlock(pca->lock); \ + return -1; \ + } + +static long getElements(const struct link *plink, long *nelements) +{ + caLink *pca; + + pcaGetCheck + *nelements = pca->nelements; + epicsMutexUnlock(pca->lock); + return 0; +} + +static long getAlarm(const struct link *plink, + epicsEnum16 *pstat, epicsEnum16 *psevr) +{ + caLink *pca; + + pcaGetCheck + if (pstat) *pstat = pca->stat; + if (psevr) *psevr = pca->sevr; + epicsMutexUnlock(pca->lock); + return 0; +} + +static long getTimeStamp(const struct link *plink, + epicsTimeStamp *pstamp) +{ + caLink *pca; + + pcaGetCheck + memcpy(pstamp, &pca->timeStamp, sizeof(epicsTimeStamp)); + epicsMutexUnlock(pca->lock); + return 0; +} + +static int getDBFtype(const struct link *plink) +{ + caLink *pca; + int type; + + pcaGetCheck + type = dbDBRoldToDBFnew[pca->dbrType]; + epicsMutexUnlock(pca->lock); + return type; +} + +long dbCaGetAttributes(const struct link *plink, + dbCaCallback callback,void *userPvt) +{ + caLink *pca; + int gotAttributes; + + assert(plink); + if (plink->type != CA_LINK) return -1; + pca = (caLink *)plink->value.pv_link.pvt; + assert(pca); + epicsMutexMustLock(pca->lock); + assert(pca->plink); + pca->getAttributes = callback; + pca->getAttributesPvt = userPvt; + gotAttributes = pca->gotAttributes; + epicsMutexUnlock(pca->lock); + if (gotAttributes && callback) callback(userPvt); + return 0; +} + +static long getControlLimits(const struct link *plink, + double *low, double *high) +{ + caLink *pca; + int gotAttributes; + + pcaGetCheck + gotAttributes = pca->gotAttributes; + if (gotAttributes) { + *low = pca->controlLimits[0]; + *high = pca->controlLimits[1]; + } + epicsMutexUnlock(pca->lock); + return gotAttributes ? 0 : -1; +} + +static long getGraphicLimits(const struct link *plink, + double *low, double *high) +{ + caLink *pca; + int gotAttributes; + + pcaGetCheck + gotAttributes = pca->gotAttributes; + if (gotAttributes) { + *low = pca->displayLimits[0]; + *high = pca->displayLimits[1]; + } + epicsMutexUnlock(pca->lock); + return gotAttributes ? 0 : -1; +} + +static long getAlarmLimits(const struct link *plink, + double *lolo, double *low, double *high, double *hihi) +{ + caLink *pca; + int gotAttributes; + + pcaGetCheck + gotAttributes = pca->gotAttributes; + if (gotAttributes) { + *lolo = pca->alarmLimits[0]; + *low = pca->alarmLimits[1]; + *high = pca->alarmLimits[2]; + *hihi = pca->alarmLimits[3]; + } + epicsMutexUnlock(pca->lock); + return gotAttributes ? 0 : -1; +} + +static long getPrecision(const struct link *plink, short *precision) +{ + caLink *pca; + int gotAttributes; + + pcaGetCheck + gotAttributes = pca->gotAttributes; + if (gotAttributes) *precision = pca->precision; + epicsMutexUnlock(pca->lock); + return gotAttributes ? 0 : -1; +} + +static long getUnits(const struct link *plink, + char *units, int unitsSize) +{ + caLink *pca; + int gotAttributes; + + pcaGetCheck + gotAttributes = pca->gotAttributes; + if (unitsSize > sizeof(pca->units)) unitsSize = sizeof(pca->units); + if (gotAttributes) strncpy(units, pca->units, unitsSize); + units[unitsSize-1] = 0; + epicsMutexUnlock(pca->lock); + return gotAttributes ? 0 : -1; +} + +static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) +{ + caLink *pca; + long status; + + pcaGetCheck + status = rtn(plink, priv); + epicsMutexUnlock(pca->lock); + return status; +} + +static void scanComplete(void *raw, dbCommon *prec) +{ + caLink *pca = raw; + epicsMutexMustLock(pca->lock); + if(!pca->plink) { + /* IOC shutdown or link re-targeted. Do nothing. */ + } else if(pca->scanningOnce==0) { + errlogPrintf("dbCa.c complete callback w/ scanningOnce==0\n"); + } else if(--pca->scanningOnce){ + /* another scan is queued */ + if(scanOnceCallback(prec, scanComplete, raw)) { + errlogPrintf("dbCa.c failed to re-queue scanOnce\n"); + } else + caLinkInc(pca); + } + epicsMutexUnlock(pca->lock); + caLinkDec(pca); +} + +/* must be called with pca->lock held */ +static void scanLinkOnce(dbCommon *prec, caLink *pca) { + if(pca->scanningOnce==0) { + if(scanOnceCallback(prec, scanComplete, pca)) { + errlogPrintf("dbCa.c failed to queue scanOnce\n"); + } else + caLinkInc(pca); + } + if(pca->scanningOnce<5) + pca->scanningOnce++; + /* else too many scans queued */ +} + +static lset dbCa_lset = { + 0, 1, /* not Constant, Volatile */ + NULL, dbCaRemoveLink, + NULL, NULL, NULL, + isConnected, + getDBFtype, getElements, + dbCaGetLink, + getControlLimits, getGraphicLimits, getAlarmLimits, + getPrecision, getUnits, + getAlarm, getTimeStamp, + dbCaPutLink, dbCaPutAsync, + scanForward, doLocked +}; + +static void connectionCallback(struct connection_handler_args arg) +{ + caLink *pca; + short link_action = 0; + struct link *plink; + + pca = ca_puser(arg.chid); + assert(pca); + epicsMutexMustLock(pca->lock); + plink = pca->plink; + if (!plink) goto done; + pca->isConnected = (ca_state(arg.chid) == cs_conn); + if (!pca->isConnected) { + struct pv_link *ppv_link = &plink->value.pv_link; + dbCommon *precord = plink->precord; + + pca->nDisconnect++; + if (precord && + ((ppv_link->pvlMask & pvlOptCP) || + ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0))) + scanLinkOnce(precord, pca); + goto done; + } + pca->hasReadAccess = ca_read_access(arg.chid); + pca->hasWriteAccess = ca_write_access(arg.chid); + + if (pca->gotFirstConnection) { + if (pca->nelements != ca_element_count(arg.chid) || + pca->dbrType != ca_field_type(arg.chid)) { + /* BUG: We have no way to clear any old subscription with the + * originally chosen data type/size. That will continue + * to send us data and will result in an assert() fail. + */ + /* Let next dbCaGetLink and/or dbCaPutLink determine options */ + plink->value.pv_link.pvlMask &= + ~(pvlOptInpNative | pvlOptInpString | + pvlOptOutNative | pvlOptOutString); + + pca->gotInNative = 0; + pca->gotOutNative = 0; + pca->gotInString = 0; + pca->gotOutString = 0; + free(pca->pgetNative); pca->pgetNative = 0; + free(pca->pgetString); pca->pgetString = 0; + free(pca->pputNative); pca->pputNative = 0; + free(pca->pputString); pca->pputString = 0; + } + } + pca->gotFirstConnection = TRUE; + pca->nelements = ca_element_count(arg.chid); + pca->usedelements = 0; + pca->dbrType = ca_field_type(arg.chid); + if ((plink->value.pv_link.pvlMask & pvlOptInpNative) && !pca->pgetNative) { + link_action |= CA_MONITOR_NATIVE; + } + if ((plink->value.pv_link.pvlMask & pvlOptInpString) && !pca->pgetString) { + link_action |= CA_MONITOR_STRING; + } + if ((plink->value.pv_link.pvlMask & pvlOptOutNative) && pca->gotOutNative) { + link_action |= CA_WRITE_NATIVE; + } + if ((plink->value.pv_link.pvlMask & pvlOptOutString) && pca->gotOutString) { + link_action |= CA_WRITE_STRING; + } + pca->gotAttributes = 0; + if (pca->dbrType != DBR_STRING) { + link_action |= CA_GET_ATTRIBUTES; + } +done: + if (link_action) addAction(pca, link_action); + epicsMutexUnlock(pca->lock); +} + +static void eventCallback(struct event_handler_args arg) +{ + caLink *pca = (caLink *)arg.usr; + struct link *plink; + size_t size; + dbCommon *precord = 0; + struct dbr_time_double *pdbr_time_double; + dbCaCallback monitor = 0; + void *userPvt = 0; + + assert(pca); + epicsMutexMustLock(pca->lock); + plink = pca->plink; + if (!plink) goto done; + pca->nUpdate++; + monitor = pca->monitor; + userPvt = pca->userPvt; + precord = plink->precord; + if (arg.status != ECA_NORMAL) { + if (precord) { + if (arg.status != ECA_NORDACCESS && + arg.status != ECA_GETFAIL) + errlogPrintf("dbCa: eventCallback record %s error %s\n", + precord->name, ca_message(arg.status)); + } else { + errlogPrintf("dbCa: eventCallback error %s\n", + ca_message(arg.status)); + } + goto done; + } + assert(arg.dbr); + assert(arg.count<=pca->nelements); + size = arg.count * dbr_value_size[arg.type]; + if (arg.type == DBR_TIME_STRING && + ca_field_type(pca->chid) == DBR_ENUM) { + assert(pca->pgetString); + memcpy(pca->pgetString, dbr_value_ptr(arg.dbr, arg.type), size); + pca->gotInString = TRUE; + } else switch (arg.type){ + case DBR_TIME_STRING: + case DBR_TIME_SHORT: + case DBR_TIME_FLOAT: + case DBR_TIME_ENUM: + case DBR_TIME_CHAR: + case DBR_TIME_LONG: + case DBR_TIME_DOUBLE: + assert(pca->pgetNative); + memcpy(pca->pgetNative, dbr_value_ptr(arg.dbr, arg.type), size); + pca->usedelements = arg.count; + pca->gotInNative = TRUE; + break; + default: + errlogPrintf("dbCa: eventCallback Logic Error. dbr=%ld dbf=%d\n", + arg.type, ca_field_type(pca->chid)); + break; + } + pdbr_time_double = (struct dbr_time_double *)arg.dbr; + pca->sevr = pdbr_time_double->severity; + pca->stat = pdbr_time_double->status; + memcpy(&pca->timeStamp, &pdbr_time_double->stamp, sizeof(epicsTimeStamp)); + if (precord) { + struct pv_link *ppv_link = &plink->value.pv_link; + + if ((ppv_link->pvlMask & pvlOptCP) || + ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)) + scanLinkOnce(precord, pca); + } +done: + epicsMutexUnlock(pca->lock); + if (monitor) monitor(userPvt); +} + +static void exceptionCallback(struct exception_handler_args args) +{ + const char *context = (args.ctx ? args.ctx : "unknown"); + + errlogPrintf("DB CA Link Exception: \"%s\", context \"%s\"\n", + ca_message(args.stat), context); + if (args.chid) { + errlogPrintf( + "DB CA Link Exception: channel \"%s\"\n", + ca_name(args.chid)); + if (ca_state(args.chid) == cs_conn) { + errlogPrintf( + "DB CA Link Exception: native T=%s, request T=%s," + " native N=%ld, request N=%ld, " + " access rights {%s%s}\n", + dbr_type_to_text(ca_field_type(args.chid)), + dbr_type_to_text(args.type), + ca_element_count(args.chid), + args.count, + ca_read_access(args.chid) ? "R" : "", + ca_write_access(args.chid) ? "W" : ""); + } + } +} + +static void putComplete(struct event_handler_args arg) +{ + caLink *pca = (caLink *)arg.usr; + struct link *plink; + dbCaCallback callback = 0; + void *userPvt = 0; + + epicsMutexMustLock(pca->lock); + plink = pca->plink; + if (!plink) goto done; + callback = pca->putCallback; + userPvt = pca->putUserPvt; + pca->putCallback = 0; + pca->putType = 0; + pca->putUserPvt = 0; +done: + epicsMutexUnlock(pca->lock); + if (callback) callback(userPvt); +} + +static void accessRightsCallback(struct access_rights_handler_args arg) +{ + caLink *pca = (caLink *)ca_puser(arg.chid); + struct link *plink; + struct pv_link *ppv_link; + dbCommon *precord; + + assert(pca); + if (ca_state(pca->chid) != cs_conn) + return; /* connectionCallback will handle */ + epicsMutexMustLock(pca->lock); + plink = pca->plink; + if (!plink) goto done; + pca->hasReadAccess = ca_read_access(arg.chid); + pca->hasWriteAccess = ca_write_access(arg.chid); + if (pca->hasReadAccess && pca->hasWriteAccess) goto done; + ppv_link = &plink->value.pv_link; + precord = plink->precord; + if (precord && + ((ppv_link->pvlMask & pvlOptCP) || + ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0))) + scanLinkOnce(precord, pca); +done: + epicsMutexUnlock(pca->lock); +} + +static void getAttribEventCallback(struct event_handler_args arg) +{ + caLink *pca = (caLink *)arg.usr; + struct link *plink; + struct dbr_ctrl_double *pdbr; + dbCaCallback connect = 0; + void *userPvt = 0; + dbCaCallback getAttributes = 0; + void *getAttributesPvt; + + assert(pca); + epicsMutexMustLock(pca->lock); + plink = pca->plink; + if (!plink) { + epicsMutexUnlock(pca->lock); + return; + } + connect = pca->connect; + userPvt = pca->userPvt; + getAttributes = pca->getAttributes; + getAttributesPvt = pca->getAttributesPvt; + if (arg.status != ECA_NORMAL) { + dbCommon *precord = plink->precord; + if (precord) { + errlogPrintf("dbCa: getAttribEventCallback record %s error %s\n", + precord->name, ca_message(arg.status)); + } else { + errlogPrintf("dbCa: getAttribEventCallback error %s\n", + ca_message(arg.status)); + } + epicsMutexUnlock(pca->lock); + return; + } + assert(arg.dbr); + pdbr = (struct dbr_ctrl_double *)arg.dbr; + pca->gotAttributes = TRUE; + pca->controlLimits[0] = pdbr->lower_ctrl_limit; + pca->controlLimits[1] = pdbr->upper_ctrl_limit; + pca->displayLimits[0] = pdbr->lower_disp_limit; + pca->displayLimits[1] = pdbr->upper_disp_limit; + pca->alarmLimits[0] = pdbr->lower_alarm_limit; + pca->alarmLimits[1] = pdbr->lower_warning_limit; + pca->alarmLimits[2] = pdbr->upper_warning_limit; + pca->alarmLimits[3] = pdbr->upper_alarm_limit; + pca->precision = pdbr->precision; + memcpy(pca->units, pdbr->units, MAX_UNITS_SIZE); + epicsMutexUnlock(pca->lock); + if (getAttributes) getAttributes(getAttributesPvt); + if (connect) connect(userPvt); +} + +static void dbCaTask(void *arg) +{ + taskwdInsert(0, NULL, NULL); + SEVCHK(ca_context_create(ca_enable_preemptive_callback), + "dbCaTask calling ca_context_create"); + dbCaClientContext = ca_current_context (); + SEVCHK(ca_add_exception_event(exceptionCallback,NULL), + "ca_add_exception_event"); + epicsEventSignal(startStopEvent); + + /* channel access event loop */ + while (TRUE){ + do { + epicsEventMustWait(workListEvent); + } while (dbCaCtl == ctlPause); + while (TRUE) { /* process all requests in workList*/ + caLink *pca; + short link_action; + int status; + + epicsMutexMustLock(workListLock); + if (!(pca = (caLink *)ellGet(&workList))){ /* Take off list head */ + epicsMutexUnlock(workListLock); + if (dbCaCtl == ctlExit) goto shutdown; + break; /* workList is empty */ + } + link_action = pca->link_action; + if (link_action&CA_SYNC) + epicsEventMustTrigger((epicsEventId)pca->userPvt); /* dbCaSync() requires workListLock to be held here */ + pca->link_action = 0; + if (link_action & CA_CLEAR_CHANNEL) --removesOutstanding; + epicsMutexUnlock(workListLock); /* Give back immediately */ + if (link_action&CA_SYNC) + continue; + if (link_action & CA_CLEAR_CHANNEL) { /* This must be first */ + caLinkDec(pca); + /* No alarm is raised. Since link is changing so what? */ + continue; /* No other link_action makes sense */ + } + if (link_action & CA_CONNECT) { + status = ca_create_channel( + pca->pvname,connectionCallback,(void *)pca, + CA_PRIORITY_DB_LINKS, &(pca->chid)); + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_create_channel %s\n", + ca_message(status)); + printLinks(pca); + continue; + } + dbca_chan_count++; + status = ca_replace_access_rights_event(pca->chid, + accessRightsCallback); + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask replace_access_rights_event %s\n", + ca_message(status)); + printLinks(pca); + } + continue; /*Other options must wait until connect*/ + } + if (ca_state(pca->chid) != cs_conn) continue; + if (link_action & CA_WRITE_NATIVE) { + assert(pca->pputNative); + if (pca->putType == CA_PUT) { + status = ca_array_put( + pca->dbrType, pca->putnelements, + pca->chid, pca->pputNative); + } else if (pca->putType==CA_PUT_CALLBACK) { + status = ca_array_put_callback( + pca->dbrType, pca->putnelements, + pca->chid, pca->pputNative, + putComplete, pca); + } else { + status = ECA_PUTFAIL; + } + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_array_put %s\n", + ca_message(status)); + printLinks(pca); + } + epicsMutexMustLock(pca->lock); + if (status == ECA_NORMAL) pca->newOutNative = FALSE; + epicsMutexUnlock(pca->lock); + } + if (link_action & CA_WRITE_STRING) { + assert(pca->pputString); + if (pca->putType == CA_PUT) { + status = ca_array_put( + DBR_STRING, 1, + pca->chid, pca->pputString); + } else if (pca->putType==CA_PUT_CALLBACK) { + status = ca_array_put_callback( + DBR_STRING, 1, + pca->chid, pca->pputString, + putComplete, pca); + } else { + status = ECA_PUTFAIL; + } + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_array_put %s\n", + ca_message(status)); + printLinks(pca); + } + epicsMutexMustLock(pca->lock); + if (status == ECA_NORMAL) pca->newOutString = FALSE; + epicsMutexUnlock(pca->lock); + } + /*CA_GET_ATTRIBUTES before CA_MONITOR so that attributes available + * before the first monitor callback */ + if (link_action & CA_GET_ATTRIBUTES) { + status = ca_get_callback(DBR_CTRL_DOUBLE, + pca->chid, getAttribEventCallback, pca); + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_get_callback %s\n", + ca_message(status)); + printLinks(pca); + } + } + if (link_action & CA_MONITOR_NATIVE) { + + epicsMutexMustLock(pca->lock); + pca->elementSize = dbr_value_size[ca_field_type(pca->chid)]; + pca->pgetNative = dbCalloc(pca->nelements, pca->elementSize); + epicsMutexUnlock(pca->lock); + + status = ca_add_array_event( + dbf_type_to_DBR_TIME(ca_field_type(pca->chid)), + 0, /* dynamic size */ + pca->chid, eventCallback, pca, 0.0, 0.0, 0.0, 0); + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_add_array_event %s\n", + ca_message(status)); + printLinks(pca); + } + } + if (link_action & CA_MONITOR_STRING) { + epicsMutexMustLock(pca->lock); + pca->pgetString = dbCalloc(1, MAX_STRING_SIZE); + epicsMutexUnlock(pca->lock); + status = ca_add_array_event(DBR_TIME_STRING, 1, + pca->chid, eventCallback, pca, 0.0, 0.0, 0.0, 0); + if (status != ECA_NORMAL) { + errlogPrintf("dbCaTask ca_add_array_event %s\n", + ca_message(status)); + printLinks(pca); + } + } + } + SEVCHK(ca_flush_io(), "dbCaTask"); + } +shutdown: + taskwdRemove(0); + if (dbca_chan_count == 0) + ca_context_destroy(); + else + fprintf(stderr, "dbCa: chan_count = %d at shutdown\n", dbca_chan_count); + epicsEventSignal(startStopEvent); +} diff --git a/modules/database/src/ioc/db/dbCa.h b/modules/database/src/ioc/db/dbCa.h new file mode 100644 index 000000000..de25ef59f --- /dev/null +++ b/modules/database/src/ioc/db/dbCa.h @@ -0,0 +1,87 @@ +/*************************************************************************\ +* Copyright (c) 2015 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbCa.h */ + +#ifndef INCdbCah +#define INCdbCah + +#include "dbLink.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*dbCaCallback)(void *userPvt); +epicsShareFunc void dbCaCallbackProcess(void *usrPvt); + +epicsShareFunc void dbCaLinkInit(void); /* internal initialization for iocBuild() */ +epicsShareFunc void dbCaLinkInitIsolated(void); /* internal initialization for iocBuildIsolated() */ +epicsShareFunc void dbCaRun(void); +epicsShareFunc void dbCaPause(void); +epicsShareFunc void dbCaShutdown(void); + +struct dbLocker; +epicsShareFunc void dbCaAddLinkCallback(struct link *plink, + dbCaCallback connect, dbCaCallback monitor, void *userPvt); +epicsShareFunc long dbCaAddLink(struct dbLocker *locker, struct link *plink, short dbfType); +epicsShareFunc void dbCaRemoveLink(struct dbLocker *locker, struct link *plink); + +epicsShareFunc long dbCaGetLink(struct link *plink, + short dbrType, void *pbuffer, long *nRequest); + +epicsShareFunc long dbCaGetAttributes(const struct link *plink, + dbCaCallback callback, void *userPvt); + +epicsShareFunc long dbCaPutLinkCallback(struct link *plink, + short dbrType, const void *pbuffer,long nRequest, + dbCaCallback callback, void *userPvt); +epicsShareFunc long dbCaPutLink(struct link *plink,short dbrType, + const void *pbuffer,long nRequest); + +extern struct ca_client_context * dbCaClientContext; + +#ifdef EPICS_DBCA_PRIVATE_API +epicsShareFunc void dbCaSync(void); +epicsShareFunc unsigned long dbCaGetUpdateCount(struct link *plink); +#endif + +/* These macros are for backwards compatibility */ + +#define dbCaIsLinkConnected(link) \ + dbIsLinkConnected(link) + +#define dbCaGetLinkDBFtype(link) \ + dbGetLinkDBFtype(link) +#define dbCaGetNelements(link, nelements) \ + dbGetNelements(link, nelements) +#define dbCaGetSevr(link, sevr) \ + dbGetAlarm(link, NULL, sevr) +#define dbCaGetAlarm(link, stat, sevr) \ + dbGetAlarm(link, stat, sevr) +#define dbCaGetTimeStamp(link, pstamp) \ + dbGetTimeStamp(link, pstamp) +#define dbCaGetControlLimits(link, low, high) \ + dbGetControlLimits(link, low, high) +#define dbCaGetGraphicLimits(link, low, high) \ + dbGetGraphicLimits(link, low, high) +#define dbCaGetAlarmLimits(link, lolo, low, high, hihi) \ + dbGetAlarmLimits(link, lolo, low, high, hihi) +#define dbCaGetPrecision(link, prec) \ + dbGetPrecision(link, prec) +#define dbCaGetUnits(link, units, unitSize) \ + dbGetUnits(link, units, unitSize) + +#define dbCaScanFwdLink(link) \ + dbScanFwdLink(link) + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbCah*/ diff --git a/modules/database/src/ioc/db/dbCaPvt.h b/modules/database/src/ioc/db/dbCaPvt.h new file mode 100644 index 000000000..454ead5ac --- /dev/null +++ b/modules/database/src/ioc/db/dbCaPvt.h @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbCaPvt.h + * + * Original Authors: Bob Dalesio, Marty Kraimer + * + */ + +#ifndef INC_dbCaPvt_H +#define INC_dbCaPvt_H + +#include "dbCa.h" +#include "ellLib.h" +#include "epicsMutex.h" +#include "epicsTypes.h" +#include "link.h" + +/* link_action mask */ +#define CA_CLEAR_CHANNEL 0x1 +#define CA_CONNECT 0x2 +#define CA_WRITE_NATIVE 0x4 +#define CA_WRITE_STRING 0x8 +#define CA_MONITOR_NATIVE 0x10 +#define CA_MONITOR_STRING 0x20 +#define CA_GET_ATTRIBUTES 0x40 +#define CA_SYNC 0x1000 +/* write type */ +#define CA_PUT 0x1 +#define CA_PUT_CALLBACK 0x2 + +typedef struct caLink +{ + ELLNODE node; + int refcount; + epicsMutexId lock; + struct link *plink; + char *pvname; + chid chid; + short link_action; + /* The following have new values after each data event*/ + epicsEnum16 sevr; + epicsEnum16 stat; + epicsTimeStamp timeStamp; + /* The following have values after connection*/ + short dbrType; + size_t elementSize; /* size of one element in pgetNative */ + unsigned long nelements; /* PVs max array size */ + unsigned long usedelements; /* currently used in pgetNative */ + unsigned long putnelements; /* currently used in pputNative */ + char hasReadAccess; + char hasWriteAccess; + char isConnected; + char gotFirstConnection; + /* The following are for dbCaAddLinkCallback */ + dbCaCallback connect; + dbCaCallback monitor; + void *userPvt; + /* The following are for write request */ + short putType; + dbCaCallback putCallback; + void *putUserPvt; + /* The following are for access to additional attributes*/ + char gotAttributes; + dbCaCallback getAttributes; + void *getAttributesPvt; + /* The following have values after getAttribEventCallback*/ + double controlLimits[2]; + double displayLimits[2]; + double alarmLimits[4]; + short precision; + char units[MAX_UNITS_SIZE]; /* units of value */ + /* The following are for handling data*/ + void *pgetNative; + char *pgetString; + void *pputNative; + char *pputString; + char gotInNative; + char gotInString; + char gotOutNative; + char gotOutString; + char newOutNative; + char newOutString; + unsigned char scanningOnce; + /* The following are for dbcar*/ + unsigned long nDisconnect; + unsigned long nNoWrite; /*only modified by dbCaPutLink*/ + unsigned long nUpdate; +}caLink; + +#endif /* INC_dbCaPvt_H */ diff --git a/modules/database/src/ioc/db/dbCaTest.c b/modules/database/src/ioc/db/dbCaTest.c new file mode 100644 index 000000000..18ef393ca --- /dev/null +++ b/modules/database/src/ioc/db/dbCaTest.c @@ -0,0 +1,206 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbCaTest.c */ + +/**************************************************************** +* +* Author: Marty Kraimer +* Date: 10APR96 +* +****************************************************************/ + +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsEvent.h" +#include "epicsPrint.h" +#include "epicsStdio.h" + +#define epicsExportSharedSymbols +#include "dbStaticLib.h" +#undef epicsExportSharedSymbols +/*definitions needed because of old vs new database access*/ +#undef DBR_SHORT +#undef DBR_PUT_ACKT +#undef DBR_PUT_ACKS +#undef VALID_DB_REQ +#undef INVALID_DB_REQ +/*end of conflicting definitions*/ + +#include "cadef.h" + +/*define DB_CONVERT_GBLSOURCE because db_access.c does not include db_access.h*/ +#define DB_CONVERT_GBLSOURCE + +#define epicsExportSharedSymbols +#include "db_access.h" +#include "db_access_routines.h" +#include "dbCa.h" +#include "dbCaPvt.h" +#include "dbCaTest.h" +#include "dbCommon.h" +#include "db_convert.h" +#include "dbLock.h" +#include "link.h" + + +long dbcar(char *precordname, int level) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + dbCommon *precord; + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + DBLINK *plink; + int ncalinks=0; + int nconnected=0; + int noReadAccess=0; + int noWriteAccess=0; + unsigned long nDisconnect=0; + unsigned long nNoWrite=0; + caLink *pca; + int j; + + if (!precordname || precordname[0] == '\0' || !strcmp(precordname, "*")) { + precordname = NULL; + printf("CA links in all records\n\n"); + } else { + printf("CA links in record named '%s'\n\n", precordname); + } + dbInitEntry(pdbbase,pdbentry); + status = dbFirstRecordType(pdbentry); + while (!status) { + status = dbFirstRecord(pdbentry); + while (!status) { + if (precordname ? + !strcmp(precordname, dbGetRecordName(pdbentry)) : + !dbIsAlias(pdbentry)) { + pdbRecordType = pdbentry->precordType; + precord = (dbCommon *)pdbentry->precnode->precord; + dbScanLock(precord); + for (j=0; jno_links; j++) { + pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; + plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + if (plink->type == CA_LINK) { + ncalinks++; + pca = (caLink *)plink->value.pv_link.pvt; + if (pca + && pca->chid + && (ca_field_type(pca->chid) != TYPENOTCONN)) { + nconnected++; + nDisconnect += pca->nDisconnect; + nNoWrite += pca->nNoWrite; + if (!ca_read_access(pca->chid)) noReadAccess++; + if (!ca_write_access(pca->chid)) noWriteAccess++; + if (level>1) { + int rw = ca_read_access(pca->chid) | + ca_write_access(pca->chid) << 1; + static const char *rights[4] = { + "No Access", "Read Only", + "Write Only", "Read/Write" + }; + int mask = plink->value.pv_link.pvlMask; + printf("%28s.%-4s ==> %-28s (%lu, %lu)\n", + precord->name, + pdbFldDes->name, + plink->value.pv_link.pvname, + pca->nDisconnect, + pca->nNoWrite); + printf("%21s [%s%s%s%s] host %s, %s\n", "", + mask & pvlOptInpNative ? "IN" : " ", + mask & pvlOptInpString ? "IS" : " ", + mask & pvlOptOutNative ? "ON" : " ", + mask & pvlOptOutString ? "OS" : " ", + ca_host_name(pca->chid), + rights[rw]); + } + } else { + if (level>0) { + printf("%28s.%-4s --> %-28s (%lu, %lu)\n", + precord->name, + pdbFldDes->name, + plink->value.pv_link.pvname, + pca ? pca->nDisconnect : 0, + pca ? pca->nNoWrite : 0); + } + } + } + } + dbScanUnlock(precord); + if (precordname) goto done; + } + status = dbNextRecord(pdbentry); + } + status = dbNextRecordType(pdbentry); + } +done: + if ((level > 1 && nconnected > 0) || + (level > 0 && ncalinks != nconnected)) printf("\n"); + printf("Total %d CA link%s; ", + ncalinks, (ncalinks != 1) ? "s" : ""); + printf("%d connected, %d not connected.\n", + nconnected, (ncalinks - nconnected)); + printf(" %d can't read, %d can't write.", + noReadAccess, noWriteAccess); + printf(" (%lu disconnects, %lu writes prohibited)\n\n", + nDisconnect, nNoWrite); + dbFinishEntry(pdbentry); + + if ( level > 2 && dbCaClientContext != 0 ) { + ca_context_status ( dbCaClientContext, level - 2 ); + } + + return(0); +} + +void dbcaStats(int *pchans, int *pdiscon) +{ + DBENTRY dbentry; + DBENTRY *pdbentry = &dbentry; + long status; + DBLINK *plink; + long ncalinks = 0; + long nconnected = 0; + + dbInitEntry(pdbbase,pdbentry); + status = dbFirstRecordType(pdbentry); + while (!status) { + dbRecordType *pdbRecordType = pdbentry->precordType; + + status = dbFirstRecord(pdbentry); + while (!status) { + dbCommon *precord = (dbCommon *)pdbentry->precnode->precord; + int j; + + if (!dbIsAlias(pdbentry)) { + for (j=0; jno_links; j++) { + int i = pdbRecordType->link_ind[j]; + + dbFldDes *pdbFldDes = pdbRecordType->papFldDes[i]; + plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + if (plink->type == CA_LINK) { + ncalinks++; + if (dbCaIsLinkConnected(plink)) { + nconnected++; + } + } + } + } + status = dbNextRecord(pdbentry); + } + status = dbNextRecordType(pdbentry); + } + dbFinishEntry(pdbentry); + if (pchans) *pchans = ncalinks; + if (pdiscon) *pdiscon = ncalinks - nconnected; +} diff --git a/modules/database/src/ioc/db/dbCaTest.h b/modules/database/src/ioc/db/dbCaTest.h new file mode 100644 index 000000000..ed501df2d --- /dev/null +++ b/modules/database/src/ioc/db/dbCaTest.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbCaTest_H +#define INC_dbCaTest_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc long dbcar(char *recordname,int level); +epicsShareFunc void dbcaStats(int *pchans, int *pdiscon); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbCaTest_H */ diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c new file mode 100644 index 000000000..92be08c77 --- /dev/null +++ b/modules/database/src/ioc/db/dbChannel.c @@ -0,0 +1,826 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Andrew Johnson + * Ralph Lange + */ + +#include +#include +#include + +#include "cantProceed.h" +#include "epicsAssert.h" +#include "epicsString.h" +#include "errlog.h" +#include "freeList.h" +#include "gpHash.h" +#include "yajl_parse.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbBase.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "dbLock.h" +#include "dbStaticLib.h" +#include "link.h" +#include "recSup.h" +#include "special.h" + +typedef struct parseContext { + dbChannel *chan; + chFilter *filter; + int depth; +} parseContext; + +#define CALLIF(rtn) !rtn ? parse_stop : rtn + +static void *dbChannelFreeList; +static void *chFilterFreeList; +static void *dbchStringFreeList; + +void dbChannelExit(void) +{ + freeListCleanup(dbChannelFreeList); + freeListCleanup(chFilterFreeList); + freeListCleanup(dbchStringFreeList); + dbChannelFreeList = chFilterFreeList = dbchStringFreeList = NULL; +} + +void dbChannelInit (void) +{ + if(dbChannelFreeList) + return; + + freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128); + freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64); + freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128); +} + +static void chf_value(parseContext *parser, parse_result *presult) +{ + chFilter *filter = parser->filter; + + if (*presult == parse_stop || parser->depth > 0) + return; + + parser->filter = NULL; + if (filter->plug->fif->parse_end(filter) == parse_continue) { + ellAdd(&parser->chan->filters, &filter->list_node); + } else { + freeListFree(chFilterFreeList, filter); + *presult = parse_stop; + } +} + +static int chf_null(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_null)(filter ); + chf_value(parser, &result); + return result; +} + +static int chf_boolean(void * ctx, int boolVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_boolean)(filter , boolVal); + chf_value(parser, &result); + return result; +} + +static int chf_integer(void * ctx, long long integerVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_integer)(filter , integerVal); + chf_value(parser, &result); + return result; +} + +static int chf_double(void * ctx, double doubleVal) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_double)(filter , doubleVal); + chf_value(parser, &result); + return result; +} + +static int chf_string(void * ctx, const unsigned char * stringVal, + size_t stringLen) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_string)(filter , (const char *) stringVal, stringLen); + chf_value(parser, &result); + return result; +} + +static int chf_start_map(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + + if (!filter) { + assert(parser->depth == 0); + return parse_continue; /* Opening '{' */ + } + + ++parser->depth; + return CALLIF(filter->plug->fif->parse_start_map)(filter ); +} + +static int chf_map_key(void * ctx, const unsigned char * key, + size_t stringLen) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + const chFilterPlugin *plug; + parse_result result; + + if (filter) { + assert(parser->depth > 0); + return CALLIF(filter->plug->fif->parse_map_key)(filter , (const char *) key, stringLen); + } + + assert(parser->depth == 0); + plug = dbFindFilter((const char *) key, stringLen); + if (!plug) { + errlogPrintf("dbChannelCreate: Channel filter '%.*s' not found\n", + (int) stringLen, key); + return parse_stop; + } + + filter = freeListCalloc(chFilterFreeList); + if (!filter) { + errlogPrintf("dbChannelCreate: Out of memory\n"); + return parse_stop; + } + filter->chan = parser->chan; + filter->plug = plug; + filter->puser = NULL; + + result = plug->fif->parse_start(filter); + if (result == parse_continue) { + parser->filter = filter; + } else { + freeListFree(chFilterFreeList, filter); + } + return result; +} + +static int chf_end_map(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + if (!filter) { + assert(parser->depth == 0); + return parse_continue; /* Final closing '}' */ + } + + assert(parser->depth > 0); + result = CALLIF(filter->plug->fif->parse_end_map)(filter ); + + --parser->depth; + chf_value(parser, &result); + return result; +} + +static int chf_start_array(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + + assert(filter); + ++parser->depth; + return CALLIF(filter->plug->fif->parse_start_array)(filter ); +} + +static int chf_end_array(void * ctx) +{ + parseContext *parser = (parseContext *) ctx; + chFilter *filter = parser->filter; + parse_result result; + + assert(filter); + result = CALLIF(filter->plug->fif->parse_end_array)(filter ); + --parser->depth; + chf_value(parser, &result); + return result; +} + +static const yajl_callbacks chf_callbacks = + { chf_null, chf_boolean, chf_integer, chf_double, NULL, chf_string, + chf_start_map, chf_map_key, chf_end_map, chf_start_array, chf_end_array }; + +static void * chf_malloc(void *ctx, size_t sz) +{ + return malloc(sz); +} + +static void * chf_realloc(void *ctx, void *ptr, size_t sz) +{ + return realloc(ptr, sz); +} + +static void chf_free(void *ctx, void *ptr) +{ + free(ptr); +} + +static yajl_alloc_funcs chf_alloc = + { chf_malloc, chf_realloc, chf_free }; + +static long chf_parse(dbChannel *chan, const char **pjson) +{ + parseContext parser = + { chan, NULL, 0 }; + yajl_handle yh = yajl_alloc(&chf_callbacks, &chf_alloc, &parser); + const char *json = *pjson; + size_t jlen = strlen(json), ylen; + yajl_status ys; + long status; + + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, jlen); + ylen = yajl_get_bytes_consumed(yh); + + if (ys == yajl_status_ok) + ys = yajl_complete_parse(yh); + + switch (ys) { + case yajl_status_ok: + *pjson += ylen; + status = 0; + break; + + case yajl_status_error: { + unsigned char *err; + + err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen); + printf("dbChannelCreate: %s\n", err); + yajl_free_error(yh, err); + } /* fall through */ + default: + status = S_db_notFound; + } + + if (parser.filter) { + assert(status); + parser.filter->plug->fif->parse_abort(parser.filter); + freeListFree(chFilterFreeList, parser.filter); + } + yajl_free(yh); + return status; +} + +static long pvNameLookup(DBENTRY *pdbe, const char **ppname) +{ + long status; + + dbInitEntry(pdbbase, pdbe); + + status = dbFindRecordPart(pdbe, ppname); + if (status) + return status; + + if (**ppname == '.') + ++*ppname; + + status = dbFindFieldPart(pdbe, ppname); + if (status == S_dbLib_fieldNotFound) + status = dbGetAttributePart(pdbe, ppname); + + return status; +} + +long dbChannelTest(const char *name) +{ + DBENTRY dbEntry; + long status; + + if (!name || !*name || !pdbbase) + return S_db_notFound; + + status = pvNameLookup(&dbEntry, &name); + + dbFinishEntry(&dbEntry); + return status; +} + +#define TRY(Func, Arg) \ +if (Func) { \ + result = Func Arg; \ + if (result != parse_continue) goto failure; \ +} + +static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppnext) { + epicsInt32 start = 0; + epicsInt32 end = -1; + epicsInt32 incr = 1; + epicsInt32 l; + char *pnext; + ptrdiff_t exist; + chFilter *filter; + const chFilterPlugin *plug; + parse_result result; + long status = 0; + + /* If no number is present, strtol() returns 0 and sets pnext=pname, + else pnext points to the first char after the number */ + pname++; + l = strtol(pname, &pnext, 0); + exist = pnext - pname; + if (exist) start = l; + pname = pnext; + if (*pname == ']' && exist) { + end = start; + goto insertplug; + } + if (*pname != ':') { + status = S_dbLib_fieldNotFound; + goto finish; + } + pname++; + l = strtol(pname, &pnext, 0); + exist = pnext - pname; + pname = pnext; + if (*pname == ']') { + if (exist) end = l; + goto insertplug; + } + if (exist) incr = l; + if (*pname != ':') { + status = S_dbLib_fieldNotFound; + goto finish; + } + pname++; + l = strtol(pname, &pnext, 0); + exist = pnext - pname; + if (exist) end = l; + pname = pnext; + if (*pname != ']') { + status = S_dbLib_fieldNotFound; + goto finish; + } + + insertplug: + pname++; + *ppnext = pname; + + plug = dbFindFilter("arr", 3); + if (!plug) { + status = S_dbLib_fieldNotFound; + goto finish; + } + + filter = freeListCalloc(chFilterFreeList); + if (!filter) { + status = S_db_noMemory; + goto finish; + } + filter->chan = chan; + filter->plug = plug; + filter->puser = NULL; + + TRY(filter->plug->fif->parse_start, (filter)); + TRY(filter->plug->fif->parse_start_map, (filter)); + if (start != 0) { + TRY(filter->plug->fif->parse_map_key, (filter, "s", 1)); + TRY(filter->plug->fif->parse_integer, (filter, start)); + } + if (incr != 1) { + TRY(filter->plug->fif->parse_map_key, (filter, "i", 1)); + TRY(filter->plug->fif->parse_integer, (filter, incr)); + } + if (end != -1) { + TRY(filter->plug->fif->parse_map_key, (filter, "e", 1)); + TRY(filter->plug->fif->parse_integer, (filter, end)); + } + TRY(filter->plug->fif->parse_end_map, (filter)); + TRY(filter->plug->fif->parse_end, (filter)); + + ellAdd(&chan->filters, &filter->list_node); + return 0; + + failure: + freeListFree(chFilterFreeList, filter); + status = S_dbLib_fieldNotFound; + + finish: + return status; +} + +/* Stolen from dbAccess.c: */ +static short mapDBFToDBR[DBF_NTYPES] = + { + /* DBF_STRING => */DBR_STRING, + /* DBF_CHAR => */DBR_CHAR, + /* DBF_UCHAR => */DBR_UCHAR, + /* DBF_SHORT => */DBR_SHORT, + /* DBF_USHORT => */DBR_USHORT, + /* DBF_LONG => */DBR_LONG, + /* DBF_ULONG => */DBR_ULONG, + /* DBF_INT64 => */DBR_INT64, + /* DBF_UINT64 => */DBR_UINT64, + /* DBF_FLOAT => */DBR_FLOAT, + /* DBF_DOUBLE => */DBR_DOUBLE, + /* DBF_ENUM, => */DBR_ENUM, + /* DBF_MENU, => */DBR_ENUM, + /* DBF_DEVICE => */DBR_ENUM, + /* DBF_INLINK => */DBR_STRING, + /* DBF_OUTLINK => */DBR_STRING, + /* DBF_FWDLINK => */DBR_STRING, + /* DBF_NOACCESS => */DBR_NOACCESS }; + +dbChannel * dbChannelCreate(const char *name) +{ + const char *pname = name; + DBENTRY dbEntry; + dbChannel *chan = NULL; + char *cname; + dbAddr *paddr; + dbFldDes *pflddes; + long status; + short dbfType; + + if (!name || !*name || !pdbbase) + return NULL; + + status = pvNameLookup(&dbEntry, &pname); + if (status) + goto finish; + + chan = freeListCalloc(dbChannelFreeList); + if (!chan) + goto finish; + cname = malloc(strlen(name) + 1); + if (!cname) + goto finish; + + strcpy(cname, name); + chan->name = cname; + ellInit(&chan->filters); + ellInit(&chan->pre_chain); + ellInit(&chan->post_chain); + + paddr = &chan->addr; + pflddes = dbEntry.pflddes; + dbfType = pflddes->field_type; + + paddr->precord = dbEntry.precnode->precord; + paddr->pfield = dbEntry.pfield; + paddr->pfldDes = pflddes; + paddr->no_elements = 1; + paddr->field_type = dbfType; + paddr->field_size = pflddes->size; + paddr->special = pflddes->special; + paddr->dbr_field_type = mapDBFToDBR[dbfType]; + + if (paddr->special == SPC_DBADDR) { + rset *prset = dbGetRset(paddr); + + /* Let record type modify paddr */ + if (prset && prset->cvt_dbaddr) { + status = prset->cvt_dbaddr(paddr); + if (status) + goto finish; + dbfType = paddr->field_type; + } + } + + /* Handle field modifiers */ + if (*pname) { + if (*pname == '$') { + /* Some field types can be accessed as char arrays */ + if (dbfType == DBF_STRING) { + paddr->no_elements = paddr->field_size; + paddr->field_type = DBF_CHAR; + paddr->field_size = 1; + paddr->dbr_field_type = DBR_CHAR; + } else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) { + /* Clients see a char array, but keep original dbfType */ + paddr->no_elements = PVLINK_STRINGSZ; + paddr->field_size = 1; + paddr->dbr_field_type = DBR_CHAR; + } else { + status = S_dbLib_fieldNotFound; + goto finish; + } + pname++; + } + + if (*pname == '[') { + status = parseArrayRange(chan, pname, &pname); + if (status) goto finish; + } + + /* JSON may follow */ + if (*pname == '{') { + status = chf_parse(chan, &pname); + if (status) goto finish; + } + + /* Make sure there's nothing else */ + if (*pname) { + status = S_dbLib_fieldNotFound; + goto finish; + } + } + +finish: + if (status && chan) { + dbChannelDelete(chan); + chan = NULL; + } + dbFinishEntry(&dbEntry); + return chan; +} + +db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn) { + chFilter *filter; + ELLNODE *node; + db_field_log *pLog = pLogIn; + + for (node = ellFirst(&chan->pre_chain); node && pLog; node = ellNext(node)) { + filter = CONTAINER(node, chFilter, pre_node); + pLog = filter->pre_func(filter->pre_arg, chan, pLog); + } + return pLog; +} + +db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn) { + chFilter *filter; + ELLNODE *node; + db_field_log *pLog = pLogIn; + + for (node = ellFirst(&chan->post_chain); node && pLog; node = ellNext(node)) { + filter = CONTAINER(node, chFilter, post_node); + pLog = filter->post_func(filter->post_arg, chan, pLog); + } + return pLog; +} + +long dbChannelOpen(dbChannel *chan) +{ + chFilter *filter; + chPostEventFunc *func; + void *arg; + long status; + ELLNODE *node; + db_field_log probe; + db_field_log p; + + for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { + filter = CONTAINER(node, chFilter, list_node); + /* Call channel_open */ + status = 0; + if (filter->plug->fif->channel_open) + status = filter->plug->fif->channel_open(filter); + if (status) return status; + } + + /* Set up type probe */ + probe.type = dbfl_type_val; + probe.ctx = dbfl_context_read; + probe.field_type = dbChannelExportType(chan); + probe.no_elements = dbChannelElements(chan); + probe.field_size = dbChannelFieldSize(chan); + p = probe; + + /* + * Build up the pre- and post-event-queue filter chains + * Separate loops because the probe must reach the filters in the right order. + */ + for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { + filter = CONTAINER(node, chFilter, list_node); + func = NULL; + arg = NULL; + if (filter->plug->fif->channel_register_pre) { + filter->plug->fif->channel_register_pre(filter, &func, &arg, &p); + if (func) { + ellAdd(&chan->pre_chain, &filter->pre_node); + filter->pre_func = func; + filter->pre_arg = arg; + probe = p; + } + } + } + for (node = ellFirst(&chan->filters); node; node = ellNext(node)) { + filter = CONTAINER(node, chFilter, list_node); + func = NULL; + arg = NULL; + if (filter->plug->fif->channel_register_post) { + filter->plug->fif->channel_register_post(filter, &func, &arg, &p); + if (func) { + ellAdd(&chan->post_chain, &filter->post_node); + filter->post_func = func; + filter->post_arg = arg; + probe = p; + } + } + } + + /* Save probe results */ + chan->final_no_elements = probe.no_elements; + chan->final_field_size = probe.field_size; + chan->final_type = probe.field_type; + + return 0; +} + +/* Only use dbChannelGet() if the record is already locked. */ +long dbChannelGet(dbChannel *chan, short type, void *pbuffer, + long *options, long *nRequest, void *pfl) +{ + return dbGet(&chan->addr, type, pbuffer, options, nRequest, pfl); +} + +long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer, + long *options, long *nRequest, void *pfl) +{ + dbCommon *precord = chan->addr.precord; + long status = 0; + + dbScanLock(precord); + status = dbChannelGet(chan, dbrType, pbuffer, options, nRequest, pfl); + dbScanUnlock(precord); + return status; +} + +/* Only use dbChannelPut() if the record is already locked. + * This routine doesn't work on link fields, ignores DISP, and + * doesn't trigger record processing on PROC or pp(TRUE). + */ +long dbChannelPut(dbChannel *chan, short type, const void *pbuffer, + long nRequest) +{ + return dbPut(&chan->addr, type, pbuffer, nRequest); +} + +long dbChannelPutField(dbChannel *chan, short type, const void *pbuffer, + long nRequest) +{ + return dbPutField(&chan->addr, type, pbuffer, nRequest); +} + +void dbChannelShow(dbChannel *chan, int level, const unsigned short indent) +{ + long elems = chan->addr.no_elements; + long felems = chan->final_no_elements; + int count = ellCount(&chan->filters); + int pre = ellCount(&chan->pre_chain); + int post = ellCount(&chan->post_chain); + + printf("%*sChannel: '%s'\n", indent, "", chan->name); + if (level > 0) { + printf("%*sfield_type=%s (%d bytes), dbr_type=%s, %ld element%s", + indent + 4, "", + dbGetFieldTypeString(chan->addr.field_type), + chan->addr.field_size, + dbGetFieldTypeString(chan->addr.dbr_field_type), + elems, elems == 1 ? "" : "s"); + if (count) + printf("\n%*s%d filter%s (%d pre eventq, %d post eventq)\n", + indent + 4, "", count, count == 1 ? "" : "s", pre, post); + else + printf(", no filters\n"); + if (level > 1) + dbChannelFilterShow(chan, level - 2, indent + 8); + if (count) { + printf("%*sfinal field_type=%s (%dB), %ld element%s\n", indent + 4, "", + dbGetFieldTypeString(chan->final_type), + chan->final_field_size, + felems, felems == 1 ? "" : "s"); + } + } +} + +void dbChannelFilterShow(dbChannel *chan, int level, const unsigned short indent) +{ + chFilter *filter = (chFilter *) ellFirst(&chan->filters); + while (filter) { + filter->plug->fif->channel_report(filter, level, indent); + filter = (chFilter *) ellNext(&filter->list_node); + } +} + +void dbChannelDelete(dbChannel *chan) +{ + chFilter *filter; + + /* Close filters in reverse order */ + while ((filter = (chFilter *) ellPop(&chan->filters))) { + filter->plug->fif->channel_close(filter); + freeListFree(chFilterFreeList, filter); + } + free((char *) chan->name); + freeListFree(dbChannelFreeList, chan); +} + +static void freeArray(db_field_log *pfl) { + if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { + freeListFree(dbchStringFreeList, pfl->u.r.field); + } else { + free(pfl->u.r.field); + } +} + +void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan) +{ + void *p; + struct dbCommon *prec = dbChannelRecord(chan); + + if (pfl->type != dbfl_type_rec) return; + + pfl->type = dbfl_type_ref; + pfl->stat = prec->stat; + pfl->sevr = prec->sevr; + pfl->time = prec->time; + pfl->field_type = chan->addr.field_type; + pfl->no_elements = chan->addr.no_elements; + pfl->field_size = chan->addr.field_size; + pfl->u.r.dtor = freeArray; + pfl->u.r.pvt = pvt; + if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) { + p = freeListCalloc(dbchStringFreeList); + } else { + p = calloc(pfl->no_elements, pfl->field_size); + } + if (p) dbGet(&chan->addr, mapDBFToDBR[pfl->field_type], p, NULL, &pfl->no_elements, NULL); + pfl->u.r.field = p; +} + +/* FIXME: Do these belong in a different file? */ + +void dbRegisterFilter(const char *name, const chFilterIf *fif, void *puser) +{ + GPHENTRY *pgph; + chFilterPlugin *pfilt; + + if (!pdbbase) { + printf("dbRegisterFilter: pdbbase not set!\n"); + return; + } + + pgph = gphFind(pdbbase->pgpHash, name, &pdbbase->filterList); + if (pgph) + return; + + pfilt = dbCalloc(1, sizeof(chFilterPlugin)); + pfilt->name = epicsStrDup(name); + pfilt->fif = fif; + pfilt->puser = puser; + + ellAdd(&pdbbase->filterList, &pfilt->node); + pgph = gphAdd(pdbbase->pgpHash, pfilt->name, &pdbbase->filterList); + if (!pgph) { + free((void *) pfilt->name); + free(pfilt); + printf("dbRegisterFilter: gphAdd failed\n"); + return; + } + pgph->userPvt = pfilt; +} + +const chFilterPlugin * dbFindFilter(const char *name, size_t len) +{ + GPHENTRY *pgph = gphFindParse(pdbbase->pgpHash, name, len, + &pdbbase->filterList); + + if (!pgph) + return NULL; + return (chFilterPlugin *) pgph->userPvt; +} diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h new file mode 100644 index 000000000..fab9c6627 --- /dev/null +++ b/modules/database/src/ioc/db/dbChannel.h @@ -0,0 +1,233 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Andrew Johnson + * Ralph Lange + */ + +#ifndef INC_dbChannel_H +#define INC_dbChannel_H + +#include "dbDefs.h" +#include "dbAddr.h" +#include "ellLib.h" +#include "epicsTypes.h" +#include "errMdef.h" +#include "shareLib.h" +#include "db_field_log.h" +#include "dbEvent.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * event subscription + */ +typedef struct evSubscrip { + ELLNODE node; + struct dbChannel *chan; + EVENTFUNC *user_sub; + void *user_arg; + struct event_que *ev_que; + db_field_log **pLastLog; + unsigned long npend; /* n times this event is on the queue */ + unsigned long nreplace; /* n times replacing event on the queue */ + unsigned char select; + char useValque; + char callBackInProgress; + char enabled; +} evSubscrip; + +typedef struct chFilter chFilter; + +/* A dbChannel points to a record field, and can have multiple filters */ +typedef struct dbChannel { + const char *name; + dbAddr addr; /* address structure for record/field */ + long final_no_elements; /* final number of elements (arrays) */ + short final_field_size; /* final size of element */ + short final_type; /* final type of database field */ + ELLLIST filters; /* list of filters as created from JSON */ + ELLLIST pre_chain; /* list of filters to be called pre-event-queue */ + ELLLIST post_chain; /* list of filters to be called post-event-queue */ +} dbChannel; + +/* Prototype for the channel event function that is called in filter stacks + * + * When invoked the scan lock for the record associated with 'chan' _may_ be locked. + * If pLog->type==dbfl_type_rec then dbScanLock() must be called before copying + * data out of the associated record. + * + * This function has ownership of the field log pLog, if it wishes to discard + * this update it should free the field log with db_delete_field_log() and + * then return NULL. + */ +typedef db_field_log* (chPostEventFunc)(void *pvt, dbChannel *chan, db_field_log *pLog); + +/* Return values from chFilterIf->parse_* routines: */ +typedef enum { + parse_stop, parse_continue +} parse_result; + +/* These routines must be implemented by each filter plug-in */ +typedef struct chFilterIf { + /* cleanup pointer passed to dbRegisterFilter(). + * Called during DB shutdown + */ + void (* priv_free)(void *puser); + /* Parsing event handlers: */ + parse_result (* parse_start)(chFilter *filter); + /* If parse_start() returns parse_continue for a filter, one of + * parse_abort() or parse_end() will later be called for that same + * filter. + */ + void (* parse_abort)(chFilter *filter); + /* If parse_abort() is called it should release any memory allocated + * for this filter; no further parse_...() calls will be made; + */ + parse_result (* parse_end)(chFilter *filter); + /* If parse_end() returns parse_stop it should have released any + * memory allocated for this filter; no further parse_...() calls will + * be made in this case. + */ + + parse_result (* parse_null)(chFilter *filter); + parse_result (* parse_boolean)(chFilter *filter, int boolVal); + parse_result (* parse_integer)(chFilter *filter, long integerVal); + parse_result (* parse_double)(chFilter *filter, double doubleVal); + parse_result (* parse_string)(chFilter *filter, const char *stringVal, + size_t stringLen); /* NB: stringVal is not zero-terminated: */ + + parse_result (* parse_start_map)(chFilter *filter); + parse_result (* parse_map_key)(chFilter *filter, const char *key, + size_t stringLen); /* NB: key is not zero-terminated: */ + parse_result (* parse_end_map)(chFilter *filter); + + parse_result (* parse_start_array)(chFilter *filter); + parse_result (* parse_end_array)(chFilter *filter); + + /* Channel operations: */ + long (* channel_open)(chFilter *filter); + void (* channel_register_pre) (chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe); + void (* channel_register_post)(chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe); + void (* channel_report)(chFilter *filter, int level, const unsigned short indent); + void (* channel_close)(chFilter *filter); +} chFilterIf; + +/* A chFilterPlugin holds data for a filter plugin */ +typedef struct chFilterPlugin { + ELLNODE node; + const char *name; + const chFilterIf *fif; + void *puser; +} chFilterPlugin; + +/* A chFilter holds data for a single filter instance */ +struct chFilter { + ELLNODE list_node; + ELLNODE pre_node; + ELLNODE post_node; + dbChannel *chan; + const chFilterPlugin *plug; + chPostEventFunc *pre_func; + void *pre_arg; + chPostEventFunc *post_func; + void *post_arg; + void *puser; +}; + +struct dbCommon; +struct dbFldDes; + +epicsShareFunc void dbChannelInit (void); +epicsShareFunc void dbChannelExit(void); +epicsShareFunc long dbChannelTest(const char *name); +epicsShareFunc dbChannel * dbChannelCreate(const char *name); +epicsShareFunc long dbChannelOpen(dbChannel *chan); + +/*Following is also defined in db_convert.h*/ +epicsShareExtern unsigned short dbDBRnewToDBRold[]; + +/* In the following macros pChan is dbChannel* */ + +/* evaluates to const char* */ +#define dbChannelName(pChan) ((pChan)->name) + +/* evaluates to struct dbCommon* */ +#define dbChannelRecord(pChan) ((pChan)->addr.precord) + +/* evaluates to struct dbFldDes* */ +#define dbChannelFldDes(pChan) ((pChan)->addr.pfldDes) + +/* evaluates to long */ +#define dbChannelElements(pChan) ((pChan)->addr.no_elements) + +/* evaluates to short */ +#define dbChannelFieldType(pChan) ((pChan)->addr.field_type) + +/* evaluates to short */ +#define dbChannelExportType(pChan) ((pChan)->addr.dbr_field_type) + +/* evaluates to short */ +#define dbChannelExportCAType(pChan) (dbDBRnewToDBRold[dbChannelExportType(pChan)]) + +/* evaluates to short */ +#define dbChannelFieldSize(pChan) ((pChan)->addr.field_size) + +/* evaluates to long */ +#define dbChannelFinalElements(pChan) ((pChan)->final_no_elements) + +/* evaluates to short */ +#define dbChannelFinalFieldType(pChan) ((pChan)->final_type) + +/* evaluates to short */ +#define dbChannelFinalCAType(pChan) (dbDBRnewToDBRold[(pChan)->final_type]) + +/* evaluates to short */ +#define dbChannelFinalFieldSize(pChan) ((pChan)->final_field_size) + +/* evaluates to short */ +#define dbChannelSpecial(pChan) ((pChan)->addr.special) + +/* Channel filters do not get to interpose here since there are many + * places where the field pointer is compared with the address of a + * specific record field, so they can't modify the pointer value. + */ +/* evaluates to void* */ +#define dbChannelField(pChan) ((pChan)->addr.pfield) + + +epicsShareFunc long dbChannelGet(dbChannel *chan, short type, + void *pbuffer, long *options, long *nRequest, void *pfl); +epicsShareFunc long dbChannelGetField(dbChannel *chan, short type, + void *pbuffer, long *options, long *nRequest, void *pfl); +epicsShareFunc long dbChannelPut(dbChannel *chan, short type, + const void *pbuffer, long nRequest); +epicsShareFunc long dbChannelPutField(dbChannel *chan, short type, + const void *pbuffer, long nRequest); +epicsShareFunc void dbChannelShow(dbChannel *chan, int level, + const unsigned short indent); +epicsShareFunc void dbChannelFilterShow(dbChannel *chan, int level, + const unsigned short indent); +epicsShareFunc void dbChannelDelete(dbChannel *chan); + +epicsShareFunc void dbRegisterFilter(const char *key, const chFilterIf *fif, void *puser); +epicsShareFunc db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn); +epicsShareFunc db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn); +epicsShareFunc const chFilterPlugin * dbFindFilter(const char *key, size_t len); +epicsShareFunc void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbChannel_H */ diff --git a/modules/database/src/ioc/db/dbChannelIO.cpp b/modules/database/src/ioc/db/dbChannelIO.cpp new file mode 100644 index 000000000..a086d3e56 --- /dev/null +++ b/modules/database/src/ioc/db/dbChannelIO.cpp @@ -0,0 +1,237 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include + +#include "tsFreeList.h" +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "db_access.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "db_access_routines.h" +#include "dbCAC.h" +#include "dbChannelIO.h" +#include "dbPutNotifyBlocker.h" + +dbChannelIO::dbChannelIO ( + epicsMutex & mutexIn, cacChannelNotify & notify, + dbChannel * dbchIn, dbContext & serviceIO ) : + cacChannel ( notify ), mutex ( mutexIn ), serviceIO ( serviceIO ), + dbch ( dbchIn ) +{ +} + +void dbChannelIO::initiateConnect ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->notify().connectNotify ( guard ); +} + +dbChannelIO::~dbChannelIO () +{ +} + +void dbChannelIO::destructor ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.destroyAllIO ( cbGuard, guard, *this ); + dbChannelDelete ( this->dbch ); + this->~dbChannelIO (); +} + +void dbChannelIO::destroy ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.destroyChannel ( cbGuard, guard, *this ); + // don't access this pointer after above call because + // object no longer exists +} + +cacChannel::ioStatus dbChannelIO::read ( + epicsGuard < epicsMutex > & guard, unsigned type, + unsigned long count, cacReadNotify & notify, ioid * ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.callReadNotify ( guard, this->dbch, + type, count, notify ); + return iosSynch; +} + +void dbChannelIO::write ( + epicsGuard < epicsMutex > & guard, unsigned type, + unsigned long count, const void *pValue ) +{ + epicsGuardRelease < epicsMutex > unguard ( guard ); + if ( count > LONG_MAX ) { + throw outOfBounds(); + } + int status = dbChannel_put ( this->dbch, type, pValue, + static_cast (count) ); + if ( status ) { + throw std::logic_error ( + "db_put_field() completed unsuccessfully" ); + } +} + +cacChannel::ioStatus dbChannelIO::write ( + epicsGuard < epicsMutex > & guard, unsigned type, + unsigned long count, const void * pValue, + cacWriteNotify & notify, ioid * pId ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( count > LONG_MAX ) { + throw outOfBounds(); + } + + this->serviceIO.initiatePutNotify ( + guard, *this, this->dbch, + type, count, pValue, notify, pId ); + + return iosAsynch; +} + +void dbChannelIO::subscribe ( + epicsGuard < epicsMutex > & guard, unsigned type, unsigned long count, + unsigned mask, cacStateNotify & notify, ioid * pId ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.subscribe ( + guard, this->dbch, *this, + type, count, mask, notify, pId ); +} + +void dbChannelIO::ioCancel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & id ) +{ + mutualExclusionGuard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.ioCancel ( cbGuard, mutualExclusionGuard, *this, id ); +} + +void dbChannelIO::ioShow ( + epicsGuard < epicsMutex > & guard, + const ioid & id, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.ioShow ( guard, id, level ); +} + +void dbChannelIO::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + + printf ("channel at %p attached to local database record %s\n", + static_cast ( this ), + dbChannelRecord ( this->dbch ) -> name ); + + if ( level > 0u ) { + printf ( " type %s, element count %li, field at %p\n", + dbf_type_to_text ( dbChannelExportCAType ( this->dbch ) ), + dbChannelElements ( this->dbch ), + dbChannelField ( this->dbch ) ); + if ( level > 1u ) { + dbChannelFilterShow ( this->dbch, level - 2u, 8 ); + this->serviceIO.show ( level - 2u ); + this->serviceIO.showAllIO ( *this, level - 2u ); + } + } +} + +unsigned long dbChannelIO::nativeElementCount ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + long elements = dbChannelElements ( this->dbch ); + if ( elements >= 0u ) { + return static_cast < unsigned long > ( elements ); + } + return 0u; +} + +// hopefully to be eventually phased out +const char * dbChannelIO::pName ( + epicsGuard < epicsMutex > & guard ) const throw () +{ + guard.assertIdenticalMutex ( this->mutex ); + return dbChannelName ( this->dbch ); +} + +unsigned dbChannelIO::getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () +{ + const char *name = dbChannelName ( this->dbch ); + size_t len = strlen ( name ); + strncpy ( pBuf, name, bufLen ); + if (len < bufLen) + return (unsigned) len; + pBuf[--bufLen] = '\0'; + return bufLen; +} + +short dbChannelIO::nativeType ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return dbChannelExportCAType( this->dbch ); +} + +void * dbChannelIO::operator new ( size_t size, + tsFreeList < dbChannelIO, 256, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +void dbChannelIO::operator delete ( void *pCadaver, + tsFreeList < dbChannelIO, 256, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +void dbChannelIO::operator delete ( void * ) +{ + // Visual C++ .net appears to require operator delete if + // placement operator delete is defined? I smell a ms rat + // because if I declare placement new and delete, but + // comment out the placement delete definition there are + // no undefined symbols. + errlogPrintf ( "%s:%d this compiler is confused about placement delete - memory was probably leaked", + __FILE__, __LINE__ ); +} + +void dbChannelIO::flush ( + epicsGuard < epicsMutex > & ) +{ +} + +unsigned dbChannelIO::requestMessageBytesPending ( + epicsGuard < epicsMutex > & ) +{ + return 0u; +} + diff --git a/modules/database/src/ioc/db/dbChannelIO.h b/modules/database/src/ioc/db/dbChannelIO.h new file mode 100644 index 000000000..5eb7c4eb2 --- /dev/null +++ b/modules/database/src/ioc/db/dbChannelIO.h @@ -0,0 +1,124 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + * + * NOTES: + * 1) This interface is preliminary and will change in the future + */ + +#ifndef dbChannelIOh +#define dbChannelIOh + +#ifdef epicsExportSharedSymbols +# define dbChannelIOh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "compilerDependencies.h" + +#ifdef dbChannelIOh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +#endif + +class dbChannelIO : public cacChannel, public dbContextPrivateListOfIO { +public: + dbChannelIO ( + epicsMutex &, cacChannelNotify &, + dbChannel *, dbContext & ); + void destructor ( + CallbackGuard &, + epicsGuard < epicsMutex > & ); + void destroy ( + CallbackGuard &, + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void callReadNotify ( + epicsGuard < epicsMutex > &, + unsigned type, unsigned long count, + cacReadNotify & notify ); + void callStateNotify ( + unsigned type, unsigned long count, + const struct db_field_log * pfl, cacStateNotify & notify ); + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; + unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw (); + const char * pName ( + epicsGuard < epicsMutex > & ) const throw (); + void * operator new ( size_t size, + tsFreeList < dbChannelIO, 256, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < dbChannelIO, 256, epicsMutexNOOP > & )) +protected: + ~dbChannelIO (); +private: + epicsMutex & mutex; + dbContext & serviceIO; + dbChannel * dbch; + + void initiateConnect ( + epicsGuard < epicsMutex > & ); + unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & ); + void flush ( + epicsGuard < epicsMutex > & ); + ioStatus read ( + epicsGuard < epicsMutex > &, + unsigned type, unsigned long count, + cacReadNotify &, ioid * ); + void write ( + epicsGuard < epicsMutex > &, + unsigned type, unsigned long count, + const void * pvalue ); + ioStatus write ( + epicsGuard < epicsMutex > &, + unsigned type, unsigned long count, + const void * pvalue, cacWriteNotify &, ioid * ); + void subscribe ( + epicsGuard < epicsMutex > &, + unsigned type, unsigned long count, + unsigned mask, cacStateNotify ¬ify, ioid * ); + void ioCancel ( + CallbackGuard &, + epicsGuard < epicsMutex > &, + const ioid & ); + void ioShow ( + epicsGuard < epicsMutex > &, + const ioid &, unsigned level ) const; + short nativeType ( + epicsGuard < epicsMutex > & ) const; + unsigned long nativeElementCount ( + epicsGuard < epicsMutex > & ) const; + dbChannelIO ( const dbChannelIO & ); + dbChannelIO & operator = ( const dbChannelIO & ); + void operator delete ( void * ); +}; + +inline void dbChannelIO::callReadNotify ( + epicsGuard < epicsMutex > & guard, unsigned type, unsigned long count, + cacReadNotify & notify ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->serviceIO.callReadNotify ( guard, this->dbch, type, count, notify ); +} + +inline void dbChannelIO::callStateNotify ( unsigned type, unsigned long count, + const struct db_field_log *pfl, cacStateNotify ¬ify ) +{ + this->serviceIO.callStateNotify ( this->dbch, type, count, pfl, notify ); +} + + +#endif // dbChannelIOh + diff --git a/modules/database/src/ioc/db/dbChannelNOOP.h b/modules/database/src/ioc/db/dbChannelNOOP.h new file mode 100644 index 000000000..d48540d88 --- /dev/null +++ b/modules/database/src/ioc/db/dbChannelNOOP.h @@ -0,0 +1,118 @@ +#ifndef DBCHANNELNOOP_H +#define DBCHANNELNOOP_H + +#include +#include + +#include "cacIO.h" +#include "caerr.h" + +/** @brief A channel which never connects + * + * Used when dbCa is placed in isolated mode for unittests + */ +class dbChannelNOOP : public cacChannel +{ + std::string myname; +public: + dbChannelNOOP(const char *name, cacChannelNotify ¬ify) + :cacChannel(notify) + ,myname(name) + {} + + virtual void destroy ( + CallbackGuard & /*callbackGuard*/, + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + { + delete this; // goodbye cruel world + } + + virtual unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () + { + const char* name = myname.c_str(); + if(bufLen>myname.size()+1) { + bufLen=myname.size()+1; + } + memcpy(pBuf, name, bufLen); + pBuf[--bufLen] = '\0'; + return bufLen; + } + + // !! deprecated, avoid use !! + virtual const char * pName ( + epicsGuard < epicsMutex > & guard ) const throw () + {return myname.c_str();} + + virtual void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const + {} + + virtual void initiateConnect ( + epicsGuard < epicsMutex > & ) + {} + + virtual unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + {return 0;} + + virtual void flush ( + epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ ) + {} + + virtual ioStatus read ( + epicsGuard < epicsMutex > &mut, + unsigned type, arrayElementCount count, + cacReadNotify ¬ify, ioid * = 0 ) + { + notify.exception(mut, ECA_NORDACCESS, "dbChannelNOOP", type, count); + return iosSynch; + } + + virtual void write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue ) + {} + + virtual ioStatus write ( + epicsGuard < epicsMutex > &mut, + unsigned type, arrayElementCount count, + const void */*pValue*/, cacWriteNotify & notify, ioid * = 0 ) + { + notify.exception(mut, ECA_NOWTACCESS, "dbChannelNOOP", type, count); + return iosSynch; + } + + virtual void subscribe ( + epicsGuard < epicsMutex > &mut, unsigned type, + arrayElementCount count, unsigned /*mask*/, cacStateNotify & notify, + ioid * = 0 ) + { + // should never subscribe + notify.exception(mut, ECA_BADMASK, "dbChannelNOOP", type, count); + } + + virtual void ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & ) + {} + + virtual void ioShow ( + epicsGuard < epicsMutex > &, + const ioid &, unsigned level ) const + {} + + virtual short nativeType ( + epicsGuard < epicsMutex > & ) const + {return 0;} // DBR_STRING + + virtual arrayElementCount nativeElementCount ( + epicsGuard < epicsMutex > & ) const + {return 1;} +}; + +#endif // DBCHANNELNOOP_H diff --git a/modules/database/src/ioc/db/dbCommon.dbd b/modules/database/src/ioc/db/dbCommon.dbd new file mode 100644 index 000000000..1b093e6a8 --- /dev/null +++ b/modules/database/src/ioc/db/dbCommon.dbd @@ -0,0 +1,267 @@ +#************************************************************************* +# Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + %#include "epicsTypes.h" + %#include "link.h" + field(NAME,DBF_STRING) { + prompt("Record Name") + special(SPC_NOMOD) + size(61) + } + field(DESC,DBF_STRING) { + prompt("Descriptor") + promptgroup("10 - Common") + size(41) + } + field(ASG,DBF_STRING) { + prompt("Access Security Group") + promptgroup("10 - Common") + special(SPC_AS) + size(29) + } + field(SCAN,DBF_MENU) { + prompt("Scan Mechanism") + promptgroup("20 - Scan") + special(SPC_SCAN) + interest(1) + menu(menuScan) + } + field(PINI,DBF_MENU) { + prompt("Process at iocInit") + promptgroup("20 - Scan") + interest(1) + menu(menuPini) + } + field(PHAS,DBF_SHORT) { + prompt("Scan Phase") + promptgroup("20 - Scan") + special(SPC_SCAN) + interest(1) + } + field(EVNT,DBF_STRING) { + prompt("Event Name") + promptgroup("20 - Scan") + special(SPC_SCAN) + size(40) + interest(1) + } + field(TSE,DBF_SHORT) { + prompt("Time Stamp Event") + promptgroup("20 - Scan") + interest(1) + } + field(TSEL,DBF_INLINK) { + prompt("Time Stamp Link") + promptgroup("20 - Scan") + interest(1) + } + field(DTYP,DBF_DEVICE) { + prompt("Device Type") + promptgroup("10 - Common") + interest(1) + } + field(DISV,DBF_SHORT) { + prompt("Disable Value") + promptgroup("20 - Scan") + initial("1") + } + field(DISA,DBF_SHORT) { + prompt("Disable") + } + field(SDIS,DBF_INLINK) { + prompt("Scanning Disable") + promptgroup("20 - Scan") + interest(1) + } + %#include "epicsMutex.h" + field(MLOK,DBF_NOACCESS) { + prompt("Monitor lock") + special(SPC_NOMOD) + interest(4) + extra("epicsMutexId mlok") + } + %#include "ellLib.h" + field(MLIS,DBF_NOACCESS) { + prompt("Monitor List") + special(SPC_NOMOD) + interest(4) + extra("ELLLIST mlis") + } + field(BKLNK,DBF_NOACCESS) { + prompt("Backwards link tracking") + special(SPC_NOMOD) + interest(4) + extra("ELLLIST bklnk") + } + field(DISP,DBF_UCHAR) { + prompt("Disable putField") + } + field(PROC,DBF_UCHAR) { + prompt("Force Processing") + pp(TRUE) + interest(3) + } + field(STAT,DBF_MENU) { + prompt("Alarm Status") + special(SPC_NOMOD) + menu(menuAlarmStat) + initial("UDF") + } + field(SEVR,DBF_MENU) { + prompt("Alarm Severity") + special(SPC_NOMOD) + menu(menuAlarmSevr) + } + field(NSTA,DBF_MENU) { + prompt("New Alarm Status") + special(SPC_NOMOD) + interest(2) + menu(menuAlarmStat) + } + field(NSEV,DBF_MENU) { + prompt("New Alarm Severity") + special(SPC_NOMOD) + interest(2) + menu(menuAlarmSevr) + } + field(ACKS,DBF_MENU) { + prompt("Alarm Ack Severity") + special(SPC_NOMOD) + interest(2) + menu(menuAlarmSevr) + } + field(ACKT,DBF_MENU) { + prompt("Alarm Ack Transient") + promptgroup("70 - Alarm") + special(SPC_NOMOD) + interest(2) + menu(menuYesNo) + initial("YES") + } + field(DISS,DBF_MENU) { + prompt("Disable Alarm Sevrty") + promptgroup("70 - Alarm") + interest(1) + menu(menuAlarmSevr) + } + field(LCNT,DBF_UCHAR) { + prompt("Lock Count") + special(SPC_NOMOD) + interest(2) + } + field(PACT,DBF_UCHAR) { + prompt("Record active") + special(SPC_NOMOD) + interest(1) + } + field(PUTF,DBF_UCHAR) { + prompt("dbPutField process") + special(SPC_NOMOD) + interest(1) + } + field(RPRO,DBF_UCHAR) { + prompt("Reprocess ") + special(SPC_NOMOD) + interest(1) + } + field(ASP,DBF_NOACCESS) { + prompt("Access Security Pvt") + special(SPC_NOMOD) + interest(4) + extra("struct asgMember *asp") + } + field(PPN,DBF_NOACCESS) { + prompt("pprocessNotify") + special(SPC_NOMOD) + interest(4) + extra("struct processNotify *ppn") + } + field(PPNR,DBF_NOACCESS) { + prompt("pprocessNotifyRecord") + special(SPC_NOMOD) + interest(4) + extra("struct processNotifyRecord *ppnr") + } + field(SPVT,DBF_NOACCESS) { + prompt("Scan Private") + special(SPC_NOMOD) + interest(4) + extra("struct scan_element *spvt") + } + field(RSET,DBF_NOACCESS) { + prompt("Address of RSET") + special(SPC_NOMOD) + interest(4) + extra("struct typed_rset *rset") + } + field(DSET,DBF_NOACCESS) { + prompt("DSET address") + special(SPC_NOMOD) + interest(4) + extra("struct dset *dset") + } + field(DPVT,DBF_NOACCESS) { + prompt("Device Private") + special(SPC_NOMOD) + interest(4) + extra("void *dpvt") + } + field(RDES,DBF_NOACCESS) { + prompt("Address of dbRecordType") + special(SPC_NOMOD) + interest(4) + extra("struct dbRecordType *rdes") + } + field(LSET,DBF_NOACCESS) { + prompt("Lock Set") + special(SPC_NOMOD) + interest(4) + extra("struct lockRecord *lset") + } + field(PRIO,DBF_MENU) { + prompt("Scheduling Priority") + promptgroup("20 - Scan") + special(SPC_SCAN) + interest(1) + menu(menuPriority) + } + field(TPRO,DBF_UCHAR) { + prompt("Trace Processing") + } + field(BKPT,DBF_NOACCESS) { + prompt("Break Point") + special(SPC_NOMOD) + interest(1) + extra("char bkpt") + } + field(UDF,DBF_UCHAR) { + prompt("Undefined") + promptgroup("10 - Common") + pp(TRUE) + interest(1) + initial("1") + } + field(UDFS,DBF_MENU) { + prompt("Undefined Alarm Sevrty") + promptgroup("70 - Alarm") + interest(1) + menu(menuAlarmSevr) + initial("INVALID") + } + %#include "epicsTime.h" + field(TIME,DBF_NOACCESS) { + prompt("Time") + special(SPC_NOMOD) + interest(2) + extra("epicsTimeStamp time") + } + field(FLNK,DBF_FWDLINK) { + prompt("Forward Process Link") + promptgroup("20 - Scan") + interest(1) + } diff --git a/modules/database/src/ioc/db/dbCommonPvt.h b/modules/database/src/ioc/db/dbCommonPvt.h new file mode 100644 index 000000000..3dfce8b27 --- /dev/null +++ b/modules/database/src/ioc/db/dbCommonPvt.h @@ -0,0 +1,14 @@ +#ifndef DBCOMMONPVT_H +#define DBCOMMONPVT_H + +#include "dbCommon.h" + +/** Base internal additional information for every record + */ +typedef struct dbCommonPvt { + struct dbRecordNode *recnode; + + struct dbCommon common; +} dbCommonPvt; + +#endif // DBCOMMONPVT_H diff --git a/modules/database/src/ioc/db/dbCommonRecord.dbd b/modules/database/src/ioc/db/dbCommonRecord.dbd new file mode 100644 index 000000000..a988619ae --- /dev/null +++ b/modules/database/src/ioc/db/dbCommonRecord.dbd @@ -0,0 +1,12 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(dbCommon) { + include "dbCommon.dbd" +} diff --git a/modules/database/src/ioc/db/dbConstLink.c b/modules/database/src/ioc/db/dbConstLink.c new file mode 100644 index 000000000..928dae983 --- /dev/null +++ b/modules/database/src/ioc/db/dbConstLink.c @@ -0,0 +1,234 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConstLink.c + * + * Original Authors: Bob Dalesio, Marty Kraimer + * Current Author: Andrew Johnson + */ + +#include +#include + +#include "dbDefs.h" +#include "epicsStdlib.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbCommon.h" +#include "dbConstLink.h" +#include "dbConvertJSON.h" +#include "dbFldTypes.h" +#include "dbLink.h" +#include "link.h" + +/**************************** Convert functions ****************************/ + +/* The difference between these and dbFastConvert is that constants + * may contain hex numbers, whereas database conversions can't. + */ + +/* Copy to STRING */ +static long cvt_st_st(const char *from, void *pfield, const dbAddr *paddr) +{ + char *to = pfield; + size_t size; + + if (paddr && paddr->field_size < MAX_STRING_SIZE) { + size = paddr->field_size - 1; + } else { + size = MAX_STRING_SIZE - 1; + } + strncpy(to, from, size); + to[size] = 0; + return 0; +} + +/* Most integer conversions are identical */ +#define cvt_st_int(TYPE) static long \ +cvt_st_ ## TYPE(const char *from, void *pfield, const dbAddr *paddr) { \ + epics##TYPE *to = pfield; \ + char *end; \ +\ + if (*from == 0) { \ + *to = 0; \ + return 0; \ + } \ + return epicsParse##TYPE(from, to, 0, &end); \ +} + +/* Instanciate for CHAR, UCHAR, SHORT, USHORT and LONG */ +cvt_st_int(Int8) +cvt_st_int(UInt8) +cvt_st_int(Int16) +cvt_st_int(UInt16) +cvt_st_int(Int32) + +/* Conversion for ULONG is different... */ +static long cvt_st_UInt32(const char *from, void *pfield, const dbAddr *paddr) +{ + epicsUInt32 *to = pfield; + char *end; + long status; + + if (*from == 0) { + *to = 0; + return 0; + } + status = epicsParseUInt32(from, to, 0, &end); + if (status == S_stdlib_noConversion || + (!status && (*end == '.' || *end == 'e' || *end == 'E'))) { + /* + * Convert via double so numbers like 1.0e3 convert properly. + * db_access pretends ULONG fields are DOUBLE. + */ + double dval; + + status = epicsParseFloat64(from, &dval, &end); + if (!status && + dval >=0 && + dval <= ULONG_MAX) + *to = dval; + } + return status; +} + +/* Instanciate for INT64 and UINT64 */ +cvt_st_int(Int64) +cvt_st_int(UInt64) + + +/* Float conversions are identical */ +#define cvt_st_float(TYPE) static long \ +cvt_st_ ## TYPE(const char *from, void *pfield, const dbAddr *paddr) { \ + epics##TYPE *to = pfield; \ + char *end; \ +\ + if (*from == 0) { \ + *to = 0; \ + return 0; \ + } \ + return epicsParse##TYPE(from, to, &end); \ +} + +/* Instanciate for FLOAT32 and FLOAT64 */ +cvt_st_float(Float32) +cvt_st_float(Float64) + + +static long (*convert[DBF_DOUBLE+1])(const char *, void *, const dbAddr *) = { + cvt_st_st, + cvt_st_Int8, cvt_st_UInt8, + cvt_st_Int16, cvt_st_UInt16, + cvt_st_Int32, cvt_st_UInt32, + cvt_st_Int64, cvt_st_UInt64, + cvt_st_Float32, cvt_st_Float64 +}; + +/***************************** Constant Links *****************************/ + +/* Forward definition */ +static lset dbConst_lset; + +void dbConstInitLink(struct link *plink) +{ + plink->lset = &dbConst_lset; +} + +void dbConstAddLink(struct link *plink) +{ + plink->lset = &dbConst_lset; +} + +/**************************** Member functions ****************************/ + +static long dbConstLoadScalar(struct link *plink, short dbrType, void *pbuffer) +{ + const char *pstr = plink->value.constantStr; + size_t len; + + if (!pstr) + return S_db_badField; + len = strlen(pstr); + + /* Choice values must be numeric */ + if (dbrType == DBF_MENU || dbrType == DBF_ENUM || dbrType == DBF_DEVICE) + dbrType = DBF_USHORT; + + if (*pstr == '[' && pstr[len-1] == ']') { + /* Convert from JSON array */ + long nReq = 1; + + return dbPutConvertJSON(pstr, dbrType, pbuffer, &nReq); + } + + return convert[dbrType](pstr, pbuffer, NULL); +} + +static long dbConstLoadLS(struct link *plink, char *pbuffer, epicsUInt32 size, + epicsUInt32 *plen) +{ + const char *pstr = plink->value.constantStr; + + if (!pstr) + return S_db_badField; + + return dbLSConvertJSON(pstr, pbuffer, size, plen); +} + +static long dbConstLoadArray(struct link *plink, short dbrType, void *pbuffer, + long *pnReq) +{ + const char *pstr = plink->value.constantStr; + + if (!pstr) + return S_db_badField; + + /* Choice values must be numeric */ + if (dbrType == DBF_MENU || dbrType == DBF_ENUM || dbrType == DBF_DEVICE) + dbrType = DBF_USHORT; + + return dbPutConvertJSON(pstr, dbrType, pbuffer, pnReq); +} + +static long dbConstGetNelements(const struct link *plink, long *nelements) +{ + *nelements = 0; + return 0; +} + +static long dbConstGetValue(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + if (pnRequest) + *pnRequest = 0; + return 0; +} + +static long dbConstPutValue(struct link *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + return 0; +} + +static lset dbConst_lset = { + 1, 0, /* Constant, not Volatile */ + NULL, NULL, + dbConstLoadScalar, + dbConstLoadLS, + dbConstLoadArray, + NULL, + NULL, dbConstGetNelements, + dbConstGetValue, + NULL, NULL, NULL, + NULL, NULL, + NULL, NULL, + dbConstPutValue, NULL, + NULL, NULL +}; diff --git a/modules/database/src/ioc/db/dbConstLink.h b/modules/database/src/ioc/db/dbConstLink.h new file mode 100644 index 000000000..c187f9fc5 --- /dev/null +++ b/modules/database/src/ioc/db/dbConstLink.h @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConstLink.h + * + * Created on: April 3rd, 2016 + * Author: Andrew Johnson + */ + +#ifndef INC_dbConstLink_H +#define INC_dbConstLink_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct link; + +epicsShareFunc void dbConstInitLink(struct link *plink); +epicsShareFunc void dbConstAddLink(struct link *plink); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbConstLink_H */ + diff --git a/modules/database/src/ioc/db/dbContext.cpp b/modules/database/src/ioc/db/dbContext.cpp new file mode 100644 index 000000000..832820206 --- /dev/null +++ b/modules/database/src/ioc/db/dbContext.cpp @@ -0,0 +1,435 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include + +#include "epicsMutex.h" +#include "tsFreeList.h" + +#include "cadef.h" // this can be eliminated when the callbacks use the new interface +#include "db_access.h" // should be eliminated here in the future +#include "caerr.h" // should be eliminated here in the future +#include "epicsEvent.h" +#include "epicsThread.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "db_access_routines.h" +#include "dbCAC.h" +#include "dbChannel.h" +#include "dbChannelIO.h" +#include "dbChannelNOOP.h" +#include "dbPutNotifyBlocker.h" + +class dbService : public cacService { +public: + ~dbService () {} + cacContext & contextCreate ( + epicsMutex & mutualExclusion, + epicsMutex & callbackControl, + cacContextNotify & ); +}; + +static dbService dbs; + +cacContext & dbService::contextCreate ( + epicsMutex & mutualExclusion, + epicsMutex & callbackControl, + cacContextNotify & notify ) +{ + return * new dbContext ( callbackControl, + mutualExclusion, notify ); +} + +extern "C" int dbServiceIsolate; +int dbServiceIsolate = 0; + +extern "C" void dbServiceIOInit () +{ + static int init=0; + if(!init) { + caInstallDefaultService ( dbs ); + init=1; + } +} + +dbBaseIO::dbBaseIO () {} + +dbContext::dbContext ( epicsMutex & cbMutexIn, + epicsMutex & mutexIn, cacContextNotify & notifyIn ) : + readNotifyCache ( mutexIn ), ctx ( 0 ), + stateNotifyCacheSize ( 0 ), mutex ( mutexIn ), cbMutex ( cbMutexIn ), + notify ( notifyIn ), pNetContext ( 0 ), pStateNotifyCache ( 0 ), + isolated(dbServiceIsolate) +{ +} + +dbContext::~dbContext () +{ + delete [] this->pStateNotifyCache; + if ( this->ctx ) { + db_close_events ( this->ctx ); + } +} + +cacChannel & dbContext::createChannel ( + epicsGuard < epicsMutex > & guard, const char * pName, + cacChannelNotify & notifyIn, cacChannel::priLev priority ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + dbChannel *dbch = dbChannel_create ( pName ); + if ( ! dbch ) { + if ( isolated ) { + return *new dbChannelNOOP(pName, notifyIn); + + } else if ( ! this->pNetContext.get() ) { + this->pNetContext.reset ( + & this->notify.createNetworkContext ( + this->mutex, this->cbMutex ) ); + } + return this->pNetContext->createChannel ( + guard, pName, notifyIn, priority ); + } + + if ( ! ca_preemtive_callback_is_enabled () ) { + dbChannelDelete ( dbch ); + errlogPrintf ( + "dbContext: preemptive callback required for direct in\n" + "memory interfacing of CA channels to the DB.\n" ); + throw cacChannel::unsupportedByService (); + } + + try { + return * new ( this->dbChannelIOFreeList ) + dbChannelIO ( this->mutex, notifyIn, dbch, *this ); + } + catch (...) { + dbChannelDelete ( dbch ); + throw; + } +} + +void dbContext::destroyChannel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard, + dbChannelIO & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( chan.dbContextPrivateListOfIO::pBlocker ) { + this->ioTable.remove ( *chan.dbContextPrivateListOfIO::pBlocker ); + chan.dbContextPrivateListOfIO::pBlocker->destructor ( cbGuard, guard ); + this->dbPutNotifyBlockerFreeList.release ( chan.dbContextPrivateListOfIO::pBlocker ); + chan.dbContextPrivateListOfIO::pBlocker = 0; + } + + chan.destructor ( cbGuard, guard ); + this->dbChannelIOFreeList.release ( & chan ); +} + +void dbContext::callStateNotify ( struct dbChannel * dbch, + unsigned type, unsigned long count, + const struct db_field_log * pfl, + cacStateNotify & notifyIn ) +{ + long realcount = (count==0)?dbChannelElements(dbch):count; + unsigned long size = dbr_size_n ( type, realcount ); + + if ( type > INT_MAX ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + notifyIn.exception ( guard, ECA_BADTYPE, + "type code out of range (high side)", + type, count ); + return; + } + + if ( count > INT_MAX ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + notifyIn.exception ( guard, ECA_BADCOUNT, + "element count out of range (high side)", + type, count); + return; + } + + // no need to lock this because state notify is + // called from only one event queue consumer thread + if ( this->stateNotifyCacheSize < size) { + char * pTmp = new char [size]; + delete [] this->pStateNotifyCache; + this->pStateNotifyCache = pTmp; + this->stateNotifyCacheSize = size; + } + void *pvfl = (void *) pfl; + int status; + if(count==0) /* fetch actual number of elements (dynamic array) */ + status = dbChannel_get_count( dbch, static_cast ( type ), + this->pStateNotifyCache, &realcount, pvfl ); + else /* fetch requested number of elements, truncated or zero padded */ + status = dbChannel_get( dbch, static_cast ( type ), + this->pStateNotifyCache, realcount, pvfl ); + if ( status ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + notifyIn.exception ( guard, ECA_GETFAIL, + "dbChannel_get() completed unsuccessfully", type, count ); + } + else { + epicsGuard < epicsMutex > guard ( this->mutex ); + notifyIn.current ( guard, type, realcount, this->pStateNotifyCache ); + } +} + +extern "C" void cacAttachClientCtx ( void * pPrivate ) +{ + int status = ca_attach_context ( (ca_client_context *) pPrivate ); + assert ( status == ECA_NORMAL ); +} + +void dbContext::subscribe ( + epicsGuard < epicsMutex > & guard, + struct dbChannel * dbch, dbChannelIO & chan, + unsigned type, unsigned long count, unsigned mask, + cacStateNotify & notifyIn, cacChannel::ioid * pId ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + /* + * the database uses type "int" to store these parameters + */ + if ( type > INT_MAX ) { + throw cacChannel::badType(); + } + if ( count > INT_MAX ) { + throw cacChannel::outOfBounds(); + } + + if ( ! this->ctx ) { + dbEventCtx tmpctx = 0; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + tmpctx = db_init_events (); + if ( ! tmpctx ) { + throw std::bad_alloc (); + } + + unsigned selfPriority = epicsThreadGetPrioritySelf (); + unsigned above; + epicsThreadBooleanStatus tbs = + epicsThreadLowestPriorityLevelAbove ( selfPriority, &above ); + if ( tbs != epicsThreadBooleanStatusSuccess ) { + above = selfPriority; + } + int status = db_start_events ( tmpctx, "CAC-event", + cacAttachClientCtx, ca_current_context (), above ); + if ( status ) { + db_close_events ( tmpctx ); + throw std::bad_alloc (); + } + } + if ( this->ctx ) { + // another thread tried to simultaneously setup + // the event system + db_close_events ( tmpctx ); + } + else { + this->ctx = tmpctx; + } + } + + dbSubscriptionIO & subscr = + * new ( this->dbSubscriptionIOFreeList ) + dbSubscriptionIO ( guard, this->mutex, *this, chan, + dbch, notifyIn, type, count, mask, this->ctx ); + chan.dbContextPrivateListOfIO::eventq.add ( subscr ); + this->ioTable.idAssignAdd ( subscr ); + + if ( pId ) { + *pId = subscr.getId (); + } +} + +void dbContext::initiatePutNotify ( + epicsGuard < epicsMutex > & guard, + dbChannelIO & chan, struct dbChannel * dbch, + unsigned type, unsigned long count, const void * pValue, + cacWriteNotify & notifyIn, cacChannel::ioid * pId ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( ! chan.dbContextPrivateListOfIO::pBlocker ) { + chan.dbContextPrivateListOfIO::pBlocker = + new ( this->dbPutNotifyBlockerFreeList ) + dbPutNotifyBlocker ( this->mutex ); + this->ioTable.idAssignAdd ( *chan.dbContextPrivateListOfIO::pBlocker ); + } + chan.dbContextPrivateListOfIO::pBlocker->initiatePutNotify ( + guard, notifyIn, dbch, type, count, pValue ); + if ( pId ) { + *pId = chan.dbContextPrivateListOfIO::pBlocker->getId (); + } +} + +void dbContext::destroyAllIO ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard, + dbChannelIO & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + dbSubscriptionIO * pIO; + tsDLList < dbSubscriptionIO > tmp; + + while ( ( pIO = chan.dbContextPrivateListOfIO::eventq.get() ) ) { + this->ioTable.remove ( *pIO ); + tmp.add ( *pIO ); + } + if ( chan.dbContextPrivateListOfIO::pBlocker ) { + this->ioTable.remove ( *chan.dbContextPrivateListOfIO::pBlocker ); + } + + while ( ( pIO = tmp.get() ) ) { + // This prevents a db event callback from coming + // through after the notify IO is deleted + pIO->unsubscribe ( cbGuard, guard ); + // If they call ioCancel() here it will be ignored + // because the IO has been unregistered above. + pIO->channelDeleteException ( cbGuard, guard ); + pIO->destructor ( cbGuard, guard ); + this->dbSubscriptionIOFreeList.release ( pIO ); + } + + if ( chan.dbContextPrivateListOfIO::pBlocker ) { + chan.dbContextPrivateListOfIO::pBlocker->destructor ( cbGuard, guard ); + this->dbPutNotifyBlockerFreeList.release ( chan.dbContextPrivateListOfIO::pBlocker ); + chan.dbContextPrivateListOfIO::pBlocker = 0; + } +} + +void dbContext::ioCancel ( + CallbackGuard & cbGuard, epicsGuard < epicsMutex > & guard, + dbChannelIO & chan, const cacChannel::ioid &id ) +{ + guard.assertIdenticalMutex ( this->mutex ); + dbBaseIO * pIO = this->ioTable.remove ( id ); + if ( pIO ) { + dbSubscriptionIO *pSIO = pIO->isSubscription (); + if ( pSIO ) { + chan.dbContextPrivateListOfIO::eventq.remove ( *pSIO ); + pSIO->unsubscribe ( cbGuard, guard ); + pSIO->channelDeleteException ( cbGuard, guard ); + pSIO->destructor ( cbGuard, guard ); + this->dbSubscriptionIOFreeList.release ( pSIO ); + } + else if ( pIO == chan.dbContextPrivateListOfIO::pBlocker ) { + chan.dbContextPrivateListOfIO::pBlocker->cancel ( cbGuard, guard ); + } + else { + errlogPrintf ( "dbContext::ioCancel() unrecognized IO was probably leaked or not canceled\n" ); + } + } +} + +void dbContext::ioShow ( + epicsGuard < epicsMutex > & guard, const cacChannel::ioid &id, + unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + const dbBaseIO * pIO = this->ioTable.lookup ( id ); + if ( pIO ) { + pIO->show ( guard, level ); + } +} + +void dbContext::showAllIO ( const dbChannelIO & chan, unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + tsDLIterConst < dbSubscriptionIO > pItem = + chan.dbContextPrivateListOfIO::eventq.firstIter (); + while ( pItem.valid () ) { + pItem->show ( guard, level ); + pItem++; + } + if ( chan.dbContextPrivateListOfIO::pBlocker ) { + chan.dbContextPrivateListOfIO::pBlocker->show ( guard, level ); + } +} + +void dbContext::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->show ( guard, level ); +} + +void dbContext::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + printf ( "dbContext at %p\n", + static_cast ( this ) ); + if ( level > 0u ) { + printf ( "\tevent call back cache location %p, and its size %lu\n", + static_cast ( this->pStateNotifyCache ), this->stateNotifyCacheSize ); + this->readNotifyCache.show ( guard, level - 1 ); + } + if ( level > 1u ) { + this->mutex.show ( level - 2u ); + } + if ( this->pNetContext.get() ) { + this->pNetContext.get()->show ( guard, level ); + } +} + +void dbContext::flush ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pNetContext.get() ) { + this->pNetContext.get()->flush ( guard ); + } +} + +unsigned dbContext::circuitCount ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pNetContext.get() ) { + return this->pNetContext.get()->circuitCount ( guard ); + } + else { + return 0u; + } +} + +void dbContext::selfTest ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + this->ioTable.verify (); + + if ( this->pNetContext.get() ) { + this->pNetContext.get()->selfTest ( guard ); + } +} + +unsigned dbContext::beaconAnomaliesSinceProgramStart ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pNetContext.get() ) { + return this->pNetContext.get()->beaconAnomaliesSinceProgramStart ( guard ); + } + else { + return 0u; + } +} + + diff --git a/modules/database/src/ioc/db/dbContextReadNotifyCache.cpp b/modules/database/src/ioc/db/dbContextReadNotifyCache.cpp new file mode 100644 index 000000000..e3196b25d --- /dev/null +++ b/modules/database/src/ioc/db/dbContextReadNotifyCache.cpp @@ -0,0 +1,180 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Auther Jeff Hill + */ + +#include + +#include "epicsMutex.h" +#include "dbDefs.h" + +#include "cadef.h" // this can be eliminated when the callbacks use the new interface +#include "db_access.h" // should be eliminated here in the future + +#define epicsExportSharedSymbols + +#include "db_access_routines.h" +#include "dbCAC.h" + +#include "epicsAssert.h" + +dbContextReadNotifyCache::dbContextReadNotifyCache ( epicsMutex & mutexIn ) : + _mutex ( mutexIn ) +{ +} + +class privateAutoDestroyPtr { +public: + privateAutoDestroyPtr ( + dbContextReadNotifyCacheAllocator & allocator, unsigned long size ) : + _allocator ( allocator ), _p ( allocator.alloc ( size ) ) {} + ~privateAutoDestroyPtr () { _allocator.free ( _p ); } + char * get () const { return _p; } +private: + dbContextReadNotifyCacheAllocator & _allocator; + char * _p; + privateAutoDestroyPtr ( const privateAutoDestroyPtr & ); + privateAutoDestroyPtr & operator = ( const privateAutoDestroyPtr & ); +}; + +// extra effort taken here to not hold the lock when calling the callback +void dbContextReadNotifyCache::callReadNotify ( + epicsGuard < epicsMutex > & guard, struct dbChannel * dbch, + unsigned type, unsigned long count, cacReadNotify & notify ) +{ + guard.assertIdenticalMutex ( _mutex ); + + if ( type > INT_MAX ) { + notify.exception ( guard, ECA_BADTYPE, + "type code out of range (high side)", + type, count ); + return; + } + + const long maxcount = dbChannelElements(dbch); + + if ( maxcount < 0 ) { + notify.exception ( guard, ECA_BADCOUNT, + "database has negetive element count", + type, count); + return; + + } else if ( count > (unsigned long)maxcount ) { + notify.exception ( guard, ECA_BADCOUNT, + "element count out of range (high side)", + type, count); + return; + } + + long realcount = (count==0)?maxcount:count; + unsigned long size = dbr_size_n ( type, realcount ); + + privateAutoDestroyPtr ptr ( _allocator, size ); + int status; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + if ( count==0 ) + status = dbChannel_get_count ( dbch, (int)type, ptr.get(), &realcount, 0); + else + status = dbChannel_get ( dbch, (int)type, ptr.get (), realcount, 0 ); + } + if ( status ) { + notify.exception ( guard, ECA_GETFAIL, + "db_get_field() completed unsuccessfuly", + type, count ); + } + else { + notify.completion ( + guard, type, realcount, ptr.get () ); + } +} + +void dbContextReadNotifyCache::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( _mutex ); + + printf ( "dbContextReadNotifyCache\n" ); + if ( level > 0 ) { + this->_allocator.show ( level - 1 ); + } +} + +dbContextReadNotifyCacheAllocator::dbContextReadNotifyCacheAllocator () : + _readNotifyCacheSize ( 0 ), _pReadNotifyCache ( 0 ) +{ +} + +dbContextReadNotifyCacheAllocator::~dbContextReadNotifyCacheAllocator () +{ + this->reclaimAllCacheEntries (); +} + +void dbContextReadNotifyCacheAllocator::reclaimAllCacheEntries () +{ + while ( _pReadNotifyCache ) { + cacheElem_t * pNext = _pReadNotifyCache->pNext; + assert(_pReadNotifyCache->size == _readNotifyCacheSize); + ::free(_pReadNotifyCache); + _pReadNotifyCache = pNext; + } +} + +char * dbContextReadNotifyCacheAllocator::alloc ( unsigned long size ) +{ + if ( size > _readNotifyCacheSize ) { + this->reclaimAllCacheEntries (); + _readNotifyCacheSize = size; + } + + cacheElem_t * pAlloc = _pReadNotifyCache; + if ( pAlloc ) { + assert(pAlloc->size == _readNotifyCacheSize); + _pReadNotifyCache = pAlloc->pNext; + } + else { + pAlloc = (cacheElem_t*)calloc(1, sizeof(cacheElem_t)+_readNotifyCacheSize); + if(!pAlloc) throw std::bad_alloc(); + pAlloc->size = _readNotifyCacheSize; + } + return pAlloc->buf; +} + +void dbContextReadNotifyCacheAllocator::free ( char * pFree ) +{ + cacheElem_t * pAlloc = (cacheElem_t*)(pFree - offsetof(cacheElem_t, buf)); + if (pAlloc->size == _readNotifyCacheSize) { + pAlloc->pNext = _pReadNotifyCache; + _pReadNotifyCache = pAlloc; + } else { + ::free(pAlloc); + } +} + +void dbContextReadNotifyCacheAllocator::show ( unsigned level ) const +{ + printf ( "dbContextReadNotifyCacheAlocator\n" ); + if ( level > 0 ) { + size_t count =0; + cacheElem_t * pNext = _pReadNotifyCache; + while ( pNext ) { + assert(pNext->size == _readNotifyCacheSize); + pNext = _pReadNotifyCache->pNext; + count++; + } + printf ( "\tcount %lu and size %lu\n", + static_cast < unsigned long > ( count ), + _readNotifyCacheSize ); + } +} + + diff --git a/modules/database/src/ioc/db/dbConvert.c b/modules/database/src/ioc/db/dbConvert.c new file mode 100644 index 000000000..5d2c71f4c --- /dev/null +++ b/modules/database/src/ioc/db/dbConvert.c @@ -0,0 +1,1839 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConvert.c */ +/* + * Original Author: Bob Dalesio + * Date: 11-7-90 +*/ + +#include +#include +#include +#include +#include + +#include "cvtFast.h" +#include "dbDefs.h" +#include "epicsConvert.h" +#include "epicsStdlib.h" +#include "errlog.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbConvert.h" +#include "dbFldTypes.h" +#include "dbStaticLib.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" + +/* Helper for copy as bytes with no type conversion. + * Assumes nRequest <= no_bytes + * nRequest, no_bytes, and offset should be given in bytes. + */ +static void copyNoConvert(const void *pfrom, + void *pto, long nRequest, long no_bytes, long offset) +{ + const void *pfrom_offset = (const char *) pfrom + offset; + + if (offset > 0 && offset < no_bytes && offset + nRequest > no_bytes) { + const size_t N = no_bytes - offset; + void *pto_N = (char *) pto + N; + + /* copy with wrap */ + memmove(pto, pfrom_offset, N); + memmove(pto_N, pfrom, nRequest - N); + } else { + /* no wrap, just copy */ + memmove(pto, pfrom_offset, nRequest); + } +} +#define COPYNOCONVERT(N, FROM, TO, NREQ, NO_ELEM, OFFSET) \ + copyNoConvert(FROM, TO, (N)*(NREQ), (N)*(NO_ELEM), (N)*(OFFSET)) + +#define GET(typea, typeb) (const dbAddr *paddr, \ + void *pto, long nRequest, long no_elements, long offset) \ +{ \ + typea *psrc = (typea *) paddr->pfield; \ + typeb *pdst = (typeb *) pto; \ + \ + if (nRequest==1 && offset==0) { \ + *pdst = (typeb) *psrc; \ + return 0; \ + } \ + psrc += offset; \ + while (nRequest--) { \ + *pdst++ = (typeb) *psrc++; \ + if (++offset == no_elements) \ + psrc = (typea *) paddr->pfield; \ + } \ + return 0; \ +} + +#define GET_NOCONVERT(typea, typeb) (const dbAddr *paddr, \ + void *pto, long nRequest, long no_elements, long offset) \ +{ \ + if (nRequest==1 && offset==0) { \ + typea *psrc = (typea *) paddr->pfield; \ + typeb *pdst = (typeb *) pto; \ + \ + *pdst = (typeb) *psrc; \ + return 0; \ + } \ + COPYNOCONVERT(sizeof(typeb), paddr->pfield, pto, nRequest, no_elements, offset); \ + return 0; \ +} + +#define PUT(typea, typeb) (dbAddr *paddr, \ + const void *pfrom, long nRequest, long no_elements, long offset) \ +{ \ + const typea *psrc = (const typea *) pfrom; \ + typeb *pdst = (typeb *) paddr->pfield; \ + \ + if (nRequest==1 && offset==0) { \ + *pdst = (typeb) *psrc; \ + return 0; \ + } \ + pdst += offset; \ + while (nRequest--) { \ + *pdst++ = (typeb) *psrc++; \ + if (++offset == no_elements) \ + pdst = (typeb *) paddr->pfield; \ + } \ + return 0; \ +} + +#define PUT_NOCONVERT(typea, typeb) (dbAddr *paddr, \ + const void *pfrom, long nRequest, long no_elements, long offset) \ +{ \ + if (nRequest==1 && offset==0) { \ + const typea *psrc = (const typea *) pfrom; \ + typeb *pdst = (typeb *) paddr->pfield; \ + \ + *pdst = (typeb) *psrc; \ + return 0; \ + } \ + COPYNOCONVERT(sizeof(typeb), pfrom, paddr->pfield, nRequest, no_elements, offset); \ + return 0; \ +} + + +/* dbAccess Get conversion support routines */ + +static long getStringString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = paddr->pfield; + char *pdst = (char *) pto; + short size = paddr->field_size; + short sizeto; + + /* always force result string to be null terminated*/ + sizeto = size; + if (sizeto >= MAX_STRING_SIZE) + sizeto = MAX_STRING_SIZE - 1; + + if (nRequest==1 && offset==0) { + strncpy(pdst, psrc, sizeto); + pdst[sizeto] = 0; + return 0; + } + psrc += size * offset; + while (nRequest--) { + strncpy(pdst, psrc, sizeto); + pdst[sizeto] = 0; + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += size; + } + return 0; +} + +static long getStringChar(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsInt8 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseInt8(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringUchar(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsUInt8 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseUInt8(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringShort(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsInt16 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseInt16(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringUshort(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsUInt16 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseUInt16(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringLong(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsInt32 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseInt32(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringUlong(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsUInt32 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseUInt32(psrc, pdst, 10, &end); + + if (status == S_stdlib_noConversion || + (!status && (*end == '.' || *end == 'e' || *end == 'E'))) { + /* + * Convert via double so numbers like 1.0e3 convert properly. + * db_access pretends unsigned long is double. + */ + epicsFloat64 dval; + + status = epicsParseFloat64(psrc, &dval, &end); + if (!status && 0 <= dval && dval <= ULONG_MAX) + *pdst = dval; + } + if (status) + return status; + pdst++; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringInt64(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsInt64 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseInt64(psrc, pdst++, 10, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringUInt64(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsUInt64 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseUInt64(psrc, pdst++, 0, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringFloat(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsFloat32 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseFloat32(psrc, pdst++, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + +static long getStringDouble(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield + MAX_STRING_SIZE * offset; + epicsFloat64 *pdst = pto; + + while (nRequest--) { + if (*psrc == 0) + *pdst++ = 0; + else { + char *end; + long status = epicsParseFloat64(psrc, pdst++, &end); + + if (status) + return status; + } + if (++offset == no_elements) + psrc = paddr->pfield; + else + psrc += MAX_STRING_SIZE; + } + return 0; +} + + +static long getCharString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtCharToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtCharToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (char *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getCharChar(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield; + char *pdst = (char *) pto; + + if (paddr->pfldDes && paddr->pfldDes->field_type == DBF_STRING) { + /* This is a DBF_STRING field being read as a long string. + * The buffer we return must be zero-terminated. + */ + pdst[--nRequest] = 0; + if (nRequest == 0) + return 0; + } + if (nRequest==1 && offset==0) { + *pdst = *psrc; + return 0; + } + COPYNOCONVERT(sizeof(char), paddr->pfield, pto, nRequest, no_elements, offset); + return 0; +} + +static long getCharUchar(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *psrc = (char *) paddr->pfield; + epicsUInt8 *pdst = (epicsUInt8 *) pto; + + if (paddr->pfldDes && paddr->pfldDes->field_type == DBF_STRING) { + /* This is a DBF_STRING field being read as a long string. + * The buffer we return must be zero-terminated. + */ + pdst[--nRequest] = 0; + if (nRequest == 0) + return 0; + } + if (nRequest==1 && offset==0) { + *pdst = *psrc; + return 0; + } + COPYNOCONVERT(sizeof(char), paddr->pfield, pto, nRequest, no_elements, offset); + return 0; +} + +static long getCharShort GET(char, epicsInt16) +static long getCharUshort GET(char, epicsUInt16) +static long getCharLong GET(char, epicsInt32) +static long getCharUlong GET(char, epicsUInt32) +static long getCharInt64 GET(char, epicsInt64) +static long getCharUInt64 GET(char, epicsUInt64) +static long getCharFloat GET(char, epicsFloat32) +static long getCharDouble GET(char, epicsFloat64) +static long getCharEnum GET(char, epicsEnum16) + +static long getUcharString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsUInt8 *psrc = (epicsUInt8 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtUcharToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtUcharToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsUInt8 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getUcharChar GET_NOCONVERT(epicsUInt8, char) +static long getUcharUchar GET_NOCONVERT(epicsUInt8, epicsUInt8) +static long getUcharShort GET(epicsUInt8, epicsInt16) +static long getUcharUshort GET(epicsUInt8, epicsUInt16) +static long getUcharLong GET(epicsUInt8, epicsInt32) +static long getUcharUlong GET(epicsUInt8, epicsUInt32) +static long getUcharInt64 GET(epicsUInt8, epicsInt64) +static long getUcharUInt64 GET(epicsUInt8, epicsUInt64) +static long getUcharFloat GET(epicsUInt8, epicsFloat32) +static long getUcharDouble GET(epicsUInt8, epicsFloat64) +static long getUcharEnum GET(epicsUInt8, epicsEnum16) + +static long getShortString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsInt16 *psrc = (epicsInt16 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtShortToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtShortToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsInt16 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getShortChar GET(epicsInt16, char) +static long getShortUchar GET(epicsInt16, epicsUInt8) +static long getShortShort GET_NOCONVERT(epicsInt16, epicsInt16) +static long getShortUshort GET_NOCONVERT(epicsInt16, epicsUInt16) +static long getShortLong GET(epicsInt16, epicsInt32) +static long getShortUlong GET(epicsInt16, epicsUInt32) +static long getShortInt64 GET(epicsInt16, epicsInt64) +static long getShortUInt64 GET(epicsInt16, epicsUInt64) +static long getShortFloat GET(epicsInt16, epicsFloat32) +static long getShortDouble GET(epicsInt16, epicsFloat64) +static long getShortEnum GET(epicsInt16, epicsEnum16) + +static long getUshortString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsUInt16 *psrc = (epicsUInt16 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtUshortToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtUshortToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsUInt16 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getUshortChar GET(epicsUInt16, char) +static long getUshortUchar GET(epicsUInt16, epicsUInt8) +static long getUshortShort GET_NOCONVERT(epicsUInt16, epicsInt16) +static long getUshortUshort GET_NOCONVERT(epicsUInt16, epicsUInt16) +static long getUshortLong GET(epicsUInt16, epicsInt32) +static long getUshortUlong GET(epicsUInt16, epicsUInt32) +static long getUshortInt64 GET(epicsUInt16, epicsInt64) +static long getUshortUInt64 GET(epicsUInt16, epicsUInt64) +static long getUshortFloat GET(epicsUInt16, epicsFloat32) +static long getUshortDouble GET(epicsUInt16, epicsFloat64) +static long getUshortEnum GET(epicsUInt16, epicsEnum16) + +static long getLongString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsInt32 *psrc = (epicsInt32 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtLongToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtLongToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsInt32 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getLongChar GET(epicsInt32, char) +static long getLongUchar GET(epicsInt32, epicsUInt8) +static long getLongShort GET(epicsInt32, epicsInt16) +static long getLongUshort GET(epicsInt32, epicsUInt16) +static long getLongLong GET_NOCONVERT(epicsInt32, epicsInt32) +static long getLongUlong GET_NOCONVERT(epicsInt32, epicsUInt32) +static long getLongInt64 GET(epicsInt32, epicsInt64) +static long getLongUInt64 GET(epicsInt32, epicsUInt64) +static long getLongFloat GET(epicsInt32, epicsFloat32) +static long getLongDouble GET(epicsInt32, epicsFloat64) +static long getLongEnum GET(epicsInt32, epicsEnum16) + +static long getUlongString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsUInt32 *psrc = (epicsUInt32 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtUlongToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtUlongToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsUInt32 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getUlongChar GET(epicsUInt32, char) +static long getUlongUchar GET(epicsUInt32, epicsUInt8) +static long getUlongShort GET(epicsUInt32, epicsInt16) +static long getUlongUshort GET(epicsUInt32, epicsUInt16) +static long getUlongLong GET_NOCONVERT(epicsUInt32, epicsInt32) +static long getUlongUlong GET_NOCONVERT(epicsUInt32, epicsUInt32) +static long getUlongInt64 GET(epicsUInt32, epicsInt64) +static long getUlongUInt64 GET(epicsUInt32, epicsUInt64) +static long getUlongFloat GET(epicsUInt32, epicsFloat32) +static long getUlongDouble GET(epicsUInt32, epicsFloat64) +static long getUlongEnum GET(epicsUInt32, epicsEnum16) + +static long getInt64String(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsInt64 *psrc = (epicsInt64 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtInt64ToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtInt64ToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsInt64 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getInt64Char GET(epicsInt64, char) +static long getInt64Uchar GET(epicsInt64, epicsUInt8) +static long getInt64Short GET(epicsInt64, epicsInt16) +static long getInt64Ushort GET(epicsInt64, epicsUInt16) +static long getInt64Long GET(epicsInt64, epicsInt32) +static long getInt64Ulong GET(epicsInt64, epicsUInt32) +static long getInt64Int64 GET_NOCONVERT(epicsInt64, epicsInt64) +static long getInt64UInt64 GET_NOCONVERT(epicsInt64, epicsUInt64) +static long getInt64Float GET(epicsInt64, epicsFloat32) +static long getInt64Double GET(epicsInt64, epicsFloat64) +static long getInt64Enum GET(epicsInt64, epicsEnum16) + +static long getUInt64String(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsUInt64 *psrc = (epicsUInt64 *) paddr->pfield; + char *pdst = (char *) pto; + + if (nRequest==1 && offset==0) { + cvtUInt64ToString(*psrc, pdst); + return 0; + } + psrc += offset; + while (nRequest--) { + cvtUInt64ToString(*psrc, pdst); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsUInt64 *) paddr->pfield; + else + psrc++; + } + return 0; +} + +static long getUInt64Char GET(epicsUInt64, char) +static long getUInt64Uchar GET(epicsUInt64, epicsUInt8) +static long getUInt64Short GET(epicsUInt64, epicsInt16) +static long getUInt64Ushort GET(epicsUInt64, epicsUInt16) +static long getUInt64Long GET(epicsUInt64, epicsInt32) +static long getUInt64Ulong GET(epicsUInt64, epicsUInt32) +static long getUInt64Int64 GET_NOCONVERT(epicsUInt64, epicsInt64) +static long getUInt64UInt64 GET_NOCONVERT(epicsUInt64, epicsUInt64) +static long getUInt64Float GET(epicsUInt64, epicsFloat32) +static long getUInt64Double GET(epicsUInt64, epicsFloat64) +static long getUInt64Enum GET(epicsUInt64, epicsEnum16) + +static long getFloatString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsFloat32 *psrc = (epicsFloat32 *) paddr->pfield; + char *pdst = (char *) pto; + long status = 0; + long precision = 6; + rset *prset = 0; + + if (paddr) + prset = dbGetRset(paddr); + if (prset && prset->get_precision) + status = prset->get_precision(paddr, &precision); + if (nRequest==1 && offset==0) { + cvtFloatToString(*psrc, pdst, precision); + return(status); + } + psrc += offset; + while (nRequest--) { + cvtFloatToString(*psrc, pdst, precision); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsFloat32 *) paddr->pfield; + else + psrc++; + } + return(status); +} + +static long getFloatChar GET(epicsFloat32, char) +static long getFloatUchar GET(epicsFloat32, epicsUInt8) +static long getFloatShort GET(epicsFloat32, epicsInt16) +static long getFloatUshort GET(epicsFloat32, epicsUInt16) +static long getFloatLong GET(epicsFloat32, epicsInt32) +static long getFloatUlong GET(epicsFloat32, epicsUInt32) +static long getFloatInt64 GET(epicsFloat32, epicsInt64) +static long getFloatUInt64 GET(epicsFloat32, epicsUInt64) +static long getFloatFloat GET_NOCONVERT(epicsFloat32, epicsFloat32) +static long getFloatDouble GET(epicsFloat32, epicsFloat64) +static long getFloatEnum GET(epicsFloat32, epicsEnum16) + +static long getDoubleString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsFloat64 *psrc = (epicsFloat64 *) paddr->pfield; + char *pdst = (char *) pto; + long status = 0; + long precision = 6; + rset *prset = 0; + + if (paddr) + prset = dbGetRset(paddr); + if (prset && prset->get_precision) + status = prset->get_precision(paddr, &precision); + if (nRequest==1 && offset==0) { + cvtDoubleToString(*psrc, pdst, precision); + return(status); + } + psrc += offset; + while (nRequest--) { + cvtDoubleToString(*psrc, pdst, precision); + pdst += MAX_STRING_SIZE; + if (++offset == no_elements) + psrc = (epicsFloat64 *) paddr->pfield; + else + psrc++; + } + return(status); +} + +static long getDoubleChar GET(epicsFloat64, char) +static long getDoubleUchar GET(epicsFloat64, epicsUInt8) +static long getDoubleShort GET(epicsFloat64, epicsInt16) +static long getDoubleUshort GET(epicsFloat64, epicsUInt16) +static long getDoubleLong GET(epicsFloat64, epicsInt32) +static long getDoubleUlong GET(epicsFloat64, epicsUInt32) +static long getDoubleInt64 GET(epicsFloat64, epicsInt64) +static long getDoubleUInt64 GET(epicsFloat64, epicsUInt64) + +static long getDoubleFloat(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + epicsFloat64 *psrc = (epicsFloat64 *) paddr->pfield; + epicsFloat32 *pdst = (epicsFloat32 *) pto; + + if (nRequest==1 && offset==0) { + *pdst = epicsConvertDoubleToFloat(*psrc); + return 0; + } + psrc += offset; + while (nRequest--) { + *pdst = epicsConvertDoubleToFloat(*psrc); + ++psrc; ++pdst; + if (++offset == no_elements) + psrc = (epicsFloat64 *) paddr->pfield; + } + return 0; +} + +static long getDoubleDouble GET_NOCONVERT(epicsFloat64, epicsFloat64) +static long getDoubleEnum GET(epicsFloat64, epicsEnum16) + +static long getEnumString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *pdst = (char *) pto; + rset *prset; + long status; + + prset = dbGetRset(paddr); + if (prset && prset->get_enum_str) + return prset->get_enum_str(paddr, pdst); + + status = S_db_noRSET; + recGblRecSupError(status, paddr, "dbGet", "get_enum_str"); + return S_db_badDbrtype; +} + +static long getEnumChar GET(epicsEnum16, char) +static long getEnumUchar GET(epicsEnum16, epicsUInt8) +static long getEnumShort GET(epicsEnum16, epicsInt16) +static long getEnumUshort GET(epicsEnum16, epicsUInt16) +static long getEnumLong GET(epicsEnum16, epicsInt32) +static long getEnumUlong GET(epicsEnum16, epicsUInt32) +static long getEnumInt64 GET(epicsEnum16, epicsInt64) +static long getEnumUInt64 GET(epicsEnum16, epicsUInt64) +static long getEnumFloat GET(epicsEnum16, epicsFloat32) +static long getEnumDouble GET(epicsEnum16, epicsFloat64) +static long getEnumEnum GET_NOCONVERT(epicsEnum16, epicsEnum16) + +static long getMenuString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *pdst = (char *) pto; + dbFldDes *pdbFldDes = paddr->pfldDes; + dbMenu *pdbMenu; + char **papChoiceValue; + char *pchoice; + epicsEnum16 choice_ind= *((epicsEnum16*) paddr->pfield); + + if (no_elements!=1){ + recGblDbaddrError(S_db_onlyOne, paddr, "dbGet(getMenuString)"); + return(S_db_onlyOne); + } + if (!pdbFldDes + || !(pdbMenu = (dbMenu *) pdbFldDes->ftPvt) + || (choice_ind>=pdbMenu->nChoice) + || !(papChoiceValue = pdbMenu->papChoiceValue) + || !(pchoice=papChoiceValue[choice_ind])) { + recGblDbaddrError(S_db_badChoice, paddr, "dbGet(getMenuString)"); + return(S_db_badChoice); + } + strncpy(pdst, pchoice, MAX_STRING_SIZE); + return 0; +} + +static long getDeviceString(const dbAddr *paddr, + void *pto, long nRequest, long no_elements, long offset) +{ + char *pdst = (char *) pto; + dbFldDes *pdbFldDes = paddr->pfldDes; + dbDeviceMenu *pdbDeviceMenu; + char **papChoice; + char *pchoice; + epicsEnum16 choice_ind= *((epicsEnum16*) paddr->pfield); + + if (no_elements!=1){ + recGblDbaddrError(S_db_onlyOne, paddr, "dbGet(getDeviceString)"); + return(S_db_onlyOne); + } + if (!pdbFldDes + || !(pdbDeviceMenu = (dbDeviceMenu *) pdbFldDes->ftPvt) + || (choice_ind>=pdbDeviceMenu->nChoice ) + || !(papChoice = pdbDeviceMenu->papChoice) + || !(pchoice=papChoice[choice_ind])) { + recGblDbaddrError(S_db_badChoice, paddr, "dbGet(getDeviceString)"); + return(S_db_badChoice); + } + strncpy(pdst, pchoice, MAX_STRING_SIZE); + return 0; +} + + +/* dbAccess put conversion support routines */ + +static long putStringString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = (const char *) pfrom; + char *pdst = paddr->pfield; + short size = paddr->field_size; + + if (nRequest==1 && offset==0) { + strncpy(pdst, psrc, size); + *(pdst+size-1) = 0; + return 0; + } + pdst+= (size*offset); + while (nRequest--) { + strncpy(pdst, psrc, size); + pdst[size-1] = 0; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putStringChar(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsInt8 *pdst = (epicsInt8 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseInt8(psrc, pdst++, 10, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringUchar(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsUInt8 *pdst = (epicsUInt8 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseUInt8(psrc, pdst++, 10, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringShort(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsInt16 *pdst = (epicsInt16 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseInt16(psrc, pdst++, 10, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringUshort(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsUInt16 *pdst = (epicsUInt16 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseUInt16(psrc, pdst++, 10, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringLong(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsInt32 *pdst = (epicsInt32 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseInt32(psrc, pdst++, 10, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringUlong(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsUInt32 *pdst = (epicsUInt32 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseUInt32(psrc, pdst, 10, &end); + + if (status == S_stdlib_noConversion || + (!status && (*end == '.' || *end == 'e' || *end == 'E'))) { + /* + * Convert via double so numbers like 1.0e3 convert properly. + * db_access pretends unsigned long is double. + */ + epicsFloat64 dval; + + status = epicsParseFloat64(psrc, &dval, &end); + if (!status && 0 <= dval && dval <= ULONG_MAX) + *pdst = dval; + } + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + else + pdst++; + } + return 0; +} + +static long putStringInt64(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsInt64 *pdst = (epicsInt64 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseInt64(psrc, pdst++, 10, &end); + + if (status) + return status; + + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringUInt64(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsUInt64 *pdst = (epicsUInt64 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseUInt64(psrc, pdst, 0, &end); + + if (status) + return status; + + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + else + pdst++; + } + return 0; +} + +static long putStringFloat(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsFloat32 *pdst = (epicsFloat32 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseFloat32(psrc, pdst++, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringDouble(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = pfrom; + epicsFloat64 *pdst = (epicsFloat64 *) paddr->pfield + offset; + + while (nRequest--) { + char *end; + long status = epicsParseFloat64(psrc, pdst++, &end); + + if (status) + return status; + psrc += MAX_STRING_SIZE; + if (++offset == no_elements) + pdst = paddr->pfield; + } + return 0; +} + +static long putStringEnum(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + epicsEnum16 *pfield = paddr->pfield; + rset *prset = dbGetRset(paddr); + long status = S_db_noRSET; + struct dbr_enumStrs enumStrs; + + if (no_elements != 1) { + recGblDbaddrError(S_db_onlyOne, paddr, "dbPut(putStringEnum)"); + return S_db_onlyOne; + } + + if (!prset || !prset->put_enum_str) { + recGblRecSupError(status, paddr, "dbPut(putStringEnum)", "put_enum_str"); + return status; + } + + status = prset->put_enum_str(paddr, pfrom); + if (!status) + return status; + + if (!prset->get_enum_strs) { + recGblRecSupError(status, paddr, "dbPut(putStringEnum)", "get_enum_strs"); + return status; + } + + status = prset->get_enum_strs(paddr, &enumStrs); + if (!status) { + epicsEnum16 val; + char *end; + + status = epicsParseUInt16(pfrom, &val, 10, &end); + if (!status && val < enumStrs.no_str) { + *pfield = val; + return 0; + } + status = S_db_badChoice; + } + + recGblRecordError(status, paddr->precord, pfrom); + return status; +} + +static long putStringMenu(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + epicsEnum16 *pfield = paddr->pfield; + dbMenu *pdbMenu; + char **pchoices, *pchoice; + + if (no_elements != 1) { + recGblDbaddrError(S_db_onlyOne, paddr, "dbPut(putStringMenu)"); + return S_db_onlyOne; + } + + if (pdbFldDes && + (pdbMenu = pdbFldDes->ftPvt) && + (pchoices = pdbMenu->papChoiceValue)) { + int i, nChoice = pdbMenu->nChoice; + epicsEnum16 val; + + for (i = 0; i < nChoice; i++) { + pchoice = pchoices[i]; + if (!pchoice) + continue; + if (strcmp(pchoice, pfrom) == 0) { + *pfield = i; + return 0; + } + } + + if (!epicsParseUInt16(pfrom, &val, 10, NULL) + && val < nChoice) { + *pfield = val; + return 0; + } + } + recGblDbaddrError(S_db_badChoice, paddr, "dbPut(putStringMenu)"); + return S_db_badChoice; +} + +static long putStringDevice(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + dbDeviceMenu *pdbDeviceMenu = pdbFldDes->ftPvt; + epicsEnum16 *pfield = paddr->pfield; + char **pchoices, *pchoice; + + if (no_elements != 1) { + recGblDbaddrError(S_db_onlyOne, paddr, "dbPut(putStringDevice)"); + return S_db_onlyOne; + } + + if (pdbFldDes && + (pdbDeviceMenu = pdbFldDes->ftPvt) && + (pchoices = pdbDeviceMenu->papChoice)) { + int i, nChoice = pdbDeviceMenu->nChoice; + epicsEnum16 val; + + for (i = 0; i < nChoice; i++) { + pchoice = pchoices[i]; + if (!pchoice) + continue; + if (strcmp(pchoice, pfrom) == 0) { + *pfield = i; + return 0; + } + } + + if (!epicsParseUInt16(pfrom, &val, 10, NULL) && val < nChoice) { + *pfield = val; + return 0; + } + } + recGblDbaddrError(S_db_badChoice, paddr, "dbPut(putStringDevice)"); + return S_db_badChoice; +} + + +static long putCharString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const char *psrc = (const char *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + if (nRequest==1 && offset==0) { + cvtCharToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtCharToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putCharChar PUT_NOCONVERT(char, char) +static long putCharUchar PUT_NOCONVERT(char, epicsUInt8) +static long putCharShort PUT(char, epicsInt16) +static long putCharUshort PUT(char, epicsUInt16) +static long putCharLong PUT(char, epicsInt32) +static long putCharUlong PUT(char, epicsUInt32) +static long putCharInt64 PUT(char, epicsInt64) +static long putCharUInt64 PUT(char, epicsUInt64) +static long putCharFloat PUT(char, epicsFloat32) +static long putCharDouble PUT(char, epicsFloat64) +static long putCharEnum PUT(char, epicsEnum16) + +static long putUcharString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsUInt8 *psrc = (const epicsUInt8 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtUcharToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtUcharToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putUcharChar PUT_NOCONVERT(epicsUInt8, char) +static long putUcharUchar PUT_NOCONVERT(epicsUInt8, epicsUInt8) +static long putUcharShort PUT(epicsUInt8, epicsInt16) +static long putUcharUshort PUT(epicsUInt8, epicsUInt16) +static long putUcharLong PUT(epicsUInt8, epicsInt32) +static long putUcharUlong PUT(epicsUInt8, epicsUInt32) +static long putUcharInt64 PUT(epicsUInt8, epicsInt64) +static long putUcharUInt64 PUT(epicsUInt8, epicsUInt64) +static long putUcharFloat PUT(epicsUInt8, epicsFloat32) +static long putUcharDouble PUT(epicsUInt8, epicsFloat64) +static long putUcharEnum PUT(epicsUInt8, epicsEnum16) + +static long putShortString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsInt16 *psrc = (const epicsInt16 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtShortToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtShortToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putShortChar PUT(epicsInt16, char) +static long putShortUchar PUT(epicsInt16, epicsUInt8) +static long putShortShort PUT_NOCONVERT(epicsInt16, epicsInt16) +static long putShortUshort PUT_NOCONVERT(epicsInt16, epicsUInt16) +static long putShortLong PUT(epicsInt16, epicsInt32) +static long putShortUlong PUT(epicsInt16, epicsUInt32) +static long putShortInt64 PUT(epicsInt16, epicsInt64) +static long putShortUInt64 PUT(epicsInt16, epicsUInt64) +static long putShortFloat PUT(epicsInt16, epicsFloat32) +static long putShortDouble PUT(epicsInt16, epicsFloat64) +static long putShortEnum PUT(epicsInt16, epicsEnum16) + +static long putUshortString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsUInt16 *psrc = (const epicsUInt16 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtUshortToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtUshortToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putUshortChar PUT(epicsUInt16, char) +static long putUshortUchar PUT(epicsUInt16, epicsUInt8) +static long putUshortShort PUT_NOCONVERT(epicsUInt16, epicsInt16) +static long putUshortUshort PUT_NOCONVERT(epicsUInt16, epicsUInt16) +static long putUshortLong PUT(epicsUInt16, epicsInt32) +static long putUshortUlong PUT(epicsUInt16, epicsUInt32) +static long putUshortInt64 PUT(epicsUInt16, epicsInt64) +static long putUshortUInt64 PUT(epicsUInt16, epicsUInt64) +static long putUshortFloat PUT(epicsUInt16, epicsFloat32) +static long putUshortDouble PUT(epicsUInt16, epicsFloat64) +static long putUshortEnum PUT(epicsUInt16, epicsEnum16) + +static long putLongString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsInt32 *psrc = (const epicsInt32 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtLongToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtLongToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putLongChar PUT(epicsInt32, char) +static long putLongUchar PUT(epicsInt32, epicsUInt8) +static long putLongShort PUT(epicsInt32, epicsInt16) +static long putLongUshort PUT(epicsInt32, epicsUInt16) +static long putLongLong PUT_NOCONVERT(epicsInt32, epicsInt32) +static long putLongUlong PUT_NOCONVERT(epicsInt32, epicsUInt32) +static long putLongInt64 PUT(epicsInt32, epicsInt64) +static long putLongUInt64 PUT(epicsInt32, epicsUInt64) +static long putLongFloat PUT(epicsInt32, epicsFloat32) +static long putLongDouble PUT(epicsInt32, epicsFloat64) +static long putLongEnum PUT(epicsInt32, epicsEnum16) + +static long putUlongString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsUInt32 *psrc = (const epicsUInt32 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtUlongToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtUlongToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putUlongChar PUT(epicsUInt32, char) +static long putUlongUchar PUT(epicsUInt32, epicsUInt8) +static long putUlongShort PUT(epicsUInt32, epicsInt16) +static long putUlongUshort PUT(epicsUInt32, epicsUInt16) +static long putUlongLong PUT_NOCONVERT(epicsUInt32, epicsInt32) +static long putUlongUlong PUT_NOCONVERT(epicsUInt32, epicsUInt32) +static long putUlongInt64 PUT(epicsUInt32, epicsInt64) +static long putUlongUInt64 PUT(epicsUInt32, epicsUInt64) +static long putUlongFloat PUT(epicsUInt32, epicsFloat32) +static long putUlongDouble PUT(epicsUInt32, epicsFloat64) +static long putUlongEnum PUT(epicsUInt32, epicsEnum16) + +static long putInt64String(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsInt64 *psrc = (const epicsInt64 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size=paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtInt64ToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtInt64ToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putInt64Char PUT(epicsInt64, char) +static long putInt64Uchar PUT(epicsInt64, epicsUInt8) +static long putInt64Short PUT(epicsInt64, epicsInt16) +static long putInt64Ushort PUT(epicsInt64, epicsUInt16) +static long putInt64Long PUT(epicsInt64, epicsInt32) +static long putInt64Ulong PUT(epicsInt64, epicsUInt32) +static long putInt64Int64 PUT_NOCONVERT(epicsInt64, epicsInt64) +static long putInt64UInt64 PUT_NOCONVERT(epicsInt64, epicsUInt64) +static long putInt64Float PUT(epicsInt64, epicsFloat32) +static long putInt64Double PUT(epicsInt64, epicsFloat64) +static long putInt64Enum PUT(epicsInt64, epicsEnum16) + +static long putUInt64String(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsUInt64 *psrc = (const epicsUInt64 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size=paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtUlongToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtUlongToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putUInt64Char PUT(epicsUInt64, char) +static long putUInt64Uchar PUT(epicsUInt64, epicsUInt8) +static long putUInt64Short PUT(epicsUInt64, epicsInt16) +static long putUInt64Ushort PUT(epicsUInt64, epicsUInt16) +static long putUInt64Long PUT(epicsUInt64, epicsInt32) +static long putUInt64Ulong PUT(epicsUInt64, epicsUInt32) +static long putUInt64Int64 PUT_NOCONVERT(epicsUInt64, epicsInt64) +static long putUInt64UInt64 PUT_NOCONVERT(epicsUInt64, epicsUInt64) +static long putUInt64Float PUT(epicsUInt64, epicsFloat32) +static long putUInt64Double PUT(epicsUInt64, epicsFloat64) +static long putUInt64Enum PUT(epicsUInt64, epicsEnum16) + +static long putFloatString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsFloat32 *psrc = (const epicsFloat32 *) pfrom; + char *pdst = (char *) paddr->pfield; + long status = 0; + long precision = 6; + rset *prset = 0; + short size = paddr->field_size; + + if (paddr) + prset = dbGetRset(paddr); + if (prset && prset->get_precision) + status = prset->get_precision(paddr, &precision); + if (nRequest==1 && offset==0) { + cvtFloatToString(*psrc, pdst, precision); + return(status); + } + pdst += (size*offset); + while (nRequest--) { + cvtFloatToString(*psrc, pdst, precision); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return(status); +} + +static long putFloatChar PUT(epicsFloat32, char) +static long putFloatUchar PUT(epicsFloat32, epicsUInt8) +static long putFloatShort PUT(epicsFloat32, epicsInt16) +static long putFloatUshort PUT(epicsFloat32, epicsUInt16) +static long putFloatLong PUT(epicsFloat32, epicsInt32) +static long putFloatUlong PUT(epicsFloat32, epicsUInt32) +static long putFloatInt64 PUT(epicsFloat32, epicsInt64) +static long putFloatUInt64 PUT(epicsFloat32, epicsUInt64) +static long putFloatFloat PUT_NOCONVERT(epicsFloat32, epicsFloat32) +static long putFloatDouble PUT(epicsFloat32, epicsFloat64) +static long putFloatEnum PUT(epicsFloat32, epicsEnum16) + +static long putDoubleString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsFloat64 *psrc = (const epicsFloat64 *) pfrom; + char *pdst = (char *) paddr->pfield; + long status = 0; + long precision = 6; + rset *prset = 0; + short size = paddr->field_size; + + if (paddr) + prset = dbGetRset(paddr); + if (prset && prset->get_precision) + status = prset->get_precision(paddr, &precision); + if (nRequest==1 && offset==0) { + cvtDoubleToString(*psrc, pdst, precision); + return status; + } + pdst += (size*offset); + while (nRequest--) { + cvtDoubleToString(*psrc, pdst, precision); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return status; +} + +static long putDoubleChar PUT(epicsFloat64, char) +static long putDoubleUchar PUT(epicsFloat64, epicsUInt8) +static long putDoubleShort PUT(epicsFloat64, epicsInt16) +static long putDoubleUshort PUT(epicsFloat64, epicsUInt16) +static long putDoubleLong PUT(epicsFloat64, epicsInt32) +static long putDoubleUlong PUT(epicsFloat64, epicsUInt32) +static long putDoubleInt64 PUT(epicsFloat64, epicsInt64) +static long putDoubleUInt64 PUT(epicsFloat64, epicsUInt64) + +static long putDoubleFloat(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsFloat64 *psrc = (const epicsFloat64 *) pfrom; + epicsFloat32 *pdst = (epicsFloat32 *) paddr->pfield; + + if (nRequest==1 && offset==0) { + *pdst = epicsConvertDoubleToFloat(*psrc); + return 0; + } + pdst += offset; + while (nRequest--) { + *pdst++ = epicsConvertDoubleToFloat(*psrc++); + if (++offset == no_elements) + pdst = (epicsFloat32 *) paddr->pfield; + } + return 0; +} + +static long putDoubleDouble PUT_NOCONVERT(epicsFloat64, epicsFloat64) +static long putDoubleEnum PUT(epicsFloat64, epicsEnum16) + +static long putEnumString(dbAddr *paddr, + const void *pfrom, long nRequest, long no_elements, long offset) +{ + const epicsEnum16 *psrc = (const epicsEnum16 *) pfrom; + char *pdst = (char *) paddr->pfield; + short size = paddr->field_size; + + + if (nRequest==1 && offset==0) { + cvtUshortToString(*psrc, pdst); + return 0; + } + pdst += (size*offset); + while (nRequest--) { + cvtUshortToString(*psrc, pdst); + psrc++; + if (++offset == no_elements) + pdst = (char *) paddr->pfield; + else + pdst += size; + } + return 0; +} + +static long putEnumChar PUT(epicsEnum16, char) +static long putEnumUchar PUT(epicsEnum16, epicsUInt8) +static long putEnumShort PUT(epicsEnum16, epicsInt16) +static long putEnumUshort PUT(epicsEnum16, epicsUInt16) +static long putEnumLong PUT(epicsEnum16, epicsInt32) +static long putEnumUlong PUT(epicsEnum16, epicsUInt32) +static long putEnumInt64 PUT(epicsEnum16, epicsInt64) +static long putEnumUInt64 PUT(epicsEnum16, epicsUInt64) +static long putEnumFloat PUT(epicsEnum16, epicsFloat32) +static long putEnumDouble PUT(epicsEnum16, epicsFloat64) +static long putEnumEnum PUT_NOCONVERT(epicsEnum16, epicsEnum16) + +/* This is the table of routines for converting database fields */ +/* the rows represent the field type of the database field */ +/* the columns represent the types of the buffer in which they are placed */ + +/* buffer types are******************************************************** + DBR_STRING, DBR_CHR, DBR_UCHAR, DBR_SHORT, DBR_USHORT, + DBR_LONG, DBR_ULONG, DBR_INT64, DBR_UINT64, + DBR_FLOAT, DBR_DOUBLE, DBR_ENUM + ***************************************************************************/ + +epicsShareDef GETCONVERTFUNC dbGetConvertRoutine[DBF_DEVICE+1][DBR_ENUM+1] = { + +/* source is a DBF_STRING */ +{getStringString, getStringChar, getStringUchar, getStringShort, getStringUshort, + getStringLong, getStringUlong, getStringInt64, getStringUInt64, + getStringFloat, getStringDouble, getStringUshort}, +/* source is a DBF_CHAR */ +{getCharString, getCharChar, getCharUchar, getCharShort, getCharUshort, + getCharLong, getCharUlong, getCharInt64, getCharUInt64, + getCharFloat, getCharDouble, getCharEnum}, +/* source is a DBF_UCHAR */ +{getUcharString, getUcharChar, getUcharUchar, getUcharShort, getUcharUshort, + getUcharLong, getUcharUlong, getUcharInt64, getUcharUInt64, + getUcharFloat, getUcharDouble, getUcharEnum}, +/* source is a DBF_SHORT */ +{getShortString, getShortChar, getShortUchar, getShortShort, getShortUshort, + getShortLong, getShortUlong, getShortInt64, getShortUInt64, + getShortFloat, getShortDouble, getShortEnum}, +/* source is a DBF_USHORT */ +{getUshortString, getUshortChar, getUshortUchar, getUshortShort, getUshortUshort, + getUshortLong, getUshortUlong, getUshortInt64, getUshortUInt64, + getUshortFloat, getUshortDouble, getUshortEnum}, +/* source is a DBF_LONG */ +{getLongString, getLongChar, getLongUchar, getLongShort, getLongUshort, + getLongLong, getLongUlong, getLongInt64, getLongUInt64, + getLongFloat, getLongDouble, getLongEnum}, +/* source is a DBF_ULONG */ +{getUlongString, getUlongChar, getUlongUchar, getUlongShort, getUlongUshort, + getUlongLong, getUlongUlong, getUlongInt64, getUlongUInt64, + getUlongFloat, getUlongDouble, getUlongEnum}, +/* source is a DBF_INT64 */ +{getInt64String, getInt64Char, getInt64Uchar, getInt64Short, getInt64Ushort, + getInt64Long, getInt64Ulong, getInt64Int64, getInt64UInt64, + getInt64Float, getInt64Double, getInt64Enum}, +/* source is a DBF_UINT64 */ +{getUInt64String, getUInt64Char, getUInt64Uchar, getUInt64Short, getUInt64Ushort, + getUInt64Long, getUInt64Ulong, getUInt64Int64, getUInt64UInt64, + getUInt64Float, getUInt64Double, getUInt64Enum}, +/* source is a DBF_FLOAT */ +{getFloatString, getFloatChar, getFloatUchar, getFloatShort, getFloatUshort, + getFloatLong, getFloatUlong, getFloatInt64, getFloatUInt64, + getFloatFloat, getFloatDouble, getFloatEnum}, +/* source is a DBF_DOUBLE */ +{getDoubleString, getDoubleChar, getDoubleUchar, getDoubleShort, getDoubleUshort, + getDoubleLong, getDoubleUlong, getDoubleInt64, getDoubleUInt64, + getDoubleFloat, getDoubleDouble, getDoubleEnum}, +/* source is a DBF_ENUM */ +{getEnumString, getEnumChar, getEnumUchar, getEnumShort, getEnumUshort, + getEnumLong, getEnumUlong, getEnumInt64, getEnumUInt64, + getEnumFloat, getEnumDouble, getEnumEnum}, +/* source is a DBF_MENU */ +{getMenuString, getEnumChar, getEnumUchar, getEnumShort, getEnumUshort, + getEnumLong, getEnumUlong, getEnumInt64, getEnumUInt64, + getEnumFloat, getEnumDouble, getEnumEnum}, +/* source is a DBF_DEVICE */ +{getDeviceString, getEnumChar, getEnumUchar, getEnumShort, getEnumUshort, + getEnumLong, getEnumUlong, getEnumInt64, getEnumUInt64, + getEnumFloat, getEnumDouble, getEnumEnum}, +}; + +/* This is the table of routines for converting database fields */ +/* the rows represent the buffer types */ +/* the columns represent the field types */ + +/* field types are******************************************************** + DBF_STRING, DBF_CHAR, DBF_UCHAR, DBF_SHORT, DBF_USHORT, + DBF_LONG, DBF_ULONG, DBF_INT64, DBF_UINT64, + DBF_FLOAT, DBF_DOUBLE, DBF_ENUM + DBF_MENU, DBF_DEVICE + ***************************************************************************/ + +epicsShareDef PUTCONVERTFUNC dbPutConvertRoutine[DBR_ENUM+1][DBF_DEVICE+1] = { +/* source is a DBR_STRING */ +{putStringString, putStringChar, putStringUchar, putStringShort, putStringUshort, + putStringLong, putStringUlong, putStringInt64, putStringUInt64, + putStringFloat, putStringDouble, putStringEnum, + putStringMenu, putStringDevice}, +/* source is a DBR_CHAR */ +{putCharString, putCharChar, putCharUchar, putCharShort, putCharUshort, + putCharLong, putCharUlong, putCharInt64, putCharUInt64, + putCharFloat, putCharDouble, putCharEnum, + putCharEnum, putCharEnum}, +/* source is a DBR_UCHAR */ +{putUcharString, putUcharChar, putUcharUchar, putUcharShort, putUcharUshort, + putUcharLong, putUcharUlong, putUcharInt64, putUcharUInt64, + putUcharFloat, putUcharDouble, putUcharEnum, + putUcharEnum, putUcharEnum}, +/* source is a DBR_SHORT */ +{putShortString, putShortChar, putShortUchar, putShortShort, putShortUshort, + putShortLong, putShortUlong, putShortInt64, putShortUInt64, + putShortFloat, putShortDouble, putShortEnum, + putShortEnum, putShortEnum}, +/* source is a DBR_USHORT */ +{putUshortString, putUshortChar, putUshortUchar, putUshortShort, putUshortUshort, + putUshortLong, putUshortUlong, putUshortInt64, putUshortUInt64, + putUshortFloat, putUshortDouble, putUshortEnum, + putUshortEnum, putUshortEnum}, +/* source is a DBR_LONG */ +{putLongString, putLongChar, putLongUchar, putLongShort, putLongUshort, + putLongLong, putLongUlong, putLongInt64, putLongUInt64, + putLongFloat, putLongDouble, putLongEnum, + putLongEnum, putLongEnum}, +/* source is a DBR_ULONG */ +{putUlongString, putUlongChar, putUlongUchar, putUlongShort, putUlongUshort, + putUlongLong, putUlongUlong, putUlongInt64, putUlongUInt64, + putUlongFloat, putUlongDouble, putUlongEnum, + putUlongEnum, putUlongEnum}, +/* source is a DBR_INT64 */ +{putInt64String, putInt64Char, putInt64Uchar, putInt64Short, putInt64Ushort, + putInt64Long, putInt64Ulong, putInt64Int64, putInt64UInt64, + putInt64Float, putInt64Double, putInt64Enum, + putInt64Enum, putInt64Enum}, +/* source is a DBR_UINT64 */ +{putUInt64String, putUInt64Char, putUInt64Uchar, putUInt64Short, putUInt64Ushort, + putUInt64Long, putUInt64Ulong, putUInt64Int64, putUInt64UInt64, + putUInt64Float, putUInt64Double, putUInt64Enum, + putUInt64Enum, putUInt64Enum}, +/* source is a DBR_FLOAT */ +{putFloatString, putFloatChar, putFloatUchar, putFloatShort, putFloatUshort, + putFloatLong, putFloatUlong, putFloatInt64, putFloatUInt64, + putFloatFloat, putFloatDouble, putFloatEnum, + putFloatEnum, putFloatEnum}, +/* source is a DBR_DOUBLE */ +{putDoubleString, putDoubleChar, putDoubleUchar, putDoubleShort, putDoubleUshort, + putDoubleLong, putDoubleUlong, putDoubleInt64, putDoubleUInt64, + putDoubleFloat, putDoubleDouble, putDoubleEnum, + putDoubleEnum, putDoubleEnum}, +/* source is a DBR_ENUM */ +{putEnumString, putEnumChar, putEnumUchar, putEnumShort, putEnumUshort, + putEnumLong, putEnumUlong, putEnumInt64, putEnumUInt64, + putEnumFloat, putEnumDouble, putEnumEnum, + putEnumEnum, putEnumEnum} +}; diff --git a/modules/database/src/ioc/db/dbConvert.h b/modules/database/src/ioc/db/dbConvert.h new file mode 100644 index 000000000..afd13c7c2 --- /dev/null +++ b/modules/database/src/ioc/db/dbConvert.h @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConvert.h */ + +#ifndef INCdbConverth +#define INCdbConverth + +#include "dbFldTypes.h" +#include "dbAddr.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef long (*GETCONVERTFUNC)(const DBADDR *paddr, void *pbuffer, + long nRequest, long no_elements, long offset); +typedef long (*PUTCONVERTFUNC)(DBADDR *paddr, const void *pbuffer, + long nRequest, long no_elements, long offset); + +epicsShareExtern GETCONVERTFUNC dbGetConvertRoutine[DBF_DEVICE+1][DBR_ENUM+1]; +epicsShareExtern PUTCONVERTFUNC dbPutConvertRoutine[DBR_ENUM+1][DBF_DEVICE+1]; + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbConverth*/ diff --git a/modules/database/src/ioc/db/dbConvertFast.h b/modules/database/src/ioc/db/dbConvertFast.h new file mode 100644 index 000000000..cd9f4f963 --- /dev/null +++ b/modules/database/src/ioc/db/dbConvertFast.h @@ -0,0 +1,29 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConvertFast.h */ + +#ifndef INCdbConvertFasth +#define INCdbConvertFasth + +#include "dbFldTypes.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareExtern long (*dbFastGetConvertRoutine[DBF_DEVICE+1][DBR_ENUM+1])(); +epicsShareExtern long (*dbFastPutConvertRoutine[DBR_ENUM+1][DBF_DEVICE+1])(); + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbConvertFasth*/ diff --git a/modules/database/src/ioc/db/dbConvertJSON.c b/modules/database/src/ioc/db/dbConvertJSON.c new file mode 100644 index 000000000..eb697eeda --- /dev/null +++ b/modules/database/src/ioc/db/dbConvertJSON.c @@ -0,0 +1,252 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConvertJSON.c */ + +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "yajl_alloc.h" +#include "yajl_parse.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbConvertFast.h" +#include "dbConvertJSON.h" + +typedef long (*FASTCONVERT)(); + +typedef struct parseContext { + int depth; + short dbrType; + short dbrSize; + char *pdest; + int elems; +} parseContext; + +static int dbcj_null(void *ctx) { + return 0; /* Illegal */ +} + +static int dbcj_boolean(void *ctx, int val) { + return 0; /* Illegal */ +} + +static int dbcj_integer(void *ctx, long long num) { + parseContext *parser = (parseContext *) ctx; + epicsInt64 val64 = num; + FASTCONVERT conv = dbFastPutConvertRoutine[DBF_INT64][parser->dbrType]; + + if (parser->elems > 0) { + conv(&val64, parser->pdest, NULL); + parser->pdest += parser->dbrSize; + parser->elems--; + } + return 1; +} + +static int dblsj_integer(void *ctx, long long num) { + return 0; /* Illegal */ +} + +static int dbcj_double(void *ctx, double num) { + parseContext *parser = (parseContext *) ctx; + FASTCONVERT conv = dbFastPutConvertRoutine[DBF_DOUBLE][parser->dbrType]; + + if (parser->elems > 0) { + conv(&num, parser->pdest, NULL); + parser->pdest += parser->dbrSize; + parser->elems--; + } + return 1; +} + +static int dblsj_double(void *ctx, double num) { + return 0; /* Illegal */ +} + +static int dbcj_string(void *ctx, const unsigned char *val, size_t len) { + parseContext *parser = (parseContext *) ctx; + char *pdest = parser->pdest; + + /* Not attempting to handle char-array fields here, they need more + * metadata about the field than we have available at the moment. + */ + if (parser->dbrType != DBF_STRING) { + errlogPrintf("dbConvertJSON: String provided, numeric value(s) expected\n"); + return 0; /* Illegal */ + } + + if (parser->elems > 0) { + if (len > parser->dbrSize - 1) + len = parser->dbrSize - 1; + strncpy(pdest, (const char *) val, len); + pdest[len] = 0; + parser->pdest += parser->dbrSize; + parser->elems--; + } + return 1; +} + +static int dblsj_string(void *ctx, const unsigned char *val, size_t len) { + parseContext *parser = (parseContext *) ctx; + char *pdest = parser->pdest; + + if (parser->dbrType != DBF_STRING) { + errlogPrintf("dbConvertJSON: dblsj_string dbrType error\n"); + return 0; /* Illegal */ + } + + if (parser->elems > 0) { + if (len > parser->dbrSize - 1) + len = parser->dbrSize - 1; + strncpy(pdest, (const char *) val, len); + pdest[len] = 0; + parser->pdest = pdest + len; + parser->elems = 0; + } + return 1; +} + +static int dbcj_start_map(void *ctx) { + errlogPrintf("dbConvertJSON: Map type not supported\n"); + return 0; /* Illegal */ +} + +static int dbcj_map_key(void *ctx, const unsigned char *key, size_t len) { + return 0; /* Illegal */ +} + +static int dbcj_end_map(void *ctx) { + return 0; /* Illegal */ +} + +static int dbcj_start_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + + if (++parser->depth > 1) + errlogPrintf("dbConvertJSON: Embedded arrays not supported\n"); + + return (parser->depth == 1); +} + +static int dbcj_end_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + + parser->depth--; + return (parser->depth == 0); +} + + +static yajl_callbacks dbcj_callbacks = { + dbcj_null, dbcj_boolean, dbcj_integer, dbcj_double, NULL, dbcj_string, + dbcj_start_map, dbcj_map_key, dbcj_end_map, + dbcj_start_array, dbcj_end_array +}; + +long dbPutConvertJSON(const char *json, short dbrType, + void *pdest, long *pnRequest) +{ + parseContext context, *parser = &context; + yajl_alloc_funcs dbcj_alloc; + yajl_handle yh; + yajl_status ys; + size_t jlen = strlen(json); + long status; + + parser->depth = 0; + parser->dbrType = dbrType; + parser->dbrSize = dbValueSize(dbrType); + parser->pdest = pdest; + parser->elems = *pnRequest; + + yajl_set_default_alloc_funcs(&dbcj_alloc); + yh = yajl_alloc(&dbcj_callbacks, &dbcj_alloc, parser); + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, jlen); + if (ys == yajl_status_ok) + ys = yajl_complete_parse(yh); + + switch (ys) { + case yajl_status_ok: + *pnRequest -= parser->elems; + status = 0; + break; + + case yajl_status_error: { + unsigned char *err = yajl_get_error(yh, 1, + (const unsigned char *) json, jlen); + fprintf(stderr, "dbConvertJSON: %s\n", err); + yajl_free_error(yh, err); + } + /* fall through */ + default: + status = S_db_badField; + } + + yajl_free(yh); + return status; +} + + +static yajl_callbacks dblsj_callbacks = { + dbcj_null, dbcj_boolean, dblsj_integer, dblsj_double, NULL, dblsj_string, + dbcj_start_map, dbcj_map_key, dbcj_end_map, + dbcj_start_array, dbcj_end_array +}; + +long dbLSConvertJSON(const char *json, char *pdest, epicsUInt32 size, + epicsUInt32 *plen) +{ + parseContext context, *parser = &context; + yajl_alloc_funcs dbcj_alloc; + yajl_handle yh; + yajl_status ys; + size_t jlen = strlen(json); + long status; + + if (!size) { + *plen = 0; + return 0; + } + + parser->depth = 0; + parser->dbrType = DBF_STRING; + parser->dbrSize = size; + parser->pdest = pdest; + parser->elems = 1; + + yajl_set_default_alloc_funcs(&dbcj_alloc); + yh = yajl_alloc(&dblsj_callbacks, &dbcj_alloc, parser); + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, jlen); + + switch (ys) { + case yajl_status_ok: + *plen = (char *) parser->pdest - pdest + 1; + status = 0; + break; + + case yajl_status_error: { + unsigned char *err = yajl_get_error(yh, 1, + (const unsigned char *) json, jlen); + fprintf(stderr, "dbLoadLS_JSON: %s\n", err); + yajl_free_error(yh, err); + } + /* fall through */ + default: + status = S_db_badField; + } + + yajl_free(yh); + return status; +} diff --git a/modules/database/src/ioc/db/dbConvertJSON.h b/modules/database/src/ioc/db/dbConvertJSON.h new file mode 100644 index 000000000..7dd8e4aed --- /dev/null +++ b/modules/database/src/ioc/db/dbConvertJSON.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbConvertJSON.h */ + +#ifndef INC_dbConvertJSON_H +#define INC_dbConvertJSON_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This name should probably be changed to inclue "array" */ +epicsShareFunc long dbPutConvertJSON(const char *json, short dbrType, + void *pdest, long *psize); +epicsShareFunc long dbLSConvertJSON(const char *json, char *pdest, + epicsUInt32 size, epicsUInt32 *plen); +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbConvertJSON_H */ + diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c new file mode 100644 index 000000000..105799876 --- /dev/null +++ b/modules/database/src/ioc/db/dbDbLink.c @@ -0,0 +1,358 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbDbLink.c + * + * Original Authors: Bob Dalesio, Marty Kraimer + * Current Author: Andrew Johnson + */ + +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "cantProceed.h" +#include "cvtFast.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsTime.h" +#include "errlog.h" + +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbBkpt.h" +#include "dbCommon.h" +#include "dbConvertFast.h" +#include "dbConvert.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbLink.h" +#include "dbLockPvt.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "dbStaticLib.h" +#include "devSup.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + +/***************************** Database Links *****************************/ + +/* Forward definition */ +static lset dbDb_lset; + +long dbDbInitLink(struct link *plink, short dbfType) +{ + DBADDR dbaddr; + long status; + DBADDR *pdbAddr; + + status = dbNameToAddr(plink->value.pv_link.pvname, &dbaddr); + if (status) + return status; + + plink->lset = &dbDb_lset; + plink->type = DB_LINK; + pdbAddr = dbCalloc(1, sizeof(struct dbAddr)); + *pdbAddr = dbaddr; /* structure copy */ + plink->value.pv_link.pvt = pdbAddr; + ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode); + /* merging into the same lockset is deferred to the caller. + * cf. initPVLinks() + */ + dbLockSetMerge(NULL, plink->precord, dbaddr.precord); + assert(plink->precord->lset->plockSet == dbaddr.precord->lset->plockSet); + return 0; +} + +void dbDbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, + DBADDR *ptarget) +{ + plink->lset = &dbDb_lset; + plink->type = DB_LINK; + plink->value.pv_link.pvt = ptarget; + ellAdd(&ptarget->precord->bklnk, &plink->value.pv_link.backlinknode); + + /* target record is already locked in dbPutFieldLink() */ + dbLockSetMerge(locker, plink->precord, ptarget->precord); +} + +static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink) +{ + DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt; + + plink->type = PV_LINK; + + /* locker is NULL when an isolated IOC is closing its links */ + if (locker) { + plink->value.pv_link.pvt = 0; + plink->value.pv_link.getCvt = 0; + plink->value.pv_link.pvlMask = 0; + plink->value.pv_link.lastGetdbrType = 0; + ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode); + dbLockSetSplit(locker, plink->precord, pdbAddr->precord); + } + free(pdbAddr); +} + +static int dbDbIsConnected(const struct link *plink) +{ + return TRUE; +} + +static int dbDbGetDBFtype(const struct link *plink) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + + return paddr->field_type; +} + +static long dbDbGetElements(const struct link *plink, long *nelements) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + + *nelements = paddr->no_elements; + return 0; +} + +static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + struct pv_link *ppv_link = &plink->value.pv_link; + DBADDR *paddr = ppv_link->pvt; + dbCommon *precord = plink->precord; + long status; + + /* scan passive records if link is process passive */ + if (ppv_link->pvlMask & pvlOptPP) { + unsigned char pact = precord->pact; + + precord->pact = TRUE; + status = dbScanPassive(precord, paddr->precord); + precord->pact = pact; + if (status) + return status; + } + + if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) { + status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr); + } else { + unsigned short dbfType = paddr->field_type; + + if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE) + return S_db_badDbrtype; + + if (paddr->no_elements == 1 && (!pnRequest || *pnRequest == 1) + && paddr->special != SPC_DBADDR + && paddr->special != SPC_ATTRIBUTE) { + ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType]; + status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr); + } else { + ppv_link->getCvt = NULL; + status = dbGet(paddr, dbrType, pbuffer, NULL, pnRequest, NULL); + } + ppv_link->lastGetdbrType = dbrType; + } + + if (!status && precord != paddr->precord) + recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode, + plink->precord, paddr->precord->stat, paddr->precord->sevr); + return status; +} + +static long dbDbGetControlLimits(const struct link *plink, double *low, + double *high) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + struct buffer { + DBRctrlDouble + double value; + } buffer; + long options = DBR_CTRL_DOUBLE; + long number_elements = 0; + long status = dbGet(paddr, DBR_DOUBLE, &buffer, &options, &number_elements, + NULL); + + if (status) + return status; + + *low = buffer.lower_ctrl_limit; + *high = buffer.upper_ctrl_limit; + return 0; +} + +static long dbDbGetGraphicLimits(const struct link *plink, double *low, + double *high) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + struct buffer { + DBRgrDouble + double value; + } buffer; + long options = DBR_GR_DOUBLE; + long number_elements = 0; + long status = dbGet(paddr, DBR_DOUBLE, &buffer, &options, &number_elements, + NULL); + + if (status) + return status; + + *low = buffer.lower_disp_limit; + *high = buffer.upper_disp_limit; + return 0; +} + +static long dbDbGetAlarmLimits(const struct link *plink, double *lolo, + double *low, double *high, double *hihi) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + struct buffer { + DBRalDouble + double value; + } buffer; + long options = DBR_AL_DOUBLE; + long number_elements = 0; + long status = dbGet(paddr, DBR_DOUBLE, &buffer, &options, &number_elements, + 0); + + if (status) + return status; + + *lolo = buffer.lower_alarm_limit; + *low = buffer.lower_warning_limit; + *high = buffer.upper_warning_limit; + *hihi = buffer.upper_alarm_limit; + return 0; +} + +static long dbDbGetPrecision(const struct link *plink, short *precision) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + struct buffer { + DBRprecision + double value; + } buffer; + long options = DBR_PRECISION; + long number_elements = 0; + long status = dbGet(paddr, DBR_DOUBLE, &buffer, &options, &number_elements, + 0); + + if (status) + return status; + + *precision = (short) buffer.precision.dp; + return 0; +} + +static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + struct buffer { + DBRunits + double value; + } buffer; + long options = DBR_UNITS; + long number_elements = 0; + long status = dbGet(paddr, DBR_DOUBLE, &buffer, &options, &number_elements, + 0); + + if (status) + return status; + + strncpy(units, buffer.units, unitsSize); + return 0; +} + +static long dbDbGetAlarm(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + + if (status) + *status = paddr->precord->stat; + if (severity) + *severity = paddr->precord->sevr; + return 0; +} + +static long dbDbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp) +{ + DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt; + + *pstamp = paddr->precord->time; + return 0; +} + +static long dbDbPutValue(struct link *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + struct pv_link *ppv_link = &plink->value.pv_link; + struct dbCommon *psrce = plink->precord; + DBADDR *paddr = (DBADDR *) ppv_link->pvt; + dbCommon *pdest = paddr->precord; + long status = dbPut(paddr, dbrType, pbuffer, nRequest); + + recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta, + psrce->nsev); + if (status) + return status; + + if (paddr->pfield == (void *) &pdest->proc || + (ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) { + /* if dbPutField caused asyn record to process */ + /* ask for reprocessing*/ + if (pdest->putf) { + pdest->rpro = TRUE; + } else { /* process dest record with source's PACT true */ + unsigned char pact; + + if (psrce && psrce->ppn) + dbNotifyAdd(psrce, pdest); + pact = psrce->pact; + psrce->pact = TRUE; + status = dbProcess(pdest); + psrce->pact = pact; + } + } + return status; +} + +static void dbDbScanFwdLink(struct link *plink) +{ + dbCommon *precord = plink->precord; + dbAddr *paddr = (dbAddr *) plink->value.pv_link.pvt; + + dbScanPassive(precord, paddr->precord); +} + +static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) +{ + return rtn(plink, priv); +} + +static lset dbDb_lset = { + 0, 0, /* not Constant, not Volatile */ + NULL, dbDbRemoveLink, + NULL, NULL, NULL, + dbDbIsConnected, + dbDbGetDBFtype, dbDbGetElements, + dbDbGetValue, + dbDbGetControlLimits, dbDbGetGraphicLimits, dbDbGetAlarmLimits, + dbDbGetPrecision, dbDbGetUnits, + dbDbGetAlarm, dbDbGetTimeStamp, + dbDbPutValue, NULL, + dbDbScanFwdLink, doLocked +}; diff --git a/modules/database/src/ioc/db/dbDbLink.h b/modules/database/src/ioc/db/dbDbLink.h new file mode 100644 index 000000000..c36772004 --- /dev/null +++ b/modules/database/src/ioc/db/dbDbLink.h @@ -0,0 +1,35 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbDbLink.h + * + * Created on: April 3rd, 2016 + * Author: Andrew Johnson + */ + +#ifndef INC_dbDbLink_H +#define INC_dbDbLink_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct link; +struct dbLocker; + +epicsShareFunc long dbDbInitLink(struct link *plink, short dbfType); +epicsShareFunc void dbDbAddLink(struct dbLocker *locker, struct link *plink, + short dbfType, DBADDR *ptarget); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbDbLink_H */ diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c new file mode 100644 index 000000000..9304f99b2 --- /dev/null +++ b/modules/database/src/ioc/db/dbEvent.c @@ -0,0 +1,1182 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbEvent.c */ + +/* + * Author: Jeffrey O. Hill + * + * Ralph Lange + */ + +#include +#include +#include +#include +#include +#include + +#include "cantProceed.h" +#include "dbDefs.h" +#include "epicsAssert.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsThread.h" +#include "errlog.h" +#include "freeList.h" +#include "taskwd.h" + +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "link.h" +#include "special.h" + +#define EVENTSPERQUE 32 +#define EVENTENTRIES 4 /* the number of que entries for each event */ +#define EVENTQUESIZE (EVENTENTRIES * EVENTSPERQUE) +#define EVENTQEMPTY ((struct evSubscrip *)NULL) + +/* + * really a ring buffer + */ +struct event_que { + /* lock writers to the ring buffer only */ + /* readers must never slow up writers */ + epicsMutexId writelock; + db_field_log *valque[EVENTQUESIZE]; + struct evSubscrip *evque[EVENTQUESIZE]; + struct event_que *nextque; /* in case que quota exceeded */ + struct event_user *evUser; /* event user parent struct */ + unsigned short putix; + unsigned short getix; + unsigned short quota; /* the number of assigned entries*/ + unsigned short nDuplicates; /* N events duplicated on this q */ + unsigned short nCanceled; /* the number of canceled entries */ +}; + +struct event_user { + struct event_que firstque; /* the first event que */ + + epicsMutexId lock; + epicsEventId ppendsem; /* Wait while empty */ + epicsEventId pflush_sem; /* wait for flush */ + epicsEventId pexitsem; /* wait for event task to join */ + + EXTRALABORFUNC *extralabor_sub;/* off load to event task */ + void *extralabor_arg;/* parameter to above */ + + epicsThreadId taskid; /* event handler task id */ + struct evSubscrip *pSuicideEvent; /* event that is deleteing itself */ + unsigned queovr; /* event que overflow count */ + unsigned char pendexit; /* exit pend task */ + unsigned char extra_labor; /* if set call extra labor func */ + unsigned char flowCtrlMode; /* replace existing monitor */ + unsigned char extraLaborBusy; + void (*init_func)(); + epicsThreadId init_func_arg; +}; + +/* + * Reliable intertask communication requires copying the current value of the + * channel for later queing so 3 stepper motor steps of 10 each do not turn + * into only 10 or 20 total steps part of the time. + */ + +#define RNGINC(OLD)\ +( (unsigned short) ( (OLD) >= (EVENTQUESIZE-1) ? 0 : (OLD)+1 ) ) + +#define LOCKEVQUE(EV_QUE) epicsMutexMustLock((EV_QUE)->writelock) +#define UNLOCKEVQUE(EV_QUE) epicsMutexUnlock((EV_QUE)->writelock) +#define LOCKREC(RECPTR) epicsMutexMustLock((RECPTR)->mlok) +#define UNLOCKREC(RECPTR) epicsMutexUnlock((RECPTR)->mlok) + +static void *dbevEventUserFreeList; +static void *dbevEventQueueFreeList; +static void *dbevEventSubscriptionFreeList; +static void *dbevFieldLogFreeList; + +static char *EVENT_PEND_NAME = "eventTask"; + +static struct evSubscrip canceledEvent; + +static epicsMutexId stopSync; + +static unsigned short ringSpace ( const struct event_que *pevq ) +{ + if ( pevq->evque[pevq->putix] == EVENTQEMPTY ) { + if ( pevq->getix > pevq->putix ) { + return ( unsigned short ) ( pevq->getix - pevq->putix ); + } + else { + return ( unsigned short ) ( ( EVENTQUESIZE + pevq->getix ) - pevq->putix ); + } + } + return 0; +} + +/* + * db_event_list () + */ +int db_event_list ( const char *pname, unsigned level ) +{ + return dbel ( pname, level ); +} + +/* + * dbel () + */ +int dbel ( const char *pname, unsigned level ) +{ + DBADDR addr; + long status; + struct evSubscrip *pevent; + dbFldDes *pdbFldDes; + + if ( ! pname ) return DB_EVENT_OK; + status = dbNameToAddr ( pname, &addr ); + if ( status != 0 ) { + errMessage ( status, " dbNameToAddr failed" ); + return DB_EVENT_ERROR; + } + + LOCKREC (addr.precord); + + pevent = (struct evSubscrip *) ellFirst ( &addr.precord->mlis ); + + if ( ! pevent ) { + printf ( "\"%s\": No PV event subscriptions ( monitors ).\n", pname ); + UNLOCKREC (addr.precord); + return DB_EVENT_OK; + } + + printf ( "%u PV Event Subscriptions ( monitors ).\n", + ellCount ( &addr.precord->mlis ) ); + + while ( pevent ) { + pdbFldDes = dbChannelFldDes(pevent->chan); + + if ( level > 0 ) { + printf ( "%4.4s", pdbFldDes->name ); + + printf ( " { " ); + if ( pevent->select & DBE_VALUE ) printf( "VALUE " ); + if ( pevent->select & DBE_LOG ) printf( "LOG " ); + if ( pevent->select & DBE_ALARM ) printf( "ALARM " ); + if ( pevent->select & DBE_PROPERTY ) printf( "PROPERTY " ); + printf ( "}" ); + + if ( pevent->npend ) { + printf ( " undelivered=%ld", pevent->npend ); + } + + if ( level > 1 ) { + unsigned nEntriesFree; + const void * taskId; + LOCKEVQUE(pevent->ev_que); + nEntriesFree = ringSpace ( pevent->ev_que ); + taskId = ( void * ) pevent->ev_que->evUser->taskid; + UNLOCKEVQUE(pevent->ev_que); + if ( nEntriesFree == 0u ) { + printf ( ", thread=%p, queue full", + (void *) taskId ); + } + else if ( nEntriesFree == EVENTQUESIZE ) { + printf ( ", thread=%p, queue empty", + (void *) taskId ); + } + else { + printf ( ", thread=%p, unused entries=%u", + (void *) taskId, nEntriesFree ); + } + } + + if ( level > 2 ) { + unsigned nDuplicates; + unsigned nCanceled; + if ( pevent->nreplace ) { + printf (", discarded by replacement=%ld", pevent->nreplace); + } + if ( ! pevent->useValque ) { + printf (", queueing disabled" ); + } + LOCKEVQUE(pevent->ev_que); + nDuplicates = pevent->ev_que->nDuplicates; + nCanceled = pevent->ev_que->nCanceled; + UNLOCKEVQUE(pevent->ev_que); + if ( nDuplicates ) { + printf (", duplicate count =%u\n", nDuplicates ); + } + if ( nCanceled ) { + printf (", canceled count =%u\n", nCanceled ); + } + } + + if ( level > 3 ) { + printf ( ", ev %p, ev que %p, ev user %p", + ( void * ) pevent, + ( void * ) pevent->ev_que, + ( void * ) pevent->ev_que->evUser ); + } + + printf( "\n" ); + } + + pevent = (struct evSubscrip *) ellNext ( &pevent->node ); + } + + UNLOCKREC (addr.precord); + + return DB_EVENT_OK; +} + +/* + * DB_INIT_EVENTS() + * + * + * Initialize the event facility for this task. Must be called at least once + * by each task which uses the db event facility + * + * returns: ptr to event user block or NULL if memory can't be allocated + */ +dbEventCtx db_init_events (void) +{ + struct event_user * evUser; + + if (!stopSync) { + stopSync = epicsMutexMustCreate(); + } + + if (!dbevEventUserFreeList) { + freeListInitPvt(&dbevEventUserFreeList, + sizeof(struct event_user),8); + } + if (!dbevEventQueueFreeList) { + freeListInitPvt(&dbevEventQueueFreeList, + sizeof(struct event_que),8); + } + if (!dbevEventSubscriptionFreeList) { + freeListInitPvt(&dbevEventSubscriptionFreeList, + sizeof(struct evSubscrip),256); + } + if (!dbevFieldLogFreeList) { + freeListInitPvt(&dbevFieldLogFreeList, + sizeof(struct db_field_log),2048); + } + + evUser = (struct event_user *) + freeListCalloc(dbevEventUserFreeList); + if (!evUser) { + return NULL; + } + + /* Flag will be cleared when event task starts */ + evUser->pendexit = TRUE; + + evUser->firstque.evUser = evUser; + evUser->firstque.writelock = epicsMutexCreate(); + if (!evUser->firstque.writelock) + goto fail; + + evUser->ppendsem = epicsEventCreate(epicsEventEmpty); + if (!evUser->ppendsem) + goto fail; + evUser->pflush_sem = epicsEventCreate(epicsEventEmpty); + if (!evUser->pflush_sem) + goto fail; + evUser->lock = epicsMutexCreate(); + if (!evUser->lock) + goto fail; + evUser->pexitsem = epicsEventCreate(epicsEventEmpty); + if (!evUser->pexitsem) + goto fail; + + evUser->flowCtrlMode = FALSE; + evUser->extraLaborBusy = FALSE; + evUser->pSuicideEvent = NULL; + return (dbEventCtx) evUser; +fail: + if(evUser->lock) + epicsMutexDestroy (evUser->lock); + if(evUser->firstque.writelock) + epicsMutexDestroy (evUser->firstque.writelock); + if(evUser->ppendsem) + epicsEventDestroy (evUser->ppendsem); + if(evUser->pflush_sem) + epicsEventDestroy (evUser->pflush_sem); + if(evUser->pexitsem) + epicsEventDestroy (evUser->pexitsem); + freeListFree(dbevEventUserFreeList,evUser); + return NULL; +} + + +epicsShareFunc void db_cleanup_events(void) +{ + if(dbevEventUserFreeList) freeListCleanup(dbevEventUserFreeList); + dbevEventUserFreeList = NULL; + + if(dbevEventQueueFreeList) freeListCleanup(dbevEventQueueFreeList); + dbevEventQueueFreeList = NULL; + + if(dbevEventSubscriptionFreeList) freeListCleanup(dbevEventSubscriptionFreeList); + dbevEventSubscriptionFreeList = NULL; + + if(dbevFieldLogFreeList) freeListCleanup(dbevFieldLogFreeList); + dbevFieldLogFreeList = NULL; +} + + /* intentionally leak stopSync to avoid possible shutdown races */ +/* + * DB_CLOSE_EVENTS() + * + * evUser block and additional event queues + * deallocated when the event thread terminates + * itself + * + */ +void db_close_events (dbEventCtx ctx) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + /* + * Exit not forced on event blocks for now - this is left to channel + * access and any other tasks using this facility which can find them + * more efficiently. + * + * NOTE: not deleting events before calling this routine could be + * hazardous to the system's health. + */ + epicsMutexMustLock ( evUser->lock ); + if(!evUser->pendexit) { /* event task running */ + evUser->pendexit = TRUE; + epicsMutexUnlock ( evUser->lock ); + + /* notify the waiting task */ + epicsEventSignal(evUser->ppendsem); + /* wait for task to exit */ + epicsEventMustWait(evUser->pexitsem); + + epicsMutexMustLock ( evUser->lock ); + } + + epicsMutexUnlock ( evUser->lock ); + + epicsMutexMustLock (stopSync); + + epicsEventDestroy(evUser->pexitsem); + epicsEventDestroy(evUser->ppendsem); + epicsEventDestroy(evUser->pflush_sem); + epicsMutexDestroy(evUser->lock); + + epicsMutexUnlock (stopSync); + + freeListFree(dbevEventUserFreeList, evUser); +} + +/* + * create_ev_que() + */ +static struct event_que * create_ev_que ( struct event_user * const evUser ) +{ + struct event_que * const ev_que = (struct event_que *) + freeListCalloc ( dbevEventQueueFreeList ); + if ( ! ev_que ) { + return NULL; + } + ev_que->writelock = epicsMutexCreate(); + if ( ! ev_que->writelock ) { + freeListFree ( dbevEventQueueFreeList, ev_que ); + return NULL; + } + ev_que->evUser = evUser; + return ev_que; +} + +/* + * DB_ADD_EVENT() + */ +dbEventSubscription db_add_event ( + dbEventCtx ctx, struct dbChannel *chan, + EVENTFUNC *user_sub, void *user_arg, unsigned select) +{ + struct event_user * const evUser = (struct event_user *) ctx; + struct event_que * ev_que; + struct evSubscrip * pevent; + + /* + * Don't add events which will not be triggered + */ + if ( select==0 || select > UCHAR_MAX ) { + return NULL; + } + + pevent = freeListCalloc (dbevEventSubscriptionFreeList); + if ( ! pevent ) { + return NULL; + } + + /* find an event que block with enough quota */ + /* otherwise add a new one to the list */ + epicsMutexMustLock ( evUser->lock ); + ev_que = & evUser->firstque; + while ( TRUE ) { + int success = 0; + LOCKEVQUE ( ev_que ); + success = ( ev_que->quota + ev_que->nCanceled < + EVENTQUESIZE - EVENTENTRIES ); + if ( success ) { + ev_que->quota += EVENTENTRIES; + } + UNLOCKEVQUE ( ev_que ); + if ( success ) { + break; + } + if ( ! ev_que->nextque ) { + ev_que->nextque = create_ev_que ( evUser ); + if ( ! ev_que->nextque ) { + ev_que = NULL; + break; + } + } + ev_que = ev_que->nextque; + } + epicsMutexUnlock ( evUser->lock ); + + if ( ! ev_que ) { + freeListFree ( dbevEventSubscriptionFreeList, pevent ); + return NULL; + } + + pevent->npend = 0ul; + pevent->nreplace = 0ul; + pevent->user_sub = user_sub; + pevent->user_arg = user_arg; + pevent->chan = chan; + pevent->select = (unsigned char) select; + pevent->pLastLog = NULL; /* not yet in the queue */ + pevent->callBackInProgress = FALSE; + pevent->enabled = FALSE; + pevent->ev_que = ev_que; + + /* + * Simple types values queued up for reliable interprocess + * communication (for other types they get whatever happens to be + * there upon wakeup) + */ + if (dbChannelElements(chan) == 1 && + dbChannelSpecial(chan) != SPC_DBADDR && + dbChannelFieldSize(chan) <= sizeof(union native_value)) { + pevent->useValque = TRUE; + } + else { + pevent->useValque = FALSE; + } + + return pevent; +} + +/* + * db_event_enable() + */ +void db_event_enable (dbEventSubscription event) +{ + struct evSubscrip * const pevent = (struct evSubscrip *) event; + struct dbCommon * const precord = dbChannelRecord(pevent->chan); + + LOCKREC (precord); + if ( ! pevent->enabled ) { + ellAdd (&precord->mlis, &pevent->node); + pevent->enabled = TRUE; + } + UNLOCKREC (precord); +} + +/* + * db_event_disable() + */ +void db_event_disable (dbEventSubscription event) +{ + struct evSubscrip * const pevent = (struct evSubscrip *) event; + struct dbCommon * const precord = dbChannelRecord(pevent->chan); + + LOCKREC (precord); + if ( pevent->enabled ) { + ellDelete(&precord->mlis, &pevent->node); + pevent->enabled = FALSE; + } + UNLOCKREC (precord); +} + +/* + * event_remove() + * event queue lock _must_ be applied + * this nulls the entry in the queue, but doesn't delete the db_field_log chunk + */ +static void event_remove ( struct event_que *ev_que, + unsigned short index, struct evSubscrip *placeHolder ) +{ + struct evSubscrip * const pevent = ev_que->evque[index]; + + ev_que->evque[index] = placeHolder; + ev_que->valque[index] = NULL; + if ( pevent->npend == 1u ) { + pevent->pLastLog = NULL; + } + else { + assert ( pevent->npend > 1u ); + assert ( ev_que->nDuplicates >= 1u ); + ev_que->nDuplicates--; + } + pevent->npend--; +} + +/* + * DB_CANCEL_EVENT() + * + * This routine does not prevent two threads from deleting + * the same block at the same time. + * + */ +void db_cancel_event (dbEventSubscription event) +{ + struct evSubscrip * const pevent = (struct evSubscrip *) event; + unsigned short getix; + + db_event_disable ( event ); + + /* + * flag the event as canceled by NULLing out the callback handler + * + * make certain that the event isnt being accessed while + * its call back changes + */ + LOCKEVQUE (pevent->ev_que); + + pevent->user_sub = NULL; + + /* + * purge this event from the queue + * + * Its better to take this approach rather than waiting + * for the event thread to finish removing this event + * from the queue because the event thread will not + * process if we are in flow control mode. Since blocking + * here will block CA's TCP input queue then a dead lock + * would be possible. + */ + for ( getix = pevent->ev_que->getix; + pevent->ev_que->evque[getix] != EVENTQEMPTY; ) { + if ( pevent->ev_que->evque[getix] == pevent ) { + assert ( pevent->ev_que->nCanceled < USHRT_MAX ); + pevent->ev_que->nCanceled++; + event_remove ( pevent->ev_que, getix, &canceledEvent ); + } + getix = RNGINC ( getix ); + if ( getix == pevent->ev_que->getix ) { + break; + } + } + assert ( pevent->npend == 0u ); + + if ( pevent->ev_que->evUser->taskid == epicsThreadGetIdSelf() ) { + pevent->ev_que->evUser->pSuicideEvent = pevent; + } + else { + while ( pevent->callBackInProgress ) { + UNLOCKEVQUE (pevent->ev_que); + epicsEventMustWait ( pevent->ev_que->evUser->pflush_sem ); + LOCKEVQUE (pevent->ev_que); + } + } + + pevent->ev_que->quota -= EVENTENTRIES; + + UNLOCKEVQUE (pevent->ev_que); + + freeListFree ( dbevEventSubscriptionFreeList, pevent ); + + return; +} + +/* + * DB_FLUSH_EXTRA_LABOR_EVENT() + * + * waits for extra labor in progress to finish + */ +void db_flush_extra_labor_event (dbEventCtx ctx) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + epicsMutexMustLock ( evUser->lock ); + while ( evUser->extraLaborBusy ) { + epicsMutexUnlock ( evUser->lock ); + epicsThreadSleep(0.1); + epicsMutexMustLock ( evUser->lock ); + } + epicsMutexUnlock ( evUser->lock ); +} + +/* + * DB_ADD_EXTRA_LABOR_EVENT() + * + * Specify a routine to be called + * when labor is offloaded to the + * event task + */ +int db_add_extra_labor_event ( + dbEventCtx ctx, EXTRALABORFUNC *func, void *arg) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + epicsMutexMustLock ( evUser->lock ); + evUser->extralabor_sub = func; + evUser->extralabor_arg = arg; + epicsMutexUnlock ( evUser->lock ); + + return DB_EVENT_OK; +} + +/* + * DB_POST_EXTRA_LABOR() + */ +int db_post_extra_labor (dbEventCtx ctx) +{ + struct event_user * const evUser = (struct event_user *) ctx; + int doit; + + epicsMutexMustLock ( evUser->lock ); + if ( ! evUser->extra_labor ) { + evUser->extra_labor = TRUE; + doit = TRUE; + } + else { + doit = FALSE; + } + epicsMutexUnlock ( evUser->lock ); + + if ( doit ) { + epicsEventSignal(evUser->ppendsem); + } + + return DB_EVENT_OK; +} + +/* + * DB_CREATE_EVENT_LOG() + * + * NOTE: This assumes that the db scan lock is already applied + * (as it copies data from the record) + */ +db_field_log* db_create_event_log (struct evSubscrip *pevent) +{ + db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); + + if (pLog) { + struct dbChannel *chan = pevent->chan; + struct dbCommon *prec = dbChannelRecord(chan); + pLog->ctx = dbfl_context_event; + if (pevent->useValque) { + pLog->type = dbfl_type_val; + pLog->stat = prec->stat; + pLog->sevr = prec->sevr; + pLog->time = prec->time; + pLog->field_type = dbChannelFieldType(chan); + pLog->no_elements = dbChannelElements(chan); + /* + * use memcpy to avoid a bus error on + * union copy of char in the db at an odd + * address + */ + memcpy(&pLog->u.v.field, + dbChannelField(chan), + dbChannelFieldSize(chan)); + } else { + pLog->type = dbfl_type_rec; + } + } + return pLog; +} + +/* + * DB_CREATE_READ_LOG() + * + */ +db_field_log* db_create_read_log (struct dbChannel *chan) +{ + db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList); + + if (pLog) { + pLog->ctx = dbfl_context_read; + pLog->type = dbfl_type_rec; + } + return pLog; +} + +/* + * DB_QUEUE_EVENT_LOG() + * + */ +static void db_queue_event_log (evSubscrip *pevent, db_field_log *pLog) +{ + struct event_que *ev_que; + int firstEventFlag; + unsigned rngSpace; + + ev_que = pevent->ev_que; + /* + * evUser ring buffer must be locked for the multiple + * threads writing/reading it + */ + + LOCKEVQUE (ev_que); + + /* + * if we have an event on the queue and both the last + * event on the queue and the current event are emtpy + * (i.e. of type dbfl_type_rec), simply ignore duplicate + * events (saving empty events serves no purpose) + */ + if (pevent->npend > 0u && + (*pevent->pLastLog)->type == dbfl_type_rec && + pLog->type == dbfl_type_rec) { + db_delete_field_log(pLog); + UNLOCKEVQUE (ev_que); + return; + } + + /* + * add to task local event que + */ + + /* + * if an event is on the queue and one of + * {flowCtrlMode, not room for one more of each monitor attached} + * then replace the last event on the queue (for this monitor) + */ + rngSpace = ringSpace ( ev_que ); + if ( pevent->npend>0u && + (ev_que->evUser->flowCtrlMode || rngSpace<=EVENTSPERQUE) ) { + /* + * replace last event if no space is left + */ + if (*pevent->pLastLog) { + db_delete_field_log(*pevent->pLastLog); + *pevent->pLastLog = pLog; + } + pevent->nreplace++; + /* + * the event task has already been notified about + * this so we dont need to post the semaphore + */ + firstEventFlag = 0; + } + /* + * Otherwise, the current entry must be available. + * Fill it in and advance the ring buffer. + */ + else { + assert ( ev_que->evque[ev_que->putix] == EVENTQEMPTY ); + ev_que->evque[ev_que->putix] = pevent; + ev_que->valque[ev_que->putix] = pLog; + pevent->pLastLog = &ev_que->valque[ev_que->putix]; + if (pevent->npend>0u) { + ev_que->nDuplicates++; + } + pevent->npend++; + /* + * if the ring buffer was empty before + * adding this event + */ + if (rngSpace==EVENTQUESIZE) { + firstEventFlag = 1; + } + else { + firstEventFlag = 0; + } + ev_que->putix = RNGINC ( ev_que->putix ); + } + + UNLOCKEVQUE (ev_que); + + /* + * its more efficent to notify the event handler + * only after the event is ready and the lock + * is off in case it runs at a higher priority + * than the caller here. + */ + if (firstEventFlag) { + /* + * notify the event handler + */ + epicsEventSignal(ev_que->evUser->ppendsem); + } +} + +/* + * DB_POST_EVENTS() + * + * NOTE: This assumes that the db scan lock is already applied + * + */ +int db_post_events( +void *pRecord, +void *pField, +unsigned int caEventMask +) +{ + struct dbCommon * const prec = (struct dbCommon *) pRecord; + struct evSubscrip *pevent; + + if (prec->mlis.count == 0) return DB_EVENT_OK; /* no monitors set */ + + LOCKREC (prec); + + for (pevent = (struct evSubscrip *) prec->mlis.node.next; + pevent; pevent = (struct evSubscrip *) pevent->node.next){ + + /* + * Only send event msg if they are waiting on the field which + * changed or pval==NULL, and are waiting on matching event + */ + if ( (dbChannelField(pevent->chan) == (void *)pField || pField==NULL) && + (caEventMask & pevent->select)) { + db_field_log *pLog = db_create_event_log(pevent); + pLog = dbChannelRunPreChain(pevent->chan, pLog); + if (pLog) db_queue_event_log(pevent, pLog); + } + } + + UNLOCKREC (prec); + return DB_EVENT_OK; + +} + +/* + * DB_POST_SINGLE_EVENT() + */ +void db_post_single_event (dbEventSubscription event) +{ + struct evSubscrip * const pevent = (struct evSubscrip *) event; + struct dbCommon * const prec = dbChannelRecord(pevent->chan); + db_field_log *pLog; + + dbScanLock (prec); + + pLog = db_create_event_log(pevent); + pLog = dbChannelRunPreChain(pevent->chan, pLog); + if(pLog) db_queue_event_log(pevent, pLog); + + dbScanUnlock (prec); +} + +/* + * EVENT_READ() + */ +static int event_read ( struct event_que *ev_que ) +{ + db_field_log *pfl; + void ( *user_sub ) ( void *user_arg, struct dbChannel *chan, + int eventsRemaining, db_field_log *pfl ); + + /* + * evUser ring buffer must be locked for the multiple + * threads writing/reading it + */ + LOCKEVQUE (ev_que); + + /* + * if in flow control mode drain duplicates and then + * suspend processing events until flow control + * mode is over + */ + if ( ev_que->evUser->flowCtrlMode && ev_que->nDuplicates == 0u ) { + UNLOCKEVQUE (ev_que); + return DB_EVENT_OK; + } + + while ( ev_que->evque[ev_que->getix] != EVENTQEMPTY ) { + struct evSubscrip *pevent = ev_que->evque[ev_que->getix]; + + pfl = ev_que->valque[ev_que->getix]; + if ( pevent == &canceledEvent ) { + ev_que->evque[ev_que->getix] = EVENTQEMPTY; + if (ev_que->valque[ev_que->getix]) { + db_delete_field_log(ev_que->valque[ev_que->getix]); + ev_que->valque[ev_que->getix] = NULL; + } + ev_que->getix = RNGINC ( ev_que->getix ); + assert ( ev_que->nCanceled > 0 ); + ev_que->nCanceled--; + continue; + } + + /* + * Simple type values queued up for reliable interprocess + * communication. (for other types they get whatever happens + * to be there upon wakeup) + */ + + event_remove ( ev_que, ev_que->getix, EVENTQEMPTY ); + ev_que->getix = RNGINC ( ev_que->getix ); + + /* + * create a local copy of the call back parameters while + * we still have the lock + */ + user_sub = pevent->user_sub; + + /* + * Next event pointer can be used by event tasks to determine + * if more events are waiting in the queue + * + * Must remove the lock here so that we dont deadlock if + * this calls dbGetField() and blocks on the record lock, + * dbPutField() is in progress in another task, it has the + * record lock, and it is calling db_post_events() waiting + * for the event queue lock (which this thread now has). + */ + if ( user_sub ) { + /* + * This provides a way to test to see if an event is in use + * despite the fact that the event queue does not point to + * it. + */ + pevent->callBackInProgress = TRUE; + UNLOCKEVQUE (ev_que); + /* Run post-event-queue filter chain */ + if (ellCount(&pevent->chan->post_chain)) { + pfl = dbChannelRunPostChain(pevent->chan, pfl); + } + if (pfl) { + /* Issue user callback */ + ( *user_sub ) ( pevent->user_arg, pevent->chan, + ev_que->evque[ev_que->getix] != EVENTQEMPTY, pfl ); + } + LOCKEVQUE (ev_que); + + /* + * check to see if this event has been canceled each + * time that the callBackInProgress flag is set to false + * while we have the event queue lock, and post the flush + * complete sem if there are no longer any events on the + * queue + */ + if ( ev_que->evUser->pSuicideEvent == pevent ) { + ev_que->evUser->pSuicideEvent = NULL; + } + else { + if ( pevent->user_sub==NULL && pevent->npend==0u ) { + pevent->callBackInProgress = FALSE; + epicsEventSignal ( ev_que->evUser->pflush_sem ); + } + else { + pevent->callBackInProgress = FALSE; + } + } + } + db_delete_field_log(pfl); + } + + UNLOCKEVQUE (ev_que); + + return DB_EVENT_OK; +} + +/* + * EVENT_TASK() + */ +static void event_task (void *pParm) +{ + struct event_user * const evUser = (struct event_user *) pParm; + struct event_que * ev_que; + unsigned char pendexit; + + /* init hook */ + if (evUser->init_func) { + (*evUser->init_func)(evUser->init_func_arg); + } + + taskwdInsert ( epicsThreadGetIdSelf(), NULL, NULL ); + + do { + void (*pExtraLaborSub) (void *); + void *pExtraLaborArg; + epicsEventMustWait(evUser->ppendsem); + + /* + * check to see if the caller has offloaded + * labor to this task + */ + epicsMutexMustLock ( evUser->lock ); + evUser->extraLaborBusy = TRUE; + if ( evUser->extra_labor && evUser->extralabor_sub ) { + evUser->extra_labor = FALSE; + pExtraLaborSub = evUser->extralabor_sub; + pExtraLaborArg = evUser->extralabor_arg; + } + else { + pExtraLaborSub = NULL; + pExtraLaborArg = NULL; + } + if ( pExtraLaborSub ) { + epicsMutexUnlock ( evUser->lock ); + (*pExtraLaborSub)(pExtraLaborArg); + epicsMutexMustLock ( evUser->lock ); + } + evUser->extraLaborBusy = FALSE; + + for ( ev_que = &evUser->firstque; ev_que; + ev_que = ev_que->nextque ) { + epicsMutexUnlock ( evUser->lock ); + event_read (ev_que); + epicsMutexMustLock ( evUser->lock ); + } + pendexit = evUser->pendexit; + epicsMutexUnlock ( evUser->lock ); + + } while( ! pendexit ); + + epicsMutexDestroy(evUser->firstque.writelock); + + { + struct event_que *nextque; + + ev_que = evUser->firstque.nextque; + while (ev_que) { + nextque = ev_que->nextque; + epicsMutexDestroy(ev_que->writelock); + freeListFree(dbevEventQueueFreeList, ev_que); + ev_que = nextque; + } + } + + taskwdRemove(epicsThreadGetIdSelf()); + + /* use stopSync to ensure pexitsem is not destroy'd + * until epicsEventSignal() has returned. + */ + epicsMutexMustLock (stopSync); + + epicsEventSignal(evUser->pexitsem); + + epicsMutexUnlock(stopSync); + + return; +} + +/* + * DB_START_EVENTS() + */ +int db_start_events ( + dbEventCtx ctx,const char *taskname, void (*init_func)(void *), + void *init_func_arg, unsigned osiPriority ) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + epicsMutexMustLock ( evUser->lock ); + + /* + * only one ca_pend_event thread may be + * started for each evUser + */ + if (evUser->taskid) { + epicsMutexUnlock ( evUser->lock ); + return DB_EVENT_OK; + } + + evUser->init_func = init_func; + evUser->init_func_arg = init_func_arg; + if (!taskname) { + taskname = EVENT_PEND_NAME; + } + evUser->taskid = epicsThreadCreate ( + taskname, osiPriority, + epicsThreadGetStackSize(epicsThreadStackMedium), + event_task, (void *)evUser); + if (!evUser->taskid) { + epicsMutexUnlock ( evUser->lock ); + return DB_EVENT_ERROR; + } + evUser->pendexit = FALSE; + epicsMutexUnlock ( evUser->lock ); + return DB_EVENT_OK; +} + +/* + * db_event_change_priority() + */ +void db_event_change_priority ( dbEventCtx ctx, + unsigned epicsPriority ) +{ + struct event_user * const evUser = ( struct event_user * ) ctx; + epicsThreadSetPriority ( evUser->taskid, epicsPriority ); +} + +/* + * db_event_flow_ctrl_mode_on() + */ +void db_event_flow_ctrl_mode_on (dbEventCtx ctx) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + epicsMutexMustLock ( evUser->lock ); + evUser->flowCtrlMode = TRUE; + epicsMutexUnlock ( evUser->lock ); + /* + * notify the event handler task + */ + epicsEventSignal(evUser->ppendsem); +#ifdef DEBUG + printf("fc on %lu\n", tickGet()); +#endif +} + +/* + * db_event_flow_ctrl_mode_off() + */ +void db_event_flow_ctrl_mode_off (dbEventCtx ctx) +{ + struct event_user * const evUser = (struct event_user *) ctx; + + epicsMutexMustLock ( evUser->lock ); + evUser->flowCtrlMode = FALSE; + epicsMutexUnlock ( evUser->lock ); + /* + * notify the event handler task + */ + epicsEventSignal (evUser->ppendsem); +#ifdef DEBUG + printf("fc off %lu\n", tickGet()); +#endif +} + +/* + * db_delete_field_log() + */ +void db_delete_field_log (db_field_log *pfl) +{ + if (pfl) { + /* Free field if reference type field log and dtor is set */ + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) pfl->u.r.dtor(pfl); + /* Free the field log chunk */ + freeListFree(dbevFieldLogFreeList, pfl); + } +} diff --git a/modules/database/src/ioc/db/dbEvent.h b/modules/database/src/ioc/db/dbEvent.h new file mode 100644 index 000000000..8ee109373 --- /dev/null +++ b/modules/database/src/ioc/db/dbEvent.h @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Jeffrey O. Hill + * + * Ralph Lange + */ + +#ifndef INCLdbEventh +#define INCLdbEventh + +#ifdef epicsExportSharedSymbols +# undef epicsExportSharedSymbols +# define INCLdbEventhExporting +#endif + +#include "epicsThread.h" + +#ifdef INCLdbEventhExporting +# define epicsExportSharedSymbols +#endif + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbChannel; +struct db_field_log; +struct evSubscrip; + +epicsShareFunc int db_event_list ( + const char *name, unsigned level); +epicsShareFunc int dbel ( + const char *name, unsigned level); +epicsShareFunc int db_post_events ( + void *pRecord, void *pField, unsigned caEventMask ); + +typedef void * dbEventCtx; + +typedef void EXTRALABORFUNC (void *extralabor_arg); +epicsShareFunc dbEventCtx db_init_events (void); +epicsShareFunc int db_start_events ( + dbEventCtx ctx, const char *taskname, void (*init_func)(void *), + void *init_func_arg, unsigned osiPriority ); +epicsShareFunc void db_close_events (dbEventCtx ctx); +epicsShareFunc void db_event_flow_ctrl_mode_on (dbEventCtx ctx); +epicsShareFunc void db_event_flow_ctrl_mode_off (dbEventCtx ctx); +epicsShareFunc int db_add_extra_labor_event ( + dbEventCtx ctx, EXTRALABORFUNC *func, void *arg); +epicsShareFunc void db_flush_extra_labor_event (dbEventCtx); +epicsShareFunc int db_post_extra_labor (dbEventCtx ctx); +epicsShareFunc void db_event_change_priority ( dbEventCtx ctx, unsigned epicsPriority ); + +#ifdef EPICS_PRIVATE_API +epicsShareFunc void db_cleanup_events(void); +#endif + +typedef void EVENTFUNC (void *user_arg, struct dbChannel *chan, + int eventsRemaining, struct db_field_log *pfl); + +typedef void * dbEventSubscription; +epicsShareFunc dbEventSubscription db_add_event ( + dbEventCtx ctx, struct dbChannel *chan, + EVENTFUNC *user_sub, void *user_arg, unsigned select); +epicsShareFunc void db_cancel_event (dbEventSubscription es); +epicsShareFunc void db_post_single_event (dbEventSubscription es); +epicsShareFunc void db_event_enable (dbEventSubscription es); +epicsShareFunc void db_event_disable (dbEventSubscription es); + +epicsShareFunc struct db_field_log* db_create_event_log (struct evSubscrip *pevent); +epicsShareFunc struct db_field_log* db_create_read_log (struct dbChannel *chan); +epicsShareFunc void db_delete_field_log (struct db_field_log *pfl); + +#define DB_EVENT_OK 0 +#define DB_EVENT_ERROR (-1) + +#ifdef __cplusplus +} +#endif + +#endif /*INCLdbEventh*/ + diff --git a/modules/database/src/ioc/db/dbExtractArray.c b/modules/database/src/ioc/db/dbExtractArray.c new file mode 100644 index 000000000..e16ab4cd2 --- /dev/null +++ b/modules/database/src/ioc/db/dbExtractArray.c @@ -0,0 +1,85 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + * + * based on dbConvert.c + * written by: Bob Dalesio, Marty Kraimer + */ + +#include + +#include "epicsTypes.h" + +#define epicsExportSharedSymbols +#include "dbAddr.h" +#include "dbExtractArray.h" + +void dbExtractArrayFromRec(const dbAddr *paddr, void *pto, + long nRequest, long no_elements, long offset, long increment) +{ + char *pdst = (char *) pto; + char *psrc = (char *) paddr->pfield; + long nUpperPart; + int i; + short srcSize = paddr->field_size; + short dstSize = srcSize; + char isString = (paddr->field_type == DBF_STRING); + + if (nRequest > no_elements) nRequest = no_elements; + if (isString && srcSize > MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE; + + if (increment == 1 && dstSize == srcSize) { + nUpperPart = nRequest < no_elements - offset ? nRequest : no_elements - offset; + memcpy(pdst, &psrc[offset * srcSize], dstSize * nUpperPart); + if (nRequest > nUpperPart) + memcpy(&pdst[dstSize * nUpperPart], psrc, dstSize * (nRequest - nUpperPart)); + if (isString) + for (i = 1; i <= nRequest; i++) + pdst[dstSize*i-1] = '\0'; + } else { + for (; nRequest > 0; nRequest--, pdst += dstSize, offset += increment) { + offset %= no_elements; + memcpy(pdst, &psrc[offset*srcSize], dstSize); + if (isString) pdst[dstSize-1] = '\0'; + } + } +} + +void dbExtractArrayFromBuf(const void *pfrom, void *pto, + short field_size, short field_type, + long nRequest, long no_elements, long offset, long increment) +{ + char *pdst = (char *) pto; + char *psrc = (char *) pfrom; + int i; + short srcSize = field_size; + short dstSize = srcSize; + char isString = (field_type == DBF_STRING); + + if (nRequest > no_elements) nRequest = no_elements; + if (offset > no_elements - 1) offset = no_elements - 1; + if (isString && dstSize >= MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE - 1; + + if (increment == 1) { + memcpy(pdst, &psrc[offset * srcSize], dstSize * nRequest); + if (isString) + for (i = 1; i <= nRequest; i++) + pdst[dstSize*i] = '\0'; + } else { + for (; nRequest > 0; nRequest--, pdst += srcSize, offset += increment) { + memcpy(pdst, &psrc[offset*srcSize], dstSize); + if (isString) pdst[dstSize] = '\0'; + } + } +} diff --git a/modules/database/src/ioc/db/dbExtractArray.h b/modules/database/src/ioc/db/dbExtractArray.h new file mode 100644 index 000000000..7ed35847f --- /dev/null +++ b/modules/database/src/ioc/db/dbExtractArray.h @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbExtractArray_H +#define INC_dbExtractArray_H + +#include "dbFldTypes.h" +#include "dbAddr.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void dbExtractArrayFromRec(const DBADDR *paddr, void *pto, + long nRequest, long no_elements, long offset, long increment); +epicsShareFunc void dbExtractArrayFromBuf(const void *pfrom, void *pto, + short field_size, short field_type, + long nRequest, long no_elements, long offset, long increment); + +#ifdef __cplusplus +} +#endif + +#endif // INC_dbExtractArray_H diff --git a/modules/database/src/ioc/db/dbFastLinkConv.c b/modules/database/src/ioc/db/dbFastLinkConv.c new file mode 100644 index 000000000..1d94bc95e --- /dev/null +++ b/modules/database/src/ioc/db/dbFastLinkConv.c @@ -0,0 +1,1486 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbFastLinkConv.c */ +/* + * Author: Matthew Needes + * Date: 12-9-93 + */ + +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "cvtFast.h" +#include "dbDefs.h" +#include "epicsConvert.h" +#include "epicsStdlib.h" +#include "errlog.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCommon.h" +#include "dbConvertFast.h" +#include "dbFldTypes.h" +#include "dbStaticLib.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + + +/* + * In the following functions: + * + * cvt_y_z(ARGS) + * + * converts from type y to type z. If + * type y and z are the same, it merely copies. + * + * where st - string + * c - epicsInt8 + * uc - epicsUInt8 + * s - epicsInt16 + * us - epicsUInt16 + * l - epicsInt32 + * ul - epicsUInt32 + * q - epicsInt64 + * uq - epicsUInt64 + * f - epicsFloat32 + * d - epicsFloat64 + * e - enum + * + * These functions are _single_ value functions, + * i.e.: do not deal with array types. + */ + +/* + * A DB_LINK that is not initialized with recGblInitFastXXXLink() + * will have this conversion. + */ + +/* Convert String to String */ +static long cvt_st_st( + char *from, + char *to, + const dbAddr *paddr) +{ + size_t size; + + if (paddr && paddr->field_size < MAX_STRING_SIZE) { + size = paddr->field_size - 1; + } else { + size = MAX_STRING_SIZE - 1; + } + strncpy(to, from, size); + to[size] = 0; + return 0; +} + +/* Convert String to Char */ +static long cvt_st_c( + char *from, + epicsInt8 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseInt8(from, to, 10, &end); +} + +/* Convert String to Unsigned Char */ +static long cvt_st_uc( + char *from, + epicsUInt8 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseUInt8(from, to, 10, &end); +} + +/* Convert String to Short */ +static long cvt_st_s( + char *from, + epicsInt16 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseInt16(from, to, 10, &end); +} + +/* Convert String to Unsigned Short */ +static long cvt_st_us( + char *from, + epicsUInt16 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseUInt16(from, to, 10, &end); +} + +/* Convert String to Long */ +static long cvt_st_l( + char *from, + epicsInt32 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseInt32(from, to, 10, &end); +} + +/* Convert String to Unsigned Long */ +static long cvt_st_ul( + char *from, + epicsUInt32 *to, + const dbAddr *paddr) +{ + char *end; + long status; + + if (*from == 0) { + *to = 0; + return 0; + } + status = epicsParseUInt32(from, to, 10, &end); + if (status == S_stdlib_noConversion || + (!status && (*end == '.' || *end == 'e' || *end == 'E'))) { + /* + * Convert via double so numbers like 1.0e3 convert properly. + * db_access pretends unsigned long is double. + */ + double dval; + + status = epicsParseFloat64(from, &dval, &end); + if (!status && + dval >=0 && + dval <= ULONG_MAX) + *to = dval; + } + return status; +} + +/* Convert String to Int64 */ +static long cvt_st_q( + char *from, + epicsInt64 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseInt64(from, to, 10, &end); +} + +/* Convert String to UInt64 */ +static long cvt_st_uq( + char *from, + epicsUInt64 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseUInt64(from, to, 0, &end); +} + +/* Convert String to Float */ +static long cvt_st_f( + char *from, + epicsFloat32 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0; + return 0; + } + return epicsParseFloat32(from, to, &end); +} + +/* Convert String to Double */ +static long cvt_st_d( + char *from, + epicsFloat64 *to, + const dbAddr *paddr) +{ + char *end; + + if (*from == 0) { + *to = 0.0; + return 0; + } + return epicsParseFloat64(from, to, &end); +} + +/* Convert String to Enumerated */ +static long cvt_st_e( + char *from, + epicsEnum16 *to, + const dbAddr *paddr) +{ + rset *prset = dbGetRset(paddr); + long status = S_db_noRSET; + struct dbr_enumStrs enumStrs; + + if (!prset || !prset->put_enum_str) { + recGblRecSupError(status, paddr, "dbPutField", "put_enum_str"); + return status; + } + + status = prset->put_enum_str(paddr, from); + if (!status) return 0; + + if (!prset->get_enum_strs) { + recGblRecSupError(status, paddr, "dbPutField", "get_enum_strs"); + return status; + } + + status = prset->get_enum_strs(paddr, &enumStrs); + if (!status) { + epicsEnum16 val; + + status = epicsParseUInt16(from, &val, 10, NULL); + if (!status && val < enumStrs.no_str) { + *to = val; + return 0; + } + status = S_db_badChoice; + } + + recGblRecordError(status, paddr->precord, from); + return status; +} + +/* Convert String to Menu */ +static long cvt_st_menu( + char *from, + epicsEnum16 *to, + const dbAddr *paddr) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + dbMenu *pdbMenu; + char **pchoices; + char *pchoice; + + if (pdbFldDes && + (pdbMenu = pdbFldDes->ftPvt) && + (pchoices = pdbMenu->papChoiceValue)) { + int i, nChoice = pdbMenu->nChoice; + epicsEnum16 val; + + for (i = 0; i < nChoice; i++) { + pchoice = pchoices[i]; + if (!pchoice) continue; + if (strcmp(pchoice, from) == 0) { + *to = i; + return 0; + } + } + + if (!epicsParseUInt16(from, &val, 10, NULL) && val < nChoice) { + *to = val; + return 0; + } + } + recGblDbaddrError(S_db_badChoice, paddr, "dbFastLinkConv(cvt_st_menu)"); + return(S_db_badChoice); +} + +/* Convert String to Device */ +static long cvt_st_device( + char *from, + epicsEnum16 *to, + const dbAddr *paddr) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + dbDeviceMenu *pdbDeviceMenu = pdbFldDes->ftPvt; + char **pchoices, *pchoice; + + if (pdbFldDes && + (pdbDeviceMenu = pdbFldDes->ftPvt) && + (pchoices = pdbDeviceMenu->papChoice)) { + int i, nChoice = pdbDeviceMenu->nChoice; + epicsEnum16 val; + + for (i = 0; i < nChoice; i++) { + pchoice = pchoices[i]; + if (!pchoice) continue; + if (strcmp(pchoice, from) == 0) { + *to = i; + return 0; + } + } + + if (!epicsParseUInt16(from, &val, 10, NULL) && val < nChoice) { + *to = val; + return 0; + } + } + recGblDbaddrError(S_db_badChoice, paddr, "dbFastLinkConv(cvt_st_device)"); + return S_db_badChoice; +} + +/* Convert Char to String */ +static long cvt_c_st( + epicsInt8 *from, + char *to, + const dbAddr *paddr) +{ cvtCharToString(*from, to); return(0); } + +/* Convert Char to Char */ +static long cvt_c_c( + epicsInt8 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Unsigned Char */ +static long cvt_c_uc( + epicsInt8 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Short */ +static long cvt_c_s( + epicsInt8 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Unsigned Short */ +static long cvt_c_us( + epicsInt8 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Long */ +static long cvt_c_l( + epicsInt8 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Unsigned Long */ +static long cvt_c_ul( + epicsInt8 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Int64 */ +static long cvt_c_q( + epicsInt8 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to UInt64 */ +static long cvt_c_uq( + epicsInt8 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Float */ +static long cvt_c_f( + epicsInt8 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Double */ +static long cvt_c_d( + epicsInt8 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Char to Enumerated */ +static long cvt_c_e( + epicsInt8 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to String */ +static long cvt_uc_st( + epicsUInt8 *from, + char *to, + const dbAddr *paddr) +{ cvtUcharToString(*from, to); return(0); } + +/* Convert Unsigned Char to Char */ +static long cvt_uc_c( + epicsUInt8 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Unsigned Char */ +static long cvt_uc_uc( + epicsUInt8 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Short */ +static long cvt_uc_s( + epicsUInt8 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Unsigned Short */ +static long cvt_uc_us( + epicsUInt8 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Long */ +static long cvt_uc_l( + epicsUInt8 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Unsigned Long */ +static long cvt_uc_ul( + epicsUInt8 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Int64 */ +static long cvt_uc_q( + epicsUInt8 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to UInt64 */ +static long cvt_uc_uq( + epicsUInt8 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Float */ +static long cvt_uc_f( + epicsUInt8 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Double */ +static long cvt_uc_d( + epicsUInt8 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Char to Enumerated */ +static long cvt_uc_e( + epicsUInt8 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to String */ +static long cvt_s_st( + epicsInt16 *from, + char *to, + const dbAddr *paddr) +{ cvtShortToString(*from, to); return(0); } + +/* Convert Short to Char */ +static long cvt_s_c( + epicsInt16 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=(epicsInt8)*from; return(0); } + +/* Convert Short to Unsigned Char */ +static long cvt_s_uc( + epicsInt16 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=(epicsUInt8)*from; return(0); } + +/* Convert Short to Short */ +static long cvt_s_s( + epicsInt16 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Unsigned Short */ +static long cvt_s_us( + epicsInt16 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Long */ +static long cvt_s_l( + epicsInt16 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Unsigned Long */ +static long cvt_s_ul( + epicsInt16 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Int64 */ +static long cvt_s_q( + epicsInt16 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to UInt64 */ +static long cvt_s_uq( + epicsInt16 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Float */ +static long cvt_s_f( + epicsInt16 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Double */ +static long cvt_s_d( + epicsInt16 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Short to Enumerated */ +static long cvt_s_e( + epicsInt16 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to String */ +static long cvt_us_st( + epicsUInt16 *from, + char *to, + const dbAddr *paddr) +{ cvtUshortToString(*from, to); return(0); } + +/* Convert Unsigned Short to Char */ +static long cvt_us_c( + epicsUInt16 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=(epicsInt8)*from; return(0); } + +/* Convert Unsigned Short to Unsigned Char */ +static long cvt_us_uc( + epicsUInt16 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=(epicsUInt8)*from; return(0); } + +/* Convert Unsigned Short to Short */ +static long cvt_us_s( + epicsUInt16 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Unsigned Short */ +static long cvt_us_us( + epicsUInt16 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Long */ +static long cvt_us_l( + epicsUInt16 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Unsigned Long */ +static long cvt_us_ul( + epicsUInt16 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Int64 */ +static long cvt_us_q( + epicsUInt16 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to UInt64 */ +static long cvt_us_uq( + epicsUInt16 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Float */ +static long cvt_us_f( + epicsUInt16 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Double */ +static long cvt_us_d( + epicsUInt16 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Short to Enumerated */ +static long cvt_us_e( + epicsUInt16 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to String */ +static long cvt_l_st( + epicsInt32 *from, + char *to, + const dbAddr *paddr) +{ cvtLongToString(*from, to); return(0); } + +/* Convert Long to Char */ +static long cvt_l_c( + epicsInt32 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Unsigned Char */ +static long cvt_l_uc( + epicsInt32 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Short */ +static long cvt_l_s( + epicsInt32 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Unsigned Short */ +static long cvt_l_us( + epicsInt32 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Long */ +static long cvt_l_l( + epicsInt32 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Unsigned Long */ +static long cvt_l_ul( + epicsInt32 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Int64 */ +static long cvt_l_q( + epicsInt32 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to UInt64 */ +static long cvt_l_uq( + epicsInt32 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Float */ +static long cvt_l_f( + epicsInt32 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=(epicsFloat32)*from; return(0); } + +/* Convert Long to Double */ +static long cvt_l_d( + epicsInt32 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Long to Enumerated */ +static long cvt_l_e( + epicsInt32 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to String */ +static long cvt_ul_st( + epicsUInt32 *from, + char *to, + const dbAddr *paddr) +{ cvtUlongToString(*from, to); return(0); } + +/* Convert Unsigned Long to Char */ +static long cvt_ul_c( + epicsUInt32 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Unsigned Char */ +static long cvt_ul_uc( + epicsUInt32 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Short */ +static long cvt_ul_s( + epicsUInt32 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Unsigned Short */ +static long cvt_ul_us( + epicsUInt32 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Long */ +static long cvt_ul_l( + epicsUInt32 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Unsigned Long */ +static long cvt_ul_ul( + epicsUInt32 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Int64 */ +static long cvt_ul_q( + epicsUInt32 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to UInt64 */ +static long cvt_ul_uq( + epicsUInt32 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Float */ +static long cvt_ul_f( + epicsUInt32 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=(epicsFloat32)*from; return(0); } + +/* Convert Unsigned Long to Double */ +static long cvt_ul_d( + epicsUInt32 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Unsigned Long to Enumerated */ +static long cvt_ul_e( + epicsUInt32 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to String */ +static long cvt_q_st( + epicsInt64 *from, + char *to, + const dbAddr *paddr) +{ cvtInt64ToString(*from, to); return(0); } + +/* Convert Int64 to Char */ +static long cvt_q_c( + epicsInt64 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Unsigned Char */ +static long cvt_q_uc( + epicsInt64 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Short */ +static long cvt_q_s( + epicsInt64 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Unsigned Short */ +static long cvt_q_us( + epicsInt64 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Long */ +static long cvt_q_l( + epicsInt64 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Unsigned Long */ +static long cvt_q_ul( + epicsInt64 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Int64 */ +static long cvt_q_q( + epicsInt64 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to UInt64 */ +static long cvt_q_uq( + epicsInt64 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Float */ +static long cvt_q_f( + epicsInt64 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Double */ +static long cvt_q_d( + epicsInt64 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Int64 to Enumerated */ +static long cvt_q_e( + epicsInt64 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to String */ +static long cvt_uq_st( + epicsUInt64 *from, + char *to, + const dbAddr *paddr) +{ cvtUInt64ToString(*from, to); return(0); } + +/* Convert UInt64 to Char */ +static long cvt_uq_c( + epicsUInt64 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Unsigned Char */ +static long cvt_uq_uc( + epicsUInt64 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Short */ +static long cvt_uq_s( + epicsUInt64 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Unsigned Short */ +static long cvt_uq_us( + epicsUInt64 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Long */ +static long cvt_uq_l( + epicsUInt64 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Unsigned Long */ +static long cvt_uq_ul( + epicsUInt64 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Int64 */ +static long cvt_uq_q( + epicsUInt64 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to UInt64 */ +static long cvt_uq_uq( + epicsUInt64 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Float */ +static long cvt_uq_f( + epicsUInt64 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Double */ +static long cvt_uq_d( + epicsUInt64 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert UInt64 to Enumerated */ +static long cvt_uq_e( + epicsUInt64 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Float to String */ +static long cvt_f_st( + epicsFloat32 *from, + char *to, + const dbAddr *paddr) + { + rset *prset = 0; + long status = 0; + long precision = 6; + + if(paddr) prset = dbGetRset(paddr); + + if (prset && prset->get_precision) + status = (*prset->get_precision)(paddr, &precision); + cvtFloatToString(*from, to, (unsigned short)precision); + return(status); + } + +/* Convert Float to Char */ +static long cvt_f_c( + epicsFloat32 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=(epicsInt8)*from; return(0); } + +/* Convert Float to Unsigned Char */ +static long cvt_f_uc( + epicsFloat32 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=(epicsUInt8)*from; return(0); } + +/* Convert Float to Short */ +static long cvt_f_s( + epicsFloat32 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=(epicsInt16)*from; return(0); } + +/* Convert Float to Unsigned Short */ +static long cvt_f_us( + epicsFloat32 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=(epicsUInt16)*from; return(0); } + +/* Convert Float to Long */ +static long cvt_f_l( + epicsFloat32 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=(epicsInt32)*from; return(0); } + +/* Convert Float to Unsigned Long */ +static long cvt_f_ul( + epicsFloat32 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=(epicsUInt32)*from; return(0); } + +/* Convert Float to Int64 */ +static long cvt_f_q( + epicsFloat32 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Float to UInt64 */ +static long cvt_f_uq( + epicsFloat32 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Float to Float */ +static long cvt_f_f( + epicsFloat32 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Float to Double */ +static long cvt_f_d( + epicsFloat32 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Float to Enumerated */ +static long cvt_f_e( + epicsFloat32 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=(epicsEnum16)*from; return(0); } + +/* Convert Double to String */ +static long cvt_d_st( + epicsFloat64 *from, + char *to, + const dbAddr *paddr) + { + rset *prset = 0; + long status = 0; + long precision = 6; + + if(paddr) prset = dbGetRset(paddr); + + if (prset && prset->get_precision) + status = (*prset->get_precision)(paddr, &precision); + cvtDoubleToString(*from, to, (unsigned short)precision); + return(status); + } + +/* Convert Double to Char */ +static long cvt_d_c( + epicsFloat64 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=(epicsInt8)*from; return(0); } + +/* Convert Double to Unsigned Char */ +static long cvt_d_uc( + epicsFloat64 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=(epicsUInt8)*from; return(0); } + +/* Convert Double to Short */ +static long cvt_d_s( + epicsFloat64 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=(epicsInt16)*from; return(0); } + +/* Convert Double to Unsigned Short */ +static long cvt_d_us( + epicsFloat64 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=(epicsUInt16)*from; return(0); } + +/* Convert Double to Long */ +static long cvt_d_l( + epicsFloat64 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Double to Unsigned Long */ +static long cvt_d_ul( + epicsFloat64 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Double to Int64 */ +static long cvt_d_q( + epicsFloat64 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Double to UInt64 */ +static long cvt_d_uq( + epicsFloat64 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Double to Float */ +static long cvt_d_f( + epicsFloat64 *from, + epicsFloat32 *to, + const dbAddr *paddr) +{ *to = epicsConvertDoubleToFloat(*from); return 0;} + +/* Convert Double to Double */ +static long cvt_d_d( + epicsFloat64 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Double to Enumerated */ +static long cvt_d_e( + epicsFloat64 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=(epicsEnum16)*from; return(0); } + +/* Convert Enumerated to Char */ +static long cvt_e_c( + epicsEnum16 *from, + epicsInt8 *to, + const dbAddr *paddr) + { *to=(epicsInt8)*from; return(0); } + +/* Convert Enumerated to Unsigned Char */ +static long cvt_e_uc( + epicsEnum16 *from, + epicsUInt8 *to, + const dbAddr *paddr) + { *to=(epicsUInt8)*from; return(0); } + +/* Convert Enumerated to Short */ +static long cvt_e_s( + epicsEnum16 *from, + epicsInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Unsigned Short */ +static long cvt_e_us( + epicsEnum16 *from, + epicsUInt16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Long */ +static long cvt_e_l( + epicsEnum16 *from, + epicsInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Unsigned Long */ +static long cvt_e_ul( + epicsEnum16 *from, + epicsUInt32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Int64 */ +static long cvt_e_q( + epicsEnum16 *from, + epicsInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to UInt64 */ +static long cvt_e_uq( + epicsEnum16 *from, + epicsUInt64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Float */ +static long cvt_e_f( + epicsEnum16 *from, + epicsFloat32 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Double */ +static long cvt_e_d( + epicsEnum16 *from, + epicsFloat64 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Enumerated to Enumerated */ +static long cvt_e_e( + epicsEnum16 *from, + epicsEnum16 *to, + const dbAddr *paddr) + { *to=*from; return(0); } + +/* Convert Choices And Enumerated Types To String ... */ + +/* Get Enumerated to String */ +static long cvt_e_st_get( + epicsEnum16 *from, + char *to, + const dbAddr *paddr) + { + rset *prset = 0; + long status; + + if(paddr) prset = dbGetRset(paddr); + + if (prset && prset->get_enum_str) + return (*prset->get_enum_str)(paddr, to); + + status = S_db_noRSET; + recGblRecSupError(status, paddr, "dbGetField", "get_enum_str"); + + return(S_db_badDbrtype); + } + +/* Put Enumerated to String */ +static long cvt_e_st_put( + epicsEnum16 *from, + char *to, + const dbAddr *paddr) + { cvtUshortToString(*from, to); return(0); } + +/* Get Menu to String */ +static long cvt_menu_st( + epicsEnum16 *from, + char *to, + const dbAddr *paddr) + { + dbFldDes *pdbFldDes; + dbMenu *pdbMenu; + char **papChoiceValue; + char *pchoice; + + if(! paddr + || !(pdbFldDes = paddr->pfldDes) + || !(pdbMenu = (dbMenu *)pdbFldDes->ftPvt) + || *from>=pdbMenu->nChoice + || !(papChoiceValue = pdbMenu->papChoiceValue) + || !(pchoice=papChoiceValue[*from])) { + recGblDbaddrError(S_db_badChoice,paddr,"dbFastLinkConv(cvt_menu_st)"); + return(S_db_badChoice); + } + strncpy(to,pchoice,MAX_STRING_SIZE); + return(0); + } + + +/* Get Device to String */ +static long cvt_device_st( + epicsEnum16 *from, + char *to, + const dbAddr *paddr) + { + dbFldDes *pdbFldDes; + dbDeviceMenu *pdbDeviceMenu; + char **papChoice; + char *pchoice; + + if(!paddr + || !(pdbFldDes = paddr->pfldDes) + || !(pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt) + || *from>=pdbDeviceMenu->nChoice + || !(papChoice= pdbDeviceMenu->papChoice) + || !(pchoice=papChoice[*from])) { + recGblDbaddrError(S_db_badChoice,paddr,"dbFastLinkConv(cvt_device_st)"); + return(S_db_badChoice); + } + strncpy(to,pchoice,MAX_STRING_SIZE); + return(0); + } + +/* + * Get conversion routine lookup table + * + * Converts type X to ... + * + * DBR_STRING, DBR_CHR, DBR_UCHAR, DBR_SHORT, DBR_USHORT, + * DBR_LONG, DBR_ULONG, DBR_INT64, DBR_UINT64, DBR_FLOAT, DBR_DOUBLE, DBR_ENUM + * + * NULL implies the conversion is not supported. + */ + +epicsShareDef long (*dbFastGetConvertRoutine[DBF_DEVICE+1][DBR_ENUM+1])() = { + + /* Convert DBF_STRING to ... */ +{ cvt_st_st, cvt_st_c, cvt_st_uc, cvt_st_s, cvt_st_us, cvt_st_l, cvt_st_ul, cvt_st_q, cvt_st_uq, cvt_st_f, cvt_st_d, cvt_st_e }, + + /* Convert DBF_CHAR to ... */ +{ cvt_c_st, cvt_c_c, cvt_c_uc, cvt_c_s, cvt_c_us, cvt_c_l, cvt_c_ul, cvt_c_q, cvt_c_uq, cvt_c_f, cvt_c_d, cvt_c_e }, + + /* Convert DBF_UCHAR to ... */ +{ cvt_uc_st, cvt_uc_c, cvt_uc_uc, cvt_uc_s, cvt_uc_us, cvt_uc_l, cvt_uc_ul, cvt_uc_q, cvt_uc_uq, cvt_uc_f, cvt_uc_d, cvt_uc_e }, + + /* Convert DBF_SHORT to ... */ +{ cvt_s_st, cvt_s_c, cvt_s_uc, cvt_s_s, cvt_s_us, cvt_s_l, cvt_s_ul, cvt_s_q, cvt_s_uq, cvt_s_f, cvt_s_d, cvt_s_e }, + + /* Convert DBF_USHORT to ... */ +{ cvt_us_st, cvt_us_c, cvt_us_uc, cvt_us_s, cvt_us_us, cvt_us_l, cvt_us_ul, cvt_us_q, cvt_us_uq, cvt_us_f, cvt_us_d, cvt_us_e }, + + /* Convert DBF_LONG to ... */ +{ cvt_l_st, cvt_l_c, cvt_l_uc, cvt_l_s, cvt_l_us, cvt_l_l, cvt_l_ul, cvt_l_q, cvt_l_uq, cvt_l_f, cvt_l_d, cvt_l_e }, + + /* Convert DBF_ULONG to ... */ +{ cvt_ul_st, cvt_ul_c, cvt_ul_uc, cvt_ul_s, cvt_ul_us, cvt_ul_l, cvt_ul_ul, cvt_ul_q, cvt_ul_uq, cvt_ul_f, cvt_ul_d, cvt_ul_e }, + + /* Convert DBF_INT64 to ... */ +{ cvt_q_st, cvt_q_c, cvt_q_uc, cvt_q_s, cvt_q_us, cvt_q_l, cvt_q_ul, cvt_q_q, cvt_q_uq, cvt_q_f, cvt_q_d, cvt_q_e }, + + /* Convert DBF_UINT64 to ... */ +{ cvt_uq_st, cvt_uq_c, cvt_uq_uc, cvt_uq_s, cvt_uq_us, cvt_uq_l, cvt_uq_ul, cvt_uq_q, cvt_uq_uq, cvt_uq_f, cvt_uq_d, cvt_uq_e }, + + /* Convert DBF_FLOAT to ... */ +{ cvt_f_st, cvt_f_c, cvt_f_uc, cvt_f_s, cvt_f_us, cvt_f_l, cvt_f_ul, cvt_f_q, cvt_f_uq, cvt_f_f, cvt_f_d, cvt_f_e }, + + /* Convert DBF_DOUBLE to ... */ +{ cvt_d_st, cvt_d_c, cvt_d_uc, cvt_d_s, cvt_d_us, cvt_d_l, cvt_d_ul, cvt_d_q, cvt_d_uq, cvt_d_f, cvt_d_d, cvt_d_e }, + + /* Convert DBF_ENUM to ... */ +{ cvt_e_st_get, cvt_e_c, cvt_e_uc, cvt_e_s, cvt_e_us, cvt_e_l, cvt_e_ul, cvt_e_q, cvt_e_uq, cvt_e_f, cvt_e_d, cvt_e_e }, + + /* Convert DBF_MENU to ... */ +{ cvt_menu_st, cvt_e_c, cvt_e_uc, cvt_e_s, cvt_e_us, cvt_e_l, cvt_e_ul, cvt_e_q, cvt_e_uq, cvt_e_f, cvt_e_d, cvt_e_e }, + + /* Convert DBF_DEVICE to ... */ +{ cvt_device_st, cvt_e_c, cvt_e_uc, cvt_e_s, cvt_e_us, cvt_e_l, cvt_e_ul, cvt_e_q, cvt_e_uq, cvt_e_f, cvt_e_d, cvt_e_e } }; + +/* + * Put conversion routine lookup table + * + * Converts type X to ... + * + * DBF_STRING DBF_CHAR DBF_UCHAR DBF_SHORT DBF_USHORT + * DBF_LONG DBF_ULONG DBF_INT64 DBF_UINT64 DBF_FLOAT DBF_DOUBLE DBF_ENUM + * DBF_MENU DBF_DEVICE + * + * NULL implies the conversion is not supported. + */ + +epicsShareDef long (*dbFastPutConvertRoutine[DBR_ENUM+1][DBF_DEVICE+1])() = { + + /* Convert DBR_STRING to ... */ +{ cvt_st_st, cvt_st_c, cvt_st_uc, cvt_st_s, cvt_st_us, cvt_st_l, cvt_st_ul, cvt_st_q, cvt_st_uq, cvt_st_f, cvt_st_d, cvt_st_e, cvt_st_menu, cvt_st_device}, + + /* Convert DBR_CHAR to ... */ +{ cvt_c_st, cvt_c_c, cvt_c_uc, cvt_c_s, cvt_c_us, cvt_c_l, cvt_c_ul, cvt_c_q, cvt_c_uq, cvt_c_f, cvt_c_d, cvt_c_e, cvt_c_e, cvt_c_e}, + + /* Convert DBR_UCHAR to ... */ +{ cvt_uc_st, cvt_uc_c, cvt_uc_uc, cvt_uc_s, cvt_uc_us, cvt_uc_l, cvt_uc_ul, cvt_uc_q, cvt_uc_uq, cvt_uc_f, cvt_uc_d, cvt_uc_e, cvt_uc_e, cvt_uc_e}, + + /* Convert DBR_SHORT to ... */ +{ cvt_s_st, cvt_s_c, cvt_s_uc, cvt_s_s, cvt_s_us, cvt_s_l, cvt_s_ul, cvt_s_q, cvt_s_uq, cvt_s_f, cvt_s_d, cvt_s_e, cvt_s_e, cvt_s_e}, + + /* Convert DBR_USHORT to ... */ +{ cvt_us_st, cvt_us_c, cvt_us_uc, cvt_us_s, cvt_us_us, cvt_us_l, cvt_us_ul, cvt_us_q, cvt_us_uq, cvt_us_f, cvt_us_d, cvt_us_e, cvt_us_e, cvt_us_e}, + + /* Convert DBR_LONG to ... */ +{ cvt_l_st, cvt_l_c, cvt_l_uc, cvt_l_s, cvt_l_us, cvt_l_l, cvt_l_ul, cvt_l_q, cvt_l_uq, cvt_l_f, cvt_l_d, cvt_l_e, cvt_l_e, cvt_l_e}, + + /* Convert DBR_ULONG to ... */ +{ cvt_ul_st, cvt_ul_c, cvt_ul_uc, cvt_ul_s, cvt_ul_us, cvt_ul_l, cvt_ul_ul, cvt_ul_q, cvt_ul_uq, cvt_ul_f, cvt_ul_d, cvt_ul_e, cvt_ul_e, cvt_ul_e}, + + /* Convert DBR_INT64 to ... */ +{ cvt_q_st, cvt_q_c, cvt_q_uc, cvt_q_s, cvt_q_us, cvt_q_l, cvt_q_ul, cvt_q_q, cvt_q_uq, cvt_q_f, cvt_q_d, cvt_q_e, cvt_q_e, cvt_q_e}, + + /* Convert DBR_UINT64 to ... */ +{ cvt_uq_st, cvt_uq_c, cvt_uq_uc, cvt_uq_s, cvt_uq_us, cvt_uq_l, cvt_uq_ul, cvt_uq_q, cvt_uq_uq, cvt_uq_f, cvt_uq_d, cvt_uq_e, cvt_uq_e, cvt_uq_e}, + + /* Convert DBR_FLOAT to ... */ +{ cvt_f_st, cvt_f_c, cvt_f_uc, cvt_f_s, cvt_f_us, cvt_f_l, cvt_f_ul, cvt_f_q, cvt_f_uq, cvt_f_f, cvt_f_d, cvt_f_e, cvt_f_e, cvt_f_e}, + + /* Convert DBR_DOUBLE to ... */ +{ cvt_d_st, cvt_d_c, cvt_d_uc, cvt_d_s, cvt_d_us, cvt_d_l, cvt_d_ul, cvt_d_q, cvt_d_uq, cvt_d_f, cvt_d_d, cvt_d_e, cvt_d_e, cvt_d_e}, + + /* Convert DBR_ENUM to ... */ +{ cvt_e_st_put, cvt_e_c, cvt_e_uc, cvt_e_s, cvt_e_us, cvt_e_l, cvt_e_ul, cvt_e_q, cvt_e_uq, cvt_e_f, cvt_e_d, cvt_e_e, cvt_e_e, cvt_e_e} }; + diff --git a/modules/database/src/ioc/db/dbIocRegister.c b/modules/database/src/ioc/db/dbIocRegister.c new file mode 100644 index 000000000..07c9836c2 --- /dev/null +++ b/modules/database/src/ioc/db/dbIocRegister.c @@ -0,0 +1,453 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "callback.h" +#include "dbAccess.h" +#include "dbBkpt.h" +#include "dbCaTest.h" +#include "dbEvent.h" +#include "dbIocRegister.h" +#include "dbJLink.h" +#include "dbLock.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "dbServer.h" +#include "dbState.h" +#include "db_test.h" +#include "dbTest.h" + +epicsShareExtern int callbackParallelThreadsDefault; + +/* dbLoadDatabase */ +static const iocshArg dbLoadDatabaseArg0 = { "file name",iocshArgString}; +static const iocshArg dbLoadDatabaseArg1 = { "path",iocshArgString}; +static const iocshArg dbLoadDatabaseArg2 = { "substitutions",iocshArgString}; +static const iocshArg * const dbLoadDatabaseArgs[3] = +{ + &dbLoadDatabaseArg0,&dbLoadDatabaseArg1,&dbLoadDatabaseArg2 +}; +static const iocshFuncDef dbLoadDatabaseFuncDef = + {"dbLoadDatabase",3,dbLoadDatabaseArgs}; +static void dbLoadDatabaseCallFunc(const iocshArgBuf *args) +{ + dbLoadDatabase(args[0].sval,args[1].sval,args[2].sval); +} + +/* dbLoadRecords */ +static const iocshArg dbLoadRecordsArg0 = { "file name",iocshArgString}; +static const iocshArg dbLoadRecordsArg1 = { "substitutions",iocshArgString}; +static const iocshArg * const dbLoadRecordsArgs[2] = {&dbLoadRecordsArg0,&dbLoadRecordsArg1}; +static const iocshFuncDef dbLoadRecordsFuncDef = {"dbLoadRecords",2,dbLoadRecordsArgs}; +static void dbLoadRecordsCallFunc(const iocshArgBuf *args) +{ + dbLoadRecords(args[0].sval,args[1].sval); +} + +/* dbb */ +static const iocshArg dbbArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbbArgs[1] = {&dbbArg0}; +static const iocshFuncDef dbbFuncDef = {"dbb",1,dbbArgs}; +static void dbbCallFunc(const iocshArgBuf *args) { dbb(args[0].sval);} + +/* dbd */ +static const iocshArg dbdArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbdArgs[1] = {&dbdArg0}; +static const iocshFuncDef dbdFuncDef = {"dbd",1,dbdArgs}; +static void dbdCallFunc(const iocshArgBuf *args) { dbd(args[0].sval);} + +/* dbc */ +static const iocshArg dbcArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbcArgs[1] = {&dbcArg0}; +static const iocshFuncDef dbcFuncDef = {"dbc",1,dbcArgs}; +static void dbcCallFunc(const iocshArgBuf *args) { dbc(args[0].sval);} + +/* dbs */ +static const iocshArg dbsArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbsArgs[1] = {&dbsArg0}; +static const iocshFuncDef dbsFuncDef = {"dbs",1,dbsArgs}; +static void dbsCallFunc(const iocshArgBuf *args) { dbs(args[0].sval);} + +/* dbstat */ +static const iocshFuncDef dbstatFuncDef = {"dbstat",0}; +static void dbstatCallFunc(const iocshArgBuf *args) { dbstat();} + +/* dbp */ +static const iocshArg dbpArg0 = { "record name",iocshArgString}; +static const iocshArg dbpArg1 = { "interest level",iocshArgInt}; +static const iocshArg * const dbpArgs[2] = {&dbpArg0,&dbpArg1}; +static const iocshFuncDef dbpFuncDef = {"dbp",2,dbpArgs}; +static void dbpCallFunc(const iocshArgBuf *args) +{ dbp(args[0].sval,args[1].ival);} + +/* dbap */ +static const iocshArg dbapArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbapArgs[1] = {&dbapArg0}; +static const iocshFuncDef dbapFuncDef = {"dbap",1,dbapArgs}; +static void dbapCallFunc(const iocshArgBuf *args) { dbap(args[0].sval);} + +/* dbsr */ +static const iocshArg dbsrArg0 = { "interest level",iocshArgInt}; +static const iocshArg * const dbsrArgs[1] = {&dbsrArg0}; +static const iocshFuncDef dbsrFuncDef = {"dbsr",1,dbsrArgs}; +static void dbsrCallFunc(const iocshArgBuf *args) { dbsr(args[0].ival);} + +/* dbcar */ +static const iocshArg dbcarArg0 = { "record name",iocshArgString}; +static const iocshArg dbcarArg1 = { "level",iocshArgInt}; +static const iocshArg * const dbcarArgs[2] = {&dbcarArg0,&dbcarArg1}; +static const iocshFuncDef dbcarFuncDef = {"dbcar",2,dbcarArgs}; +static void dbcarCallFunc(const iocshArgBuf *args) +{ + dbcar(args[0].sval,args[1].ival); +} + +/* dbjlr */ +static const iocshArg dbjlrArg0 = { "record name",iocshArgString}; +static const iocshArg dbjlrArg1 = { "level",iocshArgInt}; +static const iocshArg * const dbjlrArgs[2] = {&dbjlrArg0,&dbjlrArg1}; +static const iocshFuncDef dbjlrFuncDef = {"dbjlr",2,dbjlrArgs}; +static void dbjlrCallFunc(const iocshArgBuf *args) +{ + dbjlr(args[0].sval,args[1].ival); +} + +/* dbel */ +static const iocshArg dbelArg0 = { "record name",iocshArgString}; +static const iocshArg dbelArg1 = { "level",iocshArgInt}; +static const iocshArg * const dbelArgs[2] = {&dbelArg0,&dbelArg1}; +static const iocshFuncDef dbelFuncDef = {"dbel",2,dbelArgs}; +static void dbelCallFunc(const iocshArgBuf *args) +{ + dbel(args[0].sval, args[1].ival); +} + +/* dba */ +static const iocshArg dbaArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbaArgs[1] = {&dbaArg0}; +static const iocshFuncDef dbaFuncDef = {"dba",1,dbaArgs}; +static void dbaCallFunc(const iocshArgBuf *args) { dba(args[0].sval);} + +/* dbl */ +static const iocshArg dblArg0 = { "record type",iocshArgString}; +static const iocshArg dblArg1 = { "fields",iocshArgString}; +static const iocshArg * const dblArgs[] = {&dblArg0,&dblArg1}; +static const iocshFuncDef dblFuncDef = {"dbl",2,dblArgs}; +static void dblCallFunc(const iocshArgBuf *args) +{ + dbl(args[0].sval,args[1].sval); +} + +/* dbnr */ +static const iocshArg dbnrArg0 = { "verbose",iocshArgInt}; +static const iocshArg * const dbnrArgs[1] = {&dbnrArg0}; +static const iocshFuncDef dbnrFuncDef = {"dbnr",1,dbnrArgs}; +static void dbnrCallFunc(const iocshArgBuf *args) { dbnr(args[0].ival);} + +/* dbla */ +static const iocshArg dblaArg0 = { "pattern",iocshArgString}; +static const iocshArg * const dblaArgs[1] = {&dblaArg0}; +static const iocshFuncDef dblaFuncDef = {"dbla",1,dblaArgs}; +static void dblaCallFunc(const iocshArgBuf *args) { dbla(args[0].sval);} + +/* dbgrep */ +static const iocshArg dbgrepArg0 = { "pattern",iocshArgString}; +static const iocshArg * const dbgrepArgs[1] = {&dbgrepArg0}; +static const iocshFuncDef dbgrepFuncDef = {"dbgrep",1,dbgrepArgs}; +static void dbgrepCallFunc(const iocshArgBuf *args) { dbgrep(args[0].sval);} + +/* dbgf */ +static const iocshArg dbgfArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbgfArgs[1] = {&dbgfArg0}; +static const iocshFuncDef dbgfFuncDef = {"dbgf",1,dbgfArgs}; +static void dbgfCallFunc(const iocshArgBuf *args) { dbgf(args[0].sval);} + +/* dbpf */ +static const iocshArg dbpfArg0 = { "record name",iocshArgString}; +static const iocshArg dbpfArg1 = { "value",iocshArgString}; +static const iocshArg * const dbpfArgs[2] = {&dbpfArg0,&dbpfArg1}; +static const iocshFuncDef dbpfFuncDef = {"dbpf",2,dbpfArgs}; +static void dbpfCallFunc(const iocshArgBuf *args) +{ dbpf(args[0].sval,args[1].sval);} + +/* dbpr */ +static const iocshArg dbprArg0 = { "record name",iocshArgString}; +static const iocshArg dbprArg1 = { "interest level",iocshArgInt}; +static const iocshArg * const dbprArgs[2] = {&dbprArg0,&dbprArg1}; +static const iocshFuncDef dbprFuncDef = {"dbpr",2,dbprArgs}; +static void dbprCallFunc(const iocshArgBuf *args) +{ dbpr(args[0].sval,args[1].ival);} + +/* dbtr */ +static const iocshArg dbtrArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbtrArgs[1] = {&dbtrArg0}; +static const iocshFuncDef dbtrFuncDef = {"dbtr",1,dbtrArgs}; +static void dbtrCallFunc(const iocshArgBuf *args) { dbtr(args[0].sval);} + +/* dbtgf */ +static const iocshArg dbtgfArg0 = { "record name",iocshArgString}; +static const iocshArg * const dbtgfArgs[1] = {&dbtgfArg0}; +static const iocshFuncDef dbtgfFuncDef = {"dbtgf",1,dbtgfArgs}; +static void dbtgfCallFunc(const iocshArgBuf *args) { dbtgf(args[0].sval);} + +/* dbtpf */ +static const iocshArg dbtpfArg0 = { "record name",iocshArgString}; +static const iocshArg dbtpfArg1 = { "value",iocshArgString}; +static const iocshArg * const dbtpfArgs[2] = {&dbtpfArg0,&dbtpfArg1}; +static const iocshFuncDef dbtpfFuncDef = {"dbtpf",2,dbtpfArgs}; +static void dbtpfCallFunc(const iocshArgBuf *args) +{ dbtpf(args[0].sval,args[1].sval);} + +/* dbior */ +static const iocshArg dbiorArg0 = { "driver name",iocshArgString}; +static const iocshArg dbiorArg1 = { "interest level",iocshArgInt}; +static const iocshArg * const dbiorArgs[] = {&dbiorArg0,&dbiorArg1}; +static const iocshFuncDef dbiorFuncDef = {"dbior",2,dbiorArgs}; +static void dbiorCallFunc(const iocshArgBuf *args) +{ dbior(args[0].sval,args[1].ival);} + +/* dbhcr */ +static const iocshFuncDef dbhcrFuncDef = {"dbhcr",0,0}; +static void dbhcrCallFunc(const iocshArgBuf *args) { dbhcr();} + +/* gft */ +static const iocshArg gftArg0 = { "record name",iocshArgString}; +static const iocshArg * const gftArgs[1] = {&gftArg0}; +static const iocshFuncDef gftFuncDef = {"gft",1,gftArgs}; +static void gftCallFunc(const iocshArgBuf *args) { gft(args[0].sval);} + +/* pft */ +static const iocshArg pftArg0 = { "record name",iocshArgString}; +static const iocshArg pftArg1 = { "value",iocshArgString}; +static const iocshArg * const pftArgs[2] = {&pftArg0,&pftArg1}; +static const iocshFuncDef pftFuncDef = {"pft",2,pftArgs}; +static void pftCallFunc(const iocshArgBuf *args) +{ pft(args[0].sval,args[1].sval);} + +/* dbtpn */ +static const iocshArg dbtpnArg0 = { "record name",iocshArgString}; +static const iocshArg dbtpnArg1 = { "value",iocshArgString}; +static const iocshArg * const dbtpnArgs[2] = {&dbtpnArg0,&dbtpnArg1}; +static const iocshFuncDef dbtpnFuncDef = {"dbtpn",2,dbtpnArgs}; +static void dbtpnCallFunc(const iocshArgBuf *args) +{ dbtpn(args[0].sval,args[1].sval);} + +/* dbNotifyDump */ +static const iocshFuncDef dbNotifyDumpFuncDef = {"dbNotifyDump",0,0}; +static void dbNotifyDumpCallFunc(const iocshArgBuf *args) { dbNotifyDump();} + +/* dbPutAttribute */ +static const iocshArg dbPutAttrArg0 = { "record type",iocshArgString}; +static const iocshArg dbPutAttrArg1 = { "attribute name",iocshArgString}; +static const iocshArg dbPutAttrArg2 = { "value",iocshArgString}; +static const iocshArg * const dbPutAttrArgs[] = + {&dbPutAttrArg0, &dbPutAttrArg1, &dbPutAttrArg2}; +static const iocshFuncDef dbPutAttrFuncDef = + {"dbPutAttribute",3,dbPutAttrArgs}; +static void dbPutAttrCallFunc(const iocshArgBuf *args) +{ dbPutAttribute(args[0].sval,args[1].sval,args[2].sval);} + +/* tpn */ +static const iocshArg tpnArg0 = { "record name",iocshArgString}; +static const iocshArg tpnArg1 = { "value",iocshArgString}; +static const iocshArg * const tpnArgs[2] = {&tpnArg0,&tpnArg1}; +static const iocshFuncDef tpnFuncDef = {"tpn",2,tpnArgs}; +static void tpnCallFunc(const iocshArgBuf *args) +{ tpn(args[0].sval,args[1].sval);} + +/* dblsr */ +static const iocshArg dblsrArg0 = { "record name",iocshArgString}; +static const iocshArg dblsrArg1 = { "interest level",iocshArgInt}; +static const iocshArg * const dblsrArgs[2] = {&dblsrArg0,&dblsrArg1}; +static const iocshFuncDef dblsrFuncDef = {"dblsr",2,dblsrArgs}; +static void dblsrCallFunc(const iocshArgBuf *args) +{ dblsr(args[0].sval,args[1].ival);} + +/* dbLockShowLocked */ +static const iocshArg dbLockShowLockedArg0 = { "interest level",iocshArgInt}; +static const iocshArg * const dbLockShowLockedArgs[1] = {&dbLockShowLockedArg0}; +static const iocshFuncDef dbLockShowLockedFuncDef = + {"dbLockShowLocked",1,dbLockShowLockedArgs}; +static void dbLockShowLockedCallFunc(const iocshArgBuf *args) +{ dbLockShowLocked(args[0].ival);} + +/* scanOnceSetQueueSize */ +static const iocshArg scanOnceSetQueueSizeArg0 = { "size",iocshArgInt}; +static const iocshArg * const scanOnceSetQueueSizeArgs[1] = + {&scanOnceSetQueueSizeArg0}; +static const iocshFuncDef scanOnceSetQueueSizeFuncDef = + {"scanOnceSetQueueSize",1,scanOnceSetQueueSizeArgs}; +static void scanOnceSetQueueSizeCallFunc(const iocshArgBuf *args) +{ + scanOnceSetQueueSize(args[0].ival); +} + +/* scanppl */ +static const iocshArg scanpplArg0 = { "rate",iocshArgDouble}; +static const iocshArg * const scanpplArgs[1] = {&scanpplArg0}; +static const iocshFuncDef scanpplFuncDef = {"scanppl",1,scanpplArgs}; +static void scanpplCallFunc(const iocshArgBuf *args) +{ scanppl(args[0].dval);} + +/* scanpel */ +static const iocshArg scanpelArg0 = { "event name",iocshArgString}; +static const iocshArg * const scanpelArgs[1] = {&scanpelArg0}; +static const iocshFuncDef scanpelFuncDef = {"scanpel",1,scanpelArgs}; +static void scanpelCallFunc(const iocshArgBuf *args) +{ scanpel(args[0].sval);} + +/* postEvent */ +static const iocshArg postEventArg0 = { "event name",iocshArgString}; +static const iocshArg * const postEventArgs[1] = {&postEventArg0}; +static const iocshFuncDef postEventFuncDef = {"postEvent",1,postEventArgs}; +static void postEventCallFunc(const iocshArgBuf *args) +{ + EVENTPVT pel = eventNameToHandle(args[0].sval); + postEvent(pel); +} + +/* scanpiol */ +static const iocshFuncDef scanpiolFuncDef = {"scanpiol",0}; +static void scanpiolCallFunc(const iocshArgBuf *args) { scanpiol();} + +/* callbackSetQueueSize */ +static const iocshArg callbackSetQueueSizeArg0 = { "bufsize",iocshArgInt}; +static const iocshArg * const callbackSetQueueSizeArgs[1] = + {&callbackSetQueueSizeArg0}; +static const iocshFuncDef callbackSetQueueSizeFuncDef = + {"callbackSetQueueSize",1,callbackSetQueueSizeArgs}; +static void callbackSetQueueSizeCallFunc(const iocshArgBuf *args) +{ + callbackSetQueueSize(args[0].ival); +} + +/* callbackParallelThreads */ +static const iocshArg callbackParallelThreadsArg0 = { "no of threads", iocshArgInt}; +static const iocshArg callbackParallelThreadsArg1 = { "priority", iocshArgString}; +static const iocshArg * const callbackParallelThreadsArgs[2] = + {&callbackParallelThreadsArg0,&callbackParallelThreadsArg1}; +static const iocshFuncDef callbackParallelThreadsFuncDef = + {"callbackParallelThreads",2,callbackParallelThreadsArgs}; +static void callbackParallelThreadsCallFunc(const iocshArgBuf *args) +{ + callbackParallelThreads(args[0].ival, args[1].sval); +} + +/* dbStateCreate */ +static const iocshArg dbStateArgName = { "name", iocshArgString }; +static const iocshArg * const dbStateCreateArgs[] = { &dbStateArgName }; +static const iocshFuncDef dbStateCreateFuncDef = { "dbStateCreate", 1, dbStateCreateArgs }; +static void dbStateCreateCallFunc (const iocshArgBuf *args) +{ + dbStateCreate(args[0].sval); +} + +/* dbStateSet */ +static const iocshArg * const dbStateSetArgs[] = { &dbStateArgName }; +static const iocshFuncDef dbStateSetFuncDef = { "dbStateSet", 1, dbStateSetArgs }; +static void dbStateSetCallFunc (const iocshArgBuf *args) +{ + dbStateId sid = dbStateFind(args[0].sval); + + if (sid) + dbStateSet(sid); +} + +/* dbStateClear */ +static const iocshArg * const dbStateClearArgs[] = { &dbStateArgName }; +static const iocshFuncDef dbStateClearFuncDef = { "dbStateClear", 1, dbStateClearArgs }; +static void dbStateClearCallFunc (const iocshArgBuf *args) +{ + dbStateId sid = dbStateFind(args[0].sval); + + if (sid) + dbStateClear(sid); +} + +/* dbStateShow */ +static const iocshArg dbStateShowArg1 = { "level", iocshArgInt }; +static const iocshArg * const dbStateShowArgs[] = { &dbStateArgName, &dbStateShowArg1 }; +static const iocshFuncDef dbStateShowFuncDef = { "dbStateShow", 2, dbStateShowArgs }; +static void dbStateShowCallFunc (const iocshArgBuf *args) +{ + dbStateId sid = dbStateFind(args[0].sval); + + if (sid) + dbStateShow(sid, args[1].ival); +} + +/* dbStateShowAll */ +static const iocshArg dbStateShowAllArg0 = { "level", iocshArgInt }; +static const iocshArg * const dbStateShowAllArgs[] = { &dbStateShowAllArg0 }; +static const iocshFuncDef dbStateShowAllFuncDef = { "dbStateShowAll", 1, dbStateShowAllArgs }; +static void dbStateShowAllCallFunc (const iocshArgBuf *args) +{ + dbStateShowAll(args[0].ival); +} + +void dbIocRegister(void) +{ + iocshRegister(&dbbFuncDef,dbbCallFunc); + iocshRegister(&dbdFuncDef,dbdCallFunc); + iocshRegister(&dbcFuncDef,dbcCallFunc); + iocshRegister(&dbsFuncDef,dbsCallFunc); + iocshRegister(&dbstatFuncDef,dbstatCallFunc); + iocshRegister(&dbpFuncDef,dbpCallFunc); + iocshRegister(&dbapFuncDef,dbapCallFunc); + + iocshRegister(&dbsrFuncDef,dbsrCallFunc); + iocshRegister(&dbcarFuncDef,dbcarCallFunc); + iocshRegister(&dbelFuncDef,dbelCallFunc); + iocshRegister(&dbjlrFuncDef,dbjlrCallFunc); + + iocshRegister(&dbLoadDatabaseFuncDef,dbLoadDatabaseCallFunc); + iocshRegister(&dbLoadRecordsFuncDef,dbLoadRecordsCallFunc); + + iocshRegister(&dbaFuncDef,dbaCallFunc); + iocshRegister(&dblFuncDef,dblCallFunc); + iocshRegister(&dbnrFuncDef,dbnrCallFunc); + iocshRegister(&dblaFuncDef,dblaCallFunc); + iocshRegister(&dbgrepFuncDef,dbgrepCallFunc); + iocshRegister(&dbgfFuncDef,dbgfCallFunc); + iocshRegister(&dbpfFuncDef,dbpfCallFunc); + iocshRegister(&dbprFuncDef,dbprCallFunc); + iocshRegister(&dbtrFuncDef,dbtrCallFunc); + iocshRegister(&dbtgfFuncDef,dbtgfCallFunc); + iocshRegister(&dbtpfFuncDef,dbtpfCallFunc); + iocshRegister(&dbiorFuncDef,dbiorCallFunc); + iocshRegister(&dbhcrFuncDef,dbhcrCallFunc); + iocshRegister(&gftFuncDef,gftCallFunc); + iocshRegister(&pftFuncDef,pftCallFunc); + iocshRegister(&dbtpnFuncDef,dbtpnCallFunc); + iocshRegister(&dbNotifyDumpFuncDef,dbNotifyDumpCallFunc); + iocshRegister(&dbPutAttrFuncDef,dbPutAttrCallFunc); + iocshRegister(&tpnFuncDef,tpnCallFunc); + iocshRegister(&dblsrFuncDef,dblsrCallFunc); + iocshRegister(&dbLockShowLockedFuncDef,dbLockShowLockedCallFunc); + + iocshRegister(&scanOnceSetQueueSizeFuncDef,scanOnceSetQueueSizeCallFunc); + iocshRegister(&scanpplFuncDef,scanpplCallFunc); + iocshRegister(&scanpelFuncDef,scanpelCallFunc); + iocshRegister(&postEventFuncDef,postEventCallFunc); + iocshRegister(&scanpiolFuncDef,scanpiolCallFunc); + + iocshRegister(&callbackSetQueueSizeFuncDef,callbackSetQueueSizeCallFunc); + iocshRegister(&callbackParallelThreadsFuncDef,callbackParallelThreadsCallFunc); + + /* Needed before callback system is initialized */ + callbackParallelThreadsDefault = epicsThreadGetCPUs(); + + iocshRegister(&dbStateCreateFuncDef, dbStateCreateCallFunc); + iocshRegister(&dbStateSetFuncDef, dbStateSetCallFunc); + iocshRegister(&dbStateClearFuncDef, dbStateClearCallFunc); + iocshRegister(&dbStateShowFuncDef, dbStateShowCallFunc); + iocshRegister(&dbStateShowAllFuncDef, dbStateShowAllCallFunc); +} diff --git a/modules/database/src/ioc/db/dbIocRegister.h b/modules/database/src/ioc/db/dbIocRegister.h new file mode 100644 index 000000000..973009c5b --- /dev/null +++ b/modules/database/src/ioc/db/dbIocRegister.h @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbIocRegister_H +#define INC_dbIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void dbIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbIocRegister_H */ diff --git a/modules/database/src/ioc/db/dbJLink.c b/modules/database/src/ioc/db/dbJLink.c new file mode 100644 index 000000000..ea054eee9 --- /dev/null +++ b/modules/database/src/ioc/db/dbJLink.c @@ -0,0 +1,538 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbJLink.c */ + +#include +#include + +#include "epicsAssert.h" +#include "dbmf.h" +#include "errlog.h" +#include "yajl_alloc.h" +#include "yajl_parse.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbCommon.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "dbLink.h" +#include "dbJLink.h" +#include "dbLock.h" +#include "dbStaticLib.h" +#include "link.h" + +#define IFDEBUG(n) if(parser->parse_debug) + +typedef struct parseContext { + jlink *pjlink; + jlink *product; + short dbfType; + short jsonDepth; + unsigned key_is_link:1; + unsigned parse_debug:1; + unsigned lset_debug:1; +} parseContext; + +#define CALL_OR_STOP(routine) !(routine) ? jlif_stop : (routine) + +static int dbjl_return(parseContext *parser, jlif_result result) { + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_return(%s@%p, %d)\t", pjlink ? pjlink->pif->name : "", pjlink, result); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + if (result == jlif_stop && pjlink) { + jlink *parent; + + while ((parent = pjlink->parent)) { + pjlink->pif->free_jlink(pjlink); + pjlink = parent; + } + pjlink->pif->free_jlink(pjlink); + } + + return result; +} + +static int dbjl_value(parseContext *parser, jlif_result result) { + jlink *pjlink = parser->pjlink; + jlink *parent; + + IFDEBUG(10) { + printf("dbjl_value(%s@%p, %d)\t", pjlink ? pjlink->pif->name : "", pjlink, result); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + if (result == jlif_stop || pjlink->parseDepth > 0) + return dbjl_return(parser, result); + + parent = pjlink->parent; + if (!parent) { + parser->product = pjlink; + } else if (parent->pif->end_child) { + parent->pif->end_child(parent, pjlink); + } + pjlink->debug = 0; + + parser->pjlink = parent; + + IFDEBUG(8) + printf("dbjl_value: product = %p\n", pjlink); + + return jlif_continue; +} + +static int dbjl_null(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_null(%s@%p)\n", pjlink ? pjlink->pif->name : "", pjlink); + + assert(pjlink); + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_null)(pjlink)); +} + +static int dbjl_boolean(void *ctx, int val) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + assert(pjlink); + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_boolean)(pjlink, val)); +} + +static int dbjl_integer(void *ctx, long long num) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_integer(%s@%p, %lld)\n", + pjlink->pif->name, pjlink, num); + + assert(pjlink); + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_integer)(pjlink, num)); +} + +static int dbjl_double(void *ctx, double num) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_double(%s@%p, %g)\n", + pjlink->pif->name, pjlink, num); + + assert(pjlink); + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_double)(pjlink, num)); +} + +static int dbjl_string(void *ctx, const unsigned char *val, size_t len) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) + printf("dbjl_string(%s@%p, \"%.*s\")\n", + pjlink->pif->name, pjlink, (int) len, val); + + assert(pjlink); + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_string)(pjlink, (const char *) val, len)); +} + +static int dbjl_start_map(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + int result; + + if (!pjlink) { + IFDEBUG(10) { + printf("dbjl_start_map(NULL)\t"); + printf(" jsonDepth=%d, parseDepth=00, key_is_link=%d\n", + parser->jsonDepth, parser->key_is_link); + } + + assert(parser->jsonDepth == 0); + parser->jsonDepth++; + parser->key_is_link = 1; + return jlif_continue; /* Opening '{' */ + } + + IFDEBUG(10) { + printf("dbjl_start_map(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + pjlink->parseDepth++; + parser->jsonDepth++; + + result = CALL_OR_STOP(pjlink->pif->parse_start_map)(pjlink); + if (result == jlif_key_child_link) { + parser->key_is_link = 1; + result = jlif_continue; + } + + IFDEBUG(10) + printf("dbjl_start_map -> %d\n", result); + + return dbjl_return(parser, result); +} + +static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + char *link_name; + linkSup *linkSup; + jlif *pjlif; + + if (!parser->key_is_link) { + if (!pjlink) { + errlogPrintf("dbJLinkInit: Illegal second link key '%.*s'\n", + (int) len, key); + return dbjl_return(parser, jlif_stop); + } + + IFDEBUG(10) { + printf("dbjl_map_key(%s@%p, \"%.*s\")\t", + pjlink->pif->name, pjlink, (int) len, key); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + assert(pjlink->parseDepth > 0); + return dbjl_return(parser, + CALL_OR_STOP(pjlink->pif->parse_map_key)(pjlink, + (const char *) key, len)); + } + + IFDEBUG(10) { + printf("dbjl_map_key(NULL, \"%.*s\")\t", (int) len, key); + printf(" jsonDepth=%d, parseDepth=00, key_is_link=%d\n", + parser->jsonDepth, parser->key_is_link); + } + + link_name = dbmfStrndup((const char *) key, len); + + linkSup = dbFindLinkSup(pdbbase, link_name); + if (!linkSup) { + errlogPrintf("dbJLinkInit: Link type '%s' not found\n", + link_name); + dbmfFree(link_name); + return dbjl_return(parser, jlif_stop); + } + + pjlif = linkSup->pjlif; + if (!pjlif) { + errlogPrintf("dbJLinkInit: Support for Link type '%s' not loaded\n", + link_name); + dbmfFree(link_name); + return dbjl_return(parser, jlif_stop); + } + + dbmfFree(link_name); + + pjlink = pjlif->alloc_jlink(parser->dbfType); + if (!pjlink) { + errlogPrintf("dbJLinkInit: Out of memory\n"); + return dbjl_return(parser, jlif_stop); + } + pjlink->pif = pjlif; + pjlink->parent = NULL; + pjlink->parseDepth = 0; + pjlink->debug = !!parser->lset_debug; + + if (parser->pjlink) { + /* We're starting a child link, save its parent */ + pjlink->parent = parser->pjlink; + } + parser->pjlink = pjlink; + parser->key_is_link = 0; + + IFDEBUG(8) + printf("dbjl_map_key: New %s@%p\n", pjlink ? pjlink->pif->name : "", pjlink); + + return jlif_continue; +} + +static int dbjl_end_map(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + jlif_result result; + + IFDEBUG(10) { + printf("dbjl_end_map(%s@%p)\t", + pjlink ? pjlink->pif->name : "NULL", pjlink); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, + parser->key_is_link); + } + + parser->jsonDepth--; + if (pjlink) { + pjlink->parseDepth--; + + result = dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_end_map)(pjlink)); + } + else { + result = jlif_continue; + } + return result; +} + +static int dbjl_start_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_start_array(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + assert(pjlink); + pjlink->parseDepth++; + parser->jsonDepth++; + + return dbjl_return(parser, + CALL_OR_STOP(pjlink->pif->parse_start_array)(pjlink)); +} + +static int dbjl_end_array(void *ctx) { + parseContext *parser = (parseContext *) ctx; + jlink *pjlink = parser->pjlink; + + IFDEBUG(10) { + printf("dbjl_end_array(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink); + printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n", + parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link); + } + + assert(pjlink); + pjlink->parseDepth--; + parser->jsonDepth--; + + return dbjl_value(parser, + CALL_OR_STOP(pjlink->pif->parse_end_array)(pjlink)); +} + + +static yajl_callbacks dbjl_callbacks = { + dbjl_null, dbjl_boolean, dbjl_integer, dbjl_double, NULL, dbjl_string, + dbjl_start_map, dbjl_map_key, dbjl_end_map, dbjl_start_array, dbjl_end_array +}; + +long dbJLinkParse(const char *json, size_t jlen, short dbfType, + jlink **ppjlink, unsigned opts) +{ + parseContext context, *parser = &context; + yajl_alloc_funcs dbjl_allocs; + yajl_handle yh; + yajl_status ys; + long status; + + parser->pjlink = NULL; + parser->product = NULL; + parser->dbfType = dbfType; + parser->jsonDepth = 0; + parser->key_is_link = 0; + parser->parse_debug = !!(opts&LINK_DEBUG_JPARSE); + parser->lset_debug = !!(opts&LINK_DEBUG_LSET); + + IFDEBUG(10) + printf("dbJLinkInit(\"%.*s\", %d, %p)\n", + (int) jlen, json, dbfType, ppjlink); + + IFDEBUG(10) + printf("dbJLinkInit: jsonDepth=%d, key_is_link=%d\n", + parser->jsonDepth, parser->key_is_link); + + yajl_set_default_alloc_funcs(&dbjl_allocs); + yh = yajl_alloc(&dbjl_callbacks, &dbjl_allocs, parser); + if (!yh) + return S_db_noMemory; + + ys = yajl_parse(yh, (const unsigned char *) json, jlen); + if (ys == yajl_status_ok) + ys = yajl_complete_parse(yh); + + switch (ys) { + unsigned char *err; + + case yajl_status_ok: + assert(parser->jsonDepth == 0); + *ppjlink = parser->product; + status = 0; + break; + + case yajl_status_error: + err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen); + errlogPrintf("dbJLinkInit: %s\n", err); + yajl_free_error(yh, err); + dbJLinkFree(parser->pjlink); + dbJLinkFree(parser->product); + /* fall through */ + default: + status = S_db_badField; + } + + yajl_free(yh); + return status; +} + +long dbJLinkInit(struct link *plink) +{ + jlink *pjlink; + + assert(plink); + pjlink = plink->value.json.jlink; + + if (pjlink) + plink->lset = pjlink->pif->get_lset(pjlink); + + dbLinkOpen(plink); + return 0; +} + +void dbJLinkFree(jlink *pjlink) +{ + if (pjlink) + pjlink->pif->free_jlink(pjlink); +} + +void dbJLinkReport(jlink *pjlink, int level, int indent) { + if (pjlink && pjlink->pif->report) + pjlink->pif->report(pjlink, level, indent); +} + +long dbJLinkMapChildren(struct link *plink, jlink_map_fn rtn, void *ctx) +{ + jlink *pjlink; + long status; + + if (!plink || plink->type != JSON_LINK) + return 0; + + pjlink = plink->value.json.jlink; + if (!pjlink) + return 0; + + status = rtn(pjlink, ctx); + if (!status && pjlink->pif->map_children) + status = pjlink->pif->map_children(pjlink, rtn, ctx); + + return status; +} + +long dbjlr(const char *recname, int level) +{ + DBENTRY dbentry; + DBENTRY * const pdbentry = &dbentry; + long status; + + if (!recname || recname[0] == '\0' || !strcmp(recname, "*")) { + recname = NULL; + printf("JSON links in all records\n\n"); + } + else + printf("JSON links in record '%s'\n\n", recname); + + dbInitEntry(pdbbase, pdbentry); + for (status = dbFirstRecordType(pdbentry); + status == 0; + status = dbNextRecordType(pdbentry)) { + for (status = dbFirstRecord(pdbentry); + status == 0; + status = dbNextRecord(pdbentry)) { + dbRecordType *pdbRecordType = pdbentry->precordType; + dbCommon *precord = pdbentry->precnode->precord; + char *prec = (char *) precord; + int i; + + if (recname && strcmp(recname, dbGetRecordName(pdbentry))) + continue; + if (dbIsAlias(pdbentry)) + continue; + + printf(" %s record '%s':\n", pdbRecordType->name, precord->name); + + dbScanLock(precord); + for (i = 0; i < pdbRecordType->no_links; i++) { + int idx = pdbRecordType->link_ind[i]; + dbFldDes *pdbFldDes = pdbRecordType->papFldDes[idx]; + DBLINK *plink = (DBLINK *) (prec + pdbFldDes->offset); + + if (plink->type != JSON_LINK) + continue; + if (!dbLinkIsDefined(plink)) + continue; + + printf(" Link field '%s':\n", pdbFldDes->name); + dbJLinkReport(plink->value.json.jlink, level, 6); + } + dbScanUnlock(precord); + if (recname) + goto done; + } + } +done: + return 0; +} + +long dbJLinkMapAll(char *recname, jlink_map_fn rtn, void *ctx) +{ + DBENTRY dbentry; + DBENTRY * const pdbentry = &dbentry; + long status; + + if (recname && (recname[0] = '\0' || !strcmp(recname, "*"))) + recname = NULL; + + dbInitEntry(pdbbase, pdbentry); + for (status = dbFirstRecordType(pdbentry); + status == 0; + status = dbNextRecordType(pdbentry)) { + for (status = dbFirstRecord(pdbentry); + status == 0; + status = dbNextRecord(pdbentry)) { + dbRecordType *pdbRecordType = pdbentry->precordType; + dbCommon *precord = pdbentry->precnode->precord; + char *prec = (char *) precord; + int i; + + if (recname && strcmp(recname, dbGetRecordName(pdbentry))) + continue; + if (dbIsAlias(pdbentry)) + continue; + + dbScanLock(precord); + for (i = 0; i < pdbRecordType->no_links; i++) { + int idx = pdbRecordType->link_ind[i]; + dbFldDes *pdbFldDes = pdbRecordType->papFldDes[idx]; + DBLINK *plink = (DBLINK *) (prec + pdbFldDes->offset); + + status = dbJLinkMapChildren(plink, rtn, ctx); + if (status) + goto unlock; + } +unlock: + dbScanUnlock(precord); + if (status || recname) + goto done; + } + } +done: + return status; +} diff --git a/modules/database/src/ioc/db/dbJLink.h b/modules/database/src/ioc/db/dbJLink.h new file mode 100644 index 000000000..61b59670b --- /dev/null +++ b/modules/database/src/ioc/db/dbJLink.h @@ -0,0 +1,133 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbJLink.h */ + +#ifndef INC_dbJLink_H +#define INC_dbJLink_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + jlif_stop = 0, + jlif_continue = 1 +} jlif_result; + +typedef enum { + jlif_key_stop = jlif_stop, + jlif_key_continue = jlif_continue, + jlif_key_child_link +} jlif_key_result; + +struct link; +struct lset; +struct jlif; + +typedef struct jlink { + struct jlif *pif; /* Link methods */ + struct jlink *parent; /* NULL for top-level links */ + int parseDepth; /* Used by parser, unused afterwards */ + unsigned debug:1; /* set by caller of jlif operations to request debug output to console */ + /* Link types extend or embed this structure for private storage */ +} jlink; + +typedef long (*jlink_map_fn)(jlink *, void *ctx); + +typedef struct jlif { + /* Optional parser methods below given as NULL are equivalent to + * providing a routine that always returns jlif_stop, meaning that + * this JSON construct is not allowed at this point in the parse. + */ + + const char *name; + /* Name for the link type, used in link value */ + + jlink* (*alloc_jlink)(short dbfType); + /* Required, allocate new link structure */ + + void (*free_jlink)(jlink *); + /* Required, release all resources allocated for link */ + + jlif_result (*parse_null)(jlink *); + /* Optional, parser saw a null value */ + + jlif_result (*parse_boolean)(jlink *, int val); + /* Optional, parser saw a boolean value */ + + jlif_result (*parse_integer)(jlink *, long long num); + /* Optional, parser saw an integer value */ + + jlif_result (*parse_double)(jlink *, double num); + /* Optional, parser saw a double value */ + + jlif_result (*parse_string)(jlink *, const char *val, size_t len); + /* Optional, parser saw a string value */ + + jlif_key_result (*parse_start_map)(jlink *); + /* Optional, parser saw an open-brace '{'. Return jlif_key_child_link + * to expect a child link next (extra key/value pairs may follow). + */ + + jlif_result (*parse_map_key)(jlink *, const char *key, size_t len); + /* Optional, parser saw a map key */ + + jlif_result (*parse_end_map)(jlink *); + /* Optional, parser saw a close-brace '}' */ + + jlif_result (*parse_start_array)(jlink *); + /* Optional, parser saw an open-bracket */ + + jlif_result (*parse_end_array)(jlink *); + /* Optional, parser saw a close-bracket */ + + void (*end_child)(jlink *parent, jlink *child); + /* Optional, called with pointer to the new child link after + * parse_start_map() returned jlif_key_child_link */ + + struct lset* (*get_lset)(const jlink *); + /* Required, return lset for this link instance */ + + void (*report)(const jlink *, int level, int indent); + /* Optional, print status information about this link instance, then + * if (level > 0) print a link identifier (at indent+2) and call + * dbJLinkReport(child, level-1, indent+4) + * for each child. + */ + + long (*map_children)(jlink *, jlink_map_fn rtn, void *ctx); + /* Optional, call dbJLinkMapChildren() on all embedded links. + * Stop immediately and return status if non-zero. + */ + + /* Link types must NOT extend this table with their own routines, + * this space is reserved for extensions to the jlink interface. + */ +} jlif; + +epicsShareFunc long dbJLinkParse(const char *json, size_t len, short dbfType, + jlink **ppjlink, unsigned opts); +epicsShareFunc long dbJLinkInit(struct link *plink); + +epicsShareFunc void dbJLinkFree(jlink *); +epicsShareFunc void dbJLinkReport(jlink *, int level, int indent); + +epicsShareFunc long dbJLinkMapChildren(struct link *, + jlink_map_fn rtn, void *ctx); + +epicsShareFunc long dbjlr(const char *recname, int level); +epicsShareFunc long dbJLinkMapAll(char *recname, jlink_map_fn rtn, void *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbJLink_H */ + diff --git a/modules/database/src/ioc/db/dbLink.c b/modules/database/src/ioc/db/dbLink.c new file mode 100644 index 000000000..9ca587f71 --- /dev/null +++ b/modules/database/src/ioc/db/dbLink.c @@ -0,0 +1,505 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne + * National Laboratory. + * Copyright (c) 2002 The Regents of the University of California, as + * Operator of Los Alamos National Laboratory. + * EPICS BASE is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + \*************************************************************************/ +/* dbLink.c */ +/* + * Original Authors: Bob Dalesio, Marty Kraimer + * Current Author: Andrew Johnson + */ + +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "cvtFast.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsTime.h" +#include "errlog.h" + +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCa.h" +#include "dbCommon.h" +#include "dbConstLink.h" +#include "dbDbLink.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbJLink.h" +#include "dbLink.h" +#include "dbLock.h" +#include "dbScan.h" +#include "dbStaticLib.h" +#include "devSup.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + +/* How to identify links in error messages */ +static const char * link_field_name(const struct link *plink) +{ + const struct dbCommon *precord = plink->precord; + const dbRecordType *pdbRecordType = precord->rdes; + dbFldDes * const *papFldDes = pdbRecordType->papFldDes; + const short *link_ind = pdbRecordType->link_ind; + int i; + + for (i = 0; i < pdbRecordType->no_links; i++) { + const dbFldDes *pdbFldDes = papFldDes[link_ind[i]]; + + if (plink == (DBLINK *)((char *)precord + pdbFldDes->offset)) + return pdbFldDes->name; + } + return "????"; +} + +/* Special TSEL handler for PV links */ +/* FIXME: Generalize for new link types... */ +static void TSEL_modified(struct link *plink) +{ + struct pv_link *ppv_link; + char *pfieldname; + + if (plink->type != PV_LINK) { + errlogPrintf("dbLink::TSEL_modified called for non PV_LINK\n"); + return; + } + /* If pvname contains .TIME truncate it to point to VAL instead */ + ppv_link = &plink->value.pv_link; + pfieldname = strstr(ppv_link->pvname, ".TIME"); + if (pfieldname) { + *pfieldname = 0; + plink->flags |= DBLINK_FLAG_TSELisTIME; + } +} + + +/***************************** Generic Link API *****************************/ + +void dbInitLink(struct link *plink, short dbfType) +{ + struct dbCommon *precord = plink->precord; + + /* Only initialize link once */ + if (plink->flags & DBLINK_FLAG_INITIALIZED) + return; + else + plink->flags |= DBLINK_FLAG_INITIALIZED; + + if (plink->type == CONSTANT) { + dbConstInitLink(plink); + return; + } + + if (plink->type == JSON_LINK) { + dbJLinkInit(plink); + return; + } + + if (plink->type != PV_LINK) + return; + + if (plink == &precord->tsel) + TSEL_modified(plink); + + if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) { + /* Make it a DB link if possible */ + if (!dbDbInitLink(plink, dbfType)) + return; + } + + /* Make it a CA link */ + if (dbfType == DBF_INLINK) + plink->value.pv_link.pvlMask |= pvlOptInpNative; + + dbCaAddLink(NULL, plink, dbfType); + if (dbfType == DBF_FWDLINK) { + char *pperiod = strrchr(plink->value.pv_link.pvname, '.'); + + if (pperiod && strstr(pperiod, "PROC")) { + plink->value.pv_link.pvlMask |= pvlOptFWD; + } + else { + errlogPrintf("Forward-link uses Channel Access " + "without pointing to PROC field\n" + " %s.%s => %s\n", + precord->name, link_field_name(plink), + plink->value.pv_link.pvname); + } + } +} + +void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType, + DBADDR *ptarget) +{ + struct dbCommon *precord = plink->precord; + + /* Clear old TSELisTIME flag */ + plink->flags &= ~DBLINK_FLAG_TSELisTIME; + + if (plink->type == CONSTANT) { + dbConstAddLink(plink); + return; + } + + if (plink->type == JSON_LINK) { + /* + * FIXME: Can't create DB links as dbJLink types yet, + * dbLock.c doesn't have any way to find/track them. + */ + dbJLinkInit(plink); + return; + } + + if (plink->type != PV_LINK) + return; + + if (plink == &precord->tsel) + TSEL_modified(plink); + + if (ptarget) { + /* It's a DB link */ + dbDbAddLink(locker, plink, dbfType, ptarget); + return; + } + + /* Make it a CA link */ + if (dbfType == DBF_INLINK) + plink->value.pv_link.pvlMask |= pvlOptInpNative; + + dbCaAddLink(locker, plink, dbfType); + if (dbfType == DBF_FWDLINK) { + char *pperiod = strrchr(plink->value.pv_link.pvname, '.'); + + if (pperiod && strstr(pperiod, "PROC")) + plink->value.pv_link.pvlMask |= pvlOptFWD; + } +} + +void dbLinkOpen(struct link *plink) +{ + lset *plset = plink->lset; + + if (plset && plset->openLink) + plset->openLink(plink); +} + +void dbRemoveLink(struct dbLocker *locker, struct link *plink) +{ + lset *plset = plink->lset; + + if (plset) { + if (plset->removeLink) + plset->removeLink(locker, plink); + plink->lset = NULL; + } + if (plink->type == JSON_LINK) + plink->value.json.jlink = NULL; +} + +int dbLinkIsDefined(const struct link *plink) +{ + return (plink->lset != 0); +} + +int dbLinkIsConstant(const struct link *plink) +{ + lset *plset = plink->lset; + + return !plset || plset->isConstant; +} + +int dbLinkIsVolatile(const struct link *plink) +{ + lset *plset = plink->lset; + + return plset && plset->isVolatile; +} + +long dbLoadLink(struct link *plink, short dbrType, void *pbuffer) +{ + lset *plset = plink->lset; + + if (plset && plset->loadScalar) + return plset->loadScalar(plink, dbrType, pbuffer); + + return S_db_noLSET; +} + +long dbLoadLinkLS(struct link *plink, char *pbuffer, epicsUInt32 size, + epicsUInt32 *plen) +{ + lset *plset = plink->lset; + + if (plset && plset->loadLS) + return plset->loadLS(plink, pbuffer, size, plen); + + return S_db_noLSET; +} + +long dbLoadLinkArray(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + lset *plset = plink->lset; + + if (plset && plset->loadArray) + return plset->loadArray(plink, dbrType, pbuffer, pnRequest); + + return S_db_noLSET; +} + +int dbIsLinkConnected(const struct link *plink) +{ + lset *plset = plink->lset; + + if (!plset || !plset->isConnected) + return FALSE; + + return plset->isConnected(plink); +} + +int dbGetLinkDBFtype(const struct link *plink) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getDBFtype) + return -1; + + return plset->getDBFtype(plink); +} + +long dbGetNelements(const struct link *plink, long *nelements) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getElements) + return S_db_noLSET; + + return plset->getElements(plink, nelements); +} + +long dbTryGetLink(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getValue) + return S_db_noLSET; + + return plset->getValue(plink, dbrType, pbuffer, pnRequest); +} + +long dbGetLink(struct link *plink, short dbrType, void *pbuffer, + long *poptions, long *pnRequest) +{ + struct dbCommon *precord = plink->precord; + long status; + + if (poptions && *poptions) { + printf("dbGetLink: Use of poptions no longer supported\n"); + *poptions = 0; + } + + status = dbTryGetLink(plink, dbrType, pbuffer, pnRequest); + if (status == S_db_noLSET) + return -1; + if (status) + recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); + + return status; +} + +long dbGetControlLimits(const struct link *plink, double *low, double *high) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getControlLimits) + return S_db_noLSET; + + return plset->getControlLimits(plink, low, high); +} + +long dbGetGraphicLimits(const struct link *plink, double *low, double *high) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getGraphicLimits) + return S_db_noLSET; + + return plset->getGraphicLimits(plink, low, high); +} + +long dbGetAlarmLimits(const struct link *plink, double *lolo, double *low, + double *high, double *hihi) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getAlarmLimits) + return S_db_noLSET; + + return plset->getAlarmLimits(plink, lolo, low, high, hihi); +} + +long dbGetPrecision(const struct link *plink, short *precision) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getPrecision) + return S_db_noLSET; + + return plset->getPrecision(plink, precision); +} + +long dbGetUnits(const struct link *plink, char *units, int unitsSize) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getUnits) + return S_db_noLSET; + + return plset->getUnits(plink, units, unitsSize); +} + +long dbGetAlarm(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getAlarm) + return S_db_noLSET; + + return plset->getAlarm(plink, status, severity); +} + +long dbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp) +{ + lset *plset = plink->lset; + + if (!plset || !plset->getTimeStamp) + return S_db_noLSET; + + return plset->getTimeStamp(plink, pstamp); +} + +long dbPutLink(struct link *plink, short dbrType, const void *pbuffer, + long nRequest) +{ + lset *plset = plink->lset; + long status; + + if (!plset || !plset->putValue) + return S_db_noLSET; + + status = plset->putValue(plink, dbrType, pbuffer, nRequest); + if (status) { + struct dbCommon *precord = plink->precord; + + recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); + } + return status; +} + +void dbLinkAsyncComplete(struct link *plink) +{ + dbCommon *pdbCommon = plink->precord; + + dbScanLock(pdbCommon); + pdbCommon->rset->process(pdbCommon); + dbScanUnlock(pdbCommon); +} + +long dbPutLinkAsync(struct link *plink, short dbrType, const void *pbuffer, + long nRequest) +{ + lset *plset = plink->lset; + long status; + + if (!plset || !plset->putAsync) + return S_db_noLSET; + + status = plset->putAsync(plink, dbrType, pbuffer, nRequest); + if (status) { + struct dbCommon *precord = plink->precord; + + recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM); + } + return status; +} + +void dbScanFwdLink(struct link *plink) +{ + lset *plset = plink->lset; + + if (plset && plset->scanForward) + plset->scanForward(plink); +} + +long dbLinkDoLocked(struct link *plink, dbLinkUserCallback rtn, + void *priv) +{ + lset *plset = plink->lset; + + if (!rtn || !plset || !plset->doLocked) + return S_db_noLSET; + + return plset->doLocked(plink, rtn, priv); +} + + +/* Helper functions for long string support */ + +long dbGetLinkLS(struct link *plink, char *pbuffer, epicsUInt32 size, + epicsUInt32 *plen) +{ + int dtyp = dbGetLinkDBFtype(plink); + long len = size; + long status; + + if (dtyp < 0) /* Not connected */ + return 0; + + if (dtyp == DBR_CHAR || dtyp == DBF_UCHAR) { + status = dbGetLink(plink, dtyp, pbuffer, 0, &len); + } + else if (size >= MAX_STRING_SIZE) + status = dbGetLink(plink, DBR_STRING, pbuffer, 0, 0); + else { + /* pbuffer is too small to fetch using DBR_STRING */ + char tmp[MAX_STRING_SIZE]; + + status = dbGetLink(plink, DBR_STRING, tmp, 0, 0); + if (!status) + strncpy(pbuffer, tmp, len - 1); + } + if (!status) { + pbuffer[--len] = 0; + *plen = (epicsUInt32) strlen(pbuffer) + 1; + } + return status; +} + +long dbPutLinkLS(struct link *plink, char *pbuffer, epicsUInt32 len) +{ + int dtyp = dbGetLinkDBFtype(plink); + + if (dtyp < 0) + return 0; /* Not connected */ + + if (dtyp == DBR_CHAR || dtyp == DBF_UCHAR) + return dbPutLink(plink, dtyp, pbuffer, len); + + return dbPutLink(plink, DBR_STRING, pbuffer, 1); +} diff --git a/modules/database/src/ioc/db/dbLink.h b/modules/database/src/ioc/db/dbLink.h new file mode 100644 index 000000000..fcfadb24f --- /dev/null +++ b/modules/database/src/ioc/db/dbLink.h @@ -0,0 +1,141 @@ +/*************************************************************************\ +* Copyright (c) 2010 The UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbLink.h + * + * Created on: Mar 21, 2010 + * Author: Andrew Johnson + */ + +#ifndef INC_dbLink_H +#define INC_dbLink_H + +#include "link.h" +#include "shareLib.h" +#include "epicsTypes.h" +#include "epicsTime.h" +#include "dbAddr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbLocker; + +typedef long (*dbLinkUserCallback)(struct link *plink, void *priv); + +typedef struct lset { + /* Characteristics of the link type */ + const unsigned isConstant:1; + const unsigned isVolatile:1; + + /* Activation */ + void (*openLink)(struct link *plink); + + /* Destructor */ + void (*removeLink)(struct dbLocker *locker, struct link *plink); + + /* Const init, data type hinting */ + long (*loadScalar)(struct link *plink, short dbrType, void *pbuffer); + long (*loadLS)(struct link *plink, char *pbuffer, epicsUInt32 size, + epicsUInt32 *plen); + long (*loadArray)(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest); + + /* Metadata */ + int (*isConnected)(const struct link *plink); + int (*getDBFtype)(const struct link *plink); + long (*getElements)(const struct link *plink, long *nelements); + + /* Get data */ + long (*getValue)(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest); + long (*getControlLimits)(const struct link *plink, double *lo, double *hi); + long (*getGraphicLimits)(const struct link *plink, double *lo, double *hi); + long (*getAlarmLimits)(const struct link *plink, double *lolo, double *lo, + double *hi, double *hihi); + long (*getPrecision)(const struct link *plink, short *precision); + long (*getUnits)(const struct link *plink, char *units, int unitsSize); + long (*getAlarm)(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity); + long (*getTimeStamp)(const struct link *plink, epicsTimeStamp *pstamp); + + /* Put data */ + long (*putValue)(struct link *plink, short dbrType, + const void *pbuffer, long nRequest); + long (*putAsync)(struct link *plink, short dbrType, + const void *pbuffer, long nRequest); + + /* Process */ + void (*scanForward)(struct link *plink); + + /* Atomicity */ + long (*doLocked)(struct link *plink, dbLinkUserCallback rtn, void *priv); +} lset; + +#define dbGetSevr(link, sevr) \ + dbGetAlarm(link, NULL, sevr) + +epicsShareFunc void dbInitLink(struct link *plink, short dbfType); +epicsShareFunc void dbAddLink(struct dbLocker *locker, struct link *plink, + short dbfType, DBADDR *ptarget); + +epicsShareFunc void dbLinkOpen(struct link *plink); +epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct link *plink); + +epicsShareFunc int dbLinkIsDefined(const struct link *plink); /* 0 or 1 */ +epicsShareFunc int dbLinkIsConstant(const struct link *plink); /* 0 or 1 */ +epicsShareFunc int dbLinkIsVolatile(const struct link *plink); /* 0 or 1 */ + +epicsShareFunc long dbLoadLink(struct link *plink, short dbrType, + void *pbuffer); +epicsShareFunc long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer, + long *pnRequest); + +epicsShareFunc long dbGetNelements(const struct link *plink, long *nelements); +epicsShareFunc int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */ +epicsShareFunc int dbGetLinkDBFtype(const struct link *plink); +epicsShareFunc long dbTryGetLink(struct link *, short dbrType, void *pbuffer, + long *nRequest); +epicsShareFunc long dbGetLink(struct link *, short dbrType, void *pbuffer, + long *options, long *nRequest); +epicsShareFunc long dbGetControlLimits(const struct link *plink, double *low, + double *high); +epicsShareFunc long dbGetGraphicLimits(const struct link *plink, double *low, + double *high); +epicsShareFunc long dbGetAlarmLimits(const struct link *plink, double *lolo, + double *low, double *high, double *hihi); +epicsShareFunc long dbGetPrecision(const struct link *plink, short *precision); +epicsShareFunc long dbGetUnits(const struct link *plink, char *units, + int unitsSize); +epicsShareFunc long dbGetAlarm(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity); +epicsShareFunc long dbGetTimeStamp(const struct link *plink, + epicsTimeStamp *pstamp); +epicsShareFunc long dbPutLink(struct link *plink, short dbrType, + const void *pbuffer, long nRequest); +epicsShareFunc void dbLinkAsyncComplete(struct link *plink); +epicsShareFunc long dbPutLinkAsync(struct link *plink, short dbrType, + const void *pbuffer, long nRequest); +epicsShareFunc void dbScanFwdLink(struct link *plink); + +epicsShareFunc long dbLinkDoLocked(struct link *plink, dbLinkUserCallback rtn, + void *priv); + +epicsShareFunc long dbLoadLinkLS(struct link *plink, char *pbuffer, + epicsUInt32 size, epicsUInt32 *plen); +epicsShareFunc long dbGetLinkLS(struct link *plink, char *pbuffer, + epicsUInt32 buffer_size, epicsUInt32 *plen); +epicsShareFunc long dbPutLinkLS(struct link *plink, char *pbuffer, + epicsUInt32 len); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbLink_H */ diff --git a/modules/database/src/ioc/db/dbLock.c b/modules/database/src/ioc/db/dbLock.c new file mode 100644 index 000000000..8df755b2c --- /dev/null +++ b/modules/database/src/ioc/db/dbLock.c @@ -0,0 +1,990 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include +#include + +#include "cantProceed.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsAssert.h" +#include "epicsAtomic.h" +#include "epicsMutex.h" +#include "epicsPrint.h" +#include "epicsSpin.h" +#include "epicsStdio.h" +#include "epicsThread.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbLink.h" +#include "dbCommon.h" +#include "dbFldTypes.h" +#include "dbLockPvt.h" +#include "dbStaticLib.h" +#include "link.h" + +typedef struct dbScanLockNode dbScanLockNode; + +static epicsThreadOnceId dbLockOnceInit = EPICS_THREAD_ONCE_INIT; + +static ELLLIST lockSetsActive; /* in use */ +#ifndef LOCKSET_NOFREE +static ELLLIST lockSetsFree; /* free list */ +#endif + +/* Guard the global list */ +static epicsMutexId lockSetsGuard; + +#ifndef LOCKSET_NOCNT +/* Counter which we increment whenever + * any lockRecord::plockSet is changed. + * An optimization to avoid checking lockSet + * associations when no links have changed. + */ +static size_t recomputeCnt; +#endif + +/*private routines */ +static void dbLockOnce(void* ignore) +{ + lockSetsGuard = epicsMutexMustCreate(); +} + +/* global ID number assigned to each lockSet on creation. + * When the free-list is in use will never exceed + * the number of records +1. + * Without the free-list it can roll over, potentially + * leading to duplicate IDs. + */ +static size_t next_id = 1; + +static lockSet* makeSet(void) +{ + lockSet *ls; + int iref; + epicsMutexMustLock(lockSetsGuard); +#ifndef LOCKSET_NOFREE + ls = (lockSet*)ellGet(&lockSetsFree); + if(!ls) { + epicsMutexUnlock(lockSetsGuard); +#endif + + ls=dbCalloc(1,sizeof(*ls)); + ellInit(&ls->lockRecordList); + ls->lock = epicsMutexMustCreate(); + ls->id = epicsAtomicIncrSizeT(&next_id); + +#ifndef LOCKSET_NOFREE + epicsMutexMustLock(lockSetsGuard); + } +#endif + /* the initial reference for the first lockRecord */ + iref = epicsAtomicIncrIntT(&ls->refcount); + ellAdd(&lockSetsActive, &ls->node); + epicsMutexUnlock(lockSetsGuard); + + assert(ls->id>0); + assert(iref>0); + assert(ellCount(&ls->lockRecordList)==0); + + return ls; +} + +unsigned long dbLockGetRefs(struct dbCommon* prec) +{ + return (unsigned long)epicsAtomicGetIntT(&prec->lset->plockSet->refcount); +} + +unsigned long dbLockCountSets(void) +{ + unsigned long count; + epicsMutexMustLock(lockSetsGuard); + count = (unsigned long)ellCount(&lockSetsActive); + epicsMutexUnlock(lockSetsGuard); + return count; +} + +/* caller must lock accessLock.*/ +void dbLockIncRef(lockSet* ls) +{ + int cnt = epicsAtomicIncrIntT(&ls->refcount); + if(cnt<=1) { + errlogPrintf("dbLockIncRef(%p) on dead lockSet. refs: %d\n", ls, cnt); + cantProceed(NULL); + } +} + +/* caller must lock accessLock. + * lockSet must *not* be locked + */ +void dbLockDecRef(lockSet *ls) +{ + int cnt = epicsAtomicDecrIntT(&ls->refcount); + assert(cnt>=0); + + if(cnt) + return; + + /* lockSet is unused and will be free'd */ + + /* not necessary as no one else (should) hold a reference, + * but lock anyway to quiet valgrind + */ + epicsMutexMustLock(ls->lock); + + if(ellCount(&ls->lockRecordList)!=0) { + errlogPrintf("dbLockDecRef(%p) would free lockSet with %d records\n", + ls, ellCount(&ls->lockRecordList)); + cantProceed(NULL); + } + + epicsMutexUnlock(ls->lock); + + epicsMutexMustLock(lockSetsGuard); + ellDelete(&lockSetsActive, &ls->node); +#ifndef LOCKSET_NOFREE + ellAdd(&lockSetsFree, &ls->node); +#else + epicsMutexDestroy(ls->lock); + memset(ls, 0, sizeof(*ls)); /* paranoia */ + free(ls); +#endif + epicsMutexUnlock(lockSetsGuard); +} + +lockSet* dbLockGetRef(lockRecord *lr) +{ + lockSet *ls; + epicsSpinLock(lr->spin); + ls = lr->plockSet; + dbLockIncRef(ls); + epicsSpinUnlock(lr->spin); + return ls; +} + +unsigned long dbLockGetLockId(dbCommon *precord) +{ + unsigned long id=0; + epicsSpinLock(precord->lset->spin); + id = precord->lset->plockSet->id; + epicsSpinUnlock(precord->lset->spin); + return id; +} + +void dbScanLock(dbCommon *precord) +{ + int cnt; + lockRecord * const lr = precord->lset; + lockSet *ls; + + assert(lr); + + ls = dbLockGetRef(lr); + assert(epicsAtomicGetIntT(&ls->refcount)>0); + +retry: + epicsMutexMustLock(ls->lock); + + epicsSpinLock(lr->spin); + if(ls!=lr->plockSet) { + /* oops, collided with recompute. + * take a reference to the new lockSet. + */ + lockSet *ls2 = lr->plockSet; + int newcnt = epicsAtomicIncrIntT(&ls2->refcount); + assert(newcnt>=2); /* at least lockRecord and us */ + epicsSpinUnlock(lr->spin); + + epicsMutexUnlock(ls->lock); + dbLockDecRef(ls); + + ls = ls2; + goto retry; + } + epicsSpinUnlock(lr->spin); + + /* Release reference taken within this + * function. The count will *never* fall to zero + * as the lockRecords can't be changed while + * we hold the lock. + */ + cnt = epicsAtomicDecrIntT(&ls->refcount); + assert(cnt>0); + +#ifdef LOCKSET_DEBUG + if(ls->owner) { + assert(ls->owner==epicsThreadGetIdSelf()); + assert(ls->ownercount>=1); + ls->ownercount++; + } else { + assert(ls->ownercount==0); + ls->owner = epicsThreadGetIdSelf(); + ls->ownercount = 1; + } +#endif +} + +void dbScanUnlock(dbCommon *precord) +{ + lockSet *ls = precord->lset->plockSet; + dbLockIncRef(ls); +#ifdef LOCKSET_DEBUG + assert(ls->owner==epicsThreadGetIdSelf()); + assert(ls->ownercount>=1); + ls->ownercount--; + if(ls->ownercount==0) + ls->owner = NULL; +#endif + epicsMutexUnlock(ls->lock); + dbLockDecRef(ls); +} + +static +int lrrcompare(const void *rawA, const void *rawB) +{ + const lockRecordRef *refA=rawA, *refB=rawB; + const lockSet *A=refA->plockSet, *B=refB->plockSet; + if(!A && !B) + return 0; /* NULL == NULL */ + else if(!A) + return 1; /* NULL > !NULL */ + else if(!B) + return -1; /* !NULL < NULL */ + else if(A < B) + return -1; + else if(A > B) + return 1; + else + return 0; +} + +/* Call w/ update=1 before locking to update cached lockSet entries. + * Call w/ update=0 after locking to verify that lockRecord weren't updated + */ +static +int dbLockUpdateRefs(dbLocker *locker, int update) +{ + int changed = 0; + size_t i, nlock = locker->maxrefs; + +#ifndef LOCKSET_NOCNT + const size_t recomp = epicsAtomicGetSizeT(&recomputeCnt); + if(locker->recomp!=recomp) { +#endif + /* some lockset recompute happened. + * must re-check our references. + */ + + for(i=0; irefs[i]; + lockSet *oldref = NULL; + if(!ref->plr) { /* this lockRecord slot not used */ + assert(!ref->plockSet); + continue; + } + + epicsSpinLock(ref->plr->spin); + if(ref->plockSet!=ref->plr->plockSet) { + changed = 1; + if(update) { + /* exchange saved lockSet reference */ + oldref = ref->plockSet; /* will be NULL on first iteration */ + ref->plockSet = ref->plr->plockSet; + dbLockIncRef(ref->plockSet); + } + } + epicsSpinUnlock(ref->plr->spin); + if(oldref) + dbLockDecRef(oldref); + if(!update && changed) + return changed; + } +#ifndef LOCKSET_NOCNT + /* Use the value captured before we started. + * If it has changed in the intrim we will catch this later + * during the update==0 pass (which triggers a re-try) + */ + if(update) + locker->recomp = recomp; + } +#endif + + if(changed && update) { + qsort(locker->refs, nlock, sizeof(lockRecordRef), + &lrrcompare); + } + return changed; +} + +void dbLockerPrepare(struct dbLocker *locker, + struct dbCommon * const *precs, + size_t nrecs) +{ + size_t i; + locker->maxrefs = nrecs; + /* intentionally spoil the recomp count to ensure that + * references will be updated this first time + */ +#ifndef LOCKSET_NOCNT + locker->recomp = epicsAtomicGetSizeT(&recomputeCnt)-1; +#endif + + for(i=0; irefs[i].plr = precs[i] ? precs[i]->lset : NULL; + } + + /* acquire a reference to all lockRecords */ + dbLockUpdateRefs(locker, 1); +} + +dbLocker *dbLockerAlloc(dbCommon * const *precs, + size_t nrecs, + unsigned int flags) +{ + size_t Nextra = nrecs>DBLOCKER_NALLOC ? nrecs-DBLOCKER_NALLOC : 0; + dbLocker *locker = calloc(1, sizeof(*locker)+Nextra*sizeof(lockRecordRef)); + + if(locker) + dbLockerPrepare(locker, precs, nrecs); + + return locker; +} + +void dbLockerFinalize(dbLocker *locker) +{ + size_t i; + assert(ellCount(&locker->locked)==0); + + /* release references taken in dbLockUpdateRefs() */ + for(i=0; imaxrefs; i++) { + if(locker->refs[i].plockSet) + dbLockDecRef(locker->refs[i].plockSet); + } +} + +void dbLockerFree(dbLocker *locker) +{ + dbLockerFinalize(locker); + free(locker); +} + +/* Lock the given list of records. + * This function modifies its arguments. + */ +void dbScanLockMany(dbLocker* locker) +{ + size_t i, nlock = locker->maxrefs; + lockSet *plock; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + if(ellCount(&locker->locked)!=0) { + cantProceed("dbScanLockMany(%p) already locked. Recursive locking not allowed", locker); + return; + } + +retry: + assert(ellCount(&locker->locked)==0); + dbLockUpdateRefs(locker, 1); + + for(i=0, plock=NULL; irefs[i]; + + /* skip duplicates (same lockSet + * referenced by more than one lockRecord). + * Sorting will group these together. + */ + if(!ref->plr || (plock && plock==ref->plockSet)) + continue; + plock = ref->plockSet; + + epicsMutexMustLock(plock->lock); + assert(plock->ownerlocker==NULL); + plock->ownerlocker = locker; + ellAdd(&locker->locked, &plock->lockernode); + /* An extra ref for the locked list */ + dbLockIncRef(plock); + +#ifdef LOCKSET_DEBUG + if(plock->owner) { + if(plock->owner!=myself || plock->ownercount<1) { + errlogPrintf("dbScanLockMany(%p) ownership violation %p (%p) %u\n", + locker, plock->owner, myself, plock->ownercount); + cantProceed(NULL); + } + plock->ownercount++; + } else { + assert(plock->ownercount==0); + plock->owner = myself; + plock->ownercount = 1; + } +#endif + + } + + if(dbLockUpdateRefs(locker, 0)) { + /* oops, collided with recompute */ + dbScanUnlockMany(locker); + goto retry; + } + if(nlock!=0 && ellCount(&locker->locked)<=0) { + /* if we have at least one lockRecord, then we will always lock + * at least its present lockSet + */ + errlogPrintf("dbScanLockMany(%p) didn't lock anything\n", locker); + cantProceed(NULL); + } +} + +void dbScanUnlockMany(dbLocker* locker) +{ + ELLNODE *cur; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + while((cur=ellGet(&locker->locked))!=NULL) { + lockSet *plock = CONTAINER(cur, lockSet, lockernode); + + assert(plock->ownerlocker==locker); + plock->ownerlocker = NULL; +#ifdef LOCKSET_DEBUG + assert(plock->owner==myself); + assert(plock->ownercount>=1); + plock->ownercount--; + if(plock->ownercount==0) + plock->owner = NULL; +#endif + + epicsMutexUnlock(plock->lock); + /* release ref for locked list */ + dbLockDecRef(plock); + } +} + +typedef int (*reciter)(void*, DBENTRY*); +static int forEachRecord(void *priv, dbBase *pdbbase, reciter fn) +{ + long status; + int ret = 0; + DBENTRY dbentry; + dbInitEntry(pdbbase,&dbentry); + status = dbFirstRecordType(&dbentry); + while(!status) + { + status = dbFirstRecord(&dbentry); + while(!status) + { + /* skip alias names */ + if(!dbentry.precnode->recordname[0] || dbentry.precnode->flags & DBRN_FLAGS_ISALIAS) { + /* skip */ + } else { + ret = fn(priv, &dbentry); + if(ret) + goto done; + } + + status = dbNextRecord(&dbentry); + } + + status = dbNextRecordType(&dbentry); + } +done: + dbFinishEntry(&dbentry); + return ret; +} + +static int createLockRecord(void* junk, DBENTRY* pdbentry) +{ + dbCommon *prec = pdbentry->precnode->precord; + lockRecord *lrec; + assert(!prec->lset); + + /* TODO: one allocation for all records? */ + lrec = callocMustSucceed(1, sizeof(*lrec), "lockRecord"); + lrec->spin = epicsSpinCreate(); + if(!lrec->spin) + cantProceed("no memory for spinlock in lockRecord"); + + lrec->precord = prec; + + prec->lset = lrec; + + prec->lset->plockSet = makeSet(); + ellAdd(&prec->lset->plockSet->lockRecordList, &prec->lset->node); + return 0; +} + +void dbLockInitRecords(dbBase *pdbbase) +{ + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); + + /* create all lockRecords and lockSets */ + forEachRecord(NULL, pdbbase, &createLockRecord); +} + +static int freeLockRecord(void* junk, DBENTRY* pdbentry) +{ + dbCommon *prec = pdbentry->precnode->precord; + lockRecord *lr = prec->lset; + lockSet *ls = lr->plockSet; + + prec->lset = NULL; + lr->precord = NULL; + + assert(ls->refcount>0); + assert(ellCount(&ls->lockRecordList)>0); + ellDelete(&ls->lockRecordList, &lr->node); + dbLockDecRef(ls); + + epicsSpinDestroy(lr->spin); + free(lr); + return 0; +} + +void dbLockCleanupRecords(dbBase *pdbbase) +{ +#ifndef LOCKSET_NOFREE + ELLNODE *cur; +#endif + epicsThreadOnce(&dbLockOnceInit, &dbLockOnce, NULL); + + forEachRecord(NULL, pdbbase, &freeLockRecord); + if(ellCount(&lockSetsActive)) { + printf("Warning: dbLockCleanupRecords() leaking lockSets\n"); + dblsr(NULL,2); + } + +#ifndef LOCKSET_NOFREE + while((cur=ellGet(&lockSetsFree))!=NULL) { + lockSet *ls = (lockSet*)cur; + + assert(ls->refcount==0); + assert(ellCount(&ls->lockRecordList)==0); + epicsMutexDestroy(ls->lock); + free(ls); + } +#endif +} + +/* Called in two modes. + * During dbLockInitRecords w/ locker==NULL, then no mutex are locked. + * After dbLockInitRecords w/ locker!=NULL, then + * the caller must lock both pfirst and psecond. + * + * Assumes that pfirst has been modified + * to link to psecond. + */ +void dbLockSetMerge(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) +{ + ELLNODE *cur; + lockSet *A=pfirst->lset->plockSet, + *B=psecond->lset->plockSet; + int Nb; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + assert(A && B); + +#ifdef LOCKSET_DEBUG + if(locker && (A->owner!=myself || B->owner!=myself)) { + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + A->owner, B->owner, myself); + cantProceed(NULL); + } +#endif + if(locker && (A->ownerlocker!=locker || B->ownerlocker!=locker)) { + errlogPrintf("dbLockSetMerge(%p,\"%s\",\"%s\") locker ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + A->ownerlocker, B->ownerlocker, locker); + cantProceed(NULL); + } + + if(A==B) + return; /* already in the same lockSet */ + + Nb = ellCount(&B->lockRecordList); + assert(Nb>0); + + /* move all records from B to A */ + while((cur=ellGet(&B->lockRecordList))!=NULL) + { + lockRecord *lr = CONTAINER(cur, lockRecord, node); + assert(lr->plockSet==B); + ellAdd(&A->lockRecordList, cur); + + epicsSpinLock(lr->spin); + lr->plockSet = A; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); + } + + /* there are at minimum, 1 ref for each lockRecord, + * and one for the locker's locked list + * (and perhaps another for its refs cache) + */ + assert(epicsAtomicGetIntT(&B->refcount)>=Nb+(locker?1:0)); + + /* update ref counters. for lockRecords */ + epicsAtomicAddIntT(&A->refcount, Nb); + epicsAtomicAddIntT(&B->refcount, -Nb+1); /* drop all but one ref, see below */ + + if(locker) { + /* at least two refs, possibly three, remain. + * # One ref from above + * # locker->locked list, which is released now. + * # locker->refs array, assuming it is directly referenced, + * and not added as the result of a dbLockSetSplit, + * which will be cleaned when the locker is free'd (not here). + */ +#ifdef LOCKSET_DEBUG + B->owner = NULL; + B->ownercount = 0; +#endif + assert(B->ownerlocker==locker); + ellDelete(&locker->locked, &B->lockernode); + B->ownerlocker = NULL; + epicsAtomicDecrIntT(&B->refcount); + + epicsMutexUnlock(B->lock); + } + + dbLockDecRef(B); /* last ref we hold */ + + assert(A==psecond->lset->plockSet); +} + +/* recompute assuming a link from pfirst to psecond + * may have been removed. + * pfirst and psecond must currently be in the same lockset, + * which the caller must lock before calling this function. + * If a new lockset is created, then it is locked + * when this function returns. + */ +void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond) +{ + lockSet *ls = pfirst->lset->plockSet; + ELLLIST toInspect, newLS; +#ifdef LOCKSET_DEBUG + const epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + +#ifdef LOCKSET_DEBUG + if(ls->owner!=myself || psecond->lset->plockSet->owner!=myself) { + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") ownership violation %p %p (%p)\n", + locker, pfirst->name, psecond->name, + ls->owner, psecond->lset->plockSet->owner, myself); + cantProceed(NULL); + } +#endif + + /* lockset consistency violation */ + if(ls!=psecond->lset->plockSet) { + errlogPrintf("dbLockSetSplit(%p,\"%s\",\"%s\") consistency violation %p %p\n", + locker, pfirst->name, psecond->name, + pfirst->lset->plockSet, psecond->lset->plockSet); + cantProceed(NULL); + } + + + if(pfirst==psecond) + return; + + /* at least 1 ref for each lockRecord, + * and one for the locker + */ + assert(epicsAtomicGetIntT(&ls->refcount)>=ellCount(&ls->lockRecordList)+1); + + ellInit(&toInspect); + ellInit(&newLS); + + /* strategy is to start with psecond and do + * a breadth first traversal until all records are + * visited. If we encounter pfirst, then there + * is no need to create a new lockset so we abort + * early. + */ + ellAdd(&toInspect, &psecond->lset->compnode); + psecond->lset->compflag = 1; + + { + lockSet *splitset; + ELLNODE *cur; + while((cur=ellGet(&toInspect))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + dbCommon *prec=lr->precord; + dbRecordType *rtype = prec->rdes; + size_t i; + ELLNODE *bcur; + + ellAdd(&newLS, cur); + prec->lset->compflag = 2; + + /* Visit all the links originating from prec */ + for(i=0; ino_links; i++) { + dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]]; + DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset); + DBADDR *ptarget; + lockRecord *lr; + + if(plink->type!=DB_LINK) + continue; + + ptarget = plink->value.pv_link.pvt; + lr = ptarget->precord->lset; + assert(lr); + + if(lr->precord==pfirst) { + /* so pfirst is still reachable from psecond, + * no new lock set should be created. + */ + goto nosplit; + } + + /* have we already visited this record? */ + if(lr->compflag) + continue; + + ellAdd(&toInspect, &lr->compnode); + lr->compflag = 1; + } + + /* Visit all links terminating at prec */ + for(bcur=ellFirst(&prec->bklnk); bcur; bcur=ellNext(bcur)) + { + struct pv_link *plink1 = CONTAINER(bcur, struct pv_link, backlinknode); + union value *plink2 = CONTAINER(plink1, union value, pv_link); + DBLINK *plink = CONTAINER(plink2, DBLINK, value); + lockRecord *lr = plink->precord->lset; + + /* plink->type==DB_LINK is implied. Only DB_LINKs are tracked from BKLNK */ + + if(lr->precord==pfirst) { + goto nosplit; + } + + if(lr->compflag) + continue; + + ellAdd(&toInspect, &lr->compnode); + lr->compflag = 1; + } + } + /* All links involving psecond were traversed without finding + * pfirst. So we must create a new lockset. + * newLS contains the nodes which will + * make up this new lockset. + */ + /* newLS will have at least psecond in it */ + assert(ellCount(&newLS) > 0); + /* If we didn't find pfirst, then it must be in the + * original lockset, and not the new one + */ + assert(ellCount(&newLS) < ellCount(&ls->lockRecordList)); + assert(ellCount(&newLS) < ls->refcount); + + splitset = makeSet(); /* reference for locker->locked */ + + epicsMutexMustLock(splitset->lock); + + assert(splitset->ownerlocker==NULL); + ellAdd(&locker->locked, &splitset->lockernode); + splitset->ownerlocker = locker; + + assert(splitset->refcount==1); + +#ifdef LOCKSET_DEBUG + splitset->owner = ls->owner; + splitset->ownercount = 1; + assert(ls->ownercount==1); +#endif + + while((cur=ellGet(&newLS))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + + lr->compflag = 0; /* reset for next time */ + + assert(lr->plockSet == ls); + ellDelete(&ls->lockRecordList, &lr->node); + ellAdd(&splitset->lockRecordList, &lr->node); + + epicsSpinLock(lr->spin); + lr->plockSet = splitset; +#ifndef LOCKSET_NOCNT + epicsAtomicIncrSizeT(&recomputeCnt); +#endif + epicsSpinUnlock(lr->spin); + /* new lockSet is "live" at this point + * as other threads may find it. + */ + } + + /* refcount of ls can't go to zero as the locker + * holds at least one reference (its locked list) + */ + epicsAtomicAddIntT(&ls->refcount, -ellCount(&splitset->lockRecordList)); + assert(ls->refcount>0); + epicsAtomicAddIntT(&splitset->refcount, ellCount(&splitset->lockRecordList)); + + assert(splitset->refcount>=ellCount(&splitset->lockRecordList)+1); + + assert(psecond->lset->plockSet==splitset); + + /* must have refs from pfirst lockRecord, + * and the locked list. + */ + assert(epicsAtomicGetIntT(&ls->refcount)>=2); + + return; + } + +nosplit: + { + /* reset compflag for all nodes visited + * during the aborted search + */ + ELLNODE *cur; + while((cur=ellGet(&toInspect))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + lr->compflag = 0; + } + while((cur=ellGet(&newLS))!=NULL) + { + lockRecord *lr=CONTAINER(cur,lockRecord,compnode); + lr->compflag = 0; + } + return; + } +} + +static char *msstring[4]={"NMS","MS","MSI","MSS"}; + +long dblsr(char *recordname,int level) +{ + int link; + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + dbCommon *precord; + lockSet *plockSet; + lockRecord *plockRecord; + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + DBLINK *plink; + + if (recordname && ((*recordname == '\0') || !strcmp(recordname,"*"))) + recordname = NULL; + if(recordname) { + dbInitEntry(pdbbase,pdbentry); + status = dbFindRecord(pdbentry,recordname); + if(status) { + printf("Record not found\n"); + dbFinishEntry(pdbentry); + return 0; + } + precord = pdbentry->precnode->precord; + dbFinishEntry(pdbentry); + plockRecord = precord->lset; + if (!plockRecord) return 0; /* before iocInit */ + plockSet = plockRecord->plockSet; + } else { + plockSet = (lockSet *)ellFirst(&lockSetsActive); + } + for( ; plockSet; plockSet = (lockSet *)ellNext(&plockSet->node)) { + printf("Lock Set %lu %d members %d refs epicsMutexId %p\n", + plockSet->id,ellCount(&plockSet->lockRecordList),plockSet->refcount,plockSet->lock); + + if(level==0) { if(recordname) break; continue; } + for(plockRecord = (lockRecord *)ellFirst(&plockSet->lockRecordList); + plockRecord; plockRecord = (lockRecord *)ellNext(&plockRecord->node)) { + precord = plockRecord->precord; + pdbRecordType = precord->rdes; + printf("%s\n",precord->name); + if(level<=1) continue; + for(link=0; (linkno_links) ; link++) { + DBADDR *pdbAddr; + pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]]; + plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + if(plink->type != DB_LINK) continue; + pdbAddr = (DBADDR *)(plink->value.pv_link.pvt); + printf("\t%s",pdbFldDes->name); + if(pdbFldDes->field_type==DBF_INLINK) { + printf("\t INLINK"); + } else if(pdbFldDes->field_type==DBF_OUTLINK) { + printf("\tOUTLINK"); + } else if(pdbFldDes->field_type==DBF_FWDLINK) { + printf("\tFWDLINK"); + } + printf(" %s %s", + ((plink->value.pv_link.pvlMask&pvlOptPP)?" PP":"NPP"), + msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]); + printf(" %s\n",pdbAddr->precord->name); + } + } + if(recordname) break; + } + return 0; +} + +long dbLockShowLocked(int level) +{ + int indListType; + lockSet *plockSet; + + printf("Active lockSets: %d\n", ellCount(&lockSetsActive)); +#ifndef LOCKSET_NOFREE + printf("Free lockSets: %d\n", ellCount(&lockSetsFree)); +#endif + + /*Even if failure on lockSetModifyLock will continue */ + for(indListType=0; indListType <= 1; ++indListType) { + plockSet = (lockSet *)ellFirst(&lockSetsActive); + if(plockSet) { + if (indListType==0) + printf("listTypeScanLock\n"); + else + printf("listTypeRecordLock\n"); + } + while(plockSet) { + epicsMutexLockStatus status; + + status = epicsMutexTryLock(plockSet->lock); + if(status==epicsMutexLockOK) epicsMutexUnlock(plockSet->lock); + if(status!=epicsMutexLockOK || indListType==1) { + + epicsMutexShow(plockSet->lock,level); + } + plockSet = (lockSet *)ellNext(&plockSet->node); + } + } + return 0; +} + +int * dbLockSetAddrTrace(dbCommon *precord) +{ + lockRecord *plockRecord = precord->lset; + lockSet *plockSet = plockRecord->plockSet; + + return(&plockSet->trace); +} diff --git a/modules/database/src/ioc/db/dbLock.h b/modules/database/src/ioc/db/dbLock.h new file mode 100644 index 000000000..1d6388ed3 --- /dev/null +++ b/modules/database/src/ioc/db/dbLock.h @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbLock.h */ +/* Author: Marty Kraimer Date: 12MAR96 */ + +#ifndef INCdbLockh +#define INCdbLockh + +#include "ellLib.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbCommon; +struct dbBase; +typedef struct dbLocker dbLocker; + +epicsShareFunc void dbScanLock(struct dbCommon *precord); +epicsShareFunc void dbScanUnlock(struct dbCommon *precord); + +epicsShareFunc dbLocker *dbLockerAlloc(struct dbCommon * const *precs, + size_t nrecs, + unsigned int flags); + +epicsShareFunc void dbLockerFree(dbLocker *); + +epicsShareFunc void dbScanLockMany(dbLocker*); +epicsShareFunc void dbScanUnlockMany(dbLocker*); + +epicsShareFunc unsigned long dbLockGetLockId( + struct dbCommon *precord); + +epicsShareFunc void dbLockInitRecords(struct dbBase *pdbbase); +epicsShareFunc void dbLockCleanupRecords(struct dbBase *pdbbase); + + +/* Lock Set Report */ +epicsShareFunc long dblsr(char *recordname,int level); +/* If recordname NULL then all records*/ +/* level = (0,1,2) (lock set state, + recordname, +DB links) */ + +epicsShareFunc long dbLockShowLocked(int level); + +/*KLUDGE to support field TPRO*/ +epicsShareFunc int * dbLockSetAddrTrace(struct dbCommon *precord); + +/* debugging */ +epicsShareFunc unsigned long dbLockGetRefs(struct dbCommon*); +epicsShareFunc unsigned long dbLockCountSets(void); + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbLockh*/ diff --git a/modules/database/src/ioc/db/dbLockPvt.h b/modules/database/src/ioc/db/dbLockPvt.h new file mode 100644 index 000000000..f90d00617 --- /dev/null +++ b/modules/database/src/ioc/db/dbLockPvt.h @@ -0,0 +1,110 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc., as Operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef DBLOCKPVT_H +#define DBLOCKPVT_H + +#include "dbLock.h" +#include "epicsSpin.h" + +/* Define to enable additional error checking */ +#undef LOCKSET_DEBUG + +/* Define to disable the free list for lockSets */ +#undef LOCKSET_NOFREE + +/* Define to disable use of recomputeCnt optimization */ +#undef LOCKSET_NOCNT + +/* except for refcount (and lock), all members of dbLockSet + * are guarded by its lock. + */ +typedef struct dbLockSet { + ELLNODE node; + ELLLIST lockRecordList; /* holds lockRecord::node */ + epicsMutexId lock; + unsigned long id; + + int refcount; +#ifdef LOCKSET_DEBUG + int ownercount; + epicsThreadId owner; +#endif + dbLocker *ownerlocker; + ELLNODE lockernode; + + int trace; /*For field TPRO*/ +} lockSet; + +struct lockRecord; + +/* dbCommon.LSET is a plockRecord. + * Except for spin and plockSet, all members of lockRecord are guarded + * by the present lockset lock. + * plockSet is guarded by spin. + */ +typedef struct lockRecord { + ELLNODE node; /* in lockSet::lockRecordList */ + /* The association between lockRecord and lockSet + * can only be changed while the lockSet is held, + * and the lockRecord's spinlock is held. + * It may be read which either lock is held. + */ + lockSet *plockSet; + /* the association between lockRecord and dbCommon never changes */ + dbCommon *precord; + epicsSpinId spin; + + /* temp used during lockset split. + * lockSet must be locked for access + */ + ELLNODE compnode; + unsigned int compflag; +} lockRecord; + +typedef struct { + lockRecord *plr; + /* the last lock found associated with the ref. + * not stable unless lock is locked, or ref spin + * is locked. + */ + lockSet *plockSet; +} lockRecordRef; + +#define DBLOCKER_NALLOC 2 +/* a dbLocker can only be used by a single thread. */ +struct dbLocker { + ELLLIST locked; +#ifndef LOCKSET_NOCNT + size_t recomp; /* snapshot of recomputeCnt when refs[] cache updated */ +#endif + size_t maxrefs; + lockRecordRef refs[DBLOCKER_NALLOC]; /* actual length is maxrefs */ +}; + +/* These are exported for testing only */ +epicsShareFunc lockSet* dbLockGetRef(lockRecord *lr); /* lookup lockset and increment ref count */ +epicsShareFunc void dbLockIncRef(lockSet* ls); +epicsShareFunc void dbLockDecRef(lockSet *ls); + +/* Calling dbLockerPrepare directly is an internal + * optimization used when dbLocker on the stack. + * nrecs must be <=DBLOCKER_NALLOC. + */ +void dbLockerPrepare(struct dbLocker *locker, + struct dbCommon * const *precs, + size_t nrecs); +void dbLockerFinalize(dbLocker *); + +void dbLockSetMerge(struct dbLocker *locker, + struct dbCommon *pfirst, + struct dbCommon *psecond); +void dbLockSetSplit(struct dbLocker *locker, + struct dbCommon *psource, + struct dbCommon *psecond); + +#endif /* DBLOCKPVT_H */ diff --git a/modules/database/src/ioc/db/dbNotify.c b/modules/database/src/ioc/db/dbNotify.c new file mode 100644 index 000000000..c718d3740 --- /dev/null +++ b/modules/database/src/ioc/db/dbNotify.c @@ -0,0 +1,686 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbNotify.c */ +/* + * Author: Marty Kraimer + * Andrew Johnson + * + * Extracted from dbLink.c + */ + +#include +#include +#include +#include +#include + +#include "cantProceed.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsAssert.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsString.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "errlog.h" +#include "errMdef.h" + +#define epicsExportSharedSymbols +#include "callback.h" +#include "dbAccessDefs.h" +#include "dbBase.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "dbStaticLib.h" +#include "link.h" +#include "recGbl.h" + +/*notify state values */ +typedef enum { + notifyNotActive, + notifyWaitForRestart, + notifyRestartCallbackRequested, + notifyRestartInProgress, + notifyProcessInProgress, + notifyUserCallbackRequested, + notifyUserCallbackActive +} notifyState; + +/*structure attached to ppnr field of each record*/ +typedef struct processNotifyRecord { + ellCheckNode waitNode; + ELLLIST restartList; /*list of processNotifys to restart*/ + dbCommon *precord; +} processNotifyRecord; + +#define MAGIC 0xfedc0123 +typedef struct notifyPvt { + ELLNODE node; /*For free list*/ + long magic; + short state; + CALLBACK callback; + ELLLIST waitList; /*list of records for current processNotify*/ + short cancelWait; + short userCallbackWait; + epicsEventId cancelEvent; + epicsEventId userCallbackEvent; +} notifyPvt; + +/* processNotify groups can span locksets if links are dynamically modified*/ +/* Thus a global lock is taken while processNotify fields are accessed */ +typedef struct notifyGlobal { + epicsMutexId lock; + ELLLIST freeList; +} notifyGlobal; + +static notifyGlobal *pnotifyGlobal = 0; + +/*Local routines*/ +static void notifyInit(processNotify *ppn); +static void notifyCleanup(processNotify *ppn); +static void restartCheck(processNotifyRecord *ppnr); +static void callDone(dbCommon *precord,processNotify *ppn); +static void processNotifyCommon(processNotify *ppn,dbCommon *precord); +static void notifyCallback(CALLBACK *pcallback); + +#define ellSafeAdd(list,listnode) \ +{ \ + assert((listnode)->isOnList==0); \ + ellAdd((list),&((listnode)->node)); \ + (listnode)->isOnList=1; \ +} + +#define ellSafeDelete(list,listnode) \ +{ \ + assert((listnode)->isOnList); \ + ellDelete((list),&((listnode)->node)); \ + (listnode)->isOnList=0; \ +} + +static void notifyFree(void *raw) +{ + notifyPvt *pnotifyPvt = raw; + assert(pnotifyPvt->magic==MAGIC); + epicsEventDestroy(pnotifyPvt->cancelEvent); + epicsEventDestroy(pnotifyPvt->userCallbackEvent); + free(pnotifyPvt); +} + +static void notifyInit(processNotify *ppn) +{ + notifyPvt *pnotifyPvt; + + pnotifyPvt = (notifyPvt *) ellFirst(&pnotifyGlobal->freeList); + if (pnotifyPvt) { + ellDelete(&pnotifyGlobal->freeList, &pnotifyPvt->node); + } else { + pnotifyPvt = dbCalloc(1,sizeof(notifyPvt)); + pnotifyPvt->cancelEvent = epicsEventCreate(epicsEventEmpty); + pnotifyPvt->userCallbackEvent = epicsEventCreate(epicsEventEmpty); + pnotifyPvt->magic = MAGIC; + pnotifyPvt->state = notifyNotActive; + } + pnotifyPvt->state = notifyNotActive; + callbackSetCallback(notifyCallback,&pnotifyPvt->callback); + callbackSetUser(ppn,&pnotifyPvt->callback); + callbackSetPriority(priorityLow,&pnotifyPvt->callback); + ellInit(&pnotifyPvt->waitList); + ppn->status = notifyOK; + ppn->wasProcessed = 0; + pnotifyPvt->state = notifyNotActive; + pnotifyPvt->cancelWait = pnotifyPvt->userCallbackWait = 0; + ppn->pnotifyPvt = pnotifyPvt; +} + +static void notifyCleanup(processNotify *ppn) +{ + notifyPvt *pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + + pnotifyPvt->state = notifyNotActive; + ellAdd(&pnotifyGlobal->freeList, &pnotifyPvt->node); + ppn->pnotifyPvt = 0; +} + +static void restartCheck(processNotifyRecord *ppnr) +{ + dbCommon *precord = ppnr->precord; + processNotify *pfirst; + notifyPvt *pnotifyPvt; + + assert(precord->ppn); + pfirst = (processNotify *) ellFirst(&ppnr->restartList); + if (!pfirst) { + precord->ppn = 0; + return; + } + pnotifyPvt = (notifyPvt *) pfirst->pnotifyPvt; + assert(pnotifyPvt->state == notifyWaitForRestart); + /* remove pfirst from restartList */ + ellSafeDelete(&ppnr->restartList, &pfirst->restartNode); + /*make pfirst owner of the record*/ + precord->ppn = pfirst; + /* request callback for pfirst */ + pnotifyPvt->state = notifyRestartCallbackRequested; + callbackRequest(&pnotifyPvt->callback); +} + +static void callDone(dbCommon *precord, processNotify *ppn) +{ + notifyPvt *pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + + epicsMutexUnlock(pnotifyGlobal->lock); + if (ppn->requestType == processGetRequest || + ppn->requestType == putProcessGetRequest) { + ppn->getCallback(ppn, getFieldType); + } + dbScanUnlock(precord); + ppn->doneCallback(ppn); + epicsMutexMustLock(pnotifyGlobal->lock); + if (pnotifyPvt->cancelWait && pnotifyPvt->userCallbackWait) { + errlogPrintf("%s processNotify: both cancelWait and userCallbackWait true." + "This is illegal\n", precord->name); + pnotifyPvt->cancelWait = pnotifyPvt->userCallbackWait = 0; + } + if (!pnotifyPvt->cancelWait && !pnotifyPvt->userCallbackWait) { + notifyCleanup(ppn); + epicsMutexUnlock(pnotifyGlobal->lock); + return; + } + if (pnotifyPvt->cancelWait) { + pnotifyPvt->cancelWait = 0; + epicsEventSignal(pnotifyPvt->cancelEvent); + epicsMutexUnlock(pnotifyGlobal->lock); + return; + } + assert(pnotifyPvt->userCallbackWait); + pnotifyPvt->userCallbackWait = 0; + epicsEventSignal(pnotifyPvt->userCallbackEvent); + epicsMutexUnlock(pnotifyGlobal->lock); + return; +} + +static void processNotifyCommon(processNotify *ppn,dbCommon *precord) +{ + notifyPvt *pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + int didPut = 0; + int doProcess = 0; + + if (precord->ppn && + pnotifyPvt->state != notifyRestartCallbackRequested) { + /* Another processNotify owns the record */ + pnotifyPvt->state = notifyWaitForRestart; + ellSafeAdd(&precord->ppnr->restartList, &ppn->restartNode); + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + return; + } else if (precord->ppn) { + assert(precord->ppn == ppn); + assert(pnotifyPvt->state == notifyRestartCallbackRequested); + } + if (precord->pact) { + precord->ppn = ppn; + ellSafeAdd(&pnotifyPvt->waitList, &precord->ppnr->waitNode); + pnotifyPvt->state = notifyRestartInProgress; + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + return; + } + if (ppn->requestType == putProcessRequest || + ppn->requestType == putProcessGetRequest) { + /* Check if puts disabled */ + if (precord->disp && (dbChannelField(ppn->chan) != (void *) &precord->disp)) { + ppn->putCallback(ppn, putDisabledType); + } else { + didPut = ppn->putCallback(ppn, putType); + } + } + /* Check if dbProcess should be called */ + if (didPut && + ((dbChannelField(ppn->chan) == (void *) &precord->proc) || + (dbChannelFldDes(ppn->chan)->process_passive && precord->scan == 0))) + doProcess = 1; + else + if (ppn->requestType == processGetRequest && + precord->scan == 0) + doProcess = 1; + + if (doProcess) { + ppn->wasProcessed = 1; + precord->ppn = ppn; + ellSafeAdd(&pnotifyPvt->waitList, &precord->ppnr->waitNode); + pnotifyPvt->state = notifyProcessInProgress; + epicsMutexUnlock(pnotifyGlobal->lock); + dbProcess(precord); + dbScanUnlock(precord); + return; + } + if (pnotifyPvt->state == notifyRestartCallbackRequested) { + restartCheck(precord->ppnr); + } + pnotifyPvt->state = notifyUserCallbackActive; + assert(precord->ppn!=ppn); + callDone(precord, ppn); +} + +static void notifyCallback(CALLBACK *pcallback) +{ + processNotify *ppn = NULL; + dbCommon *precord; + notifyPvt *pnotifyPvt; + + callbackGetUser(ppn,pcallback); + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + precord = dbChannelRecord(ppn->chan); + dbScanLock(precord); + epicsMutexMustLock(pnotifyGlobal->lock); + assert(precord->ppnr); + assert(pnotifyPvt->state == notifyRestartCallbackRequested || + pnotifyPvt->state == notifyUserCallbackRequested); + assert(ellCount(&pnotifyPvt->waitList) == 0); + if (pnotifyPvt->cancelWait) { + if (pnotifyPvt->state == notifyRestartCallbackRequested) { + restartCheck(precord->ppnr); + } + epicsEventSignal(pnotifyPvt->cancelEvent); + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + return; + } + if(pnotifyPvt->state == notifyRestartCallbackRequested) { + processNotifyCommon(ppn, precord); + return; + } + /* All done. Clean up and call userCallback */ + pnotifyPvt->state = notifyUserCallbackActive; + assert(precord->ppn!=ppn); + callDone(precord, ppn); +} + +void dbProcessNotifyExit(void) +{ + ellFree2(&pnotifyGlobal->freeList, ¬ifyFree); + epicsMutexDestroy(pnotifyGlobal->lock); + free(pnotifyGlobal); + pnotifyGlobal = NULL; +} + +void dbProcessNotifyInit(void) +{ + if (pnotifyGlobal) + return; + pnotifyGlobal = dbCalloc(1,sizeof(notifyGlobal)); + pnotifyGlobal->lock = epicsMutexMustCreate(); + ellInit(&pnotifyGlobal->freeList); +} + +void dbProcessNotify(processNotify *ppn) +{ + struct dbChannel *chan = ppn->chan; + dbCommon *precord = dbChannelRecord(chan); + short dbfType = dbChannelFieldType(chan); + notifyPvt *pnotifyPvt; + + /* Must handle DBF_XXXLINKs as special case. + * Only dbPutField will change link fields. + * Also the record is not processed as a result + */ + ppn->status = notifyOK; + ppn->wasProcessed = 0; + if (dbfType>=DBF_INLINK && dbfType<=DBF_FWDLINK) { + if (ppn->requestType == putProcessRequest || + ppn->requestType == putProcessGetRequest) { + /* Check if puts disabled */ + if (precord->disp && (dbChannelField(ppn->chan) != (void *) &precord->disp)) { + ppn->putCallback(ppn, putDisabledType); + } else { + ppn->putCallback(ppn, putFieldType); + } + } + if (ppn->requestType == processGetRequest || + ppn->requestType == putProcessGetRequest) { + ppn->getCallback(ppn, getFieldType); + + } + ppn->doneCallback(ppn); + return; + } + dbScanLock(precord); + epicsMutexMustLock(pnotifyGlobal->lock); + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + if (pnotifyPvt && (pnotifyPvt->magic != MAGIC)) { + printf("dbPutNotify:pnotifyPvt was not initialized\n"); + pnotifyPvt = 0; + } + if (pnotifyPvt) { + assert(pnotifyPvt->state == notifyUserCallbackActive); + pnotifyPvt->userCallbackWait = 1; + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + epicsEventWait(pnotifyPvt->userCallbackEvent); + dbScanLock(precord); + epicsMutexMustLock(pnotifyGlobal->lock); + notifyCleanup(ppn); + } + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + assert(!pnotifyPvt); + notifyInit(ppn); + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + if (!precord->ppnr) { + /* make sure record has a processNotifyRecord*/ + precord->ppnr = dbCalloc(1, sizeof(processNotifyRecord)); + precord->ppnr->precord = precord; + ellInit(&precord->ppnr->restartList); + } + processNotifyCommon(ppn, precord); +} + +void dbNotifyCancel(processNotify *ppn) +{ + dbCommon *precord = dbChannelRecord(ppn->chan); + notifyState state; + notifyPvt *pnotifyPvt; + + dbScanLock(precord); + epicsMutexMustLock(pnotifyGlobal->lock); + ppn->status = notifyCanceled; + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + if (!pnotifyPvt || pnotifyPvt->state == notifyNotActive) { + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + return; + } + + state = pnotifyPvt->state; + switch (state) { + case notifyUserCallbackRequested: + case notifyRestartCallbackRequested: + case notifyUserCallbackActive: + /* Callback is scheduled or active, wait for it to complete */ + pnotifyPvt->cancelWait = 1; + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); + epicsEventWait(pnotifyPvt->cancelEvent); + epicsMutexMustLock(pnotifyGlobal->lock); + notifyCleanup(ppn); + epicsMutexUnlock(pnotifyGlobal->lock); + return; + case notifyNotActive: + break; + case notifyWaitForRestart: + assert(precord->ppn); + assert(precord->ppn!=ppn); + ellSafeDelete(&precord->ppnr->restartList,&ppn->restartNode); + break; + case notifyRestartInProgress: + case notifyProcessInProgress: + { /*Take all records out of wait list */ + processNotifyRecord *ppnrWait; + + while ((ppnrWait = (processNotifyRecord *) + ellFirst(&pnotifyPvt->waitList))) { + ellSafeDelete(&pnotifyPvt->waitList, &ppnrWait->waitNode); + restartCheck(ppnrWait); + } + } + if (precord->ppn == ppn) + restartCheck(precord->ppnr); + break; + default: + printf("dbNotify: illegal state for notifyCallback\n"); + } + pnotifyPvt->state = notifyNotActive; + notifyCleanup(ppn); + epicsMutexUnlock(pnotifyGlobal->lock); + dbScanUnlock(precord); +} + +void dbNotifyCompletion(dbCommon *precord) +{ + processNotify *ppn = precord->ppn; + notifyPvt *pnotifyPvt; + + epicsMutexMustLock(pnotifyGlobal->lock); + assert(ppn); + assert(precord->ppnr); + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + if (pnotifyPvt->state != notifyRestartInProgress && + pnotifyPvt->state != notifyProcessInProgress) { + epicsMutexUnlock(pnotifyGlobal->lock); + return; + } + ellSafeDelete(&pnotifyPvt->waitList, &precord->ppnr->waitNode); + if ((ellCount(&pnotifyPvt->waitList) != 0)) { + restartCheck(precord->ppnr); + } + else if (pnotifyPvt->state == notifyProcessInProgress) { + pnotifyPvt->state = notifyUserCallbackRequested; + restartCheck(precord->ppnr); + callbackRequest(&pnotifyPvt->callback); + } + else if(pnotifyPvt->state == notifyRestartInProgress) { + pnotifyPvt->state = notifyRestartCallbackRequested; + callbackRequest(&pnotifyPvt->callback); + } else { + cantProceed("dbNotifyCompletion illegal state"); + } + epicsMutexUnlock(pnotifyGlobal->lock); +} + +void dbNotifyAdd(dbCommon *pfrom, dbCommon *pto) +{ + processNotify *ppn = pfrom->ppn; + notifyPvt *pnotifyPvt; + + if (pto->pact) + return; /*if active it will not be processed*/ + epicsMutexMustLock(pnotifyGlobal->lock); + if (!pto->ppnr) {/* make sure record has a processNotifyRecord*/ + pto->ppnr = dbCalloc(1, sizeof(processNotifyRecord)); + pto->ppnr->precord = pto; + ellInit(&pto->ppnr->restartList); + } + assert(ppn); + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + if (!pto->ppn && + (pnotifyPvt->state == notifyProcessInProgress) && + (pto != dbChannelRecord(ppn->chan))) { + notifyPvt *pnotifyPvt; + pto->ppn = pfrom->ppn; + pnotifyPvt = (notifyPvt *) pfrom->ppn->pnotifyPvt; + ellSafeAdd(&pnotifyPvt->waitList, &pto->ppnr->waitNode); + } + epicsMutexUnlock(pnotifyGlobal->lock); +} + +typedef struct tpnInfo { + epicsEventId callbackDone; + processNotify *ppn; + char buffer[80]; +} tpnInfo; + +static int putCallback(processNotify *ppn, notifyPutType type) +{ + tpnInfo *ptpnInfo = (tpnInfo *) ppn->usrPvt; + int status = 0; + + if (ppn->status == notifyCanceled) + return 0; + ppn->status = notifyOK; + switch (type) { + case putDisabledType: + ppn->status = notifyError; + return 0; + case putFieldType: + status = dbChannelPutField(ppn->chan, DBR_STRING, ptpnInfo->buffer, 1); + break; + case putType: + status = dbChannelPut(ppn->chan, DBR_STRING, ptpnInfo->buffer, 1); + break; + } + if (status) + ppn->status = notifyError; + return 1; +} + +static void getCallback(processNotify *ppn,notifyGetType type) +{ + tpnInfo *ptpnInfo = (tpnInfo *)ppn->usrPvt; + int status = 0; + long no_elements = 1; + long options = 0; + + if(ppn->status==notifyCanceled) { + printf("dbtpn:getCallback notifyCanceled\n"); + return; + } + switch(type) { + case getFieldType: + status = dbChannelGetField(ppn->chan, DBR_STRING, ptpnInfo->buffer, + &options, &no_elements, 0); + break; + case getType: + status = dbChannelGet(ppn->chan, DBR_STRING, ptpnInfo->buffer, + &options, &no_elements, 0); + break; + } + if (status) { + ppn->status = notifyError; + printf("dbtpn:getCallback error\n"); + } else { + printf("dbtpn:getCallback value %s\n", ptpnInfo->buffer); + } +} + +static void doneCallback(processNotify *ppn) +{ + notifyStatus status = ppn->status; + tpnInfo *ptpnInfo = (tpnInfo *) ppn->usrPvt; + const char *pname = dbChannelRecord(ppn->chan)->name; + + if (status == 0) + printf("dbtpnCallback: success record=%s\n", pname); + else + printf("%s dbtpnCallback processNotify.status %d\n", + pname, (int) status); + epicsEventSignal(ptpnInfo->callbackDone); +} + +static void tpnThread(void *pvt) +{ + tpnInfo *ptpnInfo = (tpnInfo *) pvt; + processNotify *ppn = (processNotify *) ptpnInfo->ppn; + + dbProcessNotify(ppn); + epicsEventWait(ptpnInfo->callbackDone); + dbNotifyCancel(ppn); + epicsEventDestroy(ptpnInfo->callbackDone); + dbChannelDelete(ppn->chan); + free(ppn); + free(ptpnInfo); +} + +long dbtpn(char *pname, char *pvalue) +{ + struct dbChannel *chan; + tpnInfo *ptpnInfo; + processNotify *ppn=NULL; + + chan = dbChannelCreate(pname); + if (!chan) { + printf("dbtpn: No such channel"); + return -1; + } + + ppn = dbCalloc(1, sizeof(processNotify)); + ppn->requestType = pvalue ? putProcessRequest : processGetRequest; + ppn->chan = chan; + ppn->putCallback = putCallback; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + + ptpnInfo = dbCalloc(1, sizeof(tpnInfo)); + ptpnInfo->ppn = ppn; + ptpnInfo->callbackDone = epicsEventCreate(epicsEventEmpty); + strncpy(ptpnInfo->buffer, pvalue, 80); + ptpnInfo->buffer[79] = 0; + + ppn->usrPvt = ptpnInfo; + epicsThreadCreate("dbtpn", epicsThreadPriorityHigh, + epicsThreadGetStackSize(epicsThreadStackMedium), tpnThread, ptpnInfo); + return 0; +} + +int dbNotifyDump(void) +{ + epicsMutexLockStatus lockStatus; + dbRecordType *pdbRecordType; + processNotify *ppnRestart; + processNotifyRecord *ppnr; + int itry; + + for (itry = 0; itry < 100; itry++) { + lockStatus = epicsMutexTryLock(pnotifyGlobal->lock); + if (lockStatus == epicsMutexLockOK) + break; + epicsThreadSleep(.05); + } + + for (pdbRecordType = (dbRecordType *) ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *) ellNext(&pdbRecordType->node)) { + dbRecordNode *pdbRecordNode; + + for (pdbRecordNode = (dbRecordNode *) ellFirst(&pdbRecordType->recList); + pdbRecordNode; + pdbRecordNode = (dbRecordNode *) ellNext(&pdbRecordNode->node)) { + dbCommon *precord = pdbRecordNode->precord; + processNotify *ppn; + notifyPvt *pnotifyPvt; + + if (!precord->name[0] || pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) + continue; + ppn = precord->ppn; + if (!ppn || !precord->ppnr) + continue; + if (dbChannelRecord(precord->ppn->chan) != precord) + continue; + + pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt; + printf("%s state %d ppn %p\n waitList\n", + precord->name, pnotifyPvt->state, (void*) ppn); + ppnr = (processNotifyRecord *) ellFirst(&pnotifyPvt->waitList); + while (ppnr) { + printf(" %s pact %d\n", + ppnr->precord->name, ppnr->precord->pact); + ppnr = (processNotifyRecord *) ellNext(&ppnr->waitNode.node); + } + ppnr = precord->ppnr; + if (ppnr) { + ppnRestart = (processNotify *)ellFirst( + &precord->ppnr->restartList); + if (ppnRestart) + printf("%s restartList\n", precord->name); + while (ppnRestart) { + printf(" %s\n", dbChannelRecord(ppnRestart->chan)->name); + ppnRestart = (processNotify *) ellNext( + &ppnRestart->restartNode.node); + } + } + } + } + if (lockStatus == epicsMutexLockOK) + epicsMutexUnlock(pnotifyGlobal->lock); + return 0; +} + diff --git a/modules/database/src/ioc/db/dbNotify.h b/modules/database/src/ioc/db/dbNotify.h new file mode 100644 index 000000000..4b7c3e4dd --- /dev/null +++ b/modules/database/src/ioc/db/dbNotify.h @@ -0,0 +1,168 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbNotify.h */ + +#ifndef INCdbNotifyh +#define INCdbNotifyh + +#include "shareLib.h" +#include "ellLib.h" +#include "epicsEvent.h" +#include "callback.h" + +#ifdef __cplusplus + extern "C" { +#endif + +struct dbCommon; +struct processNotify; + +typedef struct ellCheckNode{ + ELLNODE node; + int isOnList; +} ellCheckNode; + +typedef enum { + processRequest, + putProcessRequest, + processGetRequest, + putProcessGetRequest +} notifyRequestType; + +typedef enum { + putDisabledType, + putFieldType, + putType +} notifyPutType; + +typedef enum { + getFieldType, + getType /* FIXME: Never used? */ +} notifyGetType; + +typedef enum { + notifyOK, + notifyCanceled, + notifyError, + notifyPutDisabled +} notifyStatus; + +typedef struct processNotify { + /* following fields are for private use by dbNotify implementation */ + ellCheckNode restartNode; + void *pnotifyPvt; + /* The following fields are set by dbNotify. */ + notifyStatus status; + int wasProcessed; /* (0,1) => (no,yes) */ + /*The following members are set by user*/ + notifyRequestType requestType; + struct dbChannel *chan; /*dbChannel*/ + int (*putCallback)(struct processNotify *,notifyPutType type); + void (*getCallback)(struct processNotify *,notifyGetType type); + void (*doneCallback)(struct processNotify *); + void *usrPvt; /*for private use of user*/ +} processNotify; + + +/* dbProcessNotify and dbNotifyCancel are called by user*/ +epicsShareFunc void dbProcessNotify(processNotify *pprocessNotify); +epicsShareFunc void dbNotifyCancel(processNotify *pprocessNotify); + +/* dbProcessNotifyInit called by iocInit */ +epicsShareFunc void dbProcessNotifyInit(void); +epicsShareFunc void dbProcessNotifyExit(void); + +/*dbNotifyAdd called by dbScanPassive and dbScanLink*/ +epicsShareFunc void dbNotifyAdd( + struct dbCommon *pfrom,struct dbCommon *pto); +/*dbNotifyCompletion called by recGblFwdLink or dbAccess*/ +epicsShareFunc void dbNotifyCompletion(struct dbCommon *precord); + +/* db_put_process defined here since it requires dbNotify. + * src_type is the old DBR type + * This is called by a dbNotify putCallback that uses oldDbr types + */ +epicsShareFunc int db_put_process( + processNotify *processNotify,notifyPutType type, + int src_type,const void *psrc, int no_elements); + +/* dbtpn is test routine for dbNotify putProcessRequest */ +epicsShareFunc long dbtpn(char *recordname,char *value); + +/* dbNotifyDump is an INVASIVE debug utility. Don't use this needlessly*/ +epicsShareFunc int dbNotifyDump(void); + +/* This module provides code to handle process notify. + * client code semantics are: + * 1) The client code allocates storage for a processNotify structure. + * This structure can be used for multiple calls to dbProcessNotify. + * The client is responsible for setting the following fields : + * requestType - The type of request. + * chan - This is typically set via a call to dbChannelCreate. + * putCallback - If requestType is putProcessRequest or putProcessGetRequest + * getCallback - If request is processGetRequest or putProcessGetRequest + * doneCallback - Must be set + * usrPvt - For exclusive use of client. dbNotify does not access this field + * 2) The client calls dbProcessNotify. + * 3) putCallback is called after dbNotify has claimed the record instance + * but before a potential process is requested. + * The putCallback MUST issue the correct put request + * specified by notifyPutType + * 4) getCallback is called after a possible process is complete + * (including asynchronous completion) but before dbNotify has + * released the record. + * The getCallback MUST issue the correct get request + * specified by notifyGetType + * 5) doneCallback is called when dbNotify has released the record. + * The client can issue a new dbProcessNotify request from + * doneCallback or anytime after doneCallback returns. + * 6) The client can call dbNotifyCancel at any time. + * If a dbProcessNotify is active, dbNotifyCancel will not return until + * the dbNotifyRequest is actually canceled. The client must be prepared + * for a callback to be called while dbNotifyCancel is active. + * + * dbProcessNotify handles the semantics of record locking and deciding + * if a process request is issued and also calls the client callbacks. + * + * A process request is issued if any of the following is true. + * 1) The requester has issued a processs request and record is passive. + * 2) The requester is doing a put, the record is passive, and either + * a) The field description is process passive. + * b) The field is PROC. + * 3) The requester has requested processGet and the record is passive. + * + * iocInit calls processNotifyInit. + * + * The other global routines (dbNotifyAdd and dbNotifyCompletion) are called by: + * + * dbAccess.c + * dbScanPassive and dbScanLink + * call dbNotifyAdd just before calling dbProcess + * dbProcess + * Calls dbNotifyCompletion if dbProcess does not call process + * Unless pact is already true. + * recGbl + * recGblFwdLink calls dbNotifyCompletion + * + * Two fields in dbCommon are used for put notify. + * ppn pointer to processNotify + * If a record is part of a put notify group, + * This field is the address of the associated processNotify. + * As soon as a record completes processing the field is set NULL + * ppnr pointer to processNotifyRecord, which is a private structure + * owned by dbNotify. + * dbNotify is reponsible for this structure. + * + */ +#ifdef __cplusplus +} +#endif + +#endif /*INCdbNotifyh*/ + diff --git a/modules/database/src/ioc/db/dbPutNotifyBlocker.cpp b/modules/database/src/ioc/db/dbPutNotifyBlocker.cpp new file mode 100644 index 000000000..1a796cdbd --- /dev/null +++ b/modules/database/src/ioc/db/dbPutNotifyBlocker.cpp @@ -0,0 +1,231 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: + * Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include +#include + +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "epicsTime.h" +#include "tsFreeList.h" +#include "errMdef.h" +#include "errlog.h" + +#include "caerr.h" // this needs to be eliminated +#include "db_access.h" // this needs to be eliminated + +#define epicsExportSharedSymbols +#include "dbCAC.h" +#include "dbChannelIO.h" +#include "dbPutNotifyBlocker.h" + +dbPutNotifyBlocker::dbPutNotifyBlocker ( epicsMutex & mutexIn ) : + mutex ( mutexIn ), pNotify ( 0 ), + maxValueSize ( sizeof ( this->dbrScalarValue ) ) +{ + memset ( & this->pn, '\0', sizeof ( this->pn ) ); + memset ( & this->dbrScalarValue, '\0', sizeof ( this->dbrScalarValue ) ); + this->pbuffer = & this->dbrScalarValue; +} + +dbPutNotifyBlocker::~dbPutNotifyBlocker () +{ +} + +void dbPutNotifyBlocker::destructor ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->cancel ( cbGuard, guard ); + if ( this->maxValueSize > sizeof ( this->dbrScalarValue ) ) { + char * pBuf = static_cast < char * > ( this->pbuffer ); + delete [] pBuf; + } + this->~dbPutNotifyBlocker (); +} + +void dbPutNotifyBlocker::cancel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pNotify ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + dbNotifyCancel ( &this->pn ); + } + this->pNotify = 0; + this->block.signal (); +} + +void dbPutNotifyBlocker::expandValueBuf ( + epicsGuard < epicsMutex > & guard, unsigned long newSize ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->maxValueSize < newSize ) { + if ( this->maxValueSize > sizeof ( this->dbrScalarValue ) ) { + char * pBuf = static_cast < char * > ( this->pbuffer ); + delete [] pBuf; + this->maxValueSize = sizeof ( this->dbrScalarValue ); + this->pbuffer = & this->dbrScalarValue; + } + this->pbuffer = new char [newSize]; + this->maxValueSize = newSize; + } +} + +extern "C" int putNotifyPut ( processNotify *ppn, notifyPutType type ) +{ + if(ppn->status==notifyCanceled) return 0; +/* + * No locking in this method because only a dbNotifyCancel could interrupt + * and it does not return until cancel is done. + */ + dbPutNotifyBlocker * pBlocker = static_cast < dbPutNotifyBlocker * > ( ppn->usrPvt ); + return db_put_process(ppn,type, + pBlocker->dbrType,pBlocker->pbuffer,pBlocker->nRequest); +} + +extern "C" void putNotifyCompletion ( processNotify *ppn ) +{ + dbPutNotifyBlocker * const pBlocker = + static_cast < dbPutNotifyBlocker * > ( ppn->usrPvt ); + epicsGuard < epicsMutex > guard ( pBlocker->mutex ); + cacWriteNotify * const pNtfy = pBlocker->pNotify; + if ( pNtfy ) { + pBlocker->pNotify = 0; + // Its necessary to signal the initiators now before we call + // the user callback. This is less efficent, and potentially + // causes more thread context switching, but its probably + // unavoidable because its possible that the use callback + // might destroy this object. + pBlocker->block.signal (); + if ( pBlocker->pn.status != notifyOK ) { + pNtfy->exception ( + guard, ECA_PUTFAIL, "put notify unsuccessful", + static_cast < unsigned > (pBlocker->dbrType), + static_cast < unsigned > (pBlocker->nRequest) ); + } + else { + pNtfy->completion ( guard ); + } + } + else { + errlogPrintf ( "put notify completion with nill pNotify?\n" ); + } +} + +void dbPutNotifyBlocker::initiatePutNotify ( + epicsGuard < epicsMutex > & guard, cacWriteNotify & notify, + struct dbChannel * dbch, unsigned type, unsigned long count, + const void * pValue ) +{ + guard. assertIdenticalMutex ( this->mutex ); + epicsTime begin; + bool beginTimeInit = false; + while ( true ) { + if ( this->pNotify == 0 ) { + this->pNotify = & notify; + break; + } + if ( beginTimeInit ) { + if ( epicsTime::getCurrent () - begin > 30.0 ) { + throw cacChannel::requestTimedOut (); + } + } + else { + begin = epicsTime::getCurrent (); + beginTimeInit = true; + } + { + epicsGuardRelease < epicsMutex > autoRelease ( guard ); + this->block.wait ( 1.0 ); + } + } + + if ( count > LONG_MAX ) { + throw cacChannel::outOfBounds(); + } + + if ( type > SHRT_MAX ) { + throw cacChannel::badType(); + } + + this->dbrType = type; + this->nRequest = static_cast < unsigned > ( count ); + this->pn.requestType = putProcessRequest; + this->pn.chan = dbch; + this->pn.putCallback = putNotifyPut; + this->pn.doneCallback = putNotifyCompletion; + this->pn.usrPvt = this; + + unsigned long size = dbr_size_n ( type, count ); + this->expandValueBuf ( guard, size ); + memcpy ( this->pbuffer, pValue, size ); + + { + epicsGuardRelease < epicsMutex > autoRelease ( guard ); + ::dbProcessNotify ( &this->pn ); + } +} + +void dbPutNotifyBlocker::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->show ( guard, level ); +} + +void dbPutNotifyBlocker::show ( + epicsGuard < epicsMutex > &, unsigned level ) const +{ + printf ( "put notify blocker at %p\n", + static_cast ( this ) ); + if ( level > 0u ) { + this->block.show ( level - 1u ); + } +} + +dbSubscriptionIO * dbPutNotifyBlocker::isSubscription () +{ + return 0; +} + +void * dbPutNotifyBlocker::operator new ( size_t size, + tsFreeList < dbPutNotifyBlocker, 64, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +void dbPutNotifyBlocker::operator delete ( void *pCadaver, + tsFreeList < dbPutNotifyBlocker, 64, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +void dbPutNotifyBlocker::operator delete ( void * ) +{ + // Visual C++ .net appears to require operator delete if + // placement operator delete is defined? I smell a ms rat + // because if I declare placement new and delete, but + // comment out the placement delete definition there are + // no undefined symbols. + errlogPrintf ( "%s:%d this compiler is confused about placement delete - memory was probably leaked", + __FILE__, __LINE__ ); +} diff --git a/modules/database/src/ioc/db/dbPutNotifyBlocker.h b/modules/database/src/ioc/db/dbPutNotifyBlocker.h new file mode 100644 index 000000000..6ca11cc5d --- /dev/null +++ b/modules/database/src/ioc/db/dbPutNotifyBlocker.h @@ -0,0 +1,85 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef dbPutNotifyBlockerh +#define dbPutNotifyBlockerh + +#ifdef epicsExportSharedSymbols +#define dbPutNotifyBlockerh_restore_epicsExportSharedSymbols +#undef epicsExportSharedSymbols +#endif + +#include "tsFreeList.h" +#include "compilerDependencies.h" + +#ifdef dbPutNotifyBlockerh_restore_epicsExportSharedSymbols +#define epicsExportSharedSymbols +#endif + +class dbPutNotifyBlocker : public dbBaseIO { +public: + dbPutNotifyBlocker ( epicsMutex & ); + void destructor ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void initiatePutNotify ( epicsGuard < epicsMutex > &, + cacWriteNotify &, struct dbChannel *, + unsigned type, unsigned long count, const void * pValue ); + void cancel ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; + void show ( unsigned level ) const; + void * operator new ( size_t size, + tsFreeList < dbPutNotifyBlocker, 64, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < dbPutNotifyBlocker, 64, epicsMutexNOOP > & )) +private: + processNotify pn; + // + // Include a union of all scalar types + // including fixed length strings so + // that in many cases we can avoid + // allocating another buffer + // + union { + dbr_string_t strval; + dbr_short_t shrtval; + dbr_short_t intval; + dbr_float_t fltval; + dbr_enum_t enmval; + dbr_char_t charval; + dbr_long_t longval; + dbr_double_t doubleval; + } dbrScalarValue; + epicsEvent block; + epicsMutex & mutex; + cacWriteNotify * pNotify; + unsigned long maxValueSize; + // arguments for db_put_field + void *pbuffer; + long nRequest; + short dbrType; + // end arguments for db_put_field + dbSubscriptionIO * isSubscription (); + void expandValueBuf ( + epicsGuard < epicsMutex > &, unsigned long newSize ); + friend void putNotifyCompletion ( processNotify * ppn ); + friend int putNotifyPut ( processNotify *ppn, notifyPutType type ); + dbPutNotifyBlocker ( const dbPutNotifyBlocker & ); + dbPutNotifyBlocker & operator = ( const dbPutNotifyBlocker & ); + virtual ~dbPutNotifyBlocker (); + void operator delete ( void * ); +}; + +#endif // ifndef dbPutNotifyBlockerh + diff --git a/modules/database/src/ioc/db/dbScan.c b/modules/database/src/ioc/db/dbScan.c new file mode 100644 index 000000000..9f7df4c17 --- /dev/null +++ b/modules/database/src/ioc/db/dbScan.c @@ -0,0 +1,1069 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbScan.c */ +/* tasks and subroutines to scan the database */ +/* + * Original Authors: Bob Dalesio & Marty Kraimer + */ + +#include +#include +#include +#include +#include +#include + +#include "cantProceed.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsPrint.h" +#include "epicsRingBytes.h" +#include "epicsStdio.h" +#include "epicsStdlib.h" +#include "epicsString.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "taskwd.h" + +#define epicsExportSharedSymbols +#include "callback.h" +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCommon.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "dbScan.h" +#include "dbStaticLib.h" +#include "devSup.h" +#include "link.h" +#include "recGbl.h" + + +/* Task Control */ +enum ctl {ctlInit, ctlRun, ctlPause, ctlExit}; + +/* Task Startup/Shutdown Synchronization */ +static epicsEventId startStopEvent; + +static volatile enum ctl scanCtl; + +/* SCAN ONCE */ + +static int onceQueueSize = 1000; +static epicsEventId onceSem; +static epicsRingBytesId onceQ; +static epicsThreadId onceTaskId; +static void *exitOnce; + + +/* All other scan types */ +typedef struct scan_list{ + epicsMutexId lock; + ELLLIST list; + short modified;/*has list been modified?*/ +} scan_list; +/*scan_elements are allocated and the address stored in dbCommon.spvt*/ +typedef struct scan_element{ + ELLNODE node; + scan_list *pscan_list; + struct dbCommon *precord; +} scan_element; + + +/* PERIODIC */ + +#define OVERRUN_REPORT_DELAY 10.0 /* Time between initial reports */ +#define OVERRUN_REPORT_MAX 3600.0 /* Maximum time between reports */ +typedef struct periodic_scan_list { + scan_list scan_list; + double period; + const char *name; + unsigned long overruns; + volatile enum ctl scanCtl; + epicsEventId loopEvent; +} periodic_scan_list; + +static int nPeriodic = 0; +static periodic_scan_list **papPeriodic; /* pointer to array of pointers */ +static epicsThreadId *periodicTaskId; /* array of thread ids */ + + +static char *priorityName[NUM_CALLBACK_PRIORITIES] = { + "Low", "Medium", "High" +}; + + +/* EVENT */ + +typedef struct event_list { + CALLBACK callback[NUM_CALLBACK_PRIORITIES]; + scan_list scan_list[NUM_CALLBACK_PRIORITIES]; + struct event_list *next; + char event_name[MAX_STRING_SIZE]; +} event_list; +static event_list * volatile pevent_list[256]; +static epicsMutexId event_lock; + +/* IO_EVENT*/ + +typedef struct io_scan_list { + CALLBACK callback; + scan_list scan_list; +} io_scan_list; + +typedef struct ioscan_head { + struct ioscan_head *next; + struct io_scan_list iosl[NUM_CALLBACK_PRIORITIES]; + io_scan_complete cb; + void *arg; +} ioscan_head; + +static ioscan_head *pioscan_list = NULL; +static epicsMutexId ioscan_lock; + +/* Private routines */ +static void onceTask(void *); +static void initOnce(void); +static void periodicTask(void *arg); +static void initPeriodic(void); +static void deletePeriodic(void); +static void spawnPeriodic(int ind); +static void eventCallback(CALLBACK *pcallback); +static void ioscanInit(void); +static void ioscanCallback(CALLBACK *pcallback); +static void ioscanDestroy(void); +static void printList(scan_list *psl, char *message); +static void scanList(scan_list *psl); +static void buildScanLists(void); +static void addToList(struct dbCommon *precord, scan_list *psl); +static void deleteFromList(struct dbCommon *precord, scan_list *psl); + +void scanStop(void) +{ + int i; + + if (scanCtl == ctlExit) return; + scanCtl = ctlExit; + + interruptAccept = FALSE; + + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = papPeriodic[i]; + + if (!ppsl) continue; + ppsl->scanCtl = ctlExit; + epicsEventSignal(ppsl->loopEvent); + epicsEventWait(startStopEvent); + } + + scanOnce((dbCommon *)&exitOnce); + epicsEventWait(startStopEvent); +} + +void scanCleanup(void) +{ + + deletePeriodic(); + ioscanDestroy(); + + epicsRingBytesDelete(onceQ); + + free(periodicTaskId); + papPeriodic = NULL; + periodicTaskId = NULL; +} + +long scanInit(void) +{ + int i; + + if(!startStopEvent) + startStopEvent = epicsEventMustCreate(epicsEventEmpty); + scanCtl = ctlPause; + + initPeriodic(); + initOnce(); + buildScanLists(); + for (i = 0; i < nPeriodic; i++) + spawnPeriodic(i); + + return 0; +} + +void scanRun(void) +{ + int i; + + interruptAccept = TRUE; + scanCtl = ctlRun; + + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = papPeriodic[i]; + + if (!ppsl) continue; + ppsl->scanCtl = ctlRun; + } +} + +void scanPause(void) +{ + int i; + + for (i = nPeriodic - 1; i >= 0; --i) { + periodic_scan_list *ppsl = papPeriodic[i]; + + if (!ppsl) continue; + ppsl->scanCtl = ctlPause; + } + + scanCtl = ctlPause; + interruptAccept = FALSE; +} + +void scanAdd(struct dbCommon *precord) +{ + int scan; + + /* get the list on which this record belongs */ + scan = precord->scan; + if (scan == menuScanPassive) return; + if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) { + recGblRecordError(-1, (void *)precord, + "scanAdd detected illegal SCAN value"); + } else if (scan == menuScanEvent) { + char* eventname; + int prio; + event_list *pel; + + eventname = precord->evnt; + if (strlen(eventname) >= MAX_STRING_SIZE) { + recGblRecordError(S_db_badField, (void *)precord, + "scanAdd: too long EVNT value"); + return; + } + prio = precord->prio; + if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { + recGblRecordError(-1, (void *)precord, + "scanAdd: illegal prio field"); + return; + } + pel = eventNameToHandle(eventname); + if (pel) addToList(precord, &pel->scan_list[prio]); + } else if (scan == menuScanI_O_Intr) { + ioscan_head *piosh = NULL; + int prio; + DEVSUPFUN get_ioint_info; + + if (precord->dset == NULL){ + recGblRecordError(-1, (void *)precord, + "scanAdd: I/O Intr not valid (no DSET) "); + precord->scan = menuScanPassive; + return; + } + get_ioint_info = precord->dset->get_ioint_info; + if (get_ioint_info == NULL) { + recGblRecordError(-1, (void *)precord, + "scanAdd: I/O Intr not valid (no get_ioint_info)"); + precord->scan = menuScanPassive; + return; + } + if (get_ioint_info(0, precord, &piosh)) { + precord->scan = menuScanPassive; + return; + } + if (piosh == NULL) { + recGblRecordError(-1, (void *)precord, + "scanAdd: I/O Intr not valid"); + precord->scan = menuScanPassive; + return; + } + prio = precord->prio; + if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { + recGblRecordError(-1, (void *)precord, + "scanAdd: illegal prio field"); + precord->scan = menuScanPassive; + return; + } + addToList(precord, &piosh->iosl[prio].scan_list); + } else if (scan >= SCAN_1ST_PERIODIC) { + periodic_scan_list *ppsl = papPeriodic[scan - SCAN_1ST_PERIODIC]; + + if (ppsl) + addToList(precord, &ppsl->scan_list); + } +} + +void scanDelete(struct dbCommon *precord) +{ + short scan; + + /* get the list on which this record belongs */ + scan = precord->scan; + if (scan == menuScanPassive) return; + if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) { + recGblRecordError(-1, (void *)precord, + "scanDelete detected illegal SCAN value"); + } else if (scan == menuScanEvent) { + char* eventname; + int prio; + event_list *pel; + scan_list *psl = 0; + + eventname = precord->evnt; + prio = precord->prio; + if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { + recGblRecordError(-1, (void *)precord, + "scanDelete detected illegal PRIO field"); + return; + } + do /* multithreading: make sure pel is consistent */ + pel = pevent_list[0]; + while (pel != pevent_list[0]); + for (; pel; pel=pel->next) { + if (strcmp(pel->event_name, eventname) == 0) break; + } + if (pel && (psl = &pel->scan_list[prio])) + deleteFromList(precord, psl); + } else if (scan == menuScanI_O_Intr) { + ioscan_head *piosh = NULL; + int prio; + DEVSUPFUN get_ioint_info; + + if (precord->dset==NULL) { + recGblRecordError(-1, (void *)precord, + "scanDelete: I/O Intr not valid (no DSET)"); + return; + } + get_ioint_info=precord->dset->get_ioint_info; + if (get_ioint_info == NULL) { + recGblRecordError(-1, (void *)precord, + "scanDelete: I/O Intr not valid (no get_ioint_info)"); + return; + } + if (get_ioint_info(1, precord, &piosh)) return; + if (piosh == NULL) { + recGblRecordError(-1, (void *)precord, + "scanDelete: I/O Intr not valid"); + return; + } + prio = precord->prio; + if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { + recGblRecordError(-1, (void *)precord, + "scanDelete: get_ioint_info returned illegal priority"); + return; + } + deleteFromList(precord, &piosh->iosl[prio].scan_list); + } else if (scan >= SCAN_1ST_PERIODIC) { + periodic_scan_list *ppsl = papPeriodic[scan - SCAN_1ST_PERIODIC]; + + if (ppsl) + deleteFromList(precord, &ppsl->scan_list); + } +} + +double scanPeriod(int scan) { + periodic_scan_list *ppsl; + + scan -= SCAN_1ST_PERIODIC; + if (scan < 0 || scan >= nPeriodic) + return 0.0; + ppsl = papPeriodic[scan]; + return ppsl ? ppsl->period : 0.0; +} + +int scanppl(double period) /* print periodic scan list(s) */ +{ + dbMenu *pmenu = dbFindMenu(pdbbase, "menuScan"); + char message[80]; + int i; + + if (!pmenu || !papPeriodic) { + printf("scanppl: dbScan subsystem not initialized\n"); + return -1; + } + + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = papPeriodic[i]; + + if (!ppsl) { + const char *choice = pmenu->papChoiceValue[i + SCAN_1ST_PERIODIC]; + + printf("Periodic scan list for SCAN = '%s' not initialized\n", + choice); + continue; + } + if (period > 0.0 && + (fabs(period - ppsl->period) > 0.05)) + continue; + + sprintf(message, "Records with SCAN = '%s' (%lu over-runs):", + ppsl->name, ppsl->overruns); + printList(&ppsl->scan_list, message); + } + return 0; +} + +int scanpel(const char* eventname) /* print event list */ +{ + char message[80]; + int prio; + event_list *pel; + + do /* multithreading: make sure pel is consistent */ + pel = pevent_list[0]; + while (pel != pevent_list[0]); + for (; pel; pel = pel->next) { + if (!eventname || strcmp(pel->event_name, eventname) == 0) { + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + if (ellCount(&pel->scan_list[prio].list) == 0) continue; + sprintf(message, "Event \"%s\" Priority %s", pel->event_name, priorityName[prio]); + printList(&pel->scan_list[prio], message); + } + } + } + return 0; +} + +int scanpiol(void) /* print pioscan_list */ +{ + ioscan_head *piosh; + + ioscanInit(); + epicsMutexMustLock(ioscan_lock); + piosh = pioscan_list; + + while (piosh) { + int prio; + + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + io_scan_list *piosl = &piosh->iosl[prio]; + char message[80]; + + sprintf(message, "IO Event %p: Priority %s", + piosh, priorityName[prio]); + printList(&piosl->scan_list, message); + } + piosh = piosh->next; + } + epicsMutexUnlock(ioscan_lock); + return 0; +} + +static void eventCallback(CALLBACK *pcallback) +{ + scan_list *psl; + + callbackGetUser(psl, pcallback); + scanList(psl); +} + +static void eventOnce(void *arg) +{ + event_lock = epicsMutexMustCreate(); +} + +event_list *eventNameToHandle(const char *eventname) +{ + int prio; + event_list *pel; + static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT; + + if (!eventname || eventname[0] == 0) + return NULL; + + epicsThreadOnce(&onceId, eventOnce, NULL); + epicsMutexMustLock(event_lock); + for (pel = pevent_list[0]; pel; pel=pel->next) { + if (strcmp(pel->event_name, eventname) == 0) break; + } + if (pel == NULL) { + pel = calloc(1, sizeof(event_list)); + if (!pel) + goto done; + strcpy(pel->event_name, eventname); + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + callbackSetUser(&pel->scan_list[prio], &pel->callback[prio]); + callbackSetPriority(prio, &pel->callback[prio]); + callbackSetCallback(eventCallback, &pel->callback[prio]); + pel->scan_list[prio].lock = epicsMutexMustCreate(); + ellInit(&pel->scan_list[prio].list); + } + pel->next=pevent_list[0]; + pevent_list[0]=pel; + { /* backward compatibility */ + char* p; + long e = strtol(eventname, &p, 0); + if (*p == 0 && e > 0 && e <= 255) + pevent_list[e] = pel; + } + } +done: + epicsMutexUnlock(event_lock); + return pel; +} + +void postEvent(event_list *pel) +{ + int prio; + + if (scanCtl != ctlRun) return; + if (!pel) return; + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + if (ellCount(&pel->scan_list[prio].list) >0) + callbackRequest(&pel->callback[prio]); + } +} + +/* backward compatibility */ +void post_event(int event) +{ + event_list* pel; + + if (event <= 0 || event > 255) return; + do { /* multithreading: make sure pel is consistent */ + pel = pevent_list[event]; + } while (pel != pevent_list[event]); + postEvent(pel); +} + +static void ioscanOnce(void *arg) +{ + ioscan_lock = epicsMutexMustCreate(); +} + +static void ioscanInit(void) +{ + static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT; + + epicsThreadOnce(&onceId, ioscanOnce, NULL); +} + +static void ioscanDestroy(void) +{ + ioscan_head *piosh; + + ioscanInit(); + epicsMutexMustLock(ioscan_lock); + piosh = pioscan_list; + pioscan_list = NULL; + epicsMutexUnlock(ioscan_lock); + while (piosh) { + ioscan_head *pnext = piosh->next; + int prio; + + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + epicsMutexDestroy(piosh->iosl[prio].scan_list.lock); + ellFree(&piosh->iosl[prio].scan_list.list); + } + free(piosh); + piosh = pnext; + } +} + +void scanIoInit(IOSCANPVT *pioscanpvt) +{ + ioscan_head *piosh = dbCalloc(1, sizeof(ioscan_head)); + int prio; + + ioscanInit(); + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + io_scan_list *piosl = &piosh->iosl[prio]; + + callbackSetCallback(ioscanCallback, &piosl->callback); + callbackSetPriority(prio, &piosl->callback); + callbackSetUser(piosh, &piosl->callback); + ellInit(&piosl->scan_list.list); + piosl->scan_list.lock = epicsMutexMustCreate(); + } + epicsMutexMustLock(ioscan_lock); + piosh->next = pioscan_list; + pioscan_list = piosh; + epicsMutexUnlock(ioscan_lock); + *pioscanpvt = piosh; +} + +/* Return a bit mask indicating each priority level + * in which a callback request was successfully queued. + */ +unsigned int scanIoRequest(IOSCANPVT piosh) +{ + int prio; + unsigned int queued = 0; + + if (scanCtl != ctlRun) + return 0; + + for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { + io_scan_list *piosl = &piosh->iosl[prio]; + + if (ellCount(&piosl->scan_list.list) > 0) + if (!callbackRequest(&piosl->callback)) + queued |= 1 << prio; + } + + return queued; +} + +unsigned int scanIoImmediate(IOSCANPVT piosh, int prio) +{ + io_scan_list *piosl; + + if (prio<0 || prio>=NUM_CALLBACK_PRIORITIES) + return S_db_errArg; + else if (scanCtl != ctlRun) + return 0; + + piosl = &piosh->iosl[prio]; + + if (ellCount(&piosl->scan_list.list) == 0) + return 0; + + scanList(&piosl->scan_list); + + if (piosh->cb) + piosh->cb(piosh->arg, piosh, prio); + + return 1 << prio; +} + +/* May not be called while a scan request is queued or running */ +void scanIoSetComplete(IOSCANPVT piosh, io_scan_complete cb, void *arg) +{ + piosh->cb = cb; + piosh->arg = arg; +} + +int scanOnce(struct dbCommon *precord) { + return scanOnceCallback(precord, NULL, NULL); +} + +typedef struct { + struct dbCommon *prec; + once_complete cb; + void *usr; +} onceEntry; + +int scanOnceCallback(struct dbCommon *precord, once_complete cb, void *usr) +{ + static int newOverflow = TRUE; + onceEntry ent; + int pushOK; + + ent.prec = precord; + ent.cb = cb; + ent.usr = usr; + + pushOK = epicsRingBytesPut(onceQ, (void*)&ent, sizeof(ent)); + + if (!pushOK) { + if (newOverflow) errlogPrintf("scanOnce: Ring buffer overflow\n"); + newOverflow = FALSE; + } else { + newOverflow = TRUE; + } + epicsEventSignal(onceSem); + + return !pushOK; +} + +static void onceTask(void *arg) +{ + taskwdInsert(0, NULL, NULL); + epicsEventSignal(startStopEvent); + + while (TRUE) { + + epicsEventMustWait(onceSem); + while(1) { + onceEntry ent; + int bytes = epicsRingBytesGet(onceQ, (void*)&ent, sizeof(ent)); + if(bytes==0) + break; + if(bytes!=sizeof(ent)) { + errlogPrintf("onceTask: received incomplete %d of %u\n", + bytes, (unsigned)sizeof(ent)); + continue; /* what to do? */ + } else if (ent.prec == (void*)&exitOnce) goto shutdown; + + dbScanLock(ent.prec); + dbProcess(ent.prec); + dbScanUnlock(ent.prec); + if(ent.cb) + ent.cb(ent.usr, ent.prec); + } + } + +shutdown: + taskwdRemove(0); + epicsEventSignal(startStopEvent); +} + +int scanOnceSetQueueSize(int size) +{ + onceQueueSize = size; + return 0; +} + +static void initOnce(void) +{ + if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) { + cantProceed("initOnce: Ring buffer create failed\n"); + } + if(!onceSem) + onceSem = epicsEventMustCreate(epicsEventEmpty); + onceTaskId = epicsThreadCreate("scanOnce", + epicsThreadPriorityScanLow + nPeriodic, + epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0); + + epicsEventWait(startStopEvent); +} + +static void periodicTask(void *arg) +{ + periodic_scan_list *ppsl = (periodic_scan_list *)arg; + epicsTimeStamp next, reported; + unsigned int overruns = 0; + double report_delay = OVERRUN_REPORT_DELAY; + double overtime = 0.0; + double over_min = 0.0; + double over_max = 0.0; + const double penalty = (ppsl->period >= 2) ? 1 : (ppsl->period / 2); + + taskwdInsert(0, NULL, NULL); + epicsEventSignal(startStopEvent); + + epicsTimeGetCurrent(&next); + reported = next; + + while (ppsl->scanCtl != ctlExit) { + double delay; + epicsTimeStamp now; + + if (ppsl->scanCtl == ctlRun) + scanList(&ppsl->scan_list); + + epicsTimeAddSeconds(&next, ppsl->period); + epicsTimeGetCurrent(&now); + delay = epicsTimeDiffInSeconds(&next, &now); + if (delay <= 0.0) { + if (overtime == 0.0) { + overtime = over_min = over_max = -delay; + } + else { + overtime -= delay; + if (over_min + delay > 0) + over_min = -delay; + if (over_max + delay < 0) + over_max = -delay; + } + delay = penalty; + ppsl->overruns++; + next = now; + epicsTimeAddSeconds(&next, delay); + if (++overruns >= 10 && + epicsTimeDiffInSeconds(&now, &reported) > report_delay) { + errlogPrintf("\ndbScan warning from '%s' scan thread:\n" + "\tScan processing averages %.3f seconds (%.3f .. %.3f).\n" + "\tOver-runs have now happened %u times in a row.\n" + "\tTo fix this, move some records to a slower scan rate.\n", + ppsl->name, ppsl->period + overtime / overruns, + ppsl->period + over_min, ppsl->period + over_max, overruns); + + reported = now; + if (report_delay < (OVERRUN_REPORT_MAX / 2)) + report_delay *= 2; + else + report_delay = OVERRUN_REPORT_MAX; + } + } + else { + overruns = 0; + report_delay = OVERRUN_REPORT_DELAY; + overtime = 0.0; + } + + epicsEventWaitWithTimeout(ppsl->loopEvent, delay); + } + + taskwdRemove(0); + epicsEventSignal(startStopEvent); +} + + +static void initPeriodic(void) +{ + dbMenu *pmenu = dbFindMenu(pdbbase, "menuScan"); + double quantum = epicsThreadSleepQuantum(); + int i; + + if (!pmenu) { + errlogPrintf("initPeriodic: menuScan not present\n"); + return; + } + nPeriodic = pmenu->nChoice - SCAN_1ST_PERIODIC; + papPeriodic = dbCalloc(nPeriodic, sizeof(periodic_scan_list*)); + periodicTaskId = dbCalloc(nPeriodic, sizeof(void *)); + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = dbCalloc(1, sizeof(periodic_scan_list)); + const char *choice = pmenu->papChoiceValue[i + SCAN_1ST_PERIODIC]; + double number; + char *unit; + int status = epicsParseDouble(choice, &number, &unit); + + if (status || number <= 0) { + errlogPrintf("initPeriodic: Bad menuScan choice '%s'\n", choice); + } + else if (!*unit || + !epicsStrCaseCmp(unit, "second") || + !epicsStrCaseCmp(unit, "seconds")) { + ppsl->period = number; + } + else if (!epicsStrCaseCmp(unit, "minute") || + !epicsStrCaseCmp(unit, "minutes")) { + ppsl->period = number * 60; + } + else if (!epicsStrCaseCmp(unit, "hour") || + !epicsStrCaseCmp(unit, "hours")) { + ppsl->period = number * 60 * 60; + } + else if (!epicsStrCaseCmp(unit, "Hz") || + !epicsStrCaseCmp(unit, "Hertz")) { + ppsl->period = 1 / number; + } + else { + errlogPrintf("initPeriodic: Bad menuScan choice '%s'\n", choice); + } + if (ppsl->period == 0) { + free(ppsl); + continue; + } + + ppsl->scan_list.lock = epicsMutexMustCreate(); + ellInit(&ppsl->scan_list.list); + ppsl->name = choice; + ppsl->scanCtl = ctlPause; + ppsl->loopEvent = epicsEventMustCreate(epicsEventEmpty); + + number = ppsl->period / quantum; + if ((ppsl->period < 2 * quantum) || + (number / floor(number) > 1.1)) { + errlogPrintf("initPeriodic: Scan rate '%s' is not achievable.\n", + choice); + } + + papPeriodic[i] = ppsl; + } +} + +static void deletePeriodic(void) +{ + int i; + + for (i = 0; i < nPeriodic; i++) { + periodic_scan_list *ppsl = papPeriodic[i]; + + if (!ppsl) continue; + ellFree(&ppsl->scan_list.list); + epicsEventDestroy(ppsl->loopEvent); + epicsMutexDestroy(ppsl->scan_list.lock); + free(ppsl); + } + + free(papPeriodic); + papPeriodic = NULL; +} + +static void spawnPeriodic(int ind) +{ + periodic_scan_list *ppsl = papPeriodic[ind]; + char taskName[20]; + + if (!ppsl) return; + + sprintf(taskName, "scan-%g", ppsl->period); + periodicTaskId[ind] = epicsThreadCreate( + taskName, epicsThreadPriorityScanLow + ind, + epicsThreadGetStackSize(epicsThreadStackBig), + periodicTask, (void *)ppsl); + + epicsEventWait(startStopEvent); +} + +static void ioscanCallback(CALLBACK *pcallback) +{ + ioscan_head *piosh; + int prio; + + callbackGetUser(piosh, pcallback); + callbackGetPriority(prio, pcallback); + scanList(&piosh->iosl[prio].scan_list); + if (piosh->cb) + piosh->cb(piosh->arg, piosh, prio); +} + +static void printList(scan_list *psl, char *message) +{ + scan_element *pse; + + epicsMutexMustLock(psl->lock); + pse = (scan_element *)ellFirst(&psl->list); + epicsMutexUnlock(psl->lock); + + if (!pse) + return; + + printf("%s\n", message); + while (pse) { + printf(" %-28s\n", pse->precord->name); + epicsMutexMustLock(psl->lock); + if (pse->pscan_list != psl) { + epicsMutexUnlock(psl->lock); + printf(" Scan list changed while printing, try again.\n"); + return; + } + pse = (scan_element *)ellNext(&pse->node); + epicsMutexUnlock(psl->lock); + } +} + +static void scanList(scan_list *psl) +{ + /* When reading this code remember that the call to dbProcess can result + * in the SCAN field being changed in an arbitrary number of records. + */ + + scan_element *pse; + scan_element *prev = NULL; + scan_element *next = NULL; + + epicsMutexMustLock(psl->lock); + psl->modified = FALSE; + pse = (scan_element *)ellFirst(&psl->list); + if (pse) next = (scan_element *)ellNext(&pse->node); + epicsMutexUnlock(psl->lock); + + while (pse) { + struct dbCommon *precord = pse->precord; + + dbScanLock(precord); + dbProcess(precord); + dbScanUnlock(precord); + + epicsMutexMustLock(psl->lock); + if (!psl->modified) { + prev = pse; + pse = (scan_element *)ellNext(&pse->node); + if (pse) next = (scan_element *)ellNext(&pse->node); + } else if (pse->pscan_list == psl) { + /*This scan element is still in same scan list*/ + prev = pse; + pse = (scan_element *)ellNext(&pse->node); + if (pse) next = (scan_element *)ellNext(&pse->node); + psl->modified = FALSE; + } else if (prev && prev->pscan_list == psl) { + /*Previous scan element is still in same scan list*/ + pse = (scan_element *)ellNext(&prev->node); + if (pse) { + prev = (scan_element *)ellPrevious(&pse->node); + next = (scan_element *)ellNext(&pse->node); + } + psl->modified = FALSE; + } else if (next && next->pscan_list == psl) { + /*Next scan element is still in same scan list*/ + pse = next; + prev = (scan_element *)ellPrevious(&pse->node); + next = (scan_element *)ellNext(&pse->node); + psl->modified = FALSE; + } else { + /*Too many changes. Just wait till next period*/ + epicsMutexUnlock(psl->lock); + return; + } + epicsMutexUnlock(psl->lock); + } +} + +static void buildScanLists(void) +{ + dbRecordType *pdbRecordType; + + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + dbRecordNode *pdbRecordNode; + + for (pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList); + pdbRecordNode; + pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { + dbCommon *precord = pdbRecordNode->precord; + + if (!precord->name[0] || + pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) + continue; + + scanAdd(precord); + } + } +} + +static void addToList(struct dbCommon *precord, scan_list *psl) +{ + scan_element *pse, *ptemp; + + epicsMutexMustLock(psl->lock); + pse = precord->spvt; + if (pse == NULL) { + pse = dbCalloc(1, sizeof(scan_element)); + precord->spvt = pse; + pse->precord = precord; + } + pse->pscan_list = psl; + ptemp = (scan_element *)ellLast(&psl->list); + while (ptemp) { + if (ptemp->precord->phas <= precord->phas) { + ellInsert(&psl->list, &ptemp->node, &pse->node); + break; + } + ptemp = (scan_element *)ellPrevious(&ptemp->node); + } + if (ptemp == NULL) ellAdd(&psl->list, (void *)pse); + psl->modified = TRUE; + epicsMutexUnlock(psl->lock); +} + +static void deleteFromList(struct dbCommon *precord, scan_list *psl) +{ + scan_element *pse; + + epicsMutexMustLock(psl->lock); + pse = precord->spvt; + if (pse == NULL) { + epicsMutexUnlock(psl->lock); + errlogPrintf("dbScan: Tried to delete record from wrong scan list!\n" + "\t%s.SPVT = NULL, but psl = %p\n", + precord->name, (void *)psl); + return; + } + if (pse->pscan_list != psl) { + epicsMutexUnlock(psl->lock); + errlogPrintf("dbScan: Tried to delete record from wrong scan list!\n" + "\t%s.SPVT->pscan_list = %p but psl = %p\n", + precord->name, (void *)pse, (void *)psl); + return; + } + pse->pscan_list = NULL; + ellDelete(&psl->list, (void *)pse); + psl->modified = TRUE; + epicsMutexUnlock(psl->lock); +} diff --git a/modules/database/src/ioc/db/dbScan.h b/modules/database/src/ioc/db/dbScan.h new file mode 100644 index 000000000..4ec6dda60 --- /dev/null +++ b/modules/database/src/ioc/db/dbScan.h @@ -0,0 +1,80 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Marty Kraimer + * Date: 07-17-91 + */ + +#ifndef INCdbScanH +#define INCdbScanH + +#include + +#include "menuScan.h" +#include "shareLib.h" +#include "compilerDependencies.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCAN_PASSIVE menuScanPassive +#define SCAN_EVENT menuScanEvent +#define SCAN_IO_EVENT menuScanI_O_Intr +#define SCAN_1ST_PERIODIC (menuScanI_O_Intr + 1) + +#define MAX_PHASE SHRT_MAX +#define MIN_PHASE SHRT_MIN + +/*definitions for I/O Interrupt Scanning */ +struct ioscan_head; + +typedef struct ioscan_head *IOSCANPVT; +typedef struct event_list *EVENTPVT; + +struct dbCommon; + +typedef void (*io_scan_complete)(void *usr, IOSCANPVT, int prio); +typedef void (*once_complete)(void *usr, struct dbCommon*); + +epicsShareFunc long scanInit(void); +epicsShareFunc void scanRun(void); +epicsShareFunc void scanPause(void); +epicsShareFunc void scanStop(void); +epicsShareFunc void scanCleanup(void); + +epicsShareFunc EVENTPVT eventNameToHandle(const char* event); +epicsShareFunc void postEvent(EVENTPVT epvt); +epicsShareFunc void post_event(int event) EPICS_DEPRECATED; +epicsShareFunc void scanAdd(struct dbCommon *); +epicsShareFunc void scanDelete(struct dbCommon *); +epicsShareFunc double scanPeriod(int scan); +epicsShareFunc int scanOnce(struct dbCommon *); +epicsShareFunc int scanOnceCallback(struct dbCommon *, once_complete cb, void *usr); +epicsShareFunc int scanOnceSetQueueSize(int size); + +/*print periodic lists*/ +epicsShareFunc int scanppl(double rate); + +/*print event lists*/ +epicsShareFunc int scanpel(const char *event_name); + +/*print io_event list*/ +epicsShareFunc int scanpiol(void); + +epicsShareFunc void scanIoInit(IOSCANPVT *ppios); +epicsShareFunc unsigned int scanIoRequest(IOSCANPVT pios); +epicsShareFunc unsigned int scanIoImmediate(IOSCANPVT pios, int prio); +epicsShareFunc void scanIoSetComplete(IOSCANPVT, io_scan_complete, void *usr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/database/src/ioc/db/dbServer.c b/modules/database/src/ioc/db/dbServer.c new file mode 100644 index 000000000..5374f02ad --- /dev/null +++ b/modules/database/src/ioc/db/dbServer.c @@ -0,0 +1,140 @@ +/*************************************************************************\ +* Copyright (c) 2014 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Andrew Johnson + */ + +#include +#include + +#include "ellLib.h" +#include "envDefs.h" +#include "epicsStdio.h" + +#define epicsExportSharedSymbols +#include "dbServer.h" + +static ELLLIST serverList = ELLLIST_INIT; +static enum { registering, initialized, running, paused, stopped } + state = registering; +static char *stateNames[] = { + "registering", "initialized", "running", "paused", "stopped" +}; + +int dbRegisterServer(dbServer *psrv) +{ + const char * ignore = envGetConfigParamPtr(&EPICS_IOC_IGNORE_SERVERS); + + if (!psrv || !psrv->name || state != registering) + return -1; + + if (strchr(psrv->name, ' ')) { + fprintf(stderr, "dbRegisterServer: Bad server name '%s'\n", + psrv->name); + return -1; + } + + if (ignore) { + size_t len = strlen(psrv->name); + const char *found; + while ((found = strstr(ignore, psrv->name))) { + /* Make sure the name isn't just a substring */ + if ((found == ignore || (found > ignore && found[-1] == ' ')) && + (found[len] == 0 || found[len] == ' ')) { + fprintf(stderr, "dbRegisterServer: Ignoring '%s', per environment\n", + psrv->name); + return 0; + } + /* It was, try again further down */ + ignore = found + len; + } + } + + if (ellNext(&psrv->node) || ellLast(&serverList) == &psrv->node) { + fprintf(stderr, "dbRegisterServer: '%s' registered twice?\n", + psrv->name); + return -1; + } + + ellAdd(&serverList, &psrv->node); + return 0; +} + +int dbUnregisterServer(dbServer *psrv) +{ + if (state != registering && state != stopped) { + fprintf(stderr, "dbUnregisterServer: Servers still active!\n"); + return -1; + } + if (ellFind(&serverList, &psrv->node) < 0) { + fprintf(stderr, "dbUnregisterServer: '%s' not registered.\n", + psrv->name); + return -1; + } + if (state == stopped && psrv->stop == NULL) { + fprintf(stderr, "dbUnregisterServer: '%s' has no stop() method.\n", + psrv->name); + return -1; + } + + ellDelete(&serverList, &psrv->node); + return 0; +} + +void dbsr(unsigned level) +{ + dbServer *psrv = (dbServer *)ellFirst(&serverList); + + if (!psrv) { + printf("No server layers registered with IOC\n"); + return; + } + + printf("Server state: %s\n", stateNames[state]); + + while (psrv) { + printf("Server '%s'\n", psrv->name); + if (state == running && psrv->report) + psrv->report(level); + psrv = (dbServer *)ellNext(&psrv->node); + } +} + +int dbServerClient(char *pBuf, size_t bufSize) +{ + dbServer *psrv = (dbServer *)ellFirst(&serverList); + + if (state != running) + return -1; + + while (psrv) { + if (psrv->client && + psrv->client(pBuf, bufSize) == 0) + return 0; + psrv = (dbServer *)ellNext(&psrv->node); + } + return -1; +} + +#define STARTSTOP(routine, method, newState) \ +void routine(void) \ +{ \ + dbServer *psrv = (dbServer *)ellFirst(&serverList); \ +\ + while (psrv) { \ + if (psrv->method) \ + psrv->method(); \ + psrv = (dbServer *)ellNext(&psrv->node); \ + } \ + state = newState; \ +} + +STARTSTOP(dbInitServers, init, initialized) +STARTSTOP(dbRunServers, run, running) +STARTSTOP(dbPauseServers, pause, paused) +STARTSTOP(dbStopServers, stop, stopped) diff --git a/modules/database/src/ioc/db/dbServer.h b/modules/database/src/ioc/db/dbServer.h new file mode 100644 index 000000000..ce5244e11 --- /dev/null +++ b/modules/database/src/ioc/db/dbServer.h @@ -0,0 +1,175 @@ +/*************************************************************************\ +* Copyright (c) 2014 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/** + * @file dbServer.h + * @author Andrew Johnson + * + * @brief The IOC's interface to the server layers that publish its PVs. + * + * All server layers which publish IOC record data should initialize a + * dbServer structure and register it with the IOC. The methods that + * the dbServer interface provides allow the IOC to start, pause and stop + * the servers together, and to provide status and debugging information + * to the IOC user/developer through a common set of commands. + * + * @todo No API is provided yet for calling stats() methods. + * Nothing in the IOC calls dbStopServers(), not sure where it should go. + */ + +#ifndef INC_dbServer_H +#define INC_dbServer_H + +#include + +#include "ellLib.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Server information structure. + * + * Every server layer should initialize and register an instance of this + * structure with the IOC by passing it to the dbRegisterServer() routine. + * + * All methods in this struct are optional; use @c NULL if a server is + * unable to support a particular operation (or if it hasn't been + * implemented yet). + */ + +typedef struct dbServer { + /** @brief Linked list node; initialize to @c ELLNODE_INIT */ + ELLNODE node; + + /** @brief A short server identifier; printable, with no spaces */ + const char *name; + + /** @brief Print level-dependent status report to stdout. + * + * @param level Interest level, specifies how much detail to print. + */ + void (* report) (unsigned level); + + /** @brief Get number of channels and clients currently connected. + * + * @param channels NULL or pointer for returning channel count. + * @param clients NULL or pointer for returning client count. + */ + void (* stats) (unsigned *channels, unsigned *clients); + + /** @brief Get identity of client initiating the calling thread. + * + * Must fill in the buffer with the client's identity when called from a + * thread that belongs to this server layer. For other threads, the + * method should do nothing, just return -1. + * @param pBuf Buffer for client identity string. + * @param bufSize Number of chars available in pBuf. + * @return -1 means calling thread is not owned by this server. + * 0 means the thread was recognized and pBuf has been filled in. + */ + int (* client) (char *pBuf, size_t bufSize); + + /** @name Control Methods + * These control methods for the server will be called by routines + * related to iocInit for all registered servers in turn when the IOC + * is being initialized, run, paused and stopped respectively. + * + * @{ + */ + + /** @brief Server init method. + * + * Called for all registered servers by dbInitServers(). + */ + void (* init) (void); + + /** @brief Server run method. + * + * Called for all registered servers by dbRunServers(). + */ + void (* run) (void); + + /** @brief Server pause method. + * + * Called for all registered servers by dbPauseServers(). + */ + void (* pause) (void); + + /** @brief Server stop method. + * + * Called for all registered servers by dbStopServers(). + */ + void (* stop) (void); + + /** @} + */ +} dbServer; + + +/** @brief Register a server layer with the IOC + * + * This should only be called once for each server layer. + * @param psrv Server information structure for the server + */ +epicsShareFunc int dbRegisterServer(dbServer *psrv); + +/** @brief Unregister a server layer + * + * This should only be called when the servers are inactive. + * @param psrv Server information structure for the server + */ +epicsShareFunc int dbUnregisterServer(dbServer *psrv); + +/** @brief Print dbServer Reports. +* + * Calls the report methods of all registered servers. + * This routine is provided as an IOC Shell command. + * @param level Interest level, specifies how much detail to print. + */ +epicsShareFunc void dbsr(unsigned level); + +/** @brief Query servers for client's identity. + * + * This routine is called by code that wants to identify who (or what) + * is responsible for the thread which is currently running. Setting + * the @c TPRO field of a record is one way to trigger this; the identity + * of the calling thread is printed along with the record name whenever + * the record is subsequently processed. + */ +epicsShareFunc int dbServerClient(char *pBuf, size_t bufSize); + +/** @brief Initialize all registered servers. + * + * Calls all dbServer::init() methods. + */ +epicsShareFunc void dbInitServers(void); + +/** @brief Run all registered servers. + * + * Calls all dbServer::run() methods. + */ +epicsShareFunc void dbRunServers(void); + +/** @brief Pause all registered servers. + * + * Calls all dbServer::pause() methods. + */ +epicsShareFunc void dbPauseServers(void); + +/** @brief Stop all registered servers. + * + * Calls all dbServer::stop() methods. + */ +epicsShareFunc void dbStopServers(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbServer_H */ diff --git a/modules/database/src/ioc/db/dbState.c b/modules/database/src/ioc/db/dbState.c new file mode 100644 index 000000000..6de7735cd --- /dev/null +++ b/modules/database/src/ioc/db/dbState.c @@ -0,0 +1,109 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include +#include + +#include "ellLib.h" +#include "epicsMutex.h" +#include "epicsString.h" +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "dbDefs.h" +#include "dbState.h" +#include "dbStaticLib.h" + +static ELLLIST states = ELLLIST_INIT; + +typedef struct dbState { + ELLNODE node; + int status; + char *name; + epicsMutexId lock; /* FIXME: Use atomic operations instead */ +} dbState; + +dbStateId dbStateFind(const char *name) +{ + ELLNODE *node; + dbStateId id; + + for (node = ellFirst(&states); node; node = ellNext(node)) { + id = CONTAINER(node, dbState, node); + if (strcmp(id->name, name) == 0) + return id; + } + return NULL; +} + +dbStateId dbStateCreate(const char *name) +{ + dbStateId id; + + if ((id = dbStateFind(name))) + return id; + + id = callocMustSucceed(1, sizeof(dbState), "createDbState"); + id->name = epicsStrDup(name); + id->lock = epicsMutexMustCreate(); + ellAdd(&states, &id->node); + + return id; +} + +void dbStateSet(dbStateId id) +{ + if (!id) + return; + epicsMutexMustLock(id->lock); + id->status = 1; + epicsMutexUnlock(id->lock); +} + +void dbStateClear(dbStateId id) +{ + if (!id) + return; + epicsMutexMustLock(id->lock); + id->status = 0; + epicsMutexUnlock(id->lock); +} + +int dbStateGet(dbStateId id) +{ + int status; + + if (!id) + return 0; + epicsMutexMustLock(id->lock); + status = id->status; + epicsMutexUnlock(id->lock); + return status; +} + +void dbStateShow(dbStateId id, unsigned int level) +{ + if (level >=1) + printf("id %p '%s' : ", id, id->name); + printf("%s\n", dbStateGet(id) ? "TRUE" : "FALSE"); +} + +void dbStateShowAll(unsigned int level) +{ + ELLNODE *node; + dbStateId id; + + for (node = ellFirst(&states); node; node = ellNext(node)) { + id = CONTAINER(node, dbState, node); + dbStateShow(id, level+1); + } +} diff --git a/modules/database/src/ioc/db/dbState.h b/modules/database/src/ioc/db/dbState.h new file mode 100644 index 000000000..abd23259e --- /dev/null +++ b/modules/database/src/ioc/db/dbState.h @@ -0,0 +1,92 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#ifndef INCdbStateH +#define INCdbStateH + +#include "shareLib.h" + +/** @file dbState.h + * @brief Generic IOC state facility + * + * This library provides a simple global flag facility that can be used to + * synchronize e.g. plugins with IOC-wide states, that may be derived from + * events (either soft events or hard events coming from specialized timing + * and event hardware). + * + * A subset of this API is provided as IOC Shell commands to allow + * command line debugging. + * + */ + +typedef struct dbState *dbStateId; + +/** @brief Create db state. + * + * Creates a new db state with the specified 'name', returning the new id. + * If state with that name already exists, the existing state's id is returned. + * + * Also provided as an IOC Shell command. + * + * @param name Db state name. + * @return Id of db state, NULL for failure. + */ +epicsShareFunc dbStateId dbStateCreate(const char *name); + +/** @brief Find db state. + * + * @param name Db state name. + * @return Id of db state, NULL if not found. + */ +epicsShareFunc dbStateId dbStateFind(const char *name); + +/** @brief Set db state to TRUE. + * + * Also provided as an IOC Shell command. + * + * @param id Db state id. + */ +epicsShareFunc void dbStateSet(dbStateId id); + +/** @brief Set db state to FALSE. + * + * Also provided as an IOC Shell command. + * + * @param id Db state id. + */ +epicsShareFunc void dbStateClear(dbStateId id); + +/** @brief Get db state. + * + * @param id Db state id. + * @return Current db state (0|1). + */ +epicsShareFunc int dbStateGet(dbStateId id); + +/** @brief Print info about db state. + * + * Also provided as an IOC Shell command. + * + * @param id Db state id. + * @param level Interest level. + */ +epicsShareFunc void dbStateShow(dbStateId id, unsigned int level); + +/** @brief Print info about all db states. + * + * Also provided as an IOC Shell command. + * + * @param level Interest level. + */ +epicsShareFunc void dbStateShowAll(unsigned int level); + +#endif // INCdbStateH diff --git a/modules/database/src/ioc/db/dbSubscriptionIO.cpp b/modules/database/src/ioc/db/dbSubscriptionIO.cpp new file mode 100644 index 000000000..5b9b85ef5 --- /dev/null +++ b/modules/database/src/ioc/db/dbSubscriptionIO.cpp @@ -0,0 +1,154 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include + +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "tsFreeList.h" + +#include "db_access.h" // need to eliminate this +#include "cadef.h" // this can be eliminated when the callbacks use the new interface +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "dbCAC.h" +#include "dbChannelIO.h" +#include "db_access_routines.h" + +dbSubscriptionIO::dbSubscriptionIO ( + epicsGuard < epicsMutex > & guard, epicsMutex & mutexIn, + dbContext &, dbChannelIO & chanIO, + dbChannel * dbch, cacStateNotify & notifyIn, unsigned typeIn, + unsigned long countIn, unsigned maskIn, dbEventCtx ctx ) : + mutex ( mutexIn ), count ( countIn ), notify ( notifyIn ), + chan ( chanIO ), es ( 0 ), type ( typeIn ), id ( 0u ) +{ + guard.assertIdenticalMutex ( this->mutex ); + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->es = db_add_event ( ctx, dbch, + dbSubscriptionEventCallback, (void *) this, maskIn ); + if ( this->es == 0 ) { + throw std::bad_alloc(); + } + db_post_single_event ( this->es ); + db_event_enable ( this->es ); + } +} + +dbSubscriptionIO::~dbSubscriptionIO () +{ +} + +void dbSubscriptionIO::destructor ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->~dbSubscriptionIO (); +} + +void dbSubscriptionIO::unsubscribe ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->es ) { + dbEventSubscription tmp = this->es; + this->es = 0; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + db_cancel_event ( tmp ); + } + } +} + +void dbSubscriptionIO::channelDeleteException ( + CallbackGuard &, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->notify.exception ( guard, ECA_CHANDESTROY, + this->chan.pName(guard), this->type, this->count ); +} + +void dbSubscriptionIO::operator delete ( void * ) +{ + // Visual C++ .net appears to require operator delete if + // placement operator delete is defined? I smell a ms rat + // because if I declare placement new and delete, but + // comment out the placement delete definition there are + // no undefined symbols. + errlogPrintf ( "%s:%d this compiler is confused about placement delete - memory was probably leaked", + __FILE__, __LINE__ ); +} + +void * dbSubscriptionIO::operator new ( size_t size, + tsFreeList < dbSubscriptionIO, 256, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +void dbSubscriptionIO::operator delete ( void * pCadaver, + tsFreeList < dbSubscriptionIO, 256, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +extern "C" void dbSubscriptionEventCallback ( void *pPrivate, struct dbChannel * /* dbch */, + int /* eventsRemaining */, struct db_field_log *pfl ) +{ + dbSubscriptionIO * pIO = static_cast < dbSubscriptionIO * > ( pPrivate ); + pIO->chan.callStateNotify ( pIO->type, pIO->count, pfl, pIO->notify ); +} + +void dbSubscriptionIO::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->show ( guard, level ); +} + +void dbSubscriptionIO::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + + printf ( "Data base subscription IO at %p\n", + static_cast ( this ) ); + if ( level > 0u ) { + short tmpType; + if ( this->type < SHRT_MAX ) { + tmpType = static_cast < short > ( this->type ); + printf ( "\ttype %s, count %lu, channel at %p\n", + dbf_type_to_text ( tmpType ), this->count, + static_cast ( &this->chan ) ); + } + else { + printf ( "strange type !, count %lu, channel at %p\n", + this->count, static_cast ( &this->chan ) ); + } + } +} + +dbSubscriptionIO * dbSubscriptionIO::isSubscription () +{ + return this; +} + + diff --git a/modules/database/src/ioc/db/dbTest.c b/modules/database/src/ioc/db/dbTest.c new file mode 100644 index 000000000..4bbd40630 --- /dev/null +++ b/modules/database/src/ioc/db/dbTest.c @@ -0,0 +1,1292 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* database access test subroutines */ + +#include +#include +#include + +#include "cvtFast.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsMutex.h" +#include "epicsStdio.h" +#include "epicsStdlib.h" +#include "epicsString.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "callback.h" +#include "dbAccessDefs.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "dbStaticLib.h" +#include "dbTest.h" +#include "devSup.h" +#include "drvSup.h" +#include "link.h" +#include "recGbl.h" +#include "recSup.h" +#include "special.h" + +#define MAXLINE 80 +struct msgBuff { /* line output structure */ + char out_buff[MAXLINE + 1]; + char *pNext; + char *pLast; + char *pNexTab; + char message[128]; +}; +typedef struct msgBuff TAB_BUFFER; + +#ifndef MIN +# define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#endif +#ifndef MAX +# define MAX(x,y) (((x) < (y)) ? (x) : (y)) +#endif + +/* Local Routines */ +static long nameToAddr(const char *pname, DBADDR *paddr); +static void printDbAddr(DBADDR *paddr); +static void printBuffer(long status, short dbr_type, void *pbuffer, + long reqOptions, long retOptions, long no_elements, + TAB_BUFFER *pMsgBuff, int tab_size); +static int dbpr_report(const char *pname, DBADDR *paddr, int interest_level, + TAB_BUFFER *pMsgBuff, int tab_size); +static void dbpr_msgOut(TAB_BUFFER *pMsgBuff,int tab_size); +static void dbpr_init_msg(TAB_BUFFER *pMsgBuff,int tab_size); +static void dbpr_insert_msg(TAB_BUFFER *pMsgBuff,size_t len,int tab_size); +static void dbpr_msg_flush(TAB_BUFFER *pMsgBuff,int tab_size); + +static char *dbf[DBF_NTYPES] = { + "STRING","CHAR","UCHAR","SHORT","USHORT","LONG","ULONG", + "INT64","UINT64","FLOAT","DOUBLE","ENUM","MENU","DEVICE", + "INLINK","OUTLINK","FWDLINK","NOACCESS" +}; + +static char *dbr[DBR_ENUM+2] = { + "STRING","CHAR","UCHAR","SHORT","USHORT","LONG","ULONG", + "INT64","UINT64","FLOAT","DOUBLE","ENUM","NOACCESS" +}; + +long dba(const char*pname) +{ + DBADDR addr; + + if (!pname || !*pname) { + printf("Usage: dba \"pv name\"\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + printDbAddr(&addr); + return 0; +} + +long dbl(const char *precordTypename, const char *fields) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + int nfields = 0; + int ifield; + char *fieldnames = 0; + char **papfields = 0; + + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + if (precordTypename && + ((*precordTypename == '\0') || !strcmp(precordTypename,"*"))) + precordTypename = NULL; + if (fields && (*fields == '\0')) + fields = NULL; + if (fields) { + char *pnext; + + fieldnames = epicsStrDup(fields); + nfields = 1; + pnext = fieldnames; + while (*pnext && (pnext = strchr(pnext,' '))) { + nfields++; + while (*pnext == ' ') pnext++; + } + papfields = dbCalloc(nfields,sizeof(char *)); + pnext = fieldnames; + for (ifield = 0; ifield < nfields; ifield++) { + papfields[ifield] = pnext; + if (ifield < nfields - 1) { + pnext = strchr(pnext, ' '); + *pnext++ = 0; + while (*pnext == ' ') pnext++; + } + } + } + dbInitEntry(pdbbase, pdbentry); + if (!precordTypename) + status = dbFirstRecordType(pdbentry); + else + status = dbFindRecordType(pdbentry,precordTypename); + if (status) { + printf("No record type\n"); + } + + while (!status) { + status = dbFirstRecord(pdbentry); + while (!status) { + printf("%s", dbGetRecordName(pdbentry)); + for (ifield = 0; ifield < nfields; ifield++) { + char *pvalue; + status = dbFindField(pdbentry, papfields[ifield]); + if (status) { + if (!strcmp(papfields[ifield], "recordType")) { + pvalue = dbGetRecordTypeName(pdbentry); + } + else { + printf(", "); + continue; + } + } + else { + pvalue = dbGetString(pdbentry); + } + printf(", \"%s\"", pvalue ? pvalue : ""); + } + printf("\n"); + status = dbNextRecord(pdbentry); + } + if (precordTypename) + break; + + status = dbNextRecordType(pdbentry); + } + if (nfields > 0) { + free((void *)papfields); + free((void *)fieldnames); + } + dbFinishEntry(pdbentry); + return 0; +} + +long dbnr(int verbose) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + int nrecords; + int naliases; + int trecords = 0; + int taliases = 0; + + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + dbInitEntry(pdbbase, pdbentry); + status = dbFirstRecordType(pdbentry); + if (status) { + printf("No record types loaded\n"); + return 0; + } + + printf("Records Aliases Record Type\n"); + while (!status) { + naliases = dbGetNAliases(pdbentry); + taliases += naliases; + nrecords = dbGetNRecords(pdbentry) - naliases; + trecords += nrecords; + if (verbose || nrecords) + printf(" %5d %5d %s\n", + nrecords, naliases, dbGetRecordTypeName(pdbentry)); + status = dbNextRecordType(pdbentry); + } + + dbFinishEntry(pdbentry); + printf("Total %d records, %d aliases\n", trecords, taliases); + return 0; +} + +long dbla(const char *pmask) +{ + DBENTRY dbentry; + DBENTRY *pdbentry = &dbentry; + long status; + + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + dbInitEntry(pdbbase, pdbentry); + status = dbFirstRecordType(pdbentry); + while (!status) { + for (status = dbFirstRecord(pdbentry); !status; + status = dbNextRecord(pdbentry)) { + char *palias; + + if (!dbIsAlias(pdbentry)) + continue; + + palias = dbGetRecordName(pdbentry); + if (pmask && *pmask && !epicsStrGlobMatch(palias, pmask)) + continue; + dbFindField(pdbentry, "NAME"); + printf("%s -> %s\n", palias, dbGetString(pdbentry)); + } + status = dbNextRecordType(pdbentry); + } + + dbFinishEntry(pdbentry); + return 0; +} + +long dbgrep(const char *pmask) +{ + DBENTRY dbentry; + DBENTRY *pdbentry = &dbentry; + long status; + + if (!pmask || !*pmask) { + printf("Usage: dbgrep \"pattern\"\n"); + return 1; + } + + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + dbInitEntry(pdbbase, pdbentry); + status = dbFirstRecordType(pdbentry); + while (!status) { + status = dbFirstRecord(pdbentry); + while (!status) { + char *pname = dbGetRecordName(pdbentry); + if (epicsStrGlobMatch(pname, pmask)) + puts(pname); + status = dbNextRecord(pdbentry); + } + status = dbNextRecordType(pdbentry); + } + + dbFinishEntry(pdbentry); + return 0; +} + +long dbgf(const char *pname) +{ + /* declare buffer long just to ensure correct alignment */ + long buffer[100]; + long *pbuffer=&buffer[0]; + DBADDR addr; + long options = 0; + long no_elements; + static TAB_BUFFER msg_Buff; + + if (!pname || !*pname) { + printf("Usage: dbgf \"pv name\"\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + if (addr.precord->lset == NULL) { + printf("dbgf only works after iocInit\n"); + return -1; + } + + no_elements = MIN(addr.no_elements, sizeof(buffer)/addr.field_size); + if (addr.dbr_field_type == DBR_ENUM) { + long status = dbGetField(&addr, DBR_STRING, pbuffer, + &options, &no_elements, NULL); + + printBuffer(status, DBR_STRING, pbuffer, 0L, 0L, + no_elements, &msg_Buff, 10); + } + else { + long status = dbGetField(&addr, addr.dbr_field_type, pbuffer, + &options, &no_elements, NULL); + + printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L, + no_elements, &msg_Buff, 10); + } + + msg_Buff.message[0] = '\0'; + dbpr_msgOut(&msg_Buff, 10); + return 0; +} + +long dbpf(const char *pname,const char *pvalue) +{ + DBADDR addr; + long status; + short dbrType; + size_t n = 1; + + if (!pname || !*pname || !pvalue) { + printf("Usage: dbpf \"pv name\", \"value\"\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + if (addr.precord->lset == NULL) { + printf("dbpf only works after iocInit\n"); + return -1; + } + + if (addr.no_elements > 1 && + (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR)) { + dbrType = addr.dbr_field_type; + n = strlen(pvalue) + 1; + } + else { + dbrType = DBR_STRING; + } + + status = dbPutField(&addr, dbrType, pvalue, (long) n); + dbgf(pname); + return status; +} + +long dbpr(const char *pname,int interest_level) +{ + static TAB_BUFFER msg_Buff; + TAB_BUFFER *pMsgBuff = &msg_Buff; + DBADDR addr; + char *pmsg; + int tab_size = 20; + + if (!pname || !*pname) { + printf("Usage: dbpr \"pv name\", level\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + pmsg = pMsgBuff->message; + + if (dbpr_report(pname, &addr, interest_level, pMsgBuff, tab_size)) + return 1; + + pmsg[0] = '\0'; + dbpr_msgOut(pMsgBuff, tab_size); + return 0; +} + +long dbtr(const char *pname) +{ + DBADDR addr; + long status; + struct dbCommon *precord; + + if (!pname || !*pname) { + printf("Usage: dbtr \"pv name\"\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + if (addr.precord->lset == NULL) { + printf("dbtr only works after iocInit\n"); + return -1; + } + + precord = (struct dbCommon*)addr.precord; + if (precord->pact) { + printf("record active\n"); + return 1; + } + + dbScanLock(precord); + status = dbProcess(precord); + dbScanUnlock(precord); + + if (status) + recGblRecordError(status, precord, "dbtr(dbProcess)"); + + dbpr(pname, 3); + return 0; +} + +long dbtgf(const char *pname) +{ + /* declare buffer long just to ensure correct alignment */ + long buffer[400]; + long *pbuffer = &buffer[0]; + DBADDR addr; + long status; + long req_options, ret_options, no_elements; + short dbr_type; + static TAB_BUFFER msg_Buff; + TAB_BUFFER *pMsgBuff = &msg_Buff; + char *pmsg = pMsgBuff->message; + int tab_size = 10; + + if (pname==0 || *pname==0) { + printf("Usage: dbtgf \"pv name\"\n"); + return 1; + } + + if (nameToAddr(pname, &addr)) + return -1; + + if (addr.precord->lset == NULL) { + printf("dbtgf only works after iocInit\n"); + return -1; + } + + /* try all options first */ + req_options = 0xffffffff; + ret_options = req_options; + no_elements = 0; + status = dbGetField(&addr, addr.dbr_field_type, pbuffer, + &ret_options, &no_elements, NULL); + printBuffer(status, addr.dbr_field_type, pbuffer, + req_options, ret_options, no_elements, pMsgBuff, tab_size); + + /* Now try all request types */ + ret_options=0; + + dbr_type = DBR_STRING; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/MAX_STRING_SIZE)); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_CHAR; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt8))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_UCHAR; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt8))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_SHORT; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt16))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_USHORT; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt16))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_LONG; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt32))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_ULONG; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt32))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_INT64; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt64))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_UINT64; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt64))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_FLOAT; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsFloat32))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_DOUBLE; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsFloat64))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + dbr_type = DBR_ENUM; + no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsEnum16))); + status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL); + printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size); + + pmsg[0] = '\0'; + dbpr_msgOut(pMsgBuff, tab_size); + return(0); +} + +long dbtpf(const char *pname, const char *pvalue) +{ + /* declare buffer long just to ensure correct alignment */ + long buffer[100]; + long *pbuffer = buffer; + DBADDR addr; + int put_type; + static TAB_BUFFER msg_Buff; + TAB_BUFFER *pMsgBuff = &msg_Buff; + char *pmsg = pMsgBuff->message; + int tab_size = 10; + + if (!pname || !*pname || !pvalue) { + printf("Usage: dbtpf \"pv name\", \"value\"\n"); + return 1; + } + if (nameToAddr(pname, &addr)) + return -1; + + if (addr.precord->lset == NULL) { + printf("dbtpf only works after iocInit\n"); + return -1; + } + + for (put_type = DBR_STRING; put_type <= DBF_ENUM; put_type++) { + union { + epicsInt8 i8; + epicsUInt8 u8; + epicsInt16 i16; + epicsUInt16 u16; + epicsInt32 i32; + epicsUInt32 u32; + epicsInt64 i64; + epicsUInt64 u64; + epicsFloat32 f32; + epicsFloat64 f64; + epicsEnum16 e16; + } val; + const void *pval = &val; + int valid = 1; + + switch (put_type) { + case DBR_STRING: + pval = pvalue; + break; + case DBR_CHAR: + valid = !epicsParseInt8(pvalue, &val.i8, 10, NULL); + break; + case DBR_UCHAR: + valid = !epicsParseUInt8(pvalue, &val.u8, 10, NULL); + break; + case DBR_SHORT: + valid = !epicsParseInt16(pvalue, &val.i16, 10, NULL); + break; + case DBR_USHORT: + valid = !epicsParseUInt16(pvalue, &val.u16, 10, NULL); + break; + case DBR_LONG: + valid = !epicsParseInt32(pvalue, &val.i32, 10, NULL); + break; + case DBR_ULONG: + valid = !epicsParseUInt32(pvalue, &val.u32, 10, NULL); + break; + case DBR_INT64: + valid = !epicsParseInt64(pvalue, &val.i64, 10, NULL); + break; + case DBR_UINT64: + valid = !epicsParseUInt64(pvalue, &val.u64, 10, NULL); + break; + case DBR_FLOAT: + valid = !epicsParseFloat32(pvalue, &val.f32, NULL); + break; + case DBR_DOUBLE: + valid = !epicsParseFloat64(pvalue, &val.f64, NULL); + break; + case DBR_ENUM: + valid = !epicsParseUInt16(pvalue, &val.e16, 10, NULL); + break; + } + if (valid) { + long status = dbPutField(&addr, put_type, pval, 1); + + if (status) { + printf("Put as DBR_%-6s Failed.\n", dbr[put_type]); + } + else { + long options = 0; + long no_elements = MIN(addr.no_elements, + ((sizeof(buffer))/addr.field_size)); + + printf("Put as DBR_%-6s Ok, result as ", dbr[put_type]); + status = dbGetField(&addr, addr.dbr_field_type, pbuffer, + &options, &no_elements, NULL); + printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L, + no_elements, pMsgBuff, tab_size); + } + } + else { + printf("Cvt to DBR_%s failed.\n", dbr[put_type]); + } + } + + pmsg[0] = '\0'; + dbpr_msgOut(pMsgBuff, tab_size); + return 0; +} + +long dbior(const char *pdrvName,int interest_level) +{ + drvSup *pdrvSup; + struct drvet *pdrvet; + dbRecordType *pdbRecordType; + + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + if (pdrvName && ((*pdrvName == '\0') || !strcmp(pdrvName,"*"))) + pdrvName = NULL; + + for (pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList); + pdrvSup; + pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) { + const char *pname = pdrvSup->name; + + if (pdrvName!=NULL && *pdrvName!='\0' && + (strcmp(pdrvName,pname)!=0)) + continue; + + pdrvet = pdrvSup->pdrvet ; + if (pdrvet == NULL) { + printf("No driver entry table is present for %s\n", pname); + continue; + } + + if (pdrvet->report == NULL) + printf("Driver: %s No report available\n", pname); + else { + printf("Driver: %s\n", pname); + pdrvet->report(interest_level); + } + } + + /* now check devSup reports */ + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + devSup *pdevSup; + + for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + pdevSup; + pdevSup = (devSup *)ellNext(&pdevSup->node)) { + struct dset *pdset = pdevSup->pdset; + const char *pname = pdevSup->name; + + if (!pdset || !pname) + continue; + + if (pdrvName != NULL && *pdrvName != '\0' && + (strcmp(pdrvName, pname) != 0)) + continue; + + if (pdset->report != NULL) { + printf("Device Support: %s\n", pname); + pdset->report(interest_level); + } + } + } + return 0; +} + +int dbhcr(void) +{ + if (!pdbbase) { + printf("No database loaded\n"); + return 0; + } + + dbReportDeviceConfig(pdbbase, stdout); + return 0; +} + +static long nameToAddr(const char *pname, DBADDR *paddr) +{ + long status = dbNameToAddr(pname, paddr); + + if (status) { + printf("PV '%s' not found\n", pname); + } + return status; +} + +static void printDbAddr(DBADDR *paddr) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + short field_type = paddr->field_type; + short dbr_field_type = paddr->dbr_field_type; + + printf("Record Address: %p", paddr->precord); + printf(" Field Address: %p", paddr->pfield); + printf(" Field Description: %p\n", pdbFldDes); + printf(" No Elements: %ld\n", paddr->no_elements); + printf(" Record Type: %s\n", pdbFldDes->pdbRecordType->name); + printf(" Field Type: %d = DBF_%s\n", field_type, + (field_type < 0 || field_type > DBR_NOACCESS) ? "????" : + dbf[field_type]); + printf(" Field Size: %d\n", paddr->field_size); + printf(" Special: %d\n", paddr->special); + + if (dbr_field_type == DBR_NOACCESS) + dbr_field_type = DBR_ENUM + 1; + printf("DBR Field Type: %d = DBR_%s\n", paddr->dbr_field_type, + (dbr_field_type < 0 || dbr_field_type > (DBR_ENUM+1)) ? "????" : + dbr[dbr_field_type]); +} + + +static void printBuffer( + long status, short dbr_type, void *pbuffer, long reqOptions, + long retOptions, long no_elements, TAB_BUFFER *pMsgBuff, int tab_size) +{ + char *pmsg = pMsgBuff->message; + int i; + + if (reqOptions & DBR_STATUS) { + if (retOptions & DBR_STATUS) { + struct dbr_status *pdbr_status = (void *)pbuffer; + + printf("status = %u, severity = %u\n", + pdbr_status->status, + pdbr_status->severity); + } + else { + printf("status and severity not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_status_size; + } + + if (reqOptions & DBR_UNITS) { + if (retOptions & DBR_UNITS) { + struct dbr_units *pdbr_units = (void *)pbuffer; + + printf("units = \"%s\"\n", + pdbr_units->units); + } + else { + printf("units not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_units_size; + } + + if (reqOptions & DBR_PRECISION) { + if (retOptions & DBR_PRECISION){ + struct dbr_precision *pdbr_precision = (void *)pbuffer; + + printf("precision = %ld\n", + pdbr_precision->precision.dp); + } + else { + printf("precision not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_precision_size; + } + + if (reqOptions & DBR_TIME) { + if (retOptions & DBR_TIME) { + struct dbr_time *pdbr_time = (void *)pbuffer; + char time_buf[40]; + epicsTimeToStrftime(time_buf, 40, "%Y-%m-%d %H:%M:%S.%09f", + &pdbr_time->time); + printf("time = %s\n", time_buf); + } + else { + printf("time not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_time_size; + } + + if (reqOptions & DBR_ENUM_STRS) { + if (retOptions & DBR_ENUM_STRS) { + struct dbr_enumStrs *pdbr_enumStrs = (void *)pbuffer; + + printf("no_strs = %u:\n", + pdbr_enumStrs->no_str); + for (i = 0; i < pdbr_enumStrs->no_str; i++) + printf("\t\"%s\"\n", pdbr_enumStrs->strs[i]); + } + else { + printf("enum strings not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_enumStrs_size; + } + + if (reqOptions & DBR_GR_LONG) { + if (retOptions & DBR_GR_LONG) { + struct dbr_grLong *pdbr_grLong = (void *)pbuffer; + + printf("grLong: %d .. %d\n", + pdbr_grLong->lower_disp_limit, + pdbr_grLong->upper_disp_limit); + } + else { + printf("DBRgrLong not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_grLong_size; + } + + if (reqOptions & DBR_GR_DOUBLE) { + if (retOptions & DBR_GR_DOUBLE) { + struct dbr_grDouble *pdbr_grDouble = (void *)pbuffer; + + printf("grDouble: %g .. %g\n", + pdbr_grDouble->lower_disp_limit, + pdbr_grDouble->upper_disp_limit); + } + else { + printf("DBRgrDouble not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_grDouble_size; + } + + if (reqOptions & DBR_CTRL_LONG) { + if (retOptions & DBR_CTRL_LONG){ + struct dbr_ctrlLong *pdbr_ctrlLong = (void *)pbuffer; + + printf("ctrlLong: %d .. %d\n", + pdbr_ctrlLong->lower_ctrl_limit, + pdbr_ctrlLong->upper_ctrl_limit); + } + else { + printf("DBRctrlLong not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_ctrlLong_size; + } + + if (reqOptions & DBR_CTRL_DOUBLE) { + if (retOptions & DBR_CTRL_DOUBLE) { + struct dbr_ctrlDouble *pdbr_ctrlDouble = (void *)pbuffer; + + printf("ctrlDouble: %g .. %g\n", + pdbr_ctrlDouble->lower_ctrl_limit, + pdbr_ctrlDouble->upper_ctrl_limit); + } + else { + printf("DBRctrlDouble not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_ctrlDouble_size; + } + + if (reqOptions & DBR_AL_LONG) { + if (retOptions & DBR_AL_LONG) { + struct dbr_alLong *pdbr_alLong = (void *)pbuffer; + + printf("alLong: %d < %d .. %d < %d\n", + pdbr_alLong->lower_alarm_limit, + pdbr_alLong->lower_warning_limit, + pdbr_alLong->upper_warning_limit, + pdbr_alLong->upper_alarm_limit); + } + else { + printf("DBRalLong not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_alLong_size; + } + + if (reqOptions & DBR_AL_DOUBLE) { + if (retOptions & DBR_AL_DOUBLE) { + struct dbr_alDouble *pdbr_alDouble = (void *)pbuffer; + + printf("alDouble: %g < %g .. %g < %g\n", + pdbr_alDouble->lower_alarm_limit, + pdbr_alDouble->lower_warning_limit, + pdbr_alDouble->upper_warning_limit, + pdbr_alDouble->upper_alarm_limit); + } + else { + printf("DBRalDouble not returned\n"); + } + pbuffer = (char *)pbuffer + dbr_alDouble_size; + } + + /* Now print values */ + if (no_elements == 0) + return; + + if (no_elements == 1) + sprintf(pmsg, "DBF_%s: ", dbr[dbr_type]); + else + sprintf(pmsg, "DBF_%s[%ld]: ", dbr[dbr_type], no_elements); + dbpr_msgOut(pMsgBuff, tab_size); + + if (status != 0) { + strcpy(pmsg, "failed."); + dbpr_msgOut(pMsgBuff, tab_size); + } + else { + switch (dbr_type) { + case DBR_STRING: + for(i=0; i 0) { + int chunk = (len > MAXLINE - 5) ? MAXLINE - 5 : len; + + sprintf(pmsg, "\"%.*s\"", chunk, (char *)pbuffer + i); + len -= chunk; + if (len > 0) + strcat(pmsg, " +"); + dbpr_msgOut(pMsgBuff, tab_size); + } + } + break; + + case DBR_UCHAR: + for (i = 0; i < no_elements; i++) { + epicsUInt32 val = *(epicsUInt8 *) pbuffer; + + sprintf(pmsg, "%u = 0x%x", val, val); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsUInt8); + } + break; + + case DBR_SHORT: + for (i = 0; i < no_elements; i++) { + epicsInt16 val = *(epicsInt16 *) pbuffer; + + sprintf(pmsg, "%hd = 0x%hx", val, val); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsInt16); + } + break; + + case DBR_USHORT: + for (i = 0; i < no_elements; i++) { + epicsUInt16 val = *(epicsUInt16 *) pbuffer; + + sprintf(pmsg, "%hu = 0x%hx", val, val); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsUInt16); + } + break; + + case DBR_LONG: + for (i = 0; i < no_elements; i++) { + epicsInt32 val = *(epicsInt32 *) pbuffer; + + sprintf(pmsg, "%d = 0x%x", val, val); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsInt32); + } + break; + + case DBR_ULONG: + for (i = 0; i < no_elements; i++) { + epicsUInt32 val = *(epicsUInt32 *) pbuffer; + + sprintf(pmsg, "%u = 0x%x", val, val); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsUInt32); + } + break; + + case DBR_INT64: + for (i = 0; i < no_elements; i++) { + epicsInt64 val = *(epicsInt64 *) pbuffer; + + pmsg += cvtInt64ToString(val, pmsg); + strcpy(pmsg, " = "); + pmsg += 3; + cvtInt64ToHexString(val, pmsg); + dbpr_msgOut(pMsgBuff, tab_size); + pmsg = pMsgBuff->message; + pbuffer = (char *)pbuffer + sizeof(epicsInt64); + } + break; + + case DBR_UINT64: + for (i = 0; i < no_elements; i++) { + epicsUInt64 val = *(epicsUInt64 *) pbuffer; + + pmsg += cvtUInt64ToString(val, pmsg); + strcpy(pmsg, " = "); + pmsg += 3; + cvtUInt64ToHexString(val, pmsg); + dbpr_msgOut(pMsgBuff, tab_size); + pmsg = pMsgBuff->message; + pbuffer = (char *)pbuffer + sizeof(epicsUInt64); + } + break; + + case DBR_FLOAT: + for (i = 0; i < no_elements; i++) { + sprintf(pmsg, "%.6g", *((epicsFloat32 *) pbuffer)); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsFloat32); + } + break; + + case DBR_DOUBLE: + for (i = 0; i < no_elements; i++) { + sprintf(pmsg, "%.12g", *((epicsFloat64 *) pbuffer)); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsFloat64); + } + break; + + case DBR_ENUM: + for (i = 0; i < no_elements; i++) { + sprintf(pmsg, "%u", *((epicsEnum16 *) pbuffer)); + dbpr_msgOut(pMsgBuff, tab_size); + pbuffer = (char *)pbuffer + sizeof(epicsEnum16); + } + break; + + default: + sprintf(pmsg, "Bad DBR type %d", dbr_type); + dbpr_msgOut(pMsgBuff, tab_size); + break; + } + } + + dbpr_msg_flush(pMsgBuff, tab_size); +} + +static int dbpr_report( + const char *pname, DBADDR *paddr, int interest_level, + TAB_BUFFER *pMsgBuff, int tab_size) +{ + char *pmsg; + dbFldDes *pdbFldDes = paddr->pfldDes; + dbRecordType *pdbRecordType = pdbFldDes->pdbRecordType; + short n2; + void *pfield; + char *pfield_name; + char *pfield_value; + DBENTRY dbentry; + DBENTRY *pdbentry = &dbentry; + long status; + + dbInitEntry(pdbbase,pdbentry); + status = dbFindRecord(pdbentry,pname); + if (status) { + errMessage(status,pname); + return -1; + } + + pmsg = pMsgBuff->message; + for (n2 = 0; n2 <= pdbRecordType->no_fields - 1; n2++) { + pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->sortFldInd[n2]]; + pfield_name = pdbFldDes->name; + pfield = ((char *)paddr->precord) + pdbFldDes->offset; + if (pdbFldDes->interest > interest_level ) + continue; + switch (pdbFldDes->field_type) { + case DBF_STRING: + case DBF_USHORT: + case DBF_ENUM: + case DBF_FLOAT: + case DBF_CHAR: + case DBF_UCHAR: + case DBF_SHORT: + case DBF_LONG: + case DBF_ULONG: + case DBF_INT64: + case DBF_UINT64: + case DBF_DOUBLE: + case DBF_MENU: + case DBF_DEVICE: + status = dbFindField(pdbentry,pfield_name); + pfield_value = dbGetString(pdbentry); + sprintf(pmsg, "%s: %s", pfield_name, + (pfield_value ? pfield_value : "")); + dbpr_msgOut(pMsgBuff, tab_size); + break; + + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: { + DBLINK *plink = (DBLINK *)pfield; + int ind; + + status = dbFindField(pdbentry,pfield_name); + for (ind=0; indtype) + break; + } + if (ind>=LINK_NTYPES) { + sprintf(pmsg,"%s: Illegal Link Type", pfield_name); + } + else { + sprintf(pmsg,"%s:%s %s", pfield_name, + pamaplinkType[ind].strvalue,dbGetString(pdbentry)); + } + dbpr_msgOut(pMsgBuff, tab_size); + } + break; + + case DBF_NOACCESS: + if (pfield == (void *)&paddr->precord->time) { + /* Special for the TIME field, make it human-readable */ + char time_buf[40]; + epicsTimeToStrftime(time_buf, 40, "%Y-%m-%d %H:%M:%S.%09f", + &paddr->precord->time); + sprintf(pmsg, "%s: %s", pfield_name, time_buf); + dbpr_msgOut(pMsgBuff, tab_size); + } + else if (pdbFldDes->size == sizeof(void *) && + strchr(pdbFldDes->extra, '*')) { + /* Special for pointers, needed on little-endian CPUs */ + sprintf(pmsg, "%s: %p", pfield_name, *(void **)pfield); + dbpr_msgOut(pMsgBuff, tab_size); + } + else { /* just print field as hex bytes */ + unsigned char *pchar = (unsigned char *)pfield; + char temp_buf[61]; + char *ptemp_buf = &temp_buf[0]; + short n = pdbFldDes->size; + short i; + unsigned int value; + + if (n > sizeof(temp_buf)/3) n = sizeof(temp_buf)/3; + for (i=0; imessage; + static int last_tabsize; + + if (!((tab_size == 10) || (tab_size == 20))) { + printf("tab_size not 10 or 20 - dbpr_msgOut()\n"); + return; + } + /* init if first time */ + if (!(pMsgBuff->pNext)) + dbpr_init_msg(pMsgBuff, tab_size); + if (tab_size != last_tabsize) + pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size; + + last_tabsize = tab_size; + /* flush output if NULL string command */ + if (*pmsg == 0) { + dbpr_msg_flush(pMsgBuff, tab_size); + return; + } + /* truncate if too long */ + len = strlen(pmsg); + if (len > MAXLINE) { + err = 1; + len = MAXLINE; + } + + dbpr_insert_msg(pMsgBuff, len, tab_size); + + /* warn if msg gt 80 */ + if (err == 1) { + len = strlen(pmsg); + sprintf(pmsg, "dbpr_msgOut: ERROR - msg length=%d limit=%d ", + (int)len, MAXLINE); + dbpr_insert_msg(pMsgBuff, len, tab_size); + } +} + +static void dbpr_init_msg(TAB_BUFFER *pMsgBuff,int tab_size) +{ + pMsgBuff->pNext = pMsgBuff->out_buff; + pMsgBuff->pLast = pMsgBuff->out_buff + MAXLINE; + pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size; +} + +static void dbpr_insert_msg(TAB_BUFFER *pMsgBuff,size_t len,int tab_size) +{ + size_t current_len; + size_t n; + size_t tot_line; + char *pmsg = pMsgBuff->message; + current_len = strlen(pMsgBuff->out_buff); + tot_line = current_len + len; + + /* flush buffer if overflow would occor */ + if (tot_line > MAXLINE) + dbpr_msg_flush(pMsgBuff, tab_size); + + /* append message to buffer */ + n = 0; + while ((*pmsg) && (n < len)) { + *pMsgBuff->pNext++ = *pmsg++; + + /* position to next tab stop */ + if (*(pMsgBuff->pNexTab - 1) != '\0') + pMsgBuff->pNexTab = pMsgBuff->pNexTab + tab_size; + n++; + } + + /* fill spaces to next tab stop */ + while (*(pMsgBuff->pNexTab - 1) != ' ' + && pMsgBuff->pNext < pMsgBuff->pLast) { + *pMsgBuff->pNext++ = ' '; + } +} + +static void dbpr_msg_flush(TAB_BUFFER *pMsgBuff,int tab_size) +{ + /* skip print if buffer empty */ + if (pMsgBuff->pNext != pMsgBuff->out_buff) + printf("%s\n", pMsgBuff->out_buff); + + memset(pMsgBuff->out_buff,'\0', (int) MAXLINE + 1); + pMsgBuff->pNext = pMsgBuff->out_buff; + pMsgBuff->pNexTab = pMsgBuff->out_buff + tab_size; +} diff --git a/modules/database/src/ioc/db/dbTest.h b/modules/database/src/ioc/db/dbTest.h new file mode 100644 index 000000000..6d457b59e --- /dev/null +++ b/modules/database/src/ioc/db/dbTest.h @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbTest_H +#define INC_dbTest_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*dbAddr info */ +epicsShareFunc long dba(const char *pname); +/*list records*/ +epicsShareFunc long dbl( + const char *precordTypename,const char *fields); +/*list number of records of each type*/ +epicsShareFunc long dbnr(int verbose); +/* list aliases */ +epicsShareFunc long dbla(const char *pmask); +/*list records with mask*/ +epicsShareFunc long dbgrep(const char *pmask); +/*get field value*/ +epicsShareFunc long dbgf(const char *pname); +/*put field value*/ +epicsShareFunc long dbpf(const char *pname,const char *pvalue); +/*print record*/ +epicsShareFunc long dbpr(const char *pname,int interest_level); +/*test record*/ +epicsShareFunc long dbtr(const char *pname); +/*test get field*/ +epicsShareFunc long dbtgf(const char *pname); +/*test put field*/ +epicsShareFunc long dbtpf(const char *pname,const char *pvalue); +/*I/O report */ +epicsShareFunc long dbior( + const char *pdrvName,int interest_level); +/*Hardware Configuration Report*/ +epicsShareFunc int dbhcr(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbTest_H */ diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c new file mode 100644 index 000000000..39b551987 --- /dev/null +++ b/modules/database/src/ioc/db/dbUnitTest.c @@ -0,0 +1,417 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#include + +#define EPICS_PRIVATE_API + +#include "dbmf.h" +#include "epicsUnitTest.h" +#include "osiFileName.h" +#include "osiUnistd.h" +#include "registry.h" +#include "epicsEvent.h" + +#define epicsExportSharedSymbols +#include "dbAccess.h" +#include "dbBase.h" +#include "dbChannel.h" +#include "dbEvent.h" +#include "dbStaticLib.h" +#include "dbUnitTest.h" +#include "initHooks.h" +#include "iocInit.h" +#include "errSymTbl.h" + +static dbEventCtx testEvtCtx; +static epicsMutexId testEvtLock; +static ELLLIST testEvtList; /* holds testMonitor::node */ + +struct testMonitor { + ELLNODE node; + dbEventSubscription sub; + dbChannel *chan; + epicsEventId event; + unsigned count; +}; + +void testdbPrepare(void) +{ + if(!testEvtLock) + testEvtLock = epicsMutexMustCreate(); +} + +void testdbReadDatabase(const char* file, + const char* path, + const char* substitutions) +{ + if(!path) + path = "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common"; + if(dbReadDatabase(&pdbbase, file, path, substitutions)) { + char buf[100]; + const char *cwd = getcwd(buf, sizeof(buf)); + if(!cwd) + cwd = ""; + testAbort("Failed to load test database\ndbReadDatabase(%s,%s,%s)\n from: \"%s\"", + file, path, substitutions, cwd); + } +} + +void testIocInitOk(void) +{ + if(iocBuildIsolated() || iocRun()) + testAbort("Failed to start up test database"); + if(!(testEvtCtx=db_init_events())) + testAbort("Failed to initialize test dbEvent context"); + if(DB_EVENT_OK!=db_start_events(testEvtCtx, "CAS-test", NULL, NULL, epicsThreadPriorityCAServerLow)) + testAbort("Failed to start test dbEvent context"); +} + +void testIocShutdownOk(void) +{ + epicsMutexMustLock(testEvtLock); + if(ellCount(&testEvtList)) + testDiag("Warning, testing monitors still active at testIocShutdownOk()"); + epicsMutexUnlock(testEvtLock); + + db_close_events(testEvtCtx); + testEvtCtx = NULL; + if(iocShutdown()) + testAbort("Failed to shutdown test database"); +} + +void testdbCleanup(void) +{ + dbFreeBase(pdbbase); + db_cleanup_events(); + initHookFree(); + registryFree(); + pdbbase = NULL; + dbmfFreeChunks(); +} + +union anybuf { + epicsAny val; + char valStr[MAX_STRING_SIZE]; + char bytes[sizeof(epicsAny)]; +}; + +long testdbVPutField(const char* pv, short dbrType, va_list ap) +{ + DBADDR addr; + union anybuf pod; + + if (dbNameToAddr(pv, &addr)) { + testFail("Missing PV \"%s\"", pv); + return S_dbLib_recNotFound; + } + + switch(dbrType) { + case DBR_STRING: { + const char *uarg = va_arg(ap,char*); + strncpy(pod.valStr, uarg, sizeof(pod.valStr)); + pod.valStr[sizeof(pod.valStr)-1] = '\0'; + return dbPutField(&addr, dbrType, pod.valStr, 1); + } + + /* The Type parameter takes into consideration + * the C language rules for promotion of argument types + * in variadic functions. + */ +#define OP(DBR,Type,mem) case DBR: {pod.val.mem = va_arg(ap,Type); break;} + OP(DBR_CHAR, int, int8); + OP(DBR_UCHAR, int, uInt8); + OP(DBR_SHORT, int, int16); + OP(DBR_USHORT, int, uInt16); + OP(DBR_LONG, int, int32); + OP(DBR_ULONG, unsigned int, uInt32); + OP(DBR_INT64, long long, int64); + OP(DBR_UINT64, unsigned long long, uInt64); + OP(DBR_FLOAT, double, float32); + OP(DBR_DOUBLE, double, float64); + OP(DBR_ENUM, int, enum16); +#undef OP + default: + testFail("invalid DBR: dbPutField(\"%s\", %d, ...)", + addr.precord->name, dbrType); + return S_db_badDbrtype; + } + + return dbPutField(&addr, dbrType, pod.bytes, 1); +} + +void testdbPutFieldOk(const char* pv, int dbrType, ...) +{ + long ret; + va_list ap; + + va_start(ap, dbrType); + ret = testdbVPutField(pv, dbrType, ap); + va_end(ap); + + testOk(ret==0, "dbPutField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, ret, errSymMsg(ret)); +} + +void testdbPutFieldFail(long status, const char* pv, int dbrType, ...) +{ + long ret; + va_list ap; + + va_start(ap, dbrType); + ret = testdbVPutField(pv, dbrType, ap); + va_end(ap); + + testOk(ret==status, "dbPutField(\"%s\", %d, ...) -> %#lx (%s) == %#lx (%s)", + pv, dbrType, status, errSymMsg(status), ret, errSymMsg(ret)); +} + +void testdbGetFieldEqual(const char* pv, int dbrType, ...) +{ + va_list ap; + + va_start(ap, dbrType); + testdbVGetFieldEqual(pv, dbrType, ap); + va_end(ap); +} + +void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap) +{ + DBADDR addr; + long nReq = 1; + union anybuf pod; + long status; + + if(dbNameToAddr(pv, &addr)) { + testFail("Missing PV \"%s\"", pv); + return; + } + + status = dbGetField(&addr, dbrType, pod.bytes, NULL, &nReq, NULL); + if (status) { + testFail("dbGetField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, status, errSymMsg(status)); + return; + } else if(nReq==0) { + testFail("dbGetField(\"%s\", %d, ...) -> zero length", pv, dbrType); + return; + } + + switch(dbrType) { + case DBR_STRING: { + const char *expect = va_arg(ap, char*); + testOk(strcmp(expect, pod.valStr)==0, + "dbGetField(\"%s\", %d) -> \"%s\" == \"%s\"", + pv, dbrType, expect, pod.valStr); + break; + } +#define OP(DBR,Type,mem,pat) case DBR: {Type expect = va_arg(ap,Type); \ + testOk(expect==pod.val.mem, "dbGetField(\"%s\", %d) -> " pat " == " pat, \ + pv, dbrType, expect, (Type)pod.val.mem); break;} + + OP(DBR_CHAR, int, int8, "%d"); + OP(DBR_UCHAR, int, uInt8, "%d"); + OP(DBR_SHORT, int, int16, "%d"); + OP(DBR_USHORT, int, uInt16, "%d"); + OP(DBR_LONG, int, int32, "%d"); + OP(DBR_ULONG, unsigned int, uInt32, "%u"); + OP(DBR_INT64, long long, int64, "%lld"); + OP(DBR_UINT64, unsigned long long, uInt64, "%llu"); + OP(DBR_FLOAT, double, float32, "%e"); + OP(DBR_DOUBLE, double, float64, "%e"); + OP(DBR_ENUM, int, enum16, "%d"); +#undef OP + default: + testFail("dbGetField(\"%s\", %d) -> unsupported dbf", pv, dbrType); + } +} + +void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf) +{ + DBADDR addr; + long status; + + if (dbNameToAddr(pv, &addr)) { + testFail("Missing PV \"%s\"", pv); + return; + } + + status = dbPutField(&addr, dbrType, pbuf, count); + + testOk(status==0, "dbPutField(\"%s\", dbr=%d, count=%lu, ...) -> %ld", pv, dbrType, count, status); +} + +void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long cnt, const void *pbufraw) +{ + DBADDR addr; + const long vSize = dbValueSize(dbfType); + const long nStore = vSize * nRequest; + long status; + char *gbuf, *gstore; + const char *pbuf = pbufraw; + + if(dbNameToAddr(pv, &addr)) { + testFail("Missing PV \"%s\"", pv); + return; + } + + gbuf = gstore = malloc(nStore); + if(!gbuf && nStore!=0) { /* note that malloc(0) is allowed to return NULL on success */ + testFail("Allocation failed esize=%ld total=%ld", vSize, nStore); + return; + } + + status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest, NULL); + if (status) { + testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status); + + } else { + unsigned match = nRequest==cnt; + long n, N = nRequest < cnt ? nRequest : cnt; + + if(!match) + testDiag("Length mis-match. expected=%lu actual=%lu", cnt, nRequest); + + for(n=0; ncount++; + epicsMutexUnlock(testEvtLock); + epicsEventMustTrigger(mon->event); +} + +testMonitor* testMonitorCreate(const char* pvname, unsigned mask, unsigned opt) +{ + long status; + testMonitor *mon; + dbChannel *chan; + assert(testEvtCtx); + + mon = callocMustSucceed(1, sizeof(*mon), "testMonitorCreate"); + + mon->event = epicsEventMustCreate(epicsEventEmpty); + + chan = mon->chan = dbChannelCreate(pvname); + if(!chan) + testAbort("testMonitorCreate - dbChannelCreate(\"%s\") fails", pvname); + if(!!(status=dbChannelOpen(chan))) + testAbort("testMonitorCreate - dbChannelOpen(\"%s\") fails w/ %ld", pvname, status); + + mon->sub = db_add_event(testEvtCtx, chan, &testmonupdate, mon, mask); + if(!mon->sub) + testAbort("testMonitorCreate - db_add_event(\"%s\") fails", pvname); + + db_event_enable(mon->sub); + + epicsMutexMustLock(testEvtLock); + ellAdd(&testEvtList, &mon->node); + epicsMutexUnlock(testEvtLock); + + return mon; +} + +void testMonitorDestroy(testMonitor *mon) +{ + if(!mon) return; + + db_event_disable(mon->sub); + + epicsMutexMustLock(testEvtLock); + ellDelete(&testEvtList, &mon->node); + epicsMutexUnlock(testEvtLock); + + db_cancel_event(mon->sub); + + dbChannelDelete(mon->chan); + + epicsEventDestroy(mon->event); + + free(mon); +} + +void testMonitorWait(testMonitor *mon) +{ + static const double delay = 60.0; + + switch(epicsEventWaitWithTimeout(mon->event, delay)) + { + case epicsEventOK: + return; + case epicsEventWaitTimeout: + default: + testAbort("testMonitorWait() exceeded %g second timeout", delay); + } +} + +unsigned testMonitorCount(testMonitor *mon, unsigned reset) +{ + unsigned count; + epicsMutexMustLock(testEvtLock); + count = mon->count; + if(reset) { + mon->count = 0; + epicsEventWaitWithTimeout(mon->event, 0); /* clear the event */ + } + epicsMutexUnlock(testEvtLock); + return count; +} + diff --git a/modules/database/src/ioc/db/dbUnitTest.h b/modules/database/src/ioc/db/dbUnitTest.h new file mode 100644 index 000000000..897e5c67d --- /dev/null +++ b/modules/database/src/ioc/db/dbUnitTest.h @@ -0,0 +1,105 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#ifndef EPICSUNITTESTDB_H +#define EPICSUNITTESTDB_H + +#include + +#include "epicsUnitTest.h" +#include "dbAddr.h" +#include "dbCommon.h" + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void testdbPrepare(void); +epicsShareFunc void testdbReadDatabase(const char* file, + const char* path, + const char* substitutions); +epicsShareFunc void testIocInitOk(void); +epicsShareFunc void testIocShutdownOk(void); +epicsShareFunc void testdbCleanup(void); + +/* Correct argument types must be used with this var-arg function! + * Doing otherwise will result in corruption of argument values! + * + * int for DBR_UCHAR, DBR_CHAR, DBR_USHORT, DBR_SHORT, DBR_LONG + * unsigned int for DBR_ULONG + * long long for DBF_INT64 + * unsigned long long for DBF_UINT64 + * double for DBR_FLOAT and DBR_DOUBLE + * const char* for DBR_STRING + * + * eg. + * testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5); + * testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1); + * testdbPutFieldOk("pvname", DBF_STRING, "hello world"); + */ +epicsShareFunc void testdbPutFieldOk(const char* pv, int dbrType, ...); +/* Tests for put failure */ +epicsShareFunc void testdbPutFieldFail(long status, const char* pv, int dbrType, ...); + +epicsShareFunc long testdbVPutField(const char* pv, short dbrType, va_list ap); + +epicsShareFunc void testdbGetFieldEqual(const char* pv, int dbrType, ...); +epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap); + +epicsShareFunc void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf); + +/** + * @param pv PV name string + * @param dbfType One of the DBF_* macros from dbAccess.h + * @param nRequest Number of elements to request from pv + * @param pbufcnt Number of elements pointed to be pbuf + * @param pbuf Expected value buffer + * + * Execute dbGet() of nRequest elements and compare the result with + * pbuf (pbufcnt is an element count). + * Element size is derived from dbfType. + * + * nRequest > pbufcnt will detect truncation. + * nRequest < pbufcnt always fails. + * nRequest ==pbufcnt checks prefix (actual may be longer than expected) + */ +epicsShareFunc void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long pbufcnt, const void *pbuf); + +epicsShareFunc dbCommon* testdbRecordPtr(const char* pv); + +typedef struct testMonitor testMonitor; + +/* Begin monitoring the named PV for changes */ +epicsShareFunc testMonitor* testMonitorCreate(const char* pvname, unsigned dbe_mask, unsigned opt); +/* End monitoring */ +epicsShareFunc void testMonitorDestroy(testMonitor*); +/* Return immediately if it has been updated since create, last wait, + * or reset (count w/ reset=1). + * Otherwise, block until the value of the target PV is updated. + */ +epicsShareFunc void testMonitorWait(testMonitor*); +/* Return the number of monitor events which have occured since create, + * or a pervious reset (called reset=1). + * Calling w/ reset=0 only returns the count. + * Calling w/ reset=1 resets the count to zero and ensures that the next + * wait will block unless subsequent events occur. Returns the previous + * count. + */ +epicsShareFunc unsigned testMonitorCount(testMonitor*, unsigned reset); + +#ifdef __cplusplus +} +#endif + +#endif // EPICSUNITTESTDB_H diff --git a/modules/database/src/ioc/db/db_access.c b/modules/database/src/ioc/db/db_access.c new file mode 100644 index 000000000..35712ddd6 --- /dev/null +++ b/modules/database/src/ioc/db/db_access.c @@ -0,0 +1,1031 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Interface between old database access and new + * + * Author: Marty Kraimer + * Andrew Johnson + */ + +#include +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsConvert.h" +#include "epicsTime.h" +#include "errlog.h" +#include "errMdef.h" + +#define db_accessHFORdb_accessC +#include "db_access.h" +#undef db_accessHFORdb_accessC + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "db_access_routines.h" +#include "dbBase.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "dbLock.h" +#include "dbNotify.h" +#include "dbStaticLib.h" +#include "recSup.h" + + +#define oldDBF_STRING 0 +#define oldDBF_INT 1 +#define oldDBF_SHORT 1 +#define oldDBF_FLOAT 2 +#define oldDBF_ENUM 3 +#define oldDBF_CHAR 4 +#define oldDBF_LONG 5 +#define oldDBF_DOUBLE 6 + +/* data request buffer types */ +#define oldDBR_STRING oldDBF_STRING +#define oldDBR_INT oldDBF_INT +#define oldDBR_SHORT oldDBF_INT +#define oldDBR_FLOAT oldDBF_FLOAT +#define oldDBR_ENUM oldDBF_ENUM +#define oldDBR_CHAR oldDBF_CHAR +#define oldDBR_LONG oldDBF_LONG +#define oldDBR_DOUBLE oldDBF_DOUBLE +#define oldDBR_STS_STRING 7 +#define oldDBR_STS_INT 8 +#define oldDBR_STS_SHORT 8 +#define oldDBR_STS_FLOAT 9 +#define oldDBR_STS_ENUM 10 +#define oldDBR_STS_CHAR 11 +#define oldDBR_STS_LONG 12 +#define oldDBR_STS_DOUBLE 13 +#define oldDBR_TIME_STRING 14 +#define oldDBR_TIME_INT 15 +#define oldDBR_TIME_SHORT 15 +#define oldDBR_TIME_FLOAT 16 +#define oldDBR_TIME_ENUM 17 +#define oldDBR_TIME_CHAR 18 +#define oldDBR_TIME_LONG 19 +#define oldDBR_TIME_DOUBLE 20 +#define oldDBR_GR_STRING 21 +#define oldDBR_GR_INT 22 +#define oldDBR_GR_SHORT 22 +#define oldDBR_GR_FLOAT 23 +#define oldDBR_GR_ENUM 24 +#define oldDBR_GR_CHAR 25 +#define oldDBR_GR_LONG 26 +#define oldDBR_GR_DOUBLE 27 +#define oldDBR_CTRL_STRING 28 +#define oldDBR_CTRL_INT 29 +#define oldDBR_CTRL_SHORT 29 +#define oldDBR_CTRL_FLOAT 30 +#define oldDBR_CTRL_ENUM 31 +#define oldDBR_CTRL_CHAR 32 +#define oldDBR_CTRL_LONG 33 +#define oldDBR_CTRL_DOUBLE 34 +#define oldDBR_PUT_ACKT oldDBR_CTRL_DOUBLE + 1 +#define oldDBR_PUT_ACKS oldDBR_PUT_ACKT + 1 +#define oldDBR_STSACK_STRING oldDBR_PUT_ACKS + 1 +#define oldDBR_CLASS_NAME oldDBR_STSACK_STRING + 1 + +typedef char DBSTRING[MAX_STRING_SIZE]; + +struct dbChannel * dbChannel_create(const char *pname) +{ + dbChannel *chan = dbChannelCreate(pname); + + if (!chan) + return NULL; + + if (INVALID_DB_REQ(dbChannelExportType(chan)) || + dbChannelOpen(chan)) { + dbChannelDelete(chan); + return NULL; + } + + return chan; +} + +int dbChannel_get(struct dbChannel *chan, + int buffer_type, void *pbuffer, long no_elements, void *pfl) +{ + long nRequest = no_elements; + int result = dbChannel_get_count( + chan, buffer_type, pbuffer, &nRequest, pfl); + if (nRequest < no_elements) { + /* The database request returned fewer elements than requested, so + * fill the remainder of the buffer with zeros. + */ + int val_size = dbr_value_size[buffer_type]; + int offset = dbr_size[buffer_type] + (nRequest - 1) * val_size; + int nbytes = (no_elements - nRequest) * val_size; + + memset((char *)pbuffer + offset, 0, nbytes); + } + return result; +} + +/* Performs the work of the public db_get_field API, but also returns the number + * of elements actually copied to the buffer. The caller is responsible for + * zeroing the remaining part of the buffer. */ +int dbChannel_get_count( + struct dbChannel *chan, int buffer_type, + void *pbuffer, long *nRequest, void *pfl) +{ + long status; + long options; + long i; + long zero = 0; + + /* The order of the DBR* elements in the "newSt" structures below is + * very important and must correspond to the order of processing + * in the dbAccess.c dbGet() and getOptions() routines. + */ + + dbScanLock(dbChannelRecord(chan)); + + switch(buffer_type) { + case(oldDBR_STRING): + status = dbChannelGet(chan, DBR_STRING, pbuffer, &zero, nRequest, pfl); + break; +/* case(oldDBR_INT): */ + case(oldDBR_SHORT): + status = dbChannelGet(chan, DBR_SHORT, pbuffer, &zero, nRequest, pfl); + break; + case(oldDBR_FLOAT): + status = dbChannelGet(chan, DBR_FLOAT, pbuffer, &zero, nRequest, pfl); + break; + case(oldDBR_ENUM): + status = dbChannelGet(chan, DBR_ENUM, pbuffer, &zero, nRequest, pfl); + break; + case(oldDBR_CHAR): + status = dbChannelGet(chan, DBR_CHAR, pbuffer, &zero, nRequest, pfl); + break; + case(oldDBR_LONG): + status = dbChannelGet(chan, DBR_LONG, pbuffer, &zero, nRequest, pfl); + break; + case(oldDBR_DOUBLE): + status = dbChannelGet(chan, DBR_DOUBLE, pbuffer, &zero, nRequest, pfl); + break; + + case(oldDBR_STS_STRING): + case(oldDBR_GR_STRING): + case(oldDBR_CTRL_STRING): + { + struct dbr_sts_string *pold = (struct dbr_sts_string *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_STRING, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_STRING, pold->value, &zero, + nRequest, pfl); + } + break; +/* case(oldDBR_STS_INT): */ + case(oldDBR_STS_SHORT): + { + struct dbr_sts_int *pold = (struct dbr_sts_int *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_SHORT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_SHORT, &pold->value, &zero, + nRequest, pfl); + } + break; + case(oldDBR_STS_FLOAT): + { + struct dbr_sts_float *pold = (struct dbr_sts_float *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_FLOAT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_FLOAT, &pold->value, &zero, + nRequest, pfl); + } + break; + case(oldDBR_STS_ENUM): + { + struct dbr_sts_enum *pold = (struct dbr_sts_enum *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_ENUM, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_ENUM, &pold->value, &zero, + nRequest, pfl); + } + break; + case(oldDBR_STS_CHAR): + { + struct dbr_sts_char *pold = (struct dbr_sts_char *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_UCHAR, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_UCHAR, &pold->value, &zero, + nRequest, pfl); + } + break; + case(oldDBR_STS_LONG): + { + struct dbr_sts_long *pold = (struct dbr_sts_long *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_LONG, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + status = dbChannelGet(chan, DBR_LONG, &pold->value, &zero, + nRequest, pfl); + } + break; + case(oldDBR_STS_DOUBLE): + { + struct dbr_sts_double *pold = (struct dbr_sts_double *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_DOUBLE, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + options = 0; + status = dbChannelGet(chan, DBR_DOUBLE, &pold->value, &options, + nRequest, pfl); + } + break; + + case(oldDBR_TIME_STRING): + { + struct dbr_time_string *pold = (struct dbr_time_string *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_STRING, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_STRING, pold->value, &options, + nRequest, pfl); + } + break; +/* case(oldDBR_TIME_INT): */ + case(oldDBR_TIME_SHORT): + { + struct dbr_time_short *pold = (struct dbr_time_short *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_SHORT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_SHORT, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_TIME_FLOAT): + { + struct dbr_time_float *pold = (struct dbr_time_float *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_FLOAT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_FLOAT, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_TIME_ENUM): + { + struct dbr_time_enum *pold = (struct dbr_time_enum *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_ENUM, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_ENUM, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_TIME_CHAR): + { + struct dbr_time_char *pold = (struct dbr_time_char *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_CHAR, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_CHAR, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_TIME_LONG): + { + struct dbr_time_long *pold = (struct dbr_time_long *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_LONG, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_LONG, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_TIME_DOUBLE): + { + struct dbr_time_double *pold = (struct dbr_time_double *)pbuffer; + struct { + DBRstatus + DBRtime + } newSt; + + options = DBR_STATUS | DBR_TIME; + status = dbChannelGet(chan, DBR_DOUBLE, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->stamp = newSt.time; /* structure copy */ + options = 0; + status = dbChannelGet(chan, DBR_DOUBLE, &pold->value, &options, + nRequest, pfl); + } + break; + +/* case(oldDBR_GR_STRING): NOT IMPLEMENTED - use DBR_STS_STRING */ +/* case(oldDBR_GR_INT): */ + case(oldDBR_GR_SHORT): + { + struct dbr_gr_int *pold = (struct dbr_gr_int *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_AL_LONG; + status = dbChannelGet(chan, DBR_SHORT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + options = 0; + status = dbChannelGet(chan, DBR_SHORT, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_GR_FLOAT): + { + struct dbr_gr_float *pold = (struct dbr_gr_float *)pbuffer; + struct { + DBRstatus + DBRunits + DBRprecision + DBRgrDouble + DBRalDouble + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_PRECISION | DBR_GR_DOUBLE | + DBR_AL_DOUBLE; + status = dbChannelGet(chan, DBR_FLOAT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->precision = (dbr_short_t) newSt.precision.dp; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = epicsConvertDoubleToFloat(newSt.upper_disp_limit); + pold->lower_disp_limit = epicsConvertDoubleToFloat(newSt.lower_disp_limit); + pold->upper_alarm_limit = epicsConvertDoubleToFloat(newSt.upper_alarm_limit); + pold->lower_alarm_limit = epicsConvertDoubleToFloat(newSt.lower_alarm_limit); + pold->upper_warning_limit = epicsConvertDoubleToFloat(newSt.upper_warning_limit); + pold->lower_warning_limit = epicsConvertDoubleToFloat(newSt.lower_warning_limit); + options = 0; + status = dbChannelGet(chan, DBR_FLOAT, &pold->value, &options, + nRequest, pfl); + } + break; +/* case(oldDBR_GR_ENUM): see oldDBR_CTRL_ENUM */ + case(oldDBR_GR_CHAR): + { + struct dbr_gr_char *pold = (struct dbr_gr_char *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_AL_LONG; + status = dbChannelGet(chan, DBR_UCHAR, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + options = 0; + status = dbChannelGet(chan, DBR_UCHAR, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_GR_LONG): + { + struct dbr_gr_long *pold = (struct dbr_gr_long *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_AL_LONG; + status = dbChannelGet(chan, DBR_LONG, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + options = 0; + status = dbChannelGet(chan, DBR_LONG, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_GR_DOUBLE): + { + struct dbr_gr_double *pold = (struct dbr_gr_double *)pbuffer; + struct { + DBRstatus + DBRunits + DBRprecision + DBRgrDouble + DBRalDouble + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_PRECISION | DBR_GR_DOUBLE | + DBR_AL_DOUBLE; + status = dbChannelGet(chan, DBR_DOUBLE, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->precision = (dbr_short_t) newSt.precision.dp; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + options = 0; + status = dbChannelGet(chan, DBR_DOUBLE, &pold->value, &options, + nRequest, pfl); + } + break; + +/* case(oldDBR_CTRL_INT): */ + case(oldDBR_CTRL_SHORT): + { + struct dbr_ctrl_int *pold = (struct dbr_ctrl_int *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRctrlLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_CTRL_LONG | + DBR_AL_LONG; + status = dbChannelGet(chan, DBR_SHORT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + pold->upper_ctrl_limit = newSt.upper_ctrl_limit; + pold->lower_ctrl_limit = newSt.lower_ctrl_limit; + options = 0; + status = dbChannelGet(chan, DBR_SHORT, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_CTRL_FLOAT): + { + struct dbr_ctrl_float *pold = (struct dbr_ctrl_float *)pbuffer; + struct { + DBRstatus + DBRunits + DBRprecision + DBRgrDouble + DBRctrlDouble + DBRalDouble + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_PRECISION | DBR_GR_DOUBLE | + DBR_CTRL_DOUBLE | DBR_AL_DOUBLE; + status = dbChannelGet(chan, DBR_FLOAT, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->precision = (dbr_short_t) newSt.precision.dp; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = epicsConvertDoubleToFloat(newSt.upper_disp_limit); + pold->lower_disp_limit = epicsConvertDoubleToFloat(newSt.lower_disp_limit); + pold->upper_alarm_limit = epicsConvertDoubleToFloat(newSt.upper_alarm_limit); + pold->lower_alarm_limit = epicsConvertDoubleToFloat(newSt.lower_alarm_limit); + pold->upper_warning_limit = epicsConvertDoubleToFloat(newSt.upper_warning_limit); + pold->lower_warning_limit = epicsConvertDoubleToFloat(newSt.lower_warning_limit); + pold->upper_ctrl_limit = epicsConvertDoubleToFloat(newSt.upper_ctrl_limit); + pold->lower_ctrl_limit = epicsConvertDoubleToFloat(newSt.lower_ctrl_limit); + options = 0; + status = dbChannelGet(chan, DBR_FLOAT, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_GR_ENUM): + case(oldDBR_CTRL_ENUM): + { + struct dbr_ctrl_enum *pold = (struct dbr_ctrl_enum *)pbuffer; + struct { + DBRstatus + DBRenumStrs + } newSt; + short no_str; + + memset(pold, '\0', sizeof(struct dbr_ctrl_enum)); + /* first get status and severity */ + options = DBR_STATUS | DBR_ENUM_STRS; + status = dbChannelGet(chan, DBR_ENUM, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + no_str = newSt.no_str; + if (no_str>16) no_str=16; + pold->no_str = no_str; + for (i = 0; i < no_str; i++) + strncpy(pold->strs[i], newSt.strs[i], sizeof(pold->strs[i])); + /*now get values*/ + options = 0; + status = dbChannelGet(chan, DBR_ENUM, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_CTRL_CHAR): + { + struct dbr_ctrl_char *pold = (struct dbr_ctrl_char *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRctrlLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_CTRL_LONG | + DBR_AL_LONG; + status = dbChannelGet(chan, DBR_UCHAR, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + pold->upper_ctrl_limit = newSt.upper_ctrl_limit; + pold->lower_ctrl_limit = newSt.lower_ctrl_limit; + options = 0; + status = dbChannelGet(chan, DBR_UCHAR, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_CTRL_LONG): + { + struct dbr_ctrl_long *pold = (struct dbr_ctrl_long *)pbuffer; + struct { + DBRstatus + DBRunits + DBRgrLong + DBRctrlLong + DBRalLong + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_GR_LONG | DBR_CTRL_LONG | + DBR_AL_LONG; + status = dbChannelGet(chan, DBR_LONG, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + pold->upper_ctrl_limit = newSt.upper_ctrl_limit; + pold->lower_ctrl_limit = newSt.lower_ctrl_limit; + options = 0; + status = dbChannelGet(chan, DBR_LONG, &pold->value, &options, + nRequest, pfl); + } + break; + case(oldDBR_CTRL_DOUBLE): + { + struct dbr_ctrl_double *pold = (struct dbr_ctrl_double *)pbuffer; + struct { + DBRstatus + DBRunits + DBRprecision + DBRgrDouble + DBRctrlDouble + DBRalDouble + } newSt; + + options = DBR_STATUS | DBR_UNITS | DBR_PRECISION | DBR_GR_DOUBLE | + DBR_CTRL_DOUBLE | DBR_AL_DOUBLE; + status = dbChannelGet(chan, DBR_DOUBLE, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->precision = (dbr_short_t) newSt.precision.dp; + strncpy(pold->units, newSt.units, MAX_UNITS_SIZE); + pold->units[MAX_UNITS_SIZE-1] = '\0'; + pold->upper_disp_limit = newSt.upper_disp_limit; + pold->lower_disp_limit = newSt.lower_disp_limit; + pold->upper_alarm_limit = newSt.upper_alarm_limit; + pold->upper_warning_limit = newSt.upper_warning_limit; + pold->lower_warning_limit = newSt.lower_warning_limit; + pold->lower_alarm_limit = newSt.lower_alarm_limit; + pold->upper_ctrl_limit = newSt.upper_ctrl_limit; + pold->lower_ctrl_limit = newSt.lower_ctrl_limit; + options = 0; + status = dbChannelGet(chan, DBR_DOUBLE, &pold->value, &options, + nRequest, pfl); + } + break; + + case(oldDBR_STSACK_STRING): + { + struct dbr_stsack_string *pold = (struct dbr_stsack_string *)pbuffer; + struct { + DBRstatus + } newSt; + + options = DBR_STATUS; + status = dbChannelGet(chan, DBR_STRING, &newSt, &options, &zero, pfl); + pold->status = newSt.status; + pold->severity = newSt.severity; + pold->ackt = newSt.ackt; + pold->acks = newSt.acks; + options = 0; + status = dbChannelGet(chan, DBR_STRING, pold->value, + &options, nRequest, pfl); + } + break; + + case(oldDBR_CLASS_NAME): + { + DBENTRY dbEntry; + char *name = 0; + char *pto = (char *)pbuffer; + + if (!pdbbase) { + status = S_db_notFound; + break; + } + dbInitEntry(pdbbase, &dbEntry); + status = dbFindRecord(&dbEntry, dbChannelRecord(chan)->name); + if (!status) name = dbGetRecordTypeName(&dbEntry); + dbFinishEntry(&dbEntry); + if (status) break; + if (!name) { + status = S_dbLib_recordTypeNotFound; + break; + } + pto[MAX_STRING_SIZE-1] = 0; + strncpy(pto, name, MAX_STRING_SIZE-1); + } + break; + default: + status = -1; + break; + } + + dbScanUnlock(dbChannelRecord(chan)); + + if (status) return -1; + return 0; +} + +int dbChannel_put(struct dbChannel *chan, int src_type, + const void *psrc, long no_elements) +{ + long status; + + switch (src_type) { + case(oldDBR_STRING): + status = dbChannelPutField(chan, DBR_STRING, psrc, no_elements); + break; +/* case(oldDBR_INT): */ + case(oldDBR_SHORT): + status = dbChannelPutField(chan, DBR_SHORT, psrc, no_elements); + break; + case(oldDBR_FLOAT): + status = dbChannelPutField(chan, DBR_FLOAT, psrc, no_elements); + break; + case(oldDBR_ENUM): + status = dbChannelPutField(chan, DBR_ENUM, psrc, no_elements); + break; + case(oldDBR_CHAR): + status = dbChannelPutField(chan, DBR_UCHAR, psrc, no_elements); + break; + case(oldDBR_LONG): + status = dbChannelPutField(chan, DBR_LONG, psrc, no_elements); + break; + case(oldDBR_DOUBLE): + status = dbChannelPutField(chan, DBR_DOUBLE, psrc, no_elements); + break; + + case(oldDBR_STS_STRING): + status = dbChannelPutField(chan, DBR_STRING, + ((const struct dbr_sts_string *)psrc)->value, no_elements); + break; +/* case(oldDBR_STS_INT): */ + case(oldDBR_STS_SHORT): + status = dbChannelPutField(chan, DBR_SHORT, + &((const struct dbr_sts_short *)psrc)->value, no_elements); + break; + case(oldDBR_STS_FLOAT): + status = dbChannelPutField(chan, DBR_FLOAT, + &((const struct dbr_sts_float *)psrc)->value, no_elements); + break; + case(oldDBR_STS_ENUM): + status = dbChannelPutField(chan, DBR_ENUM, + &((const struct dbr_sts_enum *)psrc)->value, no_elements); + break; + case(oldDBR_STS_CHAR): + status = dbChannelPutField(chan, DBR_UCHAR, + &((const struct dbr_sts_char *)psrc)->value, no_elements); + break; + case(oldDBR_STS_LONG): + status = dbChannelPutField(chan, DBR_LONG, + &((const struct dbr_sts_long *)psrc)->value, no_elements); + break; + case(oldDBR_STS_DOUBLE): + status = dbChannelPutField(chan, DBR_DOUBLE, + &((const struct dbr_sts_double *)psrc)->value, no_elements); + break; + + case(oldDBR_TIME_STRING): + status = dbChannelPutField(chan, DBR_TIME, + ((const struct dbr_time_string *)psrc)->value, no_elements); + break; +/* case(oldDBR_TIME_INT): */ + case(oldDBR_TIME_SHORT): + status = dbChannelPutField(chan, DBR_SHORT, + &((const struct dbr_time_short *)psrc)->value, no_elements); + break; + case(oldDBR_TIME_FLOAT): + status = dbChannelPutField(chan, DBR_FLOAT, + &((const struct dbr_time_float *)psrc)->value, no_elements); + break; + case(oldDBR_TIME_ENUM): + status = dbChannelPutField(chan, DBR_ENUM, + &((const struct dbr_time_enum *)psrc)->value, no_elements); + break; + case(oldDBR_TIME_CHAR): + status = dbChannelPutField(chan, DBR_UCHAR, + &((const struct dbr_time_char *)psrc)->value, no_elements); + break; + case(oldDBR_TIME_LONG): + status = dbChannelPutField(chan, DBR_LONG, + &((const struct dbr_time_long *)psrc)->value, no_elements); + break; + case(oldDBR_TIME_DOUBLE): + status = dbChannelPutField(chan, DBR_DOUBLE, + &((const struct dbr_time_double *)psrc)->value, no_elements); + break; + + case(oldDBR_GR_STRING): + /* no struct dbr_gr_string - use dbr_sts_string instead */ + status = dbChannelPutField(chan, DBR_STRING, + ((const struct dbr_sts_string *)psrc)->value, no_elements); + break; +/* case(oldDBR_GR_INT): */ + case(oldDBR_GR_SHORT): + status = dbChannelPutField(chan, DBR_SHORT, + &((const struct dbr_gr_short *)psrc)->value, no_elements); + break; + case(oldDBR_GR_FLOAT): + status = dbChannelPutField(chan, DBR_FLOAT, + &((const struct dbr_gr_float *)psrc)->value, no_elements); + break; + case(oldDBR_GR_ENUM): + status = dbChannelPutField(chan, DBR_ENUM, + &((const struct dbr_gr_enum *)psrc)->value, no_elements); + break; + case(oldDBR_GR_CHAR): + status = dbChannelPutField(chan, DBR_UCHAR, + &((const struct dbr_gr_char *)psrc)->value, no_elements); + break; + case(oldDBR_GR_LONG): + status = dbChannelPutField(chan, DBR_LONG, + &((const struct dbr_gr_long *)psrc)->value, no_elements); + break; + case(oldDBR_GR_DOUBLE): + status = dbChannelPutField(chan, DBR_DOUBLE, + &((const struct dbr_gr_double *)psrc)->value, no_elements); + break; + + case(oldDBR_CTRL_STRING): + /* no struct dbr_ctrl_string - use dbr_sts_string instead */ + status = dbChannelPutField(chan, DBR_STRING, + ((const struct dbr_sts_string *)psrc)->value, no_elements); + break; +/* case(oldDBR_CTRL_INT): */ + case(oldDBR_CTRL_SHORT): + status = dbChannelPutField(chan, DBR_SHORT, + &((const struct dbr_ctrl_short *)psrc)->value, no_elements); + break; + case(oldDBR_CTRL_FLOAT): + status = dbChannelPutField(chan, DBR_FLOAT, + &((const struct dbr_ctrl_float *)psrc)->value, no_elements); + break; + case(oldDBR_CTRL_ENUM): + status = dbChannelPutField(chan, DBR_ENUM, + &((const struct dbr_ctrl_enum *)psrc)->value, no_elements); + break; + case(oldDBR_CTRL_CHAR): + status = dbChannelPutField(chan, DBR_UCHAR, + &((const struct dbr_ctrl_char *)psrc)->value, no_elements); + break; + case(oldDBR_CTRL_LONG): + status = dbChannelPutField(chan, DBR_LONG, + &((const struct dbr_ctrl_long *)psrc)->value, no_elements); + break; + case(oldDBR_CTRL_DOUBLE): + status = dbChannelPutField(chan, DBR_DOUBLE, + &((const struct dbr_ctrl_double *)psrc)->value, no_elements); + break; + + case(oldDBR_PUT_ACKT): + status = dbChannelPutField(chan, DBR_PUT_ACKT, psrc, no_elements); + break; + case(oldDBR_PUT_ACKS): + status = dbChannelPutField(chan, DBR_PUT_ACKS, psrc, no_elements); + break; + + default: + return -1; + } + if (status) return -1; + return 0; +} + + +static int mapOldType (short oldtype) +{ + int dbrType = -1; + + switch (oldtype) { + case oldDBR_STRING: + dbrType = DBR_STRING; + break; +/* case oldDBR_INT: */ + case oldDBR_SHORT: + dbrType = DBR_SHORT; + break; + case oldDBR_FLOAT: + dbrType = DBR_FLOAT; + break; + case oldDBR_ENUM: + dbrType = DBR_ENUM; + break; + case oldDBR_CHAR: + dbrType = DBR_UCHAR; + break; + case oldDBR_LONG: + dbrType = DBR_LONG; + break; + case oldDBR_DOUBLE: + dbrType = DBR_DOUBLE; + break; + case oldDBR_PUT_ACKT: + dbrType = DBR_PUT_ACKT; + break; + case oldDBR_PUT_ACKS: + dbrType = DBR_PUT_ACKS; + break; + default: + return -1; + } + return dbrType; +} + +int db_put_process(processNotify *ppn, notifyPutType type, + int src_type, const void *psrc, int no_elements) +{ + int status = 0; + int dbrType = mapOldType(src_type); + switch(type) { + case putDisabledType: + ppn->status = notifyError; + return 0; + case putFieldType: + status = dbChannelPutField(ppn->chan, dbrType, psrc, no_elements); + break; + case putType: + status = dbChannelPut(ppn->chan, dbrType, psrc, no_elements); + break; + } + if (status) + ppn->status = notifyError; + return 1; +} diff --git a/modules/database/src/ioc/db/db_access_routines.h b/modules/database/src/ioc/db/db_access_routines.h new file mode 100644 index 000000000..f48ebb001 --- /dev/null +++ b/modules/database/src/ioc/db/db_access_routines.h @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* This defined routines for old database access. These were broken out of + db_access.h so that ca can be build independent of db. + src/ca contains db_access, which contains that data definitions +*/ + +#ifndef INCLdb_access_routinesh +#define INCLdb_access_routinesh + +#ifdef __cplusplus +extern "C" { +#endif + +#include "shareLib.h" + +epicsShareExtern struct dbBase *pdbbase; +epicsShareExtern volatile int interruptAccept; + + +/* + * Adaptors for db_access users + */ +epicsShareFunc struct dbChannel * dbChannel_create(const char *pname); +epicsShareFunc int dbChannel_get(struct dbChannel *chan, + int buffer_type, void *pbuffer, long no_elements, void *pfl); +epicsShareFunc int dbChannel_put(struct dbChannel *chan, int src_type, + const void *psrc, long no_elements); +epicsShareFunc int dbChannel_get_count(struct dbChannel *chan, + int buffer_type, void *pbuffer, long *nRequest, void *pfl); + + +#ifdef __cplusplus +} +#endif + +#endif /* INCLdb_access_routinesh */ diff --git a/modules/database/src/ioc/db/db_convert.h b/modules/database/src/ioc/db/db_convert.h new file mode 100644 index 000000000..d06164f02 --- /dev/null +++ b/modules/database/src/ioc/db/db_convert.h @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* db_convert.h */ + +#ifndef INCLdb_converth +#define INCLdb_converth + +#ifdef __cplusplus +extern "C" { +#endif + +#include "shareLib.h" +#include "dbAddr.h" + +epicsShareExtern struct dbBase *pdbbase; +epicsShareExtern volatile int interruptAccept; + +/*Definitions that allow old database access to use new conversion routines*/ +#define newDBF_DEVICE 13 +#define newDBR_ENUM 11 +epicsShareExtern long (*dbGetConvertRoutine[newDBF_DEVICE+1][newDBR_ENUM+1]) + (struct dbAddr *paddr, void *pbuffer,long nRequest, + long no_elements, long offset); +epicsShareExtern long (*dbPutConvertRoutine[newDBR_ENUM+1][newDBF_DEVICE+1]) + (struct dbAddr *paddr, const void *pbuffer,long nRequest, + long no_elements, long offset); +epicsShareExtern long (*dbFastGetConvertRoutine[newDBF_DEVICE+1][newDBR_ENUM+1]) + (const void *from, void *to, dbAddr *paddr); +epicsShareExtern long (*dbFastPutConvertRoutine[newDBR_ENUM+1][newDBF_DEVICE+1]) + (const void *from, void *to, dbAddr *paddr); + +/*Conversion between old and new DBR types*/ +epicsShareExtern unsigned short dbDBRoldToDBFnew[DBR_DOUBLE+1]; +epicsShareExtern unsigned short dbDBRnewToDBRold[newDBR_ENUM+1]; +#ifdef DB_CONVERT_GBLSOURCE +epicsShareDef unsigned short dbDBRoldToDBFnew[DBR_DOUBLE+1] = { + 0, /*DBR_STRING to DBF_STRING*/ + 3, /*DBR_INT to DBF_SHORT*/ + 9, /*DBR_FLOAT to DBF_FLOAT*/ + 11, /*DBR_ENUM to DBF_ENUM*/ + 1, /*DBR_CHAR to DBF_CHAR*/ + 5, /*DBR_LONG to DBF_LONG*/ + 10 /*DBR_DOUBLE to DBF_DOUBLE*/ +}; +epicsShareDef unsigned short dbDBRnewToDBRold[newDBR_ENUM+1] = { + 0, /*DBR_STRING to DBR_STRING*/ + 4, /*DBR_CHAR to DBR_CHAR*/ + 4, /*DBR_UCHAR to DBR_CHAR*/ + 1, /*DBR_SHORT to DBR_SHORT*/ + 5, /*DBR_USHORT to DBR_LONG*/ + 5, /*DBR_LONG to DBR_LONG*/ + 6, /*DBR_ULONG to DBR_DOUBLE*/ + 6, /*DBR_INT64 to DBR_DOUBLE*/ + 6, /*DBR_UINT64 to DBR_DOUBLE*/ + 2, /*DBR_FLOAT to DBR_FLOAT*/ + 6, /*DBR_DOUBLE to DBR_DOUBLE*/ + 3, /*DBR_ENUM to DBR_ENUM*/ +}; +#endif /*DB_CONVERT_GBLSOURCE*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* INCLdb_converth */ diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h new file mode 100644 index 000000000..1534517bb --- /dev/null +++ b/modules/database/src/ioc/db/db_field_log.h @@ -0,0 +1,135 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Jeffrey O. Hill + * + * Ralph Lange + */ + +#ifndef INCLdb_field_logh +#define INCLdb_field_logh + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple native types (anything which is not a string or an array for + * now) logged by db_post_events() for reliable interprocess communication. + * (for other types they get whatever happens to be there when the lower + * priority task pending on the event queue wakes up). Strings would slow down + * events for more reasonable size values. DB fields of native type string + * will most likely change infrequently. + * + * Strings can be added to the set of types for which updates will be queued + * by defining the macro DB_EVENT_LOG_STRINGS. The code in db_add_event() + * will adjust automatically, it just compares field sizes. + */ +union native_value { + epicsInt8 dbf_char; + epicsInt16 dbf_short; + epicsEnum16 dbf_enum; + epicsInt32 dbf_long; + epicsFloat32 dbf_float; + epicsFloat64 dbf_double; +#ifdef DB_EVENT_LOG_STRINGS + char dbf_string[MAX_STRING_SIZE]; +#endif +}; + +/* + * structure to log the state of a data base field at the time + * an event is triggered. + */ +struct db_field_log; +typedef void (dbfl_freeFunc)(struct db_field_log *pfl); + +/* Types of db_field_log: rec = use record, val = val inside, ref = reference inside */ +typedef enum dbfl_type { + dbfl_type_rec = 0, + dbfl_type_val, + dbfl_type_ref +} dbfl_type; + +/* Context of db_field_log: event = subscription update, read = read reply */ +typedef enum dbfl_context { + dbfl_context_read = 0, + dbfl_context_event +} dbfl_context; + +#define dbflTypeStr(t) (t==dbfl_type_val?"val":t==dbfl_type_rec?"rec":"ref") + +struct dbfl_val { + union native_value field; /* Field value */ +}; + +/* External data reference. + * If dtor is provided then it should be called when the referenced + * data is no longer needed. This is done automatically by + * db_delete_field_log(). Any code which changes a dbfl_type_ref + * field log to another type, or to reference different data, + * must explicitly call the dtor function. + */ +struct dbfl_ref { + dbfl_freeFunc *dtor; /* Callback to free filter-allocated resources */ + void *pvt; /* Private pointer */ + void *field; /* Field value */ +}; + +typedef struct db_field_log { + unsigned int type:2; /* type (union) selector */ + /* ctx is used for all types */ + unsigned int ctx:1; /* context (operation type) */ + /* the following are used for value and reference types */ + epicsTimeStamp time; /* Time stamp */ + unsigned short stat; /* Alarm Status */ + unsigned short sevr; /* Alarm Severity */ + short field_type; /* DBF type of data */ + short field_size; /* Data size */ + long no_elements; /* No of array elements */ + union { + struct dbfl_val v; + struct dbfl_ref r; + } u; +} db_field_log; + +/* + * A db_field_log will in one of three types: + * + * dbfl_type_rec - Reference to record + * The field log stores no data itself. Data must instead be taken + * via the dbChannel* which must always be provided when along + * with the field log. + * For this type only the 'type' and 'ctx' members are used. + * + * dbfl_type_ref - Reference to outside value + * Used for variable size (array) data types. Meta-data + * is stored in the field log, but value data is stored externally + * (see struct dbfl_ref). + * For this type all meta-data members are used. The dbfl_ref side of the + * data union is used. + * + * dbfl_type_val - Internal value + * Used to store small scalar data. Meta-data and value are + * present in this structure and no external references are used. + * For this type all meta-data members are used. The dbfl_val side of the + * data union is used. + */ + +#ifdef __cplusplus +} +#endif + +#endif /*INCLdb_field_logh*/ diff --git a/modules/database/src/ioc/db/db_test.c b/modules/database/src/ioc/db/db_test.c new file mode 100644 index 000000000..8d7ad31b1 --- /dev/null +++ b/modules/database/src/ioc/db/db_test.c @@ -0,0 +1,260 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* database access subroutines */ +/* + * Author: Bob Dalesio + * Andrew Johnson + */ +#include +#include +#include + +#include "cadef.h" +#include "dbDefs.h" +#include "epicsStdio.h" +#include "epicsStdlib.h" +#include "epicsString.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "db_access_routines.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbNotify.h" +#include "db_test.h" + +#define MAX_ELEMS 10 + +int gft(const char *pname) +{ + char tgf_buffer[MAX_ELEMS*MAX_STRING_SIZE + sizeof(struct dbr_ctrl_double)]; + struct dbChannel *chan; + struct dbCommon *precord; + long elements; + short type; + int i; + + chan = dbChannel_create(pname); + if (!chan) { + printf("Channel couldn't be created\n"); + return 1; + } + + precord = dbChannelRecord(chan); + elements = dbChannelElements(chan); + type = dbChannelExportCAType(chan); + + printf(" Record Name: %s\n", precord->name); + printf("Record Address: 0x%p\n", precord); + printf(" Export Type: %d\n", type); + printf(" Field Address: 0x%p\n", dbChannelField(chan)); + printf(" Field Size: %d\n", dbChannelFieldSize(chan)); + printf(" No Elements: %ld\n", elements); + + if (elements > MAX_ELEMS) + elements = MAX_ELEMS; + + for (i = 0; i <= LAST_BUFFER_TYPE; i++) { + if (type == 0) { + if ((i != DBR_STRING) && (i != DBR_STS_STRING) && + (i != DBR_TIME_STRING) && (i != DBR_GR_STRING) && + (i != DBR_CTRL_STRING)) + continue; + } + if (dbChannel_get(chan, i, tgf_buffer, elements, NULL) < 0) + printf("\t%s Failed\n", dbr_text[i]); + else + ca_dump_dbr(i, elements, tgf_buffer); + } + + dbChannelDelete(chan); + return 0; +} + +/* + * TPF + * Test put field + */ +int pft(const char *pname, const char *pvalue) +{ + struct dbChannel *chan; + struct dbCommon *precord; + long elements; + short type; + char buffer[500]; + short shortvalue; + long longvalue; + float floatvalue; + unsigned char charvalue; + double doublevalue; + + chan = dbChannel_create(pname); + if (!chan) { + printf("Channel couldn't be created\n"); + return 1; + } + + precord = dbChannelRecord(chan); + elements = dbChannelElements(chan); + type = dbChannelExportCAType(chan); + + printf(" Record Name: %s\n", precord->name); + printf("Record Address: 0x%p\n", precord); + printf(" Export Type: %d\n", type); + printf(" Field Address: 0x%p\n", dbChannelField(chan)); + printf(" Field Size: %d\n", dbChannelFieldSize(chan)); + printf(" No Elements: %ld\n", elements); + + if (dbChannel_put(chan, DBR_STRING,pvalue, 1) < 0) + printf("\n\t failed "); + if (dbChannel_get(chan, DBR_STRING,buffer, 1, NULL) < 0) + printf("\n\tfailed"); + else + ca_dump_dbr(DBR_STRING,1, buffer); + + if (type <= DBF_STRING || type == DBF_ENUM) + return 0; + + if (sscanf(pvalue, "%hd", &shortvalue) == 1) { + if (dbChannel_put(chan, DBR_SHORT,&shortvalue, 1) < 0) + printf("\n\t SHORT failed "); + if (dbChannel_get(chan, DBR_SHORT,buffer, 1, NULL) < 0) + printf("\n\t SHORT GET failed"); + else + ca_dump_dbr(DBR_SHORT,1, buffer); + } + if (sscanf(pvalue, "%ld", &longvalue) == 1) { + if (dbChannel_put(chan, DBR_LONG,&longvalue, 1) < 0) + printf("\n\t LONG failed "); + if (dbChannel_get(chan, DBR_LONG,buffer, 1, NULL) < 0) + printf("\n\t LONG GET failed"); + else + ca_dump_dbr(DBR_LONG,1, buffer); + } + if (epicsScanFloat(pvalue, &floatvalue) == 1) { + if (dbChannel_put(chan, DBR_FLOAT,&floatvalue, 1) < 0) + printf("\n\t FLOAT failed "); + if (dbChannel_get(chan, DBR_FLOAT,buffer, 1, NULL) < 0) + printf("\n\t FLOAT GET failed"); + else + ca_dump_dbr(DBR_FLOAT,1, buffer); + } + if (epicsScanFloat(pvalue, &floatvalue) == 1) { + doublevalue = floatvalue; + if (dbChannel_put(chan, DBR_DOUBLE,&doublevalue, 1) < 0) + printf("\n\t DOUBLE failed "); + if (dbChannel_get(chan, DBR_DOUBLE,buffer, 1, NULL) < 0) + printf("\n\t DOUBLE GET failed"); + else + ca_dump_dbr(DBR_DOUBLE,1, buffer); + } + if (sscanf(pvalue, "%hd", &shortvalue) == 1) { + charvalue = (unsigned char) shortvalue; + if (dbChannel_put(chan, DBR_CHAR,&charvalue, 1) < 0) + printf("\n\t CHAR failed "); + if (dbChannel_get(chan, DBR_CHAR,buffer, 1, NULL) < 0) + printf("\n\t CHAR GET failed"); + else + ca_dump_dbr(DBR_CHAR,1, buffer); + } + if (sscanf(pvalue, "%hd", &shortvalue) == 1) { + if (dbChannel_put(chan, DBR_ENUM,&shortvalue, 1) < 0) + printf("\n\t ENUM failed "); + if (dbChannel_get(chan, DBR_ENUM,buffer, 1, NULL) < 0) + printf("\n\t ENUM GET failed"); + else + ca_dump_dbr(DBR_ENUM,1, buffer); + } + printf("\n"); + dbChannelDelete(chan); + return (0); +} + + +typedef struct tpnInfo { + epicsEventId callbackDone; + processNotify *ppn; + char buffer[80]; +} tpnInfo; + + +static int putCallback(processNotify *ppn,notifyPutType type) { + tpnInfo *ptpnInfo = (tpnInfo *)ppn->usrPvt; + + return db_put_process(ppn, type, DBR_STRING, ptpnInfo->buffer, 1); +} + +static void doneCallback(processNotify *ppn) +{ + tpnInfo *ptpnInfo = (tpnInfo *) ppn->usrPvt; + notifyStatus status = ppn->status; + const char *pname = dbChannelRecord(ppn->chan)->name; + + if (status == 0) + printf("tpnCallback '%s': Success\n", pname); + else + printf("tpnCallback '%s': Notify status %d\n", pname, (int)status); + epicsEventSignal(ptpnInfo->callbackDone); +} + +static void tpnThread(void *pvt) +{ + tpnInfo *ptpnInfo = (tpnInfo *) pvt; + processNotify *ppn = (processNotify *) ptpnInfo->ppn; + + dbProcessNotify(ppn); + epicsEventWait(ptpnInfo->callbackDone); + dbNotifyCancel(ppn); + epicsEventDestroy(ptpnInfo->callbackDone); + dbChannelDelete(ppn->chan); + free(ppn); + free(ptpnInfo); +} + +int tpn(const char *pname, const char *pvalue) +{ + struct dbChannel *chan; + tpnInfo *ptpnInfo; + processNotify *ppn = NULL; + + chan = dbChannel_create(pname); + if (!chan) { + printf("Channel couldn't be created\n"); + return 1; + } + + ppn = calloc(1, sizeof(processNotify)); + if (!ppn) { + printf("calloc failed\n"); + dbChannelDelete(chan); + return -1; + } + ppn->requestType = putProcessRequest; + ppn->chan = chan; + ppn->putCallback = putCallback; + ppn->doneCallback = doneCallback; + + ptpnInfo = calloc(1, sizeof(tpnInfo)); + if (!ptpnInfo) { + printf("calloc failed\n"); + free(ppn); + dbChannelDelete(chan); + return -1; + } + ptpnInfo->ppn = ppn; + ptpnInfo->callbackDone = epicsEventCreate(epicsEventEmpty); + strncpy(ptpnInfo->buffer, pvalue, 80); + ptpnInfo->buffer[79] = 0; + + ppn->usrPvt = ptpnInfo; + epicsThreadCreate("tpn", epicsThreadPriorityHigh, + epicsThreadGetStackSize(epicsThreadStackMedium), tpnThread, ptpnInfo); + return 0; +} + diff --git a/modules/database/src/ioc/db/db_test.h b/modules/database/src/ioc/db/db_test.h new file mode 100644 index 000000000..88eb14c21 --- /dev/null +++ b/modules/database/src/ioc/db/db_test.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INCLdb_testh +#define INCLdb_testh + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int gft(const char *pname); +epicsShareFunc int pft(const char *pname, const char *pvalue); +epicsShareFunc int tpn(const char *pname, const char *pvalue); +#ifdef __cplusplus +} +#endif + +#endif /* INCLdb_testh */ diff --git a/modules/database/src/ioc/db/initHooks.c b/modules/database/src/ioc/db/initHooks.c new file mode 100644 index 000000000..9070c53f5 --- /dev/null +++ b/modules/database/src/ioc/db/initHooks.c @@ -0,0 +1,138 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Authors: Benjamin Franksen (BESY) and Marty Kraimer + * Date: 06-01-91 + * major Revision: 07JuL97 + */ + +#include +#include +#include + +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsMutex.h" +#include "epicsThread.h" + +#define epicsExportSharedSymbols +#include "initHooks.h" + +typedef struct initHookLink { + ELLNODE node; + initHookFunction func; +} initHookLink; + +static ELLLIST functionList = ELLLIST_INIT; +static epicsMutexId listLock; + +/* + * Lazy initialization functions + */ +static void initHookOnce(void *arg) +{ + listLock = epicsMutexMustCreate(); +} + +static void initHookInit(void) +{ + static epicsThreadOnceId onceFlag = EPICS_THREAD_ONCE_INIT; + epicsThreadOnce(&onceFlag, initHookOnce, NULL); +} + +/* + * To be called before iocInit reaches state desired. + */ +int initHookRegister(initHookFunction func) +{ + initHookLink *newHook; + + if (!func) return 0; + + initHookInit(); + + newHook = (initHookLink *)malloc(sizeof(initHookLink)); + if (!newHook) { + printf("Cannot malloc a new initHookLink\n"); + return -1; + } + newHook->func = func; + + epicsMutexMustLock(listLock); + ellAdd(&functionList, &newHook->node); + epicsMutexUnlock(listLock); + return 0; +} + +/* + * Called by iocInit at various points during initialization. + * This function must only be called by iocInit and relatives. + */ +void initHookAnnounce(initHookState state) +{ + initHookLink *hook; + + initHookInit(); + + epicsMutexMustLock(listLock); + hook = (initHookLink *)ellFirst(&functionList); + epicsMutexUnlock(listLock); + + while (hook != NULL) { + hook->func(state); + + epicsMutexMustLock(listLock); + hook = (initHookLink *)ellNext(&hook->node); + epicsMutexUnlock(listLock); + } +} + +void initHookFree(void) +{ + initHookInit(); + epicsMutexMustLock(listLock); + ellFree(&functionList); + epicsMutexUnlock(listLock); +} + +/* + * Call any time you want to print out a state name. + */ +const char *initHookName(int state) +{ + const char *stateName[] = { + "initHookAtIocBuild", + "initHookAtBeginning", + "initHookAfterCallbackInit", + "initHookAfterCaLinkInit", + "initHookAfterInitDrvSup", + "initHookAfterInitRecSup", + "initHookAfterInitDevSup", + "initHookAfterInitDatabase", + "initHookAfterFinishDevSup", + "initHookAfterScanInit", + "initHookAfterInitialProcess", + "initHookAfterCaServerInit", + "initHookAfterIocBuilt", + "initHookAtIocRun", + "initHookAfterDatabaseRunning", + "initHookAfterCaServerRunning", + "initHookAfterIocRunning", + "initHookAtIocPause", + "initHookAfterCaServerPaused", + "initHookAfterDatabasePaused", + "initHookAfterIocPaused", + "initHookAfterInterruptAccept", + "initHookAtEnd" + }; + if (state < 0 || state >= NELEMENTS(stateName)) { + return "Not an initHookState"; + } + return stateName[state]; +} diff --git a/modules/database/src/ioc/db/initHooks.h b/modules/database/src/ioc/db/initHooks.h new file mode 100644 index 000000000..429a768db --- /dev/null +++ b/modules/database/src/ioc/db/initHooks.h @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Authors: Benjamin Franksen (BESY) and Marty Kraimer + * Date: 06-01-91 + * major Revision: 07JuL97 + */ + +#ifndef INC_initHooks_H +#define INC_initHooks_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This enum must agree with the array of names defined in initHookName() */ +typedef enum { + initHookAtIocBuild = 0, /* Start of iocBuild/iocInit commands */ + initHookAtBeginning, + initHookAfterCallbackInit, + initHookAfterCaLinkInit, + initHookAfterInitDrvSup, + initHookAfterInitRecSup, + initHookAfterInitDevSup, + initHookAfterInitDatabase, + initHookAfterFinishDevSup, + initHookAfterScanInit, + initHookAfterInitialProcess, + initHookAfterCaServerInit, + initHookAfterIocBuilt, /* End of iocBuild command */ + + initHookAtIocRun, /* Start of iocRun command */ + initHookAfterDatabaseRunning, + initHookAfterCaServerRunning, + initHookAfterIocRunning, /* End of iocRun/iocInit commands */ + + initHookAtIocPause, /* Start of iocPause command */ + initHookAfterCaServerPaused, + initHookAfterDatabasePaused, + initHookAfterIocPaused, /* End of iocPause command */ + +/* Deprecated states, provided for backwards compatibility. + * These states are announced at the same point they were before, + * but will not be repeated if the IOC gets paused and restarted. + */ + initHookAfterInterruptAccept, /* After initHookAfterDatabaseRunning */ + initHookAtEnd, /* Before initHookAfterIocRunning */ +} initHookState; + +typedef void (*initHookFunction)(initHookState state); +epicsShareFunc int initHookRegister(initHookFunction func); +epicsShareFunc void initHookAnnounce(initHookState state); +epicsShareFunc const char *initHookName(int state); +epicsShareFunc void initHookFree(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_initHooks_H */ diff --git a/modules/database/src/ioc/db/menuAlarmSevr.dbd.pod b/modules/database/src/ioc/db/menuAlarmSevr.dbd.pod new file mode 100644 index 000000000..1d86edba6 --- /dev/null +++ b/modules/database/src/ioc/db/menuAlarmSevr.dbd.pod @@ -0,0 +1,25 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=head1 Menu menuAlarmSevr + +This menu defines the four possible alarm severities that EPICS records can +exhibit. Note that it is not possible to add or remove severities just by +changing the choices defined here. + +=menu menuAlarmSevr + +=cut + +menu(menuAlarmSevr) { + choice(menuAlarmSevrNO_ALARM,"NO_ALARM") + choice(menuAlarmSevrMINOR,"MINOR") + choice(menuAlarmSevrMAJOR,"MAJOR") + choice(menuAlarmSevrINVALID,"INVALID") +} diff --git a/modules/database/src/ioc/db/menuAlarmStat.dbd b/modules/database/src/ioc/db/menuAlarmStat.dbd new file mode 100644 index 000000000..e8d7f1ced --- /dev/null +++ b/modules/database/src/ioc/db/menuAlarmStat.dbd @@ -0,0 +1,33 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuAlarmStat) { + choice(menuAlarmStatNO_ALARM,"NO_ALARM") + choice(menuAlarmStatREAD,"READ") + choice(menuAlarmStatWRITE,"WRITE") + choice(menuAlarmStatHIHI,"HIHI") + choice(menuAlarmStatHIGH,"HIGH") + choice(menuAlarmStatLOLO,"LOLO") + choice(menuAlarmStatLOW,"LOW") + choice(menuAlarmStatSTATE,"STATE") + choice(menuAlarmStatCOS,"COS") + choice(menuAlarmStatCOMM,"COMM") + choice(menuAlarmStatTIMEOUT,"TIMEOUT") + choice(menuAlarmStatHWLIMIT,"HWLIMIT") + choice(menuAlarmStatCALC,"CALC") + choice(menuAlarmStatSCAN,"SCAN") + choice(menuAlarmStatLINK,"LINK") + choice(menuAlarmStatSOFT,"SOFT") + choice(menuAlarmStatBAD_SUB,"BAD_SUB") + choice(menuAlarmStatUDF,"UDF") + choice(menuAlarmStatDISABLE,"DISABLE") + choice(menuAlarmStatSIMM,"SIMM") + choice(menuAlarmStatREAD_ACCESS,"READ_ACCESS") + choice(menuAlarmStatWRITE_ACCESS,"WRITE_ACCESS") +} diff --git a/modules/database/src/ioc/db/menuFtype.dbd b/modules/database/src/ioc/db/menuFtype.dbd new file mode 100644 index 000000000..af6a8803d --- /dev/null +++ b/modules/database/src/ioc/db/menuFtype.dbd @@ -0,0 +1,22 @@ +#************************************************************************* +# Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuFtype) { + choice(menuFtypeSTRING,"STRING") + choice(menuFtypeCHAR,"CHAR") + choice(menuFtypeUCHAR,"UCHAR") + choice(menuFtypeSHORT,"SHORT") + choice(menuFtypeUSHORT,"USHORT") + choice(menuFtypeLONG,"LONG") + choice(menuFtypeULONG,"ULONG") + choice(menuFtypeINT64,"INT64") + choice(menuFtypeUINT64,"UINT64") + choice(menuFtypeFLOAT,"FLOAT") + choice(menuFtypeDOUBLE,"DOUBLE") + choice(menuFtypeENUM,"ENUM") +} diff --git a/modules/database/src/ioc/db/menuIvoa.dbd b/modules/database/src/ioc/db/menuIvoa.dbd new file mode 100644 index 000000000..b1b3ce51f --- /dev/null +++ b/modules/database/src/ioc/db/menuIvoa.dbd @@ -0,0 +1,14 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuIvoa) { + choice(menuIvoaContinue_normally,"Continue normally") + choice(menuIvoaDon_t_drive_outputs,"Don't drive outputs") + choice(menuIvoaSet_output_to_IVOV,"Set output to IVOV") +} diff --git a/modules/database/src/ioc/db/menuOmsl.dbd b/modules/database/src/ioc/db/menuOmsl.dbd new file mode 100644 index 000000000..3022437dc --- /dev/null +++ b/modules/database/src/ioc/db/menuOmsl.dbd @@ -0,0 +1,13 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuOmsl) { + choice(menuOmslsupervisory,"supervisory") + choice(menuOmslclosed_loop,"closed_loop") +} diff --git a/modules/database/src/ioc/db/menuPini.dbd b/modules/database/src/ioc/db/menuPini.dbd new file mode 100644 index 000000000..96aca7298 --- /dev/null +++ b/modules/database/src/ioc/db/menuPini.dbd @@ -0,0 +1,16 @@ +#************************************************************************* +# Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuPini) { + choice(menuPiniNO,"NO") + choice(menuPiniYES,"YES") + choice(menuPiniRUN,"RUN") + choice(menuPiniRUNNING,"RUNNING") + choice(menuPiniPAUSE,"PAUSE") + choice(menuPiniPAUSED,"PAUSED") +} diff --git a/modules/database/src/ioc/db/menuPost.dbd b/modules/database/src/ioc/db/menuPost.dbd new file mode 100644 index 000000000..a86b25b5e --- /dev/null +++ b/modules/database/src/ioc/db/menuPost.dbd @@ -0,0 +1,11 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +menu(menuPost) { + choice(menuPost_OnChange, "On Change") + choice(menuPost_Always, "Always") +} diff --git a/modules/database/src/ioc/db/menuPriority.dbd b/modules/database/src/ioc/db/menuPriority.dbd new file mode 100644 index 000000000..155caa5e7 --- /dev/null +++ b/modules/database/src/ioc/db/menuPriority.dbd @@ -0,0 +1,14 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuPriority) { + choice(menuPriorityLOW,"LOW") + choice(menuPriorityMEDIUM,"MEDIUM") + choice(menuPriorityHIGH,"HIGH") +} diff --git a/modules/database/src/ioc/db/menuScan.dbd b/modules/database/src/ioc/db/menuScan.dbd new file mode 100644 index 000000000..17fedd59b --- /dev/null +++ b/modules/database/src/ioc/db/menuScan.dbd @@ -0,0 +1,21 @@ +#************************************************************************* +# Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuScan) { + choice(menuScanPassive,"Passive") + choice(menuScanEvent,"Event") + choice(menuScanI_O_Intr,"I/O Intr") + # Periodic scans follow, ordered from slowest to fastest + choice(menuScan10_second,"10 second") + choice(menuScan5_second,"5 second") + choice(menuScan2_second,"2 second") + choice(menuScan1_second,"1 second") + choice(menuScan_5_second,".5 second") + choice(menuScan_2_second,".2 second") + choice(menuScan_1_second,".1 second") +} diff --git a/modules/database/src/ioc/db/menuSimm.dbd.pod b/modules/database/src/ioc/db/menuSimm.dbd.pod new file mode 100644 index 000000000..5df087ccc --- /dev/null +++ b/modules/database/src/ioc/db/menuSimm.dbd.pod @@ -0,0 +1,23 @@ +#************************************************************************* +# Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=head1 Menu menuSimm + +This menu is used for Simulation Mode (SIMM) fields of input record types that +can fetch either raw or engineering values through their SIOL link. + +=menu menuSimm + +=cut + +menu(menuSimm) { + choice(menuSimmNO,"NO") + choice(menuSimmYES,"YES") + choice(menuSimmRAW,"RAW") +} diff --git a/modules/database/src/ioc/db/menuYesNo.dbd b/modules/database/src/ioc/db/menuYesNo.dbd new file mode 100644 index 000000000..2d09dd65a --- /dev/null +++ b/modules/database/src/ioc/db/menuYesNo.dbd @@ -0,0 +1,13 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE Versions 3.13.7 +# and higher are distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(menuYesNo) { + choice(menuYesNoNO,"NO") + choice(menuYesNoYES,"YES") +} diff --git a/modules/database/src/ioc/db/recGbl.c b/modules/database/src/ioc/db/recGbl.c new file mode 100644 index 000000000..12c0a0e0f --- /dev/null +++ b/modules/database/src/ioc/db/recGbl.c @@ -0,0 +1,410 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* recGbl.c */ +/* + * Author: Marty Kraimer + * Andrew Johnson + */ + +#include +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "epicsMath.h" +#include "epicsPrint.h" +#include "epicsStdlib.h" +#include "epicsTime.h" +#include "errlog.h" + +#include "caeventmask.h" + +#define epicsExportSharedSymbols +#include "dbAccessDefs.h" +#include "dbStaticLib.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbCommon.h" +#include "menuSimm.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbFldTypes.h" +#include "dbLink.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "devSup.h" +#include "link.h" +#include "recGbl.h" + + +/* Hook Routines */ + +epicsShareDef RECGBL_ALARM_HOOK_ROUTINE recGblAlarmHook = NULL; + +/* local routines */ +static void getMaxRangeValues(short field_type, double *pupper_limit, + double *plower_limit); + + + +void recGblDbaddrError(long status, const struct dbAddr *paddr, + const char *pmessage) +{ + dbCommon *precord = 0; + dbFldDes *pdbFldDes = 0; + + if(paddr) { + pdbFldDes = paddr->pfldDes; + precord = paddr->precord; + } + errPrintf(status,0,0, + "PV: %s.%s " + "error detected in routine: %s\n", + (paddr ? precord->name : "Unknown"), + (pdbFldDes ? pdbFldDes->name : ""), + (pmessage ? pmessage : "Unknown")); + return; +} + +void recGblRecordError(long status, void *pdbc, + const char *pmessage) +{ + dbCommon *precord = pdbc; + + errPrintf(status,0,0, + "PV: %s %s\n", + (precord ? precord->name : "Unknown"), + (pmessage ? pmessage : "")); + return; +} + +void recGblRecSupError(long status, const struct dbAddr *paddr, + const char *pmessage, const char *psupport_name) +{ + dbCommon *precord = 0; + dbFldDes *pdbFldDes = 0; + dbRecordType *pdbRecordType = 0; + + if(paddr) { + precord = paddr->precord; + pdbFldDes = paddr->pfldDes; + if(pdbFldDes) pdbRecordType = pdbFldDes->pdbRecordType; + } + errPrintf(status,0,0, + "Record Support Routine (%s) " + "Record Type %s " + "PV %s.%s " + " %s\n", + (psupport_name ? psupport_name : "Unknown"), + (pdbRecordType ? pdbRecordType->name : "Unknown"), + (paddr ? precord->name : "Unknown"), + (pdbFldDes ? pdbFldDes->name : ""), + (pmessage ? pmessage : "")); + return; +} + +void recGblGetPrec(const struct dbAddr *paddr, long *precision) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + + switch (pdbFldDes->field_type) { + case DBF_CHAR: + case DBF_UCHAR: + case DBF_SHORT: + case DBF_USHORT: + case DBF_LONG: + case DBF_ULONG: + case DBF_INT64: + case DBF_UINT64: + *precision = 0; + break; + + case DBF_FLOAT: + case DBF_DOUBLE: + if (*precision < 0 || *precision > 15) + *precision = 15; + break; + + default: + break; + } +} + +void recGblGetGraphicDouble(const struct dbAddr *paddr, + struct dbr_grDouble *pgd) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + + getMaxRangeValues(pdbFldDes->field_type, + &pgd->upper_disp_limit, &pgd->lower_disp_limit); +} + +void recGblGetAlarmDouble(const struct dbAddr *paddr, + struct dbr_alDouble *pad) +{ + pad->upper_alarm_limit = epicsNAN; + pad->upper_warning_limit = epicsNAN; + pad->lower_warning_limit = epicsNAN; + pad->lower_alarm_limit = epicsNAN; +} + +void recGblGetControlDouble(const struct dbAddr *paddr, + struct dbr_ctrlDouble *pcd) +{ + dbFldDes *pdbFldDes = paddr->pfldDes; + + getMaxRangeValues(pdbFldDes->field_type, + &pcd->upper_ctrl_limit, &pcd->lower_ctrl_limit); +} + +int recGblInitConstantLink(struct link *plink, short dbftype, void *pdest) +{ + return !dbLoadLink(plink, dbftype, pdest); +} + +unsigned short recGblResetAlarms(void *precord) +{ + dbCommon *pdbc = precord; + epicsEnum16 prev_stat = pdbc->stat; + epicsEnum16 prev_sevr = pdbc->sevr; + epicsEnum16 new_stat = pdbc->nsta; + epicsEnum16 new_sevr = pdbc->nsev; + epicsEnum16 val_mask = 0; + epicsEnum16 stat_mask = 0; + + pdbc->stat = new_stat; + pdbc->sevr = new_sevr; + pdbc->nsta = 0; + pdbc->nsev = 0; + + if (prev_sevr != new_sevr) { + stat_mask = DBE_ALARM; + db_post_events(pdbc, &pdbc->sevr, DBE_VALUE); + } + if (prev_stat != new_stat) { + stat_mask |= DBE_VALUE; + } + if (stat_mask) { + db_post_events(pdbc, &pdbc->stat, stat_mask); + val_mask = DBE_ALARM; + + if (!pdbc->ackt || new_sevr >= pdbc->acks) { + pdbc->acks = new_sevr; + db_post_events(pdbc, &pdbc->acks, DBE_VALUE); + } + + if (recGblAlarmHook) { + (*recGblAlarmHook)(pdbc, prev_sevr, prev_stat); + } + } + return val_mask; +} + +int recGblSetSevr(void *precord, epicsEnum16 new_stat, epicsEnum16 new_sevr) +{ + struct dbCommon *prec = precord; + if (prec->nsev < new_sevr) { + prec->nsta = new_stat; + prec->nsev = new_sevr; + return TRUE; + } + return FALSE; +} + +void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat, + epicsEnum16 sevr) +{ + switch (msMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (sevr < INVALID_ALARM) + break; + /* Fall through */ + case pvlOptMS: + recGblSetSevr(precord, LINK_ALARM, sevr); + break; + case pvlOptMSS: + recGblSetSevr(precord, stat, sevr); + break; + } +} + + +void recGblFwdLink(void *precord) +{ + dbCommon *pdbc = precord; + + dbScanFwdLink(&pdbc->flnk); + /*Handle dbPutFieldNotify record completions*/ + if(pdbc->ppn) dbNotifyCompletion(pdbc); + if(pdbc->rpro) { + /*If anyone requested reprocessing do it*/ + pdbc->rpro = FALSE; + scanOnce(pdbc); + } + /*In case putField caused put we are all done */ + pdbc->putf = FALSE; +} + +void recGblGetTimeStamp(void *pvoid) +{ + recGblGetTimeStampSimm(pvoid, menuSimmNO, 0); +} + +void recGblGetTimeStampSimm(void *pvoid, const epicsEnum16 simm, struct link *siol) +{ + dbCommon *prec = (dbCommon *)pvoid; + struct link *plink = &prec->tsel; + + if (!dbLinkIsConstant(plink)) { + if (plink->flags & DBLINK_FLAG_TSELisTIME) { + if (dbGetTimeStamp(plink, &prec->time)) + errlogPrintf("recGblGetTimeStamp: dbGetTimeStamp failed for %s.TSEL", + prec->name); + return; + } + dbGetLink(plink, DBR_SHORT, &prec->tse, 0, 0); + } + if (prec->tse != epicsTimeEventDeviceTime) { + if (epicsTimeGetEvent(&prec->time, prec->tse)) + errlogPrintf("recGblGetTimeStampSimm: epicsTimeGetEvent failed, %s.TSE = %d\n", + prec->name, prec->tse); + } else { + if (simm != menuSimmNO) { + if (siol && !dbLinkIsConstant(siol)) { + if (dbGetTimeStamp(siol, &prec->time)) + errlogPrintf("recGblGetTimeStampSimm: dbGetTimeStamp (sim mode) failed, %s.SIOL = %s\n", + prec->name, siol->value.pv_link.pvname); + return; + } else { + if (epicsTimeGetCurrent(&prec->time)) + errlogPrintf("recGblGetTimeStampSimm: epicsTimeGetCurrent (sim mode) failed for %s.\n", + prec->name); + return; + } + } + } +} + +void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval, + const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask) +{ + double delta = 0; + + if (finite(newval) && finite(*poldval)) { + /* both are finite -> compare delta with deadband */ + delta = *poldval - newval; + if (delta < 0.0) delta = -delta; + } + else if (!isnan(newval) != !isnan(*poldval) || + !isinf(newval) != !isinf(*poldval)) { + /* one is NaN or +-inf, the other not -> send update */ + delta = epicsINF; + } + else if (isinf(newval) && newval != *poldval) { + /* one is +inf, the other -inf -> send update */ + delta = epicsINF; + } + if (delta > deadband) { + /* add bits to monitor mask */ + *monitor_mask |= add_mask; + /* update last value monitored */ + *poldval = newval; + } +} + +static void getMaxRangeValues(short field_type, double *pupper_limit, + double *plower_limit) +{ + switch(field_type){ + case DBF_CHAR: + *pupper_limit = (double) CHAR_MAX; + *plower_limit = (double) CHAR_MIN; + break; + case DBF_UCHAR: + *pupper_limit = (double) UCHAR_MAX; + *plower_limit = 0.0; + break; + case DBF_SHORT: + *pupper_limit = (double) SHRT_MAX; + *plower_limit = (double) SHRT_MIN; + break; + case DBF_ENUM: + case DBF_USHORT: + *pupper_limit = (double) USHRT_MAX; + *plower_limit = 0.0; + break; + case DBF_LONG: + *pupper_limit = 2147483647.0; + *plower_limit = -2147483648.0; + break; + case DBF_ULONG: + *pupper_limit = 4294967295.0; + *plower_limit = 0.0; + break; + case DBF_INT64: + *pupper_limit = 9223372036854775808.0; + *plower_limit = -9223372036854775808.0; + break; + case DBF_UINT64: + *pupper_limit = 18446744073709551615.0; + *plower_limit = 0.0; + break; + case DBF_FLOAT: + *pupper_limit = 1e30; + *plower_limit = -1e30; + break; + case DBF_DOUBLE: + *pupper_limit = 1e300; + *plower_limit = -1e300; + break; + } + return; +} + +void recGblSaveSimm(const epicsEnum16 sscn, + epicsEnum16 *poldsimm, const epicsEnum16 simm) { + if (sscn == USHRT_MAX) return; + *poldsimm = simm; +} + +void recGblCheckSimm(struct dbCommon *pcommon, epicsEnum16 *psscn, + const epicsEnum16 oldsimm, const epicsEnum16 simm) { + if (*psscn == USHRT_MAX) return; + if (simm != oldsimm) { + epicsUInt16 scan = pcommon->scan; + scanDelete(pcommon); + pcommon->scan = *psscn; + scanAdd(pcommon); + *psscn = scan; + } +} + +void recGblInitSimm(struct dbCommon *pcommon, epicsEnum16 *psscn, + epicsEnum16 *poldsimm, epicsEnum16 *psimm, struct link *psiml) { + if (dbLinkIsConstant(psiml)) { + recGblSaveSimm(*psscn, poldsimm, *psimm); + dbLoadLink(psiml, DBF_USHORT, psimm); + recGblCheckSimm(pcommon, psscn, *poldsimm, *psimm); + } +} + +long recGblGetSimm(struct dbCommon *pcommon, epicsEnum16 *psscn, + epicsEnum16 *poldsimm, epicsEnum16 *psimm, struct link *psiml) { + long status; + + recGblSaveSimm(*psscn, poldsimm, *psimm); + status = dbTryGetLink(psiml, DBR_USHORT, psimm, 0); + if (status && !pcommon->nsev) pcommon->nsta = LINK_ALARM; + recGblCheckSimm(pcommon, psscn, *poldsimm, *psimm); + return 0; +} diff --git a/modules/database/src/ioc/db/recGbl.h b/modules/database/src/ioc/db/recGbl.h new file mode 100644 index 000000000..20de9568a --- /dev/null +++ b/modules/database/src/ioc/db/recGbl.h @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* recGbl.h */ +/* Record Global + * Author: Marty Kraimer + * Date: 13Jun95 + */ +#ifndef INCrecGblh +#define INCrecGblh 1 + +#include "epicsTypes.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*************************************************************************/ + +/* Structures needed for args */ + +struct link; +struct dbAddr; +struct dbr_alDouble; +struct dbr_ctrlDouble; +struct dbr_grDouble; +struct dbCommon; + +/* Hook Routine */ + +typedef void (*RECGBL_ALARM_HOOK_ROUTINE)(struct dbCommon *prec, + epicsEnum16 prev_sevr, epicsEnum16 prev_stat); +epicsShareExtern RECGBL_ALARM_HOOK_ROUTINE recGblAlarmHook; + +/* Global Record Support Routines */ + +epicsShareFunc void recGblDbaddrError(long status, const struct dbAddr *paddr, + const char *pcaller_name); +epicsShareFunc void recGblRecordError(long status, void *precord, + const char *pcaller_name); +epicsShareFunc void recGblRecSupError(long status, const struct dbAddr *paddr, + const char *pcaller_name, const char *psupport_name); +epicsShareFunc void recGblGetGraphicDouble(const struct dbAddr *paddr, + struct dbr_grDouble *pgd); +epicsShareFunc void recGblGetControlDouble( + const struct dbAddr *paddr, struct dbr_ctrlDouble *pcd); +epicsShareFunc void recGblGetAlarmDouble(const struct dbAddr *paddr, + struct dbr_alDouble *pad); +epicsShareFunc void recGblGetPrec(const struct dbAddr *paddr, + long *pprecision); +epicsShareFunc int recGblInitConstantLink(struct link *plink, + short dbftype, void *pdest); +epicsShareFunc unsigned short recGblResetAlarms(void *precord); +epicsShareFunc int recGblSetSevr(void *precord, epicsEnum16 new_stat, + epicsEnum16 new_sevr); +epicsShareFunc void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat, + epicsEnum16 sevr); +epicsShareFunc void recGblFwdLink(void *precord); +epicsShareFunc void recGblGetTimeStamp(void *precord); +epicsShareFunc void recGblGetTimeStampSimm(void *prec, const epicsEnum16 simm, struct link *siol); +epicsShareFunc void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval, + const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask); +epicsShareFunc void recGblSaveSimm(const epicsEnum16 sscn, + epicsEnum16 *poldsimm, const epicsEnum16 simm); +epicsShareFunc void recGblCheckSimm(struct dbCommon *prec, epicsEnum16 *psscn, + const epicsEnum16 oldsimm, const epicsEnum16 simm); +epicsShareFunc void recGblInitSimm(struct dbCommon *prec, epicsEnum16 *psscn, + epicsEnum16 *poldsimm, epicsEnum16 *psimm, struct link *psiml); +epicsShareFunc long recGblGetSimm(struct dbCommon *prec, epicsEnum16 *psscn, + epicsEnum16 *poldsimm, epicsEnum16 *psimm, struct link *psiml); + +#ifdef __cplusplus +} +#endif + +#endif /*INCrecGblh*/ diff --git a/modules/database/src/ioc/dbCore.rc b/modules/database/src/ioc/dbCore.rc new file mode 100644 index 000000000..3267f3ca7 --- /dev/null +++ b/modules/database/src/ioc/dbCore.rc @@ -0,0 +1,36 @@ +#include +#include "epicsVersion.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + PRODUCTVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_UNKNOWN + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments","Database Core Library for EPICS\0" + VALUE "CompanyName", "The EPICS collaboration\0" + VALUE "FileDescription", "Database Core Library\0" + VALUE "FileVersion", EPICS_VERSION_STRING "\0" + VALUE "InternalName", "dbCore\0" + VALUE "LegalCopyright", "Copyright (C) Univ. of California, UChicago Argonne LLC\0" + VALUE "OriginalFilename", "dbCore.dll\0" + VALUE "ProductName", "Experimental Physics and Industrial Control System (EPICS)\0" + VALUE "ProductVersion", EPICS_VERSION_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/modules/database/src/ioc/dbStatic/Makefile b/modules/database/src/ioc/dbStatic/Makefile new file mode 100644 index 000000000..c962501d9 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/Makefile @@ -0,0 +1,32 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/dbStatic + +INC += dbBase.h +INC += dbFldTypes.h +INC += dbStaticLib.h +INC += dbStaticPvt.h +INC += link.h +INC += special.h +INC += guigroup.h +INC += devSup.h +INC += drvSup.h +INC += recSup.h +INC += dbStaticIocRegister.h + +dbCore_SRCS += dbStaticLib.c +dbCore_SRCS += dbYacc.c +dbCore_SRCS += dbPvdLib.c +dbCore_SRCS += dbStaticRun.c +dbCore_SRCS += dbStaticIocRegister.c + +CLEANS += dbLex.c dbYacc.c diff --git a/modules/database/src/ioc/dbStatic/RULES b/modules/database/src/ioc/dbStatic/RULES new file mode 100644 index 000000000..70e2cdaee --- /dev/null +++ b/modules/database/src/ioc/dbStatic/RULES @@ -0,0 +1,13 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +# dbLexRoutines.c is included in dbYacc.c +dbYacc.c: dbLex.c $(IOCDIR)/dbStatic/dbLexRoutines.c diff --git a/modules/database/src/ioc/dbStatic/dbBase.h b/modules/database/src/ioc/dbStatic/dbBase.h new file mode 100644 index 000000000..df3be4352 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbBase.h @@ -0,0 +1,186 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Current Author: Marty Kraimer + * Date: 03-19-92 + */ + +#ifndef INCdbBaseh +#define INCdbBaseh 1 + +#include "epicsTypes.h" +#include "dbFldTypes.h" +#include "ellLib.h" +#include "dbDefs.h" +#include "recSup.h" + +typedef struct dbMenu { + ELLNODE node; + char *name; + int nChoice; + char **papChoiceName; + char **papChoiceValue; +}dbMenu; + +typedef struct drvSup { + ELLNODE node; + char *name; + struct drvet *pdrvet; +}drvSup; + +typedef struct devSup { + ELLNODE node; + char *name; + char *choice; + int link_type; + /*Following only available on run time system*/ + struct dset *pdset; + struct dsxt *pdsxt; /* Extended device support */ +}devSup; + +typedef struct linkSup { + ELLNODE node; + char *name; + char *jlif_name; + struct jlif *pjlif; +} linkSup; + +typedef struct dbDeviceMenu { + int nChoice; + char **papChoice; +}dbDeviceMenu; + +/* conversion types*/ +typedef enum {CT_DECIMAL,CT_HEX} ctType; +/* access level types */ +typedef enum {ASL0,ASL1} asLevel; + +/*Breakpoint Tables */ +typedef struct brkInt{ /* breakpoint interval */ + double raw; /*raw value for beginning of interval */ + double slope; /*slope for interval */ + double eng; /*converted value for beginning of interval*/ +}brkInt; + +typedef struct brkTable { /* breakpoint table */ + ELLNODE node; + char *name; /*breakpoint table name */ + long number; /*number of brkInt in this table*/ + struct brkInt *paBrkInt; /* ptr to array of brkInts */ +}brkTable; + +typedef struct dbFldDes{ /* field description */ + char *prompt; /*Prompt string for DCT*/ + char *name; /*Field name*/ + char *extra; /*C def for DBF_NOACCESS*/ + struct dbRecordType *pdbRecordType; + short indRecordType; /*within dbRecordType.papFldDes */ + short special; /*Special processing requirements */ + dbfType field_type; /*Field type as defined in dbFldTypes.h */ + unsigned int process_passive:1;/*should dbPutField process passive */ + unsigned int prop:1;/*field is a metadata, post DBE_PROPERTY on change*/ + unsigned int isDevLink:1; /* true for INP/OUT fields */ + ctType base; /*base for integer to string conversions*/ + short promptgroup; /*prompt, i.e. gui group */ + short interest; /*interest level */ + asLevel as_level; /*access security level */ + char *initial; /*initial value */ + /*If (DBF_MENU,DBF_DEVICE) ftPvt is (pdbMenu,pdbDeviceMenu) */ + void *ftPvt; + /*On no runtime following only set for STRING */ + short size; /*length in bytes of a field element */ + /*The following are only available on run time system*/ + unsigned short offset; /*Offset in bytes from beginning of record*/ +}dbFldDes; + +typedef struct dbInfoNode { /*non-field per-record information*/ + ELLNODE node; + char *name; + char *string; + void *pointer; +}dbInfoNode; + +#define DBRN_FLAGS_VISIBLE 1 +#define DBRN_FLAGS_ISALIAS 2 +#define DBRN_FLAGS_HASALIAS 4 + +typedef struct dbRecordNode { + ELLNODE node; + void *precord; + char *recordname; + ELLLIST infoList; /*LIST head of info nodes*/ + int flags; + struct dbRecordNode *aliasedRecnode; /* NULL unless flags|DBRN_FLAGS_ISALIAS */ +}dbRecordNode; + +/*dbRecordAttribute is for "psuedo" fields */ +/*pdbFldDes is so that other access routines work correctly*/ +/*Until base supports char * value MUST be fixed length string*/ +typedef struct dbRecordAttribute { + ELLNODE node; + char *name; + dbFldDes *pdbFldDes; + char value[MAX_STRING_SIZE]; +}dbRecordAttribute; + +typedef struct dbText { + ELLNODE node; + char *text; +}dbText; + +typedef struct dbVariableDef { + ELLNODE node; + char *name; + char *type; + +}dbVariableDef; + +typedef struct dbRecordType { + ELLNODE node; + ELLLIST attributeList; /*LIST head of attributes*/ + ELLLIST recList; /*LIST head of sorted dbRecordNodes*/ + ELLLIST devList; /*List of associated device support*/ + ELLLIST cdefList; /*LIST of Cdef text items*/ + char *name; + short no_fields; /* number of fields defined */ + short no_prompt; /* number of fields to configure*/ + short no_links; /* number of links */ + short no_aliases; /* number of aliases in recList */ + short *link_ind; /* addr of array of ind in papFldDes*/ + char **papsortFldName;/* ptr to array of ptr to fld names*/ + short *sortFldInd; /* addr of array of ind in papFldDes*/ + dbFldDes *pvalFldDes; /*pointer dbFldDes for VAL field*/ + short indvalFlddes; /*ind in papFldDes*/ + dbFldDes **papFldDes; /* ptr to array of ptr to fldDes*/ + /*The following are only available on run time system*/ + rset *prset; + int rec_size; /*record size in bytes */ +}dbRecordType; + +struct dbPvd; /* Contents private to dbPvdLib code */ +struct gphPvt; /* Contents private to gpHashLib code */ + +typedef struct dbBase { + ELLLIST menuList; + ELLLIST recordTypeList; + ELLLIST drvList; + ELLLIST linkList; + ELLLIST registrarList; + ELLLIST functionList; + ELLLIST variableList; + ELLLIST bptList; + ELLLIST filterList; + ELLLIST guiGroupList; + void *pathPvt; + struct dbPvd *ppvd; + struct gphPvt *pgpHash; + short ignoreMissingMenus; + short loadCdefs; +}dbBase; +#endif diff --git a/modules/database/src/ioc/dbStatic/dbFldTypes.h b/modules/database/src/ioc/dbStatic/dbFldTypes.h new file mode 100644 index 000000000..ba1a69573 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbFldTypes.h @@ -0,0 +1,98 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Marty Kraimer + * Date: 6-1-90 + */ +#ifndef INCdbFldTypesh +#define INCdbFldTypesh 1 + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* field types */ +typedef enum { + DBF_STRING, + DBF_CHAR, + DBF_UCHAR, + DBF_SHORT, + DBF_USHORT, + DBF_LONG, + DBF_ULONG, + DBF_INT64, + DBF_UINT64, + DBF_FLOAT, + DBF_DOUBLE, + DBF_ENUM, + DBF_MENU, + DBF_DEVICE, + DBF_INLINK, + DBF_OUTLINK, + DBF_FWDLINK, + DBF_NOACCESS +}dbfType; +#define DBF_NTYPES DBF_NOACCESS+1 + +typedef struct mapdbfType{ + char *strvalue; + dbfType value; +}mapdbfType; + +epicsShareExtern mapdbfType pamapdbfType[]; +#ifdef DBFLDTYPES_GBLSOURCE +epicsShareDef mapdbfType pamapdbfType[DBF_NTYPES] = { + {"DBF_STRING",DBF_STRING}, + {"DBF_CHAR",DBF_CHAR}, + {"DBF_UCHAR",DBF_UCHAR}, + {"DBF_SHORT",DBF_SHORT}, + {"DBF_USHORT",DBF_USHORT}, + {"DBF_LONG",DBF_LONG}, + {"DBF_ULONG",DBF_ULONG}, + {"DBF_INT64",DBF_INT64}, + {"DBF_UINT64",DBF_UINT64}, + {"DBF_FLOAT",DBF_FLOAT}, + {"DBF_DOUBLE",DBF_DOUBLE}, + {"DBF_ENUM",DBF_ENUM}, + {"DBF_MENU",DBF_MENU}, + {"DBF_DEVICE",DBF_DEVICE}, + {"DBF_INLINK",DBF_INLINK}, + {"DBF_OUTLINK",DBF_OUTLINK}, + {"DBF_FWDLINK",DBF_FWDLINK}, + {"DBF_NOACCESS",DBF_NOACCESS} +}; +#endif /*DBFLDTYPES_GBLSOURCE*/ + +/* data request buffer types */ +#define DBR_STRING DBF_STRING +#define DBR_CHAR DBF_CHAR +#define DBR_UCHAR DBF_UCHAR +#define DBR_SHORT DBF_SHORT +#define DBR_USHORT DBF_USHORT +#define DBR_LONG DBF_LONG +#define DBR_ULONG DBF_ULONG +#define DBR_INT64 DBF_INT64 +#define DBR_UINT64 DBF_UINT64 +#define DBR_FLOAT DBF_FLOAT +#define DBR_DOUBLE DBF_DOUBLE +#define DBR_ENUM DBF_ENUM +#define DBR_PUT_ACKT DBR_ENUM+1 +#define DBR_PUT_ACKS DBR_PUT_ACKT+1 +#define DBR_NOACCESS DBF_NOACCESS +#define VALID_DB_REQ(x) ((x >= 0) && (x <= DBR_ENUM)) +#define INVALID_DB_REQ(x) ((x < 0) || (x > DBR_ENUM)) + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbFldTypesh*/ diff --git a/modules/database/src/ioc/dbStatic/dbLex.l b/modules/database/src/ioc/dbStatic/dbLex.l new file mode 100644 index 000000000..cfbb5bda4 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbLex.l @@ -0,0 +1,134 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +newline "\n" +backslash "\\" +doublequote "\"" +comment "#" +whitespace [ \t\r\n] +escape {backslash}. +stringchar [^"\n\\] +bareword [a-zA-Z0-9_\-+:.\[\]<>;] + +punctuation [:,\[\]{}] +normalchar [^"\\\0-\x1f] +barechar [a-zA-Z0-9_\-+.] +escapedchar ({backslash}["\\/bfnrt]) +hexdigit [0-9a-fA-F] +unicodechar ({backslash}"u"{hexdigit}{4}) +jsonchar ({normalchar}|{escapedchar}|{unicodechar}) +jsondqstr ({doublequote}{jsonchar}*{doublequote}) +int ("-"?([0-9]|[1-9][0-9]+)) +frac ("."[0-9]+) +exp ([eE][+-]?[0-9]+) +number ({int}{frac}?{exp}?) + +%{ +#undef YY_INPUT +#define YY_INPUT(b,r,ms) (r=(*db_yyinput)((char *)b,ms)) + +static int yyreset(void) +{ + BEGIN INITIAL; + return(0); +} + +%} + +%x JSON + +%% + +"include" return(tokenINCLUDE); +"path" return(tokenPATH); +"addpath" return(tokenADDPATH); +"menu" return(tokenMENU); +"choice" return(tokenCHOICE); +"recordtype" return(tokenRECORDTYPE); +"field" return(tokenFIELD); +"device" return(tokenDEVICE); +"driver" return(tokenDRIVER); +"link" return(tokenLINK); +"breaktable" return(tokenBREAKTABLE); +"record" return(tokenRECORD); +"grecord" return(tokenGRECORD); +"alias" return(tokenALIAS); +"info" return(tokenINFO); +"registrar" return(tokenREGISTRAR); +"function" return(tokenFUNCTION); +"variable" return(tokenVARIABLE); + +{bareword}+ { /* unquoted string or number */ + yylval.Str = dbmfStrdup((char *) yytext); + return(tokenSTRING); +} + +{doublequote}({stringchar}|{escape})*{doublequote} { /* quoted string */ + yylval.Str = dbmfStrdup((char *) yytext+1); + yylval.Str[strlen(yylval.Str)-1] = '\0'; + return(tokenSTRING); +} + +%.* { /*C definition in recordtype*/ + yylval.Str = dbmfStrdup((char *) yytext+1); + return(tokenCDEFS); +} + +"{" return(yytext[0]); +"}" return(yytext[0]); +"(" return(yytext[0]); +")" return(yytext[0]); +"," return(yytext[0]); + +{doublequote}({stringchar}|{escape})*{newline} { /* bad string */ + yyerrorAbort("Newline in string, closing quote missing"); +} + +"null" return jsonNULL; +"true" return jsonTRUE; +"false" return jsonFALSE; + +{punctuation} return yytext[0]; + +{jsondqstr} { + yylval.Str = dbmfStrdup((char *) yytext); + return jsonSTRING; +} + +{number} { + yylval.Str = dbmfStrdup((char *) yytext); + return jsonNUMBER; +} + +{barechar}+ { + yylval.Str = dbmfStrdup((char *) yytext); + return jsonBARE; +} + +{comment}.* ; + +{whitespace} ; + +. { + char message[40]; + YY_BUFFER_STATE *dummy=0; + + if (isprint((int) yytext[0])) { + sprintf(message, "Invalid character '%c'", yytext[0]); + } + else { + sprintf(message, "Invalid character 0x%2.2x", yytext[0]); + } + yyerrorAbort(message); + /*The following suppresses compiler warning messages*/ + if(FALSE) yyunput('c',(unsigned char *) message); + if(FALSE) yy_switch_to_buffer(*dummy); +} + +%% diff --git a/modules/database/src/ioc/dbStatic/dbLexRoutines.c b/modules/database/src/ioc/dbStatic/dbLexRoutines.c new file mode 100644 index 000000000..97a85ff54 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbLexRoutines.c @@ -0,0 +1,1170 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Author: Marty Kraimer Date: 13JUL95*/ + +/*The routines in this module are serially reusable NOT reentrant*/ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "dbmf.h" +#include "ellLib.h" +#include "epicsPrint.h" +#include "epicsString.h" +#include "errMdef.h" +#include "freeList.h" +#include "gpHash.h" +#include "macLib.h" + +#define epicsExportSharedSymbols +#include "dbBase.h" +#include "dbFldTypes.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "epicsExport.h" +#include "link.h" +#include "special.h" + + + +/*global declarations*/ +epicsShareDef char *makeDbdDepends=0; + +epicsShareDef int dbRecordsOnceOnly=0; +epicsExportAddress(int,dbRecordsOnceOnly); + +epicsShareDef int dbBptNotMonotonic=0; +epicsExportAddress(int,dbBptNotMonotonic); + +epicsShareDef int dbQuietMacroWarnings=0; +epicsExportAddress(int,dbQuietMacroWarnings); + +epicsShareDef int dbRecordsAbcSorted=0; +epicsExportAddress(int,dbRecordsAbcSorted); + +/*private routines */ +static void yyerrorAbort(char *str); +static void allocTemp(void *pvoid); +static void *popFirstTemp(void); +static void *getLastTemp(void); +static int db_yyinput(char *buf,int max_size); +static void dbIncludePrint(void); +static void dbPathCmd(char *path); +static void dbAddPathCmd(char *path); +static void dbIncludeNew(char *include_file); +static void dbMenuHead(char *name); +static void dbMenuChoice(char *name,char *value); +static void dbMenuBody(void); + +static void dbRecordtypeHead(char *name); +static void dbRecordtypeEmpty(void); +static void dbRecordtypeBody(void); +static void dbRecordtypeFieldHead(char *name,char *type); +static void dbRecordtypeFieldItem(char *name,char *value); +static short findOrAddGuiGroup(const char *name); + +static void dbDevice(char *recordtype,char *linktype, + char *dsetname,char *choicestring); +static void dbDriver(char *name); +static void dbLinkType(char *name, char *jlif_name); +static void dbRegistrar(char *name); +static void dbFunction(char *name); +static void dbVariable(char *name, char *type); + +static void dbBreakHead(char *name); +static void dbBreakItem(char *value); +static void dbBreakBody(void); + +static void dbRecordHead(char *recordType,char*name,int visible); +static void dbRecordField(char *name,char *value); +static void dbRecordBody(void); + +/*private declarations*/ +#define MY_BUFFER_SIZE 1024 +static char *my_buffer=NULL; +static char *mac_input_buffer=NULL; +static char *my_buffer_ptr=NULL; +static MAC_HANDLE *macHandle = NULL; +typedef struct inputFile{ + ELLNODE node; + char *path; + char *filename; + FILE *fp; + int line_num; +}inputFile; +static ELLLIST inputFileList = ELLLIST_INIT; + +static inputFile *pinputFileNow = NULL; +static DBBASE *pdbbase = NULL; + +typedef struct tempListNode { + ELLNODE node; + void *item; +}tempListNode; + +static ELLLIST tempList = ELLLIST_INIT; +static void *freeListPvt = NULL; +static int duplicate = FALSE; + +static void yyerrorAbort(char *str) +{ + yyerror(str); + yyAbort = TRUE; +} + +static void allocTemp(void *pvoid) +{ + tempListNode *ptempListNode; + + ptempListNode = freeListCalloc(freeListPvt); + ptempListNode->item = pvoid; + ellAdd(&tempList,&ptempListNode->node); +} + +static void *popFirstTemp(void) +{ + tempListNode *ptempListNode; + void *ptemp; + + ptempListNode = (tempListNode *)ellFirst(&tempList); + ptemp = ptempListNode->item; + ellDelete(&tempList,(ELLNODE *)ptempListNode); + freeListFree(freeListPvt,ptempListNode); + return(ptemp); +} + +static void *getLastTemp(void) +{ + tempListNode *ptempListNode; + + ptempListNode = (tempListNode *)ellLast(&tempList); + return(ptempListNode->item); +} + +static char *dbOpenFile(DBBASE *pdbbase,const char *filename,FILE **fp) +{ + ELLLIST *ppathList = (ELLLIST *)pdbbase->pathPvt; + dbPathNode *pdbPathNode; + char *fullfilename; + + *fp = 0; + if (!filename) return 0; + if (!ppathList || ellCount(ppathList) == 0 || + strchr(filename, '/') || strchr(filename, '\\')) { + *fp = fopen(filename, "r"); + if (*fp && makeDbdDepends) + fprintf(stdout, "%s:%s \n", makeDbdDepends, filename); + return 0; + } + pdbPathNode = (dbPathNode *)ellFirst(ppathList); + while (pdbPathNode) { + fullfilename = dbMalloc(strlen(pdbPathNode->directory) + + strlen(filename) + 2); + strcpy(fullfilename, pdbPathNode->directory); + strcat(fullfilename, "/"); + strcat(fullfilename, filename); + *fp = fopen(fullfilename, "r"); + if (*fp && makeDbdDepends) + fprintf(stdout, "%s:%s \n", makeDbdDepends, fullfilename); + free((void *)fullfilename); + if (*fp) return pdbPathNode->directory; + pdbPathNode = (dbPathNode *)ellNext(&pdbPathNode->node); + } + return 0; +} + + +static void freeInputFileList(void) +{ + inputFile *pinputFileNow; + + while((pinputFileNow=(inputFile *)ellFirst(&inputFileList))) { + if(fclose(pinputFileNow->fp)) + errPrintf(0,__FILE__, __LINE__, + "Closing file %s",pinputFileNow->filename); + free((void *)pinputFileNow->filename); + ellDelete(&inputFileList,(ELLNODE *)pinputFileNow); + free((void *)pinputFileNow); + } +} + +static +int cmp_dbRecordNode(const ELLNODE *lhs, const ELLNODE *rhs) +{ + dbRecordNode *LHS = (dbRecordNode*)lhs, + *RHS = (dbRecordNode*)rhs; + + return strcmp(LHS->recordname, RHS->recordname); +} + +static long dbReadCOM(DBBASE **ppdbbase,const char *filename, FILE *fp, + const char *path,const char *substitutions) +{ + long status; + inputFile *pinputFile = NULL; + char *penv; + char **macPairs; + + if(ellCount(&tempList)) { + epicsPrintf("dbReadCOM: Parser stack dirty %d\n", ellCount(&tempList)); + } + + if(*ppdbbase == 0) *ppdbbase = dbAllocBase(); + pdbbase = *ppdbbase; + if(path && strlen(path)>0) { + dbPath(pdbbase,path); + } else { + penv = getenv("EPICS_DB_INCLUDE_PATH"); + if(penv) { + dbPath(pdbbase,penv); + } else { + dbPath(pdbbase,"."); + } + } + my_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char)); + freeListInitPvt(&freeListPvt,sizeof(tempListNode),100); + if(substitutions) { + if(macCreateHandle(&macHandle,NULL)) { + epicsPrintf("macCreateHandle error\n"); + status = -1; + goto cleanup; + } + macParseDefns(macHandle,(char *)substitutions,&macPairs); + if(macPairs ==NULL) { + macDeleteHandle(macHandle); + macHandle = NULL; + } else { + macInstallMacros(macHandle,macPairs); + free((void *)macPairs); + mac_input_buffer = dbCalloc(MY_BUFFER_SIZE,sizeof(char)); + } + macSuppressWarning(macHandle,dbQuietMacroWarnings); + } + pinputFile = dbCalloc(1,sizeof(inputFile)); + if (filename) { + pinputFile->filename = macEnvExpand(filename); + } + if (!fp) { + FILE *fp1 = 0; + + if (pinputFile->filename) + pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp1); + if (!pinputFile->filename || !fp1) { + errPrintf(0, __FILE__, __LINE__, + "dbRead opening file %s",pinputFile->filename); + free(pinputFile->filename); + free(pinputFile); + status = -1; + goto cleanup; + } + pinputFile->fp = fp1; + } else { + pinputFile->fp = fp; + } + pinputFile->line_num = 0; + pinputFileNow = pinputFile; + my_buffer[0] = '\0'; + my_buffer_ptr = my_buffer; + ellAdd(&inputFileList,&pinputFile->node); + status = pvt_yy_parse(); + + if (ellCount(&tempList) && !yyAbort) + epicsPrintf("dbReadCOM: Parser stack dirty w/o error. %d\n", ellCount(&tempList)); + while (ellCount(&tempList)) + popFirstTemp(); /* Memory leak on parser failure */ + + dbFreePath(pdbbase); + if(!status) { /*add RTYP and VERS as an attribute */ + DBENTRY dbEntry; + DBENTRY *pdbEntry = &dbEntry; + long localStatus; + + dbInitEntry(pdbbase,pdbEntry); + localStatus = dbFirstRecordType(pdbEntry); + while(!localStatus) { + localStatus = dbPutRecordAttribute(pdbEntry,"RTYP", + dbGetRecordTypeName(pdbEntry)); + if(!localStatus) { + localStatus = dbPutRecordAttribute(pdbEntry,"VERS", + "none specified"); + } + if(localStatus) { + fprintf(stderr,"dbPutRecordAttribute status %ld\n",status); + } else { + localStatus = dbNextRecordType(pdbEntry); + } + } + dbFinishEntry(pdbEntry); + } +cleanup: + if(dbRecordsAbcSorted) { + ELLNODE *cur; + for(cur = ellFirst(&pdbbase->recordTypeList); cur; cur=ellNext(cur)) + { + dbRecordType *rtype = CONTAINER(cur, dbRecordType, node); + + ellSortStable(&rtype->recList, &cmp_dbRecordNode); + } + } + if(macHandle) macDeleteHandle(macHandle); + macHandle = NULL; + if(mac_input_buffer) free((void *)mac_input_buffer); + mac_input_buffer = NULL; + if(freeListPvt) freeListCleanup(freeListPvt); + freeListPvt = NULL; + if(my_buffer) free((void *)my_buffer); + my_buffer = NULL; + freeInputFileList(); + return(status); +} + +long dbReadDatabase(DBBASE **ppdbbase,const char *filename, + const char *path,const char *substitutions) +{return (dbReadCOM(ppdbbase,filename,0,path,substitutions));} + +long dbReadDatabaseFP(DBBASE **ppdbbase,FILE *fp, + const char *path,const char *substitutions) +{return (dbReadCOM(ppdbbase,0,fp,path,substitutions));} + +static int db_yyinput(char *buf, int max_size) +{ + size_t l,n; + char *fgetsRtn; + + if(yyAbort) return(0); + if(*my_buffer_ptr==0) { + while(TRUE) { /*until we get some input*/ + if(macHandle) { + fgetsRtn = fgets(mac_input_buffer,MY_BUFFER_SIZE, + pinputFileNow->fp); + if(fgetsRtn) { + int exp = macExpandString(macHandle,mac_input_buffer, + my_buffer,MY_BUFFER_SIZE); + if (exp < 0) { + fprintf(stderr, "Warning: '%s' line %d has undefined macros\n", + pinputFileNow->filename, pinputFileNow->line_num+1); + } + } + } else { + fgetsRtn = fgets(my_buffer,MY_BUFFER_SIZE,pinputFileNow->fp); + } + if(fgetsRtn) break; + if(fclose(pinputFileNow->fp)) + errPrintf(0,__FILE__, __LINE__, + "Closing file %s",pinputFileNow->filename); + free((void *)pinputFileNow->filename); + ellDelete(&inputFileList,(ELLNODE *)pinputFileNow); + free((void *)pinputFileNow); + pinputFileNow = (inputFile *)ellLast(&inputFileList); + if(!pinputFileNow) return(0); + } + if(dbStaticDebug) fprintf(stderr,"%s",my_buffer); + pinputFileNow->line_num++; + my_buffer_ptr = &my_buffer[0]; + } + l = strlen(my_buffer_ptr); + n = (l<=max_size ? l : max_size); + memcpy(buf,my_buffer_ptr,n); + my_buffer_ptr += n; + return (int)n; +} + +static void dbIncludePrint(void) +{ + inputFile *pinputFile = pinputFileNow; + + while (pinputFile) { + epicsPrintf(" in"); + if (pinputFile->path) + epicsPrintf(" path \"%s\" ",pinputFile->path); + if (pinputFile->filename) { + epicsPrintf(" file \"%s\"",pinputFile->filename); + } else { + epicsPrintf(" standard input"); + } + epicsPrintf(" line %d\n",pinputFile->line_num); + pinputFile = (inputFile *)ellPrevious(&pinputFile->node); + } + return; +} + +static void dbPathCmd(char *path) +{ + dbPath(pdbbase,path); +} + +static void dbAddPathCmd(char *path) +{ + dbAddPath(pdbbase,path); +} + +static void dbIncludeNew(char *filename) +{ + inputFile *pinputFile; + FILE *fp; + + pinputFile = dbCalloc(1,sizeof(inputFile)); + pinputFile->filename = macEnvExpand(filename); + pinputFile->path = dbOpenFile(pdbbase, pinputFile->filename, &fp); + if (!fp) { + epicsPrintf("Can't open include file \"%s\"\n", filename); + yyerror(NULL); + free((void *)pinputFile->filename); + free((void *)pinputFile); + return; + } + pinputFile->fp = fp; + ellAdd(&inputFileList,&pinputFile->node); + pinputFileNow = pinputFile; +} + +static void dbMenuHead(char *name) +{ + dbMenu *pdbMenu; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->menuList); + if(pgphentry) { + duplicate = TRUE; + return; + } + if(ellCount(&tempList)) yyerrorAbort("dbMenuHead: tempList not empty"); + pdbMenu = dbCalloc(1,sizeof(dbMenu)); + pdbMenu->name = epicsStrDup(name); + allocTemp(pdbMenu); +} + +static void dbMenuChoice(char *name,char *value) +{ + if(duplicate) return; + allocTemp(epicsStrDup(name)); + allocTemp(epicsStrDup(value)); +} + +static void dbMenuBody(void) +{ + dbMenu *pnewMenu; + dbMenu *pMenu; + int nChoice; + int i; + GPHENTRY *pgphentry; + + if(duplicate) { + duplicate = FALSE; + return; + } + pnewMenu = (dbMenu *)popFirstTemp(); + pnewMenu->nChoice = nChoice = ellCount(&tempList)/2; + pnewMenu->papChoiceName = dbCalloc(pnewMenu->nChoice,sizeof(char *)); + pnewMenu->papChoiceValue = dbCalloc(pnewMenu->nChoice,sizeof(char *)); + for(i=0; ipapChoiceName[i] = (char *)popFirstTemp(); + pnewMenu->papChoiceValue[i] = (char *)popFirstTemp(); + } + if(ellCount(&tempList)) yyerrorAbort("dbMenuBody: tempList not empty"); + /* Add menu in sorted order */ + pMenu = (dbMenu *)ellFirst(&pdbbase->menuList); + while(pMenu && strcmp(pMenu->name,pnewMenu->name) >0 ) + pMenu = (dbMenu *)ellNext(&pMenu->node); + if(pMenu) + ellInsert(&pdbbase->menuList,ellPrevious(&pMenu->node),&pnewMenu->node); + else + ellAdd(&pdbbase->menuList,&pnewMenu->node); + pgphentry = gphAdd(pdbbase->pgpHash,pnewMenu->name,&pdbbase->menuList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } else { + pgphentry->userPvt = pnewMenu; + } +} + +static void dbRecordtypeHead(char *name) +{ + dbRecordType *pdbRecordType; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->recordTypeList); + if(pgphentry) { + duplicate = TRUE; + return; + } + pdbRecordType = dbCalloc(1,sizeof(dbRecordType)); + pdbRecordType->name = epicsStrDup(name); + if (pdbbase->loadCdefs) ellInit(&pdbRecordType->cdefList); + if(ellCount(&tempList)) + yyerrorAbort("dbRecordtypeHead tempList not empty"); + allocTemp(pdbRecordType); +} + +static void dbRecordtypeFieldHead(char *name,char *type) +{ + dbFldDes *pdbFldDes; + int i; + + if(duplicate) return; + pdbFldDes = dbCalloc(1,sizeof(dbFldDes)); + allocTemp(pdbFldDes); + pdbFldDes->name = epicsStrDup(name); + pdbFldDes->as_level = ASL1; + pdbFldDes->isDevLink = strcmp(pdbFldDes->name, "INP")==0 || + strcmp(pdbFldDes->name, "OUT")==0; + i = dbFindFieldType(type); + if (i < 0) + yyerrorAbort("Illegal Field Type"); + pdbFldDes->field_type = i; +} + +static short findOrAddGuiGroup(const char *name) +{ + dbGuiGroup *pdbGuiGroup; + GPHENTRY *pgphentry; + pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList); + if (!pgphentry) { + pdbGuiGroup = dbCalloc(1,sizeof(dbGuiGroup)); + pdbGuiGroup->name = epicsStrDup(name); + ellAdd(&pdbbase->guiGroupList, &pdbGuiGroup->node); + pdbGuiGroup->key = ellCount(&pdbbase->guiGroupList); + pgphentry = gphAdd(pdbbase->pgpHash, pdbGuiGroup->name, &pdbbase->guiGroupList); + pgphentry->userPvt = pdbGuiGroup; + } + return ((dbGuiGroup *)pgphentry->userPvt)->key; +} + +static void dbRecordtypeFieldItem(char *name,char *value) +{ + dbFldDes *pdbFldDes; + + if(duplicate) return; + pdbFldDes = (dbFldDes *)getLastTemp(); + if(strcmp(name,"asl")==0) { + if(strcmp(value,"ASL0")==0) { + pdbFldDes->as_level = ASL0; + } else if(strcmp(value,"ASL1")==0) { + pdbFldDes->as_level = ASL1; + } else { + yyerror("Illegal Access Security value: Must be ASL0 or ASL1"); + } + return; + } + if(strcmp(name,"initial")==0) { + pdbFldDes->initial = epicsStrDup(value); + return; + } + if(strcmp(name,"promptgroup")==0) { + pdbFldDes->promptgroup = findOrAddGuiGroup(value); + return; + } + if(strcmp(name,"prompt")==0) { + pdbFldDes->prompt = epicsStrDup(value); + return; + } + if(strcmp(name,"special")==0) { + int i; + for(i=0; ispecial = pamapspcType[i].value; + return; + } + } + if(sscanf(value,"%hd",&pdbFldDes->special)==1) { + return; + } + yyerror("Illegal special value."); + return; + } + if(strcmp(name,"pp")==0) { + if((strcmp(value,"YES")==0) || (strcmp(value,"TRUE")==0)) { + pdbFldDes->process_passive = TRUE; + } else if((strcmp(value,"NO")==0) || (strcmp(value,"FALSE")==0)) { + pdbFldDes->process_passive = FALSE; + } else { + yyerror("Illegal value. Must be NO or YES"); + } + return; + } + if(strcmp(name,"interest")==0) { + if(sscanf(value,"%hd",&pdbFldDes->interest)!=1) + yyerror("Illegal value. Must be integer"); + return; + } + if(strcmp(name,"base")==0) { + if(strcmp(value,"DECIMAL")==0) { + pdbFldDes->base = CT_DECIMAL; + } else if(strcmp(value,"HEX")==0) { + pdbFldDes->base = CT_HEX; + } else { + yyerror("Illegal value. Must be CT_DECIMAL or CT_HEX"); + } + return; + } + if(strcmp(name,"size")==0) { + if(sscanf(value,"%hd",&pdbFldDes->size)!=1) + yyerror("Illegal value. Must be integer"); + return; + } + if(strcmp(name,"extra")==0) { + pdbFldDes->extra = epicsStrDup(value); + return; + } + if(strcmp(name,"menu")==0) { + pdbFldDes->ftPvt = (dbMenu *)dbFindMenu(pdbbase,value); + if(!pdbbase->ignoreMissingMenus && !pdbFldDes->ftPvt) + yyerrorAbort("menu not found"); + return; + } + if(strcmp(name,"prop")==0) { + if(strcmp(value, "YES")==0) + pdbFldDes->prop = 1; + else + pdbFldDes->prop = 0; + return; + } +} + +static void dbRecordtypeCdef(char *text) { + dbText *pdbCdef; + tempListNode *ptempListNode; + dbRecordType *pdbRecordType; + + if (!pdbbase->loadCdefs || duplicate) return; + ptempListNode = (tempListNode *)ellFirst(&tempList); + pdbRecordType = ptempListNode->item; + + pdbCdef = dbCalloc(1,sizeof(dbText)); + if (text[0] == ' ') text++; /* strip leading space if present */ + pdbCdef->text = epicsStrDup(text); + ellAdd(&pdbRecordType->cdefList, &pdbCdef->node); + return; +} + +static void dbRecordtypeEmpty(void) +{ + tempListNode *ptempListNode; + dbRecordType *pdbRecordType; + + if (duplicate) { + duplicate = FALSE; + return; + } + + ptempListNode = (tempListNode *)ellFirst(&tempList); + pdbRecordType = ptempListNode->item; + epicsPrintf("Declaration of recordtype(%s) preceeded full definition.\n", + pdbRecordType->name); + yyerrorAbort(NULL); +} + +static void dbRecordtypeBody(void) +{ + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + int i,j,ilink; + GPHENTRY *pgphentry; + int no_fields,no_prompt,no_links; + dbfType field_type; + char *psortFldNameTemp; + short psortFldIndTemp; + char **papsortFldName; + short *sortFldInd; + + if(duplicate) { + duplicate = FALSE; + return; + } + pdbRecordType= (dbRecordType *)popFirstTemp(); + pdbRecordType->no_fields = no_fields = ellCount(&tempList); + pdbRecordType->papFldDes = dbCalloc(no_fields,sizeof(dbFldDes *)); + pdbRecordType->papsortFldName = dbCalloc(no_fields,sizeof(char *)); + pdbRecordType->sortFldInd = dbCalloc(no_fields,sizeof(short)); + no_prompt = no_links = 0; + for(i=0; ipdbRecordType = pdbRecordType; + pdbFldDes->indRecordType = i; + pdbRecordType->papFldDes[i] = pdbFldDes; + if(pdbFldDes->promptgroup) no_prompt++; + field_type = pdbFldDes->field_type; + if((field_type>=DBF_INLINK) && (field_type<=DBF_FWDLINK))no_links++; + if((field_type==DBF_STRING) && (pdbFldDes->size==0)) + fprintf(stderr,"recordtype(%s).%s size not specified\n", + pdbRecordType->name,pdbFldDes->name); + if((field_type==DBF_NOACCESS) && (pdbFldDes->extra==0)) + fprintf(stderr,"recordtype(%s).%s extra not specified\n", + pdbRecordType->name,pdbFldDes->name); + } + if (ellCount(&tempList)) + yyerrorAbort("dbRecordtypeBody: tempList not empty"); + pdbRecordType->no_prompt = no_prompt; + pdbRecordType->no_links = no_links; + pdbRecordType->link_ind = dbCalloc(no_links,sizeof(short)); + ilink = 0; + for(i=0; ipapFldDes[i]; + /* if prompt is null make it a null string */ + if(!pdbFldDes->prompt) pdbFldDes->prompt = dbCalloc(1,sizeof(char)); + field_type = pdbFldDes->field_type; + if((field_type>=DBF_INLINK) && (field_type<=DBF_FWDLINK)) + pdbRecordType->link_ind[ilink++] = i; + if(strcmp(pdbFldDes->name,"VAL")==0) { + pdbRecordType->pvalFldDes = pdbRecordType->papFldDes[i]; + pdbRecordType->indvalFlddes = i; + } + pdbRecordType->papsortFldName[i] = pdbFldDes->name; + pdbRecordType->sortFldInd[i] = i; + } + /*Now sort fields. Sorry dumb sort algorithm */ + papsortFldName = pdbRecordType->papsortFldName; + sortFldInd = pdbRecordType->sortFldInd; + for(i=0; iattributeList); + ellInit(&pdbRecordType->recList); + ellInit(&pdbRecordType->devList); + pgphentry = gphAdd(pdbbase->pgpHash,pdbRecordType->name, + &pdbbase->recordTypeList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } else { + pgphentry->userPvt = pdbRecordType; + } + ellAdd(&pdbbase->recordTypeList,&pdbRecordType->node); +} + +static void dbDevice(char *recordtype,char *linktype, + char *dsetname,char *choicestring) +{ + devSup *pdevSup; + dbRecordType *pdbRecordType; + GPHENTRY *pgphentry; + int i,link_type; + pgphentry = gphFind(pdbbase->pgpHash,recordtype,&pdbbase->recordTypeList); + if(!pgphentry) { + epicsPrintf("Record type \"%s\" not found for device \"%s\"\n", + recordtype, choicestring); + yyerror(NULL); + return; + } + link_type=-1; + for(i=0; iuserPvt; + pgphentry = gphFind(pdbbase->pgpHash,choicestring,&pdbRecordType->devList); + if(pgphentry) { + return; + } + pdevSup = dbCalloc(1,sizeof(devSup)); + pdevSup->name = epicsStrDup(dsetname); + pdevSup->choice = epicsStrDup(choicestring); + pdevSup->link_type = link_type; + pgphentry = gphAdd(pdbbase->pgpHash,pdevSup->choice,&pdbRecordType->devList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } else { + pgphentry->userPvt = pdevSup; + } + ellAdd(&pdbRecordType->devList,&pdevSup->node); +} + +static void dbDriver(char *name) +{ + drvSup *pdrvSup; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->drvList); + if(pgphentry) { + return; + } + pdrvSup = dbCalloc(1,sizeof(drvSup)); + pdrvSup->name = epicsStrDup(name); + pgphentry = gphAdd(pdbbase->pgpHash,pdrvSup->name,&pdbbase->drvList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } + pgphentry->userPvt = pdrvSup; + ellAdd(&pdbbase->drvList,&pdrvSup->node); +} + +static void dbLinkType(char *name, char *jlif_name) +{ + linkSup *pLinkSup; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->linkList); + if (pgphentry) { + return; + } + pLinkSup = dbCalloc(1,sizeof(linkSup)); + pLinkSup->name = epicsStrDup(name); + pLinkSup->jlif_name = epicsStrDup(jlif_name); + pgphentry = gphAdd(pdbbase->pgpHash, pLinkSup->name, &pdbbase->linkList); + if (!pgphentry) { + yyerrorAbort("gphAdd failed"); + } + pgphentry->userPvt = pLinkSup; + ellAdd(&pdbbase->linkList, &pLinkSup->node); +} + +static void dbRegistrar(char *name) +{ + dbText *ptext; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->registrarList); + if(pgphentry) { + return; + } + ptext = dbCalloc(1,sizeof(dbText)); + ptext->text = epicsStrDup(name); + pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->registrarList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } + pgphentry->userPvt = ptext; + ellAdd(&pdbbase->registrarList,&ptext->node); +} + +static void dbFunction(char *name) +{ + dbText *ptext; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->functionList); + if(pgphentry) { + return; + } + ptext = dbCalloc(1,sizeof(dbText)); + ptext->text = epicsStrDup(name); + pgphentry = gphAdd(pdbbase->pgpHash,ptext->text,&pdbbase->functionList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } + pgphentry->userPvt = ptext; + ellAdd(&pdbbase->functionList,&ptext->node); +} + +static void dbVariable(char *name, char *type) +{ + dbVariableDef *pvar; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->variableList); + if(pgphentry) { + return; + } + pvar = dbCalloc(1,sizeof(dbVariableDef)); + pvar->name = epicsStrDup(name); + pvar->type = epicsStrDup(type); + pgphentry = gphAdd(pdbbase->pgpHash,pvar->name,&pdbbase->variableList); + if(!pgphentry) { + yyerrorAbort("gphAdd failed"); + } + pgphentry->userPvt = pvar; + ellAdd(&pdbbase->variableList,&pvar->node); +} + +static void dbBreakHead(char *name) +{ + brkTable *pbrkTable; + GPHENTRY *pgphentry; + + pgphentry = gphFind(pdbbase->pgpHash,name,&pdbbase->bptList); + if(pgphentry) { + duplicate = TRUE; + return; + } + pbrkTable = dbCalloc(1,sizeof(brkTable)); + pbrkTable->name = epicsStrDup(name); + if(ellCount(&tempList)) yyerrorAbort("dbBreakHead:tempList not empty"); + allocTemp(pbrkTable); +} + +static void dbBreakItem(char *value) +{ + double dummy; + if (duplicate) return; + if (epicsScanDouble(value, &dummy) != 1) { + yyerrorAbort("Non-numeric value in breaktable"); + } + allocTemp(epicsStrDup(value)); +} + +static void dbBreakBody(void) +{ + brkTable *pnewbrkTable; + brkInt *paBrkInt; + brkTable *pbrkTable; + int number, down=0; + int i; + GPHENTRY *pgphentry; + + if (duplicate) { + duplicate = FALSE; + return; + } + pnewbrkTable = (brkTable *)popFirstTemp(); + number = ellCount(&tempList); + if (number % 2) { + yyerrorAbort("breaktable: Raw value missing"); + return; + } + number /= 2; + if (number < 2) { + yyerrorAbort("breaktable: Must have at least two points!"); + return; + } + pnewbrkTable->number = number; + pnewbrkTable->paBrkInt = paBrkInt = dbCalloc(number, sizeof(brkInt)); + for (i=0; ibptList); + while (pbrkTable) { + if (strcmp(pbrkTable->name, pnewbrkTable->name) > 0) { + ellInsert(&pdbbase->bptList, ellPrevious((ELLNODE *)pbrkTable), + (ELLNODE *)pnewbrkTable); + break; + } + pbrkTable = (brkTable *)ellNext(&pbrkTable->node); + } + if (!pbrkTable) ellAdd(&pdbbase->bptList, &pnewbrkTable->node); + pgphentry = gphAdd(pdbbase->pgpHash,pnewbrkTable->name,&pdbbase->bptList); + if (!pgphentry) { + yyerrorAbort("dbBreakBody: gphAdd failed"); + return; + } + pgphentry->userPvt = pnewbrkTable; +} + +static void dbRecordHead(char *recordType, char *name, int visible) +{ + char *badch; + DBENTRY *pdbentry; + long status; + + badch = strpbrk(name, " \"'.$"); + if (badch) { + epicsPrintf("Bad character '%c' in record name \"%s\"\n", + *badch, name); + } + + pdbentry = dbAllocEntry(pdbbase); + if (ellCount(&tempList)) + yyerrorAbort("dbRecordHead: tempList not empty"); + allocTemp(pdbentry); + + if (recordType[0] == '*' && recordType[1] == 0) { + if (dbRecordsOnceOnly) + epicsPrintf("Record-type \"*\" not valid with dbRecordsOnceOnly\n"); + else { + status = dbFindRecord(pdbentry, name); + if (status == 0) + return; /* done */ + epicsPrintf("Record \"%s\" not found\n", name); + } + yyerror(NULL); + duplicate = TRUE; + return; + } + + status = dbFindRecordType(pdbentry, recordType); + if (status) { + epicsPrintf("Record \"%s\" is of unknown type \"%s\"\n", + name, recordType); + yyerrorAbort(NULL); + return; + } + + /*Duplicate records are ok if the same type */ + + status = dbCreateRecord(pdbentry,name); + if (status == S_dbLib_recExists) { + if (strcmp(recordType, dbGetRecordTypeName(pdbentry)) != 0) { + epicsPrintf("Record \"%s\" of type \"%s\" redefined with new type " + "\"%s\"\n", name, dbGetRecordTypeName(pdbentry), recordType); + yyerror(NULL); + duplicate = TRUE; + return; + } + else if (dbRecordsOnceOnly) { + epicsPrintf("Record \"%s\" already defined (dbRecordsOnceOnly is " + "set)\n", name); + yyerror(NULL); + duplicate = TRUE; + } + } + else if (status) { + epicsPrintf("Can't create record \"%s\" of type \"%s\"\n", + name, recordType); + yyerrorAbort(NULL); + } + + if (visible) + dbVisibleRecord(pdbentry); +} + +static void dbRecordField(char *name,char *value) +{ + DBENTRY *pdbentry; + tempListNode *ptempListNode; + long status; + + if(duplicate) return; + ptempListNode = (tempListNode *)ellFirst(&tempList); + pdbentry = ptempListNode->item; + status = dbFindField(pdbentry,name); + if(status) { + epicsPrintf("Record \"%s\" does not have a field \"%s\"\n", + dbGetRecordName(pdbentry), name); + yyerror(NULL); + return; + } + if (*value == '"') { + /* jsonSTRING values still have their quotes */ + value++; + value[strlen(value) - 1] = 0; + } + dbTranslateEscape(value, value); /* in-place; safe & legal */ + status = dbPutString(pdbentry,value); + if(status) { + char msg[128]; + errSymLookup(status, msg, sizeof(msg)); + epicsPrintf("Can't set \"%s.%s\" to \"%s\" %s\n", + dbGetRecordName(pdbentry), name, value, msg); + yyerror(NULL); + return; + } +} + +static void dbRecordInfo(char *name, char *value) +{ + DBENTRY *pdbentry; + tempListNode *ptempListNode; + long status; + + if(duplicate) return; + ptempListNode = (tempListNode *)ellFirst(&tempList); + pdbentry = ptempListNode->item; + if (*value == '"') { + /* jsonSTRING values still have their quotes */ + value++; + value[strlen(value) - 1] = 0; + } + dbTranslateEscape(value, value); /* yuck: in-place, but safe */ + status = dbPutInfo(pdbentry,name,value); + if(status) { + epicsPrintf("Can't set \"%s\" info \"%s\" to \"%s\"\n", + dbGetRecordName(pdbentry), name, value); + yyerror(NULL); + return; + } +} + +static void dbRecordAlias(char *name) +{ + DBENTRY *pdbentry; + tempListNode *ptempListNode; + long status; + + if(duplicate) return; + ptempListNode = (tempListNode *)ellFirst(&tempList); + pdbentry = ptempListNode->item; + status = dbCreateAlias(pdbentry, name); + if(status) { + epicsPrintf("Can't create alias \"%s\" for \"%s\"\n", + name, dbGetRecordName(pdbentry)); + yyerror(NULL); + return; + } +} + +static void dbAlias(char *name, char *alias) +{ + DBENTRY dbEntry; + DBENTRY *pdbEntry = &dbEntry; + + dbInitEntry(pdbbase, pdbEntry); + if (dbFindRecord(pdbEntry, name)) { + epicsPrintf("Alias \"%s\" refers to unknown record \"%s\"\n", + alias, name); + yyerror(NULL); + } else if (dbCreateAlias(pdbEntry, alias)) { + epicsPrintf("Can't create alias \"%s\" referring to \"%s\"\n", + alias, name); + yyerror(NULL); + } + dbFinishEntry(pdbEntry); +} + +static void dbRecordBody(void) +{ + DBENTRY *pdbentry; + + if(duplicate) { + duplicate = FALSE; + return; + } + pdbentry = (DBENTRY *)popFirstTemp(); + if(ellCount(&tempList)) + yyerrorAbort("dbRecordBody: tempList not empty"); + dbFreeEntry(pdbentry); +} diff --git a/modules/database/src/ioc/dbStatic/dbPvdLib.c b/modules/database/src/ioc/dbStatic/dbPvdLib.c new file mode 100644 index 000000000..15fc5f401 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbPvdLib.c @@ -0,0 +1,229 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* dbPvdLib.c */ + +#include +#include +#include +#include + +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsMutex.h" +#include "epicsStdio.h" +#include "epicsString.h" + +#define epicsExportSharedSymbols +#include "dbBase.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" + +typedef struct { + ELLLIST list; + epicsMutexId lock; +} dbPvdBucket; + +typedef struct dbPvd { + unsigned int size; + unsigned int mask; + dbPvdBucket **buckets; +} dbPvd; + +unsigned int dbPvdHashTableSize = 0; + +#define MIN_SIZE 256 +#define DEFAULT_SIZE 512 +#define MAX_SIZE 65536 + + +int dbPvdTableSize(int size) +{ + if (size & (size - 1)) { + printf("dbPvdTableSize: %d is not a power of 2\n", size); + return -1; + } + + if (size < MIN_SIZE) + size = MIN_SIZE; + + if (size > MAX_SIZE) + size = MAX_SIZE; + + dbPvdHashTableSize = size; + return 0; +} + +void dbPvdInitPvt(dbBase *pdbbase) +{ + dbPvd *ppvd; + + if (pdbbase->ppvd) return; + + if (dbPvdHashTableSize == 0) { + dbPvdHashTableSize = DEFAULT_SIZE; + } + + ppvd = (dbPvd *)dbMalloc(sizeof(dbPvd)); + ppvd->size = dbPvdHashTableSize; + ppvd->mask = dbPvdHashTableSize - 1; + ppvd->buckets = dbCalloc(ppvd->size, sizeof(dbPvdBucket *)); + + pdbbase->ppvd = ppvd; + return; +} + +PVDENTRY *dbPvdFind(dbBase *pdbbase, const char *name, size_t lenName) +{ + dbPvd *ppvd = pdbbase->ppvd; + dbPvdBucket *pbucket; + PVDENTRY *ppvdNode; + + pbucket = ppvd->buckets[epicsMemHash(name, lenName, 0) & ppvd->mask]; + if (pbucket == NULL) return NULL; + + epicsMutexMustLock(pbucket->lock); + ppvdNode = (PVDENTRY *) ellFirst(&pbucket->list); + while (ppvdNode) { + const char *recordname = ppvdNode->precnode->recordname; + + if (strncmp(name, recordname, lenName) == 0 && + strlen(recordname) == lenName) + break; + ppvdNode = (PVDENTRY *) ellNext((ELLNODE *)ppvdNode); + } + epicsMutexUnlock(pbucket->lock); + return ppvdNode; +} + +PVDENTRY *dbPvdAdd(dbBase *pdbbase, dbRecordType *precordType, + dbRecordNode *precnode) +{ + dbPvd *ppvd = pdbbase->ppvd; + dbPvdBucket *pbucket; + PVDENTRY *ppvdNode; + char *name = precnode->recordname; + unsigned int h; + + h = epicsStrHash(name, 0) & ppvd->mask; + pbucket = ppvd->buckets[h]; + if (pbucket == NULL) { + pbucket = dbCalloc(1, sizeof(dbPvdBucket)); + ellInit(&pbucket->list); + pbucket->lock = epicsMutexCreate(); + ppvd->buckets[h] = pbucket; + } + + epicsMutexMustLock(pbucket->lock); + ppvdNode = (PVDENTRY *) ellFirst(&pbucket->list); + while (ppvdNode) { + if (strcmp(name, ppvdNode->precnode->recordname) == 0) { + epicsMutexUnlock(pbucket->lock); + return NULL; + } + ppvdNode = (PVDENTRY *) ellNext((ELLNODE *)ppvdNode); + } + ppvdNode = dbCalloc(1, sizeof(PVDENTRY)); + ppvdNode->precordType = precordType; + ppvdNode->precnode = precnode; + ellAdd(&pbucket->list, (ELLNODE *)ppvdNode); + epicsMutexUnlock(pbucket->lock); + return ppvdNode; +} + +void dbPvdDelete(dbBase *pdbbase, dbRecordNode *precnode) +{ + dbPvd *ppvd = pdbbase->ppvd; + dbPvdBucket *pbucket; + PVDENTRY *ppvdNode; + char *name = precnode->recordname; + + pbucket = ppvd->buckets[epicsStrHash(name, 0) & ppvd->mask]; + if (pbucket == NULL) return; + + epicsMutexMustLock(pbucket->lock); + ppvdNode = (PVDENTRY *) ellFirst(&pbucket->list); + while (ppvdNode) { + if (ppvdNode->precnode && + ppvdNode->precnode->recordname && + strcmp(name, ppvdNode->precnode->recordname) == 0) { + ellDelete(&pbucket->list, (ELLNODE *)ppvdNode); + free(ppvdNode); + break; + } + ppvdNode = (PVDENTRY *) ellNext((ELLNODE *)ppvdNode); + } + epicsMutexUnlock(pbucket->lock); + return; +} + +void dbPvdFreeMem(dbBase *pdbbase) +{ + dbPvd *ppvd = pdbbase->ppvd; + unsigned int h; + + if (ppvd == NULL) return; + pdbbase->ppvd = NULL; + + for (h = 0; h < ppvd->size; h++) { + dbPvdBucket *pbucket = ppvd->buckets[h]; + PVDENTRY *ppvdNode; + + if (pbucket == NULL) continue; + epicsMutexMustLock(pbucket->lock); + ppvd->buckets[h] = NULL; + while ((ppvdNode = (PVDENTRY *) ellFirst(&pbucket->list))) { + ellDelete(&pbucket->list, (ELLNODE *)ppvdNode); + free(ppvdNode); + } + epicsMutexUnlock(pbucket->lock); + epicsMutexDestroy(pbucket->lock); + free(pbucket); + } + free(ppvd->buckets); + free(ppvd); +} + +void dbPvdDump(dbBase *pdbbase, int verbose) +{ + unsigned int empty = 0; + dbPvd *ppvd; + unsigned int h; + + if (!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + ppvd = pdbbase->ppvd; + if (ppvd == NULL) return; + + printf("Process Variable Directory has %u buckets", ppvd->size); + + for (h = 0; h < ppvd->size; h++) { + dbPvdBucket *pbucket = ppvd->buckets[h]; + PVDENTRY *ppvdNode; + int i = 1; + + if (pbucket == NULL) { + empty++; + continue; + } + epicsMutexMustLock(pbucket->lock); + ppvdNode = (PVDENTRY *) ellFirst(&pbucket->list); + printf("\n [%4u] %4d ", h, ellCount(&pbucket->list)); + while (ppvdNode && verbose) { + if (!(++i % 4)) + printf("\n "); + printf(" %s", ppvdNode->precnode->recordname); + ppvdNode = (PVDENTRY *) ellNext((ELLNODE*)ppvdNode); + } + epicsMutexUnlock(pbucket->lock); + } + printf("\n%u buckets empty.\n", empty); +} diff --git a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c new file mode 100644 index 000000000..65acc198d --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c @@ -0,0 +1,179 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "dbStaticIocRegister.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" + +/* common arguments */ + +static const iocshArg argPdbbase = { "pdbbase", iocshArgPdbbase}; +static const iocshArg argRecType = { "recordTypeName", iocshArgString}; + + +/* dbDumpPath */ +static const iocshArg * const dbDumpPathArgs[] = {&argPdbbase}; +static const iocshFuncDef dbDumpPathFuncDef = {"dbDumpPath",1,dbDumpPathArgs}; +static void dbDumpPathCallFunc(const iocshArgBuf *args) +{ + dbDumpPath(*iocshPpdbbase); +} + +/* dbDumpRecord */ +static const iocshArg dbDumpRecordArg2 = { "interest level",iocshArgInt}; +static const iocshArg * const dbDumpRecordArgs[] = + {&argPdbbase, &argRecType, &dbDumpRecordArg2}; +static const iocshFuncDef dbDumpRecordFuncDef = + {"dbDumpRecord",3,dbDumpRecordArgs}; +static void dbDumpRecordCallFunc(const iocshArgBuf *args) +{ + dbDumpRecord(*iocshPpdbbase,args[1].sval,args[2].ival); +} + +/* dbDumpMenu */ +static const iocshArg dbDumpMenuArg1 = { "menuName",iocshArgString}; +static const iocshArg * const dbDumpMenuArgs[] = { + &argPdbbase, &dbDumpMenuArg1}; +static const iocshFuncDef dbDumpMenuFuncDef = {"dbDumpMenu",2,dbDumpMenuArgs}; +static void dbDumpMenuCallFunc(const iocshArgBuf *args) +{ + dbDumpMenu(*iocshPpdbbase,args[1].sval); +} + +/* dbDumpRecordType */ +static const iocshArg * const dbDumpRecordTypeArgs[] = + {&argPdbbase, &argRecType}; +static const iocshFuncDef dbDumpRecordTypeFuncDef = + {"dbDumpRecordType",2,dbDumpRecordTypeArgs}; +static void dbDumpRecordTypeCallFunc(const iocshArgBuf *args) +{ + dbDumpRecordType(*iocshPpdbbase,args[1].sval); +} + +/* dbDumpField */ +static const iocshArg dbDumpFieldArg2 = { "fieldName",iocshArgString}; +static const iocshArg * const dbDumpFieldArgs[] = + {&argPdbbase, &argRecType,&dbDumpFieldArg2}; +static const iocshFuncDef dbDumpFieldFuncDef = {"dbDumpField",3,dbDumpFieldArgs}; +static void dbDumpFieldCallFunc(const iocshArgBuf *args) +{ + dbDumpField(*iocshPpdbbase,args[1].sval,args[2].sval); +} + +/* dbDumpDevice */ +static const iocshArg * const dbDumpDeviceArgs[] = { + &argPdbbase, &argRecType}; +static const iocshFuncDef dbDumpDeviceFuncDef = {"dbDumpDevice",2,dbDumpDeviceArgs}; +static void dbDumpDeviceCallFunc(const iocshArgBuf *args) +{ + dbDumpDevice(*iocshPpdbbase,args[1].sval); +} + +/* dbDumpDriver */ +static const iocshArg * const dbDumpDriverArgs[] = { &argPdbbase}; +static const iocshFuncDef dbDumpDriverFuncDef = {"dbDumpDriver",1,dbDumpDriverArgs}; +static void dbDumpDriverCallFunc(const iocshArgBuf *args) +{ + dbDumpDriver(*iocshPpdbbase); +} + +/* dbDumpLink */ +static const iocshArg * const dbDumpLinkArgs[] = { &argPdbbase}; +static const iocshFuncDef dbDumpLinkFuncDef = {"dbDumpLink",1,dbDumpLinkArgs}; +static void dbDumpLinkCallFunc(const iocshArgBuf *args) +{ + dbDumpLink(*iocshPpdbbase); +} + +/* dbDumpRegistrar */ +static const iocshArg * const dbDumpRegistrarArgs[] = { &argPdbbase}; +static const iocshFuncDef dbDumpRegistrarFuncDef = {"dbDumpRegistrar",1,dbDumpRegistrarArgs}; +static void dbDumpRegistrarCallFunc(const iocshArgBuf *args) +{ + dbDumpRegistrar(*iocshPpdbbase); +} + +/* dbDumpFunction */ +static const iocshArg * const dbDumpFunctionArgs[] = { &argPdbbase}; +static const iocshFuncDef dbDumpFunctionFuncDef = {"dbDumpFunction",1,dbDumpFunctionArgs}; +static void dbDumpFunctionCallFunc(const iocshArgBuf *args) +{ + dbDumpFunction(*iocshPpdbbase); +} + +/* dbDumpVariable */ +static const iocshArg * const dbDumpVariableArgs[] = { &argPdbbase}; +static const iocshFuncDef dbDumpVariableFuncDef = {"dbDumpVariable",1,dbDumpVariableArgs}; +static void dbDumpVariableCallFunc(const iocshArgBuf *args) +{ + dbDumpVariable(*iocshPpdbbase); +} + +/* dbDumpBreaktable */ +static const iocshArg dbDumpBreaktableArg1 = { "tableName",iocshArgString}; +static const iocshArg * const dbDumpBreaktableArgs[] = + {&argPdbbase,&dbDumpBreaktableArg1}; +static const iocshFuncDef dbDumpBreaktableFuncDef = + {"dbDumpBreaktable",2,dbDumpBreaktableArgs}; +static void dbDumpBreaktableCallFunc(const iocshArgBuf *args) +{ + dbDumpBreaktable(*iocshPpdbbase,args[1].sval); +} + +/* dbPvdDump */ +static const iocshArg dbPvdDumpArg1 = { "verbose",iocshArgInt}; +static const iocshArg * const dbPvdDumpArgs[] = { + &argPdbbase,&dbPvdDumpArg1}; +static const iocshFuncDef dbPvdDumpFuncDef = {"dbPvdDump",2,dbPvdDumpArgs}; +static void dbPvdDumpCallFunc(const iocshArgBuf *args) +{ + dbPvdDump(*iocshPpdbbase,args[1].ival); +} + +/* dbPvdTableSize */ +static const iocshArg dbPvdTableSizeArg0 = { "size",iocshArgInt}; +static const iocshArg * const dbPvdTableSizeArgs[1] = + {&dbPvdTableSizeArg0}; +static const iocshFuncDef dbPvdTableSizeFuncDef = + {"dbPvdTableSize",1,dbPvdTableSizeArgs}; +static void dbPvdTableSizeCallFunc(const iocshArgBuf *args) +{ + dbPvdTableSize(args[0].ival); +} + +/* dbReportDeviceConfig */ +static const iocshArg * const dbReportDeviceConfigArgs[] = {&argPdbbase}; +static const iocshFuncDef dbReportDeviceConfigFuncDef = { + "dbReportDeviceConfig",1,dbReportDeviceConfigArgs}; +static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args) +{ + dbReportDeviceConfig(*iocshPpdbbase,stdout); +} + +void dbStaticIocRegister(void) +{ + iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc); + iocshRegister(&dbDumpRecordFuncDef, dbDumpRecordCallFunc); + iocshRegister(&dbDumpMenuFuncDef, dbDumpMenuCallFunc); + iocshRegister(&dbDumpRecordTypeFuncDef, dbDumpRecordTypeCallFunc); + iocshRegister(&dbDumpFieldFuncDef, dbDumpFieldCallFunc); + iocshRegister(&dbDumpDeviceFuncDef, dbDumpDeviceCallFunc); + iocshRegister(&dbDumpDriverFuncDef, dbDumpDriverCallFunc); + iocshRegister(&dbDumpLinkFuncDef, dbDumpLinkCallFunc); + iocshRegister(&dbDumpRegistrarFuncDef,dbDumpRegistrarCallFunc); + iocshRegister(&dbDumpFunctionFuncDef, dbDumpFunctionCallFunc); + iocshRegister(&dbDumpVariableFuncDef, dbDumpVariableCallFunc); + iocshRegister(&dbDumpBreaktableFuncDef, dbDumpBreaktableCallFunc); + iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc); + iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc); + iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc); +} diff --git a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.h b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.h new file mode 100644 index 000000000..0b7ff743b --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.h @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbStaticIocRegister_H +#define INC_dbStaticIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void dbStaticIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbStaticIocRegister_H */ diff --git a/modules/database/src/ioc/dbStatic/dbStaticLib.c b/modules/database/src/ioc/dbStatic/dbStaticLib.c new file mode 100644 index 000000000..4e0755db7 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticLib.c @@ -0,0 +1,3608 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "cantProceed.h" +#include "cvtFast.h" +#include "epicsAssert.h" +#include "dbDefs.h" +#include "dbmf.h" +#include "ellLib.h" +#include "epicsPrint.h" +#include "epicsStdio.h" +#include "epicsStdlib.h" +#include "epicsString.h" +#include "errlog.h" +#include "gpHash.h" +#include "osiFileName.h" +#include "postfix.h" + +#define DBFLDTYPES_GBLSOURCE +#define SPECIAL_GBLSOURCE + +#define epicsExportSharedSymbols +#include "dbChannel.h" +#include "dbFldTypes.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "devSup.h" +#include "drvSup.h" +#include "link.h" +#include "special.h" + +#include "dbCommon.h" +#include "dbJLink.h" + +int dbStaticDebug = 0; +static char *pNullString = ""; +#define messagesize 276 +#define RPCL_LEN INFIX_TO_POSTFIX_SIZE(80) + +/* Must be big enough to hold a 64-bit integer in base 10, but in + * the future when fields hold large JSON objects this fixed size + * allocation will probably have to become variable sized. + */ +STATIC_ASSERT(messagesize >= 21); + +static char *ppstring[5]={" NPP"," PP"," CA"," CP"," CPP"}; +static char *msstring[4]={" NMS"," MS"," MSI"," MSS"}; + +epicsShareDef maplinkType pamaplinkType[LINK_NTYPES] = { + {"CONSTANT",CONSTANT}, + {"PV_LINK",PV_LINK}, + {"VME_IO",VME_IO}, + {"CAMAC_IO",CAMAC_IO}, + {"AB_IO",AB_IO}, + {"GPIB_IO",GPIB_IO}, + {"BITBUS_IO",BITBUS_IO}, + {"MACRO_LINK",MACRO_LINK}, + {"JSON_LINK",JSON_LINK}, + {"PN_LINK",PN_LINK}, + {"DB_LINK",DB_LINK}, + {"CA_LINK",CA_LINK}, + {"INST_IO",INST_IO}, + {"BBGPIB_IO",BBGPIB_IO}, + {"RF_IO",RF_IO}, + {"VXI_IO",VXI_IO} +}; + +static int mapDBFtoDCT[DBF_NOACCESS+1] = { + DCT_STRING, + DCT_INTEGER,DCT_INTEGER,DCT_INTEGER,DCT_INTEGER,DCT_INTEGER,DCT_INTEGER, + DCT_REAL,DCT_REAL, + DCT_INTEGER, + DCT_MENU, + DCT_MENUFORM, + DCT_INLINK,DCT_OUTLINK,DCT_FWDLINK, + DCT_NOACCESS}; + +/*forward references for private routines*/ +static void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) + EPICS_PRINTF_STYLE(2,3); +static long dbAddOnePath (DBBASE *pdbbase, const char *path, unsigned length); + +/* internal routines*/ +static FILE *openOutstream(const char *filename) +{ + FILE *stream; + errno = 0; + stream = fopen(filename,"w"); + if(!stream) { + fprintf(stderr,"error opening %s %s\n",filename,strerror(errno)); + return 0; + } + return stream; +} + +static void finishOutstream(FILE *stream) +{ + if(stream==stdout) { + fflush(stdout); + } else { + if(fclose(stream)) fprintf(stderr,"fclose error %s\n",strerror(errno)); + } +} + +void dbFreeLinkContents(struct link *plink) +{ + char *parm = NULL; + + switch(plink->type) { + case CONSTANT: free((void *)plink->value.constantStr); break; + case MACRO_LINK: free((void *)plink->value.macro_link.macroStr); break; + case PV_LINK: free((void *)plink->value.pv_link.pvname); break; + case JSON_LINK: + dbJLinkFree(plink->value.json.jlink); + parm = plink->value.json.string; + break; + case VME_IO: parm = plink->value.vmeio.parm; break; + case CAMAC_IO: parm = plink->value.camacio.parm; break; + case AB_IO: parm = plink->value.abio.parm; break; + case GPIB_IO: parm = plink->value.gpibio.parm; break; + case BITBUS_IO: parm = plink->value.bitbusio.parm;break; + case INST_IO: parm = plink->value.instio.string; break; + case BBGPIB_IO: parm = plink->value.bbgpibio.parm;break; + case RF_IO: break; + case VXI_IO: parm = plink->value.vxiio.parm; break; + default: + epicsPrintf("dbFreeLink called but link type %d unknown\n", plink->type); + } + if(parm && (parm != pNullString)) free((void *)parm); + if(plink->text) free(plink->text); + plink->lset = NULL; + plink->text = NULL; + memset(&plink->value, 0, sizeof(union value)); +} + +void dbFreePath(DBBASE *pdbbase) +{ + ELLLIST *ppathList; + dbPathNode *pdbPathNode; + + if(!pdbbase) return; + ppathList = (ELLLIST *)pdbbase->pathPvt; + if(!ppathList) return; + while((pdbPathNode = (dbPathNode *)ellFirst(ppathList))) { + ellDelete(ppathList,&pdbPathNode->node); + free((void *)pdbPathNode->directory); + free((void *)pdbPathNode); + } + free((void *)ppathList); + pdbbase->pathPvt = 0; + return; +} + + +static void entryErrMessage(DBENTRY *pdbentry,long status,char *mess) +{ + char message[200]; + char *pmessage=&message[0]; + dbRecordNode *precnode = pdbentry->precnode; + dbFldDes *pflddes = pdbentry->pflddes; + char *pname = NULL; + + *pmessage=0; + if(pdbentry->precordType) pname = pdbentry->precordType->name; + if(pname) { + strcat(pmessage,"RecordType:"); + strcat(pmessage,pname); + } + if(precnode){ + if (dbIsAlias(pdbentry)) + strcat(pmessage," Record Alias:"); + else + strcat(pmessage," Record:"); + strcat(pmessage,(char *)precnode->precord); + } + if(pflddes) { + char *pstr=pflddes->name; + + strcat(pmessage," Field:"); + strcat(pmessage,pstr); + } + strcat(pmessage,"\n"); + strcat(pmessage,mess); + errMessage(status,pmessage); +} + +static void zeroDbentry(DBENTRY *pdbentry) +{ + /*NOTE that pdbbase and message MUST NOT be set to NULL*/ + pdbentry->precordType=NULL; + pdbentry->pflddes=NULL; + pdbentry->precnode=NULL; + pdbentry->pfield=NULL; + pdbentry->indfield=0; +} + +static char *getpMessage(DBENTRY *pdbentry) +{ + char *msg = pdbentry->message; + + if (!msg) { + msg = dbCalloc(1, messagesize); + pdbentry->message = msg; + } + else + *msg = '\0'; + return msg; +} + +static +void dbMsgCpy(DBENTRY *pdbentry, const char *msg) +{ + getpMessage(pdbentry); + strncpy(pdbentry->message, msg, messagesize-1); + pdbentry->message[messagesize-1] = '\0'; +} + +static +void dbMsgNCpy(DBENTRY *pdbentry, const char *msg, size_t len) +{ + getpMessage(pdbentry); + if (len >= messagesize) + len = messagesize-1; /* FIXME: Quietly truncates */ + + strncpy(pdbentry->message, msg, len); + pdbentry->message[len] = '\0'; +} + +static +void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) +{ + va_list args; + getpMessage(pdbentry); + va_start(args, fmt); + epicsVsnprintf(pdbentry->message, messagesize, fmt, args); + va_end(args); +} + +static void ulongToHexString(epicsUInt32 source, char *pdest) +{ + static const char hex_digit_to_ascii[16] = "0123456789abcdef"; + epicsUInt32 val,temp; + char digit[10]; + int i,j; + + if (source==0) { + strcpy(pdest,"0x0"); + return; + } + *pdest++ = '0'; *pdest++ = 'x'; + val = source; + for (i=0; val!=0; i++) { + temp = val/16; + digit[i] = hex_digit_to_ascii[val - temp*16]; + val = temp; + } + for (j=i-1; j>=0; j--) { + *pdest++ = digit[j]; + } + *pdest = 0; + return; +} + +static void realToString(double value, char *preturn, int isdouble) +{ + static const double delta[2] = {1e-6, 1e-15}; + static const int precision[2] = {6, 14}; + double absvalue; + int logval,prec; + size_t end; + char tstr[30]; + char *ptstr = &tstr[0]; + int round; + int ise = FALSE; + char *loce = NULL; + + if (value == 0) { + strcpy(preturn, "0"); + return; + } + + absvalue = value < 0 ? -value : value; + if (absvalue < (double)INT_MAX) { + epicsInt32 intval = (epicsInt32) value; + double diff = value - intval; + + if (diff < 0) diff = -diff; + if (diff < absvalue * delta[isdouble]) { + cvtLongToString(intval, preturn); + return; + } + } + + /*Now starts the hard cases*/ + if (value < 0) { + *preturn++ = '-'; + value = -value; + } + + logval = (int)log10(value); + if (logval > 6 || logval < -2) { + int nout; + + ise = TRUE; + prec = precision[isdouble]; + nout = sprintf(ptstr, "%.*e", prec, value); + loce = strchr(ptstr, 'e'); + + if (!loce) { + ptstr[nout] = 0; + strcpy(preturn, ptstr); + return; + } + + *loce++ = 0; + } else { + prec = precision[isdouble] - logval; + if ( prec < 0) prec = 0; + sprintf(ptstr, "%.*f", prec, value); + } + + if (prec > 0) { + end = strlen(ptstr) - 1; + round = FALSE; + while (end > 0) { + if (tstr[end] == '.') {end--; break;} + if (tstr[end] == '0') {end--; continue;} + if (!round && end < precision[isdouble]) break; + if (!round && tstr[end] < '8') break; + if (tstr[end-1] == '.') { + if (round) end = end-2; + break; + } + if (tstr[end-1] != '9') break; + round = TRUE; + end--; + } + tstr[end+1] = 0; + while (round) { + if (tstr[end] < '9') {tstr[end]++; break;} + if (end == 0) { *preturn++ = '1'; tstr[end] = '0'; break;} + tstr[end--] = '0'; + } + } + strcpy(preturn, &tstr[0]); + if (ise) { + if (!(strchr(preturn, '.'))) strcat(preturn, ".0"); + strcat(preturn, "e"); + strcat(preturn, loce); + } +} + +static void floatToString(float value, char *preturn) +{ + realToString((double)value, preturn, 0); +} + +static void doubleToString(double value, char *preturn) +{ + realToString(value, preturn, 1); +} + +/*Public only for dbStaticNoRun*/ +dbDeviceMenu *dbGetDeviceMenu(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + dbFldDes *pflddes = pdbentry->pflddes; + dbDeviceMenu *pdbDeviceMenu; + devSup *pdevSup; + int ind; + int nChoice; + + if(!precordType) return(NULL); + if(!pflddes) return(NULL); + if(pflddes->field_type!=DBF_DEVICE) return(NULL); + if(pflddes->ftPvt){ + pdbDeviceMenu = (dbDeviceMenu *)pflddes->ftPvt; + if(pdbDeviceMenu->nChoice == ellCount(&precordType->devList)) + return(pdbDeviceMenu); + free((void *)pdbDeviceMenu->papChoice); + free((void *)pdbDeviceMenu); + pflddes->ftPvt = NULL; + } + nChoice = ellCount(&precordType->devList); + if(nChoice <= 0) return(NULL); + pdbDeviceMenu = dbCalloc(1,sizeof(dbDeviceMenu)); + pdbDeviceMenu->nChoice = nChoice; + pdbDeviceMenu->papChoice = dbCalloc(pdbDeviceMenu->nChoice,sizeof(char *)); + pdevSup = (devSup *)ellFirst(&precordType->devList); + ind = 0; + while(pdevSup) { + pdbDeviceMenu->papChoice[ind] = pdevSup->choice; + ind++; + pdevSup = (devSup *)ellNext(&pdevSup->node); + } + pflddes->ftPvt = pdbDeviceMenu; + return(pdbDeviceMenu); +} + +/* Beginning of Public Routines */ + +#define INC_SIZE 256 +void dbCatString(char **string,int *stringLength,char *src,char *separator) +{ + if((*string==NULL) + || ((strlen(*string)+strlen(src)+2) > (size_t)*stringLength)) { + char *newString; + size_t size; + + size = strlen(src); + if(*string) size += strlen(*string); + /*Make size multiple of INC_SIZE*/ + size = ((size + 2 + INC_SIZE)/INC_SIZE) * INC_SIZE; + newString = dbCalloc(size,sizeof(char)); + if(*string) { + strcpy(newString,*string); + free((void *)(*string)); + } + *string = newString; + } + if(*stringLength>0) { + strcat(*string,separator); + *stringLength += (int) strlen(separator); + } + strcat(*string,src); + *stringLength += (int) strlen(src); +} + +dbBase * dbAllocBase(void) +{ + dbBase *pdbbase; + + pdbbase = dbCalloc(1,sizeof(dbBase)); + ellInit(&pdbbase->menuList); + ellInit(&pdbbase->recordTypeList); + ellInit(&pdbbase->drvList); + ellInit(&pdbbase->registrarList); + ellInit(&pdbbase->functionList); + ellInit(&pdbbase->variableList); + ellInit(&pdbbase->bptList); + ellInit(&pdbbase->filterList); + ellInit(&pdbbase->guiGroupList); + gphInitPvt(&pdbbase->pgpHash,256); + dbPvdInitPvt(pdbbase); + return (pdbbase); +} +void dbFreeBase(dbBase *pdbbase) +{ + dbMenu *pdbMenu; + dbMenu *pdbMenuNext; + dbRecordType *pdbRecordType; + dbRecordType *pdbRecordTypeNext; + dbFldDes * pdbFldDes; + dbRecordAttribute *pAttribute; + dbRecordAttribute *pAttributeNext; + devSup *pdevSup; + devSup *pdevSupNext; + dbText *ptext; + dbText *ptextNext; + dbVariableDef *pvar; + dbVariableDef *pvarNext; + drvSup *pdrvSup; + drvSup *pdrvSupNext; + linkSup *plinkSup; + brkTable *pbrkTable; + brkTable *pbrkTableNext; + chFilterPlugin *pfilt; + chFilterPlugin *pfiltNext; + dbGuiGroup *pguiGroup; + dbGuiGroup *pguiGroupNext; + int i; + DBENTRY dbentry; + long status; + + dbInitEntry(pdbbase,&dbentry); + status = dbFirstRecordType(&dbentry); + while(!status) { + /* dbDeleteRecord() will remove alias or real record node. + * For real record nodes, also removes the nodes of all aliases. + * This complicates safe traversal, so we re-start iteration + * from the first record after each call. + */ + while((status = dbFirstRecord(&dbentry))==0) { + dbDeleteRecord(&dbentry); + } + assert(status==S_dbLib_recNotFound); + status = dbNextRecordType(&dbentry); + } + dbFinishEntry(&dbentry); + pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + while(pdbRecordType) { + for(i=0; ino_fields; i++) { + pdbFldDes = pdbRecordType->papFldDes[i]; + free((void *)pdbFldDes->prompt); + free((void *)pdbFldDes->name); + free((void *)pdbFldDes->extra); + free((void *)pdbFldDes->initial); + if(pdbFldDes->field_type==DBF_DEVICE && pdbFldDes->ftPvt) { + dbDeviceMenu *pdbDeviceMenu; + + pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt; + free((void *)pdbDeviceMenu->papChoice); + free((void *)pdbDeviceMenu); + pdbFldDes->ftPvt=0; + } + free((void *)pdbFldDes); + } + pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + while(pdevSup) { + pdevSupNext = (devSup *)ellNext(&pdevSup->node); + ellDelete(&pdbRecordType->devList,&pdevSup->node); + free((void *)pdevSup->name); + free((void *)pdevSup->choice); + free((void *)pdevSup); + pdevSup = pdevSupNext; + } + ptext = (dbText *)ellFirst(&pdbRecordType->cdefList); + while(ptext) { + ptextNext = (dbText *)ellNext(&ptext->node); + ellDelete(&pdbRecordType->cdefList,&ptext->node); + free((void *)ptext->text); + free((void *)ptext); + ptext = ptextNext; + } + pAttribute = + (dbRecordAttribute *)ellFirst(&pdbRecordType->attributeList); + while(pAttribute) { + pAttributeNext = (dbRecordAttribute *)ellNext(&pAttribute->node); + ellDelete(&pdbRecordType->attributeList,&pAttribute->node); + free((void *)pAttribute->name); + free((void *)pAttribute->pdbFldDes); + free(pAttribute); + pAttribute = pAttributeNext; + } + pdbRecordTypeNext = (dbRecordType *)ellNext(&pdbRecordType->node); + gphDelete(pdbbase->pgpHash,pdbRecordType->name,&pdbbase->recordTypeList); + ellDelete(&pdbbase->recordTypeList,&pdbRecordType->node); + free((void *)pdbRecordType->name); + free((void *)pdbRecordType->link_ind); + free((void *)pdbRecordType->papsortFldName); + free((void *)pdbRecordType->sortFldInd); + free((void *)pdbRecordType->papFldDes); + free((void *)pdbRecordType); + pdbRecordType = pdbRecordTypeNext; + } + pdbMenu = (dbMenu *)ellFirst(&pdbbase->menuList); + while(pdbMenu) { + pdbMenuNext = (dbMenu *)ellNext(&pdbMenu->node); + gphDelete(pdbbase->pgpHash,pdbMenu->name,&pdbbase->menuList); + ellDelete(&pdbbase->menuList,&pdbMenu->node); + for(i=0; i< pdbMenu->nChoice; i++) { + free((void *)pdbMenu->papChoiceName[i]); + free((void *)pdbMenu->papChoiceValue[i]); + } + free((void *)pdbMenu->papChoiceName); + free((void *)pdbMenu->papChoiceValue); + free((void *)pdbMenu ->name); + free((void *)pdbMenu); + pdbMenu = pdbMenuNext; + } + pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList); + while(pdrvSup) { + pdrvSupNext = (drvSup *)ellNext(&pdrvSup->node); + ellDelete(&pdbbase->drvList,&pdrvSup->node); + free((void *)pdrvSup->name); + free((void *)pdrvSup); + pdrvSup = pdrvSupNext; + } + while ((plinkSup = (linkSup *) ellGet(&pdbbase->linkList))) { + free(plinkSup->jlif_name); + free(plinkSup->name); + free(plinkSup); + } + ptext = (dbText *)ellFirst(&pdbbase->registrarList); + while(ptext) { + ptextNext = (dbText *)ellNext(&ptext->node); + ellDelete(&pdbbase->registrarList,&ptext->node); + free((void *)ptext->text); + free((void *)ptext); + ptext = ptextNext; + } + ptext = (dbText *)ellFirst(&pdbbase->functionList); + while(ptext) { + ptextNext = (dbText *)ellNext(&ptext->node); + ellDelete(&pdbbase->functionList,&ptext->node); + free((void *)ptext->text); + free((void *)ptext); + ptext = ptextNext; + } + pvar = (dbVariableDef *)ellFirst(&pdbbase->variableList); + while(pvar) { + pvarNext = (dbVariableDef *)ellNext(&pvar->node); + ellDelete(&pdbbase->variableList,&pvar->node); + free((void *)pvar->name); + free((void *)pvar->type); + free((void *)pvar); + pvar = pvarNext; + } + pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList); + while(pbrkTable) { + pbrkTableNext = (brkTable *)ellNext(&pbrkTable->node); + gphDelete(pdbbase->pgpHash,pbrkTable->name,&pdbbase->bptList); + ellDelete(&pdbbase->bptList,&pbrkTable->node); + free(pbrkTable->name); + free((void *)pbrkTable->paBrkInt); + free((void *)pbrkTable); + pbrkTable = pbrkTableNext; + } + pfilt = (chFilterPlugin *)ellFirst(&pdbbase->filterList); + while(pfilt) { + pfiltNext = (chFilterPlugin *)ellNext(&pfilt->node); + free((char*)pfilt->name); + if(pfilt->fif->priv_free) + (*pfilt->fif->priv_free)(pfilt->puser); + free(pfilt); + pfilt = pfiltNext; + } + pguiGroup = (dbGuiGroup *)ellFirst(&pdbbase->guiGroupList); + while (pguiGroup) { + pguiGroupNext = (dbGuiGroup *)ellNext(&pguiGroup->node); + gphDelete(pdbbase->pgpHash, pguiGroup->name, &pdbbase->guiGroupList); + ellDelete(&pdbbase->guiGroupList, &pguiGroup->node); + free(pguiGroup->name); + free((void *)pguiGroup); + pguiGroup = pguiGroupNext; + } + gphFreeMem(pdbbase->pgpHash); + dbPvdFreeMem(pdbbase); + dbFreePath(pdbbase); + free((void *)pdbbase); + pdbbase = NULL; + return; +} + +DBENTRY * dbAllocEntry(dbBase *pdbbase) +{ + DBENTRY *pdbentry; + + pdbentry = dbmfMalloc(sizeof(DBENTRY)); + memset(pdbentry,'\0',sizeof(DBENTRY)); + pdbentry->pdbbase = pdbbase; + return(pdbentry); +} + +void dbFreeEntry(DBENTRY *pdbentry) +{ + if (!pdbentry) + return; + if (pdbentry->message) + free((void *)pdbentry->message); + dbmfFree(pdbentry); +} + +void dbInitEntry(dbBase *pdbbase,DBENTRY *pdbentry) +{ + memset((char *)pdbentry,'\0',sizeof(DBENTRY)); + pdbentry->pdbbase = pdbbase; +} + +void dbFinishEntry(DBENTRY *pdbentry) +{ + if(pdbentry->message) { + free((void *)pdbentry->message); + pdbentry->message = NULL; + } +} + +DBENTRY * dbCopyEntry(DBENTRY *pdbentry) +{ + DBENTRY *pnew; + + pnew = dbAllocEntry(pdbentry->pdbbase); + *pnew = *pdbentry; + pnew->message = NULL; + return(pnew); +} + +void dbCopyEntryContents(DBENTRY *pfrom,DBENTRY *pto) +{ + *pto = *pfrom; + pto->message = NULL; +} + + +long dbPath(DBBASE *pdbbase,const char *path) +{ + if(!pdbbase) return(-1); + dbFreePath(pdbbase); + if(!path || strlen(path)==0) return(dbAddPath(pdbbase,".")); + return(dbAddPath(pdbbase,path)); +} + +long dbAddPath(DBBASE *pdbbase,const char *path) +{ + ELLLIST *ppathList; + const char *pcolon; + const char *plast; + unsigned expectingPath; + unsigned sawMissingPath; + + if(!pdbbase) return(-1); + ppathList = (ELLLIST *)pdbbase->pathPvt; + if(!ppathList) { + ppathList = dbCalloc(1,sizeof(ELLLIST)); + ellInit(ppathList); + pdbbase->pathPvt = (void *)ppathList; + } + if (!path) return(0); /* Empty path strings are ignored */ + /* care is taken to properly deal with white space + * 1) preceding and trailing white space is removed from paths + * 2) white space inbetween path separator counts as an empty name + * (see below) + */ + expectingPath = FALSE; + sawMissingPath = FALSE; + while (*path) { + size_t len; + + /* preceding white space is removed */ + if (isspace((int)*path)) { + path++; + continue; + } + pcolon = strstr (path, OSI_PATH_LIST_SEPARATOR); + if (pcolon==path) { + sawMissingPath = TRUE; + path += strlen (OSI_PATH_LIST_SEPARATOR); + continue; + } + if (pcolon) { + plast = pcolon - 1; + expectingPath = TRUE; + } else { + plast = strlen (path) + path - 1; + expectingPath = FALSE; + } + /* trailing white space is removed */ + while (isspace((int)*plast)) { + plast--; + } + + /* + * len is always nonzero because we found something that + * 1) isnt white space + * 2) isnt a path separator + */ + len = (plast - path) + 1; + if (dbAddOnePath (pdbbase, path, (unsigned) len)) return (-1); + path += len; + if (pcolon) { + path += strlen(OSI_PATH_LIST_SEPARATOR); + } + } + + /* + * an empty name at beginning, middle, or end of a path string that isnt + * empty means current directory + */ + if (expectingPath||sawMissingPath) { + return dbAddOnePath (pdbbase, ".", 1); + } + return(0); +} + +static long dbAddOnePath (DBBASE *pdbbase, const char *path, unsigned length) +{ + ELLLIST *ppathList; + dbPathNode *pdbPathNode; + + if(!pdbbase) return(-1); + ppathList = (ELLLIST *)pdbbase->pathPvt; + + pdbPathNode = (dbPathNode *)dbCalloc(1, sizeof(dbPathNode)); + pdbPathNode->directory = (char *)dbCalloc(length+1, sizeof(char)); + strncpy(pdbPathNode->directory, path, length); + pdbPathNode->directory[length] = '\0'; + ellAdd(ppathList, &pdbPathNode->node); + return 0; +} + +char *dbGetPromptGroupNameFromKey(DBBASE *pdbbase, const short key) +{ + dbGuiGroup *pdbGuiGroup; + + if (!pdbbase) return NULL; + for (pdbGuiGroup = (dbGuiGroup *)ellFirst(&pdbbase->guiGroupList); + pdbGuiGroup; pdbGuiGroup = (dbGuiGroup *)ellNext(&pdbGuiGroup->node)) { + if (pdbGuiGroup->key == key) return pdbGuiGroup->name; + } + return NULL; +} + +short dbGetPromptGroupKeyFromName(DBBASE *pdbbase, const char *name) +{ + GPHENTRY *pgphentry; + + if (!pdbbase) return 0; + pgphentry = gphFind(pdbbase->pgpHash, name, &pdbbase->guiGroupList); + if (!pgphentry) { + return 0; + } else { + return ((dbGuiGroup*)pgphentry->userPvt)->key; + } +} + + +long dbWriteRecord(DBBASE *ppdbbase,const char *filename, + const char *precordTypename,int level) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + if(!stream) return -1; + status = dbWriteRecordFP(ppdbbase,stream,precordTypename,level); + finishOutstream(stream); + return status; +} + +long dbWriteRecordFP( + DBBASE *pdbbase,FILE *fp,const char *precordTypename,int level) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + int dctonly; + + dctonly = ((level>1) ? FALSE : TRUE); + dbInitEntry(pdbbase,pdbentry); + if (precordTypename) { + if (*precordTypename == 0 || *precordTypename == '*') + precordTypename = 0; + } + + if(!precordTypename) { + status = dbFirstRecordType(pdbentry); + if(status) { + /* No record descriptions, so no record instances */ + dbFinishEntry(pdbentry); + return(0); + } + } else { + status = dbFindRecordType(pdbentry,precordTypename); + if(status) { + fprintf(stderr,"dbWriteRecordFP: No record description for %s\n", + precordTypename); + dbFinishEntry(pdbentry); + return(status); + } + } + while(!status) { + status = dbFirstRecord(pdbentry); + while(!status) { + if (dbIsAlias(pdbentry)) { + status = dbNextRecord(pdbentry); + continue; + } + if(dbIsVisibleRecord(pdbentry)) + fprintf(fp,"grecord(%s,\"%s\") {\n", + dbGetRecordTypeName(pdbentry),dbGetRecordName(pdbentry)); + else + fprintf(fp,"record(%s,\"%s\") {\n", + dbGetRecordTypeName(pdbentry),dbGetRecordName(pdbentry)); + status = dbFirstField(pdbentry,dctonly); + while(!status) { + if (!dbIsDefaultValue(pdbentry) || level>0) { + char *pvalstring = dbGetString(pdbentry); + + if (!pvalstring) { + fprintf(fp,"\tfield(%s,\"\")\n", + dbGetFieldName(pdbentry)); + } else { + fprintf(fp,"\tfield(%s,\"", + dbGetFieldName(pdbentry)); + epicsStrPrintEscaped(fp,pvalstring,strlen(pvalstring)); + fprintf(fp,"\")\n"); + } + } else if(level>0) { /*generate 0 length string*/ + fprintf(fp,"\tfield(%s,\"\")\n",dbGetFieldName(pdbentry)); + } + status=dbNextField(pdbentry,dctonly); + } + status = dbFirstInfo(pdbentry); + while (!status) { + const char *pinfostr = dbGetInfoString(pdbentry); + + fprintf(fp, "\tinfo(\"%s\",\"", + dbGetInfoName(pdbentry)); + epicsStrPrintEscaped(fp, pinfostr, strlen(pinfostr)); + fprintf(fp, "\")\n"); + status = dbNextInfo(pdbentry); + } + fprintf(fp,"}\n"); + status = dbNextRecord(pdbentry); + } + status = dbFirstRecord(pdbentry); + while (!status) { + if (!dbIsAlias(pdbentry)) { + status = dbNextRecord(pdbentry); + continue; + } + fprintf(fp, "alias(\"%s\",\"%s\")\n", + dbRecordName(pdbentry), dbGetRecordName(pdbentry)); + status = dbNextRecord(pdbentry); + } + if(precordTypename) break; + status = dbNextRecordType(pdbentry); + } + dbFinishEntry(pdbentry); + return(0); +} + +long dbWriteMenu( + DBBASE *ppdbbase,const char *filename,const char *menuName) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + status = dbWriteMenuFP(ppdbbase,stream,menuName); + finishOutstream(stream); + return status; +} + +long dbWriteMenuFP(DBBASE *pdbbase,FILE *fp,const char *menuName) +{ + dbMenu *pdbMenu; + int gotMatch; + int i; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + if (menuName) { + if (*menuName == 0 || *menuName == '*') + menuName = 0; + } + pdbMenu = (dbMenu *)ellFirst(&pdbbase->menuList); + while(pdbMenu) { + if(menuName) { + gotMatch = (strcmp(menuName,pdbMenu->name)==0) ? TRUE : FALSE; + }else { + gotMatch=TRUE; + } + if(gotMatch) { + fprintf(fp,"menu(%s) {\n",pdbMenu->name); + for(i=0; inChoice; i++) { + fprintf(fp,"\tchoice(%s,\"%s\")\n",pdbMenu->papChoiceName[i], + pdbMenu->papChoiceValue[i]); + } + fprintf(fp,"}\n"); + if(menuName) break; + } + pdbMenu = (dbMenu *)ellNext(&pdbMenu->node); + } + return(0); +} + +long dbWriteRecordType( + DBBASE *pdbbase,const char *filename,const char *recordTypeName) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + status = dbWriteRecordTypeFP(pdbbase,stream,recordTypeName); + finishOutstream(stream); + return status; +} + +long dbWriteRecordTypeFP( + DBBASE *pdbbase,FILE *fp,const char *recordTypeName) +{ + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + int gotMatch; + int i; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + if (recordTypeName) { + if (*recordTypeName == 0 || *recordTypeName == '*') + recordTypeName = 0; + } + + for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + if(recordTypeName) { + gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0) + ? TRUE : FALSE; + }else { + gotMatch=TRUE; + } + if(!gotMatch) continue; + fprintf(fp,"recordtype(%s) {\n",pdbRecordType->name); + for(i=0; ino_fields; i++) { + int j; + + pdbFldDes = pdbRecordType->papFldDes[i]; + fprintf(fp,"\tfield(%s,%s) {\n",pdbFldDes->name, + dbGetFieldTypeString(pdbFldDes->field_type)); + if(pdbFldDes->prompt) + fprintf(fp,"\t\tprompt(\"%s\")\n",pdbFldDes->prompt); + if(pdbFldDes->initial) + fprintf(fp,"\t\tinitial(\"%s\")\n",pdbFldDes->initial); + if (pdbFldDes->promptgroup) { + fprintf(fp,"\t\tpromptgroup(\"%s\")\n", + dbGetPromptGroupNameFromKey(pdbbase, pdbFldDes->promptgroup)); + } + if(pdbFldDes->special) { + if(pdbFldDes->special >= SPC_NTYPES) { + fprintf(fp,"\t\tspecial(%d)\n",pdbFldDes->special); + } else for(j=0; jspecial) { + fprintf(fp,"\t\tspecial(%s)\n", + pamapspcType[j].strvalue); + break; + } + } + } + if(pdbFldDes->extra) + fprintf(fp,"\t\textra(\"%s\")\n",pdbFldDes->extra); + if(pdbFldDes->field_type==DBF_MENU) { + if(pdbFldDes->ftPvt) + fprintf(fp,"\t\tmenu(%s)\n", + ((dbMenu *)pdbFldDes->ftPvt)->name); + else + fprintf(stderr,"\t\t menu: NOT FOUND\n"); + } + if(pdbFldDes->field_type==DBF_STRING) { + fprintf(fp,"\t\tsize(%d)\n", + pdbFldDes->size); + } + if(pdbFldDes->process_passive) fprintf(fp,"\t\tpp(TRUE)\n"); + if(pdbFldDes->prop) fprintf(fp,"\t\tprop(YES)\n"); + if(pdbFldDes->base) fprintf(fp,"\t\tbase(HEX)\n"); + if(pdbFldDes->interest) + fprintf(fp,"\t\tinterest(%d)\n",pdbFldDes->interest); + if(!pdbFldDes->as_level) fprintf(fp,"\t\tasl(ASL0)\n"); + fprintf(fp,"\t}\n"); + } + fprintf(fp,"}\n"); + if(recordTypeName) break; + } + return(0); +} + +long dbWriteDevice(DBBASE *pdbbase,const char *filename) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + status = dbWriteDeviceFP(pdbbase,stream); + finishOutstream(stream); + return status; +} + +long dbWriteDeviceFP(DBBASE *pdbbase,FILE *fp) +{ + dbRecordType *pdbRecordType; + devSup *pdevSup; + + if(!pdbbase) { + fprintf(stderr,"dbWriteDeviceFP: pdbbase not specified\n"); + return(-1); + } + for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + for(pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) { + int j; + + for(j=0; j< LINK_NTYPES; j++) { + if(pamaplinkType[j].value==pdevSup->link_type) break; + } + if(j>=LINK_NTYPES) { + fprintf(fp,"link_type not valid\n"); + continue; + } + fprintf(fp,"device(%s,%s,%s,\"%s\")\n", + pdbRecordType->name, + pamaplinkType[j].strvalue, + pdevSup->name,pdevSup->choice); + } + } + return(0); +} + +long dbWriteDriver(DBBASE *pdbbase,const char *filename) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + status = dbWriteDriverFP(pdbbase,stream); + finishOutstream(stream); + return status; +} + +long dbWriteDriverFP(DBBASE *pdbbase,FILE *fp) +{ + drvSup *pdrvSup; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + for(pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList); + pdrvSup; pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) { + fprintf(fp,"driver(%s)\n",pdrvSup->name); + } + return(0); +} + +long dbWriteLinkFP(DBBASE *pdbbase, FILE *fp) +{ + linkSup *plinkSup; + + if (!pdbbase) { + fprintf(stderr, "pdbbase not specified\n"); + return -1; + } + for (plinkSup = (linkSup *) ellFirst(&pdbbase->linkList); + plinkSup; plinkSup = (linkSup *) ellNext(&plinkSup->node)) { + fprintf(fp, "link(%s,%s)\n", plinkSup->name, plinkSup->jlif_name); + } + return 0; +} + +long dbWriteRegistrarFP(DBBASE *pdbbase,FILE *fp) +{ + dbText *ptext; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + for(ptext = (dbText *)ellFirst(&pdbbase->registrarList); + ptext; ptext = (dbText *)ellNext(&ptext->node)) { + fprintf(fp,"registrar(%s)\n",ptext->text); + } + return(0); +} + +long dbWriteFunctionFP(DBBASE *pdbbase,FILE *fp) +{ + dbText *ptext; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + for(ptext = (dbText *)ellFirst(&pdbbase->functionList); + ptext; ptext = (dbText *)ellNext(&ptext->node)) { + fprintf(fp,"function(%s)\n",ptext->text); + } + return(0); +} + +long dbWriteVariableFP(DBBASE *pdbbase,FILE *fp) +{ + dbVariableDef *pvar; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + for(pvar = (dbVariableDef *)ellFirst(&pdbbase->variableList); + pvar; pvar = (dbVariableDef *)ellNext(&pvar->node)) { + fprintf(fp,"variable(%s,%s)\n",pvar->name,pvar->type); + } + return(0); +} + +long dbWriteBreaktable(DBBASE *pdbbase,const char *filename) +{ + FILE *stream; + long status; + + stream = openOutstream(filename); + status = dbWriteBreaktableFP(pdbbase,stream); + finishOutstream(stream); + return status; +} + +long dbWriteBreaktableFP(DBBASE *pdbbase,FILE *fp) +{ + brkTable *pbrkTable; + brkInt *pbrkInt; + int i; + + if (!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return(-1); + } + for (pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList); + pbrkTable; + pbrkTable = (brkTable *)ellNext(&pbrkTable->node)) { + fprintf(fp,"breaktable(%s) {\n",pbrkTable->name); + pbrkInt = pbrkTable->paBrkInt; + for(i=0; i < pbrkTable->number; i++) { + fprintf(fp,"\t%e, %e\n",pbrkInt->raw,pbrkInt->eng); + pbrkInt++; + } + fprintf(fp,"}\n"); + } + return(0); +} + +long dbFindRecordType(DBENTRY *pdbentry,const char *recordType) +{ + dbBase *pdbbase = pdbentry->pdbbase; + GPHENTRY *phash; + + zeroDbentry(pdbentry); + phash = gphFind(pdbbase->pgpHash,recordType,&pdbbase->recordTypeList); + if(!phash) return(S_dbLib_recordTypeNotFound); + pdbentry->precordType = phash->userPvt; + return(0); +} + +long dbFirstRecordType(DBENTRY *pdbentry) +{ + dbRecordType *precordType; + + zeroDbentry(pdbentry); + precordType = (dbRecordType *)ellFirst(&pdbentry->pdbbase->recordTypeList); + if(!precordType) return(S_dbLib_recordTypeNotFound); + pdbentry->precordType = precordType; + return(0); +} + +long dbNextRecordType(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + + zeroDbentry(pdbentry); + precordType = (dbRecordType *)ellNext(&precordType->node); + if(!precordType) return(S_dbLib_recordTypeNotFound); + pdbentry->precordType = precordType; + return(0); +} + +char * dbGetRecordTypeName(DBENTRY *pdbentry) +{ + return(pdbentry->precordType->name); +} + +int dbGetNRecordTypes(DBENTRY *pdbentry) +{ + return(ellCount(&pdbentry->pdbbase->recordTypeList)); +} + +long dbPutRecordAttribute( + DBENTRY *pdbentry, const char *name, const char*value) +{ + dbRecordType *precordType = pdbentry->precordType; + int createNew = TRUE; + int compare; + dbRecordAttribute *pattribute; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + pattribute = (dbRecordAttribute *)ellFirst(&precordType->attributeList); + /*put new attribute name in sort order*/ + while(pattribute) { + compare = strcmp(pattribute->name,name); + if(compare==0) { + createNew = FALSE; + } + if(compare>=0) break; + pattribute = (dbRecordAttribute *)ellNext(&pattribute->node); + } + if(createNew) { + dbRecordAttribute *pnew; + dbFldDes *pdbFldDes; + + pnew = dbCalloc(1,sizeof(dbRecordAttribute)); + if(pattribute) { + ellInsert(&precordType->attributeList,pattribute->node.previous, + &pnew->node); + } else { + ellAdd(&precordType->attributeList,&pnew->node); + } + pattribute = pnew; + pattribute->name = dbCalloc(strlen(name)+1,sizeof(char)); + strcpy(pattribute->name,name); + pdbFldDes = dbCalloc(1,sizeof(dbFldDes)); + pdbFldDes->name = pattribute->name; + pdbFldDes->pdbRecordType = precordType; + pdbFldDes->special = SPC_ATTRIBUTE; + pdbFldDes->field_type = DBF_STRING; + pdbFldDes->as_level = ASL1; + pdbFldDes->size = MAX_STRING_SIZE; + pattribute->pdbFldDes = pdbFldDes; + } + strncpy(pattribute->value,value,MAX_STRING_SIZE); + pattribute->value[MAX_STRING_SIZE-1] = 0; + return(0); +} + +long dbGetAttributePart(DBENTRY *pdbentry, const char **ppname) +{ + dbRecordType *precordType = pdbentry->precordType; + const char *pname = *ppname; + dbRecordAttribute *pattribute; + + if (!precordType) + return S_dbLib_recordTypeNotFound; + + pattribute = (dbRecordAttribute *)ellFirst(&precordType->attributeList); + while (pattribute) { + size_t nameLen = strlen(pattribute->name); + int compare = strncmp(pattribute->name, pname, nameLen); + + if (compare == 0) { + int ch = pname[nameLen]; + + if (ch != '_' && !isalnum(ch)) { + /* Any other character can't be in the attribute name */ + pdbentry->pflddes = pattribute->pdbFldDes; + pdbentry->pfield = pattribute->value; + *ppname = &pname[nameLen]; + return 0; + } + if (strlen(pname) > nameLen) { + compare = -1; + } + } + if (compare >= 0) break; + pattribute = (dbRecordAttribute *)ellNext(&pattribute->node); + } + return S_dbLib_fieldNotFound; +} + +long dbGetRecordAttribute(DBENTRY *pdbentry, const char *pname) +{ + return dbGetAttributePart(pdbentry, &pname); +} + +long dbFirstField(DBENTRY *pdbentry,int dctonly) +{ + + pdbentry->indfield = -1; + return(dbNextField(pdbentry,dctonly)); +} + +long dbNextField(DBENTRY *pdbentry,int dctonly) +{ + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + dbFldDes *pflddes; + short indfield = pdbentry->indfield; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + indfield++; + while(TRUE) { + if(indfield>=precordType->no_fields) { + pdbentry->indfield = 0; + pdbentry->pflddes = NULL; + pdbentry->pfield = NULL; + return(S_dbLib_fieldNotFound); + } + pflddes = precordType->papFldDes[indfield]; + if(!dctonly || pflddes->promptgroup) { + /*Skip field if dctonly and no device support*/ + if(!dctonly || (pflddes->field_type!=DBF_DEVICE) + || (ellCount(&precordType->devList)>0)) { + pdbentry->indfield = indfield; + pdbentry->pflddes = pflddes; + pdbentry->indfield = indfield; + if(precnode) { + dbGetFieldAddress(pdbentry); + }else { + pdbentry->pfield = NULL; + } + return(0); + } + } + indfield++; + } +} + +int dbGetFieldType(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + long status; + + if(!pflddes){ + status = S_dbLib_flddesNotFound; + entryErrMessage(pdbentry,status,"dbGetFieldType"); + return(status); + } + return(mapDBFtoDCT[pflddes->field_type]); +} + +int dbGetNFields(DBENTRY *pdbentry,int dctonly) +{ + dbRecordType *precordType = pdbentry->precordType; + dbFldDes *pflddes; + int indfield,n; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + n = 0; + for(indfield=0; indfieldno_fields; indfield++) { + pflddes = precordType->papFldDes[indfield]; + if(dctonly && (pflddes->field_type==DBF_DEVICE) + && (ellCount(&precordType->devList)==0) ) continue; + if(!dctonly || pflddes->promptgroup) n++; + } + return(n); +} + +char * dbGetFieldName(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(NULL); + return(pflddes->name); +} + +char * dbGetDefault(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(NULL); + return(pflddes->initial); +} + +char * dbGetPrompt(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(NULL); + return(&pflddes->prompt[0]); +} + +int dbGetPromptGroup(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(0); + return(pflddes->promptgroup); +} + +long dbCreateRecord(DBENTRY *pdbentry,const char *precordName) +{ + dbRecordType *precordType = pdbentry->precordType; + dbFldDes *pdbFldDes; + PVDENTRY *ppvd; + ELLLIST *preclist = NULL; + dbRecordNode *pNewRecNode = NULL; + long status = 0; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + /*Get size of NAME field*/ + pdbFldDes = precordType->papFldDes[0]; + if(!pdbFldDes || (strcmp(pdbFldDes->name,"NAME")!=0)) + return(S_dbLib_nameLength); + if((int)strlen(precordName)>=pdbFldDes->size) return(S_dbLib_nameLength); + /* clear callers entry */ + zeroDbentry(pdbentry); + if(!dbFindRecord(pdbentry,precordName)) return (S_dbLib_recExists); + zeroDbentry(pdbentry); + pdbentry->precordType = precordType; + preclist = &precordType->recList; + /* create a recNode */ + pNewRecNode = dbCalloc(1,sizeof(dbRecordNode)); + /* create a new record of this record type */ + pdbentry->precnode = pNewRecNode; + if((status = dbAllocRecord(pdbentry,precordName))) return(status); + pNewRecNode->recordname = dbRecordName(pdbentry); + ellInit(&pNewRecNode->infoList); + ellAdd(preclist, &pNewRecNode->node); + pdbentry->precnode = pNewRecNode; + ppvd = dbPvdAdd(pdbentry->pdbbase,precordType,pNewRecNode); + if(!ppvd) {errMessage(-1,"Logic Err: Could not add to PVD");return(-1);} + return(0); +} + +long dbDeleteAliases(DBENTRY *pdbentry) +{ + dbBase *pdbbase = pdbentry->pdbbase; + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + ELLLIST *preclist = &precordType->recList; + dbRecordNode *pAliasNode, *pAliasNodeNext; + DBENTRY dbentry; + void *precord; + + if (!precnode) return S_dbLib_recNotFound; + if (precnode->flags & DBRN_FLAGS_ISALIAS) return S_dbLib_recExists; + precord = precnode->precord; + + dbInitEntry(pdbbase, &dbentry); + pAliasNode = (dbRecordNode *)ellFirst(preclist); + while (pAliasNode) { + pAliasNodeNext = (dbRecordNode *)ellNext(&pAliasNode->node); + if (pAliasNode->flags & DBRN_FLAGS_ISALIAS && + pAliasNode->precord == precord && + !dbFindRecord(&dbentry, pAliasNode->recordname)) { + dbDeleteRecord(&dbentry); + } + pAliasNode = pAliasNodeNext; + } + precnode->flags &= ~DBRN_FLAGS_HASALIAS; + return 0; +} + +long dbDeleteRecord(DBENTRY *pdbentry) +{ + dbBase *pdbbase = pdbentry->pdbbase; + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + ELLLIST *preclist; + long status; + + if (!precnode) return S_dbLib_recNotFound; + if (precnode->flags & DBRN_FLAGS_HASALIAS) + dbDeleteAliases(pdbentry); + + preclist = &precordType->recList; + ellDelete(preclist, &precnode->node); + dbPvdDelete(pdbbase, precnode); + while (!dbFirstInfo(pdbentry)) { + dbDeleteInfo(pdbentry); + } + if (precnode->flags & DBRN_FLAGS_ISALIAS) { + free(precnode->recordname); + precordType->no_aliases--; + } else { + status = dbFreeRecord(pdbentry); + if (status) return status; + } + free(precnode); + pdbentry->precnode = NULL; + return 0; +} + +long dbFreeRecords(DBBASE *pdbbase) +{ + DBENTRY dbentry; + dbRecordType *pdbRecordType; + dbRecordNode *pdbRecordNode; + dbRecordNode *pdbRecordNodeNext; + + dbInitEntry(pdbbase,&dbentry); + pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + while(pdbRecordType) { + pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList); + while(pdbRecordNode) { + pdbRecordNodeNext = (dbRecordNode *)ellNext(&pdbRecordNode->node); + if(!dbFindRecord(&dbentry,pdbRecordNode->recordname)) + dbDeleteRecord(&dbentry); + pdbRecordNode = pdbRecordNodeNext; + } + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node); + } + dbFinishEntry(&dbentry); + return(0); +} + +long dbFindRecordPart(DBENTRY *pdbentry, const char **ppname) +{ + dbBase *pdbbase = pdbentry->pdbbase; + const char *pname = *ppname; + const char *pfn; + size_t lenName; + PVDENTRY *ppvdNode; + + zeroDbentry(pdbentry); + pfn = strchr(pname, '.'); + if (pfn) { + lenName = (size_t) (pfn - pname); + } else { + lenName = strlen(pname); + } + + ppvdNode = dbPvdFind(pdbbase, pname, lenName); + if (!ppvdNode) + return S_dbLib_recNotFound; + + pdbentry->precnode = ppvdNode->precnode; + pdbentry->precordType = ppvdNode->precordType; + *ppname = pname + lenName; + return 0; +} + +long dbFindRecord(DBENTRY *pdbentry, const char *pname) +{ + long status = dbFindRecordPart(pdbentry, &pname); + + if (status) return status; + if (*pname == '.') + return dbFindField(pdbentry, ++pname); + return 0; +} + +long dbFirstRecord(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode; + + zeroDbentry(pdbentry); + if(!precordType) return(S_dbLib_recordTypeNotFound); + pdbentry->precordType = precordType; + precnode = (dbRecordNode *)ellFirst(&precordType->recList); + if(!precnode) return(S_dbLib_recNotFound); + pdbentry->precnode = precnode; + return(0); +} + +long dbNextRecord(DBENTRY *pdbentry) +{ + dbRecordNode *precnode=pdbentry->precnode; + long status=0; + + if(!precnode) return(S_dbLib_recNotFound); + precnode = (dbRecordNode *)ellNext(&precnode->node); + if(!precnode) status = S_dbLib_recNotFound; + pdbentry->precnode = precnode; + pdbentry->pfield = NULL; + return(status); +} + +int dbGetNRecords(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + + if(!precordType) return(0); + return(ellCount(&precordType->recList)); +} + +int dbGetNAliases(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + + if(!precordType) return(0); + return(precordType->no_aliases); +} + +char * dbGetRecordName(DBENTRY *pdbentry) +{ + dbRecordType *pdbRecordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + + if(!pdbRecordType) return NULL; + if(!precnode) return NULL; + return precnode->recordname; +} + +long dbVisibleRecord(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + + if(!precnode) return(S_dbLib_recNotFound); + precnode->flags |= DBRN_FLAGS_VISIBLE; + return 0; +} + +long dbInvisibleRecord(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + + if(!precnode) return(S_dbLib_recNotFound); + precnode->flags &= ~DBRN_FLAGS_VISIBLE; + return 0; +} + +int dbIsVisibleRecord(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + + if(!precnode) return 0; + return precnode->flags & DBRN_FLAGS_VISIBLE ? 1 : 0; +} + +long dbCreateAlias(DBENTRY *pdbentry, const char *alias) +{ + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + dbRecordNode *pnewnode; + DBENTRY tempEntry; + PVDENTRY *ppvd; + + if (!precordType) + return S_dbLib_recordTypeNotFound; + + /* alias of alias still references actual record */ + while (precnode && (precnode->flags & DBRN_FLAGS_ISALIAS)) + precnode = precnode->aliasedRecnode; + + if (!precnode) + return S_dbLib_recNotFound; + + dbInitEntry(pdbentry->pdbbase, &tempEntry); + if (!dbFindRecord(&tempEntry, alias)) + return S_dbLib_recExists; + dbFinishEntry(&tempEntry); + + pnewnode = dbCalloc(1, sizeof(dbRecordNode)); + pnewnode->recordname = epicsStrDup(alias); + pnewnode->precord = precnode->precord; + pnewnode->aliasedRecnode = precnode; + pnewnode->flags = DBRN_FLAGS_ISALIAS; + precnode->flags |= DBRN_FLAGS_HASALIAS; + ellInit(&pnewnode->infoList); + + ellAdd(&precordType->recList, &pnewnode->node); + precordType->no_aliases++; + + ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode); + if (!ppvd) { + errMessage(-1, "dbCreateAlias: Add to PVD failed"); + return -1; + } + + return 0; +} + +int dbFollowAlias(DBENTRY *pdbentry) +{ + if(!pdbentry->precnode) + return S_dbLib_recNotFound; + if(pdbentry->precnode->aliasedRecnode) + pdbentry->precnode = pdbentry->precnode->aliasedRecnode; + return 0; +} + +int dbIsAlias(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + + if(!precnode) return 0; + return precnode->flags & DBRN_FLAGS_ISALIAS ? 1 : 0; +} + +long dbCopyRecord(DBENTRY *pdbentry,const char *newRecordName,int overWriteOK) +{ + dbRecordType *precordType = pdbentry->precordType; + dbFldDes *pdbFldDes; + dbRecordNode *precnode = pdbentry->precnode; + long status; + DBENTRY dbentry; + char *pvalue; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + /*Get size of NAME field*/ + pdbFldDes = precordType->papFldDes[0]; + if(!pdbFldDes || (strcmp(pdbFldDes->name,"NAME")!=0)) + return(S_dbLib_nameLength); + if((int)strlen(newRecordName)>=pdbFldDes->size) return(S_dbLib_nameLength); + if (!precnode || dbIsAlias(pdbentry)) return S_dbLib_recNotFound; + dbInitEntry(pdbentry->pdbbase,&dbentry); + status = dbFindRecord(&dbentry,newRecordName); + if(!status) { + if(!overWriteOK) { + dbFinishEntry(&dbentry); + return(S_dbLib_recExists); + } + status = dbDeleteRecord(&dbentry); + if(status) return(status); + } + dbFinishEntry(&dbentry); + if((status = dbFindRecordType(&dbentry,precordType->name))) return(status); + if((status = dbCreateRecord(&dbentry,newRecordName))) return(status); + if((status = dbFirstField(pdbentry,TRUE))) return(status); + if((status = dbFirstField(&dbentry,TRUE))) return(status); + while(!status) { + if(!dbIsDefaultValue(pdbentry)) { + pvalue = dbGetString(pdbentry); + if((status = dbPutString(&dbentry,pvalue))) return(status); + } + status = dbNextField(pdbentry,TRUE); + if(!status) status = dbNextField(&dbentry,TRUE); + if(!status && (pdbentry->pflddes!=dbentry.pflddes)) { + epicsPrintf("dbCopyRecord: Logic Error\n"); + return(-1); + } + } + /*Copy the info strings too*/ + status = dbFirstInfo(pdbentry); + while (!status) { + status = dbPutInfo(&dbentry, dbGetInfoName(pdbentry), dbGetInfoString(pdbentry)); + if (status) return (status); + status = dbNextInfo(pdbentry); + } + /*Leave pdbentry pointing to newRecordName*/ + return(dbFindRecord(pdbentry,newRecordName)); +} + +long dbFindFieldPart(DBENTRY *pdbentry,const char **ppname) +{ + dbRecordType *precordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + const char *pname = *ppname; + short top, bottom, test; + char **papsortFldName; + short *sortFldInd; + int ch; + size_t nameLen; + + if (!precordType) return S_dbLib_recordTypeNotFound; + if (!precnode) return S_dbLib_recNotFound; + papsortFldName = precordType->papsortFldName; + sortFldInd = precordType->sortFldInd; + + /* Measure field name length; name is a valid C identifier */ + nameLen = 0; + if ((ch = *pname) && + (ch == '_' || isalpha(ch))) { + while ((ch = pname[++nameLen])) + if (!(ch == '_' || isalnum(ch))) break; + } + + /* Handle absent field name */ + if (nameLen == 0) { + dbFldDes *pflddes = precordType->pvalFldDes; + + if (!pflddes) + return S_dbLib_recordTypeNotFound; + pdbentry->pflddes = pflddes; + pdbentry->indfield = precordType->indvalFlddes; + *ppname = &pname[nameLen]; + return dbGetFieldAddress(pdbentry); + } + + /* binary search through ordered field names */ + top = precordType->no_fields - 1; + bottom = 0; + test = (top + bottom) / 2; + while (1) { + int compare = strncmp(papsortFldName[test], pname, nameLen); + if (compare == 0) + compare = (int) (strlen(papsortFldName[test]) - nameLen); + if (compare == 0) { + dbFldDes *pflddes = precordType->papFldDes[sortFldInd[test]]; + + if (!pflddes) + return S_dbLib_recordTypeNotFound; + pdbentry->pflddes = pflddes; + pdbentry->indfield = sortFldInd[test]; + *ppname = &pname[nameLen]; + return dbGetFieldAddress(pdbentry); + } else if (compare > 0) { + top = test - 1; + if (top < bottom) break; + test = (top + bottom) / 2; + } else { + bottom = test + 1; + if (top < bottom) break; + test = (top + bottom) / 2; + } + } + return S_dbLib_fieldNotFound; +} + +long dbFindField(DBENTRY *pdbentry,const char *pname) +{ + long status = dbFindFieldPart(pdbentry, &pname); + int ch; + + if (status == S_dbLib_fieldNotFound) + return dbGetRecordAttribute(pdbentry, pname); + if (status) return status; + + ch = *pname; + if (ch == 0 || isspace(ch)) return 0; + return S_dbLib_recNotFound; +} + +int dbFoundField(DBENTRY *pdbentry) +{ return((pdbentry->pfield) ? TRUE : FALSE); } + +char * dbGetString(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + DBLINK *plink; + + if (!pflddes) { + dbMsgCpy(pdbentry, "fldDes not found"); + return pdbentry->message; + } + switch (pflddes->field_type) { + case DBF_STRING: + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: + if (!pfield) { + dbMsgCpy(pdbentry, "Field not allocated (NULL)"); + return pdbentry->message; + } + break; + default: + break; + } + + switch (pflddes->field_type) { + case DBF_STRING: + /* Protect against a missing nil-terminator */ + dbMsgNCpy(pdbentry, (char *)pfield, pflddes->size); + break; + case DBF_CHAR: + case DBF_UCHAR: + case DBF_SHORT: + case DBF_USHORT: + case DBF_ENUM: + case DBF_LONG: + case DBF_ULONG: + case DBF_INT64: + case DBF_UINT64: + case DBF_FLOAT: + case DBF_DOUBLE: + case DBF_MENU: + case DBF_DEVICE: + return(dbGetStringNum(pdbentry)); + case DBF_INLINK: + case DBF_OUTLINK: + plink = (DBLINK *)pfield; + switch(plink->type) { + case CONSTANT: + if (plink->value.constantStr) { + dbMsgCpy(pdbentry, plink->value.constantStr); + } else if (plink->text) { + dbMsgCpy(pdbentry, plink->text); + } else { + dbMsgCpy(pdbentry, ""); + } + break; + case MACRO_LINK: + if (plink->value.macro_link.macroStr) { + dbMsgCpy(pdbentry, plink->value.macro_link.macroStr); + } else { + dbMsgCpy(pdbentry, ""); + } + break; + case JSON_LINK: + dbMsgCpy(pdbentry, plink->value.json.string); + break; + case PN_LINK: + dbMsgPrint(pdbentry, "%s%s", + plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "", + msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]); + break; + case PV_LINK: + case CA_LINK: + case DB_LINK: { + int ppind; + short pvlMask; + + pvlMask = plink->value.pv_link.pvlMask; + if (pvlMask&pvlOptPP) ppind=1; + else if(pvlMask&pvlOptCA) ppind=2; + else if(pvlMask&pvlOptCP) ppind=3; + else if(pvlMask&pvlOptCPP) ppind=4; + else ppind=0; + dbMsgPrint(pdbentry, "%s%s%s%s", + plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "", + (plink->flags & DBLINK_FLAG_TSELisTIME) ? ".TIME" : "", + ppstring[ppind], + msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]); + break; + } + case VME_IO: + dbMsgPrint(pdbentry, "#C%d S%d @%s", + plink->value.vmeio.card,plink->value.vmeio.signal, + plink->value.vmeio.parm); + break; + case CAMAC_IO: + dbMsgPrint(pdbentry, "#B%d C%d N%d A%d F%d @%s", + plink->value.camacio.b,plink->value.camacio.c, + plink->value.camacio.n,plink->value.camacio.a, + plink->value.camacio.f,plink->value.camacio.parm); + break; + case RF_IO: + dbMsgPrint(pdbentry, "#R%d M%d D%d E%d", + plink->value.rfio.cryo, + plink->value.rfio.micro, + plink->value.rfio.dataset, + plink->value.rfio.element); + break; + case AB_IO: + dbMsgPrint(pdbentry, "#L%d A%d C%d S%d @%s", + plink->value.abio.link,plink->value.abio.adapter, + plink->value.abio.card,plink->value.abio.signal, + plink->value.abio.parm); + break; + case GPIB_IO: + dbMsgPrint(pdbentry, "#L%d A%d @%s", + plink->value.gpibio.link,plink->value.gpibio.addr, + plink->value.gpibio.parm); + break; + case BITBUS_IO: + dbMsgPrint(pdbentry, "#L%u N%u P%u S%u @%s", + plink->value.bitbusio.link,plink->value.bitbusio.node, + plink->value.bitbusio.port,plink->value.bitbusio.signal, + plink->value.bitbusio.parm); + break; + case BBGPIB_IO: + dbMsgPrint(pdbentry, "#L%u B%u G%u @%s", + plink->value.bbgpibio.link,plink->value.bbgpibio.bbaddr, + plink->value.bbgpibio.gpibaddr,plink->value.bbgpibio.parm); + break; + case INST_IO: + dbMsgPrint(pdbentry, "@%s", plink->value.instio.string); + break; + case VXI_IO : + if (plink->value.vxiio.flag == VXIDYNAMIC) + dbMsgPrint(pdbentry, "#V%d C%d S%d @%s", + plink->value.vxiio.frame,plink->value.vxiio.slot, + plink->value.vxiio.signal,plink->value.vxiio.parm); + else + dbMsgPrint(pdbentry, "#V%d S%d @%s", + plink->value.vxiio.la,plink->value.vxiio.signal, + plink->value.vxiio.parm); + break; + default : + return(NULL); + } + break; + case DBF_FWDLINK: { + DBLINK *plink=(DBLINK *)pfield; + + switch(plink->type) { + case CONSTANT: + if (plink->value.constantStr) { + dbMsgCpy(pdbentry, plink->value.constantStr); + } else if (plink->text) { + dbMsgCpy(pdbentry, plink->text); + } else { + dbMsgCpy(pdbentry, ""); + } + break; + case MACRO_LINK: + if (plink->value.macro_link.macroStr) { + dbMsgCpy(pdbentry, plink->value.macro_link.macroStr); + } else { + dbMsgCpy(pdbentry, ""); + } + break; + case JSON_LINK: + dbMsgCpy(pdbentry, plink->value.json.string); + break; + case PV_LINK: + case CA_LINK: + case DB_LINK: { + int ppind; + short pvlMask; + + pvlMask = plink->value.pv_link.pvlMask; + if (pvlMask&pvlOptCA) ppind=2; + else ppind=0; + dbMsgPrint(pdbentry, "%s%s", + plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "", + ppind ? ppstring[ppind] : ""); + break; + } + default : + return(NULL); + } + } + break; + default: + return(NULL); + } + return pdbentry->message; +} + +char *dbGetStringNum(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + char *message; + unsigned char cvttype; + + /* the following assumes that messagesize is large enough + * to hold the base 10 encoded value of a 32-bit integer. + */ + message = getpMessage(pdbentry); + cvttype = pflddes->base; + switch (pflddes->field_type) { + case DBF_CHAR: + if (cvttype == CT_DECIMAL) + cvtCharToString(*(char *) pfield, message); + else + ulongToHexString(*(char *) pfield, message); + break; + case DBF_UCHAR: + if (cvttype==CT_DECIMAL) + cvtUcharToString(*(epicsUInt8 *) pfield, message); + else + ulongToHexString(*(epicsUInt8 *) pfield, message); + break; + case DBF_SHORT: + if (cvttype==CT_DECIMAL) + cvtShortToString(*(epicsInt16 *) pfield, message); + else + ulongToHexString(*(epicsInt16 *) pfield, message); + break; + case DBF_USHORT: + case DBF_ENUM: + if (cvttype==CT_DECIMAL) + cvtUshortToString(*(epicsUInt16 *) pfield, message); + else + ulongToHexString(*(epicsUInt16 *) pfield, message); + break; + case DBF_LONG: + if (cvttype==CT_DECIMAL) + cvtLongToString(*(epicsInt32 *) pfield, message); + else + ulongToHexString(*(epicsInt32 *) pfield, message); + break; + case DBF_ULONG: + if (cvttype==CT_DECIMAL) + cvtUlongToString(*(epicsUInt32 *) pfield, message); + else + ulongToHexString(*(epicsUInt32 *) pfield, message); + break; + case DBF_INT64: + if (cvttype==CT_DECIMAL) + cvtInt64ToString(*(epicsInt64 *) pfield, message); + else + cvtInt64ToHexString(*(epicsInt64 *) pfield, message); + break; + case DBF_UINT64: + if (cvttype==CT_DECIMAL) + cvtUInt64ToString(*(epicsUInt32 *) pfield, message); + else + cvtUInt64ToHexString(*(epicsUInt32 *) pfield, message); + break; + case DBF_FLOAT: + floatToString(*(epicsFloat32 *) pfield, message); + break; + case DBF_DOUBLE: + doubleToString(*(epicsFloat64 *) pfield, message); + break; + case DBF_MENU: + { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + epicsEnum16 choice_ind; + char *pchoice; + + if (!pfield) { + dbMsgCpy(pdbentry, "Field not found"); + return message; + } + choice_ind = *((epicsEnum16 *) pdbentry->pfield); + if (!pdbMenu || choice_ind < 0 || choice_ind >= pdbMenu->nChoice) + return NULL; + pchoice = pdbMenu->papChoiceValue[choice_ind]; + dbMsgCpy(pdbentry, pchoice); + } + break; + case DBF_DEVICE: + { + dbDeviceMenu *pdbDeviceMenu; + epicsEnum16 choice_ind; + char *pchoice; + + if (!pfield) { + dbMsgCpy(pdbentry, "Field not found"); + return message; + } + pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + if (!pdbDeviceMenu) + return NULL; + choice_ind = *((epicsEnum16 *) pdbentry->pfield); + if (choice_ind<0 || choice_ind>=pdbDeviceMenu->nChoice) + return NULL; + pchoice = pdbDeviceMenu->papChoice[choice_ind]; + dbMsgCpy(pdbentry, pchoice); + } + break; + default: + return NULL; + } + return message; +} + +long dbInitRecordLinks(dbRecordType *rtyp, struct dbCommon *prec) +{ + short i; + + for (i=0; ino_links; i++) { + dbLinkInfo link_info; + dbFldDes *pflddes = rtyp->papFldDes[rtyp->link_ind[i]]; + DBLINK *plink = (DBLINK *)(((char *)prec) + pflddes->offset); + devSup *devsup = NULL; + + plink->precord = prec; + + /* link fields are zero'd on allocation. + * so are effectively CONSTANT, but with constantStr==NULL. + * Here we initialize them to have the correct link type, + * with zero values and empty (but non-NULL) strings. + */ + + if(pflddes->isDevLink) { + devsup = (devSup *)ellNth(&rtyp->devList, prec->dtyp+1); + } + if(devsup) + plink->type = devsup->link_type; + else + plink->type = CONSTANT; + + switch (plink->type) { + /* constantStr is allowed to remain NULL if plink->text==NULL + * constantStr==NULL has special meaning in recGblInitConstantLink() + */ + case CONSTANT: plink->value.constantStr = NULL; break; + case PV_LINK: plink->value.pv_link.pvname = callocMustSucceed(1, 1, "init PV_LINK"); break; + case JSON_LINK: plink->value.json.string = pNullString; break; + case VME_IO: plink->value.vmeio.parm = pNullString; break; + case CAMAC_IO: plink->value.camacio.parm = pNullString; break; + case AB_IO: plink->value.abio.parm = pNullString; break; + case GPIB_IO: plink->value.gpibio.parm = pNullString; break; + case BITBUS_IO: plink->value.bitbusio.parm = pNullString; break; + case INST_IO: plink->value.instio.string = pNullString; break; + case BBGPIB_IO: plink->value.bbgpibio.parm = pNullString; break; + case VXI_IO: plink->value.vxiio.parm = pNullString; break; + } + + if(!plink->text) + continue; + + if(dbParseLink(plink->text, pflddes->field_type, &link_info, 0)!=0) { + /* This was already parsed once when ->text was set. + * Any syntax error messages were printed at that time. + */ + + } else if(dbCanSetLink(plink, &link_info, devsup)!=0) { + errlogPrintf("Error: %s.%s: can't initialize link type %d with \"%s\" (type %d)\n", + prec->name, pflddes->name, plink->type, plink->text, link_info.ltype); + + } else if(dbSetLink(plink, &link_info, devsup)) { + errlogPrintf("Error: %s.%s: failed to initialize link type %d with \"%s\" (type %d)\n", + prec->name, pflddes->name, plink->type, plink->text, link_info.ltype); + } + free(plink->text); + plink->text = NULL; + } + return 0; +} + +void dbFreeLinkInfo(dbLinkInfo *pinfo) +{ + if (pinfo->ltype == JSON_LINK) { + dbJLinkFree(pinfo->jlink); + pinfo->jlink = NULL; + } + free(pinfo->target); + pinfo->target = NULL; +} + +long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo, unsigned opts) +{ + char *pstr; + size_t len; + double value; + + memset(pinfo, 0, sizeof(*pinfo)); + + /* Strip leading white space */ + while (*str && isspace((int)*str)) str++; + + len = strlen(str); + /* Strip trailing white space */ + while (len > 0 && isspace((int)str[len-1])) len--; + + pstr = malloc(len + 1); + if (!pstr) + return S_dbLib_outMem; + pinfo->target = pstr; + + /* Check for Instrument I/O links */ + if (*str == '@') { + pinfo->ltype = INST_IO; + + /* Store everything after the '@' */ + memcpy(pstr, str+1, --len); + pstr[len] = '\0'; + return 0; + } + + /* Store the stripped string */ + memcpy(pstr, str, len); + pstr[len] = '\0'; + + /* Check for braces => JSON */ + if (*str == '{' && str[len-1] == '}') { + if (dbJLinkParse(str, len, ftype, &pinfo->jlink, opts)) + goto fail; + + pinfo->ltype = JSON_LINK; + return 0; + } + + /* Check for other HW link types */ + if (*pstr == '#') { + int ret; + char junk = 0; + char *parm = strchr(pstr, '@'); /* find start of parm string */ + + if (parm) { + *parm++ = '\0'; /* isolate the parm string for later */ + len -= (parm - pstr); + } + + /* generalized extraction of ID charactor and integer pairs (eg. "#C15 S14") */ + ret = sscanf(pinfo->target, "# %c%d %c%d %c%d %c%d %c%d %c", + &pinfo->hwid[0], &pinfo->hwnums[0], + &pinfo->hwid[1], &pinfo->hwnums[1], + &pinfo->hwid[2], &pinfo->hwnums[2], + &pinfo->hwid[3], &pinfo->hwnums[3], + &pinfo->hwid[4], &pinfo->hwnums[4], + &junk); + + /* ret<0 when pattern not matched + * ret==11 when extra non-space before '@'. + * ret is odd when a number is missing + */ + if (ret<0 || ret>10 || ret%2==1) goto fail; + + if (strcmp(pinfo->hwid, "CS")==0) pinfo->ltype = VME_IO; + else if (strcmp(pinfo->hwid, "BCN")==0) pinfo->ltype = CAMAC_IO; + else if (strcmp(pinfo->hwid, "BCNA")==0) pinfo->ltype = CAMAC_IO; + else if (strcmp(pinfo->hwid, "BCNF")==0) pinfo->ltype = CAMAC_IO; + else if (strcmp(pinfo->hwid, "BCNAF")==0) pinfo->ltype = CAMAC_IO; + else if (strcmp(pinfo->hwid, "RMDE")==0) pinfo->ltype = RF_IO; + else if (strcmp(pinfo->hwid, "LACS")==0) pinfo->ltype = AB_IO; + else if (strcmp(pinfo->hwid, "LA")==0) pinfo->ltype = GPIB_IO; + else if (strcmp(pinfo->hwid, "LNPS")==0) pinfo->ltype = BITBUS_IO; + else if (strcmp(pinfo->hwid, "LBG")==0) pinfo->ltype = BBGPIB_IO; + else if (strcmp(pinfo->hwid, "VCS")==0) pinfo->ltype = VXI_IO; + else if (strcmp(pinfo->hwid, "VS")==0) pinfo->ltype = VXI_IO; + else goto fail; + + if (parm && pinfo->ltype != RF_IO) { + /* move parm string to beginning of buffer */ + memmove(pinfo->target, parm, len + 1); + } else if (!parm && pinfo->ltype == RF_IO) { + /* RF_IO, the string isn't needed at all */ + free(pinfo->target); + pinfo->target = NULL; + } + else goto fail; + + return 0; + } + + /* Link is a constant if empty or it holds just a number */ + if (len == 0 || epicsParseDouble(pstr, &value, NULL) == 0) { + pinfo->ltype = CONSTANT; + return 0; + } + + /* Link may be an array constant */ + if (pstr[0] == '[' && pstr[len-1] == ']' && + (strchr(pstr, ',') || strchr(pstr, '"'))) { + pinfo->ltype = CONSTANT; + return 0; + } + + pinfo->ltype = PV_LINK; + pstr = strchr(pstr, ' '); /* find start of link modifiers (can't be seperated by tabs) */ + if (pstr) { + *pstr++ = '\0'; /* isolate modifiers. pinfo->target is PV name only for re-use in struct pv_link */ + + /* Space seperation of modifiers isn't required, and other chars are ignored. + * Order of comparisons resolves ambiguity by checking for + * longer matches first. + * eg. "QQCPPXMSITT" is pvlOptCPP|pvlOptMSI + */ + + if (strstr(pstr, "NPP")) pinfo->modifiers = 0; + else if (strstr(pstr, "CPP")) pinfo->modifiers = pvlOptCPP; + else if (strstr(pstr, "PP")) pinfo->modifiers = pvlOptPP; + else if (strstr(pstr, "CA")) pinfo->modifiers = pvlOptCA; + else if (strstr(pstr, "CP")) pinfo->modifiers = pvlOptCP; + + if (strstr(pstr, "NMS")) pinfo->modifiers |= pvlOptNMS; + else if (strstr(pstr, "MSI")) pinfo->modifiers |= pvlOptMSI; + else if (strstr(pstr, "MSS")) pinfo->modifiers |= pvlOptMSS; + else if (strstr(pstr, "MS")) pinfo->modifiers |= pvlOptMS; + + /* filter modifiers based on link type */ + switch(ftype) { + case DBF_INLINK: /* accept all */ break; + case DBF_OUTLINK: pinfo->modifiers &= ~pvlOptCPP; break; + case DBF_FWDLINK: pinfo->modifiers &= pvlOptCA; break; + } + } + + return 0; +fail: + dbFreeLinkInfo(pinfo); + return S_dbLib_badField; +} + +long dbCanSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *devsup) +{ + /* Release pinfo resources on failure */ + int expected_type = devsup ? devsup->link_type : CONSTANT; + + if (pinfo->ltype == expected_type) + return 0; + + switch (pinfo->ltype) { + case CONSTANT: + case JSON_LINK: + case PV_LINK: + if (expected_type == CONSTANT || + expected_type == JSON_LINK || + expected_type == PV_LINK) + return 0; + default: + dbFreeLinkInfo(pinfo); + return 1; + } +} + +static +void dbSetLinkConst(DBLINK *plink, dbLinkInfo *pinfo) +{ + plink->type = CONSTANT; + plink->value.constantStr = pinfo->target; + + pinfo->target = NULL; +} + +static +void dbSetLinkPV(DBLINK *plink, dbLinkInfo *pinfo) +{ + plink->type = PV_LINK; + plink->value.pv_link.pvname = pinfo->target; + plink->value.pv_link.pvlMask = pinfo->modifiers; + + pinfo->target = NULL; +} + +static +void dbSetLinkJSON(DBLINK *plink, dbLinkInfo *pinfo) +{ + plink->type = JSON_LINK; + plink->value.json.string = pinfo->target; + plink->value.json.jlink = pinfo->jlink; + + pinfo->target = NULL; + pinfo->jlink = NULL; +} + +static +void dbSetLinkHW(DBLINK *plink, dbLinkInfo *pinfo) +{ + switch(pinfo->ltype) { + case JSON_LINK: + plink->value.json.string = pinfo->target; + break; + case INST_IO: + plink->value.instio.string = pinfo->target; + break; + case VME_IO: + plink->value.vmeio.card = pinfo->hwnums[0]; + plink->value.vmeio.signal = pinfo->hwnums[1]; + plink->value.vmeio.parm = pinfo->target; + break; + case CAMAC_IO: + plink->value.camacio.b = pinfo->hwnums[0]; + plink->value.camacio.c = pinfo->hwnums[1]; + plink->value.camacio.n = pinfo->hwnums[2]; + plink->value.camacio.a = pinfo->hwnums[3]; + plink->value.camacio.f = pinfo->hwnums[4]; + plink->value.camacio.parm = pinfo->target; + break; + case RF_IO: + plink->value.rfio.cryo = pinfo->hwnums[0]; + plink->value.rfio.micro = pinfo->hwnums[1]; + plink->value.rfio.dataset = pinfo->hwnums[2]; + plink->value.rfio.element = pinfo->hwnums[3]; + break; + case AB_IO: + plink->value.abio.link = pinfo->hwnums[0]; + plink->value.abio.adapter = pinfo->hwnums[1]; + plink->value.abio.card = pinfo->hwnums[2]; + plink->value.abio.signal = pinfo->hwnums[3]; + plink->value.abio.parm = pinfo->target; + break; + case GPIB_IO: + plink->value.gpibio.link = pinfo->hwnums[0]; + plink->value.gpibio.addr = pinfo->hwnums[1]; + plink->value.gpibio.parm = pinfo->target; + break; + case BITBUS_IO: + plink->value.bitbusio.link = pinfo->hwnums[0]; + plink->value.bitbusio.node = pinfo->hwnums[1]; + plink->value.bitbusio.port = pinfo->hwnums[2]; + plink->value.bitbusio.signal = pinfo->hwnums[3]; + plink->value.bitbusio.parm = pinfo->target; + break; + case BBGPIB_IO: + plink->value.bbgpibio.link = pinfo->hwnums[0]; + plink->value.bbgpibio.bbaddr = pinfo->hwnums[1]; + plink->value.bbgpibio.gpibaddr = pinfo->hwnums[2]; + plink->value.bbgpibio.parm = pinfo->target; + break; + case VXI_IO: + if(strcmp(pinfo->hwid, "VCS")==0) { + plink->value.vxiio.flag=VXIDYNAMIC; + plink->value.vxiio.frame = pinfo->hwnums[0]; + plink->value.vxiio.slot = pinfo->hwnums[1]; + plink->value.vxiio.signal = pinfo->hwnums[2]; + } else if(strcmp(pinfo->hwid, "VS")==0) { + plink->value.vxiio.flag=VXISTATIC; + plink->value.vxiio.la = pinfo->hwnums[0]; + plink->value.vxiio.signal = pinfo->hwnums[1]; + } else { + cantProceed("dbSetLinkHW: logic error, unknown VXI_IO variant"); + } + plink->value.vxiio.parm = pinfo->target; + break; + + default: + cantProceed("dbSetLinkHW: logic error, unhandled link type"); + return; + } + + plink->type = pinfo->ltype; + + pinfo->target = NULL; /* now owned by link field */ +} + +long dbSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *devsup) +{ + int expected_type = devsup ? devsup->link_type : CONSTANT; + + if (expected_type == CONSTANT || + expected_type == JSON_LINK || + expected_type == PV_LINK) { + switch (pinfo->ltype) { + case CONSTANT: + dbFreeLinkContents(plink); + dbSetLinkConst(plink, pinfo); + break; + case PV_LINK: + dbFreeLinkContents(plink); + dbSetLinkPV(plink, pinfo); + break; + case JSON_LINK: + dbFreeLinkContents(plink); + dbSetLinkJSON(plink, pinfo); + break; + default: + errlogMessage("Warning: dbSetLink: forgot to test with dbCanSetLink() or logic error"); + goto fail; /* can't assign HW link */ + } + } + else if (expected_type == pinfo->ltype) { + dbFreeLinkContents(plink); + dbSetLinkHW(plink, pinfo); + } + else + goto fail; + + return 0; +fail: + dbFreeLinkInfo(pinfo); + return S_dbLib_badField; +} + +long dbPutString(DBENTRY *pdbentry,const char *pstring) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + long status=0; + int macroIsOk; + int stringHasMacro=FALSE; + + if(!pflddes) return(S_dbLib_flddesNotFound); + macroIsOk = dbIsMacroOk(pdbentry); + stringHasMacro = strstr(pstring,"$(") || strstr(pstring,"${"); + if(!macroIsOk && stringHasMacro) { + epicsPrintf("%s.%s Has unexpanded macro\n", + dbGetRecordName(pdbentry),dbGetFieldName(pdbentry)); + return(S_dbLib_badField); + } + switch (pflddes->field_type) { + case DBF_STRING: + if(!pfield) return(S_dbLib_fieldNotFound); + if(strlen(pstring) >= (size_t)pflddes->size) return S_dbLib_strLen; + strncpy((char *)pfield, pstring, pflddes->size-1); + ((char *)pfield)[pflddes->size-1] = 0; + + if((pflddes->special == SPC_CALC) && !stringHasMacro) { + char rpcl[RPCL_LEN]; + short err; + + if (postfix(pstring,rpcl,&err)) { + status = S_dbLib_badField; + errlogPrintf("%s in CALC expression '%s'\n", + calcErrorStr(err), pstring); + } + } + break; + + case DBF_CHAR: + case DBF_SHORT: + case DBF_LONG: + case DBF_INT64: + case DBF_UCHAR: + case DBF_USHORT: + case DBF_ULONG: + case DBF_UINT64: + case DBF_ENUM: + case DBF_FLOAT: + case DBF_DOUBLE: + case DBF_MENU: + case DBF_DEVICE: + status = dbPutStringNum(pdbentry,pstring); + break; + + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: { + dbLinkInfo link_info; + DBLINK *plink = (DBLINK *)pfield; + DBENTRY infoentry; + unsigned opts = 0; + + if(pdbentry->precnode && ellCount(&pdbentry->precnode->infoList)) { + dbCopyEntryContents(pdbentry, &infoentry); + + if(dbFindInfo(&infoentry, "base:lsetDebug")==0 && epicsStrCaseCmp(dbGetInfoString(&infoentry), "YES")==0) + opts |= LINK_DEBUG_LSET; + if(dbFindInfo(&infoentry, "base:jlinkDebug")==0 && epicsStrCaseCmp(dbGetInfoString(&infoentry), "YES")==0) + opts |= LINK_DEBUG_JPARSE; + + dbFinishEntry(&infoentry); + } + + status = dbParseLink(pstring, pflddes->field_type, &link_info, opts); + if (status) break; + + if (plink->type==CONSTANT && plink->value.constantStr==NULL) { + /* links not yet initialized by dbInitRecordLinks() */ + free(plink->text); + plink->text = epicsStrDup(pstring); + dbFreeLinkInfo(&link_info); + } else { + /* assignment after init (eg. autosave restore) */ + struct dbCommon *prec = pdbentry->precnode->precord; + devSup *devsup = (devSup *)ellNth(&pdbentry->precordType->devList, prec->dtyp+1); + + status = dbCanSetLink(plink, &link_info, devsup); + if (status == 0) + status = dbSetLink(plink, &link_info, devsup); + } + } + break; + + default: + return S_dbLib_badField; + } + + if (!status && strcmp(pflddes->name, "VAL") == 0) { + DBENTRY dbentry; + + dbCopyEntryContents(pdbentry, &dbentry); + if (!dbFindField(&dbentry, "UDF")) { + dbPutString(&dbentry, "0"); + } + dbFinishEntry(&dbentry); + } + return(status); +} + +char * dbVerify(DBENTRY *pdbentry, const char *pstring) +{ + dbFldDes *pflddes = pdbentry->pflddes; + char *message = getpMessage(pdbentry); + long status; + union { + epicsInt8 i8; + epicsUInt8 u8; + epicsInt16 i16; + epicsUInt16 u16; + epicsInt32 i32; + epicsUInt32 u32; + epicsInt64 i64; + epicsUInt64 u64; + epicsFloat32 f32; + epicsFloat64 f64; + } val; + + if (!pflddes) { + strcpy(message, "fldDes not found"); + return message; + } + + if (strstr(pstring,"$(") || strstr(pstring,"${")) + return NULL; + + switch (pflddes->field_type) { + case DBF_STRING: + { + size_t length = strlen(pstring); + + if (length >= pflddes->size) { + sprintf(message, "String too long, max %d characters", + pflddes->size - 1); + return message; + } + + if (pflddes->special == SPC_CALC) { + char rpcl[RPCL_LEN]; + short err; + + status = postfix(pstring, rpcl, &err); + if (status) { + sprintf(message,"%s in CALC expression '%s'", + calcErrorStr(err), pstring); + return message; + } + } + + return NULL; + } + + case DBF_CHAR: + status = epicsParseInt8(pstring, &val.i8, 0, NULL); + break; + + case DBF_UCHAR: + status = epicsParseUInt8(pstring, &val.u8, 0, NULL); + break; + + case DBF_SHORT: + status = epicsParseInt16(pstring, &val.i16, 0, NULL); + break; + + case DBF_ENUM: + case DBF_USHORT: + status = epicsParseUInt16(pstring, &val.u16, 0, NULL); + break; + + case DBF_LONG: + status = epicsParseInt32(pstring, &val.i32, 0, NULL); + break; + + case DBF_ULONG: + status = epicsParseUInt32(pstring, &val.u32, 0, NULL); + break; + + case DBF_INT64: + status = epicsParseInt64(pstring, &val.i64, 0, NULL); + break; + + case DBF_UINT64: + status = epicsParseUInt64(pstring, &val.u64, 0, NULL); + break; + + case DBF_FLOAT: + status = epicsParseFloat32(pstring, &val.f32, NULL); + break; + + case DBF_DOUBLE: + status = epicsParseFloat64(pstring, &val.f64, NULL); + break; + + case DBF_MENU: + { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + int i; + + if (!pdbMenu) + return NULL; + + for (i = 0; i < pdbMenu->nChoice; i++) { + const char *pchoice = pdbMenu->papChoiceValue[i]; + + if (!pchoice) + continue; + + if (strcmp(pchoice, pstring) == 0) { + return NULL; + } + } + } + strcpy(message, "Not a valid menu choice"); + return message; + + case DBF_DEVICE: + { + dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + int i; + + if (!pdbDeviceMenu || pdbDeviceMenu->nChoice == 0) + return NULL; + + for (i = 0; i < pdbDeviceMenu->nChoice; i++) { + const char *pchoice = pdbDeviceMenu->papChoice[i]; + + if (!pchoice) + continue; + + if (strcmp(pchoice, pstring) == 0) { + return NULL; + } + } + } + strcpy(message, "Not a valid device type"); + return message; + + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: + return NULL; + + default: + strcpy(message, "Not a valid field type"); + return message; + } + + switch (status) { + case 0: + message = NULL; + break; + + case S_stdlib_noConversion: + strcpy(message, "Not a valid integer"); + break; + + case S_stdlib_badBase: + strcpy(message, "Internal error (badBase)"); + break; + + case S_stdlib_overflow: + strcpy(message, "Number too large for field type"); + break; + + case S_stdlib_underflow: + strcpy(message, "Number too small for field type"); + break; + + case S_stdlib_extraneous: + strcpy(message, "Extraneous characters after number"); + break; + + default: + strcpy(message, "Unknown numeric conversion error"); + } + return message; +} + +long dbFirstInfo(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + + pdbentry->pinfonode = NULL; + if (!precnode) return (S_dbLib_recNotFound); + + pdbentry->pinfonode = (dbInfoNode *)ellFirst(&precnode->infoList); + return (pdbentry->pinfonode ? 0 : S_dbLib_infoNotFound); +} + +long dbNextInfo(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + dbInfoNode *pinfo; + + if (!precnode) return (S_dbLib_recNotFound); + pinfo = pdbentry->pinfonode; + if (!pinfo) return (S_dbLib_infoNotFound); + + pinfo = (dbInfoNode *)ellNext(&pinfo->node); + pdbentry->pinfonode = pinfo; + return (pinfo ? 0 : S_dbLib_infoNotFound); +} + +long dbFindInfo(DBENTRY *pdbentry,const char *name) +{ + dbRecordNode *precnode = pdbentry->precnode; + dbInfoNode *pinfo; + + pdbentry->pinfonode = NULL; + if (!precnode) return(S_dbLib_recNotFound); + + pinfo = (dbInfoNode *)ellFirst(&precnode->infoList); + while (pinfo) { + if (!strcmp(pinfo->name, name)) { + pdbentry->pinfonode = pinfo; + return (0); + } + pinfo = (dbInfoNode *)ellNext(&pinfo->node); + } + return (S_dbLib_infoNotFound); +} + +long dbDeleteInfo(DBENTRY *pdbentry) +{ + dbRecordNode *precnode = pdbentry->precnode; + dbInfoNode *pinfo = pdbentry->pinfonode; + + if (!precnode) return (S_dbLib_recNotFound); + if (!pinfo) return (S_dbLib_infoNotFound); + ellDelete(&precnode->infoList,&pinfo->node); + free(pinfo->name); + free(pinfo->string); + free(pinfo); + pdbentry->pinfonode = NULL; + return (0); +} + +const char * dbGetInfoName(DBENTRY *pdbentry) +{ + dbInfoNode *pinfo = pdbentry->pinfonode; + if (!pinfo) return (NULL); + return (pinfo->name); +} + +const char * dbGetInfoString(DBENTRY *pdbentry) +{ + dbInfoNode *pinfo = pdbentry->pinfonode; + if (!pinfo) return (NULL); + return (pinfo->string); +} + +long dbPutInfoString(DBENTRY *pdbentry,const char *string) +{ + dbInfoNode *pinfo = pdbentry->pinfonode; + char *newstring; + if (!pinfo) return (S_dbLib_infoNotFound); + newstring = realloc(pinfo->string,1+strlen(string)); + if (!newstring) return (S_dbLib_outMem); + strcpy(newstring, string); + pinfo->string = newstring; + return (0); +} + +long dbPutInfoPointer(DBENTRY *pdbentry, void *pointer) +{ + dbInfoNode *pinfo = pdbentry->pinfonode; + if (!pinfo) return (S_dbLib_infoNotFound); + pinfo->pointer = pointer; + return (0); +} + +void * dbGetInfoPointer(DBENTRY *pdbentry) +{ + dbInfoNode *pinfo = pdbentry->pinfonode; + if (!pinfo) return (NULL); + return (pinfo->pointer); +} + +const char * dbGetInfo(DBENTRY *pdbentry,const char *name) +{ + if (dbFindInfo(pdbentry, name)) return NULL; + return dbGetInfoString(pdbentry); +} + +long dbPutInfo(DBENTRY *pdbentry,const char *name,const char *string) +{ + dbInfoNode *pinfo; + dbRecordNode *precnode = pdbentry->precnode; + if (!precnode) return (S_dbLib_recNotFound); + + dbFindInfo(pdbentry, name); + pinfo = pdbentry->pinfonode; + if (pinfo) return (dbPutInfoString(pdbentry, string)); + + /*Create new info node*/ + pinfo = calloc(1,sizeof(dbInfoNode)); + if (!pinfo) return (S_dbLib_outMem); + pinfo->name = calloc(1,1+strlen(name)); + if (!pinfo->name) { + free(pinfo); + return (S_dbLib_outMem); + } + strcpy(pinfo->name, name); + pinfo->string = calloc(1,1+strlen(string)); + if (!pinfo->string) { + free(pinfo->name); + free(pinfo); + return (S_dbLib_outMem); + } + strcpy(pinfo->string, string); + ellAdd(&precnode->infoList,&pinfo->node); + pdbentry->pinfonode = pinfo; + return (0); +} + +brkTable * dbFindBrkTable(dbBase *pdbbase,const char *name) +{ + GPHENTRY *pgph; + + pgph = gphFind(pdbbase->pgpHash,name,(void *)&pdbbase->bptList); + if(!pgph) return(NULL); + return((brkTable *)pgph->userPvt); +} + +const char * dbGetFieldTypeString(int dbfType) +{ + int i; + + for (i=0; i < DBF_NTYPES; i++) { + if (pamapdbfType[i].value == dbfType) { + return pamapdbfType[i].strvalue; + } + } + return "BAD_DBF_TYPE"; +} + +int dbFindFieldType(const char *type) +{ + int i; + + for (i = 0; i < DBF_NTYPES; i++) { + if (strcmp(type, pamapdbfType[i].strvalue) == 0) { + return pamapdbfType[i].value; + } + } + return -1; +} + +dbMenu * dbFindMenu(dbBase *pdbbase,const char *name) +{ + GPHENTRY *pgph; + + pgph = gphFind(pdbbase->pgpHash,name,(void *)&pdbbase->menuList); + if(!pgph) return(NULL); + return((dbMenu *)pgph->userPvt); +} + +char ** dbGetMenuChoices(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(NULL); + switch (pflddes->field_type) { + case DBF_MENU: { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + + if(!pdbMenu) return(NULL); + return(pdbMenu->papChoiceValue); + } + case DBF_DEVICE: { + dbDeviceMenu *pdbDeviceMenu; + + pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + if(!pdbDeviceMenu) return(NULL); + return(pdbDeviceMenu->papChoice); + } + default: + return(NULL); + } +} + +int dbGetNMenuChoices(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(-1); + switch (pflddes->field_type) { + case DBF_MENU: { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + + if(!pdbMenu) return(0); + return(pdbMenu->nChoice); + } + case DBF_DEVICE: { + dbDeviceMenu *pdbDeviceMenu; + + pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + if(!pdbDeviceMenu) return(0); + return(pdbDeviceMenu->nChoice); + } + default: + break; + } + return (-1); +} + +char * dbGetMenuStringFromIndex(DBENTRY *pdbentry, int index) +{ + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pflddes) return(NULL); + switch (pflddes->field_type) { + case DBF_MENU: { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + + if(!pdbMenu) return(NULL); + if(index<0 || index>=pdbMenu->nChoice) return(NULL); + return(pdbMenu->papChoiceValue[index]); + } + case DBF_DEVICE: { + dbDeviceMenu *pdbDeviceMenu; + + pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + if(!pdbDeviceMenu) return(NULL); + if(index<0 || index>=pdbDeviceMenu->nChoice) return(NULL); + return(pdbDeviceMenu->papChoice[index]); + } + default: + break; + } + return (NULL); +} + +int dbGetMenuIndexFromString(DBENTRY *pdbentry, const char *choice) +{ + dbFldDes *pflddes = pdbentry->pflddes; + int ind; + int nChoice = 0; + char **papChoice = NULL; + + if(!pflddes) return(-1); + switch (pflddes->field_type) { + case DBF_MENU: { + dbMenu *pdbMenu = (dbMenu *)pflddes->ftPvt; + + if(!pdbMenu) return(-1); + papChoice = pdbMenu->papChoiceValue; + nChoice = pdbMenu->nChoice; + break; + } + case DBF_DEVICE: { + dbDeviceMenu *pdbDeviceMenu; + + pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + if(!pdbDeviceMenu) return(-1); + papChoice = pdbDeviceMenu->papChoice; + nChoice = pdbDeviceMenu->nChoice; + break; + } + default: + return(-1); + } + if(nChoice<=0 || !papChoice) return(-1); + for(ind=0; indpgpHash,name,&pdbbase->drvList); + if (!pgph) return NULL; + return (drvSup *) pgph->userPvt; +} + +char * dbGetRelatedField(DBENTRY *psave) +{ + DBENTRY dbEntry; + DBENTRY *pdbentry= &dbEntry; + dbFldDes *pflddes; + char *rtnval = NULL; + long status; + + pflddes = psave->pflddes; + if(pflddes->field_type !=DBF_DEVICE) return(NULL); + dbCopyEntryContents(psave,pdbentry); + pflddes = pdbentry->pflddes; + status = dbFindField(pdbentry,"INP"); + if(status) status = dbFindField(pdbentry,"OUT"); + if(!status) rtnval = pdbentry->pflddes->name; + dbFinishEntry(pdbentry); + return(rtnval); +} + +linkSup* dbFindLinkSup(dbBase *pdbbase, const char *name) { + GPHENTRY *pgph = gphFind(pdbbase->pgpHash,name,&pdbbase->linkList); + if (!pgph) return NULL; + return (linkSup *) pgph->userPvt; +} + +int dbGetNLinks(DBENTRY *pdbentry) +{ + dbRecordType *precordType = pdbentry->precordType; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + return((int)precordType->no_links); +} + +long dbGetLinkField(DBENTRY *pdbentry,int index) +{ + dbRecordType *precordType = pdbentry->precordType; + dbFldDes *pflddes; + + if(!precordType) return(S_dbLib_recordTypeNotFound); + if(index<0 || index>=precordType->no_links) return(S_dbLib_badLink); + pdbentry->indfield = precordType->link_ind[index]; + pdbentry->pflddes = pflddes = precordType->papFldDes[pdbentry->indfield]; + dbGetFieldAddress(pdbentry); + return(0); +} + +int dbGetLinkType(DBENTRY *pdbentry) +{ + dbFldDes *pflddes; + DBLINK *plink; + int field_type; + + dbGetFieldAddress(pdbentry); + pflddes = pdbentry->pflddes; + if(!pflddes) return(-1); + plink = (DBLINK *)pdbentry->pfield; + if(!plink) return(-1); + field_type = pflddes->field_type; + switch (field_type) { + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: + switch(plink->type) { + case CONSTANT: + return(DCT_LINK_CONSTANT); + case PV_LINK: + case PN_LINK: + case DB_LINK: + case CA_LINK: + return(DCT_LINK_PV); + default: + return(DCT_LINK_FORM); + } + } + return(-1); +} + +void dbDumpPath(DBBASE *pdbbase) +{ + ELLLIST *ppathList; + dbPathNode *pdbPathNode; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + ppathList = (ELLLIST *)pdbbase->pathPvt; + if(!ppathList || !(pdbPathNode = (dbPathNode *)ellFirst(ppathList))) { + printf("no path defined\n"); + return; + } + while(pdbPathNode) { + printf("%s",pdbPathNode->directory); + pdbPathNode = (dbPathNode *)ellNext(&pdbPathNode->node); + if(pdbPathNode) printf("%s", OSI_PATH_LIST_SEPARATOR); + } + printf("\n"); + return; +} + +void dbDumpRecord( + dbBase *pdbbase,const char *precordTypename,int level) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteRecordFP(pdbbase,stdout,precordTypename,level); +} + +void dbDumpMenu(dbBase *pdbbase,const char *menuName) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteMenuFP(pdbbase,stdout,menuName); +} + +void dbDumpRecordType(DBBASE *pdbbase,const char *recordTypeName) +{ + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + int gotMatch; + int i; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + if(recordTypeName) { + gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0) + ? TRUE : FALSE; + }else { + gotMatch=TRUE; + } + if(!gotMatch) continue; + printf("name(%s) no_fields(%hd) no_prompt(%hd) no_links(%hd)\n", + pdbRecordType->name,pdbRecordType->no_fields, + pdbRecordType->no_prompt,pdbRecordType->no_links); + printf("index name\tsortind sortname\n"); + for(i=0; ino_fields; i++) { + pdbFldDes = pdbRecordType->papFldDes[i]; + printf("%5d %s\t%7d %s\n", + i,pdbFldDes->name, + pdbRecordType->sortFldInd[i],pdbRecordType->papsortFldName[i]); + } + printf("link_ind "); + for(i=0; ino_links; i++) + printf(" %hd",pdbRecordType->link_ind[i]); + printf("\n"); + printf("indvalFlddes %d name %s\n",pdbRecordType->indvalFlddes, + pdbRecordType->pvalFldDes->name); + printf("rset * %p rec_size %d\n", + (void *)pdbRecordType->prset,pdbRecordType->rec_size); + if(recordTypeName) break; + } +} + +void dbDumpField( + DBBASE *pdbbase,const char *recordTypeName,const char *fname) +{ + dbRecordType *pdbRecordType; + dbFldDes *pdbFldDes; + int gotMatch; + int i; + dbRecordAttribute *pAttribute; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + if(recordTypeName) { + gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0) + ? TRUE : FALSE; + }else { + gotMatch=TRUE; + } + if(!gotMatch) continue; + printf("recordtype(%s) \n",pdbRecordType->name); + for(i=0; ino_fields; i++) { + int j; + + pdbFldDes = pdbRecordType->papFldDes[i]; + if(fname && strcmp(fname,pdbFldDes->name)!=0) continue; + printf(" %s\n", pdbFldDes->name); + printf("\t prompt: %s\n", + (pdbFldDes->prompt ? pdbFldDes->prompt : "")); + printf("\t extra: %s\n", + (pdbFldDes->extra ? pdbFldDes->extra: "")); + printf("\t indRecordType: %hd\n",pdbFldDes->indRecordType); + printf("\t special: %hd ",pdbFldDes->special); + if(pdbFldDes->special) { + for(j=0; jspecial) { + printf("%s",pamapspcType[j].strvalue); + break; + } + } + } + printf("\n"); + printf("\t field_type: %s\n", + dbGetFieldTypeString(pdbFldDes->field_type)); + printf("\tprocess_passive: %u\n",pdbFldDes->process_passive); + printf("\t property: %u\n",pdbFldDes->prop); + printf("\t base: %d\n",pdbFldDes->base); + if(!pdbFldDes->promptgroup) { + printf("\t promptgroup: %d\n",pdbFldDes->promptgroup); + } else { + printf("\t promptgroup: %s\n", + dbGetPromptGroupNameFromKey(pdbbase, pdbFldDes->promptgroup)); + } + printf("\t interest: %hd\n", pdbFldDes->interest); + printf("\t as_level: %d\n",pdbFldDes->as_level); + printf("\t initial: %s\n", + (pdbFldDes->initial ? pdbFldDes->initial : "")); + if(pdbFldDes->field_type==DBF_MENU) { + if(pdbFldDes->ftPvt) + printf("\t\t menu: %s\n", + ((dbMenu *)pdbFldDes->ftPvt)->name); + else + printf("\t\t menu: NOT FOUND\n"); + } + if(pdbFldDes->field_type==DBF_DEVICE) { + printf("\t ftPvt: %p\n",pdbFldDes->ftPvt); + } + printf("\t size: %hd\n",pdbFldDes->size); + printf("\t offset: %hd\n",pdbFldDes->offset); + } + pAttribute = + (dbRecordAttribute *)ellFirst(&pdbRecordType->attributeList); + while(pAttribute) { + printf("Attribute: name %s value %s\n", + pAttribute->name,pAttribute->value); + pAttribute = (dbRecordAttribute *)ellNext(&pAttribute->node); + } + if(recordTypeName) break; + } +} + +void dbDumpDevice(DBBASE *pdbbase,const char *recordTypeName) +{ + dbRecordType *pdbRecordType; + devSup *pdevSup; + int gotMatch; + + if (recordTypeName) { + if (*recordTypeName == 0 || *recordTypeName == '*') + recordTypeName = 0; + } + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + if(recordTypeName) { + gotMatch = (strcmp(recordTypeName,pdbRecordType->name)==0) + ? TRUE : FALSE; + }else { + gotMatch=TRUE; + } + if(!gotMatch) continue; + printf("recordtype(%s)\n",pdbRecordType->name); + for(pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) { + printf(" device name: %s\n",pdevSup->name); + printf("\tchoice: %s\n",pdevSup->choice); + printf("\tlink_type: %d\n",pdevSup->link_type); + printf("\tpdset: %p\n",(void *)pdevSup->pdset); + if (pdevSup->pdset) { + static const char *names[] = { + " - report()", + " - init()", + " - init_record()", + " - get_ioint_info()" + }; + int i, n = pdevSup->pdset->number; + DEVSUPFUN *pfunc = &pdevSup->pdset->report; + + printf("\t number: %d\n", n); + for (i = 0; i < n; ++i, ++pfunc) { + const char *name = (i < NELEMENTS(names)) ? names[i] : ""; + + printf("\t func %d: %p%s\n", i, (void *)*pfunc, name); + } + } + printf("\tpdsxt: %p\n",(void *)pdevSup->pdsxt); + if (pdevSup->pdsxt) { + printf("\t add_record: %p\n", + (void *)pdevSup->pdsxt->add_record); + printf("\t del_record: %p\n", + (void *)pdevSup->pdsxt->del_record); + } + } + if(recordTypeName) break; + } +} + +void dbDumpDriver(DBBASE *pdbbase) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteDriverFP(pdbbase,stdout); +} + +void dbDumpLink(DBBASE *pdbbase) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteLinkFP(pdbbase,stdout); +} + +void dbDumpRegistrar(DBBASE *pdbbase) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteRegistrarFP(pdbbase,stdout); +} + +void dbDumpFunction(DBBASE *pdbbase) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteFunctionFP(pdbbase,stdout); +} + +void dbDumpVariable(DBBASE *pdbbase) +{ + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbWriteVariableFP(pdbbase,stdout); +} + +void dbDumpBreaktable(DBBASE *pdbbase,const char *name) +{ + brkTable *pbrkTable; + brkInt *pbrkInt; + int ind; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + for(pbrkTable = (brkTable *)ellFirst(&pdbbase->bptList); + pbrkTable; pbrkTable = (brkTable *)ellNext(&pbrkTable->node)) { + if (name && strcmp(name,pbrkTable->name)!=0) continue; + printf("breaktable(%s) {\n",pbrkTable->name); + pbrkInt = pbrkTable->paBrkInt; + for(ind=0; ind < pbrkTable->number; ind++) { + printf("\traw=%f slope=%e eng=%f\n", + pbrkInt->raw, pbrkInt->slope, pbrkInt->eng); + pbrkInt++; + } + printf("}\n"); + } + return; +} + +static char *bus[VXI_IO+1] = {"","","VME","CAMAC","AB", + "GPIB","BITBUS","","","","","","INST","BBGPIB","VXI"}; +void dbReportDeviceConfig(dbBase *pdbbase,FILE *report) +{ + DBENTRY dbentry; + DBENTRY *pdbentry=&dbentry; + long status; + char linkValue[messagesize]; + char dtypValue[50]; + char cvtValue[40]; + int ilink,nlinks; + struct link *plink; + int linkType; + FILE *stream = (report==0) ? stdout : report; + + if(!pdbbase) { + fprintf(stderr,"pdbbase not specified\n"); + return; + } + dbInitEntry(pdbbase,pdbentry); + status = dbFirstRecordType(pdbentry); + while(!status) { + status = dbFirstRecord(pdbentry); + while(!status) { + nlinks = dbGetNLinks(pdbentry); + for(ilink=0; ilinkpfield; + linkType = plink->type; + if(bus[linkType][0]==0) continue; + strncpy(linkValue, dbGetString(pdbentry), NELEMENTS(linkValue)-1); + linkValue[NELEMENTS(linkValue)-1] = '\0'; + status = dbFindField(pdbentry,"DTYP"); + if(status) break; + strcpy(dtypValue,dbGetString(pdbentry)); + status = dbFindField(pdbentry,"LINR"); + if(status) { + cvtValue[0] = 0; + } else { + if(strcmp(dbGetString(pdbentry),"LINEAR")!=0) { + cvtValue[0] = 0; + } else { + strcpy(cvtValue,"cvt("); + status = dbFindField(pdbentry,"EGUL"); + if(!status) strcat(cvtValue,dbGetString(pdbentry)); + status = dbFindField(pdbentry,"EGUF"); + if(!status) { + strcat(cvtValue,","); + strcat(cvtValue,dbGetString(pdbentry)); + } + strcat(cvtValue,")"); + } + } + fprintf(stream,"%-8s %-20s %-20s %-20s %-s\n", + bus[linkType],linkValue,dtypValue, + dbGetRecordName(pdbentry),cvtValue); + break; + } + status = dbNextRecord(pdbentry); + } + status = dbNextRecordType(pdbentry); + } + dbFinishEntry(pdbentry); + finishOutstream(stream); + return; +} diff --git a/modules/database/src/ioc/dbStatic/dbStaticLib.h b/modules/database/src/ioc/dbStatic/dbStaticLib.h new file mode 100644 index 000000000..1ee999104 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticLib.h @@ -0,0 +1,285 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Marty Kraimer + * Date: 06-08-93 + */ + +#ifndef INCdbStaticLibh +#define INCdbStaticLibh 1 + +#include +#include + +#include "shareLib.h" +#include "dbFldTypes.h" +#include "dbBase.h" +#include "link.h" +#include "errMdef.h" +#include "cantProceed.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*Field types as seen by static database access clients*/ +#define DCT_STRING 0 +#define DCT_INTEGER 1 +#define DCT_REAL 2 +#define DCT_MENU 3 +#define DCT_MENUFORM 4 +#define DCT_INLINK 5 +#define DCT_OUTLINK 6 +#define DCT_FWDLINK 7 +#define DCT_NOACCESS 8 + +/*Link types as seen by static database access clients*/ +#define DCT_LINK_CONSTANT 0 +#define DCT_LINK_FORM 1 +#define DCT_LINK_PV 2 + +typedef dbBase DBBASE; + +typedef struct{ + DBBASE *pdbbase; + dbRecordType *precordType; + dbFldDes *pflddes; + dbRecordNode *precnode; + dbInfoNode *pinfonode; + void *pfield; + char *message; + short indfield; +} DBENTRY; + +struct dbAddr; +struct dbCommon; + +/* Static database access routines*/ +epicsShareFunc DBBASE * dbAllocBase(void); +epicsShareFunc void dbFreeBase(DBBASE *pdbbase); +epicsShareFunc DBENTRY * dbAllocEntry(DBBASE *pdbbase); +epicsShareFunc void dbFreeEntry(DBENTRY *pdbentry); +epicsShareFunc void dbInitEntry(DBBASE *pdbbase, + DBENTRY *pdbentry); + +/** Initialize DBENTRY from a valid dbAddr*. + * Constant time equivalent of dbInitEntry() then dbFindRecord(), + * and finally dbFollowAlias() + */ +epicsShareFunc void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry); + +/** Initialize DBENTRY from a valid record (dbCommon*). + * Constant time equivalent of dbInitEntry() then dbFindRecord(), + * and finally dbFollowAlias() when no field is specified. + */ +epicsShareFunc void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY *pdbentry); + +epicsShareFunc void dbFinishEntry(DBENTRY *pdbentry); +epicsShareFunc DBENTRY * dbCopyEntry(DBENTRY *pdbentry); +epicsShareFunc void dbCopyEntryContents(DBENTRY *pfrom, + DBENTRY *pto); + +epicsShareExtern int dbBptNotMonotonic; + +epicsShareFunc long dbReadDatabase(DBBASE **ppdbbase, + const char *filename, const char *path, const char *substitutions); +epicsShareFunc long dbReadDatabaseFP(DBBASE **ppdbbase, + FILE *fp, const char *path, const char *substitutions); +epicsShareFunc long dbPath(DBBASE *pdbbase, const char *path); +epicsShareFunc long dbAddPath(DBBASE *pdbbase, const char *path); +epicsShareFunc char * dbGetPromptGroupNameFromKey(DBBASE *pdbbase, + const short key); +epicsShareFunc short dbGetPromptGroupKeyFromName(DBBASE *pdbbase, + const char *name); +epicsShareFunc long dbWriteRecord(DBBASE *ppdbbase, + const char *filename, const char *precordTypename, int level); +epicsShareFunc long dbWriteRecordFP(DBBASE *ppdbbase, + FILE *fp, const char *precordTypename, int level); +epicsShareFunc long dbWriteMenu(DBBASE *pdbbase, + const char *filename, const char *menuName); +epicsShareFunc long dbWriteMenuFP(DBBASE *pdbbase, + FILE *fp, const char *menuName); +epicsShareFunc long dbWriteRecordType(DBBASE *pdbbase, + const char *filename, const char *recordTypeName); +epicsShareFunc long dbWriteRecordTypeFP(DBBASE *pdbbase, + FILE *fp, const char *recordTypeName); +epicsShareFunc long dbWriteDevice(DBBASE *pdbbase, + const char *filename); +epicsShareFunc long dbWriteDeviceFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteDriver(DBBASE *pdbbase, + const char *filename); +epicsShareFunc long dbWriteDriverFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteLinkFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteRegistrarFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteFunctionFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteVariableFP(DBBASE *pdbbase, FILE *fp); +epicsShareFunc long dbWriteBreaktable(DBBASE *pdbbase, + const char *filename); +epicsShareFunc long dbWriteBreaktableFP(DBBASE *pdbbase, + FILE *fp); + +epicsShareFunc long dbFindRecordType(DBENTRY *pdbentry, + const char *recordTypename); +epicsShareFunc long dbFirstRecordType(DBENTRY *pdbentry); +epicsShareFunc long dbNextRecordType(DBENTRY *pdbentry); +epicsShareFunc char * dbGetRecordTypeName(DBENTRY *pdbentry); +epicsShareFunc int dbGetNRecordTypes(DBENTRY *pdbentry); +epicsShareFunc long dbPutRecordAttribute(DBENTRY *pdbentry, + const char *name, const char*value); +epicsShareFunc long dbGetRecordAttribute(DBENTRY *pdbentry, + const char *name); +epicsShareFunc long dbGetAttributePart(DBENTRY *pdbentry, + const char **ppname); + +epicsShareFunc long dbFirstField(DBENTRY *pdbentry, int dctonly); +epicsShareFunc long dbNextField(DBENTRY *pdbentry, int dctonly); +epicsShareFunc int dbGetFieldType(DBENTRY *pdbentry); +epicsShareFunc int dbGetNFields(DBENTRY *pdbentry, int dctonly); +epicsShareFunc char * dbGetFieldName(DBENTRY *pdbentry); +epicsShareFunc char * dbGetDefault(DBENTRY *pdbentry); +epicsShareFunc char * dbGetPrompt(DBENTRY *pdbentry); +epicsShareFunc int dbGetPromptGroup(DBENTRY *pdbentry); + +epicsShareFunc long dbCreateRecord(DBENTRY *pdbentry, + const char *pname); +epicsShareFunc long dbDeleteRecord(DBENTRY *pdbentry); +epicsShareFunc long dbFreeRecords(DBBASE *pdbbase); +epicsShareFunc long dbFindRecordPart(DBENTRY *pdbentry, + const char **ppname); +epicsShareFunc long dbFindRecord(DBENTRY *pdbentry, + const char *pname); + +epicsShareFunc long dbFirstRecord(DBENTRY *pdbentry); +epicsShareFunc long dbNextRecord(DBENTRY *pdbentry); +epicsShareFunc int dbGetNRecords(DBENTRY *pdbentry); +epicsShareFunc int dbGetNAliases(DBENTRY *pdbentry); +epicsShareFunc char * dbGetRecordName(DBENTRY *pdbentry); +epicsShareFunc long dbCopyRecord(DBENTRY *pdbentry, + const char *newRecordName, int overWriteOK); + +epicsShareFunc long dbVisibleRecord(DBENTRY *pdbentry); +epicsShareFunc long dbInvisibleRecord(DBENTRY *pdbentry); +epicsShareFunc int dbIsVisibleRecord(DBENTRY *pdbentry); + +epicsShareFunc long dbCreateAlias(DBENTRY *pdbentry, + const char *paliasName); +epicsShareFunc int dbIsAlias(DBENTRY *pdbentry); +/* Follow alias to actual record */ +epicsShareFunc int dbFollowAlias(DBENTRY *pdbentry); +epicsShareFunc long dbDeleteAliases(DBENTRY *pdbentry); + +epicsShareFunc long dbFindFieldPart(DBENTRY *pdbentry, + const char **ppname); +epicsShareFunc long dbFindField(DBENTRY *pdbentry, + const char *pfieldName); +epicsShareFunc int dbFoundField(DBENTRY *pdbentry); +epicsShareFunc char * dbGetString(DBENTRY *pdbentry); +epicsShareFunc long dbPutString(DBENTRY *pdbentry, + const char *pstring); +epicsShareFunc char * dbVerify(DBENTRY *pdbentry, + const char *pstring); +epicsShareFunc int dbIsDefaultValue(DBENTRY *pdbentry); + +epicsShareFunc long dbFirstInfo(DBENTRY *pdbentry); +epicsShareFunc long dbNextInfo(DBENTRY *pdbentry); +epicsShareFunc long dbFindInfo(DBENTRY *pdbentry, + const char *name); +epicsShareFunc long dbDeleteInfo(DBENTRY *pdbentry); +epicsShareFunc const char * dbGetInfoName(DBENTRY *pdbentry); +epicsShareFunc const char * dbGetInfoString(DBENTRY *pdbentry); +epicsShareFunc long dbPutInfoString(DBENTRY *pdbentry, + const char *string); +epicsShareFunc long dbPutInfoPointer(DBENTRY *pdbentry, + void *pointer); +epicsShareFunc void * dbGetInfoPointer(DBENTRY *pdbentry); +epicsShareFunc const char * dbGetInfo(DBENTRY *pdbentry, + const char *name); +epicsShareFunc long dbPutInfo(DBENTRY *pdbentry, + const char *name, const char *string); + +epicsShareFunc brkTable * dbFindBrkTable(DBBASE *pdbbase, + const char *name); + +epicsShareFunc const char * dbGetFieldTypeString(int dbfType); +epicsShareFunc int dbFindFieldType(const char *type); + +epicsShareFunc dbMenu * dbFindMenu(DBBASE *pdbbase, + const char *name); +epicsShareFunc char ** dbGetMenuChoices(DBENTRY *pdbentry); +epicsShareFunc int dbGetMenuIndex(DBENTRY *pdbentry); +epicsShareFunc long dbPutMenuIndex(DBENTRY *pdbentry, int index); +epicsShareFunc int dbGetNMenuChoices(DBENTRY *pdbentry); +epicsShareFunc char * dbGetMenuStringFromIndex(DBENTRY *pdbentry, + int index); +epicsShareFunc int dbGetMenuIndexFromString(DBENTRY *pdbentry, + const char *choice); + +epicsShareFunc drvSup * dbFindDriver(dbBase *pdbbase, + const char *name); +epicsShareFunc char * dbGetRelatedField(DBENTRY *pdbentry); + +epicsShareFunc linkSup * dbFindLinkSup(dbBase *pdbbase, + const char *name); + +epicsShareFunc int dbGetNLinks(DBENTRY *pdbentry); +epicsShareFunc long dbGetLinkField(DBENTRY *pdbentry, int index); +epicsShareFunc int dbGetLinkType(DBENTRY *pdbentry); + +/* Dump routines */ +epicsShareFunc void dbDumpPath(DBBASE *pdbbase); +epicsShareFunc void dbDumpRecord(DBBASE *pdbbase, + const char *precordTypename, int level); +epicsShareFunc void dbDumpMenu(DBBASE *pdbbase, + const char *menuName); +epicsShareFunc void dbDumpRecordType(DBBASE *pdbbase, + const char *recordTypeName); +epicsShareFunc void dbDumpField(DBBASE *pdbbase, + const char *recordTypeName, const char *fname); +epicsShareFunc void dbDumpDevice(DBBASE *pdbbase, + const char *recordTypeName); +epicsShareFunc void dbDumpDriver(DBBASE *pdbbase); +epicsShareFunc void dbDumpLink(DBBASE *pdbbase); +epicsShareFunc void dbDumpRegistrar(DBBASE *pdbbase); +epicsShareFunc void dbDumpFunction(DBBASE *pdbbase); +epicsShareFunc void dbDumpVariable(DBBASE *pdbbase); +epicsShareFunc void dbDumpBreaktable(DBBASE *pdbbase, + const char *name); +epicsShareFunc void dbPvdDump(DBBASE *pdbbase, int verbose); +epicsShareFunc void dbReportDeviceConfig(DBBASE *pdbbase, + FILE *report); + +/* Misc useful routines*/ +#define dbCalloc(nobj,size) callocMustSucceed(nobj,size,"dbCalloc") +#define dbMalloc(size) mallocMustSucceed(size,"dbMalloc") +epicsShareFunc void dbCatString(char **string, int *stringLength, + char *pnew, char *separator); + +extern int dbStaticDebug; +extern int dbConvertStrict; + +#define S_dbLib_recordTypeNotFound (M_dbLib|1) /* Record Type does not exist */ +#define S_dbLib_recExists (M_dbLib|3) /* Record Already exists */ +#define S_dbLib_recNotFound (M_dbLib|5) /* Record Not Found */ +#define S_dbLib_flddesNotFound (M_dbLib|7) /* Field Description Not Found */ +#define S_dbLib_fieldNotFound (M_dbLib|9) /* Field Not Found */ +#define S_dbLib_badField (M_dbLib|11) /* Bad Field value */ +#define S_dbLib_menuNotFound (M_dbLib|13) /* Menu not found */ +#define S_dbLib_badLink (M_dbLib|15) /* Bad Link Field */ +#define S_dbLib_nameLength (M_dbLib|17) /* Record Name is too long */ +#define S_dbLib_noRecSup (M_dbLib|19) /* Record support not found */ +#define S_dbLib_strLen (M_dbLib|21) /* String is too long */ +#define S_dbLib_noSizeOffset (M_dbLib|23) /* Missing SizeOffset Routine - No record support? */ +#define S_dbLib_outMem (M_dbLib|27) /* Out of memory */ +#define S_dbLib_infoNotFound (M_dbLib|29) /* Info item Not Found */ + +#ifdef __cplusplus +} +#endif + +#endif /*INCdbStaticLibh*/ diff --git a/modules/database/src/ioc/dbStatic/dbStaticPvt.h b/modules/database/src/ioc/dbStatic/dbStaticPvt.h new file mode 100644 index 000000000..58d32c28c --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticPvt.h @@ -0,0 +1,114 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbStaticPvt.h */ +/* + * Author: Marty Kraimer + * Date: 06Jun95 + */ + +#ifndef INCdbStaticPvth +#define INCdbStaticPvth 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*Following are not intended for client code */ +dbDeviceMenu *dbGetDeviceMenu(DBENTRY *pdbentry); +void dbFreeLinkContents(struct link *plink); +void dbFreePath(DBBASE *pdbbase); +int dbIsMacroOk(DBENTRY *pdbentry); + +/*The following routines have different versions for run-time no-run-time*/ +long dbAllocRecord(DBENTRY *pdbentry,const char *precordName); +long dbFreeRecord(DBENTRY *pdbentry); + +long dbGetFieldAddress(DBENTRY *pdbentry); +char *dbRecordName(DBENTRY *pdbentry); + +char *dbGetStringNum(DBENTRY *pdbentry); +long dbPutStringNum(DBENTRY *pdbentry,const char *pstring); + +struct jlink; + +typedef struct dbLinkInfo { + short ltype; + + /* full link string for CONSTANT and PV_LINK, + * parm string for HW links, JSON for JSON_LINK + */ + char *target; + + /* for PV_LINK */ + short modifiers; + + /* for HW links */ + char hwid[6]; /* one extra element for a nil */ + int hwnums[5]; + + /* for JSON_LINK */ + struct jlink *jlink; +} dbLinkInfo; + +long dbInitRecordLinks(dbRecordType *rtyp, struct dbCommon *prec); + +#define LINK_DEBUG_LSET 1 +#define LINK_DEBUG_JPARSE 2 + +/* Parse link string. no record locks needed. + * on success caller must free pinfo->target + */ +epicsShareFunc long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo, unsigned opts); +/* Check if link type allow the parsed link value pinfo + * to be assigned to the given link. + * Record containing plink must be locked. + * Frees pinfo->target on failure. + */ +long dbCanSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *devsup); +/* Set link field. source record must be locked (target record too + * when a DB_LINK is created) + * Unconditionally takes ownership of pinfo->target + */ +long dbSetLink(DBLINK *plink, dbLinkInfo *pinfo, devSup *dset); +/* Free dbLinkInfo storage */ +epicsShareFunc void dbFreeLinkInfo(dbLinkInfo *pinfo); + +/* The following is for path */ +typedef struct dbPathNode { + ELLNODE node; + char *directory; +} dbPathNode; + +/* Element of the global gui group list */ +typedef struct dbGuiGroup { + ELLNODE node; + short key; + char *name; +} dbGuiGroup; + +/*The following are in dbPvdLib.c*/ +/*directory*/ +typedef struct{ + ELLNODE node; + dbRecordType *precordType; + dbRecordNode *precnode; +}PVDENTRY; +epicsShareFunc int dbPvdTableSize(int size); +extern int dbStaticDebug; +void dbPvdInitPvt(DBBASE *pdbbase); +PVDENTRY *dbPvdFind(DBBASE *pdbbase,const char *name,size_t lenname); +PVDENTRY *dbPvdAdd(DBBASE *pdbbase,dbRecordType *precordType,dbRecordNode *precnode); +void dbPvdDelete(DBBASE *pdbbase,dbRecordNode *precnode); +void dbPvdFreeMem(DBBASE *pdbbase); + +#ifdef __cplusplus +} +#endif +#endif /*INCdbStaticPvth*/ diff --git a/modules/database/src/ioc/dbStatic/dbStaticRun.c b/modules/database/src/ioc/dbStatic/dbStaticRun.c new file mode 100644 index 000000000..d3817ffc5 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbStaticRun.c @@ -0,0 +1,558 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/*dbStaticLibRun.c*/ + +#include +#include +#include +#include +#include + +#include "cvtFast.h" +#include "dbDefs.h" +#include "ellLib.h" +#include "epicsPrint.h" +#include "epicsStdlib.h" +#include "epicsTypes.h" +#include "errMdef.h" + +#include "epicsExport.h" /* #define epicsExportSharedSymbols */ +#include "dbBase.h" +#include "dbCommonPvt.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "devSup.h" +#include "special.h" + +epicsShareDef int dbConvertStrict = 0; +epicsExportAddress(int, dbConvertStrict); + +static long do_nothing(struct dbCommon *precord) { return 0; } + +/* Dummy DSXT used for soft device supports */ +struct dsxt devSoft_DSXT = { + do_nothing, + do_nothing +}; + +static devSup *pthisDevSup = NULL; + +void dbInitDevSup(devSup *pdevSup, dset *pdset) +{ + pdevSup->pdset = pdset; + if (pdevSup->link_type == CONSTANT) + pdevSup->pdsxt = &devSoft_DSXT; + + if (pdset->init) { + pthisDevSup = pdevSup; + pdset->init(0); + pthisDevSup = NULL; + } +} + +void devExtend(dsxt *pdsxt) +{ + if (!pthisDevSup) + errlogPrintf("devExtend() called outside of dbInitDevSup()\n"); + else { + pthisDevSup->pdsxt = pdsxt; + } +} + +long dbAllocRecord(DBENTRY *pdbentry,const char *precordName) +{ + dbRecordType *pdbRecordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + dbFldDes *pflddes; + int i; + dbCommonPvt *ppvt; + dbCommon *precord; + char *pfield; + + if(!pdbRecordType) return(S_dbLib_recordTypeNotFound); + if(!precnode) return(S_dbLib_recNotFound); + if(pdbRecordType->rec_size == 0) { + printf("\t*** Did you run x_RegisterRecordDeviceDriver(pdbbase) yet? ***\n"); + epicsPrintf("dbAllocRecord(%s) with %s rec_size = 0\n", + precordName, pdbRecordType->name); + return(S_dbLib_noRecSup); + } else if(pdbRecordType->rec_sizename); + epicsPrintf("dbAllocRecord(%s) with %s rec_size = %d\n", + precordName, pdbRecordType->name, pdbRecordType->rec_size); + return(S_dbLib_noRecSup); + } + ppvt = dbCalloc(1, offsetof(dbCommonPvt, common) + pdbRecordType->rec_size); + precord = &ppvt->common; + ppvt->recnode = precnode; + precord->rdes = pdbRecordType; + precnode->precord = precord; + pflddes = pdbRecordType->papFldDes[0]; + if(!pflddes) { + epicsPrintf("dbAllocRecord pflddes for NAME not found\n"); + return(S_dbLib_flddesNotFound); + } + assert(pflddes->offset == 0); + assert(pflddes->size == sizeof(precord->name)); + if(strlen(precordName) >= sizeof(precord->name)) { + epicsPrintf("dbAllocRecord: NAME(%s) too long\n",precordName); + return(S_dbLib_nameLength); + } + strcpy(precord->name, precordName); + for(i=1; ino_fields; i++) { + + pflddes = pdbRecordType->papFldDes[i]; + if(!pflddes) continue; + pfield = (char*)precord + pflddes->offset; + pdbentry->pfield = (void *)pfield; + pdbentry->pflddes = pflddes; + pdbentry->indfield = i; + switch(pflddes->field_type) { + case DBF_STRING: + if(pflddes->initial) { + if(strlen(pflddes->initial) >= pflddes->size) { + epicsPrintf("initial size > size for %s.%s\n", + pdbRecordType->name,pflddes->name); + } else { + strcpy(pfield,pflddes->initial); + } + } + break; + case DBF_CHAR: + case DBF_UCHAR: + case DBF_SHORT: + case DBF_USHORT: + case DBF_LONG: + case DBF_ULONG: + case DBF_INT64: + case DBF_UINT64: + case DBF_FLOAT: + case DBF_DOUBLE: + case DBF_ENUM: + case DBF_MENU: + if(pflddes->initial) { + long status; + + status = dbPutStringNum(pdbentry,pflddes->initial); + if(status) + epicsPrintf("Error initializing %s.%s initial %s\n", + pdbRecordType->name,pflddes->name,pflddes->initial); + } + break; + case DBF_DEVICE: + if(!pflddes->ftPvt) dbGetDeviceMenu(pdbentry); + break; + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: { + DBLINK *plink = (DBLINK *)pfield; + + plink->type = CONSTANT; + if(pflddes->initial) { + plink->text = + dbCalloc(strlen(pflddes->initial)+1,sizeof(char)); + strcpy(plink->text,pflddes->initial); + } + } + break; + case DBF_NOACCESS: + break; + default: + epicsPrintf("dbAllocRecord: Illegal field type\n"); + } + } + return(0); +} + +long dbFreeRecord(DBENTRY *pdbentry) +{ + dbRecordType *pdbRecordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + + if(!pdbRecordType) return(S_dbLib_recordTypeNotFound); + if(!precnode) return(S_dbLib_recNotFound); + if(!precnode->precord) return(S_dbLib_recNotFound); + free(CONTAINER(precnode->precord, dbCommonPvt, common)); + precnode->precord = NULL; + return(0); +} + +long dbGetFieldAddress(DBENTRY *pdbentry) +{ + dbRecordType *pdbRecordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + dbFldDes *pflddes = pdbentry->pflddes; + + if(!pdbRecordType) return(S_dbLib_recordTypeNotFound); + if(!precnode) return(S_dbLib_recNotFound); + if(!pflddes) return(S_dbLib_flddesNotFound); + if(!precnode->precord) return(0); + pdbentry->pfield = ((char *)precnode->precord) + pflddes->offset; + return(0); +} + +char *dbRecordName(DBENTRY *pdbentry) +{ + dbRecordType *pdbRecordType = pdbentry->precordType; + dbRecordNode *precnode = pdbentry->precnode; + dbFldDes *pflddes; + char *precord; + + if(!pdbRecordType) return(0); + if(!precnode) return(0); + if(!precnode->precord) return(0); + precord = (char *)precnode->precord; + pflddes = pdbRecordType->papFldDes[0]; + if(!pflddes) return(NULL); + return(precord + pflddes->offset); +} + +int dbIsMacroOk(DBENTRY *pdbentry) { return(FALSE); } + +epicsShareFunc int dbIsDefaultValue(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + + if (!pflddes || !pfield) + return FALSE; + + switch (pflddes->field_type) { + case DBF_STRING: { + char *p = (char *)pfield; + + return pflddes->initial ? ! strcmp(pflddes->initial, p) + : ! strlen(p); + } + case DBF_CHAR: { + epicsInt8 field = *(epicsInt8 *)pfield; + epicsInt8 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseInt8(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_UCHAR: { + epicsUInt8 field = *(epicsUInt8 *)pfield; + epicsUInt8 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseUInt8(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_SHORT: { + epicsInt16 field = *(epicsInt16 *)pfield; + epicsInt16 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseInt16(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_ENUM: + case DBF_USHORT: { + epicsUInt16 field = *(epicsUInt16 *)pfield; + epicsUInt16 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseUInt16(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_LONG: { + epicsInt32 field = *(epicsInt32 *)pfield; + epicsInt32 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseInt32(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_ULONG: { + epicsUInt32 field = *(epicsUInt32 *)pfield; + epicsUInt32 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseUInt32(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_INT64: { + epicsInt64 field = *(epicsInt64 *)pfield; + epicsInt64 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseInt64(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_UINT64: { + epicsUInt64 field = *(epicsUInt64 *)pfield; + epicsUInt64 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseUInt64(pflddes->initial, &def, 0, NULL) + && field == def; + } + case DBF_FLOAT: { + epicsFloat32 field = *(epicsFloat32 *)pfield; + epicsFloat32 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseFloat32(pflddes->initial, &def, NULL) + && field == def; + } + case DBF_DOUBLE: { + epicsFloat64 field = *(epicsFloat64 *)pfield; + epicsFloat64 def; + + if (!pflddes->initial) + return field == 0; + + return ! epicsParseFloat64(pflddes->initial, &def, NULL) + && field == def; + } + case DBF_MENU: { + epicsEnum16 field = *(epicsEnum16 *)pfield; + epicsEnum16 def; + int index; + + if (!pflddes->initial) + return field == 0; + + index = dbGetMenuIndexFromString(pdbentry, pflddes->initial); + if (index < 0) { + if (epicsParseUInt16(pflddes->initial, &def, 0, NULL)) + return FALSE; + } + else + def = index; + return field == def; + } + case DBF_DEVICE: { + dbRecordType *precordType = pdbentry->precordType; + + if (!precordType) { + epicsPrintf("dbIsDefaultValue: pdbRecordType is NULL??\n"); + return FALSE; + } + return ellCount(&precordType->devList) == 0; + } + case DBF_INLINK: + case DBF_OUTLINK: + case DBF_FWDLINK: { + struct link *plink = (struct link *)pfield; + + if (!plink || plink->type != CONSTANT) + return FALSE; + + /* These conditions don't make a lot of sense... */ + if (!plink->value.constantStr) + return TRUE; + + if (!pflddes->initial) /* Default value for a link field? */ + return FALSE; + + return !strcmp(plink->value.constantStr, pflddes->initial); + } + default: + return TRUE; + } +} + +long dbPutStringNum(DBENTRY *pdbentry, const char *pstring) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + long status; + epicsUInt64 u64; + epicsInt64 i64; + + if (!pfield) + return S_dbLib_fieldNotFound; + + /* empty string is the same as writing numeric zero */ + if (pstring[0] == '\0') + pstring = "0"; + + switch (pflddes->field_type) { + case DBF_CHAR: + if (dbConvertStrict) + return epicsParseInt8(pstring, pfield, 0, NULL); + goto lax_signed; + + case DBF_SHORT: + if (dbConvertStrict) + return epicsParseInt16(pstring, pfield, 0, NULL); + goto lax_signed; + + case DBF_LONG: + if (dbConvertStrict) + return epicsParseInt32(pstring, pfield, 0, NULL); + goto lax_signed; + + case DBF_INT64: + if (dbConvertStrict) + return epicsParseInt64(pstring, pfield, 0, NULL); + + lax_signed: + status = epicsParseInt64(pstring, &i64, 0, NULL); + if (status) + return status; + + switch (pflddes->field_type) { + case DBF_CHAR: *(epicsInt8 *)pfield = (epicsInt8) i64; break; + case DBF_SHORT: *(epicsInt16*)pfield = (epicsInt16)i64; break; + case DBF_LONG: *(epicsInt32*)pfield = (epicsInt32)i64; break; + case DBF_INT64: *(epicsInt64*)pfield = (epicsInt64)i64; break; + default: break; + } + return status; + + case DBF_UCHAR: + if (dbConvertStrict) + return epicsParseUInt8(pstring, pfield, 0, NULL); + goto lax_unsigned; + + case DBF_ENUM: + case DBF_USHORT: + if (dbConvertStrict) + return epicsParseUInt16(pstring, pfield, 0, NULL); + goto lax_unsigned; + + case DBF_ULONG: + if (dbConvertStrict) + return epicsParseUInt32(pstring, pfield, 0, NULL); + goto lax_unsigned; + + case DBF_UINT64: + if (dbConvertStrict) + return epicsParseUInt64(pstring, pfield, 0, NULL); + + lax_unsigned: + status = epicsParseUInt64(pstring, &u64, 0, NULL); + if (status) + return status; + + switch (pflddes->field_type) { + case DBF_UCHAR: *(epicsUInt8 *)pfield = (epicsInt8) u64; break; + case DBF_ENUM: + case DBF_USHORT: *(epicsUInt16*)pfield = (epicsInt16)u64; break; + case DBF_ULONG: *(epicsUInt32*)pfield = (epicsInt32)u64; break; + case DBF_UINT64: *(epicsUInt64*)pfield = (epicsInt64)u64; break; + default: break; + } + return status; + + case DBF_FLOAT: + return epicsParseFloat32(pstring, pfield, NULL); + + case DBF_DOUBLE: + return epicsParseFloat64(pstring, pfield, NULL); + + case DBF_MENU: + case DBF_DEVICE: { + epicsEnum16 *field = (epicsEnum16 *) pfield; + int index = dbGetMenuIndexFromString(pdbentry, pstring); + + if (index < 0) { + epicsEnum16 value; + long status = epicsParseUInt16(pstring, &value, 0, NULL); + + if (status) + return status; + + index = dbGetNMenuChoices(pdbentry); + if (value > index && index > 0 && value < USHRT_MAX) + return S_dbLib_badField; + + *field = value; + } + else + *field = index; + return 0; + } + + default: + return S_dbLib_badField; + } +} + +epicsShareFunc int dbGetMenuIndex(DBENTRY *pdbentry) +{ + dbFldDes *pflddes = pdbentry->pflddes; + void *pfield = pdbentry->pfield; + + if (!pflddes || !pfield) + return -1; + + switch (pflddes->field_type) { + case DBF_MENU: + case DBF_DEVICE: + return * (epicsEnum16 *) pfield; + default: + epicsPrintf("dbGetMenuIndex: Called for field type %d\n", + pflddes->field_type); + } + return -1; +} + +epicsShareFunc long dbPutMenuIndex(DBENTRY *pdbentry, int index) +{ + dbFldDes *pflddes = pdbentry->pflddes; + epicsEnum16 *pfield = pdbentry->pfield; + + if (!pflddes) + return S_dbLib_flddesNotFound; + if (!pfield) + return S_dbLib_fieldNotFound; + + switch (pflddes->field_type) { + case DBF_MENU: { + dbMenu *pdbMenu = (dbMenu *) pflddes->ftPvt; + + if (!pdbMenu) + return S_dbLib_menuNotFound; + if (index < 0 || index >= pdbMenu->nChoice) + return S_dbLib_badField; + + *pfield = index; + return 0; + } + + case DBF_DEVICE: { + dbDeviceMenu *pdbDeviceMenu = dbGetDeviceMenu(pdbentry); + + if (!pdbDeviceMenu) + return S_dbLib_menuNotFound; + if (index < 0 || index >= pdbDeviceMenu->nChoice) + return S_dbLib_badField; + + return dbPutString(pdbentry, pdbDeviceMenu->papChoice[index]); + } + + default: + break; + } + return S_dbLib_badField; +} diff --git a/modules/database/src/ioc/dbStatic/dbYacc.y b/modules/database/src/ioc/dbStatic/dbYacc.y new file mode 100644 index 000000000..9dc8c5081 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/dbYacc.y @@ -0,0 +1,385 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +%{ +static int yyerror(); +static int yy_start; +static long pvt_yy_parse(void); +static int yyFailed = 0; +static int yyAbort = 0; +#include "dbLexRoutines.c" +%} + +%start database + +%union +{ + char *Str; +} + +%token tokenINCLUDE tokenPATH tokenADDPATH +%token tokenALIAS tokenMENU tokenCHOICE tokenRECORDTYPE +%token tokenFIELD tokenINFO tokenREGISTRAR +%token tokenDEVICE tokenDRIVER tokenLINK tokenBREAKTABLE +%token tokenRECORD tokenGRECORD tokenVARIABLE tokenFUNCTION +%token tokenSTRING tokenCDEFS + +%token jsonNULL jsonTRUE jsonFALSE +%token jsonNUMBER jsonSTRING jsonBARE +%type json_value json_object json_array +%type json_members json_pair json_elements json_string + +%% + +database: /* empty */ + | database_item_list + ; + +database_item_list: database_item_list database_item + | database_item + ; + +database_item: include + | path + | addpath + | tokenMENU menu_head menu_body + | tokenRECORDTYPE recordtype_head recordtype_body + | device + | driver + | link + | registrar + | function + | variable + | tokenBREAKTABLE break_head break_body + | tokenRECORD record_head record_body + | tokenGRECORD grecord_head record_body + | alias + ; + +include: tokenINCLUDE tokenSTRING +{ + if(dbStaticDebug>2) printf("include : %s\n",$2); + dbIncludeNew($2); dbmfFree($2); +}; + +path: tokenPATH tokenSTRING +{ + if(dbStaticDebug>2) printf("path : %s\n",$2); + dbPathCmd($2); dbmfFree($2); +}; + +addpath: tokenADDPATH tokenSTRING +{ + if(dbStaticDebug>2) printf("addpath : %s\n",$2); + dbAddPathCmd($2); dbmfFree($2); +}; + +menu_head: '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("menu_head %s\n",$2); + dbMenuHead($2); dbmfFree($2); +}; + +menu_body: '{' choice_list '}' +{ + if(dbStaticDebug>2) printf("menu_body\n"); + dbMenuBody(); +}; + +choice_list: choice_list choice | choice; + +choice: tokenCHOICE '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("choice %s %s\n",$3,$5); + dbMenuChoice($3,$5); dbmfFree($3); dbmfFree($5); +} + | include; + +recordtype_head: '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("recordtype_head %s\n",$2); + dbRecordtypeHead($2); dbmfFree($2); +}; + +recordtype_body: '{' '}' +{ + if(dbStaticDebug>2) printf("empty recordtype_body\n"); + dbRecordtypeEmpty(); +} + | '{' recordtype_field_list '}' +{ + if(dbStaticDebug>2) printf("recordtype_body\n"); + dbRecordtypeBody(); +}; + +recordtype_field_list: recordtype_field_list recordtype_field + | recordtype_field; + +recordtype_field: tokenFIELD recordtype_field_head recordtype_field_body + | tokenCDEFS +{ + if(dbStaticDebug>2) printf("recordtype_cdef %s", $1); + dbRecordtypeCdef($1); dbmfFree($1); +} + | include ; + +recordtype_field_head: '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("recordtype_field_head %s %s\n",$2,$4); + dbRecordtypeFieldHead($2,$4); dbmfFree($2); dbmfFree($4); +}; + +recordtype_field_body: '{' recordtype_field_item_list '}' ; + +recordtype_field_item_list: recordtype_field_item_list recordtype_field_item + | recordtype_field_item; + +recordtype_field_item: tokenSTRING '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("recordtype_field_item %s %s\n",$1,$3); + dbRecordtypeFieldItem($1,$3); dbmfFree($1); dbmfFree($3); +} + | tokenMENU '(' tokenSTRING ')' +{ + + if(dbStaticDebug>2) printf("recordtype_field_item %s (%s)\n","menu",$3); + dbRecordtypeFieldItem("menu",$3); dbmfFree($3); +}; + + +device: tokenDEVICE '(' + tokenSTRING ',' tokenSTRING ',' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("device %s %s %s %s\n",$3,$5,$7,$9); + dbDevice($3,$5,$7,$9); + dbmfFree($3); dbmfFree($5); + dbmfFree($7); dbmfFree($9); +}; + + +driver: tokenDRIVER '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("driver %s\n",$3); + dbDriver($3); dbmfFree($3); +}; + +link: tokenLINK '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("link %s %s\n",$3,$5); + dbLinkType($3,$5); + dbmfFree($3); dbmfFree($5); +}; + +registrar: tokenREGISTRAR '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("registrar %s\n",$3); + dbRegistrar($3); dbmfFree($3); +}; + +function: tokenFUNCTION '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("function %s\n",$3); + dbFunction($3); dbmfFree($3); +}; + +variable: tokenVARIABLE '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("variable %s\n",$3); + dbVariable($3,"int"); dbmfFree($3); +} + | tokenVARIABLE '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("variable %s, %s\n",$3,$5); + dbVariable($3,$5); dbmfFree($3); dbmfFree($5); +}; + +break_head: '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("break_head %s\n",$2); + dbBreakHead($2); dbmfFree($2); +}; + +break_body : '{' break_list '}' +{ + if(dbStaticDebug>2) printf("break_body\n"); + dbBreakBody(); +}; + +break_list: break_list ',' break_item + | break_list break_item + | break_item; + +break_item: tokenSTRING +{ + if(dbStaticDebug>2) printf("break_item tokenSTRING %s\n",$1); + dbBreakItem($1); dbmfFree($1); +}; + + +grecord_head: '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("grecord_head %s %s\n",$2,$4); + dbRecordHead($2,$4,1); dbmfFree($2); dbmfFree($4); +}; + +record_head: '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("record_head %s %s\n",$2,$4); + dbRecordHead($2,$4,0); dbmfFree($2); dbmfFree($4); +}; + +record_body: /* empty */ +{ + if(dbStaticDebug>2) printf("null record_body\n"); + dbRecordBody(); +} + | '{' '}' +{ + if(dbStaticDebug>2) printf("empty record_body\n"); + dbRecordBody(); +} + | '{' record_field_list '}' +{ + if(dbStaticDebug>2) printf("record_body\n"); + dbRecordBody(); +}; + +record_field_list: record_field_list record_field + | record_field; + +record_field: tokenFIELD '(' tokenSTRING ',' + { BEGIN JSON; } json_value { BEGIN INITIAL; } ')' +{ + if(dbStaticDebug>2) printf("record_field %s %s\n",$3,$6); + dbRecordField($3,$6); dbmfFree($3); dbmfFree($6); +} + | tokenINFO '(' tokenSTRING ',' + { BEGIN JSON; } json_value { BEGIN INITIAL; } ')' +{ + if(dbStaticDebug>2) printf("record_info %s %s\n",$3,$6); + dbRecordInfo($3,$6); dbmfFree($3); dbmfFree($6); +} + | tokenALIAS '(' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("record_alias %s\n",$3); + dbRecordAlias($3); dbmfFree($3); +} + | include ; + +alias: tokenALIAS '(' tokenSTRING ',' tokenSTRING ')' +{ + if(dbStaticDebug>2) printf("alias %s %s\n",$3,$5); + dbAlias($3,$5); dbmfFree($3); dbmfFree($5); +}; + +json_object: '{' '}' +{ + $$ = dbmfStrdup("{}"); + if (dbStaticDebug>2) printf("json %s\n", $$); +} + | '{' json_members '}' +{ + $$ = dbmfStrcat3("{", $2, "}"); + dbmfFree($2); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_members: json_pair + | json_pair ',' + | json_pair ',' json_members +{ + $$ = dbmfStrcat3($1, ",", $3); + dbmfFree($1); dbmfFree($3); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_pair: json_string ':' json_value +{ + $$ = dbmfStrcat3($1, ":", $3); + dbmfFree($1); dbmfFree($3); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_string: jsonSTRING + | jsonBARE +{ + $$ = dbmfStrcat3("\"", $1, "\""); + dbmfFree($1); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_array: '[' ']' +{ + $$ = dbmfStrdup("[]"); + if (dbStaticDebug>2) printf("json %s\n", $$); +} + | '[' json_elements ']' +{ + $$ = dbmfStrcat3("[", $2, "]"); + dbmfFree($2); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_elements: json_value + | json_value ',' +{ /* Retain the trailing ',' so link parser can distinguish a + * 1-element const list from a PV name (commas are illegal) + */ + $$ = dbmfStrcat3($1, ",", ""); + dbmfFree($1); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + | json_value ',' json_elements +{ + $$ = dbmfStrcat3($1, ",", $3); + dbmfFree($1); dbmfFree($3); + if (dbStaticDebug>2) printf("json %s\n", $$); +}; + +json_value: jsonNULL { $$ = dbmfStrdup("null"); } + | jsonTRUE { $$ = dbmfStrdup("true"); } + | jsonFALSE { $$ = dbmfStrdup("false"); } + | jsonNUMBER + | json_string + | json_array + | json_object ; + + +%% + +#include "dbLex.c" + + +static int yyerror(char *str) +{ + if (str) + epicsPrintf("Error: %s\n", str); + else + epicsPrintf("Error"); + if (!yyFailed) { /* Only print this stuff once */ + epicsPrintf(" at or before \"%s\"", yytext); + dbIncludePrint(); + yyFailed = TRUE; + } + return(0); +} +static long pvt_yy_parse(void) +{ + static int FirstFlag = 1; + long rtnval; + + if (!FirstFlag) { + yyAbort = FALSE; + yyFailed = FALSE; + yyreset(); + yyrestart(NULL); + } + FirstFlag = 0; + rtnval = yyparse(); + if(rtnval!=0 || yyFailed) return(-1); else return(0); +} diff --git a/modules/database/src/ioc/dbStatic/devSup.h b/modules/database/src/ioc/dbStatic/devSup.h new file mode 100644 index 000000000..bd900cae4 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/devSup.h @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devSup.h Device Support */ +/* + * Author: Marty Kraimer + * Date: 6-1-90 + */ + +#ifndef INCdevSuph +#define INCdevSuph 1 + +#include "errMdef.h" +#include "shareLib.h" + +/* structures defined elsewhere */ +struct dbCommon; +struct devSup; + +#ifdef __cplusplus +extern "C" { + typedef long (*DEVSUPFUN)(void *); /* ptr to device support function*/ +#else + typedef long (*DEVSUPFUN)(); /* ptr to device support function*/ +#endif + +typedef struct dset { /* device support entry table */ + long number; /*number of support routines*/ + DEVSUPFUN report; /*print report*/ + DEVSUPFUN init; /*init support layer*/ + DEVSUPFUN init_record; /*init device for particular record*/ + DEVSUPFUN get_ioint_info; /* get io interrupt information*/ + /*other functions are record dependent*/ +} dset; + +typedef struct dsxt { /* device support extension table */ + long (*add_record)(struct dbCommon *precord); + long (*del_record)(struct dbCommon *precord); + /* Recordtypes are *not* allowed to extend this table */ +} dsxt; + +epicsShareExtern dsxt devSoft_DSXT; /* Allow anything table */ + +epicsShareFunc void devExtend(dsxt *pdsxt); +epicsShareFunc void dbInitDevSup(struct devSup *pdevSup, dset *pdset); + + +#define S_dev_noDevSup (M_devSup| 1) /*SDR_DEVSUP: Device support missing*/ +#define S_dev_noDSET (M_devSup| 3) /*Missing device support entry table*/ +#define S_dev_missingSup (M_devSup| 5) /*Missing device support routine*/ +#define S_dev_badInpType (M_devSup| 7) /*Bad INP link type*/ +#define S_dev_badOutType (M_devSup| 9) /*Bad OUT link type*/ +#define S_dev_badInitRet (M_devSup|11) /*Bad init_rec return value */ +#define S_dev_badBus (M_devSup|13) /*Illegal bus type*/ +#define S_dev_badCard (M_devSup|15) /*Illegal or nonexistant module*/ +#define S_dev_badSignal (M_devSup|17) /*Illegal signal*/ +#define S_dev_NoInit (M_devSup|19) /*No init*/ +#define S_dev_Conflict (M_devSup|21) /*Multiple records accessing same signal*/ +#define S_dev_noDeviceFound (M_devSup|23) /*No device found at specified address*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/modules/database/src/ioc/dbStatic/drvSup.h b/modules/database/src/ioc/dbStatic/drvSup.h new file mode 100644 index 000000000..5778038e7 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/drvSup.h @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* drvSup.h Driver Support */ + +/* + * Author: Marty Kraimer + * Date: 6-1-90 + */ + +#ifndef INCdrvSuph +#define INCdrvSuph 1 + +#include "errMdef.h" + +typedef long (*DRVSUPFUN) (); /* ptr to driver support function*/ + +typedef struct drvet { /* driver entry table */ + long number; /*number of support routines*/ + DRVSUPFUN report; /*print report*/ + DRVSUPFUN init; /*init support*/ + /*other functions are device dependent*/ +}drvet; +#define DRVETNUMBER ( (sizeof(struct drvet) -sizeof(long))/sizeof(DRVSUPFUN) ) + +#define S_drv_noDrvSup (M_drvSup| 1) /*SDR_DRVSUP: Driver support missing*/ +#define S_drv_noDrvet (M_drvSup| 3) /*Missing driver support entry table*/ + +#endif diff --git a/modules/database/src/ioc/dbStatic/guigroup.h b/modules/database/src/ioc/dbStatic/guigroup.h new file mode 100644 index 000000000..4e2f2e1dd --- /dev/null +++ b/modules/database/src/ioc/dbStatic/guigroup.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + these are used in the pmt (prompt) field of the record support + ascii files. They represent field groupings for dct tools +*/ + +#ifndef __gui_group_h__ +#define __gui_group_h__ + +#error As of Base 3.15.4, the promptgroup implementation has changed. \ + This header file (guigroup.h) is invalid and will be removed shortly. \ + Instead, you should include dbStaticLib.h, parse the DBD, \ + and use dbGetPromptGroupNameFromKey() and dbGetPromptGroupKeyFromName() \ + that have been added to dbStaticLib. \ + More details in the 3.15.4 release notes and the AppDev Guide. + +#endif /*__gui_group_h__*/ diff --git a/modules/database/src/ioc/dbStatic/link.h b/modules/database/src/ioc/dbStatic/link.h new file mode 100644 index 000000000..2e3c77820 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/link.h @@ -0,0 +1,206 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* link.h */ + +/* + * Original Authors: Bob Dalesio, Marty Kraimer + */ + +#ifndef INC_link_H +#define INC_link_H + +#include "dbDefs.h" +#include "ellLib.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* link types */ +#define CONSTANT 0 +#define PV_LINK 1 +#define VME_IO 2 +#define CAMAC_IO 3 +#define AB_IO 4 +#define GPIB_IO 5 +#define BITBUS_IO 6 +#define MACRO_LINK 7 +#define JSON_LINK 8 +#define PN_LINK 9 +#define DB_LINK 10 +#define CA_LINK 11 +#define INST_IO 12 /* instrument */ +#define BBGPIB_IO 13 /* bitbus -> gpib */ +#define RF_IO 14 +#define VXI_IO 15 +#define LINK_NTYPES 16 +typedef struct maplinkType { + char *strvalue; + int value; +} maplinkType; + +epicsShareExtern maplinkType pamaplinkType[]; + +#define VXIDYNAMIC 0 +#define VXISTATIC 1 + +/* structure of a PV_LINK DB_LINK and a CA_LINK */ +/*Options defined by pvlMask */ +#define pvlOptMsMode 0x3 /*Maximize Severity mode selection*/ +#define pvlOptNMS 0 /*Don't Maximize Severity*/ +#define pvlOptMS 1 /*Maximize Severity always*/ +#define pvlOptMSI 2 /*Maximize Severity if INVALID*/ +#define pvlOptMSS 3 /*Maximize Severity and copy Status*/ +#define pvlOptPP 0x4 /*Process Passive*/ +#define pvlOptCA 0x8 /*Always make it a CA link*/ +#define pvlOptCP 0x10 /*CA + process on monitor*/ +#define pvlOptCPP 0x20 /*CA + process passive record on monitor*/ +#define pvlOptFWD 0x40 /*Generate ca_put for forward link*/ +#define pvlOptInpNative 0x80 /*Input native*/ +#define pvlOptInpString 0x100 /*Input as string*/ +#define pvlOptOutNative 0x200 /*Output native*/ +#define pvlOptOutString 0x400 /*Output as string*/ + +/* DBLINK Flag bits */ +#define DBLINK_FLAG_INITIALIZED 1 /* dbInitLink() called */ +#define DBLINK_FLAG_TSELisTIME 2 /* Use TSEL to get timeStamp */ + +struct macro_link { + char *macroStr; +}; + +struct dbCommon; +typedef long (*LINKCVT)(); + +struct pv_link { + ELLNODE backlinknode; + char *pvname; /* pvname link points to */ + void *pvt; /* CA or DB private */ + LINKCVT getCvt; /* input conversion function */ + short pvlMask; /* Options mask */ + short lastGetdbrType; /* last dbrType for DB or CA get */ +}; + +struct jlink; +struct json_link { + char *string; + struct jlink *jlink; +}; + +/* structure of a VME io channel */ +struct vmeio { + short card; + short signal; + char *parm; +}; + +/* structure of a CAMAC io channel */ +struct camacio { + short b; + short c; + short n; + short a; + short f; + char *parm; +}; + +/* structure of a RF io channel */ +struct rfio { + short branch; + short cryo; + short micro; + short dataset; + short element; + long ext; +}; + +/* structure of a Allen-Bradley io channel */ +struct abio { + short link; + short adapter; + short card; + short signal; + char *parm; +}; + +/* structure of a gpib io channel */ +struct gpibio { + short link; + short addr; /* device address */ + char *parm; +}; + +/* structure of a bitbus io channel */ +struct bitbusio { + unsigned char link; + unsigned char node; + unsigned char port; + unsigned char signal; + char *parm; +}; + +/* structure of a bitbus to gpib io channel */ +struct bbgpibio { + unsigned char link; + unsigned char bbaddr; + unsigned char gpibaddr; + unsigned char pad; + char *parm; +}; + +/* structure of an instrument io link */ +struct instio { + char *string; +}; + +/* structure of a vxi link */ +struct vxiio { + short flag; /* 0 = frame/slot, 1 = SA */ + short frame; + short slot; + short la; /* logical address if flag =1 */ + short signal; + char *parm; +}; + +/* union of possible address structures */ +union value { + char *constantStr; /*constant string*/ + struct macro_link macro_link; /* link containing macro substitution*/ + struct json_link json; /* JSON-encoded link */ + struct pv_link pv_link; /* link to process variable*/ + struct vmeio vmeio; /* vme io point */ + struct camacio camacio; /* camac io point */ + struct rfio rfio; /* CEBAF RF buffer interface */ + struct abio abio; /* allen-bradley io point */ + struct gpibio gpibio; + struct bitbusio bitbusio; + struct instio instio; /* instrument io link */ + struct bbgpibio bbgpibio; /* bitbus to gpib io link */ + struct vxiio vxiio; /* vxi io */ +}; + +struct lset; + +struct link { + struct dbCommon *precord; /* Pointer to record owning link */ + short type; + short flags; + struct lset *lset; + char *text; /* Raw link text */ + union value value; +}; + +typedef struct link DBLINK; + +#ifdef __cplusplus +} +#endif +#endif /* INC_link_H */ diff --git a/modules/database/src/ioc/dbStatic/recSup.h b/modules/database/src/ioc/dbStatic/recSup.h new file mode 100644 index 000000000..2850e6292 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/recSup.h @@ -0,0 +1,102 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* recSup.h + * Record Support + * Author: Marty Kraimer + * Date: 6-1-90 + */ + +#ifndef INCrecSuph +#define INCrecSuph 1 + +#include "errMdef.h" +#include "compilerDependencies.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* RSET definition */ + +/* defined elsewhere */ +struct dbAddr; +struct dbCommon; +struct dbr_enumStrs; +struct dbr_grDouble; +struct dbr_ctrlDouble; +struct dbr_alDouble; + +/* record support entry table */ +struct typed_rset { + long number; /* number of support routines */ + long (*report)(void *precord); + long (*init)(); + long (*init_record)(struct dbCommon *precord, int pass); + long (*process)(struct dbCommon *precord); + long (*special)(struct dbAddr *paddr, int after); + long (*get_value)(void); /* DEPRECATED set to NULL */ + long (*cvt_dbaddr)(struct dbAddr *paddr); + long (*get_array_info)(struct dbAddr *paddr, long *no_elements, long *offset); + long (*put_array_info)(struct dbAddr *paddr, long nNew); + long (*get_units)(struct dbAddr *paddr, char *units); + long (*get_precision)(const struct dbAddr *paddr, long *precision); + long (*get_enum_str)(const struct dbAddr *paddr, char *pbuffer); + long (*get_enum_strs)(const struct dbAddr *paddr, struct dbr_enumStrs *p); + long (*put_enum_str)(const struct dbAddr *paddr, const char *pbuffer); + long (*get_graphic_double)(struct dbAddr *paddr, struct dbr_grDouble *p); + long (*get_control_double)(struct dbAddr *paddr, struct dbr_ctrlDouble *p); + long (*get_alarm_double)(struct dbAddr *paddr, struct dbr_alDouble *p); +}; + +#ifdef USE_TYPED_RSET + +typedef struct typed_rset rset; + +#else + +/* pre-3.16 old untyped RSET definition - DEPRECATED */ + +typedef long (*RECSUPFUN) () EPICS_DEPRECATED; /* ptr to record support function*/ + +struct rset { /* record support entry table */ + long number; /*number of support routines */ + long (*report)(); /*print report */ + long (*init)(); /*init support */ + long (*init_record)(); /*init record */ + long (*process)(); /*process record */ + long (*special)(); /*special processing */ + long (*get_value)(); /*no longer used */ + long (*cvt_dbaddr)(); /*cvt dbAddr */ + long (*get_array_info)(); + long (*put_array_info)(); + long (*get_units)(); + long (*get_precision)(); + long (*get_enum_str)(); /*get string from enum item*/ + long (*get_enum_strs)();/*get all enum strings */ + long (*put_enum_str)(); /*put string from enum item*/ + long (*get_graphic_double)(); + long (*get_control_double)(); + long (*get_alarm_double)(); +} EPICS_DEPRECATED; + +typedef struct rset rset EPICS_DEPRECATED; + +#endif + +#define RSETNUMBER 17 + +#define S_rec_noRSET (M_recSup| 1) /*Missing record support entry table*/ +#define S_rec_noSizeOffset (M_recSup| 2) /*Missing SizeOffset Routine*/ +#define S_rec_outMem (M_recSup| 3) /*Out of Memory*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*INCrecSuph*/ diff --git a/modules/database/src/ioc/dbStatic/special.h b/modules/database/src/ioc/dbStatic/special.h new file mode 100644 index 000000000..055287a64 --- /dev/null +++ b/modules/database/src/ioc/dbStatic/special.h @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* special.h */ + +/* + * Author: Marty Kraimer + * Date: 6-1-90 + */ + +#ifndef INCspecialh +#define INCspecialh 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*NOTE Do NOT add aditional definitions with out modifying dbLexRoutines.c */ +/* types 1-99 are global. Record specific must start with 100 */ +#define SPC_NOMOD 1 /*Field must not be modified */ +#define SPC_DBADDR 2 /*db_name_to_addr must call cvt_dbaddr */ +#define SPC_SCAN 3 /*A scan related field is being changed */ +#define SPC_ALARMACK 5 /*Special Alarm Acknowledgement*/ +#define SPC_AS 6 /* Access Security*/ +#define SPC_ATTRIBUTE 7 /* psuedo field, i.e. attribute field*/ +/* useful when record support must be notified of a field changing value*/ +#define SPC_MOD 100 +/* used by all records that support a reset field */ +#define SPC_RESET 101 /*The res field is being modified*/ +/* Specific to conversion (Currently only ai */ +#define SPC_LINCONV 102 /*A linear conversion field is being changed*/ +/* Specific to calculation records */ +#define SPC_CALC 103 /*The CALC field is being changed*/ + + +#define SPC_NTYPES 9 +typedef struct mapspcType{ + char *strvalue; + int value; +}mapspcType; + +#ifndef SPECIAL_GBLSOURCE +extern mapspcType pamapspcType[]; +#else +mapspcType pamapspcType[SPC_NTYPES] = { + {"SPC_NOMOD",SPC_NOMOD}, + {"SPC_DBADDR",SPC_DBADDR}, + {"SPC_SCAN",SPC_SCAN}, + {"SPC_ALARMACK",SPC_ALARMACK}, + {"SPC_AS",SPC_AS}, + {"SPC_MOD",SPC_MOD}, + {"SPC_RESET",SPC_RESET}, + {"SPC_LINCONV",SPC_LINCONV}, + {"SPC_CALC",SPC_CALC} +}; +#endif /*SPECIAL_GBLSOURCE*/ + +#ifdef __cplusplus +} +#endif + +#endif /*INCspecialh*/ diff --git a/modules/database/src/ioc/dbtemplate/Makefile b/modules/database/src/ioc/dbtemplate/Makefile new file mode 100644 index 000000000..2aa5a0c78 --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/Makefile @@ -0,0 +1,27 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/dbtemplate + +PROD_HOST += msi + +msi_SRCS = msi.c +msi_LIBS += Com +HTMLS += msi.html + +INC += dbLoadTemplate.h +INC += dbtoolsIocRegister.h + +dbCore_SRCS += dbLoadTemplate.c +dbCore_SRCS += dbtoolsIocRegister.c + +CLEANS += dbLoadTemplate_lex.c dbLoadTemplate.c + diff --git a/modules/database/src/ioc/dbtemplate/RULES b/modules/database/src/ioc/dbtemplate/RULES new file mode 100644 index 000000000..d528be9ec --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/RULES @@ -0,0 +1,14 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +# dbLoadTemplate_lex.c is included by dbLoadTemplate.c +dbLoadTemplate.c: dbLoadTemplate_lex.c $(IOCDIR)/dbtemplate/dbLoadTemplate.h + diff --git a/modules/database/src/ioc/dbtemplate/dbLoadTemplate.h b/modules/database/src/ioc/dbtemplate/dbLoadTemplate.h new file mode 100644 index 000000000..a6ca60617 --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/dbLoadTemplate.h @@ -0,0 +1,18 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* dbLoadTemplate.h */ + +#ifndef INCdbLoadTemplateh +#define INCdbLoadTemplateh + +#include "shareLib.h" +epicsShareFunc int dbLoadTemplate( + const char *sub_file, const char *cmd_collect); + +#endif /*INCdbLoadTemplateh*/ diff --git a/modules/database/src/ioc/dbtemplate/dbLoadTemplate.y b/modules/database/src/ioc/dbtemplate/dbLoadTemplate.y new file mode 100644 index 000000000..1a4a47caf --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/dbLoadTemplate.y @@ -0,0 +1,392 @@ +%{ + +/*************************************************************************\ +* Copyright (c) 2006 UChicago, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include +#include + +#include "osiUnistd.h" +#include "macLib.h" +#include "dbmf.h" + +#include "epicsExport.h" +#include "dbAccess.h" +#include "dbLoadTemplate.h" + +static int line_num; +static int yyerror(char* str); + +static char *sub_collect = NULL; +static char *sub_locals; +static char **vars = NULL; +static char *db_file_name = NULL; +static int var_count, sub_count; + +/* We allocate MAX_VAR_FACTOR chars in the sub_collect string for each + * "variable=value," segment, and will accept at most dbTemplateMaxVars + * template variables. The user can adjust that variable to increase + * the number of variables or the length allocated for the buffer. + */ +#define MAX_VAR_FACTOR 50 + +int dbTemplateMaxVars = 100; +epicsExportAddress(int, dbTemplateMaxVars); + +%} + +%start substitution_file + +%token WORD QUOTE +%token DBFILE +%token PATTERN +%token GLOBAL +%token EQUALS COMMA +%left O_PAREN C_PAREN +%left O_BRACE C_BRACE + +%union +{ + int Int; + char Char; + char *Str; + double Real; +} + +%% + +substitution_file: global_or_template + | substitution_file global_or_template + ; + +global_or_template: global_definitions + | template_substitutions + ; + +global_definitions: GLOBAL O_BRACE C_BRACE + | GLOBAL O_BRACE variable_definitions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "global_definitions: %s\n", sub_collect+1); + #endif + sub_locals += strlen(sub_locals); + } + ; + +template_substitutions: template_filename O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s unused\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + | template_filename O_BRACE substitutions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s finished\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + ; + +template_filename: DBFILE WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: %s\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + | DBFILE QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: \"%s\"\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + ; + +substitutions: pattern_substitutions + | variable_substitutions + ; + +pattern_substitutions: PATTERN O_BRACE C_BRACE + | PATTERN O_BRACE C_BRACE pattern_definitions + | PATTERN O_BRACE pattern_names C_BRACE + | PATTERN O_BRACE pattern_names C_BRACE pattern_definitions + ; + +pattern_names: pattern_name + | pattern_names COMMA + | pattern_names pattern_name + ; + +pattern_name: WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_name: [%d] = %s\n", var_count, $1); + #endif + if (var_count >= dbTemplateMaxVars) { + fprintf(stderr, + "More than dbTemplateMaxVars = %d macro variables used\n", + dbTemplateMaxVars); + yyerror(NULL); + } + else { + vars[var_count] = dbmfMalloc(strlen($1)+1); + strcpy(vars[var_count], $1); + var_count++; + dbmfFree($1); + } + } + ; + +pattern_definitions: pattern_definition + | pattern_definitions pattern_definition + ; + +pattern_definition: global_definitions + | O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition: pattern_values empty\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + } + | O_BRACE pattern_values C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + *sub_locals = '\0'; + sub_count = 0; + } + | WORD O_BRACE pattern_values C_BRACE + { /* DEPRECATED SYNTAX */ + fprintf(stderr, + "dbLoadTemplate: Substitution file uses deprecated syntax.\n" + " the string '%s' on line %d that comes just before the\n" + " '{' character is extraneous and should be removed.\n", + $1, line_num); + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_definition:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + dbmfFree($1); + *sub_locals = '\0'; + sub_count = 0; + } + ; + +pattern_values: pattern_value + | pattern_values COMMA + | pattern_values pattern_value + ; + +pattern_value: QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_value: [%d] = \"%s\"\n", sub_count, $1); + #endif + if (sub_count < var_count) { + strcat(sub_locals, ","); + strcat(sub_locals, vars[sub_count]); + strcat(sub_locals, "=\""); + strcat(sub_locals, $1); + strcat(sub_locals, "\""); + sub_count++; + } else { + fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n", + line_num); + } + dbmfFree($1); + } + | WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "pattern_value: [%d] = %s\n", sub_count, $1); + #endif + if (sub_count < var_count) { + strcat(sub_locals, ","); + strcat(sub_locals, vars[sub_count]); + strcat(sub_locals, "="); + strcat(sub_locals, $1); + sub_count++; + } else { + fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n", + line_num); + } + dbmfFree($1); + } + ; + +variable_substitutions: variable_substitution + | variable_substitutions variable_substitution + ; + +variable_substitution: global_definitions + | O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution: variable_definitions empty\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + } + | O_BRACE variable_definitions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + *sub_locals = '\0'; + } + | WORD O_BRACE variable_definitions C_BRACE + { /* DEPRECATED SYNTAX */ + fprintf(stderr, + "dbLoadTemplate: Substitution file uses deprecated syntax.\n" + " the string '%s' on line %d that comes just before the\n" + " '{' character is extraneous and should be removed.\n", + $1, line_num); + #ifdef ERROR_STUFF + fprintf(stderr, "variable_substitution:\n"); + fprintf(stderr, " dbLoadRecords(%s)\n", sub_collect+1); + #endif + dbLoadRecords(db_file_name, sub_collect+1); + dbmfFree($1); + *sub_locals = '\0'; + } + ; + +variable_definitions: variable_definition + | variable_definitions COMMA + | variable_definitions variable_definition + ; + +variable_definition: WORD EQUALS WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_definition: %s = %s\n", $1, $3); + #endif + strcat(sub_locals, ","); + strcat(sub_locals, $1); + strcat(sub_locals, "="); + strcat(sub_locals, $3); + dbmfFree($1); dbmfFree($3); + } + | WORD EQUALS QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "variable_definition: %s = \"%s\"\n", $1, $3); + #endif + strcat(sub_locals, ","); + strcat(sub_locals, $1); + strcat(sub_locals, "=\""); + strcat(sub_locals, $3); + strcat(sub_locals, "\""); + dbmfFree($1); dbmfFree($3); + } + ; + +%% + +#include "dbLoadTemplate_lex.c" + +static int yyerror(char* str) +{ + if (str) + fprintf(stderr, "Substitution file error: %s\n", str); + else + fprintf(stderr, "Substitution file error.\n"); + fprintf(stderr, "line %d: '%s'\n", line_num, yytext); + return 0; +} + +static int is_not_inited = 1; + +int dbLoadTemplate(const char *sub_file, const char *cmd_collect) +{ + FILE *fp; + int i; + + line_num = 1; + + if (!sub_file || !*sub_file) { + fprintf(stderr, "must specify variable substitution file\n"); + return -1; + } + + if (dbTemplateMaxVars < 1) + { + fprintf(stderr,"Error: dbTemplateMaxVars = %d, must be +ve\n", + dbTemplateMaxVars); + return -1; + } + + fp = fopen(sub_file, "r"); + if (!fp) { + fprintf(stderr, "dbLoadTemplate: error opening sub file %s\n", sub_file); + return -1; + } + + vars = malloc(dbTemplateMaxVars * sizeof(char*)); + sub_collect = malloc(dbTemplateMaxVars * MAX_VAR_FACTOR); + if (!vars || !sub_collect) { + free(vars); + free(sub_collect); + fclose(fp); + fprintf(stderr, "dbLoadTemplate: Out of memory!\n"); + return -1; + } + strcpy(sub_collect, ","); + + if (cmd_collect && *cmd_collect) { + strcat(sub_collect, cmd_collect); + sub_locals = sub_collect + strlen(sub_collect); + } else { + sub_locals = sub_collect; + *sub_locals = '\0'; + } + var_count = 0; + sub_count = 0; + + if (is_not_inited) { + yyin = fp; + is_not_inited = 0; + } else { + yyrestart(fp); + } + + yyparse(); + + for (i = 0; i < var_count; i++) { + dbmfFree(vars[i]); + } + free(vars); + free(sub_collect); + vars = NULL; + fclose(fp); + if (db_file_name) { + dbmfFree(db_file_name); + db_file_name = NULL; + } + return 0; +} diff --git a/modules/database/src/ioc/dbtemplate/dbLoadTemplate_lex.l b/modules/database/src/ioc/dbtemplate/dbLoadTemplate_lex.l new file mode 100644 index 000000000..c6a99a8a1 --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/dbLoadTemplate_lex.l @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (c) 2006 UChicago, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +newline "\n" +backslash "\\" +doublequote "\"" +singlequote "'" +comment "#" +whitespace [ \t\r] +escape {backslash}. +dstringchar [^"\n\\] +sstringchar [^'\n\\] +bareword [a-zA-Z0-9_\-+:./\\\[\]<>;] + +%% + +"pattern" { return(PATTERN); } +"file" { return(DBFILE); } +"global" { return(GLOBAL); } + +{doublequote}({dstringchar}|{escape})*{doublequote} | +{singlequote}({sstringchar}|{escape})*{singlequote} { + yylval.Str = dbmfStrdup((char *) yytext+1); + yylval.Str[strlen(yylval.Str)-1] = '\0'; + return(QUOTE); +} + +{bareword}+ { + yylval.Str = dbmfStrdup((char *) yytext); + return(WORD); +} + +"=" { return(EQUALS); } +"," { return(COMMA); } +"{" { return(O_BRACE); } +"}" { return(C_BRACE); } + +{comment}.* ; +{whitespace} ; +{newline} { line_num++; } + +. { + char message[40]; + + sprintf(message, "invalid character '%c'", yytext[0]); + yyerror(message); + + /* Suppress compiler warning messages */ + if (0) yyunput('c',NULL); + if (0) yy_switch_to_buffer(NULL); +} + +%% diff --git a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c new file mode 100644 index 000000000..201a32398 --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.c @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "dbtoolsIocRegister.h" +#include "dbLoadTemplate.h" + + +/* dbLoadTemplate */ +static const iocshArg dbLoadTemplateArg0 = {"filename", iocshArgString}; +static const iocshArg dbLoadTemplateArg1 = {"var=value", iocshArgString}; +static const iocshArg * const dbLoadTemplateArgs[2] = { + &dbLoadTemplateArg0, &dbLoadTemplateArg1 +}; +static const iocshFuncDef dbLoadTemplateFuncDef = + {"dbLoadTemplate", 2, dbLoadTemplateArgs}; +static void dbLoadTemplateCallFunc(const iocshArgBuf *args) +{ + dbLoadTemplate(args[0].sval, args[1].sval); +} + + +void dbtoolsIocRegister(void) +{ + iocshRegister(&dbLoadTemplateFuncDef, dbLoadTemplateCallFunc); +} diff --git a/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.h b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.h new file mode 100644 index 000000000..ef35b2dfa --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/dbtoolsIocRegister.h @@ -0,0 +1,23 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_dbtoolsIocRegister_H +#define INC_dbtoolsIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void dbtoolsIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_dbtoolsIocRegister_H */ diff --git a/modules/database/src/ioc/dbtemplate/msi.c b/modules/database/src/ioc/dbtemplate/msi.c new file mode 100644 index 000000000..5a5023163 --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/msi.c @@ -0,0 +1,895 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS Base is distributed subject to a Software License Agreement found +* in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* msi - macro substitutions and include */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MAX_BUFFER_SIZE 4096 +#define MAX_DEPS 1024 + +/* Module to read the template files */ +typedef struct inputData inputData; + +static void inputConstruct(inputData **ppvt); +static void inputDestruct(inputData *pvt); +static void inputAddPath(inputData *pvt, char *pval); +static void inputBegin(inputData *pvt, char *fileName); +static char *inputNextLine(inputData *pvt); +static void inputNewIncludeFile(inputData *pvt, char *name); +static void inputErrPrint(inputData *pvt); + +/* Module to read the substitution file */ +typedef struct subInfo subInfo; + +static void substituteOpen(subInfo **ppvt, char *substitutionName); +static void substituteDestruct(subInfo *pvt); +static int substituteGetNextSet(subInfo *pvt, char **filename); +static int substituteGetGlobalSet(subInfo *pvt); +static char *substituteGetReplacements(subInfo *pvt); +static char *substituteGetGlobalReplacements(subInfo *pvt); + +/* Forward references to local routines */ +static void usageExit(int status); +static void addMacroReplacements(MAC_HANDLE *macPvt, char *pval); +static void makeSubstitutions(inputData *inputPvt, MAC_HANDLE *macPvt, char *templateName); + +/*Global variables */ +static int opt_V = 0; +static int opt_D = 0; + +static char *outFile = 0; +static int numDeps = 0, depHashes[MAX_DEPS]; + + +int main(int argc,char **argv) +{ + inputData *inputPvt; + MAC_HANDLE *macPvt; + char *pval; + int narg; + char *substitutionName=0; + char *templateName=0; + int i; + int localScope = 1; + + inputConstruct(&inputPvt); + macCreateHandle(&macPvt,0); + while((argc>1) && (argv[1][0] == '-')) { + narg = (strlen(argv[1])==2) ? 2 : 1; + pval = (narg==1) ? (argv[1]+2) : argv[2]; + if(strncmp(argv[1],"-I",2)==0) { + inputAddPath(inputPvt,pval); + } else if (strcmp(argv[1], "-D") == 0) { + opt_D = 1; + narg = 1; /* no argument for this option */ + } else if(strncmp(argv[1],"-o",2)==0) { + outFile = epicsStrDup(pval); + } else if(strncmp(argv[1],"-M",2)==0) { + addMacroReplacements(macPvt,pval); + } else if(strncmp(argv[1],"-S",2)==0) { + substitutionName = epicsStrDup(pval); + } else if (strcmp(argv[1], "-V") == 0) { + opt_V = 1; + narg = 1; /* no argument for this option */ + } else if (strcmp(argv[1], "-g") == 0) { + localScope = 0; + narg = 1; /* no argument for this option */ + } else if (strcmp(argv[1], "-h") == 0) { + usageExit(0); + } else { + fprintf(stderr, "msi: Bad argument \"%s\"\n", argv[1]); + usageExit(1); + } + argc -= narg; + for(i=1; i2) { + fprintf(stderr,"msi: Too many arguments\n"); + usageExit(1); + } + if (opt_D) { + if (!outFile) { + fprintf(stderr, "msi: Option -D requires -o for Makefile target\n"); + exit(1); + } + printf("%s:", outFile); + } + else if (outFile && freopen(outFile, "w", stdout) == NULL) { + fprintf(stderr, "msi: Can't open %s for writing: %s\n", + outFile, strerror(errno)); + exit(1); + } + if(argc==2) { + templateName = epicsStrDup(argv[1]); + } + if(!substitutionName) { + makeSubstitutions(inputPvt,macPvt,templateName); + } else { + subInfo *substitutePvt; + char *filename = 0; + int isGlobal, isFile; + + substituteOpen(&substitutePvt,substitutionName); + do { + if ((isGlobal = substituteGetGlobalSet(substitutePvt))) { + pval = substituteGetGlobalReplacements(substitutePvt); + if(pval) { + addMacroReplacements(macPvt,pval); + } + } else if ((isFile = substituteGetNextSet(substitutePvt,&filename))) { + if(templateName) filename = templateName; + if(!filename) { + fprintf(stderr,"msi: No template file\n"); + usageExit(1); + } + while((pval = substituteGetReplacements(substitutePvt))){ + if (localScope) macPushScope(macPvt); + addMacroReplacements(macPvt,pval); + makeSubstitutions(inputPvt,macPvt,filename); + if (localScope) macPopScope(macPvt); + } + } + } while (isGlobal || isFile); + substituteDestruct(substitutePvt); + } + errlogFlush(); + macDeleteHandle(macPvt); + inputDestruct(inputPvt); + if (opt_D) { + printf("\n"); + } + free(templateName); + free(substitutionName); + return opt_V & 2; +} + +void usageExit(int status) +{ + fprintf(stderr, + "Usage: msi [options] [template]\n" + " stdin is used if neither template nor substitution file is given\n" + " options:\n" + " -h Print this help message\n" + " -D Output file dependencies, not substitutions\n" + " -V Undefined macros generate an error\n" + " -g All macros have global scope\n" + " -o Send output to \n" + " -I Add to include file search path\n" + " -M Add to (global) macro definitions\n" + " ( takes the form VAR=VALUE,...)\n" + " -S Expand the substitutions in FILE\n"); + exit(status); +} + +static void addMacroReplacements(MAC_HANDLE *macPvt,char *pval) +{ + char **pairs; + long status; + + status = macParseDefns(macPvt,pval,&pairs); + if(status==-1) { + fprintf(stderr,"msi: Error from macParseDefns\n"); + usageExit(1); + } + if(status) { + status = macInstallMacros(macPvt,pairs); + if(!status) { + fprintf(stderr,"Error from macInstallMacros\n"); + usageExit(1); + } + free(pairs); + } +} + +typedef enum {cmdInclude,cmdSubstitute} cmdType; +static const char *cmdNames[] = {"include","substitute"}; + +static void makeSubstitutions(inputData *inputPvt, MAC_HANDLE *macPvt, char *templateName) +{ + char *input; + static char buffer[MAX_BUFFER_SIZE]; + int n; + + inputBegin(inputPvt,templateName); + while((input = inputNextLine(inputPvt))) { + int expand=1; + char *p; + char *command = 0; + + p = input; + /*skip whitespace at beginning of line*/ + while(*p && (isspace((int) *p))) ++p; + /*Look for i or s */ + if(*p && (*p=='i' || *p=='s')) command = p; + if(command) { + char *pstart; + char *pend; + char *copy; + int cmdind=-1; + int i; + + for(i=0; i< NELEMENTS(cmdNames); i++) { + if(strstr(command,cmdNames[i])) { + cmdind = i; + } + } + if(cmdind<0) goto endif; + p = command + strlen(cmdNames[cmdind]); + /*skip whitespace after command*/ + while(*p && (isspace((int) *p))) ++p; + /*Next character must be quote*/ + if((*p==0) || (*p!='"')) goto endif; + pstart = ++p; + /*Look for end quote*/ + while(*p && (*p!='"')) { + /*allow escape for imbeded quote*/ + if((*p=='\\') && *(p+1)=='"') { + p += 2; continue; + } else { + if(*p=='"') break; + } + ++p; + } + pend = p; + if(*p==0) goto endif; + /*skip quote and any trailing blanks*/ + while(*++p==' ') ; + if(*p != '\n' && *p !=0) goto endif; + copy = calloc(pend-pstart+1,sizeof(char)); + strncpy(copy,pstart,pend-pstart); + switch(cmdind) { + case cmdInclude: + inputNewIncludeFile(inputPvt,copy); + break; + case cmdSubstitute: + addMacroReplacements(macPvt,copy); + break; + default: + fprintf(stderr,"msi: Logic error in makeSubstitutions\n"); + inputErrPrint(inputPvt); + exit(1); + } + free(copy); + expand = 0; + } +endif: + if (expand && !opt_D) { + n = macExpandString(macPvt,input,buffer,MAX_BUFFER_SIZE-1); + fputs(buffer,stdout); + if (opt_V == 1 && n < 0) { + fprintf(stderr,"msi: Error - undefined macros present\n"); + opt_V++; + } + } + } +} + +typedef struct inputFile{ + ELLNODE node; + char *filename; + FILE *fp; + int lineNum; +}inputFile; + +typedef struct pathNode { + ELLNODE node; + char *directory; +} pathNode; + +struct inputData { + ELLLIST inputFileList; + ELLLIST pathList; + char inputBuffer[MAX_BUFFER_SIZE]; +}; + +static void inputOpenFile(inputData *pinputData,char *filename); +static void inputCloseFile(inputData *pinputData); +static void inputCloseAllFiles(inputData *pinputData); + +static void inputConstruct(inputData **ppvt) +{ + inputData *pinputData; + + pinputData = calloc(1,sizeof(inputData)); + ellInit(&pinputData->inputFileList); + ellInit(&pinputData->pathList); + *ppvt = pinputData; +} + +static void inputDestruct(inputData *pinputData) +{ + pathNode *ppathNode; + + inputCloseAllFiles(pinputData); + while((ppathNode = (pathNode *)ellFirst(&pinputData->pathList))) { + ellDelete(&pinputData->pathList,&ppathNode->node); + free(ppathNode->directory); + free(ppathNode); + } + free(pinputData); +} + +static void inputAddPath(inputData *pinputData, char *path) +{ + ELLLIST *ppathList = &pinputData->pathList; + pathNode *ppathNode; + const char *pcolon; + const char *pdir; + size_t len; + int emptyName; + const char sep = *OSI_PATH_LIST_SEPARATOR; + + pdir = path; + /*an empty name at beginning, middle, or end means current directory*/ + while(pdir && *pdir) { + emptyName = ((*pdir == sep) ? 1 : 0); + if(emptyName) ++pdir; + ppathNode = (pathNode *)calloc(1,sizeof(pathNode)); + ellAdd(ppathList,&ppathNode->node); + if(!emptyName) { + pcolon = strchr(pdir,sep); + len = (pcolon ? (pcolon - pdir) : strlen(pdir)); + if(len>0) { + ppathNode->directory = (char *)calloc(len+1,sizeof(char)); + strncpy(ppathNode->directory,pdir,len); + pdir = pcolon; + /*unless at end skip past first colon*/ + if(pdir && *(pdir+1)!=0) ++pdir; + } else { /*must have been trailing : */ + emptyName=1; + } + } + if(emptyName) { + ppathNode->directory = (char *)calloc(2,sizeof(char)); + strcpy(ppathNode->directory,"."); + } + } + return; +} + +static void inputBegin(inputData *pinputData, char *fileName) +{ + inputCloseAllFiles(pinputData); + inputOpenFile(pinputData,fileName); +} + +static char *inputNextLine(inputData *pinputData) +{ + inputFile *pinputFile; + char *pline; + + while((pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList))) { + pline = fgets(pinputData->inputBuffer,MAX_BUFFER_SIZE,pinputFile->fp); + if(pline) { + ++pinputFile->lineNum; + return(pline); + } + inputCloseFile(pinputData); + } + return(0); +} + +static void inputNewIncludeFile(inputData *pinputData, char *name) +{ + inputOpenFile(pinputData,name); +} + +static void inputErrPrint(inputData *pinputData) +{ + inputFile *pinputFile; + + fprintf(stderr,"input: '%s' at ",pinputData->inputBuffer); + pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList); + while(pinputFile) { + fprintf(stderr,"line %d of ",pinputFile->lineNum); + if(pinputFile->filename) { + fprintf(stderr," file %s\n",pinputFile->filename); + } else { + fprintf(stderr,"stdin:\n"); + } + pinputFile = (inputFile *)ellNext(&pinputFile->node); + if(pinputFile) { + fprintf(stderr," included from "); + } else { + fprintf(stderr,"\n"); + } + } + fprintf(stderr,"\n"); +} + +static void inputOpenFile(inputData *pinputData,char *filename) +{ + ELLLIST *ppathList = &pinputData->pathList; + pathNode *ppathNode = 0; + inputFile *pinputFile; + char *fullname = 0; + FILE *fp = 0; + + if(!filename) { + fp = stdin; + } else if((ellCount(ppathList)==0) || strchr(filename,'/')){ + fp = fopen(filename,"r"); + } else { + ppathNode = (pathNode *)ellFirst(ppathList); + while(ppathNode) { + fullname = calloc(strlen(filename)+strlen(ppathNode->directory) +2, + sizeof(char)); + strcpy(fullname,ppathNode->directory); + strcat(fullname,"/"); + strcat(fullname,filename); + fp = fopen(fullname,"r"); + if(fp) break; + free(fullname); + ppathNode = (pathNode *)ellNext(&ppathNode->node); + } + } + if(!fp) { + fprintf(stderr,"msi: Can't open file '%s'\n",filename); + inputErrPrint(pinputData); + exit(1); + } + pinputFile = calloc(1,sizeof(inputFile)); + if(ppathNode) { + pinputFile->filename = fullname; + } else if(filename) { + pinputFile->filename = epicsStrDup(filename); + } else { + pinputFile->filename = epicsStrDup("stdin"); + } + + if (opt_D) { + int hash = epicsStrHash(pinputFile->filename, 12345); + int i = 0; + int match = 0; + + while (i < numDeps) { + if (hash == depHashes[i++]) { + match = 1; + break; + } + } + if (!match) { + const char *wrap = numDeps ? " \\\n" : ""; + + printf("%s %s", wrap, pinputFile->filename); + if (numDeps < MAX_DEPS) { + depHashes[numDeps++] = hash; + } + else { + fprintf(stderr, "msi: More than %d dependencies!\n", MAX_DEPS); + depHashes[0] = hash; + } + } + } + + pinputFile->fp = fp; + ellInsert(&pinputData->inputFileList,0,&pinputFile->node); +} + +static void inputCloseFile(inputData *pinputData) +{ + inputFile *pinputFile; + + pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList); + if(!pinputFile) return; + ellDelete(&pinputData->inputFileList,&pinputFile->node); + if(fclose(pinputFile->fp)) + fprintf(stderr,"msi: Can't close input file '%s'\n",pinputFile->filename); + free(pinputFile->filename); + free(pinputFile); +} + +static void inputCloseAllFiles(inputData *pinputData) +{ + inputFile *pinputFile; + + while((pinputFile=(inputFile *)ellFirst(&pinputData->inputFileList))){ + inputCloseFile(pinputData); + } +} + +/*start of code that handles substitution file*/ +typedef enum { + tokenLBrace,tokenRBrace,tokenSeparater,tokenString,tokenEOF +}tokenType; + +typedef struct subFile { + char *substitutionName; + FILE *fp; + int lineNum; + char inputBuffer[MAX_BUFFER_SIZE]; + char *pnextChar; + tokenType token; + char string[MAX_BUFFER_SIZE]; +} subFile; + +typedef struct patternNode { + ELLNODE node; + char *var; +} patternNode; + +struct subInfo { + subFile *psubFile; + int isFile; + char *filename; + int isPattern; + ELLLIST patternList; + size_t size; + size_t curLength; + char *macroReplacements; +}; + +static char *subGetNextLine(subFile *psubFile); +static tokenType subGetNextToken(subFile *psubFile); +static void subFileErrPrint(subFile *psubFile,char * message); +static void freeSubFile(subInfo *psubInfo); +static void freePattern(subInfo *psubInfo); +static void catMacroReplacements(subInfo *psubInfo,const char *value); + +void freeSubFile(subInfo *psubInfo) +{ + subFile *psubFile = psubInfo->psubFile; + if(psubFile->fp) { + if(fclose(psubFile->fp)) + fprintf(stderr,"msi: Can't close substitution file\n"); + } + free(psubFile); + free(psubInfo->filename); + psubInfo->psubFile = 0; +} + +void freePattern(subInfo *psubInfo) +{ + patternNode *ppatternNode; + while((ppatternNode = (patternNode *)ellFirst(&psubInfo->patternList))) { + ellDelete(&psubInfo->patternList,&ppatternNode->node); + free(ppatternNode->var); + free(ppatternNode); + } + psubInfo->isPattern = 0; +} + +static void substituteDestruct(subInfo *psubInfo) +{ + freeSubFile(psubInfo); + freePattern(psubInfo); + free(psubInfo); + return; +} + +static void substituteOpen(subInfo **ppvt,char *substitutionName) +{ + subInfo *psubInfo; + subFile *psubFile; + FILE *fp; + + psubInfo = calloc(1,sizeof(subInfo)); + *ppvt = psubInfo; + psubFile = calloc(1,sizeof(subFile)); + psubInfo->psubFile = psubFile; + ellInit(&psubInfo->patternList); + fp = fopen(substitutionName,"r"); + if(!fp) { + fprintf(stderr,"msi: Can't open file '%s'\n",substitutionName); + exit(1); + } + psubFile->substitutionName = substitutionName; + psubFile->fp = fp; + psubFile->lineNum = 1; + psubFile->inputBuffer[0] = 0; + psubFile->pnextChar = &psubFile->inputBuffer[0]; + subGetNextToken(psubFile); + return; +} + +static int substituteGetGlobalSet(subInfo *psubInfo) +{ + subFile *psubFile = psubInfo->psubFile; + + while(psubFile->token==tokenSeparater) subGetNextToken(psubFile); + if(psubFile->token==tokenString && strcmp(psubFile->string,"global")==0) { + subGetNextToken(psubFile); + return(1); + } + return(0); +} + +static int substituteGetNextSet(subInfo *psubInfo,char **filename) +{ + subFile *psubFile = psubInfo->psubFile; + patternNode *ppatternNode; + + *filename = 0; + while(psubFile->token==tokenSeparater) subGetNextToken(psubFile); + if(psubFile->token==tokenEOF) return(0); + if(psubFile->token==tokenString && strcmp(psubFile->string,"file")==0) { + psubInfo->isFile = 1; + if(subGetNextToken(psubFile)!=tokenString) { + subFileErrPrint(psubFile,"Parse error, expecting filename"); + exit(1); + } + freePattern(psubInfo); + free(psubInfo->filename); + if(psubFile->string[0]=='"'&&psubFile->string[strlen(psubFile->string)-1]=='"') { + psubFile->string[strlen(psubFile->string)-1]='\0'; + psubInfo->filename = macEnvExpand(psubFile->string+1); + } + else { + psubInfo->filename = macEnvExpand(psubFile->string); + } + while(subGetNextToken(psubFile)==tokenSeparater); + if(psubFile->token!=tokenLBrace) { + subFileErrPrint(psubFile,"Parse error, expecting {"); + exit(1); + } + subGetNextToken(psubFile); + } + *filename = psubInfo->filename; + while(psubFile->token==tokenSeparater) subGetNextToken(psubFile); + if(psubFile->token==tokenLBrace) return(1); + if(psubFile->token==tokenRBrace) return(1); + if(psubFile->token!=tokenString + || strcmp(psubFile->string,"pattern")!=0) { + subFileErrPrint(psubFile,"Parse error, expecting pattern"); + exit(1); + } + freePattern(psubInfo); + psubInfo->isPattern = 1; + while(subGetNextToken(psubFile)==tokenSeparater); + if(psubFile->token!=tokenLBrace) { + subFileErrPrint(psubFile,"Parse error, expecting {"); + exit(1); + } + while(1) { + while(subGetNextToken(psubFile)==tokenSeparater); + if(psubFile->token!=tokenString) break; + ppatternNode = calloc(1,sizeof(patternNode)); + ellAdd(&psubInfo->patternList,&ppatternNode->node); + ppatternNode->var = epicsStrDup(psubFile->string); + } + if(psubFile->token!=tokenRBrace) { + subFileErrPrint(psubFile,"Parse error, expecting }"); + exit(1); + } + subGetNextToken(psubFile); + return(1); +} + +static char *substituteGetGlobalReplacements(subInfo *psubInfo) +{ + subFile *psubFile = psubInfo->psubFile; + + if(psubInfo->macroReplacements) psubInfo->macroReplacements[0] = 0; + psubInfo->curLength = 0; + while(psubFile->token==tokenSeparater) subGetNextToken(psubFile); + if(psubFile->token==tokenRBrace && psubInfo->isFile) { + psubInfo->isFile = 0; + free(psubInfo->filename); + psubInfo->filename = 0; + freePattern(psubInfo); + subGetNextToken(psubFile); + return(0); + } + if(psubFile->token==tokenEOF) return(0); + if(psubFile->token!=tokenLBrace) return(0); + while(1) { + switch(subGetNextToken(psubFile)) { + case tokenRBrace: + subGetNextToken(psubFile); + if (!psubInfo->macroReplacements) { + catMacroReplacements(psubInfo,""); + } + return(psubInfo->macroReplacements); + case tokenSeparater: + catMacroReplacements(psubInfo,","); + break; + case tokenString: + catMacroReplacements(psubInfo,psubFile->string); + break; + default: + subFileErrPrint(psubFile,"Parse error, illegal token"); + exit(1); + } + } +} + +static char *substituteGetReplacements(subInfo *psubInfo) +{ + subFile *psubFile = psubInfo->psubFile; + patternNode *ppatternNode; + + if(psubInfo->macroReplacements) psubInfo->macroReplacements[0] = 0; + psubInfo->curLength = 0; + while(psubFile->token==tokenSeparater) subGetNextToken(psubFile); + if(psubFile->token==tokenRBrace && psubInfo->isFile) { + psubInfo->isFile = 0; + free(psubInfo->filename); + psubInfo->filename = 0; + freePattern(psubInfo); + subGetNextToken(psubFile); + return(0); + } + if(psubFile->token==tokenEOF) return(0); + if(psubFile->token!=tokenLBrace) return(0); + if(psubInfo->isPattern) { + int gotFirstPattern = 0; + + while(subGetNextToken(psubFile)==tokenSeparater); + ppatternNode = (patternNode *)ellFirst(&psubInfo->patternList); + while(1) { + if(psubFile->token==tokenRBrace) { + subGetNextToken(psubFile); + return(psubInfo->macroReplacements); + } + if(psubFile->token!=tokenString) { + subFileErrPrint(psubFile,"Parse error, illegal token"); + exit(-1); + } + if(gotFirstPattern) catMacroReplacements(psubInfo,","); + gotFirstPattern = 1; + if(ppatternNode) { + catMacroReplacements(psubInfo,ppatternNode->var); + catMacroReplacements(psubInfo,"="); + catMacroReplacements(psubInfo,psubFile->string); + ppatternNode = (patternNode *)ellNext(&ppatternNode->node); + } else { + subFileErrPrint(psubFile,"Warning, too many values given"); + } + while(subGetNextToken(psubFile)==tokenSeparater); + } + } else while(1) { + switch(subGetNextToken(psubFile)) { + case tokenRBrace: + subGetNextToken(psubFile); + if (!psubInfo->macroReplacements) { + catMacroReplacements(psubInfo,""); + } + return(psubInfo->macroReplacements); + case tokenSeparater: + catMacroReplacements(psubInfo,","); + break; + case tokenString: + catMacroReplacements(psubInfo,psubFile->string); + break; + default: + subFileErrPrint(psubFile,"Parse error, illegal token"); + exit(1); + } + } +} + +static char *subGetNextLine(subFile *psubFile) +{ + char *pline; + + do { + pline = fgets(psubFile->inputBuffer,MAX_BUFFER_SIZE,psubFile->fp); + ++psubFile->lineNum; + } while(pline && psubFile->inputBuffer[0]=='#'); + if(!pline) { + psubFile->token = tokenEOF; + psubFile->inputBuffer[0] = 0; + psubFile->pnextChar = 0; + return(0); + } + psubFile->pnextChar = &psubFile->inputBuffer[0]; + return(&psubFile->inputBuffer[0]); +} + +static void subFileErrPrint(subFile *psubFile,char * message) +{ + fprintf(stderr,"msi: %s\n",message); + fprintf(stderr," in substitution file '%s' at line %d:\n %s", + psubFile->substitutionName, + psubFile->lineNum,psubFile->inputBuffer); +} + + +static tokenType subGetNextToken(subFile *psubFile) +{ + char *p; + char *pto; + + p = psubFile->pnextChar; + if(!p) { psubFile->token = tokenEOF; return(tokenEOF);} + if(*p==0 || *p=='\n' || *p=='#') { + p = subGetNextLine(psubFile); + if(!p) { psubFile->token = tokenEOF; return(tokenEOF);} + else { psubFile->token = tokenSeparater; return(tokenSeparater);} + } + while(isspace((int) *p)) p++; + if(*p=='{') { + psubFile->token = tokenLBrace; + psubFile->pnextChar = ++p; + return(tokenLBrace); + } + if(*p=='}') { + psubFile->token = tokenRBrace; + psubFile->pnextChar = ++p; + return(tokenRBrace); + } + if(*p==0 || isspace((int) *p) || *p==',') { + while (isspace((int) *p) || *p==',') p++; + psubFile->token = tokenSeparater; + psubFile->pnextChar = p; + return(tokenSeparater); + } + /*now handle quoted strings*/ + if(*p=='"') { + pto = &psubFile->string[0]; + *pto++ = *p++; + while(*p!='"') { + if(*p==0 || *p=='\n') { + subFileErrPrint(psubFile,"Strings must be on single line\n"); + exit(1); + } + /*allow escape for imbeded quote*/ + if((*p=='\\') && *(p+1)=='"') { + *pto++ = *p++; + *pto++ = *p++; + continue; + } + *pto++ = *p++; + } + *pto++ = *p++; + psubFile->pnextChar = p; + *pto = 0; + psubFile->token = tokenString; + return(tokenString); + } + /*Now take anything up to next non String token and not space*/ + pto = &psubFile->string[0]; + while(!isspace((int) *p) && (strspn(p,"\",{}")==0)) *pto++ = *p++; + *pto = 0; + psubFile->pnextChar = p; + psubFile->token = tokenString; + return(tokenString); +} + +static void catMacroReplacements(subInfo *psubInfo,const char *value) +{ + size_t len = strlen(value); + + if(psubInfo->size <= (psubInfo->curLength + len)) { + size_t newsize = psubInfo->size + MAX_BUFFER_SIZE; + char *newbuf; + + if(newsize <= psubInfo->curLength + len) + newsize = psubInfo->curLength + len + 1; + newbuf = calloc(1,newsize); + if(!newbuf) { + fprintf(stderr,"calloc failed for size %lu\n", + (unsigned long) newsize); + exit(1); + } + if(psubInfo->macroReplacements) { + memcpy(newbuf,psubInfo->macroReplacements,psubInfo->curLength); + free(psubInfo->macroReplacements); + } + psubInfo->size = newsize; + psubInfo->macroReplacements = newbuf; + } + strcat(psubInfo->macroReplacements,value); + psubInfo->curLength += len; +} diff --git a/modules/database/src/ioc/dbtemplate/msi.html b/modules/database/src/ioc/dbtemplate/msi.html new file mode 100644 index 000000000..ff4341ead --- /dev/null +++ b/modules/database/src/ioc/dbtemplate/msi.html @@ -0,0 +1,445 @@ + + + + + + + + + +

msi: Macro Substitution and Include Tool

+ +

Introduction

+ +

msi is a general purpose macro substitution/include tool. It accepts as input +an ascii template file. It looks for lines containing two reserved command +names: include and substitute. It also looks for and performs +substitutions on macros of the form $(var) and ${var}. It uses the macLib +routines from EPICS Base to perform the substitutions, so it also accepts the +default value and value definition syntax that macLib implements.

+ +

msi also allows substitutions to be specified via a separate substitution +file. This substitution file allows the same format as the substitution files +accepted by the EPICS IOC's dbLoadTemplate command.

+ +

Command Syntax:

+ +
msi -V -g -D -ooutfile -Idir -Msubs -Ssubfile template
+ +

All parameters are optional. The -o, -I, -M, and -S switches may be +separated from their associated value string by spaces if desired. Output will +be written to stdout unless the -o option is given.

+ +

Switches have the following meanings:

+ +
+
-V
+
Verbose warnings; if this parameter is specified then any undefined + macro discovered in the template file which does not have an associated + default value is considered an error. An error message is generated, and + when msi terminates it will do so with an exit status of 2.
+ +
-g
+
When this flag is given all macros defined in a substitution file will + have global scope and thus their values will persist until a new value is + given for this macro. This flag is provided for backwards compatibility as + this was the behavior of previous versions of msi, but it does not follow + common scoping rules and is discouraged.
+ +
-D
+
Output dependency information suitable for including by a Makefile to + stdout instead of performing the macro substitutions. The -o option + must be given to specify the target name for the dependency rules. Other + options should be given exactly as will be used in the macro substitution + process.
+ +
-o file
+
Output will be written to the specifed file rather than to the standard + output.
+ +
-I dir
+
This parameter, which may be repeated or contain a colon-separated (or + semi-colon separated on Windows) list of directory paths, specifies a search + path for include commands. For example: + +
+
msi -I /home/mrk/examples:. -I.. template
+
+ + specifies that all named files should be searched for in the following + locations in the order given: + +
    +
  1. /home/mrk/examples
  2. +
  3. . (the current directory)
  4. +
  5. .. (the parent of the current directory)
  6. +
+
+ +
-M substitutions
+
This parameter specifies macro values for the template instance. + Multiple macro values can be specified in one substitution parameter, or in + multiple -M parameters. For example: + +
+
msi -M "a=aval,b=bval" -Mc=cval template
+
+ + specifies that in the template file each occurrence of: + +
+
$(a) or ${a} is replaced by aval
+
$(b) or ${b} is replaced by bval
+
$(c) or ${c} is replaced by cval
+
+
+ +
-S subfile
+
The substitution file. See below for format.
+ +
template
+
The input file. If no file is specified then input is taken from + stdin, i.e. msi can be used as a filter. See below for a description of + commands that can be embedded in the template file.
+
+ +

It is not possible to display usage by just typing msi since +executing the command with no arguments is a valid command. To show usage +specify an illegal switch, e.g.

+ +
+
msi -help
+
+ +

Exit Status

+ +
+
0
Success. +
1
Can't open/create file, or other I/O error. +
2
Undefined macros encountered with the -V option specified. +
+ +

Template File Format

+ +

This file contains the text to be read and written to the output after macro +substitution is performed. If no file is given then input is read from stdin. +Variable instances to be substituted by macro values are expressed in the +template using the syntax $(name) or +${name}. The template can also provide default values +to be used when a macro has not been given a value, using the syntax +$(name=default) or +${name=default}.

+ +

For example, using the command

+ +
+
msi -M name=Marty template
+
+ +

where the file template contains

+ +
+
My name is $(name)
+My age is $(age=none of your business)
+
+ +

results in this output:

+ +
+
My name is Marty
+My age is none of your business
+
+ +

Macro variables and their default values can be expressed in terms of other +macros if necessary, to almost any level of complexity. Recursive definitions +will generate warning messages on stderr and result in undefined output.

+ +

The template file is read and processed one line at a time, where the +maximum length of a line before and/or after macro expansion is 1023 characters +— longer input or output lines will cause msi to fail. Within the context +of a single line, macro expansion does not occur when the variable instance +appears inside a single-quoted string, or where the dollar sign $ is +preceded by a back-slash character \, but as with the standard Unix +shells, variables inside double quoted strings are expanded properly.

+ +

However neither back-slash characters nor quotes of either variety are +removed when generating the output file, so depending on what is being output +the single quote behaviour may not be useful and may even be a hinderance. It +cannot be disabled in the current version of msi.

+ +

Template file commands

+ +

In addition to the regular text and variable instances described above, the +template file may also contain commands which allow the insertion of other +template files and the ability to set macro values inside the template file +itself. These commands are:

+ +
+
include "file"
+substitute "var=value,var=value,..."
+
+ +

Lines containing commands must be in one of these forms:

+ +
    +
  • include "filename"
  • +
  • substitute "name1=value1, name2=value2, ..."
  • +
+ +

White space is allowed before and after the command verb, and after the +quoted string. If embedded quotes are needed, the backslash character +\ can be used as an escape character. For example

+ +
+
substitute "a=\"val\""
+
+ +

specifies that (unless a is subsequently redefined) wherever a +$(a) macro appears in the template below this point, the text +"val" (including the double quote characters) will appear in the +output instead.

+ +

If a line does match either syntax above it is just passed to macLib for +processing without any notification. Thus the input line:

+ +
+
include "myfile" #include file
+
+ +

would just be passed to macLib, i.e. it would not be considered an +include command.

+ +

As an example of these commands, let the Unix command be:

+ +
+
msi template
+
+ +

and file includeFile contain:

+ +
+
first name is ${first}
+family name is ${family}
+
+ +

and template is

+ +
+
substitute "first=Marty,family=Kraimer"
+include "includeFile"
+substitute "first=Irma,family=Kraimer"
+include "includeFile"
+
+ +

then the following is written to the output.

+ +
+
first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+ +

Note that the IOC's dbLoadTemplate command does not support the +substitute syntax in template files, although the include +syntax is supported.

+ +

Substitution File Format

+ +

The optional substitution file has three formats: regular, pattern, and +dbTemplate format. We will discuss each separately.

+ +

Regular format

+ +
+
global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+{var1=set1_val1, var2=set1_val2, ...}
+{var2=set2_val2, var1=set2_val1, ...}
+global {gbl_var1=gbl_val3, gbl_var2=gbl_val4, ...}
+{var1=set3_val1, var2=set3_val2, ...}
+{var2=set4_val2, var1=set4_val1, ...}
+
+ +

The template file is output with macro substitutions performed once for each +set of braces containing macro replacement values.

+ +

Pattern format

+ +
+
global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+pattern {var1, var2, ...}
+{set1_val1, set1_val2, ...}
+{set2_val1, set2_val2, ...}
+pattern {var2, var1, ...}
+global {gbl_var1=gbl_val3, gbl_var2=gbl_val4, ...}
+{set3_val2, set3_val1, ...}
+{set4_val2, set4_val2, ...}
+
+ +

This produces the same result as the regular format example above.

+ +

dbLoadTemplate Format

+ +

This format is an extension of the format accepted by the EPICS IOC command +dbLoadTemplate, and allows templates to be expanded on the host rather +by using dbLoadTemplate at IOC boot time.

+ +
+
global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+file templatefile {
+    pattern format or regular format
+}
+file "${WHERE}/template2" {
+    pattern format or regular format
+}
+
+ +

For the dbTemplate format, the template filename does not have to be given +on the command line, and is usually specified in the substitutions file +instead. If a template filename is given on the command line it will override +the filenames listed in the substitutions files.

+ +

Syntax for all formats

+ +

A comment line may appear anywhere in a substitution file, and will be +ignored. A comment line is any line beginning with the character #, +which must be the very first character on the line.

+ +

Global definitions may supplement or override the macro values supplied on +the command-line using the -M switch, and set default values that will +survive for the remainder of the file unless another global definition of the +same macro changes it.

+ +

For definitions within braces given in any of the file formats, a separator +must be given between items. A separator is either a comma, or one or more of +the standard white space characters (space, formfeed, newline, carriage return, +tab or vertical tab).

+ +

Each item within braces can be an alphanumeric token, or a double-quoted +string. A back-slash character \ can be used to escape a quote +character needed inside a quoted string. These three sets of substitutions are +all equivalent:

+ +
+
{a=aa b=bb c="\"cc\""}
+{b="bb",a=aa,c="\"cc\""}
+{
+    c="\"cc\""
+    b=bb
+    a="aa"
+}
+
+ +

Within a substitutions file, the file name may appear inside double quotation +marks; these are required if the name contains certain characters or environment +variable macros of the form ${ENV_VAR} or $(ENV_VAR), which will be expanded +before the file is opened.

+ +

Regular substitution example

+ +

Let the command be:

+ +
+
msi -S substitute template
+
+ +

The file template contains

+ +
+
first name is ${first}
+family name is ${family}
+
+ +

and the file substitute is

+ +
+
global {family=Kraimer}
+{first=Marty}
+{first=Irma}
+
+ +

The following is the output produced:

+ +
+
first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+ +

Pattern substitution example

+ +

Let the command be:

+ +
+
msi -S pattern template
+
+ +

The file pattern contains

+ +
+
pattern {first,last}
+{Marty,Kraimer}
+{Irma,Kraimer}
+
+ +

and template is the same as the previous example:

+ +
+
first name is ${first}
+family name is ${family}
+
+ +

This is the output:

+ +
+
first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+ +

dbTemplate example

+Let the command be + +
+
msi -S xxx.substitutions
+
+ +xxx.substitutions is + +
+
file template {
+pattern {first,last}
+{Marty,Kraimer}
+{Irma,Kraimer}
+pattern {last,first}
+{Smith,Bill}
+{Smith,Mary}
+}
+file template {
+{first=Marty,last=Kraimer}
+{first=Irma,last=Kraimer}
+}
+
+template is the same as in the previous example.. + +

The following is written to the output

+ +
+
first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+first name is Bill
+last name is Smith
+first name is Mary
+last name is Smith
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+ + + diff --git a/modules/database/src/ioc/misc/Makefile b/modules/database/src/ioc/misc/Makefile new file mode 100644 index 000000000..e97efe15a --- /dev/null +++ b/modules/database/src/ioc/misc/Makefile @@ -0,0 +1,28 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/misc + +DBD += system.dbd +DBD += dlload.dbd +DBD += dbCore.dbd + +INC += epicsRelease.h +INC += iocInit.h +INC += miscIocRegister.h +INC += iocshRegisterCommon.h + +dbCore_SRCS += epicsRelease.c +dbCore_SRCS += iocInit.c +dbCore_SRCS += miscIocRegister.c +dbCore_SRCS += dlload.c +dbCore_SRCS += iocshRegisterCommon.c + diff --git a/modules/database/src/ioc/misc/dbCore.dbd b/modules/database/src/ioc/misc/dbCore.dbd new file mode 100644 index 000000000..921b3818e --- /dev/null +++ b/modules/database/src/ioc/misc/dbCore.dbd @@ -0,0 +1,28 @@ +# dbCore.dbd +# +# This file provides iocsh access to variables that control some lesser-used +# and debugging features of the IOC database code. + +# show epicsAtExit callbacks as they are run +variable(atExitDebug,int) + +# Access security subroutines +variable(asCaDebug,int) + +# CA server debug flag (very verbose) range[0,5] +variable(CASDEBUG,int) + +# Static database access variables +variable(dbRecordsOnceOnly,int) +variable(dbRecordsAbcSorted,int) +variable(dbBptNotMonotonic,int) +variable(dbQuietMacroWarnings,int) +variable(dbConvertStrict,int) + +# dbLoadTemplate settings +variable(dbTemplateMaxVars,int) +# Default number of parallel callback threads +variable(callbackParallelThreadsDefault,int) + +# Real-time operation +variable(dbThreadRealtimeLock,int) diff --git a/modules/database/src/ioc/misc/dlload.c b/modules/database/src/ioc/misc/dlload.c new file mode 100644 index 000000000..5b0591d5f --- /dev/null +++ b/modules/database/src/ioc/misc/dlload.c @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "epicsFindSymbol.h" +#include "iocsh.h" +#include "epicsExport.h" + +static const iocshArg dlloadArg0 = { "path/library.so", iocshArgString}; +static const iocshArg * const dlloadArgs[] = {&dlloadArg0}; +static const iocshFuncDef dlloadFuncDef = {"dlload", 1, dlloadArgs}; +static void dlloadCallFunc(const iocshArgBuf *args) +{ + if (!epicsLoadLibrary(args[0].sval)) { + printf("epicsLoadLibrary failed: %s\n", epicsLoadError()); + } +} + +static void dlloadRegistar(void) { + iocshRegister(&dlloadFuncDef, dlloadCallFunc); +} +epicsExportRegistrar(dlloadRegistar); diff --git a/modules/database/src/ioc/misc/dlload.dbd b/modules/database/src/ioc/misc/dlload.dbd new file mode 100644 index 000000000..740ebbbba --- /dev/null +++ b/modules/database/src/ioc/misc/dlload.dbd @@ -0,0 +1,3 @@ +# Including this DBD file adds the 'dlload' command to the IOC shell. + +registrar(dlloadRegistar) diff --git a/modules/database/src/ioc/misc/epicsRelease.c b/modules/database/src/ioc/misc/epicsRelease.c new file mode 100644 index 000000000..5eb55960c --- /dev/null +++ b/modules/database/src/ioc/misc/epicsRelease.c @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (c) 2014 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include "compilerDependencies.h" +#include "epicsStdio.h" +#include "epicsVersion.h" + +#define epicsExportSharedSymbols +#include "epicsRelease.h" + +epicsShareFunc int coreRelease(void) +{ + printf ( "############################################################################\n" ); + printf ( "## %s\n", epicsReleaseVersion ); + printf ( "## %s\n", "EPICS Base built " __DATE__ ); + printf ( "############################################################################\n" ); + return 0; +} diff --git a/modules/database/src/ioc/misc/epicsRelease.h b/modules/database/src/ioc/misc/epicsRelease.h new file mode 100644 index 000000000..298a1403b --- /dev/null +++ b/modules/database/src/ioc/misc/epicsRelease.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* epicsRelease.h */ + +#ifndef INCepicsReleaseh +#define INCepicsReleaseh + +#ifdef __cplusplus +extern "C" { +#endif + +#include "shareLib.h" +epicsShareFunc int coreRelease(void); + +#ifdef __cplusplus +} +#endif + +#endif /*INCepicsReleaseh*/ + diff --git a/modules/database/src/ioc/misc/iocInit.c b/modules/database/src/ioc/misc/iocInit.c new file mode 100644 index 000000000..05bf7880e --- /dev/null +++ b/modules/database/src/ioc/misc/iocInit.c @@ -0,0 +1,745 @@ +/*************************************************************************\ +* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Original Author: Marty Kraimer + * Date: 06-01-91 + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "ellLib.h" +#include "envDefs.h" +#include "epicsExit.h" +#include "epicsGeneralTime.h" +#include "epicsPrint.h" +#include "epicsSignal.h" +#include "epicsThread.h" +#include "errMdef.h" +#include "iocsh.h" +#include "taskwd.h" + +#include "caeventmask.h" + +#include "epicsExport.h" /* defines epicsExportSharedSymbols */ +#include "alarm.h" +#include "asDbLib.h" +#include "callback.h" +#include "dbAccess.h" +#include "db_access_routines.h" +#include "dbAddr.h" +#include "dbBase.h" +#include "dbBkpt.h" +#include "dbCa.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbFldTypes.h" +#include "dbLock.h" +#include "dbNotify.h" +#include "dbScan.h" +#include "dbServer.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "devSup.h" +#include "drvSup.h" +#include "epicsRelease.h" +#include "initHooks.h" +#include "iocInit.h" +#include "link.h" +#include "menuConvert.h" +#include "menuPini.h" +#include "recGbl.h" +#include "recSup.h" +#include "registryDeviceSupport.h" +#include "registryDriverSupport.h" +#include "registryJLinks.h" +#include "registryRecordType.h" + +static enum { + iocVirgin, iocBuilding, iocBuilt, iocRunning, iocPaused, iocStopped +} iocState = iocVirgin; +static enum { + buildServers, buildIsolated +} iocBuildMode; + +/* define forward references*/ +static int checkDatabase(dbBase *pdbbase); +static void checkGeneralTime(void); +static void initDrvSup(void); +static void initRecSup(void); +static void initDevSup(void); +static void finishDevSup(void); +static void initDatabase(void); +static void initialProcess(void); +static void exitDatabase(void *dummy); + +/* + * Iterate through all record instances (but not aliases), + * calling a function for each one. + */ +typedef void (*recIterFunc)(dbRecordType *rtyp, dbCommon *prec, void *user); + +static void iterateRecords(recIterFunc func, void *user); + +int dbThreadRealtimeLock = 1; +epicsExportAddress(int, dbThreadRealtimeLock); + +/* + * Initialize EPICS on the IOC. + */ +int iocInit(void) +{ + return iocBuild() || iocRun(); +} + +static int iocBuild_1(void) +{ + if (iocState != iocVirgin && iocState != iocStopped) { + errlogPrintf("iocBuild: IOC can only be initialized from uninitialized or stopped state\n"); + return -1; + } + errlogInit(0); + initHookAnnounce(initHookAtIocBuild); + + if (!epicsThreadIsOkToBlock()) { + epicsThreadSetOkToBlock(1); + } + + errlogPrintf("Starting iocInit\n"); + if (checkDatabase(pdbbase)) { + errlogPrintf("iocBuild: Aborting, bad database definition (DBD)!\n"); + return -1; + } + epicsSignalInstallSigHupIgnore(); + initHookAnnounce(initHookAtBeginning); + + coreRelease(); + iocState = iocBuilding; + + checkGeneralTime(); + taskwdInit(); + callbackInit(); + initHookAnnounce(initHookAfterCallbackInit); + + return 0; +} + +static void prepareLinks(dbRecordType *rtyp, dbCommon *prec, void *junk) +{ + dbInitRecordLinks(rtyp, prec); +} + +static int iocBuild_2(void) +{ + initHookAnnounce(initHookAfterCaLinkInit); + + initDrvSup(); + initHookAnnounce(initHookAfterInitDrvSup); + + initRecSup(); + initHookAnnounce(initHookAfterInitRecSup); + + initDevSup(); + initHookAnnounce(initHookAfterInitDevSup); /* used by autosave pass 0 */ + + iterateRecords(prepareLinks, NULL); + + dbLockInitRecords(pdbbase); + initDatabase(); + dbBkptInit(); + initHookAnnounce(initHookAfterInitDatabase); /* used by autosave pass 1 */ + + finishDevSup(); + initHookAnnounce(initHookAfterFinishDevSup); + + scanInit(); + if (asInit()) { + errlogPrintf("iocBuild: asInit Failed.\n"); + return -1; + } + dbProcessNotifyInit(); + epicsThreadSleep(.5); + initHookAnnounce(initHookAfterScanInit); + + initialProcess(); + initHookAnnounce(initHookAfterInitialProcess); + return 0; +} + +static int iocBuild_3(void) +{ + initHookAnnounce(initHookAfterCaServerInit); + + iocState = iocBuilt; + initHookAnnounce(initHookAfterIocBuilt); + return 0; +} + +int iocBuild(void) +{ + int status; + + status = iocBuild_1(); + if (status) return status; + + dbCaLinkInit(); + + status = iocBuild_2(); + if (status) return status; + + dbInitServers(); + + status = iocBuild_3(); + + if (dbThreadRealtimeLock) + epicsThreadRealtimeLock(); + + if (!status) iocBuildMode = buildServers; + return status; +} + +int iocBuildIsolated(void) +{ + int status; + + status = iocBuild_1(); + if (status) return status; + + dbCaLinkInitIsolated(); + + status = iocBuild_2(); + if (status) return status; + + status = iocBuild_3(); + if (!status) iocBuildMode = buildIsolated; + return status; +} + +int iocRun(void) +{ + if (iocState != iocPaused && iocState != iocBuilt) { + errlogPrintf("iocRun: IOC not paused\n"); + return -1; + } + initHookAnnounce(initHookAtIocRun); + + /* Enable scan tasks and some driver support functions. */ + scanRun(); + dbCaRun(); + initHookAnnounce(initHookAfterDatabaseRunning); + if (iocState == iocBuilt) + initHookAnnounce(initHookAfterInterruptAccept); + + if (iocBuildMode == buildServers) { + dbRunServers(); + initHookAnnounce(initHookAfterCaServerRunning); + } + + if (iocState == iocBuilt) + initHookAnnounce(initHookAtEnd); + + errlogPrintf("iocRun: %s\n", iocState == iocBuilt ? + "All initialization complete" : + "IOC restarted"); + iocState = iocRunning; + initHookAnnounce(initHookAfterIocRunning); + return 0; +} + +int iocPause(void) +{ + if (iocState != iocRunning) { + errlogPrintf("iocPause: IOC not running\n"); + return -1; + } + initHookAnnounce(initHookAtIocPause); + + if (iocBuildMode == buildServers) { + dbPauseServers(); + initHookAnnounce(initHookAfterCaServerPaused); + } + + dbCaPause(); + scanPause(); + initHookAnnounce(initHookAfterDatabasePaused); + + iocState = iocPaused; + errlogPrintf("iocPause: IOC suspended\n"); + initHookAnnounce(initHookAfterIocPaused); + return 0; +} + +/* + * Database sanity checks + * + * This is not an attempt to sanity-check the whole .dbd file, only + * two menus normally get modified by users: menuConvert and menuScan. + * + * The menuConvert checks were added to flag problems with IOCs + * converted from 3.13.x, where the SLOPE choice didn't exist. + * + * The menuScan checks make sure the user didn't fiddle too much + * when creating new periodic scan choices. + */ + +static int checkDatabase(dbBase *pdbbase) +{ + const dbMenu *pMenu; + + if (!pdbbase) { + errlogPrintf("checkDatabase: No database definitions loaded.\n"); + return -1; + } + + pMenu = dbFindMenu(pdbbase, "menuConvert"); + if (!pMenu) { + errlogPrintf("checkDatabase: menuConvert not defined.\n"); + return -1; + } + if (pMenu->nChoice <= menuConvertLINEAR) { + errlogPrintf("checkDatabase: menuConvert has too few choices.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuConvertNO_CONVERSION], + "menuConvertNO_CONVERSION")) { + errlogPrintf("checkDatabase: menuConvertNO_CONVERSION doesn't match.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuConvertSLOPE], "menuConvertSLOPE")) { + errlogPrintf("checkDatabase: menuConvertSLOPE doesn't match.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuConvertLINEAR], "menuConvertLINEAR")) { + errlogPrintf("checkDatabase: menuConvertLINEAR doesn't match.\n"); + return -1; + } + + pMenu = dbFindMenu(pdbbase, "menuScan"); + if (!pMenu) { + errlogPrintf("checkDatabase: menuScan not defined.\n"); + return -1; + } + if (pMenu->nChoice <= menuScanI_O_Intr) { + errlogPrintf("checkDatabase: menuScan has too few choices.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuScanPassive], + "menuScanPassive")) { + errlogPrintf("checkDatabase: menuScanPassive doesn't match.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuScanEvent], + "menuScanEvent")) { + errlogPrintf("checkDatabase: menuScanEvent doesn't match.\n"); + return -1; + } + if (strcmp(pMenu->papChoiceName[menuScanI_O_Intr], + "menuScanI_O_Intr")) { + errlogPrintf("checkDatabase: menuScanI_O_Intr doesn't match.\n"); + return -1; + } + if (pMenu->nChoice <= SCAN_1ST_PERIODIC) { + errlogPrintf("checkDatabase: menuScan has no periodic choices.\n"); + return -1; + } + + return 0; +} + +static void checkGeneralTime(void) +{ + epicsTimeStamp ts; + + epicsTimeGetCurrent(&ts); + if (ts.secPastEpoch < 2*24*60*60) { + static const char * const tsfmt = "%Y-%m-%d %H:%M:%S.%09f"; + char buff[40]; + + epicsTimeToStrftime(buff, sizeof(buff), tsfmt, &ts); + errlogPrintf("iocInit: Time provider has not yet synchronized.\n"); + } + + epicsTimeGetEvent(&ts, 1); /* Prime gtPvt.lastEventProvider for ISRs */ +} + + +static void initDrvSup(void) /* Locate all driver support entry tables */ +{ + drvSup *pdrvSup; + + for (pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList); pdrvSup; + pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) { + struct drvet *pdrvet = registryDriverSupportFind(pdrvSup->name); + + if (!pdrvet) { + errlogPrintf("iocInit: driver %s not found\n", pdrvSup->name); + continue; + } + pdrvSup->pdrvet = pdrvet; + + if (pdrvet->init) + pdrvet->init(); + } +} + +static void initRecSup(void) +{ + dbRecordType *pdbRecordType; + + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + recordTypeLocation *precordTypeLocation = + registryRecordTypeFind(pdbRecordType->name); + rset *prset; + + if (!precordTypeLocation) { + errlogPrintf("iocInit record support for %s not found\n", + pdbRecordType->name); + continue; + } + prset = precordTypeLocation->prset; + pdbRecordType->prset = prset; + if (prset->init) { + prset->init(); + } + } +} + +static void initDevSup(void) +{ + dbRecordType *pdbRecordType; + + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + devSup *pdevSup; + + for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + pdevSup; + pdevSup = (devSup *)ellNext(&pdevSup->node)) { + struct dset *pdset = registryDeviceSupportFind(pdevSup->name); + + if (!pdset) { + errlogPrintf("device support %s not found\n",pdevSup->name); + continue; + } + dbInitDevSup(pdevSup, pdset); /* Calls pdset->init(0) */ + } + } +} + +static void finishDevSup(void) +{ + dbRecordType *pdbRecordType; + + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + devSup *pdevSup; + + for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); + pdevSup; + pdevSup = (devSup *)ellNext(&pdevSup->node)) { + struct dset *pdset = pdevSup->pdset; + + if (pdset && pdset->init) + pdset->init(1); + } + } +} + +static void iterateRecords(recIterFunc func, void *user) +{ + dbRecordType *pdbRecordType; + + for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); + pdbRecordType; + pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { + dbRecordNode *pdbRecordNode; + + for (pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList); + pdbRecordNode; + pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { + dbCommon *precord = pdbRecordNode->precord; + + if (!precord->name[0] || + pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) + continue; + + func(pdbRecordType, precord, user); + } + } + return; +} + +static void doInitRecord0(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + rset *prset = pdbRecordType->prset; + devSup *pdevSup; + + if (!prset) return; /* unlikely */ + + precord->rset = prset; + precord->mlok = epicsMutexMustCreate(); + ellInit(&precord->mlis); + + /* Reset the process active field */ + precord->pact = FALSE; + + /* Initial UDF severity */ + if (precord->udf && precord->stat == UDF_ALARM) + precord->sevr = precord->udfs; + + /* Init DSET NOTE that result may be NULL */ + pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); + precord->dset = pdevSup ? pdevSup->pdset : NULL; + + if (prset->init_record) + prset->init_record(precord, 0); +} + +static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + dbFldDes **papFldDes = pdbRecordType->papFldDes; + short *link_ind = pdbRecordType->link_ind; + int j; + + /* For all the links in the record type... */ + for (j = 0; j < pdbRecordType->no_links; j++) { + dbFldDes *pdbFldDes = papFldDes[link_ind[j]]; + DBLINK *plink = (DBLINK*)((char*)precord + pdbFldDes->offset); + + if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { + devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); + + if (pdevSup) { + struct dsxt *pdsxt = pdevSup->pdsxt; + if (pdsxt && pdsxt->add_record) { + pdsxt->add_record(precord); + } + } + } + + dbInitLink(plink, pdbFldDes->field_type); + } +} + +static void doInitRecord1(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + rset *prset = pdbRecordType->prset; + + if (!prset) return; /* unlikely */ + + if (prset->init_record) + prset->init_record(precord, 1); +} + +static void initDatabase(void) +{ + dbChannelInit(); + iterateRecords(doInitRecord0, NULL); + iterateRecords(doResolveLinks, NULL); + iterateRecords(doInitRecord1, NULL); + + epicsAtExit(exitDatabase, NULL); + return; +} + +/* + * Process database records at initialization ordered by phase + * if their pini (process at init) field is set. + */ +typedef struct { + int this; + int next; + epicsEnum16 pini; +} phaseData_t; + +static void doRecordPini(dbRecordType *rtype, dbCommon *precord, void *user) +{ + phaseData_t *pphase = (phaseData_t *)user; + int phas; + + if (precord->pini != pphase->pini) return; + + phas = precord->phas; + if (phas == pphase->this) { + dbScanLock(precord); + dbProcess(precord); + dbScanUnlock(precord); + } else if (phas > pphase->this && phas < pphase->next) + pphase->next = phas; +} + +static void piniProcess(int pini) +{ + phaseData_t phase; + phase.next = MIN_PHASE; + phase.pini = pini; + + /* This scans through the whole database as many times as needed. + * During the first pass it is unlikely to find any records with + * PHAS = MIN_PHASE, but during each iteration it looks for the + * phase value of the next pass to run. Note that PHAS fields can + * be changed at runtime, so we have to look for the lowest value + * of PHAS each time. + */ + do { + phase.this = phase.next; + phase.next = MAX_PHASE + 1; + iterateRecords(doRecordPini, &phase); + } while (phase.next != MAX_PHASE + 1); +} + +static void piniProcessHook(initHookState state) +{ + switch (state) { + case initHookAtIocRun: + piniProcess(menuPiniRUN); + break; + + case initHookAfterIocRunning: + piniProcess(menuPiniRUNNING); + break; + + case initHookAtIocPause: + piniProcess(menuPiniPAUSE); + break; + + case initHookAfterIocPaused: + piniProcess(menuPiniPAUSED); + break; + + default: + break; + } +} + +static void initialProcess(void) +{ + initHookRegister(piniProcessHook); + piniProcess(menuPiniYES); +} + + +/* + * set DB_LINK and CA_LINK to PV_LINK + * Delete record scans + */ +static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + devSup *pdevSup; + struct dsxt *pdsxt; + int j; + int locked = 0; + + for (j = 0; j < pdbRecordType->no_links; j++) { + dbFldDes *pdbFldDes = + pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; + DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + + if (plink->type == CA_LINK || + plink->type == JSON_LINK || + (plink->type == DB_LINK && iocBuildMode == buildIsolated)) { + if (!locked) { + dbScanLock(precord); + locked = 1; + } + dbRemoveLink(NULL, plink); + } + } + + if (precord->dset && + (pdevSup = dbDSETtoDevSup(pdbRecordType, precord->dset)) && + (pdsxt = pdevSup->pdsxt) && + pdsxt->del_record) { + if (!locked) { + dbScanLock(precord); + locked = 1; + } + scanDelete(precord); /* Being consistent... */ + pdsxt->del_record(precord); + } + if (locked) { + precord->pact = TRUE; + dbScanUnlock(precord); + } +} + +static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, + void *user) +{ + int j; + + for (j = 0; j < pdbRecordType->no_links; j++) { + dbFldDes *pdbFldDes = + pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; + DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); + + dbFreeLinkContents(plink); + } + + epicsMutexDestroy(precord->mlok); + free(precord->ppnr); /* may be allocated in dbNotify.c */ +} + +int iocShutdown(void) +{ + if (iocState == iocVirgin || iocState == iocStopped) + return 0; + + iterateRecords(doCloseLinks, NULL); + + if (iocBuildMode == buildIsolated) { + /* stop and "join" threads */ + scanStop(); + callbackStop(); + } + else + dbStopServers(); + + dbCaShutdown(); /* must be before dbFreeRecord and dbChannelExit */ + + if (iocBuildMode == buildIsolated) { + /* free resources */ + scanCleanup(); + callbackCleanup(); + + iterateRecords(doFreeRecord, NULL); + dbLockCleanupRecords(pdbbase); + + asShutdown(); + dbChannelExit(); + dbProcessNotifyExit(); + iocshFree(); + } + + iocState = iocStopped; + iocBuildMode = buildServers; + return 0; +} + +static void exitDatabase(void *dummy) +{ + iocShutdown(); +} diff --git a/modules/database/src/ioc/misc/iocInit.h b/modules/database/src/ioc/misc/iocInit.h new file mode 100644 index 000000000..24ae45e06 --- /dev/null +++ b/modules/database/src/ioc/misc/iocInit.h @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* iocInit.h ioc initialization */ + +#ifndef INCiocInith +#define INCiocInith + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int iocInit(void); +epicsShareFunc int iocBuild(void); +epicsShareFunc int iocBuildIsolated(void); +epicsShareFunc int iocRun(void); +epicsShareFunc int iocPause(void); +epicsShareFunc int iocShutdown(void); + +#ifdef __cplusplus +} +#endif + + +#endif /*INCiocInith*/ diff --git a/modules/database/src/ioc/misc/iocshRegisterCommon.c b/modules/database/src/ioc/misc/iocshRegisterCommon.c new file mode 100644 index 000000000..4f8ce6646 --- /dev/null +++ b/modules/database/src/ioc/misc/iocshRegisterCommon.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "envDefs.h" +#include "epicsVersion.h" +#include "iocsh.h" +#include "libComRegister.h" + +#define epicsExportSharedSymbols +#include "asIocRegister.h" +#include "dbAccess.h" +#include "dbIocRegister.h" +#include "dbStaticIocRegister.h" +#include "dbtoolsIocRegister.h" +#include "iocshRegisterCommon.h" +#include "miscIocRegister.h" +#include "registryIocRegister.h" + +#define quote(v) #v +#define str(v) quote(v) + +void iocshRegisterCommon(void) +{ + const char *targetArch = envGetConfigParamPtr(&EPICS_BUILD_TARGET_ARCH); + iocshPpdbbase = &pdbbase; + + /* This uses a config param so the user can override it */ + if (targetArch) { + epicsEnvSet("ARCH", targetArch); + } + + /* Base build version variables */ + epicsEnvSet("EPICS_VERSION_MAJOR", str(EPICS_VERSION)); + epicsEnvSet("EPICS_VERSION_MIDDLE", str(EPICS_REVISION)); + epicsEnvSet("EPICS_VERSION_MINOR", str(EPICS_MODIFICATION)); + epicsEnvSet("EPICS_VERSION_PATCH", str(EPICS_PATCH_LEVEL)); + epicsEnvSet("EPICS_VERSION_SNAPSHOT", EPICS_DEV_SNAPSHOT); + epicsEnvSet("EPICS_VERSION_SITE", EPICS_SITE_VERSION); + epicsEnvSet("EPICS_VERSION_SHORT", EPICS_VERSION_SHORT); + epicsEnvSet("EPICS_VERSION_FULL", EPICS_VERSION_FULL); + + dbStaticIocRegister(); + registryIocRegister(); + dbIocRegister(); + dbtoolsIocRegister(); + asIocRegister(); + miscIocRegister(); + libComRegister(); +} diff --git a/modules/database/src/ioc/misc/iocshRegisterCommon.h b/modules/database/src/ioc/misc/iocshRegisterCommon.h new file mode 100644 index 000000000..62a5d7c1b --- /dev/null +++ b/modules/database/src/ioc/misc/iocshRegisterCommon.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* iocshRegisterCommon.h */ +/* Author: Marty Kraimer Date: 27APR2000 */ + +#ifndef INCiocshRegisterCommonH +#define INCiocshRegisterCommonH + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* register many useful commands */ +epicsShareFunc void iocshRegisterCommon(void); + +#ifdef __cplusplus +} +#endif + +#endif /*INCiocshRegisterCommonH*/ diff --git a/modules/database/src/ioc/misc/miscIocRegister.c b/modules/database/src/ioc/misc/miscIocRegister.c new file mode 100644 index 000000000..233852ed2 --- /dev/null +++ b/modules/database/src/ioc/misc/miscIocRegister.c @@ -0,0 +1,88 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "iocsh.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocInit.h" +#include "epicsExport.h" +#include "epicsRelease.h" +#include "miscIocRegister.h" + +/* iocInit */ +static const iocshFuncDef iocInitFuncDef = {"iocInit",0,NULL}; +static void iocInitCallFunc(const iocshArgBuf *args) +{ + iocInit(); +} + +/* iocBuild */ +static const iocshFuncDef iocBuildFuncDef = {"iocBuild",0,NULL}; +static void iocBuildCallFunc(const iocshArgBuf *args) +{ + iocBuild(); +} + +/* iocRun */ +static const iocshFuncDef iocRunFuncDef = {"iocRun",0,NULL}; +static void iocRunCallFunc(const iocshArgBuf *args) +{ + iocRun(); +} + +/* iocPause */ +static const iocshFuncDef iocPauseFuncDef = {"iocPause",0,NULL}; +static void iocPauseCallFunc(const iocshArgBuf *args) +{ + iocPause(); +} + +/* coreRelease */ +static const iocshFuncDef coreReleaseFuncDef = {"coreRelease",0,NULL}; +static void coreReleaseCallFunc(const iocshArgBuf *args) +{ + coreRelease (); +} + + +void miscIocRegister(void) +{ + iocshRegister(&iocInitFuncDef,iocInitCallFunc); + iocshRegister(&iocBuildFuncDef,iocBuildCallFunc); + iocshRegister(&iocRunFuncDef,iocRunCallFunc); + iocshRegister(&iocPauseFuncDef,iocPauseCallFunc); + iocshRegister(&coreReleaseFuncDef, coreReleaseCallFunc); +} + + +/* system -- escape to system command interpreter. + * + * Disabled by default, for security reasons. To enable this command, add + * registrar(iocshSystemCommand) + * to an application dbd file. + */ +static const iocshArg systemArg0 = { "command string",iocshArgString}; +static const iocshArg * const systemArgs[] = {&systemArg0}; +static const iocshFuncDef systemFuncDef = {"system",1,systemArgs}; +static void systemCallFunc(const iocshArgBuf *args) +{ + system(args[0].sval); +} + +static void iocshSystemCommand(void) +{ + if (system(NULL)) + iocshRegister(&systemFuncDef, systemCallFunc); + else + errlogPrintf ("Can't register 'system' command -- no command interpreter available.\n"); +} +epicsExportRegistrar(iocshSystemCommand); diff --git a/modules/database/src/ioc/misc/miscIocRegister.h b/modules/database/src/ioc/misc/miscIocRegister.h new file mode 100644 index 000000000..78a54d620 --- /dev/null +++ b/modules/database/src/ioc/misc/miscIocRegister.h @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2007 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_miscIocRegister_H +#define INC_miscIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void miscIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_miscIocRegister_H */ diff --git a/modules/database/src/ioc/misc/system.dbd b/modules/database/src/ioc/misc/system.dbd new file mode 100644 index 000000000..e50c19820 --- /dev/null +++ b/modules/database/src/ioc/misc/system.dbd @@ -0,0 +1,3 @@ +# Including this DBD file adds a 'system' command to the IOC shell. + +registrar(iocshSystemCommand) diff --git a/modules/database/src/ioc/registry/Makefile b/modules/database/src/ioc/registry/Makefile new file mode 100644 index 000000000..a85320a9d --- /dev/null +++ b/modules/database/src/ioc/registry/Makefile @@ -0,0 +1,29 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/registry + +INC += registryRecordType.h +INC += registryDeviceSupport.h +INC += registryDriverSupport.h +INC += registryJLinks.h +INC += registryFunction.h +INC += registryCommon.h +INC += registryIocRegister.h + +dbCore_SRCS += registryRecordType.c +dbCore_SRCS += registryDeviceSupport.c +dbCore_SRCS += registryDriverSupport.c +dbCore_SRCS += registryJLinks.c +dbCore_SRCS += registryFunction.c +dbCore_SRCS += registryCommon.c +dbCore_SRCS += registryIocRegister.c + diff --git a/modules/database/src/ioc/registry/registryCommon.c b/modules/database/src/ioc/registry/registryCommon.c new file mode 100644 index 000000000..56664c5bb --- /dev/null +++ b/modules/database/src/ioc/registry/registryCommon.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* registryCommon.c */ + +/* Author: Andrew Johnson + * Date: 2004-03-19 + */ + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "registryCommon.h" +#include "registryDeviceSupport.h" +#include "registryDriverSupport.h" +#include "registryJLinks.h" + + +void registerRecordTypes(DBBASE *pbase, int nRecordTypes, + const char * const *recordTypeNames, const recordTypeLocation *rtl) +{ + int i; + for (i = 0; i < nRecordTypes; i++) { + recordTypeLocation *precordTypeLocation; + computeSizeOffset sizeOffset; + DBENTRY dbEntry; + + if (registryRecordTypeFind(recordTypeNames[i])) continue; + if (!registryRecordTypeAdd(recordTypeNames[i], &rtl[i])) { + errlogPrintf("registryRecordTypeAdd failed %s\n", + recordTypeNames[i]); + continue; + } + dbInitEntry(pbase,&dbEntry); + precordTypeLocation = registryRecordTypeFind(recordTypeNames[i]); + sizeOffset = precordTypeLocation->sizeOffset; + if (dbFindRecordType(&dbEntry, recordTypeNames[i])) { + errlogPrintf("registerRecordDeviceDriver failed %s\n", + recordTypeNames[i]); + } else { + sizeOffset(dbEntry.precordType); + } + } +} + +void registerDevices(DBBASE *pbase, int nDevices, + const char * const *deviceSupportNames, const dset * const *devsl) +{ + int i; + for (i = 0; i < nDevices; i++) { + if (registryDeviceSupportFind(deviceSupportNames[i])) continue; + if (!registryDeviceSupportAdd(deviceSupportNames[i], devsl[i])) { + errlogPrintf("registryDeviceSupportAdd failed %s\n", + deviceSupportNames[i]); + continue; + } + } +} + +void registerDrivers(DBBASE *pbase, int nDrivers, + const char * const * driverSupportNames, struct drvet * const *drvsl) +{ + int i; + for (i = 0; i < nDrivers; i++) { + if (registryDriverSupportFind(driverSupportNames[i])) continue; + if (!registryDriverSupportAdd(driverSupportNames[i], drvsl[i])) { + errlogPrintf("registryDriverSupportAdd failed %s\n", + driverSupportNames[i]); + continue; + } + } +} + +void registerJLinks(DBBASE *pbase, int nLinks, jlif * const *jlifsl) +{ + int i; + for (i = 0; i < nLinks; i++) { + if (!registryJLinkAdd(pbase, jlifsl[i])) { + errlogPrintf("registryJLinkAdd failed %s\n", + jlifsl[i]->name); + continue; + } + } +} + diff --git a/modules/database/src/ioc/registry/registryCommon.h b/modules/database/src/ioc/registry/registryCommon.h new file mode 100644 index 000000000..51b32dee3 --- /dev/null +++ b/modules/database/src/ioc/registry/registryCommon.h @@ -0,0 +1,39 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryCommon_H +#define INC_registryCommon_H + +#include "dbStaticLib.h" +#include "devSup.h" +#include "dbJLink.h" +#include "registryRecordType.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void registerRecordTypes( + DBBASE *pbase, int nRecordTypes, + const char * const *recordTypeNames, const recordTypeLocation *rtl); +epicsShareFunc void registerDevices( + DBBASE *pbase, int nDevices, + const char * const *deviceSupportNames, const dset * const *devsl); +epicsShareFunc void registerDrivers( + DBBASE *pbase, int nDrivers, + const char * const *driverSupportNames, struct drvet * const *drvsl); +epicsShareFunc void registerJLinks( + DBBASE *pbase, int nDrivers, jlif * const *jlifsl); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_registryCommon_H */ diff --git a/modules/database/src/ioc/registry/registryDeviceSupport.c b/modules/database/src/ioc/registry/registryDeviceSupport.c new file mode 100644 index 000000000..4310c923d --- /dev/null +++ b/modules/database/src/ioc/registry/registryDeviceSupport.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryDeviceSupport.c */ + +/* Author: Marty Kraimer Date: 08JUN99 */ + +#define epicsExportSharedSymbols +#include "registry.h" +#include "registryDeviceSupport.h" + +static void *registryID = "device support"; + + +epicsShareFunc int registryDeviceSupportAdd( + const char *name, const struct dset *pdset) +{ + return registryAdd(registryID, name, (void *)pdset); +} + +epicsShareFunc struct dset * registryDeviceSupportFind( + const char *name) +{ + return registryFind(registryID, name); +} diff --git a/modules/database/src/ioc/registry/registryDeviceSupport.h b/modules/database/src/ioc/registry/registryDeviceSupport.h new file mode 100644 index 000000000..52a269863 --- /dev/null +++ b/modules/database/src/ioc/registry/registryDeviceSupport.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryDeviceSupport_H +#define INC_registryDeviceSupport_H + +#include "devSup.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int registryDeviceSupportAdd( + const char *name, const struct dset *pdset); +epicsShareFunc struct dset * registryDeviceSupportFind( + const char *name); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryDeviceSupport_H */ diff --git a/modules/database/src/ioc/registry/registryDriverSupport.c b/modules/database/src/ioc/registry/registryDriverSupport.c new file mode 100644 index 000000000..8393bc414 --- /dev/null +++ b/modules/database/src/ioc/registry/registryDriverSupport.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryDriverSupport.c */ + +/* Author: Marty Kraimer Date: 08JUN99 */ + +#define epicsExportSharedSymbols +#include "registry.h" +#include "registryDriverSupport.h" + +static void *registryID = "driver support"; + + +epicsShareFunc int registryDriverSupportAdd( + const char *name, struct drvet *pdrvet) +{ + return registryAdd(registryID, name, pdrvet); +} + +epicsShareFunc struct drvet * registryDriverSupportFind( + const char *name) +{ + return registryFind(registryID, name); +} diff --git a/modules/database/src/ioc/registry/registryDriverSupport.h b/modules/database/src/ioc/registry/registryDriverSupport.h new file mode 100644 index 000000000..79be29295 --- /dev/null +++ b/modules/database/src/ioc/registry/registryDriverSupport.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryDriverSupport_H +#define INC_registryDriverSupport_H + +#include "drvSup.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int registryDriverSupportAdd( + const char *name, struct drvet *pdrvet); +epicsShareFunc struct drvet * registryDriverSupportFind( + const char *name); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryDriverSupport_H */ diff --git a/modules/database/src/ioc/registry/registryFunction.c b/modules/database/src/ioc/registry/registryFunction.c new file mode 100644 index 000000000..194f078c1 --- /dev/null +++ b/modules/database/src/ioc/registry/registryFunction.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryFunction.c */ + +/* Author: Marty Kraimer Date: 01MAY2000 */ + +#include + +#define epicsExportSharedSymbols +#include "registry.h" +#include "registryFunction.h" + +static void * const registryID = "function"; + + +epicsShareFunc int registryFunctionAdd( + const char *name, REGISTRYFUNCTION func) +{ + return registryAdd(registryID, name, func); +} + +epicsShareFunc REGISTRYFUNCTION registryFunctionFind( + const char *name) +{ + REGISTRYFUNCTION func = registryFind(registryID, name); + + if (!func) { + func = registryFind(0, name); + if (func) registryFunctionAdd(name, func); + } + return func; +} + +epicsShareFunc int registryFunctionRefAdd( + registryFunctionRef ref[], int nfunctions) +{ + int i; + + for (i = 0; i < nfunctions; i++) { + if (!registryFunctionAdd(ref[i].name, ref[i].addr)) { + printf("registryFunctionRefAdd: could not register %s\n", + ref[i].name); + return 0; + } + } + return 1; +} diff --git a/modules/database/src/ioc/registry/registryFunction.h b/modules/database/src/ioc/registry/registryFunction.h new file mode 100644 index 000000000..e20513771 --- /dev/null +++ b/modules/database/src/ioc/registry/registryFunction.h @@ -0,0 +1,39 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryFunction_H +#define INC_registryFunction_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*REGISTRYFUNCTION)(void); + +typedef struct registryFunctionRef { + const char * name; + REGISTRYFUNCTION addr; +} registryFunctionRef; + + +epicsShareFunc int registryFunctionAdd( + const char *name, REGISTRYFUNCTION func); +epicsShareFunc REGISTRYFUNCTION registryFunctionFind( + const char *name); +epicsShareFunc int registryFunctionRefAdd( + registryFunctionRef ref[], int nfunctions); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryFunction_H */ diff --git a/modules/database/src/ioc/registry/registryIocRegister.c b/modules/database/src/ioc/registry/registryIocRegister.c new file mode 100644 index 000000000..e36a809df --- /dev/null +++ b/modules/database/src/ioc/registry/registryIocRegister.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "registryDeviceSupport.h" +#include "registryDriverSupport.h" +#include "registryFunction.h" +#include "registryIocRegister.h" +#include "registryRecordType.h" + +static const iocshArg registryXxxFindArg0 = { "name",iocshArgString}; +static const iocshArg * const registryXxxFindArgs[1] = {®istryXxxFindArg0}; + +/* registryRecordTypeFind */ +static const iocshFuncDef registryRecordTypeFindFuncDef = { + "registryRecordTypeFind",1,registryXxxFindArgs}; +static void registryRecordTypeFindCallFunc(const iocshArgBuf *args) { + printf("%p\n", (void*) registryRecordTypeFind(args[0].sval)); +} + +/* registryDeviceSupportFind */ +static const iocshFuncDef registryDeviceSupportFindFuncDef = { + "registryDeviceSupportFind",1,registryXxxFindArgs}; +static void registryDeviceSupportFindCallFunc(const iocshArgBuf *args) { + printf("%p\n", (void*) registryDeviceSupportFind(args[0].sval)); +} + +/* registryDriverSupportFind */ +static const iocshFuncDef registryDriverSupportFindFuncDef = { + "registryDriverSupportFind",1,registryXxxFindArgs}; +static void registryDriverSupportFindCallFunc(const iocshArgBuf *args) { + printf("%p\n", (void*) registryDriverSupportFind(args[0].sval)); +} + +/* registryFunctionFind */ +static const iocshFuncDef registryFunctionFindFuncDef = { + "registryFunctionFind",1,registryXxxFindArgs}; +static void registryFunctionFindCallFunc(const iocshArgBuf *args) { + printf("%p\n", (void*) registryFunctionFind(args[0].sval)); +} + +void registryIocRegister(void) +{ + iocshRegister(®istryRecordTypeFindFuncDef,registryRecordTypeFindCallFunc); + iocshRegister(®istryDeviceSupportFindFuncDef,registryDeviceSupportFindCallFunc); + iocshRegister(®istryDriverSupportFindFuncDef,registryDriverSupportFindCallFunc); + iocshRegister(®istryFunctionFindFuncDef,registryFunctionFindCallFunc); +} diff --git a/modules/database/src/ioc/registry/registryIocRegister.h b/modules/database/src/ioc/registry/registryIocRegister.h new file mode 100644 index 000000000..ca02ce0e3 --- /dev/null +++ b/modules/database/src/ioc/registry/registryIocRegister.h @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryIocRegister_H +#define INC_registryIocRegister_H + +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void registryIocRegister(void); + +#ifdef __cplusplus +} +#endif + +#endif /* INC_registryIocRegister_H */ diff --git a/modules/database/src/ioc/registry/registryJLinks.c b/modules/database/src/ioc/registry/registryJLinks.c new file mode 100644 index 000000000..921a2cbcc --- /dev/null +++ b/modules/database/src/ioc/registry/registryJLinks.c @@ -0,0 +1,23 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryJLinks.c */ + +#include "registry.h" +#define epicsExportSharedSymbols +#include "dbBase.h" +#include "dbStaticLib.h" +#include "registryJLinks.h" +#include "dbJLink.h" + +epicsShareFunc int registryJLinkAdd(DBBASE *pbase, struct jlif *pjlif) +{ + linkSup *plinkSup = dbFindLinkSup(pbase, pjlif->name); + + if (plinkSup) + plinkSup->pjlif = pjlif; + return !!plinkSup; +} diff --git a/modules/database/src/ioc/registry/registryJLinks.h b/modules/database/src/ioc/registry/registryJLinks.h new file mode 100644 index 000000000..7e6a8933e --- /dev/null +++ b/modules/database/src/ioc/registry/registryJLinks.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryJLinks_H +#define INC_registryJLinks_H + +#include "dbBase.h" +#include "dbJLink.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc int registryJLinkAdd(DBBASE *pbase, jlif *pjlif); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryDriverSupport_H */ diff --git a/modules/database/src/ioc/registry/registryRecordType.c b/modules/database/src/ioc/registry/registryRecordType.c new file mode 100644 index 000000000..eced6f0f6 --- /dev/null +++ b/modules/database/src/ioc/registry/registryRecordType.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* registryRecordType.c */ + +/* Author: Marty Kraimer Date: 08JUN99 */ + +#define epicsExportSharedSymbols +#include "registry.h" +#include "registryRecordType.h" + +static void * const registryID = "record type"; + + +epicsShareFunc int registryRecordTypeAdd( + const char *name, const recordTypeLocation *prtl) +{ + return registryAdd(registryID, name, (void *)prtl); +} + +epicsShareFunc recordTypeLocation * registryRecordTypeFind( + const char *name) +{ + return registryFind(registryID, name); +} diff --git a/modules/database/src/ioc/registry/registryRecordType.h b/modules/database/src/ioc/registry/registryRecordType.h new file mode 100644 index 000000000..29ba714e8 --- /dev/null +++ b/modules/database/src/ioc/registry/registryRecordType.h @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef INC_registryRecordType_H +#define INC_registryRecordType_H + +#include "recSup.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dbRecordType; +struct dbBase; + +typedef int (*computeSizeOffset)(struct dbRecordType *pdbRecordType); + +typedef struct recordTypeLocation { + struct typed_rset *prset; + computeSizeOffset sizeOffset; +}recordTypeLocation; + +epicsShareFunc int registryRecordTypeAdd( + const char *name, const recordTypeLocation *prtl); +epicsShareFunc recordTypeLocation * registryRecordTypeFind( + const char *name); + +int registerRecordDeviceDriver(struct dbBase *pdbbase); + +#ifdef __cplusplus +} +#endif + + +#endif /* INC_registryRecordType_H */ diff --git a/modules/database/src/ioc/rsrv/Makefile b/modules/database/src/ioc/rsrv/Makefile new file mode 100644 index 000000000..8ad7d01c4 --- /dev/null +++ b/modules/database/src/ioc/rsrv/Makefile @@ -0,0 +1,28 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(IOCDIR)/rsrv + +# These need access to net_convert.h from the CA client +caserverio_INCLUDES = -I$(SRC)/ca/client +camessage_INCLUDES = -I$(SRC)/ca/client + +INC += rsrv.h + +DBD += rsrv.dbd + +dbCore_SRCS += caserverio.c +dbCore_SRCS += caservertask.c +dbCore_SRCS += camsgtask.c +dbCore_SRCS += camessage.c +dbCore_SRCS += cast_server.c +dbCore_SRCS += online_notify.c +dbCore_SRCS += rsrvIocRegister.c diff --git a/modules/database/src/ioc/rsrv/camessage.c b/modules/database/src/ioc/rsrv/camessage.c new file mode 100644 index 000000000..72a4b17a1 --- /dev/null +++ b/modules/database/src/ioc/rsrv/camessage.c @@ -0,0 +1,2569 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Jeffrey O. Hill + * + * Ralph Lange + */ + +#include +#include +#include +#include +#include +#include + +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsStdio.h" +#include "epicsString.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "errlog.h" +#include "freeList.h" +#include "osiPoolStatus.h" +#include "osiSock.h" + +#include "caerr.h" +#include "net_convert.h" + +#define epicsExportSharedSymbols +#include "asDbLib.h" +#include "callback.h" +#include "db_access.h" +#include "db_access_routines.h" +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbNotify.h" +#include "rsrv.h" +#include "server.h" +#include "special.h" + +#define RECORD_NAME(CHAN) (dbChannelRecord(CHAN)->name) + +static EVENTFUNC read_reply; + +#define logBadId(CLIENT, MP, PPL)\ +logBadIdWithFileAndLineno(CLIENT, MP, PPL, __FILE__, __LINE__) + +/* + * for tracking db put notifies + */ +typedef struct rsrv_put_notify { + ELLNODE node; + processNotify dbPutNotify; + caHdrLargeArray msg; + /* + * Include a union of all scalar types + * including fixed length strings so + * that in many cases we can avoid + * allocating another buffer and only + * use an rsrv_put_notify from its + * free list. + */ + union { + dbr_string_t strval; + dbr_short_t shrtval; + dbr_short_t intval; + dbr_float_t fltval; + dbr_enum_t enmval; + dbr_char_t charval; + dbr_long_t longval; + dbr_double_t doubleval; + } dbrScalarValue; + /* arguments for db_put_field */ + void *pbuffer; + long nRequest; + short dbrType; + /* end arguments for db_put_field */ + void * asWritePvt; + unsigned valueSize; /* size of block pointed to by pbuffer */ + char busy; /* put notify in progress */ + char onExtraLaborQueue; +} RSRVPUTNOTIFY; + +/* + * casCalloc() + * + * (dont drop below some max block threshold) + */ +static void *casCalloc(size_t count, size_t size) +{ + if ( UINT_MAX / size >= count ) { + if (!osiSufficentSpaceInPool(size*count)) { + return NULL; + } + return calloc(count, size); + } + else { + return NULL; + } +} + +/* + * MPTOPCIU() + * + * used to be a macro + */ +static struct channel_in_use *MPTOPCIU (const caHdrLargeArray *mp) +{ + struct channel_in_use *pciu; + const unsigned id = mp->m_cid; + + LOCK_CLIENTQ; + pciu = bucketLookupItemUnsignedId (pCaBucket, &id); + UNLOCK_CLIENTQ; + + return pciu; +} + +/* vsend_err() + * + * reflect error msg back to the client + * + * send buffer lock must be on while in this routine + * + */ +static void vsend_err( +const caHdrLargeArray *curp, +int status, +struct client *client, +const char *pformat, +va_list args +) +{ + static const ca_uint32_t maxDiagLen = 512; + struct channel_in_use *pciu; + caHdr *pReqOut; + char *pMsgString; + ca_uint32_t size; + ca_uint32_t cid; + int localStatus; + + switch ( curp->m_cmmd ) { + case CA_PROTO_EVENT_ADD: + case CA_PROTO_EVENT_CANCEL: + case CA_PROTO_READ: + case CA_PROTO_READ_NOTIFY: + case CA_PROTO_WRITE: + case CA_PROTO_WRITE_NOTIFY: + pciu = MPTOPCIU(curp); + if(pciu){ + cid = pciu->cid; + } + else{ + cid = 0xffffffff; + } + break; + + case CA_PROTO_SEARCH: + cid = curp->m_cid; + break; + + case CA_PROTO_EVENTS_ON: + case CA_PROTO_EVENTS_OFF: + case CA_PROTO_READ_SYNC: + case CA_PROTO_SNAPSHOT: + default: + cid = 0xffffffff; + break; + } + + /* + * allocate plenty of space for a sprintf() buffer + */ + localStatus = cas_copy_in_header ( client, + CA_PROTO_ERROR, maxDiagLen, 0, 0, cid, status, + ( void * ) &pReqOut ); + if ( localStatus != ECA_NORMAL ) { + errlogPrintf ( "caserver: Unable to deliver err msg \"%s\" to client because \"%s\"\n", + ca_message (status), ca_message (localStatus) ); + errlogVprintf ( pformat, args ); + return; + } + + /* + * copy back the request protocol + * (in network byte order) + */ + if ( ( curp->m_postsize >= 0xffff || curp->m_count >= 0xffff ) && + CA_V49( client->minor_version_number ) ) { + ca_uint32_t *pLW = ( ca_uint32_t * ) ( pReqOut + 1 ); + pReqOut->m_cmmd = htons ( curp->m_cmmd ); + pReqOut->m_postsize = htons ( 0xffff ); + pReqOut->m_dataType = htons ( curp->m_dataType ); + pReqOut->m_count = htons ( 0u ); + pReqOut->m_cid = htonl ( curp->m_cid ); + pReqOut->m_available = htonl ( curp->m_available ); + pLW[0] = htonl ( curp->m_postsize ); + pLW[1] = htonl ( curp->m_count ); + pMsgString = ( char * ) ( pLW + 2 ); + size = sizeof ( caHdr ) + 2 * sizeof ( *pLW ); + } + else { + pReqOut->m_cmmd = htons (curp->m_cmmd); + pReqOut->m_postsize = htons ( ( (ca_uint16_t) curp->m_postsize ) ); + pReqOut->m_dataType = htons (curp->m_dataType); + pReqOut->m_count = htons ( ( (ca_uint16_t) curp->m_count ) ); + pReqOut->m_cid = htonl (curp->m_cid); + pReqOut->m_available = htonl (curp->m_available); + pMsgString = ( char * ) ( pReqOut + 1 ); + size = sizeof ( caHdr ); + } + + /* + * add their context string into the protocol + */ + localStatus = epicsVsnprintf ( pMsgString, maxDiagLen - size, pformat, args ); + if ( localStatus >= 1 ) { + unsigned diagLen = ( unsigned ) localStatus; + if ( diagLen < maxDiagLen - size ) { + size += (ca_uint32_t) (diagLen + 1u); + } + else { + errlogPrintf ( + "caserver: vsend_err: epicsVsnprintf detected " + "error message truncation, pFormat = \"%s\"\n", + pformat ); + size = maxDiagLen; + pMsgString [ maxDiagLen - 1 ] = '\0'; + } + } + cas_commit_msg ( client, size ); +} + +/* send_err() + * + * reflect error msg back to the client + * + * send buffer lock must be on while in this routine + * + */ +static void send_err ( +const caHdrLargeArray *curp, +int status, +struct client *client, +const char *pformat, + ... ) +{ + va_list args; + va_start ( args, pformat ); + vsend_err ( curp, status, client, pformat, args ); + va_end ( args ); +} + +/* log_header() + * + * Debug aid - print the header part of a message. + * + */ +static void log_header ( + const char *pContext, + struct client *client, + const caHdrLargeArray *mp, + const void *pPayLoad, + unsigned mnum +) +{ + struct channel_in_use *pciu; + char hostName[256]; + + ipAddrToDottedIP (&client->addr, hostName, sizeof(hostName)); + + pciu = MPTOPCIU(mp); + + if (pContext) { + epicsPrintf ("CAS: request from %s => %s\n", + hostName, pContext); + } + + epicsPrintf ( "CAS: Request from %s => cmmd=%d cid=0x%x type=%d count=%d postsize=%u\n", + hostName, mp->m_cmmd, mp->m_cid, mp->m_dataType, mp->m_count, mp->m_postsize); + + epicsPrintf ( "CAS: Request from %s => available=0x%x \tN=%u paddr=%p\n", + hostName, mp->m_available, mnum, (pciu ? (void *)&pciu->dbch : NULL)); + + if (mp->m_cmmd==CA_PROTO_WRITE && mp->m_dataType==DBF_STRING && pPayLoad ) { + epicsPrintf ( "CAS: Request from %s => Wrote string \"%s\"\n", + hostName, (char *)pPayLoad ); + } +} + +/* + * logBadIdWithFileAndLineno() + */ +static void logBadIdWithFileAndLineno( +struct client *client, +caHdrLargeArray *mp, +const void *pPayload, +char *pFileName, +unsigned lineno +) +{ + log_header ( "bad resource ID", client, mp, pPayload, 0 ); + SEND_LOCK ( client ); + send_err ( mp, ECA_INTERNAL, client, "Bad Resource ID at %s.%d", + pFileName, lineno ); + SEND_UNLOCK ( client ); +} + +/* + * bad_udp_cmd_action() + */ +static int bad_udp_cmd_action ( caHdrLargeArray *mp, + void *pPayload, struct client *pClient ) +{ + if (CASDEBUG > 0) + log_header ("invalid (damaged?) request code from UDP", + pClient, mp, pPayload, 0); + return RSRV_ERROR; +} + +/* + * bad_tcp_cmd_action() + */ +static int bad_tcp_cmd_action ( caHdrLargeArray *mp, void *pPayload, + struct client *client ) +{ + const char *pCtx = "invalid (damaged?) request code from TCP"; + log_header ( pCtx, client, mp, pPayload, 0 ); + + /* + * by default, clients dont recover + * from this + */ + SEND_LOCK (client); + send_err (mp, ECA_INTERNAL, client, pCtx); + SEND_UNLOCK (client); + + return RSRV_ERROR; +} + +/* + * tcp_version_action() + */ +static int tcp_version_action ( caHdrLargeArray *mp, void *pPayload, + struct client *client ) +{ + double tmp; + unsigned epicsPriorityNew; + unsigned epicsPrioritySelf; + + client->minor_version_number = mp->m_count; + + if (!CA_VSUPPORTED(mp->m_count)) { + DLOG ( 2, ( "CAS: Ignore version from unsupported client %u\n", mp->m_count ) ); + return RSRV_ERROR; + } + + if ( mp->m_dataType > CA_PROTO_PRIORITY_MAX ) { + return RSRV_ERROR; + } + + tmp = mp->m_dataType - CA_PROTO_PRIORITY_MIN; + tmp *= epicsThreadPriorityCAServerHigh - epicsThreadPriorityCAServerLow; + tmp /= CA_PROTO_PRIORITY_MAX - CA_PROTO_PRIORITY_MIN; + tmp += epicsThreadPriorityCAServerLow; + epicsPriorityNew = (unsigned) tmp; + epicsPrioritySelf = epicsThreadGetPrioritySelf(); + if ( epicsPriorityNew != epicsPrioritySelf ) { + epicsThreadBooleanStatus tbs; + unsigned priorityOfEvents; + tbs = epicsThreadHighestPriorityLevelBelow ( epicsPriorityNew, &priorityOfEvents ); + if ( tbs != epicsThreadBooleanStatusSuccess ) { + priorityOfEvents = epicsPriorityNew; + } + + if ( epicsPriorityNew > epicsPrioritySelf ) { + epicsThreadSetPriority ( epicsThreadGetIdSelf(), epicsPriorityNew ); + db_event_change_priority ( client->evuser, priorityOfEvents ); + } + else { + db_event_change_priority ( client->evuser, priorityOfEvents ); + epicsThreadSetPriority ( epicsThreadGetIdSelf(), epicsPriorityNew ); + } + client->priority = mp->m_dataType; + } + return RSRV_OK; +} + +/* + * tcp_echo_action() + */ +static int tcp_echo_action ( caHdrLargeArray *mp, + void *pPayload, struct client *pClient ) +{ + char *pPayloadOut; + int status; + SEND_LOCK ( pClient ); + status = cas_copy_in_header ( pClient, mp->m_cmmd, mp->m_postsize, + mp->m_dataType, mp->m_count, mp->m_cid, mp->m_available, + ( void * ) &pPayloadOut ); + if ( status == ECA_NORMAL ) { + memcpy ( pPayloadOut, pPayload, mp->m_postsize ); + cas_commit_msg ( pClient, mp->m_postsize ); + } + SEND_UNLOCK ( pClient ); + return RSRV_OK; +} + +/* + * events_on_action () + */ +static int events_on_action ( caHdrLargeArray *mp, + void *pPayload, struct client *pClient ) +{ + db_event_flow_ctrl_mode_off ( pClient->evuser ); + return RSRV_OK; +} + +/* + * events_off_action () + */ +static int events_off_action ( caHdrLargeArray *mp, + void *pPayload, struct client *pClient ) +{ + db_event_flow_ctrl_mode_on ( pClient->evuser ); + return RSRV_OK; +} + +/* + * no_read_access_event() + * + * !! LOCK needs to applied by caller !! + * + * substantial complication introduced here by the need for backwards + * compatibility + */ +static void no_read_access_event ( struct client *pClient, + struct event_ext *pevext ) +{ + char *pPayloadOut; + int status; + + /* + * New clients recv the status of the + * operation directly to the + * event/put/get callback. + * + * Fetched value is zerod in case they + * use it even when the status indicates + * failure. + * + * The m_cid field in the protocol + * header is abused to carry the status + */ + status = cas_copy_in_header ( pClient, pevext->msg.m_cmmd, pevext->size, + pevext->msg.m_dataType, pevext->msg.m_count, ECA_NORDACCESS, + pevext->msg.m_available, ( void * ) &pPayloadOut ); + if ( status == ECA_NORMAL ) { + memset ( pPayloadOut, 0, pevext->size ); + cas_commit_msg ( pClient, pevext->size ); + } + else { + send_err ( &pevext->msg, status, pClient, + "server unable to load read access denied response into protocol buffer PV=\"%s max bytes=%u\"", + RECORD_NAME ( pevext->pciu->dbch ), rsrvSizeofLargeBufTCP ); + } +} + +/* + * read_reply() + */ +static void read_reply ( void *pArg, struct dbChannel *dbch, + int eventsRemaining, db_field_log *pfl ) +{ + ca_uint32_t cid; + void *pPayload; + struct event_ext *pevext = pArg; + struct client *pClient = pevext->pciu->client; + struct channel_in_use *pciu = pevext->pciu; + const int readAccess = asCheckGet ( pciu->asClientPVT ); + int status; + int autosize; + int local_fl = 0; + long item_count; + ca_uint32_t payload_size; + dbAddr *paddr=&dbch->addr; + + SEND_LOCK ( pClient ); + + cid = ECA_NORMAL; + + /* If the client has requested a zero element count we interpret this as a + * request for all avaiable elements. In this case we initialise the + * header with the maximum element size specified by the database. */ + autosize = pevext->msg.m_count == 0; + item_count = + autosize ? paddr->no_elements : pevext->msg.m_count; + payload_size = dbr_size_n(pevext->msg.m_dataType, item_count); + status = cas_copy_in_header( + pClient, pevext->msg.m_cmmd, payload_size, + pevext->msg.m_dataType, item_count, cid, pevext->msg.m_available, + &pPayload ); + if ( status != ECA_NORMAL ) { + send_err ( &pevext->msg, status, pClient, + "server unable to load read (or subscription update) response " + "into protocol buffer PV=\"%s\" max bytes=%u", + RECORD_NAME ( dbch ), rsrvSizeofLargeBufTCP ); + if ( ! eventsRemaining ) + cas_send_bs_msg ( pClient, FALSE ); + SEND_UNLOCK ( pClient ); + return; + } + + /* + * verify read access + */ + if ( ! readAccess ) { + no_read_access_event ( pClient, pevext ); + if ( ! eventsRemaining ) + cas_send_bs_msg ( pClient, FALSE ); + SEND_UNLOCK ( pClient ); + return; + } + + /* If filters are involved in a read, create field log and run filters */ + if (!pfl && (ellCount(&dbch->pre_chain) || ellCount(&dbch->post_chain))) { + pfl = db_create_read_log(dbch); + if (pfl) { + local_fl = 1; + pfl = dbChannelRunPreChain(dbch, pfl); + pfl = dbChannelRunPostChain(dbch, pfl); + } + } + + status = dbChannel_get_count ( dbch, pevext->msg.m_dataType, + pPayload, &item_count, pfl); + + if (local_fl) db_delete_field_log(pfl); + + if ( status < 0 ) { + /* Clients recv the status of the operation directly to the + * event/put/get callback. (from CA_V41()) + * + * Fetched value is set to zero in case they use it even when the + * status indicates failure -- unless the client selected autosizing + * data, in which case they'd better know what they're doing! + * + * The m_cid field in the protocol header is abused to carry the + * status */ + if (autosize) { + payload_size = dbr_size_n(pevext->msg.m_dataType, 0); + cas_set_header_count(pClient, 0); + } + memset ( pPayload, 0, payload_size ); + cas_set_header_cid ( pClient, ECA_GETFAIL ); + cas_commit_msg ( pClient, payload_size ); + } + else { + int cacStatus = caNetConvert ( + pevext->msg.m_dataType, pPayload, pPayload, + TRUE /* host -> net format */, item_count ); + if ( cacStatus == ECA_NORMAL ) { + ca_uint32_t data_size = + dbr_size_n(pevext->msg.m_dataType, item_count); + if (autosize) { + payload_size = data_size; + cas_set_header_count(pClient, item_count); + } + else if (payload_size > data_size) + memset( + (char *) pPayload + data_size, 0, payload_size - data_size); + } + else { + if (autosize) { + payload_size = dbr_size_n(pevext->msg.m_dataType, 0); + cas_set_header_count(pClient, 0); + } + memset ( pPayload, 0, payload_size ); + cas_set_header_cid ( pClient, cacStatus ); + } + cas_commit_msg ( pClient, payload_size ); + } + + /* + * Ensures timely response for events, but does queue + * them up like db requests when the OPI does not keep up. + */ + if ( ! eventsRemaining ) + cas_send_bs_msg ( pClient, FALSE ); + + SEND_UNLOCK ( pClient ); + + return; +} + +/* + * read_action () + */ +static int read_action ( caHdrLargeArray *mp, void *pPayloadIn, struct client *pClient ) +{ + struct channel_in_use *pciu = MPTOPCIU ( mp ); + int readAccess; + ca_uint32_t payloadSize; + void *pPayload; + int status; + int local_fl = 0; + db_field_log *pfl = NULL; + + if ( ! pciu ) { + logBadId ( pClient, mp, 0 ); + return RSRV_ERROR; + } + readAccess = asCheckGet ( pciu->asClientPVT ); + + SEND_LOCK ( pClient ); + + if ( INVALID_DB_REQ ( mp->m_dataType ) ) { + send_err ( mp, ECA_BADTYPE, pClient, RECORD_NAME ( pciu->dbch ) ); + SEND_UNLOCK ( pClient ); + return RSRV_ERROR; + } + + payloadSize = dbr_size_n ( mp->m_dataType, mp->m_count ); + status = cas_copy_in_header ( pClient, mp->m_cmmd, payloadSize, + mp->m_dataType, mp->m_count, pciu->cid, mp->m_available, &pPayload ); + if ( status != ECA_NORMAL ) { + send_err ( mp, status, pClient, + "server unable to load read response into protocol buffer PV=\"%s\" max bytes=%u", + RECORD_NAME ( pciu->dbch ), rsrvSizeofLargeBufTCP ); + SEND_UNLOCK ( pClient ); + return RSRV_OK; + } + + /* + * verify read access + */ + if ( ! readAccess ) { + status = ECA_NORDACCESS; + send_err ( mp, status, + pClient, RECORD_NAME ( pciu->dbch ) ); + SEND_UNLOCK ( pClient ); + return RSRV_OK; + } + + /* If filters are involved in a read, create field log and run filters */ + if (ellCount(&pciu->dbch->pre_chain) || ellCount(&pciu->dbch->post_chain)) { + pfl = db_create_read_log(pciu->dbch); + if (pfl) { + local_fl = 1; + pfl = dbChannelRunPreChain(pciu->dbch, pfl); + pfl = dbChannelRunPostChain(pciu->dbch, pfl); + } + } + + status = dbChannel_get ( pciu->dbch, mp->m_dataType, + pPayload, mp->m_count, pfl ); + + if (local_fl) db_delete_field_log(pfl); + + if ( status < 0 ) { + send_err ( mp, ECA_GETFAIL, pClient, RECORD_NAME ( pciu->dbch ) ); + SEND_UNLOCK ( pClient ); + return RSRV_OK; + } + + status = caNetConvert ( + mp->m_dataType, pPayload, pPayload, + TRUE /* host -> net format */, mp->m_count ); + if ( status != ECA_NORMAL ) { + send_err ( mp, status, pClient, RECORD_NAME ( pciu->dbch ) ); + SEND_UNLOCK ( pClient ); + return RSRV_OK; + } + + /* + * force string message size to be the true size rounded to even + * boundary + */ + if ( mp->m_dataType == DBR_STRING && mp->m_count == 1 ) { + char * pStr = (char *) pPayload; + size_t strcnt = epicsStrnLen( pStr, payloadSize ); + if ( strcnt < payloadSize ) { + payloadSize = ( ca_uint32_t ) ( strcnt + 1u ); + } + else { + pStr[payloadSize-1] = '\0'; + errlogPrintf ( + "caserver: read_action: detected DBR_STRING w/o nill termination " + "in response from db_get_field, pPayload = \"%s\"\n", + pStr ); + } + } + cas_commit_msg ( pClient, payloadSize ); + + SEND_UNLOCK ( pClient ); + + return RSRV_OK; +} + +/* + * read_notify_action() + */ +static int read_notify_action ( caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + struct channel_in_use *pciu; + struct event_ext evext; + + if ( INVALID_DB_REQ(mp->m_dataType) ) { + return RSRV_ERROR; + } + + pciu = MPTOPCIU ( mp ); + if ( !pciu ) { + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + + evext.msg = *mp; + evext.pciu = pciu; + evext.pdbev = NULL; + evext.size = dbr_size_n ( mp->m_dataType, mp->m_count ); + + /* + * Arguments to this routine organized in + * favor of the standard db event calling + * mechanism- routine(userarg, paddr). See + * events added above. + * + * Hold argument set true so the send message + * buffer is not flushed once each call. + */ + read_reply ( &evext, pciu->dbch, TRUE, NULL ); + + return RSRV_OK; +} + +/* + * write_action() + */ +static int write_action ( caHdrLargeArray *mp, + void *pPayload, struct client *client ) +{ + struct channel_in_use *pciu; + int status; + long dbStatus; + void *asWritePvt; + + pciu = MPTOPCIU(mp); + if(!pciu){ + logBadId(client, mp, pPayload); + return RSRV_ERROR; + } + + if(!rsrvCheckPut(pciu)){ + status = ECA_NOWTACCESS; + SEND_LOCK(client); + send_err( + mp, + status, + client, + RECORD_NAME ( pciu->dbch )); + SEND_UNLOCK(client); + return RSRV_OK; + } + + status = caNetConvert ( + mp->m_dataType, pPayload, pPayload, + FALSE /* net -> host format */, mp->m_count ); + if ( status != ECA_NORMAL ) { + log_header ("invalid data type", client, mp, pPayload, 0); + SEND_LOCK(client); + send_err( + mp, + status, + client, + RECORD_NAME ( pciu->dbch )); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + asWritePvt = asTrapWriteWithData ( pciu->asClientPVT, + pciu->client->pUserName ? pciu->client->pUserName : "", + pciu->client->pHostName ? pciu->client->pHostName : "", + pciu->dbch, mp->m_dataType, mp->m_count, pPayload ); + + dbStatus = dbChannel_put( + pciu->dbch, + mp->m_dataType, + pPayload, + mp->m_count); + + asTrapWriteAfter(asWritePvt); + + if (dbStatus < 0) { + SEND_LOCK(client); + send_err( + mp, + ECA_PUTFAIL, + client, + RECORD_NAME ( pciu->dbch )); + SEND_UNLOCK(client); + } + + return RSRV_OK; +} + +/* + * host_name_action() + */ +static int host_name_action ( caHdrLargeArray *mp, void *pPayload, + struct client *client ) +{ + ca_uint32_t size; + char *pName; + char *pMalloc; + int chanCount; + + epicsMutexMustLock ( client->chanListLock ); + chanCount = + ellCount ( &client->chanList ) + + ellCount ( &client->chanPendingUpdateARList ); + epicsMutexUnlock( client->chanListLock ); + + if ( chanCount != 0 ) { + SEND_LOCK ( client ); + send_err( + mp, + ECA_INTERNAL, + client, + "attempts to use protocol to set host name " + "after creating first channel ignored by server" ); + SEND_UNLOCK ( client ); + return RSRV_OK; + } + + pName = (char *) pPayload; + size = epicsStrnLen(pName, mp->m_postsize)+1; + if (size > 512 || size > mp->m_postsize) { + log_header ( "bad (very long) host name", + client, mp, pPayload, 0 ); + SEND_LOCK(client); + send_err( + mp, + ECA_INTERNAL, + client, + "bad (very long) host name"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + /* + * user name will not change if there isnt enough memory + */ + pMalloc = malloc(size); + if(!pMalloc){ + log_header ( "no space in pool for new host name", + client, mp, pPayload, 0 ); + SEND_LOCK(client); + send_err( + mp, + ECA_ALLOCMEM, + client, + "no space in pool for new host name"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + strncpy( + pMalloc, + pName, + size-1); + pMalloc[size-1]='\0'; + + pName = client->pHostName; + client->pHostName = pMalloc; + if ( pName ) { + free ( pName ); + } + + DLOG (2, ( "CAS: host_name_action for \"%s\"\n", + client->pHostName ? client->pHostName : "" ) ); + + return RSRV_OK; +} + + +/* + * client_name_action() + */ +static int client_name_action ( caHdrLargeArray *mp, void *pPayload, + struct client *client ) +{ + ca_uint32_t size; + char *pName; + char *pMalloc; + int chanCount; + + epicsMutexMustLock ( client->chanListLock ); + chanCount = + ellCount ( &client->chanList ) + + ellCount ( &client->chanPendingUpdateARList ); + epicsMutexUnlock( client->chanListLock ); + + if ( chanCount != 0 ) { + SEND_LOCK ( client ); + send_err( + mp, + ECA_INTERNAL, + client, + "attempts to use protocol to set user name " + "after creating first channel ignored by server" ); + SEND_UNLOCK ( client ); + return RSRV_OK; + } + + pName = (char *) pPayload; + size = epicsStrnLen(pName, mp->m_postsize)+1; + if (size > 512 || size > mp->m_postsize) { + log_header ("a very long user name was specified", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err( + mp, + ECA_INTERNAL, + client, + "a very long user name was specified"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + /* + * user name will not change if there isnt enough memory + */ + pMalloc = malloc(size); + if(!pMalloc){ + log_header ("no memory for new user name", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err( + mp, + ECA_ALLOCMEM, + client, + "no memory for new user name"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + strncpy( + pMalloc, + pName, + size-1); + pMalloc[size-1]='\0'; + + pName = client->pUserName; + client->pUserName = pMalloc; + if ( pName ) { + free ( pName ); + } + + return RSRV_OK; +} + +/* + * casCreateChannel () + */ +static struct channel_in_use *casCreateChannel ( +struct client *client, +struct dbChannel *dbch, +unsigned cid +) +{ + static unsigned bucketID; + unsigned *pCID; + struct channel_in_use *pchannel; + int status; + + /* get block off free list if possible */ + pchannel = (struct channel_in_use *) + freeListCalloc(rsrvChanFreeList); + if (!pchannel) { + return NULL; + } + ellInit(&pchannel->eventq); + epicsTimeGetCurrent(&pchannel->time_at_creation); + pchannel->dbch = dbch; + pchannel->client = client; + /* + * bypass read only warning + */ + pCID = (unsigned *) &pchannel->cid; + *pCID = cid; + + /* + * allocate a server id and enter the channel pointer + * in the table + * + * NOTE: This detects the case where the PV id wraps + * around and we attempt to have two resources on the same id. + * The lock is applied here because on some architectures the + * ++ operator isnt atomic. + */ + LOCK_CLIENTQ; + + do { + /* + * bypass read only warning + */ + pCID = (unsigned *) &pchannel->sid; + *pCID = bucketID++; + + /* + * Verify that this id is not in use + */ + status = bucketAddItemUnsignedId ( + pCaBucket, + &pchannel->sid, + pchannel); + } while (status == S_bucket_idInUse); + + if ( status == S_bucket_success ) { + rsrvChannelCount++; + } + + UNLOCK_CLIENTQ; + + if(status!=S_bucket_success){ + freeListFree(rsrvChanFreeList, pchannel); + errMessage (status, "Unable to allocate server id"); + return NULL; + } + + epicsMutexMustLock( client->chanListLock ); + pchannel->state = rsrvCS_pendConnectResp; + ellAdd ( &client->chanList, &pchannel->node ); + epicsMutexUnlock ( client->chanListLock ); + + return pchannel; +} + +/* + * casAccessRightsCB() + * + * If access right state changes then inform the client. + * asLock is held + */ +static void casAccessRightsCB(ASCLIENTPVT ascpvt, asClientStatus type) +{ + struct client * pclient; + struct channel_in_use * pciu; + struct event_ext * pevext; + + pciu = asGetClientPvt(ascpvt); + assert(pciu); + + pclient = pciu->client; + assert(pclient); + + if(pclient->proto==IPPROTO_UDP){ + return; + } + + switch(type) + { + case asClientCOAR: + { + const int readAccess = asCheckGet ( pciu->asClientPVT ); + unsigned sigReq = 0; + + epicsMutexMustLock ( pclient->chanListLock ); + if ( pciu->state == rsrvCS_pendConnectResp ) { + ellDelete ( &pclient->chanList, &pciu->node ); + pciu->state = rsrvCS_pendConnectRespUpdatePendAR; + ellAdd ( &pclient->chanPendingUpdateARList, &pciu->node ); + sigReq = 1; + } + else if ( pciu->state == rsrvCS_inService ) { + ellDelete ( &pclient->chanList, &pciu->node ); + pciu->state = rsrvCS_inServiceUpdatePendAR; + ellAdd ( &pclient->chanPendingUpdateARList, &pciu->node ); + sigReq = 1; + } + epicsMutexUnlock ( pclient->chanListLock ); + + /* + * Update all event call backs + */ + epicsMutexMustLock(pclient->eventqLock); + for (pevext = (struct event_ext *) ellFirst(&pciu->eventq); + pevext; + pevext = (struct event_ext *) ellNext(&pevext->node)){ + + if ( pevext->pdbev ) { + if ( readAccess ){ + db_event_enable ( pevext->pdbev ); + db_post_single_event ( pevext->pdbev ); + } + else { + db_post_single_event ( pevext->pdbev ); + db_event_disable ( pevext->pdbev ); + } + } + } + epicsMutexUnlock(pclient->eventqLock); + + if ( sigReq ) { + db_post_extra_labor( pclient->evuser ); + } + + break; + } + default: + break; + } +} + +/* + * access_rights_reply() + */ +static void access_rights_reply ( struct channel_in_use * pciu ) +{ + unsigned ar; + int status; + + assert ( pciu->client->proto!=IPPROTO_UDP ); + + ar = 0; /* none */ + if ( asCheckGet ( pciu->asClientPVT ) ) { + ar |= CA_PROTO_ACCESS_RIGHT_READ; + } + if ( rsrvCheckPut ( pciu ) ) { + ar |= CA_PROTO_ACCESS_RIGHT_WRITE; + } + + SEND_LOCK ( pciu->client ); + status = cas_copy_in_header ( + pciu->client, CA_PROTO_ACCESS_RIGHTS, 0, + 0, 0, pciu->cid, ar, 0 ); + /* + * OK to just ignore the request if the connection drops + */ + if ( status == ECA_NORMAL ) { + cas_commit_msg ( pciu->client, 0u ); + } + SEND_UNLOCK ( pciu->client ); +} + +/* + * claim_ciu_reply() + */ +static void claim_ciu_reply ( struct channel_in_use * pciu ) +{ + int status; + ca_uint32_t nElem; + long dbElem; + + access_rights_reply ( pciu ); + + SEND_LOCK ( pciu->client ); + dbElem = dbChannelFinalElements(pciu->dbch); + if ( dbElem < 0 ) { + nElem = 0; + } + else { + if ( ! CA_V49 ( pciu->client->minor_version_number ) ) { + if ( dbElem >= 0xffff ) { + nElem = 0xfffe; + } + else { + nElem = (ca_uint32_t) dbElem; + } + } + else { + nElem = (ca_uint32_t) dbElem; + } + } + status = cas_copy_in_header ( + pciu->client, CA_PROTO_CREATE_CHAN, 0u, + dbChannelFinalCAType(pciu->dbch), nElem, pciu->cid, + pciu->sid, NULL ); + if ( status == ECA_NORMAL ) { + cas_commit_msg ( pciu->client, 0u ); + } + SEND_UNLOCK(pciu->client); +} + +/* + * claim_ciu_action() + */ +static int claim_ciu_action ( caHdrLargeArray *mp, + void *pPayload, client *client ) +{ + int status; + struct channel_in_use *pciu; + struct dbChannel *dbch; + char *pName = (char *) pPayload; + + /* + * The available field is used (abused) + * here to communicate the miner version number + * starting with CA 4.1. The field was set to zero + * prior to 4.1 + */ + client->minor_version_number = mp->m_available; + + if (!CA_V44(client->minor_version_number)) + return RSRV_ERROR; /* shouldn't actually get here due to VSUPPORTED test in camessage() */ + + /* + * check the sanity of the message + */ + if (mp->m_postsize<=1) { + log_header ( "empty PV name in UDP search request?", + client, mp, pPayload, 0 ); + return RSRV_OK; + } + pName[mp->m_postsize-1] = '\0'; + + dbch = dbChannel_create (pName); + if (!dbch) { + SEND_LOCK(client); + status = cas_copy_in_header ( client, + CA_PROTO_CREATE_CH_FAIL, 0, 0, 0, mp->m_cid, 0, NULL ); + if (status == ECA_NORMAL) + cas_commit_msg ( client, 0u ); + SEND_UNLOCK(client); + return RSRV_OK; + } + + DLOG ( 2, ("CAS: claim_ciu_action found '%s', type %d, count %d\n", + pName, dbChannelCAType(dbch), dbChannelElements(dbch)) ); + + pciu = casCreateChannel ( + client, + dbch, + mp->m_cid); + if (!pciu) { + log_header ("no memory to create new channel", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err(mp, + ECA_ALLOCMEM, + client, + RECORD_NAME(dbch)); + SEND_UNLOCK(client); + dbChannelDelete(dbch); + return RSRV_ERROR; + } + + /* + * set up access security for this channel + */ + status = asAddClient( + &pciu->asClientPVT, + asDbGetMemberPvt(pciu->dbch), + asDbGetAsl(pciu->dbch), + client->pUserName ? client->pUserName : "", + client->pHostName ? client->pHostName : ""); + if(status != 0 && status != S_asLib_asNotActive){ + log_header ("No room for security table", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err(mp, ECA_ALLOCMEM, client, "No room for security table"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + /* + * store ptr to channel in use block + * in access security private + */ + asPutClientPvt(pciu->asClientPVT, pciu); + + /* + * register for asynch updates of access rights changes + */ + status = asRegisterClientCallback( + pciu->asClientPVT, + casAccessRightsCB); + if ( status == S_asLib_asNotActive ) { + epicsMutexMustLock ( client->chanListLock ); + pciu->state = rsrvCS_inService; + epicsMutexUnlock ( client->chanListLock ); + /* + * force the initial AR update followed by claim response + */ + claim_ciu_reply ( pciu ); + } + else if (status!=0) { + log_header ("No room for access security state change subscription", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err(mp, ECA_ALLOCMEM, client, + "No room for access security state change subscription"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + return RSRV_OK; +} + +/* + * write_notify_put_callback() + * + * (called by the db call back thread) + */ + LOCAL int write_notify_put_callback(processNotify *ppn,notifyPutType type) + { + struct channel_in_use * pciu = (struct channel_in_use *) ppn->usrPvt; + struct rsrv_put_notify *pNotify; + + if(ppn->status==notifyCanceled) return 0; + /* + * No locking in this method because only a dbNotifyCancel could interrupt + * and it does not return until cancel is done. + */ + assert(pciu); + assert(pciu->pPutNotify); + pNotify = pciu->pPutNotify; + return db_put_process(ppn,type, + pNotify->dbrType,pNotify->pbuffer,pNotify->nRequest); + } + + /* + * write_notify_done_callback() + * + * (called by the db call back thread) + */ + LOCAL void write_notify_done_callback(processNotify *ppn) +{ + struct channel_in_use * pciu = (struct channel_in_use *) ppn->usrPvt; + struct client * pClient; + + /* + * independent lock used here in order to + * avoid any possibility of blocking + * the database (or indirectly blocking + * one client on another client). + */ + assert(pciu); + assert(pciu->pPutNotify); + pClient = pciu->client; + epicsMutexMustLock(pClient->putNotifyLock); + + if ( ! pciu->pPutNotify->busy || pciu->pPutNotify->onExtraLaborQueue ) { + epicsMutexUnlock(pClient->putNotifyLock); + errlogPrintf("Double DB put notify call back!!\n"); + return; + } + + ellAdd(&pClient->putNotifyQue, &pciu->pPutNotify->node); + pciu->pPutNotify->onExtraLaborQueue = TRUE; + + epicsMutexUnlock(pClient->putNotifyLock); + + /* + * offload the labor for this to the + * event task so that we never block + * the db or another client. + */ + db_post_extra_labor(pClient->evuser); +} + +/* + * write_notify_reply() + * (called by the CA server event task via the extra labor interface) + */ +static void write_notify_reply ( struct client * pClient ) +{ + while(TRUE){ + caHdrLargeArray msgtmp; + void * asWritePvtTmp; + ca_uint32_t status; + int localStatus; + + /* + * independent lock used here in order to + * avoid any possibility of blocking + * the database (or indirectly blocking + * one client on another client). + */ + epicsMutexMustLock(pClient->putNotifyLock); + { + RSRVPUTNOTIFY * ppnb = (RSRVPUTNOTIFY *) + ellGet ( &pClient->putNotifyQue ); + if ( ! ppnb ) { + epicsMutexUnlock(pClient->putNotifyLock); + break; + } + /* + * + * Map from DB status to CA status + * + */ + if ( ppnb->dbPutNotify.status != notifyOK ) { + status = ECA_PUTFAIL; + } + else{ + status = ECA_NORMAL; + } + msgtmp = ppnb->msg; + asWritePvtTmp = ppnb->asWritePvt; + ppnb->asWritePvt = 0; + ppnb->onExtraLaborQueue = FALSE; + ppnb->busy = FALSE; + } + epicsMutexUnlock(pClient->putNotifyLock); + + asTrapWriteAfter ( asWritePvtTmp ); + + /* + * the channel id field is being abused to carry + * status here + */ + SEND_LOCK(pClient); + localStatus = cas_copy_in_header ( + pClient, CA_PROTO_WRITE_NOTIFY, + 0u, msgtmp.m_dataType, msgtmp.m_count, status, + msgtmp.m_available, 0 ); + if ( localStatus != ECA_NORMAL ) { + /* + * inability to aquire buffer space + * Indicates corruption + */ + errlogPrintf("CA server corrupted - put call back(s) discarded\n"); + SEND_UNLOCK ( pClient ); + break; + } + + /* commit the message */ + cas_commit_msg ( pClient, 0u ); + SEND_UNLOCK ( pClient ); + } + + /* + * wakeup the TCP thread if it is waiting for a cb to complete + */ + epicsEventSignal ( pClient->blockSem ); +} + +/* + * sendAllUpdateAS() + */ +static void sendAllUpdateAS ( struct client *client ) +{ + struct channel_in_use *pciu; + + epicsMutexMustLock ( client->chanListLock ); + + pciu = ( struct channel_in_use * ) + ellGet ( & client->chanPendingUpdateARList ); + while ( pciu ) { + if ( pciu->state == rsrvCS_pendConnectRespUpdatePendAR ) { + claim_ciu_reply ( pciu ); + } + else if ( pciu->state == rsrvCS_inServiceUpdatePendAR ) { + access_rights_reply ( pciu ); + } + else if ( pciu->state == rsrvCS_shutdown ) { + /* no-op */ + } + else { + errlogPrintf ( + "%s at %d: corrupt channel state detected durring AR update\n", + __FILE__, __LINE__); + } + pciu->state = rsrvCS_inService; + ellAdd ( & client->chanList, & pciu->node ); + pciu = ( struct channel_in_use * ) + ellGet ( & client->chanPendingUpdateARList ); + } + + epicsMutexUnlock( client->chanListLock ); +} + +/* + * rsrv_extra_labor() + * (called by the CA server event task via the extra labor interface) + */ +void rsrv_extra_labor ( void * pArg ) +{ + struct client * pClient = pArg; + write_notify_reply ( pClient ); + sendAllUpdateAS ( pClient ); + cas_send_bs_msg ( pClient, TRUE ); +} + +/* + * putNotifyErrorReply + */ +static void putNotifyErrorReply ( struct client *client, caHdrLargeArray *mp, int statusCA ) +{ + int status; + + SEND_LOCK ( client ); + /* + * the cid field abused to contain status + * during put cb replies + */ + status = cas_copy_in_header ( client, CA_PROTO_WRITE_NOTIFY, + 0u, mp->m_dataType, mp->m_count, statusCA, + mp->m_available, 0 ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK ( client ); + errlogPrintf ("%s at %d: should always get sufficent space for put notify error reply\n", + __FILE__, __LINE__); + return; + } + cas_commit_msg ( client, 0u ); + SEND_UNLOCK ( client ); +} + +void initializePutNotifyFreeList (void) +{ + if ( ! rsrvPutNotifyFreeList ) { + freeListInitPvt ( &rsrvPutNotifyFreeList, + sizeof(struct rsrv_put_notify), 512 ); + assert ( rsrvPutNotifyFreeList ); + } +} + +static struct rsrv_put_notify * + rsrvAllocPutNotify ( struct channel_in_use * pciu ) +{ + struct rsrv_put_notify *pNotify; + + if ( rsrvPutNotifyFreeList ) { + pNotify = (RSRVPUTNOTIFY *) + freeListCalloc ( rsrvPutNotifyFreeList ); + if ( pNotify ) { + pNotify->pbuffer = &pNotify->dbrScalarValue; + pNotify->valueSize = + sizeof (pNotify->dbrScalarValue); + pNotify->dbPutNotify.usrPvt = pciu; + pNotify->dbPutNotify.chan = pciu->dbch; + pNotify->dbPutNotify.putCallback = + write_notify_put_callback; + pNotify->dbPutNotify.doneCallback = + write_notify_done_callback; + pNotify->dbPutNotify.requestType = putProcessRequest; + } + } + else { + pNotify = NULL; + } + return pNotify; +} + +static int rsrvExpandPutNotify ( + struct rsrv_put_notify * pNotify, unsigned sizeNeeded ) +{ + int booleanStatus; + + if ( sizeNeeded > pNotify->valueSize ) { + /* + * try to use the union embeded in the free list + * item, but allocate a random sized block if they + * writing a vector. + */ + if ( pNotify->valueSize > + sizeof (pNotify->dbrScalarValue) ) { + free ( pNotify->pbuffer ); + } + pNotify->pbuffer = casCalloc(1,sizeNeeded); + if ( pNotify->pbuffer ) { + pNotify->valueSize = sizeNeeded; + booleanStatus = TRUE; + } + else { + /* + * revert back to the embedded union + */ + pNotify->pbuffer = + &pNotify->dbrScalarValue; + pNotify->valueSize = + sizeof (pNotify->dbrScalarValue); + booleanStatus = FALSE; + } + } + else { + booleanStatus = TRUE; + } + + return booleanStatus; +} + +unsigned rsrvSizeOfPutNotify ( struct rsrv_put_notify *pNotify ) +{ + unsigned size = sizeof ( *pNotify ); + if ( pNotify ) { + if ( pNotify->valueSize > + sizeof ( pNotify->dbrScalarValue ) ) { + size += pNotify->valueSize; + } + } + return size; +} + +void rsrvFreePutNotify ( client *pClient, + struct rsrv_put_notify *pNotify ) +{ + if ( pNotify ) { + char busyTmp; + void * asWritePvtTmp = 0; + + epicsMutexMustLock ( pClient->putNotifyLock ); + busyTmp = pNotify->busy; + epicsMutexUnlock ( pClient->putNotifyLock ); + + /* + * if any possiblity that the put notify is + * outstanding then cancel it + */ + if ( busyTmp ) { + dbNotifyCancel ( &pNotify->dbPutNotify ); + } + + epicsMutexMustLock ( pClient->putNotifyLock ); + if ( pNotify->onExtraLaborQueue ) { + ellDelete ( &pClient->putNotifyQue, + &pNotify->node ); + } + busyTmp = pNotify->busy; + asWritePvtTmp = pNotify->asWritePvt; + pNotify->asWritePvt = 0; + epicsMutexUnlock ( pClient->putNotifyLock ); + + if ( busyTmp ) { + asTrapWriteAfter ( asWritePvtTmp ); + } + + if ( pNotify->valueSize > + sizeof(pNotify->dbrScalarValue) ) { + free ( pNotify->pbuffer ); + } + freeListFree ( rsrvPutNotifyFreeList, pNotify ); + } +} + +/* + * write_notify_action() + */ +static int write_notify_action ( caHdrLargeArray *mp, void *pPayload, + struct client *client ) +{ + unsigned size; + int status; + struct channel_in_use *pciu; + + pciu = MPTOPCIU(mp); + if(!pciu){ + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + + if (mp->m_dataType > LAST_BUFFER_TYPE) { + log_header ("bad put notify data type", client, mp, pPayload, 0); + putNotifyErrorReply (client, mp, ECA_BADTYPE); + return RSRV_ERROR; + } + + if(!rsrvCheckPut(pciu)){ + putNotifyErrorReply (client, mp, ECA_NOWTACCESS); + return RSRV_OK; + } + + size = dbr_size_n (mp->m_dataType, mp->m_count); + + if ( pciu->pPutNotify ) { + + /* + * serialize concurrent put notifies + */ + epicsMutexMustLock(client->putNotifyLock); + while(pciu->pPutNotify->busy){ + epicsMutexUnlock(client->putNotifyLock); + status = epicsEventWaitWithTimeout(client->blockSem,60.0); + if ( status != epicsEventWaitOK ) { + char busyTmp; + void * asWritePvtTmp = 0; + + epicsMutexMustLock(client->putNotifyLock); + busyTmp = pciu->pPutNotify->busy; + epicsMutexUnlock(client->putNotifyLock); + + /* + * if any possibility of put notify still running + * then cancel it + */ + if ( busyTmp ) { + dbNotifyCancel(&pciu->pPutNotify->dbPutNotify); + } + epicsMutexMustLock(client->putNotifyLock); + busyTmp = pciu->pPutNotify->busy; + if ( busyTmp ) { + if ( pciu->pPutNotify->onExtraLaborQueue ) { + ellDelete ( &client->putNotifyQue, + &pciu->pPutNotify->node ); + } + pciu->pPutNotify->busy = FALSE; + asWritePvtTmp = pciu->pPutNotify->asWritePvt; + pciu->pPutNotify->asWritePvt = 0; + } + epicsMutexUnlock(client->putNotifyLock); + + if ( busyTmp ) { + log_header("put call back time out", client, + &pciu->pPutNotify->msg, pciu->pPutNotify->pbuffer, 0); + asTrapWriteAfter ( asWritePvtTmp ); + putNotifyErrorReply (client, &pciu->pPutNotify->msg, ECA_PUTCBINPROG); + } + } + epicsMutexMustLock(client->putNotifyLock); + } + epicsMutexUnlock(client->putNotifyLock); + } + else { + pciu->pPutNotify = rsrvAllocPutNotify ( pciu ); + if ( ! pciu->pPutNotify ) { + /* + * send error and go to next request + * if there isnt enough memory left + */ + log_header ( "no memory to initiate put notify", + client, mp, pPayload, 0 ); + putNotifyErrorReply (client, mp, ECA_ALLOCMEM); + return RSRV_ERROR; + } + } + + if ( ! rsrvExpandPutNotify ( pciu->pPutNotify, size ) ) { + log_header ( "no memory to initiate vector put notify", + client, mp, pPayload, 0 ); + putNotifyErrorReply ( client, mp, ECA_ALLOCMEM ); + return RSRV_ERROR; + } + + pciu->pPutNotify->busy = TRUE; + pciu->pPutNotify->onExtraLaborQueue = FALSE; + pciu->pPutNotify->msg = *mp; + pciu->pPutNotify->nRequest = mp->m_count; + + status = caNetConvert ( + mp->m_dataType, pPayload, pciu->pPutNotify->pbuffer, + FALSE /* net -> host format */, mp->m_count ); + if ( status != ECA_NORMAL ) { + log_header ("invalid data type", client, mp, pPayload, 0); + putNotifyErrorReply ( client, mp, status ); + return RSRV_ERROR; + } + + pciu->pPutNotify->dbrType = mp->m_dataType; + + pciu->pPutNotify->asWritePvt = asTrapWriteWithData ( + pciu->asClientPVT, + pciu->client->pUserName ? pciu->client->pUserName : "", + pciu->client->pHostName ? pciu->client->pHostName : "", + pciu->dbch, mp->m_dataType, mp->m_count, + pciu->pPutNotify->pbuffer ); + + dbProcessNotify(&pciu->pPutNotify->dbPutNotify); + + return RSRV_OK; +} + +/* + * + * event_add_action() + * + */ +static int event_add_action (caHdrLargeArray *mp, void *pPayload, struct client *client) +{ + struct mon_info *pmi = (struct mon_info *) pPayload; + int spaceAvailOnFreeList; + struct channel_in_use *pciu; + struct event_ext *pevext; + + if ( INVALID_DB_REQ(mp->m_dataType) ) { + return RSRV_ERROR; + } + + pciu = MPTOPCIU ( mp ); + if ( ! pciu ) { + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + + /* + * stop further use of server if memory becomes scarce + */ + spaceAvailOnFreeList = freeListItemsAvail ( rsrvEventFreeList ) > 0; + if ( osiSufficentSpaceInPool(sizeof(*pevext)) || spaceAvailOnFreeList ) { + pevext = (struct event_ext *) freeListCalloc (rsrvEventFreeList); + } + else { + pevext = 0; + } + + if (!pevext) { + log_header ("no memory to add subscription", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err( + mp, + ECA_ALLOCMEM, + client, + RECORD_NAME(pciu->dbch)); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + pevext->msg = *mp; + pevext->pciu = pciu; + pevext->size = dbr_size_n(mp->m_dataType, mp->m_count); + pevext->mask = ntohs ( pmi->m_mask ); + + epicsMutexMustLock(client->eventqLock); + ellAdd( &pciu->eventq, &pevext->node); + epicsMutexUnlock(client->eventqLock); + + pevext->pdbev = db_add_event (client->evuser, pciu->dbch, + read_reply, pevext, pevext->mask); + if (pevext->pdbev == NULL) { + log_header ("no memory to add subscription to db", + client, mp, pPayload, 0); + SEND_LOCK(client); + send_err (mp, ECA_ALLOCMEM, client, + "subscription install into record %s failed", + RECORD_NAME(pciu->dbch)); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + /* + * always send it once at event add + */ + /* + * if the client program issues many monitors + * in a row then I recv when the send side + * of the socket would block. This prevents + * a application program initiated deadlock. + * + * However when I am reconnecting I reissue + * the monitors and I could get deadlocked. + * The client is blocked sending and the server + * task for the client is blocked sending in + * this case. I cant check the recv part of the + * socket in the client since I am still handling an + * outstanding recv ( they must be processed in order). + * I handle this problem in the server by using + * post_single_event() below instead of calling + * read_reply() in this module. This is a complete + * fix since a monitor setup is the only request + * soliciting a reply in the client which is + * issued from inside of service.c (from inside + * of the part of the ca client which services + * messages sent by the server). + */ + + DLOG ( 3, ("event_add_action: db_post_single_event (0x%X)\n", + pevext->pdbev) ); + db_post_single_event(pevext->pdbev); + + /* + * enable future labor if we have read access + */ + if(asCheckGet(pciu->asClientPVT)){ + db_event_enable(pevext->pdbev); + } + else { + DLOG ( 3, ( "Disable event because cannot read\n" ) ); + } + + return RSRV_OK; +} + +/* + * clear_channel_reply() + */ +static int clear_channel_reply ( caHdrLargeArray *mp, + void *pPayload, struct client *client ) +{ + struct event_ext *pevext; + struct channel_in_use *pciu; + int status; + + /* + * + * Verify the channel + * + */ + pciu = MPTOPCIU(mp); + if(pciu?pciu->client!=client:TRUE){ + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + + rsrvFreePutNotify ( client, pciu->pPutNotify ); + + while (TRUE){ + epicsMutexMustLock(client->eventqLock); + pevext = (struct event_ext *) ellGet(&pciu->eventq); + epicsMutexUnlock(client->eventqLock); + + if(!pevext){ + break; + } + + if (pevext->pdbev) { + db_cancel_event (pevext->pdbev); + } + freeListFree(rsrvEventFreeList, pevext); + } + + db_flush_extra_labor_event ( client->evuser ); + + /* + * send delete confirmed message + */ + SEND_LOCK(client); + status = cas_copy_in_header ( client, CA_PROTO_CLEAR_CHANNEL, + 0u, mp->m_dataType, mp->m_count, mp->m_cid, + mp->m_available, NULL ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + cas_commit_msg ( client, 0u ); + SEND_UNLOCK(client); + + /* + * remove from access control list + */ + status = asRemoveClient(&pciu->asClientPVT); + if(status != 0 && status != S_asLib_asNotActive){ + errMessage(status, RECORD_NAME(pciu->dbch)); + return RSRV_ERROR; + } + + epicsMutexMustLock ( client->chanListLock ); + if ( pciu->state == rsrvCS_inService || + pciu->state == rsrvCS_pendConnectResp ) { + ellDelete ( &client->chanList, &pciu->node ); + pciu->state = rsrvCS_shutdown; + } + else if ( pciu->state == rsrvCS_inServiceUpdatePendAR || + pciu->state == rsrvCS_pendConnectRespUpdatePendAR ) { + ellDelete ( &client->chanPendingUpdateARList, &pciu->node ); + pciu->state = rsrvCS_shutdown; + } + else if ( pciu->state == rsrvCS_shutdown ) { + /* no-op */ + } + else { + epicsMutexUnlock( client->chanListLock ); + SEND_LOCK(client); + send_err(mp, ECA_INTERNAL, client, + "channel was in strange state or corrupted during cleanup"); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + epicsMutexUnlock( client->chanListLock ); + + LOCK_CLIENTQ; + status = bucketRemoveItemUnsignedId (pCaBucket, &pciu->sid); + if(status != S_bucket_success){ + UNLOCK_CLIENTQ; + errMessage (status, "Bad resource id during channel clear"); + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + rsrvChannelCount--; + UNLOCK_CLIENTQ; + + dbChannelDelete(pciu->dbch); + freeListFree(rsrvChanFreeList, pciu); + + return RSRV_OK; +} + +/* + * + * event_cancel_reply() + * + * + * Much more efficient now since the event blocks hang off the channel in use + * blocks not all together off the client block. + */ +static int event_cancel_reply ( caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + struct channel_in_use *pciu; + struct event_ext *pevext; + int status; + + /* + * + * Verify the channel + * + */ + pciu = MPTOPCIU(mp); + if (pciu?pciu->client!=client:TRUE) { + logBadId ( client, mp, pPayload ); + return RSRV_ERROR; + } + + /* + * search events on this channel for a match + * (there are usually very few monitors per channel) + */ + epicsMutexMustLock(client->eventqLock); + for (pevext = (struct event_ext *) ellFirst(&pciu->eventq); + pevext; pevext = (struct event_ext *) ellNext(&pevext->node)){ + + if (pevext->msg.m_available == mp->m_available) { + ellDelete(&pciu->eventq, &pevext->node); + break; + } + } + epicsMutexUnlock(client->eventqLock); + + /* + * Not Found- return an exception event + */ + if(!pevext){ + SEND_LOCK(client); + send_err(mp, ECA_BADMONID, client, RECORD_NAME(pciu->dbch)); + SEND_UNLOCK(client); + return RSRV_ERROR; + } + + /* + * cancel monitor activity in progress + */ + if (pevext->pdbev) { + db_cancel_event (pevext->pdbev); + } + + /* + * send delete confirmed message + */ + SEND_LOCK(client); + + status = cas_copy_in_header ( client, pevext->msg.m_cmmd, + 0u, pevext->msg.m_dataType, pevext->msg.m_count, pevext->msg.m_cid, + pevext->msg.m_available, NULL ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK(client); + return RSRV_ERROR; + } + cas_commit_msg ( client, 0 ); + SEND_UNLOCK(client); + + freeListFree (rsrvEventFreeList, pevext); + + return RSRV_OK; +} + +/* + * read_sync_reply() + */ +static int read_sync_reply ( caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + int status; + SEND_LOCK(client); + status = cas_copy_in_header ( client, mp->m_cmmd, + 0u, mp->m_dataType, mp->m_count, mp->m_cid, + mp->m_available, NULL ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK(client); + return RSRV_ERROR; + } + cas_commit_msg ( client, 0 ); + SEND_UNLOCK(client); + return RSRV_OK; +} + +/* + * search_fail_reply() + * + * Only when requested by the client + * send search failed reply + */ +static void search_fail_reply ( caHdrLargeArray *mp, void *pPayload, struct client *client) +{ + int status; + SEND_LOCK ( client ); + status = cas_copy_in_header ( client, CA_PROTO_NOT_FOUND, + 0u, mp->m_dataType, mp->m_count, mp->m_cid, mp->m_available, NULL ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK ( client ); + errlogPrintf ( "%s at %d: should always get sufficent space for search fail reply?\n", + __FILE__, __LINE__ ); + return; + } + cas_commit_msg ( client, 0 ); + SEND_UNLOCK ( client ); +} + +/* + * udp_version_action() + */ +static int udp_version_action ( caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + client->minor_version_number = mp->m_count; + + if (!CA_VSUPPORTED(mp->m_count)) { + DLOG ( 2, ( "CAS: Ignore version from unsupported client %u\n", mp->m_count ) ); + return RSRV_ERROR; + } + + if ( CA_V411 ( mp->m_count ) ) { + client->seqNoOfReq = mp->m_cid; + } + else { + client->seqNoOfReq = 0; + } + return RSRV_OK; +} + +/* + * rsrv_version_reply() + */ +int rsrv_version_reply ( struct client *client ) +{ + int status; + SEND_LOCK ( client ); + /* + * sequence number is specified zero when we copy in the + * header because we dont know it until we receive a datagram + * from the client + */ + status = cas_copy_in_header ( client, CA_PROTO_VERSION, + 0, 0, CA_MINOR_PROTOCOL_REVISION, + 0, 0, 0 ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK ( client ); + return RSRV_ERROR; + } + cas_commit_msg ( client, 0 ); + SEND_UNLOCK ( client ); + return RSRV_OK; +} + +/* + * search_reply_udp () + */ +static int search_reply_udp ( caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + ca_uint16_t *pMinorVersion; + char *pName = (char *) pPayload; + int status; + unsigned sid; + ca_uint16_t count; + ca_uint16_t type; + int spaceAvailOnFreeList; + size_t spaceNeeded; + size_t reasonableMonitorSpace = 10; + + if (!CA_VSUPPORTED(mp->m_count)) { + DLOG ( 2, ( "CAS: Ignore search from unsupported client %u\n", mp->m_count ) ); + return RSRV_ERROR; + } + + /* + * check the sanity of the message + */ + if (mp->m_postsize<=1) { + log_header ("empty PV name in UDP search request?", + client, mp, pPayload, 0); + return RSRV_OK; + } + pName[mp->m_postsize-1] = '\0'; + + /* Exit quickly if channel not on this node */ + if (dbChannelTest(pName)) { + DLOG ( 2, ( "CAS: Lookup for channel \"%s\" failed\n", pPayLoad ) ); + return RSRV_OK; + } + + /* + * stop further use of server if memory becomes scarce + */ + spaceAvailOnFreeList = freeListItemsAvail ( rsrvChanFreeList ) > 0 + && freeListItemsAvail ( rsrvEventFreeList ) > reasonableMonitorSpace; + spaceNeeded = sizeof (struct channel_in_use) + + reasonableMonitorSpace * sizeof (struct event_ext); + if ( ! ( osiSufficentSpaceInPool(spaceNeeded) || spaceAvailOnFreeList ) ) { + return RSRV_ERROR; + } + + /* + * starting with V4.4 the count field is used (abused) + * to store the minor version number of the client. + * + * New versions dont alloc the channel in response + * to a search request. + * For these, allocation has been moved to claim_ciu_action(). + * + * m_count, m_cid are already in host format... + */ + if (CA_V44(mp->m_count)) { + sid = ~0U; + count = 0; + type = ca_server_port; + } + else { + /* shouldn't actually get here due to VSUPPORTED test */ + return RSRV_ERROR; + } + + SEND_LOCK ( client ); + status = cas_copy_in_header ( client, CA_PROTO_SEARCH, + sizeof(*pMinorVersion), type, count, + sid, mp->m_available, + ( void * ) &pMinorVersion ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK ( client ); + return RSRV_ERROR; + } + + /* + * Starting with CA V4.1 the minor version number + * is appended to the end of each search reply. + * This value is ignored by earlier clients. + */ + *pMinorVersion = htons ( CA_MINOR_PROTOCOL_REVISION ); + + cas_commit_msg ( client, sizeof ( *pMinorVersion ) ); + SEND_UNLOCK ( client ); + + return RSRV_OK; +} + +/* + * search_reply_tcp () + */ +static int search_reply_tcp ( + caHdrLargeArray *mp, void *pPayload, struct client *client ) +{ + char *pName = (char *) pPayload; + int status; + int spaceAvailOnFreeList; + size_t spaceNeeded; + size_t reasonableMonitorSpace = 10; + + if (!CA_VSUPPORTED(mp->m_count)) { + DLOG ( 2, ( "CAS: Ignore search from unsupported client %u\n", mp->m_count ) ); + return RSRV_ERROR; + } + + /* + * check the sanity of the message + */ + if (mp->m_postsize<=1) { + log_header ("empty PV name in UDP search request?", + client, mp, pPayload, 0); + return RSRV_OK; + } + pName[mp->m_postsize-1] = '\0'; + + /* Exit quickly if channel not on this node */ + if (dbChannelTest(pName)) { + DLOG ( 2, ( "CAS: Lookup for channel \"%s\" failed\n", pPayLoad ) ); + if (mp->m_dataType == DOREPLY) + search_fail_reply ( mp, pPayload, client ); + return RSRV_OK; + } + + /* + * stop further use of server if memory becomes scarse + */ + spaceAvailOnFreeList = freeListItemsAvail ( rsrvChanFreeList ) > 0 + && freeListItemsAvail ( rsrvEventFreeList ) > reasonableMonitorSpace; + spaceNeeded = sizeof (struct channel_in_use) + + reasonableMonitorSpace * sizeof (struct event_ext); + if ( ! ( osiSufficentSpaceInPool(spaceNeeded) || spaceAvailOnFreeList ) ) { + SEND_LOCK(client); + send_err ( mp, ECA_ALLOCMEM, client, "Server memory exhausted" ); + SEND_UNLOCK(client); + return RSRV_OK; + } + + SEND_LOCK ( client ); + status = cas_copy_in_header ( client, CA_PROTO_SEARCH, + 0, ca_server_port, 0, ~0U, mp->m_available, 0 ); + if ( status != ECA_NORMAL ) { + SEND_UNLOCK ( client ); + return RSRV_ERROR; + } + + cas_commit_msg ( client, 0 ); + SEND_UNLOCK ( client ); + + return RSRV_OK; +} + +typedef int (*pProtoStubTCP) (caHdrLargeArray *mp, void *pPayload, struct client *client); + +/* + * TCP protocol jump table + */ +static const pProtoStubTCP tcpJumpTable[] = +{ + tcp_version_action, + event_add_action, + event_cancel_reply, + read_action, + write_action, + bad_tcp_cmd_action, + search_reply_tcp, + bad_tcp_cmd_action, + events_off_action, + events_on_action, + read_sync_reply, + bad_tcp_cmd_action, + clear_channel_reply, + bad_tcp_cmd_action, + bad_tcp_cmd_action, + read_notify_action, + bad_tcp_cmd_action, + bad_tcp_cmd_action, + claim_ciu_action, + write_notify_action, + client_name_action, + host_name_action, + bad_tcp_cmd_action, + tcp_echo_action, + bad_tcp_cmd_action, + bad_tcp_cmd_action, + bad_tcp_cmd_action, + bad_tcp_cmd_action +}; + +/* + * UDP protocol jump table + */ +typedef int (*pProtoStubUDP) (caHdrLargeArray *mp, void *pPayload, struct client *client); +static const pProtoStubUDP udpJumpTable[] = +{ + udp_version_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + search_reply_udp, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action, + bad_udp_cmd_action +}; + +/* + * CAMESSAGE() + */ +int camessage ( struct client *client ) +{ + unsigned nmsg = 0; + unsigned msgsize; + unsigned bytes_left; + int status = RSRV_ERROR; + + assert(pCaBucket); + + /* drain remnents of large messages that will not fit */ + if ( client->recvBytesToDrain ) { + if ( client->recvBytesToDrain >= client->recv.cnt ) { + client->recvBytesToDrain -= client->recv.cnt; + client->recv.stk = client->recv.cnt; + return RSRV_OK; + } + else { + client->recv.stk += client->recvBytesToDrain; + client->recvBytesToDrain = 0u; + } + } + + DLOG ( 2, ( "CAS: Parsing %d(decimal) bytes\n", recv->cnt ) ); + + while ( 1 ) + { + caHdrLargeArray msg; + caHdr *mp; + void *pBody; + + /* wait for at least a complete caHdr */ + bytes_left = client->recv.cnt - client->recv.stk; + if ( bytes_left < sizeof(*mp) ) { + status = RSRV_OK; + break; + } + + mp = (caHdr *) &client->recv.buf[client->recv.stk]; + msg.m_cmmd = ntohs ( mp->m_cmmd ); + msg.m_postsize = ntohs ( mp->m_postsize ); + msg.m_dataType = ntohs ( mp->m_dataType ); + msg.m_count = ntohs ( mp->m_count ); + msg.m_cid = ntohl ( mp->m_cid ); + msg.m_available = ntohl ( mp->m_available ); + + if ( CA_V49(client->minor_version_number) && msg.m_postsize == 0xffff ) { + ca_uint32_t *pLW = ( ca_uint32_t * ) ( mp + 1 ); + if ( bytes_left < sizeof(*mp) + 2 * sizeof(*pLW) ) { + status = RSRV_OK; + break; + } + msg.m_postsize = ntohl ( pLW[0] ); + msg.m_count = ntohl ( pLW[1] ); + msgsize = msg.m_postsize + sizeof(*mp) + 2 * sizeof ( *pLW ); + pBody = ( void * ) ( pLW + 2 ); + } + else { + msgsize = msg.m_postsize + sizeof(*mp); + pBody = ( void * ) ( mp + 1 ); + } + + /* ignore deprecated clients, but let newer clients identify themselves. */ + if (msg.m_cmmd!=CA_PROTO_VERSION && !CA_VSUPPORTED(client->minor_version_number)) { + if (client->proto==IPPROTO_TCP) { + /* log and error for too old clients, but keep the connection open to avoid a + * re-connect loop. + */ + SEND_LOCK(client); + send_err ( &msg, ECA_DEFUNCT, client, + "CAS: Client version %u too old", client->minor_version_number ); + SEND_UNLOCK(client); + log_header ( "CAS: Client version too old", + client, &msg, 0, nmsg ); + client->recvBytesToDrain = msgsize - bytes_left; + client->recv.stk = client->recv.cnt; + status = RSRV_OK; + } else { + /* silently ignore UDP from old clients */ + status = RSRV_ERROR; + } + break; + } + + /* + * disconnect clients that dont send 8 byte + * aligned payloads + */ + if ( msgsize & 0x7 ) { + if (client->proto==IPPROTO_TCP) { + SEND_LOCK(client); + send_err ( &msg, ECA_INTERNAL, client, + "CAS: Missaligned protocol rejected" ); + SEND_UNLOCK(client); + log_header ( "CAS: Missaligned protocol rejected", + client, &msg, 0, nmsg ); + } + status = RSRV_ERROR; + break; + } + + /* problem: we have a complete header, + * but before we check msgsize we don't know + * if we have a complete message body + * -> we may be called again with the same header + * after receiving the full message + */ + if ( msgsize > client->recv.maxstk ) { + casExpandRecvBuffer ( client, msgsize ); + if ( msgsize > client->recv.maxstk ) { + if (client->proto==IPPROTO_TCP) { + SEND_LOCK(client); + send_err ( &msg, ECA_TOLARGE, client, + "CAS: Server unable to load large request message. Max bytes=%lu", + rsrvSizeofLargeBufTCP ); + SEND_UNLOCK(client); + log_header ( "CAS: server unable to load large request message", + client, &msg, 0, nmsg ); + } + assert ( client->recv.cnt <= client->recv.maxstk ); + assert ( msgsize >= bytes_left ); + client->recvBytesToDrain = msgsize - bytes_left; + client->recv.stk = client->recv.cnt; + status = RSRV_OK; + break; + } + } + + /* + * wait for complete message body + */ + if ( msgsize > bytes_left ) { + status = RSRV_OK; + break; + } + + nmsg++; + + if ( CASDEBUG > 2 ) + log_header (NULL, client, &msg, pBody, nmsg); + + if ( client->proto==IPPROTO_UDP ) { + if ( msg.m_cmmd < NELEMENTS ( udpJumpTable ) ) { + status = ( *udpJumpTable[msg.m_cmmd] )( &msg, pBody, client ); + if (status!=RSRV_OK) { + status = RSRV_ERROR; + break; + } + } + else { + status = bad_udp_cmd_action ( &msg, pBody, client ); + break; + } + } + else { + if ( msg.m_cmmd < NELEMENTS(tcpJumpTable) ) { + status = ( *tcpJumpTable[msg.m_cmmd] ) ( &msg, pBody, client ); + if ( status != RSRV_OK ) { + status = RSRV_ERROR; + break; + } + } + else { + return bad_tcp_cmd_action ( &msg, pBody, client ); + } + } + + client->recv.stk += msgsize; + } + + return status; +} + +/* + * rsrvCheckPut () + */ +int rsrvCheckPut (const struct channel_in_use *pciu) +{ + /* + * SPC_NOMOD fields are always unwritable + */ + if (dbChannelSpecial(pciu->dbch) == SPC_NOMOD) { + return 0; + } + else { + return asCheckPut (pciu->asClientPVT); + } +} diff --git a/modules/database/src/ioc/rsrv/camsgtask.c b/modules/database/src/ioc/rsrv/camsgtask.c new file mode 100644 index 000000000..04a7e780e --- /dev/null +++ b/modules/database/src/ioc/rsrv/camsgtask.c @@ -0,0 +1,179 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Jeffrey O. Hill + * hill@luke.lanl.gov + * (505) 665 1831 + * Date: 6-88 + */ + + +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsStdio.h" +#include "epicsTime.h" +#include "errlog.h" +#include "osiSock.h" +#include "taskwd.h" + +#include "caerr.h" + +#define epicsExportSharedSymbols +#include "db_access.h" +#include "rsrv.h" +#include "server.h" + +/* + * camsgtask() + * + * CA server TCP client task (one spawned for each client) + */ +void camsgtask ( void *pParm ) +{ + struct client *client = (struct client *) pParm; + + casAttachThreadToClient ( client ); + + while (castcp_ctl == ctlRun && !client->disconnect) { + osiSockIoctl_t check_nchars; + long nchars; + int status; + + /* + * allow message to batch up if more are comming + */ + status = socket_ioctl (client->sock, FIONREAD, &check_nchars); + if (status < 0) { + char sockErrBuf[64]; + + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf("CAS: FIONREAD error: %s\n", + sockErrBuf); + cas_send_bs_msg(client, TRUE); + } + else if (check_nchars == 0){ + cas_send_bs_msg(client, TRUE); + } + + client->recv.stk = 0; + assert ( client->recv.maxstk >= client->recv.cnt ); + nchars = recv ( client->sock, &client->recv.buf[client->recv.cnt], + (int) ( client->recv.maxstk - client->recv.cnt ), 0 ); + if ( nchars == 0 ){ + if ( CASDEBUG > 0 ) { + /* convert to u long so that %lu works on both 32 and 64 bit archs */ + unsigned long cnt = sizeof ( client->recv.buf ) - client->recv.cnt; + errlogPrintf ( "CAS: nill message disconnect ( %lu bytes request )\n", + cnt ); + } + break; + } + else if ( nchars < 0 ) { + int anerrno = SOCKERRNO; + + if ( anerrno == SOCK_EINTR ) { + continue; + } + + if ( anerrno == SOCK_ENOBUFS ) { + errlogPrintf ( + "CAS: Out of network buffers, retring receive in 15 seconds\n" ); + epicsThreadSleep ( 15.0 ); + continue; + } + + /* + * normal conn lost conditions + */ + if ( ( anerrno != SOCK_ECONNABORTED && + anerrno != SOCK_ECONNRESET && + anerrno != SOCK_ETIMEDOUT ) || + CASDEBUG > 2 ) { + char sockErrBuf[64]; + + epicsSocketConvertErrorToString( + sockErrBuf, sizeof ( sockErrBuf ), anerrno); + errlogPrintf ( "CAS: Client disconnected - %s\n", + sockErrBuf ); + } + break; + } + + epicsTimeGetCurrent ( &client->time_at_last_recv ); + client->recv.cnt += ( unsigned ) nchars; + + status = camessage ( client ); + if (status == 0) { + /* + * if there is a partial message + * align it with the start of the buffer + */ + if (client->recv.cnt > client->recv.stk) { + unsigned bytes_left; + + bytes_left = client->recv.cnt - client->recv.stk; + + /* + * overlapping regions handled + * properly by memmove + */ + memmove (client->recv.buf, + &client->recv.buf[client->recv.stk], bytes_left); + client->recv.cnt = bytes_left; + } + else { + client->recv.cnt = 0ul; + } + } + else { + char buf[64]; + + /* flush any queued messages before shutdown */ + cas_send_bs_msg(client, 1); + + client->recv.cnt = 0ul; + + /* + * disconnect when there are severe message errors + */ + ipAddrToDottedIP (&client->addr, buf, sizeof(buf)); + epicsPrintf ("CAS: forcing disconnect from %s\n", buf); + break; + } + } + + LOCK_CLIENTQ; + ellDelete ( &clientQ, &client->node ); + UNLOCK_CLIENTQ; + + destroy_tcp_client ( client ); +} + + +int casClientInitiatingCurrentThread ( char * pBuf, size_t bufSize ) +{ + struct client * pClient = ( struct client * ) + epicsThreadPrivateGet ( rsrvCurrentClient ); + + if ( ! pClient ) + return RSRV_ERROR; + + if ( pBuf && bufSize ) { + epicsSnprintf(pBuf, bufSize, "ca:%s@%s", + pClient->pUserName, pClient->pHostName); + } + return RSRV_OK; +} + diff --git a/modules/database/src/ioc/rsrv/caserverio.c b/modules/database/src/ioc/rsrv/caserverio.c new file mode 100644 index 000000000..20a1c1c5b --- /dev/null +++ b/modules/database/src/ioc/rsrv/caserverio.c @@ -0,0 +1,405 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Jeffrey O. Hill + * hill@luke.lanl.gov + * (505) 665 1831 + * Date: 060791 + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsSignal.h" +#include "epicsTime.h" +#include "errlog.h" +#include "osiSock.h" + +#include "caerr.h" +#include "net_convert.h" + +#define epicsExportSharedSymbols +#include "server.h" + +/* + * cas_send_bs_msg() + * + * (channel access server send message) + * + * + * Set lock_needed=1 unless SEND_LOCK() is held by caller + */ +void cas_send_bs_msg ( struct client *pclient, int lock_needed ) +{ + int status; + + if ( lock_needed ) { + SEND_LOCK ( pclient ); + } + + if ( CASDEBUG > 2 && pclient->send.stk ) { + errlogPrintf ( "CAS: Sending a message of %d bytes\n", pclient->send.stk ); + } + + if ( pclient->disconnect ) { + if ( CASDEBUG > 2 ) { + errlogPrintf ( "CAS: msg Discard for sock %d addr %x\n", + pclient->sock, (unsigned) pclient->addr.sin_addr.s_addr ); + } + pclient->send.stk = 0u; + if(lock_needed) + SEND_UNLOCK(pclient); + return; + } + + while ( pclient->send.stk && ! pclient->disconnect ) { + status = send ( pclient->sock, pclient->send.buf, pclient->send.stk, 0 ); + if ( status >= 0 ) { + unsigned transferSize = (unsigned) status; + if ( transferSize >= pclient->send.stk ) { + pclient->send.stk = 0; + epicsTimeGetCurrent ( &pclient->time_at_last_send ); + break; + } + else { + unsigned bytesLeft = pclient->send.stk - transferSize; + memmove ( pclient->send.buf, &pclient->send.buf[transferSize], + bytesLeft ); + pclient->send.stk = bytesLeft; + } + } + else { + int causeWasSocketHangup = 0; + int anerrno = SOCKERRNO; + char buf[64]; + + if ( pclient->disconnect ) { + pclient->send.stk = 0u; + break; + } + + if ( anerrno == SOCK_EINTR ) { + continue; + } + + if ( anerrno == SOCK_ENOBUFS ) { + errlogPrintf ( + "CAS: Out of network buffers, retrying send in 15 seconds\n" ); + epicsThreadSleep ( 15.0 ); + continue; + } + + ipAddrToDottedIP ( &pclient->addr, buf, sizeof(buf) ); + + if ( + anerrno == SOCK_ECONNABORTED || + anerrno == SOCK_ECONNRESET || + anerrno == SOCK_EPIPE || + anerrno == SOCK_ETIMEDOUT ) { + causeWasSocketHangup = 1; + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAS: TCP send to %s failed: %s\n", + buf, sockErrBuf); + } + pclient->disconnect = TRUE; + pclient->send.stk = 0u; + + /* + * wakeup the receive thread + */ + if ( ! causeWasSocketHangup ) { + enum epicsSocketSystemCallInterruptMechanismQueryInfo info = + epicsSocketSystemCallInterruptMechanismQuery (); + switch ( info ) { + case esscimqi_socketCloseRequired: + if ( pclient->sock != INVALID_SOCKET ) { + epicsSocketDestroy ( pclient->sock ); + pclient->sock = INVALID_SOCKET; + } + break; + case esscimqi_socketBothShutdownRequired: + { + int status = shutdown ( pclient->sock, SHUT_RDWR ); + if ( status ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAS: Socket shutdown error: %s\n", + sockErrBuf ); + } + } + break; + case esscimqi_socketSigAlarmRequired: + epicsSignalRaiseSigAlarm ( pclient->tid ); + break; + default: + break; + }; + break; + } + } + } + + if ( lock_needed ) { + SEND_UNLOCK(pclient); + } + + DLOG ( 3, ( "------------------------------\n\n" ) ); + + return; +} + +/* + * cas_send_dg_msg() + * + * (channel access server send udp message) + */ +void cas_send_dg_msg ( struct client * pclient ) +{ + int status; + int sizeDG; + char * pDG; + caHdr * pMsg; + + if ( CASDEBUG > 2 && pclient->send.stk ) { + errlogPrintf ( "CAS: Sending a udp message of %d bytes\n", pclient->send.stk ); + } + + SEND_LOCK ( pclient ); + + if ( pclient->send.stk <= sizeof (caHdr) ) { + SEND_UNLOCK(pclient); + return; + } + + pDG = pclient->send.buf; + pMsg = ( caHdr * ) pDG; + sizeDG = pclient->send.stk; + assert ( ntohs ( pMsg->m_cmmd ) == CA_PROTO_VERSION ); + if ( CA_V411 ( pclient->minor_version_number ) ) { + pMsg->m_cid = htonl ( pclient->seqNoOfReq ); + pMsg->m_dataType = htons ( sequenceNoIsValid ); + } + else { + pDG += sizeof (caHdr); + sizeDG -= sizeof (caHdr); + } + + status = sendto ( pclient->sock, pDG, sizeDG, 0, + (struct sockaddr *)&pclient->addr, sizeof(pclient->addr) ); + if ( status >= 0 ) { + if ( status >= sizeDG ) { + epicsTimeGetCurrent ( &pclient->time_at_last_send ); + } + else { + errlogPrintf ( + "CAS: System failed to send entire udp frame?\n" ); + } + } + else { + char sockErrBuf[64]; + char buf[128]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + ipAddrToDottedIP ( &pclient->addr, buf, sizeof(buf) ); + errlogPrintf( "CAS: UDP send to %s failed: %s\n", + buf, sockErrBuf); + } + + pclient->send.stk = 0u; + + /* + * add placeholder for the first version message should it be needed + */ + rsrv_version_reply ( pclient ); + + SEND_UNLOCK(pclient); + + DLOG ( 3, ( "------------------------------\n\n" ) ); + + return; +} + +/* + * + * cas_copy_in_header() + * + * Allocate space in the outgoing message buffer and + * copy in message header. Return pointer to message body. + * + * send lock must be on while in this routine + * + * Returns a valid ptr to message body or NULL if the msg + * will not fit. + */ +int cas_copy_in_header ( + struct client *pclient, ca_uint16_t response, ca_uint32_t payloadSize, + ca_uint16_t dataType, ca_uint32_t nElem, ca_uint32_t cid, + ca_uint32_t responseSpecific, void **ppPayload ) +{ + unsigned msgSize; + ca_uint32_t alignedPayloadSize; + caHdr *pMsg; + + if ( payloadSize > UINT_MAX - sizeof ( caHdr ) - 8u ) { + return ECA_TOLARGE; + } + + alignedPayloadSize = CA_MESSAGE_ALIGN ( payloadSize ); + + msgSize = alignedPayloadSize + sizeof ( caHdr ); + if ( alignedPayloadSize >= 0xffff || nElem >= 0xffff ) { + if ( ! CA_V49 ( pclient->minor_version_number ) ) { + return ECA_16KARRAYCLIENT; + } + msgSize += 2 * sizeof ( ca_uint32_t ); + } + + if ( msgSize > pclient->send.maxstk ) { + casExpandSendBuffer ( pclient, msgSize ); + if ( msgSize > pclient->send.maxstk ) { + return ECA_TOLARGE; + } + } + + if ( pclient->send.stk > pclient->send.maxstk - msgSize ) { + if ( pclient->disconnect ) { + pclient->send.stk = 0; + } + else{ + if ( pclient->proto == IPPROTO_TCP) { + cas_send_bs_msg ( pclient, FALSE ); + } + else if ( pclient->proto == IPPROTO_UDP ) { + cas_send_dg_msg ( pclient ); + } + else { + return ECA_INTERNAL; + } + } + } + + pMsg = (caHdr *) &pclient->send.buf[pclient->send.stk]; + pMsg->m_cmmd = htons(response); + pMsg->m_dataType = htons(dataType); + pMsg->m_cid = htonl(cid); + pMsg->m_available = htonl(responseSpecific); + if (alignedPayloadSize < 0xffff && nElem < 0xffff) { + pMsg->m_postsize = htons(((ca_uint16_t) alignedPayloadSize)); + pMsg->m_count = htons(((ca_uint16_t) nElem)); + if (ppPayload) + *ppPayload = (void *) (pMsg + 1); + } + else { + ca_uint32_t *pW32 = (ca_uint32_t *) (pMsg + 1); + pMsg->m_postsize = htons(0xffff); + pMsg->m_count = htons(0u); + pW32[0] = htonl(alignedPayloadSize); + pW32[1] = htonl(nElem); + if (ppPayload) + *ppPayload = (void *) (pW32 + 2); + } + + /* zero out pad bytes */ + if ( alignedPayloadSize > payloadSize ) { + char *p = ( char * ) *ppPayload; + memset ( p + payloadSize, '\0', + alignedPayloadSize - payloadSize ); + } + + return ECA_NORMAL; +} + +void cas_set_header_cid ( struct client *pClient, ca_uint32_t cid ) +{ + caHdr *pMsg = ( caHdr * ) &pClient->send.buf[pClient->send.stk]; + pMsg->m_cid = htonl ( cid ); +} + +void cas_set_header_count (struct client *pClient, ca_uint32_t count) +{ + caHdr *pMsg = (caHdr *) &pClient->send.buf[pClient->send.stk]; + if (pMsg->m_postsize == htons(0xffff)) { + ca_uint32_t *pLW; + + assert(pMsg->m_count == 0); + pLW = (ca_uint32_t *) (pMsg + 1); + pLW[1] = htonl(count); + } + else { + assert(count < 65536); + pMsg->m_count = htons((ca_uint16_t) count); + } +} + +void cas_commit_msg ( struct client *pClient, ca_uint32_t size ) +{ + caHdr * pMsg = ( caHdr * ) &pClient->send.buf[pClient->send.stk]; + size = CA_MESSAGE_ALIGN ( size ); + if ( pMsg->m_postsize == htons ( 0xffff ) ) { + ca_uint32_t * pLW = ( ca_uint32_t * ) ( pMsg + 1 ); + assert ( size <= ntohl ( *pLW ) ); + pLW[0] = htonl ( size ); + size += sizeof ( caHdr ) + 2 * sizeof ( *pLW ); + } + else { + assert ( size <= ntohs ( pMsg->m_postsize ) ); + pMsg->m_postsize = htons ( (ca_uint16_t) size ); + size += sizeof ( caHdr ); + } + pClient->send.stk += size; +} + +/* + * this assumes that we have already checked to see + * if sufficent bytes are available + */ +ca_uint16_t rsrvGetUInt16 ( struct message_buffer *recv ) +{ + ca_uint8_t *pBuf = ( ca_uint8_t * ) recv->buf; + ca_uint16_t result; + /* + * this assumes that we have already checked to see + * if sufficent bytes are available + */ + assert ( recv->cnt - recv->stk >= 2u ); + result = pBuf[recv->stk++] << 8u; + result |= pBuf[recv->stk++] << 0u; + return result; +} + +/* + * this assumes that we have already checked to see + * if sufficent bytes are available + */ +ca_uint32_t rsrvGetUInt32 ( struct message_buffer *recv ) +{ + ca_uint8_t *pBuf = ( ca_uint8_t * ) recv->buf; + ca_uint32_t result; + /* + * this assumes that we have already checked to see + * if sufficent bytes are available + */ + assert ( recv->cnt - recv->stk >= 4u ); + result = pBuf[recv->stk++] << 24u; + result |= pBuf[recv->stk++] << 16u; + result |= pBuf[recv->stk++] << 8u; + result |= pBuf[recv->stk++] << 0u; + return result; +} diff --git a/modules/database/src/ioc/rsrv/caservertask.c b/modules/database/src/ioc/rsrv/caservertask.c new file mode 100644 index 000000000..0b7a068a5 --- /dev/null +++ b/modules/database/src/ioc/rsrv/caservertask.c @@ -0,0 +1,1558 @@ +/*************************************************************************\ +* Copyright (c) 2016 Michael Davidsaver +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Jeffrey O. Hill + */ + +#include +#include +#include +#include +#include +#include + +#include "addrList.h" +#include "epicsEvent.h" +#include "epicsMutex.h" +#include "epicsSignal.h" +#include "epicsStdio.h" +#include "epicsTime.h" +#include "errlog.h" +#include "freeList.h" +#include "osiPoolStatus.h" +#include "osiSock.h" +#include "taskwd.h" +#include "cantProceed.h" + +#include "epicsExport.h" + +#define epicsExportSharedSymbols +#include "dbChannel.h" +#include "dbCommon.h" +#include "dbEvent.h" +#include "db_field_log.h" +#include "dbServer.h" +#include "rsrv.h" + +#define GLBLSOURCE +#include "server.h" + +epicsThreadPrivateId rsrvCurrentClient; + +/* + * + * req_server() + * + * CA server task + * + * Waits for connections at the CA port and spawns a task to + * handle each of them + * + */ +static void req_server (void *pParm) +{ + rsrv_iface_config *conf = pParm; + SOCKET IOC_sock; + + taskwdInsert ( epicsThreadGetIdSelf (), NULL, NULL ); + + IOC_sock = conf->tcp; + + /* listen and accept new connections */ + if ( listen ( IOC_sock, 20 ) < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAS: Listen error: %s\n", + sockErrBuf ); + epicsSocketDestroy (IOC_sock); + epicsThreadSuspendSelf (); + } + + epicsEventSignal(castcp_startStopEvent); + + while (TRUE) { + SOCKET clientSock; + struct sockaddr sockAddr; + osiSocklen_t addLen = sizeof(sockAddr); + + while (castcp_ctl == ctlPause) { + epicsThreadSleep(0.1); + } + + clientSock = epicsSocketAccept ( IOC_sock, &sockAddr, &addLen ); + if ( clientSock == INVALID_SOCKET ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf("CAS: Client accept error: %s\n", + sockErrBuf ); + epicsThreadSleep(15.0); + continue; + } + else { + epicsThreadId id; + struct client *pClient; + + /* socket passed in is closed if unsuccessful here */ + pClient = create_tcp_client ( clientSock ); + if ( ! pClient ) { + epicsThreadSleep ( 15.0 ); + continue; + } + + LOCK_CLIENTQ; + ellAdd ( &clientQ, &pClient->node ); + UNLOCK_CLIENTQ; + + id = epicsThreadCreate ( "CAS-client", epicsThreadPriorityCAServerLow, + epicsThreadGetStackSize ( epicsThreadStackBig ), + camsgtask, pClient ); + if ( id == 0 ) { + LOCK_CLIENTQ; + ellDelete ( &clientQ, &pClient->node ); + UNLOCK_CLIENTQ; + destroy_tcp_client ( pClient ); + errlogPrintf ( "CAS: task creation for new client failed\n" ); + epicsThreadSleep ( 15.0 ); + continue; + } + } + } +} + +static +int tryBind(SOCKET sock, const osiSockAddr* addr, const char *name) +{ + if(bind(sock, (struct sockaddr *) &addr->sa, sizeof(*addr))<0) { + char sockErrBuf[64]; + if(SOCKERRNO!=SOCK_EADDRINUSE) + { + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAS: %s bind error: %s\n", + name, sockErrBuf ); + epicsThreadSuspendSelf (); + } + return -1; + } else + return 0; +} + +/* need to collect a set of TCP sockets, one for each interface, + * which are bound to the same TCP port number. + * Needed to avoid the complications and confusion of different TCP + * ports for each interface (name server and beacon sender would need + * to know this). + */ +static +SOCKET* rsrv_grab_tcp(unsigned short *port) +{ + SOCKET *socks; + osiSockAddr scratch; + unsigned i; + + socks = mallocMustSucceed(ellCount(&casIntfAddrList)*sizeof(*socks), "rsrv_grab_tcp"); + for(i=0; i0) { + ELLNODE *cur, *next; + unsigned ok = 1; + + for(i=0; iaddr; + + scratch.ia.sin_addr = ifaceAddr.ia.sin_addr; + + tcpsock = socks[i] = epicsSocketCreate (AF_INET, SOCK_STREAM, 0); + if(tcpsock==INVALID_SOCKET) + cantProceed("rsrv ran out of sockets during initialization"); + + epicsSocketEnableAddressReuseDuringTimeWaitState ( tcpsock ); + + if(bind(tcpsock, &scratch.sa, sizeof(scratch))==0) { + if(scratch.ia.sin_port==0) { + /* use first socket to pick a random port */ + osiSocklen_t alen = sizeof(ifaceAddr); + assert(i==0); + if(getsockname(tcpsock, &ifaceAddr.sa, &alen)) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAS: getsockname error: %s\n", + sockErrBuf ); + epicsThreadSuspendSelf (); + ok = 0; + break; + } + scratch.ia.sin_port = ifaceAddr.ia.sin_port; + assert(scratch.ia.sin_port!=0); + } + } else { + int errcode = SOCKERRNO; + /* bind fails. React harshly to unexpected errors to avoid an infinite loop */ + if(errcode==SOCK_EADDRNOTAVAIL) { + /* this is not a bind()able address. */ + int j; + char name[40]; + ipAddrToDottedIP(&scratch.ia, name, sizeof(name)); + printf("Skipping %s which is not an interface address\n", name); + + for(j=0; j<=i; j++) { + epicsSocketDestroy(socks[j]); + socks[j] = INVALID_SOCKET; + } + + ellDelete(&casIntfAddrList, cur); + free(cur); + ok = 0; + break; + } + /* if SOCK_EADDRINUSE or SOCK_EACCES try again with a different + * port number, otherwise fail hard. + */ + if (errcode != SOCK_EADDRINUSE && + errcode != SOCK_EACCES) { + char name[40]; + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + ipAddrToDottedIP(&scratch.ia, name, sizeof(name)); + cantProceed( "CAS: Socket bind %s error: %s\n", + name, sockErrBuf ); + } + ok = 0; + break; + } + } + + if (ok) { + assert(scratch.ia.sin_port!=0); + *port = ntohs(scratch.ia.sin_port); + + break; + } else { + + for(i=0; inode) : NULL; + pNode; + pNode = pNext, + pNext = pNext ? (osiSockAddrNode*)ellNext(&pNext->node) : NULL) + { + osiSockAddr match; + epicsUInt32 top = ntohl(pNode->addr.ia.sin_addr.s_addr)>>24; + + if(pNode->addr.ia.sin_family==AF_INET && pNode->addr.ia.sin_addr.s_addr==htonl(INADDR_ANY)) + { + foundWildcard = 1; + + } else if(pNode->addr.ia.sin_family==AF_INET && top>=224 && top<=239) { + /* This is a multi-cast address */ + ellDelete(&casIntfAddrList, &pNode->node); + ellAdd(&casMCastAddrList, &pNode->node); + continue; + } + + if(!doautobeacon) + continue; + /* when given a specific interface address, auto populate with the + * corresponding broadcast address. + */ + + autobeaconlist = 0; /* prevent later population from wildcard */ + + memset(&match, 0, sizeof(match)); + match.ia.sin_family = AF_INET; + match.ia.sin_addr.s_addr = pNode->addr.ia.sin_addr.s_addr; + match.ia.sin_port = htons(ca_beacon_port); + + osiSockDiscoverBroadcastAddresses(&beaconAddrList, beaconSocket, &match); + } + + if (foundWildcard && ellCount(&casIntfAddrList) != 1) { + cantProceed("CAS interface address list can not contain 0.0.0.0 and other interface addresses.\n"); + } + } + + if (ellCount(&casIntfAddrList) == 0) { + /* default to wildcard 0.0.0.0 when interface address list is empty */ + osiSockAddrNode *pNode = (osiSockAddrNode *) callocMustSucceed( 1, sizeof(*pNode), "rsrv_init" ); + pNode->addr.ia.sin_family = AF_INET; + pNode->addr.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + pNode->addr.ia.sin_port = 0; + ellAdd ( &casIntfAddrList, &pNode->node ); + } + + { + ELLLIST temp = ELLLIST_INIT; + osiSockAddrNode *pNode; + + ellConcat(&temp, &beaconAddrList); + + /* collect user specified beacon address list + * prefer EPICS_CAS_BEACON_ADDR_LIST, fallback to EPICS_CA_ADDR_LIST + */ + addAddrToChannelAccessAddressList ( &temp, &EPICS_CAS_BEACON_ADDR_LIST, ca_beacon_port, 0 ); + + if (autobeaconlist) { + /* auto populate with all broadcast addresses. + * Note that autobeaconlist is zeroed above if an interface + * address list is provided. + */ + osiSockAddr match; + memset(&match, 0, sizeof(match)); + match.ia.sin_family = AF_INET; + match.ia.sin_addr.s_addr = htonl(INADDR_ANY); + match.ia.sin_port = htons(ca_beacon_port); + + osiSockDiscoverBroadcastAddresses(&temp, beaconSocket, &match); + } + + /* set the port for any automatically discovered destinations. */ + for(pNode = (osiSockAddrNode*)ellFirst(&temp); + pNode; + pNode = (osiSockAddrNode*)ellNext(&pNode->node)) + { + if(pNode->addr.ia.sin_port==0) + pNode->addr.ia.sin_port = htons(ca_beacon_port); + } + + removeDuplicateAddresses(&beaconAddrList, &temp, 0); + } + + if (ellCount(&beaconAddrList)==0) + fprintf(stderr, "Warning: RSRV has empty beacon address list\n"); + + { + osiSockAddrNode *node; + ELLLIST temp = ELLLIST_INIT, + temp2= ELLLIST_INIT; + size_t idx = 0; + + addAddrToChannelAccessAddressList ( &temp, &EPICS_CAS_IGNORE_ADDR_LIST, 0, 0 ); + removeDuplicateAddresses(&temp2, &temp, 0); + + /* Keep the list of addresses to ignore in an array on the assumption that + * it is short enough that using a hash table would be slower. + * 0.0.0.0 indicates end of list + */ + casIgnoreAddrs = callocMustSucceed(1+ellCount(&temp2), sizeof(casIgnoreAddrs[0]), "casIgnoreAddrs"); + + while((node=(osiSockAddrNode*)ellGet(&temp2))!=NULL) + { + casIgnoreAddrs[idx++] = node->addr.ia.sin_addr.s_addr; + free(node); + } + casIgnoreAddrs[idx] = 0; + } +} + +/* + * rsrv_init () + */ +static +void rsrv_init (void) +{ + long maxBytesAsALong; + long status; + SOCKET *socks; + int autoMaxBytes; + + clientQlock = epicsMutexMustCreate(); + + freeListInitPvt ( &rsrvClientFreeList, sizeof(struct client), 8 ); + freeListInitPvt ( &rsrvChanFreeList, sizeof(struct channel_in_use), 512 ); + freeListInitPvt ( &rsrvEventFreeList, sizeof(struct event_ext), 512 ); + freeListInitPvt ( &rsrvSmallBufFreeListTCP, MAX_TCP, 16 ); + initializePutNotifyFreeList (); + + epicsSignalInstallSigPipeIgnore (); + + rsrvCurrentClient = epicsThreadPrivateCreate (); + + if ( envGetConfigParamPtr ( &EPICS_CAS_SERVER_PORT ) ) { + ca_server_port = envGetInetPortConfigParam ( &EPICS_CAS_SERVER_PORT, + (unsigned short) CA_SERVER_PORT ); + } + else { + ca_server_port = envGetInetPortConfigParam ( &EPICS_CA_SERVER_PORT, + (unsigned short) CA_SERVER_PORT ); + } + ca_udp_port = ca_server_port; + + if (envGetConfigParamPtr(&EPICS_CAS_BEACON_PORT)) { + ca_beacon_port = envGetInetPortConfigParam (&EPICS_CAS_BEACON_PORT, + (unsigned short) CA_REPEATER_PORT ); + } + else { + ca_beacon_port = envGetInetPortConfigParam (&EPICS_CA_REPEATER_PORT, + (unsigned short) CA_REPEATER_PORT ); + } + + status = envGetLongConfigParam ( &EPICS_CA_MAX_ARRAY_BYTES, &maxBytesAsALong ); + if ( status || maxBytesAsALong < 0 ) { + errlogPrintf ( "CAS: EPICS_CA_MAX_ARRAY_BYTES was not a positive integer\n" ); + rsrvSizeofLargeBufTCP = MAX_TCP; + } + else { + /* allow room for the protocol header so that they get the array size they requested */ + static const unsigned headerSize = sizeof ( caHdr ) + 2 * sizeof ( ca_uint32_t ); + ca_uint32_t maxBytes = ( unsigned ) maxBytesAsALong; + if ( maxBytes < 0xffffffff - headerSize ) { + maxBytes += headerSize; + } + else { + maxBytes = 0xffffffff; + } + if ( maxBytes < MAX_TCP ) { + errlogPrintf ( "CAS: EPICS_CA_MAX_ARRAY_BYTES was rounded up to %u\n", MAX_TCP ); + rsrvSizeofLargeBufTCP = MAX_TCP; + } + else { + rsrvSizeofLargeBufTCP = maxBytes; + } + } + + if(envGetBoolConfigParam(&EPICS_CA_AUTO_ARRAY_BYTES, &autoMaxBytes)) + autoMaxBytes = 1; + + if (!autoMaxBytes) + freeListInitPvt ( &rsrvLargeBufFreeListTCP, rsrvSizeofLargeBufTCP, 1 ); + else + rsrvLargeBufFreeListTCP = NULL; + pCaBucket = bucketCreate(CAS_HASH_TABLE_SIZE); + if (!pCaBucket) + cantProceed("RSRV failed to allocate ID lookup table\n"); + + rsrv_build_addr_lists(); + + castcp_startStopEvent = epicsEventMustCreate(epicsEventEmpty); + casudp_startStopEvent = epicsEventMustCreate(epicsEventEmpty); + beacon_startStopEvent = epicsEventMustCreate(epicsEventEmpty); + castcp_ctl = ctlPause; + + /* Thread priorites + * Now starting per interface + * TCP Listener: epicsThreadPriorityCAServerLow-2 + * Name receiver: epicsThreadPriorityCAServerLow-4 + * Now starting global + * Beacon sender: epicsThreadPriorityCAServerLow-3 + * Started later per TCP client + * TCP receiver: epicsThreadPriorityCAServerLow + * TCP sender : epicsThreadPriorityCAServerLow-1 + */ + { + unsigned i; + threadPrios[0] = epicsThreadPriorityCAServerLow; + + for(i=1; itcpAddr = ((osiSockAddrNode *)cur)->addr; + conf->tcpAddr.ia.sin_port = htons(ca_server_port); + conf->tcp = socks[i]; + socks[i] = INVALID_SOCKET; + + ipAddrToDottedIP (&conf->tcpAddr.ia, ifaceName, sizeof(ifaceName)); + + conf->udp = conf->udpbcast = INVALID_SOCKET; + + /* create and bind UDP name receiver socket(s) */ + + conf->udp = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); + if(conf->udp==INVALID_SOCKET) + cantProceed("rsrv_init ran out of udp sockets"); + + conf->udpAddr = conf->tcpAddr; + conf->udpAddr.ia.sin_port = htons(ca_udp_port); + + epicsSocketEnableAddressUseForDatagramFanout ( conf->udp ); + + if(tryBind(conf->udp, &conf->udpAddr, "UDP unicast socket")) + goto cleanup; + +#ifdef IP_ADD_MEMBERSHIP + /* join UDP socket to any multicast groups */ + { + osiSockAddrNode *pNode; + + for(pNode = (osiSockAddrNode*)ellFirst(&casMCastAddrList); + pNode; + pNode = (osiSockAddrNode*)ellNext(&pNode->node)) + { + struct ip_mreq mreq; + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = pNode->addr.ia.sin_addr; + mreq.imr_interface.s_addr = conf->udpAddr.ia.sin_addr.s_addr; + + if (setsockopt(conf->udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *) &mreq, sizeof(mreq))!=0) { + struct sockaddr_in temp; + char name[40]; + char sockErrBuf[64]; + temp.sin_family = AF_INET; + temp.sin_addr = mreq.imr_multiaddr; + temp.sin_port = conf->udpAddr.ia.sin_port; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + ipAddrToDottedIP (&temp, name, sizeof(name)); + errlogPrintf("CAS: Socket mcast join %s to %s failed: %s\n", + ifaceName, name, sockErrBuf ); + } + } + } +#else + if(ellCount(&casMCastAddrList)){ + fprintf(stderr, "IPv4 Multicast name lookup not supported by this target\n"); + } +#endif + +#if !(defined(_WIN32) || defined(__CYGWIN__)) + /* An oddness of BSD sockets (not winsock) is that binding to + * INADDR_ANY will receive unicast and broadcast, but binding to + * a specific interface address receives only unicast. The trick + * is to bind a second socket to the interface broadcast address, + * which will then receive only broadcasts. + */ + if(conf->udpAddr.ia.sin_addr.s_addr!=htonl(INADDR_ANY)) { + /* find interface broadcast address */ + ELLLIST bcastList = ELLLIST_INIT; + osiSockAddrNode *pNode; + + osiSockDiscoverBroadcastAddresses (&bcastList, + conf->udp, &conf->udpAddr); // match addr + + if(ellCount(&bcastList)==0) { + fprintf(stderr, "Warning: Can't find broadcast address of interface %s\n" + " Name lookup may not work on this interface\n", ifaceName); + } else { + if(ellCount(&bcastList)>1 && conf->udpAddr.ia.sin_addr.s_addr!=htonl(INADDR_ANY)) + printf("Interface %s has more than one broadcast address?\n", ifaceName); + + pNode = (osiSockAddrNode*)ellFirst(&bcastList); + + conf->udpbcast = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); + if(conf->udpbcast==INVALID_SOCKET) + cantProceed("rsrv_init ran out of udp sockets for bcast"); + + epicsSocketEnableAddressUseForDatagramFanout ( conf->udpbcast ); + + conf->udpbcastAddr = conf->udpAddr; + conf->udpbcastAddr.ia.sin_addr.s_addr = pNode->addr.ia.sin_addr.s_addr; + + if(tryBind(conf->udpbcast, &conf->udpbcastAddr, "UDP Socket bcast")) + goto cleanup; + } + + ellFree(&bcastList); + } + +#endif /* !(defined(_WIN32) || defined(__CYGWIN__)) */ + + ellAdd(&servers, &conf->node); + + /* have all sockets, time to start some threads */ + + epicsThreadMustCreate("CAS-TCP", threadPrios[2], + epicsThreadGetStackSize(epicsThreadStackMedium), + &req_server, conf); + + epicsEventMustWait(castcp_startStopEvent); + + epicsThreadMustCreate("CAS-UDP", threadPrios[4], + epicsThreadGetStackSize(epicsThreadStackMedium), + &cast_server, conf); + + epicsEventMustWait(casudp_startStopEvent); + +#if !(defined(_WIN32) || defined(__CYGWIN__)) + if(conf->udpbcast != INVALID_SOCKET) { + conf->startbcast = 1; + + epicsThreadMustCreate("CAS-UDP2", threadPrios[4], + epicsThreadGetStackSize(epicsThreadStackMedium), + &cast_server, conf); + + epicsEventMustWait(casudp_startStopEvent); + + conf->startbcast = 0; + } +#endif /* !(defined(_WIN32) || defined(__CYGWIN__)) */ + + havesometcp = 1; + continue; + cleanup: + epicsSocketDestroy(conf->tcp); + if(conf->udp!=INVALID_SOCKET) epicsSocketDestroy(conf->udp); + if(conf->udpbcast!=INVALID_SOCKET) epicsSocketDestroy(conf->udpbcast); + free(conf); + } + + if(!havesometcp) + cantProceed("CAS: No TCP server started\n"); + } + + /* servers list is considered read-only from this point */ + + epicsThreadMustCreate("CAS-beacon", threadPrios[3], + epicsThreadGetStackSize(epicsThreadStackSmall), + &rsrv_online_notify_task, NULL); + + epicsEventMustWait(beacon_startStopEvent); +} + +static +void rsrv_run (void) +{ + castcp_ctl = ctlRun; + casudp_ctl = ctlRun; + beacon_ctl = ctlRun; +} + +static +void rsrv_pause (void) +{ + beacon_ctl = ctlPause; + casudp_ctl = ctlPause; + castcp_ctl = ctlPause; +} + +static unsigned countChanListBytes ( + struct client *client, ELLLIST * pList ) +{ + struct channel_in_use * pciu; + unsigned bytes_reserved = 0; + + epicsMutexMustLock ( client->chanListLock ); + pciu = ( struct channel_in_use * ) pList->node.next; + while ( pciu ) { + bytes_reserved += sizeof(struct channel_in_use); + bytes_reserved += sizeof(struct event_ext)*ellCount( &pciu->eventq ); + bytes_reserved += rsrvSizeOfPutNotify ( pciu->pPutNotify ); + pciu = ( struct channel_in_use * ) ellNext( &pciu->node ); + } + epicsMutexUnlock ( client->chanListLock ); + + return bytes_reserved; +} + +static void showChanList ( + struct client * client, unsigned level, ELLLIST * pList ) +{ + struct channel_in_use * pciu; + epicsMutexMustLock ( client->chanListLock ); + pciu = (struct channel_in_use *) pList->node.next; + while ( pciu ){ + dbChannelShow ( pciu->dbch, level, 8 ); + if ( level >= 1u ) + printf( "%12s# on eventq=%d, access=%c%c\n", "", + ellCount ( &pciu->eventq ), + asCheckGet ( pciu->asClientPVT ) ? 'r': '-', + rsrvCheckPut ( pciu ) ? 'w': '-' ); + pciu = ( struct channel_in_use * ) ellNext ( &pciu->node ); + } + epicsMutexUnlock ( client->chanListLock ); +} + +/* + * log_one_client () + */ +static void log_one_client (struct client *client, unsigned level) +{ + char clientIP[40]; + int n; + + ipAddrToDottedIP (&client->addr, clientIP, sizeof(clientIP)); + + if ( client->proto == IPPROTO_UDP ) { + printf ( "\tLast name requested by %s:\n", + clientIP ); + } + else if ( client->proto == IPPROTO_TCP ) { + printf ( " TCP client at %s '%s':\n", + clientIP, + client->pHostName ? client->pHostName : "" ); + } + else { + printf ( " Unknown client at %s '%s':\n", + clientIP, + client->pHostName ? client->pHostName : "" ); + } + + n = ellCount(&client->chanList) + ellCount(&client->chanPendingUpdateARList); + printf ( "\tUser '%s', V%u.%u, Priority = %u, %d Channel%s\n", + client->pUserName ? client->pUserName : "", + CA_MAJOR_PROTOCOL_REVISION, + client->minor_version_number, + client->priority, + n, n == 1 ? "" : "s" ); + + if ( level >= 3u ) { + double send_delay; + double recv_delay; + char *state[] = {"up", "down"}; + epicsTimeStamp current; + + epicsTimeGetCurrent(¤t); + send_delay = epicsTimeDiffInSeconds(¤t,&client->time_at_last_send); + recv_delay = epicsTimeDiffInSeconds(¤t,&client->time_at_last_recv); + + printf ("\tTask Id = %p, Socket FD = %d\n", + (void *) client->tid, (int)client->sock); + printf( + "\t%.2f secs since last send, %.2f secs since last receive\n", + send_delay, recv_delay); + printf( + "\tUnprocessed request bytes = %u, Undelivered response bytes = %u\n", + client->recv.cnt - client->recv.stk, + client->send.stk ); + printf( + "\tState = %s%s%s\n", + state[client->disconnect?1:0], + client->send.type == mbtLargeTCP ? " jumbo-send-buf" : "", + client->recv.type == mbtLargeTCP ? " jumbo-recv-buf" : ""); + } + + if ( level >= 1u ) { + showChanList ( client, level - 1u, & client->chanList ); + showChanList ( client, level - 1u, & client->chanPendingUpdateARList ); + } + + if ( level >= 4u ) { + unsigned bytes_reserved = sizeof(struct client); + + bytes_reserved += countChanListBytes ( + client, & client->chanList ); + bytes_reserved += countChanListBytes ( + client, & client->chanPendingUpdateARList ); + printf( "\t%d bytes allocated\n", bytes_reserved); + printf( "\tSend Lock:\n\t "); + epicsMutexShow(client->lock,1); + printf( "\tPut Notify Lock:\n\t "); + epicsMutexShow (client->putNotifyLock,1); + printf( "\tAddress Queue Lock:\n\t "); + epicsMutexShow (client->chanListLock,1); + printf( "\tEvent Queue Lock:\n\t "); + epicsMutexShow (client->eventqLock,1); + printf( "\tBlock Semaphore:\n\t "); + epicsEventShow (client->blockSem,1); + } +} + +/* + * casr() + */ +void casr (unsigned level) +{ + size_t bytes_reserved; + int n; + + if ( ! clientQlock ) { + return; + } + + printf ("Channel Access Server V%s\n", + CA_VERSION_STRING ( CA_MINOR_PROTOCOL_REVISION ) ); + + LOCK_CLIENTQ + n = ellCount ( &clientQ ); + if (n == 0) { + printf("No clients connected.\n"); + } + else if (level == 0) { + printf("%d client%s connected.\n", + n, n == 1 ? "" : "s" ); + } + else { + struct client *client = (struct client *) ellFirst ( &clientQ ); + + printf("%d client%s connected:\n", + n, n == 1 ? "" : "s" ); + while (client) { + log_one_client(client, level - 1); + client = (struct client *) ellNext(&client->node); + } + } + UNLOCK_CLIENTQ + + if (level>=1) { + rsrv_iface_config *iface = (rsrv_iface_config *) ellFirst ( &servers ); + while (iface) { + char buf[40]; + + ipAddrToDottedIP (&iface->tcpAddr.ia, buf, sizeof(buf)); + printf("CAS-TCP server on %s with\n", buf); + + ipAddrToDottedIP (&iface->udpAddr.ia, buf, sizeof(buf)); +#if defined(_WIN32) + printf(" CAS-UDP name server on %s\n", buf); + if (level >= 2) + log_one_client(iface->client, level - 2); +#else + if (iface->udpbcast==INVALID_SOCKET) { + printf(" CAS-UDP name server on %s\n", buf); + if (level >= 2) + log_one_client(iface->client, level - 2); + } + else { + printf(" CAS-UDP unicast name server on %s\n", buf); + if (level >= 2) + log_one_client(iface->client, level - 2); + ipAddrToDottedIP (&iface->udpbcastAddr.ia, buf, sizeof(buf)); + printf(" CAS-UDP broadcast name server on %s\n", buf); + if (level >= 2) + log_one_client(iface->bclient, level - 2); + } +#endif + + iface = (rsrv_iface_config *) ellNext(&iface->node); + } + } + + if (level>=1) { + osiSockAddrNode * pAddr; + char buf[40]; + int n = ellCount(&casMCastAddrList); + + if (n) { + printf("Monitoring %d multicast address%s:\n", + n, n == 1 ? "" : "es"); + for(pAddr = (osiSockAddrNode*)ellFirst(&casMCastAddrList); + pAddr; + pAddr = (osiSockAddrNode*)ellNext(&pAddr->node)) + { + ipAddrToDottedIP (&pAddr->addr.ia, buf, sizeof(buf)); + printf(" %s\n", buf); + } + } + + n = ellCount(&beaconAddrList); + printf("Sending CAS-beacons to %d address%s:\n", + n, n == 1 ? "" : "es"); + for(pAddr = (osiSockAddrNode*)ellFirst(&beaconAddrList); + pAddr; + pAddr = (osiSockAddrNode*)ellNext(&pAddr->node)) + { + ipAddrToDottedIP (&pAddr->addr.ia, buf, sizeof(buf)); + printf(" %s\n", buf); + } + + if (casIgnoreAddrs[0]) { /* 0 indicates end of array */ + size_t i; + printf("Ignoring UDP messages from address%s\n", + n == 1 ? "" : "es"); + for(i=0; casIgnoreAddrs[i]; i++) + { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = casIgnoreAddrs[i]; + addr.sin_port = 0; + ipAddrToDottedIP(&addr, buf, sizeof(buf)); + printf(" %s\n", buf); + } + } + } + + if (level>=4u) { + bytes_reserved = 0u; + bytes_reserved += sizeof (struct client) * + freeListItemsAvail (rsrvClientFreeList); + bytes_reserved += sizeof (struct channel_in_use) * + freeListItemsAvail (rsrvChanFreeList); + bytes_reserved += sizeof(struct event_ext) * + freeListItemsAvail (rsrvEventFreeList); + bytes_reserved += MAX_TCP * + freeListItemsAvail ( rsrvSmallBufFreeListTCP ); + if(rsrvLargeBufFreeListTCP) { + bytes_reserved += rsrvSizeofLargeBufTCP * + freeListItemsAvail ( rsrvLargeBufFreeListTCP ); + } + bytes_reserved += rsrvSizeOfPutNotify ( 0 ) * + freeListItemsAvail ( rsrvPutNotifyFreeList ); + printf( "Free-lists total %u bytes, comprising\n", + (unsigned int) bytes_reserved); + printf( " %u client(s), %u channel(s), %u monitor event(s), %u putNotify(s)\n", + (unsigned int) freeListItemsAvail ( rsrvClientFreeList ), + (unsigned int) freeListItemsAvail ( rsrvChanFreeList ), + (unsigned int) freeListItemsAvail ( rsrvEventFreeList ), + (unsigned int) freeListItemsAvail ( rsrvPutNotifyFreeList )); + printf( " %u small (%u byte) buffers, %u jumbo (%u byte) buffers\n", + (unsigned int) freeListItemsAvail ( rsrvSmallBufFreeListTCP ), + MAX_TCP, + (unsigned int)(rsrvLargeBufFreeListTCP ? freeListItemsAvail ( rsrvLargeBufFreeListTCP ) : -1), + rsrvSizeofLargeBufTCP ); + printf( "Server resource id table:\n"); + LOCK_CLIENTQ; + bucketShow (pCaBucket); + UNLOCK_CLIENTQ; + } +} + +/* + * destroy_client () + */ +void destroy_client ( struct client *client ) +{ + if ( ! client ) { + return; + } + + if ( client->tid != 0 ) { + taskwdRemove ( client->tid ); + } + + if ( client->sock != INVALID_SOCKET ) { + epicsSocketDestroy ( client->sock ); + } + + if ( client->proto == IPPROTO_TCP ) { + if ( client->send.buf ) { + if ( client->send.type == mbtSmallTCP ) { + freeListFree ( rsrvSmallBufFreeListTCP, client->send.buf ); + } + else if ( client->send.type == mbtLargeTCP ) { + if(rsrvLargeBufFreeListTCP) + freeListFree ( rsrvLargeBufFreeListTCP, client->send.buf ); + else + free(client->send.buf); + } + else { + errlogPrintf ( "CAS: Corrupt send buffer free list type code=%u during client cleanup?\n", + client->send.type ); + } + } + if ( client->recv.buf ) { + if ( client->recv.type == mbtSmallTCP ) { + freeListFree ( rsrvSmallBufFreeListTCP, client->recv.buf ); + } + else if ( client->recv.type == mbtLargeTCP ) { + if(rsrvLargeBufFreeListTCP) + freeListFree ( rsrvLargeBufFreeListTCP, client->recv.buf ); + else + free(client->recv.buf); + } + else { + errlogPrintf ( "CAS: Corrupt recv buffer free list type code=%u during client cleanup?\n", + client->send.type ); + } + } + } + else if ( client->proto == IPPROTO_UDP ) { + if ( client->send.buf ) { + free ( client->send.buf ); + } + if ( client->recv.buf ) { + free ( client->recv.buf ); + } + } + + if ( client->eventqLock ) { + epicsMutexDestroy ( client->eventqLock ); + } + + if ( client->chanListLock ) { + epicsMutexDestroy ( client->chanListLock ); + } + + if ( client->putNotifyLock ) { + epicsMutexDestroy ( client->putNotifyLock ); + } + + if ( client->lock ) { + epicsMutexDestroy ( client->lock ); + } + + if ( client->blockSem ) { + epicsEventDestroy ( client->blockSem ); + } + + if ( client->pUserName ) { + free ( client->pUserName ); + } + + if ( client->pHostName ) { + free ( client->pHostName ); + } + + freeListFree ( rsrvClientFreeList, client ); +} + +static void destroyAllChannels ( + struct client * client, ELLLIST * pList ) +{ + if ( !client->chanListLock || !client->eventqLock ) { + return; + } + + while ( TRUE ) { + struct event_ext *pevext; + int status; + struct channel_in_use *pciu; + + epicsMutexMustLock ( client->chanListLock ); + pciu = (struct channel_in_use *) ellGet ( pList ); + if(pciu) pciu->state = rsrvCS_shutdown; + epicsMutexUnlock ( client->chanListLock ); + + if ( ! pciu ) { + break; + } + + while ( TRUE ) { + /* + * AS state change could be using this list + */ + epicsMutexMustLock ( client->eventqLock ); + pevext = (struct event_ext *) ellGet ( &pciu->eventq ); + epicsMutexUnlock ( client->eventqLock ); + + if ( ! pevext ) { + break; + } + + if ( pevext->pdbev ) { + db_cancel_event (pevext->pdbev); + } + freeListFree (rsrvEventFreeList, pevext); + } + rsrvFreePutNotify ( client, pciu->pPutNotify ); + LOCK_CLIENTQ; + status = bucketRemoveItemUnsignedId ( pCaBucket, &pciu->sid); + rsrvChannelCount--; + UNLOCK_CLIENTQ; + if ( status != S_bucket_success ) { + errPrintf ( status, __FILE__, __LINE__, + "Bad id=%d at close", pciu->sid); + } + status = asRemoveClient(&pciu->asClientPVT); + if ( status && status != S_asLib_asNotActive ) { + printf ( "bad asRemoveClient() status was %x \n", status ); + errPrintf ( status, __FILE__, __LINE__, "asRemoveClient" ); + } + + dbChannelDelete(pciu->dbch); + freeListFree ( rsrvChanFreeList, pciu ); + } +} + +void destroy_tcp_client ( struct client *client ) +{ + int status; + + if ( CASDEBUG > 0 ) { + errlogPrintf ( "CAS: Connection %d Terminated\n", (int)client->sock ); + } + + if ( client->evuser ) { + /* + * turn off extra labor callbacks from the event thread + */ + status = db_add_extra_labor_event ( client->evuser, NULL, NULL ); + assert ( ! status ); + + /* + * wait for extra labor in progress to comple + */ + db_flush_extra_labor_event ( client->evuser ); + } + + destroyAllChannels ( client, & client->chanList ); + destroyAllChannels ( client, & client->chanPendingUpdateARList ); + + if ( client->evuser ) { + db_close_events (client->evuser); + } + + destroy_client ( client ); +} + +/* + * create_client () + */ +struct client * create_client ( SOCKET sock, int proto ) +{ + struct client *client; + int spaceAvailOnFreeList; + size_t spaceNeeded; + + /* + * stop further use of server if memory becomes scarse + */ + spaceAvailOnFreeList = freeListItemsAvail ( rsrvClientFreeList ) > 0 + && freeListItemsAvail ( rsrvSmallBufFreeListTCP ) > 0; + spaceNeeded = sizeof (struct client) + MAX_TCP; + if ( ! ( osiSufficentSpaceInPool(spaceNeeded) || spaceAvailOnFreeList ) ) { + epicsSocketDestroy ( sock ); + epicsPrintf ("CAS: no space in pool for a new client (below max block thresh)\n"); + return NULL; + } + + client = freeListCalloc ( rsrvClientFreeList ); + if ( ! client ) { + epicsSocketDestroy ( sock ); + epicsPrintf ("CAS: no space in pool for a new client (alloc failed)\n"); + return NULL; + } + + client->sock = sock; + client->proto = proto; + + client->blockSem = epicsEventCreate ( epicsEventEmpty ); + client->lock = epicsMutexCreate(); + client->putNotifyLock = epicsMutexCreate(); + client->chanListLock = epicsMutexCreate(); + client->eventqLock = epicsMutexCreate(); + if ( ! client->blockSem || ! client->lock || ! client->putNotifyLock || + ! client->chanListLock || ! client->eventqLock ) { + destroy_client ( client ); + return NULL; + } + + client->pUserName = NULL; + client->pHostName = NULL; + ellInit ( & client->chanList ); + ellInit ( & client->chanPendingUpdateARList ); + ellInit ( & client->putNotifyQue ); + memset ( (char *)&client->addr, 0, sizeof (client->addr) ); + client->tid = 0; + + if ( proto == IPPROTO_TCP ) { + client->send.buf = (char *) freeListCalloc ( rsrvSmallBufFreeListTCP ); + client->send.maxstk = MAX_TCP; + client->send.type = mbtSmallTCP; + client->recv.buf = (char *) freeListCalloc ( rsrvSmallBufFreeListTCP ); + client->recv.maxstk = MAX_TCP; + client->recv.type = mbtSmallTCP; + } + else if ( proto == IPPROTO_UDP ) { + client->send.buf = malloc ( MAX_UDP_SEND ); + client->send.maxstk = MAX_UDP_SEND; + client->send.type = mbtUDP; + client->recv.buf = malloc ( MAX_UDP_RECV ); + client->recv.maxstk = MAX_UDP_RECV; + client->recv.type = mbtUDP; + } + if ( ! client->send.buf || ! client->recv.buf ) { + destroy_client ( client ); + return NULL; + } + client->send.stk = 0u; + client->send.cnt = 0u; + client->recv.stk = 0u; + client->recv.cnt = 0u; + client->evuser = NULL; + client->priority = CA_PROTO_PRIORITY_MIN; + client->disconnect = FALSE; + epicsTimeGetCurrent ( &client->time_at_last_send ); + epicsTimeGetCurrent ( &client->time_at_last_recv ); + client->minor_version_number = CA_UKN_MINOR_VERSION; + client->recvBytesToDrain = 0u; + + return client; +} + +void casAttachThreadToClient ( struct client *pClient ) +{ + epicsSignalInstallSigAlarmIgnore (); + epicsSignalInstallSigPipeIgnore (); + pClient->tid = epicsThreadGetIdSelf (); + epicsThreadPrivateSet ( rsrvCurrentClient, pClient ); + taskwdInsert ( pClient->tid, NULL, NULL ); +} + +static +void casExpandBuffer ( struct message_buffer *buf, ca_uint32_t size, int sendbuf ) +{ + char *newbuf = NULL; + unsigned newsize; + enum messageBufferType newtype; + + assert (size > MAX_TCP); + + if ( size <= buf->maxstk || buf->type == mbtUDP ) return; + + /* try to alloc new buffer */ + if (size <= MAX_TCP) { + return; /* shouldn't happen */ + + } else if(!rsrvLargeBufFreeListTCP) { + // round up to multiple of 4K + size = ((size-1)|0xfff)+1; + + if (buf->type==mbtLargeTCP) + newbuf = realloc (buf->buf, size); + else + newbuf = malloc (size); + newtype = mbtLargeTCP; + newsize = size; + + } else if (size <= rsrvSizeofLargeBufTCP) { + newbuf = freeListCalloc ( rsrvLargeBufFreeListTCP ); + newsize = rsrvSizeofLargeBufTCP; + newtype = mbtLargeTCP; + } + + if (newbuf) { + /* copy existing buffer */ + if (sendbuf) { + /* send buffer uses [0, stk) */ + if (!rsrvLargeBufFreeListTCP && buf->type==mbtLargeTCP) { + /* realloc already copied */ + } else { + memcpy ( newbuf, buf->buf, buf->stk ); + } + } else { + /* recv buffer uses [stk, cnt) */ + unsigned used; + assert ( buf->cnt >= buf->stk ); + used = buf->cnt - buf->stk; + + /* buf->buf may be the same as newbuf if realloc() used */ + memmove ( newbuf, &buf->buf[buf->stk], used ); + + buf->cnt = used; + buf->stk = 0; + + } + + /* free existing buffer */ + if(buf->type==mbtSmallTCP) { + freeListFree ( rsrvSmallBufFreeListTCP, buf->buf ); + } else if(rsrvLargeBufFreeListTCP && buf->type==mbtLargeTCP) { + freeListFree ( rsrvLargeBufFreeListTCP, buf->buf ); + } else { + /* realloc() already free()'d if necessary */ + } + + buf->buf = newbuf; + buf->type = newtype; + buf->maxstk = newsize; + } +} + +void casExpandSendBuffer ( struct client *pClient, ca_uint32_t size ) +{ + casExpandBuffer (&pClient->send, size, 1); +} + +void casExpandRecvBuffer ( struct client *pClient, ca_uint32_t size ) +{ + casExpandBuffer (&pClient->recv, size, 0); +} + +/* + * create_tcp_client () + */ +struct client *create_tcp_client ( SOCKET sock ) +{ + int status; + struct client *client; + int intTrue = TRUE; + osiSocklen_t addrSize; + unsigned priorityOfEvents; + + /* socket passed in is destroyed here if unsuccessful */ + client = create_client ( sock, IPPROTO_TCP ); + if ( ! client ) { + return NULL; + } + + /* + * see TCP(4P) this seems to make unsolicited single events much + * faster. I take care of queue up as load increases. + */ + status = setsockopt ( sock, IPPROTO_TCP, TCP_NODELAY, + (char *) &intTrue, sizeof (intTrue) ); + if (status < 0) { + errlogPrintf ( "CAS: TCP_NODELAY option set failed\n" ); + destroy_client ( client ); + return NULL; + } + + /* + * turn on KEEPALIVE so if the client crashes + * this task will find out and exit + */ + status = setsockopt ( sock, SOL_SOCKET, SO_KEEPALIVE, + (char *) &intTrue, sizeof (intTrue) ); + if ( status < 0 ) { + errlogPrintf ( "CAS: SO_KEEPALIVE option set failed\n" ); + destroy_client ( client ); + return NULL; + } + + /* + * some concern that vxWorks will run out of mBuf's + * if this change is made + * + * joh 11-10-98 + */ +#if 0 + /* + * set TCP buffer sizes to be synergistic + * with CA internal buffering + */ + i = MAX_MSG_SIZE; + status = setsockopt ( sock, SOL_SOCKET, SO_SNDBUF, (char *) &i, sizeof (i) ); + if (status < 0) { + errlogPrintf ( "CAS: SO_SNDBUF set failed\n" ); + destroy_client ( client ); + return NULL; + } + i = MAX_MSG_SIZE; + status = setsockopt ( sock, SOL_SOCKET, SO_RCVBUF, (char *) &i, sizeof (i) ); + if (status < 0) { + errlogPrintf ( "CAS: SO_RCVBUF set failed\n" ); + destroy_client ( client ); + return NULL; + } +#endif + + addrSize = sizeof ( client->addr ); + status = getpeername ( sock, (struct sockaddr *)&client->addr, + &addrSize ); + if ( status < 0 ) { + epicsPrintf ("CAS: peer address fetch failed\n"); + destroy_tcp_client (client); + return NULL; + } + + client->evuser = (struct event_user *) db_init_events (); + if ( ! client->evuser ) { + errlogPrintf ("CAS: unable to init the event facility\n"); + destroy_tcp_client (client); + return NULL; + } + + status = db_add_extra_labor_event ( client->evuser, rsrv_extra_labor, client ); + if (status != DB_EVENT_OK) { + errlogPrintf("CAS: unable to setup the event facility\n"); + destroy_tcp_client (client); + return NULL; + } + + { + epicsThreadBooleanStatus tbs; + + tbs = epicsThreadHighestPriorityLevelBelow ( epicsThreadPriorityCAServerLow, &priorityOfEvents ); + if ( tbs != epicsThreadBooleanStatusSuccess ) { + priorityOfEvents = epicsThreadPriorityCAServerLow; + } + } + + status = db_start_events ( client->evuser, "CAS-event", + NULL, NULL, priorityOfEvents ); + if ( status != DB_EVENT_OK ) { + errlogPrintf ( "CAS: unable to start the event facility\n" ); + destroy_tcp_client ( client ); + return NULL; + } + + /* + * add first version message should it be needed + */ + rsrv_version_reply ( client ); + + if ( CASDEBUG > 0 ) { + char buf[64]; + ipAddrToDottedIP ( &client->addr, buf, sizeof(buf) ); + errlogPrintf ( "CAS: conn req from %s\n", buf ); + } + + return client; +} + +void casStatsFetch ( unsigned *pChanCount, unsigned *pCircuitCount ) +{ + LOCK_CLIENTQ; + { + int circuitCount = ellCount ( &clientQ ); + if ( circuitCount < 0 ) { + *pCircuitCount = 0; + } + else { + *pCircuitCount = (unsigned) circuitCount; + } + *pChanCount = rsrvChannelCount; + } + UNLOCK_CLIENTQ; +} + + +static dbServer rsrv_server = { + ELLNODE_INIT, + "rsrv", + casr, + casStatsFetch, + casClientInitiatingCurrentThread, + rsrv_init, + rsrv_run, + rsrv_pause +}; + +void rsrv_register_server(void) +{ + dbRegisterServer(&rsrv_server); +} diff --git a/modules/database/src/ioc/rsrv/cast_server.c b/modules/database/src/ioc/rsrv/cast_server.c new file mode 100644 index 000000000..4039d0f6f --- /dev/null +++ b/modules/database/src/ioc/rsrv/cast_server.c @@ -0,0 +1,293 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Jeffrey O. Hill + * hill@luke.lanl.gov + * (505) 665 1831 + * Date: 5-88 + * + * Improvements + * ------------ + * .01 + * Dont send channel found message unless there is memory, a task slot, + * and a TCP socket available. Send a diagnostic instead. + * Or ... make the timeout shorter? This is only a problem if + * they persist in trying to make a connection after getting no + * response. + * + * Notes: + * ------ + * .01 + * Replies to broadcasts are not returned over + * an existing TCP connection to avoid a TCP + * pend which could lock up the cast server. + */ + + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "envDefs.h" +#include "epicsMutex.h" +#include "epicsTime.h" +#include "errlog.h" +#include "freeList.h" +#include "osiSock.h" +#include "taskwd.h" + +#define epicsExportSharedSymbols +#include "rsrv.h" +#include "server.h" + +#define TIMEOUT 60.0 /* sec */ + +/* + * clean_addrq + */ +static void clean_addrq(struct client *client) +{ + struct channel_in_use * pciu; + struct channel_in_use * pnextciu; + epicsTimeStamp current; + double delay; + double maxdelay = 0; + unsigned ndelete=0; + double timeout = TIMEOUT; + int s; + + epicsTimeGetCurrent ( ¤t ); + + epicsMutexMustLock ( client->chanListLock ); + pnextciu = (struct channel_in_use *) + client->chanList.node.next; + + while( (pciu = pnextciu) ) { + pnextciu = (struct channel_in_use *)pciu->node.next; + + delay = epicsTimeDiffInSeconds(¤t,&pciu->time_at_creation); + if (delay > timeout) { + + ellDelete(&client->chanList, &pciu->node); + LOCK_CLIENTQ; + s = bucketRemoveItemUnsignedId ( + pCaBucket, + &pciu->sid); + if(s){ + errMessage (s, "Bad id at close"); + } + else { + rsrvChannelCount--; + } + UNLOCK_CLIENTQ; + if ( ! s ) { + freeListFree(rsrvChanFreeList, pciu); + ndelete++; + } + if(delay>maxdelay) maxdelay = delay; + } + } + epicsMutexUnlock ( client->chanListLock ); + +# ifdef DEBUG + if(ndelete){ + epicsPrintf ("CAS: %d CA channels have expired after %f sec\n", + ndelete, maxdelay); + } +# endif + +} + +/* + * CAST_SERVER + * + * service UDP messages + * + */ +void cast_server(void *pParm) +{ + rsrv_iface_config *conf = pParm; + int status; + int count=0; + int mysocket=0; + struct sockaddr_in new_recv_addr; + osiSocklen_t recv_addr_size; + osiSockIoctl_t nchars; + SOCKET recv_sock, reply_sock; + struct client *client; + + recv_addr_size = sizeof(new_recv_addr); + + reply_sock = conf->udp; + + /* + * setup new client structure but reuse old structure if + * possible + * + */ + while ( TRUE ) { + client = create_client ( reply_sock, IPPROTO_UDP ); + if ( client ) { + break; + } + epicsThreadSleep(300.0); + } + if (conf->startbcast) { + recv_sock = conf->udpbcast; + conf->bclient = client; + } + else { + recv_sock = conf->udp; + conf->client = client; + } + client->udpRecv = recv_sock; + + casAttachThreadToClient ( client ); + + /* + * add placeholder for the first version message should it be needed + */ + rsrv_version_reply ( client ); + + /* these pointers become invalid after signaling casudp_startStopEvent */ + conf = NULL; + + epicsEventSignal(casudp_startStopEvent); + + while (TRUE) { + status = recvfrom ( + recv_sock, + client->recv.buf, + client->recv.maxstk, + 0, + (struct sockaddr *)&new_recv_addr, + &recv_addr_size); + if (status < 0) { + if (SOCKERRNO != SOCK_EINTR) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsPrintf ("CAS: UDP recv error: %s\n", + sockErrBuf); + epicsThreadSleep(1.0); + } + + } else { + size_t idx; + for(idx=0; casIgnoreAddrs[idx]; idx++) + { + if(new_recv_addr.sin_addr.s_addr==casIgnoreAddrs[idx]) { + status = -1; /* ignore */ + break; + } + } + } + + if (status >= 0 && casudp_ctl == ctlRun) { + client->recv.cnt = (unsigned) status; + client->recv.stk = 0ul; + epicsTimeGetCurrent(&client->time_at_last_recv); + + client->minor_version_number = CA_UKN_MINOR_VERSION; + client->seqNoOfReq = 0; + + /* + * If we are talking to a new client flush to the old one + * in case we are holding UDP messages waiting to + * see if the next message is for this same client. + */ + if (client->send.stk>sizeof(caHdr)) { + status = memcmp(&client->addr, + &new_recv_addr, recv_addr_size); + if(status){ + /* + * if the address is different + */ + cas_send_dg_msg(client); + client->addr = new_recv_addr; + } + } + else { + client->addr = new_recv_addr; + } + + if (CASDEBUG>1) { + char buf[40]; + + ipAddrToDottedIP (&client->addr, buf, sizeof(buf)); + errlogPrintf ("CAS: cast server msg of %d bytes from addr %s\n", + client->recv.cnt, buf); + } + + if (CASDEBUG>2) + count = ellCount (&client->chanList); + + status = camessage ( client ); + if(status == RSRV_OK){ + if(client->recv.cnt != + client->recv.stk){ + char buf[40]; + + ipAddrToDottedIP (&client->addr, buf, sizeof(buf)); + + epicsPrintf ("CAS: partial (damaged?) UDP msg of %d bytes from %s ?\n", + client->recv.cnt - client->recv.stk, buf); + + epicsTimeToStrftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", + &client->time_at_last_recv); + epicsPrintf ("CAS: message received at %s\n", buf); + } + } + else if (CASDEBUG>0){ + char buf[40]; + + ipAddrToDottedIP (&client->addr, buf, sizeof(buf)); + + epicsPrintf ("CAS: invalid (damaged?) UDP request from %s ?\n", buf); + + epicsTimeToStrftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", + &client->time_at_last_recv); + epicsPrintf ("CAS: message received at %s\n", buf); + } + + if (CASDEBUG>2) { + if ( ellCount (&client->chanList) ) { + errlogPrintf ("CAS: Fnd %d name matches (%d tot)\n", + ellCount(&client->chanList)-count, + ellCount(&client->chanList)); + } + } + } + + /* + * allow messages to batch up if more are comming + */ + nchars = 0; /* supress purify warning */ + status = socket_ioctl(recv_sock, FIONREAD, &nchars); + if (status<0) { + errlogPrintf ("CA cast server: Unable to fetch N characters pending\n"); + cas_send_dg_msg (client); + clean_addrq (client); + } + else if (nchars == 0) { + cas_send_dg_msg (client); + clean_addrq (client); + } + } + + /* ATM never reached, just a placeholder */ + + if(!mysocket) + client->sock = INVALID_SOCKET; /* only one cast_server should destroy the reply socket */ + destroy_client(client); + epicsSocketDestroy(recv_sock); +} diff --git a/modules/database/src/ioc/rsrv/online_notify.c b/modules/database/src/ioc/rsrv/online_notify.c new file mode 100644 index 000000000..6b6157370 --- /dev/null +++ b/modules/database/src/ioc/rsrv/online_notify.c @@ -0,0 +1,116 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * tell CA clients this a server has joined the network + * + * Author: Jeffrey O. Hill + * hill@luke.lanl.gov + * (505) 665 1831 + * Date: 103090 + * + */ + +#include +#include +#include +#include +#include + +#include "addrList.h" +#include "dbDefs.h" +#include "envDefs.h" +#include "errlog.h" +#include "osiSock.h" +#include "taskwd.h" + +#define epicsExportSharedSymbols +#include "server.h" + +/* + * RSRV_ONLINE_NOTIFY_TASK + */ +void rsrv_online_notify_task(void *pParm) +{ + double delay; + double maxdelay; + long longStatus; + double maxPeriod; + caHdr msg; + int status; + ca_uint32_t beaconCounter = 0; + + taskwdInsert (epicsThreadGetIdSelf(),NULL,NULL); + + if ( envGetConfigParamPtr ( & EPICS_CAS_BEACON_PERIOD ) ) { + longStatus = envGetDoubleConfigParam ( & EPICS_CAS_BEACON_PERIOD, & maxPeriod ); + } + else { + longStatus = envGetDoubleConfigParam ( & EPICS_CA_BEACON_PERIOD, & maxPeriod ); + } + if (longStatus || maxPeriod<=0.0) { + maxPeriod = 15.0; + epicsPrintf ("EPICS \"%s\" float fetch failed\n", + EPICS_CAS_BEACON_PERIOD.name); + epicsPrintf ("Setting \"%s\" = %f\n", + EPICS_CAS_BEACON_PERIOD.name, maxPeriod); + } + + delay = 0.02; /* initial beacon period in sec */ + maxdelay = maxPeriod; + + memset((char *)&msg, 0, sizeof msg); + msg.m_cmmd = htons (CA_PROTO_RSRV_IS_UP); + msg.m_count = htons (ca_server_port); + msg.m_dataType = htons (CA_MINOR_PROTOCOL_REVISION); + + + epicsEventSignal(beacon_startStopEvent); + + while (TRUE) { + ELLNODE *cur; + + /* send beacon to each interface */ + for(cur=ellFirst(&beaconAddrList); cur; cur=ellNext(cur)) + { + osiSockAddrNode *pAddr = CONTAINER(cur, osiSockAddrNode, node); + status = sendto (beaconSocket, (char *)&msg, sizeof(msg), 0, + &pAddr->addr.sa, sizeof(pAddr->addr)); + if (status < 0) { + char sockErrBuf[64]; + char sockDipBuf[22]; + + epicsSocketConvertErrnoToString(sockErrBuf, sizeof(sockErrBuf)); + ipAddrToDottedIP(&pAddr->addr.ia, sockDipBuf, sizeof(sockDipBuf)); + errlogPrintf ( "CAS: CA beacon send to %s error: %s\n", + sockDipBuf, sockErrBuf); + } + else { + assert (status == sizeof(msg)); + } + } + + epicsThreadSleep(delay); + if (delaymaxdelay) { + delay = maxdelay; + } + } + + msg.m_cid = htonl ( beaconCounter++ ); /* expected to overflow */ + + while (beacon_ctl == ctlPause) { + epicsThreadSleep(0.1); + delay = 0.02; /* Restart beacon timing if paused */ + } + } +} + + diff --git a/modules/database/src/ioc/rsrv/rsrv.dbd b/modules/database/src/ioc/rsrv/rsrv.dbd new file mode 100644 index 000000000..0c3118f82 --- /dev/null +++ b/modules/database/src/ioc/rsrv/rsrv.dbd @@ -0,0 +1,3 @@ +# This DBD file links the RSRV CA server into the IOC + +registrar(rsrvRegistrar) diff --git a/modules/database/src/ioc/rsrv/rsrv.h b/modules/database/src/ioc/rsrv/rsrv.h new file mode 100644 index 000000000..cb966df2f --- /dev/null +++ b/modules/database/src/ioc/rsrv/rsrv.h @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Jeffrey O. Hill + * hill@luke.lanl.gov + * (505) 665 1831 + * Date: 5-88 + */ + +#ifndef rsrvh +#define rsrvh + +#include +#include "shareLib.h" + +#define RSRV_OK 0 +#define RSRV_ERROR (-1) + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void rsrv_register_server(void); + +epicsShareFunc void casr (unsigned level); +epicsShareFunc int casClientInitiatingCurrentThread ( + char * pBuf, size_t bufSize ); +epicsShareFunc void casStatsFetch ( + unsigned *pChanCount, unsigned *pConnCount ); + +#ifdef __cplusplus +} +#endif + +#endif /*rsrvh */ diff --git a/modules/database/src/ioc/rsrv/rsrvIocRegister.c b/modules/database/src/ioc/rsrv/rsrvIocRegister.c new file mode 100644 index 000000000..83c84c334 --- /dev/null +++ b/modules/database/src/ioc/rsrv/rsrvIocRegister.c @@ -0,0 +1,36 @@ +/*************************************************************************\ +* Copyright (c) 2007 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "osiSock.h" +#include "iocsh.h" + +#define epicsExportSharedSymbols +#include "rsrv.h" +#include "server.h" +#include "epicsExport.h" + + +/* casr */ +static const iocshArg casrArg0 = { "level",iocshArgInt}; +static const iocshArg * const casrArgs[1] = {&casrArg0}; +static const iocshFuncDef casrFuncDef = {"casr",1,casrArgs}; +static void casrCallFunc(const iocshArgBuf *args) +{ + casr(args[0].ival); +} + +static +void rsrvRegistrar(void) +{ + rsrv_register_server(); + iocshRegister(&casrFuncDef,casrCallFunc); +} + +epicsExportAddress(int, CASDEBUG); +epicsExportRegistrar(rsrvRegistrar); diff --git a/modules/database/src/ioc/rsrv/server.h b/modules/database/src/ioc/rsrv/server.h new file mode 100644 index 000000000..ef9730ba8 --- /dev/null +++ b/modules/database/src/ioc/rsrv/server.h @@ -0,0 +1,262 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Jeffrey O. Hill + * + */ + +#ifndef INCLserverh +#define INCLserverh + +#ifdef epicsExportSharedSymbols +# define rsrvRestore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif /* ifdef epicsExportSharedSymbols */ + +#include "epicsThread.h" +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "bucketLib.h" +#include "asLib.h" +#include "dbChannel.h" +#include "dbNotify.h" +#define CA_MINOR_PROTOCOL_REVISION 13 +#include "caProto.h" +#include "ellLib.h" +#include "epicsTime.h" +#include "epicsAssert.h" +#include "osiSock.h" + +#ifdef rsrvRestore_epicsExportSharedSymbols +#define epicsExportSharedSymbols +#endif + +/* a modified ca header with capacity for large arrays */ +typedef struct caHdrLargeArray { + ca_uint32_t m_postsize; /* size of message extension */ + ca_uint32_t m_count; /* operation data count */ + ca_uint32_t m_cid; /* channel identifier */ + ca_uint32_t m_available; /* protocol stub dependent */ + ca_uint16_t m_dataType; /* operation data type */ + ca_uint16_t m_cmmd; /* operation to be performed */ +} caHdrLargeArray; + +/* + * !! buf must be the first item in this structure !! + * This guarantees that buf will have 8 byte natural + * alignment + * + * The terminating unsigned pad0 field is there to force the + * length of the message_buffer to be a multiple of 8 bytes. + * This is due to the sequential placing of two message_buffer + * structures (trans, rec) within the client structure. + * Eight-byte alignment is required by the Sparc 5 and other RISC + * processors. + */ +enum messageBufferType { mbtUDP, mbtSmallTCP, mbtLargeTCP }; +struct message_buffer { + char *buf; + /*! points to first filled byte in buffer */ + unsigned stk; + unsigned maxstk; + /*! points to first unused byte in buffer (after filled bytes) */ + unsigned cnt; + enum messageBufferType type; +}; + +extern epicsThreadPrivateId rsrvCurrentClient; + +typedef struct client { + ELLNODE node; + /*! guarded by SEND_LOCK() aka. client::lock */ + struct message_buffer send; + /*! accessed by receive thread w/o locks cf. camsgtask() */ + struct message_buffer recv; + epicsMutexId lock; + epicsMutexId putNotifyLock; + epicsMutexId chanListLock; + epicsMutexId eventqLock; + ELLLIST chanList; + ELLLIST chanPendingUpdateARList; + ELLLIST putNotifyQue; + struct sockaddr_in addr; + epicsTimeStamp time_at_last_send; + epicsTimeStamp time_at_last_recv; + void *evuser; + char *pUserName; + char *pHostName; + epicsEventId blockSem; /* used whenever the client blocks */ + SOCKET sock, udpRecv; + int proto; + epicsThreadId tid; + unsigned minor_version_number; + ca_uint32_t seqNoOfReq; /* for udp */ + unsigned recvBytesToDrain; + unsigned priority; + char disconnect; /* disconnect detected */ +} client; + +/* Channel state shows which struct client list a + * channel_in_us::node is in. + * + * client::chanList + * rsrvCS_pendConnectResp, rsrvCS_inService + * client::chanPendingUpdateARList + * rsrvCS_pendConnectRespUpdatePendAR, rsrvCS_inServiceUpdatePendAR + * Not in any list + * rsrvCS_shutdown + * + * rsrvCS_invalid is not used + */ +enum rsrvChanState { + rsrvCS_invalid, + rsrvCS_pendConnectResp, + rsrvCS_inService, + rsrvCS_pendConnectRespUpdatePendAR, + rsrvCS_inServiceUpdatePendAR, + rsrvCS_shutdown +}; + +/* + * per channel structure + * (stored in chanList or chanPendingUpdateARList off of a client block) + */ +struct channel_in_use { + ELLNODE node; + ELLLIST eventq; + struct client *client; + struct rsrv_put_notify *pPutNotify; /* potential active put notify */ + const unsigned cid; /* client id */ + const unsigned sid; /* server id */ + epicsTimeStamp time_at_creation; /* for UDP timeout */ + struct dbChannel *dbch; + ASCLIENTPVT asClientPVT; + enum rsrvChanState state; +}; + +/* + * Event block extension for channel access + * some things duplicated for speed + */ +struct event_ext { + ELLNODE node; + caHdrLargeArray msg; + struct channel_in_use *pciu; + struct event_block *pdbev; /* ptr to db event block */ + unsigned size; /* for speed */ + unsigned mask; + char modified; /* mod & ev flw ctrl enbl */ +}; + +typedef struct { + ELLNODE node; + osiSockAddr tcpAddr, /* TCP listener endpoint */ + udpAddr, /* UDP name unicast receiver endpoint */ + udpbcastAddr; /* UDP name broadcast receiver endpoint */ + SOCKET tcp, udp, udpbcast; + struct client *client, *bclient; + + unsigned int startbcast:1; +} rsrv_iface_config; + +enum ctl {ctlInit, ctlRun, ctlPause, ctlExit}; + +/* NOTE: external used so they remember the state across loads */ +#ifdef GLBLSOURCE +# define GLBLTYPE +# define GLBLTYPE_INIT(A) = A +#else +# define GLBLTYPE extern +# define GLBLTYPE_INIT(A) +#endif + +/* + * for debug-level dependent messages: + */ +#ifdef DEBUG +# define DLOG(LEVEL,ARGSINPAREN) \ + if (CASDEBUG > LEVEL) errlogPrintf ARGSINPAREN +#else +# define DLOG(LEVEL,ARGSINPAREN) +#endif + +GLBLTYPE int CASDEBUG; +GLBLTYPE unsigned short ca_server_port, ca_udp_port, ca_beacon_port; +GLBLTYPE ELLLIST clientQ GLBLTYPE_INIT(ELLLIST_INIT); +GLBLTYPE ELLLIST servers; /* rsrv_iface_config::node, read-only after rsrv_init() */ +GLBLTYPE ELLLIST beaconAddrList; +GLBLTYPE SOCKET beaconSocket; +GLBLTYPE ELLLIST casIntfAddrList, casMCastAddrList; +GLBLTYPE epicsUInt32 *casIgnoreAddrs; +GLBLTYPE epicsMutexId clientQlock; +GLBLTYPE BUCKET *pCaBucket; /* locked by clientQlock */ +GLBLTYPE void *rsrvClientFreeList; +GLBLTYPE void *rsrvChanFreeList; +GLBLTYPE void *rsrvEventFreeList; +GLBLTYPE void *rsrvSmallBufFreeListTCP; +GLBLTYPE void *rsrvLargeBufFreeListTCP; +GLBLTYPE unsigned rsrvSizeofLargeBufTCP; +GLBLTYPE void *rsrvPutNotifyFreeList; +GLBLTYPE unsigned rsrvChannelCount; /* locked by clientQlock */ + +GLBLTYPE epicsEventId casudp_startStopEvent; +GLBLTYPE epicsEventId beacon_startStopEvent; +GLBLTYPE epicsEventId castcp_startStopEvent; +GLBLTYPE volatile enum ctl casudp_ctl; +GLBLTYPE volatile enum ctl beacon_ctl; +GLBLTYPE volatile enum ctl castcp_ctl; + +GLBLTYPE unsigned int threadPrios[5]; + +#define CAS_HASH_TABLE_SIZE 4096 + +#define SEND_LOCK(CLIENT) epicsMutexMustLock((CLIENT)->lock) +#define SEND_UNLOCK(CLIENT) epicsMutexUnlock((CLIENT)->lock) + +#define LOCK_CLIENTQ epicsMutexMustLock (clientQlock); +#define UNLOCK_CLIENTQ epicsMutexUnlock (clientQlock); + +void camsgtask (void *client); +void cas_send_bs_msg ( struct client *pclient, int lock_needed ); +void cas_send_dg_msg ( struct client *pclient ); +void rsrv_online_notify_task (void *); +void cast_server (void *); +struct client *create_client ( SOCKET sock, int proto ); +void destroy_client ( struct client * ); +struct client *create_tcp_client ( SOCKET sock ); +void destroy_tcp_client ( struct client * ); +void casAttachThreadToClient ( struct client * ); +int camessage ( struct client *client ); +void rsrv_extra_labor ( void * pArg ); +int rsrvCheckPut ( const struct channel_in_use *pciu ); +int rsrv_version_reply ( struct client *client ); +void rsrvFreePutNotify ( struct client *pClient, + struct rsrv_put_notify *pNotify ); +void initializePutNotifyFreeList (void); +unsigned rsrvSizeOfPutNotify ( struct rsrv_put_notify *pNotify ); + +/* + * inclming protocol maintetnance + */ +void casExpandRecvBuffer ( struct client *pClient, ca_uint32_t size ); + +/* + * outgoing protocol maintenance + */ +void casExpandSendBuffer ( struct client *pClient, ca_uint32_t size ); +int cas_copy_in_header ( + struct client *pClient, ca_uint16_t response, ca_uint32_t payloadSize, + ca_uint16_t dataType, ca_uint32_t nElem, ca_uint32_t cid, + ca_uint32_t responseSpecific, void **pPayload ); +void cas_set_header_cid ( struct client *pClient, ca_uint32_t ); +void cas_set_header_count (struct client *pClient, ca_uint32_t count); +void cas_commit_msg ( struct client *pClient, ca_uint32_t size ); + +#endif /*INCLserverh*/ diff --git a/modules/database/src/std/Makefile b/modules/database/src/std/Makefile new file mode 100644 index 000000000..20a8658cb --- /dev/null +++ b/modules/database/src/std/Makefile @@ -0,0 +1,29 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../.. +include $(TOP)/configure/CONFIG + +STDDIR=$(TOP)/src/std + +LIBRARY_IOC += dbRecStd +dbRecStd_LIBS = dbCore ca Com + +dbRecStd_RCS += dbRecStd.rc + +include $(STDDIR)/rec/Makefile +include $(STDDIR)/dev/Makefile +include $(STDDIR)/filters/Makefile +include $(STDDIR)/link/Makefile +include $(STDDIR)/softIoc/Makefile + +include $(TOP)/configure/RULES + +include $(STDDIR)/rec/RULES +include $(STDDIR)/softIoc/RULES diff --git a/modules/database/src/std/dbRecStd.rc b/modules/database/src/std/dbRecStd.rc new file mode 100644 index 000000000..f55b7d682 --- /dev/null +++ b/modules/database/src/std/dbRecStd.rc @@ -0,0 +1,36 @@ +#include +#include "epicsVersion.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + PRODUCTVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_UNKNOWN + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments","Record and Soft Device Support Library for EPICS\0" + VALUE "CompanyName", "The EPICS collaboration\0" + VALUE "FileDescription", "Record and Soft Device Support Library\0" + VALUE "FileVersion", EPICS_VERSION_STRING "\0" + VALUE "InternalName", "dbRecStd\0" + VALUE "LegalCopyright", "Copyright (C) Univ. of California, Univ. of Chicago\0" + VALUE "OriginalFilename", "dbRecStd.dll\0" + VALUE "ProductName", "Experimental Physics and Industrial Control System (EPICS)\0" + VALUE "ProductVersion", EPICS_VERSION_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/modules/database/src/std/dev/Makefile b/modules/database/src/std/dev/Makefile new file mode 100644 index 000000000..ec3713bd6 --- /dev/null +++ b/modules/database/src/std/dev/Makefile @@ -0,0 +1,76 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/std/Makefile. + +SRC_DIRS += $(STDDIR)/dev + +DBD += devSoft.dbd + +dbRecStd_SRCS += devAaiSoft.c +dbRecStd_SRCS += devAaoSoft.c +dbRecStd_SRCS += devAiSoft.c +dbRecStd_SRCS += devAiSoftRaw.c +dbRecStd_SRCS += devAoSoft.c +dbRecStd_SRCS += devAoSoftRaw.c +dbRecStd_SRCS += devBiSoft.c +dbRecStd_SRCS += devBiSoftRaw.c +dbRecStd_SRCS += devBiDbState.c +dbRecStd_SRCS += devBoSoft.c +dbRecStd_SRCS += devBoSoftRaw.c +dbRecStd_SRCS += devBoDbState.c +dbRecStd_SRCS += devCalcoutSoft.c +dbRecStd_SRCS += devEventSoft.c +dbRecStd_SRCS += devHistogramSoft.c +dbRecStd_SRCS += devI64inSoft.c +dbRecStd_SRCS += devI64outSoft.c +dbRecStd_SRCS += devLiSoft.c +dbRecStd_SRCS += devLoSoft.c +dbRecStd_SRCS += devLsiSoft.c +dbRecStd_SRCS += devLsoSoft.c +dbRecStd_SRCS += devMbbiDirectSoft.c +dbRecStd_SRCS += devMbbiDirectSoftRaw.c +dbRecStd_SRCS += devMbbiSoft.c +dbRecStd_SRCS += devMbbiSoftRaw.c +dbRecStd_SRCS += devMbboDirectSoft.c +dbRecStd_SRCS += devMbboDirectSoftRaw.c +dbRecStd_SRCS += devMbboSoft.c +dbRecStd_SRCS += devMbboSoftRaw.c +dbRecStd_SRCS += devPrintfSoft.c +dbRecStd_SRCS += devSASoft.c +dbRecStd_SRCS += devSiSoft.c +dbRecStd_SRCS += devSoSoft.c +dbRecStd_SRCS += devWfSoft.c +dbRecStd_SRCS += devGeneralTime.c + +dbRecStd_SRCS += devAiSoftCallback.c +dbRecStd_SRCS += devBiSoftCallback.c +dbRecStd_SRCS += devI64inSoftCallback.c +dbRecStd_SRCS += devLiSoftCallback.c +dbRecStd_SRCS += devMbbiDirectSoftCallback.c +dbRecStd_SRCS += devMbbiSoftCallback.c +dbRecStd_SRCS += devSiSoftCallback.c + +dbRecStd_SRCS += devAoSoftCallback.c +dbRecStd_SRCS += devBoSoftCallback.c +dbRecStd_SRCS += devCalcoutSoftCallback.c +dbRecStd_SRCS += devI64outSoftCallback.c +dbRecStd_SRCS += devLoSoftCallback.c +dbRecStd_SRCS += devLsoSoftCallback.c +dbRecStd_SRCS += devMbboSoftCallback.c +dbRecStd_SRCS += devMbboDirectSoftCallback.c +dbRecStd_SRCS += devPrintfSoftCallback.c +dbRecStd_SRCS += devSoSoftCallback.c + +dbRecStd_SRCS += devTimestamp.c +dbRecStd_SRCS += devStdio.c +dbRecStd_SRCS += devEnviron.c + +dbRecStd_SRCS += asSubRecordFunctions.c + diff --git a/modules/database/src/std/dev/asSubRecordFunctions.c b/modules/database/src/std/dev/asSubRecordFunctions.c new file mode 100644 index 000000000..76943e94b --- /dev/null +++ b/modules/database/src/std/dev/asSubRecordFunctions.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* asSubRecordFunctions.c */ + +/* Author: Marty Kraimer Date: 01MAY2000 */ + +#include +#include +#include +#include +#include + +#include "dbAccess.h" +#include "cantProceed.h" +#include "callback.h" +#include "alarm.h" +#include "errlog.h" +#include "dbEvent.h" +#include "recSup.h" +#include "recGbl.h" +#include "registryFunction.h" +#include "asLib.h" +#include "asDbLib.h" +#include "subRecord.h" +#include "epicsExport.h" + +/* The following is provided for access security*/ +/*It allows a CA client to force access security initialization*/ + +static void myCallback(CALLBACK *pcallback) +{ + ASDBCALLBACK *pasdbcallback = (ASDBCALLBACK *)pcallback; + subRecord *precord; + rset *prset; + + callbackGetUser(precord,pcallback); + prset=(rset *)(precord->rset); + precord->val = 0.0; + if(pasdbcallback->status) { + recGblSetSevr(precord,READ_ALARM,precord->brsv); + recGblRecordError(pasdbcallback->status,precord,"asInit Failed"); + } + dbScanLock((dbCommon *)precord); + (*prset->process)((dbCommon *)precord); + dbScanUnlock((dbCommon *)precord); +} + +long asSubInit(subRecord *precord,void *process) +{ + ASDBCALLBACK *pcallback; + + pcallback = (ASDBCALLBACK *)callocMustSucceed( + 1,sizeof(ASDBCALLBACK),"asSubInit"); + precord->dpvt = (void *)pcallback; + callbackSetCallback(myCallback,&pcallback->callback); + callbackSetUser(precord,&pcallback->callback); + return(0); +} + +long asSubProcess(subRecord *precord) +{ + ASDBCALLBACK *pcallback = (ASDBCALLBACK *)precord->dpvt; + + if(!precord->pact && precord->val==1.0) { + db_post_events(precord,&precord->val,DBE_VALUE); + callbackSetPriority(precord->prio,&pcallback->callback); + asInitAsyn(pcallback); + precord->pact=TRUE; + return(1); + } + db_post_events(precord,&precord->val,DBE_VALUE); + return(0); +} + +static registryFunctionRef asSubRef[] = { + {"asSubInit",(REGISTRYFUNCTION)asSubInit}, + {"asSubProcess",(REGISTRYFUNCTION)asSubProcess} +}; + +static void asSub(void) +{ + registryFunctionRefAdd(asSubRef,NELEMENTS(asSubRef)); +} +epicsExportRegistrar(asSub); diff --git a/modules/database/src/std/dev/devAaiSoft.c b/modules/database/src/std/dev/devAaiSoft.c new file mode 100644 index 000000000..d2a9875ee --- /dev/null +++ b/modules/database/src/std/dev/devAaiSoft.c @@ -0,0 +1,106 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * devAaiSoft.c - Device Support Routines for soft Waveform Records + * + * Original Author: Bob Dalesio + * Current Author: Dirk Zimoch + * Date: 27-MAY-2010 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbConstLink.h" +#include "recGbl.h" +#include "devSup.h" +#include "cantProceed.h" +#include "menuYesNo.h" +#include "aaiRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAaiSoft */ +static long init_record(); +static long read_aai(); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_aai; +} devAaiSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_aai +}; +epicsExportAddress(dset,devAaiSoft); + +static long init_record(aaiRecord *prec) +{ + DBLINK *plink = &prec->inp; + + /* This is pass 0, link hasn't been initialized yet */ + dbInitLink(plink, DBF_INLINK); + + if (dbLinkIsConstant(plink)) { + long nRequest = prec->nelm; + long status; + + /* Allocate a buffer, record support hasn't done that yet */ + if (!prec->bptr) { + prec->bptr = callocMustSucceed(nRequest, dbValueSize(prec->ftvl), + "devAaiSoft: buffer calloc failed"); + } + + status = dbLoadLinkArray(plink, prec->ftvl, prec->bptr, &nRequest); + if (!status && nRequest > 0) { + prec->nord = nRequest; + prec->udf = FALSE; + } + } + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + aaiRecord *prec = (aaiRecord *) pinp->precord; + long nRequest = prec->nelm; + long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &nRequest); + + if (!status && nRequest > 0) { + prec->nord = nRequest; + prec->udf = FALSE; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + } + return status; +} + +static long read_aai(aaiRecord *prec) +{ + struct link *pinp = prec->simm == menuYesNoYES ? &prec->siol : &prec->inp; + long status = dbLinkDoLocked(pinp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(pinp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devAaoSoft.c b/modules/database/src/std/dev/devAaoSoft.c new file mode 100644 index 000000000..3331ec1bf --- /dev/null +++ b/modules/database/src/std/dev/devAaoSoft.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * devAaoSoft.c - Device Support Routines for soft Waveform Records + * + * Original Author: Bob Dalesio + * Current Author: Dirk Zimoch + * Date: 27-MAY-2010 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "cantProceed.h" +#include "menuYesNo.h" +#include "aaoRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAaoSoft */ +static long init_record(); +static long write_aao(); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_aao; +} devAaoSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + write_aao +}; +epicsExportAddress(dset,devAaoSoft); + +static long init_record(aaoRecord *prec) +{ + if (dbLinkIsConstant(&prec->out)) { + prec->nord = 0; + } + return 0; +} + +static long write_aao(aaoRecord *prec) +{ + long nRequest = prec->nord; + dbPutLink(prec->simm == menuYesNoYES ? &prec->siol : &prec->out, + prec->ftvl, prec->bptr, nRequest); + + return 0; +} diff --git a/modules/database/src/std/dev/devAiSoft.c b/modules/database/src/std/dev/devAiSoft.c new file mode 100644 index 000000000..0ecc1b13f --- /dev/null +++ b/modules/database/src/std/dev/devAiSoft.c @@ -0,0 +1,104 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 3/6/91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "epicsMath.h" +#include "recGbl.h" +#include "devSup.h" +#include "aiRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAiSoft */ +static long init_record(aiRecord *prec); +static long read_ai(aiRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_ai; + DEVSUPFUN special_linconv; +} devAiSoft = { + 6, + NULL, + NULL, + init_record, + NULL, + read_ai, + NULL +}; +epicsExportAddress(dset, devAiSoft); + +static long init_record(aiRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_DOUBLE, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +struct aivt { + double val; + epicsTimeStamp *ptime; +}; + +static long readLocked(struct link *pinp, void *vvt) +{ + struct aivt *pvt = (struct aivt *) vvt; + long status = dbGetLink(pinp, DBR_DOUBLE, &pvt->val, 0, 0); + + if (!status && pvt->ptime) + dbGetTimeStamp(pinp, pvt->ptime); + + return status; +} + +static long read_ai(aiRecord *prec) +{ + long status; + struct aivt vt; + + if (dbLinkIsConstant(&prec->inp)) + return 2; + + vt.ptime = (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL; + + status = dbLinkDoLocked(&prec->inp, readLocked, &vt); + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &vt); + + if (!status) { + /* Apply smoothing algorithm */ + if (prec->smoo != 0.0 && prec->dpvt && finite(prec->val)) + prec->val = vt.val * (1.0 - prec->smoo) + (prec->val * prec->smoo); + else + prec->val = vt.val; + + prec->udf = FALSE; + prec->dpvt = &devAiSoft; /* Any non-zero value */ + } + else + prec->dpvt = NULL; + + return 2; +} diff --git a/modules/database/src/std/dev/devAiSoftCallback.c b/modules/database/src/std/dev/devAiSoftCallback.c new file mode 100644 index 000000000..cf38c8713 --- /dev/null +++ b/modules/database/src/std/dev/devAiSoftCallback.c @@ -0,0 +1,226 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devAiSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "epicsMath.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "aiRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + int smooth; + struct { + DBRstatus + DBRtime + epicsFloat64 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + aiRecord *prec = (aiRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devAiSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_DOUBLE, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + aiRecord *prec = (aiRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + aiRecord *prec = (aiRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devAiSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devAiSoftCallback (add_record) link target not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devAiSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + aiRecord *prec = (aiRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(aiRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_DOUBLE, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_ai(aiRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 2; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + pdevPvt->smooth = FALSE; + return 2; + } + + /* Apply smoothing algorithm */ + if (prec->smoo != 0.0 && pdevPvt->smooth && finite(prec->val)) + prec->val = prec->val * prec->smoo + + pdevPvt->buffer.value * (1.0 - prec->smoo); + else + prec->val = pdevPvt->buffer.value; + + prec->udf = FALSE; + pdevPvt->smooth = TRUE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 2; +} + +/* Create the dset for devAiSoftCallback */ +struct { + dset common; + DEVSUPFUN read_ai; + DEVSUPFUN special_linconv; +} devAiSoftCallback = { + {6, NULL, init, init_record, NULL}, + read_ai, + NULL +}; +epicsExportAddress(dset, devAiSoftCallback); diff --git a/modules/database/src/std/dev/devAiSoftRaw.c b/modules/database/src/std/dev/devAiSoftRaw.c new file mode 100644 index 000000000..f2cfd5df5 --- /dev/null +++ b/modules/database/src/std/dev/devAiSoftRaw.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "aiRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAiSoftRaw */ +static long init_record(aiRecord *prec); +static long read_ai(aiRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_ai; + DEVSUPFUN special_linconv; +} devAiSoftRaw = { + 6, + NULL, + NULL, + init_record, + NULL, + read_ai, + NULL +}; +epicsExportAddress(dset, devAiSoftRaw); + +static long init_record(aiRecord *prec) +{ + recGblInitConstantLink(&prec->inp, DBF_LONG, &prec->rval); + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + aiRecord *prec = (aiRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_LONG, &prec->rval, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_ai(aiRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devAoSoft.c b/modules/database/src/std/dev/devAoSoft.c new file mode 100644 index 000000000..56bd05620 --- /dev/null +++ b/modules/database/src/std/dev/devAoSoft.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devAoSoft.c */ + +/* Device Support Routines for soft Analog Output Records*/ +/* + * Original Author: Bob Dalesio + * Current Author: Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "special.h" +#include "aoRecord.h" +#include "epicsExport.h" + +/* added for Channel Access Links */ +static long init_record(aoRecord *prec); + +/* Create the dset for devAoSoft */ +static long write_ao(aoRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_ao; + DEVSUPFUN special_linconv; +}devAoSoft={ + 6, + NULL, + NULL, + init_record, + NULL, + write_ao, + NULL}; +epicsExportAddress(dset,devAoSoft); + + +static long init_record(aoRecord *prec) +{ + + long status=0; + status = 2; + return status; + +} /* end init_record() */ + +static long write_ao(aoRecord *prec) +{ + long status; + + status = dbPutLink(&prec->out,DBR_DOUBLE, &prec->oval,1); + + return(status); +} diff --git a/modules/database/src/std/dev/devAoSoftCallback.c b/modules/database/src/std/dev/devAoSoftCallback.c new file mode 100644 index 000000000..c1fb72f13 --- /dev/null +++ b/modules/database/src/std/dev/devAoSoftCallback.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devAoSoftCallbackCallback.c */ +/* + * Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "dbCa.h" +#include "link.h" +#include "special.h" +#include "aoRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAoSoftCallback */ +static long write_ao(aoRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_ao; + DEVSUPFUN special_linconv; +}devAoSoftCallback={ + 6, + NULL, + NULL, + NULL, + NULL, + write_ao, + NULL}; +epicsExportAddress(dset,devAoSoftCallback); + +static long write_ao(aoRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_DOUBLE, &prec->oval, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_DOUBLE, &prec->oval, 1); + + return status; +} + diff --git a/modules/database/src/std/dev/devAoSoftRaw.c b/modules/database/src/std/dev/devAoSoftRaw.c new file mode 100644 index 000000000..05aed035e --- /dev/null +++ b/modules/database/src/std/dev/devAoSoftRaw.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devAoSoftRaw.c */ + +/* Device Support Routines for soft raw Analog Output Records*/ +/* + * Author: Janet Anderson + * Date: 09-25-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "special.h" +#include "aoRecord.h" +#include "epicsExport.h" + +/* Create the dset for devAoSoftRaw */ +static long write_ao(aoRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_ao; + DEVSUPFUN special_linconv; +}devAoSoftRaw={ + 6, + NULL, + NULL, + NULL, + NULL, + write_ao, + NULL +}; +epicsExportAddress(dset,devAoSoftRaw); + +static long write_ao(aoRecord *prec) +{ + long status; + + status = dbPutLink(&prec->out,DBR_LONG,&prec->rval,1); + + return(status); +} diff --git a/modules/database/src/std/dev/devBiDbState.c b/modules/database/src/std/dev/devBiDbState.c new file mode 100644 index 000000000..fcb6c8f63 --- /dev/null +++ b/modules/database/src/std/dev/devBiDbState.c @@ -0,0 +1,88 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include "errlog.h" +#include "dbState.h" +#include "devSup.h" +#include "recGbl.h" +#include "dbLink.h" +#include "dbAccessDefs.h" +#include "biRecord.h" +#include "epicsExport.h" + +#define DEVSUPNAME "devBiDbState" + +static long add_record (struct dbCommon *pdbc) +{ + biRecord *prec = (biRecord *) pdbc; + + if (INST_IO != prec->inp.type) { + recGblRecordError(S_db_badField, (void *) prec, DEVSUPNAME ": Illegal INP field"); + return(S_db_badField); + } + + if (!(prec->dpvt = dbStateFind(prec->inp.value.instio.string)) && + prec->inp.value.instio.string && + '\0' != *prec->inp.value.instio.string) { + errlogSevPrintf(errlogInfo, DEVSUPNAME ": Creating new db state '%s'\n", + prec->inp.value.instio.string); + prec->dpvt = dbStateCreate(prec->inp.value.instio.string); + } + return 0; +} + +static long del_record (struct dbCommon *pdbc) +{ + biRecord *prec = (biRecord *) pdbc; + prec->dpvt = NULL; + return 0; +} + +static struct dsxt myDsxt = { + add_record, + del_record +}; + +static long init(int pass) +{ + if (pass == 0) + devExtend(&myDsxt); + return 0; +} + +static long read_bi(biRecord *prec) +{ + if (prec->dpvt) { + prec->val = dbStateGet(prec->dpvt); + prec->udf = FALSE; + } + + return 2; +} + +static struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_bi; +} devBiDbState = { + 5, + NULL, + init, + NULL, + NULL, + read_bi +}; + +epicsExportAddress(dset, devBiDbState); diff --git a/modules/database/src/std/dev/devBiSoft.c b/modules/database/src/std/dev/devBiSoft.c new file mode 100644 index 000000000..12640ad0c --- /dev/null +++ b/modules/database/src/std/dev/devBiSoft.c @@ -0,0 +1,78 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "biRecord.h" +#include "epicsExport.h" + +/* Create the dset for devBiSoft */ +static long init_record(biRecord *prec); +static long read_bi(biRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_bi; +} devBiSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_bi +}; +epicsExportAddress(dset, devBiSoft); + +static long init_record(biRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_ENUM, &prec->val)) + prec->udf = FALSE; + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + biRecord *prec = (biRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_USHORT, &prec->val, 0, 0); + + if (status) return status; + + prec->udf = FALSE; + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return 2; +} + +static long read_bi(biRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devBiSoftCallback.c b/modules/database/src/std/dev/devBiSoftCallback.c new file mode 100644 index 000000000..314460049 --- /dev/null +++ b/modules/database/src/std/dev/devBiSoftCallback.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devBiSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "biRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsEnum16 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn,notifyGetType type) +{ + biRecord *prec = (biRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devBiSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_ENUM, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + biRecord *prec = (biRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + biRecord *prec = (biRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devBiSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devBiSoftCallback (add_record) link target not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devBiSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + biRecord *prec = (biRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(biRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_ENUM, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_bi(biRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 2; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return 2; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 2; +} + +/* Create the dset for devBiSoftCallback */ +struct { + dset common; + DEVSUPFUN read_bi; +} devBiSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_bi +}; +epicsExportAddress(dset, devBiSoftCallback); diff --git a/modules/database/src/std/dev/devBiSoftRaw.c b/modules/database/src/std/dev/devBiSoftRaw.c new file mode 100644 index 000000000..a71bf89cb --- /dev/null +++ b/modules/database/src/std/dev/devBiSoftRaw.c @@ -0,0 +1,77 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "biRecord.h" +#include "epicsExport.h" + +/* Create the dset for devBiSoftRaw */ +static long init_record(biRecord *prec); +static long read_bi(biRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_bi; +} devBiSoftRaw = { + 5, + NULL, + NULL, + init_record, + NULL, + read_bi +}; +epicsExportAddress(dset, devBiSoftRaw); + +static long init_record(biRecord *prec) +{ + recGblInitConstantLink(&prec->inp, DBF_ULONG, &prec->rval); + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + biRecord *prec = (biRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_ULONG, &prec->rval, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_bi(biRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devBoDbState.c b/modules/database/src/std/dev/devBoDbState.c new file mode 100644 index 000000000..26e97ae5b --- /dev/null +++ b/modules/database/src/std/dev/devBoDbState.c @@ -0,0 +1,86 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include "errlog.h" +#include "dbState.h" +#include "devSup.h" +#include "recGbl.h" +#include "dbAccessDefs.h" +#include "boRecord.h" +#include "epicsExport.h" + +#define DEVSUPNAME "devBoDbState" + +static long add_record (struct dbCommon *pdbc) +{ + boRecord *prec = (boRecord *) pdbc; + + if (INST_IO != prec->out.type) { + recGblRecordError(S_db_badField, (void *) prec, DEVSUPNAME ": Illegal OUT field"); + return(S_db_badField); + } + + if (!(prec->dpvt = dbStateFind(prec->out.value.instio.string)) && + prec->out.value.instio.string && + '\0' != *prec->out.value.instio.string) { + errlogSevPrintf(errlogInfo, DEVSUPNAME ": Creating new db state '%s'\n", + prec->out.value.instio.string); + prec->dpvt = dbStateCreate(prec->out.value.instio.string); + } + return 0; +} + +static long del_record (struct dbCommon *pdbc) +{ + boRecord *prec = (boRecord *) pdbc; + prec->dpvt = NULL; + return 0; +} + +static struct dsxt myDsxt = { + add_record, + del_record +}; + +static long init(int pass) +{ + if (pass == 0) + devExtend(&myDsxt); + return 0; +} + +static long write_bo(boRecord *prec) +{ + if (prec->val) + dbStateSet(prec->dpvt); + else + dbStateClear(prec->dpvt); + return 0; +} + +static struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_bo; +} devBoDbState = { + 5, + NULL, + init, + NULL, + NULL, + write_bo +}; + +epicsExportAddress(dset, devBoDbState); diff --git a/modules/database/src/std/dev/devBoSoft.c b/modules/database/src/std/dev/devBoSoft.c new file mode 100644 index 000000000..ba6ff14a7 --- /dev/null +++ b/modules/database/src/std/dev/devBoSoft.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devBoSoft.c - Device Support Routines for Soft Binary Output*/ +/* + * Original Author: Bob Dalesio + * Current Author: Marty Kraimer + * Date: 6-1-90 + */ + + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "boRecord.h" +#include "epicsExport.h" + +static long init_record(boRecord *prec); + +/* Create the dset for devBoSoft */ +static long write_bo(boRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_bo; +}devBoSoft={ + 5, + NULL, + NULL, + init_record, + NULL, + write_bo +}; +epicsExportAddress(dset,devBoSoft); + +static long init_record(boRecord *prec) +{ + + long status=0; + + /* dont convert */ + status=2; + return status; + +} /* end init_record() */ + +static long write_bo(boRecord *prec) +{ + long status; + + status = dbPutLink(&prec->out,DBR_USHORT,&prec->val,1); + + return(status); +} diff --git a/modules/database/src/std/dev/devBoSoftCallback.c b/modules/database/src/std/dev/devBoSoftCallback.c new file mode 100644 index 000000000..ffb68e525 --- /dev/null +++ b/modules/database/src/std/dev/devBoSoftCallback.c @@ -0,0 +1,67 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devBoCallbackSoft.c */ +/* + * Author: Marty Kraimer + * Date: 04NOV2003 + */ + + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbLock.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "boRecord.h" +#include "epicsExport.h" + +/* Create the dset for devBoCallbackSoft */ +static long write_bo(boRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_bo; +}devBoSoftCallback={ + 5, + NULL, + NULL, + NULL, + NULL, + write_bo +}; +epicsExportAddress(dset,devBoSoftCallback); + +static long write_bo(boRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_USHORT, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_USHORT, &prec->val, 1); + + return status; +} + diff --git a/modules/database/src/std/dev/devBoSoftRaw.c b/modules/database/src/std/dev/devBoSoftRaw.c new file mode 100644 index 000000000..df1ba5b4e --- /dev/null +++ b/modules/database/src/std/dev/devBoSoftRaw.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devBoSoftRaw.c - Device Support Routines for SoftRaw Binary Output*/ +/* + * Author: Janet Anderson + * Date: 3-28-92 + */ + + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "boRecord.h" +#include "epicsExport.h" + +/* added for Channel Access Links */ +static long init_record(boRecord *prec); + +/* Create the dset for devBoSoftRaw */ +static long write_bo(boRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_bo; +}devBoSoftRaw={ + 5, + NULL, + NULL, + init_record, + NULL, + write_bo +}; +epicsExportAddress(dset,devBoSoftRaw); + +static long init_record(boRecord *prec) +{ + long status; + + /*Don't convert*/ + status = 2; + return status; + +} /* end init_record() */ + +static long write_bo(boRecord *prec) +{ + long status; + + status = dbPutLink(&prec->out,DBR_LONG, &prec->rval,1); + + return(status); +} diff --git a/modules/database/src/std/dev/devCalcoutSoft.c b/modules/database/src/std/dev/devCalcoutSoft.c new file mode 100644 index 000000000..f931e6ac0 --- /dev/null +++ b/modules/database/src/std/dev/devCalcoutSoft.c @@ -0,0 +1,49 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devCalcoutSoft.c */ + +/* + * Author: Marty Kraimer + * Date: 05DEC2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "special.h" +#include "postfix.h" +#include "calcoutRecord.h" +#include "epicsExport.h" + +static long write_calcout(calcoutRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devCalcoutSoft = { + 5, NULL, NULL, NULL, NULL, write_calcout +}; +epicsExportAddress(dset, devCalcoutSoft); + +static long write_calcout(calcoutRecord *prec) +{ + return dbPutLink(&prec->out, DBR_DOUBLE, &prec->oval, 1); +} diff --git a/modules/database/src/std/dev/devCalcoutSoftCallback.c b/modules/database/src/std/dev/devCalcoutSoftCallback.c new file mode 100644 index 000000000..94f9d4f99 --- /dev/null +++ b/modules/database/src/std/dev/devCalcoutSoftCallback.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devCalcoutSoftCallback.c */ + +/* + * Author: Marty Kraimer + * Date: 05DEC2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "special.h" +#include "postfix.h" +#include "calcoutRecord.h" +#include "epicsExport.h" + +static long write_calcout(calcoutRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +} devCalcoutSoftCallback = { + 5, NULL, NULL, NULL, NULL, write_calcout +}; +epicsExportAddress(dset, devCalcoutSoftCallback); + +static long write_calcout(calcoutRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_DOUBLE, &prec->oval, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_DOUBLE, &prec->oval, 1); + + return 0; +} + diff --git a/modules/database/src/std/dev/devEnviron.c b/modules/database/src/std/dev/devEnviron.c new file mode 100644 index 000000000..9672d6ce5 --- /dev/null +++ b/modules/database/src/std/dev/devEnviron.c @@ -0,0 +1,128 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devEnviron.c */ + +#include +#include + +#include "alarm.h" +#include "dbCommon.h" +#include "devSup.h" +#include "errlog.h" +#include "recGbl.h" +#include "recSup.h" + +#include "lsiRecord.h" +#include "stringinRecord.h" +#include "epicsExport.h" + +/* lsi device support */ + +static long add_lsi(dbCommon *pcommon) { + lsiRecord *prec = (lsiRecord *) pcommon; + + if (prec->inp.type != INST_IO) + return S_dev_badInpType; + + return 0; +} + +static long del_lsi(dbCommon *pcommon) { + return 0; +} + +static struct dsxt dsxtLsiEnviron = { + add_lsi, del_lsi +}; + +static long init_lsi(int pass) +{ + if (pass == 0) + devExtend(&dsxtLsiEnviron); + + return 0; +} + +static long read_lsi(lsiRecord *prec) +{ + const char *val = getenv(prec->inp.value.instio.string); + + if (val) { + strncpy(prec->val, val, prec->sizv); + prec->val[prec->sizv - 1] = 0; + prec->len = strlen(prec->val); + prec->udf = FALSE; + } + else { + prec->val[0] = 0; + prec->len = 1; + prec->udf = TRUE; + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + } + + return 0; +} + +lsidset devLsiEnviron = { + 5, NULL, init_lsi, NULL, NULL, read_lsi +}; +epicsExportAddress(dset, devLsiEnviron); + + +/* stringin device support */ + +static long add_stringin(dbCommon *pcommon) { + stringinRecord *prec = (stringinRecord *) pcommon; + + if (prec->inp.type != INST_IO) + return S_dev_badInpType; + + return 0; +} + +static long del_stringin(dbCommon *pcommon) { + return 0; +} + +static struct dsxt dsxtSiEnviron = { + add_stringin, del_stringin +}; + +static long init_stringin(int pass) +{ + if (pass == 0) + devExtend(&dsxtSiEnviron); + + return 0; +} + +static long read_stringin(stringinRecord *prec) +{ + const char *val = getenv(prec->inp.value.instio.string); + + if (val) { + strncpy(prec->val, val, MAX_STRING_SIZE); + prec->val[MAX_STRING_SIZE - 1] = 0; + prec->udf = FALSE; + } + else { + prec->val[0] = 0; + prec->udf = TRUE; + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + } + + return 0; +} + +static struct { + dset common; + DEVSUPFUN read; +} devSiEnviron = { + {5, NULL, init_stringin, NULL, NULL}, read_stringin +}; +epicsExportAddress(dset, devSiEnviron); diff --git a/modules/database/src/std/dev/devEventSoft.c b/modules/database/src/std/dev/devEventSoft.c new file mode 100644 index 000000000..a748dda66 --- /dev/null +++ b/modules/database/src/std/dev/devEventSoft.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Janet Anderson + * Date: 04-21-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "eventRecord.h" +#include "epicsExport.h" + +/* Create the dset for devEventSoft */ +static long init_record(eventRecord *prec); +static long read_event(eventRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_event; +} devEventSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_event +}; +epicsExportAddress(dset, devEventSoft); + +static long init_record(eventRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_STRING, prec->val)) + prec->udf = FALSE; + + return 0; +} + +struct eventvt { + char newEvent[MAX_STRING_SIZE]; + epicsTimeStamp *ptime; +}; + +static long readLocked(struct link *pinp, void *vvt) +{ + struct eventvt *pvt = (struct eventvt *) vvt; + long status = dbGetLink(pinp, DBR_STRING, pvt->newEvent, 0, 0); + + if (!status && pvt->ptime) + dbGetTimeStamp(pinp, pvt->ptime); + + return status; +} + +static long read_event(eventRecord *prec) +{ + long status; + struct eventvt vt; + + if (dbLinkIsConstant(&prec->inp)) + return 0; + + vt.ptime = (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL; + + status = dbLinkDoLocked(&prec->inp, readLocked, &vt); + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &vt); + + if (!status) { + if (strcmp(vt.newEvent, prec->val) != 0) { + strcpy(prec->val, vt.newEvent); + prec->epvt = eventNameToHandle(prec->val); + } + prec->udf = FALSE; + } + + return status; +} diff --git a/modules/database/src/std/dev/devGeneralTime.c b/modules/database/src/std/dev/devGeneralTime.c new file mode 100644 index 000000000..1b821d582 --- /dev/null +++ b/modules/database/src/std/dev/devGeneralTime.c @@ -0,0 +1,297 @@ +/*************************************************************************\ +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Sheng Peng, ORNL / SNS Project + * Date: 07/2004 + * + * EPICS device support for general timestamp support + * + * Integrated into base by Peter Denison, Diamond Light Source + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "epicsString.h" +#include "epicsGeneralTime.h" + +#include "aiRecord.h" +#include "boRecord.h" +#include "longinRecord.h" +#include "stringinRecord.h" +#include "epicsExport.h" + + +/********* ai record **********/ +static int getCurrentTime(double * pseconds) +{ + epicsTimeStamp ts; + + if (epicsTimeOK == epicsTimeGetCurrent(&ts)) { + *pseconds = ts.secPastEpoch + ((double)(ts.nsec)) * 1e-9; + return 0; + } + return -1; +} + +static struct ai_channel { + char *name; + int (*get)(double *); +} ai_channels[] = { + {"TIME", getCurrentTime}, +}; + +static long init_ai(aiRecord *prec) +{ + int i; + + if (prec->inp.type != INST_IO) { + recGblRecordError(S_db_badField, (void *)prec, + "devAiGeneralTime::init_ai: Illegal INP field"); + prec->pact = TRUE; + return S_db_badField; + } + + for (i = 0; i < NELEMENTS(ai_channels); i++) { + struct ai_channel *pchan = &ai_channels[i]; + if (!epicsStrCaseCmp(prec->inp.value.instio.string, pchan->name)) { + prec->dpvt = pchan; + return 0; + } + } + + recGblRecordError(S_db_badField, (void *)prec, + "devAiGeneralTime::init_ai: Bad parm"); + prec->pact = TRUE; + prec->dpvt = NULL; + return S_db_badField; +} + +static long read_ai(aiRecord *prec) +{ + struct ai_channel *pchan = (struct ai_channel *)prec->dpvt; + + if (!pchan) return -1; + + if (pchan->get(&prec->val) == 0) { + prec->udf = FALSE; + return 2; + } + prec->udf = TRUE; + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return -1; +} + +struct { + dset common; + DEVSUPFUN read_write; + DEVSUPFUN special_linconv; +} devAiGeneralTime = { + {6, NULL, NULL, init_ai, NULL}, read_ai, NULL +}; +epicsExportAddress(dset, devAiGeneralTime); + + +/********* bo record **********/ +static void resetErrors(void) +{ + generalTimeResetErrorCounts(); +} + +static struct bo_channel { + char *name; + void (*put)(void); +} bo_channels[] = { + {"RSTERRCNT", resetErrors}, +}; + +static long init_bo(boRecord *prec) +{ + int i; + + if (prec->out.type != INST_IO) { + recGblRecordError(S_db_badField, (void *)prec, + "devAiGeneralTime::init_ai: Illegal INP field"); + prec->pact = TRUE; + return S_db_badField; + } + + for (i = 0; i < NELEMENTS(bo_channels); i++) { + struct bo_channel *pchan = &bo_channels[i]; + if (!epicsStrCaseCmp(prec->out.value.instio.string, pchan->name)) { + prec->dpvt = pchan; + prec->mask = 0; + return 2; + } + } + + recGblRecordError(S_db_badField, (void *)prec, + "devBoGeneralTime::init_bo: Bad parm"); + prec->pact = TRUE; + prec->dpvt = NULL; + return S_db_badField; +} + +static long write_bo(boRecord *prec) +{ + struct bo_channel *pchan = (struct bo_channel *)prec->dpvt; + + if (!pchan) return -1; + + pchan->put(); + return 0; +} + +struct { + dset common; + DEVSUPFUN read_write; +} devBoGeneralTime = { + {5, NULL, NULL, init_bo, NULL}, write_bo +}; +epicsExportAddress(dset, devBoGeneralTime); + + +/******* longin record *************/ +static int errorCount(void) +{ + return generalTimeGetErrorCounts(); +} + +static struct li_channel { + char *name; + int (*get)(void); +} li_channels[] = { + {"GETERRCNT", errorCount}, +}; + +static long init_li(longinRecord *prec) +{ + int i; + + if (prec->inp.type != INST_IO) { + recGblRecordError(S_db_badField, (void *)prec, + "devLiGeneralTime::init_li: Illegal INP field"); + prec->pact = TRUE; + return S_db_badField; + } + + for (i = 0; i < NELEMENTS(li_channels); i++) { + struct li_channel *pchan = &li_channels[i]; + if (!epicsStrCaseCmp(prec->inp.value.instio.string, pchan->name)) { + prec->dpvt = pchan; + return 0; + } + } + + recGblRecordError(S_db_badField, (void *)prec, + "devLiGeneralTime::init_li: Bad parm"); + prec->pact = TRUE; + prec->dpvt = NULL; + return S_db_badField; +} + +static long read_li(longinRecord *prec) +{ + struct li_channel *pchan = (struct li_channel *)prec->dpvt; + + if (!pchan) return -1; + + prec->val = pchan->get(); + return 0; +} + +struct { + dset common; + DEVSUPFUN read_write; +} devLiGeneralTime = { + {5, NULL, NULL, init_li, NULL}, read_li +}; +epicsExportAddress(dset, devLiGeneralTime); + + +/********** stringin record **********/ +static const char * timeProvider(void) +{ + return generalTimeCurrentProviderName(); +} + +static const char * highestProvider(void) +{ + return generalTimeHighestCurrentName(); +} + +static const char * eventProvider(void) +{ + return generalTimeEventProviderName(); +} + +static struct si_channel { + char *name; + const char * (*get)(void); +} si_channels[] = { + {"BESTTCP", timeProvider}, + {"TOPTCP", highestProvider}, + {"BESTTEP", eventProvider}, +}; + +static long init_si(stringinRecord *prec) +{ + int i; + + if (prec->inp.type != INST_IO) { + recGblRecordError(S_db_badField, (void *)prec, + "devSiGeneralTime::init_si: Illegal INP field"); + prec->pact = TRUE; + return S_db_badField; + } + + for (i = 0; i < NELEMENTS(si_channels); i++) { + struct si_channel *pchan = &si_channels[i]; + if (!epicsStrCaseCmp(prec->inp.value.instio.string, pchan->name)) { + prec->dpvt = pchan; + return 0; + } + } + + recGblRecordError(S_db_badField, (void *)prec, + "devSiGeneralTime::init_si: Bad parm"); + prec->pact = TRUE; + prec->dpvt = NULL; + return S_db_badField; +} + +static long read_si(stringinRecord *prec) +{ + struct si_channel *pchan = (struct si_channel *)prec->dpvt; + const char *name; + + if (!pchan) return -1; + + name = pchan->get(); + if (name) { + strncpy(prec->val, name, sizeof(prec->val)); + prec->val[sizeof(prec->val) - 1] = '\0'; + } else { + strcpy(prec->val, "No working providers"); + recGblSetSevr(prec, READ_ALARM, MAJOR_ALARM); + } + prec->udf = FALSE; + return 0; +} + +struct { + dset common; + DEVSUPFUN read_write; +} devSiGeneralTime = { + {5, NULL, NULL, init_si, NULL}, read_si +}; +epicsExportAddress(dset, devSiGeneralTime); diff --git a/modules/database/src/std/dev/devHistogramSoft.c b/modules/database/src/std/dev/devHistogramSoft.c new file mode 100644 index 000000000..3b46b5d99 --- /dev/null +++ b/modules/database/src/std/dev/devHistogramSoft.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devHistogramSoft.c */ +/* + * Author: Janet Anderson + * Date: 07/02/91 + */ +#include +#include +#include + +#include "alarm.h" +#include "cvtTable.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "histogramRecord.h" +#include "epicsExport.h" + +/* Create the dset for devHistogramSoft */ +static long init_record(histogramRecord *prec); +static long read_histogram(histogramRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_histogram; + DEVSUPFUN special_linconv; +}devHistogramSoft={ + 6, + NULL, + NULL, + init_record, + NULL, + read_histogram, + NULL +}; +epicsExportAddress(dset,devHistogramSoft); + +static long init_record(histogramRecord *prec) +{ + if (recGblInitConstantLink(&prec->svl,DBF_DOUBLE,&prec->sgnl)) + prec->udf = FALSE; + + return 0; +} + +static long read_histogram(histogramRecord *prec) +{ + dbGetLink(&prec->svl, DBR_DOUBLE, &prec->sgnl, 0, 0); + return 0; /*add count*/ +} diff --git a/modules/database/src/std/dev/devI64inSoft.c b/modules/database/src/std/dev/devI64inSoft.c new file mode 100644 index 000000000..8d4ad90b7 --- /dev/null +++ b/modules/database/src/std/dev/devI64inSoft.c @@ -0,0 +1,78 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Janet Anderson + * Date: 09-23-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "int64inRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64inSoft */ +static long init_record(int64inRecord *prec); +static long read_int64in(int64inRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_int64in; +} devI64inSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_int64in +}; +epicsExportAddress(dset, devI64inSoft); + +static long init_record(int64inRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_INT64, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + int64inRecord *prec = (int64inRecord *) pinp->precord; + long status = dbGetLink(&prec->inp, DBR_INT64, &prec->val, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_int64in(int64inRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devI64inSoftCallback.c b/modules/database/src/std/dev/devI64inSoftCallback.c new file mode 100644 index 000000000..9eb5656bb --- /dev/null +++ b/modules/database/src/std/dev/devI64inSoftCallback.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devI64inSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "int64inRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsInt64 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + int64inRecord *prec = (int64inRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devI64inSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_INT64, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + int64inRecord *prec = (int64inRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + int64inRecord *prec = (int64inRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (init_record) linked record not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + int64inRecord *prec = (int64inRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(int64inRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_INT64, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_int64in(int64inRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 0; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return pdevPvt->status; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 0; +} + +/* Create the dset for devI64inSoftCallback */ +struct { + dset common; + DEVSUPFUN read_int64in; +} devI64inSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_int64in +}; +epicsExportAddress(dset, devI64inSoftCallback); diff --git a/modules/database/src/std/dev/devI64outSoft.c b/modules/database/src/std/dev/devI64outSoft.c new file mode 100644 index 000000000..f9ac70a7e --- /dev/null +++ b/modules/database/src/std/dev/devI64outSoft.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Janet Anderson + * Date: 09-23-91 +*/ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "int64outRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64outSoft */ +static long init_record(int64outRecord *prec); +static long write_int64out(int64outRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out; +} devI64outSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + write_int64out +}; +epicsExportAddress(dset, devI64outSoft); + +static long init_record(int64outRecord *prec) +{ + return 0; +} + +static long write_int64out(int64outRecord *prec) +{ + dbPutLink(&prec->out, DBR_INT64, &prec->val,1); + return 0; +} diff --git a/modules/database/src/std/dev/devI64outSoftCallback.c b/modules/database/src/std/dev/devI64outSoftCallback.c new file mode 100644 index 000000000..e8041a26f --- /dev/null +++ b/modules/database/src/std/dev/devI64outSoftCallback.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "int64outRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64outSoftCallback */ +static long write_int64out(int64outRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out; +} devI64outSoftCallback = { + 5, + NULL, + NULL, + NULL, + NULL, + write_int64out +}; +epicsExportAddress(dset, devI64outSoftCallback); + +static long write_int64out(int64outRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_INT64, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_INT64, &prec->val, 1); + + return status; +} diff --git a/modules/database/src/std/dev/devLiSoft.c b/modules/database/src/std/dev/devLiSoft.c new file mode 100644 index 000000000..6d7b7fda1 --- /dev/null +++ b/modules/database/src/std/dev/devLiSoft.c @@ -0,0 +1,78 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Janet Anderson + * Date: 09-23-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "longinRecord.h" +#include "epicsExport.h" + +/* Create the dset for devLiSoft */ +static long init_record(longinRecord *prec); +static long read_longin(longinRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_longin; +} devLiSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_longin +}; +epicsExportAddress(dset, devLiSoft); + +static long init_record(longinRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_LONG, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + longinRecord *prec = (longinRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_LONG, &prec->val, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_longin(longinRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devLiSoftCallback.c b/modules/database/src/std/dev/devLiSoftCallback.c new file mode 100644 index 000000000..caab523e5 --- /dev/null +++ b/modules/database/src/std/dev/devLiSoftCallback.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devLiSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "longinRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsInt32 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + longinRecord *prec = (longinRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devLiSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_LONG, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + longinRecord *prec = (longinRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + longinRecord *prec = (longinRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devLiSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devLiSoftCallback (init_record) linked record not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devLiSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + longinRecord *prec = (longinRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(longinRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_LONG, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_li(longinRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 0; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return pdevPvt->status; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 0; +} + +/* Create the dset for devLiSoftCallback */ +struct { + dset common; + DEVSUPFUN read_li; +} devLiSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_li +}; +epicsExportAddress(dset, devLiSoftCallback); diff --git a/modules/database/src/std/dev/devLoSoft.c b/modules/database/src/std/dev/devLoSoft.c new file mode 100644 index 000000000..af49c60ff --- /dev/null +++ b/modules/database/src/std/dev/devLoSoft.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devLoSoft.c */ +/* + * Author: Janet Anderson + * Date: 09-23-91 +*/ +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "longoutRecord.h" +#include "epicsExport.h" + +/* Create the dset for devLoSoft */ +static long init_record(longoutRecord *prec); +static long write_longout(longoutRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_longout; +}devLoSoft={ + 5, + NULL, + NULL, + init_record, + NULL, + write_longout +}; +epicsExportAddress(dset,devLoSoft); + +static long init_record(longoutRecord *prec) +{ + return 0; +} /* end init_record() */ + +static long write_longout(longoutRecord *prec) +{ + dbPutLink(&prec->out,DBR_LONG, &prec->val,1); + return 0; +} diff --git a/modules/database/src/std/dev/devLoSoftCallback.c b/modules/database/src/std/dev/devLoSoftCallback.c new file mode 100644 index 000000000..f211957b5 --- /dev/null +++ b/modules/database/src/std/dev/devLoSoftCallback.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devLoSoftCallback.c */ +/* + * Author: Marty Kraimer + * Date: 04NOV2003 + */ + + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "longoutRecord.h" +#include "epicsExport.h" + +/* Create the dset for devLoSoftCallback */ +static long write_longout(longoutRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_longout; +}devLoSoftCallback={ + 5, + NULL, + NULL, + NULL, + NULL, + write_longout +}; +epicsExportAddress(dset,devLoSoftCallback); + +static long write_longout(longoutRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_LONG, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_LONG, &prec->val, 1); + + return status; +} + diff --git a/modules/database/src/std/dev/devLsiSoft.c b/modules/database/src/std/dev/devLsiSoft.c new file mode 100644 index 000000000..3076c9900 --- /dev/null +++ b/modules/database/src/std/dev/devLsiSoft.c @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Long String Input soft device support + * + * Author: Andrew Johnson + * Date: 2012-11-28 + */ + +#include "dbAccess.h" +#include "epicsTime.h" +#include "link.h" +#include "lsiRecord.h" +#include "epicsExport.h" + +static long init_record(lsiRecord *prec) +{ + dbLoadLinkLS(&prec->inp, prec->val, prec->sizv, &prec->len); + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + lsiRecord *prec = (lsiRecord *) pinp->precord; + long status = dbGetLinkLS(pinp, prec->val, prec->sizv, &prec->len); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_string(lsiRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} + +lsidset devLsiSoft = { + 5, NULL, NULL, init_record, NULL, read_string +}; +epicsExportAddress(dset, devLsiSoft); diff --git a/modules/database/src/std/dev/devLsoSoft.c b/modules/database/src/std/dev/devLsoSoft.c new file mode 100644 index 000000000..02079a053 --- /dev/null +++ b/modules/database/src/std/dev/devLsoSoft.c @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Long String Output soft device support + * + * Author: Andrew Johnson + * Date: 2012-11-29 + */ + +#include "dbAccess.h" +#include "lsoRecord.h" +#include "epicsExport.h" + +static long write_string(lsoRecord *prec) +{ + return dbPutLinkLS(&prec->out, prec->val, prec->len); +} + +lsodset devLsoSoft = { + 5, NULL, NULL, NULL, NULL, write_string +}; +epicsExportAddress(dset, devLsoSoft); diff --git a/modules/database/src/std/dev/devLsoSoftCallback.c b/modules/database/src/std/dev/devLsoSoftCallback.c new file mode 100644 index 000000000..59579d558 --- /dev/null +++ b/modules/database/src/std/dev/devLsoSoftCallback.c @@ -0,0 +1,46 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Andrew Johnson + * Date: 30 Nov 2012 + */ + +#include "alarm.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "lsoRecord.h" +#include "epicsExport.h" + +static long write_string(lsoRecord *prec) +{ + struct link *plink = &prec->out; + int dtyp = dbGetLinkDBFtype(plink); + long len = prec->len; + long status; + + if (prec->pact || dtyp < 0) + return 0; + + if (dtyp != DBR_CHAR && dtyp != DBF_UCHAR) { + dtyp = DBR_STRING; + len = 1; + } + + status = dbPutLinkAsync(plink, dtyp, prec->val, len); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, dtyp, prec->val, len); + + return status; +} + +lsodset devLsoSoftCallback = { + 5, NULL, NULL, NULL, NULL, write_string +}; +epicsExportAddress(dset, devLsoSoftCallback); + diff --git a/modules/database/src/std/dev/devMbbiDirectSoft.c b/modules/database/src/std/dev/devMbbiDirectSoft.c new file mode 100644 index 000000000..9c929b2b6 --- /dev/null +++ b/modules/database/src/std/dev/devMbbiDirectSoft.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Matthew Needes + * Date: 10-08-93 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "mbbiDirectRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbbiDirectSoft */ +static long init_record(mbbiDirectRecord *prec); +static long read_mbbi(mbbiDirectRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi; +} devMbbiDirectSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_mbbi +}; +epicsExportAddress(dset, devMbbiDirectSoft); + +static long init_record(mbbiDirectRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_ENUM, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + mbbiDirectRecord *prec = (mbbiDirectRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_USHORT, &prec->val, 0, 0); + + if (status) return status; + + prec->udf = FALSE; + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return 2; +} + +static long read_mbbi(mbbiDirectRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devMbbiDirectSoftCallback.c b/modules/database/src/std/dev/devMbbiDirectSoftCallback.c new file mode 100644 index 000000000..93016329f --- /dev/null +++ b/modules/database/src/std/dev/devMbbiDirectSoftCallback.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbbiDirectSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "mbbiDirectRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsUInt16 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + mbbiDirectRecord *prec = (mbbiDirectRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devMbbiDirectSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_USHORT, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + mbbiDirectRecord *prec = (mbbiDirectRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + mbbiDirectRecord *prec = (mbbiDirectRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devMbbiDirectSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status,(void *)prec, + "devMbbiDirectSoftCallback (add_record) linked record not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devMbbiDirectSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + mbbiDirectRecord *prec = (mbbiDirectRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(mbbiDirectRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_ENUM, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_mbbiDirect(mbbiDirectRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 2; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return 2; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 2; +} + +/* Create the dset for devMbbiDirectSoftCallback */ +struct { + dset common; + DEVSUPFUN read_mbbiDirect; +} devMbbiDirectSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_mbbiDirect +}; +epicsExportAddress(dset, devMbbiDirectSoftCallback); diff --git a/modules/database/src/std/dev/devMbbiDirectSoftRaw.c b/modules/database/src/std/dev/devMbbiDirectSoftRaw.c new file mode 100644 index 000000000..f6172cdec --- /dev/null +++ b/modules/database/src/std/dev/devMbbiDirectSoftRaw.c @@ -0,0 +1,69 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Matthew Needes + * Date: 10-08-93 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "mbbiDirectRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbbiDirectSoftRaw */ +static long init_record(mbbiDirectRecord *prec); +static long read_mbbi(mbbiDirectRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi; +} devMbbiDirectSoftRaw = { + 5, + NULL, + NULL, + init_record, + NULL, + read_mbbi +}; +epicsExportAddress(dset, devMbbiDirectSoftRaw); + +static long init_record(mbbiDirectRecord *prec) +{ + recGblInitConstantLink(&prec->inp, DBF_ULONG, &prec->rval); + + /* Preserve old functionality */ + if (prec->nobt == 0) + prec->mask = 0xffffffff; + + prec->mask <<= prec->shft; + return 0; +} + +static long read_mbbi(mbbiDirectRecord *prec) +{ + if (!dbGetLink(&prec->inp, DBR_LONG, &prec->rval, 0, 0)) { + prec->rval &= prec->mask; + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(&prec->inp, &prec->time); + } + return 0; +} diff --git a/modules/database/src/std/dev/devMbbiSoft.c b/modules/database/src/std/dev/devMbbiSoft.c new file mode 100644 index 000000000..b0b57144f --- /dev/null +++ b/modules/database/src/std/dev/devMbbiSoft.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "mbbiRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbbiSoft */ +static long init_record(mbbiRecord *prec); +static long read_mbbi(mbbiRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi; +} devMbbiSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_mbbi +}; +epicsExportAddress(dset, devMbbiSoft); + +static long init_record(mbbiRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_ENUM, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + mbbiRecord *prec = (mbbiRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_USHORT, &prec->val, 0, 0); + + if (status) return status; + + prec->udf = FALSE; + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return 2; +} + +static long read_mbbi(mbbiRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + return status; +} diff --git a/modules/database/src/std/dev/devMbbiSoftCallback.c b/modules/database/src/std/dev/devMbbiSoftCallback.c new file mode 100644 index 000000000..3796bcee6 --- /dev/null +++ b/modules/database/src/std/dev/devMbbiSoftCallback.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbbiSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "mbbiRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsEnum16 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + mbbiRecord *prec = (mbbiRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devMbbiSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_ENUM, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + mbbiRecord *prec = (mbbiRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + mbbiRecord *prec = (mbbiRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devMbbiSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devMbbiSoftCallback (add_record) linked record not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devMbbiSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + mbbiRecord *prec = (mbbiRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(mbbiRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_ENUM, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_mbbi(mbbiRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 2; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return 2; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 2; +} + +/* Create the dset for devMbbiSoftCallback */ +struct { + dset common; + DEVSUPFUN read_mbbi; +} devMbbiSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_mbbi +}; +epicsExportAddress(dset,devMbbiSoftCallback); diff --git a/modules/database/src/std/dev/devMbbiSoftRaw.c b/modules/database/src/std/dev/devMbbiSoftRaw.c new file mode 100644 index 000000000..3bd6b21da --- /dev/null +++ b/modules/database/src/std/dev/devMbbiSoftRaw.c @@ -0,0 +1,85 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "mbbiRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbbiSoftRaw */ +static long init_record(mbbiRecord *prec); +static long read_mbbi(mbbiRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi; +} devMbbiSoftRaw = { + 5, + NULL, + NULL, + init_record, + NULL, + read_mbbi +}; +epicsExportAddress(dset, devMbbiSoftRaw); + +static long init_record(mbbiRecord *prec) +{ + recGblInitConstantLink(&prec->inp, DBF_ULONG, &prec->rval); + + /* Preserve old functionality*/ + if (prec->nobt == 0) + prec->mask = 0xffffffff; + + prec->mask <<= prec->shft; + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + mbbiRecord *prec = (mbbiRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_LONG, &prec->rval, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_mbbi(mbbiRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + if (!status) + prec->rval &= prec->mask; + + return status; +} diff --git a/modules/database/src/std/dev/devMbboDirectSoft.c b/modules/database/src/std/dev/devMbboDirectSoft.c new file mode 100644 index 000000000..0c0851980 --- /dev/null +++ b/modules/database/src/std/dev/devMbboDirectSoft.c @@ -0,0 +1,36 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbboDirectSoft.c */ +/* + * Original Author: Bob Dalesio + * Date: 10-08-93 + */ + +#include + +#include "dbAccess.h" +#include "devSup.h" +#include "mbboDirectRecord.h" +#include "epicsExport.h" + +static long write_mbbo(mbboDirectRecord *prec) +{ + dbPutLink(&prec->out, DBR_USHORT, &prec->val, 1); + return 0; +} + +/* Create the dset for devMbboDirectSoft */ +struct { + dset common; + DEVSUPFUN write; +} devMbboDirectSoft = { + {5, NULL, NULL, NULL, NULL}, + write_mbbo +}; +epicsExportAddress(dset, devMbboDirectSoft); diff --git a/modules/database/src/std/dev/devMbboDirectSoftCallback.c b/modules/database/src/std/dev/devMbboDirectSoftCallback.c new file mode 100644 index 000000000..e64c3d41c --- /dev/null +++ b/modules/database/src/std/dev/devMbboDirectSoftCallback.c @@ -0,0 +1,49 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbboDirectSoftCallback.c */ +/* + * Original Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include + +#include "alarm.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "mbboDirectRecord.h" +#include "epicsExport.h" + +static long write_mbbo(mbboDirectRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_USHORT, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_USHORT, &prec->val, 1); + + return status; +} + +/* Create the dset for devMbboSoft */ +struct { + dset common; + DEVSUPFUN write; +} devMbboDirectSoftCallback = { + {5, NULL, NULL, NULL, NULL}, + write_mbbo +}; +epicsExportAddress(dset, devMbboDirectSoftCallback); diff --git a/modules/database/src/std/dev/devMbboDirectSoftRaw.c b/modules/database/src/std/dev/devMbboDirectSoftRaw.c new file mode 100644 index 000000000..c3bbdc898 --- /dev/null +++ b/modules/database/src/std/dev/devMbboDirectSoftRaw.c @@ -0,0 +1,50 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbboDirectSoftRaw.c */ +/* + * Original Author: Janet Anderson + * Date: 10-08-93 + */ + +#include + +#include "dbAccess.h" +#include "epicsTypes.h" +#include "devSup.h" +#include "mbboDirectRecord.h" +#include "epicsExport.h" + +static long init_record(mbboDirectRecord *prec) +{ + if (prec->nobt == 0) + prec->mask = 0xffffffff; + + prec->mask <<= prec->shft; + + return 2; /* Don't convert */ +} + +static long write_mbbo(mbboDirectRecord *prec) +{ + epicsUInt32 data; + + data = prec->rval & prec->mask; + dbPutLink(&prec->out, DBR_ULONG, &data, 1); + return 0; +} + +/* Create the dset for devMbboDirectSoftRaw */ +struct { + dset common; + DEVSUPFUN write; +} devMbboDirectSoftRaw = { + {5, NULL, NULL, init_record, NULL}, + write_mbbo +}; +epicsExportAddress(dset, devMbboDirectSoftRaw); diff --git a/modules/database/src/std/dev/devMbboSoft.c b/modules/database/src/std/dev/devMbboSoft.c new file mode 100644 index 000000000..b2fe6b094 --- /dev/null +++ b/modules/database/src/std/dev/devMbboSoft.c @@ -0,0 +1,60 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbboSoft.c */ +/* + * Original Author: Bob Dalesio + * Current Author: Marty Kraimer + * Date: 6-1-90 + */ +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "mbboRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbboSoft */ +static long init_record(mbboRecord *prec); +static long write_mbbo(mbboRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_mbbo; +}devMbboSoft={ + 5, + NULL, + NULL, + init_record, + NULL, + write_mbbo +}; +epicsExportAddress(dset,devMbboSoft); + +static long init_record(mbboRecord *prec) +{ + /*dont convert*/ + return 2; + +} /* end init_record() */ + +static long write_mbbo(mbboRecord *prec) +{ + dbPutLink(&prec->out,DBR_USHORT, &prec->val,1); + return 0; +} diff --git a/modules/database/src/std/dev/devMbboSoftCallback.c b/modules/database/src/std/dev/devMbboSoftCallback.c new file mode 100644 index 000000000..fd5fe405a --- /dev/null +++ b/modules/database/src/std/dev/devMbboSoftCallback.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* devMbboSoftCallback.c */ +/* + * Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "mbboRecord.h" +#include "epicsExport.h" + +/* Create the dset for devMbboSoftCallback */ +static long write_mbbo(mbboRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_mbbo; +}devMbboSoftCallback={ + 5, + NULL, + NULL, + NULL, + NULL, + write_mbbo +}; +epicsExportAddress(dset,devMbboSoftCallback); + +static long write_mbbo(mbboRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_USHORT, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_USHORT, &prec->val, 1); + + return status; +} + diff --git a/modules/database/src/std/dev/devMbboSoftRaw.c b/modules/database/src/std/dev/devMbboSoftRaw.c new file mode 100644 index 000000000..092b6a8a7 --- /dev/null +++ b/modules/database/src/std/dev/devMbboSoftRaw.c @@ -0,0 +1,50 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devMbboSoftRaw.c */ +/* + * Original Author: Janet Anderson + * Date: 3-28-92 + */ + +#include + +#include "dbAccess.h" +#include "epicsTypes.h" +#include "devSup.h" +#include "mbboRecord.h" +#include "epicsExport.h" + +static long init_record(mbboRecord *prec) +{ + if (prec->nobt == 0) + prec->mask = 0xffffffff; + + prec->mask <<= prec->shft; + + return 2; /* Don't convert */ +} + +static long write_mbbo(mbboRecord *prec) +{ + epicsUInt32 data; + + data = prec->rval & prec->mask; + dbPutLink(&prec->out, DBR_ULONG, &data, 1); + return 0; +} + +/* Create the dset for devMbboSoftRaw */ +struct { + dset common; + DEVSUPFUN write; +} devMbboSoftRaw = { + {5, NULL, NULL, init_record, NULL}, + write_mbbo +}; +epicsExportAddress(dset, devMbboSoftRaw); diff --git a/modules/database/src/std/dev/devPrintfSoft.c b/modules/database/src/std/dev/devPrintfSoft.c new file mode 100644 index 000000000..ca06f04be --- /dev/null +++ b/modules/database/src/std/dev/devPrintfSoft.c @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Andrew Johnson + * Date: 28 Sept 2012 + */ + +#include "dbAccess.h" +#include "printfRecord.h" +#include "epicsExport.h" + +static long write_string(printfRecord *prec) +{ + return dbPutLinkLS(&prec->out, prec->val, prec->len); +} + +printfdset devPrintfSoft = { + 5, NULL, NULL, NULL, NULL, write_string +}; +epicsExportAddress(dset, devPrintfSoft); + diff --git a/modules/database/src/std/dev/devPrintfSoftCallback.c b/modules/database/src/std/dev/devPrintfSoftCallback.c new file mode 100644 index 000000000..e89afd53b --- /dev/null +++ b/modules/database/src/std/dev/devPrintfSoftCallback.c @@ -0,0 +1,45 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Andrew Johnson + * Date: 28 Sept 2012 + */ + +#include "alarm.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "printfRecord.h" +#include "epicsExport.h" + +static long write_string(printfRecord *prec) +{ + struct link *plink = &prec->out; + int dtyp = dbGetLinkDBFtype(plink); + long len = prec->len; + long status; + + if (prec->pact || dtyp < 0) + return 0; + + if (dtyp != DBR_CHAR && dtyp != DBF_UCHAR) { + dtyp = DBR_STRING; + len = 1; + } + + status = dbPutLinkAsync(plink, dtyp, prec->val, len); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, dtyp, prec->val, len); + + return status; +} + +printfdset devPrintfSoftCallback = { + 5, NULL, NULL, NULL, NULL, write_string +}; +epicsExportAddress(dset, devPrintfSoftCallback); diff --git a/modules/database/src/std/dev/devSASoft.c b/modules/database/src/std/dev/devSASoft.c new file mode 100644 index 000000000..01a076e6d --- /dev/null +++ b/modules/database/src/std/dev/devSASoft.c @@ -0,0 +1,130 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 Lawrence Berkeley Laboratory,The Control Systems +* Group, Systems Engineering Department +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Carl Lionberger + * Date: 9-2-93 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "subArrayRecord.h" +#include "epicsExport.h" + +/* Create the dset for devSASoft */ +static long init_record(subArrayRecord *prec); +static long read_sa(subArrayRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_sa; +} devSASoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_sa +}; +epicsExportAddress(dset, devSASoft); + +static void subset(subArrayRecord *prec, long nRequest) +{ + long ecount = nRequest - prec->indx; + + if (ecount > 0) { + int esize = dbValueSize(prec->ftvl); + + if (ecount > prec->nelm) + ecount = prec->nelm; + + memmove(prec->bptr, (char *)prec->bptr + prec->indx * esize, + ecount * esize); + } else + ecount = 0; + + prec->nord = ecount; + prec->udf = FALSE; +} + +static long init_record(subArrayRecord *prec) +{ + long nRequest = prec->indx + prec->nelm; + long status; + + if (nRequest > prec->malm) + nRequest = prec->malm; + + status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nRequest); + + if (!status && nRequest > 0) + subset(prec, nRequest); + + return status; +} + +struct sart { + long nRequest; + epicsTimeStamp *ptime; +}; + +static long readLocked(struct link *pinp, void *vrt) +{ + subArrayRecord *prec = (subArrayRecord *) pinp->precord; + struct sart *prt = (struct sart *) vrt; + long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &prt->nRequest); + + if (!status && prt->ptime) + dbGetTimeStamp(pinp, prt->ptime); + + return status; +} + +static long read_sa(subArrayRecord *prec) +{ + long status; + struct sart rt; + + rt.nRequest = prec->indx + prec->nelm; + if (rt.nRequest > prec->malm) + rt.nRequest = prec->malm; + + rt.ptime = (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL; + + if (dbLinkIsConstant(&prec->inp)) { + status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &rt.nRequest); + if (status == S_db_badField) { /* INP was empty */ + rt.nRequest = prec->nord; + status = 0; + } + } + else { + status = dbLinkDoLocked(&prec->inp, readLocked, &rt); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &rt); + } + + if (!status && rt.nRequest > 0) + subset(prec, rt.nRequest); + + return status; +} diff --git a/modules/database/src/std/dev/devSiSoft.c b/modules/database/src/std/dev/devSiSoft.c new file mode 100644 index 000000000..5141c1038 --- /dev/null +++ b/modules/database/src/std/dev/devSiSoft.c @@ -0,0 +1,83 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Janet Anderson + * Date: 04-21-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "epicsTime.h" +#include "recGbl.h" +#include "devSup.h" +#include "link.h" +#include "stringinRecord.h" +#include "epicsExport.h" + +/* Create the dset for devSiSoft */ +static long init_record(stringinRecord *prec); +static long read_stringin(stringinRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_stringin; +} devSiSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_stringin +}; +epicsExportAddress(dset, devSiSoft); + +static long init_record(stringinRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBF_STRING, prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long readLocked(struct link *pinp, void *dummy) +{ + stringinRecord *prec = (stringinRecord *) pinp->precord; + long status = dbGetLink(pinp, DBR_STRING, prec->val, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +static long read_stringin(stringinRecord *prec) +{ + long status = dbLinkDoLocked(&prec->inp, readLocked, NULL); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, NULL); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; +} diff --git a/modules/database/src/std/dev/devSiSoftCallback.c b/modules/database/src/std/dev/devSiSoftCallback.c new file mode 100644 index 000000000..8f679889e --- /dev/null +++ b/modules/database/src/std/dev/devSiSoftCallback.c @@ -0,0 +1,217 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devSiSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "stringinRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + DBADDR dbaddr; + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + char value[MAX_STRING_SIZE]; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + stringinRecord *prec = (stringinRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status==notifyCanceled) { + printf("devSiSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_STRING, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + stringinRecord *prec = (stringinRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + stringinRecord *prec = (stringinRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devSiSoftCallback (add_record) Illegal INP field"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devSiSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devSiSoftCallback (add_record) linked record not found"); + return status; + } + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + stringinRecord *prec = (stringinRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (dbLinkIsDefined(plink) && dbLinkIsConstant(plink)) + return 0; + + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(stringinRecord *prec) +{ + if (recGblInitConstantLink(&prec->inp, DBR_STRING, &prec->val)) + prec->udf = FALSE; + + return 0; +} + +static long read_si(stringinRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 0; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return pdevPvt->status; + } + + strncpy(prec->val, pdevPvt->buffer.value, MAX_STRING_SIZE); + prec->val[MAX_STRING_SIZE-1] = 0; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + + return 0; +} + +/* Create the dset for devSiSoftCallback */ +struct { + dset common; + DEVSUPFUN read_li; +} devSiSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_si +}; +epicsExportAddress(dset,devSiSoftCallback); diff --git a/modules/database/src/std/dev/devSoSoft.c b/modules/database/src/std/dev/devSoSoft.c new file mode 100644 index 000000000..6dda4a765 --- /dev/null +++ b/modules/database/src/std/dev/devSoSoft.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * Author: Janet Anderson + * Date: 21APR1991 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "stringoutRecord.h" +#include "epicsExport.h" + +/* Create the dset for devSoSoft */ +static long write_stringout(stringoutRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_stringout; +} devSoSoft = { + 5, + NULL, + NULL, + NULL, + NULL, + write_stringout +}; +epicsExportAddress(dset, devSoSoft); + +static long write_stringout(stringoutRecord *prec) +{ + long status; + + status = dbPutLink(&prec->out, DBR_STRING, prec->val, 1); + return status; +} diff --git a/modules/database/src/std/dev/devSoSoftCallback.c b/modules/database/src/std/dev/devSoSoftCallback.c new file mode 100644 index 000000000..df8c5d819 --- /dev/null +++ b/modules/database/src/std/dev/devSoSoftCallback.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (c) 2002 The University of Chicago, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "stringoutRecord.h" +#include "epicsExport.h" + +/* Create the dset for devSoSoftCallback */ +static long write_stringout(stringoutRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_stringout; +} devSoSoftCallback = { + 5, + NULL, + NULL, + NULL, + NULL, + write_stringout +}; +epicsExportAddress(dset, devSoSoftCallback); + +static long write_stringout(stringoutRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + status = dbPutLinkAsync(plink, DBR_STRING, &prec->val, 1); + if (!status) + prec->pact = TRUE; + else if (status == S_db_noLSET) + status = dbPutLink(plink, DBR_STRING, &prec->val, 1); + + return status; +} + diff --git a/modules/database/src/std/dev/devSoft.dbd b/modules/database/src/std/dev/devSoft.dbd new file mode 100644 index 000000000..2d7a64be3 --- /dev/null +++ b/modules/database/src/std/dev/devSoft.dbd @@ -0,0 +1,71 @@ +# devSoft.dbd + +device(aai,CONSTANT,devAaiSoft,"Soft Channel") +device(aao,CONSTANT,devAaoSoft,"Soft Channel") +device(ai,CONSTANT,devAiSoft,"Soft Channel") +device(ao,CONSTANT,devAoSoft,"Soft Channel") +device(bi,CONSTANT,devBiSoft,"Soft Channel") +device(bo,CONSTANT,devBoSoft,"Soft Channel") +device(calcout,CONSTANT,devCalcoutSoft,"Soft Channel") +device(event,CONSTANT,devEventSoft,"Soft Channel") +device(histogram,CONSTANT,devHistogramSoft,"Soft Channel") +device(int64in,CONSTANT,devI64inSoft,"Soft Channel") +device(int64out,CONSTANT,devI64outSoft,"Soft Channel") +device(longin,CONSTANT,devLiSoft,"Soft Channel") +device(longout,CONSTANT,devLoSoft,"Soft Channel") +device(lsi,CONSTANT,devLsiSoft,"Soft Channel") +device(lso,CONSTANT,devLsoSoft,"Soft Channel") +device(mbbi,CONSTANT,devMbbiSoft,"Soft Channel") +device(mbbiDirect,CONSTANT,devMbbiDirectSoft,"Soft Channel") +device(mbbo,CONSTANT,devMbboSoft,"Soft Channel") +device(mbboDirect,CONSTANT,devMbboDirectSoft,"Soft Channel") +device(printf,CONSTANT,devPrintfSoft,"Soft Channel") +device(stringin,CONSTANT,devSiSoft,"Soft Channel") +device(stringout,CONSTANT,devSoSoft,"Soft Channel") +device(subArray,CONSTANT,devSASoft,"Soft Channel") +device(waveform,CONSTANT,devWfSoft,"Soft Channel") + +device(ai,CONSTANT,devAiSoftRaw,"Raw Soft Channel") +device(ao,CONSTANT,devAoSoftRaw,"Raw Soft Channel") +device(bi,CONSTANT,devBiSoftRaw,"Raw Soft Channel") +device(bo,CONSTANT,devBoSoftRaw,"Raw Soft Channel") +device(mbbi,CONSTANT,devMbbiSoftRaw,"Raw Soft Channel") +device(mbbiDirect,CONSTANT,devMbbiDirectSoftRaw,"Raw Soft Channel") +device(mbbo,CONSTANT,devMbboSoftRaw,"Raw Soft Channel") +device(mbboDirect,CONSTANT,devMbboDirectSoftRaw,"Raw Soft Channel") + +device(ai,CONSTANT,devAiSoftCallback,"Async Soft Channel") +device(ao,CONSTANT,devAoSoftCallback,"Async Soft Channel") +device(bi,CONSTANT,devBiSoftCallback,"Async Soft Channel") +device(bo,CONSTANT,devBoSoftCallback,"Async Soft Channel") +device(calcout,CONSTANT,devCalcoutSoftCallback,"Async Soft Channel") +device(int64in,CONSTANT,devI64inSoftCallback,"Async Soft Channel") +device(int64out,CONSTANT,devI64outSoftCallback,"Async Soft Channel") +device(longin,CONSTANT,devLiSoftCallback,"Async Soft Channel") +device(longout,CONSTANT,devLoSoftCallback,"Async Soft Channel") +device(lso,CONSTANT,devLsoSoftCallback,"Async Soft Channel") +device(mbbi,CONSTANT,devMbbiSoftCallback,"Async Soft Channel") +device(mbbiDirect,CONSTANT,devMbbiDirectSoftCallback,"Async Soft Channel") +device(mbbo,CONSTANT,devMbboSoftCallback,"Async Soft Channel") +device(mbboDirect,CONSTANT,devMbboDirectSoftCallback,"Async Soft Channel") +device(printf,CONSTANT,devPrintfSoftCallback,"Async Soft Channel") +device(stringin,CONSTANT,devSiSoftCallback,"Async Soft Channel") +device(stringout,CONSTANT,devSoSoftCallback,"Async Soft Channel") + +device(ai, INST_IO,devTimestampAI,"Soft Timestamp") +device(stringin,INST_IO,devTimestampSI,"Soft Timestamp") + +device(ai, INST_IO,devAiGeneralTime,"General Time") +device(bo, INST_IO,devBoGeneralTime,"General Time") +device(longin, INST_IO,devLiGeneralTime,"General Time") +device(stringin,INST_IO,devSiGeneralTime,"General Time") + +device(lso,INST_IO,devLsoStdio,"stdio") +device(printf,INST_IO,devPrintfStdio,"stdio") +device(stringout,INST_IO,devSoStdio,"stdio") + +device(lsi,INST_IO,devLsiEnviron,"getenv") +device(stringin,INST_IO,devSiEnviron,"getenv") + +device(bi, INST_IO, devBiDbState, "Db State") +device(bo, INST_IO, devBoDbState, "Db State") diff --git a/modules/database/src/std/dev/devStdio.c b/modules/database/src/std/dev/devStdio.c new file mode 100644 index 000000000..e957bfce0 --- /dev/null +++ b/modules/database/src/std/dev/devStdio.c @@ -0,0 +1,211 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include "dbCommon.h" +#include "devSup.h" +#include "errlog.h" +#include "recGbl.h" +#include "recSup.h" + +#include "lsoRecord.h" +#include "printfRecord.h" +#include "stringoutRecord.h" +#include "epicsExport.h" + +typedef int (*PRINTFFUNC)(const char *fmt, ...); + +static int stderrPrintf(const char *fmt, ...); +static int logPrintf(const char *fmt, ...); + + +static struct outStream { + const char *name; + PRINTFFUNC print; +} outStreams[] = { + {"stdout", printf}, + {"stderr", stderrPrintf}, + {"errlog", logPrintf}, + {NULL, NULL} +}; + +static int stderrPrintf(const char *fmt, ...) { + va_list pvar; + int retval; + + va_start(pvar, fmt); + retval = vfprintf(stderr, fmt, pvar); + va_end (pvar); + + return retval; +} + +static int logPrintf(const char *fmt, ...) { + va_list pvar; + int retval; + + va_start(pvar, fmt); + retval = errlogVprintf(fmt, pvar); + va_end (pvar); + + return retval; +} + + +/* lso device support */ + +static long add_lso(dbCommon *pcommon) { + lsoRecord *prec = (lsoRecord *) pcommon; + struct outStream *pstream; + + if (prec->out.type != INST_IO) + return S_dev_badOutType; + + for (pstream = outStreams; pstream->name; ++pstream) { + if (strcmp(prec->out.value.instio.string, pstream->name) == 0) { + prec->dpvt = pstream; + return 0; + } + } + prec->dpvt = NULL; + return -1; +} + +static long del_lso(dbCommon *pcommon) { + lsoRecord *prec = (lsoRecord *) pcommon; + + prec->dpvt = NULL; + return 0; +} + +static struct dsxt dsxtLsoStdio = { + add_lso, del_lso +}; + +static long init_lso(int pass) +{ + if (pass == 0) devExtend(&dsxtLsoStdio); + return 0; +} + +static long write_lso(lsoRecord *prec) +{ + struct outStream *pstream = (struct outStream *)prec->dpvt; + if (pstream) + pstream->print("%s\n", prec->val); + return 0; +} + +lsodset devLsoStdio = { + 5, NULL, init_lso, NULL, NULL, write_lso +}; +epicsExportAddress(dset, devLsoStdio); + + +/* printf device support */ + +static long add_printf(dbCommon *pcommon) { + printfRecord *prec = (printfRecord *) pcommon; + struct outStream *pstream; + + if (prec->out.type != INST_IO) + return S_dev_badOutType; + + for (pstream = outStreams; pstream->name; ++pstream) { + if (strcmp(prec->out.value.instio.string, pstream->name) == 0) { + prec->dpvt = pstream; + return 0; + } + } + prec->dpvt = NULL; + return -1; +} + +static long del_printf(dbCommon *pcommon) { + printfRecord *prec = (printfRecord *) pcommon; + + prec->dpvt = NULL; + return 0; +} + +static struct dsxt dsxtPrintfStdio = { + add_printf, del_printf +}; + +static long init_printf(int pass) +{ + if (pass == 0) devExtend(&dsxtPrintfStdio); + return 0; +} + +static long write_printf(printfRecord *prec) +{ + struct outStream *pstream = (struct outStream *)prec->dpvt; + if (pstream) + pstream->print("%s\n", prec->val); + return 0; +} + +printfdset devPrintfStdio = { + 5, NULL, init_printf, NULL, NULL, write_printf +}; +epicsExportAddress(dset, devPrintfStdio); + + +/* stringout device support */ + +static long add_stringout(dbCommon *pcommon) { + stringoutRecord *prec = (stringoutRecord *) pcommon; + struct outStream *pstream; + + if (prec->out.type != INST_IO) + return S_dev_badOutType; + + for (pstream = outStreams; pstream->name; ++pstream) { + if (strcmp(prec->out.value.instio.string, pstream->name) == 0) { + prec->dpvt = pstream; + return 0; + } + } + prec->dpvt = NULL; + return -1; +} + +static long del_stringout(dbCommon *pcommon) { + stringoutRecord *prec = (stringoutRecord *) pcommon; + + prec->dpvt = NULL; + return 0; +} + +static struct dsxt dsxtSoStdio = { + add_stringout, del_stringout +}; + +static long init_stringout(int pass) +{ + if (pass == 0) devExtend(&dsxtSoStdio); + return 0; +} + +static long write_stringout(stringoutRecord *prec) +{ + struct outStream *pstream = (struct outStream *)prec->dpvt; + if (pstream) + pstream->print("%s\n", prec->val); + return 0; +} + +static struct { + dset common; + DEVSUPFUN write; +} devSoStdio = { + {5, NULL, init_stringout, NULL, NULL}, write_stringout +}; +epicsExportAddress(dset, devSoStdio); diff --git a/modules/database/src/std/dev/devTimestamp.c b/modules/database/src/std/dev/devTimestamp.c new file mode 100644 index 000000000..936d7767d --- /dev/null +++ b/modules/database/src/std/dev/devTimestamp.c @@ -0,0 +1,77 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Device support for EPICS time stamps + * + * Original Author: Eric Norum + */ + +#include "dbDefs.h" +#include "epicsTime.h" +#include "alarm.h" +#include "devSup.h" +#include "recGbl.h" + +#include "aiRecord.h" +#include "stringinRecord.h" +#include "epicsExport.h" + + +/* Extended device support to allow INP field changes */ + +static long initAllow(int pass) { + if (pass == 0) devExtend(&devSoft_DSXT); + return 0; +} + + +/* ai record */ + +static long read_ai(aiRecord *prec) +{ + recGblGetTimeStamp(prec); + prec->val = prec->time.secPastEpoch + (double)prec->time.nsec * 1e-9; + prec->udf = FALSE; + return 2; +} + +struct { + dset common; + DEVSUPFUN read_write; + DEVSUPFUN special_linconv; +} devTimestampAI = { + {6, NULL, initAllow, NULL, NULL}, read_ai, NULL +}; +epicsExportAddress(dset, devTimestampAI); + + +/* stringin record */ + +static long read_stringin (stringinRecord *prec) +{ + int len; + + recGblGetTimeStamp(prec); + len = epicsTimeToStrftime(prec->val, sizeof prec->val, + prec->inp.value.instio.string, &prec->time); + if (len >= sizeof prec->val) { + prec->udf = TRUE; + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return -1; + } + prec->udf = FALSE; + return 0; +} + +struct { + dset common; + DEVSUPFUN read_stringin; +} devTimestampSI = { + {5, NULL, initAllow, NULL, NULL}, read_stringin +}; +epicsExportAddress(dset, devTimestampSI); diff --git a/modules/database/src/std/dev/devWfSoft.c b/modules/database/src/std/dev/devWfSoft.c new file mode 100644 index 000000000..8d8295696 --- /dev/null +++ b/modules/database/src/std/dev/devWfSoft.c @@ -0,0 +1,99 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Authors: Bob Dalesio and Marty Kraimer + * Date: 6-1-90 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "recGbl.h" +#include "devSup.h" +#include "waveformRecord.h" +#include "epicsExport.h" + +/* Create the dset for devWfSoft */ +static long init_record(waveformRecord *prec); +static long read_wf(waveformRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_wf; +} devWfSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_wf +}; +epicsExportAddress(dset, devWfSoft); + +static long init_record(waveformRecord *prec) +{ + long nelm = prec->nelm; + long status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nelm); + + if (!status && nelm > 0) { + prec->nord = nelm; + prec->udf = FALSE; + } + else + prec->nord = 0; + return status; +} + +struct wfrt { + long nRequest; + epicsTimeStamp *ptime; +}; + +static long readLocked(struct link *pinp, void *vrt) +{ + waveformRecord *prec = (waveformRecord *) pinp->precord; + struct wfrt *prt = (struct wfrt *) vrt; + long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &prt->nRequest); + + if (!status && prt->ptime) + dbGetTimeStamp(pinp, prt->ptime); + + return status; +} + +static long read_wf(waveformRecord *prec) +{ + long status; + struct wfrt rt; + + rt.nRequest = prec->nelm; + rt.ptime = (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL; + + status = dbLinkDoLocked(&prec->inp, readLocked, &rt); + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &rt); + + if (!status && rt.nRequest > 0) { + prec->nord = rt.nRequest; + prec->udf = FALSE; + } + + return status; +} diff --git a/modules/database/src/std/dev/softDevIoc.rc b/modules/database/src/std/dev/softDevIoc.rc new file mode 100644 index 000000000..7e789bb2e --- /dev/null +++ b/modules/database/src/std/dev/softDevIoc.rc @@ -0,0 +1,36 @@ +#include +#include "epicsVersion.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + PRODUCTVERSION EPICS_VERSION,EPICS_REVISION,EPICS_MODIFICATION,EPICS_PATCH_LEVEL + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_UNKNOWN + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments","Soft Device Support Library for EPICS\0" + VALUE "CompanyName", "The EPICS collaboration\0" + VALUE "FileDescription", "Soft Device Support Library\0" + VALUE "FileVersion", EPICS_VERSION_STRING "\0" + VALUE "InternalName", "softDevIoc\0" + VALUE "LegalCopyright", "Copyright (C) Univ. of California, Univ. of Chicago\0" + VALUE "OriginalFilename", "softDevIoc.dll\0" + VALUE "ProductName", "Experimental Physics and Industrial Control System (EPICS)\0" + VALUE "ProductVersion", EPICS_VERSION_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/modules/database/src/std/filters/Makefile b/modules/database/src/std/filters/Makefile new file mode 100644 index 000000000..6b4dc7917 --- /dev/null +++ b/modules/database/src/std/filters/Makefile @@ -0,0 +1,20 @@ +#************************************************************************* +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/std/Makefile. + +SRC_DIRS += $(STDDIR)/filters + +DBD += filters.dbd + +dbRecStd_SRCS += ts.c +dbRecStd_SRCS += dbnd.c +dbRecStd_SRCS += arr.c +dbRecStd_SRCS += sync.c + +HTMLS += filters.html + diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c new file mode 100644 index 000000000..0bd73e295 --- /dev/null +++ b/modules/database/src/std/filters/arr.c @@ -0,0 +1,232 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct myStruct { + epicsInt32 start; + epicsInt32 incr; + epicsInt32 end; + void *arrayFreeList; + long no_elements; +} myStruct; + +static void *myStructFreeList; + +static const chfPluginArgDef opts[] = { + chfInt32 (myStruct, start, "s", 0, 1), + chfInt32 (myStruct, incr, "i", 0, 1), + chfInt32 (myStruct, end, "e", 0, 1), + chfPluginArgEnd +}; + +static void * allocPvt(void) +{ + myStruct *my = (myStruct*) freeListCalloc(myStructFreeList); + if (!my) return NULL; + + my->incr = 1; + my->end = -1; + return (void *) my; +} + +static void freePvt(void *pvt) +{ + myStruct *my = (myStruct*) pvt; + + if (my->arrayFreeList) freeListCleanup(my->arrayFreeList); + freeListFree(myStructFreeList, pvt); +} + +static int parse_ok(void *pvt) +{ + myStruct *my = (myStruct*) pvt; + + if (my->incr <= 0) my->incr = 1; + return 0; +} + +static void freeArray(db_field_log *pfl) +{ + if (pfl->type == dbfl_type_ref) { + freeListFree(pfl->u.r.pvt, pfl->u.r.field); + } +} + +static long wrapArrayIndices(long *start, const long increment, long *end, + const long no_elements) +{ + long len = 0; + + if (*start < 0) *start = no_elements + *start; + if (*start < 0) *start = 0; + if (*start > no_elements) *start = no_elements; + + if (*end < 0) *end = no_elements + *end; + if (*end < 0) *end = 0; + if (*end >= no_elements) *end = no_elements - 1; + + if (*end - *start >= 0) len = 1 + (*end - *start) / increment; + return len; +} + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) +{ + myStruct *my = (myStruct*) pvt; + struct dbCommon *prec; + rset *prset; + long start = my->start; + long end = my->end; + long nTarget = 0; + long offset = 0; + long nSource = chan->addr.no_elements; + long capacity = nSource; + void *pdst; + + switch (pfl->type) { + case dbfl_type_val: + /* Only filter arrays */ + break; + + case dbfl_type_rec: + /* Extract from record */ + if (chan->addr.special == SPC_DBADDR && + nSource > 1 && + (prset = dbGetRset(&chan->addr)) && + prset->get_array_info) + { + void *pfieldsave = chan->addr.pfield; + prec = dbChannelRecord(chan); + dbScanLock(prec); + prset->get_array_info(&chan->addr, &nSource, &offset); + nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); + pfl->type = dbfl_type_ref; + pfl->stat = prec->stat; + pfl->sevr = prec->sevr; + pfl->time = prec->time; + pfl->field_type = chan->addr.field_type; + pfl->field_size = chan->addr.field_size; + pfl->no_elements = nTarget; + if (nTarget) { + pdst = freeListCalloc(my->arrayFreeList); + if (pdst) { + pfl->u.r.dtor = freeArray; + pfl->u.r.pvt = my->arrayFreeList; + offset = (offset + start) % chan->addr.no_elements; + dbExtractArrayFromRec(&chan->addr, pdst, nTarget, capacity, + offset, my->incr); + pfl->u.r.field = pdst; + } + } + dbScanUnlock(prec); + chan->addr.pfield = pfieldsave; + } + break; + + /* Extract from buffer */ + case dbfl_type_ref: + pdst = NULL; + nSource = pfl->no_elements; + nTarget = wrapArrayIndices(&start, my->incr, &end, nSource); + pfl->no_elements = nTarget; + if (nTarget) { + /* Copy the data out */ + void *psrc = pfl->u.r.field; + + pdst = freeListCalloc(my->arrayFreeList); + if (!pdst) break; + offset = start; + dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type, + nTarget, nSource, offset, my->incr); + } + if (pfl->u.r.dtor) pfl->u.r.dtor(pfl); + if (nTarget) { + pfl->u.r.dtor = freeArray; + pfl->u.r.pvt = my->arrayFreeList; + pfl->u.r.field = pdst; + } + break; + } + return pfl; +} + +static void channelRegisterPost(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + myStruct *my = (myStruct*) pvt; + long start = my->start; + long end = my->end; + long max = 0; + + if (probe->no_elements <= 1) return; /* array data only */ + + max = wrapArrayIndices(&start, my->incr, &end, probe->no_elements); + if (max) { + if (!my->arrayFreeList) + freeListInitPvt(&my->arrayFreeList, max * probe->field_size, 2); + if (!my->arrayFreeList) return; + } + probe->no_elements = my->no_elements = max; + *cb_out = filter; + *arg_out = pvt; +} + +static void channel_report(dbChannel *chan, void *pvt, int level, + const unsigned short indent) +{ + myStruct *my = (myStruct*) pvt; + printf("%*sArray (arr): start=%d, incr=%d, end=%d\n", indent, "", + my->start, my->incr, my->end); +} + +static chfPluginIf pif = { + allocPvt, + freePvt, + + NULL, /* parse_error, */ + parse_ok, + + NULL, /* channel_open, */ + NULL, /* channelRegisterPre, */ + channelRegisterPost, + channel_report, + NULL /* channel_close */ +}; + +static void arrShutdown(void* ignore) +{ + if(myStructFreeList) + freeListCleanup(myStructFreeList); + myStructFreeList = NULL; +} + +static void arrInitialize(void) +{ + if (!myStructFreeList) + freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64); + + chfPluginRegister("arr", &pif, opts); + epicsAtExit(arrShutdown, NULL); +} + +epicsExportRegistrar(arrInitialize); diff --git a/modules/database/src/std/filters/dbnd.c b/modules/database/src/std/filters/dbnd.c new file mode 100644 index 000000000..e96c6b652 --- /dev/null +++ b/modules/database/src/std/filters/dbnd.c @@ -0,0 +1,141 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct myStruct { + int mode; + double cval; + double hyst; + double last; +} myStruct; + +static void *myStructFreeList; + +static const +chfPluginEnumType modeEnum[] = { {"abs", 0}, {"rel", 1}, {NULL,0} }; + +static const +chfPluginArgDef opts[] = { + chfDouble (myStruct, cval, "d", 0, 1), + chfEnum (myStruct, mode, "m", 0, 1, modeEnum), + chfTagDouble (myStruct, cval, "abs", mode, 0, 0, 1), + chfTagDouble (myStruct, cval, "rel", mode, 1, 0, 1), + chfPluginArgEnd +}; + +static void * allocPvt(void) +{ + return freeListCalloc(myStructFreeList); +} + +static void freePvt(void *pvt) +{ + freeListFree(myStructFreeList, pvt); +} + +static int parse_ok(void *pvt) +{ + myStruct *my = (myStruct*) pvt; + my->hyst = my->cval; + my->last = epicsNAN; + return 0; +} + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { + myStruct *my = (myStruct*) pvt; + long status; + double val; + unsigned send = 1; + + /* + * Only scalar values supported - strings, arrays, and conversion errors + * are just passed on + */ + if (pfl->type == dbfl_type_val) { + DBADDR localAddr = chan->addr; /* Structure copy */ + localAddr.field_type = pfl->field_type; + localAddr.field_size = pfl->field_size; + localAddr.no_elements = pfl->no_elements; + localAddr.pfield = (char *) &pfl->u.v.field; + status = dbFastGetConvertRoutine[pfl->field_type][DBR_DOUBLE] + (localAddr.pfield, (void*) &val, &localAddr); + if (!status) { + send = 0; + recGblCheckDeadband(&my->last, val, my->hyst, &send, 1); + if (send && my->mode == 1) { + my->hyst = val * my->cval/100.; + } + } + } + if (!send) { + db_delete_field_log(pfl); + return NULL; + } else return pfl; +} + +static void channelRegisterPre(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + *cb_out = filter; + *arg_out = pvt; +} + +static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent) +{ + myStruct *my = (myStruct*) pvt; + printf("%*sDeadband (dbnd): mode=%s, delta=%g%s\n", indent, "", + chfPluginEnumString(modeEnum, my->mode, "n/a"), my->cval, + my->mode == 1 ? "%" : ""); +} + +static chfPluginIf pif = { + allocPvt, + freePvt, + + NULL, /* parse_error, */ + parse_ok, + + NULL, /* channel_open, */ + channelRegisterPre, + NULL, /* channelRegisterPost, */ + channel_report, + NULL /* channel_close */ +}; + +static void dbndShutdown(void* ignore) +{ + if(myStructFreeList) + freeListCleanup(myStructFreeList); + myStructFreeList = NULL; +} + +static void dbndInitialize(void) +{ + if (!myStructFreeList) + freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64); + + chfPluginRegister("dbnd", &pif, opts); + epicsAtExit(dbndShutdown, NULL); +} + +epicsExportRegistrar(dbndInitialize); diff --git a/modules/database/src/std/filters/filters.dbd.pod b/modules/database/src/std/filters/filters.dbd.pod new file mode 100644 index 000000000..db97907a5 --- /dev/null +++ b/modules/database/src/std/filters/filters.dbd.pod @@ -0,0 +1,247 @@ +=head1 Channel Filters + +Channel Filters can be applied to Channel Access channels by a client, using +a JSON Field Modifier to select the filter and any parameters. +The following filters are available in this release: + +=over + +=item * L + +=item * L + +=item * L + +=item * L + +=back + +=head2 Using Filters + +Channel filters can be added to any Channel Access channel name. +There can be more than one filter applied to the same channel, in which case the +order that they are specified will control the order in which they are applied +to the resulting data-stream. +The filter specification must appear after the field name, or if the default +(VAL) field is used after a dot C<.> appended to the record name. +With the exception of the array short-hand which is described below, all filters +must appear inside a pair of braces C< {} > after the dot expressed as a JSON +(L) object, which allows filter +parameters to be included as needed. + +Each filter is given as a name/value pair. The filter name (given in parentheses +in the titles below) is a string, and must be enclosed inside double-quotes C<"> +characters as per the JSON specification. +Parameters to that filter are provided as the value part of the name/value pair, +and will normally appear as a child JSON object consisting of name/value pairs +inside a nested pair of braces C< {} >. + +=head4 Example Filter + +Given a record called C the following would apply a filter C to +the VAL field of that record, giving the filter two numeric parameters named +C and C: + + test:channel.{"f":{"lo":0,"hi":10}} + +Note that due to the required presence of the double-quote characters in the +JSON strings in the name string, it will usually be necessary to enclose a +filtered name within single-quotes C<< ' ... ' >> when typing it as an +argument to a Unix shell command. + +=head2 Filter Reference + +=cut + +registrar(tsInitialize) + +=head3 TimeStamp Filter C<"ts"> + +This filter is used to set the timestamp of the value fetched through +the channel to the time the value was fetched (or an update was sent), +rather than the time the record last +processed which could have been days or even weeks ago for some records, or set +to the EPICS epoch if the record has never processed. + +=head4 Parameters + +None, use an empty pair of braces. + +=head4 Example + + Hal$ caget -a 'test:channel.{"ts":{}}' + test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID + Hal$ caget -a 'test:channel' + test:channel 0 UDF INVALID + +=cut + +registrar(dbndInitialize) + +=head3 Deadband Filter C<"dbnd"> + +This filter implements a channel-specific monitor deadband, which is applied +after any deadbands implemented by the record itself (it can only drop updates +that the unfiltered channel generates, never add additional updates). + +The deadband can be specified as an absolute value change, or as a relative +percentage. + +=head4 Parameters + +=over + +=item Mode+Deadband C<"abs">/C<"rel"> (shorthand) + +Mode and deadband can be specified in one definition (shorthand). +The desired mode is given as parameter name (C<"abs"> or C<"rel">), with the +numeric size of the deadband (absolute value or numeric percentage) as value. + +=item Deadband C<"d"> + +The size of the deadband to use. +Relative deadband values are given as a numeric percentage, but without any +trailing percent character. + +=item Mode C<"m"> (optional) + +A string (enclosed in double-quotes C<">), which should contain either +C or C. +The default mode is C if no mode parameter is included. + +=back + +=head4 Example + + Hal$ camonitor 'test:channel' + test:channel 2012-09-01 22:10:19.600595 1 LOLO MAJOR + test:channel 2012-09-01 22:10:20.600661 2 LOLO MAJOR + test:channel 2012-09-01 22:10:21.600819 3 LOW MINOR + test:channel 2012-09-01 22:10:22.600905 4 LOW MINOR + test:channel 2012-09-01 22:10:23.601023 5 + test:channel 2012-09-01 22:10:24.601136 6 HIGH MINOR + ^C + Hal$ camonitor 'test:channel.{"dbnd":{"abs":1.5}}' + test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR + test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR + test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:53.613804 5 + test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:55.614074 7 HIGH MINOR + test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:57.614305 9 HIHI MAJOR + ^C + +=cut + +registrar(arrInitialize) + +=head3 Array Filter C<"arr"> + +This filter is used to retrieve parts of an array (subarrays and strided +subarrays). + +=head4 Parameters + +Note: Negative index numbers address from the end of the array, with C<-1> being the last element. + +=over + +=item Square bracket notation C<[start:increment:end]> (shorthand) + +The common square bracket notation with can be used in place of JSON. +Any parameter may be omitted (keeping the colons) to use the default value. +If only one colon is included, this means C<[start:end]> with a increment of 1. +If only a single parameter is used C<[index]> the filter returns one element. + +=item Start index C<"s"> + +Index of the first original array element to retrieve. + +=item Increment C<"i"> + +Index increment between retrieved elements of the original array; must be +a positive number. + +=item End index C<"e"> + +Index of the last original array element to retrieve. + +=back + +Defaults (when parameters are omitted) are: +C (first element), C (fetch all elements), C +(last element) + +=head4 Example + + Hal$ caget test:channel 'test:channel.{"arr":{"s":2,"i":2,"e":8}}' test:channel.[3:5] test:channel.[3:2:-3] + test:channel 10 0 1 2 3 4 5 6 7 8 9 + test:channel.{"arr":{"s":2,"i":2,"e":8}} 4 2 4 6 8 + test:channel.[3:5] 3 3 4 5 + test:channel.[3:2:-3] 3 3 5 7 + +=cut + +registrar(syncInitialize) + +=head3 Synchronize Filter C<"sync"> + +This filter is used to dynamically enable or disable monitors according +to a condition and a state variable declared by the IOC. + +State variables have a boolean value and can be set by a binary output +record, an iocsh command or by other software running in the IOC calling +C. + +=head4 Parameters + +=over + +=item Mode+State + +Mode and state can be specified in one definition (shorthand). +The desired mode is given as parameter name (C<"before"> / C<"first"> / +C<"while"> / C<"last"> / C<"after"> / C<"unless">), with the state name +(enclosed in double quotes C<">) as value. + +=item Mode C<"m"> + +A single word from the list below, enclosed in double quotes C<">. +This controls how the state value should affect the monitor stream. + +=over + +=item C<"before"> E only the last value received before the state +changes from false to true is forwarded to the client. + +=item C<"first"> E only the first value received after the state +changes from true to false is forwarded to the client. + +=item C<"while"> E values are forwarded to the client as long as +the state is true. + +=item C<"last"> E only the last value received before the state +changes from true to false is forwarded to the client. + +=item C<"after"> E only the first value received after the state +changes from true to false is forwarded to the client. + +=item C<"unless"> E values are forwarded to the client as long +as the state is false. + +=back + +=item State C<"s"> + +The name of a state variable, enclosed in double quotes C<">. + +=back + +=head4 Example + +Assuming there is a system state called "blue", that is being controlled by +some other facility such as a timing system, updates could be restricted to +periods only when "blue" is true by using + + Hal$ camonitor 'test:channel' 'test:channel.{"while":"blue"}' + ... + +=cut diff --git a/modules/database/src/std/filters/sync.c b/modules/database/src/std/filters/sync.c new file mode 100644 index 000000000..d137dd7a9 --- /dev/null +++ b/modules/database/src/std/filters/sync.c @@ -0,0 +1,194 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "freeList.h" +#include "db_field_log.h" +#include "chfPlugin.h" +#include "dbState.h" +#include "epicsExit.h" +#include "epicsAssert.h" +#include "epicsExport.h" + +#define STATE_NAME_LENGTH 20 + +typedef enum syncMode { + syncModeBefore=0, + syncModeFirst=1, + syncModeLast=2, + syncModeAfter=3, + syncModeWhile=4, + syncModeUnless=5 +} syncMode; + +static const +chfPluginEnumType modeEnum[] = { + {"before", syncModeBefore}, + {"first", syncModeFirst}, + {"last", syncModeLast}, + {"after", syncModeAfter}, + {"while", syncModeWhile}, + {"unless", syncModeUnless}, + {NULL, 0} +}; + +typedef struct myStruct { + syncMode mode; + char state[STATE_NAME_LENGTH]; + dbStateId id; + db_field_log *lastfl; + int laststate:1; +} myStruct; + +static void *myStructFreeList; + +static const +chfPluginArgDef opts[] = { + chfEnum (myStruct, mode, "m", 1, 1, modeEnum), + chfString (myStruct, state, "s", 1, 0), + chfTagString (myStruct, state, "before", mode, 0, 1, 0), + chfTagString (myStruct, state, "first", mode, 1, 1, 0), + chfTagString (myStruct, state, "last", mode, 2, 1, 0), + chfTagString (myStruct, state, "after", mode, 3, 1, 0), + chfTagString (myStruct, state, "while", mode, 4, 1, 0), + chfTagString (myStruct, state, "unless", mode, 5, 1, 0), + chfPluginArgEnd +}; + +static void * allocPvt(void) +{ + myStruct *my = (myStruct*) freeListCalloc(myStructFreeList); + return (void *) my; +} + +static void freePvt(void *pvt) +{ + myStruct *my = (myStruct*) pvt; + db_delete_field_log(my->lastfl); + freeListFree(myStructFreeList, pvt); +} + +static int parse_ok(void *pvt) +{ + myStruct *my = (myStruct*) pvt; + + if (!(my->id = dbStateFind(my->state))) + return -1; + + return 0; +} + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { + db_field_log *passfl = NULL; + myStruct *my = (myStruct*) pvt; + int actstate; + + if (pfl->ctx == dbfl_context_read) + return pfl; + + actstate = dbStateGet(my->id); + + switch (my->mode) { + case syncModeBefore: + if (actstate && !my->laststate) { + passfl = my->lastfl; + my->lastfl = NULL; + } + break; + case syncModeFirst: + if (actstate && !my->laststate) { + passfl = pfl; + pfl = NULL; + } + break; + case syncModeLast: + if (!actstate && my->laststate) { + passfl = my->lastfl; + my->lastfl = NULL; + } + break; + case syncModeAfter: + if (!actstate && my->laststate) { + passfl = pfl; + pfl = NULL; + } + break; + case syncModeWhile: + if (actstate) { + passfl = pfl; + } + goto no_shift; + case syncModeUnless: + if (!actstate) { + passfl = pfl; + } + goto no_shift; + } + + if (my->lastfl) + db_delete_field_log(my->lastfl); + my->lastfl = pfl; + my->laststate = actstate; + + /* since no copy is made we can't keep a reference to the returned fl */ + assert(my->lastfl != passfl); + + no_shift: + return passfl; +} + +static void channelRegisterPre(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + *cb_out = filter; + *arg_out = pvt; +} + +static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent) +{ + myStruct *my = (myStruct*) pvt; + printf("%*sSynchronize (sync): mode=%s, state=%s\n", indent, "", + chfPluginEnumString(modeEnum, my->mode, "n/a"), my->state); +} + +static chfPluginIf pif = { + allocPvt, + freePvt, + + NULL, /* parse_error, */ + parse_ok, + + NULL, /* channel_open, */ + channelRegisterPre, + NULL, /* channelRegisterPost, */ + channel_report, + NULL /* channel_close */ +}; + +static void syncShutdown(void* ignore) +{ + if(myStructFreeList) + freeListCleanup(myStructFreeList); + myStructFreeList = NULL; +} + +static void syncInitialize(void) +{ + if (!myStructFreeList) + freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64); + + chfPluginRegister("sync", &pif, opts); + epicsAtExit(syncShutdown, NULL); +} + +epicsExportRegistrar(syncInitialize); diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c new file mode 100644 index 000000000..5925b0bf2 --- /dev/null +++ b/modules/database/src/std/filters/ts.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include +#include +#include +#include + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { + epicsTimeStamp now; + epicsTimeGetCurrent(&now); + + /* If string or array, must make a copy (to ensure coherence between time and data) */ + if (pfl->type == dbfl_type_rec) { + dbScanLock(dbChannelRecord(chan)); + dbChannelMakeArrayCopy(pvt, pfl, chan); + dbScanUnlock(dbChannelRecord(chan)); + } + + pfl->time = now; + return pfl; +} + +static void channelRegisterPre(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + *cb_out = filter; +} + +static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent) +{ + printf("%*sTimestamp (ts)\n", indent, ""); +} + +static chfPluginIf pif = { + NULL, /* allocPvt, */ + NULL, /* freePvt, */ + + NULL, /* parse_error, */ + NULL, /* parse_ok, */ + + NULL, /* channel_open, */ + channelRegisterPre, + NULL, /* channelRegisterPost, */ + channel_report, + NULL /* channel_close */ +}; + +static void tsInitialize(void) +{ + chfPluginRegister("ts", &pif, NULL); +} + +epicsExportRegistrar(tsInitialize); diff --git a/modules/database/src/std/link/Makefile b/modules/database/src/std/link/Makefile new file mode 100644 index 000000000..31d14b825 --- /dev/null +++ b/modules/database/src/std/link/Makefile @@ -0,0 +1,18 @@ +#************************************************************************* +# Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# This is a Makefile fragment, see src/std/Makefile. + +SRC_DIRS += $(STDDIR)/link + +DBD += links.dbd + +dbRecStd_SRCS += lnkConst.c +dbRecStd_SRCS += lnkCalc.c + +HTMLS += links.html + diff --git a/modules/database/src/std/link/links.dbd.pod b/modules/database/src/std/link/links.dbd.pod new file mode 100644 index 000000000..d94e9e8f0 --- /dev/null +++ b/modules/database/src/std/link/links.dbd.pod @@ -0,0 +1,131 @@ +=head1 Extensible Links + +The extensible link mechanism allows new kinds of record links to be created, +using JSON for the link address syntax. +The IOC continues to support the older link types that do not use JSON to +specify their link addresses. + +The following additional link types are available in this release: + +=over + +=item * L + +=item * L + +=back + +=head2 Using JSON Links + +When setting a record link field to a JSON link address, the link specification +must appear inside a pair of braces C< {} > expressed as a JSON (L) object, which allows link parameters to +be defined as needed by the particular link type. When link fields are set from +an IOC database file at initialization time, the field definitions may take +advantage of a "relaxed JSON" syntax that reduces the number of double-quote +characters required and maintains backwards compatibility with the older +database file syntax. + + +=head2 Link Type Reference + +=cut + +link(const, lnkConstIf) + +=head3 Constant Link C<"const"> + +Constant links provide one or more values at link initalization time, but do not +return any data when their C routine is called. Most record types +support the use of constant links by calling C at +record initialization, which results in the constant value being loaded into the +target field at that time. + +Note that for most record types (the C and C records are the +main exceptions) it is pointless to set an input link to a constant link at +runtime since the link initialization that loads the field value usually only +happens when a record is initialized. A constant link that is embedded inside +another input link type such as a calculation link should be OK though since the +link initialization will take place when the record's field gets set. + +=head4 Parameters + +A const link takes a parameter which may be an integer, double or string, or an +array of those types. If an array contains both integers and double values the +integers will be promoted to doubles. Mixing strings and numbers in an array +results in an error. + +=head4 Examples + + {const: 3.14159265358979} + {const: "Pi"} + {const: [1, 2.718281828459, 3.14159265358979]} + {const: ["One", "e", "Pi"]} + +The JSON syntax does not support Infinity or NaN values when parsing numbers, +but (for scalars) it is possible to provide these in a string which will be +converted to the desired double value at initialization, for example: + + field(INP, {const:"Inf"}) + +=cut + +link(calc, lnkCalcIf) + +=head3 Calculation Link C<"calc"> + +Calculation links can perform simple mathematical expressions on scalar +(double-precision floating-point) values obtained from other link types and +return a single double-precision floating-point result. The expressions are +evaluated by the EPICS Calc engine, and up to 12 inputs can be provided. + +=head4 Parameters + +The link address is a JSON map with the following keys: + +=over + +=item expr + +The primary expression to be evaluated, given as a string. + +=item major + +An optional expression that returns non-zero to raise a major alarm. + +=item minor + +An optional expression that returns non-zero to raise a minor alarm. + +=item args + +A JSON list of up to 12 input arguments for the expression, which are assigned +to the inputs C, C, C, ... C. Each input argument may be either a +numeric literal or an embedded JSON link inside C<{}> braces. The same input +values are provided to the two alarm expressions as to the primary expression. + +=item units + +An optional string specifying the engineering units for the result of the +expression. Equivalent to the C field of a record. + +=item prec + +An optional integer specifying the numeric precision with which the calculation +result should be displayed. Equivalent to the C field of a record. + +=item time + +An optional string containing a single upper or lower-case letter C ... C +which must correspond to an input provided in the c parameter. When the +record containing such a link has C set to -2 (epicsTimeEventDeviceTime) +the record's timestamp field C
for a description of the various hardware +address formats supported. + +=head3 Units Conversion + +These fields control if and how the raw input value gets converted into +engineering units: + +=fields RVAL, ROFF, ASLO, AOFF, LINR, ESLO, EOFF, EGUL, EGUF + +These fields are not used if the device support layer reads its value in +engineering units and puts it directly into the VAL field. +This applies to Soft Channel and Async Soft Channel device support, and is also +fairly common for GPIB and similar high-level device interfaces. + +If the device support sets the RVAL field, the LINR field controls how this gets +converted into engineering units and placed in the VAL field as follows: + +=over + +=item 1. +RVAL is converted to a double and ROFF is added to it. + +=item 2. +If ASLO is non-zero the value is multiplied by ASLO. + +=item 3. +AOFF is added. + +=item 4. +If LINR is C the units conversion is finished after the above +steps. + +=item 5. +If LINR is C or C, the value from step 3 above is multiplied by +ESLO and EOFF is added to complete the units conversion process. + +=item 6. +Any other value for LINR selects a particular breakpoint table to be used on the +value from step 3 above. + +=back + +The distinction between the C and C settings for the LINR field +are in how the conversion parameters are calculated: + +=over + +=item * +With C conversion the user must set EGUL and EGUF to the lowest and +highest possible engineering units values respectively that can be converted by +the hardware. +The device support knows the range of the raw data and calculates ESLO and EOFF +from them. + +=item * +C conversion requires the user to calculate the appropriate scaling and +offset factors and put them directly in ESLO and EOFF. + +=back + +=head3 Smoothing Filter + +This filter is usually only used if the device support sets the RVAL field and +the Units Conversion process is used. +Device support that directly sets the VAL field may implement the filter if +desired. + +The filter is controlled with a single parameter field: + +=fields SMOO + +The SMOO field should be set to a number between 0 and 1. +If set to zero the filter is not used (no smoothing), while if set to one the +result is infinite smoothing (the VAL field will never change). +The calculation performed is: + +=over + +VAL = VAL * SMOO + (1 - SMOO) * New Data + +=back + +where C was the result from the Units Conversion above. +This implements a first-order infinite impulse response (IIR) digital filter +with z-plane pole at SMOO. +The equivalent continuous-time filter time constant E is given by + +=over + +E = ET / ln(SMOO) + +=back + +where T is the time between record processing. + +=head3 Undefined Check + +If after applying the smoothing filter the VAL field contains a NaN +(Not-a-Number) value, the UDF field is set to a non-zero value, indicating that +the record value is undefined, which will trigger a C with severity +C. + +=fields UDF + +=head3 Operator Display Parameters + +These parameters are used to present meaningful data to the operator. +They do not affect the functioning of the record at all. + +=over + +=item * +DESC is a string that is usually used to briefly describe the record. + +=item * +EGU is a string of up to 16 characters naming the engineering units that the VAL +field represents. + +=item * +The HOPR and LOPR fields set the upper and lower display limits for the VAL, +HIHI, HIGH, LOW, and LOLO fields. + +=item * +The PREC field determines the floating point precision (i.e. the number of +digits to show after the decimal point) with which to display VAL and the other +DOUBLE fields. + +=back + +=fields DESC, EGU, HOPR, LOPR, PREC + +=head3 Alarm Limits + +The user configures limit alarms by putting numerical values into the HIHI, +HIGH, LOW and LOLO fields, and by setting the associated alarm severity in the +corresponding HHSV, HSV, LSV and LLSV menu fields. + +The HYST field controls hysteresis to prevent alarm chattering from an input +signal that is close to one of the limits and suffers from significant readout +noise. + +The AFTC field sets the time constant on a low-pass filter that delays the +reporting of limit alarms until the signal has been within the alarm range for +that number of seconds (the default AFTC value of zero retains the previous +behavior). + +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC, LALM + +=head3 Monitor Parameters + +These parameters are used to determine when to send monitors placed on the VAL +field. +The monitors are sent when the current value exceeds the last transmitted value +by the appropriate deadband. +If these fields are set to zero, a monitor will be triggered every time the +value changes; if set to -1, a monitor will be sent every time the record is +processed. + +The ADEL field sets the deadband for archive monitors (C events), while +the MDEL field controls value monitors (C events). + +The remaining fields are used by the record at run-time to implement the record +monitoring functionality. + +=fields ADEL, MDEL, ALST, MLST, ORAW + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Current EGU Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LINR,DBF_MENU) { + prompt("Linearization") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + menu(menuConvert) + } + field(EGUF,DBF_DOUBLE) { + prompt("Engineer Units Full") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + } + field(EGUL,DBF_DOUBLE) { + prompt("Engineer Units Low") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(AOFF,DBF_DOUBLE) { + prompt("Adjustment Offset") + promptgroup("60 - Convert") + pp(TRUE) + interest(1) + } + field(ASLO,DBF_DOUBLE) { + prompt("Adjustment Slope") + promptgroup("60 - Convert") + pp(TRUE) + interest(1) + initial("1") + } + field(SMOO,DBF_DOUBLE) { + prompt("Smoothing") + promptgroup("60 - Convert") + interest(1) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(AFTC,DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(AFVL,DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(ESLO,DBF_DOUBLE) { + prompt("Raw to EGU Slope") + promptgroup("60 - Convert") + pp(TRUE) + interest(2) + initial("1") + } + field(EOFF,DBF_DOUBLE) { + prompt("Raw to EGU Offset") + promptgroup("60 - Convert") + pp(TRUE) + interest(2) + } + field(ROFF,DBF_ULONG) { + prompt("Raw Offset") + pp(TRUE) + interest(2) + } + field(PBRK,DBF_NOACCESS) { + prompt("Ptrto brkTable") + special(SPC_NOMOD) + interest(4) + extra("void * pbrk") + } + field(INIT,DBF_SHORT) { + prompt("Initialized?") + special(SPC_NOMOD) + interest(3) + } + field(LBRK,DBF_SHORT) { + prompt("LastBreak Point") + special(SPC_NOMOD) + interest(3) + } + field(RVAL,DBF_LONG) { + prompt("Current Raw Value") + pp(TRUE) + } + field(ORAW,DBF_LONG) { + prompt("Previous Raw Value") + special(SPC_NOMOD) + interest(3) + } + +=head3 Simulation Mode + +The record provides several fields to support simulation of absent hardware. +If the SIML field is set it is used to read a value into the SIMM field, which +controls whether simulation is used or not: + +=over + +=item * +SIMM must be zero (C) for the record to request a value from the device +support. + +=item * +If SIMM is C and the SIOL link field is set, a simlated value in +engineering units is read using the link into the SVAL field, from where it will +subsequently be copied into the VAL field. + +=item * +If SIMM is C the SIOL link is still read into SVAL, but is then truncated +and copied into the RVAL field. +The L process described above is then followed to transform +the simulated raw value into engineering units. + +=back + +The SIMS field can be set to give the record an alarm severity while it is in +simulation mode. + +=fields SIML, SIMM, SIOL, SVAL, SIMS + +=cut + + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_DOUBLE) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuSimm) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} + +=head2 Device Support Interface + +The record requires device support to provide an entry table (dset) which +defines the following members: + + typedef struct { + long number; + long (*report)(int level); + long (*init)(int after); + long (*init_record)(aiRecord *prec); + long (*get_ioint_info)(int cmd, aiRecord *prec, IOSCANPVT *piosl); + long (*read_ai)(aiRecord *prec); + long (*special_linconv)(aiRecord *prec, int after); + } aidset; + +The module must set C to at least 6, and provide a pointer to its +C routine; the other function pointers may be C if their +associated functionality is not required for this support layer. +Most device supports also provide an C routine to configure the +record instance and connect it to the hardware or driver support layer, and if +using the record's L features they set C +as well. + +The individual routines are described below. + +=head3 Device Support Routines + +=head4 long report(int level) + +This optional routine is called by the IOC command C and is passed the +report level that was requested by the user. +It should print a report on the state of the device support to stdout. +The C parameter may be used to output increasingly more detailed +information at higher levels, or to select different types of information with +different levels. +Level zero should print no more than a small summary. + +=head4 long init(int after) + +This optional routine is called twice at IOC initialization time. +The first call happens before any of the C calls are made, with +the integer parameter C set to 0. +The second call happens after all of the C calls have been made, +with C set to 1. + +=head4 long init_record(aiRecord *prec) + +This optional routine is called by the record initialization code for each ai +record instance that has its DTYP field set to use this device support. +It is normally used to check that the INP address is the expected type and that +it points to a valid device; to allocate any record-specific buffer space and +other memory; and to connect any communication channels needed for the +C routine to work properly. + +If the record type's unit conversion features are used, the C +routine should calculate appropriate values for the ESLO and EOFF fields from +the EGUL and EGUF field values. +This calculation only has to be performed if the record's LINR field is set to +C, but it is not necessary to check that condition first. +This same calculation takes place in the C routine, so the +implementation can usually just call that routine to perform the task. + +=head4 long get_ioint_info(int cmd, aiRecord *prec, IOSCANPVT *piosl) + +This optional routine is called whenever the record's SCAN field is being +changed to or from the value C to find out which I/O Interrupt Scan +list the record should be added to or deleted from. +If this routine is not provided, it will not be possible to set the SCAN field +to the value C at all. + +The C parameter is zero when the record is being added to the scan list, +and one when it is being removed from the list. +The routine must determine which interrupt source the record should be connected +to, which it indicates by the scan list that it points the location at C<*piosl> +to before returning. +It can prevent the SCAN field from being changed at all by returning a non-zero +value to its caller. + +In most cases the device support will create the I/O Interrupt Scan lists that +it returns for itself, by calling C once for +each separate interrupt source. +That routine allocates memory and inializes the list, then passes back a pointer +to the new list in the location at C<*piosl>. + +When the device support receives notification that the interrupt has occurred, +it announces that to the IOC by calling C +which will arrange for the appropriate records to be processed in a suitable +thread. +The C routine is safe to call from an interrupt service routine +on embedded architectures (vxWorks and RTEMS). + +=head4 long read_ai(aiRecord *prec) + +This essential routine is called when the record wants a new value from the +addressed device. +It is responsible for performing (or at least initiating) a read operation, and +(eventually) returning its value to the record. + +... PACT and asynchronous processing ... + +... return value ... + +=head4 long special_linconv(aiRecord *prec, int after) + +This optional routine should be provided if the record type's unit conversion +features are used by the device support's C routine returning a +status value of zero. +It is called by the record code whenever any of the the fields LINR, EGUL or +EGUF are modified and LINR has the value C. +The routine must calculate and set the fields EOFF and ESLO appropriately based +on the new values of EGUL and EGUF. + +These calculations can be expressed in terms of the minimum and maximum raw +values that the C routine can put in the RVAL field. +When RVAL is set to I the VAL field will be set to EGUF, and when RVAL +is set to I the VAL field will become EGUL. + +The formulae to use are: + +=over + +EOFF = (I * EGUL E I * EGUF) / +(I E I) + +ESLO = (EGUF E EGUL) / (I E I) + +=back + +Note that the record support sets EOFF to EGUL before calling this routine, +which is a very common case (when I is zero). + +=head3 Extended Device Support + +... + +=cut diff --git a/modules/database/src/std/rec/aoRecord.c b/modules/database/src/std/rec/aoRecord.c new file mode 100644 index 000000000..c5a84f615 --- /dev/null +++ b/modules/database/src/std/rec/aoRecord.c @@ -0,0 +1,594 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* aoRecord.c - Record Support Routines for Analog Output records */ +/* + * Original Author: Bob Dalesio + * Date: 7-14-89 + * + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "epicsMath.h" +#include "alarm.h" +#include "callback.h" +#include "cvtTable.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "special.h" +#include "recSup.h" +#include "recGbl.h" +#include "menuConvert.h" +#include "menuOmsl.h" +#include "menuYesNo.h" +#include "menuIvoa.h" + +#define GEN_SIZE_OFFSET +#include "aoRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset aoRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double }; + +struct aodset { /* analog input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (0,2)=>(success,success no convert)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_ao;/*(0)=>(success ) */ + DEVSUPFUN special_linconv; +}; +epicsExportAddress(rset,aoRSET); + + +static void checkAlarms(aoRecord *); +static long fetch_value(aoRecord *, double *); +static void convert(aoRecord *, double); +static void monitor(aoRecord *); +static long writeValue(aoRecord *); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct aoRecord *prec = (struct aoRecord *)pcommon; + struct aodset *pdset; + double eoff = prec->eoff, eslo = prec->eslo; + double value; + long status = 0; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if(!(pdset = (struct aodset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"ao: init_record"); + return(S_dev_noDSET); + } + /* get the initial value if dol is a constant*/ + if (recGblInitConstantLink(&prec->dol,DBF_DOUBLE,&prec->val)) + prec->udf = isnan(prec->val); + + /* must have write_ao function defined */ + if ((pdset->number < 6) || (pdset->write_ao ==NULL)) { + recGblRecordError(S_dev_missingSup,(void *)prec,"ao: init_record"); + return(S_dev_missingSup); + } + prec->init = TRUE; + /*The following is for old device support that doesnt know about eoff*/ + if ((prec->eslo==1.0) && (prec->eoff==0.0)) { + prec->eoff = prec->egul; + } + + if (pdset->init_record) { + status = (*pdset->init_record)(prec); + if (prec->linr == menuConvertSLOPE) { + prec->eoff = eoff; + prec->eslo = eslo; + } + switch(status){ + case(0): /* convert */ + value = (double)prec->rval + (double)prec->roff; + if(prec->aslo!=0.0) value *= prec->aslo; + value += prec->aoff; + if (prec->linr == menuConvertNO_CONVERSION){ + ; /*do nothing*/ + } else if ((prec->linr == menuConvertLINEAR) || + (prec->linr == menuConvertSLOPE)) { + value = value*prec->eslo + prec->eoff; + }else{ + if(cvtRawToEngBpt(&value,prec->linr,prec->init, + (void *)&prec->pbrk,&prec->lbrk)!=0) break; + } + prec->val = value; + prec->udf = isnan(value); + break; + case(2): /* no convert */ + break; + default: + recGblRecordError(S_dev_badInitRet,(void *)prec,"ao: init_record"); + return(S_dev_badInitRet); + } + } + prec->oval = prec->pval = prec->val; + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + prec->oraw = prec->rval; + prec->orbv = prec->rbv; + return(0); +} + +static long process(struct dbCommon *pcommon) +{ + struct aoRecord *prec = (struct aoRecord *)pcommon; + struct aodset *pdset = (struct aodset *)(prec->dset); + long status=0; + unsigned char pact=prec->pact; + double value; + + if ((pdset==NULL) || (pdset->write_ao==NULL)) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_ao"); + return(S_dev_missingSup); + } + + /* fetch value and convert*/ + if (prec->pact == FALSE) { + if (!dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + status = fetch_value(prec, &value); + } + else { + value = prec->val; + } + if(!status) convert(prec, value); + prec->udf = isnan(prec->val); + } + + /* check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + prec->val=prec->ivov; + value=prec->ivov; + convert(prec,value); + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "ao:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->init=FALSE; + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + aoRecord *prec = (aoRecord *)(paddr->precord); + struct aodset *pdset = (struct aodset *) (prec->dset); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_LINCONV): + if(pdset->number<6 ) { + recGblDbaddrError(S_db_noMod,paddr,"ao: special"); + return(S_db_noMod); + } + prec->init=TRUE; + if ((prec->linr == menuConvertLINEAR) && pdset->special_linconv) { + double eoff = prec->eoff; + double eslo = prec->eslo; + long status; + prec->eoff = prec->egul; + status = (*pdset->special_linconv)(prec,after); + if (eoff != prec->eoff) + db_post_events(prec, &prec->eoff, DBE_VALUE|DBE_LOG); + if (eslo != prec->eslo) + db_post_events(prec, &prec->eslo, DBE_VALUE|DBE_LOG); + return (status); + } + return (0); + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == aoRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice,paddr,"ao: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) aoRecord##field + +static long get_units(DBADDR * paddr,char *units) +{ + aoRecord *prec=(aoRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + switch (dbGetFieldIndex(paddr)) { + case indexof(ASLO): + case indexof(AOFF): + break; + default: + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + } + return(0); +} + +static long get_precision(const DBADDR *paddr,long *precision) +{ + aoRecord *prec=(aoRecord *)paddr->precord; + + *precision = prec->prec; + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(OVAL): + case indexof(PVAL): + break; + default: + recGblGetPrec(paddr,precision); + } + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + aoRecord *prec=(aoRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(OVAL): + case indexof(PVAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + case indexof(IVOV): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + aoRecord *prec=(aoRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(OVAL): + case indexof(PVAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pcd->upper_ctrl_limit = prec->drvh; + pcd->lower_ctrl_limit = prec->drvl; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + aoRecord *prec=(aoRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)){ + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(aoRecord *prec) +{ + double val, hyst, lalm; + double alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static long fetch_value(aoRecord *prec,double *pvalue) +{ + short save_pact; + long status; + + save_pact = prec->pact; + prec->pact = TRUE; + + /* don't allow dbputs to val field */ + prec->val=prec->pval; + + status = dbGetLink(&prec->dol,DBR_DOUBLE,pvalue,0,0); + prec->pact = save_pact; + + if (status) { + recGblSetSevr(prec,LINK_ALARM,INVALID_ALARM); + return(status); + } + + if (prec->oif == aoOIF_Incremental) + *pvalue += prec->val; + + return(0); +} + +static void convert(aoRecord *prec, double value) +{ + /* check drive limits */ + if (prec->drvh > prec->drvl) { + if (value > prec->drvh) + value = prec->drvh; + else if (value < prec->drvl) + value = prec->drvl; + } + prec->val = value; + prec->pval = value; + + /* now set value equal to desired output value */ + /* apply the output rate of change */ + if (prec->oroc != 0){/*must be defined and >0*/ + double diff; + + diff = value - prec->oval; + if (diff < 0) { + if (prec->oroc < -diff) + value = prec->oval - prec->oroc; + } else if (prec->oroc < diff) + value = prec->oval + prec->oroc; + } + prec->omod = (prec->oval!=value); + prec->oval = value; + + /* convert */ + switch (prec->linr) { + case menuConvertNO_CONVERSION: + break; /* do nothing*/ + case menuConvertLINEAR: + case menuConvertSLOPE: + if (prec->eslo == 0.0) value = 0; + else value = (value - prec->eoff) / prec->eslo; + break; + default: + if (cvtEngToRawBpt(&value, prec->linr, prec->init, + (void *)&prec->pbrk, &prec->lbrk) != 0) { + recGblSetSevr(prec, SOFT_ALARM, MAJOR_ALARM); + return; + } + } + value -= prec->aoff; + if (prec->aslo != 0) value /= prec->aslo; + + /* Apply raw offset and limits, round to 32-bit integer */ + value -= prec->roff; + if (value >= 0.0) { + if (value >= (0x7fffffff - 0.5)) + prec->rval = 0x7fffffff; + else + prec->rval = (epicsInt32)(value + 0.5); + } else { + if (value > (0.5 - 0x80000000)) + prec->rval = (epicsInt32)(value - 0.5); + else + prec->rval = 0x80000000; + } +} + + +static void monitor(aoRecord *prec) +{ + unsigned monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + + if(prec->omod) monitor_mask |= (DBE_VALUE|DBE_LOG); + if(monitor_mask) { + prec->omod = FALSE; + db_post_events(prec,&prec->oval,monitor_mask); + if(prec->oraw != prec->rval) { + db_post_events(prec,&prec->rval, + monitor_mask|DBE_VALUE|DBE_LOG); + prec->oraw = prec->rval; + } + if(prec->orbv != prec->rbv) { + db_post_events(prec,&prec->rbv, + monitor_mask|DBE_VALUE|DBE_LOG); + prec->orbv = prec->rbv; + } + } + return; +} + +static long writeValue(aoRecord *prec) +{ + struct aodset *pdset = (struct aodset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_ao(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_DOUBLE, &prec->oval, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/aoRecord.dbd.pod b/modules/database/src/std/rec/aoRecord.dbd.pod new file mode 100644 index 000000000..f2361df7e --- /dev/null +++ b/modules/database/src/std/rec/aoRecord.dbd.pod @@ -0,0 +1,944 @@ +#************************************************************************* +# Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=title Analog Output Record (ao) + +This record type is normally used to send an analog value to an output device, +converting it from engineering units into an integer value if necessary. +The record supports alarm and drive limits, rate-of-change limiting, output +value integration, linear and break-point conversion from engineering units, and +graphics and control limits. + +=head2 Record-specific Menus + +=head3 Menu aoOIF + +The OIF field which uses this menu controls whether the record acts as an +integrator (C) or not (C). + +=menu aoOIF + +=head2 Parameter Fields + +The record-specific fields are described below. + +=recordtype ao + +=cut + +menu(aoOIF) { + choice(aoOIF_Full,"Full") + choice(aoOIF_Incremental,"Incremental") +} + +recordtype(ao) { + +=head3 Output Value Determination + +These fields control how the record determines the value to be output when it +gets processed: + +=fields OMSL, DOL, OIF, PVAL, DRVH, DRVL, VAL, OROC, OVAL + +The following steps are performed in order during record processing. + +=head4 Fetch Value, Integrate + +The OMSL menu field is used to determine whether the DOL link and OIF menu +fields should be used during processing or not: + +=over + +=item * +If OMSL is C the DOL and OIF fields are not used. +The new output value is taken from the VAL field, which may have been set from +elsewhere. + +=item * +If OMSL is C the DOL link field is read to obtain a value; if OIF +is C and the DOL link was read successfully, the record's previous +output value PVAL is added to it. + +=back + +=head4 Drive Limits + +The output value is now clipped to the range DRVL to DRVH inclusive, provided +that DRVH > DRVL. +The result is copied into both the VAL and PVAL fields. + +=head4 Limit Rate of Change + +If the OROC field is not zero, the VAL field is now adjusted so it is no more +than OROC different to the previous output value given in OVAL. +OROC thus determines the maximum change in the output value that can occur each +time the record gets processed. +The result is copied into the OVAL field, which is used as the input to the +following Units Conversion processing stage. + +=head3 Units Conversion + +... + + +For analog output records that do not use the Soft Channel device support +routine, the specified conversions (if any) are performed on the OVAL field and +the resulting value in the RVAL field is sent to the address contained in the +output link after it is adjusted by the values in the AOFF and ASLO fields. + +=fields LINR, RVAL, ROFF, EGUF, EGUL, AOFF, ASLO, ESLO, EOFF + +=head4 Conversion Related Fields and the Conversion Process + +Except for analog outputs that use Soft Channel device support, the LINR field +determines if a conversion is performed and which conversion algorithm is used +to convert OVAL to RVAL. + +The LINR field can specify C or C for linear conversions, +C for no conversions at all, or the name of a breakpoint table +such as C for breakpoint conversions. + +Note that the ESLO, EOFF, EGUF, and EGUL fields are only used for linear +conversions. +Also note that none of these fields have any significance for records that use +the Soft Channel device support module. + +=over + +=item EGUF, EGUF + +The user must calculate these fields when configuring the database for records +that use C conversions. +They are used to calculate the values for ESLO and EOFF. +See Conversion Specification for more information on how to calculate these +fields. + +=item AOFF, ASLO + +These fields are adjustment parameters for the raw output values. +They are applied to the raw output value after conversion from engineering +units. + +=item ESLO, EOFF + +Computed by device support using EGUF and EGUL when LINR specifies C. +These values must be supplied by the user when LINR specifies C. +Used only when LINR is C or C. + +=item ROFF + +This field can be used to offset the raw value generated by the conversion +process, which is needed for some kinds of hardware. + +=back + +Conversion proceeds as follows: + +=over + +=item 1. If LINR==LINEAR or LINR==SLOPE, then X = (VAL - EOFF) / ESLO, +else if LINR==NO_CONVERSION, then X = VAL, +else X is obtained via breakpoint table. + +=item 2. X = (X - AOFF) / ASLO + +=item 3. RVAL = round(X) - ROFF + +=back + +To see how the Raw Soft Channel device support routine uses these +fields, see L below for more +information. + +=head3 Output Specification + +The analog output record sends its desired output to the address in the +OUT field. For analog outputs that write their values to devices, the +OUT field must specify the address of the I/O card. In addition, the +DTYP field must contain the name of the device support module. Be aware +that the address format differs according to the I/O bus used. See +Address Specification for information on the format of hardware +addresses. The user can see a list of the device support modules +currently supported at the user's local site by using the dbst utility +in R3.13. + +For soft records the output link can be a database link, a channel +access link, or a constant value. If the link is a constant, no output +is sent. See Address Specification for information on the format of +database and channel access addresses. + +=fields DTYP, OUT + +=head3 Operator Display Parameters + +These parameters are used to present meaningful data to the operator. +They display the value and other parameters of the analog output either +textually or graphically. + +EGU is a string of up to 16 characters describing the units that the +analog output measures. It is retrieved by the get_units record support +routine. + +The HOPR and LOPR fields set the upper and lower display limits for the +VAL, OVAL, PVAL, HIHI, HIGH, LOW, and LOLO fields. Both the +get_graphic_double and get_control_double record support routines +retrieve these fields. If these values are defined, they must be in the +range: DRVL E= LOPR E= HOPR E= DRVH. + +The PREC field determines the floating point precision with which to +display VAL, OVAL and PVAL. It is used whenever the get_precision +record support routine is called. + +See Fields Common to All Record Types for more on the record name +(NAME) and description (DESC) fields. + +=fields EGU, HOPR, LOPR, PREC, NAME, DESC + +=head3 Alarm Parameters + +The possible alarm conditions for analog outputs are the SCAN, READ, +INVALID and limit alarms. The SCAN, READ, and INVALID alarms are called +by the record or device support routines. + +The limit alarms are configured by the user in the HIHI, LOLO, HIGH, +and LOW fields, which must be floating-point values. For each of these +fields, there is a corresponding severity field which can be either +NO_ALARM, MINOR, or MAJOR. + +See Alarm Specification for a complete explanation of alarms and these +fields. See Invalid Alarm Output Action for more information on the +IVOA and IVOV fields. Alarm Fields lists other fields related to a +alarms that are common to all record types. + +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, IVOA, IVOV + +=head3 Monitor Parameters + +These parameters are used to specify deadbands for monitors on the VAL +field. The monitors are sent when the value field exceeds the last +monitored field by the specified deadband. If these fields have a value +of zero, everytime the value changes, a monitor will be triggered; if +they have a value of -1, everytime the record is processed, monitors +are triggered. ADEL is the deadband for archive monitors, and MDEL the +deadband for all other types of monitors. See Monitor Specification for +a complete explanation of monitors. + +=fields ADEL, MDEL + +=head3 Run-time and Simulation Mode Parameters + +These parameters are used by the run-time code for processing the +analog output. They are not configurable. They represent the current +state of the record. The record support routines use some of them for +more efficient processing. + +The ORAW field is used to decide if monitors should be triggered for +RVAL when monitors are triggered for VAL. The RBV field is the actual +read back value obtained from the hardware itself or from the +associated device driver. It is the responsibility of the device +support routine to give this field a value. + +ORBV is used to decide if monitors should be triggered for RBV at the +same time monitors are triggered for changes in VAL. + +The LALM, MLST, and ALST fields are used to implement the hysteresis +factors for monitor callbacks. + +The INIT field is used to initialize the LBRK field and for smoothing. + +The PBRK field contains a pointer to the current breakpoint table (if +any), and LBRK contains a pointer to the last breakpoint table used. + +The OMOD field indicates whether OVAL differs from VAL. It will be +different if VAL or OVAL have changed since the last time the record +was processed, or if VAL has been adjusted by OROC during the current +processing. + +=fields ORAW, RBV, ORBV, LALM, ALST, MLST, INIT, PBRK, LBRK, PVAL, OMOD + +The following fields are used to operate the analog output in the +simulation mode. See Fields Common to Many Record Types for more +information on these fields. + +=fields SIOL, SIML, SIMM, SIMS + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Desired Output") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + } + field(OVAL,DBF_DOUBLE) { + prompt("Output Value") + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(OROC,DBF_DOUBLE) { + prompt("Output Rate of Change") + promptgroup("50 - Output") + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(OIF,DBF_MENU) { + prompt("Out Full/Incremental") + promptgroup("50 - Output") + interest(1) + menu(aoOIF) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LINR,DBF_MENU) { + prompt("Linearization") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + menu(menuConvert) + } + field(EGUF,DBF_DOUBLE) { + prompt("Eng Units Full") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + } + field(EGUL,DBF_DOUBLE) { + prompt("Eng Units Low") + promptgroup("60 - Convert") + special(SPC_LINCONV) + pp(TRUE) + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(ROFF,DBF_ULONG) { + prompt("Raw Offset") + pp(TRUE) + interest(2) + } + field(EOFF,DBF_DOUBLE) { + prompt("EGU to Raw Offset") + promptgroup("60 - Convert") + pp(TRUE) + interest(2) + } + field(ESLO,DBF_DOUBLE) { + prompt("EGU to Raw Slope") + promptgroup("60 - Convert") + pp(TRUE) + interest(2) + initial("1") + } + field(DRVH,DBF_DOUBLE) { + prompt("Drive High Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(DRVL,DBF_DOUBLE) { + prompt("Drive Low Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(AOFF,DBF_DOUBLE) { + prompt("Adjustment Offset") + promptgroup("60 - Convert") + pp(TRUE) + interest(1) + } + field(ASLO,DBF_DOUBLE) { + prompt("Adjustment Slope") + promptgroup("60 - Convert") + pp(TRUE) + interest(1) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(RVAL,DBF_LONG) { + prompt("Current Raw Value") + pp(TRUE) + } + field(ORAW,DBF_LONG) { + prompt("Previous Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(RBV,DBF_LONG) { + prompt("Readback Value") + special(SPC_NOMOD) + } + field(ORBV,DBF_LONG) { + prompt("Prev Readback Value") + special(SPC_NOMOD) + interest(3) + } + field(PVAL,DBF_DOUBLE) { + prompt("Previous value") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(PBRK,DBF_NOACCESS) { + prompt("Ptrto brkTable") + special(SPC_NOMOD) + interest(4) + extra("void * pbrk") + } + field(INIT,DBF_SHORT) { + prompt("Initialized?") + special(SPC_NOMOD) + interest(3) + } + field(LBRK,DBF_SHORT) { + prompt("LastBreak Point") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_DOUBLE) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } + field(OMOD,DBF_UCHAR) { + prompt("Was OVAL modified?") + special(SPC_NOMOD) + } +} + +=head2 Record Support + +=head3 Record Support Routines + +The following are the record support routines that would be of interest +to an application developer. Other routines are the get_units, +get_precision, get_graphic_double, and get_control_double routines. + +=over + +=item init_record + +This routine initializes SIMM if SIML is a constant or creates a +channel access link if SIML is PV_LINK. If SIOL is PV_LINK a channel +access link is created. + +This routine next checks to see that device support is available. If +DOL is a constant, then VAL is initialized with its value and UDF is +set to FALSE. + +The routine next checks to see if the device support write routine is +defined. If either device support or the device support write routine +does not exist, an error message is issued and processing is +terminated. + +For compatibility with old device supports that don't know EOFF, if +both EOFF and ESLO have their default value, EOFF is set to EGUL. + +If device support includes init_record, it is called. + +INIT is set TRUE. This causes PBRK, LBRK, and smoothing to be +re-initialized. If "backwards" linear conversion is requested, then VAL +is computed from RVAL using the algorithm: + + VAL = ((RVAL+ROFF) * ASLO + AOFF) * ESLO + EOFF + +and UDF is set to FALSE. + +For breakpoint conversion, a call is made to cvtEngToRawBpt and UDF is +then set to FALSE. PVAL is set to VAL. + +=item process + +See next section. + +=item special + +The only special processing for analog output records is SPC_LINCONV +which is invoked whenever either of the fields LINR, EGUF, EGUL or ROFF +is changed If the device support routine special_linconv exists it is +called. + +INIT is set TRUE. This causes PBRK, LBRK, and smoothing to be +re-initialized. + +=item get_value + +Fills in the values of struct valueDes so that they refer to VAL. + +=item get_alarm_double + +Sets the following values: + + upper_alarm_limit = HIHI + upper_warning_limit = HIGH + lower_warning_limit = LOW + lower_alarm_limit = LOLO + +=back + +=head3 Record Processing + +Routine process implements the following algorithm: + +=over + +=item 1. Check to see that the appropriate device support module +exists. If it doesn't, an error message is issued and processing is +terminated with the PACT field set to TRUE. This ensures that processes +will no longer be called for this record. Thus error storms will not +occur. + +=item 2. Check PACT: If PACT is FALSE call fetch_values and convert +which perform the following steps: + +=over + +=item * fetch_values: + +=over + +=item * if DOL is DB_LINK and OMSL is CLOSED_LOOP then get value from +DOL + +=item * if OIF is INCREMENTAL then set value = value + VAL else value = +VAL + +=back + +=item * convert: + +=over + +=item * If Drive limits are defined force value to be within limits + +=item * Set VAL equal to value + +=item * Set UDF to FALSE. + +=item * If OVAL is undefined set it equal to value + +=item * If OROC is defined and not 0 make |value-OVAL| E=OROC + +=item * Set OVAL equal to value + +=item * Compute RVAL from OVAL. using linear or break point table +conversion. For linear conversions the algorithm is RVAL = +(OVAL-EOFF)/ESLO. + +=item * For break point table conversion a call is made to +cvtEngToRawBpt. + +=item * After that, for all conversion types AOFF, ASLO, and ROFF are +calculated in, using the formula RVAL = (RVAL -AOFF) / ASLO - ROFF. + +=back + +=back + +=item 3. Check alarms: This routine checks to see if the new VAL causes +the alarm status and severity to change. If so, NSEV, NSTA and y are +set. It also honors the alarm hysteresis factor (HYST). Thus the value +must change by at least HYST before the alarm status and severity is +reduced. + +=item 4. Check severity and write the new value. See Invalid Alarm +Output Action for details on how invalid alarms affect output records. + +=item 5. If PACT has been changed to TRUE, the device support write +output routine has started but has not completed writing the new value. +In this case, the processing routine merely returns, leaving PACT TRUE. + +=item 6. Check to see if monitors should be invoked: + +=over + +=item * Alarm monitors are invoked if the alarm status or severity has +changed. + +=item * Archive and value change monitors are invoked if ADEL and MDEL +conditions are met. + +=item * Monitors for RVAL and for RBV are checked whenever other +monitors are invoked. + +=item * NSEV and NSTA are reset to 0. + +=back + +=item 7. Scan forward link if necessary, set PACT and INIT FALSE, and +return. + +=back + +=head2 Device Support + +=head3 Fields Of Interest To Device Support + +Each analog output record must have an associated set of device support +routines. The primary responsibility of the device support routines is +to output a new value whenever write_ao is called. The device support +routines are primarily interested in the following fields: + +=over + +=item * +PACT E Process Active, used to indicate asynchronous completion + +=item * +DPVT E Device Private, reserved for device support to use + +=item * +OUT E Output Link, provides addressing information + +=item * +EGUF E Engineering Units Full + +=item * +EGUL E Engineering Units Low + +=item * +ESLO E Engineering Unit Slope + +=item * +EOFF E Engineering Unit Offset + +=item * +OVAL E Output Value, in Engineering units + +=item * +RVAL E Raw Output Value, after conversion + +=back + +=head3 Device Support routines + +Device support consists of the following routines: + +=over + +=item C + +This optional routine is called by the IOC command C and is passed the +report level that was requested by the user. +It should print a report on the state of the device support to stdout. +The C parameter may be used to output increasingly more detailed +information at higher levels, or to select different types of information with +different levels. +Level zero should print no more than a small summary. + +=item C + +This optional routine is called twice at IOC initialization time. +The first call happens before any of the C calls are made, with +the integer parameter C set to 0. +The second call happens after all of the C calls have been made, +with C set to 1. + +=item C + +This optional routine is called by the record initialization code for each ao +record instance that has its DTYP field set to use this device support. +It is normally used to check that the OUT address has the expected type and +points to a valid device; to allocate any record-specific buffer space and +other memory; and to connect any communication channels needed for the +C routine to work properly. + +If the record type's unit conversion features are used, the C +routine should calculate appropriate values for the ESLO and EOFF fields from +the EGUL and EGUF field values. +This calculation only has to be performed if the record's LINR field is set to +C, but it is not necessary to check that condition first. +This same calculation takes place in the C routine, so the +implementation can usually just call that routine to perform the task. + +If the the last output value can be read back from the hardware, this routine +should also fetch that value and put it into the record's RVAL or VAL field. The +return value should be zero if the RVAL field has been set, or 2 if either the +VAL field has been set or if the last output value cannot be retrieved. + +=item C + +This optional routine is called whenever the record's SCAN field is being +changed to or from the value C to find out which I/O Interrupt Scan +list the record should be added to or deleted from. +If this routine is not provided, it will not be possible to set the SCAN field +to the value C at all. + +The C parameter is zero when the record is being added to the scan list, +and one when it is being removed from the list. +The routine must determine which interrupt source the record should be connected +to, which it indicates by the scan list that it points the location at C<*piosl> +to before returning. +It can prevent the SCAN field from being changed at all by returning a non-zero +value to its caller. + +In most cases the device support will create the I/O Interrupt Scan lists that +it returns for itself, by calling C once for +each separate interrupt source. +That API allocates memory and inializes the list, then passes back a pointer to +the new list in the location at C<*piosl>. +When the device support receives notification that the interrupt has occurred, +it announces that to the IOC by calling C +which will arrange for the appropriate records to be processed in a suitable +thread. +The C routine is safe to call from an interrupt service routine +on embedded architectures (vxWorks and RTEMS). + +=item C + +This essential routine is called whenever the record has a new output value to +send to the device. It is responsible for performing the write operation, using +either the engineering units value found in the record's OVAL field, or the raw +value from the record's RVAL field if the record type's unit conversion +facilities are used. A return value of zero indicates success, any other value +indicates that an error occurred. + +This routine must not block (busy-wait) if the device takes more than a few +microseconds to accept the new value. In that case the routine must use +asynchronous completion to tell the record when the write operation eventually +completes. It signals that this is an asynchronous operation by setting the +record's PACT field to TRUE before it returns, having arranged for the record's +C routine to be called later once the write operation is over. When +that happens the C routine will be called again with PACT still set +to TRUE; it should then set it to FALSE to indicate the write has completed, and +return. + +=item C + +This optional routine should be provided if the record type's unit conversion +features are used by the device support's C routine utilizing the +RVAL field rather than OVAL or VAL. +It is called by the record code whenever any of the the fields LINR, EGUL or +EGUF are modified and LINR has the value C. +The routine must calculate and set the fields EOFF and ESLO appropriately based +on the new values of EGUL and EGUF. + +These calculations can be expressed in terms of the minimum and maximum raw +values that the C routine can accept in the RVAL field. +When VAL is EGUF the RVAL field will be set to I, and when VAL is +EGUL the RVAL field will become I. +The fomulae to use are: + +=over + +EOFF = (I * EGUL E I * EGUF) / +(I E I) + +ESLO = (EGUF E EGUL) / (I E I) + +=back + +Note that the record support sets EOFF to EGUL before calling this routine, +which is a very common case (I is zero). + +=back + +=head2 Device Support For Soft Records + +Two soft device support modules Soft Channel and Raw Soft Channel are +provided for output records not related to actual hardware devices. The +OUT link type must be either a CONSTANT, DB_LINK, or CA_LINK. + +=head3 Soft Channel + +This module writes the current value of OVAL. + +If the OUT link type is PV_LINK, then dbCaAddInlink is called by +init_record. init_record always returns a value of 2, which means that +no conversion will ever be attempted. + +write_ao calls recGblPutLinkValue to write the current value of VAL. +See Soft Output for details. + +=head3 Raw Soft Channel + +This module is like the previous except that it writes the current +value of RVAL. + +=cut diff --git a/modules/database/src/std/rec/biRecord.c b/modules/database/src/std/rec/biRecord.c new file mode 100644 index 000000000..be700a56a --- /dev/null +++ b/modules/database/src/std/rec/biRecord.c @@ -0,0 +1,324 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recBi.c - Record Support Routines for Binary Input records */ +/* + * Original Author: Bob Dalesio + * Date: 7-14-89 + * + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbFldTypes.h" +#include "dbEvent.h" +#include "devSup.h" +#include "errMdef.h" +#include "menuSimm.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" + +#define GEN_SIZE_OFFSET +#include "biRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +static long get_enum_str(const DBADDR *, char *); +static long get_enum_strs(const DBADDR *, struct dbr_enumStrs *); +static long put_enum_str(const DBADDR *, const char *); +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL +rset biRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double }; +struct bidset { /* binary input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_bi;/*(0,2)=> success and convert, don't convert)*/ + /* if convert then raw value stored in rval */ +}; +epicsExportAddress(rset,biRSET); +static void checkAlarms(biRecord *); +static void monitor(biRecord *); +static long readValue(biRecord *); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct biRecord *prec = (struct biRecord *)pcommon; + struct bidset *pdset; + long status; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_USHORT, &prec->sval); + + if(!(pdset = (struct bidset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"bi: init_record"); + return(S_dev_noDSET); + } + /* must have read_bi function defined */ + if( (pdset->number < 5) || (pdset->read_bi == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"bi: init_record"); + return(S_dev_missingSup); + } + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + prec->mlst = prec->val; + prec->lalm = prec->val; + prec->oraw = prec->rval; + return(0); +} + +static long process(struct dbCommon *pcommon) +{ + struct biRecord *prec = (struct biRecord *)pcommon; + struct bidset *pdset = (struct bidset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->read_bi==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_bi"); + return(S_dev_missingSup); + } + + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if(status==0) { /* convert rval to val */ + if(prec->rval==0) prec->val =0; + else prec->val = 1; + prec->udf = FALSE; + } + else if(status==2) status=0; + /* check for alarms */ + checkAlarms(prec); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + biRecord *prec = (biRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == biRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "bi: special"); + return(S_db_badChoice); + } +} + +static long get_enum_str(const DBADDR *paddr, char *pstring) +{ + biRecord *prec=(biRecord *)paddr->precord; + int index; + unsigned short *pfield = (unsigned short *)paddr->pfield; + + + index = dbGetFieldIndex(paddr); + if(index!=biRecordVAL) { + strcpy(pstring,"Illegal_Value"); + } else if(*pfield==0) { + strncpy(pstring,prec->znam,sizeof(prec->znam)); + pstring[sizeof(prec->znam)] = 0; + } else if(*pfield==1) { + strncpy(pstring,prec->onam,sizeof(prec->onam)); + pstring[sizeof(prec->onam)] = 0; + } else { + strcpy(pstring,"Illegal_Value"); + } + return(0); +} + +static long get_enum_strs(const DBADDR *paddr,struct dbr_enumStrs *pes) +{ + biRecord *prec=(biRecord *)paddr->precord; + + pes->no_str = 2; + memset(pes->strs,'\0',sizeof(pes->strs)); + strncpy(pes->strs[0],prec->znam,sizeof(prec->znam)); + if(*prec->znam!=0) pes->no_str=1; + strncpy(pes->strs[1],prec->onam,sizeof(prec->onam)); + if(*prec->onam!=0) pes->no_str=2; + return(0); +} + +static long put_enum_str(const DBADDR *paddr, const char *pstring) +{ + biRecord *prec=(biRecord *)paddr->precord; + + if(strncmp(pstring,prec->znam,sizeof(prec->znam))==0) prec->val = 0; + else if(strncmp(pstring,prec->onam,sizeof(prec->onam))==0) prec->val = 1; + else return(S_db_badChoice); + prec->udf=FALSE; + return(0); +} + + +static void checkAlarms(biRecord *prec) +{ + unsigned short val = prec->val; + + + if(prec->udf == TRUE){ + recGblSetSevr(prec,UDF_ALARM,prec->udfs); + return; + } + + if(val>1)return; + /* check for state alarm */ + if (val == 0){ + recGblSetSevr(prec,STATE_ALARM,prec->zsv); + }else{ + recGblSetSevr(prec,STATE_ALARM,prec->osv); + } + + /* check for cos alarm */ + if(val == prec->lalm) return; + recGblSetSevr(prec,COS_ALARM,prec->cosv); + prec->lalm = val; + return; +} + +static void monitor(biRecord *prec) +{ + unsigned short monitor_mask; + + monitor_mask = recGblResetAlarms(prec); + /* check for value change */ + if (prec->mlst != prec->val){ + /* post events for value change and archive change */ + monitor_mask |= (DBE_VALUE | DBE_LOG); + /* update last value monitored */ + prec->mlst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + if(prec->oraw!=prec->rval) { + db_post_events(prec,&prec->rval, + monitor_mask|DBE_VALUE|DBE_LOG); + prec->oraw = prec->rval; + } + return; +} + +static long readValue(biRecord *prec) +{ + struct bidset *pdset = (struct bidset *)prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuSimmNO: + status = pdset->read_bi(prec); + break; + + case menuSimmYES: + case menuSimmRAW: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_ULONG, &prec->sval, 0, 0); + if (status == 0) { + if (prec->simm == menuSimmYES) { + prec->val = prec->sval; + status = 2; /* Don't convert */ + } else { + prec->rval = (unsigned short) prec->sval; + } + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/biRecord.dbd b/modules/database/src/std/rec/biRecord.dbd new file mode 100644 index 000000000..1646d5df1 --- /dev/null +++ b/modules/database/src/std/rec/biRecord.dbd @@ -0,0 +1,134 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(bi) { + include "dbCommon.dbd" + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(VAL,DBF_ENUM) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(ZSV,DBF_MENU) { + prompt("Zero Error Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(OSV,DBF_MENU) { + prompt("One Error Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(COSV,DBF_MENU) { + prompt("Change of State Svr") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(ZNAM,DBF_STRING) { + prompt("Zero Name") + promptgroup("80 - Display") + pp(TRUE) + interest(1) + size(26) + prop(YES) + } + field(ONAM,DBF_STRING) { + prompt("One Name") + promptgroup("80 - Display") + pp(TRUE) + interest(1) + size(26) + prop(YES) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(LALM,DBF_USHORT) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_USHORT) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_ULONG) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuSimm) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/boRecord.c b/modules/database/src/std/rec/boRecord.c new file mode 100644 index 000000000..5fb881f55 --- /dev/null +++ b/modules/database/src/std/rec/boRecord.c @@ -0,0 +1,459 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recBo.c - Record Support Routines for Binary Output records */ +/* + * Original Author: Bob Dalesio + * Date: 7-14-89 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuIvoa.h" +#include "menuOmsl.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "boRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +static long get_enum_str(const DBADDR *, char *); +static long get_enum_strs(const DBADDR *, struct dbr_enumStrs *); +static long put_enum_str(const DBADDR *, const char *); +#define get_graphic_double NULL +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +#define get_alarm_double NULL + +rset boRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,boRSET); + +int boHIGHprecision = 2; +epicsExportAddress(int, boHIGHprecision); +double boHIGHlimit = 100000; +epicsExportAddress(double, boHIGHlimit); + +struct bodset { /* binary output dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns:(0,2)=>(success,success no convert*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_bo;/*returns: (-1,0)=>(failure,success)*/ +}; + + +/* control block for callback*/ +typedef struct myCallback { + CALLBACK callback; + struct dbCommon *precord; +}myCallback; + +static void checkAlarms(boRecord *); +static void monitor(boRecord *); +static long writeValue(boRecord *); + +static void myCallbackFunc(CALLBACK *arg) +{ + myCallback *pcallback; + boRecord *prec; + + callbackGetUser(pcallback,arg); + prec=(boRecord *)pcallback->precord; + dbScanLock((struct dbCommon *)prec); + if(prec->pact) { + if((prec->val==1) && (prec->high>0)){ + myCallback *pcallback; + pcallback = (myCallback *)(prec->rpvt); + callbackSetPriority(prec->prio, &pcallback->callback); + callbackRequestDelayed(&pcallback->callback,(double)prec->high); + } + } else { + prec->val = 0; + dbProcess((struct dbCommon *)prec); + } + dbScanUnlock((struct dbCommon *)prec); +} + +static long init_record(struct dbCommon *pcommon,int pass) +{ + struct boRecord *prec = (struct boRecord *)pcommon; + struct bodset *pdset = (struct bodset *) prec->dset; + unsigned short ival = 0; + long status = 0; + myCallback *pcallback; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "bo: init_record"); + return S_dev_noDSET; + } + + /* must have write_bo functions defined */ + if ((pdset->number < 5) || (pdset->write_bo == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "bo: init_record"); + return S_dev_missingSup; + } + + /* get the initial value */ + if (recGblInitConstantLink(&prec->dol, DBF_USHORT, &ival)) { + prec->val = !!ival; + prec->udf = FALSE; + } + + pcallback = (myCallback *) calloc(1, sizeof(myCallback)); + prec->rpvt = pcallback; + callbackSetCallback(myCallbackFunc, &pcallback->callback); + callbackSetUser(pcallback, &pcallback->callback); + pcallback->precord = (struct dbCommon *) prec; + + if (pdset->init_record) { + status=(*pdset->init_record)(prec); + if(status==0) { + if(prec->rval==0) prec->val = 0; + else prec->val = 1; + prec->udf = FALSE; + } else if (status==2) status=0; + } + prec->mlst = prec->val; + /* convert val to rval */ + if ( prec->mask != 0 ) { + if(prec->val==0) prec->rval = 0; + else prec->rval = prec->mask; + } else prec->rval = (epicsUInt32)prec->val; + + prec->mlst = prec->val; + prec->lalm = prec->val; + prec->oraw = prec->rval; + prec->orbv = prec->rbv; + return(status); +} + +static long process(struct dbCommon *pcommon) +{ + struct boRecord *prec = (struct boRecord *)pcommon; + struct bodset *pdset = (struct bodset *)(prec->dset); + long status=0; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->write_bo==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_bo"); + return(S_dev_missingSup); + } + if (!prec->pact) { + if (!dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + unsigned short val; + + prec->pact = TRUE; + status=dbGetLink(&prec->dol,DBR_USHORT, &val,0,0); + prec->pact = FALSE; + if(status==0){ + prec->val = val; + prec->udf = FALSE; + }else { + recGblSetSevr(prec,LINK_ALARM,INVALID_ALARM); + } + } + + /* convert val to rval */ + if ( prec->mask != 0 ) { + if(prec->val==0) prec->rval = 0; + else prec->rval = prec->mask; + } else prec->rval = (epicsUInt32)prec->val; + } + + /* check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + /* convert val to rval */ + prec->val=prec->ivov; + if ( prec->mask != 0 ) { + if(prec->val==0) prec->rval = 0; + else prec->rval = prec->mask; + } else prec->rval = (epicsUInt32)prec->val; + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "bo:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + if((prec->val==1) && (prec->high>0)){ + myCallback *pcallback; + pcallback = (myCallback *)(prec->rpvt); + callbackSetPriority(prec->prio, &pcallback->callback); + callbackRequestDelayed(&pcallback->callback,(double)prec->high); + } + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + boRecord *prec = (boRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == boRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "bo: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) boRecord##field + +static long get_units(DBADDR *paddr, char *units) +{ + if(dbGetFieldIndex(paddr) == indexof(HIGH)) + strcpy(units, "s"); + return(0); +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + if(dbGetFieldIndex(paddr) == indexof(HIGH)) + *precision = boHIGHprecision; + else + recGblGetPrec(paddr,precision); + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + if(dbGetFieldIndex(paddr) == indexof(HIGH)) { + pcd->lower_ctrl_limit = 0.0; + pcd->upper_ctrl_limit = boHIGHlimit; + } else + recGblGetControlDouble(paddr,pcd); + return(0); +} + +static long get_enum_str(const DBADDR *paddr, char *pstring) +{ + boRecord *prec=(boRecord *)paddr->precord; + int index; + unsigned short *pfield = (unsigned short *)paddr->pfield; + + + index = dbGetFieldIndex(paddr); + if(index!=indexof(VAL)) { + strcpy(pstring,"Illegal_Value"); + } else if(*pfield==0) { + strncpy(pstring,prec->znam,sizeof(prec->znam)); + pstring[sizeof(prec->znam)] = 0; + } else if(*pfield==1) { + strncpy(pstring,prec->onam,sizeof(prec->onam)); + pstring[sizeof(prec->onam)] = 0; + } else { + strcpy(pstring,"Illegal_Value"); + } + return(0); +} + +static long get_enum_strs(const DBADDR *paddr,struct dbr_enumStrs *pes) +{ + boRecord *prec=(boRecord *)paddr->precord; + + /*SETTING no_str=0 breaks channel access clients*/ + pes->no_str = 2; + memset(pes->strs,'\0',sizeof(pes->strs)); + strncpy(pes->strs[0],prec->znam,sizeof(prec->znam)); + if(*prec->znam!=0) pes->no_str=1; + strncpy(pes->strs[1],prec->onam,sizeof(prec->onam)); + if(*prec->onam!=0) pes->no_str=2; + return(0); +} +static long put_enum_str(const DBADDR *paddr, const char *pstring) +{ + boRecord *prec=(boRecord *)paddr->precord; + + if(strncmp(pstring,prec->znam,sizeof(prec->znam))==0) prec->val = 0; + else if(strncmp(pstring,prec->onam,sizeof(prec->onam))==0) prec->val = 1; + else return(S_db_badChoice); + return(0); +} + + +static void checkAlarms(boRecord *prec) +{ + unsigned short val = prec->val; + + /* check for udf alarm */ + if(prec->udf == TRUE ){ + recGblSetSevr(prec,UDF_ALARM,prec->udfs); + } + + /* check for state alarm */ + if (val == 0){ + recGblSetSevr(prec,STATE_ALARM,prec->zsv); + }else{ + recGblSetSevr(prec,STATE_ALARM,prec->osv); + } + + /* check for cos alarm */ + if(val == prec->lalm) return; + recGblSetSevr(prec,COS_ALARM,prec->cosv); + prec->lalm = val; + return; +} + +static void monitor(boRecord *prec) +{ + unsigned short monitor_mask; + + monitor_mask = recGblResetAlarms(prec); + /* check for value change */ + if (prec->mlst != prec->val){ + /* post events for value change and archive change */ + monitor_mask |= (DBE_VALUE | DBE_LOG); + /* update last value monitored */ + prec->mlst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + if(prec->oraw!=prec->rval) { + db_post_events(prec,&prec->rval, + monitor_mask|DBE_VALUE|DBE_LOG); + prec->oraw = prec->rval; + } + if(prec->orbv!=prec->rbv) { + db_post_events(prec,&prec->rbv, + monitor_mask|DBE_VALUE|DBE_LOG); + prec->orbv = prec->rbv; + } + return; +} + +static long writeValue(boRecord *prec) +{ + struct bodset *pdset = (struct bodset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_bo(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_USHORT, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/boRecord.dbd b/modules/database/src/std/rec/boRecord.dbd new file mode 100644 index 000000000..346f0f22c --- /dev/null +++ b/modules/database/src/std/rec/boRecord.dbd @@ -0,0 +1,182 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(bo) { + include "dbCommon.dbd" + field(VAL,DBF_ENUM) { + prompt("Current Value") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(HIGH,DBF_DOUBLE) { + prompt("Seconds to Hold High") + promptgroup("30 - Action") + interest(1) + } + field(ZNAM,DBF_STRING) { + prompt("Zero Name") + promptgroup("80 - Display") + pp(TRUE) + interest(1) + size(26) + prop(YES) + } + field(ONAM,DBF_STRING) { + prompt("One Name") + promptgroup("80 - Display") + pp(TRUE) + interest(1) + size(26) + prop(YES) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(RPVT,DBF_NOACCESS) { + prompt("Record Private") + special(SPC_NOMOD) + interest(4) + extra("void * rpvt") + } + field(WDPT,DBF_NOACCESS) { + prompt("Watch Dog Timer ID") + special(SPC_NOMOD) + interest(4) + extra("void * wdpt") + } + field(ZSV,DBF_MENU) { + prompt("Zero Error Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(OSV,DBF_MENU) { + prompt("One Error Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(COSV,DBF_MENU) { + prompt("Change of State Sevr") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(RBV,DBF_ULONG) { + prompt("Readback Value") + special(SPC_NOMOD) + } + field(ORBV,DBF_ULONG) { + prompt("Prev Readback Value") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_USHORT) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_USHORT) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID outpt action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_USHORT) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } +} + +variable(boHIGHprecision, int) +variable(boHIGHlimit, double) diff --git a/modules/database/src/std/rec/calcRecord.c b/modules/database/src/std/rec/calcRecord.c new file mode 100644 index 000000000..12a58d1c8 --- /dev/null +++ b/modules/database/src/std/rec/calcRecord.c @@ -0,0 +1,442 @@ +/*************************************************************************\ +* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Record Support Routines for Calculation records */ +/* + * Original Author: Julie Sander and Bob Dalesio + * Date: 7-27-87 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "epicsMath.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" + +#define GEN_SIZE_OFFSET +#include "calcRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Hysterisis for alarm filtering: 1-1/e */ +#define THRESHOLD 0.6321 + +/* Create RSET - Record Support Entry Table */ + +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *prec, int pass); +static long process(struct dbCommon *prec); +static long special(DBADDR *paddr, int after); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *paddr, char *units); +static long get_precision(const DBADDR *paddr, long *precision); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd); +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd); +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad); + +rset calcRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, calcRSET); + +static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast); +static void monitor(calcRecord *prec); +static int fetch_values(calcRecord *prec); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct calcRecord *prec = (struct calcRecord *)pcommon; + struct link *plink; + double *pvalue; + int i; + short error_number; + + if (pass==0) return(0); + + plink = &prec->inpa; + pvalue = &prec->a; + for (i = 0; i < CALCPERFORM_NARGS; i++, plink++, pvalue++) { + recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); + } + if (postfix(prec->calc, prec->rpcl, &error_number)) { + recGblRecordError(S_db_badField, (void *)prec, + "calc: init_record: Illegal CALC field"); + errlogPrintf("%s.CALC: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->calc); + } + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct calcRecord *prec = (struct calcRecord *)pcommon; + epicsTimeStamp timeLast; + + prec->pact = TRUE; + if (fetch_values(prec) == 0) { + if (calcPerform(&prec->a, &prec->val, prec->rpcl)) { + recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM); + } else + prec->udf = isnan(prec->val); + } + + timeLast = prec->time; + recGblGetTimeStamp(prec); + /* check for alarms */ + checkAlarms(prec, &timeLast); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + short error_number; + + if (!after) return 0; + if (paddr->special == SPC_CALC) { + if (postfix(prec->calc, prec->rpcl, &error_number)) { + recGblRecordError(S_db_badField, (void *)prec, + "calc: Illegal CALC field"); + errlogPrintf("%s.CALC: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->calc); + return S_db_badField; + } + return 0; + } + recGblDbaddrError(S_db_badChoice, paddr, "calc::special - bad special value!"); + return S_db_badChoice; +} + +#define indexof(field) calcRecord##field + +static long get_linkNumber(int fieldIndex) { + if (fieldIndex >= indexof(A) && fieldIndex <= indexof(L)) + return fieldIndex - indexof(A); + if (fieldIndex >= indexof(LA) && fieldIndex <= indexof(LL)) + return fieldIndex - indexof(LA); + return -1; +} + +static long get_units(DBADDR *paddr, char *units) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + int linkNumber; + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + linkNumber = get_linkNumber(dbGetFieldIndex(paddr)); + if (linkNumber >= 0) + dbGetUnits(&prec->inpa + linkNumber, units, DB_UNITS_SIZE); + else + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *pprecision) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + *pprecision = prec->prec; + if (fieldIndex == indexof(VAL)) + return 0; + + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + short precision; + + if (dbGetPrecision(&prec->inpa + linkNumber, &precision) == 0) + *pprecision = precision; + } else + recGblGetPrec(paddr, pprecision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + switch (fieldIndex) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->lower_disp_limit = prec->lopr; + pgd->upper_disp_limit = prec->hopr; + break; + default: + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetGraphicLimits(&prec->inpa + linkNumber, + &pgd->lower_disp_limit, + &pgd->upper_disp_limit); + } else + recGblGetGraphicDouble(paddr,pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pcd->lower_ctrl_limit = prec->lopr; + pcd->upper_ctrl_limit = prec->hopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return 0; +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + calcRecord *prec = (calcRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + if (fieldIndex == indexof(VAL)) { + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + } else { + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetAlarmLimits(&prec->inpa + linkNumber, + &pad->lower_alarm_limit, + &pad->lower_warning_limit, + &pad->upper_warning_limit, + &pad->upper_alarm_limit); + } else + recGblGetAlarmDouble(paddr, pad); + } + return 0; +} + +static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast) +{ + + enum { + range_Lolo = 1, + range_Low, + range_Normal, + range_High, + range_Hihi + } alarmRange; + static const epicsEnum16 range_stat[] = { + SOFT_ALARM, LOLO_ALARM, LOW_ALARM, + NO_ALARM, HIGH_ALARM, HIHI_ALARM + }; + + double val, hyst, lalm, alev, aftc, afvl; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + prec->afvl = 0; + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* check VAL against alarm limits */ + if ((asev = prec->hhsv) && + (val >= (alev = prec->hihi) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_Hihi; + else + if ((asev = prec->llsv) && + (val <= (alev = prec->lolo) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Lolo; + else + if ((asev = prec->hsv) && + (val >= (alev = prec->high) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_High; + else + if ((asev = prec->lsv) && + (val <= (alev = prec->low) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Low; + else { + alev = val; + asev = NO_ALARM; + alarmRange = range_Normal; + } + + aftc = prec->aftc; + afvl = 0; + + if (aftc > 0) { + /* Apply level filtering */ + afvl = prec->afvl; + if (afvl == 0) { + afvl = (double)alarmRange; + } else { + double t = epicsTimeDiffInSeconds(&prec->time, timeLast); + double alpha = aftc / (t + aftc); + + /* The sign of afvl indicates whether the result should be + * rounded up or down. This gives the filter hysteresis. + * If afvl > 0 the floor() function rounds to a lower alarm + * level, otherwise to a higher. + */ + afvl = alpha * afvl + + ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange; + if (afvl - floor(afvl) > THRESHOLD) + afvl = -afvl; /* reverse rounding */ + + alarmRange = abs((int)floor(afvl)); + switch (alarmRange) { + case range_Hihi: + asev = prec->hhsv; + alev = prec->hihi; + break; + case range_High: + asev = prec->hsv; + alev = prec->high; + break; + case range_Normal: + asev = NO_ALARM; + break; + case range_Low: + asev = prec->lsv; + alev = prec->low; + break; + case range_Lolo: + asev = prec->llsv; + alev = prec->lolo; + break; + } + } + } + prec->afvl = afvl; + + if (asev) { + /* Report alarm condition, store LALM for future HYST calculations */ + if (recGblSetSevr(prec, range_stat[alarmRange], asev)) + prec->lalm = alev; + } else { + /* No alarm condition, reset LALM */ + prec->lalm = val; + } + +} + +static void monitor(calcRecord *prec) +{ + unsigned monitor_mask; + double *pnew, *pprev; + int i; + + monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec, &prec->val, monitor_mask); + } + + /* check all input fields for changes*/ + pnew = &prec->a; + pprev = &prec->la; + for (i = 0; i < CALCPERFORM_NARGS; i++, pnew++, pprev++) { + if (*pnew != *pprev || + monitor_mask & DBE_ALARM) { + db_post_events(prec, pnew, monitor_mask | DBE_VALUE | DBE_LOG); + *pprev = *pnew; + } + } + return; +} + +static int fetch_values(calcRecord *prec) +{ + struct link *plink; + double *pvalue; + long status = 0; + int i; + + plink = &prec->inpa; + pvalue = &prec->a; + for(i = 0; i < CALCPERFORM_NARGS; i++, plink++, pvalue++) { + int newStatus; + + newStatus = dbGetLink(plink, DBR_DOUBLE, pvalue, 0, 0); + if (status == 0) status = newStatus; + } + return status; +} diff --git a/modules/database/src/std/rec/calcRecord.dbd b/modules/database/src/std/rec/calcRecord.dbd new file mode 100644 index 000000000..e7eb0eee3 --- /dev/null +++ b/modules/database/src/std/rec/calcRecord.dbd @@ -0,0 +1,324 @@ +#************************************************************************* +# Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(calc) { + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Result") + promptgroup("50 - Output") + asl(ASL0) + } + field(CALC,DBF_STRING) { + prompt("Calculation") + promptgroup("30 - Action") + special(SPC_CALC) + pp(TRUE) + size(80) + initial("0") + } + field(INPA,DBF_INLINK) { + prompt("Input A") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPB,DBF_INLINK) { + prompt("Input B") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPC,DBF_INLINK) { + prompt("Input C") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPD,DBF_INLINK) { + prompt("Input D") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPE,DBF_INLINK) { + prompt("Input E") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPF,DBF_INLINK) { + prompt("Input F") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPG,DBF_INLINK) { + prompt("Input G") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPH,DBF_INLINK) { + prompt("Input H") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPI,DBF_INLINK) { + prompt("Input I") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPJ,DBF_INLINK) { + prompt("Input J") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPK,DBF_INLINK) { + prompt("Input K") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPL,DBF_INLINK) { + prompt("Input L") + promptgroup("42 - Input G-L") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Rng") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(AFTC, DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup("70 - Alarm") + interest(1) + } + field(AFVL, DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(A,DBF_DOUBLE) { + prompt("Value of Input A") + pp(TRUE) + } + field(B,DBF_DOUBLE) { + prompt("Value of Input B") + pp(TRUE) + } + field(C,DBF_DOUBLE) { + prompt("Value of Input C") + pp(TRUE) + } + field(D,DBF_DOUBLE) { + prompt("Value of Input D") + pp(TRUE) + } + field(E,DBF_DOUBLE) { + prompt("Value of Input E") + pp(TRUE) + } + field(F,DBF_DOUBLE) { + prompt("Value of Input F") + pp(TRUE) + } + field(G,DBF_DOUBLE) { + prompt("Value of Input G") + pp(TRUE) + } + field(H,DBF_DOUBLE) { + prompt("Value of Input H") + pp(TRUE) + } + field(I,DBF_DOUBLE) { + prompt("Value of Input I") + pp(TRUE) + } + field(J,DBF_DOUBLE) { + prompt("Value of Input J") + pp(TRUE) + } + field(K,DBF_DOUBLE) { + prompt("Value of Input K") + pp(TRUE) + } + field(L,DBF_DOUBLE) { + prompt("Value of Input L") + pp(TRUE) + } + field(LA,DBF_DOUBLE) { + prompt("Prev Value of A") + special(SPC_NOMOD) + interest(3) + } + field(LB,DBF_DOUBLE) { + prompt("Prev Value of B") + special(SPC_NOMOD) + interest(3) + } + field(LC,DBF_DOUBLE) { + prompt("Prev Value of C") + special(SPC_NOMOD) + interest(3) + } + field(LD,DBF_DOUBLE) { + prompt("Prev Value of D") + special(SPC_NOMOD) + interest(3) + } + field(LE,DBF_DOUBLE) { + prompt("Prev Value of E") + special(SPC_NOMOD) + interest(3) + } + field(LF,DBF_DOUBLE) { + prompt("Prev Value of F") + special(SPC_NOMOD) + interest(3) + } + field(LG,DBF_DOUBLE) { + prompt("Prev Value of G") + special(SPC_NOMOD) + interest(3) + } + field(LH,DBF_DOUBLE) { + prompt("Prev Value of H") + special(SPC_NOMOD) + interest(3) + } + field(LI,DBF_DOUBLE) { + prompt("Prev Value of I") + special(SPC_NOMOD) + interest(3) + } + field(LJ,DBF_DOUBLE) { + prompt("Prev Value of J") + special(SPC_NOMOD) + interest(3) + } + field(LK,DBF_DOUBLE) { + prompt("Prev Value of K") + special(SPC_NOMOD) + interest(3) + } + field(LL,DBF_DOUBLE) { + prompt("Prev Value of L") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + %#include "postfix.h" + field(RPCL,DBF_NOACCESS) { + prompt("Reverse Polish Calc") + special(SPC_NOMOD) + interest(4) + extra("char rpcl[INFIX_TO_POSTFIX_SIZE(80)]") + } +} diff --git a/modules/database/src/std/rec/calcoutRecord.c b/modules/database/src/std/rec/calcoutRecord.c new file mode 100644 index 000000000..8b5525527 --- /dev/null +++ b/modules/database/src/std/rec/calcoutRecord.c @@ -0,0 +1,781 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* calcout.c - Record Support Routines for calc with output records */ +/* + * Author : Ned Arnold + * Based on recCalc.c by Julie Sander and Bob Dalesio + * Date: 7-27-87 + */ + +#include +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbLink.h" +#include "dbScan.h" +#include "cantProceed.h" +#include "epicsMath.h" +#include "errMdef.h" +#include "errlog.h" +#include "recSup.h" +#include "devSup.h" +#include "recGbl.h" +#include "special.h" +#include "callback.h" +#include "taskwd.h" +#include "menuIvoa.h" + +#define GEN_SIZE_OFFSET +#include "calcoutRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset calcoutRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, calcoutRSET); + +int calcoutODLYprecision = 2; +epicsExportAddress(int, calcoutODLYprecision); +double calcoutODLYlimit = 100000; +epicsExportAddress(double, calcoutODLYlimit); + +typedef struct calcoutDSET { + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write; +}calcoutDSET; + + +/* To provide feedback to the user as to the connection status of the + * links (.INxV and .OUTV), the following algorithm has been implemented ... + * + * A new PV_LINK is checked [in both init() and special()] to see if the + * target is local -- if so it is marked as such. If not, a checkLinkCb + * callback is scheduled to check the connection status later by calling + * dbIsLinkConnected(). Anytime there are unconnected CA_LINKs, another + * callback is scheduled. Once all connections are established, the CA_LINKs + * are checked whenever the record processes. + * + */ + +#define NO_CA_LINKS 0 +#define CA_LINKS_ALL_OK 1 +#define CA_LINKS_NOT_OK 2 + +typedef struct rpvtStruct { + CALLBACK doOutCb; + CALLBACK checkLinkCb; + short cbScheduled; + short caLinkStat; /* NO_CA_LINKS, CA_LINKS_ALL_OK, CA_LINKS_NOT_OK */ +} rpvtStruct; + +static void checkAlarms(calcoutRecord *prec); +static void monitor(calcoutRecord *prec); +static int fetch_values(calcoutRecord *prec); +static void execOutput(calcoutRecord *prec); +static void checkLinks(calcoutRecord *prec); +static void checkLinksCallback(CALLBACK *arg); +static long writeValue(calcoutRecord *prec); + +int calcoutRecDebug; + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct calcoutRecord *prec = (struct calcoutRecord *)pcommon; + DBLINK *plink; + int i; + double *pvalue; + epicsEnum16 *plinkValid; + short error_number; + calcoutDSET *pcalcoutDSET; + rpvtStruct *prpvt; + + if (pass == 0) { + prec->rpvt = (rpvtStruct *) callocMustSucceed(1, sizeof(rpvtStruct), "calcoutRecord"); + return 0; + } + + if (!(pcalcoutDSET = (calcoutDSET *)prec->dset)) { + recGblRecordError(S_dev_noDSET, (void *)prec, "calcout:init_record"); + return S_dev_noDSET; + } + + /* must have write defined */ + if ((pcalcoutDSET->number < 5) || (pcalcoutDSET->write ==NULL)) { + recGblRecordError(S_dev_missingSup, (void *)prec, "calcout:init_record"); + return S_dev_missingSup; + } + + prpvt = prec->rpvt; + plink = &prec->inpa; + pvalue = &prec->a; + plinkValid = &prec->inav; + + for (i = 0; i <= CALCPERFORM_NARGS; i++, plink++, pvalue++, plinkValid++) { + /* Don't InitConstantLink the .OUT link */ + if (i < CALCPERFORM_NARGS) { + recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); + } + + if (dbLinkIsConstant(plink)) { + *plinkValid = calcoutINAV_CON; + } + else if (dbLinkIsVolatile(plink)) { + int conn = dbIsLinkConnected(plink); + + if (conn) + *plinkValid = calcoutINAV_EXT; + else { + /* Monitor for connection */ + *plinkValid = calcoutINAV_EXT_NC; + prpvt->caLinkStat = CA_LINKS_NOT_OK; + } + } + else { + /* PV must reside on this ioc */ + *plinkValid = calcoutINAV_LOC; + + if (!dbIsLinkConnected(plink)) { + errlogPrintf("calcout: %s.INP%c in no-vo disco state\n", + prec->name, i+'A'); + } + } + } + + prec->clcv = postfix(prec->calc, prec->rpcl, &error_number); + if (prec->clcv){ + recGblRecordError(S_db_badField, (void *)prec, + "calcout: init_record: Illegal CALC field"); + errlogPrintf("%s.CALC: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->calc); + } + + prec->oclv = postfix(prec->ocal, prec->orpc, &error_number); + if (prec->dopt == calcoutDOPT_Use_OVAL && prec->oclv){ + recGblRecordError(S_db_badField, (void *)prec, + "calcout: init_record: Illegal OCAL field"); + errlogPrintf("%s.OCAL: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->ocal); + } + + prpvt = prec->rpvt; + callbackSetCallback(checkLinksCallback, &prpvt->checkLinkCb); + callbackSetPriority(0, &prpvt->checkLinkCb); + callbackSetUser(prec, &prpvt->checkLinkCb); + prpvt->cbScheduled = 0; + + prec->epvt = eventNameToHandle(prec->oevt); + + if (pcalcoutDSET->init_record) pcalcoutDSET->init_record(prec); + prec->pval = prec->val; + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + prec->povl = prec->oval; + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct calcoutRecord *prec = (struct calcoutRecord *)pcommon; + rpvtStruct *prpvt = prec->rpvt; + int doOutput; + + if (!prec->pact) { + prec->pact = TRUE; + /* if some links are CA, check connections */ + if (prpvt->caLinkStat != NO_CA_LINKS) { + checkLinks(prec); + } + if (fetch_values(prec) == 0) { + if (calcPerform(&prec->a, &prec->val, prec->rpcl)) { + recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM); + } else { + prec->udf = isnan(prec->val); + } + } + checkAlarms(prec); + /* check for output link execution */ + switch (prec->oopt) { + case calcoutOOPT_Every_Time: + doOutput = 1; + break; + case calcoutOOPT_On_Change: + doOutput = ! (fabs(prec->pval - prec->val) <= prec->mdel); + break; + case calcoutOOPT_Transition_To_Zero: + doOutput = ((prec->pval != 0.0) && (prec->val == 0.0)); + break; + case calcoutOOPT_Transition_To_Non_zero: + doOutput = ((prec->pval == 0.0) && (prec->val != 0.0)); + break; + case calcoutOOPT_When_Zero: + doOutput = (prec->val == 0.0); + break; + case calcoutOOPT_When_Non_zero: + doOutput = (prec->val != 0.0); + break; + default: + doOutput = 0; + break; + } + prec->pval = prec->val; + if (doOutput) { + if (prec->odly > 0.0) { + prec->dlya = 1; + recGblGetTimeStamp(prec); + db_post_events(prec, &prec->dlya, DBE_VALUE); + callbackRequestProcessCallbackDelayed(&prpvt->doOutCb, + prec->prio, prec, (double)prec->odly); + return 0; + } else { + prec->pact = FALSE; + execOutput(prec); + if (prec->pact) return 0; + prec->pact = TRUE; + } + } + recGblGetTimeStamp(prec); + } else { /* pact == TRUE */ + if (prec->dlya) { + prec->dlya = 0; + recGblGetTimeStamp(prec); + db_post_events(prec, &prec->dlya, DBE_VALUE); + /* Make pact FALSE for asynchronous device support*/ + prec->pact = FALSE; + execOutput(prec); + if (prec->pact) return 0; + prec->pact = TRUE; + } else {/*Device Support is asynchronous*/ + writeValue(prec); + recGblGetTimeStamp(prec); + } + } + monitor(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + rpvtStruct *prpvt = prec->rpvt; + short error_number; + int fieldIndex = dbGetFieldIndex(paddr); + int lnkIndex; + DBLINK *plink; + double *pvalue; + epicsEnum16 *plinkValid; + + if (!after) return 0; + switch(fieldIndex) { + case(calcoutRecordCALC): + prec->clcv = postfix(prec->calc, prec->rpcl, &error_number); + if (prec->clcv){ + recGblRecordError(S_db_badField, (void *)prec, + "calcout: special(): Illegal CALC field"); + errlogPrintf("%s.CALC: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->calc); + } + db_post_events(prec, &prec->clcv, DBE_VALUE); + return 0; + + case(calcoutRecordOCAL): + prec->oclv = postfix(prec->ocal, prec->orpc, &error_number); + if (prec->dopt == calcoutDOPT_Use_OVAL && prec->oclv){ + recGblRecordError(S_db_badField, (void *)prec, + "calcout: special(): Illegal OCAL field"); + errlogPrintf("%s.OCAL: %s in expression \"%s\"\n", + prec->name, calcErrorStr(error_number), prec->ocal); + } + db_post_events(prec, &prec->oclv, DBE_VALUE); + return 0; + case(calcoutRecordINPA): + case(calcoutRecordINPB): + case(calcoutRecordINPC): + case(calcoutRecordINPD): + case(calcoutRecordINPE): + case(calcoutRecordINPF): + case(calcoutRecordINPG): + case(calcoutRecordINPH): + case(calcoutRecordINPI): + case(calcoutRecordINPJ): + case(calcoutRecordINPK): + case(calcoutRecordINPL): + case(calcoutRecordOUT): + lnkIndex = fieldIndex - calcoutRecordINPA; + plink = &prec->inpa + lnkIndex; + pvalue = &prec->a + lnkIndex; + plinkValid = &prec->inav + lnkIndex; + + if (fieldIndex != calcoutRecordOUT) + recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); + + if (dbLinkIsConstant(plink)) { + db_post_events(prec, pvalue, DBE_VALUE); + *plinkValid = calcoutINAV_CON; + } else if (dbLinkIsVolatile(plink)) { + int conn = dbIsLinkConnected(plink); + + if (conn) + *plinkValid = calcoutINAV_EXT; + else { + /* Monitor for connection */ + *plinkValid = calcoutINAV_EXT_NC; + /* DO_CALLBACK, if not already scheduled */ + if (!prpvt->cbScheduled) { + callbackRequestDelayed(&prpvt->checkLinkCb, .5); + prpvt->cbScheduled = 1; + prpvt->caLinkStat = CA_LINKS_NOT_OK; + } + } + } + else { + /* PV must reside on this ioc */ + *plinkValid = calcoutINAV_LOC; + + if (!dbIsLinkConnected(plink)) { + errlogPrintf("calcout: %s.INP%c in no-vo diso state\n", + prec->name, lnkIndex); + } + } + db_post_events(prec, plinkValid, DBE_VALUE); + return 0; + case(calcoutRecordOEVT): + prec->epvt = eventNameToHandle(prec->oevt); + return 0; + default: + recGblDbaddrError(S_db_badChoice, paddr, "calc: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) calcoutRecord##field + +static long get_linkNumber(int fieldIndex) { + if (fieldIndex >= indexof(A) && fieldIndex <= indexof(L)) + return fieldIndex - indexof(A); + if (fieldIndex >= indexof(LA) && fieldIndex <= indexof(LL)) + return fieldIndex - indexof(LA); + return -1; +} + +static long get_units(DBADDR *paddr, char *units) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + if(fieldIndex == indexof(ODLY)) { + strcpy(units, "s"); + return 0; + } + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + linkNumber = get_linkNumber(dbGetFieldIndex(paddr)); + if (linkNumber >= 0) + dbGetUnits(&prec->inpa + linkNumber, units, DB_UNITS_SIZE); + else + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *pprecision) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + if (fieldIndex == indexof(ODLY)) { + *pprecision = calcoutODLYprecision; + return 0; + } + + *pprecision = prec->prec; + if (fieldIndex == indexof(VAL)) + return 0; + + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + short precision; + + if (dbGetPrecision(&prec->inpa + linkNumber, &precision) == 0) + *pprecision = precision; + } else + recGblGetPrec(paddr, pprecision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + switch (fieldIndex) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->lower_disp_limit = prec->lopr; + pgd->upper_disp_limit = prec->hopr; + break; + case indexof(ODLY): + recGblGetGraphicDouble(paddr,pgd); + pgd->lower_disp_limit = 0.0; + break; + default: + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetGraphicLimits(&prec->inpa + linkNumber, + &pgd->lower_disp_limit, + &pgd->upper_disp_limit); + } else + recGblGetGraphicDouble(paddr,pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pcd->lower_ctrl_limit = prec->lopr; + pcd->upper_ctrl_limit = prec->hopr; + break; + case indexof(ODLY): + pcd->lower_ctrl_limit = 0.0; + pcd->upper_ctrl_limit = calcoutODLYlimit; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return 0; +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + calcoutRecord *prec = (calcoutRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + if (fieldIndex == indexof(VAL)) { + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else { + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetAlarmLimits(&prec->inpa + linkNumber, + &pad->lower_alarm_limit, + &pad->lower_warning_limit, + &pad->upper_warning_limit, + &pad->upper_alarm_limit); + } else + recGblGetAlarmDouble(paddr, pad); + } + return 0; +} + +static void checkAlarms(calcoutRecord *prec) +{ + double val, hyst, lalm; + double alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static void execOutput(calcoutRecord *prec) +{ + /* Determine output data */ + switch(prec->dopt) { + case calcoutDOPT_Use_VAL: + prec->oval = prec->val; + break; + case calcoutDOPT_Use_OVAL: + if (calcPerform(&prec->a, &prec->oval, prec->orpc)) { + recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM); + } else { + prec->udf = isnan(prec->oval); + } + break; + } + if (prec->udf){ + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + } + + /* Check to see what to do if INVALID */ + if (prec->nsev < INVALID_ALARM ) { + /* Output the value */ + writeValue(prec); + /* post output event if set */ + if (prec->epvt) postEvent(prec->epvt); + } else switch (prec->ivoa) { + case menuIvoaContinue_normally: + writeValue(prec); + /* post output event if set */ + if (prec->epvt) postEvent(prec->epvt); + break; + case menuIvoaDon_t_drive_outputs: + break; + case menuIvoaSet_output_to_IVOV: + prec->oval = prec->ivov; + writeValue(prec); + /* post output event if set */ + if (prec->epvt) postEvent(prec->epvt); + break; + default: + recGblRecordError(S_db_badField, (void *)prec, + "calcout:process Illegal IVOA field"); + } +} + +static void monitor(calcoutRecord *prec) +{ + unsigned monitor_mask; + double *pnew; + double *pprev; + int i; + + monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec, &prec->val, monitor_mask); + } + + /* check all input fields for changes*/ + for (i = 0, pnew = &prec->a, pprev = &prec->la; ipovl != prec->oval) { + db_post_events(prec, &prec->oval, monitor_mask|DBE_VALUE|DBE_LOG); + prec->povl = prec->oval; + } + return; +} + +static int fetch_values(calcoutRecord *prec) +{ + DBLINK *plink; /* structure of the link field */ + double *pvalue; + long status = 0; + int i; + + for (i = 0, plink = &prec->inpa, pvalue = &prec->a; irpvt; + + dbScanLock((dbCommon *)prec); + prpvt->cbScheduled = 0; + checkLinks(prec); + dbScanUnlock((dbCommon *)prec); + +} + +static void checkLinks(calcoutRecord *prec) +{ + + DBLINK *plink; + rpvtStruct *prpvt = prec->rpvt; + int i; + int stat; + int caLink = 0; + int caLinkNc = 0; + epicsEnum16 *plinkValid; + + if (calcoutRecDebug) printf("checkLinks() for %p\n", prec); + + plink = &prec->inpa; + plinkValid = &prec->inav; + + for (i = 0; icaLinkStat = CA_LINKS_NOT_OK; + else if (caLink) + prpvt->caLinkStat = CA_LINKS_ALL_OK; + else + prpvt->caLinkStat = NO_CA_LINKS; + + if (!prpvt->cbScheduled && caLinkNc) { + /* Schedule another CALLBACK */ + prpvt->cbScheduled = 1; + callbackRequestDelayed(&prpvt->checkLinkCb, .5); + } +} + +static long writeValue(calcoutRecord *prec) +{ + calcoutDSET *pcalcoutDSET = (calcoutDSET *)prec->dset; + + + if (!pcalcoutDSET || !pcalcoutDSET->write) { + errlogPrintf("%s DSET write does not exist\n", prec->name); + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + prec->pact = TRUE; + return(-1); + } + return pcalcoutDSET->write(prec); +} diff --git a/modules/database/src/std/rec/calcoutRecord.dbd b/modules/database/src/std/rec/calcoutRecord.dbd new file mode 100644 index 000000000..5bf2e54de --- /dev/null +++ b/modules/database/src/std/rec/calcoutRecord.dbd @@ -0,0 +1,530 @@ +#************************************************************************* +# Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(calcoutOOPT) { + choice(calcoutOOPT_Every_Time,"Every Time") + choice(calcoutOOPT_On_Change,"On Change") + choice(calcoutOOPT_When_Zero,"When Zero") + choice(calcoutOOPT_When_Non_zero,"When Non-zero") + choice(calcoutOOPT_Transition_To_Zero,"Transition To Zero") + choice(calcoutOOPT_Transition_To_Non_zero,"Transition To Non-zero") +} +menu(calcoutDOPT) { + choice(calcoutDOPT_Use_VAL,"Use CALC") + choice(calcoutDOPT_Use_OVAL,"Use OCAL") +} +menu(calcoutINAV) { + choice(calcoutINAV_EXT_NC,"Ext PV NC") + choice(calcoutINAV_EXT,"Ext PV OK") + choice(calcoutINAV_LOC,"Local PV") + choice(calcoutINAV_CON,"Constant") +} +recordtype(calcout) { + include "dbCommon.dbd" + field(RPVT,DBF_NOACCESS) { + prompt("Record Private") + special(SPC_NOMOD) + interest(4) + extra("struct rpvtStruct *rpvt") + } + field(VAL,DBF_DOUBLE) { + prompt("Result") + promptgroup("50 - Output") + asl(ASL0) + } + field(PVAL,DBF_DOUBLE) { + prompt("Previous Value") + } + field(CALC,DBF_STRING) { + prompt("Calculation") + promptgroup("30 - Action") + special(SPC_CALC) + pp(TRUE) + size(80) + initial("0") + } + field(CLCV,DBF_LONG) { + prompt("CALC Valid") + interest(1) + } + field(INPA,DBF_INLINK) { + prompt("Input A") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPB,DBF_INLINK) { + prompt("Input B") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPC,DBF_INLINK) { + prompt("Input C") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPD,DBF_INLINK) { + prompt("Input D") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPE,DBF_INLINK) { + prompt("Input E") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPF,DBF_INLINK) { + prompt("Input F") + special(SPC_MOD) + promptgroup("41 - Input A-F") + interest(1) + } + field(INPG,DBF_INLINK) { + prompt("Input G") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(INPH,DBF_INLINK) { + prompt("Input H") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(INPI,DBF_INLINK) { + prompt("Input I") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(INPJ,DBF_INLINK) { + prompt("Input J") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(INPK,DBF_INLINK) { + prompt("Input K") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(INPL,DBF_INLINK) { + prompt("Input L") + special(SPC_MOD) + promptgroup("42 - Input G-L") + interest(1) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + special(SPC_MOD) + promptgroup("50 - Output") + interest(1) + } + field(INAV,DBF_MENU) { + prompt("INPA PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INBV,DBF_MENU) { + prompt("INPB PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INCV,DBF_MENU) { + prompt("INPC PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INDV,DBF_MENU) { + prompt("INPD PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INEV,DBF_MENU) { + prompt("INPE PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INFV,DBF_MENU) { + prompt("INPF PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INGV,DBF_MENU) { + prompt("INPG PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INHV,DBF_MENU) { + prompt("INPH PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INIV,DBF_MENU) { + prompt("INPI PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INJV,DBF_MENU) { + prompt("INPJ PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INKV,DBF_MENU) { + prompt("INPK PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(INLV,DBF_MENU) { + prompt("INPL PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + initial("1") + } + field(OUTV,DBF_MENU) { + prompt("OUT PV Status") + special(SPC_NOMOD) + interest(1) + menu(calcoutINAV) + } + field(OOPT,DBF_MENU) { + prompt("Output Execute Opt") + promptgroup("50 - Output") + interest(1) + menu(calcoutOOPT) + } + field(ODLY,DBF_DOUBLE) { + prompt("Output Execute Delay") + promptgroup("50 - Output") + asl(ASL0) + interest(1) + } + field(DLYA,DBF_USHORT) { + prompt("Output Delay Active") + special(SPC_NOMOD) + asl(ASL0) + } + field(DOPT,DBF_MENU) { + prompt("Output Data Opt") + promptgroup("30 - Action") + interest(1) + menu(calcoutDOPT) + } + field(OCAL,DBF_STRING) { + prompt("Output Calculation") + promptgroup("30 - Action") + special(SPC_CALC) + pp(TRUE) + size(80) + initial("0") + } + field(OCLV,DBF_LONG) { + prompt("OCAL Valid") + interest(1) + } + field(OEVT,DBF_STRING) { + prompt("Event To Issue") + promptgroup("30 - Action") + special(SPC_MOD) + asl(ASL0) + size(40) + } + %#include "dbScan.h" + field(EPVT, DBF_NOACCESS) { + prompt("Event private") + special(SPC_NOMOD) + interest(4) + extra("EVENTPVT epvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_DOUBLE) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Rng") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(A,DBF_DOUBLE) { + prompt("Value of Input A") + pp(TRUE) + } + field(B,DBF_DOUBLE) { + prompt("Value of Input B") + pp(TRUE) + } + field(C,DBF_DOUBLE) { + prompt("Value of Input C") + pp(TRUE) + } + field(D,DBF_DOUBLE) { + prompt("Value of Input D") + pp(TRUE) + } + field(E,DBF_DOUBLE) { + prompt("Value of Input E") + pp(TRUE) + } + field(F,DBF_DOUBLE) { + prompt("Value of Input F") + pp(TRUE) + } + field(G,DBF_DOUBLE) { + prompt("Value of Input G") + pp(TRUE) + } + field(H,DBF_DOUBLE) { + prompt("Value of Input H") + pp(TRUE) + } + field(I,DBF_DOUBLE) { + prompt("Value of Input I") + pp(TRUE) + } + field(J,DBF_DOUBLE) { + prompt("Value of Input J") + pp(TRUE) + } + field(K,DBF_DOUBLE) { + prompt("Value of Input K") + pp(TRUE) + } + field(L,DBF_DOUBLE) { + prompt("Value of Input L") + pp(TRUE) + } + field(OVAL,DBF_DOUBLE) { + prompt("Output Value") + asl(ASL0) + } + field(LA,DBF_DOUBLE) { + prompt("Prev Value of A") + special(SPC_NOMOD) + interest(3) + } + field(LB,DBF_DOUBLE) { + prompt("Prev Value of B") + special(SPC_NOMOD) + interest(3) + } + field(LC,DBF_DOUBLE) { + prompt("Prev Value of C") + special(SPC_NOMOD) + interest(3) + } + field(LD,DBF_DOUBLE) { + prompt("Prev Value of D") + special(SPC_NOMOD) + interest(3) + } + field(LE,DBF_DOUBLE) { + prompt("Prev Value of E") + special(SPC_NOMOD) + interest(3) + } + field(LF,DBF_DOUBLE) { + prompt("Prev Value of F") + special(SPC_NOMOD) + interest(3) + } + field(LG,DBF_DOUBLE) { + prompt("Prev Value of G") + special(SPC_NOMOD) + interest(3) + } + field(LH,DBF_DOUBLE) { + prompt("Prev Value of H") + special(SPC_NOMOD) + interest(3) + } + field(LI,DBF_DOUBLE) { + prompt("Prev Value of I") + special(SPC_NOMOD) + interest(3) + } + field(LJ,DBF_DOUBLE) { + prompt("Prev Value of J") + special(SPC_NOMOD) + interest(3) + } + field(LK,DBF_DOUBLE) { + prompt("Prev Value of K") + special(SPC_NOMOD) + interest(3) + } + field(LL,DBF_DOUBLE) { + prompt("Prev Value of L") + special(SPC_NOMOD) + interest(3) + } + field(POVL,DBF_DOUBLE) { + prompt("Prev Value of OVAL") + asl(ASL0) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + %#include "postfix.h" + field(RPCL,DBF_NOACCESS) { + prompt("Reverse Polish Calc") + special(SPC_NOMOD) + interest(4) + extra("char rpcl[INFIX_TO_POSTFIX_SIZE(80)]") + } + field(ORPC,DBF_NOACCESS) { + prompt("Reverse Polish OCalc") + special(SPC_NOMOD) + interest(4) + extra("char orpc[INFIX_TO_POSTFIX_SIZE(80)]") + } +} + +variable(calcoutODLYprecision, int) +variable(calcoutODLYlimit, double) diff --git a/modules/database/src/std/rec/compressRecord.c b/modules/database/src/std/rec/compressRecord.c new file mode 100644 index 000000000..4b4de5f15 --- /dev/null +++ b/modules/database/src/std/rec/compressRecord.c @@ -0,0 +1,504 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Bob Dalesio + * Date: 7-14-89 + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "dbStaticLib.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "special.h" +#include "recSup.h" +#include "recGbl.h" + +#define GEN_SIZE_OFFSET +#include "compressRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +#define indexof(field) compressRecord##field + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +static long put_array_info(DBADDR *, long); +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +#define get_alarm_double NULL + +rset compressRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,compressRSET); + + +static void reset(compressRecord *prec) +{ + prec->nuse = 0; + prec->off = 0; + prec->inx = 0; + prec->cvb = 0.0; + prec->res = 0; + /* allocate memory for the summing buffer for conversions requiring it */ + if (prec->alg == compressALG_Average && prec->sptr == NULL) { + prec->sptr = calloc(prec->nsam, sizeof(double)); + } + + if (prec->bptr && prec->nsam) + memset(prec->bptr, 0, prec->nsam * sizeof(double)); +} + +static void monitor(compressRecord *prec) +{ + unsigned short alarm_mask = recGblResetAlarms(prec); + unsigned short monitor_mask = alarm_mask | DBE_LOG | DBE_VALUE; + + if (alarm_mask || prec->nuse != prec->ouse) { + db_post_events(prec, &prec->nuse, monitor_mask); + prec->ouse = prec->nuse; + } + db_post_events(prec, prec->bptr, monitor_mask); +} + +static void put_value(compressRecord *prec, double *psource, int n) +{ + int fifo = (prec->balg == bufferingALG_FIFO); + epicsUInt32 offset = prec->off; + epicsUInt32 nuse = prec->nuse; + epicsUInt32 nsam = prec->nsam; + + nuse += n; + if (nuse > nsam) + nuse = nsam; + + while (n--) { + /* for LIFO, decrement before */ + if (!fifo) + offset = (offset - 1) % nsam; + + prec->bptr[offset] = *psource++; + + /* for FIFO, increment after */ + if (fifo) + offset = (offset + 1) % nsam; + } + + prec->off = offset; + prec->nuse = nuse; +} + + +/* qsort comparison function (for median calculation) */ +static int compare(const void *arg1, const void *arg2) +{ + double a = *(double *)arg1; + double b = *(double *)arg2; + + if ( a < b ) return -1; + else if ( a == b ) return 0; + else return 1; +} + +static int compress_array(compressRecord *prec, + double *psource, int no_elements) +{ + epicsInt32 i,j; + epicsInt32 n, nnew; + epicsInt32 nsam = prec->nsam; + double value; + + /* skip out of limit data */ + if (prec->ilil < prec->ihil) { + while (((*psource < prec->ilil) || (*psource > prec->ihil)) + && (no_elements > 0)) { + no_elements--; + psource++; + } + } + if (prec->n <= 0) + prec->n = 1; + n = prec->n; + if (no_elements < n) + return 1; /*dont do anything*/ + + /* determine number of samples to take */ + if (no_elements < nsam * n) + nnew = (no_elements / n); + else nnew = nsam; + + /* compress according to specified algorithm */ + switch (prec->alg){ + case compressALG_N_to_1_Low_Value: + /* compress N to 1 keeping the lowest value */ + for (i = 0; i < nnew; i++) { + value = *psource++; + for (j = 1; j < n; j++, psource++) { + if (value > *psource) + value = *psource; + } + put_value(prec, &value, 1); + } + break; + case compressALG_N_to_1_High_Value: + /* compress N to 1 keeping the highest value */ + for (i = 0; i < nnew; i++){ + value = *psource++; + for (j = 1; j < n; j++, psource++) { + if (value < *psource) + value = *psource; + } + put_value(prec, &value, 1); + } + break; + case compressALG_N_to_1_Average: + /* compress N to 1 keeping the average value */ + for (i = 0; i < nnew; i++) { + value = 0; + for (j = 0; j < n; j++, psource++) + value += *psource; + value /= n; + put_value(prec, &value, 1); + } + break; + + case compressALG_N_to_1_Median: + /* compress N to 1 keeping the median value */ + /* note: sorts source array (OK; it's a work pointer) */ + for (i = 0; i < nnew; i++, psource += nnew) { + qsort(psource, n, sizeof(double), compare); + value = psource[n / 2]; + put_value(prec, &value, 1); + } + break; + } + return 0; +} + +static int array_average(compressRecord *prec, + double *psource, epicsInt32 no_elements) +{ + epicsInt32 i; + epicsInt32 nnow; + epicsInt32 nsam=prec->nsam; + double *psum; + double multiplier; + epicsInt32 inx = prec->inx; + epicsInt32 nuse, n; + + nuse = nsam; + if (nuse > no_elements) + nuse = no_elements; + nnow = nuse; + if (nnow > no_elements) + nnow=no_elements; + psum = (double *)prec->sptr; + + /* add in the new waveform */ + if (inx == 0) { + for (i = 0; i < nnow; i++) + *psum++ = *psource++; + for (i = nnow; i < nuse; i++) + *psum++ = 0; + } else { + for (i = 0; i < nnow; i++) + *psum++ += *psource++; + } + + /* do we need to calculate the result */ + inx++; + if (prec->n <= 0) + prec->n = 1; + n = prec->n; + if (inx < n) { + prec->inx = inx; + return 1; + } + if (n > 1) { + psum = (double *)prec->sptr; + multiplier = 1.0 / n; + for (i = 0; i < nuse; i++, psum++) + *psum = *psum * multiplier; + } + put_value(prec, prec->sptr, nuse); + prec->inx = 0; + return 0; +} + +static int compress_scalar(struct compressRecord *prec,double *psource) +{ + double value = *psource; + double *pdest=&prec->cvb; + epicsInt32 inx = prec->inx; + + /* compress according to specified algorithm */ + switch (prec->alg) { + case (compressALG_N_to_1_Low_Value): + if ((value < *pdest) || (inx == 0)) + *pdest = value; + break; + case (compressALG_N_to_1_High_Value): + if ((value > *pdest) || (inx == 0)) + *pdest = value; + break; + /* for scalars, Median not implemented => use average */ + case (compressALG_N_to_1_Average): + case (compressALG_N_to_1_Median): + if (inx == 0) + *pdest = value; + else { + *pdest += value; + if (inx + 1 >= prec->n) + *pdest = *pdest / (inx + 1); + } + break; + } + inx++; + if (inx >= prec->n) { + put_value(prec,pdest,1); + prec->inx = 0; + return 0; + } else { + prec->inx = inx; + return 1; + } +} + +/*Beginning of record support routines*/ +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct compressRecord *prec = (struct compressRecord *)pcommon; + if (pass == 0) { + if (prec->nsam < 1) + prec->nsam = 1; + prec->bptr = calloc(prec->nsam, sizeof(double)); + reset(prec); + } + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct compressRecord *prec = (struct compressRecord *)pcommon; + long status = 0; + long nelements = 0; + int alg = prec->alg; + + prec->pact = TRUE; + if (!dbIsLinkConnected(&prec->inp) || + dbGetNelements(&prec->inp, &nelements) || + nelements <= 0) { + recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + } + else { + if (!prec->wptr || nelements != prec->inpn) { + if (prec->wptr) { + free(prec->wptr); + reset(prec); + } + prec->wptr = dbCalloc(nelements, sizeof(double)); + prec->inpn = nelements; + } + status = dbGetLink(&prec->inp, DBF_DOUBLE, prec->wptr, 0, &nelements); + if (status || nelements <= 0) { + recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + status = 0; + } + else if (alg == compressALG_Average) { + status = array_average(prec, prec->wptr, nelements); + } + else if (alg == compressALG_Circular_Buffer) { + put_value(prec, prec->wptr, nelements); + status = 0; + } + else if (nelements > 1) { + status = compress_array(prec, prec->wptr, nelements); + } + else if (nelements == 1){ + status = compress_scalar(prec, prec->wptr); + } + else + status = 1; + } + + /* check event list */ + if (status != 1) { + prec->udf = FALSE; + recGblGetTimeStamp(prec); + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + } + + prec->pact = FALSE; + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + int special_type = paddr->special; + + if (!after) + return 0; + + if (special_type == SPC_RESET) { + reset(prec); + return 0; + } + + recGblDbaddrError(S_db_badChoice, paddr, "compress: special"); + return S_db_badChoice; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->nsam; + paddr->field_type = DBF_DOUBLE; + paddr->field_size = sizeof(double); + paddr->dbr_field_type = DBF_DOUBLE; + + if (prec->balg == bufferingALG_LIFO) + paddr->special = SPC_NOMOD; + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + /* offset indicates the next element which would be written. + * In FIFO mode offset-1 is the last valid element + * In LIFO mode offset is the first valid element + * (*offset) should be set to the index of the first valid element + */ + compressRecord *prec = (compressRecord *) paddr->precord; + epicsUInt32 off = prec->off; + epicsUInt32 nuse = prec->nuse; + epicsUInt32 nsam = prec->nsam; + + *no_elements = nuse; + if (prec->balg == bufferingALG_FIFO) { + *offset = (off - nuse) % nsam; + } else { + *offset = off; + } + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + if (prec->balg == bufferingALG_FIFO) + prec->off = (prec->off + nNew) % prec->nsam; + prec->nuse += nNew; + if (prec->nuse > prec->nsam) + prec->nuse = prec->nsam; + return 0; +} + +static long get_units(DBADDR *paddr, char *units) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + if (paddr->pfldDes->field_type == DBF_DOUBLE || + dbGetFieldIndex(paddr) == indexof(VAL)) { + strncpy(units, prec->egu, DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + *precision = prec->prec; + if (dbGetFieldIndex(paddr) != indexof(VAL)) + recGblGetPrec(paddr,precision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(IHIL): + case indexof(ILIL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + compressRecord *prec = (compressRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(IHIL): + case indexof(ILIL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + default: + recGblGetControlDouble(paddr, pcd); + } + return 0; +} diff --git a/modules/database/src/std/rec/compressRecord.dbd.pod b/modules/database/src/std/rec/compressRecord.dbd.pod new file mode 100644 index 000000000..265cdfe8a --- /dev/null +++ b/modules/database/src/std/rec/compressRecord.dbd.pod @@ -0,0 +1,199 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=title Compress Record (compress) + +The data compression record is used to collect and compress data from arrays. +When the INP field references a data array field, it immediately compresses the +entire array into an element of an array using one of several algorithms, +overwriting the previous element. If the INP field obtains its value from a +scalar-value field, the compression record will collect a new sample each time +the record is processed and add it to the compressed data array as a circular +buffer. + +The INP link can also specify a constant; however, if this is the case, the +compression algorithms are ignored, and the record support routines merely +return after checking the FLNK field. + +=head2 Record-specific Menus + +=head3 Menu compressALG + +The ALG field which uses this menu controls the compression algorithm used by +the record. + +=menu compressALG + +=head3 Menu bufferingALG + +The BALG field which uses this menu controls whether new values are inserted at +the beginning or the end of the VAL array. + +=menu bufferingALG + + +=head2 Parameter Fields + +The record-specific fields are described below. + +=recordtype compress + +... + +=cut + +menu(compressALG) { + choice(compressALG_N_to_1_Low_Value,"N to 1 Low Value") + choice(compressALG_N_to_1_High_Value,"N to 1 High Value") + choice(compressALG_N_to_1_Average,"N to 1 Average") + choice(compressALG_Average,"Average") + choice(compressALG_Circular_Buffer,"Circular Buffer") + choice(compressALG_N_to_1_Median,"N to 1 Median") +} +menu(bufferingALG) { + choice(bufferingALG_FIFO, "FIFO Buffer") + choice(bufferingALG_LIFO, "LIFO Buffer") +} +recordtype(compress) { + +=fields VAL + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_NOACCESS) { + prompt("Value") + asl(ASL0) + special(SPC_DBADDR) + pp(TRUE) + extra("void * val") + #=type DOUBLE[] + #=read Yes + #=write Yes + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(RES,DBF_SHORT) { + prompt("Reset") + asl(ASL0) + special(SPC_RESET) + interest(3) + } + field(ALG,DBF_MENU) { + prompt("Compression Algorithm") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + menu(compressALG) + } + field(BALG,DBF_MENU) { + prompt("Buffering Algorithm") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + menu(bufferingALG) + } + field(NSAM,DBF_ULONG) { + prompt("Number of Values") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(N,DBF_ULONG) { + prompt("N to 1 Compression") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + initial("1") + } + field(IHIL,DBF_DOUBLE) { + prompt("Init High Interest Lim") + promptgroup("30 - Action") + interest(1) + } + field(ILIL,DBF_DOUBLE) { + prompt("Init Low Interest Lim") + promptgroup("30 - Action") + interest(1) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(OFF,DBF_ULONG) { + prompt("Offset") + special(SPC_NOMOD) + } + field(NUSE,DBF_ULONG) { + prompt("Number Used") + special(SPC_NOMOD) + } + field(OUSE,DBF_ULONG) { + prompt("Old Number Used") + special(SPC_NOMOD) + } + field(BPTR,DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + interest(4) + extra("double *bptr") + } + field(SPTR,DBF_NOACCESS) { + prompt("Summing Buffer Ptr") + special(SPC_NOMOD) + interest(4) + extra("double *sptr") + } + field(WPTR,DBF_NOACCESS) { + prompt("Working Buffer Ptr") + special(SPC_NOMOD) + interest(4) + extra("double *wptr") + } + field(INPN,DBF_LONG) { + prompt("Number of elements in Working Buffer") + special(SPC_NOMOD) + interest(4) + } + field(CVB,DBF_DOUBLE) { + prompt("Compress Value Buffer") + special(SPC_NOMOD) + interest(3) + } + field(INX,DBF_ULONG) { + prompt("Compressed Array Inx") + special(SPC_NOMOD) + interest(3) + } +} diff --git a/modules/database/src/std/rec/dfanoutRecord.c b/modules/database/src/std/rec/dfanoutRecord.c new file mode 100644 index 000000000..4672fc413 --- /dev/null +++ b/modules/database/src/std/rec/dfanoutRecord.c @@ -0,0 +1,321 @@ +/*************************************************************************\ +* Copyright (c) 2002 Southeastern Universities Research Association, as +* Operator of Thomas Jefferson National Accelerator Facility. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recDfanout.c - Record Support Routines for Dfanout records */ +/* + * Original Author: Matt Bickley (Sometime in 1994) + * + * Modification Log: + * ----------------- + * .01 1994 mhb Started with longout record to make the data fanout + * .02 May 10, 96 jt Bug Fix + * .03 11SEP2000 mrk LONG=>DOUBLE, add SELL,SELN,SELM + */ + + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "epicsMath.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuOmsl.h" + +#define GEN_SIZE_OFFSET +#include "dfanoutRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *,struct dbr_grDouble *); +static long get_control_double(DBADDR *,struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *,struct dbr_alDouble *); + +rset dfanoutRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,dfanoutRSET); + + +static void checkAlarms(dfanoutRecord *); +static void monitor(dfanoutRecord *); +static void push_values(dfanoutRecord *); + +#define OUT_ARG_MAX 8 + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct dfanoutRecord *prec = (struct dfanoutRecord *)pcommon; + if (pass==0) + return 0; + + recGblInitConstantLink(&prec->sell, DBF_USHORT, &prec->seln); + + /* get the initial value dol is a constant*/ + if (recGblInitConstantLink(&prec->dol, DBF_DOUBLE, &prec->val)) + prec->udf = isnan(prec->val); + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct dfanoutRecord *prec = (struct dfanoutRecord *)pcommon; + long status=0; + + if (!prec->pact && + !dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + status = dbGetLink(&prec->dol, DBR_DOUBLE, &prec->val, 0, 0); + if (!dbLinkIsConstant(&prec->dol) && !status) + prec->udf = isnan(prec->val); + } + prec->pact = TRUE; + recGblGetTimeStamp(prec); + /* Push out the data to all the forward links */ + dbGetLink(&(prec->sell),DBR_USHORT,&(prec->seln),0,0); + checkAlarms(prec); + push_values(prec); + monitor(prec); + recGblFwdLink(prec); + prec->pact=FALSE; + return(status); +} + +#define indexof(field) dfanoutRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + dfanoutRecord *prec=(dfanoutRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + +static long get_precision(const DBADDR *paddr,long *precision) +{ + dfanoutRecord *prec=(dfanoutRecord *)paddr->precord; + + *precision = prec->prec; + if (dbGetFieldIndex(paddr) == indexof(VAL)) return(0); + recGblGetPrec(paddr,precision); + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + dfanoutRecord *prec=(dfanoutRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + dfanoutRecord *prec=(dfanoutRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} +static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad) +{ + dfanoutRecord *prec=(dfanoutRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)) { + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(dfanoutRecord *prec) +{ + double val, hyst, lalm; + double alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static void monitor(dfanoutRecord *prec) +{ + unsigned monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + + return; +} + +static void push_values(dfanoutRecord *prec) +{ + struct link *plink; /* structure of the link field */ + int i; + long status; + unsigned short state; + + switch (prec->selm){ + case (dfanoutSELM_All): + for(i=0, plink=&(prec->outa); ival),1); + if(status) recGblSetSevr(prec,LINK_ALARM,MAJOR_ALARM); + } + break; + case (dfanoutSELM_Specified): + if(prec->seln>OUT_ARG_MAX) { + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + break; + } + if(prec->seln==0) break; + plink=&(prec->outa); + plink += (prec->seln -1); + status=dbPutLink(plink,DBR_DOUBLE,&(prec->val),1); + if(status) recGblSetSevr(prec,LINK_ALARM,MAJOR_ALARM); + break; + case (dfanoutSELM_Mask): + if(prec->seln==0) break; + for(i=0, plink=&(prec->outa), state=prec->seln; + i>=1) { + if(state&1) { + status=dbPutLink(plink,DBR_DOUBLE,&(prec->val),1); + if(status) recGblSetSevr(prec,LINK_ALARM,MAJOR_ALARM); + } + } + break; + default: + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + } + +} diff --git a/modules/database/src/std/rec/dfanoutRecord.dbd b/modules/database/src/std/rec/dfanoutRecord.dbd new file mode 100644 index 000000000..c2eb42a75 --- /dev/null +++ b/modules/database/src/std/rec/dfanoutRecord.dbd @@ -0,0 +1,204 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(dfanoutSELM) { + choice(dfanoutSELM_All,"All") + choice(dfanoutSELM_Specified,"Specified") + choice(dfanoutSELM_Mask,"Mask") +} +recordtype(dfanout) { + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Desired Output") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(SELM,DBF_MENU) { + prompt("Select Mechanism") + promptgroup("30 - Action") + interest(1) + menu(dfanoutSELM) + } + field(SELN,DBF_USHORT) { + prompt("Link Selection") + interest(1) + initial("1") + } + field(SELL,DBF_INLINK) { + prompt("Link Selection Loc") + promptgroup("30 - Action") + interest(1) + } + field(OUTA,DBF_OUTLINK) { + prompt("Output Spec A") + promptgroup("50 - Output") + interest(1) + } + field(OUTB,DBF_OUTLINK) { + prompt("Output Spec B") + promptgroup("50 - Output") + interest(1) + } + field(OUTC,DBF_OUTLINK) { + prompt("Output Spec C") + promptgroup("50 - Output") + interest(1) + } + field(OUTD,DBF_OUTLINK) { + prompt("Output Spec D") + promptgroup("50 - Output") + interest(1) + } + field(OUTE,DBF_OUTLINK) { + prompt("Output Spec E") + promptgroup("50 - Output") + interest(1) + } + field(OUTF,DBF_OUTLINK) { + prompt("Output Spec F") + promptgroup("50 - Output") + interest(1) + } + field(OUTG,DBF_OUTLINK) { + prompt("Output Spec G") + promptgroup("50 - Output") + interest(1) + } + field(OUTH,DBF_OUTLINK) { + prompt("Output Spec H") + promptgroup("50 - Output") + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } +} diff --git a/modules/database/src/std/rec/eventRecord.c b/modules/database/src/std/rec/eventRecord.c new file mode 100644 index 000000000..dbaef0114 --- /dev/null +++ b/modules/database/src/std/rec/eventRecord.c @@ -0,0 +1,219 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recEvent.c - Record Support Routines for Event records */ +/* + * Author: Janet Anderson + * Date: 12-13-91 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbScan.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "eventRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset eventRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,eventRSET); + +struct eventdset { /* event input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_event;/*(0)=> success */ +}; +static void monitor(eventRecord *); +static long readValue(eventRecord *); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct eventRecord *prec = (struct eventRecord *)pcommon; + struct eventdset *pdset; + long status=0; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_STRING, &prec->sval); + + if( (pdset=(struct eventdset *)(prec->dset)) && (pdset->init_record) ) + status=(*pdset->init_record)(prec); + + prec->epvt = eventNameToHandle(prec->val); + + return(status); +} + +static long process(struct dbCommon *pcommon) +{ + struct eventRecord *prec = (struct eventRecord *)pcommon; + struct eventdset *pdset = (struct eventdset *)(prec->dset); + long status=0; + unsigned char pact=prec->pact; + + if((pdset!=NULL) && (pdset->number >= 5) && pdset->read_event ) + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + postEvent(prec->epvt); + + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + + +static long special(DBADDR *paddr, int after) +{ + eventRecord *prec = (eventRecord *)paddr->precord; + + if (dbGetFieldIndex(paddr) == eventRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + + if (!after) return 0; + + if (dbGetFieldIndex(paddr) == eventRecordVAL) { + prec->epvt = eventNameToHandle(prec->val); + } + + return 0; +} + + +static void monitor(eventRecord *prec) +{ + unsigned short monitor_mask; + + /* get previous stat and sevr and new stat and sevr*/ + monitor_mask = recGblResetAlarms(prec); + db_post_events(prec,&prec->val,monitor_mask|DBE_VALUE); + return; +} + +static long readValue(eventRecord *prec) +{ + struct eventdset *pdset = (struct eventdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_event(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_STRING, &prec->sval, 0, 0); + if (status == 0) { + if (strcmp(prec->sval, prec->val) != 0) { + strcpy(prec->val, prec->sval); + prec->epvt = eventNameToHandle(prec->val); + } + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/eventRecord.dbd b/modules/database/src/std/rec/eventRecord.dbd new file mode 100644 index 000000000..57306f5f5 --- /dev/null +++ b/modules/database/src/std/rec/eventRecord.dbd @@ -0,0 +1,82 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(event) { + include "dbCommon.dbd" + field(VAL,DBF_STRING) { + prompt("Event Name To Post") + promptgroup("40 - Input") + special(SPC_MOD) + asl(ASL0) + size(40) + } + %#include "dbScan.h" + field(EPVT, DBF_NOACCESS) { + prompt("Event private") + special(SPC_NOMOD) + interest(4) + extra("EVENTPVT epvt") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_STRING) { + prompt("Simulation Value") + size(40) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/fanoutRecord.c b/modules/database/src/std/rec/fanoutRecord.c new file mode 100644 index 000000000..3ac6b66c6 --- /dev/null +++ b/modules/database/src/std/rec/fanoutRecord.c @@ -0,0 +1,157 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Bob Dalesio + * Date: 12-20-88 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "epicsTypes.h" +#include "recSup.h" +#include "recGbl.h" +#include "dbCommon.h" + +#define GEN_SIZE_OFFSET +#include "fanoutRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +#define NLINKS 16 + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset fanoutRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,fanoutRSET); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct fanoutRecord *prec = (struct fanoutRecord *)pcommon; + if (pass == 0) + return 0; + + recGblInitConstantLink(&prec->sell, DBF_USHORT, &prec->seln); + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct fanoutRecord *prec = (struct fanoutRecord *)pcommon; + struct link *plink; + epicsUInt16 seln, events; + int i; + epicsUInt16 oldn = prec->seln; + + prec->pact = TRUE; + + /* fetch link selection */ + dbGetLink(&prec->sell, DBR_USHORT, &prec->seln, 0, 0); + seln = prec->seln; + + switch (prec->selm) { + case fanoutSELM_All: + plink = &prec->lnk0; + for (i = 0; i < NLINKS; i++, plink++) { + dbScanFwdLink(plink); + } + break; + + case fanoutSELM_Specified: + i = seln + prec->offs; + if (i < 0 || i >= NLINKS) { + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + break; + } + plink = &prec->lnk0 + i; + dbScanFwdLink(plink); + break; + + case fanoutSELM_Mask: + i = prec->shft; + if (i < -15 || i > 15) { + /* Shifting by more than the number of bits in the + * value produces undefined behavior in C */ + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + break; + } + seln = (i >= 0) ? seln >> i : seln << -i; + if (seln == 0) + break; + plink = &prec->lnk0; + for (i = 0; i < NLINKS; i++, seln >>= 1, plink++) { + if (seln & 1) + dbScanFwdLink(plink); + } + break; + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + } + prec->udf = FALSE; + recGblGetTimeStamp(prec); + + /* post monitors */ + events = recGblResetAlarms(prec); + if (events) + db_post_events(prec, &prec->val, events); + if (prec->seln != oldn) + db_post_events(prec, &prec->seln, events | DBE_VALUE | DBE_LOG); + + /* finish off */ + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} diff --git a/modules/database/src/std/rec/fanoutRecord.dbd b/modules/database/src/std/rec/fanoutRecord.dbd new file mode 100644 index 000000000..251d63a11 --- /dev/null +++ b/modules/database/src/std/rec/fanoutRecord.dbd @@ -0,0 +1,129 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(fanoutSELM) { + choice(fanoutSELM_All,"All") + choice(fanoutSELM_Specified,"Specified") + choice(fanoutSELM_Mask,"Mask") +} +recordtype(fanout) { + include "dbCommon.dbd" + field(VAL,DBF_LONG) { + prompt("Used to trigger") + asl(ASL0) + pp(TRUE) + } + field(SELM,DBF_MENU) { + prompt("Select Mechanism") + promptgroup("30 - Action") + interest(1) + menu(fanoutSELM) + } + field(SELN,DBF_USHORT) { + prompt("Link Selection") + interest(1) + initial("1") + } + field(SELL,DBF_INLINK) { + prompt("Link Selection Loc") + promptgroup("30 - Action") + interest(1) + } + field(OFFS,DBF_SHORT) { + prompt("Offset for Specified") + promptgroup("30 - Action") + interest(1) + initial("0") + } + field(SHFT,DBF_SHORT) { + prompt("Shift for Mask mode") + promptgroup("30 - Action") + interest(1) + initial("-1") + } + field(LNK0,DBF_FWDLINK) { + prompt("Forward Link 0") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK1,DBF_FWDLINK) { + prompt("Forward Link 1") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK2,DBF_FWDLINK) { + prompt("Forward Link 2") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK3,DBF_FWDLINK) { + prompt("Forward Link 3") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK4,DBF_FWDLINK) { + prompt("Forward Link 4") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK5,DBF_FWDLINK) { + prompt("Forward Link 5") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK6,DBF_FWDLINK) { + prompt("Forward Link 6") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK7,DBF_FWDLINK) { + prompt("Forward Link 7") + promptgroup("51 - Output 0-7") + interest(1) + } + field(LNK8,DBF_FWDLINK) { + prompt("Forward Link 8") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNK9,DBF_FWDLINK) { + prompt("Forward Link 9") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKA,DBF_FWDLINK) { + prompt("Forward Link 10") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKB,DBF_FWDLINK) { + prompt("Forward Link 11") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKC,DBF_FWDLINK) { + prompt("Forward Link 12") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKD,DBF_FWDLINK) { + prompt("Forward Link 13") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKE,DBF_FWDLINK) { + prompt("Forward Link 14") + promptgroup("52 - Output 8-F") + interest(1) + } + field(LNKF,DBF_FWDLINK) { + prompt("Forward Link 15") + promptgroup("52 - Output 8-F") + interest(1) + } +} diff --git a/modules/database/src/std/rec/histogramRecord.c b/modules/database/src/std/rec/histogramRecord.c new file mode 100644 index 000000000..cac81ffd0 --- /dev/null +++ b/modules/database/src/std/rec/histogramRecord.c @@ -0,0 +1,490 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* histogramRecord.c - Record Support Routines for Histogram records */ +/* + * Author: Janet Anderson + * Date: 5/20/91 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "epicsPrint.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "special.h" +#include "recSup.h" +#include "recGbl.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "histogramRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +#define indexof(field) histogramRecord##field + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *paddr,long *precision); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_alarm_double NULL +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd); +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd); + +rset histogramRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,histogramRSET); + +int histogramSDELprecision = 2; +epicsExportAddress(int, histogramSDELprecision); + +struct histogramdset { /* histogram input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_histogram;/*(0,2)=> success and add_count, don't add_count)*/ + /* if add_count then sgnl added to array */ + DEVSUPFUN special_linconv; +}; + +/* control block for callback*/ +typedef struct myCallback { + CALLBACK callback; + histogramRecord *prec; +} myCallback; + +static long add_count(histogramRecord *); +static long clear_histogram(histogramRecord *); +static void monitor(histogramRecord *); +static long readValue(histogramRecord *); + + +static void wdogCallback(CALLBACK *arg) +{ + myCallback *pcallback; + histogramRecord *prec; + + callbackGetUser(pcallback, arg); + prec = pcallback->prec; + /* force post events for any count change */ + if (prec->mcnt > 0){ + dbScanLock((struct dbCommon *)prec); + recGblGetTimeStamp(prec); + db_post_events(prec, prec->bptr, DBE_VALUE | DBE_LOG); + prec->mcnt = 0; + dbScanUnlock((struct dbCommon *)prec); + } + + if (prec->sdel > 0) { + /* restart timer */ + callbackRequestDelayed(&pcallback->callback, prec->sdel); + } + + return; +} +static long wdogInit(histogramRecord *prec) +{ + myCallback *pcallback; + + if (!prec->wdog && prec->sdel > 0) { + /* initialize a callback object */ + pcallback = calloc(1, sizeof(myCallback)); + pcallback->prec = prec; + if (!pcallback) + return -1; + + callbackSetCallback(wdogCallback, &pcallback->callback); + callbackSetUser(pcallback, &pcallback->callback); + callbackSetPriority(priorityLow, &pcallback->callback); + prec->wdog = pcallback; + } + + if (!prec->wdog) + return -1; + pcallback = prec->wdog; + if (!pcallback) + return -1; + if (prec->sdel > 0) { + /* start new timer on monitor */ + callbackRequestDelayed(&pcallback->callback, prec->sdel); + } + return 0; +} + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct histogramRecord *prec = (struct histogramRecord *)pcommon; + struct histogramdset *pdset; + + if (pass == 0) { + /* allocate space for histogram array */ + if (!prec->bptr) { + if (prec->nelm <= 0) + prec->nelm = 1; + prec->bptr = calloc(prec->nelm, sizeof(epicsUInt32)); + } + + /* calulate width of array element */ + prec->wdth = (prec->ulim - prec->llim) / prec->nelm; + return 0; + } + + wdogInit(prec); + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_DOUBLE, &prec->sval); + + /* must have device support defined */ + pdset = (struct histogramdset *) prec->dset; + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "histogram: init_record"); + return S_dev_noDSET; + } + + /* must have read_histogram function defined */ + if (pdset->number < 6 || !pdset->read_histogram) { + recGblRecordError(S_dev_missingSup, prec, "histogram: init_record"); + return S_dev_missingSup; + } + + /* call device support init_record */ + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct histogramRecord *prec = (struct histogramRecord *)pcommon; + struct histogramdset *pdset = (struct histogramdset *) prec->dset; + int pact = prec->pact; + long status; + + if (!pdset || !pdset->read_histogram) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "read_histogram"); + return S_dev_missingSup; + } + + status = readValue(prec); /* read the new value */ + + /* check if device support set pact */ + if (!pact && prec->pact) + return 0; + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if (status == 0) + add_count(prec); + else if (status == 2) + status = 0; + + monitor(prec); + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; +} + +static long special(DBADDR *paddr, int after) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + if (paddr->special == SPC_MOD && dbGetFieldIndex(paddr) == histogramRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + + if (!after) + return 0; + + switch (paddr->special) { + case SPC_CALC: + if (prec->cmd <= 1) { + clear_histogram(prec); + prec->cmd = 0; + } + else if (prec->cmd == 2) { + prec->csta = TRUE; + prec->cmd = 0; + } + else if (prec->cmd == 3) { + prec->csta = FALSE; + prec->cmd = 0; + } + return 0; + + case SPC_MOD: + /* increment frequency in histogram array */ + add_count(prec); + return 0; + + case SPC_RESET: + if (dbGetFieldIndex(paddr) == histogramRecordSDEL) { + wdogInit(prec); + } + else { + prec->wdth = (prec->ulim - prec->llim) / prec->nelm; + clear_histogram(prec); + } + return 0; + + default: + recGblDbaddrError(S_db_badChoice, paddr, "histogram: special"); + return S_db_badChoice; + } +} + +static void monitor(histogramRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + /* post events for count change */ + if (prec->mcnt > prec->mdel){ + monitor_mask |= DBE_VALUE | DBE_LOG; + /* reset counts since monitor */ + prec->mcnt = 0; + } + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, prec->bptr, monitor_mask); + + return; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->nelm; + paddr->field_type = DBF_ULONG; + paddr->field_size = sizeof(epicsUInt32); + paddr->dbr_field_type = DBF_ULONG; + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + *no_elements = prec->nelm; + *offset = 0; + return 0; +} + +static long add_count(histogramRecord *prec) +{ + double temp; + epicsUInt32 *pdest; + int i; + + if (prec->csta == FALSE) + return 0; + + if (prec->llim >= prec->ulim) { + if (prec->nsev < INVALID_ALARM) { + prec->stat = SOFT_ALARM; + prec->sevr = INVALID_ALARM; + return -1; + } + } + if (prec->sgnl < prec->llim || + prec->sgnl >= prec->ulim) + return 0; + + temp = prec->sgnl - prec->llim; + for (i = 1; i <= prec->nelm; i++){ + if (temp <= (double) i * prec->wdth) + break; + } + pdest = prec->bptr + i - 1; + if (*pdest == (epicsUInt32) UINT_MAX) + *pdest = 0; + (*pdest)++; + prec->mcnt++; + + return 0; +} + +static long clear_histogram(histogramRecord *prec) +{ + int i; + + for (i = 0; i < prec->nelm; i++) + prec->bptr[i] = 0; + prec->mcnt = prec->mdel + 1; + prec->udf = FALSE; + + return 0; +} + +static long readValue(histogramRecord *prec) +{ + struct histogramdset *pdset = (struct histogramdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_histogram(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_DOUBLE, &prec->sval, 0, 0); + if (status == 0) { + prec->sgnl = prec->sval; + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} + +static long get_units(DBADDR *paddr, char *units) +{ + if (dbGetFieldIndex(paddr) == indexof(SDEL)) { + strcpy(units,"s"); + } + /* We should have EGU for other DOUBLE values or probably get it from input link SVL */ + return 0; +} + +static long get_precision(const DBADDR *paddr,long *precision) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(ULIM): + case indexof(LLIM): + case indexof(SGNL): + case indexof(SVAL): + case indexof(WDTH): + *precision = prec->prec; + break; + case indexof(SDEL): + *precision = histogramSDELprecision; + break; + default: + recGblGetPrec(paddr,precision); + } + return 0; +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + case indexof(WDTH): + pgd->upper_disp_limit = prec->ulim - prec->llim; + pgd->lower_disp_limit = 0.0; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return 0; +} +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + histogramRecord *prec = (histogramRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + case indexof(WDTH): + pcd->upper_ctrl_limit = prec->ulim - prec->llim; + pcd->lower_ctrl_limit = 0.0; + break; + default: + recGblGetControlDouble(paddr, pcd); + } + return 0; +} diff --git a/modules/database/src/std/rec/histogramRecord.dbd b/modules/database/src/std/rec/histogramRecord.dbd new file mode 100644 index 000000000..545ae3fcc --- /dev/null +++ b/modules/database/src/std/rec/histogramRecord.dbd @@ -0,0 +1,173 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(histogramCMD) { + choice(histogramCMD_Read,"Read") + choice(histogramCMD_Clear,"Clear") + choice(histogramCMD_Start,"Start") + choice(histogramCMD_Stop,"Stop") +} +recordtype(histogram) { + include "dbCommon.dbd" + field(VAL,DBF_NOACCESS) { + prompt("Value") + asl(ASL0) + special(SPC_DBADDR) + extra("void * val") + #=type ULONG[] + #=read Yes + #=write Yes + } + field(NELM,DBF_USHORT) { + prompt("Num of Array Elements") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(CSTA,DBF_SHORT) { + prompt("Collection Status") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(CMD,DBF_MENU) { + prompt("Collection Control") + asl(ASL0) + special(SPC_CALC) + interest(1) + menu(histogramCMD) + } + field(ULIM,DBF_DOUBLE) { + prompt("Upper Signal Limit") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + prop(YES) + } + field(LLIM,DBF_DOUBLE) { + prompt("Lower Signal Limit ") + promptgroup("30 - Action") + special(SPC_RESET) + interest(1) + prop(YES) + } + field(WDTH,DBF_DOUBLE) { + prompt("Element Width") + special(SPC_NOMOD) + interest(3) + } + field(SGNL,DBF_DOUBLE) { + prompt("Signal Value") + special(SPC_MOD) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(SVL,DBF_INLINK) { + prompt("Signal Value Location") + promptgroup("40 - Input") + interest(1) + } + field(BPTR,DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + interest(4) + extra("epicsUInt32 *bptr") + } + field(WDOG,DBF_NOACCESS) { + prompt("Watchdog callback") + special(SPC_NOMOD) + interest(4) + extra("void * wdog") + } + field(MDEL,DBF_SHORT) { + prompt("Monitor Count Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MCNT,DBF_SHORT) { + prompt("Counts Since Monitor") + special(SPC_NOMOD) + interest(3) + } + field(SDEL,DBF_DOUBLE) { + prompt("Monitor Seconds Dband") + promptgroup("80 - Display") + special(SPC_RESET) + interest(1) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_DOUBLE) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(HOPR,DBF_ULONG) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_ULONG) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } +} + +variable(histogramSDELprecision, int) diff --git a/modules/database/src/std/rec/int64inRecord.c b/modules/database/src/std/rec/int64inRecord.c new file mode 100644 index 000000000..865d05fbb --- /dev/null +++ b/modules/database/src/std/rec/int64inRecord.c @@ -0,0 +1,441 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* int64inRecord.c - Record Support Routines for int64in records */ +/* + * Original Author: Janet Anderson + * Date: 9/23/91 + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "int64inRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + + /* Hysterisis for alarm filtering: 1-1/e */ +#define THRESHOLD 0.6321 +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(dbCommon *, int); +static long process(dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset int64inRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,int64inRSET); + + +struct int64indset { /* int64in input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_int64in; /*returns: (-1,0)=>(failure,success)*/ +}; +static void checkAlarms(int64inRecord *prec, epicsTimeStamp *timeLast); +static void monitor(int64inRecord *prec); +static long readValue(int64inRecord *prec); + + +static long init_record(dbCommon *pcommon, int pass) +{ + int64inRecord *prec = (int64inRecord*)pcommon; + struct int64indset *pdset; + long status; + + if (pass == 0) return 0; + + /* int64in.siml and .siol must be a CONSTANT or a PV_LINK or a DB_LINK */ + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_INT64, &prec->sval); + + if(!(pdset = (struct int64indset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"int64in: init_record"); + return(S_dev_noDSET); + } + /* must have read_int64in function defined */ + if( (pdset->number < 5) || (pdset->read_int64in == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"int64in: init_record"); + return(S_dev_missingSup); + } + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return(0); +} + +static long process(dbCommon *pcommon) +{ + int64inRecord *prec = (int64inRecord*)pcommon; + struct int64indset *pdset = (struct int64indset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + epicsTimeStamp timeLast; + + if( (pdset==NULL) || (pdset->read_int64in==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_int64in"); + return(S_dev_missingSup); + } + timeLast = prec->time; + + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if (status==0) prec->udf = FALSE; + + /* check for alarms */ + checkAlarms(prec, &timeLast); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + int64inRecord *prec = (int64inRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == int64inRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "int64in: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) int64inRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_LONG) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + case indexof(SVAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + case indexof(SVAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)){ + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(int64inRecord *prec, epicsTimeStamp *timeLast) +{ + enum { + range_Lolo = 1, + range_Low, + range_Normal, + range_High, + range_Hihi + } alarmRange; + static const epicsEnum16 range_stat[] = { + SOFT_ALARM, LOLO_ALARM, LOW_ALARM, + NO_ALARM, HIGH_ALARM, HIHI_ALARM + }; + + double aftc, afvl; + epicsInt64 val, hyst, lalm; + epicsInt64 alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + prec->afvl = 0; + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* check VAL against alarm limits */ + if ((asev = prec->hhsv) && + (val >= (alev = prec->hihi) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_Hihi; + else + if ((asev = prec->llsv) && + (val <= (alev = prec->lolo) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Lolo; + else + if ((asev = prec->hsv) && + (val >= (alev = prec->high) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_High; + else + if ((asev = prec->lsv) && + (val <= (alev = prec->low) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Low; + else { + alev = val; + asev = NO_ALARM; + alarmRange = range_Normal; + } + + aftc = prec->aftc; + afvl = 0; + + if (aftc > 0) { + /* Apply level filtering */ + afvl = prec->afvl; + if (afvl == 0) { + afvl = (double)alarmRange; + } else { + double t = epicsTimeDiffInSeconds(&prec->time, timeLast); + double alpha = aftc / (t + aftc); + + /* The sign of afvl indicates whether the result should be + * rounded up or down. This gives the filter hysteresis. + * If afvl > 0 the floor() function rounds to a lower alarm + * level, otherwise to a higher. + */ + afvl = alpha * afvl + + ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange; + if (afvl - floor(afvl) > THRESHOLD) + afvl = -afvl; /* reverse rounding */ + + alarmRange = abs((int)floor(afvl)); + switch (alarmRange) { + case range_Hihi: + asev = prec->hhsv; + alev = prec->hihi; + break; + case range_High: + asev = prec->hsv; + alev = prec->high; + break; + case range_Normal: + asev = NO_ALARM; + break; + case range_Low: + asev = prec->lsv; + alev = prec->low; + break; + case range_Lolo: + asev = prec->llsv; + alev = prec->lolo; + break; + } + } + } + prec->afvl = afvl; + + if (asev) { + /* Report alarm condition, store LALM for future HYST calculations */ + if (recGblSetSevr(prec, range_stat[alarmRange], asev)) + prec->lalm = alev; + } else { + /* No alarm condition, reset LALM */ + prec->lalm = val; + } +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 32-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt32) ((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) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt32) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long readValue(int64inRecord *prec) +{ + struct int64indset *pdset = (struct int64indset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_int64in(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_INT64, &prec->sval, 0, 0); + if (status == 0) { + prec->val = prec->sval; + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/int64inRecord.dbd.pod b/modules/database/src/std/rec/int64inRecord.dbd.pod new file mode 100644 index 000000000..c85992f05 --- /dev/null +++ b/modules/database/src/std/rec/int64inRecord.dbd.pod @@ -0,0 +1,555 @@ +#************************************************************************* +# Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=title 64bit Integer Input Record (int64in) + +This record type is normally used to obtain an integer value of up to 64 bits +from a hardware input. +The record supports alarm limits, alarm filtering, graphics and control +limits. + +=head2 Parameter Fields + +The record-specific fields are described below. + +=recordtype int64in + +=cut + +recordtype(int64in) { + +=head3 Input Specification + +These fields control where the record will read data from when it is processed: + +=fields DTYP, INP + +The DTYP field selects which device support layer should be responsible for +providing input data to the record. +The int64in device support layers provided by EPICS Base are documented in the +L section. +External support modules may provide additional device support for this record +type. +If not set explicitly, the DTYP value defaults to the first device support that +is loaded for the record type, which will usually be the C support +that comes with Base. + +The INP link field contains a database or channel access link or provides +hardware address information that the device support uses to determine where the +input data should come from. +The format for the INP field value depends on the device support layer that is +selected by the DTYP field. +See L
for a description of the various hardware +address formats supported. + +=head3 Operator Display Parameters + +These parameters are used to present meaningful data to the operator. +They do not affect the functioning of the record. + +=over + +=item * +DESC is a string that is usually used to briefly describe the record. + +=item * +EGU is a string of up to 16 characters naming the engineering units +that the VAL field represents. + +=item * +The HOPR and LOPR fields set the upper and lower display limits for the VAL, +HIHI, HIGH, LOW, and LOLO fields. + +=back + +=fields DESC, EGU, HOPR, LOPR + +=head3 Alarm Limits + +The user configures limit alarms by putting numerical values into the HIHI, +HIGH, LOW and LOLO fields, and by setting the associated alarm severity in the +corresponding HHSV, HSV, LSV and LLSV menu fields. + +The HYST field controls hysteresis to prevent alarm chattering from an input +signal that is close to one of the limits and suffers from significant readout +noise. + +The AFTC field sets the time constant on a low-pass filter that delays the +reporting of limit alarms until the signal has been within the alarm range for +that number of seconds (the default AFTC value of zero retains the previous +behavior). + +The LALM field is used by the record at run-time to implement the alarm limit +functionality. + +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC, LALM + +=head3 Monitor Parameters + +These parameters are used to determine when to send monitors placed on the VAL +field. +The monitors are sent when the current value exceeds the last transmitted value +by the appropriate deadband. +If these fields are set to zero, a monitor will be triggered every time the +value changes; if set to -1, a monitor will be sent every time the record is +processed. + +The ADEL field sets the deadband for archive monitors (C events), while +the MDEL field controls value monitors (C events). + +The remaining fields are used by the record at run-time to implement the record +monitoring deadband functionality. + +=fields ADEL, MDEL, ALST, MLST + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_INT64) { + prompt("Current value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Units name") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_INT64) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_INT64) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_INT64) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_INT64) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_INT64) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_INT64) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_INT64) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(AFTC, DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup("70 - Alarm") + interest(1) + } + field(AFVL, DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(ADEL,DBF_INT64) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_INT64) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_INT64) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_INT64) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_INT64) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + +=head3 Simulation Mode + +The record provides several fields to support simulation of absent hardware. +If the SIML field is set it is used to read a value into the SIMM field, which +controls whether simulation is used or not: + +=over + +=item * +SIMM must be zero (C) for the record to request a value from the device +support. + +=item * +If SIMM is C and the SIOL link field is set, a simulated value in +engineering units is read using the link into the SVAL field, from where it will +subsequently be copied into the VAL field. + +=back + +The SIMS field can be set to give the record an alarm severity while it is in +simulation mode. + +=fields SIML, SIMM, SIOL, SVAL, SIMS + +=cut + + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_INT64) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} + +=head2 Record Support + +=head3 Record Support Routines + +The following are the record support routines that would be of interest +to an application developer. +Other routines are the C, C, +C and C routines, which are used to +collect properties from the record for the complex DBR data structures. + +=head4 init_record + +This routine first initializes the simulation mode mechanism by setting SIMM +if SIML is a constant, and setting SVAL if SIOL is a constant. + +It then checks if the device support and the device support's +C routine are defined. +If either one does not exist, an error message is issued +and processing is terminated. + +If device support includes C, it is called. + +Finally, the deadband mechanisms for monitors and level alarms are +initialized. + +=head4 process + +See next section. + +=head3 Record Processing + +Routine C implements the following algorithm: + +=over + +=item 1. + +Check to see that the appropriate device support module and its +C routine are defined. +If either one does not exist, an error message is issued and processing is +terminated with the PACT field set to TRUE, effectively blocking the record +to avoid error storms. + +=item 2. + +Determine the value: + +If PACT is TRUE, call the device support C routine and return. + +If PACT is FALSE, read the value, honoring simulation mode: + +=over + +=item * Get SIMM by reading the SIML link. + +=item * If SIMM is C, +call the device support C routine and return. + +=item * If SIMM is C, +read the simulated value into SVAL using the SIOL link, +then copy the value into VAL and set UDF to 0 on success. + +=item * Raise an alarm for other values of SIMM. + +=item * Set the record to the severity configured in SIMS. + +=back + +=item 3. + +If PACT has been changed to TRUE, the device support signals asynchronous +processing: its C output routine has started, but not +completed reading the new value. +In this case, the processing routine merely returns, leaving PACT TRUE. + +=item 4. + +Set PACT to TRUE. Get the processing time stamp. Set UDF to 0 if reading +the value was successful. + +=item 5. + +Check UDF and level alarms: This routine checks to see if the record is +undefined (UDF is TRUE) or if the new VAL causes the alarm status +and severity to change. In the latter case, NSEV, NSTA and LALM are set. +It also honors the alarm hysteresis factor (HYST): the value must change +by at least HYST between level alarm status and severity changes. +If AFTC is set, alarm level filtering is applied. + +=item 6. + +Check to see if monitors should be invoked: + +=over + +=item * Alarm monitors are posted if the alarm status or severity have +changed. + +=item * Archive and value change monitors are posted if ADEL and MDEL +conditions (see L) are met. + +=back + +=item 7. + +Scan (process) forward link if necessary, set PACT to FALSE, and return. + +=back + +=head2 Device Support + +=head3 Device Support Interface + +The record requires device support to provide an entry table (dset) which +defines the following members: + + typedef struct { + long number; + long (*report)(int level); + long (*init)(int after); + long (*init_record)(int64inRecord *prec); + long (*get_ioint_info)(int cmd, int64inRecord *prec, IOSCANPVT *piosl); + long (*read_int64in)(int64inRecord *prec); + } int64indset; + +The module must set C to at least 5, and provide a pointer to its +C routine; the other function pointers may be C if their +associated functionality is not required for this support layer. +Most device supports also provide an C routine to configure the +record instance and connect it to the hardware or driver support layer. + +The individual routines are described below. + +=head3 Device Support Routines + +=head4 long report(int level) + +This optional routine is called by the IOC command C and is passed the +report level that was requested by the user. +It should print a report on the state of the device support to stdout. +The C parameter may be used to output increasingly more detailed +information at higher levels, or to select different types of information with +different levels. +Level zero should print no more than a small summary. + +=head4 long init(int after) + +This optional routine is called twice at IOC initialization time. +The first call happens before any of the C calls are made, with +the integer parameter C set to 0. +The second call happens after all of the C calls have been made, +with C set to 1. + +=head4 long init_record(int64inRecord *prec) + +This optional routine is called by the record initialization code for each +int64in record instance that has its DTYP field set to use this device support. +It is normally used to check that the INP address is the expected type and that +it points to a valid device; to allocate any record-specific buffer space and +other memory; and to connect any communication channels needed for the +C routine to work properly. + +=head4 long get_ioint_info(int cmd, int64inRecord *prec, IOSCANPVT *piosl) + +This optional routine is called whenever the record's SCAN field is being +changed to or from the value C to find out which I/O Interrupt Scan +list the record should be added to or deleted from. +If this routine is not provided, it will not be possible to set the SCAN field +to the value C at all. + +The C parameter is zero when the record is being added to the scan list, +and one when it is being removed from the list. +The routine must determine which interrupt source the record should be connected +to, which it indicates by the scan list that it points the location at C<*piosl> +to before returning. +It can prevent the SCAN field from being changed at all by returning a non-zero +value to its caller. + +In most cases the device support will create the I/O Interrupt Scan lists that +it returns for itself, by calling C once for +each separate interrupt source. +That routine allocates memory and inializes the list, then passes back a pointer +to the new list in the location at C<*piosl>. + +When the device support receives notification that the interrupt has occurred, +it announces that to the IOC by calling C +which will arrange for the appropriate records to be processed in a suitable +thread. +The C routine is safe to call from an interrupt service routine +on embedded architectures (vxWorks and RTEMS). + +=head4 long read_int64in(int64inRecord *prec) + +This essential routine is called when the record wants a new value from the +addressed device. +It is responsible for performing (or at least initiating) a read operation, and +(eventually) returning its value to the record. + +If the device may take more than a few microseconds to return the new value, +this routine must never block (busy-wait), but use the asynchronous +processing mechanism. +In that case it signals the asynchronous operation by setting the record's +PACT field to TRUE before it returns, having arranged for the record's +C routine to be called later once the read operation is finished. +When that happens, the C routine will be called again with +PACT still set to TRUE; it should then set it to FALSE to indicate the read +has completed, and return. + +A return value of zero indicates success, any other value indicates that an +error occurred. + +=head3 Extended Device Support + +... + +=cut + +=head2 Device Support For Soft Records + +Two soft device support modules, Soft Channel and Soft Callback Channel, are +provided for input records not related to actual hardware devices. The +INP link type must be either a CONSTANT, DB_LINK, or CA_LINK. + +=head3 Soft Channel + +This module reads the value using the record's INP link. + +C calls C to read the value. + +=head3 Soft Callback Channel + +This module is like the previous except that it reads the value +using asynchronous processing that will not complete until an asynchronous +processing of the INP target record has completed. + +=cut diff --git a/modules/database/src/std/rec/int64outRecord.c b/modules/database/src/std/rec/int64outRecord.c new file mode 100644 index 000000000..da5a76d8f --- /dev/null +++ b/modules/database/src/std/rec/int64outRecord.c @@ -0,0 +1,426 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Janet Anderson + * Date: 9/23/91 + */ +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" +#include "menuIvoa.h" +#include "menuOmsl.h" + +#define GEN_SIZE_OFFSET +#include "int64outRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(dbCommon *, int); +static long process(dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset int64outRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,int64outRSET); + + +struct int64outdset { /* int64out input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out;/*(-1,0)=>(failure,success*/ +}; +static void checkAlarms(int64outRecord *prec); +static void monitor(int64outRecord *prec); +static long writeValue(int64outRecord *prec); +static void convert(int64outRecord *prec, epicsInt64 value); + + +static long init_record(dbCommon *pcommon, int pass) +{ + int64outRecord *prec = (int64outRecord*)pcommon; + struct int64outdset *pdset; + long status=0; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if(!(pdset = (struct int64outdset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"int64out: init_record"); + return(S_dev_noDSET); + } + /* must have write_int64out functions defined */ + if( (pdset->number < 5) || (pdset->write_int64out == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"int64out: init_record"); + return(S_dev_missingSup); + } + if (prec->dol.type == CONSTANT) { + if(recGblInitConstantLink(&prec->dol,DBF_INT64,&prec->val)) + prec->udf=FALSE; + } + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return(0); +} + +static long process(dbCommon *pcommon) +{ + int64outRecord *prec = (int64outRecord*)pcommon; + struct int64outdset *pdset = (struct int64outdset *)(prec->dset); + long status=0; + epicsInt64 value; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->write_int64out==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_int64out"); + return(S_dev_missingSup); + } + if (!prec->pact) { + if((prec->dol.type != CONSTANT) + && (prec->omsl == menuOmslclosed_loop)) { + status = dbGetLink(&(prec->dol),DBR_INT64, + &value,0,0); + if (prec->dol.type!=CONSTANT && RTN_SUCCESS(status)) + prec->udf=FALSE; + } + else { + value = prec->val; + } + if (!status) convert(prec,value); + } + + /* check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + prec->val=prec->ivov; + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "int64out:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + int64outRecord *prec = (int64outRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == int64outRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "int64out: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) int64outRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_INT64) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + /* do not change pre drvh/drvl behavior */ + if(prec->drvh > prec->drvl) { + pcd->upper_ctrl_limit = prec->drvh; + pcd->lower_ctrl_limit = prec->drvl; + } else { + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + } + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)) { + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(int64outRecord *prec) +{ + epicsInt64 val, hyst, lalm; + epicsInt64 alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 64-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt64) ((last) > (val) ? (last) - (val) : (val) - (last))) + +static void monitor(int64outRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + if (prec->mdel < 0 || + DELTA(prec->mlst, prec->val) > (epicsUInt64) prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt64) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long writeValue(int64outRecord *prec) +{ + struct int64outdset *pdset = (struct int64outdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_int64out(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_INT64, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} + +static void convert(int64outRecord *prec, epicsInt64 value) +{ + /* check drive limits */ + if(prec->drvh > prec->drvl) { + if (value > prec->drvh) value = prec->drvh; + else if (value < prec->drvl) value = prec->drvl; + } + prec->val = value; +} diff --git a/modules/database/src/std/rec/int64outRecord.dbd.pod b/modules/database/src/std/rec/int64outRecord.dbd.pod new file mode 100644 index 000000000..f76541489 --- /dev/null +++ b/modules/database/src/std/rec/int64outRecord.dbd.pod @@ -0,0 +1,623 @@ +#************************************************************************* +# Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=title 64bit Integer Output Record (int64out) + +This record type is normally used to send an integer value of up to 64 bits +to an output device. +The record supports alarm, drive, graphics and control limits. + +=head2 Parameter Fields + +The record-specific fields are described below. + +=recordtype int64out + +=cut + +recordtype(int64out) { + +=head3 Output Value Determination + +These fields control how the record determines the value to be output when it +gets processed: + +=fields OMSL, DOL, DRVH, DRVL, VAL + +The following steps are performed in order during record processing. + +=head4 Fetch Value + +The OMSL menu field is used to determine whether the DOL link field +should be used during processing or not: + +=over + +=item * +If OMSL is C the DOL link field is not used. +The new output value is taken from the VAL field, which may have been set from +elsewhere. + +=item * +If OMSL is C the DOL link field is used to obtain a value. + +=back + +=head4 Drive Limits + +The output value is clipped to the range DRVL to DRVH inclusive, provided +that DRVH > DRVL. +The result is copied into the VAL field. + +=head3 Output Specification + +These fields control where the record will read data from when it is processed: + +=fields DTYP, OUT + +The DTYP field selects which device support layer should be responsible for +writing output data. +The int64out device support layers provided by EPICS Base are documented in the +L section. +External support modules may provide additional device support for this record +type. +If not set explicitly, the DTYP value defaults to the first device support that +is loaded for the record type, which will usually be the C support +that comes with Base. + +The OUT link field contains a database or channel access link or provides +hardware address information that the device support uses to determine where the +output data should be sent to. +The format for the OUT field value depends on the device support layer that is +selected by the DTYP field. +See L
for a description of the various hardware +address formats supported. + +=head3 Operator Display Parameters + +These parameters are used to present meaningful data to the operator. +They do not affect the functioning of the record. + +=over + +=item * +DESC is a string that is usually used to briefly describe the record. + +=item * +EGU is a string of up to 16 characters naming the engineering units +that the VAL field represents. + +=item * +The HOPR and LOPR fields set the upper and lower display limits for the VAL, +HIHI, HIGH, LOW, and LOLO fields. + +=back + +=fields DESC, EGU, HOPR, LOPR + +=head3 Alarm Limits + +The user configures limit alarms by putting numerical values into the HIHI, +HIGH, LOW and LOLO fields, and by setting the associated alarm severities +in the corresponding HHSV, HSV, LSV and LLSV menu fields. + +The HYST field controls hysteresis to prevent alarm chattering from an input +signal that is close to one of the limits and suffers from significant readout +noise. + +The LALM field is used by the record at run-time to implement the alarm limit +hysteresis functionality. + +=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, LALM + +=head3 Monitor Parameters + +These parameters are used to determine when to send monitors placed on the VAL +field. +The monitors are sent when the current value exceeds the last transmitted value +by the appropriate deadband. +If these fields are set to zero, a monitor will be triggered every time the +value changes; if set to -1, a monitor will be sent every time the record is +processed. + +The ADEL field sets the deadband for archive monitors (C events), while +the MDEL field controls value monitors (C events). + +The remaining fields are used by the record at run-time to implement the record +monitoring deadband functionality. + +=fields ADEL, MDEL, ALST, MLST + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_INT64) { + prompt("Desired Output") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(EGU,DBF_STRING) { + prompt("Units name") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(DRVH,DBF_INT64) { + prompt("Drive High Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(DRVL,DBF_INT64) { + prompt("Drive Low Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(HOPR,DBF_INT64) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_INT64) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_INT64) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_INT64) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_INT64) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_INT64) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_INT64) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_INT64) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_INT64) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_INT64) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_INT64) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_INT64) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + +=head3 Simulation Mode + +The record provides several fields to support simulation of absent hardware. +If the SIML field is set it is used to read a value into the SIMM field, +which controls whether simulation is used or not: + +=over + +=item * +SIMM must be zero (C) for the record to write a value to the device +support. + +=item * +If SIMM is C and the SIOL link field is set, the value in engineering +units is written using the link. + +=back + +The SIMS field can be set to give the record an alarm severity while it is in +simulation mode. + +=fields SIML, SIMM, SIOL, SIMS + +=cut + + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + +=head3 Invalid Alarm Output Action + +Whenever an output record is put into INVALID alarm severity, IVOA specifies +the action to take. + +=over + +=item C (default) + +Write the value. Same as if severity is lower than INVALID. + +=item C + +Do not write value. + +=item C + +Set VAL to IVOV, then write the value. + +=back + +=fields IVOA, IVOV + +=cut + + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_INT64) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } +} + +=head2 Record Support + +=head3 Record Support Routines + +The following are the record support routines that would be of interest +to an application developer. +Other routines are the C, C, +C and C routines, which are used to +collect properties from the record for the complex DBR data structures. + +=head4 init_record + +This routine first initializes the simulation mode mechanism by setting SIMM +if SIML is a constant. + +It then checks if the device support and the device support's +C routine are defined. +If either one does not exist, an error message is issued +and processing is terminated. + +If DOL is a constant, then VAL is initialized with its value and UDF is +set to FALSE. + +If device support includes C, it is called. + +Finally, the deadband mechanisms for monitors and level alarms are +initialized. + +=head4 process + +See next section. + +=head3 Record Processing + +Routine C implements the following algorithm: + +=over + +=item 1. + +Check to see that the appropriate device support module and its +C routine are defined. +If either one does not exist, an error message is issued and processing is +terminated with the PACT field set to TRUE, effectively blocking the record +to avoid error storms. + +=item 2. + +Check PACT. If PACT is FALSE, do the following: + +=over + +=item * Determine value, honoring closed loop mode: +if DOL is not a CONSTANT and OMSL is CLOSED_LOOP then get value from DOL +setting UDF to FALSE in case of success, else use the VAL field. + +=item * Call C: +if drive limits are defined then force value to be within those limits. + +=back + +=item 3. + +Check UDF and level alarms: This routine checks to see if the record is +undefined (UDF is TRUE) or if the new VAL causes the alarm status +and severity to change. In the latter case, NSEV, NSTA and LALM are set. +It also honors the alarm hysteresis factor (HYST): the value must change +by at least HYST between level alarm status and severity changes. + +=item 4. + +Check severity and write the new value. See L +for details on how invalid alarms affect output records. + +=item 5. + +If PACT has been changed to TRUE, the device support signals asynchronous +processing: its C output routine has started, but not +completed writing the new value. +In this case, the processing routine merely returns, leaving PACT TRUE. + +=item 6. + +Check to see if monitors should be invoked: + +=over + +=item * Alarm monitors are posted if the alarm status or severity have +changed. + +=item * Archive and value change monitors are posted if ADEL and MDEL +conditions (see L) are met. + +=item * NSEV and NSTA are reset to 0. + +=back + +=item 7. + +Scan (process) forward link if necessary, set PACT to FALSE, and return. + +=back + +=head2 Device Support + +=head3 Device Support Interface + +The record requires device support to provide an entry table (dset) which +defines the following members: + + typedef struct { + long number; + long (*report)(int level); + long (*init)(int after); + long (*init_record)(int64outRecord *prec); + long (*get_ioint_info)(int cmd, int64outRecord *prec, IOSCANPVT *piosl); + long (*write_int64out)(int64outRecord *prec); + } int64outdset; + +The module must set C to at least 5, and provide a pointer to its +C routine; the other function pointers may be C if their +associated functionality is not required for this support layer. +Most device supports also provide an C routine to configure the +record instance and connect it to the hardware or driver support layer. + +The individual routines are described below. + +=head3 Device Support Routines + +=head4 long report(int level) + +This optional routine is called by the IOC command C and is passed the +report level that was requested by the user. +It should print a report on the state of the device support to stdout. +The C parameter may be used to output increasingly more detailed +information at higher levels, or to select different types of information with +different levels. +Level zero should print no more than a small summary. + +=head4 long init(int after) + +This optional routine is called twice at IOC initialization time. +The first call happens before any of the C calls are made, with +the integer parameter C set to 0. +The second call happens after all of the C calls have been made, +with C set to 1. + +=head4 long init_record(int64outRecord *prec) + +This optional routine is called by the record initialization code for each +int64out record instance that has its DTYP field set to use this device support. +It is normally used to check that the OUT address is the expected type and that +it points to a valid device, to allocate any record-specific buffer space and +other memory, and to connect any communication channels needed for the +C routine to work properly. + +=head4 long get_ioint_info(int cmd, int64outRecord *prec, IOSCANPVT *piosl) + +This optional routine is called whenever the record's SCAN field is being +changed to or from the value C to find out which I/O Interrupt Scan +list the record should be added to or deleted from. +If this routine is not provided, it will not be possible to set the SCAN field +to the value C at all. + +The C parameter is zero when the record is being added to the scan list, +and one when it is being removed from the list. +The routine must determine which interrupt source the record should be connected +to, which it indicates by the scan list that it points the location at C<*piosl> +to before returning. +It can prevent the SCAN field from being changed at all by returning a non-zero +value to its caller. + +In most cases the device support will create the I/O Interrupt Scan lists that +it returns for itself, by calling C once for +each separate interrupt source. +That routine allocates memory and inializes the list, then passes back a pointer +to the new list in the location at C<*piosl>. + +When the device support receives notification that the interrupt has occurred, +it announces that to the IOC by calling C +which will arrange for the appropriate records to be processed in a suitable +thread. +The C routine is safe to call from an interrupt service routine +on embedded architectures (vxWorks and RTEMS). + +=head4 long write_int64out(int64outRecord *prec) + +This essential routine is called when the record wants to write a new value +to the addressed device. +It is responsible for performing (or at least initiating) a write operation, +using the value from the record's VAL field. + +If the device may take more than a few microseconds to accept the new value, +this routine must never block (busy-wait), but use the asynchronous +processing mechanism. +In that case it signals the asynchronous operation by setting the record's +PACT field to TRUE before it returns, having arranged for the record's +C routine to be called later once the write operation is over. +When that happens, the C routine will be called again with +PACT still set to TRUE; it should then set it to FALSE to indicate the write +has completed, and return. + +A return value of zero indicates success, any other value indicates that an +error occurred. + +=head3 Extended Device Support + +... + +=cut + +=head2 Device Support For Soft Records + +Two soft device support modules, Soft Channel and Soft Callback Channel, are +provided for output records not related to actual hardware devices. The +OUT link type must be either a CONSTANT, DB_LINK, or CA_LINK. + +=head3 Soft Channel + +This module writes the current value using the record's VAL field. + +C calls C to write the current value. + +=head3 Soft Callback Channel + +This module is like the previous except that it writes the current value +using asynchronous processing that will not complete until an asynchronous +processing of the target record has completed. + +=cut diff --git a/modules/database/src/std/rec/longinRecord.c b/modules/database/src/std/rec/longinRecord.c new file mode 100644 index 000000000..a302a3d29 --- /dev/null +++ b/modules/database/src/std/rec/longinRecord.c @@ -0,0 +1,445 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recLongin.c - Record Support Routines for Longin records */ +/* + * Author: Janet Anderson + * Date: 9/23/91 + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "longinRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + + /* Hysterisis for alarm filtering: 1-1/e */ +#define THRESHOLD 0.6321 +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset longinRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,longinRSET); + + +struct longindset { /* longin input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_longin; /*returns: (-1,0)=>(failure,success)*/ +}; +static void checkAlarms(longinRecord *prec, epicsTimeStamp *timeLast); +static void monitor(longinRecord *prec); +static long readValue(longinRecord *prec); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct longinRecord *prec = (struct longinRecord *)pcommon; + struct longindset *pdset = (struct longindset *) prec->dset; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_LONG, &prec->sval); + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "longin: init_record"); + return S_dev_noDSET; + } + + /* must have read_longin function defined */ + if ((pdset->number < 5) || (pdset->read_longin == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "longin: init_record"); + return S_dev_missingSup; + } + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct longinRecord *prec = (struct longinRecord *)pcommon; + struct longindset *pdset = (struct longindset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + epicsTimeStamp timeLast; + + if( (pdset==NULL) || (pdset->read_longin==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_longin"); + return(S_dev_missingSup); + } + timeLast = prec->time; + + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if (status==0) prec->udf = FALSE; + + /* check for alarms */ + checkAlarms(prec, &timeLast); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + longinRecord *prec = (longinRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == longinRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "longin: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) longinRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + longinRecord *prec=(longinRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_LONG) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + longinRecord *prec=(longinRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + case indexof(SVAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + longinRecord *prec=(longinRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + case indexof(SVAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + longinRecord *prec=(longinRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)){ + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(longinRecord *prec, epicsTimeStamp *timeLast) +{ + enum { + range_Lolo = 1, + range_Low, + range_Normal, + range_High, + range_Hihi + } alarmRange; + static const epicsEnum16 range_stat[] = { + SOFT_ALARM, LOLO_ALARM, LOW_ALARM, + NO_ALARM, HIGH_ALARM, HIHI_ALARM + }; + + double aftc, afvl; + epicsInt32 val, hyst, lalm; + epicsInt32 alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + prec->afvl = 0; + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* check VAL against alarm limits */ + if ((asev = prec->hhsv) && + (val >= (alev = prec->hihi) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_Hihi; + else + if ((asev = prec->llsv) && + (val <= (alev = prec->lolo) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Lolo; + else + if ((asev = prec->hsv) && + (val >= (alev = prec->high) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_High; + else + if ((asev = prec->lsv) && + (val <= (alev = prec->low) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Low; + else { + alev = val; + asev = NO_ALARM; + alarmRange = range_Normal; + } + + aftc = prec->aftc; + afvl = 0; + + if (aftc > 0) { + /* Apply level filtering */ + afvl = prec->afvl; + if (afvl == 0) { + afvl = (double)alarmRange; + } else { + double t = epicsTimeDiffInSeconds(&prec->time, timeLast); + double alpha = aftc / (t + aftc); + + /* The sign of afvl indicates whether the result should be + * rounded up or down. This gives the filter hysteresis. + * If afvl > 0 the floor() function rounds to a lower alarm + * level, otherwise to a higher. + */ + afvl = alpha * afvl + + ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange; + if (afvl - floor(afvl) > THRESHOLD) + afvl = -afvl; /* reverse rounding */ + + alarmRange = abs((int)floor(afvl)); + switch (alarmRange) { + case range_Hihi: + asev = prec->hhsv; + alev = prec->hihi; + break; + case range_High: + asev = prec->hsv; + alev = prec->high; + break; + case range_Normal: + asev = NO_ALARM; + break; + case range_Low: + asev = prec->lsv; + alev = prec->low; + break; + case range_Lolo: + asev = prec->llsv; + alev = prec->lolo; + break; + } + } + } + prec->afvl = afvl; + + if (asev) { + /* Report alarm condition, store LALM for future HYST calculations */ + if (recGblSetSevr(prec, range_stat[alarmRange], asev)) + prec->lalm = alev; + } else { + /* No alarm condition, reset LALM */ + prec->lalm = val; + } +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 32-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt32) ((last) > (val) ? (last) - (val) : (val) - (last))) + +static void monitor(longinRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + if (prec->mdel < 0 || + DELTA(prec->mlst, prec->val) > (epicsUInt32) prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt32) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long readValue(longinRecord *prec) +{ + struct longindset *pdset = (struct longindset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_longin(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_LONG, &prec->sval, 0, 0); + if (status == 0) { + prec->val = prec->sval; + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/longinRecord.dbd b/modules/database/src/std/rec/longinRecord.dbd new file mode 100644 index 000000000..f0e5a8de5 --- /dev/null +++ b/modules/database/src/std/rec/longinRecord.dbd @@ -0,0 +1,188 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(longin) { + include "dbCommon.dbd" + field(VAL,DBF_LONG) { + prompt("Current value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_LONG) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_LONG) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_LONG) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_LONG) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_LONG) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_LONG) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_LONG) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(AFTC, DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup("70 - Alarm") + interest(1) + } + field(AFVL, DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(ADEL,DBF_LONG) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_LONG) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_LONG) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_LONG) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_LONG) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_LONG) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c new file mode 100644 index 000000000..0b1d66d50 --- /dev/null +++ b/modules/database/src/std/rec/longoutRecord.c @@ -0,0 +1,428 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Janet Anderson + * Date: 9/23/91 + */ +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" +#include "menuIvoa.h" +#include "menuOmsl.h" + +#define GEN_SIZE_OFFSET +#include "longoutRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset longoutRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,longoutRSET); + + +struct longoutdset { /* longout input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_longout;/*(-1,0)=>(failure,success*/ +}; +static void checkAlarms(longoutRecord *prec); +static void monitor(longoutRecord *prec); +static long writeValue(longoutRecord *prec); +static void convert(longoutRecord *prec, epicsInt32 value); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct longoutRecord *prec = (struct longoutRecord *)pcommon; + struct longoutdset *pdset = (struct longoutdset *) prec->dset; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "longout: init_record"); + return S_dev_noDSET; + } + + /* must have write_longout functions defined */ + if ((pdset->number < 5) || (pdset->write_longout == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "longout: init_record"); + return S_dev_missingSup; + } + + if (recGblInitConstantLink(&prec->dol, DBF_LONG, &prec->val)) + prec->udf=FALSE; + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct longoutRecord *prec = (struct longoutRecord *)pcommon; + struct longoutdset *pdset = (struct longoutdset *)(prec->dset); + long status=0; + epicsInt32 value; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->write_longout==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_longout"); + return(S_dev_missingSup); + } + if (!prec->pact) { + if (!dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + status = dbGetLink(&prec->dol, DBR_LONG, &value, 0, 0); + if (!dbLinkIsConstant(&prec->dol) && !status) + prec->udf=FALSE; + } + else { + value = prec->val; + } + if (!status) convert(prec,value); + } + + /* check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + prec->val=prec->ivov; + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "longout:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + longoutRecord *prec = (longoutRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == longoutRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "longout: special"); + return(S_db_badChoice); + } +} + +#define indexof(field) longoutRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + longoutRecord *prec=(longoutRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_LONG) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + longoutRecord *prec=(longoutRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + longoutRecord *prec=(longoutRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + /* do not change pre drvh/drvl behavior */ + if(prec->drvh > prec->drvl) { + pcd->upper_ctrl_limit = prec->drvh; + pcd->lower_ctrl_limit = prec->drvl; + } else { + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + } + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad) +{ + longoutRecord *prec=(longoutRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)) { + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(longoutRecord *prec) +{ + epicsInt32 val, hyst, lalm; + epicsInt32 alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 32-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt32) ((last) > (val) ? (last) - (val) : (val) - (last))) + +static void monitor(longoutRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + if (prec->mdel < 0 || + DELTA(prec->mlst, prec->val) > (epicsUInt32) prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt32) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long writeValue(longoutRecord *prec) +{ + struct longoutdset *pdset = (struct longoutdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_longout(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_LONG, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} + +static void convert(longoutRecord *prec, epicsInt32 value) +{ + /* check drive limits */ + if(prec->drvh > prec->drvl) { + if (value > prec->drvh) value = prec->drvh; + else if (value < prec->drvl) value = prec->drvl; + } + prec->val = value; +} diff --git a/modules/database/src/std/rec/longoutRecord.dbd b/modules/database/src/std/rec/longoutRecord.dbd new file mode 100644 index 000000000..626be1645 --- /dev/null +++ b/modules/database/src/std/rec/longoutRecord.dbd @@ -0,0 +1,211 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(longout) { + include "dbCommon.dbd" + field(VAL,DBF_LONG) { + prompt("Desired Output") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(DRVH,DBF_LONG) { + prompt("Drive High Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(DRVL,DBF_LONG) { + prompt("Drive Low Limit") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + prop(YES) + } + field(HOPR,DBF_LONG) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_LONG) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_LONG) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_LONG) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_LONG) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_LONG) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_LONG) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_LONG) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_LONG) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_LONG) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_LONG) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_LONG) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_LONG) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } +} diff --git a/modules/database/src/std/rec/lsiRecord.c b/modules/database/src/std/rec/lsiRecord.c new file mode 100644 index 000000000..261313f07 --- /dev/null +++ b/modules/database/src/std/rec/lsiRecord.c @@ -0,0 +1,303 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Long String Input record type */ +/* + * Author: Andrew Johnson + * Date: 2012-11-27 + */ + +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "menuPost.h" +#include "menuYesNo.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#define GEN_SIZE_OFFSET +#include "lsiRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +static void monitor(lsiRecord *); +static long readValue(lsiRecord *); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct lsiRecord *prec = (struct lsiRecord *)pcommon; + lsidset *pdset; + + if (pass == 0) { + size_t sizv = prec->sizv; + + if (sizv < 16) { + sizv = 16; /* Enforce a minimum size for the VAL field */ + prec->sizv = sizv; + } + + prec->val = callocMustSucceed(1, sizv, "lsi::init_record"); + prec->len = 0; + prec->oval = callocMustSucceed(1, sizv, "lsi::init_record"); + prec->olen = 0; + return 0; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + pdset = (lsidset *) prec->dset; + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "lsi: init_record"); + return S_dev_noDSET; + } + + /* must have a read_string function */ + if (pdset->number < 5 || !pdset->read_string) { + recGblRecordError(S_dev_missingSup, prec, "lsi: init_record"); + return S_dev_missingSup; + } + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + + if (prec->len) { + strcpy(prec->oval, prec->val); + prec->olen = prec->len; + prec->udf = FALSE; + } + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct lsiRecord *prec = (struct lsiRecord *)pcommon; + int pact = prec->pact; + lsidset *pdset = (lsidset *) prec->dset; + long status = 0; + + if (!pdset || !pdset->read_string) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "lsi: read_string"); + return S_dev_missingSup; + } + + status = readValue(prec); /* read the new value */ + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + lsiRecord *prec = (lsiRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == lsiRecordVAL) { + paddr->pfield = prec->val; + paddr->special = SPC_MOD; + } + else if (fieldIndex == lsiRecordOVAL) { + paddr->pfield = prec->oval; + paddr->special = SPC_NOMOD; + } + else { + errlogPrintf("lsiRecord::cvt_dbaddr called for %s.%s\n", + prec->name, paddr->pfldDes->name); + return -1; + } + + paddr->no_elements = 1; + paddr->field_type = DBF_STRING; + paddr->dbr_field_type = DBF_STRING; + paddr->field_size = prec->sizv; + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + lsiRecord *prec = (lsiRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == lsiRecordVAL) + *no_elements = prec->len; + else if (fieldIndex == lsiRecordOVAL) + *no_elements = prec->olen; + else + return -1; + + *offset = 0; + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + lsiRecord *prec = (lsiRecord *) paddr->precord; + + if (nNew >= prec->sizv) + nNew = prec->sizv - 1; /* truncated string */ + if (paddr->field_type == DBF_CHAR) + prec->val[nNew] = 0; /* ensure data is terminated */ + + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + lsiRecord *prec = (lsiRecord *) paddr->precord; + int special_type = paddr->special; + + if (special_type == SPC_MOD && dbGetFieldIndex(paddr) == lsiRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + + if (!after) + return 0; + + /* We set prec->len here and not in put_array_info() + * because that does not get called if the put was + * done using a DBR_STRING type. + */ + prec->len = strlen(prec->val) + 1; + db_post_events(prec, &prec->len, DBE_VALUE | DBE_LOG); + + return 0; +} + +static void monitor(lsiRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + + if (prec->len != prec->olen || + memcmp(prec->oval, prec->val, prec->len)) { + events |= DBE_VALUE | DBE_LOG; + memcpy(prec->oval, prec->val, prec->len); + } + + if (prec->len != prec->olen) { + prec->olen = prec->len; + db_post_events(prec, &prec->len, DBE_VALUE | DBE_LOG); + } + + if (prec->mpst == menuPost_Always) + events |= DBE_VALUE; + if (prec->apst == menuPost_Always) + events |= DBE_LOG; + + if (events) + db_post_events(prec, prec->val, events); +} + +static long readValue(lsiRecord *prec) +{ + struct lsidset *pdset = (struct lsidset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_string(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLinkLS(&prec->siol, prec->val, prec->sizv, &prec->len); + if (status == 0) prec->udf = FALSE; + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} + +/* Create Record Support Entry Table */ + +#define report NULL +#define initialize NULL +/* init_record */ +/* process */ +/* special */ +#define get_value NULL +/* cvt_dbaddr */ +/* get_array_info */ +/* put_array_info */ +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset lsiRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, lsiRSET); diff --git a/modules/database/src/std/rec/lsiRecord.dbd b/modules/database/src/std/rec/lsiRecord.dbd new file mode 100644 index 000000000..689366f98 --- /dev/null +++ b/modules/database/src/std/rec/lsiRecord.dbd @@ -0,0 +1,115 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +recordtype(lsi) { + include "dbCommon.dbd" + %#include "devSup.h" + % + %/* Declare Device Support Entry Table */ + %typedef struct lsidset { + % long number; + % DEVSUPFUN report; + % DEVSUPFUN init; + % DEVSUPFUN init_record; + % DEVSUPFUN get_ioint_info; + % DEVSUPFUN read_string; + %} lsidset; + % + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("char *val") + } + field(OVAL,DBF_NOACCESS) { + prompt("Old Value") + special(SPC_DBADDR) + interest(3) + extra("char *oval") + } + field(SIZV,DBF_USHORT) { + prompt("Size of buffers") + promptgroup("40 - Input") + special(SPC_NOMOD) + interest(1) + initial("41") + } + field(LEN,DBF_ULONG) { + prompt("Length of VAL") + special(SPC_NOMOD) + } + field(OLEN,DBF_ULONG) { + prompt("Length of OVAL") + special(SPC_NOMOD) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(MPST,DBF_MENU) { + prompt("Post Value Monitors") + promptgroup("80 - Display") + interest(1) + menu(menuPost) + } + field(APST,DBF_MENU) { + prompt("Post Archive Monitors") + promptgroup("80 - Display") + interest(1) + menu(menuPost) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/lsoRecord.c b/modules/database/src/std/rec/lsoRecord.c new file mode 100644 index 000000000..dcbe1500c --- /dev/null +++ b/modules/database/src/std/rec/lsoRecord.c @@ -0,0 +1,344 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Long String Output record type */ +/* + * Author: Andrew Johnson + * Date: 2012-11-28 + */ + + +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "menuIvoa.h" +#include "menuOmsl.h" +#include "menuPost.h" +#include "menuYesNo.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#define GEN_SIZE_OFFSET +#include "lsoRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +static void monitor(lsoRecord *); +static long writeValue(lsoRecord *); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct lsoRecord *prec = (struct lsoRecord *)pcommon; + lsodset *pdset; + + if (pass == 0) { + size_t sizv = prec->sizv; + + if (sizv < 16) { + sizv = 16; /* Enforce a minimum size for the VAL field */ + prec->sizv = sizv; + } + + prec->val = callocMustSucceed(1, sizv, "lso::init_record"); + prec->len = 0; + prec->oval = callocMustSucceed(1, sizv, "lso::init_record"); + prec->olen = 0; + return 0; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + pdset = (lsodset *) prec->dset; + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "lso: init_record"); + return S_dev_noDSET; + } + + /* must have a write_string function defined */ + if (pdset->number < 5 || !pdset->write_string) { + recGblRecordError(S_dev_missingSup, prec, "lso: init_record"); + return S_dev_missingSup; + } + + dbLoadLinkLS(&prec->dol, prec->val, prec->sizv, &prec->len); + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + + if (prec->len) { + strcpy(prec->oval, prec->val); + prec->olen = prec->len; + prec->udf = FALSE; + } + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct lsoRecord *prec = (struct lsoRecord *)pcommon; + int pact = prec->pact; + lsodset *pdset = (lsodset *) prec->dset; + long status = 0; + + if (!pdset || !pdset->write_string) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "lso: write_string"); + return S_dev_missingSup; + } + + if (!pact && prec->omsl == menuOmslclosed_loop) + if (!dbGetLinkLS(&prec->dol, prec->val, prec->sizv, &prec->len)) + prec->udf = FALSE; + + if (prec->udf) + recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM); + + if (prec->nsev < INVALID_ALARM ) + status = writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case menuIvoaContinue_normally: + status = writeValue(prec); /* write the new value */ + break; + + case menuIvoaDon_t_drive_outputs: + break; + + case menuIvoaSet_output_to_IVOV: + if (!prec->pact) { + size_t size = prec->sizv - 1; + + strncpy(prec->val, prec->ivov, size); + prec->val[size] = 0; + prec->len = strlen(prec->val) + 1; + } + status = writeValue(prec); /* write the new value */ + break; + + default: + status = -1; + recGblRecordError(S_db_badField, prec, + "lso:process Bad IVOA choice"); + } + } + + /* Asynchronous if device support set pact */ + if (!pact && prec->pact) + return status; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + lsoRecord *prec = (lsoRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == lsoRecordVAL) { + paddr->pfield = prec->val; + paddr->special = SPC_MOD; + } + else if (fieldIndex == lsoRecordOVAL) { + paddr->pfield = prec->oval; + paddr->special = SPC_NOMOD; + } + else { + errlogPrintf("lsoRecord::cvt_dbaddr called for %s.%s\n", + prec->name, paddr->pfldDes->name); + return -1; + } + + paddr->no_elements = 1; + paddr->field_type = DBF_STRING; + paddr->dbr_field_type = DBF_STRING; + paddr->field_size = prec->sizv; + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + lsoRecord *prec = (lsoRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == lsoRecordVAL) + *no_elements = prec->len; + else if (fieldIndex == lsoRecordOVAL) + *no_elements = prec->olen; + else + return -1; + + *offset = 0; + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + lsoRecord *prec = (lsoRecord *) paddr->precord; + + if (nNew >= prec->sizv) + nNew = prec->sizv - 1; /* truncated string */ + if (paddr->field_type == DBF_CHAR) + prec->val[nNew] = 0; /* ensure data is terminated */ + + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + lsoRecord *prec = (lsoRecord *) paddr->precord; + int special_type = paddr->special; + + if (special_type == SPC_MOD && dbGetFieldIndex(paddr) == lsoRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + + if (!after) + return 0; + + /* We set prec->len here and not in put_array_info() + * because that does not get called if the put was + * done using a DBR_STRING type. + */ + prec->len = strlen(prec->val) + 1; + db_post_events(prec, &prec->len, DBE_VALUE | DBE_LOG); + + return 0; +} + +static void monitor(lsoRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + + if (prec->len != prec->olen || + memcmp(prec->oval, prec->val, prec->len)) { + events |= DBE_VALUE | DBE_LOG; + memcpy(prec->oval, prec->val, prec->len); + } + + if (prec->len != prec->olen) { + prec->olen = prec->len; + db_post_events(prec, &prec->len, DBE_VALUE | DBE_LOG); + } + + if (prec->mpst == menuPost_Always) + events |= DBE_VALUE; + if (prec->apst == menuPost_Always) + events |= DBE_LOG; + + if (events) + db_post_events(prec, prec->val, events); +} + +static long writeValue(lsoRecord *prec) +{ + lsodset *pdset = (lsodset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_string(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLinkLS(&prec->siol, prec->val, prec->len); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} + +/* Create Record Support Entry Table*/ + +#define report NULL +#define initialize NULL +/* init_record */ +/* process */ +/* special */ +#define get_value NULL +/* cvt_dbaddr */ +/* get_array_info */ +/* put_array_info */ +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset lsoRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, lsoRSET); diff --git a/modules/database/src/std/rec/lsoRecord.dbd b/modules/database/src/std/rec/lsoRecord.dbd new file mode 100644 index 000000000..8d3a64edc --- /dev/null +++ b/modules/database/src/std/rec/lsoRecord.dbd @@ -0,0 +1,139 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +recordtype(lso) { + include "dbCommon.dbd" + %#include "devSup.h" + % + %/* Declare Device Support Entry Table */ + %typedef struct lsodset { + % long number; + % DEVSUPFUN report; + % DEVSUPFUN init; + % DEVSUPFUN init_record; + % DEVSUPFUN get_ioint_info; + % DEVSUPFUN write_string; + %} lsodset; + % + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("char *val") + } + field(OVAL,DBF_NOACCESS) { + prompt("Previous Value") + special(SPC_DBADDR) + interest(3) + extra("char *oval") + } + field(SIZV,DBF_USHORT) { + prompt("Size of buffers") + promptgroup("50 - Output") + special(SPC_NOMOD) + interest(1) + initial("41") + } + field(LEN,DBF_ULONG) { + prompt("Length of VAL") + special(SPC_NOMOD) + } + field(OLEN,DBF_ULONG) { + prompt("Length of OVAL") + special(SPC_NOMOD) + interest(3) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Link") + promptgroup("40 - Input") + interest(1) + } + field(IVOA,DBF_MENU) { + prompt("INVALID Output Action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_STRING) { + prompt("INVALID Output Value") + promptgroup("50 - Output") + interest(2) + size(40) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(MPST,DBF_MENU) { + prompt("Post Value Monitors") + promptgroup("80 - Display") + interest(1) + menu(menuPost) + } + field(APST,DBF_MENU) { + prompt("Post Archive Monitors") + promptgroup("80 - Display") + interest(1) + menu(menuPost) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/mbbiDirectRecord.c b/modules/database/src/std/rec/mbbiDirectRecord.c new file mode 100644 index 000000000..566bbe21d --- /dev/null +++ b/modules/database/src/std/rec/mbbiDirectRecord.c @@ -0,0 +1,287 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2002 Southeastern Universities Research Association, as +* Operator of Thomas Jefferson National Accelerator Facility. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* mbbiDirectRecord.c - Record Support routines for mbboDirect records */ +/* + * Original Authors: Bob Dalesio and Matthew Needes + * Date: 10-07-93 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "menuSimm.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" + +#define GEN_SIZE_OFFSET +#include "mbbiDirectRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset mbbiDirectRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,mbbiDirectRSET); + +struct mbbidset { /* multi bit binary input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure, success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi; /*returns: (0,2)=>(success, success no convert)*/ +}; + +static void monitor(mbbiDirectRecord *); +static long readValue(mbbiDirectRecord *); + +#define NUM_BITS 16 + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct mbbiDirectRecord *prec = (struct mbbiDirectRecord *)pcommon; + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status = 0; + + if (pass == 0) return 0; + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "mbbiDirect: init_record"); + return S_dev_noDSET; + } + + if ((pdset->number < 5) || (pdset->read_mbbi == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "mbbiDirect: init_record"); + return S_dev_missingSup; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_USHORT, &prec->sval); + + /* Initialize MASK if the user set NOBT instead */ + if (prec->mask == 0 && prec->nobt <= 32) + prec->mask = ((epicsUInt64) 1u << prec->nobt) - 1; + + if (pdset->init_record) { + status = pdset->init_record(prec); + if (status == 0) { + epicsUInt16 val = prec->val; + epicsUInt8 *pBn = &prec->b0; + int i; + + /* Initialize B0 - BF from VAL */ + for (i = 0; i < NUM_BITS; i++, pBn++, val >>= 1) + *pBn = !! (val & 1); + } + } + + prec->mlst = prec->val; + prec->oraw = prec->rval; + return status; +} + +static long process(struct dbCommon *pcommon) +{ + struct mbbiDirectRecord *prec = (struct mbbiDirectRecord *)pcommon; + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status; + int pact = prec->pact; + + if ((pdset == NULL) || (pdset->read_mbbi == NULL)) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "read_mbbi"); + return S_dev_missingSup; + } + + status = readValue(prec); + + /* Done if device support set PACT */ + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if (status == 0) { + /* Convert RVAL to VAL */ + epicsUInt32 rval = prec->rval; + + if (prec->shft > 0) + rval >>= prec->shft; + + prec->val = rval; + prec->udf = FALSE; + } + else if (status == 2) + status = 0; + + if (prec->udf) + recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM); + + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long special(DBADDR *paddr, int after) +{ + mbbiDirectRecord *prec = (mbbiDirectRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == mbbiDirectRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "mbbiDirect: special"); + return(S_db_badChoice); + } +} + +static void monitor(mbbiDirectRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + epicsUInt16 vl_events = events | DBE_VALUE | DBE_LOG; + epicsUInt16 val = prec->val; + epicsUInt8 *pBn = &prec->b0; + int i; + + /* Update B0 - BF from VAL and post monitors */ + for (i = 0; i < NUM_BITS; i++, pBn++, val >>= 1) { + epicsUInt8 oBn = *pBn; + + *pBn = !! (val & 1); + if (oBn != *pBn) + db_post_events(prec, pBn, vl_events); + else if (events) + db_post_events(prec, pBn, events); + } + + if (prec->mlst != prec->val) { + events = vl_events; + prec->mlst = prec->val; + } + if (events) + db_post_events(prec, &prec->val, events); + + if (prec->oraw != prec->rval) { + db_post_events(prec, &prec->rval, vl_events); + prec->oraw = prec->rval; + } +} + +static long readValue(mbbiDirectRecord *prec) +{ + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuSimmNO: + status = pdset->read_mbbi(prec); + break; + + case menuSimmYES: + case menuSimmRAW: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_ULONG, &prec->sval, 0, 0); + if (status == 0) { + if (prec->simm == menuSimmYES) { + prec->val = prec->sval; + status = 2; /* Don't convert */ + } else { + prec->rval = prec->sval; + } + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/mbbiDirectRecord.dbd b/modules/database/src/std/rec/mbbiDirectRecord.dbd new file mode 100644 index 000000000..c7ca0f466 --- /dev/null +++ b/modules/database/src/std/rec/mbbiDirectRecord.dbd @@ -0,0 +1,183 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(mbbiDirect) { + include "dbCommon.dbd" + field(VAL,DBF_USHORT) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(NOBT,DBF_SHORT) { + prompt("Number of Bits") + promptgroup("40 - Input") + special(SPC_NOMOD) + interest(1) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("Prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(MLST,DBF_USHORT) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SHFT,DBF_USHORT) { + prompt("Shift") + promptgroup("40 - Input") + interest(1) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_ULONG) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuSimm) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(B0,DBF_UCHAR) { + prompt("Bit 0") + pp(TRUE) + interest(1) + } + field(B1,DBF_UCHAR) { + prompt("Bit 1") + pp(TRUE) + interest(1) + } + field(B2,DBF_UCHAR) { + prompt("Bit 2") + pp(TRUE) + interest(1) + } + field(B3,DBF_UCHAR) { + prompt("Bit 3") + pp(TRUE) + interest(1) + } + field(B4,DBF_UCHAR) { + prompt("Bit 4") + pp(TRUE) + interest(1) + } + field(B5,DBF_UCHAR) { + prompt("Bit 5") + pp(TRUE) + interest(1) + } + field(B6,DBF_UCHAR) { + prompt("Bit 6") + pp(TRUE) + interest(1) + } + field(B7,DBF_UCHAR) { + prompt("Bit 7") + pp(TRUE) + interest(1) + } + field(B8,DBF_UCHAR) { + prompt("Bit 8") + pp(TRUE) + interest(1) + } + field(B9,DBF_UCHAR) { + prompt("Bit 9") + pp(TRUE) + interest(1) + } + field(BA,DBF_UCHAR) { + prompt("Bit A") + pp(TRUE) + interest(1) + } + field(BB,DBF_UCHAR) { + prompt("Bit B") + pp(TRUE) + interest(1) + } + field(BC,DBF_UCHAR) { + prompt("Bit C") + pp(TRUE) + interest(1) + } + field(BD,DBF_UCHAR) { + prompt("Bit D") + pp(TRUE) + interest(1) + } + field(BE,DBF_UCHAR) { + prompt("Bit E") + pp(TRUE) + interest(1) + } + field(BF,DBF_UCHAR) { + prompt("Bit F") + pp(TRUE) + interest(1) + } +} diff --git a/modules/database/src/std/rec/mbbiRecord.c b/modules/database/src/std/rec/mbbiRecord.c new file mode 100644 index 000000000..d04161d56 --- /dev/null +++ b/modules/database/src/std/rec/mbbiRecord.c @@ -0,0 +1,425 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Bob Dalesio + * Date: 5-9-88 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "epicsMath.h" +#include "errMdef.h" +#include "menuSimm.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" + +#define GEN_SIZE_OFFSET +#include "mbbiRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Hysterisis for alarm filtering: 1-1/e */ +#define THRESHOLD 0.6321 + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +static long get_enum_str(const DBADDR *, char *); +static long get_enum_strs(const DBADDR *, struct dbr_enumStrs *); +static long put_enum_str(const DBADDR *, const char *); +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset mbbiRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,mbbiRSET); + +struct mbbidset { /* multi bit binary input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /* returns: (-1,0) => (failure, success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_mbbi;/* (0, 2) => (success, success no convert)*/ +}; + +static void checkAlarms(mbbiRecord *, epicsTimeStamp *); +static void monitor(mbbiRecord *); +static long readValue(mbbiRecord *); + +static void init_common(mbbiRecord *prec) +{ + epicsUInt32 *pstate_values = &prec->zrvl; + char *pstate_string = prec->zrst; + int i; + + /* Check if any states are defined */ + for (i = 0; i < 16; i++, pstate_string += sizeof(prec->zrst)) { + if ((pstate_values[i] != 0) || (*pstate_string != '\0')) { + prec->sdef = TRUE; + return; + } + } + prec->sdef = FALSE; +} + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct mbbiRecord *prec = (struct mbbiRecord *)pcommon; + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status = 0; + + if (pass == 0) return 0; + + pdset = (struct mbbidset *) prec->dset; + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "mbbi: init_record"); + return S_dev_noDSET; + } + + if ((pdset->number < 5) || (pdset->read_mbbi == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "mbbi: init_record"); + return S_dev_missingSup; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_USHORT, &prec->sval); + + /* Initialize MASK if the user set NOBT instead */ + if (prec->mask == 0 && prec->nobt <= 32) + prec->mask = ((epicsUInt64) 1u << prec->nobt) - 1; + + if (pdset->init_record) + status = pdset->init_record(prec); + + init_common(prec); + + prec->mlst = prec->val; + prec->lalm = prec->val; + prec->oraw = prec->rval; + return status; +} + +static long process(struct dbCommon *pcommon) +{ + struct mbbiRecord *prec = (struct mbbiRecord *)pcommon; + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status; + int pact = prec->pact; + epicsTimeStamp timeLast; + + if ((pdset == NULL) || (pdset->read_mbbi == NULL)) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "read_mbbi"); + return S_dev_missingSup; + } + + timeLast = prec->time; + + status = readValue(prec); + + /* Done if device support set PACT */ + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + if (status == 0) { + /* Convert RVAL to VAL */ + epicsUInt32 *pstate_values; + short i; + epicsUInt32 rval = prec->rval; + + prec->udf = FALSE; + if (prec->shft > 0) + rval >>= prec->shft; + + if (prec->sdef) { + pstate_values = &(prec->zrvl); + prec->val = 65535; /* Initalize to unknown state*/ + for (i = 0; i < 16; i++) { + if (*pstate_values == rval) { + prec->val = i; + break; + } + pstate_values++; + } + } + else /* No states defined, set VAL = RVAL */ + prec->val = rval; + } + else if (status == 2) + status = 0; + + checkAlarms(prec, &timeLast); + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact=FALSE; + return status; +} + +static long special(DBADDR *paddr, int after) +{ + mbbiRecord *prec = (mbbiRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + switch (paddr->special) { + case SPC_MOD: + if (fieldIndex >= mbbiRecordZRST && fieldIndex <= mbbiRecordFFST) { + int event = DBE_PROPERTY; + + if (!after) return 0; + init_common(prec); + if (prec->val == fieldIndex - mbbiRecordZRST) + event |= DBE_VALUE | DBE_LOG; + db_post_events(prec, &prec->val, event); + return 0; + } + if (fieldIndex == mbbiRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "mbbi: special"); + return S_db_badChoice; + } +} + +static long get_enum_str(const DBADDR *paddr, char *pstring) +{ + mbbiRecord *prec = (mbbiRecord *) paddr->precord; + int index; + unsigned short *pfield = paddr->pfield; + epicsEnum16 val = *pfield; + + index = dbGetFieldIndex(paddr); + if (index != mbbiRecordVAL) { + strcpy(pstring, "Illegal_Value"); + } + else if (val <= 15) { + char *pstate = prec->zrst + val * sizeof(prec->zrst); + + strncpy(pstring, pstate, sizeof(prec->zrst)); + } + else { + strcpy(pstring, "Illegal Value"); + } + return 0; +} + +static long get_enum_strs(const DBADDR *paddr, struct dbr_enumStrs *pes) +{ + mbbiRecord *prec = (mbbiRecord *) paddr->precord; + char *pstate = prec->zrst; + int i; + short states = 0; + + memset(pes->strs, '\0', sizeof(pes->strs)); + for (i = 0; i < 16; i++, pstate += sizeof(prec->zrst) ) { + strncpy(pes->strs[i], pstate, sizeof(prec->zrst)); + if (*pstate!=0) states = i+1; + } + pes->no_str = states; + return 0; +} + +static long put_enum_str(const DBADDR *paddr, const char *pstring) +{ + mbbiRecord *prec = (mbbiRecord *) paddr->precord; + char *pstate; + short i; + + if (prec->sdef) { + pstate = prec->zrst; + for (i = 0; i < 16; i++) { + if (strncmp(pstate, pstring, sizeof(prec->zrst)) == 0) { + prec->val = i; + prec->udf = FALSE; + return 0; + } + pstate += sizeof(prec->zrst); + } + } + return S_db_badChoice; +} + +static void checkAlarms(mbbiRecord *prec, epicsTimeStamp *timeLast) +{ + double aftc, afvl; + unsigned short alarm; + epicsEnum16 asev; + epicsEnum16 val = prec->val; + + /* Check for UDF alarm */ + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + prec->afvl = 0; + return; + } + + /* Check for STATE alarm */ + if (val > 15) { + /* Unknown state */ + alarm = prec->unsv; + } + else { + /* State has a severity field */ + epicsEnum16 *severities = &prec->zrsv; + + alarm = severities[prec->val]; + } + + aftc = prec->aftc; + afvl = 0; + + if (aftc > 0) { + afvl = prec->afvl; + if (afvl == 0) { + afvl = (double) alarm; + } + else { + double t = epicsTimeDiffInSeconds(&prec->time, timeLast); + double alpha = aftc / (t + aftc); + + afvl = alpha * afvl + + ((afvl > 0) ? (1.0 - alpha) : (alpha - 1.0)) * alarm; + if (afvl - floor(afvl) > THRESHOLD) + afvl = -afvl; + + alarm = abs((int)floor(afvl)); + } + } + + asev = alarm; + recGblSetSevr(prec, STATE_ALARM, asev); + + /* Check for COS alarm */ + if (val == prec->lalm || + recGblSetSevr(prec, COS_ALARM, prec->cosv)) + return; + + prec->lalm = val; +} + +static void monitor(mbbiRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + + if (prec->mlst != prec->val) { + events |= DBE_VALUE | DBE_LOG; + prec->mlst = prec->val; + } + + if (events) + db_post_events(prec, &prec->val, events); + + if (prec->oraw != prec->rval) { + db_post_events(prec, &prec->rval, events | DBE_VALUE | DBE_LOG); + prec->oraw = prec->rval; + } +} + +static long readValue(mbbiRecord *prec) +{ + struct mbbidset *pdset = (struct mbbidset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuSimmNO: + status = pdset->read_mbbi(prec); + break; + + case menuSimmYES: + case menuSimmRAW: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_ULONG, &prec->sval, 0, 0); + if (status == 0) { + if (prec->simm == menuSimmYES) { + prec->val = prec->sval; + status = 2; /* Don't convert */ + } else { + prec->rval = prec->sval; + } + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/mbbiRecord.dbd b/modules/database/src/std/rec/mbbiRecord.dbd new file mode 100644 index 000000000..52f18e43d --- /dev/null +++ b/modules/database/src/std/rec/mbbiRecord.dbd @@ -0,0 +1,505 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(mbbi) { + include "dbCommon.dbd" + field(VAL,DBF_ENUM) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(NOBT,DBF_USHORT) { + prompt("Number of Bits") + promptgroup("40 - Input") + special(SPC_NOMOD) + interest(1) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(ZRVL,DBF_ULONG) { + prompt("Zero Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ONVL,DBF_ULONG) { + prompt("One Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TWVL,DBF_ULONG) { + prompt("Two Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(THVL,DBF_ULONG) { + prompt("Three Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FRVL,DBF_ULONG) { + prompt("Four Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FVVL,DBF_ULONG) { + prompt("Five Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(SXVL,DBF_ULONG) { + prompt("Six Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(SVVL,DBF_ULONG) { + prompt("Seven Value") + promptgroup("41 - Input 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(EIVL,DBF_ULONG) { + prompt("Eight Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(NIVL,DBF_ULONG) { + prompt("Nine Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TEVL,DBF_ULONG) { + prompt("Ten Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ELVL,DBF_ULONG) { + prompt("Eleven Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TVVL,DBF_ULONG) { + prompt("Twelve Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TTVL,DBF_ULONG) { + prompt("Thirteen Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FTVL,DBF_ULONG) { + prompt("Fourteen Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FFVL,DBF_ULONG) { + prompt("Fifteen Value") + promptgroup("42 - Input 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ZRST,DBF_STRING) { + prompt("Zero String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ONST,DBF_STRING) { + prompt("One String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TWST,DBF_STRING) { + prompt("Two String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(THST,DBF_STRING) { + prompt("Three String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FRST,DBF_STRING) { + prompt("Four String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FVST,DBF_STRING) { + prompt("Five String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(SXST,DBF_STRING) { + prompt("Six String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(SVST,DBF_STRING) { + prompt("Seven String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(EIST,DBF_STRING) { + prompt("Eight String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(NIST,DBF_STRING) { + prompt("Nine String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TEST,DBF_STRING) { + prompt("Ten String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ELST,DBF_STRING) { + prompt("Eleven String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TVST,DBF_STRING) { + prompt("Twelve String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TTST,DBF_STRING) { + prompt("Thirteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FTST,DBF_STRING) { + prompt("Fourteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FFST,DBF_STRING) { + prompt("Fifteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ZRSV,DBF_MENU) { + prompt("State Zero Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(ONSV,DBF_MENU) { + prompt("State One Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TWSV,DBF_MENU) { + prompt("State Two Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(THSV,DBF_MENU) { + prompt("State Three Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FRSV,DBF_MENU) { + prompt("State Four Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FVSV,DBF_MENU) { + prompt("State Five Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(SXSV,DBF_MENU) { + prompt("State Six Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(SVSV,DBF_MENU) { + prompt("State Seven Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(EISV,DBF_MENU) { + prompt("State Eight Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(NISV,DBF_MENU) { + prompt("State Nine Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TESV,DBF_MENU) { + prompt("State Ten Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(ELSV,DBF_MENU) { + prompt("State Eleven Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TVSV,DBF_MENU) { + prompt("State Twelve Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TTSV,DBF_MENU) { + prompt("State Thirteen Sevr") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FTSV,DBF_MENU) { + prompt("State Fourteen Sevr") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FFSV,DBF_MENU) { + prompt("State Fifteen Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(AFTC, DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup("70 - Alarm") + interest(1) + } + field(AFVL, DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(UNSV,DBF_MENU) { + prompt("Unknown State Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(COSV,DBF_MENU) { + prompt("Change of State Svr") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("Prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(MLST,DBF_USHORT) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_USHORT) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(SDEF,DBF_SHORT) { + prompt("States Defined") + special(SPC_NOMOD) + interest(3) + } + field(SHFT,DBF_USHORT) { + prompt("Shift") + promptgroup("40 - Input") + interest(1) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_ULONG) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuSimm) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/mbboDirectRecord.c b/modules/database/src/std/rec/mbboDirectRecord.c new file mode 100644 index 000000000..750228bc9 --- /dev/null +++ b/modules/database/src/std/rec/mbboDirectRecord.c @@ -0,0 +1,385 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* mbboDirectRecord.c - Record Support for mbboDirect records */ +/* + * Original Author: Bob Dalesio + * Date: 10-06-93 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuOmsl.h" +#include "menuIvoa.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "mbboDirectRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset mbboDirectRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, mbboDirectRSET); + +struct mbbodset { /* multi bit binary output dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (0, 2)=>(success, success no convert)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_mbbo; /*returns: (0, 2)=>(success, success no convert)*/ +}; + + +static void convert(mbboDirectRecord *); +static void monitor(mbboDirectRecord *); +static long writeValue(mbboDirectRecord *); + +#define NUM_BITS 16 + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct mbboDirectRecord *prec = (struct mbboDirectRecord *)pcommon; + struct mbbodset *pdset = (struct mbbodset *) prec->dset; + long status = 0; + + if (pass == 0) return 0; + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "mbboDirect: init_record"); + return S_dev_noDSET; + } + + if ((pdset->number < 5) || (pdset->write_mbbo == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "mbboDirect: init_record"); + return S_dev_missingSup; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if (recGblInitConstantLink(&prec->dol, DBF_USHORT, &prec->val)) + prec->udf = FALSE; + + /* Initialize MASK if the user set NOBT instead */ + if (prec->mask == 0 && prec->nobt <= 32) + prec->mask = ((epicsUInt64) 1u << prec->nobt) - 1; + + if (pdset->init_record) { + status = pdset->init_record(prec); + if (status == 0) { + /* Convert initial read-back */ + epicsUInt32 rval = prec->rval; + + if (prec->shft > 0) + rval >>= prec->shft; + + prec->val = rval; + prec->udf = FALSE; + } + else if (status == 2) + status = 0; + } + + if (!prec->udf && + prec->omsl == menuOmslsupervisory) { + /* Set initial B0 - BF from VAL */ + epicsUInt16 val = prec->val; + epicsUInt8 *pBn = &prec->b0; + int i; + + for (i = 0; i < NUM_BITS; i++) { + *pBn++ = !! (val & 1); + val >>= 1; + } + } + + prec->mlst = prec->val; + prec->oraw = prec->rval; + prec->orbv = prec->rbv; + return status; +} + +static long process(struct dbCommon *pcommon) +{ + struct mbboDirectRecord *prec = (struct mbboDirectRecord *)pcommon; + struct mbbodset *pdset = (struct mbbodset *)(prec->dset); + long status = 0; + int pact = prec->pact; + + if ((pdset == NULL) || (pdset->write_mbbo == NULL)) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "write_mbbo"); + return S_dev_missingSup; + } + + if (!pact) { + if (!dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + epicsUInt16 val; + + if (dbGetLink(&prec->dol, DBR_USHORT, &val, 0, 0)) { + recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + goto CONTINUE; + } + prec->val = val; + } + else if (prec->omsl == menuOmslsupervisory) { + epicsUInt8 *pBn = &prec->b0; + epicsUInt16 val = 0; + epicsUInt16 bit = 1; + int i; + + /* Construct VAL from B0 - BF */ + for (i = 0; i < NUM_BITS; i++, bit <<= 1) + if (*pBn++) + val |= bit; + prec->val = val; + } + else if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + goto CONTINUE; + } + + prec->udf = FALSE; + /* Convert VAL to RVAL */ + convert(prec); + } + +CONTINUE: + if (prec->nsev < INVALID_ALARM) + status = writeValue(prec); + else { + switch (prec->ivoa) { + case menuIvoaSet_output_to_IVOV: + if (!prec->pact) { + prec->val = prec->ivov; + convert(prec); + } + /* No break, fall through... */ + case menuIvoaContinue_normally: + status = writeValue(prec); + break; + case menuIvoaDon_t_drive_outputs: + break; + default: + status = -1; + recGblRecordError(S_db_badField, prec, + "mbboDirect: process Illegal IVOA field"); + } + } + + /* Done if device support set PACT */ + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long special(DBADDR *paddr, int after) +{ + mbboDirectRecord *prec = (mbboDirectRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (paddr->special == SPC_MOD && fieldIndex == mbboDirectRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + + if (!after) + return 0; + + switch (paddr->special) { + case SPC_MOD: /* Bn field modified */ + if (prec->omsl == menuOmslsupervisory) { + /* Adjust VAL corresponding to the bit changed */ + epicsUInt8 *pBn = (epicsUInt8 *) paddr->pfield; + int bit = 1 << (pBn - &prec->b0); + + if (*pBn) + prec->val |= bit; + else + prec->val &= ~bit; + + prec->udf = FALSE; + convert(prec); + } + break; + + case SPC_RESET: /* OMSL field modified */ + if (prec->omsl == menuOmslclosed_loop) { + /* Construct VAL from B0 - BF */ + epicsUInt8 *pBn = &prec->b0; + epicsUInt16 val = 0, bit = 1; + int i; + + for (i = 0; i < NUM_BITS; i++, bit <<= 1) + if (*pBn++) + val |= bit; + prec->val = val; + } + else if (prec->omsl == menuOmslsupervisory) { + /* Set B0 - BF from VAL and post monitors */ + epicsUInt16 val = prec->val; + epicsUInt8 *pBn = &prec->b0; + int i; + + for (i = 0; i < NUM_BITS; i++, pBn++, val >>= 1) { + epicsUInt8 oBn = *pBn; + + *pBn = !! (val & 1); + if (oBn != *pBn) + db_post_events(prec, pBn, DBE_VALUE | DBE_LOG); + } + } + break; + + default: + recGblDbaddrError(S_db_badChoice, paddr, "mbboDirect: special"); + return S_db_badChoice; + } + + prec->udf = FALSE; + return 0; +} + +static void monitor(mbboDirectRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + + if (prec->mlst != prec->val) { + events |= DBE_VALUE | DBE_LOG; + prec->mlst = prec->val; + } + if (events) + db_post_events(prec, &prec->val, events); + + events |= DBE_VALUE | DBE_LOG; + if (prec->oraw != prec->rval) { + db_post_events(prec, &prec->rval, events); + prec->oraw = prec->rval; + } + if (prec->orbv != prec->rbv) { + db_post_events(prec, &prec->rbv, events); + prec->orbv = prec->rbv; + } +} + +static void convert(mbboDirectRecord *prec) +{ + /* Convert VAL to RVAL */ + prec->rval = prec->val; + + if (prec->shft > 0) + prec->rval <<= prec->shft; +} + +static long writeValue(mbboDirectRecord *prec) +{ + struct mbbodset *pdset = (struct mbbodset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_mbbo(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_USHORT, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/mbboDirectRecord.dbd b/modules/database/src/std/rec/mbboDirectRecord.dbd new file mode 100644 index 000000000..241199d9e --- /dev/null +++ b/modules/database/src/std/rec/mbboDirectRecord.dbd @@ -0,0 +1,246 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(mbboDirect) { + include "dbCommon.dbd" + field(VAL,DBF_USHORT) { + prompt("Word") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + special(SPC_RESET) + pp(TRUE) + interest(1) + menu(menuOmsl) + } + field(NOBT,DBF_SHORT) { + prompt("Number of Bits") + promptgroup("50 - Output") + special(SPC_NOMOD) + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(B0,DBF_UCHAR) { + prompt("Bit 0") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B1,DBF_UCHAR) { + prompt("Bit 1") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B2,DBF_UCHAR) { + prompt("Bit 2") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B3,DBF_UCHAR) { + prompt("Bit 3") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B4,DBF_UCHAR) { + prompt("Bit 4") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B5,DBF_UCHAR) { + prompt("Bit 5") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B6,DBF_UCHAR) { + prompt("Bit 6") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B7,DBF_UCHAR) { + prompt("Bit 7") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B8,DBF_UCHAR) { + prompt("Bit 8") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(B9,DBF_UCHAR) { + prompt("Bit 9") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BA,DBF_UCHAR) { + prompt("Bit 10") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BB,DBF_UCHAR) { + prompt("Bit 11") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BC,DBF_UCHAR) { + prompt("Bit 12") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BD,DBF_UCHAR) { + prompt("Bit 13") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BE,DBF_UCHAR) { + prompt("Bit 14") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(BF,DBF_UCHAR) { + prompt("Bit 15") + promptgroup("52 - Output 9-F") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + special(SPC_NOMOD) + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("Prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(RBV,DBF_ULONG) { + prompt("Readback Value") + special(SPC_NOMOD) + } + field(ORBV,DBF_ULONG) { + prompt("Prev Readback Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(MLST,DBF_ULONG) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SHFT,DBF_ULONG) { + prompt("Shift") + promptgroup("50 - Output") + interest(1) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID outpt action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_USHORT) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } +} diff --git a/modules/database/src/std/rec/mbboRecord.c b/modules/database/src/std/rec/mbboRecord.c new file mode 100644 index 000000000..2e2a35bb2 --- /dev/null +++ b/modules/database/src/std/rec/mbboRecord.c @@ -0,0 +1,474 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* mbboRecord.c - Record Support Routines for multi bit binary Output records */ +/* + * Original Author: Bob Dalesio + * Date: 7-17-87 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuOmsl.h" +#include "menuIvoa.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "mbboRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +static long get_enum_str(const DBADDR *, char *); +static long get_enum_strs(const DBADDR *, struct dbr_enumStrs *); +static long put_enum_str(const DBADDR *, const char *); +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset mbboRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,mbboRSET); + +struct mbbodset { /* multi bit binary output dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (0, 2) => (success, success no convert)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_mbbo; /*returns: (0, 2) => (success, success no convert)*/ +}; + + +static void checkAlarms(mbboRecord *); +static void convert(mbboRecord *); +static void monitor(mbboRecord *); +static long writeValue(mbboRecord *); + + +static void init_common(mbboRecord *prec) +{ + epicsUInt32 *pstate_values = &prec->zrvl; + char *pstate_string = prec->zrst; + int i; + + /* Check if any states are defined */ + for (i = 0; i < 16; i++, pstate_string += sizeof(prec->zrst)) { + if ((pstate_values[i] != 0) || (*pstate_string != '\0')) { + prec->sdef = TRUE; + return; + } + } + prec->sdef = FALSE; +} + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct mbboRecord *prec = (struct mbboRecord *)pcommon; + struct mbbodset *pdset; + long status; + + if (pass == 0) { + init_common(prec); + return 0; + } + + pdset = (struct mbbodset *) prec->dset; + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "mbbo: init_record"); + return S_dev_noDSET; + } + + if ((pdset->number < 5) || (pdset->write_mbbo == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "mbbo: init_record"); + return S_dev_missingSup; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if (recGblInitConstantLink(&prec->dol, DBF_USHORT, &prec->val)) + prec->udf = FALSE; + + /* Initialize MASK if the user set NOBT instead */ + if (prec->mask == 0 && prec->nobt <= 32) + prec->mask = ((epicsUInt64) 1u << prec->nobt) - 1; + + if (pdset->init_record) { + status = pdset->init_record(prec); + init_common(prec); + if (status == 0) { + /* Convert initial read-back */ + epicsUInt32 rval = prec->rval; + + if (prec->shft > 0) + rval >>= prec->shft; + + if (prec->sdef) { + epicsUInt32 *pstate_values = &prec->zrvl; + int i; + + prec->val = 65535; /* initalize to unknown state */ + for (i = 0; i < 16; i++) { + if (*pstate_values == rval) { + prec->val = i; + break; + } + pstate_values++; + } + } + else { + /* No defined states, punt */ + prec->val = rval; + } + prec->udf = FALSE; + } + else if (status == 2) + status = 0; + } + else { + init_common(prec); + status = 0; + } + /* Convert VAL to RVAL */ + convert(prec); + + prec->mlst = prec->val; + prec->lalm = prec->val; + prec->oraw = prec->rval; + prec->orbv = prec->rbv; + return status; +} + +static long process(struct dbCommon *pcommon) +{ + struct mbboRecord *prec = (struct mbboRecord *)pcommon; + struct mbbodset *pdset = (struct mbbodset *) prec->dset; + long status = 0; + int pact = prec->pact; + + if ((pdset == NULL) || (pdset->write_mbbo == NULL)) { + prec->pact = TRUE; + recGblRecordError(S_dev_missingSup, prec, "write_mbbo"); + return S_dev_missingSup; + } + + if (!pact) { + if (!dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + epicsUInt16 val; + + if (dbGetLink(&prec->dol, DBR_USHORT, &val, 0, 0)) { + recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + goto CONTINUE; + } + prec->val = val; + } + else if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + goto CONTINUE; + } + + prec->udf = FALSE; + /* Convert VAL to RVAL */ + convert(prec); + } + +CONTINUE: + /* Check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM) + status = writeValue(prec); + else { + switch (prec->ivoa) { + case menuIvoaSet_output_to_IVOV: + if (!prec->pact) { + prec->val = prec->ivov; + convert(prec); + } + /* No break, fall through... */ + case menuIvoaContinue_normally: + status = writeValue(prec); + break; + case menuIvoaDon_t_drive_outputs: + break; + default : + status = -1; + recGblRecordError(S_db_badField, prec, + "mbbo::process Illegal IVOA field"); + } + } + + /* Done if device support set pact */ + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + monitor(prec); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long special(DBADDR *paddr, int after) +{ + mbboRecord *prec = (mbboRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + switch (paddr->special) { + case SPC_MOD: + if (fieldIndex >= mbboRecordZRST && fieldIndex <= mbboRecordFFST) { + int event = DBE_PROPERTY; + + if (!after) return 0; + init_common(prec); + if (prec->val == fieldIndex - mbboRecordZRST) + event |= DBE_VALUE | DBE_LOG; + db_post_events(prec, &prec->val, event); + return 0; + } + if (fieldIndex == mbboRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "mbbo: special"); + return S_db_badChoice; + } +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + mbboRecord *prec = (mbboRecord *) paddr->precord; + + if (dbGetFieldIndex(paddr) != mbboRecordVAL) { + recGblDbaddrError(S_db_badField, paddr, "mbbo: cvt_dbaddr"); + return 0; + } + if (!prec->sdef) { + paddr->field_type = DBF_USHORT; + paddr->dbr_field_type = DBF_USHORT; + } + return 0; +} + +static long get_enum_str(const DBADDR *paddr, char *pstring) +{ + mbboRecord *prec = (mbboRecord *) paddr->precord; + epicsEnum16 *pfield = paddr->pfield; + epicsEnum16 val = *pfield; + + if (dbGetFieldIndex(paddr) != mbboRecordVAL) { + strcpy(pstring, "Bad Field"); + } + else if (val <= 15) { + const char *pstate = prec->zrst + val * sizeof(prec->zrst); + + strncpy(pstring, pstate, sizeof(prec->zrst)); + } + else { + strcpy(pstring, "Illegal Value"); + } + return 0; +} + +static long get_enum_strs(const DBADDR *paddr, struct dbr_enumStrs *pes) +{ + mbboRecord *prec = (mbboRecord *) paddr->precord; + const char *pstate; + int i, states = 0; + + memset(pes->strs, '\0', sizeof(pes->strs)); + pstate = prec->zrst; + for (i = 0; i < 16; i++) { + strncpy(pes->strs[i], pstate, sizeof(prec->zrst)); + if (*pstate) + states = i + 1; + pstate += sizeof(prec->zrst); + } + pes->no_str = states; + + return 0; +} + +static long put_enum_str(const DBADDR *paddr, const char *pstring) +{ + mbboRecord *prec = (mbboRecord *) paddr->precord; + const char *pstate; + int i; + + if (prec->sdef) { + pstate = prec->zrst; + for (i = 0; i < 16; i++) { + if (strncmp(pstate, pstring, sizeof(prec->zrst)) == 0) { + prec->val = i; + return 0; + } + pstate += sizeof(prec->zrst); + } + } + return S_db_badChoice; +} + +static void checkAlarms(mbboRecord *prec) +{ + epicsEnum16 val = prec->val; + + /* Check for STATE alarm */ + if (val > 15) { + /* Unknown state */ + recGblSetSevr(prec, STATE_ALARM, prec->unsv); + } + else { + /* State has a severity field */ + epicsEnum16 *severities = &prec->zrsv; + recGblSetSevr(prec, STATE_ALARM, severities[prec->val]); + } + + /* Check for COS alarm */ + if (val == prec->lalm || + recGblSetSevr(prec, COS_ALARM, prec->cosv)) + return; + + prec->lalm = val; +} + +static void monitor(mbboRecord *prec) +{ + epicsUInt16 events = recGblResetAlarms(prec); + + if (prec->mlst != prec->val) { + events |= DBE_VALUE | DBE_LOG; + prec->mlst = prec->val; + } + if (events) + db_post_events(prec, &prec->val, events); + + events |= DBE_VALUE | DBE_LOG; + if (prec->oraw != prec->rval) { + db_post_events(prec, &prec->rval, events); + prec->oraw = prec->rval; + } + if (prec->orbv != prec->rbv) { + db_post_events(prec, &prec->rbv, events); + prec->orbv = prec->rbv; + } +} + +static void convert(mbboRecord *prec) +{ + /* Convert VAL to RVAL */ + if (prec->sdef) { + epicsUInt32 *pvalues = &prec->zrvl; + + if (prec->val > 15) { + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + return; + } + prec->rval = pvalues[prec->val]; + } + else + prec->rval = prec->val; + + if (prec->shft > 0) + prec->rval <<= prec->shft; +} + +static long writeValue(mbboRecord *prec) +{ + struct mbbodset *pdset = (struct mbbodset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_mbbo(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_USHORT, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/mbboRecord.dbd b/modules/database/src/std/rec/mbboRecord.dbd new file mode 100644 index 000000000..5475029df --- /dev/null +++ b/modules/database/src/std/rec/mbboRecord.dbd @@ -0,0 +1,526 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(mbbo) { + include "dbCommon.dbd" + field(VAL,DBF_ENUM) { + prompt("Desired Value") + promptgroup("50 - Output") + special(SPC_DBADDR) + asl(ASL0) + pp(TRUE) + #=read Yes + #=write Yes + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(NOBT,DBF_USHORT) { + prompt("Number of Bits") + promptgroup("50 - Output") + special(SPC_NOMOD) + interest(1) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(ZRVL,DBF_ULONG) { + prompt("Zero Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ONVL,DBF_ULONG) { + prompt("One Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TWVL,DBF_ULONG) { + prompt("Two Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(THVL,DBF_ULONG) { + prompt("Three Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FRVL,DBF_ULONG) { + prompt("Four Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FVVL,DBF_ULONG) { + prompt("Five Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(SXVL,DBF_ULONG) { + prompt("Six Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(SVVL,DBF_ULONG) { + prompt("Seven Value") + promptgroup("51 - Output 0-7") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(EIVL,DBF_ULONG) { + prompt("Eight Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(NIVL,DBF_ULONG) { + prompt("Nine Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TEVL,DBF_ULONG) { + prompt("Ten Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ELVL,DBF_ULONG) { + prompt("Eleven Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TVVL,DBF_ULONG) { + prompt("Twelve Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(TTVL,DBF_ULONG) { + prompt("Thirteen Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FTVL,DBF_ULONG) { + prompt("Fourteen Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(FFVL,DBF_ULONG) { + prompt("Fifteen Value") + promptgroup("52 - Output 8-15") + special(SPC_MOD) + pp(TRUE) + base(HEX) + interest(1) + } + field(ZRST,DBF_STRING) { + prompt("Zero String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ONST,DBF_STRING) { + prompt("One String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TWST,DBF_STRING) { + prompt("Two String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(THST,DBF_STRING) { + prompt("Three String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FRST,DBF_STRING) { + prompt("Four String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FVST,DBF_STRING) { + prompt("Five String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(SXST,DBF_STRING) { + prompt("Six String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(SVST,DBF_STRING) { + prompt("Seven String") + promptgroup("81 - Display 0-7") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(EIST,DBF_STRING) { + prompt("Eight String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(NIST,DBF_STRING) { + prompt("Nine String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TEST,DBF_STRING) { + prompt("Ten String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ELST,DBF_STRING) { + prompt("Eleven String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TVST,DBF_STRING) { + prompt("Twelve String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(TTST,DBF_STRING) { + prompt("Thirteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FTST,DBF_STRING) { + prompt("Fourteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(FFST,DBF_STRING) { + prompt("Fifteen String") + promptgroup("82 - Display 8-15") + special(SPC_MOD) + pp(TRUE) + interest(1) + size(26) + } + field(ZRSV,DBF_MENU) { + prompt("State Zero Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(ONSV,DBF_MENU) { + prompt("State One Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TWSV,DBF_MENU) { + prompt("State Two Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(THSV,DBF_MENU) { + prompt("State Three Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FRSV,DBF_MENU) { + prompt("State Four Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FVSV,DBF_MENU) { + prompt("State Five Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(SXSV,DBF_MENU) { + prompt("State Six Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(SVSV,DBF_MENU) { + prompt("State Seven Severity") + promptgroup("71 - Alarm 0-7") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(EISV,DBF_MENU) { + prompt("State Eight Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(NISV,DBF_MENU) { + prompt("State Nine Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TESV,DBF_MENU) { + prompt("State Ten Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(ELSV,DBF_MENU) { + prompt("State Eleven Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TVSV,DBF_MENU) { + prompt("State Twelve Severity") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(TTSV,DBF_MENU) { + prompt("State Thirteen Sevr") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FTSV,DBF_MENU) { + prompt("State Fourteen Sevr") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(FFSV,DBF_MENU) { + prompt("State Fifteen Sevr") + promptgroup("72 - Alarm 8-15") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(UNSV,DBF_MENU) { + prompt("Unknown State Sevr") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(COSV,DBF_MENU) { + prompt("Change of State Sevr") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(RVAL,DBF_ULONG) { + prompt("Raw Value") + pp(TRUE) + } + field(ORAW,DBF_ULONG) { + prompt("Prev Raw Value") + special(SPC_NOMOD) + interest(3) + } + field(RBV,DBF_ULONG) { + prompt("Readback Value") + special(SPC_NOMOD) + } + field(ORBV,DBF_ULONG) { + prompt("Prev Readback Value") + special(SPC_NOMOD) + interest(3) + } + field(MASK,DBF_ULONG) { + prompt("Hardware Mask") + special(SPC_NOMOD) + interest(1) + } + field(MLST,DBF_USHORT) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_USHORT) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(SDEF,DBF_SHORT) { + prompt("States Defined") + special(SPC_NOMOD) + interest(3) + } + field(SHFT,DBF_USHORT) { + prompt("Shift") + promptgroup("50 - Output") + interest(1) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID outpt action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_USHORT) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + } +} diff --git a/modules/database/src/std/rec/permissiveRecord.c b/modules/database/src/std/rec/permissiveRecord.c new file mode 100644 index 000000000..e553931bc --- /dev/null +++ b/modules/database/src/std/rec/permissiveRecord.c @@ -0,0 +1,115 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recPermissive.c - Record Support Routines for Permissive records */ +/* + * Original Author: Bob Dalesio + * Date: 10-10-90 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" + +#define GEN_SIZE_OFFSET +#include "permissiveRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +#define init_record NULL +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset permissiveRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,permissiveRSET); + +static void monitor(permissiveRecord *); + +static long process(struct dbCommon *pcommon) +{ + struct permissiveRecord *prec = (struct permissiveRecord *)pcommon; + + prec->pact=TRUE; + prec->udf=FALSE; + recGblGetTimeStamp(prec); + monitor(prec); + recGblFwdLink(prec); + prec->pact=FALSE; + return(0); +} + +static void monitor(permissiveRecord *prec) +{ + unsigned short monitor_mask; + unsigned short val,oval,wflg,oflg; + + monitor_mask = recGblResetAlarms(prec); + /* get val,oval,wflg,oflg*/ + val=prec->val; + oval=prec->oval; + wflg=prec->wflg; + oflg=prec->oflg; + /*set oval and oflg*/ + prec->oval = val; + prec->oflg = wflg; + if(oval != val) { + db_post_events(prec,&prec->val, + monitor_mask|DBE_VALUE|DBE_LOG); + } + if(oflg != wflg) { + db_post_events(prec,&prec->wflg, + monitor_mask|DBE_VALUE|DBE_LOG); + } + return; +} diff --git a/modules/database/src/std/rec/permissiveRecord.dbd b/modules/database/src/std/rec/permissiveRecord.dbd new file mode 100644 index 000000000..7eb04bf95 --- /dev/null +++ b/modules/database/src/std/rec/permissiveRecord.dbd @@ -0,0 +1,38 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(permissive) { + include "dbCommon.dbd" + field(LABL,DBF_STRING) { + prompt("Button Label") + promptgroup("80 - Display") + pp(TRUE) + interest(1) + size(20) + } + field(VAL,DBF_USHORT) { + prompt("Status") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(OVAL,DBF_USHORT) { + prompt("Old Status") + special(SPC_NOMOD) + interest(3) + } + field(WFLG,DBF_USHORT) { + prompt("Wait Flag") + pp(TRUE) + } + field(OFLG,DBF_USHORT) { + prompt("Old Flag") + special(SPC_NOMOD) + interest(3) + } +} diff --git a/modules/database/src/std/rec/printfRecord.c b/modules/database/src/std/rec/printfRecord.c new file mode 100644 index 000000000..7011453d4 --- /dev/null +++ b/modules/database/src/std/rec/printfRecord.c @@ -0,0 +1,448 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Printf record type */ +/* + * Author: Andrew Johnson + * Date: 2012-09-18 + */ + +#include +#include + +#include "dbDefs.h" +#include "errlog.h" +#include "alarm.h" +#include "cantProceed.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "epicsMath.h" +#include "epicsStdio.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#define GEN_SIZE_OFFSET +#include "printfRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + + +/* Flag bits */ +#define F_CHAR 1 +#define F_SHORT 2 +#define F_LONG 4 +#define F_LEFT 8 +#define F_BADFMT 0x10 +#define F_BADLNK 0x20 +#define F_BAD (F_BADFMT | F_BADLNK) + +#define GET_PRINT(VALTYPE, DBRTYPE) \ + VALTYPE val; \ + int ok; \ +\ + if (dbLinkIsConstant(plink)) \ + ok = recGblInitConstantLink(plink++, DBRTYPE, &val); \ + else \ + ok = ! dbGetLink(plink++, DBRTYPE, &val, 0, 0); \ + if (ok) \ + added = epicsSnprintf(pval, vspace + 1, format, val); \ + else \ + flags |= F_BADLNK + +static void doPrintf(printfRecord *prec) +{ + const char *pfmt = prec->fmt; + DBLINK *plink = &prec->inp0; + int linkn = 0; + char *pval = prec->val; + int vspace = prec->sizv - 1; + int ch; + + while (vspace > 0 && (ch = *pfmt++)) { + if (ch != '%') { + /* Copy literal strings directly into prec->val */ + *pval++ = ch; + --vspace; + } + else { + char format[20]; + char *pformat = format; + int width = 0; + int precision = 0; + int *pnum = &width; + int flags = 0; + int added = 0; + int cont = 1; + + /* The format directive parsing here is not comprehensive, + * in most cases we just copy each directive into format[] + * and get epicsSnprintf() do all the work. We do replace + * all variable-length field width or precision '*' chars + * with an integer read from the next input link, and we + * also convert %ls (long string) directives ourself, so + * we need to know the width, precision and justification. + */ + + *pformat++ = ch; /* '%' */ + while (cont && (ch = *pfmt++)) { + *pformat++ = ch; + switch (ch) { + case '+': case ' ': case '#': + break; + case '-': + flags |= F_LEFT; + break; + case '.': + pnum = &precision; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + *pnum = *pnum * 10 + ch - '0'; + break; + case '*': + if (*pnum) { + flags |= F_BADFMT; + } + else if (linkn++ < PRINTF_NLINKS) { + epicsInt16 i; + int ok; + + if (dbLinkIsConstant(plink)) + ok = recGblInitConstantLink(plink++, DBR_SHORT, &i); + else + ok = ! dbGetLink(plink++, DBR_SHORT, &i, 0, 0); + if (ok) { + *pnum = i; + added = epicsSnprintf(--pformat, 6, "%d", i); + pformat += added; + } + else /* No more LNKn fields */ + flags |= F_BADLNK; + } + else + flags |= F_BADLNK; + break; + case 'h': + if (flags & F_SHORT) + flags = (flags & ~F_SHORT) | F_CHAR; + else + flags |= F_SHORT; + break; + case 'l': + flags |= F_LONG; + break; + default: + if (strchr("diouxXeEfFgGcs%", ch) == NULL) + flags |= F_BADFMT; + cont = 0; + break; + } + } + if (!ch) /* End of format string */ + break; + + if (flags & F_BAD) + goto bad_format; + + *pformat = 0; /* Terminate our format string */ + + if (width < 0) { + width = -width; + flags |= F_LEFT; + } + if (precision < 0) + precision = 0; + + if (ch == '%') { + added = epicsSnprintf(pval, vspace + 1, "%s", format); + } + else if (linkn++ >= PRINTF_NLINKS) { + /* No more LNKn fields */ + flags |= F_BADLNK; + } + else + switch (ch) { /* Conversion character */ + case 'c': case 'd': case 'i': + if (ch == 'c' || flags & F_CHAR) { + GET_PRINT(epicsInt8, DBR_CHAR); + } + else if (flags & F_SHORT) { + GET_PRINT(epicsInt16, DBR_SHORT); + } + else { /* F_LONG has no real effect */ + GET_PRINT(epicsInt32, DBR_LONG); + } + break; + + case 'o': case 'x': case 'X': case 'u': + if (flags & F_CHAR) { + GET_PRINT(epicsUInt8, DBR_UCHAR); + } + else if (flags & F_SHORT) { + GET_PRINT(epicsUInt16, DBR_USHORT); + } + else { /* F_LONG has no real effect */ + GET_PRINT(epicsUInt32, DBR_ULONG); + } + break; + + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + if (flags & F_SHORT) { + GET_PRINT(epicsFloat32, DBR_FLOAT); + } + else { + GET_PRINT(epicsFloat64, DBR_DOUBLE); + } + break; + + case 's': + if (flags & F_LONG) { + long n = vspace + 1; + long status; + + if (precision && n > precision) + n = precision + 1; + /* If set, precision is the maximum number of + * characters to be printed from the string. + * It does not limit the field width however. + */ + if (dbLinkIsConstant(plink)) { + epicsUInt32 len = n; + status = dbLoadLinkLS(plink++, pval, n, &len); + n = len; + } + else + status = dbGetLink(plink++, DBR_CHAR, pval, 0, &n); + if (status) + flags |= F_BADLNK; + else { + int padding; + + /* Terminate string and measure its length */ + pval[n] = 0; + added = strlen(pval); + padding = width - added; + + if (padding > 0) { + if (flags & F_LEFT) { + /* add spaces on RHS */ + if (width > vspace) + padding = vspace - added; + memset(pval + added, ' ', padding); + } + else { + /* insert spaces on LHS */ + int trunc = width - vspace; + + if (trunc < added) { + added -= trunc; + memmove(pval + padding, pval, added); + } + else { + padding = vspace; + added = 0; + } + memset(pval, ' ', padding); + } + added += padding; + } + } + } + else { + char val[MAX_STRING_SIZE]; + int ok; + + if (dbLinkIsConstant(plink)) + ok = recGblInitConstantLink(plink++, DBR_STRING, val); + else + ok = ! dbGetLink(plink++, DBR_STRING, val, 0, 0); + if (ok) + added = epicsSnprintf(pval, vspace + 1, format, val); + else + flags |= F_BADLNK; + } + break; + + default: + errlogPrintf("printfRecord: Unexpected conversion '%s'\n", + format); + flags |= F_BADFMT; + break; + } + + if (flags & F_BAD) { + bad_format: + added = epicsSnprintf(pval, vspace + 1, "%s", + flags & F_BADLNK ? prec->ivls : format); + } + + if (added <= vspace) { + pval += added; + vspace -= added; + } + else { + /* Output was truncated */ + pval += vspace; + vspace = 0; + } + } + } + *pval++ = 0; /* Terminate the VAL string */ + prec->len = pval - prec->val; +} + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct printfRecord *prec = (struct printfRecord *)pcommon; + printfdset *pdset; + + if (pass == 0) { + size_t sizv = prec->sizv; + + if (sizv < 16) { + sizv = 16; /* Enforce a minimum size for the VAL field */ + prec->sizv = sizv; + } + + prec->val = callocMustSucceed(1, sizv, "printf::init_record"); + prec->len = 0; + return 0; + } + + pdset = (printfdset *) prec->dset; + if (!pdset) + return 0; /* Device support is optional */ + + if (pdset->number < 5) { + recGblRecordError(S_dev_missingSup, prec, "printf::init_record"); + return S_dev_missingSup; + } + + if (pdset->init_record) { + long status = pdset->init_record(prec); + if (status) + return status; + } + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct printfRecord *prec = (struct printfRecord *)pcommon; + int pact = prec->pact; + printfdset *pdset; + long status = 0; + epicsUInt16 events; + + if (!pact) { + doPrintf(prec); + + prec->udf = FALSE; + recGblGetTimeStamp(prec); + } + + /* Call device support */ + pdset = (printfdset *) prec->dset; + if (pdset && + pdset->number >= 5 && + pdset->write_string) { + status = pdset->write_string(prec); + + /* Asynchronous if device support set pact */ + if (!pact && prec->pact) + return status; + } + + prec->pact = TRUE; + + /* Post monitor */ + events = recGblResetAlarms(prec); + db_post_events(prec, prec->val, events | DBE_VALUE | DBE_LOG); + db_post_events(prec, &prec->len, events | DBE_VALUE | DBE_LOG); + + /* Wrap up */ + recGblFwdLink(prec); + prec->pact = FALSE; + return status; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + printfRecord *prec = (printfRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == printfRecordVAL) { + paddr->pfield = prec->val; + paddr->no_elements = 1; + paddr->field_type = DBF_STRING; + paddr->dbr_field_type = DBF_STRING; + paddr->field_size = prec->sizv; + } + else + errlogPrintf("printfRecord::cvt_dbaddr called for %s.%s\n", + prec->name, paddr->pfldDes->name); + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + printfRecord *prec = (printfRecord *) paddr->precord; + + *no_elements = prec->len; + *offset = 0; + return 0; +} + + +/* Create Record Support Entry Table */ + +#define report NULL +#define initialize NULL +/* init_record */ +/* process */ +#define special NULL +#define get_value NULL +/* cvt_dbaddr */ +/* get_array_info */ +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset printfRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, printfRSET); + diff --git a/modules/database/src/std/rec/printfRecord.dbd b/modules/database/src/std/rec/printfRecord.dbd new file mode 100644 index 000000000..4fd63ef3c --- /dev/null +++ b/modules/database/src/std/rec/printfRecord.dbd @@ -0,0 +1,109 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +recordtype(printf) { + include "dbCommon.dbd" + %#include "devSup.h" + % + %/* Declare Device Support Entry Table */ + %typedef struct printfdset { + % long number; + % DEVSUPFUN report; + % DEVSUPFUN init; + % DEVSUPFUN init_record; + % DEVSUPFUN get_ioint_info; + % DEVSUPFUN write_string; + %} printfdset; + % + field(VAL,DBF_NOACCESS) { + prompt("Result") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("char *val") + } + field(SIZV,DBF_USHORT) { + prompt("Size of VAL buffer") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("41") + } + field(LEN,DBF_ULONG) { + prompt("Length of VAL") + special(SPC_NOMOD) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(FMT,DBF_STRING) { + prompt("Format String") + promptgroup("30 - Action") + pp(TRUE) + size(81) + } + field(IVLS,DBF_STRING) { + prompt("Invalid Link String") + promptgroup("30 - Action") + size(16) + initial("LNK") + } + field(INP0,DBF_INLINK) { + prompt("Input 0") + promptgroup("40 - Input") + interest(1) + } + field(INP1,DBF_INLINK) { + prompt("Input 1") + promptgroup("40 - Input") + interest(1) + } + field(INP2,DBF_INLINK) { + prompt("Input 2") + promptgroup("40 - Input") + interest(1) + } + field(INP3,DBF_INLINK) { + prompt("Input 3") + promptgroup("40 - Input") + interest(1) + } + field(INP4,DBF_INLINK) { + prompt("Input 4") + promptgroup("40 - Input") + interest(1) + } + field(INP5,DBF_INLINK) { + prompt("Input 5") + promptgroup("40 - Input") + interest(1) + } + field(INP6,DBF_INLINK) { + prompt("Input 6") + promptgroup("40 - Input") + interest(1) + } + field(INP7,DBF_INLINK) { + prompt("Input 7") + promptgroup("40 - Input") + interest(1) + } + field(INP8,DBF_INLINK) { + prompt("Input 8") + promptgroup("40 - Input") + interest(1) + } + field(INP9,DBF_INLINK) { + prompt("Input 9") + promptgroup("40 - Input") + interest(1) + } + %/* Number of INPx fields defined */ + %#define PRINTF_NLINKS 10 +} diff --git a/modules/database/src/std/rec/selRecord.c b/modules/database/src/std/rec/selRecord.c new file mode 100644 index 000000000..56a995c8f --- /dev/null +++ b/modules/database/src/std/rec/selRecord.c @@ -0,0 +1,437 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* selRecord.c - Record Support Routines for Select records */ +/* + * Original Author: Bob Dalesio + * Date: 6-2-89 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "epicsMath.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" + +#define GEN_SIZE_OFFSET +#include "selRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset selRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,selRSET); + +#define SEL_MAX 12 + +static void checkAlarms(selRecord *); +static void do_sel(selRecord *); +static int fetch_values(selRecord *); +static void monitor(selRecord *); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct selRecord *prec = (struct selRecord *)pcommon; + struct link *plink; + int i; + double *pvalue; + + if (pass==0) + return 0; + + /* get seln initial value if nvl is a constant*/ + recGblInitConstantLink(&prec->nvl, DBF_USHORT, &prec->seln); + + plink = &prec->inpa; + pvalue = &prec->a; + for (i=0; ipact = TRUE; + if ( RTN_SUCCESS(fetch_values(prec)) ) { + do_sel(prec); + } + + recGblGetTimeStamp(prec); + /* check for alarms */ + checkAlarms(prec); + + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(0); +} + + +#define indexof(field) selRecord##field + +static long get_units(DBADDR *paddr, char *units) +{ + selRecord *prec=(selRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + selRecord *prec=(selRecord *)paddr->precord; + double *pvalue,*plvalue; + int i; + + *precision = prec->prec; + if(paddr->pfield==(void *)&prec->val){ + return(0); + } + pvalue = &prec->a; + plvalue = &prec->la; + for(i=0; ipfield==(void *)&pvalue + || paddr->pfield==(void *)&plvalue){ + return(0); + } + } + recGblGetPrec(paddr,precision); + return(0); +} + + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + selRecord *prec=(selRecord *)paddr->precord; + int index = dbGetFieldIndex(paddr); + + switch (index) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): +#ifdef __GNUC__ + case indexof(A) ... indexof(L): + case indexof(LA) ... indexof(LL): + break; + default: +#else + break; + default: + if((index >= indexof(A) && index <= indexof(L)) + || (index >= indexof(LA) && index <= indexof(LL))) + break; +#endif + recGblGetGraphicDouble(paddr,pgd); + return(0); + } + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + return(0); +} + +static long get_control_double(struct dbAddr *paddr, struct dbr_ctrlDouble *pcd) +{ + selRecord *prec=(selRecord *)paddr->precord; + int index = dbGetFieldIndex(paddr); + + switch (index) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): +#ifdef __GNUC__ + case indexof(A) ... indexof(L): + case indexof(LA) ... indexof(LL): + break; + default: +#else + break; + default: + if((index >= indexof(A) && index <= indexof(L)) + || (index >= indexof(LA) && index <= indexof(LL))) + break; +#endif + recGblGetControlDouble(paddr,pcd); + return(0); + } + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + return(0); +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + selRecord *prec=(selRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)) { + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(selRecord *prec) +{ + double val, hyst, lalm; + double alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static void monitor(selRecord *prec) +{ + unsigned monitor_mask; + double *pnew; + double *pprev; + int i; + + monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); + + monitor_mask |= DBE_VALUE|DBE_LOG; + + /* trigger monitors of the SELN field */ + if (prec->nlst != prec->seln) { + prec->nlst = prec->seln; + db_post_events(prec, &prec->seln, monitor_mask); + } + + /* check all input fields for changes, even if VAL hasn't changed */ + for(i=0, pnew=&prec->a, pprev=&prec->la; ia; + switch (prec->selm){ + case (selSELM_Specified): + if (prec->seln >= SEL_MAX) { + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + return; + } + val = *(pvalue+prec->seln); + break; + case (selSELM_High_Signal): + val = -epicsINF; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue) && val < *pvalue) { + val = *pvalue; + prec->seln = i; + } + } + break; + case (selSELM_Low_Signal): + val = epicsINF; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue) && val > *pvalue) { + val = *pvalue; + prec->seln = i; + } + } + break; + case (selSELM_Median_Signal): + count = 0; + order[0] = epicsNAN; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue)){ + /* Insertion sort */ + j = count; + while ((j > 0) && (order[j-1] > *pvalue)){ + order[j] = order[j-1]; + j--; + } + order[j] = *pvalue; + count++; + } + } + prec->seln = count; + val = order[count / 2]; + break; + default: + recGblSetSevr(prec,CALC_ALARM,INVALID_ALARM); + return; + } + prec->val = val; + prec->udf = isnan(prec->val); + return; +} + +/* + * FETCH_VALUES + * + * fetch the values for the variables from which to select + */ +static int fetch_values(selRecord *prec) +{ + struct link *plink; + double *pvalue; + int i; + long status; + + plink = &prec->inpa; + pvalue = &prec->a; + /* If mechanism is selSELM_Specified, only get the selected input*/ + if(prec->selm == selSELM_Specified) { + /* fetch the select index */ + status=dbGetLink(&(prec->nvl),DBR_USHORT,&(prec->seln),0,0); + if (!RTN_SUCCESS(status) || (prec->seln >= SEL_MAX)) + return(status); + + plink += prec->seln; + pvalue += prec->seln; + + status=dbGetLink(plink,DBR_DOUBLE, pvalue,0,0); + return(status); + } + /* fetch all inputs*/ + for(i=0; i +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "epicsTypes.h" +#include "link.h" +#include "recSup.h" +#include "recGbl.h" + +#define GEN_SIZE_OFFSET +#include "seqRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +static void processNextLink(seqRecord *prec); +static long asyncFinish(seqRecord *prec); +static void processCallback(CALLBACK *arg); + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *prec, int pass); +static long process(struct dbCommon *prec); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *paddr, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset seqRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, seqRSET); + +int seqDLYprecision = 2; +epicsExportAddress(int, seqDLYprecision); + +double seqDLYlimit = 100000; +epicsExportAddress(double, seqDLYlimit); + + +/* Total number of link-groups */ +#define NUM_LINKS 16 + +/* Each link-group looks like this */ +typedef struct linkGrp { + double dly; /* Delay in seconds */ + DBLINK dol; /* Input link */ + double dov; /* Value storage */ + DBLINK lnk; /* Output link */ +} linkGrp; + +/* The list of link-groups for processing */ +typedef struct seqRecPvt { + CALLBACK callback; + seqRecord *prec; + linkGrp *grps[NUM_LINKS + 1]; /* List of link-groups */ + int index; /* Where we are now */ +} seqRecPvt; + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct seqRecord *prec = (struct seqRecord *)pcommon; + int index; + linkGrp *grp; + seqRecPvt *pseqRecPvt; + + if (pass == 0) + return 0; + + pseqRecPvt = (seqRecPvt *)calloc(1, sizeof(seqRecPvt)); + pseqRecPvt->prec = prec; + callbackSetCallback(processCallback, &pseqRecPvt->callback); + callbackSetUser(pseqRecPvt, &pseqRecPvt->callback); + prec->dpvt = pseqRecPvt; + + recGblInitConstantLink(&prec->sell, DBF_USHORT, &prec->seln); + + grp = (linkGrp *) &prec->dly0; + for (index = 0; index < NUM_LINKS; index++, grp++) { + recGblInitConstantLink(&grp->dol, DBF_DOUBLE, &grp->dov); + } + + prec->oldn = prec->seln; + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct seqRecord *prec = (struct seqRecord *)pcommon; + seqRecPvt *pcb = (seqRecPvt *) prec->dpvt; + linkGrp *pgrp; + epicsUInt16 lmask; + int i; + + if (prec->pact) + return asyncFinish(prec); + prec->pact = TRUE; + + /* Set callback from PRIO */ + callbackSetPriority(prec->prio, &pcb->callback); + + if (prec->selm == seqSELM_All) + lmask = (1 << NUM_LINKS) - 1; + else { + /* Get SELN value */ + dbGetLink(&prec->sell, DBR_USHORT, &prec->seln, 0, 0); + + if (prec->selm == seqSELM_Specified) { + int grpn = prec->seln + prec->offs; + if (grpn < 0 || grpn >= NUM_LINKS) { + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + return asyncFinish(prec); + } + if (grpn == 0) + return asyncFinish(prec); + + lmask = 1 << grpn; + } + else if (prec->selm == seqSELM_Mask) { + int shft = prec->shft; + if (shft < -15 || shft > 15) { + /* Shifting by more than the number of bits in the + * value produces undefined behavior in C */ + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + return asyncFinish(prec); + } + lmask = (shft >= 0) ? prec->seln >> shft : prec->seln << -shft; + } + else { + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + return asyncFinish(prec); + } + } + + /* Figure out which groups are to be processed */ + pcb->index = 0; + pgrp = (linkGrp *) &prec->dly0; + for (i = 0; lmask; lmask >>= 1) { + if ((lmask & 1) && + (!dbLinkIsConstant(&pgrp->lnk) || + !dbLinkIsConstant(&pgrp->dol))) { + pcb->grps[i++] = pgrp; + } + pgrp++; + } + pcb->grps[i] = NULL; /* mark the end of the list */ + + if (!i) + return asyncFinish(prec); + + /* Start processing link groups (we have at least one) */ + processNextLink(prec); + + return 0; +} + +static void processNextLink(seqRecord *prec) +{ + seqRecPvt *pcb = (seqRecPvt *) prec->dpvt; + linkGrp *pgrp = pcb->grps[pcb->index]; + + if (pgrp == NULL) { + /* None left, finish up. */ + prec->rset->process((dbCommon *)prec); + return; + } + + /* Always use the callback task to avoid recursion */ + if (pgrp->dly > 0.0) + callbackRequestDelayed(&pcb->callback, pgrp->dly); + else + callbackRequest(&pcb->callback); +} + +static long asyncFinish(seqRecord *prec) +{ + epicsUInt16 events; + + prec->udf = FALSE; + recGblGetTimeStamp(prec); + + /* post monitors */ + events = recGblResetAlarms(prec); + if (events) + db_post_events(prec, &prec->val, events); + if (prec->seln != prec->oldn) { + db_post_events(prec, &prec->seln, events | DBE_VALUE | DBE_LOG); + prec->oldn = prec->seln; + } + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact = FALSE; + return 0; +} + + +static void processCallback(CALLBACK *arg) +{ + seqRecPvt *pcb; + seqRecord *prec; + linkGrp *pgrp; + double odov; + + callbackGetUser(pcb, arg); + prec = pcb->prec; + dbScanLock((struct dbCommon *)prec); + + pgrp = pcb->grps[pcb->index]; + + /* Save the old value */ + odov = pgrp->dov; + + dbGetLink(&pgrp->dol, DBR_DOUBLE, &pgrp->dov, 0, 0); + + recGblGetTimeStamp(prec); + + /* Dump the value to the destination field */ + dbPutLink(&pgrp->lnk, DBR_DOUBLE, &pgrp->dov, 1); + + if (odov != pgrp->dov) { + db_post_events(prec, &pgrp->dov, DBE_VALUE | DBE_LOG); + } + + /* Start the next link-group */ + pcb->index++; + processNextLink(prec); + + dbScanUnlock((struct dbCommon *)prec); +} + + +#define indexof(field) seqRecord##field +#define get_dol(prec, fieldOffset) \ + &((linkGrp *) &prec->dly0)[fieldOffset >> 2].dol + +static long get_units(DBADDR *paddr, char *units) +{ + seqRecord *prec = (seqRecord *) paddr->precord; + int fieldOffset = dbGetFieldIndex(paddr) - indexof(DLY1); + + if (fieldOffset >= 0) + switch (fieldOffset & 2) { + case 0: /* DLYn */ + strcpy(units, "s"); + break; + case 2: /* DOn */ + dbGetUnits(get_dol(prec, fieldOffset), + units, DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *pprecision) +{ + seqRecord *prec = (seqRecord *) paddr->precord; + int fieldOffset = dbGetFieldIndex(paddr) - indexof(DLY1); + short precision; + + if (fieldOffset >= 0) + switch (fieldOffset & 2) { + case 0: /* DLYn */ + *pprecision = seqDLYprecision; + return 0; + case 2: /* DOn */ + if (dbGetPrecision(get_dol(prec, fieldOffset), &precision) == 0) { + *pprecision = precision; + return 0; + } + } + *pprecision = prec->prec; + recGblGetPrec(paddr, pprecision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + seqRecord *prec = (seqRecord *) paddr->precord; + int fieldOffset = dbGetFieldIndex(paddr) - indexof(DLY1); + + if (fieldOffset >= 0) + switch (fieldOffset & 2) { + case 0: /* DLYn */ + pgd->lower_disp_limit = 0.0; + pgd->lower_disp_limit = 10.0; + return 0; + case 2: /* DOn */ + dbGetGraphicLimits(get_dol(prec, fieldOffset), + &pgd->lower_disp_limit, + &pgd->upper_disp_limit); + return 0; + } + recGblGetGraphicDouble(paddr, pgd); + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + int fieldOffset = dbGetFieldIndex(paddr) - indexof(DLY1); + + if (fieldOffset >= 0 && (fieldOffset & 2) == 0) { /* DLYn */ + pcd->lower_ctrl_limit = 0.0; + pcd->upper_ctrl_limit = seqDLYlimit; + } + else + recGblGetControlDouble(paddr, pcd); + return 0; +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + seqRecord *prec = (seqRecord *) paddr->precord; + int fieldOffset = dbGetFieldIndex(paddr) - indexof(DLY1); + + if (fieldOffset >= 0 && (fieldOffset & 2) == 2) /* DOn */ + dbGetAlarmLimits(get_dol(prec, fieldOffset), + &pad->lower_alarm_limit, &pad->lower_warning_limit, + &pad->upper_warning_limit, &pad->upper_alarm_limit); + else + recGblGetAlarmDouble(paddr, pad); + return 0; +} diff --git a/modules/database/src/std/rec/seqRecord.dbd b/modules/database/src/std/rec/seqRecord.dbd new file mode 100644 index 000000000..826f3ecf6 --- /dev/null +++ b/modules/database/src/std/rec/seqRecord.dbd @@ -0,0 +1,365 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(seqSELM) { + choice(seqSELM_All,"All") + choice(seqSELM_Specified,"Specified") + choice(seqSELM_Mask,"Mask") +} +recordtype(seq) { + include "dbCommon.dbd" + field(VAL,DBF_LONG) { + prompt("Used to trigger") + asl(ASL0) + pp(TRUE) + } + field(SELM,DBF_MENU) { + prompt("Select Mechanism") + promptgroup("30 - Action") + interest(1) + menu(seqSELM) + } + field(SELN,DBF_USHORT) { + prompt("Link Selection") + interest(1) + initial("1") + } + field(SELL,DBF_INLINK) { + prompt("Link Selection Loc") + promptgroup("30 - Action") + interest(1) + } + field(OFFS,DBF_SHORT) { + prompt("Offset for Specified") + promptgroup("30 - Action") + interest(1) + initial("0") + } + field(SHFT,DBF_SHORT) { + prompt("Shift for Mask mode") + promptgroup("30 - Action") + interest(1) + initial("-1") + } + field(OLDN,DBF_USHORT) { + prompt("Old Selection") + interest(4) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + } + field(DLY0,DBF_DOUBLE) { + prompt("Delay 0") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL0,DBF_INLINK) { + prompt("Input link 0") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO0,DBF_DOUBLE) { + prompt("Value 0") + interest(1) + } + field(LNK0,DBF_OUTLINK) { + prompt("Output Link 0") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY1,DBF_DOUBLE) { + prompt("Delay 1") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL1,DBF_INLINK) { + prompt("Input link1") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO1,DBF_DOUBLE) { + prompt("Value 1") + interest(1) + } + field(LNK1,DBF_OUTLINK) { + prompt("Output Link 1") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY2,DBF_DOUBLE) { + prompt("Delay 2") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL2,DBF_INLINK) { + prompt("Input link 2") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO2,DBF_DOUBLE) { + prompt("Value 2") + interest(1) + } + field(LNK2,DBF_OUTLINK) { + prompt("Output Link 2") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY3,DBF_DOUBLE) { + prompt("Delay 3") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL3,DBF_INLINK) { + prompt("Input link 3") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO3,DBF_DOUBLE) { + prompt("Value 3") + interest(1) + } + field(LNK3,DBF_OUTLINK) { + prompt("Output Link 3") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY4,DBF_DOUBLE) { + prompt("Delay 4") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL4,DBF_INLINK) { + prompt("Input link 4") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO4,DBF_DOUBLE) { + prompt("Value 4") + interest(1) + } + field(LNK4,DBF_OUTLINK) { + prompt("Output Link 4") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY5,DBF_DOUBLE) { + prompt("Delay 5") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL5,DBF_INLINK) { + prompt("Input link 5") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO5,DBF_DOUBLE) { + prompt("Value 5") + interest(1) + } + field(LNK5,DBF_OUTLINK) { + prompt("Output Link 5") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY6,DBF_DOUBLE) { + prompt("Delay 6") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL6,DBF_INLINK) { + prompt("Input link 6") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO6,DBF_DOUBLE) { + prompt("Value 6") + interest(1) + } + field(LNK6,DBF_OUTLINK) { + prompt("Output Link 6") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY7,DBF_DOUBLE) { + prompt("Delay 7") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DOL7,DBF_INLINK) { + prompt("Input link 7") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DO7,DBF_DOUBLE) { + prompt("Value 7") + interest(1) + } + field(LNK7,DBF_OUTLINK) { + prompt("Output Link 7") + promptgroup("41 - Link 0-7") + interest(1) + } + field(DLY8,DBF_DOUBLE) { + prompt("Delay 8") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOL8,DBF_INLINK) { + prompt("Input link 8") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DO8,DBF_DOUBLE) { + prompt("Value 8") + interest(1) + } + field(LNK8,DBF_OUTLINK) { + prompt("Output Link 8") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLY9,DBF_DOUBLE) { + prompt("Delay 9") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOL9,DBF_INLINK) { + prompt("Input link 9") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DO9,DBF_DOUBLE) { + prompt("Value 9") + interest(1) + } + field(LNK9,DBF_OUTLINK) { + prompt("Output Link 9") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYA,DBF_DOUBLE) { + prompt("Delay 10") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLA,DBF_INLINK) { + prompt("Input link 10") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOA,DBF_DOUBLE) { + prompt("Value 10") + interest(1) + } + field(LNKA,DBF_OUTLINK) { + prompt("Output Link 10") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYB,DBF_DOUBLE) { + prompt("Delay 11") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLB,DBF_INLINK) { + prompt("Input link 11") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOB,DBF_DOUBLE) { + prompt("Value 11") + interest(1) + } + field(LNKB,DBF_OUTLINK) { + prompt("Output Link 11") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYC,DBF_DOUBLE) { + prompt("Delay 12") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLC,DBF_INLINK) { + prompt("Input link 12") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOC,DBF_DOUBLE) { + prompt("Value 12") + interest(1) + } + field(LNKC,DBF_OUTLINK) { + prompt("Output Link 12") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYD,DBF_DOUBLE) { + prompt("Delay 13") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLD,DBF_INLINK) { + prompt("Input link 13") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOD,DBF_DOUBLE) { + prompt("Value 13") + interest(1) + } + field(LNKD,DBF_OUTLINK) { + prompt("Output Link 13") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYE,DBF_DOUBLE) { + prompt("Delay 14") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLE,DBF_INLINK) { + prompt("Input link 14") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOE,DBF_DOUBLE) { + prompt("Value 14") + interest(1) + } + field(LNKE,DBF_OUTLINK) { + prompt("Output Link 14") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DLYF,DBF_DOUBLE) { + prompt("Delay 15") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOLF,DBF_INLINK) { + prompt("Input link 15") + promptgroup("42 - Link 8-F") + interest(1) + } + field(DOF,DBF_DOUBLE) { + prompt("Value 15") + interest(1) + } + field(LNKF,DBF_OUTLINK) { + prompt("Output Link 15") + promptgroup("42 - Link 8-F") + interest(1) + } +} + +variable(seqDLYprecision, int) +variable(seqDLYlimit, double) diff --git a/modules/database/src/std/rec/stateRecord.c b/modules/database/src/std/rec/stateRecord.c new file mode 100644 index 000000000..ca5df0295 --- /dev/null +++ b/modules/database/src/std/rec/stateRecord.c @@ -0,0 +1,105 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recState.c - Record Support Routines for State records */ +/* + * Original Author: Bob Dalesio + * Date: 10-10-90 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" + +#define GEN_SIZE_OFFSET +#include "stateRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +#define init_record NULL +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset stateRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,stateRSET); + +static void monitor(stateRecord *); + +static long process(struct dbCommon *pcommon) +{ + struct stateRecord *prec = (struct stateRecord *)pcommon; + + prec->udf = FALSE; + prec->pact=TRUE; + recGblGetTimeStamp(prec); + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + prec->pact=FALSE; + return(0); +} + +static void monitor(stateRecord *prec) +{ + unsigned short monitor_mask; + + /* get previous stat and sevr and new stat and sevr*/ + monitor_mask = recGblResetAlarms(prec); + if(strncmp(prec->oval,prec->val,sizeof(prec->val))) { + db_post_events(prec,&(prec->val[0]),monitor_mask|DBE_VALUE|DBE_LOG); + strncpy(prec->oval,prec->val,sizeof(prec->val)); + } + return; +} diff --git a/modules/database/src/std/rec/stateRecord.dbd b/modules/database/src/std/rec/stateRecord.dbd new file mode 100644 index 000000000..6e43ddbba --- /dev/null +++ b/modules/database/src/std/rec/stateRecord.dbd @@ -0,0 +1,24 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(state) { + include "dbCommon.dbd" + field(VAL,DBF_STRING) { + prompt("Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + size(20) + } + field(OVAL,DBF_STRING) { + prompt("Prev Value") + special(SPC_NOMOD) + interest(3) + size(20) + } +} diff --git a/modules/database/src/std/rec/stringinRecord.c b/modules/database/src/std/rec/stringinRecord.c new file mode 100644 index 000000000..ee84f0dc9 --- /dev/null +++ b/modules/database/src/std/rec/stringinRecord.c @@ -0,0 +1,240 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recStringin.c - Record Support Routines for Stringin records */ +/* + * Author: Janet Anderson + * Date: 4/23/91 + */ + + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "stringinRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset stringinRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,stringinRSET); + +struct stringindset { /* stringin input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_stringin; /*returns: (-1,0)=>(failure,success)*/ +}; +static void monitor(stringinRecord *); +static long readValue(stringinRecord *); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct stringinRecord *prec = (struct stringinRecord *)pcommon; + STATIC_ASSERT(sizeof(prec->oval)==sizeof(prec->val)); + STATIC_ASSERT(sizeof(prec->sval)==sizeof(prec->val)); + struct stringindset *pdset = (struct stringindset *) prec->dset; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + recGblInitConstantLink(&prec->siol, DBF_STRING, prec->sval); + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "stringin: init_record"); + return S_dev_noDSET; + } + + /* must have read_stringin function defined */ + if ((pdset->number < 5) || (pdset->read_stringin == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "stringin: init_record"); + return S_dev_missingSup; + } + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if (status) + return status; + } + + strncpy(prec->oval, prec->val, sizeof(prec->val)); + return 0; +} + +/* + */ +static long process(struct dbCommon *pcommon) +{ + struct stringinRecord *prec = (struct stringinRecord *)pcommon; + struct stringindset *pdset = (struct stringindset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->read_stringin==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_stringin"); + return(S_dev_missingSup); + } + + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + stringinRecord *prec = (stringinRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == stringinRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "stringin: special"); + return(S_db_badChoice); + } +} + +static void monitor(stringinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if (strncmp(prec->oval, prec->val, sizeof(prec->val))) { + monitor_mask |= DBE_VALUE | DBE_LOG; + strncpy(prec->oval, prec->val, sizeof(prec->val)); + } + + if (prec->mpst == stringinPOST_Always) + monitor_mask |= DBE_VALUE; + if (prec->apst == stringinPOST_Always) + monitor_mask |= DBE_LOG; + + if (monitor_mask) + db_post_events(prec, prec->val, monitor_mask); +} + +static long readValue(stringinRecord *prec) +{ + struct stringindset *pdset = (struct stringindset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->read_stringin(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, DBR_STRING, prec->sval, 0, 0); + if (status == 0) { + strncpy(prec->val, prec->sval, sizeof(prec->val)); + prec->udf = FALSE; + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/stringinRecord.dbd b/modules/database/src/std/rec/stringinRecord.dbd new file mode 100644 index 000000000..ec7b2d570 --- /dev/null +++ b/modules/database/src/std/rec/stringinRecord.dbd @@ -0,0 +1,98 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(stringinPOST) { + choice(stringinPOST_OnChange,"On Change") + choice(stringinPOST_Always,"Always") +} +recordtype(stringin) { + include "dbCommon.dbd" + field(VAL,DBF_STRING) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + size(40) + } + field(OVAL,DBF_STRING) { + prompt("Previous Value") + special(SPC_NOMOD) + interest(3) + size(40) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(MPST,DBF_MENU) { + prompt("Post Value Monitors") + promptgroup("80 - Display") + interest(1) + menu(stringinPOST) + } + field(APST,DBF_MENU) { + prompt("Post Archive Monitors") + promptgroup("80 - Display") + interest(1) + menu(stringinPOST) + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SVAL,DBF_STRING) { + prompt("Simulation Value") + pp(TRUE) + size(40) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } +} diff --git a/modules/database/src/std/rec/stringoutRecord.c b/modules/database/src/std/rec/stringoutRecord.c new file mode 100644 index 000000000..0c871fc4d --- /dev/null +++ b/modules/database/src/std/rec/stringoutRecord.c @@ -0,0 +1,268 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recStringout.c - Record Support Routines for Stringout records */ +/* + * Author: Janet Anderson + * Date: 4/23/91 + */ + + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "menuOmsl.h" +#include "menuIvoa.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "stringoutRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset stringoutRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,stringoutRSET); + +struct stringoutdset { /* stringout input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_stringout;/*(-1,0)=>(failure,success)*/ +}; +static void monitor(stringoutRecord *); +static long writeValue(stringoutRecord *); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct stringoutRecord *prec = (struct stringoutRecord *)pcommon; + STATIC_ASSERT(sizeof(prec->oval)==sizeof(prec->val)); + STATIC_ASSERT(sizeof(prec->ivov)==sizeof(prec->val)); + struct stringoutdset *pdset = (struct stringoutdset *) prec->dset; + + if (pass == 0) return 0; + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + if (!pdset) { + recGblRecordError(S_dev_noDSET, prec, "stringout: init_record"); + return S_dev_noDSET; + } + + /* must have write_stringout functions defined */ + if ((pdset->number < 5) || (pdset->write_stringout == NULL)) { + recGblRecordError(S_dev_missingSup, prec, "stringout: init_record"); + return S_dev_missingSup; + } + + /* get the initial value dol is a constant*/ + if (recGblInitConstantLink(&prec->dol, DBF_STRING, prec->val)) + prec->udf = FALSE; + + if (pdset->init_record) { + long status = pdset->init_record(prec); + + if(status) + return status; + } + + strncpy(prec->oval, prec->val, sizeof(prec->val)); + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct stringoutRecord *prec = (struct stringoutRecord *)pcommon; + struct stringoutdset *pdset = (struct stringoutdset *)(prec->dset); + long status=0; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->write_stringout==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_stringout"); + return(S_dev_missingSup); + } + if (!prec->pact && + !dbLinkIsConstant(&prec->dol) && + prec->omsl == menuOmslclosed_loop) { + status = dbGetLink(&prec->dol, DBR_STRING, prec->val, 0, 0); + if (!dbLinkIsConstant(&prec->dol) && !status) + prec->udf=FALSE; + } + + if(prec->udf == TRUE ){ + recGblSetSevr(prec,UDF_ALARM,prec->udfs); + } + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + strncpy(prec->val, prec->ivov, sizeof(prec->val)); + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "stringout:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, NULL); + + monitor(prec); + recGblFwdLink(prec); + prec->pact=FALSE; + return(status); +} + +static long special(DBADDR *paddr, int after) +{ + stringoutRecord *prec = (stringoutRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == stringoutRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return 0; + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "stringout: special"); + return(S_db_badChoice); + } +} + +static void monitor(stringoutRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if (strncmp(prec->oval, prec->val, sizeof(prec->val))) { + monitor_mask |= DBE_VALUE | DBE_LOG; + strncpy(prec->oval, prec->val, sizeof(prec->val)); + } + + if (prec->mpst == stringoutPOST_Always) + monitor_mask |= DBE_VALUE; + if (prec->apst == stringoutPOST_Always) + monitor_mask |= DBE_LOG; + + if (monitor_mask) + db_post_events(prec, prec->val, monitor_mask); +} + +static long writeValue(stringoutRecord *prec) +{ + struct stringoutdset *pdset = (struct stringoutdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: + status = pdset->write_stringout(prec); + break; + + case menuYesNoYES: { + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbPutLink(&prec->siol, DBR_STRING, &prec->val, 1); + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/stringoutRecord.dbd b/modules/database/src/std/rec/stringoutRecord.dbd new file mode 100644 index 000000000..358340c6d --- /dev/null +++ b/modules/database/src/std/rec/stringoutRecord.dbd @@ -0,0 +1,116 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +menu(stringoutPOST) { + choice(stringoutPOST_OnChange,"On Change") + choice(stringoutPOST_Always,"Always") +} +recordtype(stringout) { + include "dbCommon.dbd" + field(VAL,DBF_STRING) { + prompt("Current Value") + promptgroup("50 - Output") + asl(ASL0) + pp(TRUE) + size(40) + } + field(OVAL,DBF_STRING) { + prompt("Previous Value") + special(SPC_NOMOD) + interest(3) + size(40) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup("40 - Input") + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup("50 - Output") + interest(1) + menu(menuOmsl) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(MPST,DBF_MENU) { + prompt("Post Value Monitors") + promptgroup("80 - Display") + interest(1) + menu(stringoutPOST) + } + field(APST,DBF_MENU) { + prompt("Post Archive Monitors") + promptgroup("80 - Display") + interest(1) + menu(stringoutPOST) + } + field(SIOL,DBF_OUTLINK) { + prompt("Simulation Output Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup("50 - Output") + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_STRING) { + prompt("INVALID output value") + promptgroup("50 - Output") + interest(2) + size(40) + } +} diff --git a/modules/database/src/std/rec/subArrayRecord.c b/modules/database/src/std/rec/subArrayRecord.c new file mode 100644 index 000000000..d9d1c33e1 --- /dev/null +++ b/modules/database/src/std/rec/subArrayRecord.c @@ -0,0 +1,324 @@ +/*************************************************************************\ +* Copyright (c) 2002 Lawrence Berkeley Laboratory,The Control Systems +* Group, Systems Engineering Department +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recSubArray.c - Record Support Routines for SubArray records + * + * + * Author: Carl Lionberger + * Date: 090293 + * + * NOTES: + * Derived from waveform record. + * Modification Log: + * ----------------- + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "dbScan.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "cantProceed.h" + +#define GEN_SIZE_OFFSET +#include "subArrayRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *prec, int pass); +static long process(struct dbCommon *prec); +#define special NULL +#define get_value NULL +static long cvt_dbaddr(DBADDR *paddr); +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset); +static long put_array_info(DBADDR *paddr, long nNew); +static long get_units(DBADDR *paddr, char *units); +static long get_precision(const DBADDR *paddr, long *precision); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd); +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd); +#define get_alarm_double NULL + +rset subArrayRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,subArrayRSET); + +struct sadset { /* subArray dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_sa; /*returns: (-1,0)=>(failure,success)*/ +}; + +static void monitor(subArrayRecord *prec); +static long readValue(subArrayRecord *prec); + + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct subArrayRecord *prec = (struct subArrayRecord *)pcommon; + struct sadset *pdset; + + if (pass==0){ + if (prec->malm <= 0) + prec->malm = 1; + if (prec->ftvl > DBF_ENUM) + prec->ftvl = DBF_UCHAR; + prec->bptr = callocMustSucceed(prec->malm, dbValueSize(prec->ftvl), + "subArrayRecord calloc failed"); + prec->nord = 0; + if (prec->nelm > prec->malm) + prec->nelm = prec->malm; + return 0; + } + + /* must have dset defined */ + if (!(pdset = (struct sadset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"sa: init_record"); + return S_dev_noDSET; + } + + /* must have read_sa function defined */ + if ( (pdset->number < 5) || (pdset->read_sa == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"sa: init_record"); + return S_dev_missingSup; + } + + if (pdset->init_record) + return (*pdset->init_record)(prec); + + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct subArrayRecord *prec = (struct subArrayRecord *)pcommon; + struct sadset *pdset = (struct sadset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + + if ((pdset==NULL) || (pdset->read_sa==NULL)) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup, (void *)prec, "read_sa"); + return S_dev_missingSup; + } + + if (pact && prec->busy) return 0; + + status=readValue(prec); /* read the new value */ + if (!pact && prec->pact) return 0; + prec->pact = TRUE; + + recGblGetTimeStamp(prec); + + prec->udf = !!status; /* 0 or 1 */ + if (status) + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return 0; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->malm; + paddr->field_type = prec->ftvl; + paddr->field_size = dbValueSize(prec->ftvl); + paddr->dbr_field_type = prec->ftvl; + + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + if (prec->udf) + *no_elements = 0; + else + *no_elements = prec->nord; + *offset = 0; + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + if (nNew > prec->malm) + nNew = prec->malm; + prec->nord = nNew; + + return 0; +} + +#define indexof(field) subArrayRecord##field + +static long get_units(DBADDR *paddr, char *units) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + if (prec->ftvl == DBF_STRING || prec->ftvl == DBF_ENUM) + break; + case indexof(HOPR): + case indexof(LOPR): + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + *precision = prec->prec; + if (dbGetFieldIndex(paddr) != indexof(VAL)) + recGblGetPrec(paddr, precision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + case indexof(INDX): + pgd->upper_disp_limit = prec->malm - 1; + pgd->lower_disp_limit = 0; + break; + case indexof(NELM): + pgd->upper_disp_limit = prec->malm; + pgd->lower_disp_limit = 0; + break; + case indexof(NORD): + pgd->upper_disp_limit = prec->malm; + pgd->lower_disp_limit = 0; + break; + case indexof(BUSY): + pgd->upper_disp_limit = 1; + pgd->lower_disp_limit = 0; + break; + default: + recGblGetGraphicDouble(paddr, pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + subArrayRecord *prec = (subArrayRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + case indexof(INDX): + pcd->upper_ctrl_limit = prec->malm - 1; + pcd->lower_ctrl_limit = 0; + break; + case indexof(NELM): + pcd->upper_ctrl_limit = prec->malm; + pcd->lower_ctrl_limit = 1; + break; + case indexof(NORD): + pcd->upper_ctrl_limit = prec->malm; + pcd->lower_ctrl_limit = 0; + break; + case indexof(BUSY): + pcd->upper_ctrl_limit = 1; + pcd->lower_ctrl_limit = 0; + break; + default: + recGblGetControlDouble(paddr, pcd); + } + return 0; +} + +static void monitor(subArrayRecord *prec) +{ + unsigned short monitor_mask; + + monitor_mask = recGblResetAlarms(prec); + monitor_mask |= (DBE_LOG|DBE_VALUE); + + db_post_events(prec, prec->bptr, monitor_mask); + + return; +} + +static long readValue(subArrayRecord *prec) +{ + long status; + struct sadset *pdset = (struct sadset *) (prec->dset); + + if (prec->nelm > prec->malm) + prec->nelm = prec->malm; + + if (prec->indx >= prec->malm) + prec->indx = prec->malm - 1; + + status = (*pdset->read_sa)(prec); + + if (prec->nord <= 0) + status = -1; + + return status; +} + diff --git a/modules/database/src/std/rec/subArrayRecord.dbd b/modules/database/src/std/rec/subArrayRecord.dbd new file mode 100644 index 000000000..7814a2e48 --- /dev/null +++ b/modules/database/src/std/rec/subArrayRecord.dbd @@ -0,0 +1,90 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(subArray) { + include "dbCommon.dbd" + field(VAL,DBF_NOACCESS) { + prompt("Value") + asl(ASL0) + special(SPC_DBADDR) + pp(TRUE) + extra("void * val") + #=type Set by FTVL + #=read Yes + #=write Yes + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(FTVL,DBF_MENU) { + prompt("Field Type of Value") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + menu(menuFtype) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(MALM,DBF_ULONG) { + prompt("Maximum Elements") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(NELM,DBF_ULONG) { + prompt("Number of Elements") + promptgroup("30 - Action") + pp(TRUE) + initial("1") + } + field(INDX,DBF_ULONG) { + prompt("Substring Index") + promptgroup("30 - Action") + pp(TRUE) + } + field(BUSY,DBF_SHORT) { + prompt("Busy Indicator") + special(SPC_NOMOD) + } + field(NORD,DBF_LONG) { + prompt("Number elements read") + special(SPC_NOMOD) + } + field(BPTR,DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + interest(4) + extra("void * bptr") + } +} diff --git a/modules/database/src/std/rec/subRecord.c b/modules/database/src/std/rec/subRecord.c new file mode 100644 index 000000000..1fc007034 --- /dev/null +++ b/modules/database/src/std/rec/subRecord.c @@ -0,0 +1,434 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Record Support Routines for Subroutine records */ +/* + * Original Author: Bob Dalesio + * Date: 01-25-90 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "epicsMath.h" +#include "registryFunction.h" +#include "alarm.h" +#include "cantProceed.h" +#include "dbAccess.h" +#include "epicsPrint.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" + +#define GEN_SIZE_OFFSET +#include "subRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset subRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, subRSET); + +static void checkAlarms(subRecord *); +static long do_sub(subRecord *); +static long fetch_values(subRecord *); +static void monitor(subRecord *); + +#define INP_ARG_MAX 12 + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct subRecord *prec = (struct subRecord *)pcommon; + SUBFUNCPTR psubroutine; + struct link *plink; + int i; + double *pvalue; + + if (pass==0) return(0); + + plink = &prec->inpa; + pvalue = &prec->a; + for (i = 0; i < INP_ARG_MAX; i++, plink++, pvalue++) { + recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); + } + + if (prec->inam[0]) { + /* convert the initialization subroutine name */ + psubroutine = (SUBFUNCPTR)registryFunctionFind(prec->inam); + if (psubroutine == 0) { + recGblRecordError(S_db_BadSub, (void *)prec, "Init subroutine (INAM)"); + return S_db_BadSub; + } + /* invoke the initialization subroutine */ + (*psubroutine)(prec); + } + + if (prec->snam[0] == 0) { + epicsPrintf("%s.SNAM is empty\n", prec->name); + prec->pact = TRUE; + return 0; + } + prec->sadr = (SUBFUNCPTR)registryFunctionFind(prec->snam); + if (prec->sadr == NULL) { + recGblRecordError(S_db_BadSub, (void *)prec, "Proc subroutine (SNAM)"); + return S_db_BadSub; + } + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct subRecord *prec = (struct subRecord *)pcommon; + long status = 0; + int pact = prec->pact; + + if (!pact) { + prec->pact = TRUE; + status = fetch_values(prec); + prec->pact = FALSE; + } + if (status == 0) status = do_sub(prec); + + /* Is subroutine asynchronous? */ + if (!pact && prec->pact) return 0; + prec->pact = TRUE; + + /* Asynchronous function (documented API!) */ + if (status == 1) return 0; + + recGblGetTimeStamp(prec); + + /* check for alarms */ + checkAlarms(prec); + + /* publish changes */ + monitor(prec); + + recGblFwdLink(prec); + prec->pact = FALSE; + + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + subRecord *prec = (subRecord *)paddr->precord; + + if (!after) { + if (prec->snam[0] == 0 && prec->pact) { + prec->pact = FALSE; + prec->rpro = FALSE; + } + return 0; + } + + if (prec->snam[0] == 0) { + epicsPrintf("%s.SNAM is empty\n", prec->name); + prec->pact = TRUE; + return 0; + } + + prec->sadr = (SUBFUNCPTR)registryFunctionFind(prec->snam); + if (prec->sadr) return 0; + + recGblRecordError(S_db_BadSub, (void *)prec, + "subRecord(special) registryFunctionFind failed"); + return S_db_BadSub; +} + +#define indexof(field) subRecord##field + +static long get_linkNumber(int fieldIndex) { + if (fieldIndex >= indexof(A) && fieldIndex <= indexof(L)) + return fieldIndex - indexof(A); + if (fieldIndex >= indexof(LA) && fieldIndex <= indexof(LL)) + return fieldIndex - indexof(LA); + return -1; +} + +static long get_units(DBADDR *paddr, char *units) +{ + subRecord *prec = (subRecord *)paddr->precord; + int linkNumber; + + if(paddr->pfldDes->field_type == DBF_DOUBLE) { + linkNumber = get_linkNumber(dbGetFieldIndex(paddr)); + if (linkNumber >= 0) + dbGetUnits(&prec->inpa + linkNumber, units, DB_UNITS_SIZE); + else + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *pprecision) +{ + subRecord *prec = (subRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + *pprecision = prec->prec; + if (fieldIndex == indexof(VAL)) + return 0; + + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + short precision; + + if (dbGetPrecision(&prec->inpa + linkNumber, &precision) == 0) + *pprecision = precision; + } else + recGblGetPrec(paddr, pprecision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + subRecord *prec = (subRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + switch (fieldIndex) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pgd->lower_disp_limit = prec->lopr; + pgd->upper_disp_limit = prec->hopr; + break; + default: + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetGraphicLimits(&prec->inpa + linkNumber, + &pgd->lower_disp_limit, + &pgd->upper_disp_limit); + } else + recGblGetGraphicDouble(paddr,pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + subRecord *prec = (subRecord *)paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + case indexof(HIHI): + case indexof(HIGH): + case indexof(LOW): + case indexof(LOLO): + case indexof(LALM): + case indexof(ALST): + case indexof(MLST): + pcd->lower_ctrl_limit = prec->lopr; + pcd->upper_ctrl_limit = prec->hopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return 0; +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + subRecord *prec = (subRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + int linkNumber; + + if (fieldIndex == subRecordVAL) { + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else { + linkNumber = get_linkNumber(fieldIndex); + if (linkNumber >= 0) { + dbGetAlarmLimits(&prec->inpa + linkNumber, + &pad->lower_alarm_limit, + &pad->lower_warning_limit, + &pad->upper_warning_limit, + &pad->upper_alarm_limit); + } else + recGblGetAlarmDouble(paddr, pad); + } + return 0; +} + +static void checkAlarms(subRecord *prec) +{ + double val, hyst, lalm; + double alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* alarm condition hihi */ + asev = prec->hhsv; + alev = prec->hihi; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIHI_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition lolo */ + asev = prec->llsv; + alev = prec->lolo; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOLO_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition high */ + asev = prec->hsv; + alev = prec->high; + if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) { + if (recGblSetSevr(prec, HIGH_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* alarm condition low */ + asev = prec->lsv; + alev = prec->low; + if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) { + if (recGblSetSevr(prec, LOW_ALARM, asev)) + prec->lalm = alev; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static void monitor(subRecord *prec) +{ + unsigned monitor_mask; + double *pnew; + double *pold; + int i; + + /* get alarm mask */ + monitor_mask = recGblResetAlarms(prec); + + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } + + /* check all input fields for changes */ + for (i = 0, pnew = &prec->a, pold = &prec->la; + i < INP_ARG_MAX; i++, pnew++, pold++) { + if (*pnew != *pold) { + db_post_events(prec, pnew, monitor_mask | DBE_VALUE | DBE_LOG); + *pold = *pnew; + } + } + return; +} + +static long fetch_values(subRecord *prec) +{ + struct link *plink = &prec->inpa; + double *pvalue = &prec->a; + int i; + + for (i = 0; i < INP_ARG_MAX; i++, plink++, pvalue++) { + if (dbGetLink(plink, DBR_DOUBLE, pvalue, 0, 0)) + return -1; + } + return 0; +} + +static long do_sub(subRecord *prec) +{ + SUBFUNCPTR psubroutine = prec->sadr; + long status; + + if (psubroutine == NULL) { + recGblSetSevr(prec, BAD_SUB_ALARM, INVALID_ALARM); + return 0; + } + + status = (*psubroutine)(prec); + if (status < 0) { + recGblSetSevr(prec, SOFT_ALARM, prec->brsv); + } else { + prec->udf = isnan(prec->val); + } + return status; +} diff --git a/modules/database/src/std/rec/subRecord.dbd b/modules/database/src/std/rec/subRecord.dbd new file mode 100644 index 000000000..48cfc1385 --- /dev/null +++ b/modules/database/src/std/rec/subRecord.dbd @@ -0,0 +1,328 @@ +#************************************************************************* +# Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(sub) { + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Result") + asl(ASL0) + pp(TRUE) + } + field(INAM,DBF_STRING) { + prompt("Init Routine Name") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + size(40) + } + field(SNAM,DBF_STRING) { + prompt("Subroutine Name") + promptgroup("30 - Action") + special(SPC_MOD) + interest(1) + size(40) + } + %struct subRecord; + %typedef long (*SUBFUNCPTR)(struct subRecord *); + field(SADR,DBF_NOACCESS) { + prompt("Subroutine Address") + special(SPC_NOMOD) + interest(4) + extra("SUBFUNCPTR sadr") + } + field(INPA,DBF_INLINK) { + prompt("Input A") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPB,DBF_INLINK) { + prompt("Input B") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPC,DBF_INLINK) { + prompt("Input C") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPD,DBF_INLINK) { + prompt("Input D") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPE,DBF_INLINK) { + prompt("Input E") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPF,DBF_INLINK) { + prompt("Input F") + promptgroup("41 - Input A-F") + interest(1) + } + field(INPG,DBF_INLINK) { + prompt("Input G") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPH,DBF_INLINK) { + prompt("Input H") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPI,DBF_INLINK) { + prompt("Input I") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPJ,DBF_INLINK) { + prompt("Input J") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPK,DBF_INLINK) { + prompt("Input K") + promptgroup("42 - Input G-L") + interest(1) + } + field(INPL,DBF_INLINK) { + prompt("Input L") + promptgroup("42 - Input G-L") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(HIHI,DBF_DOUBLE) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_DOUBLE) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_DOUBLE) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_DOUBLE) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(BRSV,DBF_MENU) { + prompt("Bad Return Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + prop(YES) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(A,DBF_DOUBLE) { + prompt("Value of Input A") + pp(TRUE) + } + field(B,DBF_DOUBLE) { + prompt("Value of Input B") + pp(TRUE) + } + field(C,DBF_DOUBLE) { + prompt("Value of Input C") + pp(TRUE) + } + field(D,DBF_DOUBLE) { + prompt("Value of Input D") + pp(TRUE) + } + field(E,DBF_DOUBLE) { + prompt("Value of Input E") + pp(TRUE) + } + field(F,DBF_DOUBLE) { + prompt("Value of Input F") + pp(TRUE) + } + field(G,DBF_DOUBLE) { + prompt("Value of Input G") + pp(TRUE) + } + field(H,DBF_DOUBLE) { + prompt("Value of Input H") + pp(TRUE) + } + field(I,DBF_DOUBLE) { + prompt("Value of Input I") + pp(TRUE) + } + field(J,DBF_DOUBLE) { + prompt("Value of Input J") + pp(TRUE) + } + field(K,DBF_DOUBLE) { + prompt("Value of Input K") + pp(TRUE) + } + field(L,DBF_DOUBLE) { + prompt("Value of Input L") + pp(TRUE) + } + field(LA,DBF_DOUBLE) { + prompt("Prev Value of A") + special(SPC_NOMOD) + interest(3) + } + field(LB,DBF_DOUBLE) { + prompt("Prev Value of B") + special(SPC_NOMOD) + interest(3) + } + field(LC,DBF_DOUBLE) { + prompt("Prev Value of C") + special(SPC_NOMOD) + interest(3) + } + field(LD,DBF_DOUBLE) { + prompt("Prev Value of D") + special(SPC_NOMOD) + interest(3) + } + field(LE,DBF_DOUBLE) { + prompt("Prev Value of E") + special(SPC_NOMOD) + interest(3) + } + field(LF,DBF_DOUBLE) { + prompt("Prev Value of F") + special(SPC_NOMOD) + interest(3) + } + field(LG,DBF_DOUBLE) { + prompt("Prev Value of G") + special(SPC_NOMOD) + interest(3) + } + field(LH,DBF_DOUBLE) { + prompt("Prev Value of H") + special(SPC_NOMOD) + interest(3) + } + field(LI,DBF_DOUBLE) { + prompt("Prev Value of I") + special(SPC_NOMOD) + interest(3) + } + field(LJ,DBF_DOUBLE) { + prompt("Prev Value of J") + special(SPC_NOMOD) + interest(3) + } + field(LK,DBF_DOUBLE) { + prompt("Prev Value of K") + special(SPC_NOMOD) + interest(3) + } + field(LL,DBF_DOUBLE) { + prompt("Prev Value of L") + special(SPC_NOMOD) + interest(3) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Value Monitored") + special(SPC_NOMOD) + interest(3) + } +} diff --git a/modules/database/src/std/rec/waveformRecord.c b/modules/database/src/std/rec/waveformRecord.c new file mode 100644 index 000000000..c987cfbc4 --- /dev/null +++ b/modules/database/src/std/rec/waveformRecord.c @@ -0,0 +1,378 @@ +/*************************************************************************\ +* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* recWaveform.c - Record Support Routines for Waveform records */ +/* + * Original Author: Bob Dalesio + * Date: 7-14-89 + */ + +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "epicsString.h" +#include "alarm.h" +#include "callback.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "dbScan.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "special.h" +#include "cantProceed.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "waveformRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +static long special(DBADDR *, int); +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +static long put_array_info(DBADDR *, long); +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +#define get_alarm_double NULL +rset waveformRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,waveformRSET); +struct wfdset { /* waveform dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_wf; /*returns: (-1,0)=>(failure,success)*/ +}; + +static void monitor(waveformRecord *); +static long readValue(waveformRecord *); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct waveformRecord *prec = (struct waveformRecord *)pcommon; + struct wfdset *pdset; + + if (pass == 0){ + if (prec->nelm <= 0) + prec->nelm = 1; + if (prec->ftvl > DBF_ENUM) + prec->ftvl = DBF_UCHAR; + prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl), + "waveform calloc failed"); + if (prec->nelm == 1) { + prec->nord = 1; + } else { + prec->nord = 0; + } + return 0; + } + + recGblInitSimm(pcommon, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + + /* must have dset defined */ + if (!(pdset = (struct wfdset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"wf: init_record"); + return S_dev_noDSET; + } + /* must have read_wf function defined */ + if ((pdset->number < 5) || (pdset->read_wf == NULL)) { + recGblRecordError(S_dev_missingSup,(void *)prec,"wf: init_record"); + return S_dev_missingSup; + } + if (! pdset->init_record) return 0; + + return (*pdset->init_record)(prec); +} + +static long process(struct dbCommon *pcommon) +{ + struct waveformRecord *prec = (struct waveformRecord *)pcommon; + struct wfdset *pdset = (struct wfdset *)(prec->dset); + unsigned char pact=prec->pact; + + if ((pdset==NULL) || (pdset->read_wf==NULL)) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup, (void *)prec, "read_wf"); + return S_dev_missingSup; + } + + if (pact && prec->busy) return 0; + + readValue(prec); /* read the new value */ + if (!pact && prec->pact) return 0; + + prec->udf = FALSE; + prec->pact = TRUE; + recGblGetTimeStampSimm(prec, prec->simm, &prec->siol); + + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return 0; +} + +static long special(DBADDR *paddr, int after) +{ + waveformRecord *prec = (waveformRecord *)(paddr->precord); + int special_type = paddr->special; + + switch(special_type) { + case(SPC_MOD): + if (dbGetFieldIndex(paddr) == waveformRecordSIMM) { + if (!after) + recGblSaveSimm(prec->sscn, &prec->oldsimm, prec->simm); + else + recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); + return(0); + } + default: + recGblDbaddrError(S_db_badChoice, paddr, "waveform: special"); + return(S_db_badChoice); + } +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + paddr->no_elements = prec->nelm; + paddr->field_type = prec->ftvl; + paddr->field_size = dbValueSize(prec->ftvl); + paddr->dbr_field_type = prec->ftvl; + + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + *no_elements = prec->nord; + *offset = 0; + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + prec->nord = nNew; + if (prec->nord > prec->nelm) + prec->nord = prec->nelm; + + db_post_events(prec, &prec->nord, DBE_VALUE | DBE_LOG); + return 0; +} + +#define indexof(field) waveformRecord##field + +static long get_units(DBADDR *paddr, char *units) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + if (prec->ftvl == DBF_STRING || prec->ftvl == DBF_ENUM) + break; + case indexof(HOPR): + case indexof(LOPR): + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return 0; +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + *precision = prec->prec; + if (dbGetFieldIndex(paddr) != indexof(VAL)) + recGblGetPrec(paddr, precision); + return 0; +} + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + case indexof(BUSY): + pgd->upper_disp_limit = 1; + pgd->lower_disp_limit = 0; + break; + case indexof(NORD): + pgd->upper_disp_limit = prec->nelm; + pgd->lower_disp_limit = 0; + break; + default: + recGblGetGraphicDouble(paddr, pgd); + } + return 0; +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + waveformRecord *prec = (waveformRecord *) paddr->precord; + + switch (dbGetFieldIndex(paddr)) { + case indexof(VAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + case indexof(BUSY): + pcd->upper_ctrl_limit = 1; + pcd->lower_ctrl_limit = 0; + break; + case indexof(NORD): + pcd->upper_ctrl_limit = prec->nelm; + pcd->lower_ctrl_limit = 0; + break; + default: + recGblGetControlDouble(paddr, pcd); + } + return 0; +} + +static void monitor(waveformRecord *prec) +{ + unsigned short monitor_mask = 0; + unsigned int hash = 0; + + monitor_mask = recGblResetAlarms(prec); + + if (prec->mpst == waveformPOST_Always) + monitor_mask |= DBE_VALUE; + if (prec->apst == waveformPOST_Always) + monitor_mask |= DBE_LOG; + + /* Calculate hash if we are interested in OnChange events. */ + if ((prec->mpst == waveformPOST_OnChange) || + (prec->apst == waveformPOST_OnChange)) { + hash = epicsMemHash((char *)prec->bptr, + prec->nord * dbValueSize(prec->ftvl), 0); + + /* Only post OnChange values if the hash is different. */ + if (hash != prec->hash) { + if (prec->mpst == waveformPOST_OnChange) + monitor_mask |= DBE_VALUE; + if (prec->apst == waveformPOST_OnChange) + monitor_mask |= DBE_LOG; + + /* Store hash for next process. */ + prec->hash = hash; + /* Post HASH. */ + db_post_events(prec, &prec->hash, DBE_VALUE); + } + } + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } +} + +static long readValue(waveformRecord *prec) +{ + struct wfdset *pdset = (struct wfdset *) prec->dset; + long status = 0; + + if (!prec->pact) { + status = recGblGetSimm((dbCommon *)prec, &prec->sscn, &prec->oldsimm, &prec->simm, &prec->siml); + if (status) return status; + } + + switch (prec->simm) { + case menuYesNoNO: { + epicsUInt32 nord = prec->nord; + + status = pdset->read_wf(prec); + if (nord != prec->nord) + db_post_events(prec, &prec->nord, DBE_VALUE | DBE_LOG); + break; + } + + case menuYesNoYES: { + long nRequest = prec->nelm; + + recGblSetSevr(prec, SIMM_ALARM, prec->sims); + if (prec->pact || (prec->sdly < 0.)) { + status = dbGetLink(&prec->siol, prec->ftvl, prec->bptr, 0, &nRequest); + if (status == 0) prec->udf = FALSE; + /* nord set only for db links: needed for old db_access */ + if (!dbLinkIsConstant(&prec->siol)) { + prec->nord = nRequest; + db_post_events(prec, &prec->nord, DBE_VALUE | DBE_LOG); + } + prec->pact = FALSE; + } else { /* !prec->pact && delay >= 0. */ + CALLBACK *pvt = prec->simpvt; + if (!pvt) { + pvt = calloc(1, sizeof(CALLBACK)); /* very lazy allocation of callback structure */ + prec->simpvt = pvt; + } + if (pvt) callbackRequestProcessCallbackDelayed(pvt, prec->prio, prec, prec->sdly); + prec->pact = TRUE; + } + break; + } + + default: + recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); + status = -1; + } + + return status; +} diff --git a/modules/database/src/std/rec/waveformRecord.dbd.pod b/modules/database/src/std/rec/waveformRecord.dbd.pod new file mode 100644 index 000000000..82561d076 --- /dev/null +++ b/modules/database/src/std/rec/waveformRecord.dbd.pod @@ -0,0 +1,185 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +=title Waveform Record (waveform) + +... + +=head2 Record-specific Menus + +=head3 Menu waveformPOST + +The MPST and APST fields use this menu to determine when to post new value +and archive monitors respectively. + +=menu waveformPOST + +... + +=head2 Parameter Fields + +The record-specific fields are described below. + +=recordtype waveform + +... + +=cut + +menu(waveformPOST) { + choice(waveformPOST_Always,"Always") + choice(waveformPOST_OnChange,"On Change") +} + +recordtype(waveform) { + +=fields VAL, FTVL, MPST, APST + +=cut + + include "dbCommon.dbd" + field(VAL,DBF_NOACCESS) { + prompt("Value") + asl(ASL0) + special(SPC_DBADDR) + pp(TRUE) + extra("void * val") + #=type Set by FTVL + #=read Yes + #=write Yes + } + field(RARM,DBF_SHORT) { + prompt("Rearm the waveform") + promptgroup("30 - Action") + pp(TRUE) + interest(1) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_DOUBLE) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(LOPR,DBF_DOUBLE) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + prop(YES) + } + field(NELM,DBF_ULONG) { + prompt("Number of Elements") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(FTVL,DBF_MENU) { + prompt("Field Type of Value") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + menu(menuFtype) + } + field(BUSY,DBF_SHORT) { + prompt("Busy Indicator") + special(SPC_NOMOD) + } + field(NORD,DBF_ULONG) { + prompt("Number elements read") + special(SPC_NOMOD) + } + field(BPTR,DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + interest(4) + extra("void * bptr") + } + field(SIOL,DBF_INLINK) { + prompt("Simulation Input Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Simulation Mode Link") + promptgroup("90 - Simulate") + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + special(SPC_MOD) + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Simulation Mode Severity") + promptgroup("90 - Simulate") + interest(2) + menu(menuAlarmSevr) + } + field(OLDSIMM,DBF_MENU) { + prompt("Prev. Simulation Mode") + special(SPC_NOMOD) + interest(4) + menu(menuSimm) + } + field(SSCN,DBF_MENU) { + prompt("Sim. Mode Scan") + promptgroup("90 - Simulate") + interest(1) + menu(menuScan) + initial("65535") + } + field(SDLY,DBF_DOUBLE) { + prompt("Sim. Mode Async Delay") + promptgroup("90 - Simulate") + interest(2) + initial("-1.0") + } + %#include "callback.h" + field(SIMPVT,DBF_NOACCESS) { + prompt("Sim. Mode Private") + special(SPC_NOMOD) + interest(4) + extra("CALLBACK *simpvt") + } + field(MPST,DBF_MENU) { + prompt("Post Value Monitors") + promptgroup("80 - Display") + interest(1) + menu(waveformPOST) + } + field(APST,DBF_MENU) { + prompt("Post Archive Monitors") + promptgroup("80 - Display") + interest(1) + menu(waveformPOST) + } + field(HASH,DBF_ULONG) { + prompt("Hash of OnChange data.") + interest(3) + } +} diff --git a/modules/database/src/std/softIoc/Makefile b/modules/database/src/std/softIoc/Makefile new file mode 100644 index 000000000..a432d358d --- /dev/null +++ b/modules/database/src/std/softIoc/Makefile @@ -0,0 +1,34 @@ +########################################################################## +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +########################################################################## + +# This is a Makefile fragment, see src/ioc/Makefile. + +SRC_DIRS += $(STDDIR)/softIoc + +PROD_IOC_DEFAULT = softIoc +PROD_IOC_iOS = -nil- + +DBD += base.dbd +DBD += asSub.dbd +DBD += softIoc.dbd + +softIoc_DBD += base.dbd +softIoc_DBD += dlload.dbd +softIoc_DBD += system.dbd + +softIoc_SRCS += softIoc_registerRecordDeviceDriver.cpp +softIoc_SRCS_DEFAULT += softMain.cpp +softIoc_SRCS_vxWorks = -nil- + +softIoc_LIBS = $(EPICS_BASE_IOC_LIBS) + +DB += softIocExit.db + +FINAL_LOCATION ?= $(shell $(PERL) $(TOOLS)/fullPathName.pl $(INSTALL_LOCATION)) + +CLEANS += epicsInstallDir.h + diff --git a/modules/database/src/std/softIoc/RULES b/modules/database/src/std/softIoc/RULES new file mode 100644 index 000000000..81f5135ad --- /dev/null +++ b/modules/database/src/std/softIoc/RULES @@ -0,0 +1,23 @@ +########################################################################## +# Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +########################################################################## + +# This is a Makefile fragment, see src/ioc/Makefile. + +softIoc.dbd$(DEP): $(COMMON_DIR)/stdRecords.dbd +softIoc.dbd$(DEP): $(COMMON_DIR)/filters.dbd +softIoc.dbd$(DEP): $(COMMON_DIR)/links.dbd +$(COMMON_DIR)/softIoc.dbd: $(COMMON_DIR)/stdRecords.dbd +$(COMMON_DIR)/softIoc.dbd: $(COMMON_DIR)/filters.dbd +$(COMMON_DIR)/softIoc.dbd: $(COMMON_DIR)/links.dbd +$(COMMON_DIR)/softIoc.dbd: $(STDDIR)/softIoc/Makefile + +softMain$(DEP): epicsInstallDir.h + +epicsInstallDir.h: $(TOP)/configure/CONFIG_SITE* + $(ECHO) "FINAL_LOCATION=$(FINAL_LOCATION)" + $(PERL) $(STDDIR)/softIoc/makeInstallDir.pl "$(FINAL_LOCATION)" > $@ + diff --git a/modules/database/src/std/softIoc/asSub.dbd b/modules/database/src/std/softIoc/asSub.dbd new file mode 100644 index 000000000..d02852fae --- /dev/null +++ b/modules/database/src/std/softIoc/asSub.dbd @@ -0,0 +1,3 @@ +# Register access security subroutines +registrar(asSub) + diff --git a/modules/database/src/std/softIoc/base.dbd b/modules/database/src/std/softIoc/base.dbd new file mode 100644 index 000000000..564a83845 --- /dev/null +++ b/modules/database/src/std/softIoc/base.dbd @@ -0,0 +1,30 @@ +# This file includes the standard record types and device support +# provided by Base and (usually) loaded into all IOCs. + +# Fixed menus +include "menuGlobal.dbd" + +# Modifyable menus +include "menuConvert.dbd" +include "menuScan.dbd" + +# Record types +include "stdRecords.dbd" + +# Channel filters & plugins +include "filters.dbd" + +# Link types +include "links.dbd" + +# Standard device support +include "devSoft.dbd" + +# Access security subroutines +include "asSub.dbd" + +# IOC Core variables +include "dbCore.dbd" + +# RSRV server +include "rsrv.dbd" diff --git a/modules/database/src/std/softIoc/makeInstallDir.pl b/modules/database/src/std/softIoc/makeInstallDir.pl new file mode 100644 index 000000000..61f271f15 --- /dev/null +++ b/modules/database/src/std/softIoc/makeInstallDir.pl @@ -0,0 +1,28 @@ +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use strict; + +die "$0: Argument missing, INSTALL_LOCATION\n" if @ARGV == 0; +die "$0: Too many arguments, expecting one\n" unless @ARGV == 1; + +my $path = shift; + +$path =~ s/\\/\\\\/gx; +$path =~ s/^'//; +$path =~ s/'$//; + +print "/* THIS IS A GENERATED FILE. DO NOT EDIT! */\n", + "\n", + "#ifndef INC_epicsInstallDir_H\n", + "#define INC_epicsInstallDir_H\n", + "\n", + "#define EPICS_BASE \"$path\"\n", + "\n", + "#endif /* INC_epicsInstallDir_H */\n"; diff --git a/modules/database/src/std/softIoc/softIocExit.db b/modules/database/src/std/softIoc/softIocExit.db new file mode 100644 index 000000000..c530f778a --- /dev/null +++ b/modules/database/src/std/softIoc/softIocExit.db @@ -0,0 +1,15 @@ +# softIocExit.db + +record(sub,"$(IOC):exit") { + field(DESC,"Exit subroutine") + field(SCAN,"Passive") + field(SNAM,"exit") +} + +record(stringin,"$(IOC):BaseVersion") { + field(DESC,"EPICS Base Version") + field(DTYP,"getenv") + field(INP,"@EPICS_VERSION_FULL") + field(PINI,"YES") + field(DISP,1) +} diff --git a/modules/database/src/std/softIoc/softMain.cpp b/modules/database/src/std/softIoc/softMain.cpp new file mode 100644 index 000000000..8400a6554 --- /dev/null +++ b/modules/database/src/std/softIoc/softMain.cpp @@ -0,0 +1,233 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2003 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to the Software License Agreement +* found in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Author: Andrew Johnson Date: 2003-04-08 */ + +/* Usage: + * softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf] + * [-m macro=value,macro2=value2] [-d file.db] + * [-x prefix] [st.cmd] + * + * If used the -D option must come first, and specify the + * path to the softIoc.dbd file. The compile-time install + * location is saved in the binary as a default. + * + * Usage information will be printed if -h is given, then + * the program will exit normally. + * + * The -S option prevents an interactive shell being started + * after all arguments have been processed. + * + * Previous versions accepted a -s option to cause a shell + * to be started; this option is still accepted but ignored + * since a command shell is now started by default. + * + * Access Security can be enabled with the -a option giving + * the name of the configuration file; if any macros were + * set with -m before the -a option was given, they will be + * used as access security substitution macros. + * + * Any number of -m and -d arguments can be interspersed; + * the macros are applied to the following .db files. Each + * later -m option causes earlier macros to be discarded. + * + * The -x option loads the softIocExit.db with the macro + * IOC set to the string provided. This database contains + * a subroutine record named $(IOC):exit which has its field + * SNAM set to "exit". When this record is processed, the + * subroutine that runs will call epicsExit() with the value + * of the field A determining whether the exit status is + * EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0). + * + * A st.cmd file is optional. If any databases were loaded + * the st.cmd file will be run *after* iocInit. To perform + * iocsh commands before iocInit, all database loading must + * be performed by the script itself, or by the user from + * the interactive IOC shell. + */ + +#include +#include +#include +#include +#include + +#include "registryFunction.h" +#include "epicsThread.h" +#include "epicsExit.h" +#include "epicsStdio.h" +#include "dbStaticLib.h" +#include "subRecord.h" +#include "dbAccess.h" +#include "asDbLib.h" +#include "iocInit.h" +#include "iocsh.h" +#include "epicsInstallDir.h" + +extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase); + +#define DBD_FILE EPICS_BASE "/dbd/softIoc.dbd" +#define EXIT_FILE EPICS_BASE "/db/softIocExit.db" + +const char *arg0; +const char *base_dbd = DBD_FILE; +const char *exit_db = EXIT_FILE; + + +static void exitSubroutine(subRecord *precord) { + epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void usage(int status) { + printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0); + puts("\t[-m macro=value,macro2=value2] [-d file.db]"); + puts("\t[-x prefix] [st.cmd]"); + puts("Compiled-in path to softIoc.dbd is:"); + printf("\t%s\n", base_dbd); + epicsExit(status); +} + + +int main(int argc, char *argv[]) +{ + char *dbd_file = const_cast(base_dbd); + char *macros = NULL; + char xmacro[PVNAME_STRINGSZ + 4]; + int startIocsh = 1; /* default = start shell */ + int loadedDb = 0; + + arg0 = strrchr(*argv, '/'); + if (!arg0) { + arg0 = *argv; + } else { + ++arg0; /* skip the '/' */ + } + + --argc, ++argv; + + /* Do this here in case the dbd file not available */ + if (argc>0 && **argv=='-' && (*argv)[1]=='h') { + usage(EXIT_SUCCESS); + } + + if (argc>1 && **argv=='-' && (*argv)[1]=='D') { + dbd_file = *++argv; + argc -= 2; + ++argv; + } + + if (dbLoadDatabase(dbd_file, NULL, NULL)) { + epicsExit(EXIT_FAILURE); + } + + softIoc_registerRecordDeviceDriver(pdbbase); + registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine); + + while (argc>1 && **argv == '-') { + switch ((*argv)[1]) { + case 'a': + if (macros) asSetSubstitutions(macros); + asSetFilename(*++argv); + --argc; + break; + + case 'd': + if (dbLoadRecords(*++argv, macros)) { + epicsExit(EXIT_FAILURE); + } + loadedDb = 1; + --argc; + break; + + case 'h': + usage(EXIT_SUCCESS); + + case 'm': + macros = *++argv; + --argc; + break; + + case 'S': + startIocsh = 0; + break; + + case 's': + break; + + case 'x': + epicsSnprintf(xmacro, sizeof xmacro, "IOC=%s", *++argv); + if (dbLoadRecords(exit_db, xmacro)) { + epicsExit(EXIT_FAILURE); + } + loadedDb = 1; + --argc; + break; + + default: + printf("%s: option '%s' not recognized\n", arg0, *argv); + usage(EXIT_FAILURE); + } + --argc; + ++argv; + } + + if (argc>0 && **argv=='-') { + switch((*argv)[1]) { + case 'a': + case 'd': + case 'm': + case 'x': + printf("%s: missing argument to option '%s'\n", arg0, *argv); + usage(EXIT_FAILURE); + + case 'h': + usage(EXIT_SUCCESS); + + case 'S': + startIocsh = 0; + break; + + case 's': + break; + + default: + printf("%s: option '%s' not recognized\n", arg0, *argv); + usage(EXIT_FAILURE); + } + --argc; + ++argv; + } + + if (loadedDb) { + iocInit(); + epicsThreadSleep(0.2); + } + + /* run user's startup script */ + if (argc>0) { + if (iocsh(*argv)) epicsExit(EXIT_FAILURE); + epicsThreadSleep(0.2); + loadedDb = 1; /* Give it the benefit of the doubt... */ + } + + /* start an interactive shell if it was requested */ + if (startIocsh) { + iocsh(NULL); + } else { + if (loadedDb) { + epicsThreadExitMain(); + } else { + printf("%s: Nothing to do!\n", arg0); + usage(EXIT_FAILURE); + } + } + epicsExit(EXIT_SUCCESS); + /*Note that the following statement will never be executed*/ + return 0; +} diff --git a/modules/database/src/template/Makefile b/modules/database/src/template/Makefile new file mode 100644 index 000000000..6259b6fca --- /dev/null +++ b/modules/database/src/template/Makefile @@ -0,0 +1,65 @@ +TOP=../.. + +include $(TOP)/configure/CONFIG + +TEMPLATES_DIR = makeBaseApp + +TEMPLATES += top/iocApp/Makefile +TEMPLATES += top/iocApp/Db/Makefile +TEMPLATES += top/iocApp/src/Makefile +TEMPLATES += top/iocApp/src/_APPNAME_Main.cpp + +TEMPLATES += top/exampleApp/Makefile +TEMPLATES += top/exampleApp/Db/Makefile +TEMPLATES += top/exampleApp/Db/circle.db +TEMPLATES += top/exampleApp/Db/dbExample1.db +TEMPLATES += top/exampleApp/Db/dbExample2.db +TEMPLATES += top/exampleApp/Db/_APPNAME_Version.db +TEMPLATES += top/exampleApp/Db/dbSubExample.db +TEMPLATES += top/exampleApp/Db/user.substitutions +TEMPLATES += top/exampleApp/src/Makefile +TEMPLATES += top/exampleApp/src/dev_APPNAME_Version.c +TEMPLATES += top/exampleApp/src/dev_APPNAME_Version.dbd +TEMPLATES += top/exampleApp/src/xxxRecord.dbd +TEMPLATES += top/exampleApp/src/xxxRecord.c +TEMPLATES += top/exampleApp/src/devXxxSoft.c +TEMPLATES += top/exampleApp/src/xxxSupport.dbd +TEMPLATES += top/exampleApp/src/sncExample.stt +TEMPLATES += top/exampleApp/src/sncProgram.st +TEMPLATES += top/exampleApp/src/sncExample.dbd +TEMPLATES += top/exampleApp/src/dbSubExample.c +TEMPLATES += top/exampleApp/src/dbSubExample.dbd +TEMPLATES += top/exampleApp/src/_APPNAME_Main.cpp +TEMPLATES += top/exampleApp/src/_APPNAME_Hello.c +TEMPLATES += top/exampleApp/src/_APPNAME_Hello.dbd +TEMPLATES += top/exampleApp/src/initTrace.c +TEMPLATES += top/exampleApp/src/initTrace.dbd + +TEMPLATES += top/exampleBoot/Makefile +TEMPLATES += top/exampleBoot/nfsCommands@vxWorks +TEMPLATES += top/exampleBoot/nfsCommands@RTEMS +TEMPLATES += top/exampleBoot/ioc/Makefile@Common +TEMPLATES += top/exampleBoot/ioc/Makefile@vxWorks +TEMPLATES += top/exampleBoot/ioc/Makefile@win32 +TEMPLATES += top/exampleBoot/ioc/Makefile@windows +TEMPLATES += top/exampleBoot/ioc/Makefile@cygwin +TEMPLATES += top/exampleBoot/ioc/st.cmd@Common +TEMPLATES += top/exampleBoot/ioc/st.cmd@vxWorks +TEMPLATES += top/exampleBoot/ioc/st.cmd@RTEMS +TEMPLATES += top/exampleBoot/ioc/README@Common +TEMPLATES += top/exampleBoot/ioc/README@vxWorks + +TEMPLATES += top/iocBoot/Makefile +TEMPLATES += top/iocBoot/nfsCommands@vxWorks +TEMPLATES += top/iocBoot/nfsCommands@RTEMS +TEMPLATES += top/iocBoot/ioc/Makefile@Common +TEMPLATES += top/iocBoot/ioc/Makefile@vxWorks +TEMPLATES += top/iocBoot/ioc/Makefile@win32 +TEMPLATES += top/iocBoot/ioc/Makefile@windows +TEMPLATES += top/iocBoot/ioc/Makefile@cygwin +TEMPLATES += top/iocBoot/ioc/st.cmd@Common +TEMPLATES += top/iocBoot/ioc/st.cmd@Cross +TEMPLATES += top/iocBoot/ioc/st.cmd@vxWorks +TEMPLATES += top/iocBoot/ioc/st.cmd@RTEMS + +include $(TOP)/configure/RULES diff --git a/modules/database/src/template/top/Makefile b/modules/database/src/template/top/Makefile new file mode 100644 index 000000000..19c9068d1 --- /dev/null +++ b/modules/database/src/template/top/Makefile @@ -0,0 +1,31 @@ +# Makefile at top of application tree +TOP = . +include $(TOP)/configure/CONFIG + +# Directories to build, any order +DIRS += configure +DIRS += $(wildcard *Sup) +DIRS += $(wildcard *App) +DIRS += $(wildcard *Top) +DIRS += $(wildcard iocBoot) + +# The build order is controlled by these dependency rules: + +# All dirs except configure depend on configure +$(foreach dir, $(filter-out configure, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += configure)) + +# Any *App dirs depend on all *Sup dirs +$(foreach dir, $(filter %App, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += $(filter %Sup, $(DIRS)))) + +# Any *Top dirs depend on all *Sup and *App dirs +$(foreach dir, $(filter %Top, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += $(filter %Sup %App, $(DIRS)))) + +# iocBoot depends on all *App dirs +iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS)) + +# Add any additional dependency rules here: + +include $(TOP)/configure/RULES_TOP diff --git a/modules/database/src/template/top/exampleApp/Db/Makefile b/modules/database/src/template/top/exampleApp/Db/Makefile new file mode 100644 index 000000000..978810d20 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/Db/Makefile @@ -0,0 +1,20 @@ +TOP=../.. +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS BELOW HERE + +# Install databases, templates & substitutions like this +DB += circle.db +DB += dbExample1.db +DB += dbExample2.db +DB += _APPNAME_Version.db +DB += dbSubExample.db +DB += user.substitutions + +# If .db template is not named *.template add +# _TEMPLATE = + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD EXTRA GNUMAKE RULES BELOW HERE + diff --git a/modules/database/src/template/top/exampleApp/Db/_APPNAME_Version.db b/modules/database/src/template/top/exampleApp/Db/_APPNAME_Version.db new file mode 100644 index 000000000..1b03f08ba --- /dev/null +++ b/modules/database/src/template/top/exampleApp/Db/_APPNAME_Version.db @@ -0,0 +1,6 @@ +record(lsi, "$(user):_APPNAME_:version") { + field(DTYP, "_APPNAME_ version") + field(DESC, "Version string") + field(SIZV, "$(SIZV=200)") + field(PINI, "YES") +} diff --git a/modules/database/src/template/top/exampleApp/Db/circle.db b/modules/database/src/template/top/exampleApp/Db/circle.db new file mode 100644 index 000000000..5e792c81b --- /dev/null +++ b/modules/database/src/template/top/exampleApp/Db/circle.db @@ -0,0 +1,72 @@ +record(ao, "$(user):circle:step") { + field(VAL , "1.0") + field(DRVL, "0.0") + field(DRVH, "359") + field(PINI, "YES") +} + +record(ao, "$(user):circle:period") { + field(VAL , "1.0") + field(PINI, "YES") + field(OUT , "$(user):circle:tick.ODLY NPP") +} + +record(calc, "$(user):circle:angle") { + field(PINI, "RUNNING") # bootstrap + field(INPA, "$(user):circle:angle NPP") + field(INPB, "$(user):circle:step NPP") + field(INPD, "360") + field(DESC, "Angle") + field(EGU , "deg") + field(LOLO, "45") + field(LOW , "135") + field(HIGH, "225") + field(HIHI, "315") + field(LLSV, "MAJOR") + field(LSV , "MINOR") + field(HSV , "MINOR") + field(HHSV, "MAJOR") + field(CALC, "C:=A+B;(C>=D)?C-D:C") + field(FLNK, "$(user):circle:x") + field(PREC, "3") + info(Q:group, { + "$(user):circle":{"angle": {+channel:"VAL"}}, + "$(user):line":{"a": {+channel:"VAL"}} + }) + alias("$(user):line:a") +} + +record(calc, "$(user):circle:x") { + field(INPA, "$(user):circle:angle NPP") + field(CALC, "cos(A*PI/180)") + field(TSEL, "$(user):circle:angle.TIME") + field(FLNK, "$(user):circle:y") + field(PREC, "3") + info(Q:group, { + "$(user):circle":{"x": {+channel:"VAL"}} + }) +} + +record(calc, "$(user):circle:y") { + field(INPA, "$(user):circle:angle NPP") + field(CALC, "sin(A*PI/180)") + field(TSEL, "$(user):circle:angle.TIME") + field(PREC, "3") + field(FLNK, "$(user):line:b") + info(Q:group, { + "$(user):circle":{"y": {+channel:"VAL", +trigger:"*"}} + }) +} + +record(ai, "$(user):line:b") { + field(INP, "$(user):line:a NPP") + field(FLNK, "$(user):circle:tick") + info(Q:group, { + "$(user):line":{"b": {+channel:"VAL", +trigger:"*"}} + }) +} + +record(calcout, "$(user):circle:tick") { + field(ODLY, "1.0") + field(OUT , "$(user):circle:angle.PROC CA") # loop +} diff --git a/modules/database/src/template/top/exampleApp/Db/dbExample1.db b/modules/database/src/template/top/exampleApp/Db/dbExample1.db new file mode 100644 index 000000000..4f16adac9 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/Db/dbExample1.db @@ -0,0 +1,62 @@ +record(ai, "$(user):aiExample") +{ + field(DESC, "Analog input") + field(INP, "$(user):calcExample.VAL NPP NMS") + field(EGUF, "10") + field(EGU, "Counts") + field(HOPR, "10") + field(LOPR, "0") + field(HIHI, "8") + field(HIGH, "6") + field(LOW, "4") + field(LOLO, "2") + field(HHSV, "MAJOR") + field(HSV, "MINOR") + field(LSV, "MINOR") + field(LLSV, "MAJOR") +} +record(calc, "$(user):calcExample") +{ + field(DESC, "Counter") + field(SCAN,"1 second") + field(FLNK, "$(user):aiExample") + field(CALC, "(A/dbd +DBD += xxxSupport.dbd + +# Build an IOC support library +LIBRARY_IOC += _APPNAME_Support + +# Compile and add code to the support library +_APPNAME_Support_SRCS += xxxRecord.c +_APPNAME_Support_SRCS += devXxxSoft.c + +# Link locally-provided code into the support library, +# rather than directly into the IOC application, that +# causes problems on Windows DLL builds +_APPNAME_Support_SRCS += dbSubExample.c +_APPNAME_Support_SRCS += dev_APPNAME_Version.c +_APPNAME_Support_SRCS += _APPNAME_Hello.c +_APPNAME_Support_SRCS += initTrace.c + +_APPNAME_Support_LIBS += $(EPICS_BASE_IOC_LIBS) + +# Auto-generate a header file containing a version string. +# Version comes from the VCS if available, else date+time. +GENVERSION = _APPNAME_Version.h +# Macro name +GENVERSIONMACRO = _APPNAME_VERSION + +# Build the IOC application +PROD_IOC = _APPNAME_ + +# _APPNAME_.dbd will be created and installed +DBD += _APPNAME_.dbd + +# _APPNAME_.dbd will include these files: +_APPNAME__DBD += base.dbd +_APPNAME__DBD += xxxSupport.dbd +_APPNAME__DBD += dbSubExample.dbd +_APPNAME__DBD += dev_APPNAME_Version.dbd +_APPNAME__DBD += _APPNAME_Hello.dbd +_APPNAME__DBD += initTrace.dbd + +# _APPNAME__registerRecordDeviceDriver.cpp derives from _APPNAME_.dbd +_APPNAME__SRCS += _APPNAME__registerRecordDeviceDriver.cpp + +# Build the main IOC entry point where needed +_APPNAME__SRCS_DEFAULT += _APPNAME_Main.cpp +_APPNAME__SRCS_vxWorks += -nil- + +# Link in the code from our support library +_APPNAME__LIBS += _APPNAME_Support + +# To build SNL programs, SNCSEQ must be defined +# in the /configure/RELEASE file +ifneq ($(SNCSEQ),) + # Build sncExample into _APPNAME_Support + sncExample_SNCFLAGS += +r + _APPNAME__DBD += sncExample.dbd + # A .stt sequence program is *not* pre-processed: + _APPNAME_Support_SRCS += sncExample.stt + _APPNAME_Support_LIBS += seq pv + _APPNAME__LIBS += seq pv + + # Build sncProgram as a standalone program + PROD_HOST += sncProgram + sncProgram_SNCFLAGS += +m + # A .st sequence program *is* pre-processed: + sncProgram_SRCS += sncProgram.st + sncProgram_LIBS += seq pv + sncProgram_LIBS += $(EPICS_BASE_HOST_LIBS) +endif + +# Link QSRV (pvAccess Server) if available +ifdef EPICS_QSRV_MAJOR_VERSION + _APPNAME__LIBS += qsrv + _APPNAME__LIBS += $(EPICS_BASE_PVA_CORE_LIBS) + _APPNAME__DBD += PVAServerRegister.dbd + _APPNAME__DBD += qsrv.dbd +endif + +# Finally link IOC to the EPICS Base libraries +_APPNAME__LIBS += $(EPICS_BASE_IOC_LIBS) + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD EXTRA GNUMAKE RULES BELOW HERE + +# Explicit dependency needed for generated header file +dev_APPNAME_Version$(DEP): $(COMMON_DIR)/$(GENVERSION) diff --git a/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.c b/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.c new file mode 100644 index 000000000..6582b84a4 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.c @@ -0,0 +1,31 @@ +/* Example showing how to register a new command with iocsh */ + +#include + +#include +#include + +/* This is the command, which the vxWorks shell will call directly */ +void hello(const char *name) { + if (name) { + printf("Hello %s, from _APPNAME_\n", name); + } else { + puts("Hello from _APPNAME_"); + } +} + +/* Information needed by iocsh */ +static const iocshArg helloArg0 = {"name", iocshArgString}; +static const iocshArg *helloArgs[] = {&helloArg0}; +static const iocshFuncDef helloFuncDef = {"hello", 1, helloArgs}; + +/* Wrapper called by iocsh, selects the argument types that hello needs */ +static void helloCallFunc(const iocshArgBuf *args) { + hello(args[0].sval); +} + +/* Registration routine, runs at startup */ +static void helloRegister(void) { + iocshRegister(&helloFuncDef, helloCallFunc); +} +epicsExportRegistrar(helloRegister); diff --git a/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.dbd b/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.dbd new file mode 100644 index 000000000..64eb0389a --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/_APPNAME_Hello.dbd @@ -0,0 +1 @@ +registrar(helloRegister) diff --git a/modules/database/src/template/top/exampleApp/src/_APPNAME_Main.cpp b/modules/database/src/template/top/exampleApp/src/_APPNAME_Main.cpp new file mode 100644 index 000000000..ae0ecb68a --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/_APPNAME_Main.cpp @@ -0,0 +1,23 @@ +/* _APPNAME_Main.cpp */ +/* Author: Marty Kraimer Date: 17MAR2000 */ + +#include +#include +#include +#include +#include + +#include "epicsExit.h" +#include "epicsThread.h" +#include "iocsh.h" + +int main(int argc,char *argv[]) +{ + if(argc>=2) { + iocsh(argv[1]); + epicsThreadSleep(.2); + } + iocsh(NULL); + epicsExit(0); + return(0); +} diff --git a/modules/database/src/template/top/exampleApp/src/dbSubExample.c b/modules/database/src/template/top/exampleApp/src/dbSubExample.c new file mode 100644 index 000000000..1cc748b12 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/dbSubExample.c @@ -0,0 +1,49 @@ +#include + +#include +#include +#include +#include +#include + +int mySubDebug; + +static long mySubInit(subRecord *precord) +{ + if (mySubDebug) + printf("Record %s called mySubInit(%p)\n", + precord->name, (void*) precord); + return 0; +} + +static long mySubProcess(subRecord *precord) +{ + if (mySubDebug) + printf("Record %s called mySubProcess(%p)\n", + precord->name, (void*) precord); + return 0; +} + +static long myAsubInit(aSubRecord *precord) +{ + if (mySubDebug) + printf("Record %s called myAsubInit(%p)\n", + precord->name, (void*) precord); + return 0; +} + +static long myAsubProcess(aSubRecord *precord) +{ + if (mySubDebug) + printf("Record %s called myAsubProcess(%p)\n", + precord->name, (void*) precord); + return 0; +} + +/* Register these symbols for use by IOC code: */ + +epicsExportAddress(int, mySubDebug); +epicsRegisterFunction(mySubInit); +epicsRegisterFunction(mySubProcess); +epicsRegisterFunction(myAsubInit); +epicsRegisterFunction(myAsubProcess); diff --git a/modules/database/src/template/top/exampleApp/src/dbSubExample.dbd b/modules/database/src/template/top/exampleApp/src/dbSubExample.dbd new file mode 100644 index 000000000..5f6e40ac7 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/dbSubExample.dbd @@ -0,0 +1,5 @@ +variable(mySubDebug) +function(mySubInit) +function(mySubProcess) +function(myAsubInit) +function(myAsubProcess) diff --git a/modules/database/src/template/top/exampleApp/src/devXxxSoft.c b/modules/database/src/template/top/exampleApp/src/devXxxSoft.c new file mode 100644 index 000000000..0507fdfd0 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/devXxxSoft.c @@ -0,0 +1,58 @@ +/* devXxxSoft.c */ +/* Example device support module */ + +#include +#include +#include +#include + +#include "alarm.h" +#include "cvtTable.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "xxxRecord.h" +#include "epicsExport.h" + +/*Create the dset for devXxxSoft */ +static long init_record(); +static long read_xxx(); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_xxx; +}devXxxSoft={ + 5, + NULL, + NULL, + init_record, + NULL, + read_xxx, +}; +epicsExportAddress(dset,devXxxSoft); + + +static long init_record(pxxx) + struct xxxRecord *pxxx; +{ + if(recGblInitConstantLink(&pxxx->inp,DBF_DOUBLE,&pxxx->val)) + pxxx->udf = FALSE; + return(0); +} + +static long read_xxx(pxxx) + struct xxxRecord *pxxx; +{ + long status; + + status = dbGetLink(&(pxxx->inp),DBF_DOUBLE, &(pxxx->val),0,0); + /*If return was succesful then set undefined false*/ + if(!status) pxxx->udf = FALSE; + return(0); +} diff --git a/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.c b/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.c new file mode 100644 index 000000000..b998e6db3 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.c @@ -0,0 +1,38 @@ +/* dev_APPNAME_Version.c */ +/* Example device support for the lsi (long string input) record + * providing the module version string as the value + */ + +#include +#include +#include + +#include "devSup.h" +#include "lsiRecord.h" + +#include "_APPNAME_Version.h" + +/* must be last include */ +#include "epicsExport.h" + +const char version[] = _APPNAME_VERSION; + +static long read_string(lsiRecord *prec) +{ + size_t N = sizeof version; + char *buf = prec->val; + + if (N > prec->sizv) + N = prec->sizv; + prec->len = N; + + memcpy(buf, version, N); + buf[N - 1] = '\0'; + + return 0; +} + +static lsidset dev_CSAFEAPPNAME_Version = { + 5, NULL, NULL, NULL, NULL, read_string +}; +epicsExportAddress(dset,dev_CSAFEAPPNAME_Version); diff --git a/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.dbd b/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.dbd new file mode 100644 index 000000000..67295f3f0 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/dev_APPNAME_Version.dbd @@ -0,0 +1 @@ +device(lsi,INST_IO,dev_CSAFEAPPNAME_Version,"_APPNAME_ version") diff --git a/modules/database/src/template/top/exampleApp/src/initTrace.c b/modules/database/src/template/top/exampleApp/src/initTrace.c new file mode 100644 index 000000000..50bc8e8c7 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/initTrace.c @@ -0,0 +1,39 @@ +/* initTrace.c */ + +/* + * An initHook routine to trace the iocInit() process. + * Prints out the name of each state as it is reached. + */ + +#include + +#include "initHooks.h" +#include "epicsExport.h" +#include "iocsh.h" + + +static void trace(initHookState state) { + printf("iocInit: Reached %s\n", initHookName(state)); +} + +int traceIocInit(void) { + static int done = 0; + if (done) + return -1; + done = 1; + + initHookRegister(trace); + puts("iocInit will be traced"); + return 0; +} + + +static const iocshFuncDef traceInitFuncDef = {"traceIocInit", 0, NULL}; +static void traceInitFunc(const iocshArgBuf *args) { + traceIocInit(); +} + +static void initTraceRegister(void) { + iocshRegister(&traceInitFuncDef, traceInitFunc); +} +epicsExportRegistrar(initTraceRegister); diff --git a/modules/database/src/template/top/exampleApp/src/initTrace.dbd b/modules/database/src/template/top/exampleApp/src/initTrace.dbd new file mode 100644 index 000000000..8083c0a50 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/initTrace.dbd @@ -0,0 +1 @@ +registrar(initTraceRegister) diff --git a/modules/database/src/template/top/exampleApp/src/sncExample.dbd b/modules/database/src/template/top/exampleApp/src/sncExample.dbd new file mode 100644 index 000000000..df61066c3 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/sncExample.dbd @@ -0,0 +1 @@ +registrar(sncExampleRegistrar) diff --git a/modules/database/src/template/top/exampleApp/src/sncExample.stt b/modules/database/src/template/top/exampleApp/src/sncExample.stt new file mode 100644 index 000000000..235f3f45c --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/sncExample.stt @@ -0,0 +1,22 @@ +program sncExample +double v; +assign v to "{user}:aiExample"; +monitor v; + +ss ss1 { + state init { + when (delay(10)) { + printf("sncExample: Startup delay over\n"); + } state low + } + state low { + when (v > 5.0) { + printf("sncExample: Changing to high\n"); + } state high + } + state high { + when (v <= 5.0) { + printf("sncExample: Changing to low\n"); + } state low + } +} diff --git a/modules/database/src/template/top/exampleApp/src/sncProgram.st b/modules/database/src/template/top/exampleApp/src/sncProgram.st new file mode 100644 index 000000000..1ba29893e --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/sncProgram.st @@ -0,0 +1 @@ +#include "../sncExample.stt" diff --git a/modules/database/src/template/top/exampleApp/src/xxxRecord.c b/modules/database/src/template/top/exampleApp/src/xxxRecord.c new file mode 100644 index 000000000..c2693c48e --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/xxxRecord.c @@ -0,0 +1,273 @@ +/* xxxRecord.c */ +/* Example record support module */ + +#include +#include +#include +#include + +#include "epicsMath.h" +#include "alarm.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "dbEvent.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "special.h" +#define GEN_SIZE_OFFSET +#include "xxxRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table */ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +static long get_precision(const DBADDR *, long *); +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +static long get_graphic_double(DBADDR *, struct dbr_grDouble *); +static long get_control_double(DBADDR *, struct dbr_ctrlDouble *); +static long get_alarm_double(DBADDR *, struct dbr_alDouble *); + +rset xxxRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset,xxxRSET); + +typedef struct xxxset { /* xxx input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_xxx; +}xxxdset; + +static void checkAlarms(xxxRecord *prec); +static void monitor(xxxRecord *prec); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + xxxRecord *prec = (xxxRecord *)pcommon; + xxxdset *pdset; + long status; + + if (pass==0) return(0); + + if(!(pdset = (xxxdset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"xxx: init_record"); + return(S_dev_noDSET); + } + /* must have read_xxx function defined */ + if( (pdset->number < 5) || (pdset->read_xxx == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"xxx: init_record"); + return(S_dev_missingSup); + } + + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + return(0); +} + +static long process(struct dbCommon *pcommon) +{ + xxxRecord *prec = (xxxRecord *)pcommon; + xxxdset *pdset = (xxxdset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->read_xxx==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_xxx"); + return(S_dev_missingSup); + } + + /* pact must not be set until after calling device support */ + status=(*pdset->read_xxx)(prec); + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStamp(prec); + /* check for alarms */ + checkAlarms(prec); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +static long get_units(DBADDR *paddr, char *units) +{ + xxxRecord *prec=(xxxRecord *)paddr->precord; + + strncpy(units,prec->egu,DB_UNITS_SIZE); + return(0); +} + +static long get_precision(const DBADDR *paddr, long *precision) +{ + xxxRecord *prec=(xxxRecord *)paddr->precord; + + *precision = prec->prec; + if(paddr->pfield == (void *)&prec->val) return(0); + recGblGetPrec(paddr,precision); + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + xxxRecord *prec=(xxxRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if(fieldIndex == xxxRecordVAL + || fieldIndex == xxxRecordHIHI + || fieldIndex == xxxRecordHIGH + || fieldIndex == xxxRecordLOW + || fieldIndex == xxxRecordLOLO + || fieldIndex == xxxRecordHOPR + || fieldIndex == xxxRecordLOPR) { + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + } else recGblGetGraphicDouble(paddr,pgd); + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + xxxRecord *prec=(xxxRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if(fieldIndex == xxxRecordVAL + || fieldIndex == xxxRecordHIHI + || fieldIndex == xxxRecordHIGH + || fieldIndex == xxxRecordLOW + || fieldIndex == xxxRecordLOLO) { + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + } else recGblGetControlDouble(paddr,pcd); + return(0); +} + +static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad) +{ + xxxRecord *prec=(xxxRecord *)paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if(fieldIndex == xxxRecordVAL) { + pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN; + pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN; + pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN; + pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(xxxRecord *prec) +{ + double val; + float hyst, lalm, hihi, high, low, lolo; + unsigned short hhsv, llsv, hsv, lsv; + + if(prec->udf == TRUE ){ + recGblSetSevr(prec,UDF_ALARM,prec->udfs); + return; + } + hihi = prec->hihi; lolo = prec->lolo; high = prec->high; low = prec->low; + hhsv = prec->hhsv; llsv = prec->llsv; hsv = prec->hsv; lsv = prec->lsv; + val = prec->val; hyst = prec->hyst; lalm = prec->lalm; + + /* alarm condition hihi */ + if (hhsv && (val >= hihi || ((lalm==hihi) && (val >= hihi-hyst)))){ + if (recGblSetSevr(prec,HIHI_ALARM,prec->hhsv)) prec->lalm = hihi; + return; + } + + /* alarm condition lolo */ + if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))){ + if (recGblSetSevr(prec,LOLO_ALARM,prec->llsv)) prec->lalm = lolo; + return; + } + + /* alarm condition high */ + if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))){ + if (recGblSetSevr(prec,HIGH_ALARM,prec->hsv)) prec->lalm = high; + return; + } + + /* alarm condition low */ + if (lsv && (val <= low || ((lalm==low) && (val <= low+hyst)))){ + if (recGblSetSevr(prec,LOW_ALARM,prec->lsv)) prec->lalm = low; + return; + } + + /* we get here only if val is out of alarm by at least hyst */ + prec->lalm = val; + return; +} + +static void monitor(xxxRecord *prec) +{ + unsigned short monitor_mask; + double delta; + + monitor_mask = recGblResetAlarms(prec); + /* check for value change */ + delta = prec->mlst - prec->val; + if(delta<0.0) delta = -delta; + if (delta > prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + /* check for archive change */ + delta = prec->alst - prec->val; + if(delta<0.0) delta = -delta; + if (delta > prec->adel) { + /* post events on value field for archive change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + return; +} diff --git a/modules/database/src/template/top/exampleApp/src/xxxRecord.dbd b/modules/database/src/template/top/exampleApp/src/xxxRecord.dbd new file mode 100644 index 000000000..12c1d6202 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/xxxRecord.dbd @@ -0,0 +1,118 @@ +recordtype(xxx) { + include "dbCommon.dbd" + field(VAL,DBF_DOUBLE) { + prompt("Current EGU Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + special(SPC_NOMOD) + interest(1) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup("80 - Display") + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup("80 - Display") + interest(1) + size(16) + } + field(HOPR,DBF_FLOAT) { + prompt("High Operating Range") + promptgroup("80 - Display") + interest(1) + } + field(LOPR,DBF_FLOAT) { + prompt("Low Operating Range") + promptgroup("80 - Display") + interest(1) + } + field(HIHI,DBF_FLOAT) { + prompt("Hihi Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + } + field(LOLO,DBF_FLOAT) { + prompt("Lolo Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + } + field(HIGH,DBF_FLOAT) { + prompt("High Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + } + field(LOW,DBF_FLOAT) { + prompt("Low Alarm Limit") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup("70 - Alarm") + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_DOUBLE) { + prompt("Alarm Deadband") + promptgroup("70 - Alarm") + interest(1) + } + field(ADEL,DBF_DOUBLE) { + prompt("Archive Deadband") + promptgroup("80 - Display") + interest(1) + } + field(MDEL,DBF_DOUBLE) { + prompt("Monitor Deadband") + promptgroup("80 - Display") + interest(1) + } + field(LALM,DBF_DOUBLE) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_DOUBLE) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_DOUBLE) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } +} diff --git a/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd b/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd new file mode 100644 index 000000000..8094bdda6 --- /dev/null +++ b/modules/database/src/template/top/exampleApp/src/xxxSupport.dbd @@ -0,0 +1,2 @@ +include "xxxRecord.dbd" +device(xxx,CONSTANT,devXxxSoft,"SoftChannel") diff --git a/modules/database/src/template/top/exampleBoot/Makefile b/modules/database/src/template/top/exampleBoot/Makefile new file mode 100644 index 000000000..91e47d0b5 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/Makefile @@ -0,0 +1,6 @@ +TOP = .. +include $(TOP)/configure/CONFIG +DIRS += $(wildcard *ioc*) +DIRS += $(wildcard as*) +include $(CONFIG)/RULES_DIRS + diff --git a/modules/database/src/template/top/exampleBoot/ioc/Makefile@Common b/modules/database/src/template/top/exampleBoot/ioc/Makefile@Common new file mode 100644 index 000000000..e064d7344 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/Makefile@Common @@ -0,0 +1,4 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +TARGETS = envPaths +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/exampleBoot/ioc/Makefile@cygwin b/modules/database/src/template/top/exampleBoot/ioc/Makefile@cygwin new file mode 100644 index 000000000..77c3215d7 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/Makefile@cygwin @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths relPaths.sh +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/exampleBoot/ioc/Makefile@vxWorks b/modules/database/src/template/top/exampleBoot/ioc/Makefile@vxWorks new file mode 100644 index 000000000..12ff7f494 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/Makefile@vxWorks @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = cdCommands +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/exampleBoot/ioc/Makefile@win32 b/modules/database/src/template/top/exampleBoot/ioc/Makefile@win32 new file mode 100644 index 000000000..59b32d734 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/Makefile@win32 @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths dllPath.bat +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/exampleBoot/ioc/Makefile@windows b/modules/database/src/template/top/exampleBoot/ioc/Makefile@windows new file mode 100644 index 000000000..59b32d734 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/Makefile@windows @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths dllPath.bat +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/exampleBoot/ioc/README@Common b/modules/database/src/template/top/exampleBoot/ioc/README@Common new file mode 100644 index 000000000..f6dc23be8 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/README@Common @@ -0,0 +1,9 @@ +To start the ioc from this directory execute the command + ../../bin/_ARCH_/ st.cmd + +Alternatively make the st.cmd file directly executable with + chmod +x st.cmd +and check the executable name on the first line of the st.cmd file + +You may need to change the name of the .dbd file given in the +st.cmd's dbLoadDatabase() command before starting the ioc. diff --git a/modules/database/src/template/top/exampleBoot/ioc/README@RTEMS b/modules/database/src/template/top/exampleBoot/ioc/README@RTEMS new file mode 100644 index 000000000..b040e848c --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/README@RTEMS @@ -0,0 +1,6 @@ +Copy the startup script (st.cmd) and top level db and dbd directories and +contents to +<>/epics/<>/ + +Then load the executable into the IOC (floppy disk, network boot, debugger, +etc.) and start it. diff --git a/modules/database/src/template/top/exampleBoot/ioc/README@vxWorks b/modules/database/src/template/top/exampleBoot/ioc/README@vxWorks new file mode 100644 index 000000000..e69de29bb diff --git a/modules/database/src/template/top/exampleBoot/ioc/st.cmd@Common b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@Common new file mode 100644 index 000000000..b2dfb4ae4 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@Common @@ -0,0 +1,29 @@ +#!../../bin/_ARCH_/_APPNAME_ + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file + +< envPaths + +cd "${TOP}" + +## Register all support components +dbLoadDatabase "dbd/_APPNAME_.dbd" +_CSAFEAPPNAME__registerRecordDeviceDriver pdbbase + +## Load record instances +dbLoadTemplate "db/user.substitutions" +dbLoadRecords "db/_APPNAME_Version.db", "user=_USER_" +dbLoadRecords "db/dbSubExample.db", "user=_USER_" + +#- Set this to see messages from mySub +#var mySubDebug 1 + +#- Run this to trace the stages of iocInit +#traceIocInit + +cd "${TOP}/iocBoot/${IOC}" +iocInit + +## Start any sequence programs +#seq sncExample, "user=_USER_" diff --git a/modules/database/src/template/top/exampleBoot/ioc/st.cmd@RTEMS b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@RTEMS new file mode 100644 index 000000000..87e9e3b13 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@RTEMS @@ -0,0 +1,26 @@ +#- Example RTEMS startup script + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file + +#< envPaths + +## Register all support components +dbLoadDatabase("dbd/_APPNAME_.dbd") +_CSAFEAPPNAME__registerRecordDeviceDriver(pdbbase) + +## Load record instances +dbLoadTemplate("db/user.substitutions") +dbLoadRecords("db/_APPNAME_Version.db", "user=_USER_") +dbLoadRecords("db/dbSubExample.db", "user=_USER_") + +#- Set this to see messages from mySub +#var mySubDebug 1 + +#- Run this to trace the stages of iocInit +#traceIocInit + +iocInit + +## Start any sequence programs +#seq(sncExample, "user=_USER_") diff --git a/modules/database/src/template/top/exampleBoot/ioc/st.cmd@vxWorks b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@vxWorks new file mode 100644 index 000000000..617ba6111 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/ioc/st.cmd@vxWorks @@ -0,0 +1,36 @@ +#- Example vxWorks startup file + +#- The following is needed if your board support package doesn't at boot time +#- automatically cd to the directory containing its startup script +#cd "_TOP_/iocBoot/_IOC_" + +< cdCommands +#< ../nfsCommands + +cd topbin + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file +ld 0,0, "_APPNAME_.munch" + +## Register all support components +cd top +dbLoadDatabase "dbd/_APPNAME_.dbd" +_CSAFEAPPNAME__registerRecordDeviceDriver pdbbase + +## Load record instances +dbLoadTemplate "db/user.substitutions" +dbLoadRecords "db/_APPNAME_Version.db", "user=_USER_" +dbLoadRecords "db/dbSubExample.db", "user=_USER_" + +#- Set this to see messages from mySub +#mySubDebug = 1 + +#- Run this to trace the stages of iocInit +#traceIocInit + +cd startup +iocInit + +## Start any sequence programs +#seq &sncExample, "user=_USER_" diff --git a/modules/database/src/template/top/exampleBoot/nfsCommands@RTEMS b/modules/database/src/template/top/exampleBoot/nfsCommands@RTEMS new file mode 100644 index 000000000..18ae461fa --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/nfsCommands@RTEMS @@ -0,0 +1,26 @@ +#- Instructions for creating and using a real nfsCommands file +#- +#- in order to use nfs do the following: +#- 1) Create hostAdd and nfsMount commands for each nfs server +#- 2) In each st.cmd file add the following two commands BEFORE any load commands +#- ../nfs.cmd +#- cd " +#- +#- The hostAdd command has the form: +#- hostAdd("","xxx.xxx.xxx.xxx") +#- +#- You can also mount subdirectories as follows: +#- nfsMount("", "/xxx/xxx/xxx", "/xxx") +#- +#- For example assume +#- +#- host is mercury with inet address 155.77.2.56 +#- You want to mount the directory (which is a file system of mercury) +#- /home/mercury5/iocinfo +#- as +#- /iocinfo +#- +#- The commands would be +#- +#- hostAdd("mercury","155.77.2.56") +#- nfsMount("mercury","/home/mercury5/iocinfo","/iocinfo") diff --git a/modules/database/src/template/top/exampleBoot/nfsCommands@vxWorks b/modules/database/src/template/top/exampleBoot/nfsCommands@vxWorks new file mode 100644 index 000000000..eb302c569 --- /dev/null +++ b/modules/database/src/template/top/exampleBoot/nfsCommands@vxWorks @@ -0,0 +1,29 @@ +#- Instructions for creating and using a real nfsCommands file +#- +#- in order to use nfs do the following: +#- 1) Create hostAdd and nfsMount commands for each nfs server +#- 2) In each st.cmd file add the following two commands BEFORE any load commands +#- ../nfs.cmd +#- cd " +#- +#- The hostAdd command has the form: +#- hostAdd("","xxx.xxx.xxx.xxx") +#- +#- The nfsMount command has the form: +#- nfsMount("", "/xxx/xxx/xxx", "/xxx") +#- +#- You can also mount subdirectories as follows: +#- nfsMountAll("") +#- +#- For example assume +#- +#- host is mercury with inet address 155.77.2.56 +#- You want to mount the directory (which is a file system of mercury) +#- /home/mercury5/iocinfo +#- as +#- /iocinfo +#- +#- The commands would be +#- +#- hostAdd("mercury","155.77.2.56") +#- nfsMount("mercury","/home/mercury5/iocinfo","/iocinfo") diff --git a/modules/database/src/template/top/iocApp/Db/Makefile b/modules/database/src/template/top/iocApp/Db/Makefile new file mode 100644 index 000000000..8eb97279d --- /dev/null +++ b/modules/database/src/template/top/iocApp/Db/Makefile @@ -0,0 +1,18 @@ +TOP=../.. +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +#---------------------------------------------------- +# Create and install (or just install) into /db +# databases, templates, substitutions like this +#DB += xxx.db + +#---------------------------------------------------- +# If .db template is not named *.template add +# _template = + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/database/src/template/top/iocApp/Makefile b/modules/database/src/template/top/iocApp/Makefile new file mode 100644 index 000000000..10e0126aa --- /dev/null +++ b/modules/database/src/template/top/iocApp/Makefile @@ -0,0 +1,8 @@ +TOP = .. +include $(TOP)/configure/CONFIG +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *src*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *Src*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *db*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *Db*)) +include $(TOP)/configure/RULES_DIRS + diff --git a/modules/database/src/template/top/iocApp/src/Makefile b/modules/database/src/template/top/iocApp/src/Makefile new file mode 100644 index 000000000..de6b93a5b --- /dev/null +++ b/modules/database/src/template/top/iocApp/src/Makefile @@ -0,0 +1,42 @@ +TOP=../.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +#============================= +# Build the IOC application + +PROD_IOC = _APPNAME_ +# _APPNAME_.dbd will be created and installed +DBD += _APPNAME_.dbd + +# _APPNAME_.dbd will be made up from these files: +_APPNAME__DBD += base.dbd + +# Include dbd files from all support applications: +#_APPNAME__DBD += xxx.dbd + +# Add all the support libraries needed by this IOC +#_APPNAME__LIBS += xxx + +# _APPNAME__registerRecordDeviceDriver.cpp derives from _APPNAME_.dbd +_APPNAME__SRCS += _APPNAME__registerRecordDeviceDriver.cpp + +# Build the main IOC entry point on workstation OSs. +_APPNAME__SRCS_DEFAULT += _APPNAME_Main.cpp +_APPNAME__SRCS_vxWorks += -nil- + +# Add support from base/src/vxWorks if needed +#_APPNAME__OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary + +# Finally link to the EPICS Base libraries +_APPNAME__LIBS += $(EPICS_BASE_IOC_LIBS) + +#=========================== + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/database/src/template/top/iocApp/src/_APPNAME_Main.cpp b/modules/database/src/template/top/iocApp/src/_APPNAME_Main.cpp new file mode 100644 index 000000000..ae0ecb68a --- /dev/null +++ b/modules/database/src/template/top/iocApp/src/_APPNAME_Main.cpp @@ -0,0 +1,23 @@ +/* _APPNAME_Main.cpp */ +/* Author: Marty Kraimer Date: 17MAR2000 */ + +#include +#include +#include +#include +#include + +#include "epicsExit.h" +#include "epicsThread.h" +#include "iocsh.h" + +int main(int argc,char *argv[]) +{ + if(argc>=2) { + iocsh(argv[1]); + epicsThreadSleep(.2); + } + iocsh(NULL); + epicsExit(0); + return(0); +} diff --git a/modules/database/src/template/top/iocBoot/Makefile b/modules/database/src/template/top/iocBoot/Makefile new file mode 100644 index 000000000..91e47d0b5 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/Makefile @@ -0,0 +1,6 @@ +TOP = .. +include $(TOP)/configure/CONFIG +DIRS += $(wildcard *ioc*) +DIRS += $(wildcard as*) +include $(CONFIG)/RULES_DIRS + diff --git a/modules/database/src/template/top/iocBoot/ioc/Makefile@Common b/modules/database/src/template/top/iocBoot/ioc/Makefile@Common new file mode 100644 index 000000000..e064d7344 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/Makefile@Common @@ -0,0 +1,4 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +TARGETS = envPaths +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/iocBoot/ioc/Makefile@cygwin b/modules/database/src/template/top/iocBoot/ioc/Makefile@cygwin new file mode 100644 index 000000000..77c3215d7 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/Makefile@cygwin @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths relPaths.sh +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/iocBoot/ioc/Makefile@vxWorks b/modules/database/src/template/top/iocBoot/ioc/Makefile@vxWorks new file mode 100644 index 000000000..12ff7f494 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/Makefile@vxWorks @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = cdCommands +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/iocBoot/ioc/Makefile@win32 b/modules/database/src/template/top/iocBoot/ioc/Makefile@win32 new file mode 100644 index 000000000..59b32d734 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/Makefile@win32 @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths dllPath.bat +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/iocBoot/ioc/Makefile@windows b/modules/database/src/template/top/iocBoot/ioc/Makefile@windows new file mode 100644 index 000000000..59b32d734 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/Makefile@windows @@ -0,0 +1,5 @@ +TOP = ../.. +include $(TOP)/configure/CONFIG +ARCH = _ARCH_ +TARGETS = envPaths dllPath.bat +include $(TOP)/configure/RULES.ioc diff --git a/modules/database/src/template/top/iocBoot/ioc/st.cmd@Common b/modules/database/src/template/top/iocBoot/ioc/st.cmd@Common new file mode 100644 index 000000000..dd0811dfb --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/st.cmd@Common @@ -0,0 +1,21 @@ +#!../../bin/_ARCH_/_APPNAME_ + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file + +< envPaths + +cd "${TOP}" + +## Register all support components +dbLoadDatabase "dbd/_APPNAME_.dbd" +_CSAFEAPPNAME__registerRecordDeviceDriver pdbbase + +## Load record instances +#dbLoadRecords("db/xxx.db","user=_USER_") + +cd "${TOP}/iocBoot/${IOC}" +iocInit + +## Start any sequence programs +#seq sncxxx,"user=_USER_" diff --git a/modules/database/src/template/top/iocBoot/ioc/st.cmd@Cross b/modules/database/src/template/top/iocBoot/ioc/st.cmd@Cross new file mode 100644 index 000000000..f42a26c1b --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/st.cmd@Cross @@ -0,0 +1,18 @@ +#!../../bin/_ARCH_/_APPNAME_ + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file + +#< envPaths + +## Register all support components +dbLoadDatabase("../../dbd/_APPNAME_.dbd",0,0) +_CSAFEAPPNAME__registerRecordDeviceDriver(pdbbase) + +## Load record instances +dbLoadRecords("../../db/_APPNAME_.db","user=_USER_") + +iocInit() + +## Start any sequence programs +#seq snc_APPNAME_,"user=_USER_" diff --git a/modules/database/src/template/top/iocBoot/ioc/st.cmd@RTEMS b/modules/database/src/template/top/iocBoot/ioc/st.cmd@RTEMS new file mode 100644 index 000000000..a7d08fbcf --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/st.cmd@RTEMS @@ -0,0 +1,19 @@ +#- Example RTEMS startup script + +#- You may have to change _APPNAME_ to something else +#- everywhere it appears in this file + +#< envPaths + +## Register all support components +dbLoadDatabase("dbd/_APPNAME_.dbd") +_CSAFEAPPNAME__registerRecordDeviceDriver(pdbbase) + +## Load record instances +#dbLoadTemplate("db/_APPNAME_.substitutions") +#dbLoadRecords("db/_APPNAME_.db", "user=_USER_") + +iocInit + +## Start any sequence programs +#seq(sncxxx, "user=_USER_") diff --git a/modules/database/src/template/top/iocBoot/ioc/st.cmd@vxWorks b/modules/database/src/template/top/iocBoot/ioc/st.cmd@vxWorks new file mode 100644 index 000000000..43287d68b --- /dev/null +++ b/modules/database/src/template/top/iocBoot/ioc/st.cmd@vxWorks @@ -0,0 +1,29 @@ +#- Example vxWorks startup file + +#- The following is needed if your board support package doesn't at boot time +#- automatically cd to the directory containing its startup script +#cd "_TOP_/iocBoot/_IOC_" + +< cdCommands +#< ../nfsCommands + +cd topbin + +## You may have to change _APPNAME_ to something else +## everywhere it appears in this file +ld 0,0, "_APPNAME_.munch" + +## Register all support components +cd top +dbLoadDatabase "dbd/_APPNAME_.dbd" +_CSAFEAPPNAME__registerRecordDeviceDriver pdbbase + +## Load record instances +#dbLoadTemplate "db/_APPNAME_.substitutions" +#dbLoadRecords "db/_APPNAME_.db", "user=_USER_" + +cd startup +iocInit + +## Start any sequence programs +#seq &sncxxx, "user=_USER_" diff --git a/modules/database/src/template/top/iocBoot/nfsCommands@RTEMS b/modules/database/src/template/top/iocBoot/nfsCommands@RTEMS new file mode 100644 index 000000000..dd8811319 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/nfsCommands@RTEMS @@ -0,0 +1,29 @@ +#- Instructions for creating and using a real nfsCommands file +#- +#- in order to use nfs do the following: +#- 1) Create hostAdd and nfsMount commands for each nfs server +#- 2) In each st.cmd file add the following two commands BEFORE any load commands +#- ../nfs.cmd +#- cd " +#- +#- The hostAdd command has the form: +#- hostAdd("","xxx.xxx.xxx.xxx") +#- +#- The vxWorks nfsMount command has the form: +#- nfsMount("") +#- +#- You can also mount subdirectories as follows: +#- nfsMount("", "/xxx/xxx/xxx", "/xxx") +#- +#- For example assume +#- +#- host is mercury with inet address 155.77.2.56 +#- You want to mount the directory (which is a file system of mercury) +#- /home/mercury5/iocinfo +#- as +#- /iocinfo +#- +#- The commands would be +#- +#- hostAdd("mercury","155.77.2.56") +#- nfsMount("mercury","/home/mercury5/iocinfo","/iocinfo") diff --git a/modules/database/src/template/top/iocBoot/nfsCommands@vxWorks b/modules/database/src/template/top/iocBoot/nfsCommands@vxWorks new file mode 100644 index 000000000..dd8811319 --- /dev/null +++ b/modules/database/src/template/top/iocBoot/nfsCommands@vxWorks @@ -0,0 +1,29 @@ +#- Instructions for creating and using a real nfsCommands file +#- +#- in order to use nfs do the following: +#- 1) Create hostAdd and nfsMount commands for each nfs server +#- 2) In each st.cmd file add the following two commands BEFORE any load commands +#- ../nfs.cmd +#- cd " +#- +#- The hostAdd command has the form: +#- hostAdd("","xxx.xxx.xxx.xxx") +#- +#- The vxWorks nfsMount command has the form: +#- nfsMount("") +#- +#- You can also mount subdirectories as follows: +#- nfsMount("", "/xxx/xxx/xxx", "/xxx") +#- +#- For example assume +#- +#- host is mercury with inet address 155.77.2.56 +#- You want to mount the directory (which is a file system of mercury) +#- /home/mercury5/iocinfo +#- as +#- /iocinfo +#- +#- The commands would be +#- +#- hostAdd("mercury","155.77.2.56") +#- nfsMount("mercury","/home/mercury5/iocinfo","/iocinfo") diff --git a/modules/database/src/template/top/supportApp/Db/Makefile b/modules/database/src/template/top/supportApp/Db/Makefile new file mode 100644 index 000000000..8eb97279d --- /dev/null +++ b/modules/database/src/template/top/supportApp/Db/Makefile @@ -0,0 +1,18 @@ +TOP=../.. +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +#---------------------------------------------------- +# Create and install (or just install) into /db +# databases, templates, substitutions like this +#DB += xxx.db + +#---------------------------------------------------- +# If .db template is not named *.template add +# _template = + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/database/src/template/top/supportApp/Makefile b/modules/database/src/template/top/supportApp/Makefile new file mode 100644 index 000000000..ab15bfb1c --- /dev/null +++ b/modules/database/src/template/top/supportApp/Makefile @@ -0,0 +1,7 @@ +TOP = .. +include $(TOP)/configure/CONFIG +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *src*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *Src*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *db*)) +DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *Db*)) +include $(TOP)/configure/RULES_DIRS diff --git a/modules/database/src/template/top/supportApp/src/Makefile b/modules/database/src/template/top/supportApp/src/Makefile new file mode 100644 index 000000000..941d94f8b --- /dev/null +++ b/modules/database/src/template/top/supportApp/src/Makefile @@ -0,0 +1,28 @@ +TOP=../.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +#================================================== +# build a support library + +LIBRARY_IOC += _APPNAME_ + +# xxxRecord.h will be created from xxxRecord.dbd +#DBDINC += xxxRecord +# install _APPNAME_.dbd into /dbd +DBD += _APPNAME_.dbd + +# specify all source files to be compiled and added to the library +#_APPNAME__SRCS += xxx + +_APPNAME__LIBS += $(EPICS_BASE_IOC_LIBS) + +#=========================== + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/database/src/template/top/supportApp/src/_APPNAME_.dbd b/modules/database/src/template/top/supportApp/src/_APPNAME_.dbd new file mode 100644 index 000000000..e70223b19 --- /dev/null +++ b/modules/database/src/template/top/supportApp/src/_APPNAME_.dbd @@ -0,0 +1,6 @@ +# provide definitions such as +#include "xxxRecord.dbd" +#device(xxx,CONSTANT,devXxxSoft,"SoftChannel") +#driver(myDriver) +#registrar(myRegistrar) +#variable(myVariable) diff --git a/modules/database/src/tools/DBD.pm b/modules/database/src/tools/DBD.pm new file mode 100644 index 000000000..aabe26d26 --- /dev/null +++ b/modules/database/src/tools/DBD.pm @@ -0,0 +1,125 @@ +package DBD; + +use strict; +use warnings; + +use DBD::Base; +use DBD::Breaktable; +use DBD::Driver; +use DBD::Link; +use DBD::Menu; +use DBD::Recordtype; +use DBD::Recfield; +use DBD::Record; +use DBD::Registrar; +use DBD::Function; +use DBD::Variable; + +use Carp; + +sub new { + my ($class) = @_; + my $this = { + 'DBD::Breaktable' => {}, + 'DBD::Driver' => {}, + 'DBD::Link' => {}, + 'DBD::Function' => {}, + 'DBD::Menu' => {}, + 'DBD::Recordtype' => {}, + 'DBD::Record' => {}, + 'DBD::Registrar' => {}, + 'DBD::Variable' => {}, + 'COMMENTS' => [], + 'POD' => [] + }; + bless $this, $class; + return $this; +} + +sub add { + my ($this, $obj, $obj_name) = @_; + my $obj_class = ref $obj; + confess "DBD::add: Unknown DBD object type '$obj_class'" + unless $obj_class =~ m/^DBD::/ + and exists $this->{$obj_class}; + $obj_name = $obj->name unless defined $obj_name; + if (exists $this->{$obj_class}->{$obj_name}) { + return if $obj->equals($this->{$obj_class}->{$obj_name}); + dieContext("A different $obj->{WHAT} named '$obj_name' already exists"); + } + else { + $this->{$obj_class}->{$obj_name} = $obj; + } +} + +sub add_comment { + my $this = shift; + push @{$this->{COMMENTS}}, @_; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + +sub add_pod { + my $this = shift; + push @{$this->{POD}}, @_; +} + +sub pod { + return @{shift->{POD}}; +} + +sub breaktables { + return shift->{'DBD::Breaktable'}; +} +sub breaktable { + my ($this, $name) = @_; + return $this->{'DBD::Breaktable'}->{$name}; +} + +sub drivers { + return shift->{'DBD::Driver'}; +} + +sub links { + return shift->{'DBD::Link'}; +} + +sub functions { + return shift->{'DBD::Function'}; +} + +sub menus { + return shift->{'DBD::Menu'}; +} +sub menu { + my ($this, $menu_name) = @_; + return $this->{'DBD::Menu'}->{$menu_name}; +} + +sub recordtypes { + return shift->{'DBD::Recordtype'}; +} +sub recordtype { + my ($this, $rtyp_name) = @_; + return $this->{'DBD::Recordtype'}->{$rtyp_name}; +} + +sub records { + return shift->{'DBD::Record'}; +} +sub record { + my ($this, $record_name) = @_; + return $this->{'DBD::Record'}->{$record_name}; +} + +sub registrars { + return shift->{'DBD::Registrar'}; +} + +sub variables { + return shift->{'DBD::Variable'}; +} + +1; diff --git a/modules/database/src/tools/DBD/Base.pm b/modules/database/src/tools/DBD/Base.pm new file mode 100644 index 000000000..3a316037f --- /dev/null +++ b/modules/database/src/tools/DBD/Base.pm @@ -0,0 +1,143 @@ +# Common utility functions used by the DBD components + +package DBD::Base; + +use strict; +use warnings; + +use Carp; +require Exporter; + +our @ISA = qw(Exporter); + +our @EXPORT = qw(&pushContext &popContext &dieContext &warnContext &is_reserved + &escapeCcomment &escapeCstring $RXident $RXname $RXuint $RXint $RXhex $RXoct + $RXuintx $RXintx $RXnum $RXdqs $RXstr); + + +our $RXident = qr/ [a-zA-Z] [a-zA-Z0-9_]* /x; +our $RXnchr = qr/ [a-zA-Z0-9_\-:.\[\]<>;] /x; +our $RXname = qr/ $RXnchr+ (?: [{}] $RXnchr+ )* /x; +our $RXhex = qr/ (?: 0 [xX] [0-9A-Fa-f]+ ) /x; +our $RXoct = qr/ 0 [0-7]* /x; +our $RXuint = qr/ [0-9]+ /x; +our $RXint = qr/ -? $RXuint /x; +our $RXuintx = qr/ ( $RXhex | $RXoct | $RXuint ) /x; +our $RXintx = qr/ ( $RXhex | $RXoct | $RXint ) /x; +our $RXnum = qr/ -? (?: [0-9]+ | [0-9]* \. [0-9]+ ) (?: [eE] [-+]? [0-9]+ )? /x; +our $RXdqs = qr/ " (?> \\. | [^"\\] )* " /x; +our $RXstr = qr/ ( $RXname | $RXnum | $RXdqs ) /x; + +our @context; + + +sub pushContext { + my ($ctxt) = @_; + unshift @context, $ctxt; +} + +sub popContext { + my ($ctxt) = @_; + my $pop = shift @context; + ($ctxt ne $pop) and + dieContext("Leaving context \"$ctxt\", found \"$pop\" instead.", + "\tBraces must be closed in the same file they open in."); +} + +sub dieContext { + my $msg = join "\n\t", @_; + die "$msg\nContext: ", join(' in ', @context), "\n"; +} + +sub warnContext { + my $msg = join "\n\t", @_; + print STDERR "$msg\nContext: ", join(' in ', @context), "\n"; +} + + +# Reserved words from C++ and the DB/DBD file parser +my %reserved = map { $_ => undef } qw(and and_eq asm auto bitand bitor bool + break case catch char class compl const const_cast continue default delete + do double dynamic_cast else enum explicit export extern false float for + friend goto if inline int long mutable namespace new not not_eq operator or + or_eq private protected public register reinterpret_cast return short signed + sizeof static static_cast struct switch template this throw true try typedef + typeid typename union unsigned using virtual void volatile wchar_t while xor + xor_eq addpath alias breaktable choice device driver field function grecord + include info menu path record recordtype registrar variable); +sub is_reserved { + my $id = shift; + return exists $reserved{$id}; +} + +sub identifier { + my ($this, $id, $what) = @_; + confess "DBD::Base::identifier: $what undefined!" + unless defined $id; + $id =~ m/^$RXident$/ or dieContext("Illegal $what '$id'", + "Identifiers are used in C code so must start with a letter, followed", + "by letters, digits and/or underscore characters only."); + dieContext("Illegal $what '$id'", + "Identifier is a C++ reserved word.") + if is_reserved($id); + return $id; +} + + +# Output filtering + +sub escapeCcomment { + ($_) = @_; + s/\*\//**/g; + return $_; +} + +sub escapeCstring { + ($_) = @_; + # FIXME: How to do this? + return $_; +} + + +# Base methods for the DBD component objects + +sub new { + my $class = shift; + my $this = {}; + bless $this, $class; + return $this->init(@_); +} + +sub init { + my ($this, $name, $what) = @_; + $this->{NAME} = $this->identifier($name, "$what name"); + $this->{WHAT} = $what; + return $this; +} + +sub name { + return shift->{NAME}; +} + +sub what { + return shift->{WHAT}; +} + +sub add_comment { + my $this = shift; + confess "add_comment() not supported by $this->{WHAT} ($this)\n", + "Context: ", join(' in ', @context), "\n"; +} + +sub add_pod { + my $this = shift; + warnContext "Warning: Pod text inside $this->{WHAT} will be ignored"; +} + +sub equals { + my ($a, $b) = @_; + return $a->{NAME} eq $b->{NAME} + && $a->{WHAT} eq $b->{WHAT}; +} + +1; diff --git a/modules/database/src/tools/DBD/Breaktable.pm b/modules/database/src/tools/DBD/Breaktable.pm new file mode 100644 index 000000000..c14ab8d65 --- /dev/null +++ b/modules/database/src/tools/DBD/Breaktable.pm @@ -0,0 +1,59 @@ +package DBD::Breaktable; +use DBD::Base; +@ISA = qw(DBD::Base); + +use Carp; + +sub init { + my ($this, $name) = @_; + $this->SUPER::init($name, "breakpoint table"); + $this->{POINT_LIST} = []; + $this->{COMMENTS} = []; + $this->{POD} = []; + return $this; +} + +sub add_point { + my ($this, $raw, $eng) = @_; + confess "DBD::Breaktable::add_point: Raw value undefined!" + unless defined $raw; + confess "DBD::Breaktable::add_point: Engineering value undefined!" + unless defined $eng; + push @{$this->{POINT_LIST}}, [$raw, $eng]; +} + +sub points { + return @{shift->{POINT_LIST}}; +} + +sub point { + my ($this, $idx) = @_; + return $this->{POINT_LIST}[$idx]; +} + +sub add_comment { + my $this = shift; + push @{$this->{COMMENTS}}, @_; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + +sub add_pod { + my $this = shift; + push @{$this->{POD}}, @_; +} + +sub pod { + return @{shift->{POD}}; +} + +sub equals { + my ($a, $b) = @_; + return $a->SUPER::equals($b) + && join(',', map "$_->[0]:$_->[1]", @{$a->{POINT_LIST}}) + eq join(',', map "$_->[0]:$_->[1]", @{$b->{POINT_LIST}}); +} + +1; diff --git a/modules/database/src/tools/DBD/Device.pm b/modules/database/src/tools/DBD/Device.pm new file mode 100644 index 000000000..1e6d0684c --- /dev/null +++ b/modules/database/src/tools/DBD/Device.pm @@ -0,0 +1,51 @@ +package DBD::Device; +use DBD::Base; +@ISA = qw(DBD::Base); + +my %link_types = ( + CONSTANT => qr/$RXnum/, + PV_LINK => qr/$RXname \s+ [.NPCAMS ]*/x, + JSON_LINK => qr/\{ .* \}/x, + VME_IO => qr/\# (?: \s* [CS] \s* $RXintx)* \s* (?: @ .*)?/x, + CAMAC_IO => qr/\# (?: \s* [BCNAF] \s* $RXintx)* \s* (?: @ .*)?/x, + RF_IO => qr/\# (?: \s* [RMDE] \s* $RXintx)*/x, + AB_IO => qr/\# (?: \s* [LACS] \s* $RXintx)* \s* (?: @ .*)?/x, + GPIB_IO => qr/\# (?: \s* [LA] \s* $RXintx)* \s* (?: @ .*)?/x, + BITBUS_IO => qr/\# (?: \s* [LNPS] \s* $RXuintx)* \s* (?: @ .*)?/x, + BBGPIB_IO => qr/\# (?: \s* [LBG] \s* $RXuintx)* \s* (?: @ .*)?/x, + VXI_IO => qr/\# (?: \s* [VCS] \s* $RXintx)* \s* (?: @ .*)?/x, + INST_IO => qr/@.*/ +); + +sub init { + my ($this, $link_type, $dset, $choice) = @_; + dieContext("Unknown link type '$link_type', valid types are:", + sort keys %link_types) unless exists $link_types{$link_type}; + $this->SUPER::init($dset, "device support (dset)"); + $this->{LINK_TYPE} = $link_type; + $this->{CHOICE} = $choice; + return $this; +} + +sub link_type { + return shift->{LINK_TYPE}; +} + +sub choice { + return shift->{CHOICE}; +} + +sub legal_addr { + my ($this, $addr) = @_; + my $rx = $link_types{$this->{LINK_TYPE}}; + return $addr =~ m/^ $rx $/x; +} + +sub equals { + my ($a, $b) = @_; + return $a->SUPER::equals($b) + && $a->{LINK_TYPE} eq $b->{LINK_TYPE} + && $a->{CHOICE} eq $b->{CHOICE}; +} + +1; diff --git a/modules/database/src/tools/DBD/Driver.pm b/modules/database/src/tools/DBD/Driver.pm new file mode 100644 index 000000000..ddbae8f38 --- /dev/null +++ b/modules/database/src/tools/DBD/Driver.pm @@ -0,0 +1,9 @@ +package DBD::Driver; +use DBD::Base; +@ISA = qw(DBD::Base); + +sub init { + return shift->SUPER::init(shift, "driver support (drvet)"); +} + +1; diff --git a/modules/database/src/tools/DBD/Function.pm b/modules/database/src/tools/DBD/Function.pm new file mode 100644 index 000000000..4a4a4cbfe --- /dev/null +++ b/modules/database/src/tools/DBD/Function.pm @@ -0,0 +1,9 @@ +package DBD::Function; +use DBD::Base; +@ISA = qw(DBD::Base); + +sub init { + return shift->SUPER::init(shift, "function"); +} + +1; diff --git a/modules/database/src/tools/DBD/Link.pm b/modules/database/src/tools/DBD/Link.pm new file mode 100644 index 000000000..4a4568e82 --- /dev/null +++ b/modules/database/src/tools/DBD/Link.pm @@ -0,0 +1,22 @@ +package DBD::Link; +use DBD::Base; +@ISA = qw(DBD::Base); + +sub init { + my ($this, $name, $jlif) = @_; + $this->SUPER::init($jlif, "link support (jlif)"); + $this->{KEY} = $name; + return $this; +} + +sub key { + return shift->{KEY}; +} + +sub equals { + my ($a, $b) = @_; + return $a->SUPER::equals($b) + && $a->{KEY} eq $b->{KEY}; +} + +1; diff --git a/modules/database/src/tools/DBD/Menu.pm b/modules/database/src/tools/DBD/Menu.pm new file mode 100644 index 000000000..e5521ed49 --- /dev/null +++ b/modules/database/src/tools/DBD/Menu.pm @@ -0,0 +1,84 @@ +package DBD::Menu; +use DBD::Base; +@ISA = qw(DBD::Base); + +sub init { + my ($this, $name) = @_; + $this->SUPER::init($name, "menu"); + $this->{CHOICE_LIST} = []; + $this->{CHOICE_INDEX} = {}; + $this->{COMMENTS} = []; + return $this; +} + +sub add_choice { + my ($this, $name, $value) = @_; + $name = $this->identifier($name, "Choice name"); + foreach $pair ($this->choices) { + dieContext("Duplicate menu choice name '$name'") + if ($pair->[0] eq $name); + dieContext("Duplicate menu choice string '$value'") + if ($pair->[1] eq $value); + } + push @{$this->{CHOICE_LIST}}, [$name, $value]; + $this->{CHOICE_INDEX}->{$value} = $name; +} + +sub choices { + return @{shift->{CHOICE_LIST}}; +} + +sub choice { + my ($this, $idx) = @_; + return $this->{CHOICE_LIST}[$idx]; +} + +sub legal_choice { + my ($this, $value) = @_; + return exists $this->{CHOICE_INDEX}->{$value}; +} + +sub add_comment { + my $this = shift; + push @{$this->{COMMENTS}}, @_; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + +sub equals { + my ($a, $b) = @_; + return $a->SUPER::equals($b) + && join(',', map "$_->[0]:$_->[1]", @{$a->{CHOICE_LIST}}) + eq join(',', map "$_->[0]:$_->[1]", @{$b->{CHOICE_LIST}}); +} + +sub toDeclaration { + my $this = shift; + my $name = $this->name; + my @choices = map { + sprintf " %-31s /* %s */", @{$_}[0], escapeCcomment(@{$_}[1]); + } $this->choices; + my $num = scalar @choices; + return "typedef enum {\n" . + join(",\n", @choices) . + "\n} $name;\n" . + "#define ${name}_NUM_CHOICES $num\n\n"; +} + +sub toDefinition { + my $this = shift; + my $name = $this->name; + my @strings = map { + "\t\"" . escapeCstring(@{$_}[1]) . "\"" + } $this->choices; + return "static const char * const ${name}ChoiceStrings[] = {\n" . + join(",\n", @strings) . "\n};\n" . + "const dbMenu ${name}MenuMetaData = {\n" . + "\t\"" . escapeCstring($name) . "\",\n" . + "\t${name}_NUM_CHOICES,\n" . + "\t${name}ChoiceStrings\n};\n\n"; +} + +1; diff --git a/modules/database/src/tools/DBD/Output.pm b/modules/database/src/tools/DBD/Output.pm new file mode 100644 index 000000000..55051dfb3 --- /dev/null +++ b/modules/database/src/tools/DBD/Output.pm @@ -0,0 +1,138 @@ +package DBD::Output; + +use strict; +use warnings; + +require Exporter; + +our @ISA = qw(Exporter); +our @EXPORT = qw(&OutputDBD &OutputDB); + +use DBD; +use DBD::Base; +use DBD::Breaktable; +use DBD::Device; +use DBD::Driver; +use DBD::Link; +use DBD::Menu; +use DBD::Recordtype; +use DBD::Recfield; +use DBD::Record; +use DBD::Registrar; +use DBD::Function; +use DBD::Variable; + +sub OutputDBD { + my ($out, $dbd) = @_; + OutputMenus($out, $dbd->menus); + OutputRecordtypes($out, $dbd->recordtypes); + OutputDrivers($out, $dbd->drivers); + OutputLinks($out, $dbd->links); + OutputRegistrars($out, $dbd->registrars); + OutputFunctions($out, $dbd->functions); + OutputVariables($out, $dbd->variables); + OutputBreaktables($out, $dbd->breaktables); +} + +sub OutputDB { + my ($out, $dbd) = @_; + OutputRecords($out, $dbd->records); +} + +sub OutputMenus { + my ($out, $menus) = @_; + while (my ($name, $menu) = each %{$menus}) { + printf $out "menu(%s) {\n", $name; + printf $out " choice(%s, \"%s\")\n", @{$_} + foreach $menu->choices; + print $out "}\n"; + } +} + +sub OutputRecordtypes { + my ($out, $recordtypes) = @_; + while (my ($name, $recordtype) = each %{$recordtypes}) { + printf $out "recordtype(%s) {\n", $name; + print $out " %$_\n" + foreach $recordtype->cdefs; + foreach my $field ($recordtype->fields) { + printf $out " field(%s, %s) {\n", + $field->name, $field->dbf_type; + while (my ($attr, $val) = each %{$field->attributes}) { + $val = "\"$val\"" + if $val !~ m/^$RXname$/x + || $attr eq 'prompt' + || $attr eq 'initial'; + printf $out " %s(%s)\n", $attr, $val; + } + print $out " }\n"; + } + printf $out "}\n"; + printf $out "device(%s, %s, %s, \"%s\")\n", + $name, $_->link_type, $_->name, $_->choice + foreach $recordtype->devices; + } +} + +sub OutputDrivers { + my ($out, $drivers) = @_; + printf $out "driver(%s)\n", $_ + foreach keys %{$drivers}; +} + +sub OutputLinks { + my ($out, $links) = @_; + while (my ($name, $link) = each %{$links}) { + printf $out "link(%s, %s)\n", $link->key, $name; + } +} + +sub OutputRegistrars { + my ($out, $registrars) = @_; + printf $out "registrar(%s)\n", $_ + foreach keys %{$registrars}; +} + +sub OutputFunctions { + my ($out, $functions) = @_; + printf $out "function(%s)\n", $_ + foreach keys %{$functions}; +} + +sub OutputVariables { + my ($out, $variables) = @_; + while (my ($name, $variable) = each %{$variables}) { + printf $out "variable(%s, %s)\n", $name, $variable->var_type; + } +} + +sub OutputBreaktables { + my ($out, $breaktables) = @_; + while (my ($name, $breaktable) = each %{$breaktables}) { + printf $out "breaktable(\"%s\") {\n", $name; + printf $out " %s, %s\n", @{$_} + foreach $breaktable->points; + print $out "}\n"; + } +} + +sub OutputRecords { + my ($out, $records) = @_; + while (my ($name, $rec) = each %{$records}) { + next if $name ne $rec->name; # Alias + printf $out "record(%s, \"%s\") {\n", $rec->recordtype->name, $name; + printf $out " alias(\"%s\")\n", $_ + foreach $rec->aliases; + foreach my $recfield ($rec->recfields) { + my $field_name = $recfield->name; + my $value = $rec->get_field($field_name); + printf $out " field(%s, \"%s\")\n", $field_name, $value + if defined $value; + } + printf $out " info(\"%s\", \"%s\")\n", $_, $rec->info_value($_) + foreach $rec->info_names; + print $out "}\n"; + } +} + +1; diff --git a/modules/database/src/tools/DBD/Parser.pm b/modules/database/src/tools/DBD/Parser.pm new file mode 100644 index 000000000..6065486c8 --- /dev/null +++ b/modules/database/src/tools/DBD/Parser.pm @@ -0,0 +1,318 @@ +package DBD::Parser; + +use strict; +use warnings; + +require Exporter; + +our @ISA = qw(Exporter); +our @EXPORT = qw(&ParseDBD); + +use DBD; +use DBD::Base; +use DBD::Breaktable; +use DBD::Device; +use DBD::Driver; +use DBD::Link; +use DBD::Menu; +use DBD::Recordtype; +use DBD::Recfield; +use DBD::Record; +use DBD::Registrar; +use DBD::Function; +use DBD::Variable; + +our $debug=0; + +sub ParseDBD { + (my $dbd, $_) = @_; + while (1) { + parseCommon($dbd); + if (m/\G menu \s* \( \s* $RXstr \s* \) \s* \{/xgc) { + print "Menu: $1\n" if $debug; + my ($menu_name) = unquote($1); + parse_menu($dbd, $menu_name); + } + elsif (m/\G driver \s* \( \s* $RXstr \s* \)/xgc) { + print "Driver: $1\n" if $debug; + my ($driver_name) = unquote($1); + $dbd->add(DBD::Driver->new($driver_name)); + } + elsif (m/\G link \s* \( \s* $RXstr \s*, \s* $RXstr \s* \)/xgc) { + print "Link $1, $2\n" if $debug; + my ($key, $lset) = unquote($1, $2); + $dbd->add(DBD::Link->new($key, $lset)); + } + elsif (m/\G registrar \s* \( \s* $RXstr \s* \)/xgc) { + print "Registrar: $1\n" if $debug; + my ($registrar_name) = unquote($1); + $dbd->add(DBD::Registrar->new($registrar_name)); + } + elsif (m/\G function \s* \( \s* $RXstr \s* \)/xgc) { + print "Function: $1\n" if $debug; + my ($function_name) = unquote($1); + $dbd->add(DBD::Function->new($function_name)); + } + elsif (m/\G breaktable \s* \( \s* $RXstr \s* \) \s* \{/xgc) { + print "Breaktable: $1\n" if $debug; + my ($breaktable_name) = unquote($1); + parse_breaktable($dbd, $breaktable_name); + } + elsif (m/\G recordtype \s* \( \s* $RXstr \s* \) \s* \{/xgc) { + print "Recordtype: $1\n" if $debug; + my ($recordtype_name) = unquote($1); + parse_recordtype($dbd, $recordtype_name); + } + elsif (m/\G g?record \s* \( \s* $RXstr \s*, \s* $RXstr \s* \) \s* \{/xgc) { + print "Record: $1, $2\n" if $debug; + my ($record_type, $record_name) = unquote($1, $2); + parse_record($dbd, $record_type, $record_name); + } + elsif (m/\G alias \s* \( \s* $RXstr \s*, \s* $RXstr \s* \)/xgc) { + print "Alias: $1, $2\n" if $debug; + my ($record_name, $alias) = unquote($1, $2); + my $rec = $dbd->record($record_name); + dieContext("Alias '$alias' refers to unknown record '$record_name'") + unless defined $rec; + dieContext("Can't create alias '$alias', name already used") + if defined $dbd->record($alias); + $rec->add_alias($alias); + $dbd->add($rec, $alias); + } + elsif (m/\G variable \s* \( \s* $RXstr \s* \)/xgc) { + print "Variable: $1\n" if $debug; + my ($variable_name) = unquote($1); + $dbd->add(DBD::Variable->new($variable_name)); + } + elsif (m/\G variable \s* \( \s* $RXstr \s* , \s* $RXstr \s* \)/xgc) { + print "Variable: $1, $2\n" if $debug; + my ($variable_name, $variable_type) = unquote($1, $2); + $dbd->add(DBD::Variable->new($variable_name, $variable_type)); + } + elsif (m/\G device \s* \( \s* $RXstr \s* , \s* $RXstr \s* , + \s* $RXstr \s* , \s*$RXstr \s* \)/xgc) { + print "Device: $1, $2, $3, $4\n" if $debug; + my ($record_type, $link_type, $dset, $choice) = + unquote($1, $2, $3, $4); + my $rtyp = $dbd->recordtype($record_type); + if (!defined $rtyp) { + $rtyp = DBD::Recordtype->new($record_type); + warn "Device using undefined record type '$record_type', place-holder created\n"; + $dbd->add($rtyp); + } + $rtyp->add_device(DBD::Device->new($link_type, $dset, $choice)); + } else { + last unless m/\G (.*) $/moxgc; + dieContext("Syntax error in '$1'"); + } + } +} + +sub parseCommon { + my ($obj) = @_; + while (1) { + # Skip leading whitespace + m/\G \s* /xgc; + + # Extract POD + if (m/\G ( = [a-zA-Z] )/xgc) { + # The above regex was split from the one below for performance. + # Using m/\G ( = [a-zA-Z] .* ) \n/ is slow in Perl 5.20 and later. + my $directive = $1; + m/\G ( .* ) \n/xgc; + $directive .= $1; + $obj->add_pod($directive, parsePod()); + } + elsif (m/\G \# /xgc) { + if (m/\G \# ! BEGIN \{ ( [^}]* ) \} ! \# \# \n/xgc) { + print "File-Begin: $1\n" if $debug; + pushContext("file '$1'"); + } + elsif (m/\G \# ! END \{ ( [^}]* ) \} ! \# \# \n?/xgc) { + print "File-End: $1\n" if $debug; + popContext("file '$1'"); + } + else { + m/\G (.*) \n/xgc; + $obj->add_comment($1); + print "Comment: $1\n" if $debug; + } + } else { + return; + } + } +} + +sub unquote { + return map { m/^ ("?) (.*) \1 $/x; $2 } @_; +} + +sub parsePod { + pushContext("Pod markup"); + my @pod; + while (1) { + if (m/\G ( =cut .* ) \n?/xgc) { + popContext("Pod markup"); + return @pod; + } + elsif (m/\G ( .* ) $/xgc) { + dieContext("Unexpected end of input file, Pod block not closed"); + } + elsif (m/\G ( .* ) \n/xgc) { + push @pod, $1 + } + } +} + +sub parse_menu { + my ($dbd, $menu_name) = @_; + pushContext("menu($menu_name)"); + my $menu = DBD::Menu->new($menu_name); + while(1) { + parseCommon($menu); + if (m/\G choice \s* \( \s* $RXstr \s* , \s* $RXstr \s* \)/xgc) { + print " Menu-Choice: $1, $2\n" if $debug; + my ($choice_name, $value) = unquote($1, $2); + $menu->add_choice($choice_name, $value); + } + elsif (m/\G \}/xgc) { + print " Menu-End:\n" if $debug; + $dbd->add($menu); + popContext("menu($menu_name)"); + return; + } else { + m/\G (.*) $/moxgc or dieContext("Unexpected end of input"); + dieContext("Syntax error in '$1'"); + } + } +} + +sub parse_breaktable { + my ($dbd, $breaktable_name) = @_; + pushContext("breaktable($breaktable_name)"); + my $bt = DBD::Breaktable->new($breaktable_name); + while(1) { + parseCommon($bt); + if (m/\G point\s* \(\s* $RXstr \s* , \s* $RXstr \s* \)/xgc) { + print " Breaktable-Point: $1, $2\n" if $debug; + my ($raw, $eng) = unquote($1, $2); + $bt->add_point($raw, $eng); + } + elsif (m/\G $RXstr \s* (?: , \s*)? $RXstr (?: \s* ,)?/xgc) { + print " Breaktable-Data: $1, $2\n" if $debug; + my ($raw, $eng) = unquote($1, $2); + $bt->add_point($raw, $eng); + } + elsif (m/\G \}/xgc) { + print " Breaktable-End:\n" if $debug; + $dbd->add($bt); + popContext("breaktable($breaktable_name)"); + return; + } else { + m/\G (.*) $/moxgc or dieContext("Unexpected end of input"); + dieContext("Syntax error in '$1'"); + } + } +} + +sub parse_recordtype { + my ($dbd, $record_type) = @_; + pushContext("recordtype($record_type)"); + my $rtyp = DBD::Recordtype->new($record_type); + while(1) { + parseCommon($rtyp); + if (m/\G field \s* \( \s* $RXstr \s* , \s* $RXstr \s* \) \s* \{/xgc) { + print " Recordtype-Field: $1, $2\n" if $debug; + my ($field_name, $field_type) = unquote($1, $2); + parse_field($rtyp, $field_name, $field_type); + } + elsif (m/\G % (.*) \n/xgc) { + print " Recordtype-Cdef: $1\n" if $debug; + $rtyp->add_cdef($1); + } + elsif (m/\G \}/xgc) { + print " Recordtype-End:\n" if $debug; + $dbd->add($rtyp); + popContext("recordtype($record_type)"); + return; + } else { + m/\G (.*) $/moxgc or dieContext("Unexpected end of input"); + dieContext("Syntax error in '$1'"); + } + } +} + +sub parse_record { + my ($dbd, $record_type, $record_name) = @_; + pushContext("record($record_type, $record_name)"); + my $rtyp = $dbd->recordtype($record_type); + my $rec = $dbd->record($record_name); + if (defined $rec) { + my $otyp = $rec->recordtype; + my $otyp_name = $otyp->name; + $rtyp = $otyp if $record_type eq '*'; + dieContext("A(n) $otyp_name record '$record_name' already exists") + unless $otyp == $rtyp; + } else { + dieContext("No record exists named '$record_name'") + if $record_type eq '*'; + dieContext("No recordtype exists named '$record_type'") + unless defined $rtyp; + $rec = DBD::Record->new($rtyp, $record_name); + } + while (1) { + parseCommon($rec); + if (m/\G field \s* \( \s* $RXstr \s* , \s* $RXstr \s* \)/xgc) { + print " Record-Field: $1, $2\n" if $debug; + my ($field_name, $value) = unquote($1, $2); + $rec->put_field($field_name, $value); + } + elsif (m/\G info \s* \( \s* $RXstr \s* , \s* $RXstr \s* \)/xgc) { + print " Record-Info: $1, $2\n" if $debug; + my ($info_name, $value) = unquote($1, $2); + $rec->add_info($info_name, $value); + } + elsif (m/\G alias \s* \( \s* $RXstr \s* \)/xgc) { + print " Record-Alias: $1\n" if $debug; + my ($alias) = unquote($1); + dieContext("Can't create alias '$alias', name in use") + if defined $dbd->record($1); + $rec->add_alias($alias); + $dbd->add($rec, $alias); + } + elsif (m/\G \}/xgc) { + print " Record-End:\n" if $debug; + $dbd->add($rec); + popContext("record($record_type, $record_name)"); + return; + } else { + m/\G (.*) $/moxgc or dieContext("Unexpected end of input"); + dieContext("Syntax error in '$1'"); + } + } +} + +sub parse_field { + my ($rtyp, $field_name, $field_type) = @_; + my $fld = DBD::Recfield->new($field_name, $field_type); + pushContext("field($field_name, $field_type)"); + while(1) { + parseCommon($fld); + if (m/\G (\w+) \s* \( \s* $RXstr \s* \)/xgc) { + print " Field-Attribute: $1, $2\n" if $debug; + my ($attr, $value) = unquote($1, $2); + $fld->add_attribute($attr, $value); + } + elsif (m/\G \}/xgc) { + print " Field-End:\n" if $debug; + $rtyp->add_field($fld); + popContext("field($field_name, $field_type)"); + return; + } else { + m/\G (.*) $/moxgc or dieContext("Unexpected end of input"); + dieContext("Syntax error in '$1'"); + } + } +} + +1; diff --git a/modules/database/src/tools/DBD/Recfield.pm b/modules/database/src/tools/DBD/Recfield.pm new file mode 100644 index 000000000..f7bfbae46 --- /dev/null +++ b/modules/database/src/tools/DBD/Recfield.pm @@ -0,0 +1,525 @@ +package DBD::Recfield; +use DBD::Base; +@ISA = qw(DBD::Base); + +# The hash value is a regexp that matches all legal values of this field +# NB: The regexps are not currently used, and are wrong for some types. +our %field_types = ( + DBF_STRING => qr/.{0,40}/, + DBF_CHAR => $RXintx, + DBF_UCHAR => $RXuintx, + DBF_SHORT => $RXintx, + DBF_USHORT => $RXuintx, + DBF_LONG => $RXintx, + DBF_ULONG => $RXuintx, + DBF_INT64 => $RXintx, + DBF_UINT64 => $RXuintx, + DBF_FLOAT => $RXnum, + DBF_DOUBLE => $RXnum, + DBF_ENUM => qr/.*/, + DBF_MENU => qr/.*/, + DBF_DEVICE => qr/.*/, + DBF_INLINK => qr/.*/, + DBF_OUTLINK => qr/.*/, + DBF_FWDLINK => qr/.*/, + DBF_NOACCESS => qr// +); + +# The hash value is a regexp that matches all legal values of this attribute +our %field_attrs = ( + asl => qr/^ASL[01]$/, + initial => qr/^.*$/, + promptgroup => qr/^.*$/, + prompt => qr/^.*$/, + special => qr/^(?:SPC_\w+|\d{3,})$/, + pp => qr/^(?:TRUE|FALSE)$/, + interest => qr/^\d+$/, + base => qr/^(?:DECIMAL|HEX)$/, + size => qr/^\d+$/, + extra => qr/^.*$/, + menu => qr/^$RXident$/, + prop => qr/^(?:YES|NO)$/ +); + +# Convert old promptgroups into new-style +my %promptgroupMap = ( + GUI_COMMON => '10 - Common', + GUI_ALARMS => '70 - Alarm', + GUI_BITS1 => '41 - Bits (1)', + GUI_BITS2 => '42 - Bits (2)', + GUI_CALC => '30 - Action', + GUI_CLOCK => '30 - Action', + GUI_COMPRESS => '30 - Action', + GUI_CONVERT => '60 - Convert', + GUI_DISPLAY => '80 - Display', + GUI_HIST => '30 - Action', + GUI_INPUTS => '40 - Input', + GUI_LINKS => '40 - Link', + GUI_MBB => '30 - Action', + GUI_MOTOR => '30 - Action', + GUI_OUTPUT => '50 - Output', + GUI_PID => '30 - Action', + GUI_PULSE => '30 - Action', + GUI_SELECT => '40 - Input', + GUI_SEQ1 => '51 - Output (1)', + GUI_SEQ2 => '52 - Output (2)', + GUI_SEQ3 => '53 - Output (3)', + GUI_SUB => '30 - Action', + GUI_TIMER => '30 - Action', + GUI_WAVE => '30 - Action', + GUI_SCAN => '20 - Scan', +); + +sub new { + my ($class, $name, $type) = @_; + dieContext("Illegal field type '$type', valid field types are:", + sort keys %field_types) unless exists $field_types{$type}; + my $this = {}; + bless $this, "${class}::${type}"; + return $this->init($name, $type); +} + +sub init { + my ($this, $name, $type) = @_; + $this->SUPER::init($name, "record field"); + dieContext("Illegal field type '$type', valid field types are:", + sort keys %field_types) unless exists $field_types{$type}; + $this->{DBF_TYPE} = $type; + $this->{ATTR_INDEX} = {}; + $this->{COMMENTS} = []; + return $this; +} + +sub dbf_type { + return shift->{DBF_TYPE}; +} + +sub set_number { + my ($this, $number) = @_; + $this->{NUMBER} = $number; +} + +sub number { + return shift->{NUMBER}; +} + +sub add_attribute { + my ($this, $attr, $value) = @_; + $value = $promptgroupMap{$value} + if $attr eq 'promptgroup' && exists $promptgroupMap{$value}; + my $match = $field_attrs{$attr}; + if (defined $match) { + dieContext("Bad value '$value' for field attribute '$attr'") + unless $value =~ m/$match/; + } + else { + warnContext("Unknown field attribute '$attr' with value '$value'; " . + "known attributes are:", + join(", ", sort keys %field_attrs)); + } + $this->{ATTR_INDEX}->{$attr} = $value; +} + +sub attributes { + return shift->{ATTR_INDEX}; +} + +sub attribute { + my ($this, $attr) = @_; + return $this->attributes->{$attr}; +} + +sub equals { + dieContext("Record field objects are not comparable"); +} + +sub check_valid { + my ($this) = @_; + my $name = $this->name; + my $default = $this->attribute("initial"); + dieContext("Default value '$default' is invalid for field '$name'") + if (defined($default) and !$this->legal_value($default)); +} + +sub add_comment { + my $this = shift; + push @{$this->{COMMENTS}}, @_; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + + +# The C structure member name is usually the field name converted to +# lower-case. However if that is a reserved word, use the original. +sub C_name { + my ($this) = @_; + my $name = lc $this->name; + $name = $this->name + if is_reserved($name); + return $name; +} + +sub toDeclaration { + my ($this, $ctype) = @_; + my $name = $this->C_name; + my $result = sprintf " %-19s %-12s", $ctype, "$name;"; + my $prompt = $this->attribute('prompt'); + $result .= "/* $prompt */" if defined $prompt; + return $result; +} + + +################################################################################ + +package DBD::Recfield::DBF_STRING; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + return (length $value < $this->attribute('size')); + # NB - we use '<' to allow space for the terminating nil byte +} + +sub check_valid { + my ($this) = @_; + dieContext("Size missing for DBF_STRING field '$name'") + unless exists $this->attributes->{'size'}; + $this->SUPER::check_valid; +} + +sub toDeclaration { + my ($this) = @_; + my $name = lc $this->name; + my $size = $this->attribute('size'); + my $result = sprintf " %-19s %-12s", 'char', "${name}[${size}];"; + my $prompt = $this->attribute('prompt'); + $result .= "/* $prompt */" if defined $prompt; + return $result; +} + + +################################################################################ + +package DBD::Recfield::DBF_CHAR; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXint $/x and + $value >= -128 and + $value <= 127); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsInt8"); +} + + +################################################################################ + +package DBD::Recfield::DBF_UCHAR; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXuint $/x and + $value >= 0 and + $value <= 255); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsUInt8"); +} + + +################################################################################ + +package DBD::Recfield::DBF_SHORT; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXint $/x and + $value >= -32768 and + $value <= 32767); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsInt16"); +} + + +################################################################################ + +package DBD::Recfield::DBF_USHORT; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXuint $/x and + $value >= 0 and + $value <= 65535); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsUInt16"); +} + + +################################################################################ + +package DBD::Recfield::DBF_LONG; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXint $/x); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsInt32"); +} + + +################################################################################ + +package DBD::Recfield::DBF_ULONG; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXuint $/x and + $value >= 0); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsUInt32"); +} + + +################################################################################ + +package DBD::Recfield::DBF_INT64; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXint $/x); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsInt64"); +} + + +################################################################################ + +package DBD::Recfield::DBF_UINT64; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + $value =~ s/^ ( $RXhex | $RXoct ) $/ oct($1) /xe; + return ($value =~ m/^ $RXuint $/x and + $value >= 0); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsUInt64"); +} + + +################################################################################ + +package DBD::Recfield::DBF_FLOAT; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + return ($value =~ m/^ $RXnum $/x); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsFloat32"); +} + + +################################################################################ + +package DBD::Recfield::DBF_DOUBLE; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + return ($value =~ m/^ $RXnum $/x); +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsFloat64"); +} + + +################################################################################ + +package DBD::Recfield::DBF_ENUM; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + return 1; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsEnum16"); +} + + +################################################################################ + +package DBD::Recfield::DBF_MENU; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + # FIXME: If we know the menu name and the menu exists, check further + return 1; +} + +sub check_valid { + my ($this) = @_; + dieContext("Menu name missing for DBF_MENU field '$name'") + unless defined($this->attribute("menu")); + $this->SUPER::check_valid; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsEnum16"); +} + + +################################################################################ + +package DBD::Recfield::DBF_DEVICE; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + return 1; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("epicsEnum16"); +} + + +################################################################################ + +package DBD::Recfield::DBF_INLINK; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + return 1; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("DBLINK"); +} + + +################################################################################ + +package DBD::Recfield::DBF_OUTLINK; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + return 1; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("DBLINK"); +} + + +################################################################################ + +package DBD::Recfield::DBF_FWDLINK; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + return 1; +} + +sub toDeclaration { + return shift->SUPER::toDeclaration("DBLINK"); +} + + +################################################################################ + +package DBD::Recfield::DBF_NOACCESS; + +use DBD::Base; +@ISA = qw(DBD::Recfield); + +sub legal_value { + my ($this, $value) = @_; + return ($value eq ''); +} + +sub check_valid { + my ($this) = @_; + dieContext("Type information missing for DBF_NOACCESS field '$name'") + unless defined($this->attribute("extra")); + $this->SUPER::check_valid; +} + +sub toDeclaration { + my ($this) = @_; + my $extra = $this->attribute('extra'); + my $result = sprintf " %-31s ", "$extra;"; + my $prompt = $this->attribute('prompt'); + $result .= "/* $prompt */" if defined $prompt; + return $result; +} + +1; diff --git a/modules/database/src/tools/DBD/Record.pm b/modules/database/src/tools/DBD/Record.pm new file mode 100644 index 000000000..e232e2a0b --- /dev/null +++ b/modules/database/src/tools/DBD/Record.pm @@ -0,0 +1,123 @@ +package DBD::Record; + +use strict; +use warnings; + +use DBD::Base; + +our @ISA = qw(DBD::Base); + +use Carp; + +our ($macrosOk); +my $warned; + +sub init { + my ($this, $type, $name) = @_; + confess "DBD::Record::init: Not a DBD::Recordtype" + unless $type->isa('DBD::Recordtype'); + $this->SUPER::init($name, "record"); + $this->{RECORD_TYPE} = $type; + $this->{ALIASES} = []; + $this->{RECFIELD_LIST} = []; + $this->{FIELD_INDEX} = {}; + $this->{INFO_LIST} = []; + $this->{INFO_ITEMS} = {}; + $this->{COMMENTS} = []; + $this->{POD} = []; + return $this; +} + +# Override, record names are not as strict as recordtype and menu names +sub identifier { + my ($this, $id, $what) = @_; + confess "DBD::Record::identifier: $what undefined!" + unless defined $id; + if ($macrosOk) { + # FIXME - Check name with macro + } + elsif ($id !~ m/^$RXname$/) { + my @message; + push @message, "A $what should contain only letters, digits and these", + "special characters: _ - : . [ ] < > ;" unless $warned++; + warnContext("Deprecated $what '$id'", @message); + } + return $id; +} + +sub recordtype { + return shift->{RECORD_TYPE}; +} + +sub add_alias { + my ($this, $alias) = @_; + push @{$this->{ALIASES}}, $this->identifier($alias, "alias name"); +} + +sub aliases { + return @{shift->{ALIASES}}; +} + +sub put_field { + my ($this, $field_name, $value) = @_; + my $recfield = $this->{RECORD_TYPE}->field($field_name); + dieContext("No field named '$field_name'") + unless defined $recfield; + dieContext("Can't set $field_name to '$value'") + unless $recfield->legal_value($value); + push @{$this->{RECFIELD_LIST}}, $recfield + unless exists $this->{FIELD_INDEX}->{$field_name}; + $this->{FIELD_INDEX}->{$field_name} = $value; +} + +sub recfields { + return @{shift->{RECFIELD_LIST}}; +} + +sub field_names { # In their original order... + return map {$_->name} @{shift->{RECFIELD_LIST}}; +} + +sub get_field { + my ($this, $field_name) = @_; + return $this->{FIELD_INDEX}->{$field_name} + if exists $this->{FIELD_INDEX}->{$field_name}; + my $recfield = $this->{RECORD_TYPE}->field($field_name); + return $recfield->attribute("initial"); +} + +sub add_info { + my ($this, $info_name, $value) = @_; + push @{$this->{INFO_LIST}}, $info_name + unless exists $this->{INFO_ITEMS}->{$info_name}; + $this->{INFO_ITEMS}->{$info_name} = $value; +} + +sub info_names { + return @{shift->{INFO_LIST}}; +} + +sub info_value { + my ($this, $info_name) = @_; + return $this->{INFO_ITEMS}->{$info_name}; +} + +sub add_comment { + my ($this, $comment) = @_; + push @{$this->{COMMENTS}}, $comment; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + +sub add_pod { + my $this = shift; + push @{$this->{POD}}, @_; +} + +sub pod { + return @{shift->{POD}}; +} + +1; diff --git a/modules/database/src/tools/DBD/Recordtype.pm b/modules/database/src/tools/DBD/Recordtype.pm new file mode 100644 index 000000000..e4b0d5481 --- /dev/null +++ b/modules/database/src/tools/DBD/Recordtype.pm @@ -0,0 +1,124 @@ +package DBD::Recordtype; +use DBD::Base; +@ISA = qw(DBD::Base); + +use Carp; + +sub init { + my ($this, $name) = @_; + $this->SUPER::init($name, "record type"); + $this->{FIELD_LIST} = []; + $this->{FIELD_INDEX} = {}; + $this->{DEVICE_LIST} = []; + $this->{DEVICE_INDEX} = {}; + $this->{CDEFS} = []; + $this->{COMMENTS} = []; + $this->{POD} = []; + return $this; +} + +sub add_field { + my ($this, $field) = @_; + confess "DBD::Recordtype::add_field: Not a DBD::Recfield" + unless $field->isa('DBD::Recfield'); + my $field_name = $field->name; + dieContext("Duplicate field name '$field_name'") + if exists $this->{FIELD_INDEX}->{$field_name}; + $field->check_valid; + $field->set_number(scalar @{$this->{FIELD_LIST}}); + push @{$this->{FIELD_LIST}}, $field; + $this->{FIELD_INDEX}->{$field_name} = $field; +} + +sub fields { + return @{shift->{FIELD_LIST}}; +} + +sub field_names { # In their original order... + return map {$_->name} @{shift->{FIELD_LIST}}; +} + +sub field { + my ($this, $field_name) = @_; + return $this->{FIELD_INDEX}->{$field_name}; +} + +sub add_device { + my ($this, $device) = @_; + confess "DBD::Recordtype::add_device: Not a DBD::Device" + unless $device->isa('DBD::Device'); + my $choice = $device->choice; + if (exists $this->{DEVICE_INDEX}->{$choice}) { + return if $device->equals($this->{DEVICE_INDEX}->{$choice}); + my @warning = ("Two $this->{NAME} device supports '$choice' conflict"); + my $old = $this->{DEVICE_INDEX}->{$choice}; + push @warning, "Link types differ" + if $old->link_type ne $device->link_type; + push @warning, "DSETs differ" + if $old->name ne $device->name; + dieContext(@warning); + } + push @{$this->{DEVICE_LIST}}, $device; + $this->{DEVICE_INDEX}->{$choice} = $device; +} + +sub devices { + return @{shift->{DEVICE_LIST}}; +} + +sub device { + my ($this, $choice) = @_; + return $this->{DEVICE_INDEX}->{$choice}; +} + +sub add_comment { + my ($this, $comment) = @_; + push @{$this->{COMMENTS}}, $comment; +} + +sub comments { + return @{shift->{COMMENTS}}; +} + +sub add_cdef { + my ($this, $cdef) = @_; + push @{$this->{CDEFS}}, $cdef; +} + +sub cdefs { + return @{shift->{CDEFS}}; +} + +sub toCdefs { + return join("\n", shift->cdefs) . "\n\n"; +} + +sub add_pod { + my $this = shift; + push @{$this->{POD}}, @_; +} + +sub pod { + return @{shift->{POD}}; +} + +sub equals { + my ($new, $known) = @_; + return 0 if ! $known->fields; + return 1 if ! $new->fields; + dieContext("Duplicate definition of record type '$known->{NAME}'"); +} + +sub toDeclaration { + my $this = shift; + my @fields = map { + $_->toDeclaration + } $this->fields; + my $name = $this->name; + $name .= "Record" unless $name eq "dbCommon"; + return "typedef struct $name {\n" . + join("\n", @fields) . + "\n} $name;\n\n"; +} + +1; diff --git a/modules/database/src/tools/DBD/Registrar.pm b/modules/database/src/tools/DBD/Registrar.pm new file mode 100644 index 000000000..29d12cd11 --- /dev/null +++ b/modules/database/src/tools/DBD/Registrar.pm @@ -0,0 +1,9 @@ +package DBD::Registrar; +use DBD::Base; +@ISA = qw(DBD::Base); + +sub init { + return shift->SUPER::init(shift, "registrar function"); +} + +1; diff --git a/modules/database/src/tools/DBD/Variable.pm b/modules/database/src/tools/DBD/Variable.pm new file mode 100644 index 000000000..cd1b0a334 --- /dev/null +++ b/modules/database/src/tools/DBD/Variable.pm @@ -0,0 +1,38 @@ +package DBD::Variable; +use DBD::Base; +@ISA = qw(DBD::Base); + +my %valid_types = ( + # C type name => corresponding iocshArg type identifier + int => 'iocshArgInt', + double => 'iocshArgDouble' +); + +sub init { + my ($this, $name, $type) = @_; + $type = "int" unless defined $type; + exists $valid_types{$type} or + dieContext("Unknown variable type '$type', valid types are:", + sort keys %valid_types); + $this->SUPER::init($name, "variable"); + $this->{VAR_TYPE} = $type; + return $this; +} + +sub var_type { + my $this = shift; + return $this->{VAR_TYPE}; +} + +sub iocshArg_type { + my $this = shift; + return $valid_types{$this->{VAR_TYPE}}; +} + +sub equals { + my ($a, $b) = @_; + return $a->SUPER::equals($b) + && $a->{VAR_TYPE} eq $b->{VAR_TYPE}; +} + +1; diff --git a/modules/database/src/tools/Makefile b/modules/database/src/tools/Makefile new file mode 100644 index 000000000..2aef4d5c0 --- /dev/null +++ b/modules/database/src/tools/Makefile @@ -0,0 +1,38 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* +TOP=../.. + +include $(TOP)/configure/CONFIG + +PERL_MODULES += DBD.pm +PERL_MODULES += DBD/Base.pm +PERL_MODULES += DBD/Breaktable.pm +PERL_MODULES += DBD/Device.pm +PERL_MODULES += DBD/Driver.pm +PERL_MODULES += DBD/Link.pm +PERL_MODULES += DBD/Function.pm +PERL_MODULES += DBD/Menu.pm +PERL_MODULES += DBD/Output.pm +PERL_MODULES += DBD/Parser.pm +PERL_MODULES += DBD/Recfield.pm +PERL_MODULES += DBD/Recordtype.pm +PERL_MODULES += DBD/Record.pm +PERL_MODULES += DBD/Registrar.pm +PERL_MODULES += DBD/Variable.pm + +PERL_SCRIPTS += databaseModuleDirs.pm + +PERL_SCRIPTS += makeIncludeDbd.pl + +PERL_SCRIPTS += dbdToMenuH.pl +PERL_SCRIPTS += dbdToRecordtypeH.pl +PERL_SCRIPTS += dbdExpand.pl +PERL_SCRIPTS += dbExpand.pl +PERL_SCRIPTS += dbdToHtml.pl +PERL_SCRIPTS += registerRecordDeviceDriver.pl + +include $(TOP)/configure/RULES diff --git a/modules/database/src/tools/dbExpand.pl b/modules/database/src/tools/dbExpand.pl new file mode 100644 index 000000000..25cab26cc --- /dev/null +++ b/modules/database/src/tools/dbExpand.pl @@ -0,0 +1,90 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# $Id$ + +use strict; + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use DBD; +use DBD::Parser; +use DBD::Output; +use EPICS::Getopts; +use EPICS::Readfile; +use EPICS::macLib; + +our ($opt_D, @opt_I, @opt_S, $opt_o, $opt_V); + +getopts('DI@S@o:V') or + die "Usage: dbExpand [-D] [-I dir] [-S macro=val] [-o out.db] in.dbd in.db ..."; + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? +my $macros = EPICS::macLib->new(@opt_S); +my $dbd = DBD->new(); + +$macros->suppressWarning(!$opt_V); +$DBD::Record::macrosOk = !$opt_V; + +# Calculate filename for the dependency warning message below +my $dep = $opt_o; +my $dot_d = ''; +if ($opt_D) { + $dep =~ s{\.\./O\.Common/(.*)}{$1\$\(DEP\)}; + $dot_d = '.d'; +} else { + $dep = "\$(COMMON_DIR)/$dep"; +} + +die "dbExpand.pl: No input files for $opt_o\n" if !@ARGV; + +my $errors = 0; + +while (@ARGV) { + my $file = shift @ARGV; + eval { + &ParseDBD($dbd, &Readfile($file, $macros, \@opt_I)); + }; + if ($@) { + warn "dbExpand.pl: $@"; + my $outfile = $opt_o ? " to create '$opt_o$dot_d'" : ''; + warn " while reading '$file'$outfile\n"; + warn " Your Makefile may need this dependency rule:\n", + " $dep: \$(COMMON_DIR)/$file\n" + if $@ =~ m/Can't find file '$file'/; + ++$errors; + } +} + +if ($opt_D) { # Output dependencies only, ignore errors + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$opt_o: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; + exit 0; +} + +die "dbExpand.pl: Exiting due to errors\n" if $errors; + +my $out; +if ($opt_o) { + open $out, '>', $opt_o or die "Can't create $opt_o: $!\n"; +} else { + $out = *STDOUT; +} + +&OutputDB($out, $dbd); + +if ($opt_o) { + close $out or die "Closing $opt_o failed: $!\n"; +} +exit 0; diff --git a/modules/database/src/tools/dbdExpand.pl b/modules/database/src/tools/dbdExpand.pl new file mode 100644 index 000000000..a87ef47bb --- /dev/null +++ b/modules/database/src/tools/dbdExpand.pl @@ -0,0 +1,86 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use strict; + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use DBD; +use DBD::Parser; +use DBD::Output; +use EPICS::Getopts; +use EPICS::Readfile; +use EPICS::macLib; + +our ($opt_D, @opt_I, @opt_S, $opt_o); + +getopts('DI@S@o:') or + die "Usage: dbdExpand [-D] [-I dir] [-S macro=val] [-o out.dbd] in.dbd ..."; + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? +my $macros = EPICS::macLib->new(@opt_S); +my $dbd = DBD->new(); + +$macros->suppressWarning(1); + +# Calculate filename for the dependency warning message below +my $dep = $opt_o; +my $dot_d = ''; +if ($opt_D) { + $dep =~ s{\.\./O\.Common/(.*)}{\1\$\(DEP\)}; + $dot_d = '.d'; +} else { + $dep = "\$(COMMON_DIR)/$dep"; +} + +die "dbdExpand.pl: No input files for $opt_o\n" if !@ARGV; + +my $errors = 0; + +while (@ARGV) { + my $file = shift @ARGV; + eval { + ParseDBD($dbd, Readfile($file, $macros, \@opt_I)); + }; + if ($@) { + warn "dbdExpand.pl: $@"; + warn " while reading '$file' to create '$opt_o$dot_d'\n"; + warn " Your Makefile may need this dependency rule:\n", + " $dep: \$(COMMON_DIR)/$file\n" + if $@ =~ m/Can't find file '$file'/; + ++$errors; + } +} + +if ($opt_D) { # Output dependencies only, ignore errors + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$opt_o: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; + exit 0; +} + +die "dbdExpand.pl: Exiting due to errors\n" if $errors; + +my $out; +if ($opt_o) { + open $out, '>', $opt_o or die "Can't create $opt_o: $!\n"; +} else { + $out = *STDOUT; +} + +OutputDBD($out, $dbd); + +if ($opt_o) { + close $out or die "Closing $opt_o failed: $!\n"; +} +exit 0; diff --git a/modules/database/src/tools/dbdReport.pl b/modules/database/src/tools/dbdReport.pl new file mode 100644 index 000000000..0d4c85494 --- /dev/null +++ b/modules/database/src/tools/dbdReport.pl @@ -0,0 +1,67 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use DBD; +use DBD::Parser; +use EPICS::Getopts; +use EPICS::macLib; +use EPICS::Readfile; +use Text::Wrap; + +#$EPICS::Readfile::debug = 1; +#$DBD::Parser::debug = 1; + +getopts('I@S@') or die usage(); + +sub usage() { + "Usage: dbdReport [-I dir:dir2] [-S macro=val,...] file.dbd ..."; +} + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? +my $macros = EPICS::macLib->new(@opt_S); +my $dbd = DBD->new(); + +ParseDBD($dbd, Readfile(shift @ARGV, $macros, \@opt_I)) while @ARGV; + +$Text::Wrap::columns = 75; + +my @menus = sort keys %{$dbd->menus}; +print wrap("Menus:\t", "\t", join(', ', @menus)), "\n" + if @menus; +my @drivers = sort keys %{$dbd->drivers}; +print wrap("Drivers: ", "\t", join(', ', @drivers)), "\n" + if @drivers; +my @variables = sort keys %{$dbd->variables}; +print wrap("Variables: ", "\t", join(', ', @variables)), "\n" + if @variables; +my @registrars = sort keys %{$dbd->registrars}; +print wrap("Registrars: ", "\t", join(', ', @registrars)), "\n" + if @registrars; +my @breaktables = sort keys %{$dbd->breaktables}; +print wrap("Breaktables: ", "\t", join(', ', @breaktables)), "\n" + if @breaktables; +my %recordtypes = %{$dbd->recordtypes}; +if (%recordtypes) { + @rtypes = sort keys %recordtypes; + print wrap("Recordtypes: ", "\t", join(', ', @rtypes)), "\n"; + foreach my $rtyp (@rtypes) { + my @devices = $recordtypes{$rtyp}->devices; + print wrap("Devices($rtyp): ", "\t", + join(', ', map {$_->choice} @devices)), "\n" + if @devices; + } +} +my @records = sort keys %{$dbd->records}; +print wrap("Records: ", "\t", join(', ', @records)), "\n" + if @records; diff --git a/modules/database/src/tools/dbdToHtml.pl b/modules/database/src/tools/dbdToHtml.pl new file mode 100644 index 000000000..76b187147 --- /dev/null +++ b/modules/database/src/tools/dbdToHtml.pl @@ -0,0 +1,247 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use strict; + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use DBD; +use DBD::Parser; +use EPICS::Getopts; +use EPICS::macLib; +use EPICS::Readfile; + +BEGIN { + $::XHTML = eval "require Pod::Simple::XHTML; 1"; + $::ENTITIES = eval "require HTML::Entities; 1"; + if (!$::XHTML) { + require Pod::Simple::HTML; + } + if (!$::ENTITIES) { + my %entities = ( + q{>} => 'gt', + q{<} => 'lt', + q{'} => '#39', + q{"} => 'quot', + q{&} => 'amp', + ); + + sub encode_entities { + my $str = shift; + my $ents = join '', keys %entities; + $str =~ s/([ $ents ])/'&' . ($entities{$1} || sprintf '#x%X', ord $1) . ';'/xge; + return $str; + } + } +} + +my $tool = 'dbdToHtml'; + +use vars qw($opt_D @opt_I $opt_o); +getopts('DI@o:') or + die "Usage: $tool [-D] [-I dir] [-o file.html] file.dbd.pod\n"; + +my $dbd = DBD->new(); + +my $infile = shift @ARGV; +$infile =~ m/\.dbd.pod$/ or + die "$tool: Input file '$infile' must have '.dbd.pod' extension\n"; + +ParseDBD($dbd, Readfile($infile, 0, \@opt_I)); + +if (!$opt_o) { + ($opt_o = $infile) =~ s/\.dbd\.pod$/.html/; + $opt_o =~ s/^.*\///; + $opt_o =~ s/dbCommonRecord/dbCommon/; +} + +if ($opt_D) { # Output dependencies only + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$opt_o: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; + exit 0; +} + +(my $title = $opt_o) =~ s/\.html$//; + +open my $out, '>', $opt_o or + die "Can't create $opt_o: $!\n"; + +# Parse the Pod text from the root DBD object +my $pod = join "\n", '=for html
', '', + map { + # Handle a 'recordtype' Pod directive + if (m/^ =recordtype \s+ (\w+) /x) { + my $rn = $1; + my $rtyp = $dbd->recordtype($rn); + die "Unknown recordtype '$rn' in $infile POD directive\n" + unless $rtyp; + rtypeToPod($rtyp, $dbd); + } + # Handle a 'menu' Pod directive + elsif (m/^ =menu \s+ (\w+) /x) { + my $mn = $1; + my $menu = $dbd->menu($mn); + die "Unknown menu '$mn' in $infile POD directive\n" + unless $menu; + menuToPod($menu); + } + elsif (m/^ =title \s+ (.*)/x) { + $title = $1; + "=head1 $title"; + } + else { + $_; + } + } $dbd->pod, + '=for html
', ''; + +my $podHtml; + +if ($::XHTML) { + $podHtml = Pod::Simple::XHTML->new(); + $podHtml->html_doctype(<< '__END_DOCTYPE'); + + +__END_DOCTYPE + $podHtml->html_header_tags($podHtml->html_header_tags . + "\n"); +} else { # Fall back to HTML + $podHtml = Pod::Simple::HTML->new(); + $podHtml->html_css('style.css'); +} + +$podHtml->force_title(encode_entities($title)); +$podHtml->perldoc_url_prefix(''); +$podHtml->perldoc_url_postfix('.html'); +$podHtml->output_fh($out); +$podHtml->parse_string_document($pod); +close $out; + + +sub menuToPod { + my ($menu) = @_; + my $index = 0; + return '=begin html', '', '
', + '', + map({choiceTableRow($_, $index++)} $menu->choices), + '
IndexIdentifierChoice String
', '', '=end html'; +} + +sub choiceTableRow { + my ($ch, $index) = @_; + my ($id, $name) = @{$ch}; + return '', + "$index", + "$id", + "$name", + ''; +} + +sub rtypeToPod { + my ($rtyp, $dbd) = @_; + return map { + # Handle a 'fields' Pod directive + if (m/^ =fields \s+ (\w+ (?:\s* , \s* \w+ )* )/x) { + my @names = split /\s*,\s*/, $1; + # Look up the named fields + my @fields = map { + my $field = $rtyp->field($_); + die "Unknown field name '$_' in $infile POD\n" + unless $field; + $field; + } @names; + # Generate Pod for the table + '=begin html', '', '
', + '', + '', + '', + map({fieldTableRow($_, $dbd)} @fields), + '
FieldSummaryTypeDCTDefaultReadWriteCA PP
', '', '=end html'; + } + # Handle a 'menu' Pod directive + elsif (m/^ =menu \s+ (\w+) /x) { + my $mn = $1; + my $menu = $dbd->menu($mn); + die "Unknown menu '$mn' in $infile POD directive\n" + unless $menu; + menuToPod($menu); + } + else { + # Raw text line + $_; + } + } $rtyp->pod; +} + +sub fieldTableRow { + my ($fld, $dbd) = @_; + my $html = ''; + $html .= $fld->name; + $html .= ''; + $html .= $fld->attribute('prompt'); + $html .= ''; + my $type = $fld->public_type; + $html .= $type; + $html .= ' [' . $fld->attribute('size') . ']' + if $type eq 'STRING'; + if ($type eq 'MENU') { + my $mn = $fld->attribute('menu'); + my $menu = $dbd->menu($mn); + my $url = $menu ? "#Menu_$mn" : "${mn}.html"; + $html .= " (
$mn)"; + } + $html .= ''; + $html .= $fld->attribute('promptgroup') ? 'Yes' : 'No'; + $html .= ''; + $html .= $fld->attribute('initial') || ' '; + $html .= ''; + $html .= $fld->readable; + $html .= ''; + $html .= $fld->writable; + $html .= ''; + $html .= $fld->attribute('pp') eq 'TRUE' ? 'Yes' : 'No'; + $html .= "\n"; + return $html; +} + +# Native type presented to dbAccess users +sub DBD::Recfield::public_type { + my $fld = shift; + m/^ =type \s+ (.+) /x && return $1 for $fld->comments; + my $type = $fld->dbf_type; + $type =~ s/^DBF_//; + return $type; +} + +# Check if this field is readable +sub DBD::Recfield::readable { + my $fld = shift; + m/^ =read \s+ (?i) (Yes|No) /x && return $1 for $fld->comments; + return 'Probably' + if $fld->attribute('special') eq "SPC_DBADDR"; + return $fld->dbf_type eq 'DBF_NOACCESS' ? 'No' : 'Yes'; +} + +# Check if this field is writable +sub DBD::Recfield::writable { + my $fld = shift; + m/^ =write \s+ (?i) (Yes|No) /x && return $1 for $fld->comments; + my $special = $fld->attribute('special'); + return 'No' + if $special eq "SPC_NOMOD"; + return 'Maybe' + if $special eq "SPC_DBADDR"; + return $fld->dbf_type eq "DBF_NOACCESS" ? 'No' : 'Yes'; +} diff --git a/modules/database/src/tools/dbdToMenuH.pl b/modules/database/src/tools/dbdToMenuH.pl new file mode 100644 index 000000000..6bdcb8a6a --- /dev/null +++ b/modules/database/src/tools/dbdToMenuH.pl @@ -0,0 +1,80 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use EPICS::Getopts; +use File::Basename; +use DBD; +use DBD::Parser; +use EPICS::macLib; +use EPICS::Readfile; + +my $tool = 'dbdToMenuH.pl'; + +use vars qw($opt_D @opt_I $opt_o $opt_s); +getopts('DI@o:') or + die "Usage: $tool: [-D] [-I dir] [-o menu.h] menu.dbd [menu.h]\n"; + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? +my $dbd = DBD->new(); + +my $infile = shift @ARGV; +$infile =~ m/\.dbd$/ or + die "$tool: Input file '$infile' must have '.dbd' extension\n"; +my $inbase = basename($infile); + +my $outfile; +if ($opt_o) { + $outfile = $opt_o; +} elsif (@ARGV) { + $outfile = shift @ARGV; +} else { + ($outfile = $infile) =~ s/\.dbd$/.h/; + $outfile =~ s/^.*\///; +} +my $outbase = basename($outfile); + +# Derive a name for the include guard +my $guard_name = "INC_$outbase"; +$guard_name =~ tr/a-zA-Z0-9_/_/cs; +$guard_name =~ s/(_[hH])?$/_H/; + +ParseDBD($dbd, Readfile($infile, 0, \@opt_I)); + +if ($opt_D) { + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$outfile: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; +} else { + open OUTFILE, ">$outfile" or die "$tool: Can't open $outfile: $!\n"; + print OUTFILE "/* $outbase generated from $inbase */\n\n", + "#ifndef $guard_name\n", + "#define $guard_name\n\n"; + my $menus = $dbd->menus; + while (my ($name, $menu) = each %{$menus}) { + print OUTFILE $menu->toDeclaration; + } +# FIXME: Where to put metadata for widely used menus? +# In the generated menu.h file is wrong: can't create a list of menu.h files. +# Can only rely on registerRecordDeviceDriver output, so we must require that +# all such menus be named "menu...", and any other menus must be defined in +# the record.dbd file that needs them. +# print OUTFILE "\n#ifdef GEN_MENU_METADATA\n\n"; +# while (($name, $menu) = each %{$menus}) { +# print OUTFILE $menu->toDefinition; +# } +# print OUTFILE "\n#endif /* GEN_MENU_METADATA */\n"; + print OUTFILE "\n#endif /* $guard_name */\n"; + close OUTFILE; +} diff --git a/modules/database/src/tools/dbdToRecordtypeH.pl b/modules/database/src/tools/dbdToRecordtypeH.pl new file mode 100644 index 000000000..730599029 --- /dev/null +++ b/modules/database/src/tools/dbdToRecordtypeH.pl @@ -0,0 +1,232 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use EPICS::Getopts; +use File::Basename; +use DBD; +use DBD::Parser; +use EPICS::macLib; +use EPICS::Readfile; + +my $tool = 'dbdToRecordtypeH.pl'; + +use vars qw($opt_D @opt_I $opt_o $opt_s); +getopts('DI@o:s') or + die "Usage: $tool [-D] [-I dir] [-o xRecord.h] xRecord.dbd [xRecord.h]\n"; + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? +my $dbd = DBD->new(); + +my $infile = shift @ARGV; +$infile =~ m/\.dbd$/ or + die "$tool: Input file '$infile' must have '.dbd' extension\n"; +my $inbase = basename($infile); + +my $outfile; +if ($opt_o) { + $outfile = $opt_o; +} elsif (@ARGV) { + $outfile = shift @ARGV; +} else { + ($outfile = $infile) =~ s/\.dbd$/.h/; + $outfile =~ s/^.*\///; + $outfile =~ s/dbCommonRecord/dbCommon/; +} +my $outbase = basename($outfile); + +# Derive a name for the include guard +my $guard_name = "INC_$outbase"; +$guard_name =~ tr/a-zA-Z0-9_/_/cs; +$guard_name =~ s/(_[hH])?$/_H/; + +ParseDBD($dbd, Readfile($infile, 0, \@opt_I)); + +my $rtypes = $dbd->recordtypes; +die "$tool: Input file must contain a single recordtype definition.\n" + unless (1 == keys %{$rtypes}); + +if ($opt_D) { # Output dependencies only, to stdout + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$outfile: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; +} else { + open OUTFILE, ">$outfile" or die "$tool: Can't open $outfile: $!\n"; + print OUTFILE "/* $outbase generated from $inbase */\n\n", + "#ifndef $guard_name\n", + "#define $guard_name\n\n"; + + our ($rn, $rtyp) = each %{$rtypes}; + + print OUTFILE $rtyp->toCdefs; + + my @menu_fields = grep { + $_->dbf_type eq 'DBF_MENU' + } $rtyp->fields; + my %menu_used; + grep { + !$menu_used{$_}++ + } map { + $_->attribute('menu') + } @menu_fields; + our $menus_defined = $dbd->menus; + while (my ($name, $menu) = each %{$menus_defined}) { + print OUTFILE $menu->toDeclaration; + if ($menu_used{$name}) { + delete $menu_used{$name} + } + } + our @menus_external = keys %menu_used; + + print OUTFILE $rtyp->toDeclaration; + + unless ($rn eq 'dbCommon') { + my $n = 0; + print OUTFILE "typedef enum {\n", + join(",\n", + map { "\t${rn}Record$_ = " . $n++ } $rtyp->field_names), + "\n} ${rn}FieldIndex;\n\n"; + print OUTFILE "#ifdef GEN_SIZE_OFFSET\n\n"; + if ($opt_s) { + newtables(); + } else { + oldtables(); + } + print OUTFILE "#endif /* GEN_SIZE_OFFSET */\n"; + } + print OUTFILE "\n", + "#endif /* $guard_name */\n"; + close OUTFILE; +} + +sub oldtables { + # Output compatible with R3.14.x + print OUTFILE + "#include \n" . + "#include \n" . + "#ifdef __cplusplus\n" . + "extern \"C\" {\n" . + "#endif\n" . + "static int ${rn}RecordSizeOffset(dbRecordType *prt)\n" . + "{\n" . + " ${rn}Record *prec = 0;\n\n" . + " assert(prt->no_fields == " . scalar($rtyp->fields) . ");\n" . + join("\n", map { + " prt->papFldDes[${rn}Record" . $_->name . "]->size = " . + "sizeof(prec->" . $_->C_name . ");" + } $rtyp->fields) . "\n" . + join("\n", map { + " prt->papFldDes[${rn}Record" . $_->name . "]->offset = (unsigned short)(" . + "(char *)&prec->" . $_->C_name . " - (char *)prec);" + } $rtyp->fields) . "\n" . + " prt->rec_size = sizeof(*prec);\n" . + " return 0;\n" . + "}\n" . + "epicsExportRegistrar(${rn}RecordSizeOffset);\n\n" . + "#ifdef __cplusplus\n" . + "}\n" . + "#endif\n"; +} + +sub newtables { + # Output for an eventual DBD-less IOC + print OUTFILE (map { + "extern const dbMenu ${_}MenuMetaData;\n" + } @menus_external), "\n"; + while (my ($name, $menu) = each %{$menus_defined}) { + print OUTFILE $menu->toDefinition; + } + print OUTFILE (map { + "static const char ${rn}FieldName$_\[] = \"$_\";\n" } + $rtyp->field_names), "\n"; + my $n = 0; + print OUTFILE "static const dbRecordData ${rn}RecordMetaData;\n\n", + "static dbFldDes ${rn}FieldMetaData[] = {\n", + join(",\n", map { + my $fn = $_->name; + my $cn = $_->C_name; + " { ${rn}FieldName${fn}," . + $_->dbf_type . ',"' . + $_->attribute('initial') . '",' . + ($_->attribute('special') || '0') . ',' . + ($_->attribute('pp') || 'FALSE') . ',' . + ($_->attribute('interest') || '0') . ',' . + ($_->attribute('asl') || 'ASL0') . ',' . + $n++ . ",\n\t\&${rn}RecordMetaData," . + "GEOMETRY_DATA(${rn}Record,$cn) }"; + } $rtyp->fields), + "\n};\n\n"; + print OUTFILE "static const ${rn}FieldIndex ${rn}RecordLinkFieldIndices[] = {\n", + join(",\n", map { + " ${rn}Record" . $_->name; + } grep { + $_->dbf_type =~ m/^DBF_(IN|OUT|FWD)LINK/; + } $rtyp->fields), + "\n};\n\n"; + my @sorted_names = sort $rtyp->field_names; + print OUTFILE "static const char * const ${rn}RecordSortedFieldNames[] = {\n", + join(",\n", map { + " ${rn}FieldName$_" + } @sorted_names), + "\n};\n\n"; + print OUTFILE "static const ${rn}FieldIndex ${rn}RecordSortedFieldIndices[] = {\n", + join(",\n", map { + " ${rn}Record$_" + } @sorted_names), + "\n};\n\n"; + print OUTFILE "extern rset ${rn}RSET;\n\n", + "static const dbRecordData ${rn}RecordMetaData = {\n", + " \"$rn\",\n", + " sizeof(${rn}Record),\n", + " NELEMENTS(${rn}FieldMetaData),\n", + " ${rn}FieldMetaData,\n", + " ${rn}RecordVAL,\n", + " \&${rn}FieldMetaData[${rn}RecordVAL],\n", + " NELEMENTS(${rn}RecordLinkFieldIndices),\n", + " ${rn}RecordLinkFieldIndices,\n", + " ${rn}RecordSortedFieldNames,\n", + " ${rn}RecordSortedFieldIndices,\n", + " \&${rn}RSET\n", + "};\n\n", + "#ifdef __cplusplus\n", + "extern \"C\" {\n", + "#endif\n\n"; + print OUTFILE "dbRecordType * epicsShareAPI ${rn}RecordRegistrar(dbBase *pbase, int nDevs)\n", + "{\n", + " dbRecordType *prt = dbCreateRecordtype(&${rn}RecordMetaData, nDevs);\n"; + print OUTFILE " ${rn}FieldMetaData[${rn}RecordDTYP].typDat.pdevMenu = \&prt->devMenu;\n"; + while (my ($name, $menu) = each %{$menus_defined}) { + print OUTFILE " dbRegisterMenu(pbase, \&${name}MenuMetaData);\n"; + } + print OUTFILE map { + " ${rn}FieldMetaData[${rn}Record" . + $_->name . + "].typDat.pmenu = \n". + " \&" . + $_->attribute('menu') . + "MenuMetaData;\n"; + } @menu_fields; + print OUTFILE map { + " ${rn}FieldMetaData[${rn}Record" . + $_->name . + "].typDat.base = CT_HEX;\n"; + } grep { + $_->attribute('base') eq 'HEX'; + } $rtyp->fields; + print OUTFILE " dbRegisterRecordtype(pbase, prt);\n"; + print OUTFILE " return prt;\n}\n\n", + "#ifdef __cplusplus\n", + "} /* extern \"C\" */\n", + "#endif\n\n"; +} diff --git a/modules/database/src/tools/makeIncludeDbd.pl b/modules/database/src/tools/makeIncludeDbd.pl new file mode 100644 index 000000000..ab728da83 --- /dev/null +++ b/modules/database/src/tools/makeIncludeDbd.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2014 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use strict; +use File::Basename; + +sub Usage { + my $txt = shift; + + print "Usage: makeIncludeDbd.pl input file list ... outfile\n"; + print "Error: $txt\n" if $txt; + exit 2; +} + +Usage("No input files specified") + unless $#ARGV > 1; + +my $target = pop @ARGV; +my @inputs = map { basename($_); } @ARGV; + +open(my $OUT, '>', $target) + or die "$0: Can't create $target, $!\n"; + +print $OUT "# Generated file $target\n\n"; +print $OUT map { "include \"$_\"\n"; } @inputs; + +close $OUT; diff --git a/modules/database/src/tools/registerRecordDeviceDriver.pl b/modules/database/src/tools/registerRecordDeviceDriver.pl new file mode 100644 index 000000000..02bb9b772 --- /dev/null +++ b/modules/database/src/tools/registerRecordDeviceDriver.pl @@ -0,0 +1,300 @@ +#!/usr/bin/env perl + +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +use strict; + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use databaseModuleDirs; +no lib $Bin; + +use DBD; +use DBD::Parser; +use EPICS::Readfile; +use EPICS::Path; +use EPICS::Getopts; +use Text::Wrap; + +our ($opt_D, @opt_I, $opt_o, $opt_l); + +getopts('Dlo:I@') or + die "Usage: registerRecordDeviceDriver [-D] [-l] [-o out.c] [-I dir] in.dbd subname [TOP]"; + +my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? + +my ($file, $subname, $bldTop) = @ARGV; + +my $dbd = DBD->new(); +ParseDBD($dbd, Readfile($file, "", \@path)); + +if ($opt_D) { # Output dependencies only + my %filecount; + my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; + print "$opt_o: ", join(" \\\n ", @uniqfiles), "\n\n"; + print map { "$_:\n" } @uniqfiles; + exit 0; +} + +$Text::Wrap::columns = 75; + +# Eliminate chars not allowed in C symbol names +my $c_bad_ident_chars = '[^0-9A-Za-z_]'; +$subname =~ s/$c_bad_ident_chars/_/g; + +# Process bldTop like convertRelease.pl does +$bldTop = LocalPath(UnixPath($bldTop)); +$bldTop =~ s/([\\"])/\\\1/g; # escape back-slashes and double-quotes + +# Create output file +my $out; +if ($opt_o) { + open $out, '>', $opt_o or die "Can't create $opt_o: $!\n"; +} else { + $out = *STDOUT; +} + +print $out (<< "END"); +/* THIS IS A GENERATED FILE. DO NOT EDIT! */ +/* Generated from $file */ + +#include +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#include "compilerDependencies.h" +#include "epicsStdlib.h" +#include "iocsh.h" +#include "iocshRegisterCommon.h" +#include "registryCommon.h" +#include "recSup.h" + +END + +print $out (<< "END") if $opt_l; +#define epicsExportSharedSymbols +#include "shareLib.h" + +END + +print $out (<< "END"); +extern "C" { + +END + +my %rectypes = %{$dbd->recordtypes}; +my @rtypnames; +my @dsets; +if (%rectypes) { + my @allrtypnames = sort keys %rectypes; + # Record types with no fields defined are declarations, + # for building shared libraries containing device support. + @rtypnames = grep { scalar $rectypes{$_}->fields } @allrtypnames; + + if (@rtypnames) { + # Declare the record support entry tables + print $out wrap('epicsShareExtern typed_rset ', ' ', + join(', ', map {"*pvar_rset_${_}RSET"} @rtypnames)), ";\n\n"; + + # Declare the RecordSizeOffset functions + print $out "typedef int (*rso_func)(dbRecordType *pdbRecordType);\n"; + print $out wrap('epicsShareExtern rso_func ', ' ', + join(', ', map {"pvar_func_${_}RecordSizeOffset"} @rtypnames)), ";\n\n"; + + # List of record type names + print $out "static const char * const recordTypeNames[] = {\n"; + print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @rtypnames)); + print $out "\n};\n\n"; + + # List of pointers to each RSET and RecordSizeOffset function + print $out "static const recordTypeLocation rtl[] = {\n"; + print $out join(",\n", map { + " {(struct typed_rset *)pvar_rset_${_}RSET, pvar_func_${_}RecordSizeOffset}" + } @rtypnames); + print $out "\n};\n\n"; + } + + for my $rtype (@allrtypnames) { + my @devices = $rectypes{$rtype}->devices; + for my $dtype (@devices) { + my $dset = $dtype->name; + push @dsets, $dset; + } + } + + if (@dsets) { + # Declare the device support entry tables + print $out wrap('epicsShareExtern dset ', ' ', + join(', ', map {"*pvar_dset_$_"} @dsets)), ";\n\n"; + + # List of dset names + print $out "static const char * const deviceSupportNames[] = {\n"; + print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @dsets)); + print $out "\n};\n\n"; + + # List of pointers to each dset + print $out "static const dset * const devsl[] = {\n"; + print $out wrap(' ', ' ', join(", ", map {"pvar_dset_$_"} @dsets)); + print $out "\n};\n\n"; + } +} + +my %drivers = %{$dbd->drivers}; +if (%drivers) { + my @drivers = sort keys %drivers; + + # Declare the driver entry tables + print $out wrap('epicsShareExtern drvet ', ' ', + join(', ', map {"*pvar_drvet_$_"} @drivers)), ";\n\n"; + + # List of drvet names + print $out "static const char *driverSupportNames[] = {\n"; + print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @drivers)); + print $out "};\n\n"; + + # List of pointers to each drvet + print $out "static struct drvet *drvsl[] = {\n"; + print $out join(",\n", map {" pvar_drvet_$_"} @drivers); + print $out "};\n\n"; +} + +my %links = %{$dbd->links}; +if (%links) { + my @links = sort keys %links; + + # Declare the link interfaces + print $out wrap('epicsShareExtern jlif ', ' ', + join(', ', map {"*pvar_jlif_$_"} @links)), ";\n\n"; + + # List of pointers to each link interface + print $out "static struct jlif *jlifsl[] = {\n"; + print $out join(",\n", map {" pvar_jlif_$_"} @links); + print $out "};\n\n"; +} + +my @registrars = sort keys %{$dbd->registrars}; +my @functions = sort keys %{$dbd->functions}; +push @registrars, map {"register_func_$_"} @functions; +if (@registrars) { + # Declare the registrar functions + print $out "typedef void (*reg_func)(void);\n"; + print $out wrap('epicsShareExtern reg_func ', ' ', + join(', ', map {"pvar_func_$_"} @registrars)), ";\n\n"; +} + +my %variables = %{$dbd->variables}; +if (%variables) { + my @varnames = sort keys %variables; + + # Declare the variables + for my $var (@varnames) { + my $vtype = $variables{$var}->var_type; + print $out "epicsShareExtern $vtype * const pvar_${vtype}_$var;\n"; + } + + # Generate the structure for registering variables with iocsh + print $out "\nstatic struct iocshVarDef vardefs[] = {\n"; + for my $var (@varnames) { + my $vtype = $variables{$var}->var_type; + my $itype = $variables{$var}->iocshArg_type; + print $out " {\"$var\", $itype, pvar_${vtype}_$var},\n"; + } + print $out " {NULL, iocshArgInt, NULL}\n};\n\n"; +} + +# Now for actual registration routine + +print $out (<< "END"); +int $subname(DBBASE *pbase) +{ + static int executed = 0; +END + +print $out (<< "END") if $bldTop ne ''; + const char *bldTop = "$bldTop"; + const char *envTop = getenv("TOP"); + + if (envTop && strcmp(envTop, bldTop)) { + printf("Warning: IOC is booting with TOP = \\"%s\\"\\n" + " but was built with TOP = \\"%s\\"\\n", + envTop, bldTop); + } + +END + +print $out (<< 'END'); + if (!pbase) { + printf("pdbbase is NULL; you must load a DBD file first.\n"); + return -1; + } + + if (executed) { + printf("Warning: Registration already done.\n"); + } + executed = 1; + +END + +print $out (<< 'END') if %rectypes && @rtypnames; + registerRecordTypes(pbase, NELEMENTS(rtl), recordTypeNames, rtl); +END + +print $out (<< 'END') if @dsets; + registerDevices(pbase, NELEMENTS(devsl), deviceSupportNames, devsl); +END + +print $out (<< 'END') if %drivers; + registerDrivers(pbase, NELEMENTS(drvsl), driverSupportNames, drvsl); +END + +print $out (<< 'END') if %links; + registerJLinks(pbase, NELEMENTS(jlifsl), jlifsl); +END + +print $out (<< "END") for @registrars; + pvar_func_$_(); +END + +print $out (<< 'END') if %variables; + iocshRegisterVariable(vardefs); +END + +print $out (<< "END"); + return 0; +} + +/* $subname */ +static const iocshArg rrddArg0 = {"pdbbase", iocshArgPdbbase}; +static const iocshArg *rrddArgs[] = {&rrddArg0}; +static const iocshFuncDef rrddFuncDef = + {"$subname", 1, rrddArgs}; +static void rrddCallFunc(const iocshArgBuf *) +{ + $subname(*iocshPpdbbase); +} + +} // extern "C" + +/* + * Register commands on application startup + */ +static int Registration() { + iocshRegisterCommon(); + iocshRegister(&rrddFuncDef, rrddCallFunc); + return 0; +} + +static int done EPICS_UNUSED = Registration(); +END + +if ($opt_o) { + close $out or die "Closing $opt_o failed: $!\n"; +} +exit 0; diff --git a/modules/database/test/Makefile b/modules/database/test/Makefile new file mode 100644 index 000000000..b47389f94 --- /dev/null +++ b/modules/database/test/Makefile @@ -0,0 +1,21 @@ +#************************************************************************* +# Copyright (c) 2002 The University of Chicago, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = .. +include $(TOP)/configure/CONFIG + +DIRS += ioc/db +DIRS += ioc/dbtemplate + +DIRS += std/rec +DIRS += std/filters + +DIRS += tools + +include $(TOP)/configure/RULES_TOP diff --git a/modules/database/test/ioc/db/Makefile b/modules/database/test/ioc/db/Makefile new file mode 100644 index 000000000..840fd7394 --- /dev/null +++ b/modules/database/test/ioc/db/Makefile @@ -0,0 +1,199 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../../.. +include $(TOP)/configure/CONFIG + +# Allow access to private headers in db/ +USR_CPPFLAGS = -I $(TOP)/src/ioc/db + +TESTLIBRARY = dbTestIoc + +dbTestIoc_SRCS += arrRecord.c +dbTestIoc_SRCS += xRecord.c +dbTestIoc_SRCS += dbLinkdset.c +dbTestIoc_SRCS += xLink.c +dbTestIoc_SRCS += devx.c +dbTestIoc_SRCS += jlinkz.c +dbTestIoc_LIBS = dbCore ca Com + +TARGETS += $(COMMON_DIR)/dbTestIoc.dbd +DBDDEPENDS_FILES += dbTestIoc.dbd$(DEP) +dbTestIoc_DBD += menuGlobal.dbd +dbTestIoc_DBD += menuConvert.dbd +dbTestIoc_DBD += menuScan.dbd +dbTestIoc_DBD += xRecord.dbd +dbTestIoc_DBD += arrRecord.dbd +dbTestIoc_DBD += xLink.dbd +dbTestIoc_DBD += devx.dbd +dbTestIoc_DBD += jlinkz.dbd +dbTestIoc_DBD += dbLinkdset.dbd +TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db + +testHarness_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp + +PROD_LIBS = dbTestIoc dbCore ca Com + +TESTPROD_HOST += dbScanTest +dbScanTest_SRCS += dbScanTest.c +dbScanTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbScanTest.c +TESTS += dbScanTest + +TESTPROD_HOST += dbShutdownTest +dbShutdownTest_SRCS += dbShutdownTest.c +dbShutdownTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbShutdownTest.c +TESTS += dbShutdownTest + +TESTPROD_HOST += dbPutLinkTest +dbPutLinkTest_SRCS += dbPutLinkTest.c +dbPutLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbPutLinkTest.c +TESTS += dbPutLinkTest +TESTFILES += ../dbPutLinkTest.db ../dbPutLinkTestJ.db ../dbBadLink.db + +TESTPROD_HOST += dbLockTest +dbLockTest_SRCS += dbLockTest.c +dbLockTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbLockTest.c +TESTS += dbLockTest +TESTFILES += ../dbLockTest.db + +TESTPROD_HOST += dbStressTest +dbStressTest_SRCS += dbStressLock.c +dbStressTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +dbStressTest_SYS_LIBS_solaris += rt +dbStressTest_SYS_LIBS_Linux += rt +TESTS += dbStressTest +TESTFILES += ../dbStressLock.db + +TESTPROD_HOST += testdbConvert +testdbConvert_SRCS += testdbConvert.c +testHarness_SRCS += testdbConvert.c +TESTS += testdbConvert + +TESTPROD_HOST += callbackTest +callbackTest_SRCS += callbackTest.c +testHarness_SRCS += callbackTest.c +TESTS += callbackTest + +TESTPROD_HOST += callbackParallelTest +callbackParallelTest_SRCS += callbackParallelTest.c +testHarness_SRCS += callbackParallelTest.c +TESTS += callbackParallelTest + +TESTPROD_HOST += dbStateTest +dbStateTest_SRCS += dbStateTest.c +testHarness_SRCS += dbStateTest.c +TESTS += dbStateTest + +TESTPROD_HOST += dbServerTest +dbServerTest_SRCS += dbServerTest.c +testHarness_SRCS += dbServerTest.c +TESTS += dbServerTest + +TESTPROD_HOST += dbCaStatsTest +dbCaStatsTest_SRCS += dbCaStatsTest.c +dbCaStatsTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbCaStatsTest.c +TESTS += dbCaStatsTest +TESTFILES += ../dbCaStats.db + +TESTPROD_HOST += dbCaLinkTest +dbCaLinkTest_SRCS += dbCaLinkTest.c +dbCaLinkTest_SRCS += dbCACTest.cpp +dbCaLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbCaLinkTest.c +testHarness_SRCS += dbCACTest.cpp +TESTS += dbCaLinkTest +TESTFILES += ../dbCaLinkTest1.db ../dbCaLinkTest2.db ../dbCaLinkTest3.db + +TESTPROD_HOST += scanIoTest +scanIoTest_SRCS += scanIoTest.c +scanIoTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += scanIoTest.c +TESTFILES += ../scanIoTest.db +TESTS += scanIoTest + +TESTPROD_HOST += dbChannelTest +dbChannelTest_SRCS += dbChannelTest.c +dbChannelTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbChannelTest.c +TESTS += dbChannelTest + +TARGETS += $(COMMON_DIR)/dbChArrTest.dbd +DBDDEPENDS_FILES += dbChArrTest.dbd$(DEP) +dbChArrTest_DBD += arrRecord.dbd +TESTPROD_HOST += dbChArrTest +dbChArrTest_SRCS += dbChArrTest.cpp +dbChArrTest_SRCS += dbChArrTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbChArrTest.cpp +testHarness_SRCS += dbChArrTest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/dbChArrTest.dbd ../dbChArrTest.db +TESTS += dbChArrTest + +TESTPROD_HOST += chfPluginTest +chfPluginTest_SRCS += chfPluginTest.c +chfPluginTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += chfPluginTest.c +TESTS += chfPluginTest + +TESTPROD_HOST += arrShorthandTest +arrShorthandTest_SRCS += arrShorthandTest.c +arrShorthandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += arrShorthandTest.c +TESTS += arrShorthandTest + +TESTPROD_HOST += benchdbConvert +benchdbConvert_SRCS += benchdbConvert.c + +TESTPROD_HOST += recGblCheckDeadbandTest +recGblCheckDeadbandTest_SRCS += recGblCheckDeadbandTest.c +recGblCheckDeadbandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += recGblCheckDeadbandTest.c +TESTS += recGblCheckDeadbandTest + +TESTPROD_HOST += testPutGetTest +testPutGetTest_SRCS += dbPutGetTest.c +testPutGetTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbPutGetTest.c +TESTFILES += ../dbPutGetTest.db +TESTS += testPutGetTest + +TESTPROD_HOST += dbStaticTest +dbStaticTest_SRCS += dbStaticTest.c +dbStaticTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbStaticTest.c +TESTFILES += ../dbStaticTest.db +TESTS += dbStaticTest + +# This runs all the test programs in a known working order: +testHarness_SRCS += epicsRunDbTests.c + +dbTestHarness_SRCS += $(testHarness_SRCS) +dbTestHarness_SRCS_RTEMS += rtemsTestHarness.c + +PROD_vxWorks = dbTestHarness +PROD_RTEMS = dbTestHarness + +TESTSPEC_vxWorks = dbTestHarness.munch; epicsRunDbTests +TESTSPEC_RTEMS = dbTestHarness.boot; epicsRunDbTests + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES + +arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h +dbCaLinkTest$(DEP): $(COMMON_DIR)/xRecord.h $(COMMON_DIR)/arrRecord.h +dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h +dbStressLock$(DEP): $(COMMON_DIR)/xRecord.h +devx$(DEP): $(COMMON_DIR)/xRecord.h +scanIoTest$(DEP): $(COMMON_DIR)/xRecord.h +xRecord$(DEP): $(COMMON_DIR)/xRecord.h diff --git a/modules/database/test/ioc/db/arrRecord.c b/modules/database/test/ioc/db/arrRecord.c new file mode 100644 index 000000000..16b1b32e2 --- /dev/null +++ b/modules/database/test/ioc/db/arrRecord.c @@ -0,0 +1,144 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* arrRecord.c - minimal array record for test purposes: no processing */ + +/* + * Author: Ralph Lange + * + * vaguely implemented like parts of recWaveform.c by Bob Dalesio + * + */ + +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "recSup.h" +#include "recGbl.h" +#include "cantProceed.h" +#define GEN_SIZE_OFFSET +#include "arrRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +static long put_array_info(DBADDR *, long); +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset arrRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, arrRSET); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct arrRecord *prec = (struct arrRecord *)pcommon; + + if (pass == 0) { + if (prec->nelm <= 0) + prec->nelm = 1; + if (prec->ftvl > DBF_ENUM) + prec->ftvl = DBF_UCHAR; + prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl), + "arr calloc failed"); + + if (prec->nelm == 1) { + prec->nord = 1; + } else { + prec->nord = 0; + } + return 0; + } + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct arrRecord *prec = (struct arrRecord *)pcommon; + if(prec->clbk) + (*prec->clbk)(prec); + prec->pact = TRUE; + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->nelm; + paddr->field_type = prec->ftvl; + paddr->field_size = dbValueSize(prec->ftvl); + paddr->dbr_field_type = prec->ftvl; + + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + *no_elements = prec->nord; + *offset = prec->off; + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + prec->nord = nNew; + if (prec->nord > prec->nelm) + prec->nord = prec->nelm; + + return 0; +} diff --git a/modules/database/test/ioc/db/arrRecord.dbd b/modules/database/test/ioc/db/arrRecord.dbd new file mode 100644 index 000000000..b504be1cb --- /dev/null +++ b/modules/database/test/ioc/db/arrRecord.dbd @@ -0,0 +1,42 @@ +include "menuGlobal.dbd" +include "menuConvert.dbd" +include "menuScan.dbd" +recordtype(arr) { + include "dbCommon.dbd" + field(VAL, DBF_NOACCESS) { + prompt("Value") + special(SPC_DBADDR) + pp(TRUE) + extra("void *val") + } + field(NELM, DBF_ULONG) { + prompt("Number of Elements") + special(SPC_NOMOD) + initial("1") + } + field(FTVL, DBF_MENU) { + prompt("Field Type of Value") + special(SPC_NOMOD) + menu(menuFtype) + } + field(NORD, DBF_ULONG) { + prompt("Number elements read") + special(SPC_NOMOD) + } + field(OFF, DBF_ULONG) { + prompt("Offset into array") + } + field(BPTR, DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + extra("void *bptr") + } + field(INP, DBF_INLINK) { + prompt("Input Link") + } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct arrRecord*)") + } +} diff --git a/modules/database/test/ioc/db/arrShorthandTest.c b/modules/database/test/ioc/db/arrShorthandTest.c new file mode 100644 index 000000000..8f130ab57 --- /dev/null +++ b/modules/database/test/ioc/db/arrShorthandTest.c @@ -0,0 +1,136 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + + /* + * Test the shorthand array notation [ start : incr : end ] + * by registering a thin fake arr plugin + * and checking if values are forwarded correctly + */ + +#include + +#include "chfPlugin.h" +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "registry.h" +#include "errlog.h" +#include "epicsExit.h" +#include "dbUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" + +typedef struct myStruct { + epicsInt32 start; + epicsInt32 incr; + epicsInt32 end; +} myStruct; + +static const +chfPluginArgDef opts[] = { + chfInt32 (myStruct, start, "s", 0, 1), + chfInt32 (myStruct, incr, "i", 0, 1), + chfInt32 (myStruct, end, "e", 0, 1), + chfPluginArgEnd +}; + +static myStruct my; + +static void * allocPvt(void) +{ + my.start = 0; + my.incr = 1; + my.end = -1; + return &my; +} + +static chfPluginIf myPif = { + allocPvt, + NULL, /* freePvt, */ + + NULL, /* parse_error, */ + NULL, /* parse_ok, */ + + NULL, /* channel_open, */ + NULL, /* channelRegisterPre, */ + NULL, /* channelRegisterPost, */ + NULL, /* channel_report, */ + NULL /* channel_close */ +}; + +static int checkValues(epicsUInt32 s, epicsUInt32 i, epicsUInt32 e) { + if (s == my.start && i == my.incr && e == my.end) + return 1; + else + return 0; +} + +static void testHead (char* title) { + testDiag("--------------------------------------------------------"); + testDiag("%s", title); + testDiag("--------------------------------------------------------"); +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(arrShorthandTest) +{ + dbChannel *pch; + + testPlan(26); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("xRecord.db", NULL, NULL); + + testHead("Register plugin"); + testOk(!chfPluginRegister("arr", &myPif, opts), "register fake arr plugin"); + + eltc(0); + testIocInitOk(); + eltc(1); + +#define TESTBAD(Title, Expr) \ + testDiag(Title); \ + testOk(!(pch = dbChannelCreate("x." Expr)), "dbChannelCreate (" Expr ") fails"); \ + if (pch) dbChannelDelete(pch); + +#define TESTGOOD(Title, Expr, Start, Incr, End) \ + testDiag(Title); \ + testOk(!!(pch = dbChannelCreate("x." Expr)), "dbChannelCreate (" Expr ")"); \ + testOk(checkValues(Start, Incr, End), "parameters set correctly: s=%d i=%d e=%d", Start, Incr, End); \ + if (pch) dbChannelDelete(pch); + + TESTBAD("no parameters []", "[]"); + TESTBAD("invalid char at beginning [x", "[x"); + TESTBAD("invalid char after 1st arg [2x", "[2x"); + TESTBAD("invalid char after 2nd arg [2:3x", "[2:3x"); + TESTBAD("invalid char after 3rd arg [2:3:4x", "[2:3:4x"); + + TESTGOOD("one element [index]", "[2]", 2, 1, 2); + TESTGOOD("to end [s:]", "[2:]", 2, 1, -1); + TESTGOOD("to end [s::]", "[2::]", 2, 1, -1); + TESTGOOD("to end with incr [s:i:]", "[2:3:]", 2, 3, -1); + TESTGOOD("from beginning [:e]", "[:2]", 0, 1, 2); + TESTGOOD("from beginning [::e]", "[::2]", 0, 1, 2); + TESTGOOD("from begin with incr [:i:e]", "[:3:2]", 0, 3, 2); + TESTGOOD("range [s:e]", "[2:4]", 2, 1, 4); + TESTGOOD("range [s::e]", "[2::4]", 2, 1, 4); + TESTGOOD("range with incr [s:i:e]", "[2:3:4]", 2, 3, 4); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/benchdbConvert.c b/modules/database/test/ioc/db/benchdbConvert.c new file mode 100644 index 000000000..cbc87ad24 --- /dev/null +++ b/modules/database/test/ioc/db/benchdbConvert.c @@ -0,0 +1,124 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven Science Assoc, as Operator of Brookhaven +* National Laboratory. +\*************************************************************************/ +#include "string.h" + +#include "cantProceed.h" +#include "dbAddr.h" +#include "dbConvert.h" +#include "dbDefs.h" +#include "epicsTime.h" +#include "epicsMath.h" +#include "epicsAssert.h" + +#include "epicsUnitTest.h" +#include "testMain.h" + +typedef struct { + size_t nelem, niter; + + short *output; + short *input; + + GETCONVERTFUNC getter; + + DBADDR addr; +} testData; + +static long runRep(testData *D) +{ + size_t i; + + for(i=0; initer; i++) { + D->getter(&D->addr, D->output, D->nelem, D->nelem, 0); + } + return 0; +} + +static void runBench(size_t nelem, size_t niter, size_t nrep) +{ + size_t i; + testData tdat; + double *reptimes; + testDiag("Using %lu element arrays.",(unsigned long)nelem); + testDiag("run %lu reps with %lu iterations each", + (unsigned long)nrep, (unsigned long)niter); + + reptimes = callocMustSucceed(nrep, sizeof(*reptimes), "runBench"); + tdat.output = callocMustSucceed(nelem, sizeof(*tdat.output), "runBench"); + tdat.input = callocMustSucceed(nelem, sizeof(*tdat.input), "runBench"); + + tdat.nelem = nelem; + tdat.niter = niter; + + tdat.getter = dbGetConvertRoutine[DBF_SHORT][DBF_SHORT]; + + memset(&tdat.addr, 0, sizeof(tdat.addr)); + tdat.addr.field_type = DBF_SHORT; + tdat.addr.field_size = nelem*sizeof(*tdat.input); + tdat.addr.no_elements = nelem; + tdat.addr.pfield = (void*)tdat.input; + + for(i=0; i +#include +#include +#include +#include +#include + +#include "callback.h" +#include "cantProceed.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "epicsTime.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* + * This test checks both immediate and delayed callbacks in two steps. + * In the first step (pass1) NCALLBACKS immediate callbacks are queued. + * As each is run it starts a second delayed callback (pass2). + * The last delayed callback which runs signals an epicsEvent + * to the main thread. + * + * Two time intervals are measured. The time to queue and run each of + * the immediate callbacks, and the actual delay of the delayed callback. + * + * Slow callbacks no longer fail the test, they just emit a diagnostic. + */ + +#define NCALLBACKS 169 +#define DELAY_QUANTUM 0.25 + +#define TEST_DELAY(i) ((i / NUM_CALLBACK_PRIORITIES) * DELAY_QUANTUM) + +typedef struct myPvt { + CALLBACK cb1; + CALLBACK cb2; + epicsTimeStamp pass1Time; + epicsTimeStamp pass2Time; + double delay; + int pass; + int resultFail; +} myPvt; + +epicsEventId finished; + +static void myCallback(CALLBACK *pCallback) +{ + myPvt *pmyPvt; + + callbackGetUser(pmyPvt, pCallback); + + pmyPvt->pass++; + + if (pmyPvt->pass == 1) { + epicsTimeGetCurrent(&pmyPvt->pass1Time); + callbackRequestDelayed(&pmyPvt->cb2, pmyPvt->delay); + } else if (pmyPvt->pass == 2) { + epicsTimeGetCurrent(&pmyPvt->pass2Time); + } else { + pmyPvt->resultFail = 1; + return; + } +} + +static void finalCallback(CALLBACK *pCallback) +{ + myCallback(pCallback); + epicsEventSignal(finished); +} + +static void updateStats(double *stats, double val) +{ + if (stats[0] > val) stats[0] = val; + if (stats[1] < val) stats[1] = val; + stats[2] += val; + stats[3] += pow(val, 2.0); + stats[4] += 1.; +} + +static void printStats(double *stats, const char* tag) { + testDiag("Priority %4s min/avg/max/sigma = %f / %f / %f / %f", + tag, stats[0], stats[2]/stats[4], stats[1], + sqrt(stats[4]*stats[3]-pow(stats[2], 2.0))/stats[4]); +} + +MAIN(callbackParallelTest) +{ + myPvt *pcbt[NCALLBACKS]; + epicsTimeStamp start; + int noCpus = epicsThreadGetCPUs(); + int i, j, slowups, faults; + /* Statistics: min/max/sum/sum^2/n for each priority */ + double setupError[NUM_CALLBACK_PRIORITIES][5]; + double timeError[NUM_CALLBACK_PRIORITIES][5]; + double defaultError[5] = {1,-1,0,0,0}; + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) + for (j = 0; j < 5; j++) + setupError[i][j] = timeError[i][j] = defaultError[j]; + + testPlan(2); + + testDiag("Starting %d parallel callback threads", noCpus); + + callbackParallelThreads(noCpus, ""); + callbackInit(); + epicsThreadSleep(1.0); + + finished = epicsEventMustCreate(epicsEventEmpty); + + for (i = 0; i < NCALLBACKS ; i++) { + pcbt[i] = callocMustSucceed(1, sizeof(myPvt), "pcbt"); + callbackSetCallback(myCallback, &pcbt[i]->cb1); + callbackSetCallback(myCallback, &pcbt[i]->cb2); + callbackSetUser(pcbt[i], &pcbt[i]->cb1); + callbackSetUser(pcbt[i], &pcbt[i]->cb2); + callbackSetPriority(i % NUM_CALLBACK_PRIORITIES, &pcbt[i]->cb1); + callbackSetPriority(i % NUM_CALLBACK_PRIORITIES, &pcbt[i]->cb2); + pcbt[i]->delay = TEST_DELAY(i); + pcbt[i]->pass = 0; + } + + /* Last callback is special */ + callbackSetCallback(finalCallback, &pcbt[NCALLBACKS-1]->cb2); + callbackSetPriority(0, &pcbt[NCALLBACKS-1]->cb1); + callbackSetPriority(0, &pcbt[NCALLBACKS-1]->cb2); + pcbt[NCALLBACKS-1]->delay = TEST_DELAY(NCALLBACKS) + 1.0; + pcbt[NCALLBACKS-1]->pass = 0; + + testOk(epicsTimeGetCurrent(&start)==epicsTimeOK, "Time-of-day clock Ok"); + + for (i = 0; i < NCALLBACKS ; i++) { + callbackRequest(&pcbt[i]->cb1); + } + + testDiag("Waiting %.02f sec", pcbt[NCALLBACKS-1]->delay); + + epicsEventWait(finished); + slowups = 0; + faults = 0; + + for (i = 0; i < NCALLBACKS ; i++) { + if(pcbt[i]->resultFail || pcbt[i]->pass!=2) + testDiag("callback setup fault #%d: pass = %d for delay = %.02f", + ++faults, pcbt[i]->pass, pcbt[i]->delay); + else { + double delta = epicsTimeDiffInSeconds(&pcbt[i]->pass1Time, &start); + + if (fabs(delta) >= 0.05) { + slowups++; + testDiag("callback %.02f setup time |%f| >= 0.05 seconds", + pcbt[i]->delay, delta); + } + updateStats(setupError[i%NUM_CALLBACK_PRIORITIES], delta); + } + } + testOk(faults == 0, "%d faults during callback setup", faults); + if (slowups) + testDiag("%d slowups during callback setup", slowups); + + slowups = 0; + for (i = 0; i < NCALLBACKS ; i++) { + double delta, error; + + if(pcbt[i]->resultFail || pcbt[i]->pass!=2) + continue; + delta = epicsTimeDiffInSeconds(&pcbt[i]->pass2Time, &pcbt[i]->pass1Time); + error = delta - pcbt[i]->delay; + if (fabs(error) >= 0.05) { + slowups++; + testDiag("delay %.02f seconds, delay error |%.04f| >= 0.05", + pcbt[i]->delay, error); + } + updateStats(timeError[i%NUM_CALLBACK_PRIORITIES], error); + } + if (slowups) + testDiag("%d slowups during callback setup", slowups); + + testDiag("Setup time statistics"); + printStats(setupError[0], "LOW"); + printStats(setupError[1], "MID"); + printStats(setupError[2], "HIGH"); + + testDiag("Delay time statistics"); + printStats(timeError[0], "LOW"); + printStats(timeError[1], "MID"); + printStats(timeError[2], "HIGH"); + + for (i = 0; i < NCALLBACKS ; i++) { + free(pcbt[i]); + } + + callbackStop(); + callbackCleanup(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/callbackTest.c b/modules/database/test/ioc/db/callbackTest.c new file mode 100644 index 000000000..3ccc2c2f3 --- /dev/null +++ b/modules/database/test/ioc/db/callbackTest.c @@ -0,0 +1,206 @@ +/*************************************************************************\ +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Author: Marty Kraimer Date: 26JAN2000 */ + +#include +#include +#include +#include +#include +#include + +#include "callback.h" +#include "cantProceed.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "epicsTime.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* + * This test checks both immediate and delayed callbacks in two steps. + * In the first step (pass1) NCALLBACKS immediate callbacks are queued. + * As each is run it starts a second delayed callback (pass2). + * The last delayed callback which runs signals an epicsEvent + * to the main thread. + * + * Two time intervals are measured. The time to queue and run each of + * the immediate callbacks, and the actual delay of the delayed callback. + * + * Slow callbacks no longer fail the test, they just emit a diagnostic. + */ + +#define NCALLBACKS 169 +#define DELAY_QUANTUM 0.25 + +#define TEST_DELAY(i) ((i / NUM_CALLBACK_PRIORITIES) * DELAY_QUANTUM) + +typedef struct myPvt { + CALLBACK cb1; + CALLBACK cb2; + epicsTimeStamp pass1Time; + epicsTimeStamp pass2Time; + double delay; + int pass; + int resultFail; +} myPvt; + +epicsEventId finished; + + +static void myCallback(CALLBACK *pCallback) +{ + myPvt *pmyPvt; + + callbackGetUser(pmyPvt, pCallback); + + pmyPvt->pass++; + + if (pmyPvt->pass == 1) { + epicsTimeGetCurrent(&pmyPvt->pass1Time); + callbackRequestDelayed(&pmyPvt->cb2, pmyPvt->delay); + } else if (pmyPvt->pass == 2) { + epicsTimeGetCurrent(&pmyPvt->pass2Time); + } else { + pmyPvt->resultFail = 1; + return; + } +} + +static void finalCallback(CALLBACK *pCallback) +{ + myCallback(pCallback); + epicsEventSignal(finished); +} + +static void updateStats(double *stats, double val) +{ + if (stats[0] > val) stats[0] = val; + if (stats[1] < val) stats[1] = val; + stats[2] += val; + stats[3] += pow(val, 2.0); + stats[4] += 1.; +} + +static void printStats(double *stats, const char* tag) { + testDiag("Priority %4s min/avg/max/sigma = %f / %f / %f / %f", + tag, stats[0], stats[2]/stats[4], stats[1], + sqrt(stats[4]*stats[3]-pow(stats[2], 2.0))/stats[4]); +} + +MAIN(callbackTest) +{ + myPvt *pcbt[NCALLBACKS]; + epicsTimeStamp start; + int i, j, slowups, faults; + /* Statistics: min/max/sum/sum^2/n for each priority */ + double setupError[NUM_CALLBACK_PRIORITIES][5]; + double timeError[NUM_CALLBACK_PRIORITIES][5]; + double defaultError[5] = {1,-1,0,0,0}; + + for (i = 0; i < NUM_CALLBACK_PRIORITIES; i++) + for (j = 0; j < 5; j++) + setupError[i][j] = timeError[i][j] = defaultError[j]; + + testPlan(2); + + callbackInit(); + epicsThreadSleep(1.0); + + finished = epicsEventMustCreate(epicsEventEmpty); + + for (i = 0; i < NCALLBACKS ; i++) { + pcbt[i] = callocMustSucceed(1, sizeof(myPvt), "pcbt"); + callbackSetCallback(myCallback, &pcbt[i]->cb1); + callbackSetCallback(myCallback, &pcbt[i]->cb2); + callbackSetUser(pcbt[i], &pcbt[i]->cb1); + callbackSetUser(pcbt[i], &pcbt[i]->cb2); + callbackSetPriority(i % NUM_CALLBACK_PRIORITIES, &pcbt[i]->cb1); + callbackSetPriority(i % NUM_CALLBACK_PRIORITIES, &pcbt[i]->cb2); + pcbt[i]->delay = TEST_DELAY(i); + pcbt[i]->pass = 0; + } + + /* Last callback is special */ + callbackSetCallback(finalCallback, &pcbt[NCALLBACKS-1]->cb2); + callbackSetPriority(0, &pcbt[NCALLBACKS-1]->cb1); + callbackSetPriority(0, &pcbt[NCALLBACKS-1]->cb2); + pcbt[NCALLBACKS-1]->delay = TEST_DELAY(NCALLBACKS) + 1.0; + pcbt[NCALLBACKS-1]->pass = 0; + + testOk(epicsTimeGetCurrent(&start)==epicsTimeOK, "Time-of-day clock Ok"); + + for (i = 0; i < NCALLBACKS ; i++) { + callbackRequest(&pcbt[i]->cb1); + } + + testDiag("Waiting %.02f sec", pcbt[NCALLBACKS-1]->delay); + + epicsEventWait(finished); + slowups = 0; + faults = 0; + + for (i = 0; i < NCALLBACKS ; i++) { + if(pcbt[i]->resultFail || pcbt[i]->pass!=2) + testDiag("callback setup fault #%d: pass = %d for delay = %.02f", + ++faults, pcbt[i]->pass, pcbt[i]->delay); + else { + double delta = epicsTimeDiffInSeconds(&pcbt[i]->pass1Time, &start); + + if (fabs(delta) >= 0.05) { + slowups++; + testDiag("callback %.02f setup time |%f| >= 0.05 seconds", + pcbt[i]->delay, delta); + } + updateStats(setupError[i%NUM_CALLBACK_PRIORITIES], delta); + } + } + testOk(faults == 0, "%d faults during callback setup", faults); + if (slowups) + testDiag("%d slowups during callback setup", slowups); + + slowups = 0; + for (i = 0; i < NCALLBACKS ; i++) { + double delta, error; + + if(pcbt[i]->resultFail || pcbt[i]->pass!=2) + continue; + delta = epicsTimeDiffInSeconds(&pcbt[i]->pass2Time, &pcbt[i]->pass1Time); + error = delta - pcbt[i]->delay; + if (fabs(error) >= 0.05) { + slowups++; + testDiag("delay %.02f seconds, delay error |%.04f| >= 0.05", + pcbt[i]->delay, error); + } + updateStats(timeError[i%NUM_CALLBACK_PRIORITIES], error); + } + if (slowups) + testDiag("%d slowups during callback setup", slowups); + + testDiag("Setup time statistics"); + printStats(setupError[0], "LOW"); + printStats(setupError[1], "MID"); + printStats(setupError[2], "HIGH"); + + testDiag("Delay time statistics"); + printStats(timeError[0], "LOW"); + printStats(timeError[1], "MID"); + printStats(timeError[2], "HIGH"); + + for (i = 0; i < NCALLBACKS ; i++) { + free(pcbt[i]); + } + + callbackStop(); + callbackCleanup(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/chfPluginTest.c b/modules/database/test/ioc/db/chfPluginTest.c new file mode 100644 index 000000000..becd876e8 --- /dev/null +++ b/modules/database/test/ioc/db/chfPluginTest.c @@ -0,0 +1,1058 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include +#include + +#include "chfPlugin.h" +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "errlog.h" +#include "registry.h" +#include "epicsUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" + +#define PATTERN 0x55555555 +#define TYPE_START 0xAAA +#define R_LEVEL 42 + +/* Expected / actually run callback bit definitions */ +#define e_alloc 0x00000001 +#define e_free 0x00000002 +#define e_error 0x00000004 +#define e_ok 0x00000008 +#define e_open 0x00000010 +#define e_reg_pre 0x00000020 +#define e_reg_post 0x00000040 +#define e_report 0x00000080 +#define e_close 0x00000100 +#define e_pre 0x00000200 +#define e_post 0x00000400 +#define e_dtor 0x00000800 + +unsigned int e1, e2, c1, c2; +unsigned int offset; +int drop = -1; +db_field_log *dtorpfl; + +#define e_any (e_alloc | e_free | e_error | e_ok | e_open \ +| e_reg_pre | e_reg_post | e_pre | e_post | e_dtor | e_report | e_close) + +typedef struct myStruct { + int sent1; + char flag; + int sent2; + epicsUInt32 ival; + int sent3; + double dval; + int sent4; + int enumval; + int sent5; + char str[20]; + int sent6; + epicsUInt32 tval; + int sent7; + char c; + char c1[2]; + int offpre; + int offpost; +} myStruct; + +static const +chfPluginEnumType colorEnum[] = { {"R", 1}, {"G", 2}, {"B", 4}, {NULL,0} }; + +static const +chfPluginArgDef sloppyTaggedOpts[] = { + chfInt32 (myStruct, tval, "t" , 0, 0), + chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0), + chfTagBoolean(myStruct, flag, "F" , tval, 2, 0, 0), + chfTagDouble (myStruct, dval, "D" , tval, 3, 0, 0), + chfTagString (myStruct, str, "S" , tval, 4, 0, 0), + chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef strictTaggedOpts[] = { + chfInt32 (myStruct, tval, "t" , 1, 0), + chfBoolean (myStruct, flag, "f" , 1, 0), + chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0), + chfTagBoolean(myStruct, flag, "F" , tval, 2, 0, 0), + chfTagDouble (myStruct, dval, "D" , tval, 3, 1, 0), + chfTagDouble (myStruct, dval, "D2", tval, 4, 1, 0), + chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef strictOpts[] = { + chfInt32 (myStruct, ival, "i" , 1, 0), + chfBoolean(myStruct, flag, "f" , 1, 0), + chfDouble (myStruct, dval, "d" , 1, 0), + chfString (myStruct, str, "s" , 1, 0), + chfEnum (myStruct, enumval, "c" , 1, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef noconvOpts[] = { + chfInt32 (myStruct, ival, "i" , 0, 0), + chfBoolean(myStruct, flag, "f" , 0, 0), + chfDouble (myStruct, dval, "d" , 0, 0), + chfString (myStruct, str, "s" , 0, 0), + chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef sloppyOpts[] = { + chfInt32 (myStruct, ival, "i" , 0, 1), + chfBoolean(myStruct, flag, "f" , 0, 1), + chfDouble (myStruct, dval, "d" , 0, 1), + chfString (myStruct, str, "s" , 0, 1), + chfEnum (myStruct, enumval, "c" , 0, 1, colorEnum), + chfPluginArgEnd +}; + +/* Options defs with not enough room provided */ +static const +chfPluginArgDef brokenOpts1[] = { + chfInt32 (myStruct, c1, "i" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts2[] = { + chfDouble(myStruct, c1, "d" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts3[] = { + chfString(myStruct, c1, "s" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts4[] = { + chfEnum (myStruct, c1, "c" , 0, 1, colorEnum), + chfPluginArgEnd +}; + +int p_ok_return = 0; +int c_open_return = 0; +void *puser1, *puser2; + +static void clearStruct(void *p) { + myStruct *my = (myStruct*) p; + + if (!my) return; + memset(my, 0, sizeof(myStruct)); + my->sent1 = my->sent2 = my->sent3 = my->sent4 = + my->sent5 = my->sent6 = my->sent7 = PATTERN; + my->ival = 12; + my->tval = 99; + my->flag = 1; + my->dval = 1.234e5; + strcpy(my->str, "hello"); + my->enumval = 4; +} + +static char inst(void* user) { + return user == puser1 ? '1' : user == puser2 ? '2' : 'x'; +} + +static void * allocPvt(void) +{ + myStruct *my = (myStruct*) calloc(1, sizeof(myStruct)); + + if (!puser1) { + puser1 = my; + testOk(e1 & e_alloc, "allocPvt (1) called"); + c1 |= e_alloc; + } else if (!puser2) { + puser2 = my; + testOk(e2 & e_alloc, "allocPvt (2) called"); + c2 |= e_alloc; + } + clearStruct (my); + return my; +} + +static void * allocPvtFail(void) +{ + if (!puser1) { + testOk(e1 & e_alloc, "allocPvt (1) called"); + c1 |= e_alloc; + } + return NULL; +} + +static void freePvt(void *user) +{ + if (user == puser1) { + testOk(e1 & e_free, "freePvt (1) called"); + c1 |= e_free; + free(user); + puser1 = NULL; + } else if (user == puser2) { + testOk(e2 & e_free, "freePvt (2) called"); + c2 |= e_free; + free(user); + puser2 = NULL; + } else + testFail("freePvt: user pointer invalid"); +} + +static void parse_error(void *user) +{ + if (user == puser1) { + testOk(e1 & e_error, "parse_error (1) called"); + c1 |= e_error; + } else if (user == puser2) { + testOk(e2 & e_error, "parse_error (2) called"); + c2 |= e_error; + } else + testFail("parse_error: user pointer invalid"); +} + +static int parse_ok(void *user) +{ + if (user == puser1) { + testOk(e1 & e_ok, "parse_ok (1) called"); + c1 |= e_ok; + } else if (user == puser2) { + testOk(e2 & e_ok, "parse_ok (2) called"); + c2 |= e_ok; + } else + testFail("parse_ok: user pointer invalid"); + + return p_ok_return; +} + +static long channel_open(dbChannel *chan, void *user) +{ + if (user == puser1) { + testOk(e1 & e_open, "channel_open (1) called"); + c1 |= e_open; + } else if (user == puser2) { + testOk(e2 & e_open, "channel_open (2) called"); + c2 |= e_open; + } else + testFail("channel_open: user pointer invalid"); + + return c_open_return; +} + +static void dbfl_free1(db_field_log *pfl) { + testOk(e1 & e_dtor, "dbfl_free (1) called"); + testOk(dtorpfl == pfl, "dbfl_free (1): db_field_log pointer correct"); + dtorpfl = NULL; + c1 |= e_dtor; +} + +static void dbfl_free2(db_field_log *pfl) { + testOk(e2 & e_dtor, "dbfl_free (2) called"); + testOk(dtorpfl == pfl, "dbfl_free (2): db_field_log pointer correct"); + dtorpfl = NULL; + c2 |= e_dtor; +} + +static db_field_log * pre(void *user, dbChannel *chan, db_field_log *pLog) { + myStruct *my = (myStruct*)user; + dbfl_freeFunc *dtor = NULL; + + if (my == puser1) { + testOk(e1 & e_pre, "pre (1) called"); + testOk(!(c2 & e_pre), + "pre (2) was not called before pre (1)"); + c1 |= e_pre; + dtor = dbfl_free1; + } else if (my == puser2) { + testOk(e2 & e_pre, "pre (2) called"); + testOk(!(e1 & e_pre) || c1 & e_pre, + "pre (1) was called before pre (2)"); + c2 |= e_pre; + dtor = dbfl_free2; + } else { + testFail("pre: user pointer invalid"); + testSkip(1, "Can't check order of pre(1)/pre(2)"); + } + testOk(!(c1 & e_post), + "post (1) was not called before pre (%c)", inst(user)); + testOk(!(c2 & e_post), + "post (2) was not called before pre (%c)", inst(user)); + + if (!testOk(pLog->field_type == TYPE_START + my->offpre, + "pre (%c) got field log of expected type", inst(user))) + testDiag("expected: %d, got %d", + TYPE_START + my->offpre, pLog->field_type); + pLog->field_type++; + + if (my->offpre == 0) { /* The first one registers a dtor and saves pfl */ + pLog->u.r.dtor = dtor; + dtorpfl = pLog; + } + + if (my->offpre == drop) { + testDiag("pre (%c) is dropping the field log", inst(user)); + return NULL; + } + return pLog; +} + +static db_field_log * post(void *user, dbChannel *chan, db_field_log *pLog) { + myStruct *my = (myStruct*)user; + dbfl_freeFunc *dtor = NULL; + + if (my == puser1) { + testOk(e1 & e_post, "post (1) called"); + testOk(!(c2 & e_post), + "post (2) was not called before post (1)"); + c1 |= e_post; + dtor = dbfl_free1; + } else if (my == puser2) { + testOk(e2 & e_post, "post (2) called"); + testOk(!(e1 & e_post) || c1 & e_post, + "post (1) was called before post (2)"); + c2 |= e_post; + dtor = dbfl_free2; + } else { + testFail("post: user pointer invalid"); + testSkip(1, "Can't check order of post(1)/post(2)"); + } + testOk(!(e1 & e_pre) || c1 & e_pre, + "pre (1) was called before post (%c)", inst(user)); + testOk(!(e2 & e_pre) || c2 & e_pre, + "pre (2) was called before post (%c)", inst(user)); + + if (!testOk(pLog->field_type == TYPE_START + my->offpost, + "post (%c) got field log of expected type", inst(user))) + testDiag("expected: %d, got %d", + TYPE_START + my->offpost, pLog->field_type); + pLog->field_type++; + + if (my->offpost == 0) { /* The first one registers a dtor and saves pfl */ + pLog->u.r.dtor = dtor; + dtorpfl = pLog; + } + + if (my->offpost == drop) { + testDiag("post (%c) is dropping the field log", inst(user)); + return NULL; + } + return pLog; +} + +static void channelRegisterPre(dbChannel *chan, void *user, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + myStruct *my = (myStruct*)user; + + if (my == puser1) { + testOk(e1 & e_reg_pre, "register_pre (1) called"); + testOk(!(c2 & e_reg_pre), + "register_pre (2) was not called before register_pre (1)"); + c1 |= e_reg_pre; + } else if (my == puser2) { + testOk(e2 & e_reg_pre, "register_pre (2) called"); + testOk(!(e1 & e_reg_pre) || c1 & e_reg_pre, + "register_pre (1) was called before register_pre (2)"); + c2 |= e_reg_pre; + } else { + testFail("register_pre: user pointer invalid"); + testSkip(1, "Can't check order of register_pre(1)/register_pre(2)"); + } + testOk(!(c1 & e_reg_post), + "register_post (1) was not called before register_pre (%c)", inst(user)); + testOk(!(c2 & e_reg_post), + "register_post (2) was not called before register_pre (%c)", inst(user)); + + my->offpre = offset++; + probe->field_type++; + *cb_out = pre; + *arg_out = user; +} + +static void channelRegisterPost(dbChannel *chan, void *user, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + myStruct *my = (myStruct*)user; + + if (my == puser1) { + testOk(e1 & e_reg_post, "register_post (1) called"); + testOk(!(c2 & e_reg_post), + "register_post (2) was not called before register_post (1)"); + c1 |= e_reg_post; + } else if (my == puser2) { + testOk(e2 & e_reg_post, "register_post (2) called"); + testOk(!(e1 & e_reg_post) || c1 & e_reg_post, + "register_post (1) was called before register_post (2)"); + c2 |= e_reg_post; + } else { + testFail("register_post: user pointer invalid"); + testSkip(1, "Can't check order of register_post(1)/register_post(2)"); + } + testOk(!(e1 & e_reg_pre) || c1 & e_reg_pre, + "register_pre (1) was called before register_post (%c)", inst(user)); + testOk(!(e2 & e_reg_pre) || c2 & e_reg_pre, + "register_pre (2) was called before register_post (%c)", inst(user)); + + my->offpost = offset++; + probe->field_type++; + *cb_out = post; + *arg_out = user; +} + +static void channel_report(dbChannel *chan, void *user, int level, + const unsigned short indent) +{ + testOk(level == R_LEVEL - 2, "channel_report: level correct %u == %u", level, R_LEVEL-2); + if (user == puser1) { + testOk(e1 & e_report, "channel_report (1) called"); + c1 |= e_report; + } else if (user == puser2) { + testOk(e2 & e_report, "channel_report (2) called"); + c2 |= e_report; + } else + testFail("channel_report: user pointer invalid"); +} + +static void channel_close(dbChannel *chan, void *user) +{ + if (user == puser1) { + testOk(e1 & e_close, "channel_close (1) called"); + c1 |= e_close; + } else if (user == puser2) { + testOk(e2 & e_close, "channel_close (2) called"); + c2 |= e_close; + } else + testFail("channel_close: user pointer invalid"); +} + +static chfPluginIf myPif = { + allocPvt, + freePvt, + + parse_error, + parse_ok, + + channel_open, + channelRegisterPre, + channelRegisterPost, + channel_report, + channel_close +}; + +static chfPluginIf prePif = { + allocPvt, + freePvt, + + parse_error, + parse_ok, + + channel_open, + channelRegisterPre, + NULL, + channel_report, + channel_close +}; + +static chfPluginIf postPif = { + allocPvt, + freePvt, + + parse_error, + parse_ok, + + channel_open, + NULL, + channelRegisterPost, + channel_report, + channel_close +}; + +static chfPluginIf allocFailPif = { + allocPvtFail, + freePvt, + + parse_error, + parse_ok, + + channel_open, + channelRegisterPre, + channelRegisterPost, + channel_report, + channel_close +}; + +static int checkValues(myStruct *my, + char t, epicsUInt32 i, int f, double d, char *s1, char *s2, int c) { + int ret = 1; + int s1fail, s2fail; + int s2valid = (s2 && s2[0] != '\0'); + + if (!my) return 0; +#define CHK(A,B,FMT) if((A)!=(B)) {testDiag("Fail: " #A " (" FMT ") != " #B " (" FMT")", A, B); ret=0;} + CHK(my->sent1, PATTERN, "%08x") + CHK(my->sent2, PATTERN, "%08x") + CHK(my->sent3, PATTERN, "%08x") + CHK(my->sent4, PATTERN, "%08x") + CHK(my->sent5, PATTERN, "%08x") + CHK(my->sent6, PATTERN, "%08x") + CHK(my->sent7, PATTERN, "%08x") + CHK(my->tval, t, "%08x") + CHK(my->ival, i, "%08x") + CHK(my->flag, f, "%02x") + CHK(my->dval, d, "%f") + CHK(my->enumval, c, "%d") +#undef CHK + s2fail = s1fail = strcmp(s1, my->str); + if (s2valid) s2fail = strcmp(s2, my->str); + if (s1fail && s2fail) { + if (s1fail) testDiag("Fail: my->str (%s) != s (%s)", my->str, s1); + if (s2valid && s2fail) testDiag("Fail: my->str (%s) != s (%s)", my->str, s2); + ret = 0; + } + return ret; +} + +static void testHead (char* title) { + testDiag("--------------------------------------------------------"); + testDiag("%s", title); + testDiag("--------------------------------------------------------"); +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(chfPluginTest) +{ + dbChannel *pch; + db_field_log *pfl; + +#ifdef _WIN32 +#if (defined(_MSC_VER) && _MSC_VER < 1900) || \ + (defined(_MINGW) && defined(_TWO_DIGIT_EXPONENT)) + _set_output_format(_TWO_DIGIT_EXPONENT); +#endif +#endif + + testPlan(1433); + + dbChannelInit(); + db_init_events(); + + /* Enum to string conversion */ + testHead("Enum to string conversion"); + testOk(strcmp(chfPluginEnumString(colorEnum, 1, "-"), "R") == 0, + "Enum to string: R"); + testOk(strcmp(chfPluginEnumString(colorEnum, 2, "-"), "G") == 0, + "Enum to string: G"); + testOk(strcmp(chfPluginEnumString(colorEnum, 4, "-"), "B") == 0, + "Enum to string: B"); + testOk(strcmp(chfPluginEnumString(colorEnum, 3, "-"), "-") == 0, + "Enum to string: invalid index"); + + if (dbReadDatabase(&pdbbase, "dbTestIoc.dbd", + "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) + testAbort("Database description 'dbTestIoc.dbd' not found"); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + if (dbReadDatabase(&pdbbase, "xRecord.db", + "." OSI_PATH_LIST_SEPARATOR "..", NULL)) + testAbort("Test database 'xRecord.db' not found"); + + testHead("Try to register buggy plugins"); + eltc(0); + testOk(!!chfPluginRegister("buggy", &myPif, brokenOpts1), + "not enough storage for integer"); + testOk(!!chfPluginRegister("buggy", &myPif, brokenOpts2), + "not enough storage for double"); + testOk(!!chfPluginRegister("buggy", &myPif, brokenOpts3), + "not enough storage for string"); + testOk(!!chfPluginRegister("buggy", &myPif, brokenOpts4), + "not enough storage for enum"); + errlogFlush(); + eltc(1); + + testHead("Register plugins"); + testOk(!chfPluginRegister("sloppy-tagged", &myPif, sloppyTaggedOpts), + "register plugin sloppy-tagged"); + testOk(!chfPluginRegister("strict-tagged", &myPif, strictTaggedOpts), + "register plugin strict-tagged"); + testOk(!chfPluginRegister("strict", &myPif, strictOpts), + "register plugin strict"); + testOk(!chfPluginRegister("noconv", &myPif, noconvOpts), + "register plugin noconv"); + testOk(!chfPluginRegister("sloppy", &myPif, sloppyOpts), + "register plugin sloppy"); + testOk(!chfPluginRegister("pre", &prePif, sloppyOpts), + "register plugin pre"); + testOk(!chfPluginRegister("post", &postPif, sloppyOpts), + "register plugin post"); + testOk(!chfPluginRegister("alloc-fail", &allocFailPif, sloppyOpts), + "register plugin alloc-fail"); + + /* Check failing allocation of plugin private structures */ + + testHead("Failing allocation of plugin private structures"); + /* tag i */ + e1 = e_alloc; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"alloc-fail\":{\"i\":1}}")), + "create channel for alloc-fail: allocPvt returning NULL"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + /* TAGGED parsing: shorthand for integer plus other parameter */ + + /* STRICT TAGGED parsing: mandatory, no conversions */ + + /* All perfect */ + testHead("STRICT TAGGED parsing: all ok"); + /* tag D (t and d) and f */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"strict-tagged\":{\"D\":1.2e15,\"f\":false}}")), + "create channel for strict-tagged parsing: D (t and d) and f"); + testOk(checkValues(puser1, 3, 12, 0, 1.2e15, "hello", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag D2 (t and d) and f */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"strict-tagged\":{\"D2\":1.2e15,\"f\":false}}")), + "create channel for strict-tagged parsing: D2 (t and d) and f"); + testOk(checkValues(puser1, 4, 12, 0, 1.2e15, "hello", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag F: (t and f), d missing) */ + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict-tagged\":{\"F\":false}}")), + "create channel for strict-tagged parsing: F (t and f), d missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag I: (t and i) and f, d missing) */ + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict-tagged\":{\"I\":1,\"f\":false}}")), + "create channel for strict-tagged parsing: I (t and i) and f, d missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + /* SLOPPY TAGGED parsing: optional, all others have defaults */ + + testHead("SLOPPY TAGGED parsing: all ok"); + + /* tag i */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"sloppy-tagged\":{\"I\":1}}")), + "create channel for sloppy-tagged parsing: I"); + testOk(checkValues(puser1, 1, 1, 1, 1.234e5, "hello", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag f */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"sloppy-tagged\":{\"F\":false}}")), + "create channel for sloppy-tagged parsing: F"); + testOk(checkValues(puser1, 2, 12, 0, 1.234e5, "hello", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag d */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"sloppy-tagged\":{\"D\":1.2e15}}")), + "create channel for sloppy-tagged parsing: D"); + testOk(checkValues(puser1, 3, 12, 1, 1.2e15, "hello", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag s */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"sloppy-tagged\":{\"S\":\"bar\"}}")), + "create channel for sloppy-tagged parsing: S"); + testOk(checkValues(puser1, 4, 12, 1, 1.234e5, "bar", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + /* tag c */ + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"sloppy-tagged\":{\"C\":\"R\"}}")), + "create channel for sloppy-tagged parsing: C"); + testOk(checkValues(puser1, 5, 12, 1, 1.234e5, "hello", 0, 1), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + /* STRICT parsing: mandatory, no conversion */ + + /* All perfect */ + testHead("STRICT parsing: all ok"); + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), + "create channel for strict parsing: JSON correct"); + testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 1), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + /* Any one missing must fail */ + testHead("STRICT parsing: any missing parameter must fail"); + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), + "create channel for strict parsing: c missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict\":{\"f\":false,\"i\":1,\"d\":1.2e15,\"c\":\"R\"}}")), + "create channel for strict parsing: s missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict\":{\"i\":1,\"c\":\"R\",\"f\":false,\"s\":\"bar\"}}")), + "create channel for strict parsing: d missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict\":{\"d\":1.2e15,\"c\":\"R\",\"i\":1,\"s\":\"bar\"}}")), + "create channel for strict parsing: f missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_alloc | e_error | e_free; c1 = 0; + testOk(!(pch = dbChannelCreate( + "x.{\"strict\":{\"c\":\"R\",\"s\":\"bar\",\"f\":false,\"d\":1.2e15}}")), + "create channel for strict parsing: i missing"); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + /* NOCONV parsing: optional, no conversion */ + + /* Any one missing must leave the default intact */ + testHead("NOCONV parsing: missing parameters get default value"); + e1 = e_alloc | e_ok; c1 = 0; + testOk(!!(pch = dbChannelCreate( + "x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), + "create channel for noconv parsing: c missing"); + testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 4), + "guards intact, values correct"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + e1 = e_close | e_free; c1 = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser1, "user part cleaned up"); + if (!testOk(c1 == e1, "all expected calls happened")) + testDiag("expected %#x - called %#x", e1, c1); + + e1 = e_any; + testOk(!!(pch = dbChannelCreate( + "x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"c\":\"R\"}}")), + "create channel for noconv parsing: s missing"); + testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "hello", 0, 1), + "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate( + "x.{\"noconv\":{\"i\":1,\"f\":false,\"s\":\"bar\",\"c\":\"R\"}}")), + "create channel for noconv parsing: d missing"); + testOk(checkValues(puser1, 99, 1, 0, 1.234e5, "bar", 0, 1), + "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate( + "x.{\"noconv\":{\"i\":1,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), + "create channel for noconv parsing: f missing"); + testOk(checkValues(puser1, 99, 1, 1, 1.2e15, "bar", 0, 1), + "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate( + "x.{\"noconv\":{\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), + "create channel for noconv parsing: i missing"); + testOk(checkValues(puser1, 99, 12, 0, 1.2e15, "bar", 0, 1), + "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + /* Reject wrong types */ +#define WRONGTYPETEST(Var, Val, Typ) \ + e1 = e_alloc | e_error | e_free; c1 = 0; \ + testOk(!(pch = dbChannelCreate("x.{\"noconv\":{\""#Var"\":"#Val"}}")), \ + "create channel for noconv parsing: wrong type "#Typ" for "#Var); \ + testOk(!puser1, "user part cleaned up"); \ + if (!testOk(c1 == e1, "all expected calls happened")) \ + testDiag("expected %#x - called %#x", e1, c1); + + testHead("NOCONV parsing: rejection of wrong parameter types"); + + WRONGTYPETEST(i, 123.0, double); + WRONGTYPETEST(i, true, boolean); + WRONGTYPETEST(i, "1", string); + WRONGTYPETEST(f, "false", string); + WRONGTYPETEST(f, 0.0, double); + WRONGTYPETEST(f, 1, integer); + WRONGTYPETEST(d, "1.2", string); + WRONGTYPETEST(d, true, boolean); + WRONGTYPETEST(d, 123, integer); + WRONGTYPETEST(s, 1.23, double); + WRONGTYPETEST(s, true, boolean); + WRONGTYPETEST(s, 123, integer); + WRONGTYPETEST(c, 1.23, double); + WRONGTYPETEST(c, true, boolean); + WRONGTYPETEST(c, 2, integer); + + /* SLOPPY parsing: optional, with conversion */ + +#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval1, Sval2, Cval) \ + e1 = e_alloc | e_ok; c1 = 0; \ + testDiag("Calling dbChannelCreate x.{\"sloppy\":{\""#Var"\":"#Val"}}"); \ + testOk(!!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), \ + "create channel for sloppy parsing: "#Typ" (good) for "#Var); \ + testOk(checkValues(puser1, 99, Ival, Fval, Dval, Sval1, Sval2, Cval), \ + "guards intact, values correct"); \ + if (!testOk(c1 == e1, "create channel: all expected calls happened")) \ + testDiag("expected %#x - called %#x", e1, c1); \ + e1 = e_close | e_free; c1 = 0; \ + if (pch) dbChannelDelete(pch); \ + testOk(!puser1, "user part cleaned up"); \ + if (!testOk(c1 == e1, "delete channel: all expected calls happened")) \ + testDiag("expected %#x - called %#x", e1, c1); + +#define CONVTESTBAD(Var, Val, Typ) \ + e1 = e_alloc | e_error | e_free; c1 = 0; \ + testDiag("Calling dbChannelCreate x.{\"sloppy\":{\""#Var"\":"#Val"}}"); \ + testOk(!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), \ + "create channel for sloppy parsing: "#Typ" (bad) for "#Var); \ + testOk(!puser1, "user part cleaned up"); \ + if (!testOk(c1 == e1, "create channel: all expected calls happened")) \ + testDiag("expected %#x - called %#x", e1, c1); + + /* To integer */ + testHead("SLOPPY parsing: conversion to integer"); + CONVTESTGOOD(i, "123e4", positive string, 123, 1, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(i, "-12345", negative string, -12345, 1, 1.234e5, "hello", 0, 4); + CONVTESTBAD(i, "9234567890", out-of-range string); + CONVTESTBAD(i, ".4", invalid string); + CONVTESTGOOD(i, false, valid boolean, 0, 1, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(i, 3456.789, valid double, 3456, 1, 1.234e5, "hello", 0, 4); + CONVTESTBAD(i, 34.7e14, out-of-range double); + + /* To boolean */ + testHead("SLOPPY parsing: conversion to boolean"); + CONVTESTGOOD(f, "false", valid string, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, "False", capital valid string, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, "0", 0 string, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, "15", 15 string, 12, 1, 1.234e5, "hello", 0, 4); + CONVTESTBAD(f, ".4", invalid .4 string); + CONVTESTBAD(f, "Flase", misspelled invalid string); + CONVTESTGOOD(f, 0, zero integer, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, 12, positive integer, 12, 1, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, -1234, negative integer, 12, 1, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, 0.4, positive non-zero double, 12, 1, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, 0.0, zero double, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, -0.0, minus-zero double, 12, 0, 1.234e5, "hello", 0, 4); + CONVTESTGOOD(f, -1.24e14, negative double, 12, 1, 1.234e5, "hello", 0, 4); + + /* To double */ + testHead("SLOPPY parsing: conversion to double"); + CONVTESTGOOD(d, "123e4", positive double string, 12, 1, 1.23e6, "hello", 0, 4); + CONVTESTGOOD(d, "-7.89e-14", negative double string, 12, 1, -7.89e-14, "hello", 0, 4); + CONVTESTGOOD(d, "123", positive integer string, 12, 1, 123.0, "hello", 0, 4); + CONVTESTGOOD(d, "-1234567", negative integer string, 12, 1, -1.234567e6, "hello", 0, 4); + CONVTESTBAD(d, "1.67e407", out-of-range double string); + CONVTESTBAD(d, "blubb", invalid blubb string); + CONVTESTGOOD(d, 123, positive integer, 12, 1, 123.0, "hello", 0, 4); + CONVTESTGOOD(d, -12345, negative integer, 12, 1, -1.2345e4, "hello", 0, 4); + CONVTESTGOOD(d, true, true boolean, 12, 1, 1.0, "hello", 0, 4); + CONVTESTGOOD(d, false, false boolean, 12, 1, 0.0, "hello", 0, 4); + + /* To string */ + testHead("SLOPPY parsing: conversion to string"); + CONVTESTGOOD(s, 12345, positive integer, 12, 1, 1.234e5, "12345", 0, 4); + CONVTESTGOOD(s, -1234567891, negative integer, 12, 1, 1.234e5, "-1234567891", 0, 4); + CONVTESTGOOD(s, true, true boolean, 12, 1, 1.234e5, "true", 0, 4); + CONVTESTGOOD(s, false, false boolean, 12, 1, 1.234e5, "false", 0, 4); + CONVTESTGOOD(s, 123e4, small positive double, 12, 1, 1.234e5, "1230000", 0, 4); + CONVTESTGOOD(s, -123e24, negative double, 12, 1, 1.234e5, "-1.23e+26", "-1.23e+026", 4); + CONVTESTGOOD(s, -1.23456789123e26, large negative double, 12, 1, 1.234e5, "-1.23456789123e+26", "-1.23456789123e+026", 4); + + /* To Enum */ + testHead("SLOPPY parsing: conversion to enum"); + CONVTESTGOOD(c, 2, valid integer choice, 12, 1, 1.234e5, "hello", 0, 2); + CONVTESTBAD(c, 3, invalid integer choice); + CONVTESTBAD(c, 3.2, double); + CONVTESTGOOD(c, "R", valid string choice, 12, 1, 1.234e5, "hello", 0, 1); + CONVTESTBAD(c, "blubb", invalid string choice); + + /* Registering and running filter callbacks */ + +#define CHAINTEST1(Type, Json, ExpReg, ExpRun, DType) \ + testHead("Filter chain test, "Type" filter"); \ + offset = 0; \ + e1 = e_alloc | e_ok; c1 = 0; \ + testOk(!!(pch = dbChannelCreate("x."Json)), "filter chains: create channel with "Type" filter"); \ + if (!testOk(c1 == e1, "create channel: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + e1 = e_open | ExpReg; c1 = 0; \ + testOk(!dbChannelOpen(pch), "dbChannelOpen returned channel"); \ + if (!testOk(c1 == e1, "open channel: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + e1 = ExpRun; c1 = 0; \ + testOk(!!(pfl = db_create_read_log(pch)), "create db_field_log"); \ + pfl->type = dbfl_type_ref; \ + pfl->field_type = TYPE_START; \ + testOk(!!(pfl = dbChannelRunPreChain(pch, pfl)), "run pre eventq chain"); \ + testOk(!!(pfl = dbChannelRunPostChain(pch, pfl)), "run post eventq chain"); \ + testOk(pfl->field_type == TYPE_START + DType, "final data type is correct"); \ + db_delete_field_log(pfl); \ + if (!testOk(c1 == e1, "run filter chains: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + e1 = e_report; c1 = 0; \ + dbChannelShow(pch, R_LEVEL, 0); \ + if (!testOk(c1 == e1, "report: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + e1 = e_close | e_free; c1 = 0; \ + if (pch) dbChannelDelete(pch); \ + if (!testOk(c1 == e1, "delete channel: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); + +#define CHAINTEST2(Type, Json, ExpReg1, ExpRun1, ExpReg2, ExpRun2, DType) \ + testHead("Filter chain test, "Type" filters"); \ + offset = 0; \ + e1 = e_alloc | e_ok; c1 = 0; \ + e2 = e_alloc | e_ok; c2 = 0; \ + testOk(!!(pch = dbChannelCreate("x."Json)), "filter chains: create channel with "Type" filters"); \ + if (!testOk(c1 == e1, "create channel (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + if (!testOk(c2 == e2, "create channel (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2); \ + e1 = e_open | ExpReg1; c1 = 0; \ + e2 = e_open | ExpReg2; c2 = 0; \ + if (pch) testOk(!dbChannelOpen(pch), "dbChannelOpen returned channel"); \ + if (!testOk(c1 == e1, "open channel (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + if (!testOk(c2 == e2, "open channel (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2); \ + e1 = ExpRun1; c1 = 0; \ + e2 = ExpRun2; c2 = 0; \ + if (pch) testOk(!!(pfl = db_create_read_log(pch)), "create db_field_log"); \ + pfl->type = dbfl_type_ref; \ + pfl->field_type = TYPE_START; \ + if (pch) testOk(!!(pfl = dbChannelRunPreChain(pch, pfl)) || (drop >=0 && drop <= 1), "run pre eventq chain"); \ + if (pch && (drop < 0 || drop >= 2)) testOk(!!(pfl = dbChannelRunPostChain(pch, pfl)) || drop >=2, "run post eventq chain"); \ + if (pfl) testOk(pfl->field_type == TYPE_START + DType, "final data type is correct"); \ + if (pfl) db_delete_field_log(pfl); \ + if (!testOk(c1 == e1, "run filter chains (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + if (!testOk(c2 == e2, "run filter chains (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2); \ + e1 = e_report; c1 = 0; \ + e2 = e_report; c2 = 0; \ + dbChannelShow(pch, R_LEVEL, 0); \ + if (!testOk(c1 == e1, "report (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + if (!testOk(c2 == e2, "report (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2); \ + e1 = e_close | e_free; c1 = 0; \ + e2 = e_close | e_free; c2 = 0; \ + if (pch) dbChannelDelete(pch); \ + if (!testOk(c1 == e1, "delete channel (1): all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \ + if (!testOk(c2 == e2, "delete channel (2): all expected calls happened")) testDiag("expected %#x - called %#x", e2, c2); + + CHAINTEST1("1 pre", "{\"pre\":{}}", e_reg_pre, e_pre | e_dtor, 1); /* One filter, pre chain */ + CHAINTEST1("1 post", "{\"post\":{}}", e_reg_post, e_post | e_dtor, 1); /* One filter, post chain */ + CHAINTEST1("1 both", "{\"sloppy\":{}}", e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, 2); /* One, both chains */ + CHAINTEST2("2 pre", "{\"pre\":{},\"pre\":{}}", e_reg_pre, e_pre | e_dtor, e_reg_pre, e_pre, 2); /* Two filters, pre chain */ + CHAINTEST2("2 post", "{\"post\":{},\"post\":{}}", e_reg_post, e_post | e_dtor, e_reg_post, e_post, 2); /* Two filters, post chain */ + CHAINTEST2("2 both", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains */ + e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_pre | e_reg_post, e_pre | e_post, 4); + CHAINTEST2("1 pre, 1 post", "{\"pre\":{},\"post\":{}}", e_reg_pre, e_pre | e_dtor, e_reg_post, e_post, 2); /* Two, pre then post */ + CHAINTEST2("1 post, 1 pre", "{\"post\":{},\"pre\":{}}", e_reg_post, e_post, e_reg_pre, e_pre | e_dtor, 2); /* Two, post then pre */ + CHAINTEST2("1 pre, 1 both", "{\"pre\":{},\"sloppy\":{}}", /* Two, pre then both */ + e_reg_pre, e_pre | e_dtor, e_reg_pre | e_reg_post, e_pre | e_post, 3); + CHAINTEST2("1 both, 1 pre", "{\"sloppy\":{},\"pre\":{}}", /* Two, both then pre */ + e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_pre, e_pre, 3); + CHAINTEST2("1 post, 1 both", "{\"post\":{},\"sloppy\":{}}", /* Two, post then both */ + e_reg_post, e_post, e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, 3); + CHAINTEST2("1 both, 1 post", "{\"sloppy\":{},\"post\":{}}", /* Two, both then post */ + e_reg_pre | e_reg_post, e_pre | e_post | e_dtor, e_reg_post, e_post, 3); + + /* Plugins dropping updates */ + drop = 0; + CHAINTEST2("2 both (drop at 0)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 0 */ + e_reg_pre | e_reg_post, e_pre, e_reg_pre | e_reg_post, 0, -1); + drop = 1; + CHAINTEST2("2 both (drop at 1)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 1 */ + e_reg_pre | e_reg_post, e_pre, e_reg_pre | e_reg_post, e_pre, -1); + drop = 2; + CHAINTEST2("2 both (drop at 2)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 2 */ + e_reg_pre | e_reg_post, e_pre | e_post, e_reg_pre | e_reg_post, e_pre, -1); + drop = 3; + CHAINTEST2("2 both (drop at 3)", "{\"sloppy\":{},\"sloppy\":{}}", /* Two, both chains, drop at filter 3 */ + e_reg_pre | e_reg_post, e_pre | e_post, e_reg_pre | e_reg_post, e_pre | e_post, -1); + drop = -1; + + dbFreeBase(pdbbase); + registryFree(); + pdbbase = NULL; + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbBadLink.db b/modules/database/test/ioc/db/dbBadLink.db new file mode 100644 index 000000000..6c41e854b --- /dev/null +++ b/modules/database/test/ioc/db/dbBadLink.db @@ -0,0 +1,16 @@ +# The records in this file have intentional +# syntax error in their input links + +record(x, "eVME_IO1") { + field(DTYP, "Unit Test VME_IO") + field(INP, "C100 S101 @parm VME_IO") +} +record(x, "eVME_IO2") { + field(DTYP, "Unit Test VME_IO") + field(INP, "#C200 201 @parm VME_IO") +} + +record(x, "eINST_IO") { + field(DTYP, "Unit Test INST_IO") + field(INP, "hello") +} diff --git a/modules/database/test/ioc/db/dbCACTest.cpp b/modules/database/test/ioc/db/dbCACTest.cpp new file mode 100644 index 000000000..4343ce5c0 --- /dev/null +++ b/modules/database/test/ioc/db/dbCACTest.cpp @@ -0,0 +1,84 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ +/* + * Part of dbCaLinkTest, compiled seperately to avoid + * dbAccess.h vs. db_access.h conflicts + */ + +#include + +#include +#include + +#include + +#include "epicsUnitTest.h" + +#include "cadef.h" + +#define testECA(OP) if((OP)!=ECA_NORMAL) {testAbort("%s", #OP);} else {testPass("%s", #OP);} + +void putgetarray(chid chanid, double first, size_t count) +{ + testDiag("putgetarray(%f,%u)", first, (unsigned)count); + + std::vector buf(count); + for(size_t i=0; i buf2(count); + + testECA(ca_array_get(DBR_DOUBLE, count, chanid, &buf2[0])); + + testECA(ca_pend_io(1.0)); + + for(size_t i=0; i + */ + +#include +#include +#include + +#define EPICS_DBCA_PRIVATE_API + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "cantProceed.h" +#include "epicsEvent.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "dbAccess.h" +#include "epicsStdio.h" +#include "dbEvent.h" + +/* Declarations from cadef.h and db_access.h which we can't include here */ +typedef void * chid; +epicsShareExtern const unsigned short dbr_value_size[]; +epicsShareExtern short epicsShareAPI ca_field_type (chid chan); +#define MAX_UNITS_SIZE 8 + +#include "dbCaPvt.h" +#include "errlog.h" +#include "testMain.h" + +#include "xRecord.h" +#include "arrRecord.h" + +#define testOp(FMT,A,OP,B) testOk((A)OP(B), #A " ("FMT") " #OP " " #B " ("FMT")", A,B) + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static epicsEventId waitEvent; +static unsigned waitCounter; + +static +void waitForUpdateN(DBLINK *plink, unsigned long n) +{ + while(dbCaGetUpdateCount(plink)val = val; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + epicsThreadSleep(0.1); + } + + if (waitEvent) + epicsEventMustTrigger(waitEvent); +} + +static void testNativeLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 temp; + long nReq; + + testDiag("Link to a scalar numeric field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + /* ensure this is really a CA link */ + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + waitForUpdateN(psrclnk, 1); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdateN(psrclnk, 2); + + dbScanLock((dbCommon*)psrc); + /* local CA_LINK connects immediately */ + testOk1(dbCaIsLinkConnected(psrclnk)); + nReq = 422; + testOk1(dbCaGetNelements(psrclnk, &nReq)==0); + testOp("%ld",nReq,==,1l); + + nReq = 1; + temp = 0x0f0f0f0f; + testOk1(dbGetLink(psrclnk, DBR_LONG, (void*)&temp, NULL, &nReq)==0); + testOp("%d",temp,==,42); + dbScanUnlock((dbCommon*)psrc); + + temp = 1010; + nReq = 1; + putLink(psrclnk, DBR_LONG, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk1(ptarg->val==1010); + dbScanUnlock((dbCommon*)ptarg); + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + /* Start counter */ + epicsThreadCreate("countUp", epicsThreadPriorityHigh, + epicsThreadGetStackSize(epicsThreadStackSmall), countUp, ptarg); + + dbScanLock((dbCommon*)psrc); + /* Check that unlocked gets change */ + temp = getTwice(psrclnk, NULL); + testOk(temp == -1, "unlocked, getTwice returned %d (-1)", temp); + + /* Check locked gets are atomic */ + temp = dbLinkDoLocked(psrclnk, getTwice, NULL); + testOk(temp == 0, "locked, getTwice returned %d (0)", temp); + dbScanUnlock((dbCommon*)psrc); + + epicsEventMustWait(waitEvent); + epicsEventDestroy(waitEvent); + waitEvent = NULL; + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testStringLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + char temp[MAX_STRING_SIZE]; + long nReq; + + testDiag("Link to a string field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target.DESC CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + /* ensure this is really a CA link */ + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + waitForUpdateN(psrclnk, 1); + + dbScanLock((dbCommon*)ptarg); + strcpy(ptarg->desc, "hello"); + db_post_events(ptarg, &ptarg->desc, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdateN(psrclnk, 2); + + dbScanLock((dbCommon*)psrc); + /* local CA_LINK connects immediately */ + testOk1(dbCaIsLinkConnected(psrclnk)); + + nReq = 1; + memset(temp, '!', sizeof(temp)); + testOk1(dbGetLink(psrclnk, DBR_STRING, (void*)&temp, NULL, &nReq)==0); + testOk(strcmp(temp, "hello")==0, "%s == hello", temp); + dbScanUnlock((dbCommon*)psrc); + + strcpy(temp, "world"); + putLink(psrclnk, DBR_STRING, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk(strcmp(ptarg->desc, "world")==0, "%s == world", ptarg->desc); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void wasproc(xRecord *prec) +{ + waitCounter++; + epicsEventTrigger(waitEvent); +} + +static void testCP(void) +{ + xRecord *psrc, *ptarg; + + testDiag("Link CP modifier"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CP"); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &wasproc; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + dbCaSync(); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,1); /* initial processing */ + dbScanUnlock((dbCommon*)psrc); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,2); /* process due to monitor update */ + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static void fillArray(epicsInt32 *buf, unsigned count, epicsInt32 first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void fillArrayDouble(double *buf, unsigned count, double first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void checkArray(const char *msg, + epicsInt32 *buf, epicsInt32 first, + unsigned used) +{ + int match = 1; + unsigned i; + epicsInt32 x, *b; + + for(b=buf,x=first,i=0;ivalue.pv_link.pvt; + + if(lnk->type!=CA_LINK || !pca->pputNative) + return; + epicsMutexMustLock(pca->lock); + memset(pca->pputNative, '!', + pca->nelements*dbr_value_size[ca_field_type(pca->chid)]); + epicsMutexUnlock(pca->lock); +} + +static void testArrayLink(unsigned nsrc, unsigned ntarg) +{ + char buf[100]; + arrRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 *bufsrc, *buftarg, *tmpbuf; + long nReq; + unsigned num_min, num_max; + + testDiag("Link to a array numeric field"); + + /* source.INP = "target CA" */ + epicsSnprintf(buf, sizeof(buf), "TARGET=target CA,FTVL=LONG,SNELM=%u,TNELM=%u", + nsrc, ntarg); + testDiag("%s", buf); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest2.db", NULL, buf); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg= (arrRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->inp; + + eltc(0); + testIocInitOk(); + eltc(1); + + waitForUpdateN(psrclnk, 1); + + bufsrc = psrc->bptr; + buftarg= ptarg->bptr; + + num_max=num_min=psrc->nelm; + if(num_min>ptarg->nelm) + num_min=ptarg->nelm; + if(num_maxnelm) + num_max=ptarg->nelm; + /* always request more than can possibly be filled */ + num_max += 2; + + tmpbuf = callocMustSucceed(num_max, sizeof(*tmpbuf), "tmpbuf"); + + dbScanLock((dbCommon*)ptarg); + fillArray(buftarg, ptarg->nelm, 1); + ptarg->nord = ptarg->nelm; + db_post_events(ptarg, ptarg->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdateN(psrclnk, 2); + + dbScanLock((dbCommon*)psrc); + testDiag("fetch source.INP into source.BPTR"); + nReq = psrc->nelm; + if(dbGetLink(psrclnk, DBR_LONG, bufsrc, NULL, &nReq)==0) { + testPass("dbGetLink"); + testOp("%ld",nReq,==,(long)num_min); + checkArray("array update", bufsrc, 1, nReq); + } else { + testFail("dbGetLink"); + testSkip(2, "dbGetLink fails"); + } + testDiag("fetch source.INP into temp buffer w/ larger capacity"); + nReq = num_max; + if(dbGetLink(psrclnk, DBR_LONG, tmpbuf, NULL, &nReq)==0) { + testPass("dbGetLink"); + testOp("%ld",nReq,==,(long)ntarg); + checkArray("array update", tmpbuf, 1, nReq); + } else { + testFail("dbGetLink"); + testSkip(2, "dbGetLink fails"); + } + dbScanUnlock((dbCommon*)psrc); + + fillArray(bufsrc, psrc->nelm, 2); + /* write buffer allocated on first put */ + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + testOp("%ld",(long)ptarg->nord,==,(long)num_min); + + dbScanUnlock((dbCommon*)ptarg); + + /* write again to ensure that buffer is completely updated */ + spoilputbuf(psrclnk); + fillArray(bufsrc, psrc->nelm, 3); + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + testOp("%ld",(long)ptarg->nord,==,(long)num_min); + checkArray("array update", buftarg, 3, num_min); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); + + free(tmpbuf); + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg); +} + + +static void softarr(arrRecord *prec) +{ + long nReq = prec->nelm; + long status = dbGetLink(&prec->inp, DBR_DOUBLE, prec->bptr, NULL, &nReq); + + if(status) { + testFail("dbGetLink() -> %ld", status); + } else { + testPass("dbGetLink() succeeds"); + prec->nord = nReq; + if(nReq>0) + testDiag("%s.VAL[0] - %f", prec->name, *(double*)prec->bptr); + } + waitCounter++; + epicsEventTrigger(waitEvent); +} + +static void testreTargetTypeChange(void) +{ + arrRecord *psrc, *ptarg1, *ptarg2; + double *bufsrc, *buftarg1; + epicsInt32 *buftarg2; + + testDiag("Retarget an link to a PV with a different type DOUBLE->LONG"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest3.db", NULL, "NELM=5,TARGET=target1 CP"); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg1= (arrRecord*)testdbRecordPtr("target1"); + ptarg2= (arrRecord*)testdbRecordPtr("target2"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &softarr; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsEventMustWait(waitEvent); /* wait for initial processing */ + + bufsrc = psrc->bptr; + buftarg1= ptarg1->bptr; + buftarg2= ptarg2->bptr; + + testDiag("Update one with original target"); + + dbScanLock((dbCommon*)ptarg2); + fillArray(buftarg2, ptarg2->nelm, 2); + ptarg2->nord = ptarg2->nelm; + dbScanUnlock((dbCommon*)ptarg2); + + /* initialize buffers */ + dbScanLock((dbCommon*)ptarg1); + fillArrayDouble(buftarg1, ptarg1->nelm, 1); + ptarg1->nord = ptarg1->nelm; + db_post_events(ptarg1, ptarg1->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg1); + + epicsEventMustWait(waitEvent); /* wait for update */ + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 1, 5); + dbScanUnlock((dbCommon*)psrc); + + testDiag("Retarget"); + testdbPutFieldOk("source.INP", DBR_STRING, "target2 CP"); + + epicsEventMustWait(waitEvent); /* wait for update */ + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 2, 5); + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg1); + free(buftarg2); +} + +void dbCaLinkTest_testCAC(void); + +static void testCAC(void) +{ + arrRecord *psrc, *ptarg1, *ptarg2; + double *bufsrc, *buftarg1; + epicsInt32 *buftarg2; + + testDiag("Check local CA through libca"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest3.db", NULL, "NELM=5,TARGET=target1 CP"); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg1= (arrRecord*)testdbRecordPtr("target1"); + ptarg2= (arrRecord*)testdbRecordPtr("target2"); + + eltc(0); + testIocInitOk(); + eltc(1); + + bufsrc = psrc->bptr; + buftarg1= ptarg1->bptr; + buftarg2= ptarg2->bptr; + + dbCaLinkTest_testCAC(); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg1); + free(buftarg2); +} + +MAIN(dbCaLinkTest) +{ + testPlan(101); + testNativeLink(); + testStringLink(); + testCP(); + testArrayLink(1,1); + testArrayLink(10,1); + testArrayLink(1,10); + testArrayLink(10,10); + testreTargetTypeChange(); + testCAC(); + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbCaLinkTest1.db b/modules/database/test/ioc/db/dbCaLinkTest1.db new file mode 100644 index 000000000..aab5ceb86 --- /dev/null +++ b/modules/database/test/ioc/db/dbCaLinkTest1.db @@ -0,0 +1,5 @@ +record(x, "target") {} + +record(x, "source") { + field(LNK, "$(TARGET)") +} diff --git a/modules/database/test/ioc/db/dbCaLinkTest2.db b/modules/database/test/ioc/db/dbCaLinkTest2.db new file mode 100644 index 000000000..9cfa4ba00 --- /dev/null +++ b/modules/database/test/ioc/db/dbCaLinkTest2.db @@ -0,0 +1,10 @@ +record(arr, "target") { + field(FTVL, "$(TFTVL=$(FTVL=))") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "$(SFTVL=$(FTVL=))") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/modules/database/test/ioc/db/dbCaLinkTest3.db b/modules/database/test/ioc/db/dbCaLinkTest3.db new file mode 100644 index 000000000..f820bd554 --- /dev/null +++ b/modules/database/test/ioc/db/dbCaLinkTest3.db @@ -0,0 +1,14 @@ +record(arr, "target1") { + field(FTVL, "DOUBLE") + field(NELM, "$(TNELM=$(NELM=))") +} +record(arr, "target2") { + field(FTVL, "LONG") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "DOUBLE") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/modules/database/test/ioc/db/dbCaStats.db b/modules/database/test/ioc/db/dbCaStats.db new file mode 100644 index 000000000..bb909968e --- /dev/null +++ b/modules/database/test/ioc/db/dbCaStats.db @@ -0,0 +1,4 @@ +record(x, "e1") { +} +record(x, "e2") { +} diff --git a/modules/database/test/ioc/db/dbCaStatsTest.c b/modules/database/test/ioc/db/dbCaStatsTest.c new file mode 100644 index 000000000..020c65f10 --- /dev/null +++ b/modules/database/test/ioc/db/dbCaStatsTest.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include "dbCaTest.h" +#include "dbAccess.h" + +#include "dbUnitTest.h" +#include "testMain.h" + +#include "dbAccess.h" +#include "errlog.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static +void testCaStats(void) { + int channels; + int disconnected; + + testDiag("Check dbcaStats"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbCaStats.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + + testDiag("No CA links"); + + channels = disconnected = -1; + dbcaStats(&channels, &disconnected); + + testOk(channels==0, "channels==0 (got %d)", channels); + testOk(disconnected==0, "disconnected==0 (got %d)", disconnected); + + + testDiag("One CA link, disconnected"); + + testdbPutFieldOk("e2.INP", DBR_STRING, "e12 CA", 1); + + channels = disconnected = -1; + dbcaStats(&channels, &disconnected); + + testOk(channels==1, "channels==1 (got %d)", channels); + testOk(disconnected==1, "disconnected==1 (got %d)", disconnected); + + /* Connected CA links can not be tested without fully starting the IOC + which we will skip for the moment as it is not allowed inside the vxWorks/RTEMS test harness. + The above is good enough to check for bug lp:1394212 */ + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(dbCaStatsTest) +{ + testPlan(5); + testCaStats(); + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp new file mode 100644 index 000000000..8a788bed6 --- /dev/null +++ b/modules/database/test/ioc/db/dbChArrTest.cpp @@ -0,0 +1,246 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2003 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to the Software License Agreement +* found in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Authors: Ralph Lange , + * Andrew Johnson + */ + +#include +#include +#include +#include +#include + +#include "registryFunction.h" +#include "epicsThread.h" +#include "epicsExit.h" +#include "epicsStdio.h" +#include "epicsString.h" +#include "envDefs.h" +#include "dbStaticLib.h" +#include "dbmf.h" +#include "registry.h" +#include "dbAddr.h" +#include "dbAccess.h" +#include "asDbLib.h" +#include "iocInit.h" +#include "iocsh.h" +#include "dbChannel.h" +#include "epicsUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" + +extern "C" { + int dbChArrTest_registerRecordDeviceDriver(struct dbBase *pdbbase); +} + +#define CA_SERVER_PORT "65535" + +const char *server_port = CA_SERVER_PORT; + +static void createAndOpen(const char *name, dbChannel**pch) +{ + testOk(!!(*pch = dbChannelCreate(name)), "dbChannel %s created", name); + testOk(!(dbChannelOpen(*pch)), "dbChannel opened"); + testOk((ellCount(&(*pch)->pre_chain) == 0), "no filters in pre chain"); + testOk((ellCount(&(*pch)->post_chain) == 0), "no filters in post chain"); +} + +extern "C" { +static void freeArray(db_field_log *pfl) { + if (pfl->type == dbfl_type_ref) { + free(pfl->u.r.field); + } +} +} + +static void testHead (const char *title, const char *typ = "") { + const char *line = "------------------------------------------------------------------------------"; + testDiag("%s", line); + testDiag(title, typ); + testDiag("%s", line); +} + +static void check(short dbr_type) { + dbChannel *pch; + db_field_log *pfl; + dbAddr valaddr; + dbAddr offaddr; + const char *offname = NULL, *valname = NULL, *typname = NULL; + epicsInt32 buf[26]; + long off, req; + int i; + + switch (dbr_type) { + case DBR_LONG: + offname = "i32.OFF"; + valname = "i32.VAL"; + typname = "long"; + break; + case DBR_DOUBLE: + offname = "f64.OFF"; + valname = "f64.VAL"; + typname = "double"; + break; + case DBR_STRING: + offname = "c40.OFF"; + valname = "c40.VAL"; + typname = "string"; + break; + default: + testDiag("Invalid data type %d", dbr_type); + } + + (void) dbNameToAddr(offname, &offaddr); + (void) dbNameToAddr(valname, &valaddr); + + testHead("Ten %s elements", typname); + + /* Fill the record's array field with data, 10..19 */ + + epicsInt32 ar[10] = {10,11,12,13,14,15,16,17,18,19}; + (void) dbPutField(&valaddr, DBR_LONG, ar, 10); + + /* Open a channel to it, make sure no filters present */ + + createAndOpen(valname, &pch); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + + /* TEST1 sets the record's OFF field, then requests 10 elements from the channel, + * passing in a transparent db_field_log and converting the data to LONG on the way in. + * It checks that it got back the expected data and the right number of elements. + */ + +#define TEST1(Size, Offset, Text, Expected) \ + testDiag("Reading from offset = %d (%s)", Offset, Text); \ + off = Offset; req = 10; \ + memset(buf, 0, sizeof(buf)); \ + (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ + pfl = db_create_read_log(pch); \ + testOk(pfl && pfl->type == dbfl_type_rec, "Valid pfl, type = rec"); \ + testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \ + testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \ + if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \ + for (i=0; ifinal_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + + const epicsInt32 res_5_0[] = {15,16,17,18,19}; + TEST1(5, 0, "no offset", res_5_0); + + const epicsInt32 res_5_3[] = {18,19,15,16,17}; + TEST1(5, 3, "wrapped", res_5_3); + + /* TEST2 sets the record's OFF field, then requests 15 elements from the channel + * but passes in a db_field_log with alternate data, converting that data to LONG. + * It checks that it got back the expected data and the right number of elements. + */ + +#define TEST2(Size, Offset, Text, Expected) \ + testDiag("Reading from offset = %d (%s)", Offset, Text); \ + off = Offset; req = 15; \ + memset(buf, 0, sizeof(buf)); \ + (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ + pfl = db_create_read_log(pch); \ + pfl->type = dbfl_type_ref; \ + pfl->field_type = DBF_CHAR; \ + pfl->field_size = 1; \ + pfl->no_elements = 26; \ + pfl->u.r.dtor = freeArray; \ + pfl->u.r.field = epicsStrDup("abcdefghijklmnopqrsstuvwxyz"); \ + testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \ + testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \ + if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \ + for (i=0; i + * Ralph Lange + */ + +#include "dbChannel.h" +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "registry.h" +#include "recSup.h" +#include "dbUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" +#include "errlog.h" + +/* Expected call bit definitions */ +#define e_start 0x00000001 +#define e_abort 0x00000002 +#define e_end 0x00000004 +#define e_null 0x00000008 +#define e_boolean 0x00000010 +#define e_integer 0x00000020 +#define e_double 0x00000040 +#define e_string 0x00000080 +#define e_start_map 0x00000100 +#define e_map_key 0x00000200 +#define e_end_map 0x00000400 +#define e_start_array 0x00000800 +#define e_end_array 0x00001000 +#define e_open 0x00002000 +#define e_reg_pre 0x00004000 +#define e_reg_post 0x00008000 +#define e_report 0x00010000 +#define e_close 0x00020000 + +#define r_any (e_start | e_abort | e_end | \ + e_null | e_boolean | e_integer | e_double | e_string | \ + e_start_map | e_map_key | e_end_map | e_start_array | e_end_array) +#define r_scalar (e_start | e_abort | e_end | \ + e_null | e_boolean | e_integer | e_double | e_string) + +unsigned int e, r; +#define p_ret(x) return r & x ? parse_continue : parse_stop + +parse_result p_start(chFilter *filter) +{ + testOk(e & e_start, "parse_start called"); + p_ret(e_start); +} + +void p_abort(chFilter *filter) +{ + testOk(e & e_abort, "parse_abort called"); +} + +parse_result p_end(chFilter *filter) +{ + testOk(e & e_end, "parse_end called"); + p_ret(e_end); +} + +parse_result p_null(chFilter *filter) +{ + testOk(e & e_null, "parse_null called"); + p_ret(e_null); +} +parse_result p_boolean(chFilter *filter, int boolVal) +{ + testOk(e & e_boolean, "parse_boolean called, val = %d", boolVal); + p_ret(e_boolean); +} +parse_result p_integer(chFilter *filter, long integerVal) +{ + testOk(e & e_integer, "parse_integer called, val = %ld", integerVal); + p_ret(e_integer); +} +parse_result p_double(chFilter *filter, double doubleVal) +{ + testOk(e & e_double, "parse_double called, val = %g", doubleVal); + p_ret(e_double); +} +parse_result p_string(chFilter *filter, const char *stringVal, size_t stringLen) +{ + testOk(e & e_string, "parse_string called, val = '%.*s'", (int) stringLen, + stringVal); + p_ret(e_string); +} + +parse_result p_start_map(chFilter *filter) +{ + testOk(e & e_start_map, "parse_start_map called"); + p_ret(e_start_map); +} +parse_result p_map_key(chFilter *filter, const char *key, size_t stringLen) +{ + testOk(e & e_map_key, "parse_map_key called, key = '%.*s'", (int) stringLen, key); + p_ret(e_map_key); +} +parse_result p_end_map(chFilter *filter) +{ + testOk(e & e_end_map, "parse_end_map called"); + p_ret(e_end_map); +} + +parse_result p_start_array(chFilter *filter) +{ + testOk(e & e_start_array, "parse_start_array called"); + p_ret(e_start_array); +} +parse_result p_end_array(chFilter *filter) +{ + testOk(e & e_end_array, "parse_end_array called"); + p_ret(e_end_array); +} + +long c_open(chFilter *filter) +{ + testOk(e & e_open, "channel_open called"); + return 0; +} +void c_reg_pre(chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + testOk(e & e_reg_pre, "channel_register_pre called"); +} +void c_reg_post(chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + testOk(e & e_reg_post, "channel_register_post called"); +} +void c_report(chFilter *filter, int level, const unsigned short indent) +{ + testOk(e & e_report, "channel_report called, level = %d", level); +} +void c_close(chFilter *filter) +{ + testOk(e & e_close, "channel_close called"); +} + +chFilterIf testIf = + { NULL, p_start, p_abort, p_end, p_null, p_boolean, p_integer, p_double, + p_string, p_start_map, p_map_key, p_end_map, p_start_array, p_end_array, + c_open, c_reg_pre, c_reg_post, c_report, c_close }; + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(testDbChannel) /* dbChannelTest is an API routine... */ +{ + dbChannel *pch; + + testPlan(76); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + r = e = 0; + /* dbChannelTest() checks record and field names */ + testOk1(!dbChannelTest("x.NAME")); + testOk1(!dbChannelTest("x.INP")); + testOk1(!dbChannelTest("x.VAL")); + testOk1(!dbChannelTest("x.")); + testOk1(!dbChannelTest("x")); + testOk(dbChannelTest("y"), "Test, nonexistent record"); + testOk(dbChannelTest("x.NOFIELD"), "Test, nonexistent field"); + + /* dbChannelTest() allows but ignores field modifiers */ + testOk1(!dbChannelTest("x.NAME$")); + testOk1(!dbChannelTest("x.{}")); + testOk1(!dbChannelTest("x.VAL{\"json\":true}")); + + /* dbChannelCreate() accepts field modifiers */ + testOk1(!!(pch = dbChannelCreate("x.{}"))); + if (pch) dbChannelDelete(pch); + testOk1(!!(pch = dbChannelCreate("x.VAL{}"))); + testOk1(pch && dbChannelElements(pch) == 1); + if (pch) dbChannelDelete(pch); + testOk1(!!(pch = dbChannelCreate("x.NAME$"))); + testOk1(pch && dbChannelFieldType(pch) == DBF_CHAR); + testOk1(pch && dbChannelExportType(pch) == DBR_CHAR); + testOk1(pch && dbChannelElements(pch) == PVNAME_STRINGSZ); + if (pch) dbChannelDelete(pch); + testOk1(!!(pch = dbChannelCreate("x.INP$"))); + testOk1(pch && dbChannelFieldType(pch) == DBF_INLINK); + testOk1(pch && dbChannelExportType(pch) == DBR_CHAR); + testOk1(pch && dbChannelElements(pch) > PVNAME_STRINGSZ); + if (pch) dbChannelDelete(pch); + testOk1(!!(pch = dbChannelCreate("x.NAME${}"))); + testOk1(pch && dbChannelFieldType(pch) == DBF_CHAR); + testOk1(pch && dbChannelExportType(pch) == DBR_CHAR); + testOk1(pch && dbChannelElements(pch) == PVNAME_STRINGSZ); + if (pch) dbChannelDelete(pch); + + /* dbChannelCreate() rejects bad PVs */ + testOk(!dbChannelCreate("y"), "Create, bad record"); + testOk(!dbChannelCreate("x.NOFIELD"), "Create, bad field"); + testOk(!dbChannelCreate("x.{not-json}"), "Create, bad JSON"); + eltc(0); + testOk(!dbChannelCreate("x.{\"none\":null}"), "Create, bad filter"); + eltc(1); + + dbRegisterFilter("any", &testIf, NULL); + + /* Parser event rejection by filter */ + e = e_start; + testOk1(!dbChannelCreate("x.{\"any\":null}")); + + r = e_start; + e = e_start | e_null | e_abort; + testOk1(!dbChannelCreate("x.{\"any\":null}")); + + r = e_start | e_null; + e = e_start | e_null | e_end; + testOk1(!dbChannelCreate("x.{\"any\":null}")); + + /* Successful parsing... */ + r = r_any; + e = e_start | e_null | e_end; + testOk1(!!(pch = dbChannelCreate("x.{\"any\":null}"))); + e = e_close; + if (pch) dbChannelDelete(pch); + + dbRegisterFilter("scalar", &testIf, NULL); + + e = e_start | e_null | e_end; + testOk1(!!(pch = dbChannelCreate("x.{\"scalar\":null}"))); + + e = e_report; + dbChannelShow(pch, 2, 2); + + e = e_close; + if (pch) dbChannelDelete(pch); + + e = e_start | e_start_array | e_boolean | e_integer | e_end_array + | e_end; + testOk1(!!(pch = dbChannelCreate("x.{\"any\":[true,1]}"))); + e = e_close; + if (pch) dbChannelDelete(pch); + + e = e_start | e_start_map | e_map_key | e_double | e_string | e_end_map + | e_end; + testOk1(!!(pch = dbChannelCreate("x.{\"any\":{\"a\":2.7183,\"b\":\"c\"}}"))); + e = e_close; + if (pch) dbChannelDelete(pch); + + /* More event rejection */ + r = r_scalar; + e = e_start | e_start_array | e_abort; + testOk1(!dbChannelCreate("x.{\"scalar\":[null]}")); + + e = e_start | e_start_map | e_abort; + testOk1(!dbChannelCreate("x.{\"scalar\":{}}")); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbLinkdset.c b/modules/database/test/ioc/db/dbLinkdset.c new file mode 100644 index 000000000..d7b8230f0 --- /dev/null +++ b/modules/database/test/ioc/db/dbLinkdset.c @@ -0,0 +1,41 @@ + +#include + +#include + +#include + +static +long link_test_extend(struct dbCommon *junk) +{ return 0; } + +static dsxt xrecextend = {&link_test_extend, &link_test_extend}; + +static +long link_test_init(int pass) +{ + if (pass == 0) + devExtend(&xrecextend); + return 0; +} + +static +long link_test_noop(void *junk) +{ return 0; } + + + +#define DEFDSET(LTYPE) \ + static dset devxLTest ## LTYPE = {4, NULL, &link_test_init, &link_test_noop, &link_test_noop}; \ + epicsExportAddress(dset, devxLTest ## LTYPE); + +DEFDSET(JSON_LINK) +DEFDSET(VME_IO) +DEFDSET(CAMAC_IO) +DEFDSET(AB_IO) +DEFDSET(GPIB_IO) +DEFDSET(BITBUS_IO) +DEFDSET(INST_IO) +DEFDSET(BBGPIB_IO) +DEFDSET(RF_IO) +DEFDSET(VXI_IO) diff --git a/modules/database/test/ioc/db/dbLinkdset.dbd b/modules/database/test/ioc/db/dbLinkdset.dbd new file mode 100644 index 000000000..da2d4d946 --- /dev/null +++ b/modules/database/test/ioc/db/dbLinkdset.dbd @@ -0,0 +1,10 @@ +device(x, JSON_LINK, devxLTestJSON_LINK, "Unit Test JSON_LINK") +device(x, VME_IO, devxLTestVME_IO, "Unit Test VME_IO") +device(x, CAMAC_IO, devxLTestCAMAC_IO, "Unit Test CAMAC_IO") +device(x, AB_IO, devxLTestAB_IO, "Unit Test AB_IO") +device(x, GPIB_IO, devxLTestGPIB_IO, "Unit Test GPIB_IO") +device(x, BITBUS_IO, devxLTestBITBUS_IO, "Unit Test BITBUS_IO") +device(x, INST_IO, devxLTestINST_IO, "Unit Test INST_IO") +device(x, BBGPIB_IO, devxLTestBBGPIB_IO, "Unit Test BBGPIB_IO") +device(x, RF_IO, devxLTestRF_IO, "Unit Test RF_IO") +device(x, VXI_IO, devxLTestVXI_IO, "Unit Test VXI_IO") diff --git a/modules/database/test/ioc/db/dbLockTest.c b/modules/database/test/ioc/db/dbLockTest.c new file mode 100644 index 000000000..39e21a650 --- /dev/null +++ b/modules/database/test/ioc/db/dbLockTest.c @@ -0,0 +1,431 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + */ + +#include + +#include "epicsSpin.h" +#include "epicsMutex.h" +#include "dbCommon.h" +#include "epicsThread.h" + +#include "dbLockPvt.h" +#include "dbStaticLib.h" + +#include "dbUnitTest.h" +#include "testMain.h" + +#include "dbAccess.h" +#include "errlog.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static +void compareSets(int match, const char *A, const char *B) +{ + int actual; + dbCommon *rA, *rB; + + rA = testdbRecordPtr(A); + rB = testdbRecordPtr(B); + + actual = rA->lset->plockSet==rB->lset->plockSet; + + testOk(match==actual, "dbLockGetLockId(\"%s\")%c=dbLockGetLockId(\"%s\")", + A, match?'=':'!', B); +} + +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); + +static +void testSets(void) { + DBENTRY entry; + long status; + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Check that all records have initialized lockRecord and lockSet"); + + dbInitEntry(pdbbase, &entry); + for(status = dbFirstRecordType(&entry); + !status; + status = dbNextRecordType(&entry)) { + for(status = dbFirstRecord(&entry); + !status; + status = dbNextRecord(&entry)) { + dbCommon *prec = entry.precnode->precord; + testOk(prec->lset!=NULL, "%s.LSET != NULL", prec->name); + if(prec->lset!=NULL) + testOk(prec->lset->plockSet!=NULL, "%s.LSET.plockSet != NULL", prec->name); + else + testSkip(1, "lockRecord missing"); + } + } + dbFinishEntry(&entry); + + testDiag("Check initial creation of DB links"); + + /* reca is by itself */ + compareSets(0, "reca", "recb"); + compareSets(0, "reca", "recc"); + compareSets(0, "reca", "recd"); + compareSets(0, "reca", "rece"); + compareSets(0, "reca", "recf"); + + /* recb and recc should be in a lockset */ + compareSets(1, "recb", "recc"); + compareSets(0, "recb", "recd"); + compareSets(0, "recb", "rece"); + compareSets(0, "recb", "recf"); + + compareSets(0, "recc", "recd"); + compareSets(0, "recc", "rece"); + compareSets(0, "recc", "recf"); + + /* recd, e, and f should be in a lockset */ + compareSets(1, "recd", "rece"); + compareSets(1, "recd", "recf"); + + compareSets(1, "rece", "recf"); + + testOk1(testdbRecordPtr("reca")->lset->plockSet->refcount==1); + testOk1(testdbRecordPtr("recb")->lset->plockSet->refcount==2); + testOk1(testdbRecordPtr("recd")->lset->plockSet->refcount==3); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testSingleLock(void) +{ + dbCommon *prec; + testDiag("testing dbScanLock()/dbScanUnlock()"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec = testdbRecordPtr("reca"); + testOk1(prec->lset->plockSet->refcount==1); + dbScanLock(prec); + /* scan lock does not keep a reference to the lockSet */ + testOk1(prec->lset->plockSet->refcount==1); + dbScanUnlock(prec); + + dbScanLock(prec); + dbScanLock(prec); + /* scan lock can be recursive */ + testOk1(prec->lset->plockSet->refcount==1); + dbScanUnlock(prec); + dbScanUnlock(prec); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testMultiLock(void) +{ + dbCommon *prec[8]; + dbLocker *plockA; +#ifdef LOCKSET_DEBUG + epicsThreadId myself = epicsThreadGetIdSelf(); +#endif + + testDiag("Test multi-locker function (lock everything)"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec[0] = testdbRecordPtr("reca"); + prec[1] = testdbRecordPtr("recb"); + prec[2] = testdbRecordPtr("recc"); + prec[3] = NULL; + prec[4] = testdbRecordPtr("recd"); + prec[5] = testdbRecordPtr("rece"); + prec[6] = testdbRecordPtr("recf"); + prec[7] = testdbRecordPtr("recg"); + + testDiag("Test init refcounts"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); + + plockA = dbLockerAlloc(prec, 8, 0); + if(!plockA) + testAbort("dbLockerAlloc() failed"); + + + testDiag("After locker created"); + /* locker takes 7 references, one for each lockRecord. */ + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); +#endif + + dbScanLockMany(plockA); + + testDiag("After locker locked"); + /* locker takes 4 references, one for each lockSet. */ + testIntOk1(ellCount(&plockA->locked),==,4); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,5); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,7); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,3); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,myself); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,myself); +#endif + + /* recursive locking of individual records is allowed */ + dbScanLock(prec[0]); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,3); + dbScanUnlock(prec[0]); + + /* recursive locking with dbScanLockMany() isn't + * dbScanLockMany(plockA); <-- would fail + */ + + dbScanUnlockMany(plockA); + + testDiag("After locker unlocked"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,4); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,6); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,2); +#ifdef LOCKSET_DEBUG + testPtrOk1(testdbRecordPtr("reca")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recb")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recd")->lset->plockSet->owner,==,NULL); + testPtrOk1(testdbRecordPtr("recg")->lset->plockSet->owner,==,NULL); +#endif + + dbLockerFree(plockA); + + testDiag("After locker free'd"); + testIntOk1(testdbRecordPtr("reca")->lset->plockSet->refcount,==,1); + testIntOk1(testdbRecordPtr("recb")->lset->plockSet->refcount,==,2); + testIntOk1(testdbRecordPtr("recd")->lset->plockSet->refcount,==,3); + testIntOk1(testdbRecordPtr("recg")->lset->plockSet->refcount,==,1); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkBreak(void) +{ + dbCommon *precB, *precC; + testDiag("Test break link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + + /* break the link between B and C */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, ""); + + testOk1(precB->lset->plockSet!=precC->lset->plockSet); + testIntOk1(precB->lset->plockSet->refcount, ==, 1); + testIntOk1(precC->lset->plockSet->refcount, ==, 1); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkMake(void) +{ + dbCommon *precA, *precG; + lockSet *lA, *lG; + testDiag("Test make link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precA = testdbRecordPtr("reca"); + lA = dbLockGetRef(precA->lset); + precG = testdbRecordPtr("recg"); + lG = dbLockGetRef(precG->lset); + + testPtrOk1(precA->lset->plockSet, !=, precG->lset->plockSet); + testIntOk1(precA->lset->plockSet->refcount, ==, 2); + testIntOk1(precG->lset->plockSet->refcount, ==, 2); + + /* make a link between A and G */ + testdbPutFieldOk("reca.SDIS", DBR_STRING, "recg"); + + testPtrOk1(precA->lset->plockSet, ==, precG->lset->plockSet); + testIntOk1(precA->lset->plockSet->refcount, ==, 3); + + if(precA->lset->plockSet==lG) { + testIntOk1(lA->refcount, ==, 1); + testIntOk1(lG->refcount, ==, 3); + } else { + testIntOk1(lA->refcount, ==, 3); + testIntOk1(lG->refcount, ==, 1); + } + dbLockDecRef(lG); + dbLockDecRef(lA); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkChange(void) +{ + dbCommon *precB, *precC, *precG; + lockSet *lB, *lG; + testDiag("Test re-target link"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + precG = testdbRecordPtr("recg"); + + lB = dbLockGetRef(precB->lset); + lG = dbLockGetRef(precG->lset); + + testPtrOk1(lB,==,precC->lset->plockSet); + testPtrOk1(lB,!=,lG); + testIntOk1(lB->refcount,==,3); + testIntOk1(lG->refcount,==,2); + + /* break the link between B and C and replace it + * with a link between B and G + */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recg"); + + testPtrOk1(precB->lset->plockSet,==,lB); + testPtrOk1(precG->lset->plockSet,==,lB); + testPtrOk1(precC->lset->plockSet,!=,lB); + testPtrOk1(precC->lset->plockSet,!=,lG); + + testIntOk1(lB->refcount,==,3); + testIntOk1(lG->refcount,==,1); + testIntOk1(precC->lset->plockSet->refcount,==,1); + + dbLockDecRef(lB); + dbLockDecRef(lG); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkNOP(void) +{ + dbCommon *precB, *precC; + testDiag("Test re-target link to the same destination"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + precB = testdbRecordPtr("recb"); + precC = testdbRecordPtr("recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + + /* renew link between B and C */ + testdbPutFieldOk("recb.SDIS", DBR_STRING, "recc"); + + testOk1(precB->lset->plockSet==precC->lset->plockSet); + testOk1(precB->lset->plockSet->refcount==2); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(dbLockTest) +{ +#ifdef LOCKSET_DEBUG + testPlan(100); +#else + testPlan(88); +#endif + testSets(); + testSingleLock(); + testMultiLock(); + testLinkBreak(); + testLinkMake(); + testLinkChange(); + testLinkNOP(); + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbLockTest.db b/modules/database/test/ioc/db/dbLockTest.db new file mode 100644 index 000000000..37ff6022e --- /dev/null +++ b/modules/database/test/ioc/db/dbLockTest.db @@ -0,0 +1,24 @@ +record(x, "reca") { +} + +record(x, "recb") { + field(SDIS, "recc") +} + +record(x, "recc") { + field(SDIS, "recc") +} + +record(x, "recd") { + field(SDIS, "rece") +} + +record(x, "rece") { + field(SDIS, "recf") +} + +record(x, "recf") { +} + +record(x, "recg") { +} diff --git a/modules/database/test/ioc/db/dbPutGetTest.c b/modules/database/test/ioc/db/dbPutGetTest.c new file mode 100644 index 000000000..61b31a785 --- /dev/null +++ b/modules/database/test/ioc/db/dbPutGetTest.c @@ -0,0 +1,150 @@ + +#include + +#include +#include +#include +#include +#include +#include + +static +void testdbGetStringEqual(const char *pv, const char *expected) +{ + const char *actual; + int ok; + DBENTRY ent; + + dbInitEntry(pdbbase, &ent); + + if(dbFindRecord(&ent, pv)) { + testFail("Failed to find record '%s'", pv); + return; + } + + actual = dbGetString(&ent); + ok = (!actual && !expected) + || (actual && expected && strcmp(actual, expected)==0); + + testOk(ok, "dbGetString(\"%s\") -> \"%s\" == \"%s\"", pv, actual, expected); + + dbFinishEntry(&ent); +} + +static +void testGetString(void) +{ + testDiag("testGetString()"); + + testdbGetStringEqual("recempty.DTYP", "Soft Channel"); + testdbGetStringEqual("recempty.DESC", ""); + testdbGetStringEqual("recempty.PHAS", "0"); + testdbGetStringEqual("recempty.TSE" , "0"); + testdbGetStringEqual("recempty.DISA", "0"); + testdbGetStringEqual("recempty.DISV", "0"); + + testdbGetStringEqual("recoverwrite.DTYP", "Soft Channel"); + testdbGetStringEqual("recoverwrite.DESC", ""); + testdbGetStringEqual("recoverwrite.PHAS", "0"); + testdbGetStringEqual("recoverwrite.TSE" , "0"); + testdbGetStringEqual("recoverwrite.DISA", "0"); + testdbGetStringEqual("recoverwrite.DISV", "0"); +} + +static +void testStringMax(void) +{ + testDiag("testStringMax()"); + + testdbGetStringEqual("recmax.DISA", "-1"); +} + +static +void testLongLink(void) +{ + testDiag("testLonkLink()"); + + testdbGetFieldEqual("lnktest.INP", DBR_STRING, "lnktarget NPP NMS"); + testdbGetFieldEqual("lnktest.INP$", DBR_STRING, "lnktarget NPP NMS"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.INP$", DBR_CHAR, '\0'); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 19, 18, "lnktarget NPP NMS"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 2, 2, "l"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 3, 3, "ln"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 17, 17, "lnktarget NPP NM"); + testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 18, 18, "lnktarget NPP NMS"); + + testdbGetArrFieldEqual("lnktest.INP", DBR_STRING, 2, 1, "lnktarget NPP NMS"); +} + +static +void testLongAttr(void) +{ + testDiag("testLongAttr()"); + + testdbGetFieldEqual("lnktest.RTYP", DBR_STRING, "x"); + testdbGetFieldEqual("lnktest.RTYP$", DBR_STRING, "x"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.RTYP$", DBR_CHAR, '\0'); + + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 4, 2, "x"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 2, 2, "x"); +} + +static +void testLongField(void) +{ + testDiag("testLongField()"); + + testdbGetFieldEqual("lnktest.NAME", DBR_STRING, "lnktest"); + testdbGetFieldEqual("lnktest.NAME$", DBR_STRING, "108"); + testDiag("dbGet() w/ nRequest==1 gets only trailing nil"); + testdbGetFieldEqual("lnktest.NAME$", DBR_CHAR, '\0'); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 10, 8, "lnktest"); + + testDiag("get w/ truncation"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 0, 0, NULL); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 1, 1, ""); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 2, 2, "l"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 3, 3, "ln"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 7, 7, "lnktes"); + testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 8, 8, "lnktest"); +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(dbPutGet) +{ + testPlan(41); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbPutGetTest.db", NULL, NULL); + + testGetString(); + + testStringMax(); + + eltc(0); + testIocInitOk(); + eltc(1); + + testLongLink(); + testLongAttr(); + testLongField(); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbPutGetTest.db b/modules/database/test/ioc/db/dbPutGetTest.db new file mode 100644 index 000000000..793750e6e --- /dev/null +++ b/modules/database/test/ioc/db/dbPutGetTest.db @@ -0,0 +1,41 @@ + +record(x, "recempty") { +# empty string is the same "0" for numeric fields + field(DTYP, "") + field(DESC, "") + field(PHAS, "") + field(TSE , "") + field(DISA, "") + field(DISV, "") +} + +record(x, "recoverwrite") { +# first with non-default values + field(DTYP, "Scan I/O") + field(DESC, "hello") + field(PHAS, "2") + field(TSE , "5") + field(DISA, "6") + field(DISV, "7") +} + +record(x, "recoverwrite") { +# now restore default values + field(DTYP, "") + field(DESC, "") + field(PHAS, "") + field(TSE , "") + field(TSEL, "") + field(DISA, "") + field(DISV, "") +} + +record(x, "recmax") { + field(DISA, "0xffffffff") +} + +record(x, "lnktarget") {} + +record(x, "lnktest") { + field(INP, "lnktarget NPP NMS") +} diff --git a/modules/database/test/ioc/db/dbPutLinkTest.c b/modules/database/test/ioc/db/dbPutLinkTest.c new file mode 100644 index 000000000..d6748a9eb --- /dev/null +++ b/modules/database/test/ioc/db/dbPutLinkTest.c @@ -0,0 +1,698 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + */ + +#include "string.h" + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "dbDefs.h" +#include "link.h" +#include "dbAccess.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "osiFileName.h" +#include "dbmf.h" +#include "errlog.h" +#include + +#include "xRecord.h" +#include "jlinkz.h" + +#include "testMain.h" + +static +int testStrcmp(int expect, const char *A, const char *B) { + static const char op[] = "<=>"; + int ret = strcmp(A,B); + testOk(ret==expect, "\"%s\" %c= \"%s\"", + A, op[expect+1], B); + return ret==expect; +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +#define TEST_CONSTANT(SET, EXPECT) {SET, {CONSTANT, EXPECT}} +#define TEST_PV_LINK(SET, PV, MOD) {SET, {PV_LINK, PV, MOD}} + +static const struct testParseDataT { + const char * const str; + dbLinkInfo info; +} testParseData[] = { + TEST_CONSTANT("", ""), + TEST_CONSTANT("0.1", "0.1"), + TEST_CONSTANT(" 0.2\t ", "0.2"), + + TEST_PV_LINK("0.1a", "0.1a", 0), + TEST_PV_LINK(" hello ", "hello", 0), + TEST_PV_LINK(" hellox MSI", "hellox", pvlOptMSI), + TEST_PV_LINK(" world MSICP", "world", pvlOptMSI|pvlOptCP), + + {"#C14 S145 @testing", {VME_IO, "testing", 0, "CS", {14, 145}}}, + {"#B11 C12 N13 A14 F15 @cparam", {CAMAC_IO, "cparam", 0, "BCNAF", {11, 12, 13, 14, 15}}}, + {" #B111 C112 N113 @cparam", {CAMAC_IO, "cparam", 0, "BCN", {111, 112, 113}}}, + {" @hello world ", {INST_IO, "hello world", 0, "", /*{}*/}}, + {" {\"x\":true} ", {JSON_LINK, "{\"x\":true}", 0, "", /*{}*/}}, + {NULL} +}; + +static void testLinkParse(void) +{ + const struct testParseDataT *td = testParseData; + dbLinkInfo info; + testDiag("\n# Checking link parsing\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + for (;td->str; td++) { + int i, N; + testDiag("Parsing \"%s\"", td->str); + testOk(dbParseLink(td->str, DBF_INLINK, &info, 0) == 0, "Parser returned OK"); + if (!testOk(info.ltype == td->info.ltype, "Link type value")) + testDiag("Expected %d, got %d", td->info.ltype, info.ltype); + if (td->info.target) + testStrcmp(0, info.target, td->info.target); + if (info.ltype == td->info.ltype) { + switch (info.ltype) { + case PV_LINK: + if (!testOk(info.modifiers == td->info.modifiers, + "PV Link modifier flags")) + testDiag("Expected %d, got %d", td->info.modifiers, + info.modifiers); + break; + case VME_IO: + case CAMAC_IO: + testStrcmp(0, info.hwid, td->info.hwid); + N = strlen(td->info.hwid); + for (i=0; iinfo.hwnums[i], "%d == %d", + info.hwnums[i], td->info.hwnums[i]); + } + } + dbFreeLinkInfo(&info); + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +static const char *testParseFailData[] = { + "#", + "#S", + "#ABC", + "#A0 B", + "#A0 B @", + "#A0 B C @", + "#R1 M2 D3 E4 @oops", /* RF_IO has no parm */ + "#C1 S2", /* VME_IO needs parm */ + NULL +}; + +static void testLinkFailParse(void) +{ + const char * const *td = testParseFailData; + dbLinkInfo info; + testDiag("\n# Check parsing of invalid inputs\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + for(;*td; td++) { + testOk(dbParseLink(*td, DBF_INLINK, &info, 0) == S_dbLib_badField, + "dbParseLink correctly rejected \"%s\"", *td); + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +static const struct testDataT { + const char * const linkstring; + short linkType; + unsigned int pvlMask; + const char * const linkback; +} testSetData[] = { + {"", CONSTANT, 0}, + {"0", CONSTANT, 0}, + {"42", CONSTANT, 0}, + {"0x1", CONSTANT, 0}, + + {"x1", DB_LINK, 0, "x1 NPP NMS"}, + {"x1.VAL", DB_LINK, 0, "x1.VAL NPP NMS"}, + {"x1.TIME", DB_LINK, 0, "x1.TIME NPP NMS"}, + {"x1 PP", DB_LINK, pvlOptPP, "x1 PP NMS"}, + {"x1 PP MSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, + {"x1 PPMSS", DB_LINK, pvlOptPP|pvlOptMSS, "x1 PP MSS"}, + {"x1 PPMSI", DB_LINK, pvlOptPP|pvlOptMSI, "x1 PP MSI"}, + + {"qq", CA_LINK, pvlOptInpNative, "qq NPP NMS"}, + {"qq MSI", CA_LINK, pvlOptInpNative|pvlOptMSI, "qq NPP MSI"}, + {"qq MSICA", CA_LINK, pvlOptInpNative|pvlOptCA|pvlOptMSI, "qq CA MSI"}, + + {"x1 CA", CA_LINK, pvlOptInpNative|pvlOptCA, "x1 CA NMS"}, + {NULL} +}; + +static void testCADBSet(void) +{ + const struct testDataT *td = testSetData; + xRecord *prec; + DBLINK *plink; + testDiag("\n# Checking DB/CA link retargeting\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec = (xRecord*)testdbRecordPtr("x1"); + plink = &prec->lnk; + + for (;td->linkstring;td++) { + testDiag("Trying field value \"%s\"", td->linkstring); + + testdbPutFieldOk("x1.LNK", DBF_STRING, td->linkstring); + if (td->linkback) + testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkback); + else + testdbGetFieldEqual("x1.LNK", DBF_STRING, td->linkstring); + if (!testOk(plink->type == td->linkType, "Link type")) + testDiag("Expected %d, got %d", td->linkType, plink->type); + + if (plink->type == td->linkType) { + switch (td->linkType) { + case CONSTANT: + if (plink->value.constantStr) + testOk1(strcmp(plink->value.constantStr, td->linkstring) == 0); + else if (td->linkstring[0]=='\0') + testPass("Empty String"); + else + testFail("oops"); + break; + + case DB_LINK: + case CA_LINK: + testOk(plink->value.pv_link.pvlMask == td->pvlMask, + "pvlMask %x == %x", plink->value.pv_link.pvlMask, td->pvlMask); + break; + } + } + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +typedef struct { + const char * const recname; + short ltype; + const char * const wval; + short vals[5]; + const char * const parm; +} testHWDataT; + +static const testHWDataT testHWData[] = { + {"rJSON_LINK", JSON_LINK, "{\"x\":true}", {0}, "{\"x\":true}"}, + {"rVME_IO", VME_IO, "#C100 S101 @parm VME_IO", {100, 101}, "parm VME_IO"}, + {"rCAMAC_IO", CAMAC_IO, "#B11 C12 N13 A14 F15 @parm CAMAC_IO", {11, 12, 13, 14, 15}, "parm CAMAC_IO"}, + {"rAB_IO", AB_IO, "#L21 A22 C23 S24 @parm AB_IO", {21, 22, 23, 24}, "parm AB_IO"}, + {"rGPIB_IO", GPIB_IO, "#L31 A32 @parm GPIB_IO", {31, 32}, "parm GPIB_IO"}, + {"rBITBUS_IO", BITBUS_IO, "#L41 N42 P43 S44 @parm BITBUS_IO", {41, 42, 43, 44}, "parm BITBUS_IO"}, + {"rINST_IO", INST_IO, "@parm INST_IO", {0}, "parm INST_IO"}, + {"rBBGPIB_IO", BBGPIB_IO, "#L51 B52 G53 @parm BBGPIB_IO", {51, 52, 53}, "parm BBGPIB_IO"}, + {"rRF_IO", RF_IO, "#R61 M62 D63 E64", {61, 62, 63, 64}, NULL}, + {"rVXI_IO1", VXI_IO, "#V71 C72 S73 @parm1 VXI_IO", {71, 72, 73}, "parm1 VXI_IO"}, + {"rVXI_IO2", VXI_IO, "#V81 S82 @parm2 VXI_IO", {81, 82}, "parm2 VXI_IO"}, + {NULL} +}; + +static void testLink(DBLINK *plink, const testHWDataT *td) +{ + switch(td->ltype) { + case JSON_LINK: + testOk1(strcmp(plink->value.json.string, td->parm) == 0); + break; + case VME_IO: + testOk1(plink->value.vmeio.card == td->vals[0]); + testOk1(plink->value.vmeio.signal == td->vals[1]); + testOk1(strcmp(plink->value.vmeio.parm, td->parm) == 0); + break; + case CAMAC_IO: + testOk1(plink->value.camacio.b == td->vals[0]); + testOk1(plink->value.camacio.c == td->vals[1]); + testOk1(plink->value.camacio.n == td->vals[2]); + testOk1(plink->value.camacio.a == td->vals[3]); + testOk1(plink->value.camacio.f == td->vals[4]); + testOk1(strcmp(plink->value.camacio.parm, td->parm) == 0); + break; + case AB_IO: + testOk1(plink->value.abio.link == td->vals[0]); + testOk1(plink->value.abio.adapter == td->vals[1]); + testOk1(plink->value.abio.card == td->vals[2]); + testOk1(plink->value.abio.signal == td->vals[3]); + testOk1(strcmp(plink->value.abio.parm, td->parm) == 0); + break; + case GPIB_IO: + testOk1(plink->value.gpibio.link == td->vals[0]); + testOk1(plink->value.gpibio.addr == td->vals[1]); + testOk1(strcmp(plink->value.gpibio.parm, td->parm) == 0); + break; + case BITBUS_IO: + testOk1(plink->value.bitbusio.link == td->vals[0]); + testOk1(plink->value.bitbusio.node == td->vals[1]); + testOk1(plink->value.bitbusio.port == td->vals[2]); + testOk1(plink->value.bitbusio.signal == td->vals[3]); + testOk1(strcmp(plink->value.bitbusio.parm, td->parm) == 0); + break; + case INST_IO: + testOk1(strcmp(plink->value.instio.string, td->parm) == 0); + break; + case BBGPIB_IO: + testOk1(plink->value.bbgpibio.link == td->vals[0]); + testOk1(plink->value.bbgpibio.bbaddr == td->vals[1]); + testOk1(plink->value.bbgpibio.gpibaddr == td->vals[2]); + testOk1(strcmp(plink->value.bbgpibio.parm, td->parm) == 0); + break; + case RF_IO: + testOk1(plink->value.rfio.cryo == td->vals[0]); + testOk1(plink->value.rfio.micro == td->vals[1]); + testOk1(plink->value.rfio.dataset == td->vals[2]); + testOk1(plink->value.rfio.element == td->vals[3]); + break; + case VXI_IO: + if(plink->value.vxiio.flag == VXIDYNAMIC) { + testOk1(plink->value.vxiio.frame == td->vals[0]); + testOk1(plink->value.vxiio.slot == td->vals[1]); + testOk1(plink->value.vxiio.signal == td->vals[2]); + } else { + testOk1(plink->value.vxiio.la == td->vals[0]); + testOk1(plink->value.vxiio.signal == td->vals[1]); + } + testOk1(strcmp(plink->value.vxiio.parm, td->parm) == 0); + break; + } +} + +static void testHWInitSet(void) +{ + const testHWDataT *td = testHWData; + testDiag("\n# Checking HW link parsing during initialization\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + for (;td->recname; td++) { + char buf[MAX_STRING_SIZE]; + xRecord *prec; + DBLINK *plink; + + testDiag("%s == \"%s\"", td->recname, td->wval); + + prec = (xRecord *) testdbRecordPtr(td->recname); + plink = &prec->inp; + + strcpy(buf, td->recname); + strcat(buf, ".INP"); + + testdbGetFieldEqual(buf, DBR_STRING, td->wval); + + if (!testOk(plink->type == td->ltype, "Link type")) { + testDiag("Expected %d, got %d", + td->ltype, plink->type); + } + else { + testLink(plink, td); + } + + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +static const testHWDataT testHWData2[] = { + {"rJSON_LINK", JSON_LINK, "{\"x\":true}", {0}, "{\"x\":true}"}, + {"rVME_IO", VME_IO, "#C200 S201 @another VME_IO", {200, 201}, "another VME_IO"}, + {"rCAMAC_IO", CAMAC_IO, "#B111 C112 N113 A114 F115 @CAMAC_IO", {111, 112, 113, 114, 115}, "CAMAC_IO"}, + {"rAB_IO", AB_IO, "#L121 A122 C123 S124 @another AB_IO", {121, 122, 123, 124}, "another AB_IO"}, + {"rGPIB_IO", GPIB_IO, "#L131 A132 @another GPIB_IO", {131, 132}, "another GPIB_IO"}, + {"rBITBUS_IO", BITBUS_IO, "#L141 N142 P143 S144 @BITBUS_IO", {141, 142, 143, 144}, "BITBUS_IO"}, + {"rINST_IO", INST_IO, "@another INST_IO", {0}, "another INST_IO"}, + {"rBBGPIB_IO", BBGPIB_IO, "#L151 B152 G153 @another BBGPIB_IO", {151, 152, 153}, "another BBGPIB_IO"}, + {"rRF_IO", RF_IO, "#R161 M162 D163 E164", {161, 162, 163, 164}, NULL}, + {"rVXI_IO1", VXI_IO, "#V171 C172 S173 @another1 VXI_IO", {171, 172, 173}, "another1 VXI_IO"}, + {"rVXI_IO2", VXI_IO, "#V181 S182 @another2 VXI_IO", {181, 182}, "another2 VXI_IO"}, + {NULL} +}; + +static void testHWMod(void) +{ + const testHWDataT *td = testHWData2; + + testDiag("\n# Checking HW link parsing during retarget\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + for(;td->recname;td++) { + char buf[MAX_STRING_SIZE]; + xRecord *prec; + DBLINK *plink; + testDiag("%s -> \"%s\"", td->recname, td->wval); + + prec = (xRecord*)testdbRecordPtr(td->recname); + plink = &prec->inp; + + strcpy(buf, td->recname); + strcat(buf, ".INP"); + + testdbPutFieldOk(buf, DBR_STRING, td->wval); + + testdbGetFieldEqual(buf, DBR_STRING, td->wval); + + if (!testOk(plink->type == td->ltype, "Link type")) { + testDiag("Expected %d, got %d", + td->ltype, plink->type); + } + else { + testLink(plink, td); + } + + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkInitFail(void) +{ + xRecord *prec; + DBLINK *plink; + testDiag("\n# Checking link parse failures at iocInit\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + /* this load will fail */ + eltc(0); + testOk(dbReadDatabase(&pdbbase, "dbBadLink.db", "." OSI_PATH_LIST_SEPARATOR + ".." OSI_PATH_LIST_SEPARATOR "../O.Common" OSI_PATH_LIST_SEPARATOR + "O.Common", NULL) != 0, "dbReadDatabase returned error (expected)"); + + testIocInitOk(); + eltc(1); + + testdbGetFieldEqual("eVME_IO1.INP", DBR_STRING, "#C0 S0 @"); + + prec = (xRecord *) testdbRecordPtr("eVME_IO1"); + plink = &prec->inp; + testOk1(plink->type == VME_IO); + testOk1(plink->value.vmeio.parm != NULL); + + testdbGetFieldEqual("eVME_IO2.INP", DBR_STRING, "#C0 S0 @"); + + prec = (xRecord *) testdbRecordPtr("eVME_IO2"); + plink = &prec->inp; + testOk1(plink->type == VME_IO); + testOk1(plink->value.vmeio.parm != NULL); + + testdbGetFieldEqual("eINST_IO.INP", DBR_STRING, "@"); + + prec = (xRecord *) testdbRecordPtr("eINST_IO"); + plink = &prec->inp; + testOk1(plink->type == INST_IO); + testOk1(plink->value.instio.string != NULL); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testLinkFail(void) +{ + testDiag("\n# Checking runtime link parse failures\n#"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + /* INST_IO doesn't accept empty string */ + testdbPutFieldFail(S_dbLib_badField, "rINST_IO.INP", DBR_STRING, ""); + + /* INST_IO doesn't accept string without @ */ + testdbPutFieldFail(S_dbLib_badField, "rINST_IO.INP", DBR_STRING, "abc"); + + /* JSON_LINK dies when expected */ + testdbPutFieldOk("rJSON_LINK.INP", DBR_STRING, "{\"x\":true}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":false}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":null}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":1}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":1.1}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":\"x\"}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":[]}"); + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":{}}"); + + /* JSON_LINK syntax errors */ + testdbPutFieldFail(S_dbLib_badField, "rJSON_LINK.INP", DBR_STRING, "{\"x\":bbbb}"); + + /* syntax errors */ + testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "#S201 C200 @another VME_IO"); + testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "C200 #S201"); + + /* VME_IO doesn't accept empty string */ + testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, ""); + + testdbPutFieldOk("rVME_IO.INP", DBR_STRING, "#C1 S2 @hello"); + + /* VME_IO fails invalid input */ + testdbPutFieldFail(S_dbLib_badField, "rVME_IO.INP", DBR_STRING, "abc"); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static +void testNumZ(int expect) +{ + int numz = epicsAtomicGetIntT(&numzalloc); + testOk(numz==expect, "numzalloc==%d (%d)", expect, numz); +} + +static +void testJLink(void) +{ + testDiag("Test json link setup/retarget"); + + testNumZ(0); + + testDiag("Link parsing failures"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + testdbReadDatabase("dbPutLinkTestJ.db", NULL, NULL); + + testNumZ(0); + + eltc(0); + testIocInitOk(); + eltc(1); + + testNumZ(6); + + testdbPutFieldOk("j1.PROC", DBF_LONG, 1); + testdbPutFieldOk("j2.PROC", DBF_LONG, 1); + testdbPutFieldOk("j3.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("j1.INP", DBF_STRING, "{\"z\":{\"good\":1}}"); + testdbGetFieldEqual("j1.VAL", DBF_LONG, 1); + testdbGetFieldEqual("j2.VAL", DBF_LONG, 2); + testdbGetFieldEqual("j3.VAL", DBF_LONG, 3); + + testNumZ(6); + + testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}"); + testdbPutFieldOk("j1.PROC", DBF_LONG, 1); + testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); + + testdbPutFieldOk("j2.TSEL", DBF_STRING, "{\"z\":{\"good\":0}}"); + testdbPutFieldOk("j2.PROC", DBF_LONG, 1); + + testNumZ(7); + + testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"fail\":5}}"); + testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"good\":6}"); + testdbPutFieldOk("j1.PROC", DBF_LONG, 1); + testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); + /* put failures in parsing stage don't modify link */ + testdbGetFieldEqual("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}"); + + testNumZ(7); + + /* Check SDIS using a JSON link prevents processing */ + testdbPutFieldOk("j1.SDIS", DBF_STRING, "{\"z\":{\"good\":1}}"); + testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":1}}"); + testdbPutFieldOk("j1.PROC", DBF_LONG, 1); + testdbGetFieldEqual("j1.VAL", DBF_LONG, 4); + + testIocShutdownOk(); + + testNumZ(0); + + testdbCleanup(); +} + +static +void testTSEL(void) +{ + dbCommon *rec[2]; + dbLocker *locker; + + testDiag("Test TSEL link to .TIME"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbPutLinkTest.db", NULL, NULL); + + rec[0] = testdbRecordPtr("time:one"); + rec[1] = testdbRecordPtr("time:two"); + + eltc(0); + testIocInitOk(); + eltc(1); + + locker = dbLockerAlloc(rec, NELEMENTS(rec), 0); + if(!locker) + testAbort("dbLockerAlloc() fails"); + + testdbPutFieldOk("time:one.PROC", DBF_LONG, 1); + + /* wait a bit so that we would get different timestamps */ + epicsThreadSleep(0.001); + + testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); + +#define COMPARE(MSG, C, TS1, TS2) testOk((C)^((TS1)->secPastEpoch == (TS2)->secPastEpoch && (TS1)->nsec == (TS2)->nsec), \ + MSG " %u:%u == %u:%u", (unsigned)(TS1)->secPastEpoch, (unsigned)(TS1)->nsec, \ + (unsigned)(TS2)->secPastEpoch, (unsigned)(TS2)->nsec) + + testDiag("Check initially connected TSEL link"); + dbScanLockMany(locker); + COMPARE("first", 0, &rec[0]->time, &rec[1]->time); + testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME); + dbScanUnlockMany(locker); + + testdbPutFieldOk("time:two.TSEL", DBF_STRING, ""); + + testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); + + testDiag("Check no TSEL link"); + dbScanLockMany(locker); + COMPARE("second", 1, &rec[0]->time, &rec[1]->time); + testOk1(!(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME)); + dbScanUnlockMany(locker); + + testdbPutFieldOk("time:two.TSEL", DBF_STRING, "time:one.TIME"); + + testdbPutFieldOk("time:two.PROC", DBF_LONG, 1); + + testDiag("Check re-connected TSEL link"); + dbScanLockMany(locker); + COMPARE("third", 0, &rec[0]->time, &rec[1]->time); + testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME); + dbScanUnlockMany(locker); + + dbLockerFree(locker); + + testIocShutdownOk(); + + testdbCleanup(); +#undef COMPARE +} + +MAIN(dbPutLinkTest) +{ + testPlan(320); + testLinkParse(); + testLinkFailParse(); + testCADBSet(); + testHWInitSet(); + testHWMod(); + testLinkInitFail(); + testLinkFail(); + testJLink(); + testTSEL(); + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbPutLinkTest.db b/modules/database/test/ioc/db/dbPutLinkTest.db new file mode 100644 index 000000000..ee657c5e4 --- /dev/null +++ b/modules/database/test/ioc/db/dbPutLinkTest.db @@ -0,0 +1,54 @@ +record(x, "x1") {} +record(x, "x2") {} +record(x, "x3") {} +record(x, "x4") {} + +record(x, "rJSON_LINK") { + field(DTYP, "Unit Test JSON_LINK") + field(INP, {x:true}) +} +record(x, "rVME_IO") { + field(DTYP, "Unit Test VME_IO") + field(INP, "#C100 S101 @parm VME_IO") +} +record(x, "rCAMAC_IO") { + field(DTYP, "Unit Test CAMAC_IO") + field(INP, "#B11 C12 N13 A14 F15 @parm CAMAC_IO") +} +record(x, "rAB_IO") { + field(DTYP, "Unit Test AB_IO") + field(INP, "#L21 A22 C23 S24 @parm AB_IO") +} +record(x, "rGPIB_IO") { + field(DTYP, "Unit Test GPIB_IO") + field(INP, "#L31 A32 @parm GPIB_IO") +} +record(x, "rBITBUS_IO") { + field(DTYP, "Unit Test BITBUS_IO") + field(INP, "#L41 N42 P43 S44 @parm BITBUS_IO") +} +record(x, "rINST_IO") { + field(DTYP, "Unit Test INST_IO") + field(INP, "@parm INST_IO") +} +record(x, "rBBGPIB_IO") { + field(DTYP, "Unit Test BBGPIB_IO") + field(INP, "#L51 B52 G53 @parm BBGPIB_IO") +} +record(x, "rRF_IO") { + field(DTYP, "Unit Test RF_IO") + field(INP, "#R61 M62 D63 E64") +} +record(x, "rVXI_IO1") { + field(DTYP, "Unit Test VXI_IO") + field(INP, "#V71 C72 S73 @parm1 VXI_IO") +} +record(x, "rVXI_IO2") { + field(DTYP, "Unit Test VXI_IO") + field(INP, "#V81 S82 @parm2 VXI_IO") +} + +record(x, "time:one") {} +record(x, "time:two") { + field(TSEL, "time:one.TIME") +} diff --git a/modules/database/test/ioc/db/dbPutLinkTestJ.db b/modules/database/test/ioc/db/dbPutLinkTestJ.db new file mode 100644 index 000000000..621557c2c --- /dev/null +++ b/modules/database/test/ioc/db/dbPutLinkTestJ.db @@ -0,0 +1,16 @@ + +record(x, "j1") { + field(INP, {z:{good:1}}) + field(TSEL, {z:{good:0}}) + field(SDIS, {z:{good:0}}) + field(FLNK, {z:{good:0}}) +} + +record(x, "j2") { + field(INP, {z:{good:2}}) + field(TSEL, "j1.TIME") +} + +record(x, "j3") { + field(INP, {z:{good:3}}) +} diff --git a/modules/database/test/ioc/db/dbScanTest.c b/modules/database/test/ioc/db/dbScanTest.c new file mode 100644 index 000000000..905827e0c --- /dev/null +++ b/modules/database/test/ioc/db/dbScanTest.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + */ + +#include + +#include "dbScan.h" +#include "epicsEvent.h" + +#include "dbUnitTest.h" +#include "testMain.h" + +#include "dbAccess.h" +#include "errlog.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static epicsEventId waiter; +static int called; +static dbCommon *prec; + +static void onceComp(void *junk, dbCommon *prec) +{ + testOk1(junk==(void*)&waiter); + testOk1(strcmp(prec->name, "reca")==0); + called = 1; + epicsEventMustTrigger(waiter); +} + +static void testOnce(void) +{ + testDiag("check scanOnceCallback() callback"); + waiter = epicsEventMustCreate(epicsEventEmpty); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbLockTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + prec = testdbRecordPtr("reca"); + + testDiag("scanOnce %s", prec->name); + scanOnceCallback(prec, onceComp, &waiter); + testDiag("Waiting"); + epicsEventMustWait(waiter); + testOk1(called==1); + if(!called) + testSkip(2, "callback failed to run"); + + testIocShutdownOk(); + + testdbCleanup(); + epicsEventDestroy(waiter); +} + +MAIN(dbScanTest) +{ + testPlan(3); + testOnce(); + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbServerTest.c b/modules/database/test/ioc/db/dbServerTest.c new file mode 100644 index 000000000..f0d5bf6ac --- /dev/null +++ b/modules/database/test/ioc/db/dbServerTest.c @@ -0,0 +1,190 @@ +/*************************************************************************\ +* Copyright (c) 2017 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Andrew Johnson + */ + +#include + +#include "dbServer.h" + +#include +#include +#include + +enum { + NOTHING_CALLED, + REPORT_CALLED, + CLIENT_CALLED_UNKNOWN, + CLIENT_CALLED_KNOWN, + STATS_CALLED, + INIT_CALLED, + RUN_CALLED, + PAUSE_CALLED, + STOP_CALLED +} oneState; + +char *oneSim; + +void oneReport(unsigned level) +{ + oneState = REPORT_CALLED; +} + +void oneStats(unsigned *channels, unsigned *clients) +{ + oneState = STATS_CALLED; +} + +int oneClient(char *pbuf, size_t len) +{ + if (oneSim) { + strncpy(pbuf, oneSim, len); + oneState = CLIENT_CALLED_KNOWN; + oneSim = NULL; + return 0; + } + oneState = CLIENT_CALLED_UNKNOWN; + return -1; +} + +void oneInit(void) +{ + oneState = INIT_CALLED; +} + +void oneRun(void) +{ + oneState = RUN_CALLED; +} + +void onePause(void) +{ + oneState = PAUSE_CALLED; +} + +void oneStop(void) +{ + oneState = STOP_CALLED; +} + +dbServer one = { + ELLNODE_INIT, "one", + oneReport, oneStats, oneClient, + oneInit, oneRun, onePause, oneStop +}; + + +/* Server layer for testing NULL methods */ + +dbServer no_routines = { + ELLNODE_INIT, "no-routines", + NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + + +/* Server layer which should be disabled */ + +int disInitialized = 0; + +void disInit(void) +{ + disInitialized = 1; +} + +dbServer disabled = { + ELLNODE_INIT, "disabled", + NULL, NULL, NULL, + disInit, NULL, NULL, NULL +}; + +dbServer illegal = { + ELLNODE_INIT, "bad name", + NULL, NULL, NULL, + disInit, NULL, NULL, NULL +}; + +dbServer toolate = { + ELLNODE_INIT, "toolate", + NULL, NULL, NULL, + disInit, NULL, NULL, NULL +}; + + +MAIN(dbServerTest) +{ + char name[16]; + char *theName = "The One"; + int status; + + testPlan(24); + + /* Prove that we handle substring names properly */ + epicsEnvSet("EPICS_IOC_IGNORE_SERVERS", "none ones"); + + testDiag("Registering dbServer 'one'"); + testOk(dbRegisterServer(&one) == 0, "Registered 'one'"); + testOk1(oneState == NOTHING_CALLED); + + testOk(dbRegisterServer(&one) != 0, "Duplicate registration rejected"); + testOk(dbRegisterServer(&illegal) != 0, "Illegal registration rejected"); + + testDiag("Registering dbServer 'no-routines'"); + testOk(dbRegisterServer(&no_routines) == 0, "Registered 'no-routines'"); + testOk(dbUnregisterServer(&no_routines) == 0, "'no-routines' unreg'd"); + testOk(dbRegisterServer(&no_routines) == 0, "Re-registered 'no-routines'"); + + epicsEnvSet("EPICS_IOC_IGNORE_SERVERS", "disabled nonexistent"); + testDiag("Registering dbServer 'disabled'"); + testOk(dbRegisterServer(&disabled) == 0, "Registration accepted"); + + testDiag("Changing server state"); + dbInitServers(); + testOk(oneState == INIT_CALLED, "dbInitServers"); + testOk(disInitialized == 0, "Disabled server not initialized"); + testOk(dbRegisterServer(&toolate) != 0, "No registration while active"); + + dbRunServers(); + testOk(oneState == RUN_CALLED, "dbRunServers"); + testOk(dbUnregisterServer(&one) != 0, "No unregistration while active"); + + testDiag("Checking server methods called"); + dbsr(0); + testOk(oneState == REPORT_CALLED, "dbsr called report()"); + + oneSim = NULL; + name[0] = 0; + status = dbServerClient(name, sizeof(name)); + testOk(oneState == CLIENT_CALLED_UNKNOWN, "Client unknown"); + testOk(status == -1 && name[0] == 0, + "dbServerClient mismatch"); + + oneSim = theName; + name[0] = 0; + status = dbServerClient(name, sizeof(name)); + testOk(oneState == CLIENT_CALLED_KNOWN, "Client known"); + testOk(status == 0 && strcmp(name, theName) == 0, + "dbServerClient match"); + + dbPauseServers(); + testOk(oneState == PAUSE_CALLED, "dbPauseServers"); + + dbsr(0); + testOk(oneState != REPORT_CALLED, "No call to report() when paused"); + + status = dbServerClient(name, sizeof(name)); + testOk(oneState != CLIENT_CALLED_KNOWN, "No call to client() when paused"); + + dbStopServers(); + testOk(oneState == STOP_CALLED, "dbStopServers"); + testOk(dbUnregisterServer(&toolate) != 0, "No unreg' if not reg'ed"); + testOk(dbUnregisterServer(&no_routines) != 0, "No unreg' of 'no-routines'"); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbShutdownTest.c b/modules/database/test/ioc/db/dbShutdownTest.c new file mode 100644 index 000000000..55a8f3e0c --- /dev/null +++ b/modules/database/test/ioc/db/dbShutdownTest.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (c) 2013 Brookhaven National Laboratory. +* Copyright (c) 2013 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * Ralph Lange + */ + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "dbAccess.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "osiFileName.h" +#include "dbmf.h" +#include "errlog.h" + +#include "testMain.h" + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static struct threadItem { + char *name; + char found; +} commonThreads[] = { + { "errlog", 0 }, + { "taskwd", 0 }, + { "timerQueue", 0 }, + { "cbLow", 0 }, + { "scanOnce", 0 }, + { NULL, 0 } +}; + +static +void findCommonThread (epicsThreadId id) { + struct threadItem *thr; + char name[32]; + + epicsThreadGetName(id, name, 32); + + for (thr = commonThreads; thr->name; thr++) { + if (epicsStrCaseCmp(thr->name, name) == 0) { + thr->found = 1; + } + } +} + +static +void checkCommonThreads (void) { + struct threadItem *thr; + + for (thr = commonThreads; thr->name; thr++) { + testOk(thr->found, "Thread %s is running", thr->name); + thr->found = 0; + } +} + +static +void cycle(void) { + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsThreadMap(findCommonThread); + checkCommonThreads(); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(dbShutdownTest) +{ + testPlan(10); + + cycle(); + cycle(); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbStateTest.c b/modules/database/test/ioc/db/dbStateTest.c new file mode 100644 index 000000000..ae7b6512c --- /dev/null +++ b/modules/database/test/ioc/db/dbStateTest.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "dbState.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +MAIN(dbStateTest) +{ + dbStateId red, red2, blue, blue2; + int i; + + testPlan(20); + + testOk(!dbStateFind("y"), "Finding nonexisting state fails"); + + testOk(!!(red = dbStateCreate("red")), "Create state 'red'"); + testOk((red2 = dbStateFind("red")) == red, "Find 'red' returns correct id"); + testOk((red2 = dbStateCreate("red")) == red, "Create existing 'red' returns correct id"); + testOk(!dbStateFind("y"), "Finding nonexisting state still fails"); + + testOk(!!(blue = dbStateCreate("blue")), "Create state 'blue'"); + testOk((blue2 = dbStateFind("blue")) == blue, "Find 'blue' returns correct id"); + testOk((blue2 = dbStateCreate("blue")) == blue, "Create existing 'blue' returns correct id"); + testOk(!dbStateFind("y"), "Finding nonexisting state still fails"); + + testOk((i = dbStateGet(red)) == 0, "Default 'red' state is 0"); + testOk((i = dbStateGet(blue)) == 0, "Default 'blue' state is 0"); + dbStateSet(red); + testOk((i = dbStateGet(red)) == 1, "After setting, 'red' state is 1"); + testOk((i = dbStateGet(blue)) == 0, "'blue' state is 0"); + dbStateSet(blue); + testOk((i = dbStateGet(blue)) == 1, "After setting, 'blue' state is 1"); + testOk((i = dbStateGet(red)) == 1, "'red' state is 1"); + dbStateClear(blue); + testOk((i = dbStateGet(blue)) == 0, "After clearing, 'blue' state is 0"); + testOk((i = dbStateGet(red)) == 1, "'red' state is 1"); + dbStateClear(red); + testOk((i = dbStateGet(red)) == 0, "After clearing, 'red' state is 0"); + testOk((i = dbStateGet(blue)) == 0, "'red' state is 0"); + + testOk(!dbStateFind("y"), "Finding nonexisting state still fails"); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbStaticTest.c b/modules/database/test/ioc/db/dbStaticTest.c new file mode 100644 index 000000000..b956ce449 --- /dev/null +++ b/modules/database/test/ioc/db/dbStaticTest.c @@ -0,0 +1,235 @@ +#include + +#include +#include +#include +#include +#include +#include + +static void testEntry(const char *pv) +{ + DBENTRY entry; + + testDiag("testEntry(\"%s\")", pv); + + dbInitEntry(pdbbase, &entry); + + testOk1(dbFindRecord(&entry, pv)==0); + + testDiag("precordType=%p precnode=%p", entry.precordType, entry.precnode); + testOk(entry.precordType && + strcmp(entry.precordType->name, "x")==0, + "Record type is '%s' ('x')", entry.precordType->name); + testOk(entry.precnode && + strcmp(((dbCommon*)entry.precnode->precord)->name, "testrec")==0, + "Record name is '%s' ('testrec')", ((dbCommon*)entry.precnode->precord)->name); + testOk(entry.pflddes && + strcmp(entry.pflddes->name, "VAL")==0, + "Field name is '%s' ('VAL')", entry.pflddes->name); + + /* all tested PVs are either a record with aliases, or an alias */ + testOk(entry.precnode && + (!(entry.precnode->flags & DBRN_FLAGS_ISALIAS) ^ + !(entry.precnode->flags & DBRN_FLAGS_HASALIAS)), + "Recnode flags %d", entry.precnode->flags); + + testOk1(dbFollowAlias(&entry)==0); + + testOk(dbFindInfo(&entry, "A")==0 && + strcmp(dbGetInfoString(&entry), "B")==0, + "Info item is set"); + + dbFinishEntry(&entry); +} + +static void testAddr2Entry(const char *pv) +{ + DBENTRY entry, entry2; + dbAddr addr; + + testDiag("testAddr2Entry(\"%s\")", pv); + + memset(&entry, 0, sizeof(entry)); + memset(&entry2, 0, sizeof(entry2)); + + dbInitEntry(pdbbase, &entry); + + if(dbFindRecord(&entry, pv)!=0) + testAbort("no entry for %s", pv); + + if(dbFollowAlias(&entry)) + testAbort("Can't follow alias"); + + if(dbNameToAddr(pv, &addr)) + testAbort("no addr for %s", pv); + + dbInitEntryFromAddr(&addr, &entry2); + + testOk1(entry.pdbbase==entry2.pdbbase); + testOk1(entry.precordType==entry2.precordType); + testOk1(entry.pflddes==entry2.pflddes); + testOk1(entry.precnode==entry2.precnode); + testOk1(entry.pfield==entry2.pfield); + testOk1(entry.indfield==entry2.indfield); + testOk1(!entry2.pinfonode); + testOk1(!entry2.message); + + testOk(memcmp(&entry, &entry2, sizeof(entry))==0, "dbEntries identical"); + + dbFinishEntry(&entry); + dbFinishEntry(&entry2); +} + +static void testRec2Entry(const char *recname) +{ + DBENTRY entry, entry2; + dbCommon *prec; + + testDiag("testRec2Entry(\"%s\")", recname); + + memset(&entry, 0, sizeof(entry)); + memset(&entry2, 0, sizeof(entry2)); + + prec = testdbRecordPtr(recname); + + dbInitEntry(pdbbase, &entry); + + if(dbFindRecord(&entry, recname)!=0) + testAbort("no entry for %s", recname); + + if(dbFollowAlias(&entry)) + testAbort("Can't follow alias"); + + dbInitEntryFromRecord(prec, &entry2); + + testOk1(entry.pdbbase==entry2.pdbbase); + testOk1(entry.precordType==entry2.precordType); + testOk1(entry.pflddes==entry2.pflddes); + testOk1(entry.precnode==entry2.precnode); + testOk1(entry.pfield==entry2.pfield); + testOk1(entry.indfield==entry2.indfield); + testOk1(!entry2.pinfonode); + testOk1(!entry2.message); + + testOk(memcmp(&entry, &entry2, sizeof(entry))==0, "dbEntries identical"); + + dbFinishEntry(&entry); + dbFinishEntry(&entry2); +} + +static void verify(DBENTRY *pentry, const char *put, const char *exp) +{ + const char *msg; + int result; + + msg = dbVerify(pentry, put); + result = (!msg && !exp) || (msg && exp && strcmp(msg, exp) == 0); + + if (!testOk(result, "dbVerify('%s.%s', '%s') => '%s'", + (char *) pentry->precnode->precord, pentry->pflddes->name, + put, msg ? msg : "OK")) + testDiag("Expected => '%s'", exp ? exp : "OK"); +} + +static void testDbVerify(const char *record) +{ + DBENTRY entry; + + testDiag("# # # # # # # testDbVerify('%s') # # # # # # # #", record); + + dbInitEntry(pdbbase, &entry); + if (dbFindRecord(&entry, record) != 0) + testAbort("Can't find record '%s'", record); + + dbFindField(&entry, "UDF"); + verify(&entry, "0", NULL); + verify(&entry, "255", NULL); + verify(&entry, "256", "Number too large for field type"); + verify(&entry, "0x100", "Number too large for field type"); + + dbFindField(&entry, "PHAS"); + verify(&entry, "0", NULL); + verify(&entry, "-32768", NULL); + verify(&entry, "-32769", "Number too large for field type"); + verify(&entry, "0x7fff", NULL); + verify(&entry, "32768", "Number too large for field type"); + + dbFindField(&entry, "VAL"); + verify(&entry, "0", NULL); + verify(&entry, "-123456789", NULL); + verify(&entry, "123456789", NULL); + verify(&entry, "0x1234FEDC", NULL); + verify(&entry, "0x100000000", "Number too large for field type"); + verify(&entry, "1.2345", "Extraneous characters after number"); + + dbFindField(&entry, "DESC"); + verify(&entry, "", NULL); + verify(&entry, "abcdefghijklmnopqrstuvwxyz", NULL); + verify(&entry, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", + "String too long, max 40 characters"); + + dbFindField(&entry, "DTYP"); + verify(&entry, "Soft Channel", NULL); + verify(&entry, "zzzz", "Not a valid device type"); + + dbFindField(&entry, "SCAN"); + verify(&entry, "1 second", NULL); + verify(&entry, "zzzz", "Not a valid menu choice"); + + dbFindField(&entry, "FLNK"); + verify(&entry, "Anything works here!", NULL); + + dbFinishEntry(&entry); +} + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(dbStaticTest) +{ + testPlan(223); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbStaticTest.db", NULL, NULL); + + testEntry("testrec.VAL"); + testEntry("testalias.VAL"); + testEntry("testalias2.VAL"); + testEntry("testalias3.VAL"); + testAddr2Entry("testrec.VAL"); + testAddr2Entry("testalias.VAL"); + testAddr2Entry("testalias2.VAL"); + testAddr2Entry("testalias3.VAL"); + testRec2Entry("testrec"); + testRec2Entry("testalias"); + testRec2Entry("testalias2"); + testRec2Entry("testalias3"); + + eltc(0); + testIocInitOk(); + eltc(1); + + testEntry("testrec.VAL"); + testEntry("testalias.VAL"); + testEntry("testalias2.VAL"); + testEntry("testalias3.VAL"); + testAddr2Entry("testrec.VAL"); + testAddr2Entry("testalias.VAL"); + testAddr2Entry("testalias2.VAL"); + testAddr2Entry("testalias3.VAL"); + testRec2Entry("testrec"); + testRec2Entry("testalias"); + testRec2Entry("testalias2"); + testRec2Entry("testalias3"); + + testDbVerify("testrec"); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} + diff --git a/modules/database/test/ioc/db/dbStaticTest.db b/modules/database/test/ioc/db/dbStaticTest.db new file mode 100644 index 000000000..c35ff9a91 --- /dev/null +++ b/modules/database/test/ioc/db/dbStaticTest.db @@ -0,0 +1,8 @@ + +record(x, "testrec") { + info("A", "B") + alias("testalias") +} + +alias("testrec", "testalias2") +alias("testalias2", "testalias3") diff --git a/modules/database/test/ioc/db/dbStressLock.c b/modules/database/test/ioc/db/dbStressLock.c new file mode 100644 index 000000000..aed0116e0 --- /dev/null +++ b/modules/database/test/ioc/db/dbStressLock.c @@ -0,0 +1,353 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Lockset stress test. + * + * The test stratagy is for N threads to contend for M records. + * Each thread will perform one of three operations: + * 1) Lock a single record. + * 2) Lock several records. + * 3) Retarget the TSEL link of a record + * + * Author: Michael Davidsaver + */ + +#include +#include +#include + +#include "envDefs.h" +#include "epicsEvent.h" +#include "epicsStdlib.h" +#include "epicsSpin.h" +#include "epicsThread.h" +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "epicsMath.h" +#include "dbCommon.h" +#include "epicsTime.h" + +#include "dbLockPvt.h" +#include "dbStaticLib.h" + +#include "dbUnitTest.h" +#include "testMain.h" + +#include "dbAccess.h" +#include "errlog.h" + +#include "xRecord.h" + +#define testIntOk1(A, OP, B) testOk((A) OP (B), "%s (%d) %s %s (%d)", #A, A, #OP, #B, B); +#define testPtrOk1(A, OP, B) testOk((A) OP (B), "%s (%p) %s %s (%p)", #A, A, #OP, #B, B); + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +/* number of seconds for the test to run */ +static double runningtime = 18.0; + +/* number of worker threads */ +static unsigned int nworkers = 5; + +static unsigned int nrecords; + +#define MAXLOCK 20 + +static dbCommon **precords; + +typedef struct { + int id; + unsigned long N[3]; + double X[3]; + double X2[3]; + double min[3], max[3]; + + unsigned int done; + epicsEventId donevent; + + dbCommon *prec[MAXLOCK]; +} workerPriv; + +/* hopefully a uniform random number in [0.0, 1.0] */ +static +double getRand(void) +{ + return rand()/(double)RAND_MAX; +} + +static +void doSingle(workerPriv *p) +{ + size_t recn = (size_t)(getRand()*(nrecords-1)); + dbCommon *prec = precords[recn]; + xRecord *px = (xRecord*)prec; + + dbScanLock(prec); + px->val++; + dbScanUnlock(prec); +} + +static +void doMulti(workerPriv *p) +{ + int sum = 0; + size_t i; + size_t nlock = 2 + (size_t)(getRand()*(MAXLOCK-3)); + size_t nrec = (size_t)(getRand()*(nrecords-1)); + dbLocker *locker; + + assert(nlock>=2); + assert(nlockprec[i] = precords[nrec]; + } + + locker = dbLockerAlloc(p->prec, nlock, 0); + if(!locker) + testAbort("locker allocation fails"); + + dbScanLockMany(locker); + for(i=0; iprec[i]; + sum += px->val; + } + dbScanUnlockMany(locker); + + dbLockerFree(locker); +} + +static +void doreTarget(workerPriv *p) +{ + char scratchsrc[60]; + char scratchdst[MAX_STRING_SIZE]; + long ret; + DBADDR dbaddr; + double action = getRand(); + size_t nsrc = (size_t)(getRand()*(nrecords-1)); + size_t ntarg = (size_t)(getRand()*(nrecords-1)); + xRecord *psrc = (xRecord*)precords[nsrc]; + xRecord *ptarg = (xRecord*)precords[ntarg]; + + strcpy(scratchsrc, psrc->name); + strcat(scratchsrc, ".TSEL"); + + ret = dbNameToAddr(scratchsrc, &dbaddr); + if(ret) + testAbort("bad record name? %ld", ret); + + if(action<=0.6) { + scratchdst[0] = '\0'; + } else { + strcpy(scratchdst, ptarg->name); + } + + ret = dbPutField(&dbaddr, DBR_STRING, ptarg->name, 1); + if(ret) + testAbort("put fails with %ld", ret); +} + +static +void worker(void *raw) +{ + unsigned init = 1; + workerPriv *priv = raw; + + testDiag("worker %d is %p", priv->id, epicsThreadGetIdSelf()); + + while(!priv->done) { + epicsUInt64 after, before; + double sel = getRand(); + double duration; + int act; + + before = epicsMonotonicGet(); + + if(sel<0.33) { + doSingle(priv); + act = 0; + } else if(sel<0.66) { + doMulti(priv); + act = 1; + } else { + doreTarget(priv); + act = 2; + } + + after = epicsMonotonicGet(); + duration = (after-before)*1e-9; + + priv->N[act]++; + priv->X[act] += duration; + priv->X2[act] += duration*duration; + if(durationmin[act] || init) { + priv->min[act] = duration; + init = 0; + } + if(duration>priv->max[act]) + priv->max[act] = duration; + } + + epicsEventMustTrigger(priv->donevent); +} + +MAIN(dbStressTest) +{ + DBENTRY ent; + long status; + unsigned int i; + workerPriv *priv; + char *nwork=getenv("NWORK"); + epicsTimeStamp seed; + + epicsTimeGetCurrent(&seed); + + srand(seed.nsec); + + if(nwork) { + long val = 0; + epicsParseLong(nwork, &val, 0, NULL); + if(val>2) + nworkers = val; + } + + testPlan(80+nworkers*3); + + priv = callocMustSucceed(nworkers, sizeof(*priv), "no memory"); + + testDiag("lock set stress test"); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + dbTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("dbStressLock.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + /* collect an array of all records */ + dbInitEntry(pdbbase, &ent); + for(status = dbFirstRecordType(&ent); + !status; + status = dbNextRecordType(&ent)) + { + for(status = dbFirstRecord(&ent); + !status; + status = dbNextRecord(&ent)) + { + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + nrecords++; + } + + } + if(nrecords<2) + testAbort("where are the records!"); + precords = callocMustSucceed(nrecords, sizeof(*precords), "no mem"); + for(status = dbFirstRecordType(&ent), i = 0; + !status; + status = dbNextRecordType(&ent)) + { + for(status = dbFirstRecord(&ent); + !status; + status = dbNextRecord(&ent)) + { + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + precords[i++] = ent.precnode->precord; + } + + } + dbFinishEntry(&ent); + + testDiag("Running with %u workers and %u records", + nworkers, nrecords); + + for(i=0; iprecord; + lockSet *ls; + if(ent.precnode->flags&DBRN_FLAGS_ISALIAS) + continue; + ls = prec->lset->plockSet; + testOk(ellCount(&ls->lockRecordList)==ls->refcount, "%s only lockRecords hold refs. %d == %d", + prec->name,ellCount(&ls->lockRecordList),ls->refcount); + testOk1(ls->ownerlocker==NULL); + } + + } + dbFinishEntry(&ent); + + testDiag("Statistics"); + for(i=0; i0); + testOk1(priv[i].N[1]>0); + testOk1(priv[i].N[2]>0); + } + + testIocShutdownOk(); + + testdbCleanup(); + + free(priv); + free(precords); + + return testDone(); +} diff --git a/modules/database/test/ioc/db/dbStressLock.db b/modules/database/test/ioc/db/dbStressLock.db new file mode 100644 index 000000000..5aecf860a --- /dev/null +++ b/modules/database/test/ioc/db/dbStressLock.db @@ -0,0 +1,40 @@ +record(x, "rec01") {} +record(x, "rec02") {} +record(x, "rec03") {} +record(x, "rec04") {} +record(x, "rec05") {} +record(x, "rec06") {} +record(x, "rec07") {} +record(x, "rec08") {} +record(x, "rec09") {} +record(x, "rec10") {} +record(x, "rec11") {} +record(x, "rec12") {} +record(x, "rec13") {} +record(x, "rec14") {} +record(x, "rec15") {} +record(x, "rec16") {} +record(x, "rec17") {} +record(x, "rec18") {} +record(x, "rec19") {} +record(x, "rec20") {} +record(x, "rec21") {} +record(x, "rec22") {} +record(x, "rec23") {} +record(x, "rec24") {} +record(x, "rec25") {} +record(x, "rec26") {} +record(x, "rec27") {} +record(x, "rec28") {} +record(x, "rec29") {} +record(x, "rec30") {} +record(x, "rec31") {} +record(x, "rec32") {} +record(x, "rec33") {} +record(x, "rec34") {} +record(x, "rec35") {} +record(x, "rec36") {} +record(x, "rec37") {} +record(x, "rec38") {} +record(x, "rec39") {} +record(x, "rec40") {} diff --git a/modules/database/test/ioc/db/devx.c b/modules/database/test/ioc/db/devx.c new file mode 100644 index 000000000..c5527f14e --- /dev/null +++ b/modules/database/test/ioc/db/devx.c @@ -0,0 +1,159 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven +* National Lab +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "devx.h" + +/* xRecord DTYP="Scan I/O" + * + * dset to test I/O Intr scanning. + * INP="@drvname" names a "driver" which + * provides a IOSCANPVT. + * The driver also defines a callback function which + * is invoked when the record is processed. + */ + +struct ELLLIST xdrivers; + +/* Add a new "driver" with the given group id + * and processing callback + */ +xdrv* xdrv_add(int group, xdrvcb cb, void *arg) +{ + xdrv *drv=callocMustSucceed(1, sizeof(*drv), "xdrv_add"); + drv->cb = cb; + drv->arg = arg; + drv->group = group; + scanIoInit(&drv->scan); + ellAdd(&xdrivers, &drv->drvnode); + return drv; +} + +/* Trigger the named "driver" group to scan */ +xdrv *xdrv_get(int group) +{ + ELLNODE *cur; + for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) { + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + if(curd->group==group) { + return curd; + } + } + cantProceed("xdrv_get() for non-existant group"); + return NULL; +} + +/* Free all "driver" groups and record private structures. + * Call after testdbCleanup() + */ +void xdrv_reset() +{ + ELLNODE *cur; + while((cur=ellGet(&xdrivers))!=NULL) { + ELLNODE *cur2; + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + while((cur2=ellGet(&curd->privlist))!=NULL) { + xpriv *priv = CONTAINER(cur2, xpriv, privnode); + free(priv); + } + free(curd); + } +} + +static long xscanio_init_record(xRecord *prec) +{ + ELLNODE *cur; + xpriv *priv; + xdrv *drv = NULL; + int group, member; + assert(prec->inp.type==INST_IO); + + if(sscanf(prec->inp.value.instio.string, "%d %d", + &group, &member)!=2) + cantProceed("xscanio_init_record invalid INP string"); + + for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) { + xdrv *curd = CONTAINER(cur, xdrv, drvnode); + if(curd->group==group) { + drv = curd; + break; + } + } + + assert(drv!=NULL); + priv = mallocMustSucceed(sizeof(*priv), "xscanio_init_record"); + priv->prec = prec; + priv->drv = drv; + priv->member = member; + ellAdd(&drv->privlist, &priv->privnode); + prec->dpvt = priv; + + return 0; +} + +static long xscanio_get_ioint_info(int cmd, xRecord *prec, IOSCANPVT *ppvt) +{ + xpriv *priv = prec->dpvt; + if(!priv || !priv->drv) + return 0; + *ppvt = priv->drv->scan; + return 0; +} + +static long xscanio_read(xRecord *prec) +{ + xpriv *priv = prec->dpvt; + if(!priv || !priv->drv) + return 0; + if(priv->drv->cb) + (*priv->drv->cb)(priv, priv->drv->arg); + return 0; +} + +static xdset devxScanIO = { + 5, NULL, NULL, + &xscanio_init_record, + &xscanio_get_ioint_info, + &xscanio_read +}; +epicsExportAddress(dset, devxScanIO); + +/* basic DTYP="Soft Channel" */ +static long xsoft_init_record(xRecord *prec) +{ + recGblInitConstantLink(&prec->inp, DBF_LONG, &prec->val); + return 0; +} + +static long xsoft_read(xRecord *prec) +{ + return dbGetLink(&prec->inp, DBR_LONG, &prec->val, NULL, NULL); +} + +static struct xdset devxSoft = { + 5, NULL, NULL, + &xsoft_init_record, + NULL, + &xsoft_read +}; +epicsExportAddress(dset, devxSoft); diff --git a/modules/database/test/ioc/db/devx.dbd b/modules/database/test/ioc/db/devx.dbd new file mode 100644 index 000000000..ee6421bc0 --- /dev/null +++ b/modules/database/test/ioc/db/devx.dbd @@ -0,0 +1,2 @@ +device(x, CONSTANT, devxSoft, "Soft Channel") +device(x, INST_IO , devxScanIO, "Scan I/O") diff --git a/modules/database/test/ioc/db/devx.h b/modules/database/test/ioc/db/devx.h new file mode 100644 index 000000000..f8f417a14 --- /dev/null +++ b/modules/database/test/ioc/db/devx.h @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven +* National Lab +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef DEVXSCANIO_H +#define DEVXSCANIO_H + +#include +#include + +#include + +struct xRecord; +struct xpriv; + +epicsShareExtern struct ELLLIST xdrivers; + +typedef void (*xdrvcb)(struct xpriv *, void *); + +typedef struct { + ELLNODE drvnode; + IOSCANPVT scan; + xdrvcb cb; + void *arg; + ELLLIST privlist; + int group; +} xdrv; + +typedef struct xpriv { + ELLNODE privnode; + xdrv *drv; + struct xRecord *prec; + int member; +} xpriv; + +epicsShareFunc xdrv *xdrv_add(int group, xdrvcb cb, void *arg); +epicsShareFunc xdrv *xdrv_get(int group); +epicsShareFunc void xdrv_reset(); + +typedef struct xdset { + long number; + long (*report)(int); + long (*init)(int); + long (*init_record)(struct xRecord *); + long (*get_ioint_info)(int, struct xRecord*, IOSCANPVT*); + long (*process)(struct xRecord *); +} xdset; + +#endif /* DEVXSCANIO_H */ diff --git a/modules/database/test/ioc/db/epicsRunDbTests.c b/modules/database/test/ioc/db/epicsRunDbTests.c new file mode 100644 index 000000000..4036268fa --- /dev/null +++ b/modules/database/test/ioc/db/epicsRunDbTests.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Run Db tests as a batch. + * + * Do not include performance measurements here, they don't prove + * functionality (which is the purpose of this convenience routine). + */ + +#include "epicsUnitTest.h" +#include "epicsExit.h" +#include "dbmf.h" + +int testdbConvert(void); +int callbackTest(void); +int callbackParallelTest(void); +int dbStateTest(void); +int dbServerTest(void); +int dbCaStatsTest(void); +int dbShutdownTest(void); +int dbScanTest(void); +int scanIoTest(void); +int dbLockTest(void); +int dbPutLinkTest(void); +int dbStaticTest(void); +int dbCaLinkTest(void); +int testDbChannel(void); +int chfPluginTest(void); +int arrShorthandTest(void); +int recGblCheckDeadbandTest(void); + +void epicsRunDbTests(void) +{ + testHarness(); + + runTest(testdbConvert); + runTest(callbackTest); + runTest(callbackParallelTest); + runTest(dbStateTest); + runTest(dbServerTest); + runTest(dbCaStatsTest); + runTest(dbShutdownTest); + runTest(dbScanTest); + runTest(scanIoTest); + runTest(dbLockTest); + runTest(dbPutLinkTest); + runTest(dbStaticTest); + runTest(dbCaLinkTest); + runTest(testDbChannel); + runTest(arrShorthandTest); + runTest(recGblCheckDeadbandTest); + runTest(chfPluginTest); + + dbmfFreeChunks(); + + epicsExit(0); /* Trigger test harness */ +} diff --git a/modules/database/test/ioc/db/jlinkz.c b/modules/database/test/ioc/db/jlinkz.c new file mode 100644 index 000000000..dd6919ffb --- /dev/null +++ b/modules/database/test/ioc/db/jlinkz.c @@ -0,0 +1,252 @@ +/*************************************************************************\ +* Copyright (c) 2016 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define epicsExportSharedSymbols + +#include "jlinkz.h" + +#include + +int numzalloc; + + +static +void z_open(struct link *plink) +{ + zpriv *priv = CONTAINER(plink->value.json.jlink, zpriv, base); + + if(priv->isopen) + testDiag("lsetZ re-open"); + priv->isopen = 1; + testDiag("Open jlinkz %p", priv); +} + +static +void z_remove(struct dbLocker *locker, struct link *plink) +{ + zpriv *priv = CONTAINER(plink->value.json.jlink, zpriv, base); + + epicsMutexLock(priv->lock); + + if(!priv->isopen) + testDiag("lsetZ remove without open"); + + epicsMutexUnlock(priv->lock); + + testDiag("Remove/free jlinkz %p", priv); + + epicsAtomicDecrIntT(&numzalloc); + + epicsMutexDestroy(priv->lock); + free(priv); + plink->value.json.jlink = NULL; /* paranoia */ +} + +static +int z_connected(const struct link *plink) +{ + return 1; /* TODO: not provided should be connected */ +} + +static +int z_dbftype(const struct link *plink) +{ + return DBF_LONG; +} + +static +long z_elements(const struct link *plink, long *nelements) +{ + *nelements = 1; + return 0; +} + +static +long z_getval(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + long ret; + long (*pconv)(const epicsInt32 *, void *, const dbAddr *) = dbFastGetConvertRoutine[DBF_LONG][dbrType]; + zpriv *priv = CONTAINER(plink->value.json.jlink, zpriv, base); + + if(pnRequest && *pnRequest==0) return 0; + + epicsMutexLock(priv->lock); + ret = (*pconv)(&priv->value, pbuffer, NULL); + epicsMutexUnlock(priv->lock); + if(ret==0 && pnRequest) *pnRequest = 1; + return ret; +} + +/* TODO: atomicly get value and alarm */ +static +long z_getalarm(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity) +{ + zpriv *priv = CONTAINER(plink->value.json.jlink, zpriv, base); + epicsEnum16 sevr, stat; + + epicsMutexLock(priv->lock); + sevr = priv->isset ? 0 : INVALID_ALARM; + stat = priv->isset ? 0 : LINK_ALARM; + epicsMutexUnlock(priv->lock); + + if(status) *status = stat; + if(severity) *severity = sevr; + return 0; +} + +static +long z_putval(struct link *plink, short dbrType, + const void *pbuffer, long nRequest) +{ + long ret; + long (*pconv)(epicsInt32 *, const void *, const dbAddr *) = dbFastPutConvertRoutine[DBF_LONG][dbrType]; + zpriv *priv = CONTAINER(plink->value.json.jlink, zpriv, base); + + if(nRequest==0) return 0; + + epicsMutexLock(priv->lock); + ret = (*pconv)(&priv->value, pbuffer, NULL); + epicsMutexUnlock(priv->lock); + return ret; +} + +static lset lsetZ = { + 0, 0, /* non-const, non-volatile */ + &z_open, + &z_remove, + NULL, NULL, NULL, /* load */ + &z_connected, + &z_dbftype, + &z_elements, + &z_getval, + NULL, /* control limits */ + NULL, /* display limits */ + NULL, /* alarm limits */ + NULL, /* prec */ + NULL, /* units */ + &z_getalarm, + NULL, /* time */ + &z_putval, + NULL, /* putasync */ + NULL, /* forward */ + NULL, /* doLocked */ +}; + +static +jlink* z_alloc(short dbfType) +{ + zpriv *priv; + priv = calloc(1, sizeof(*priv)); + if(!priv) goto fail; + + priv->lock = epicsMutexCreate(); + if(!priv->lock) goto fail; + + epicsAtomicIncrIntT(&numzalloc); + + testDiag("Alloc jlinkz %p", priv); + + return &priv->base; +fail: + if(priv && priv->lock) epicsMutexDestroy(priv->lock); + free(priv); + return NULL; +} + +static +void z_free(jlink *pj) +{ + zpriv *priv = CONTAINER(pj, zpriv, base); + + if(priv->isopen) + testDiag("lsetZ jlink free after open()"); + + testDiag("Free jlinkz %p", priv); + + epicsAtomicDecrIntT(&numzalloc); + + epicsMutexDestroy(priv->lock); + free(priv); +} + +static +jlif_result z_int(jlink *pj, long long num) +{ + zpriv *priv = CONTAINER(pj, zpriv, base); + + priv->value = num; + priv->isset = 1; + + return jlif_continue; +} + +static +jlif_key_result z_start(jlink *pj) +{ + return jlif_key_continue; +} + +static +jlif_result z_key(jlink *pj, const char *key, size_t len) +{ + zpriv *priv = CONTAINER(pj, zpriv, base); + + if(len==4 && strncmp(key,"fail", len)==0) { + testDiag("Found fail key jlinkz %p", priv); + return jlif_stop; + } else { + return jlif_continue; + } +} + +static +jlif_result z_end(jlink *pj) +{ + return jlif_continue; +} + +static +struct lset* z_lset(const jlink *pj) +{ + return &lsetZ; +} + +static jlif jlifZ = { + "z", + &z_alloc, + &z_free, + NULL, /* null */ + NULL, /* bool */ + &z_int, + NULL, /* double */ + NULL, /* string */ + &z_start, + &z_key, + &z_end, + NULL, /* start array */ + NULL, /* end array */ + NULL, /* end child */ + &z_lset, + NULL, /* report */ + NULL /* map child */ +}; + +epicsExportAddress(jlif, jlifZ); diff --git a/modules/database/test/ioc/db/jlinkz.dbd b/modules/database/test/ioc/db/jlinkz.dbd new file mode 100644 index 000000000..5408a88b6 --- /dev/null +++ b/modules/database/test/ioc/db/jlinkz.dbd @@ -0,0 +1 @@ +link("z", "jlifZ") diff --git a/modules/database/test/ioc/db/jlinkz.h b/modules/database/test/ioc/db/jlinkz.h new file mode 100644 index 000000000..5c34d2eec --- /dev/null +++ b/modules/database/test/ioc/db/jlinkz.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (c) 2016 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#ifndef JLINKZ_H +#define JLINKZ_H + +#include +#include +#include + +#include + +epicsShareExtern +int numzalloc; + +typedef struct { + jlink base; + epicsMutexId lock; + unsigned isset:1; + unsigned isopen:1; + epicsInt32 value; +} zpriv; + +#endif /* JLINKZ_H */ diff --git a/modules/database/test/ioc/db/recGblCheckDeadbandTest.c b/modules/database/test/ioc/db/recGblCheckDeadbandTest.c new file mode 100644 index 000000000..d3cac3a45 --- /dev/null +++ b/modules/database/test/ioc/db/recGblCheckDeadbandTest.c @@ -0,0 +1,119 @@ +/*************************************************************************\ +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "recGbl.h" +#include "dbBase.h" +#include "epicsMath.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* Test parameters */ + +#define NO_OF_DEADBANDS 3 +#define NO_OF_PATTERNS 19 + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +/* Indices for deadband value, test number, val in sequence */ +static int idbnd, itest; + +/* Different deadbands to test with */ +static double t_Deadband[NO_OF_DEADBANDS] = { -1, 0, 1.5 }; +/* Value sequences for each of the 16 tests */ +static double t_SetValues[NO_OF_PATTERNS][2]; +/* Expected updates (1=yes) for each sequence of each test of each deadband */ +static int t_ExpectedUpdates[NO_OF_DEADBANDS][NO_OF_PATTERNS] = { + { /* deadband = -1 */ + 1, 1, 1, 1, + 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + }, + { /* deadband = 0 */ + 1, 1, 0, 0, + 1, 1, 1, + 1, 0, 1, 1, + 1, 1, 0, 1, + 1, 1, 1, 0, + }, + { /* deadband = 1.5 */ + 0, 1, 0, 0, + 1, 1, 1, + 1, 0, 1, 1, + 1, 1, 0, 1, + 1, 1, 1, 0, + }, +}; + +MAIN(recGblCheckDeadbandTest) +{ + unsigned mask; + double oldval, newval; + + /* Test patterns: + * 0: step less than deadband (of 1.5) + * 1: step larger than deadband (of 1.5) + * 2: no change + * 3: -0.0 -> +0.0 + * ... all possible combinations of steps + * between: finite / NaN / -inf / +inf + */ + t_SetValues[ 0][0] = 1.0; t_SetValues[ 0][1] = 2.0; + t_SetValues[ 1][0] = 0.0; t_SetValues[ 1][1] = 2.0; + t_SetValues[ 2][0] = 0.0; t_SetValues[ 2][1] = 0.0; + t_SetValues[ 3][0] = -0.0; t_SetValues[ 3][1] = 0.0; + t_SetValues[ 4][0] = 1.0; t_SetValues[ 4][1] = epicsNAN; + t_SetValues[ 5][0] = 1.0; t_SetValues[ 5][1] = epicsINF; + t_SetValues[ 6][0] = 1.0; t_SetValues[ 6][1] = -epicsINF; + t_SetValues[ 7][0] = epicsNAN; t_SetValues[ 7][1] = 1.0; + t_SetValues[ 8][0] = epicsNAN; t_SetValues[ 8][1] = epicsNAN; + t_SetValues[ 9][0] = epicsNAN; t_SetValues[ 9][1] = epicsINF; + t_SetValues[10][0] = epicsNAN; t_SetValues[10][1] = -epicsINF; + t_SetValues[11][0] = epicsINF; t_SetValues[11][1] = 1.0; + t_SetValues[12][0] = epicsINF; t_SetValues[12][1] = epicsNAN; + t_SetValues[13][0] = epicsINF; t_SetValues[13][1] = epicsINF; + t_SetValues[14][0] = epicsINF; t_SetValues[14][1] = -epicsINF; + t_SetValues[15][0] = -epicsINF; t_SetValues[15][1] = 1.0; + t_SetValues[16][0] = -epicsINF; t_SetValues[16][1] = epicsNAN; + t_SetValues[17][0] = -epicsINF; t_SetValues[17][1] = epicsINF; + t_SetValues[18][0] = -epicsINF; t_SetValues[18][1] = -epicsINF; + + testPlan(114); + + /* Loop over all tested deadband values */ + for (idbnd = 0; idbnd < NO_OF_DEADBANDS; idbnd++) { + + /* Loop over all test patterns */ + for (itest = 0; itest < NO_OF_PATTERNS; itest++) { + oldval = t_SetValues[itest][0]; + newval = t_SetValues[itest][1]; + mask = 0; + + recGblCheckDeadband(&oldval, newval, t_Deadband[idbnd], &mask, 1); + + /* Check expected vs. actual test result */ + testOk(t_ExpectedUpdates[idbnd][itest] == mask, + "deadband=%2.1f: check for oldvalue=%f newvalue=%f (expected %d, got %d)", + t_Deadband[idbnd], t_SetValues[itest][0], t_SetValues[itest][1], + t_ExpectedUpdates[idbnd][itest], mask); + + if (mask) { + testOk((oldval == newval) || (isnan(oldval) && isnan(newval)), "mask set, oldval equals newval"); + } else { + testOk((oldval == t_SetValues[itest][0]) || (isnan(oldval) && isnan(t_SetValues[itest][0])), + "mask not set, oldval unchanged"); + } + } + } + return testDone(); +} diff --git a/modules/database/test/ioc/db/rtemsTestHarness.c b/modules/database/test/ioc/db/rtemsTestHarness.c new file mode 100644 index 000000000..c9ab2a68c --- /dev/null +++ b/modules/database/test/ioc/db/rtemsTestHarness.c @@ -0,0 +1,14 @@ +/*************************************************************************\ +* Copyright (c) 2011 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +extern void epicsRunDbTests(void); + +int main(int argc, char **argv) +{ + epicsRunDbTests(); /* calls epicsExit(0) */ + return 0; +} diff --git a/modules/database/test/ioc/db/scanIoTest.c b/modules/database/test/ioc/db/scanIoTest.c new file mode 100644 index 000000000..ee6d8b462 --- /dev/null +++ b/modules/database/test/ioc/db/scanIoTest.c @@ -0,0 +1,308 @@ +/*************************************************************************\ +* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2013 Helmholtz-Zentrum Berlin +* für Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include +#include +#include + +#include "epicsEvent.h" +#include "epicsMessageQueue.h" +#include "epicsPrint.h" +#include "epicsMath.h" +#include "alarm.h" +#include "menuPriority.h" +#include "dbChannel.h" +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "dbScan.h" +#include "dbLock.h" +#include "dbUnitTest.h" +#include "dbCommon.h" +#include "recSup.h" +#include "devSup.h" +#include "iocInit.h" +#include "callback.h" +#include "ellLib.h" +#include "epicsUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" + +#include "epicsExport.h" + +#include "devx.h" +#include "xRecord.h" + +STATIC_ASSERT(NUM_CALLBACK_PRIORITIES==3); + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void loadRecord(int group, int member, const char *prio) +{ + char buf[40]; + sprintf(buf, "GROUP=%d,MEMBER=%d,PRIO=%s", + group, member, prio); + testdbReadDatabase("scanIoTest.db", NULL, buf); +} + +typedef struct { + int hasprocd[NUM_CALLBACK_PRIORITIES]; + int getcomplete[NUM_CALLBACK_PRIORITIES]; + epicsEventId wait[NUM_CALLBACK_PRIORITIES]; + epicsEventId wake[NUM_CALLBACK_PRIORITIES]; +} testsingle; + +static void testcb(xpriv *priv, void *raw) +{ + testsingle *td = raw; + int prio = priv->prec->prio; + + testOk1(td->hasprocd[prio]==0); + td->hasprocd[prio] = 1; +} + +static void testcomp(void *raw, IOSCANPVT scan, int prio) +{ + testsingle *td = raw; + + testOk1(td->hasprocd[prio]==1); + testOk1(td->getcomplete[prio]==0); + td->getcomplete[prio] = 1; + epicsEventMustTrigger(td->wait[prio]); + epicsEventMustWait(td->wake[prio]); +} + +static void testSingleThreading(void) +{ + int i; + testsingle data[2]; + xdrv *drvs[2]; + + memset(data, 0, sizeof(data)); + + for(i=0; i<2; i++) { + int p; + for(p=0; pscan, &testcomp, &data[0]); + scanIoSetComplete(drvs[1]->scan, &testcomp, &data[1]); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Scan first list"); + scanIoRequest(drvs[0]->scan); + testDiag("Scan second list"); + scanIoRequest(drvs[1]->scan); + + testDiag("Wait for first list to complete"); + for(i=0; imember; + + testOk1(td->hasprocd==0); + td->hasprocd = 1; + epicsEventMustTrigger(td->wait); + epicsEventMustWait(td->wake); + td->getcomplete = 1; +} + +static void testcompmulti(void *raw, IOSCANPVT scan, int prio) +{ + int *mask = raw; + testOk(((*mask)&(1<scan, &testcompmulti, &masks[0]); + scanIoSetComplete(drvs[1]->scan, &testcompmulti, &masks[1]); + + /* just enough workers to process all records concurrently */ + callbackParallelThreads(2, "LOW"); + callbackParallelThreads(2, "MEDIUM"); + callbackParallelThreads(2, "HIGH"); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Scan first list"); + testOk1(scanIoRequest(drvs[0]->scan)==0x7); + testDiag("Scan second list"); + testOk1(scanIoRequest(drvs[1]->scan)==0x7); + + testDiag("Wait for everything to start"); + for(i=0; ijlink; +} + +static void xlink_free(jlink *pjlink) +{ + xlink *xlink = CONTAINER(pjlink, struct xlink, jlink); + + free(xlink); +} + +static jlif_result xlink_boolean(jlink *pjlink, int val) +{ + return val; /* False triggers a parse failure */ +} + +static struct lset* xlink_get_lset(const jlink *pjlink) +{ + return &xlink_lset; +} + + +static void xlink_remove(struct dbLocker *locker, struct link *plink) +{ + xlink_free(plink->value.json.jlink); +} + +static long xlink_getNelements(const struct link *plink, long *nelements) +{ + *nelements = 0; + return 0; +} + +static long xlink_getValue(struct link *plink, short dbrType, void *pbuffer, + long *pnRequest) +{ + if (pnRequest) + *pnRequest = 0; + return 0; +} + + +static lset xlink_lset = { + 1, 0, /* Constant, not Volatile */ + NULL, xlink_remove, + NULL, NULL, NULL, NULL, + NULL, xlink_getNelements, xlink_getValue, + NULL, NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL +}; + +static jlif xlinkIf = { + "x", xlink_alloc, xlink_free, + NULL, xlink_boolean, NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, + NULL, xlink_get_lset, + NULL, NULL +}; +epicsExportAddress(jlif, xlinkIf); + diff --git a/modules/database/test/ioc/db/xLink.dbd b/modules/database/test/ioc/db/xLink.dbd new file mode 100644 index 000000000..290b0ba02 --- /dev/null +++ b/modules/database/test/ioc/db/xLink.dbd @@ -0,0 +1 @@ +link(x, xlinkIf) diff --git a/modules/database/test/ioc/db/xRecord.c b/modules/database/test/ioc/db/xRecord.c new file mode 100644 index 000000000..5188bf187 --- /dev/null +++ b/modules/database/test/ioc/db/xRecord.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Andrew Johnson + * Ralph Lange + */ + +#include "dbAccessDefs.h" +#include "recSup.h" +#include "recGbl.h" +#include "devSup.h" +#include "dbScan.h" + +#define GEN_SIZE_OFFSET +#include "xRecord.h" + +#include + +#include "devx.h" + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct xRecord *prec = (struct xRecord *)pcommon; + long ret = 0; + xdset *xset = (xdset*)prec->dset; + if(!pass) return 0; + + if(!xset) { + recGblRecordError(S_dev_noDSET, prec, "x: init_record"); + return S_dev_noDSET; + } + if(xset->init_record) + ret = (*xset->init_record)(prec); + return ret; +} + +static long process(struct dbCommon *pcommon) +{ + struct xRecord *prec = (struct xRecord *)pcommon; + long ret = 0; + xdset *xset = (xdset*)prec->dset; + + if(prec->clbk) + (*prec->clbk)(prec); + prec->pact = TRUE; + if(xset && xset->process) + ret = (*xset->process)(prec); + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return ret; +} + +static rset xRSET = { + RSETNUMBER, NULL, NULL, init_record, process +}; +epicsExportAddress(rset,xRSET); diff --git a/modules/database/test/ioc/db/xRecord.db b/modules/database/test/ioc/db/xRecord.db new file mode 100644 index 000000000..a6fa08e40 --- /dev/null +++ b/modules/database/test/ioc/db/xRecord.db @@ -0,0 +1,2 @@ +record(x, x) {} + diff --git a/modules/database/test/ioc/db/xRecord.dbd b/modules/database/test/ioc/db/xRecord.dbd new file mode 100644 index 000000000..915746a25 --- /dev/null +++ b/modules/database/test/ioc/db/xRecord.dbd @@ -0,0 +1,19 @@ +# This is a minimal record definition + +recordtype(x) { + include "dbCommon.dbd" + field(VAL, DBF_LONG) { + prompt("Value") + } + field(LNK, DBF_INLINK) { + prompt("Link") + } + field(INP, DBF_INLINK) { + prompt("Input Link") + } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct xRecord*)") + } +} diff --git a/modules/database/test/ioc/dbtemplate/Makefile b/modules/database/test/ioc/dbtemplate/Makefile new file mode 100644 index 000000000..4442a4ff8 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/Makefile @@ -0,0 +1,21 @@ +#************************************************************************* +# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../../.. + +include $(TOP)/configure/CONFIG + +TESTPROD_HOST_DEFAULT = dbltExpand +TESTPROD_HOST_WIN32 = -nil- +dbltExpand_SRCS += dbltExpand.c +dbltExpand_LIBS += dbCore ca Com + +TESTS += msi + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES diff --git a/modules/database/test/ioc/dbtemplate/dbltExpand.c b/modules/database/test/ioc/dbtemplate/dbltExpand.c new file mode 100644 index 000000000..4fda7e30e --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/dbltExpand.c @@ -0,0 +1,100 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS Base is distributed subject to a Software License Agreement found +* in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* This is a simple version of msi for testing the dbLoadTemplate() code. + * + * It calls dbLoadTemplate() to parse the substitution file, but replaces + * dbLoadRecords() with its own version that reads the template file, + * expands any macros in the text and prints the result to stdout. + * + * This technique won't work on Windows, dbLoadRecords() has to be + * epicsShare... decorated and loaded from a shared library. + */ + +#include +#include +#include +#include + +#include "macLib.h" +#include "dbLoadTemplate.h" + + +#define BUFFER_SIZE 0x10000 + +static char *input_buffer, *output_buffer; + +int dbLoadRecords(const char *file, const char *macros) +{ + MAC_HANDLE *macHandle = NULL; + char **macPairs; + FILE *fp; + size_t input_len; + + if (macCreateHandle(&macHandle, NULL)) { + fprintf(stderr, "macCreateHandle failed\n"); + exit(1); + } + + macSuppressWarning(macHandle, 1); + macParseDefns(macHandle, macros, &macPairs); + if (!macPairs) { + macDeleteHandle(macHandle); + macHandle = NULL; + } else { + macInstallMacros(macHandle, macPairs); + free(macPairs); + } + + fp = fopen(file, "r"); + if (!fp) { + fprintf(stderr, "fopen('%s') failed: %s\n", file, strerror(errno)); + exit(1); + } + + input_len = fread(input_buffer, 1, BUFFER_SIZE, fp); + if (!feof(fp)) { + fprintf(stderr, "input file > 64K!\n"); + fclose(fp); + exit(1); + } + input_buffer[input_len] = 0; + + if (fclose(fp)) { + fprintf(stderr, "fclose('%s') failed: %s\n", file, strerror(errno)); + exit(1); + } + + macExpandString(macHandle, input_buffer, output_buffer, BUFFER_SIZE-1); + printf("%s", output_buffer); + + if (macHandle) macDeleteHandle(macHandle); + + return 0; +} + +int main(int argc, char **argv) +{ + input_buffer = malloc(BUFFER_SIZE); + output_buffer = malloc(BUFFER_SIZE); + + if (!input_buffer || !output_buffer) { + fprintf(stderr, "malloc(%d) failed\n", BUFFER_SIZE); + exit(1); + } + + if (argc != 2) { + fprintf(stderr, "Usage: %s file.sub\n", argv[0]); + exit(1); + } + + dbLoadTemplate(argv[1], NULL); + + free(output_buffer); + free(input_buffer); + return 0; +} diff --git a/modules/database/test/ioc/dbtemplate/msi.plt b/modules/database/test/ioc/dbtemplate/msi.plt new file mode 100644 index 000000000..ab49fd9cd --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/msi.plt @@ -0,0 +1,83 @@ +#!/usr/bin/perl +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +# Script to run tests on the msi program + +use strict; +use Test; + +BEGIN {plan tests => 9} + +# Check include/substitute command model +ok(msi('-I .. ../t1-template.txt'), slurp('../t1-result.txt')); + +# Substitution file, dbLoadTemplate format +ok(msi('-I.. -S ../t2-substitution.txt'), slurp('../t2-result.txt')); + +# Macro scoping +ok(msi('-I. -I.. -S ../t3-substitution.txt'), slurp('../t3-result.txt')); + +# Global scope (backwards compatibility check) +ok(msi('-g -I.. -S ../t4-substitution.txt'), slurp('../t4-result.txt')); + +# Substitution file, regular format +ok(msi('-S ../t5-substitute.txt ../t5-template.txt'), slurp('../t5-result.txt')); + +# Substitution file, pattern format +ok(msi('-S../t6-substitute.txt ../t6-template.txt'), slurp('../t6-result.txt')); + +# Output option -o +my $out = 't7-output.txt'; +my $count = 5; # Try up to 5 times... +my $result; +do { + unlink $out; + msi("-I.. -o $out ../t1-template.txt"); + $result = slurp($out); + print "# msi output file empty, retrying\n" + if $result eq ''; +} while ($result eq '') && (--$count > 0); +ok($result, slurp('../t1-result.txt')); + +# Dependency generation, include/substitute model +ok(msi('-I.. -D -o t8.txt ../t1-template.txt'), slurp('../t8-result.txt')); + +# Dependency generation, dbLoadTemplate format +ok(msi('-I.. -D -ot9.txt -S ../t2-substitution.txt'), slurp('../t9-result.txt')); + + +# Test support routines + +sub slurp { + my ($file) = @_; + open my $in, '<', $file + or die "Can't open file $file: $!\n"; + my $contents = do { local $/; <$in> }; + return $contents; +} + +sub msi { + my ($args) = @_; + my $msi = '@TOP@/bin/@ARCH@/msi'; + $msi .= '.exe' if ($^O eq 'MSWin32') || ($^O eq 'cygwin'); + my $result; + if ($args =~ m/-o / && $args !~ m/-D/) { + # An empty result is expected + $result = `$msi $args`; + } + else { + # Try up to 5 times, sometimes msi fails on Windows + my $count = 5; + do { + $result = `$msi $args`; + print "# result of '$msi $args' empty, retrying\n" + if $result eq ''; + } while ($result eq '') && (--$count > 0); + } + return $result; +} diff --git a/modules/database/test/ioc/dbtemplate/t1-include.txt b/modules/database/test/ioc/dbtemplate/t1-include.txt new file mode 100644 index 000000000..9793c73bd --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t1-include.txt @@ -0,0 +1,5 @@ +This is t1-include.txt $(include-file-again=) + a = $(a=default value used when a is undefined) + b = $(b=default value used when b is undefined) +substitute "include-file-again=again" +End of t1-include.txt diff --git a/modules/database/test/ioc/dbtemplate/t1-result.txt b/modules/database/test/ioc/dbtemplate/t1-result.txt new file mode 100644 index 000000000..fcd065585 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t1-result.txt @@ -0,0 +1,21 @@ +This is t1-template.txt + +With $(a,undefined) & $(b,undefined): +This is t1-include.txt + a = default value used when a is undefined + b = default value used when b is undefined +End of t1-include.txt + +On defining a=aaa & b=bbb: +This is t1-include.txt again + a = aaa + b = bbb +End of t1-include.txt + +On setting a="aa": +This is t1-include.txt again + a = "aa" + b = bbb +End of t1-include.txt + +End of t1-template.txt diff --git a/modules/database/test/ioc/dbtemplate/t1-template.txt b/modules/database/test/ioc/dbtemplate/t1-template.txt new file mode 100644 index 000000000..9ef49e8fe --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t1-template.txt @@ -0,0 +1,14 @@ +This is t1-template.txt + +With $(a) & ${b}: +include "t1-include.txt" + +substitute "a=aaa,b=bbb" +On defining a=$(a) & b=${b}: +include "t1-include.txt" + +substitute "a=\"aa\"" +On setting a=$(a): +include "t1-include.txt" + +End of t1-template.txt diff --git a/modules/database/test/ioc/dbtemplate/t2-result.txt b/modules/database/test/ioc/dbtemplate/t2-result.txt new file mode 100644 index 000000000..5239e6a05 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t2-result.txt @@ -0,0 +1,6 @@ +a = va1-a b = def-b c = def-c d = $(d,undefined) +a = va2-a b = va2-b c = def-c d = $(d,undefined) +a = va3-a b = va3-b c = va3-c d = $(d,undefined) +a = va4-a b = va4-b c = def-c d = $(d,undefined) +a = va5-a b = def-b c = def-c d = $(d,undefined) +a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined) diff --git a/modules/database/test/ioc/dbtemplate/t2-substitution.txt b/modules/database/test/ioc/dbtemplate/t2-substitution.txt new file mode 100644 index 000000000..a3f3d6874 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t2-substitution.txt @@ -0,0 +1,11 @@ +file t2-template.txt { + {a=va1-a} + {a=va2-a, b=va2-b} + {a=va3-a, b=va3-b, c=va3-c} + {a=va4-a, b=va4-b} + {a=va5-a} +} +file t2-template.txt { + pattern {a, b, c} + {pt3-a, pt3-b, pt3-c} +} diff --git a/modules/database/test/ioc/dbtemplate/t2-template.txt b/modules/database/test/ioc/dbtemplate/t2-template.txt new file mode 100644 index 000000000..c4ac7b427 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t2-template.txt @@ -0,0 +1 @@ +a = $(a=def-a) b = $(b=def-b) c = $(c=def-c) d = $(d,undef) diff --git a/modules/database/test/ioc/dbtemplate/t3-result.txt b/modules/database/test/ioc/dbtemplate/t3-result.txt new file mode 100644 index 000000000..c6961507f --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t3-result.txt @@ -0,0 +1,28 @@ +a = gb1-a b = gb1-b c = def-c d = $(d,undefined) +a = va1-a b = gb1-b c = def-c d = $(d,undefined) +a = va2-a b = va2-b c = def-c d = $(d,undefined) +a = va3-a b = va3-b c = va3-c d = $(d,undefined) +a = va4-a b = va4-b c = def-c d = $(d,undefined) +a = va5-a b = gb1-b c = def-c d = $(d,undefined) +a = gb1-a b = gb1-b c = def-c d = $(d,undefined) +a = gb2-a b = gb2-b c = def-c d = $(d,undefined) +a = va1-a b = gb2-b c = def-c d = $(d,undefined) +a = va2-a b = va2-b c = def-c d = $(d,undefined) +a = va3-a b = va3-b c = va3-c d = $(d,undefined) +a = va4-a b = va4-b c = def-c d = $(d,undefined) +a = va5-a b = gb2-b c = def-c d = $(d,undefined) +a = gb2-a b = gb2-b c = def-c d = $(d,undefined) +a = gb3-a b = gb3-b c = def-c d = $(d,undefined) +a = pt1-a b = gb3-b c = def-c d = $(d,undefined) +a = pt2-a b = pt2-b c = def-c d = $(d,undefined) +a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined) +a = pt4-a b = pt4-b c = def-c d = $(d,undefined) +a = pt5-a b = gb3-b c = def-c d = $(d,undefined) +a = gb3-a b = gb3-b c = def-c d = $(d,undefined) +a = gb4-a b = gb4-b c = def-c d = $(d,undefined) +a = pt1-a b = gb4-b c = def-c d = $(d,undefined) +a = pt2-a b = pt2-b c = def-c d = $(d,undefined) +a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined) +a = pt4-a b = pt4-b c = def-c d = $(d,undefined) +a = pt5-a b = gb4-b c = def-c d = $(d,undefined) +a = gb4-a b = gb4-b c = def-c d = $(d,undefined) diff --git a/modules/database/test/ioc/dbtemplate/t3-substitution.txt b/modules/database/test/ioc/dbtemplate/t3-substitution.txt new file mode 100644 index 000000000..30b0741d3 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t3-substitution.txt @@ -0,0 +1,37 @@ +global {a=gb1-a, b=gb1-b} +file t3-template.txt { + {} + {a=va1-a} + {a=va2-a, b=va2-b} + {a=va3-a, b=va3-b, c=va3-c} + {a=va4-a, b=va4-b} + {a=va5-a} + {} + global {a=gb2-a, b=gb2-b} + {} + {a=va1-a} + {a=va2-a, b=va2-b} + {a=va3-a, b=va3-b, c=va3-c} + {a=va4-a, b=va4-b} + {a=va5-a} + {} +} +global {b=gb3-b, a=gb3-a} +file t3-template.txt { + pattern {a, b, c} + {} + {pt1-a} + {pt2-a, pt2-b} + {pt3-a, pt3-b, pt3-c} + {pt4-a, pt4-b} + {pt5-a} + {} + global {b=gb4-b, a=gb4-a} + {} + {pt1-a} + {pt2-a, pt2-b} + {pt3-a, pt3-b, pt3-c} + {pt4-a, pt4-b} + {pt5-a} + {} +} diff --git a/modules/database/test/ioc/dbtemplate/t3-template.txt b/modules/database/test/ioc/dbtemplate/t3-template.txt new file mode 100644 index 000000000..c4ac7b427 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t3-template.txt @@ -0,0 +1 @@ +a = $(a=def-a) b = $(b=def-b) c = $(c=def-c) d = $(d,undef) diff --git a/modules/database/test/ioc/dbtemplate/t4-result.txt b/modules/database/test/ioc/dbtemplate/t4-result.txt new file mode 100644 index 000000000..664872013 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t4-result.txt @@ -0,0 +1,6 @@ +a = va1-a b = def-b c = def-c d = $(d,undefined) +a = va2-a b = va2-b c = def-c d = $(d,undefined) +a = va3-a b = va3-b c = va3-c d = $(d,undefined) +a = va4-a b = va4-b c = va3-c d = $(d,undefined) +a = va5-a b = va4-b c = va3-c d = $(d,undefined) +a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined) diff --git a/modules/database/test/ioc/dbtemplate/t4-substitution.txt b/modules/database/test/ioc/dbtemplate/t4-substitution.txt new file mode 100644 index 000000000..a3f3d6874 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t4-substitution.txt @@ -0,0 +1,11 @@ +file t2-template.txt { + {a=va1-a} + {a=va2-a, b=va2-b} + {a=va3-a, b=va3-b, c=va3-c} + {a=va4-a, b=va4-b} + {a=va5-a} +} +file t2-template.txt { + pattern {a, b, c} + {pt3-a, pt3-b, pt3-c} +} diff --git a/modules/database/test/ioc/dbtemplate/t5-result.txt b/modules/database/test/ioc/dbtemplate/t5-result.txt new file mode 100644 index 000000000..19a57c87c --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t5-result.txt @@ -0,0 +1,20 @@ +# comment line +a = 111 +b = 222 +c = xx +d = $(d,undefined) +# comment line +a = aaa +b = bbb +c = ccc +d = $(d,undefined) +# comment line +a = AA +b = BB +c = xx +d = $(d,undefined) +# comment line +a = aaa +b = bbb +c = yy +d = $(d,undefined) diff --git a/modules/database/test/ioc/dbtemplate/t5-substitute.txt b/modules/database/test/ioc/dbtemplate/t5-substitute.txt new file mode 100644 index 000000000..6b6ca985d --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t5-substitute.txt @@ -0,0 +1,9 @@ +global {c=xx} +{a=111,b="222"} +{ a = aaa , b=bbb , c = ccc} +{a=AA,b='BB'} +global { c = yy } +{ + a= aaa + b= bbb +} diff --git a/modules/database/test/ioc/dbtemplate/t5-template.txt b/modules/database/test/ioc/dbtemplate/t5-template.txt new file mode 100644 index 000000000..561b52a0a --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t5-template.txt @@ -0,0 +1,5 @@ +# comment line +a = $(a) +b = $(b) +c = $(c) +d = $(d) diff --git a/modules/database/test/ioc/dbtemplate/t6-result.txt b/modules/database/test/ioc/dbtemplate/t6-result.txt new file mode 100644 index 000000000..19a57c87c --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t6-result.txt @@ -0,0 +1,20 @@ +# comment line +a = 111 +b = 222 +c = xx +d = $(d,undefined) +# comment line +a = aaa +b = bbb +c = ccc +d = $(d,undefined) +# comment line +a = AA +b = BB +c = xx +d = $(d,undefined) +# comment line +a = aaa +b = bbb +c = yy +d = $(d,undefined) diff --git a/modules/database/test/ioc/dbtemplate/t6-substitute.txt b/modules/database/test/ioc/dbtemplate/t6-substitute.txt new file mode 100644 index 000000000..7d80824fd --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t6-substitute.txt @@ -0,0 +1,13 @@ +global {c=xx} +pattern {b,a} +{"222",111} +pattern {a b c} +{ aaa , bbb , ccc} +pattern { a , b } +{AA,'BB'} +global { c = yy } +pattern { a , b } +{ + aaa + bbb +} diff --git a/modules/database/test/ioc/dbtemplate/t6-template.txt b/modules/database/test/ioc/dbtemplate/t6-template.txt new file mode 100644 index 000000000..561b52a0a --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t6-template.txt @@ -0,0 +1,5 @@ +# comment line +a = $(a) +b = $(b) +c = $(c) +d = $(d) diff --git a/modules/database/test/ioc/dbtemplate/t8-result.txt b/modules/database/test/ioc/dbtemplate/t8-result.txt new file mode 100644 index 000000000..478c00614 --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t8-result.txt @@ -0,0 +1,2 @@ +t8.txt: ../t1-template.txt \ + ../t1-include.txt diff --git a/modules/database/test/ioc/dbtemplate/t9-result.txt b/modules/database/test/ioc/dbtemplate/t9-result.txt new file mode 100644 index 000000000..9f122bbcf --- /dev/null +++ b/modules/database/test/ioc/dbtemplate/t9-result.txt @@ -0,0 +1 @@ +t9.txt: ../t2-template.txt diff --git a/modules/database/test/std/filters/Makefile b/modules/database/test/std/filters/Makefile new file mode 100644 index 000000000..540a4d76c --- /dev/null +++ b/modules/database/test/std/filters/Makefile @@ -0,0 +1,81 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../../.. + +include $(TOP)/configure/CONFIG + +TESTLIBRARY = Recs + +Recs_SRCS += xRecord.c +Recs_SRCS += arrRecord.c +Recs_LIBS += dbCore ca Com + +PROD_LIBS = Recs dbRecStd dbCore ca Com + +DBDDEPENDS_FILES += filterTest.dbd$(DEP) +TARGETS += $(COMMON_DIR)/filterTest.dbd +filterTest_DBD += menuGlobal.dbd +filterTest_DBD += menuConvert.dbd +filterTest_DBD += menuScan.dbd +filterTest_DBD += filters.dbd +filterTest_DBD += xRecord.dbd +filterTest_DBD += arrRecord.dbd +TESTFILES += $(COMMON_DIR)/filterTest.dbd + +testHarness_SRCS += filterTest_registerRecordDeviceDriver.cpp + +TESTPROD_HOST += tsTest +tsTest_SRCS += tsTest.c +tsTest_SRCS += filterTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += tsTest.c +TESTFILES += ../xRecord.db +TESTS += tsTest + +TESTPROD_HOST += dbndTest +dbndTest_SRCS += dbndTest.c +dbndTest_SRCS += filterTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbndTest.c +TESTS += dbndTest + +TESTPROD_HOST += arrTest +arrTest_SRCS += arrTest.cpp +arrTest_SRCS += filterTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += arrTest.cpp +TESTFILES += ../arrTest.db +TESTS += arrTest + +TESTPROD_HOST += syncTest +syncTest_SRCS += syncTest.c +syncTest_SRCS += filterTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += syncTest.c +TESTS += syncTest + +# epicsRunFilterTests runs all the test programs in a known working order. +testHarness_SRCS += epicsRunFilterTests.c + +filterTestHarness_SRCS += $(testHarness_SRCS) +filterTestHarness_SRCS_RTEMS += rtemsTestHarness.c + +PROD_vxWorks = filterTestHarness +PROD_RTEMS = filterTestHarness + +TESTSPEC_vxWorks = filterTestHarness.munch; epicsRunFilterTests +TESTSPEC_RTEMS = filterTestHarness.boot; epicsRunFilterTests + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES + +xRecord$(DEP): $(COMMON_DIR)/xRecord.h +tsTest$(DEP): $(COMMON_DIR)/xRecord.h +dbndTest$(DEP): $(COMMON_DIR)/xRecord.h +syncTest$(DEP): $(COMMON_DIR)/xRecord.h +arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h +arrTest$(DEP): $(COMMON_DIR)/arrRecord.h diff --git a/modules/database/test/std/filters/arrRecord.c b/modules/database/test/std/filters/arrRecord.c new file mode 100644 index 000000000..8f1881f02 --- /dev/null +++ b/modules/database/test/std/filters/arrRecord.c @@ -0,0 +1,145 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* arrRecord.c - minimal array record for test purposes: no processing */ + +/* + * Author: Ralph Lange + * + * vaguely implemented like parts of recWaveform.c by Bob Dalesio + * + */ + +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "recSup.h" +#include "recGbl.h" +#include "cantProceed.h" +#define GEN_SIZE_OFFSET +#include "arrRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(struct dbCommon *, int); +static long process(struct dbCommon *); +#define special NULL +#define get_value NULL +static long cvt_dbaddr(DBADDR *); +static long get_array_info(DBADDR *, long *, long *); +static long put_array_info(DBADDR *, long); +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset arrRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double +}; +epicsExportAddress(rset, arrRSET); + +static long init_record(struct dbCommon *pcommon, int pass) +{ + struct arrRecord *prec = (struct arrRecord *)pcommon; + + if (pass == 0) { + if (prec->nelm <= 0) + prec->nelm = 1; + if (prec->ftvl > DBF_ENUM) + prec->ftvl = DBF_UCHAR; + prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl), + "arr calloc failed"); + + if (prec->nelm == 1) { + prec->nord = 1; + } else { + prec->nord = 0; + } + return 0; + } + return 0; +} + +static long process(struct dbCommon *pcommon) +{ + struct arrRecord *prec = (struct arrRecord *)pcommon; + + if(prec->clbk) + (*prec->clbk)(prec); + prec->pact = TRUE; + recGblGetTimeStamp(prec); + recGblFwdLink(prec); + prec->pact = FALSE; + return 0; +} + +static long cvt_dbaddr(DBADDR *paddr) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + paddr->pfield = prec->bptr; + paddr->no_elements = prec->nelm; + paddr->field_type = prec->ftvl; + paddr->field_size = dbValueSize(prec->ftvl); + paddr->dbr_field_type = prec->ftvl; + + return 0; +} + +static long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + *no_elements = prec->nord; + *offset = prec->off; + + return 0; +} + +static long put_array_info(DBADDR *paddr, long nNew) +{ + arrRecord *prec = (arrRecord *) paddr->precord; + + prec->nord = nNew; + if (prec->nord > prec->nelm) + prec->nord = prec->nelm; + + return 0; +} diff --git a/modules/database/test/std/filters/arrRecord.dbd b/modules/database/test/std/filters/arrRecord.dbd new file mode 100644 index 000000000..b504be1cb --- /dev/null +++ b/modules/database/test/std/filters/arrRecord.dbd @@ -0,0 +1,42 @@ +include "menuGlobal.dbd" +include "menuConvert.dbd" +include "menuScan.dbd" +recordtype(arr) { + include "dbCommon.dbd" + field(VAL, DBF_NOACCESS) { + prompt("Value") + special(SPC_DBADDR) + pp(TRUE) + extra("void *val") + } + field(NELM, DBF_ULONG) { + prompt("Number of Elements") + special(SPC_NOMOD) + initial("1") + } + field(FTVL, DBF_MENU) { + prompt("Field Type of Value") + special(SPC_NOMOD) + menu(menuFtype) + } + field(NORD, DBF_ULONG) { + prompt("Number elements read") + special(SPC_NOMOD) + } + field(OFF, DBF_ULONG) { + prompt("Offset into array") + } + field(BPTR, DBF_NOACCESS) { + prompt("Buffer Pointer") + special(SPC_NOMOD) + extra("void *bptr") + } + field(INP, DBF_INLINK) { + prompt("Input Link") + } + field(CLBK, DBF_NOACCESS) { + prompt("Processing callback") + special(SPC_NOMOD) + extra("void (*clbk)(struct arrRecord*)") + } +} diff --git a/modules/database/test/std/filters/arrTest.cpp b/modules/database/test/std/filters/arrTest.cpp new file mode 100644 index 000000000..1ec16b32f --- /dev/null +++ b/modules/database/test/std/filters/arrTest.cpp @@ -0,0 +1,336 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2003 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS BASE is distributed subject to the Software License Agreement +* found in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +/* using stuff from softIoc.cpp by Andrew Johnson */ + +#include +#include +#include +#include +#include + +#include "registryFunction.h" +#include "epicsThread.h" +#include "epicsExit.h" +#include "epicsStdio.h" +#include "envDefs.h" +#include "dbStaticLib.h" +#include "dbmf.h" +#include "errlog.h" +#include "registry.h" +#include "dbAddr.h" +#include "dbAccess.h" +#include "asDbLib.h" +#include "iocInit.h" +#include "iocsh.h" +#include "dbChannel.h" +#include "epicsUnitTest.h" +#include "dbUnitTest.h" +#include "testMain.h" +#include "osiFileName.h" + +#include "arrRecord.h" + +extern "C" { + void filterTest_registerRecordDeviceDriver(struct dbBase *); +} + +#define CA_SERVER_PORT "65535" + +#define PATTERN 0x55 + +const char *server_port = CA_SERVER_PORT; + +static int fl_equals_array(short type, const db_field_log *pfl1, void *p2) { + for (int i = 0; i < pfl1->no_elements; i++) { + switch (type) { + case DBR_DOUBLE: + if (((epicsFloat64*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) { + testDiag("at index=%d: field log has %g, should be %d", + i, ((epicsFloat64*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]); + return 0; + } + break; + case DBR_LONG: + if (((epicsInt32*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) { + testDiag("at index=%d: field log has %d, should be %d", + i, ((epicsInt32*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]); + return 0; + } + break; + case DBR_STRING: + if (strtol(&((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], NULL, 0) != ((epicsInt32*)p2)[i]) { + testDiag("at index=%d: field log has '%s', should be '%d'", + i, &((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], ((epicsInt32*)p2)[i]); + return 0; + } + break; + default: + return 0; + } + } + return 1; +} + +static void createAndOpen(const char *chan, const char *json, const char *type, dbChannel**pch, short no) { + ELLNODE *node; + char name[80]; + + strncpy(name, chan, sizeof(name)-1); + strncat(name, json, sizeof(name)-strlen(name)-1); + + testOk(!!(*pch = dbChannelCreate(name)), "dbChannel with plugin arr %s created", type); + testOk((ellCount(&(*pch)->filters) == no), "channel has %d filter(s) in filter list", no); + + testOk(!(dbChannelOpen(*pch)), "dbChannel with plugin arr opened"); + + node = ellFirst(&(*pch)->pre_chain); + (void) CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&(*pch)->pre_chain) == 0), "arr has no filter in pre chain"); + + node = ellFirst(&(*pch)->post_chain); + (void) CONTAINER(node, chFilter, post_node); + testOk((ellCount(&(*pch)->post_chain) == no), + "arr has %d filter(s) in post chain", no); +} + +static void testHead (const char *title, const char *typ = "") { + const char *line = "------------------------------------------------------------------------------"; + testDiag("%s", line); + testDiag(title, typ); + testDiag("%s", line); +} + +#define TEST1(Size, Offset, Incr, Text) \ + testDiag("Offset: %d (%s)", Offset, Text); \ + off = Offset; \ + (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \ + pfl = db_create_read_log(pch); \ + testOk(pfl->type == dbfl_type_rec, "original field log has type rec"); \ + pfl2 = dbChannelRunPostChain(pch, pfl); \ + testOk(pfl2 == pfl, "call does not drop or replace field_log"); \ + testOk(pfl->type == dbfl_type_ref, "filtered field log has type ref"); \ + testOk(fl_equals_array(dbr_type, pfl2, ar##Size##_##Offset##_##Incr), "array data correct"); \ + db_delete_field_log(pfl); + +static void check(short dbr_type) { + dbChannel *pch; + db_field_log *pfl, *pfl2; + dbAddr valaddr; + dbAddr offaddr; + const char *offname = NULL, *valname = NULL, *typname = NULL; + epicsInt32 ar[10] = {10,11,12,13,14,15,16,17,18,19}; + epicsInt32 *ar10_0_1 = ar; + epicsInt32 ar10_4_1[10] = {14,15,16,17,18,19,10,11,12,13}; + epicsInt32 ar5_0_1[10] = {12,13,14,15,16}; + epicsInt32 ar5_3_1[10] = {15,16,17,18,19}; + epicsInt32 ar5_5_1[10] = {17,18,19,10,11}; + epicsInt32 ar5_9_1[10] = {11,12,13,14,15}; + epicsInt32 ar5_0_2[10] = {12,14,16}; + epicsInt32 ar5_3_2[10] = {15,17,19}; + epicsInt32 ar5_5_2[10] = {17,19,11}; + epicsInt32 ar5_9_2[10] = {11,13,15}; + epicsInt32 ar5_0_3[10] = {12,15}; + epicsInt32 ar5_3_3[10] = {15,18}; + epicsInt32 ar5_5_3[10] = {17,10}; + epicsInt32 ar5_9_3[10] = {11,14}; + epicsInt32 off = 0; + + switch (dbr_type) { + case DBR_LONG: + offname = "x.OFF"; + valname = "x.VAL"; + typname = "long"; + break; + case DBR_DOUBLE: + offname = "y.OFF"; + valname = "y.VAL"; + typname = "double"; + break; + case DBR_STRING: + offname = "z.OFF"; + valname = "z.VAL"; + typname = "string"; + break; + default: + testDiag("Invalid data type %d", dbr_type); + } + + (void) dbNameToAddr(offname, &offaddr); + + (void) dbNameToAddr(valname, &valaddr); + (void) dbPutField(&valaddr, DBR_LONG, ar, 10); + + /* Default: should not change anything */ + + testHead("Ten %s elements from rec, increment 1, full size (default)", typname); + createAndOpen(valname, "{\"arr\":{}}", "(default)", &pch, 1); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + TEST1(10, 0, 1, "no offset"); + TEST1(10, 4, 1, "wrapped"); + dbChannelDelete(pch); + + testHead("Ten %s elements from rec, increment 1, out-of-bound start parameter", typname); + createAndOpen(valname, "{\"arr\":{\"s\":-500}}", "out-of-bound start", &pch, 1); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + TEST1(10, 4, 1, "wrapped"); + dbChannelDelete(pch); + + testHead("Ten %s elements from rec, increment 1, out-of-bound end parameter", typname); + createAndOpen(valname, "{\"arr\":{\"e\":500}}", "out-of-bound end", &pch, 1); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + TEST1(10, 4, 1, "wrapped"); + dbChannelDelete(pch); + + testHead("Ten %s elements from rec, increment 1, zero increment parameter", typname); + createAndOpen(valname, "{\"arr\":{\"i\":0}}", "zero increment", &pch, 1); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + TEST1(10, 4, 1, "wrapped"); + dbChannelDelete(pch); + + testHead("Ten %s elements from rec, increment 1, invalid increment parameter", typname); + createAndOpen(valname, "{\"arr\":{\"i\":-30}}", "invalid increment", &pch, 1); + testOk(pch->final_type == valaddr.field_type, + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); + testOk(pch->final_no_elements == valaddr.no_elements, + "final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); + TEST1(10, 4, 1, "wrapped"); + dbChannelDelete(pch); + +#define TEST5(Incr, Left, Right, Type) \ + testHead("Five %s elements from rec, increment " #Incr ", " Type " addressing", typname); \ + createAndOpen(valname, "{\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \ + "(" #Left ":" #Incr ":" #Right ")", &pch, 1); \ + testOk(pch->final_type == valaddr.field_type, \ + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); \ + testOk(pch->final_no_elements == 4 / Incr + 1, \ + "final no_elements correct (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); \ + TEST1(5, 0, Incr, "no offset"); \ + TEST1(5, 3, Incr, "from upper block"); \ + TEST1(5, 5, Incr, "wrapped"); \ + TEST1(5, 9, Incr, "from lower block"); \ + dbChannelDelete(pch); + + /* Contiguous block of 5 */ + + TEST5(1, 2, 6, "regular"); + TEST5(1, -8, 6, "left side from-end"); + TEST5(1, 2, -4, "right side from-end"); + TEST5(1, -8, -4, "both sides from-end"); + + /* 5 elements with increment 2 */ + + TEST5(2, 2, 6, "regular"); + TEST5(2, -8, 6, "left side from-end"); + TEST5(2, 2, -4, "right side from-end"); + TEST5(2, -8, -4, "both sides from-end"); + + /* 5 elements with increment 3 */ + + TEST5(3, 2, 6, "regular"); + TEST5(3, -8, 6, "left side from-end"); + TEST5(3, 2, -4, "right side from-end"); + TEST5(3, -8, -4, "both sides from-end"); + + /* From buffer (plugin chain) */ + +#define TEST5B(Incr, Left, Right, Type) \ + testHead("Five %s elements from buffer, increment " #Incr ", " Type " addressing", typname); \ + createAndOpen(valname, "{\"arr\":{},\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \ + "(" #Left ":" #Incr ":" #Right ")", &pch, 2); \ + testOk(pch->final_type == valaddr.field_type, \ + "final type unchanged (%d->%d)", valaddr.field_type, pch->final_type); \ + testOk(pch->final_no_elements == 4 / Incr + 1, \ + "final no_elements correct (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); \ + TEST1(5, 0, Incr, "no offset"); \ + dbChannelDelete(pch); + + /* Contiguous block of 5 */ + + TEST5B(1, 2, 6, "regular"); + TEST5B(1, -8, 6, "left side from-end"); + TEST5B(1, 2, -4, "right side from-end"); + TEST5B(1, -8, -4, "both sides from-end"); + + /* 5 elements with increment 2 */ + + TEST5B(2, 2, 6, "regular"); + TEST5B(2, -8, 6, "left side from-end"); + TEST5B(2, 2, -4, "right side from-end"); + TEST5B(2, -8, -4, "both sides from-end"); + + /* 5 elements with increment 3 */ + + TEST5B(3, 2, 6, "regular"); + TEST5B(3, -8, 6, "left side from-end"); + TEST5B(3, 2, -4, "right side from-end"); + TEST5B(3, -8, -4, "both sides from-end"); +} + +MAIN(arrTest) +{ + dbEventCtx evtctx; + const chFilterPlugin *plug; + char arr[] = "arr"; + + testPlan(1402); + + /* Prepare the IOC */ + + epicsEnvSet("EPICS_CA_SERVER_PORT", server_port); + + testdbPrepare(); + + testdbReadDatabase("filterTest.dbd", NULL, NULL); + + filterTest_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("arrTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + /* Start the IOC */ + + evtctx = db_init_events(); + + testOk(!!(plug = dbFindFilter(arr, strlen(arr))), "plugin arr registered correctly"); + + check(DBR_LONG); + check(DBR_DOUBLE); + check(DBR_STRING); + + db_close_events(evtctx); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/filters/arrTest.db b/modules/database/test/std/filters/arrTest.db new file mode 100644 index 000000000..467cf0d08 --- /dev/null +++ b/modules/database/test/std/filters/arrTest.db @@ -0,0 +1,15 @@ +record(arr, "x") { + field(DESC, "test array record") + field(NELM, "10") + field(FTVL, "LONG") +} +record(arr, "y") { + field(DESC, "test array record") + field(NELM, "10") + field(FTVL, "DOUBLE") +} +record(arr, "z") { + field(DESC, "test array record") + field(NELM, "10") + field(FTVL, "STRING") +} diff --git a/modules/database/test/std/filters/dbndTest.c b/modules/database/test/std/filters/dbndTest.c new file mode 100644 index 000000000..b35b9a6cc --- /dev/null +++ b/modules/database/test/std/filters/dbndTest.c @@ -0,0 +1,285 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "db_field_log.h" +#include "dbCommon.h" +#include "registry.h" +#include "errlog.h" +#include "chfPlugin.h" +#include "epicsUnitTest.h" +#include "dbUnitTest.h" +#include "epicsTime.h" +#include "dbmf.h" +#include "testMain.h" +#include "osiFileName.h" + +#define PATTERN 0x55 + +void filterTest_registerRecordDeviceDriver(struct dbBase *); + +static db_field_log fl; + +static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { + return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); +} + +static void fl_setup(dbChannel *chan, db_field_log *pfl) { + struct dbCommon *prec = dbChannelRecord(chan); + + pfl->ctx = dbfl_context_read; + pfl->type = dbfl_type_val; + pfl->stat = prec->stat; + pfl->sevr = prec->sevr; + pfl->time = prec->time; + pfl->field_type = dbChannelFieldType(chan); + pfl->no_elements = dbChannelElements(chan); + /* + * use memcpy to avoid a bus error on + * union copy of char in the db at an odd + * address + */ + memcpy(&pfl->u.v.field, + dbChannelField(chan), + dbChannelFieldSize(chan)); +} + +static void changeValue(db_field_log *pfl2, long val) { + pfl2->u.v.field.dbf_long = val; + testDiag("new value: %ld", val); +} + +static void mustPassOnce(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + db_field_log *pfl; + + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must pass once", m, d); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 1 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 1 does not change field_log data"); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(NULL == pfl, "call 2 drops field_log"); +} + +static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + db_field_log *pfl; + + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must drop", m, d); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(NULL == pfl, "call 1 drops field_log"); +} + +static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + db_field_log *pfl; + + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must pass twice", m, d); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 1 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 1 does not change field_log data"); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 2 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 2 does not change field_log data"); +} + +static void testHead (char* title) { + testDiag("--------------------------------------------------------"); + testDiag("%s", title); + testDiag("--------------------------------------------------------"); +} + +MAIN(dbndTest) +{ + dbChannel *pch; + chFilter *filter; + const chFilterPlugin *plug; + char dbnd[] = "dbnd"; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + db_field_log *pfl2; + db_field_log fl1; + dbEventCtx evtctx; + + testPlan(59); + + testdbPrepare(); + + testdbReadDatabase("filterTest.dbd", NULL, NULL); + + filterTest_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + evtctx = db_init_events(); + + testOk(!!(plug = dbFindFilter(dbnd, strlen(dbnd))), "plugin dbnd registered correctly"); + + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{}}")), "dbChannel with plugin dbnd (delta=0) created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !!(arg_out), "register_pre registers one filter with argument"); + testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg != NULL), + "dbnd has one filter with argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "dbnd has no filter in post chain"); + + /* Field logs of type ref and rec: pass any update */ + + testHead("Field logs of type ref and rec"); + fl1.type = dbfl_type_rec; + mustPassTwice(pch, &fl1, "abs field_log=rec", 0., 0); + + fl1.type = dbfl_type_ref; + mustPassTwice(pch, &fl1, "abs field_log=ref", 0., 0); + + /* Delta = 0: pass any change */ + + testHead("Delta = 0: pass any change"); + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 0., 0); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 0., 1); + + dbChannelDelete(pch); + + /* Delta = -1: pass any update */ + + testHead("Delta = -1: pass any update"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":-1.0}}")), "dbChannel with plugin dbnd (delta=-1) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassTwice(pch, pfl2, "abs", -1., 0); + mustPassTwice(pch, pfl2, "abs", -1., 1); + + db_delete_field_log(pfl2); + dbChannelDelete(pch); + + /* Delta = absolute */ + + testHead("Delta = absolute"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":3}}")), "dbChannel with plugin dbnd (delta=3) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 3., 1); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustDrop(pch, pfl2, "abs", 3., 3); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustDrop(pch, pfl2, "abs", 3., 4); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 3., 5); + + dbChannelDelete(pch); + + /* Delta = relative */ + + testHead("Delta = relative"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"m\":\"rel\",\"d\":50}}")), + "dbChannel with plugin dbnd (mode=rel, delta=50) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "rel", 50., 1); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "rel", 50., 2); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustDrop(pch, pfl2, "rel", 50., 3); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "rel", 50., 4); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustDrop(pch, pfl2, "rel", 50., 5); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustDrop(pch, pfl2, "rel", 50., 6); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "rel", 50., 7); + + dbChannelDelete(pch); + + db_close_events(evtctx); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/filters/epicsRunFilterTests.c b/modules/database/test/std/filters/epicsRunFilterTests.c new file mode 100644 index 000000000..236364391 --- /dev/null +++ b/modules/database/test/std/filters/epicsRunFilterTests.c @@ -0,0 +1,33 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Run filter tests as a batch. + */ + +#include "epicsUnitTest.h" +#include "epicsExit.h" +#include "dbmf.h" + +int tsTest(void); +int dbndTest(void); +int syncTest(void); +int arrTest(void); + +void epicsRunFilterTests(void) +{ + testHarness(); + + runTest(tsTest); + runTest(dbndTest); + runTest(syncTest); + runTest(arrTest); + + dbmfFreeChunks(); + + epicsExit(0); /* Trigger test harness */ +} diff --git a/modules/database/test/std/filters/rtemsTestHarness.c b/modules/database/test/std/filters/rtemsTestHarness.c new file mode 100644 index 000000000..5215c7775 --- /dev/null +++ b/modules/database/test/std/filters/rtemsTestHarness.c @@ -0,0 +1,14 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +extern void epicsRunFilterTests(void); + +int main(int argc, char **argv) +{ + epicsRunFilterTests(); /* calls epicsExit(0) */ + return 0; +} diff --git a/modules/database/test/std/filters/syncTest.c b/modules/database/test/std/filters/syncTest.c new file mode 100644 index 000000000..9af44afd7 --- /dev/null +++ b/modules/database/test/std/filters/syncTest.c @@ -0,0 +1,378 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "db_field_log.h" +#include "dbCommon.h" +#include "dbChannel.h" +#include "registry.h" +#include "chfPlugin.h" +#include "errlog.h" +#include "dbmf.h" +#include "epicsUnitTest.h" +#include "dbUnitTest.h" +#include "epicsTime.h" +#include "dbState.h" +#include "testMain.h" +#include "osiFileName.h" + +#define PATTERN 0x55 + +void filterTest_registerRecordDeviceDriver(struct dbBase *); + +static db_field_log fl; +static dbStateId red; + +static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { + return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); +} + +static void fl_setup(dbChannel *chan, db_field_log *pfl, long val) { + struct dbCommon *prec = dbChannelRecord(chan); + + pfl->ctx = dbfl_context_event; + pfl->type = dbfl_type_val; + pfl->stat = prec->stat; + pfl->sevr = prec->sevr; + pfl->time = prec->time; + pfl->field_type = DBF_LONG; + pfl->no_elements = 1; + /* + * use memcpy to avoid a bus error on + * union copy of char in the db at an odd + * address + */ + memcpy(&pfl->u.v.field, + dbChannelField(chan), + dbChannelFieldSize(chan)); + pfl->u.v.field.dbf_long = val; +} + +static void testHead (char* title) { + testDiag("--------------------------------------------------------"); + testDiag("%s", title); + testDiag("--------------------------------------------------------"); +} + +static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m) { + db_field_log *pfl = dbChannelRunPreChain(pch, pfl2); + testOk(NULL == pfl, "filter drops field_log (%s)", m); +} + +static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m) { + db_field_log *pfl; + + testDiag("%s: filter must pass twice", m); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 1 does not drop or replace field_log"); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 2 does not drop or replace field_log"); +} + +static void mustPassOld(dbChannel *pch, db_field_log *old, db_field_log *cur, char* m) { + db_field_log *pfl = dbChannelRunPreChain(pch, cur); + + testOk(old == pfl, "filter passes previous field log (%s)", m); +} + +static void mustPass(dbChannel *pch, db_field_log *cur, char* m) { + db_field_log *pfl = dbChannelRunPreChain(pch, cur); + + testOk(cur == pfl, "filter passes field_log (%s)", m); +} + +static void checkCtxRead(dbChannel *pch, dbStateId id) { + fl.ctx = dbfl_context_read; + dbStateClear(id); + mustPassTwice(pch, &fl, "ctx='read', state=FALSE"); + dbStateSet(id); + mustPassTwice(pch, &fl, "ctx='read', state=TRUE"); + dbStateClear(id); + mustPassTwice(pch, &fl, "ctx='read', state=FALSE"); + fl.ctx = dbfl_context_event; +} + +static void checkAndOpenChannel(dbChannel *pch, const chFilterPlugin *plug) { + ELLNODE *node; + chFilter *filter; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + db_field_log fl1; + + testDiag("Test filter structure and open channel"); + + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !!(arg_out), "register_pre registers one filter with argument"); + testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin sync opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg != NULL), + "sync has one filter with argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "sync has no filter in post chain"); + + checkCtxRead(pch, red); +} + +MAIN(syncTest) +{ + dbChannel *pch; + const chFilterPlugin *plug; + char myname[] = "sync"; + db_field_log *pfl[10]; + int i; + dbEventCtx evtctx; + + testPlan(139); + + testdbPrepare(); + + testdbReadDatabase("filterTest.dbd", NULL, NULL); + + filterTest_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + evtctx = db_init_events(); + + testOk(!!(plug = dbFindFilter(myname, strlen(myname))), "plugin %s registered correctly", myname); + testOk(!!(red = dbStateCreate("red")), "state 'red' created successfully"); + + /* nonexisting state */ + testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"blue\"}}")), + "dbChannel with sync (m='while' s='blue') (nonex state) failed"); + /* missing state */ + testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\"}}")), + "dbChannel with sync (m='while') (no state) failed"); + /* missing mode */ + testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"s\":\"red\"}}")), + "dbChannel with sync (s='red') (no mode) failed"); + + /* mode WHILE */ + + testHead("Mode WHILE (m='while', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='while' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustDrop(pch, pfl[0], "state=FALSE, log0"); + mustDrop(pch, pfl[1], "state=FALSE, log1"); + mustDrop(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustPass(pch, pfl[3], "state=TRUE, log3"); + mustPass(pch, pfl[4], "state=TRUE, log4"); + mustPass(pch, pfl[5], "state=TRUE, log5"); + dbStateClear(red); + mustDrop(pch, pfl[6], "state=FALSE, log6"); + mustDrop(pch, pfl[7], "state=FALSE, log7"); + mustDrop(pch, pfl[8], "state=FALSE, log8"); + + for (i = 0; i < 10; i++) + db_delete_field_log(pfl[i]); + + dbChannelDelete(pch); + + /* mode UNLESS */ + + testHead("Mode UNLESS (m='unless', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"unless\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='unless' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustPass(pch, pfl[0], "state=FALSE, log0"); + mustPass(pch, pfl[1], "state=FALSE, log1"); + mustPass(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustDrop(pch, pfl[3], "state=TRUE, log3"); + mustDrop(pch, pfl[4], "state=TRUE, log4"); + mustDrop(pch, pfl[5], "state=TRUE, log5"); + dbStateClear(red); + mustPass(pch, pfl[6], "state=FALSE, log6"); + mustPass(pch, pfl[7], "state=FALSE, log7"); + mustPass(pch, pfl[8], "state=FALSE, log8"); + + for (i = 0; i < 10; i++) + db_delete_field_log(pfl[i]); + + dbChannelDelete(pch); + + /* mode BEFORE */ + + testHead("Mode BEFORE (m='before', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"before\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='before' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustDrop(pch, pfl[0], "state=FALSE, log0"); + mustDrop(pch, pfl[1], "state=FALSE, log1"); + mustDrop(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustPassOld(pch, pfl[2], pfl[3], "state=TRUE, log3, pass=log2"); + mustDrop(pch, pfl[4], "state=TRUE, log4"); + mustDrop(pch, pfl[5], "state=TRUE, log5"); + mustDrop(pch, pfl[6], "state=TRUE, log6"); + dbStateClear(red); + mustDrop(pch, pfl[7], "state=FALSE, log7"); + mustDrop(pch, pfl[8], "state=FALSE, log8"); + mustDrop(pch, pfl[9], "state=FALSE, log9"); + + db_delete_field_log(pfl[2]); + + dbChannelDelete(pch); + + /* mode FIRST */ + + testHead("Mode FIRST (m='first', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"first\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='first' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustDrop(pch, pfl[0], "state=FALSE, log0"); + mustDrop(pch, pfl[1], "state=FALSE, log1"); + mustDrop(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustPass(pch, pfl[3], "state=TRUE, log3"); + mustDrop(pch, pfl[4], "state=TRUE, log4"); + mustDrop(pch, pfl[5], "state=TRUE, log5"); + dbStateClear(red); + mustDrop(pch, pfl[6], "state=FALSE, log6"); + mustDrop(pch, pfl[7], "state=FALSE, log7"); + mustDrop(pch, pfl[8], "state=FALSE, log8"); + + db_delete_field_log(pfl[3]); + db_delete_field_log(pfl[9]); + + dbChannelDelete(pch); + + /* mode LAST */ + + testHead("Mode LAST (m='last', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"last\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='last' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustDrop(pch, pfl[0], "state=FALSE, log0"); + mustDrop(pch, pfl[1], "state=FALSE, log1"); + mustDrop(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustDrop(pch, pfl[3], "state=TRUE, log3"); + mustDrop(pch, pfl[4], "state=TRUE, log4"); + mustDrop(pch, pfl[5], "state=TRUE, log5"); + dbStateClear(red); + mustPassOld(pch, pfl[5], pfl[6], "state=TRUE, log6, pass=log5"); + mustDrop(pch, pfl[7], "state=FALSE, log7"); + mustDrop(pch, pfl[8], "state=FALSE, log8"); + mustDrop(pch, pfl[9], "state=FALSE, log9"); + + db_delete_field_log(pfl[5]); + + dbChannelDelete(pch); + + /* mode AFTER */ + + testHead("Mode AFTER (m='after', s='red')"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"after\",\"s\":\"red\"}}")), + "dbChannel with plugin sync (m='after' s='red') created"); + + checkAndOpenChannel(pch, plug); + + for (i = 0; i < 10; i++) { + pfl[i] = db_create_read_log(pch); + fl_setup(pch, pfl[i], 120 + i); + } + + testDiag("Test event stream"); + + dbStateClear(red); + mustDrop(pch, pfl[0], "state=FALSE, log0"); + mustDrop(pch, pfl[1], "state=FALSE, log1"); + mustDrop(pch, pfl[2], "state=FALSE, log2"); + dbStateSet(red); + mustDrop(pch, pfl[3], "state=TRUE, log3"); + mustDrop(pch, pfl[4], "state=TRUE, log4"); + mustDrop(pch, pfl[5], "state=TRUE, log5"); + dbStateClear(red); + mustPass(pch, pfl[6], "state=FALSE, log6"); + mustDrop(pch, pfl[7], "state=FALSE, log7"); + mustDrop(pch, pfl[8], "state=FALSE, log8"); + + db_delete_field_log(pfl[6]); + db_delete_field_log(pfl[9]); + + dbChannelDelete(pch); + + db_close_events(evtctx); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/filters/tsTest.c b/modules/database/test/std/filters/tsTest.c new file mode 100644 index 000000000..0315ab442 --- /dev/null +++ b/modules/database/test/std/filters/tsTest.c @@ -0,0 +1,119 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "chfPlugin.h" +#include "errlog.h" +#include "epicsUnitTest.h" +#include "dbUnitTest.h" +#include "registry.h" +#include "dbmf.h" +#include "epicsTime.h" +#include "testMain.h" +#include "osiFileName.h" + +#define PATTERN 0x55 + +void filterTest_registerRecordDeviceDriver(struct dbBase *); + +static db_field_log fl; + +static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { + return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); +} + +static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) { + db_field_log fl1 = *pfl1; + + fl1.time = pfl2->time; + return fl_equal(&fl1, pfl2); +} + +MAIN(tsTest) +{ + dbChannel *pch; + chFilter *filter; + const chFilterPlugin *plug; + char ts[] = "ts"; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + db_field_log fl1; + db_field_log *pfl2; + epicsTimeStamp stamp, now; + dbEventCtx evtctx; + + testPlan(12); + + testdbPrepare(); + + testdbReadDatabase("filterTest.dbd", NULL, NULL); + + filterTest_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("xRecord.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + evtctx = db_init_events(); + + testOk(!!(plug = dbFindFilter(ts, strlen(ts))), "plugin ts registered correctly"); + + testOk(!!(pch = dbChannelCreate("x.VAL{\"ts\":{}}")), "dbChannel with plugin ts created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument"); + testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL), + "ts has one filter w/o argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + pfl2 = dbChannelRunPreChain(pch, &fl1); + testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log"); + testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data"); + + testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel"); + stamp = pfl2->time; + db_delete_field_log(pfl2); + + pfl2 = dbChannelRunPreChain(pch, &fl1); + epicsTimeGetCurrent(&now); + testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 && + epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0, + "ts filter sets time stamp to \"now\""); + + dbChannelDelete(pch); + + db_close_events(evtctx); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/filters/xRecord.c b/modules/database/test/std/filters/xRecord.c new file mode 100644 index 000000000..568fbb838 --- /dev/null +++ b/modules/database/test/std/filters/xRecord.c @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Andrew Johnson + * Ralph Lange + */ + +#include "dbAccessDefs.h" +#include + +#define GEN_SIZE_OFFSET +#include "xRecord.h" + +#include + +static rset xRSET; +epicsExportAddress(rset,xRSET); diff --git a/modules/database/test/std/filters/xRecord.db b/modules/database/test/std/filters/xRecord.db new file mode 100644 index 000000000..a6fa08e40 --- /dev/null +++ b/modules/database/test/std/filters/xRecord.db @@ -0,0 +1,2 @@ +record(x, x) {} + diff --git a/modules/database/test/std/filters/xRecord.dbd b/modules/database/test/std/filters/xRecord.dbd new file mode 100644 index 000000000..fd59d1780 --- /dev/null +++ b/modules/database/test/std/filters/xRecord.dbd @@ -0,0 +1,8 @@ +# This is a combined minimal DBD and DB file + +recordtype(x) { + include "dbCommon.dbd" + field(VAL, DBF_LONG) { + prompt("Value") + } +} diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile new file mode 100644 index 000000000..892f6cc10 --- /dev/null +++ b/modules/database/test/std/rec/Makefile @@ -0,0 +1,135 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../../.. +include $(TOP)/configure/CONFIG + +TESTLIBRARY = dbRecStdTest + +dbRecStdTest_SRCS += asTestLib.c +dbRecStdTest_LIBS += dbRecStd dbCore ca Com + +PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com + +TARGETS += $(COMMON_DIR)/recTestIoc.dbd +DBDDEPENDS_FILES += recTestIoc.dbd$(DEP) +recTestIoc_DBD = base.dbd +TESTFILES += $(COMMON_DIR)/recTestIoc.dbd + +testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += asTestIoc_registerRecordDeviceDriver.cpp + +TESTPROD_HOST += arrayOpTest +arrayOpTest_SRCS += arrayOpTest.c +arrayOpTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += arrayOpTest.c +TESTFILES += ../arrayOpTest.db +TESTS += arrayOpTest + +TESTPROD_HOST += recMiscTest +recMiscTest_SRCS += recMiscTest.c +recMiscTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += recMiscTest.c +TESTFILES += ../recMiscTest.db +TESTS += recMiscTest + +TESTPROD_HOST += linkRetargetLinkTest +linkRetargetLinkTest_SRCS += linkRetargetLinkTest.c +linkRetargetLinkTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += linkRetargetLinkTest.c +TESTFILES += ../linkRetargetLink.db +TESTS += linkRetargetLinkTest + +TESTPROD_HOST += linkInitTest +linkInitTest_SRCS += linkInitTest.c +linkInitTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += linkInitTest.c +TESTFILES += ../linkInitTest.db +TESTS += linkInitTest + +TESTPROD_HOST += compressTest +compressTest_SRCS += compressTest.c +compressTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += compressTest.c +TESTFILES += ../compressTest.db +TESTS += compressTest + +TESTPROD_HOST += asyncSoftTest +asyncSoftTest_SRCS += asyncSoftTest.c +asyncSoftTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += asyncSoftTest.c +TESTFILES += ../asyncSoftTest.db +TESTS += asyncSoftTest + +TESTPROD_HOST += softTest +softTest_SRCS += softTest.c +softTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += softTest.c +TESTFILES += ../softTest.db +TESTS += softTest + +TARGETS += $(COMMON_DIR)/asTestIoc.dbd +DBDDEPENDS_FILES += asTestIoc.dbd$(DEP) +asTestIoc_DBD += base.dbd +asTestIoc_DBD += asTest.dbd +TESTPROD_HOST += asTest +asTest_SRCS += asTest.c +asTest_SRCS += asTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += asTest.c +TESTFILES += $(COMMON_DIR)/asTestIoc.dbd ../asTest.db +TESTS += asTest + +TARGETS += $(COMMON_DIR)/analogMonitorTest.dbd +DBDDEPENDS_FILES += analogMonitorTest.dbd$(DEP) +analogMonitorTest_DBD += base.dbd +TESTPROD_HOST += analogMonitorTest +analogMonitorTest_SRCS += analogMonitorTest.c +analogMonitorTest_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += analogMonitorTest.c +testHarness_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/analogMonitorTest.dbd ../analogMonitorTest.db +TESTS += analogMonitorTest + +TARGETS += $(COMMON_DIR)/regressTest.dbd +DBDDEPENDS_FILES += regressTest.dbd$(DEP) +regressTest_DBD += base.dbd +TESTPROD_HOST += regressTest +regressTest_SRCS += regressTest.c +regressTest_SRCS += regressTest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/regressTest.dbd ../regressArray1.db ../regressHex.db ../regressLinkMS.db +TESTS += regressTest + +TARGETS += $(COMMON_DIR)/simmTest.dbd +TARGETS += $(COMMON_DIR)/simmTest.db +DBDDEPENDS_FILES += simmTest.dbd$(DEP) +DBDDEPENDS_FILES += simmTest.db$(DEP) +simmTest_DBD += base.dbd +TESTPROD_HOST += simmTest +simmTest_SRCS += simmTest.c +simmTest_SRCS += simmTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += simmTest.c +testHarness_SRCS += simmTest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/simmTest.dbd $(COMMON_DIR)/simmTest.db +TESTS += simmTest + +# epicsRunRecordTests runs all the test programs in a known working order. +testHarness_SRCS += epicsRunRecordTests.c + +recordTestHarness_SRCS += $(testHarness_SRCS) +recordTestHarness_SRCS_RTEMS += rtemsTestHarness.c + +PROD_vxWorks = recordTestHarness +PROD_RTEMS = recordTestHarness + +TESTSPEC_vxWorks = recordTestHarness.munch; epicsRunRecordTests +TESTSPEC_RTEMS = recordTestHarness.boot; epicsRunRecordTests + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES diff --git a/modules/database/test/std/rec/analogMonitorTest.c b/modules/database/test/std/rec/analogMonitorTest.c new file mode 100644 index 000000000..fb7f61e39 --- /dev/null +++ b/modules/database/test/std/rec/analogMonitorTest.c @@ -0,0 +1,240 @@ +/*************************************************************************\ +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "registryFunction.h" +#include "osiFileName.h" +#include "epicsThread.h" +#include "epicsMath.h" +#include "epicsUnitTest.h" +#include "dbAccessDefs.h" +#include "dbStaticLib.h" +#include "dbEvent.h" +#include "caeventmask.h" +#include "db_field_log.h" +#include "chfPlugin.h" +#include "iocInit.h" +#include "testMain.h" +#include "epicsExport.h" + +/* Test parameters */ + +#define NO_OF_RECORD_TYPES 7 +#define NO_OF_DEADBANDS 3 +#define NO_OF_PATTERNS 16 +#define NO_OF_VALUES_PER_SEQUENCE 2 + +void analogMonitorTest_registerRecordDeviceDriver(struct dbBase *); + +/* Indices for record type, deadband type, deadband value, test number, val in sequence */ +static int irec, ityp, idbnd, itest, iseq; + +/* Records to test with */ +static const char t_Record[NO_OF_RECORD_TYPES][10] = { + {"ai"}, {"ao"}, {"calc"}, {"calcout"}, {"dfanout"}, {"sel"}, {"sub"}, +}; +/* Deadband types to test */ +static const char t_DbndType[2][6] = { {".MDEL"}, {".ADEL"} }; +/* Different deadbands to test with */ +static double t_Deadband[NO_OF_DEADBANDS] = { -1, 0, 1.5 }; +/* Value sequences for each of the 16 tests */ +static double t_SetValues[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE]; +/* Expected updates (1=yes) for each sequence of each test of each deadband */ +static int t_ExpectedUpdates[NO_OF_DEADBANDS][NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE] = { + { /* deadband = -1 */ + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + }, + { /* deadband = 0 */ + {1, 1}, {0, 1}, {0, 0}, {0, 0}, + {1, 1}, {1, 0}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 0}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 0}, + }, + { /* deadband = 1.5 */ + {0, 1}, {0, 1}, {0, 0}, {0, 0}, + {1, 1}, {1, 0}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 0}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 0}, + }, +}; +static int t_ReceivedUpdates[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE]; + +/* Dummy subroutine needed for sub record */ + +static long myTestSub(void *p) { + return 0; +} + + +/* Minimal pre-chain plugin to divert all monitors back into the test (before they hit the queue) */ + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { + double val = *((double*)chan->addr.pfield); + + /* iseq == -1 is the value reset before the test pattern -> do not count */ + if (iseq >= 0) { + t_ReceivedUpdates[itest][iseq] = 1; + testOk((val == t_SetValues[itest][iseq]) || (isnan(val) && isnan(t_SetValues[itest][iseq])), + "update %d pattern %2d with %s = %2.1f (expected %f, got %f)", + iseq, itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd], t_SetValues[itest][iseq], val); + } + db_delete_field_log(pfl); + return NULL; +} + +static void channelRegisterPre(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + *cb_out = filter; +} + +static chfPluginIf pif = { + NULL, /* allocPvt, */ + NULL, /* freePvt, */ + NULL, /* parse_error, */ + NULL, /* parse_ok, */ + NULL, /* channel_open, */ + channelRegisterPre, + NULL, /* channelRegisterPost, */ + NULL, /* channel_report, */ + NULL /* channel_close */ +}; + + +MAIN(analogMonitorTest) +{ + dbChannel *pch; + const chFilterPlugin *plug; + const char test[] = "test"; + dbEventCtx evtctx; + dbEventSubscription subscr; + unsigned mask; + struct dbAddr vaddr, daddr; + double val; + char chan[50]; /* Channel name */ + char cval[50]; /* Name for test values */ + char cdel[50]; /* Name for deadband values */ + + /* Test patterns: + * 0: step less than deadband (of 1.5) + * 1: step larger than deadband (of 1.5) + * 2: no change + * 3: -0.0 -> +0.0 + * ... all possible combinations of steps + * between: finite / NaN / -inf / +inf + */ + t_SetValues[ 0][0] = 1.0; t_SetValues[ 0][1] = 2.0; + t_SetValues[ 1][0] = 0.0; t_SetValues[ 1][1] = 2.0; + t_SetValues[ 2][0] = 0.0; t_SetValues[ 2][1] = 0.0; + t_SetValues[ 3][0] = -0.0; t_SetValues[ 3][1] = 0.0; + t_SetValues[ 4][0] = epicsNAN; t_SetValues[ 4][1] = 1.0; + t_SetValues[ 5][0] = epicsNAN; t_SetValues[ 5][1] = epicsNAN; + t_SetValues[ 6][0] = epicsNAN; t_SetValues[ 6][1] = epicsINF; + t_SetValues[ 7][0] = epicsNAN; t_SetValues[ 7][1] = -epicsINF; + t_SetValues[ 8][0] = epicsINF; t_SetValues[ 8][1] = 1.0; + t_SetValues[ 9][0] = epicsINF; t_SetValues[ 9][1] = epicsNAN; + t_SetValues[10][0] = epicsINF; t_SetValues[10][1] = epicsINF; + t_SetValues[11][0] = epicsINF; t_SetValues[11][1] = -epicsINF; + t_SetValues[12][0] = -epicsINF; t_SetValues[12][1] = 1.0; + t_SetValues[13][0] = -epicsINF; t_SetValues[13][1] = epicsNAN; + t_SetValues[14][0] = -epicsINF; t_SetValues[14][1] = epicsINF; + t_SetValues[15][0] = -epicsINF; t_SetValues[15][1] = -epicsINF; + + registryFunctionAdd("myTestSub", (REGISTRYFUNCTION) myTestSub); + + testPlan(1793); + + if (dbReadDatabase(&pdbbase, "analogMonitorTest.dbd", + "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) + testAbort("Error reading database description 'analogMonitorTest.dbd'"); + + analogMonitorTest_registerRecordDeviceDriver(pdbbase); + + if (dbReadDatabase(&pdbbase, "analogMonitorTest.db", + "." OSI_PATH_LIST_SEPARATOR "..", NULL)) + testAbort("Error reading test database 'analogMonitorTest.db'"); + + /* Start the core IOC (no CA) */ + iocBuildIsolated(); + + evtctx = db_init_events(); + chfPluginRegister(test, &pif, NULL); + + plug = dbFindFilter(test, strlen(test)); + testOk(!!plug, "interceptor plugin registered"); + + /* Loop over all analog record types (one instance each) */ + for (irec = 0; irec < NO_OF_RECORD_TYPES; irec++) { + strcpy(cval, t_Record[irec]); + strcpy(chan, cval); + strcat(chan, ".VAL{\"test\":{}}"); + if ((strcmp(t_Record[irec], "sel") == 0) + || (strcmp(t_Record[irec], "calc") == 0) + || (strcmp(t_Record[irec], "calcout") == 0)) { + strcat(cval, ".A"); + } else { + strcat(cval, ".VAL"); + } + + testDiag("--------------------------------------------------------"); + testDiag("Testing the %s record", t_Record[irec]); + testDiag("--------------------------------------------------------"); + + pch = dbChannelCreate(chan); + testOk(!!pch, "dbChannel with test plugin created"); + testOk(!dbChannelOpen(pch), "dbChannel opened"); + + dbNameToAddr(cval, &vaddr); + + /* Loop over both tested deadband types */ + for (ityp = 0; ityp < 2; ityp++) { + strcpy(cdel, t_Record[irec]); + strcat(cdel, t_DbndType[ityp]); + dbNameToAddr(cdel, &daddr); + mask = (ityp==0?DBE_VALUE:DBE_ARCHIVE); + subscr = db_add_event(evtctx, pch, NULL, NULL, mask); + db_event_enable(subscr); + + /* Loop over all tested deadband values */ + for (idbnd = 0; idbnd < NO_OF_DEADBANDS; idbnd++) { + testDiag("Test %s%s = %g", t_Record[irec], t_DbndType[ityp], t_Deadband[idbnd]); + dbPutField(&daddr, DBR_DOUBLE, (void*) &t_Deadband[idbnd], 1); + memset(t_ReceivedUpdates, 0, sizeof(t_ReceivedUpdates)); + + /* Loop over all test patterns */ + for (itest = 0; itest < NO_OF_PATTERNS; itest++) { + iseq = -1; + val = 0.0; + dbPutField(&vaddr, DBR_DOUBLE, (void*) &val, 1); + + /* Loop over the test sequence */ + for (iseq = 0; iseq < NO_OF_VALUES_PER_SEQUENCE; iseq++) { + dbPutField(&vaddr, DBR_DOUBLE, (void*) &t_SetValues[itest][iseq], 1); + } + /* Check expected vs. actual monitors */ + testOk( (t_ExpectedUpdates[idbnd][itest][0] == t_ReceivedUpdates[itest][0]) && + (t_ExpectedUpdates[idbnd][itest][1] == t_ReceivedUpdates[itest][1]), + "pattern %2d with %s = %2.1f (expected %d-%d, got %d-%d)", + itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd], + t_ExpectedUpdates[idbnd][itest][0], t_ExpectedUpdates[idbnd][itest][1], + t_ReceivedUpdates[itest][0], t_ReceivedUpdates[itest][1]); + } + } + db_cancel_event(subscr); + } + } + iocShutdown(); + return testDone(); +} diff --git a/modules/database/test/std/rec/analogMonitorTest.db b/modules/database/test/std/rec/analogMonitorTest.db new file mode 100644 index 000000000..e67ca1526 --- /dev/null +++ b/modules/database/test/std/rec/analogMonitorTest.db @@ -0,0 +1,13 @@ +record(ai, "ai") {} +record(ao, "ao") {} +record(calc, "calc") { + field(CALC, "A") +} +record(calcout, "calcout") { + field(CALC, "A") +} +record(dfanout, "dfanout") {} +record(sel, "sel") {} +record(sub, "sub") { + field(SNAM, "myTestSub") +} diff --git a/modules/database/test/std/rec/arrayOpTest.c b/modules/database/test/std/rec/arrayOpTest.c new file mode 100644 index 000000000..b37c725e2 --- /dev/null +++ b/modules/database/test/std/rec/arrayOpTest.c @@ -0,0 +1,166 @@ +/*************************************************************************\ +* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "dbTest.h" + +#include "dbUnitTest.h" +#include "errlog.h" + +#include "waveformRecord.h" + +#include "testMain.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void testGetPutArray(void) +{ + double data[4] = {11, 12, 13, 14}; + DBADDR addr, save; + long nreq; + epicsInt32 *pbtr; + waveformRecord *prec; + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("arrayOpTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Test dbGet() and dbPut() from/to an array"); + + prec = (waveformRecord*)testdbRecordPtr("wfrec"); + if(!prec || dbNameToAddr("wfrec", &addr)) + testAbort("Failed to find record wfrec"); + memcpy(&save, &addr, sizeof(save)); + + testDiag("Fetch initial value of %s", prec->name); + + dbScanLock(addr.precord); + testOk(prec->nord==3, "prec->nord==3 (got %d)", prec->nord); + + nreq = NELEMENTS(data); + if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL)) { + testFail("dbGet fails"); + testSkip(1, "failed get"); + } else { + testOk(nreq==3, "nreq==3 (got %ld)", nreq); + testOk1(data[0]==1.0 && data[1]==2.0 && data[2]==3.0); + } + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + addr=save; + + testDiag("Write a new value"); + + data[0] = 4.0; + data[1] = 5.0; + data[2] = 6.0; + data[3] = 7.0; + + dbScanLock(addr.precord); + testOk1(dbPut(&addr, DBF_DOUBLE, &data, NELEMENTS(data))==0); + pbtr = prec->bptr; + testOk(prec->nord==4, "prec->nord==4 (got %u)", prec->nord); + testOk1(pbtr[0]==4 && pbtr[1]==5 && pbtr[2]==6 && pbtr[3]==7); + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + addr=save; + + memset(&data, 0, sizeof(data)); + + testDiag("Reread the value"); + + dbScanLock(addr.precord); + nreq = NELEMENTS(data); + if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL)) + testFail("dbGet fails"); + else { + testOk1(nreq==NELEMENTS(data)); + testOk1(data[0]==4.0 && data[1]==5.0 && data[2]==6.0 && data[3]==7.0); + } + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + + testDiag("Test dbGet() and dbPut() from/to an array of size 1"); + + prec = (waveformRecord*)testdbRecordPtr("wfrec1"); + if(!prec || dbNameToAddr("wfrec1", &addr)) + testAbort("Failed to find record wfrec1"); + memcpy(&save, &addr, sizeof(save)); + + testDiag("Fetch initial value of %s", prec->name); + + dbScanLock(addr.precord); + testOk(prec->nord==0, "prec->nord==0 (got %d)", prec->nord); + + nreq = NELEMENTS(data); + if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL)) + testFail("dbGet fails"); + else { + testOk(nreq==0, "nreq==0 (got %ld)", nreq); + } + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + addr=save; + + testDiag("Write a new value"); + + data[0] = 4.0; + data[1] = 5.0; + data[2] = 6.0; + data[3] = 7.0; + + dbScanLock(addr.precord); + testOk1(dbPut(&addr, DBF_DOUBLE, &data, 1)==0); + pbtr = prec->bptr; + testOk(prec->nord==1, "prec->nord==1 (got %u)", prec->nord); + testOk1(pbtr[0]==4); + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + addr=save; + + memset(&data, 0, sizeof(data)); + + testDiag("Reread the value"); + + dbScanLock(addr.precord); + nreq = NELEMENTS(data); + if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL)) + testFail("dbGet fails"); + else { + testOk1(nreq==1); + testOk1(data[0]==4.0); + } + dbScanUnlock(addr.precord); + + testOk1(memcmp(&addr, &save, sizeof(save))==0); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(arrayOpTest) +{ + testPlan(21); + testGetPutArray(); + return testDone(); +} diff --git a/modules/database/test/std/rec/arrayOpTest.db b/modules/database/test/std/rec/arrayOpTest.db new file mode 100644 index 000000000..1c011e755 --- /dev/null +++ b/modules/database/test/std/rec/arrayOpTest.db @@ -0,0 +1,9 @@ +record(waveform, "wfrec") { + field(NELM, "10") + field(FTVL, "LONG") + field(INP, [1, 2, 3]) +} +record(waveform, "wfrec1") { + field(NELM, "1") + field(FTVL, "LONG") +} diff --git a/modules/database/test/std/rec/asTest.c b/modules/database/test/std/rec/asTest.c new file mode 100644 index 000000000..44ef66fd2 --- /dev/null +++ b/modules/database/test/std/rec/asTest.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * + * Test the hooks that autosave uses during initialization + */ + +#include "string.h" + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "recSup.h" +#include "dbAccess.h" +#include "dbConvert.h" +#include "dbStaticLib.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "osiFileName.h" +#include "initHooks.h" +#include "devSup.h" +#include "errlog.h" + +#include "aoRecord.h" +#include "waveformRecord.h" + +#include "testMain.h" + +epicsShareFunc void testRestore(void); + +#include "epicsExport.h" + +MAIN(asTest) +{ + testPlan(42); + testRestore(); + return testDone(); +} diff --git a/modules/database/test/std/rec/asTest.db b/modules/database/test/std/rec/asTest.db new file mode 100644 index 000000000..6a4eea4bd --- /dev/null +++ b/modules/database/test/std/rec/asTest.db @@ -0,0 +1,12 @@ +record(ao, "rec0") { + field(DESC, "foobar") + field(DTYP, "asTest") + field(VAL, "1") + field(OUT, "rec0.DISV") +} + +record(waveform, "rec1") { + field(DTYP, "asTest") + field(FTVL, "DOUBLE") + field(NELM, "5") +} diff --git a/modules/database/test/std/rec/asTest.dbd b/modules/database/test/std/rec/asTest.dbd new file mode 100644 index 000000000..c53b52a8b --- /dev/null +++ b/modules/database/test/std/rec/asTest.dbd @@ -0,0 +1,2 @@ +device(ao, CONSTANT, devAOasTest, "asTest") +device(waveform, CONSTANT, devWFasTest, "asTest") diff --git a/modules/database/test/std/rec/asTestLib.c b/modules/database/test/std/rec/asTestLib.c new file mode 100644 index 000000000..2043ed770 --- /dev/null +++ b/modules/database/test/std/rec/asTestLib.c @@ -0,0 +1,291 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * + * Test the hooks that autosave uses during initialization + */ + +#include "string.h" + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "recSup.h" +#include "iocsh.h" +#include "dbAccess.h" +#include "dbConvert.h" +#include "dbStaticLib.h" +#include "registry.h" +#include "dbStaticLib.h" +#include "dbStaticPvt.h" +#include "osiFileName.h" +#include "initHooks.h" +#include "devSup.h" +#include "errlog.h" + +#include "aoRecord.h" +#include "waveformRecord.h" + +#include "epicsExport.h" + +static unsigned iran; + +static +int checkGetString(DBENTRY *pent, const char *expect) +{ + dbCommon *prec = pent->precnode->precord; + const char *actual = dbGetString(pent); + int ret = strcmp(actual, expect); + testOk(ret==0, "dbGetString(\"%s.%s\") -> '%s' == '%s'", prec->name, + pent->pflddes->name, actual, expect); + return ret; +} + +static void hookPass0(initHookState state) +{ + DBENTRY entry; + if(state!=initHookAfterInitDevSup) + return; + testDiag("initHookAfterInitDevSup"); + + dbInitEntry(pdbbase, &entry); + + testDiag("restore integer pass0"); + /* rec0.VAL is initially 1, set it to 2 */ + if(dbFindRecord(&entry, "rec0.VAL")==0) { + aoRecord *prec = entry.precnode->precord; + testOk(prec->val==1, "VAL %d==1 (initial value from .db)", (int)prec->val); + checkGetString(&entry, "1"); + testOk1(dbPutString(&entry, "2")==0); + testOk(prec->val==2, "VAL %d==2", (int)prec->val); + checkGetString(&entry, "2"); + } else { + testFail("Missing rec0"); + testSkip(4, "missing record"); + } + + testDiag("restore string pass0"); + if(dbFindRecord(&entry, "rec0.DESC")==0) { + aoRecord *prec = entry.precnode->precord; + testOk1(strcmp(prec->desc, "foobar")==0); + checkGetString(&entry, "foobar"); + testOk1(dbPutString(&entry, "hello")==0); + testOk1(strcmp(prec->desc, "hello")==0); + checkGetString(&entry, "hello"); + } else { + testFail("Missing rec0"); + testSkip(4, "missing record"); + } + + if(dbFindRecord(&entry, "rec1.DESC")==0) { + aoRecord *prec = entry.precnode->precord; + testOk1(strcmp(prec->desc, "")==0); + checkGetString(&entry, ""); + testOk1(dbPutString(&entry, "world")==0); + testOk1(strcmp(prec->desc, "world")==0); + checkGetString(&entry, "world"); + } else { + testFail("Missing rec1"); + testSkip(4, "missing record"); + } + + testDiag("restore link pass0"); + /* rec0.OUT is initially "rec0.DISV", set it to "rec0.SEVR" */ + if(dbFindRecord(&entry, "rec0.OUT")==0) { + aoRecord *prec = entry.precnode->precord; + if(prec->out.type==CONSTANT) + testOk(strcmp(prec->out.text,"rec0.DISV")==0, + "%s==rec0.DISV (initial value from .db)", + prec->out.text); + else + testFail("Wrong link type: %d", (int)prec->out.type); + + checkGetString(&entry, "rec0.DISV"); + + testOk1(dbPutString(&entry, "rec0.SEVR")==0); + } else{ + testFail("Missing rec0"); + testSkip(1, "missing record"); + } + + /* rec0.SDIS is initially NULL, set it to "rec0.STAT" */ + if(dbFindRecord(&entry, "rec0.SDIS")==0) { + aoRecord *prec = entry.precnode->precord; + if(prec->sdis.type==CONSTANT) + testOk1(prec->sdis.value.constantStr==NULL); + else + testFail("Wrong link type: %d", (int)prec->sdis.type); + + testOk1(dbPutString(&entry, "rec0.STAT")==0); + } else{ + testFail("Missing rec0"); + testSkip(1, "missing record"); + } + + /* can't restore array field in pass0 */ + + dbFinishEntry(&entry); +} + +static long initRec0(aoRecord *prec) +{ + DBLINK *plink = &prec->out; + testDiag("init_record(%s)", prec->name); + testOk(prec->val==2, "VAL %d==2 (pass0 value)", (int)prec->val); + prec->val = 3; + testOk(prec->val==3, "VAL %d==3", (int)prec->val); + + testOk1(plink->type==DB_LINK); + if(plink->type==DB_LINK) + testOk(strcmp(plink->value.pv_link.pvname,"rec0.SEVR")==0, + "%s==rec0.SEVR (pass0 value)", plink->value.pv_link.pvname); + else + testFail("Wrong link type"); + + plink = &prec->sdis; + + testOk1(plink->type==DB_LINK); + if(plink->type==DB_LINK) + testOk(strcmp(plink->value.pv_link.pvname,"rec0.STAT")==0, + "%s==rec0.STAT (pass0 value)", plink->value.pv_link.pvname); + else + testFail("Wrong link type"); + + iran |= 1; + return 2; /* we set .VAL, so don't use RVAL */ +} + +static long initRec1(waveformRecord *prec) +{ + testDiag("init_record(%s)", prec->name); + testOk(prec->nord==0, "NORD %d==0", (int)prec->nord); + iran |= 2; + return 0; +} + +static double values[] = {1,2,3,4,5}; + +static void hookPass1(initHookState state) +{ + DBENTRY entry; + DBADDR addr; + if(state!=initHookAfterInitDatabase) + return; + testDiag("initHookAfterInitDatabase"); + + dbInitEntry(pdbbase, &entry); + + if(dbFindRecord(&entry, "rec0.VAL")==0) { + aoRecord *prec = entry.precnode->precord; + testOk(prec->val==3, "VAL %d==3 (init_record value)", (int)prec->val); + testOk1(dbPutString(&entry, "4")==0); + testOk(prec->val==4, "VAL %d==4", (int)prec->val); + } else{ + testFail("Missing rec0"); + testSkip(1, "missing record"); + } + + /* Can't restore links in pass 1 */ + + if(dbNameToAddr("rec1.VAL", &addr)) { + testFail("missing rec1"); + testSkip(3, "missing record"); + } else { + rset *prset = dbGetRset(&addr); + dbfType ftype = addr.field_type; + long count=-1, offset=-1, maxcount = addr.no_elements; + testOk1(prset && prset->get_array_info && prset->put_array_info); + testOk1((*prset->get_array_info)(&addr, &count, &offset)==0); + /* count is ignored */ + testOk1((*dbPutConvertRoutine[DBF_DOUBLE][ftype])(&addr, values, NELEMENTS(values), maxcount,offset)==0); + testOk1((*prset->put_array_info)(&addr, NELEMENTS(values))==0); + } + + dbFinishEntry(&entry); +} + +#if defined(__rtems__) || defined(vxWorks) +void asTestIoc_registerRecordDeviceDriver(struct dbBase *); +#endif + +epicsShareFunc +void testRestore(void) +{ + aoRecord *rec0; + waveformRecord *rec1; + testDiag("test Restore"); + + initHookRegister(hookPass0); + initHookRegister(hookPass1); + + testdbPrepare(); + + testdbReadDatabase("asTestIoc.dbd", NULL, NULL); + + /* since this test has device support it must appear in a + * DLL for windows dynamic builds. + * However, the rRDD function is in the executable, + * and not accessible here. So use iocsh. + * For rtems/vxworks the test harness clears + * iocsh registrations, so iocsh can't work here. + */ +#if defined(__rtems__) || defined(vxWorks) + asTestIoc_registerRecordDeviceDriver(pdbbase); +#else + iocshCmd("asTestIoc_registerRecordDeviceDriver(pdbbase)"); +#endif + + testdbReadDatabase("asTest.db", NULL, NULL); + + rec0 = (aoRecord*)testdbRecordPtr("rec0"); + rec1 = (waveformRecord*)testdbRecordPtr("rec1"); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Post initialization"); + + testOk1(iran==3); + + testOk1(rec0->val==4); + testOk1(rec1->nord==5); + { + double *buf = rec1->bptr; + testOk(buf[0]==1, "buf[0] %f==1", buf[0]); + testOk1(buf[1]==2); + testOk1(buf[2]==3); + testOk1(buf[3]==4); + testOk1(buf[4]==5); + } + + testIocShutdownOk(); + + /* recSup doesn't cleanup after itself */ + free(rec1->bptr); + + testdbCleanup(); +} + +struct dset6 { + dset common; + DEVSUPFUN proc; + DEVSUPFUN linconv; +}; + +static long noop() {return 0;} + +static struct dset6 devAOasTest = { {6, NULL, NULL, (DEVSUPFUN)initRec0, NULL}, (DEVSUPFUN)noop, NULL}; +static struct dset6 devWFasTest = { {6, NULL, NULL, (DEVSUPFUN)initRec1, NULL}, (DEVSUPFUN)noop, NULL}; + +epicsExportAddress(dset, devAOasTest); +epicsExportAddress(dset, devWFasTest); diff --git a/modules/database/test/std/rec/asyncSoftTest.c b/modules/database/test/std/rec/asyncSoftTest.c new file mode 100644 index 000000000..7b494a349 --- /dev/null +++ b/modules/database/test/std/rec/asyncSoftTest.c @@ -0,0 +1,189 @@ +/*************************************************************************\ +* Copyright (c) 2017 UChicago Argonne LLC, as operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "dbStaticLib.h" +#include "dbTest.h" +#include "dbUnitTest.h" +#include "epicsEvent.h" +#include "errlog.h" +#include "registryFunction.h" +#include "subRecord.h" +#include "testMain.h" + +static int startCounter, doneCounter; +static epicsEventId asyncEvent, doneEvent; + +static +long asyncSubr(subRecord *prec) +{ + testDiag("Processing %s, pact=%d", prec->name, prec->pact); + + if (!prec->pact) { + epicsEventTrigger(asyncEvent); + prec->pact = 1; /* Make asynchronous */ + } + + return 0; +} + +static +long doneSubr(subRecord *prec) +{ + epicsEventTrigger(doneEvent); + return 0; +} + +static +void checkAsyncInput(const char *rec, int init, dbCommon *async) +{ + char inp[16], proc[16]; + + testDiag("Checking record '%s'", rec); + + strcpy(inp, rec); + strcat(inp, ".INP"); + strcpy(proc, rec); + strcat(proc, ".PROC"); + + if (init) { + testdbGetFieldEqual(rec, DBF_LONG, init); + + testdbPutFieldOk(inp, DBF_STRING, "async"); + } + + testdbPutFieldOk(proc, DBF_CHAR, 1); + + epicsEventWait(asyncEvent); + testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter); + testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter); + + dbScanLock(async); + async->rset->process(async); + dbScanUnlock(async); + + epicsEventWait(doneEvent); + testdbGetFieldEqual("startCounter", DBF_LONG, startCounter); + testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter); +} + +static +void testAsynInputs(dbCommon *async) +{ + const char * records[] = { + "ai0", "bi0", "di0", "ii0", "li0", "mi0", "si0", NULL, + "bi1", /* bi1 must be first in this group */ + "ai1", "di1", "ii1", "li1", "mi1", "si1", NULL, + }; + const char ** rec = &records[0]; + int init = 1; /* bi1 initializes to 1 */ + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + startCounter = doneCounter = 0; + testdbPutFieldOk("startCounter", DBF_LONG, startCounter); + testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter); + + epicsEventTryWait(asyncEvent); + epicsEventTryWait(doneEvent); + + while (*rec) { /* 1st group don't need initializing */ + checkAsyncInput(*rec++, 0, async); + } + rec++; + while (*rec) { + checkAsyncInput(*rec++, init, async); + init = 9; /* remainder initialize to 9 */ + } + + testDiag("============= Ending %s =============", EPICS_FUNCTION); +} + +static +void checkAsyncOutput(const char *rec, dbCommon *async) +{ + char proc[16]; + + testDiag("Checking record '%s'", rec); + + strcpy(proc, rec); + strcat(proc, ".PROC"); + + testdbPutFieldOk(proc, DBF_CHAR, 1); + + epicsEventWait(asyncEvent); + testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter); + testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter); + + dbScanLock(async); + async->rset->process(async); + dbScanUnlock(async); + + epicsEventWait(doneEvent); + testdbGetFieldEqual("startCounter", DBF_LONG, startCounter); + testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter); +} + +static +void testAsyncOutputs(dbCommon *async) +{ + const char * records[] = { + "ao1", "bo1", "do1", "io1", "lo1", "lso1", "mo1", "so1", NULL, + }; + const char ** rec = &records[0]; + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + startCounter = doneCounter = 0; + testdbPutFieldOk("startCounter", DBF_LONG, startCounter); + testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter); + + epicsEventTryWait(asyncEvent); + epicsEventTryWait(doneEvent); + + while (*rec) { + checkAsyncOutput(*rec++, async); + } + + testDiag("============= Ending %s =============", EPICS_FUNCTION); +} + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(asyncSoftTest) +{ + dbCommon *async; + + testPlan(128); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + registryFunctionAdd("asyncSubr", (REGISTRYFUNCTION) asyncSubr); + registryFunctionAdd("doneSubr", (REGISTRYFUNCTION) doneSubr); + + testdbReadDatabase("asyncSoftTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + async = testdbRecordPtr("async"); + asyncEvent = epicsEventCreate(epicsEventEmpty); + doneEvent = epicsEventCreate(epicsEventEmpty); + + testAsynInputs(async); + testAsyncOutputs(async); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/asyncSoftTest.db b/modules/database/test/std/rec/asyncSoftTest.db new file mode 100644 index 000000000..6cb51422b --- /dev/null +++ b/modules/database/test/std/rec/asyncSoftTest.db @@ -0,0 +1,188 @@ +record(ai, "ai0") { + field(DTYP, "Async Soft Channel") + field(INP, "async") + field(FLNK, "done") +} +record(bi, "bi0") { + field(DTYP, "Async Soft Channel") + field(INP, "async") + field(FLNK, "done") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii0") { + field(DTYP, "Async Soft Channel") + field(INP, "async") + field(FLNK, "done") +} +record(longin, "li0") { + field(DTYP, "Async Soft Channel") + field(INP, "async") + field(FLNK, "done") +} +record(mbbiDirect, "di0") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(INP, "async") + field(FLNK, "done") +} +record(mbbi, "mi0") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(INP, "async") + field(FLNK, "done") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(stringin, "si0") { + field(DTYP, "Async Soft Channel") + field(INP, "async") + field(FLNK, "done") +} + +record(ai, "ai1") { + field(DTYP, "Async Soft Channel") + field(INP, {const:9}) + field(FLNK, "done") +} +record(bi, "bi1") { + field(DTYP, "Async Soft Channel") + field(INP, {const:1}) + field(FLNK, "done") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii1") { + field(DTYP, "Async Soft Channel") + field(INP, {const:9}) + field(FLNK, "done") +} +record(longin, "li1") { + field(DTYP, "Async Soft Channel") + field(INP, {const:9}) + field(FLNK, "done") +} +record(mbbiDirect, "di1") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(INP, {const:9}) + field(FLNK, "done") +} +record(mbbi, "mi1") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(INP, {const:9}) + field(FLNK, "done") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(stringin, "si1") { + field(DTYP, "Async Soft Channel") + field(INP, {const:"9"}) + field(FLNK, "done") +} + +record(sub, "async") { + field(INPA, "startCounter PP") + field(SNAM, "asyncSubr") +} +record(calc, "startCounter") { + field(CALC, "VAL+1") +} +record(sub, "done") { + field(INPA, "doneCounter PP") + field(SNAM, "doneSubr") +} +record(calc, "doneCounter") { + field(CALC, "VAL+1") +} + +record(ao, "ao1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") +} +record(bo, "bo1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64out, "io1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") +} +record(longout, "lo1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") +} +record(mbboDirect, "do1") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(OUT, "async.PROC CA") + field(FLNK, "done") +} +record(mbbo, "mo1") { + field(DTYP, "Async Soft Channel") + field(NOBT, 4) + field(OUT, "async.PROC CA") + field(FLNK, "done") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lso, "lso1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") + field(SIZV, 40) +} +record(stringout, "so1") { + field(DTYP, "Async Soft Channel") + field(OUT, "async.PROC CA") + field(FLNK, "done") +} diff --git a/modules/database/test/std/rec/compressTest.c b/modules/database/test/std/rec/compressTest.c new file mode 100644 index 000000000..99a937115 --- /dev/null +++ b/modules/database/test/std/rec/compressTest.c @@ -0,0 +1,354 @@ +/*************************************************************************\ +* Copyright (c) 2016 Michael Davidsaver +* 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 "aiRecord.h" +#include "compressRecord.h" + +#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B) + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static +void checkArrD(const char *pv, long elen, double a, double b, double c, double d) +{ + double buf[4]; + double expect[4]; + long nReq = NELEMENTS(buf), i; + unsigned match; + DBADDR addr; + + expect[0] = a; + expect[1] = b; + expect[2] = c; + expect[3] = d; + + if (dbNameToAddr(pv, &addr)) + testAbort("Unknown PV '%s'", pv); + + if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL)) + testAbort("Failed to get '%s'", pv); + + match = elen==nReq; + for (i=0; i=0.01) + testDiag("[%ld] -> %f != %f", i, expect[i], buf[i]); + } +} + +static +void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32 c, epicsInt32 d) +{ + epicsInt32 buf[4]; + epicsInt32 expect[4]; + long nReq = NELEMENTS(buf), i; + unsigned match; + DBADDR addr; + + expect[0] = a; + expect[1] = b; + expect[2] = c; + expect[3] = d; + + if (dbNameToAddr(pv, &addr)) + testAbort("Unknown PV '%s'", pv); + + if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL)) + testAbort("Failed to get '%s'", pv); + + match = elen==nReq; + for (i=0; i %d != %d", i, (int)expect[i], (int)buf[i]); + } +} + +static +void testFIFOCirc(void) +{ + aiRecord *vrec; + compressRecord *crec; + double *cbuf; + + testDiag("Test FIFO"); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4"); + + vrec = (aiRecord*)testdbRecordPtr("val"); + crec = (compressRecord*)testdbRecordPtr("comp"); + + eltc(0); + testIocInitOk(); + eltc(1); + + dbScanLock((dbCommon*)crec); + cbuf = crec->bptr; + + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==0); + + testDiag("Push 1.1"); + vrec->val = 1.1; + dbProcess((dbCommon*)crec); + + /* In FIFO mode the valid elements are + * cbuf[(off-nuse-1) % size] through cbuf[(off-1) % size] + */ + testOk1(crec->off==1); + testOk1(crec->inx==0); + testOk1(crec->nuse==1); + testDEq(cbuf[0], 1.1, 0.1); + testDEq(cbuf[1], 0.0, 0.1); + testDEq(cbuf[2], 0.0, 0.1); + testDEq(cbuf[3], 0.0, 0.1); + checkArrD("comp", 1, 1.1, 0, 0, 0); + + testDiag("Push 2.1"); + vrec->val = 2.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==2); + testOk1(crec->inx==0); + testOk1(crec->nuse==2); + testDEq(cbuf[0], 1.1, 0.1); + testDEq(cbuf[1], 2.1, 0.1); + testDEq(cbuf[2], 0.0, 0.1); + testDEq(cbuf[3], 0.0, 0.1); + checkArrD("comp", 2, 1.1, 2.1, 0, 0); + + testDiag("Push 3.1"); + vrec->val = 3.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==3); + testOk1(crec->inx==0); + testOk1(crec->nuse==3); + testDEq(cbuf[0], 1.1, 0.1); + testDEq(cbuf[1], 2.1, 0.1); + testDEq(cbuf[2], 3.1, 0.1); + testDEq(cbuf[3], 0.0, 0.1); + checkArrD("comp", 3, 1.1, 2.1, 3.1, 0); + + testDiag("Push 4.1"); + vrec->val = 4.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 1.1, 0.1); + testDEq(cbuf[1], 2.1, 0.1); + testDEq(cbuf[2], 3.1, 0.1); + testDEq(cbuf[3], 4.1, 0.1); + checkArrD("comp", 4, 1.1, 2.1, 3.1, 4.1); + + testDiag("Push 5.1"); + vrec->val = 5.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==1); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 5.1, 0.1); + testDEq(cbuf[1], 2.1, 0.1); + testDEq(cbuf[2], 3.1, 0.1); + testDEq(cbuf[3], 4.1, 0.1); + checkArrD("comp", 4, 2.1, 3.1, 4.1, 5.1); + + testDiag("Push 6.1"); + vrec->val = 6.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==2); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 5.1, 0.1); + testDEq(cbuf[1], 6.1, 0.1); + testDEq(cbuf[2], 3.1, 0.1); + testDEq(cbuf[3], 4.1, 0.1); + checkArrD("comp", 4, 3.1, 4.1, 5.1, 6.1); + + dbScanUnlock((dbCommon*)crec); + + testDiag("Reset"); + testdbPutFieldOk("comp.RES", DBF_LONG, 0); + + dbScanLock((dbCommon*)crec); + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==0); + checkArrD("comp", 0, 0, 0, 0, 0); + dbScanUnlock((dbCommon*)crec); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static +void testLIFOCirc(void) +{ + aiRecord *vrec; + compressRecord *crec; + double *cbuf; + + testDiag("Test LIFO"); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("compressTest.db", NULL, + "ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4"); + + vrec = (aiRecord*)testdbRecordPtr("val"); + crec = (compressRecord*)testdbRecordPtr("comp"); + + eltc(0); + testIocInitOk(); + eltc(1); + + dbScanLock((dbCommon*)crec); + cbuf = crec->bptr; + + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==0); + + testDiag("Push 1.1"); + vrec->val = 1.1; + dbProcess((dbCommon*)crec); + + testDiag("off %u", crec->off); + testOk1(crec->off==3); + testOk1(crec->inx==0); + testOk1(crec->nuse==1); + testDEq(cbuf[0], 0.0, 0.1); + testDEq(cbuf[1], 0.0, 0.1); + testDEq(cbuf[2], 0.0, 0.1); + testDEq(cbuf[3], 1.1, 0.1); + checkArrD("comp", 1, 1.1, 0, 0, 0); + + testDiag("Push 2.1"); + vrec->val = 2.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==2); + testOk1(crec->inx==0); + testOk1(crec->nuse==2); + testDEq(cbuf[0], 0.0, 0.1); + testDEq(cbuf[1], 0.0, 0.1); + testDEq(cbuf[2], 2.1, 0.1); + testDEq(cbuf[3], 1.1, 0.1); + checkArrD("comp", 2, 2.1, 1.1, 0, 0); + checkArrI("comp", 2, 2, 1, 0, 0); + + testDiag("Push 3.1"); + vrec->val = 3.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==1); + testOk1(crec->inx==0); + testOk1(crec->nuse==3); + testDEq(cbuf[0], 0.0, 0.1); + testDEq(cbuf[1], 3.1, 0.1); + testDEq(cbuf[2], 2.1, 0.1); + testDEq(cbuf[3], 1.1, 0.1); + checkArrD("comp", 3, 3.1, 2.1, 1.1, 0); + checkArrI("comp", 3, 3, 2, 1, 0); + + testDiag("Push 4.1"); + vrec->val = 4.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 4.1, 0.1); + testDEq(cbuf[1], 3.1, 0.1); + testDEq(cbuf[2], 2.1, 0.1); + testDEq(cbuf[3], 1.1, 0.1); + checkArrD("comp", 4, 4.1, 3.1, 2.1, 1.1); + checkArrI("comp", 4, 4, 3, 2, 1); + + testDiag("Push 5.1"); + vrec->val = 5.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==3); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 4.1, 0.1); + testDEq(cbuf[1], 3.1, 0.1); + testDEq(cbuf[2], 2.1, 0.1); + testDEq(cbuf[3], 5.1, 0.1); + checkArrD("comp", 4, 5.1, 4.1, 3.1, 2.1); + checkArrI("comp", 4, 5, 4, 3, 2); + + testDiag("Push 6.1"); + vrec->val = 6.1; + dbProcess((dbCommon*)crec); + + testOk1(crec->off==2); + testOk1(crec->inx==0); + testOk1(crec->nuse==4); + testDEq(cbuf[0], 4.1, 0.1); + testDEq(cbuf[1], 3.1, 0.1); + testDEq(cbuf[2], 6.1, 0.1); + testDEq(cbuf[3], 5.1, 0.1); + checkArrD("comp", 4, 6.1, 5.1, 4.1, 3.1); + + dbScanUnlock((dbCommon*)crec); + + testDiag("Reset"); + testdbPutFieldOk("comp.RES", DBF_LONG, 0); + + dbScanLock((dbCommon*)crec); + testOk1(crec->off==0); + testOk1(crec->inx==0); + testOk1(crec->nuse==0); + checkArrD("comp", 0, 0, 0, 0, 0); + dbScanUnlock((dbCommon*)crec); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(compressTest) +{ + testPlan(116); + testFIFOCirc(); + testLIFOCirc(); + return testDone(); +} diff --git a/modules/database/test/std/rec/compressTest.db b/modules/database/test/std/rec/compressTest.db new file mode 100644 index 000000000..59fc620ba --- /dev/null +++ b/modules/database/test/std/rec/compressTest.db @@ -0,0 +1,7 @@ +record(ai, "val") {} +record(compress, "comp") { + field(INP, "val NPP") + field(ALG, "$(ALG)") + field(BALG,"$(BALG)") + field(NSAM,"$(NSAM)") +} diff --git a/modules/database/test/std/rec/epicsRunRecordTests.c b/modules/database/test/std/rec/epicsRunRecordTests.c new file mode 100644 index 000000000..6214f4aec --- /dev/null +++ b/modules/database/test/std/rec/epicsRunRecordTests.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Run tests as a batch. + */ + +#include "epicsUnitTest.h" +#include "epicsExit.h" + +int analogMonitorTest(void); +int compressTest(void); +int recMiscTest(void); +int arrayOpTest(void); +int asTest(void); +int linkRetargetLinkTest(void); +int linkInitTest(void); +int asyncSoftTest(void); +int simmTest(void); + +void epicsRunRecordTests(void) +{ + testHarness(); + + runTest(analogMonitorTest); + + runTest(compressTest); + + runTest(recMiscTest); + + runTest(arrayOpTest); + + runTest(asTest); + + runTest(linkRetargetLinkTest); + + runTest(linkInitTest); + + runTest(asyncSoftTest); + + runTest(simmTest); + + epicsExit(0); /* Trigger test harness */ +} diff --git a/modules/database/test/std/rec/linkInitTest.c b/modules/database/test/std/rec/linkInitTest.c new file mode 100644 index 000000000..7225beb1c --- /dev/null +++ b/modules/database/test/std/rec/linkInitTest.c @@ -0,0 +1,243 @@ +/*************************************************************************\ +* Copyright (c) 2015 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "alarm.h" +#include "dbUnitTest.h" +#include "errlog.h" +#include "epicsThread.h" + +#include "testMain.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void startTestIoc(const char *dbfile) +{ + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase(dbfile, NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); +} + +static void testLongStringInit() +{ + testDiag("testLongStringInit"); + + startTestIoc("linkInitTest.db"); + + { + const char buf[] = "!----------------------------------------------!"; + testdbGetArrFieldEqual("longstr1.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf); + testdbGetFieldEqual("longstr1.VAL", DBR_STRING, "!--------------------------------------"); + } + + { + const char buf[] = "!----------------------------------------------!"; + testdbGetArrFieldEqual("longstr2.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf); + testdbGetFieldEqual("longstr2.VAL", DBR_STRING, "!--------------------------------------"); + } + + { + const char buf[] = "!----------------------------------------------!"; + testdbGetArrFieldEqual("longstr3.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf); + testdbGetFieldEqual("longstr3.VAL", DBR_STRING, "!--------------------------------------"); + } + + testdbGetFieldEqual("longstr4.VAL", DBR_STRING, "One"); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testCalcInit() +{ + testDiag("testCalcInit"); + + startTestIoc("linkInitTest.db"); + + testdbGetFieldEqual("emptylink.VAL", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("emptylink.SEVR", DBR_LONG, INVALID_ALARM); + + testdbPutFieldOk("emptylink.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("emptylink.VAL", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("emptylink.SEVR", DBR_LONG, 0); + + testdbGetFieldEqual("emptylink1.VAL", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("emptylink1.SEVR", DBR_LONG, INVALID_ALARM); + + testdbPutFieldOk("emptylink1.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("emptylink1.VAL", DBR_DOUBLE, 1.0); + testdbGetFieldEqual("emptylink1.SEVR", DBR_LONG, 0); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testPrintfStrings() +{ + testDiag("testPrintfStrings"); + + startTestIoc("linkInitTest.db"); + + { + const char buf1[] = "Test string, exactly 40 characters long"; + const char buf2[] = "Longer test string, more that 40 characters long"; + const char buf2t[] = "Longer test string, more that 40 charac"; + + /* The FMT field is pp(TRUE), so this put triggers processing */ + testdbPutFieldOk("printf1.FMT", DBF_STRING, "%s"); + testdbGetArrFieldEqual("printf1.VAL$", DBF_CHAR, NELEMENTS(buf1)+2, + NELEMENTS(buf1), buf1); + testdbGetFieldEqual("printf1.VAL", DBR_STRING, buf1); + + testdbPutFieldOk("printf1.FMT", DBF_STRING, "%ls"); + testdbGetArrFieldEqual("printf1.VAL$", DBF_CHAR, NELEMENTS(buf1)+2, + NELEMENTS(buf1), buf1); + testdbGetFieldEqual("printf1.VAL", DBR_STRING, buf1); + + testdbPutFieldOk("printf2.FMT", DBF_STRING, "%s"); + testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2, + NELEMENTS(buf2t), buf2t); + testdbGetFieldEqual("printf2.VAL", DBR_STRING, buf2t); + + testdbPutFieldOk("printf2.FMT", DBF_STRING, "%ls"); + testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2, + NELEMENTS(buf2), buf2); + testdbGetFieldEqual("printf2.VAL", DBR_STRING, buf2t); + + testdbPutFieldOk("printf2.FMT", DBF_STRING, "%.39ls"); + testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2, + NELEMENTS(buf2t), buf2t); + } + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testArrayInputs() +{ + epicsInt32 oneToTwelve[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + testDiag("testArrayInputs"); + + startTestIoc("linkInitTest.db"); + + testdbGetFieldEqual("aai1.NORD", DBR_LONG, 10); + testdbGetFieldEqual("aai1.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("aai2.NORD", DBR_LONG, 10); + testdbGetFieldEqual("aai2.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("sa1.NORD", DBR_LONG, 10); + testdbGetFieldEqual("sa1.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("sa2.NORD", DBR_LONG, 0); + testdbGetFieldEqual("sa2.UDF", DBR_UCHAR, 1); + testdbGetFieldEqual("wf1.NORD", DBR_LONG, 10); + testdbGetFieldEqual("wf1.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("wf2.NORD", DBR_LONG, 10); + testdbGetFieldEqual("wf2.UDF", DBR_UCHAR, 0); + + testdbGetArrFieldEqual("aai1.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]); + testdbGetArrFieldEqual("aai2.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]); + testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 10, &oneToTwelve[2]); + testdbGetArrFieldEqual("sa2.VAL", DBF_LONG, 10, 0, NULL); + testdbGetArrFieldEqual("wf1.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]); + testdbGetArrFieldEqual("wf2.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]); + + testdbPutFieldOk("sa1.INDX", DBF_LONG, 3); + testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 9, &oneToTwelve[3]); + + testdbPutFieldOk("sa1.NELM", DBF_LONG, 3); + testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 3, &oneToTwelve[3]); + + testdbPutFieldOk("sa2.VAL", DBF_LONG, 1); + testdbGetArrFieldEqual("sa2.VAL", DBF_LONG, 10, 1, &oneToTwelve[0]); + + testDiag("testScalarInputs"); + + testdbGetFieldEqual("li1", DBR_LONG, 1); + testdbGetFieldEqual("i64i1", DBR_INT64, 1LL); + testdbGetFieldEqual("li2", DBR_LONG, 1); + testdbGetFieldEqual("i64i2", DBR_INT64, 1LL); + + testIocShutdownOk(); + testdbCleanup(); +} + +static void testEventRecord() +{ + testMonitor *countmon; + + testDiag("testEventRecord"); + + startTestIoc("linkInitTest.db"); + countmon = testMonitorCreate("count1.VAL", DBR_LONG, 0); + + testdbGetFieldEqual("ev1.VAL", DBR_STRING, "soft event 1"); + testdbGetFieldEqual("ev1.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("ev2.VAL", DBR_STRING, ""); + testdbGetFieldEqual("ev2.UDF", DBR_UCHAR, 1); + testdbGetFieldEqual("count1.VAL", DBR_LONG, 0); + + testdbPutFieldOk("ev1.PROC", DBF_UCHAR, 1); + testMonitorWait(countmon); + testdbGetFieldEqual("count1.VAL", DBR_LONG, 1); + + testdbPutFieldOk("ev2.PROC", DBF_UCHAR, 1); + testMonitorWait(countmon); + testdbGetFieldEqual("ev2.UDF", DBR_UCHAR, 0); + testdbGetFieldEqual("count1.VAL", DBR_LONG, 2); + + testdbPutFieldOk("count1.EVNT", DBF_STRING, "Tock"); + testdbPutFieldOk("ev2.PROC", DBF_UCHAR, 1); + testMonitorWait(countmon); + testdbGetFieldEqual("count1.VAL", DBR_LONG, 3); + + testMonitorDestroy(countmon); + testIocShutdownOk(); + testdbCleanup(); +} + +void testInt64Inputs(void) +{ + testDiag("testInt64Inputs"); + + startTestIoc("linkInitTest.db"); + + testdbGetFieldEqual("i1.VAL", DBR_INT64, 1234567890123456789LL); + testdbGetFieldEqual("i2.VAL", DBR_INT64, 1234567890123456789LL); + testdbGetFieldEqual("i3.VAL", DBR_INT64, 1234567890123456789LL); + + testdbGetFieldEqual("i4.NORD", DBR_LONG, 1); + testdbGetFieldEqual("i4.VAL", DBR_INT64, 1234567890123456789LL); + + testIocShutdownOk(); + testdbCleanup(); +} + + +MAIN(linkInitTest) +{ + testPlan(77); + + testLongStringInit(); + testCalcInit(); + testPrintfStrings(); + testArrayInputs(); + testEventRecord(); + testInt64Inputs(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/linkInitTest.db b/modules/database/test/std/rec/linkInitTest.db new file mode 100644 index 000000000..2a2cfc8ec --- /dev/null +++ b/modules/database/test/std/rec/linkInitTest.db @@ -0,0 +1,107 @@ +record(lsi, "longstr1") { + field(SIZV, "100") + field(INP, ["!----------------------------------------------!"]) +} +record(lsi, "longstr2") { + field(SIZV, "100") + field(INP, {const: ["!----------------------------------------------!"]}) +} +record(lsi, "longstr3") { + field(SIZV, "100") + field(INP, {const: "!----------------------------------------------!"}) +} +record(lsi, "longstr4") { + field(SIZV, "100") + field(INP, ["One","Two","Three","Four"]) +} + +record(ai, "emptylink" ) { + field(INP, {calc: {expr:"0"}}) +} +record(ai, "emptylink1" ) { + field(INP, {calc: {expr:"A", args:[1], time:"a"}}) + field(TSEL, -2) +} + +record(printf, "printf1") { + field(SIZV, "100") + field(INP0, ["Test string, exactly 40 characters long"]) +} +record(printf, "printf2") { + field(SIZV, "100") + field(INP0, ["Longer test string, more that 40 characters long"]) +} + +record(aai, "aai1") { + field(NELM, 10) + field(FTVL, "LONG") + field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) +} +record(aai, "aai2") { + field(NELM, 10) + field(FTVL, "LONG") + field(INP, {const:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}) +} +record(subArray, "sa1") { + field(FTVL, "LONG") + field(MALM, 12) + field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + field(INDX, 2) + field(NELM, 10) +} +record(subArray, "sa2") { + field(FTVL, "LONG") + field(MALM, 10) + field(NELM, 1) +} +record(waveform, "wf1") { + field(NELM, 10) + field(FTVL, "LONG") + field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) +} +record(waveform, "wf2") { + field(NELM, 10) + field(FTVL, "LONG") + field(INP, {const:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}) +} + +record(longin, "li1") { + field(INP, 1) +} +record(int64in, "i64i1") { + field(INP, 1) +} +record(longin, "li2") { + field(INP, {const:1}) +} +record(int64in, "i64i2") { + field(INP, {const:1}) +} + +record(longin, "count1" ) { + field(INP, {calc: {expr:"VAL+1"}}) + field(SCAN, "Event") + field(EVNT, "soft event 1") +} +record(event, "ev1") { + field(INP, ["soft event 1"]) +} +record(event, "ev2") { + field(INP, "count1.EVNT") +} + +record(int64in, "i1") { + field(INP, [1234567890123456789,]) +} +record(int64in, "i2") { + field(INP, {const:1234567890123456789,}) +} +record(int64in, "i3") { + field(INP, 1234567890123456789) +} +record(waveform, "i4") { + field(NELM, 1) + field(FTVL, "INT64") + field(INP, [1234567890123456789,]) +} + diff --git a/modules/database/test/std/rec/linkRetargetLink.db b/modules/database/test/std/rec/linkRetargetLink.db new file mode 100644 index 000000000..148e2d50b --- /dev/null +++ b/modules/database/test/std/rec/linkRetargetLink.db @@ -0,0 +1,25 @@ +record(ai, "rec:ai") { + field(INP, "0") +} +record(ai, "rec:src1") { + field(VAL, "1") +} +record(ai, "rec:src2") { + field(VAL, "2") +} +record(stringout, "rec:link1") { + field(VAL, "rec:src1") + field(OUT, "rec:ai.INP CA") +} +record(stringout, "rec:link2") { + field(VAL, "rec:src2 CP") + field(OUT, "rec:ai.INP CA") +} + +record(ai, "rec:j1") { + field(INP, {calc:{ + expr:"A+5", + args:5 + }}) + field(PINI, "YES") +} diff --git a/modules/database/test/std/rec/linkRetargetLinkTest.c b/modules/database/test/std/rec/linkRetargetLinkTest.c new file mode 100644 index 000000000..2a37696aa --- /dev/null +++ b/modules/database/test/std/rec/linkRetargetLinkTest.c @@ -0,0 +1,122 @@ +/*************************************************************************\ +* Copyright (c) 2015 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * + * Test using several stringout records to retarget the link of another record + */ +#define EPICS_DBCA_PRIVATE_API + +#include + +#include "dbAccess.h" + +#include "dbUnitTest.h" +#include "errlog.h" +#include "epicsThread.h" + +#include "testMain.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void testRetarget(void) +{ + testMonitor *lnkmon, *valmon; + + testDiag("In testRetarget"); + + lnkmon = testMonitorCreate("rec:ai.INP", DBE_VALUE, 0); + valmon = testMonitorCreate("rec:ai", DBE_VALUE, 0); + + /* initially rec:ai.INP is CONSTANT */ + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "0"); + + /* rec:ai.INP becomes DB_LINK, but no processing is triggered */ + testdbPutFieldOk("rec:link1.PROC", DBF_LONG, 0); + + testMonitorWait(lnkmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src1 NPP NMS"); + + /* trigger a read from rec:ai.INP */ + testdbPutFieldOk("rec:ai.PROC", DBF_LONG, 0); + + testMonitorWait(valmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 1.0); + + /* rec:ai.INP becomes CA_LINK w/ CP, processing is triggered */ + testdbPutFieldOk("rec:link2.PROC", DBF_LONG, 0); + + testMonitorWait(lnkmon); + testMonitorWait(valmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 2.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src2 CP NMS"); + + testMonitorDestroy(lnkmon); + testMonitorDestroy(valmon); +} + +#define testLongStrEq(PV, VAL) testdbGetArrFieldEqual(PV, DBF_CHAR, sizeof(VAL)+2, sizeof(VAL), VAL) +#define testPutLongStr(PV, VAL) testdbPutArrFieldOk(PV, DBF_CHAR, sizeof(VAL), VAL); + +static void testRetargetJLink(void) +{ + testDiag("In testRetargetJLink"); + + testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 10.0); + /* minimal args */ + testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":5}}"); + + /* with [] */ + testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[7]}}"); + testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1); + + /* with const */ + testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}"); + testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 12.0); + testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}"); +} + +MAIN(linkRetargetLinkTest) +{ + testPlan(18); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("linkRetargetLink.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + /* wait for local CA links to be connected or dbPutField() will fail */ + /* wait for initial CA_CONNECT actions to be processed. + * Assume that local CA links deliver callbacks synchronously + * eg. that ca_create_channel() will invoke the connection callback + * before returning. + */ + dbCaSync(); + + testRetarget(); + testRetargetJLink(); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/recMiscTest.c b/modules/database/test/std/rec/recMiscTest.c new file mode 100644 index 000000000..567638b78 --- /dev/null +++ b/modules/database/test/std/rec/recMiscTest.c @@ -0,0 +1,88 @@ +/*************************************************************************\ +* Copyright (c) 2017 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "errlog.h" +#include "dbStaticLib.h" +#include "dbUnitTest.h" +#include "testMain.h" + +static +void testint64BeforeInit(void) +{ + const char *S; + DBENTRY dbent; + + /* check dbGet/PutString */ + + testDiag("In %s", EPICS_FUNCTION); + + dbInitEntryFromRecord(testdbRecordPtr("out64"), &dbent); + if(dbFindField(&dbent, "VAL")) + testAbort("Failed to find out64.VAL"); + + S = dbGetString(&dbent); + testOk(S && strcmp(S, "0")==0, "initial value \"%s\"", S); + + testOk1(dbPutString(&dbent, "0x12345678abcdef00")==0); + + S = dbGetString(&dbent); + testOk(S && strcmp(S, "1311768467750121216")==0, "1311768467750121216 \"%s\"", S); + + dbFinishEntry(&dbent); +} + +static +void testint64AfterInit(void) +{ + testDiag("In %s", EPICS_FUNCTION); + + /* check dbGet/PutField and DB links */ + + testdbGetFieldEqual("in64", DBF_UINT64, 0ULL); + testdbGetFieldEqual("out64", DBF_UINT64, 0x12345678abcdef00ULL); + + testdbPutFieldOk("out64.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("in64", DBF_UINT64, 0x12345678abcdef00ULL); + + testdbPutFieldOk("out64.VAL", DBF_UINT64, 0x22345678abcdef00ULL); + + testdbPutFieldOk("in64.PROC", DBF_LONG, 1); + + testdbGetFieldEqual("in64", DBF_UINT64, 0x22345678abcdef00ULL); +} + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +MAIN(recMiscTest) +{ + testPlan(10); + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("recMiscTest.db", NULL, NULL); + + testint64BeforeInit(); + + eltc(0); + testIocInitOk(); + eltc(1); + + testint64AfterInit(); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/recMiscTest.db b/modules/database/test/std/rec/recMiscTest.db new file mode 100644 index 000000000..46fc8ae11 --- /dev/null +++ b/modules/database/test/std/rec/recMiscTest.db @@ -0,0 +1,10 @@ + +# check int64in/out + +record(int64in, "in64") { + field(INP , "out64 NPP") +} + +record(int64out, "out64") { + field(OUT , "in64 NPP") +} diff --git a/modules/database/test/std/rec/regressArray1.db b/modules/database/test/std/rec/regressArray1.db new file mode 100644 index 000000000..a09f3bfba --- /dev/null +++ b/modules/database/test/std/rec/regressArray1.db @@ -0,0 +1,9 @@ +record(waveform, "wf") { + field(FTVL, "DOUBLE") + field(NELM, "1") + field(FLNK, "co") +} +record(calcout, "co") { + field(CALC, "A") + field(INPA, "wf") +} diff --git a/modules/database/test/std/rec/regressHex.db b/modules/database/test/std/rec/regressHex.db new file mode 100644 index 000000000..83f6916e9 --- /dev/null +++ b/modules/database/test/std/rec/regressHex.db @@ -0,0 +1,28 @@ +record(ai, "ai1") { + field(INP, 0x10) +} +record(longin, "li1") { + field(INP, 0x10) +} +record(mbbiDirect, "mi1") { + field(INP, 0x10) + field(NOBT, 8) +} +record(aSub, "as1") { + field(INPA, 0x10) + field(FTA, "CHAR") + field(INPB, 0x10) + field(FTB, "UCHAR") + field(INPC, 0x10) + field(FTC, "SHORT") + field(INPD, 0x10) + field(FTD, "USHORT") + field(INPE, 0x10) + field(FTE, "LONG") + field(INPF, 0x10) + field(FTF, "ULONG") + field(INPG, 0x10) + field(FTG, "FLOAT") + field(INPH, 0x10) + field(FTH, "DOUBLE") +} diff --git a/modules/database/test/std/rec/regressLinkMS.db b/modules/database/test/std/rec/regressLinkMS.db new file mode 100644 index 000000000..612c18afa --- /dev/null +++ b/modules/database/test/std/rec/regressLinkMS.db @@ -0,0 +1,13 @@ +record(ai, "alarm") { + field(HIGH, 1) + field(HSV, MINOR) + field(HIHI, 2) + field(HHSV, MAJOR) + field(FLNK, "latch") +} + +record(calc, "latch") { + field(INPA, "alarm NPP MS") + field(INPB, "latch NPP MS") + field(CALC, "A") +} diff --git a/modules/database/test/std/rec/regressTest.c b/modules/database/test/std/rec/regressTest.c new file mode 100644 index 000000000..15010cebf --- /dev/null +++ b/modules/database/test/std/rec/regressTest.c @@ -0,0 +1,134 @@ +/*************************************************************************\ +* Copyright (c) 2016 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +/* + * Tests for specific regressions + */ + +void regressTest_registerRecordDeviceDriver(struct dbBase *); + +static +void startRegressTestIoc(const char *dbfile) +{ + testdbPrepare(); + testdbReadDatabase("regressTest.dbd", NULL, NULL); + regressTest_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase(dbfile, NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); +} + +/* + * https://bugs.launchpad.net/epics-base/+bug/1577108 + */ +static +void testArrayLength1(void) +{ + waveformRecord *precwf; + calcoutRecord *precco; + double *pbuf; + + startRegressTestIoc("regressArray1.db"); + + precwf = (waveformRecord*)testdbRecordPtr("wf"); + precco = (calcoutRecord*)testdbRecordPtr("co"); + + dbScanLock((dbCommon*)precwf); + pbuf = (double*)precwf->bptr; + dbScanUnlock((dbCommon*)precwf); + + testdbPutFieldOk("wf", DBF_DOUBLE, 2.0); + + dbScanLock((dbCommon*)precwf); + testOk(precwf->nord==1, "wf.NORD = %u == 1", (unsigned)precwf->nord); + testOk(pbuf[0]==2.0, "wf.VAL[0] = %f == 2.0", pbuf[0]); + dbScanUnlock((dbCommon*)precwf); + + dbScanLock((dbCommon*)precco); + testOk(precco->a==2.0, "co.A = %f == 2.0", precco->a); + dbScanUnlock((dbCommon*)precco); + + testdbGetFieldEqual("co", DBF_DOUBLE, 2.0); + + testIocShutdownOk(); + + testdbCleanup(); +} + +/* + * https://bugs.launchpad.net/epics-base/+bug/1699445 + */ +static +void testHexConstantLinks(void) +{ + startRegressTestIoc("regressHex.db"); + + testdbGetFieldEqual("ai1", DBR_LONG, 0x10); + testdbGetFieldEqual("li1", DBR_LONG, 0x10); + testdbGetFieldEqual("mi1", DBR_LONG, 0x10); + testTodoBegin("Needs JSON5 for hex arrays"); + testdbGetFieldEqual("as1.A", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.B", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.C", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.D", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.E", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.F", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.G", DBR_LONG, 0x10); + testdbGetFieldEqual("as1.H", DBR_LONG, 0x10); + testTodoEnd(); + + testIocShutdownOk(); + testdbCleanup(); +} + +static +void testLinkMS(void) +{ + startRegressTestIoc("regressLinkMS.db"); + + testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5); + testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5); + testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0); + + testdbPutFieldOk("alarm", DBF_DOUBLE, 1.5); + testdbGetFieldEqual("latch", DBR_DOUBLE, 1.5); + testdbGetFieldEqual("latch.SEVR", DBR_LONG, 1); + + testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5); + testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5); + testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0); + + testdbPutFieldOk("alarm", DBF_DOUBLE, 2.5); + testdbGetFieldEqual("latch", DBR_DOUBLE, 2.5); + testdbGetFieldEqual("latch.SEVR", DBR_LONG, 2); + + testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5); + testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5); + testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0); + + testIocShutdownOk(); + testdbCleanup(); +} + + +MAIN(regressTest) +{ + testPlan(31); + testArrayLength1(); + testHexConstantLinks(); + testLinkMS(); + return testDone(); +} diff --git a/modules/database/test/std/rec/rtemsTestHarness.c b/modules/database/test/std/rec/rtemsTestHarness.c new file mode 100644 index 000000000..772d5394e --- /dev/null +++ b/modules/database/test/std/rec/rtemsTestHarness.c @@ -0,0 +1,14 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +extern void epicsRunRecordTests(void); + +int main(int argc, char **argv) +{ + epicsRunRecordTests(); /* calls epicsExit(0) */ + return 0; +} diff --git a/modules/database/test/std/rec/simmSetup.db b/modules/database/test/std/rec/simmSetup.db new file mode 100644 index 000000000..c8650e08b --- /dev/null +++ b/modules/database/test/std/rec/simmSetup.db @@ -0,0 +1,16 @@ +# no info +record(ai, "ai-0") { +} +# only scan +record(ai, "ai-1") { + field(SSCN,"2 second") +} +# only delay +record(ai, "ai-2") { + field(SDLY,".234") +} +# scan and delay +record(ai, "ai-3") { + field(SSCN,"5 second") + field(SDLY,".345") +} diff --git a/modules/database/test/std/rec/simmSimlFail.db b/modules/database/test/std/rec/simmSimlFail.db new file mode 100644 index 000000000..644b2f321 --- /dev/null +++ b/modules/database/test/std/rec/simmSimlFail.db @@ -0,0 +1,6 @@ +# siml target doesn't exist +record(ao, "ao-0") { + field(SIML, "non-exist") + field(HIGH, "1.5") + field(HSV, "MINOR") +} diff --git a/modules/database/test/std/rec/simmTest.c b/modules/database/test/std/rec/simmTest.c new file mode 100644 index 000000000..6558bfec7 --- /dev/null +++ b/modules/database/test/std/rec/simmTest.c @@ -0,0 +1,502 @@ +/*************************************************************************\ +* Copyright (c) 2017 ITER Organization +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "recSup.h" +#include "aiRecord.h" +#include "aoRecord.h" +#include "aaiRecord.h" +#include "aaoRecord.h" +#include "biRecord.h" +#include "boRecord.h" +#include "mbbiRecord.h" +#include "mbboRecord.h" +#include "mbbiDirectRecord.h" +#include "mbboDirectRecord.h" +#include "longinRecord.h" +#include "longoutRecord.h" +#include "int64inRecord.h" +#include "int64outRecord.h" +#include "stringinRecord.h" +#include "stringoutRecord.h" +#include "lsiRecord.h" +#include "lsoRecord.h" +#include "eventRecord.h" +#include "histogramRecord.h" +#include "waveformRecord.h" + +/* + * Tests for simulation mode + */ + +void simmTest_registerRecordDeviceDriver(struct dbBase *); + +static +void startSimmTestIoc(const char *dbfile) +{ + testdbPrepare(); + testdbReadDatabase("simmTest.dbd", NULL, NULL); + simmTest_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase(dbfile, NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); +} + +static char *rawSupp[] = { + "ai", + "bi", + "mbbi", + "mbbiDirect", +}; + +static +int hasRawSimmSupport(const char *rectype) { + int i; + for (i = 0; i < (sizeof(rawSupp)/sizeof(rawSupp[0])); i++) + if (strcmp(rectype, rawSupp[i]) == 0) return 1; + return 0; +} + +#define PVNAMELENGTH 60 +static char nameVAL[PVNAMELENGTH]; +static char nameB0[PVNAMELENGTH]; +static char nameRVAL[PVNAMELENGTH]; +static char nameSGNL[PVNAMELENGTH]; +static char nameSIMM[PVNAMELENGTH]; +static char nameSIML[PVNAMELENGTH]; +static char nameSVAL[PVNAMELENGTH]; +static char nameSIOL[PVNAMELENGTH]; +static char nameSCAN[PVNAMELENGTH]; +static char namePROC[PVNAMELENGTH]; +static char namePACT[PVNAMELENGTH]; +static char nameSTAT[PVNAMELENGTH]; +static char nameSEVR[PVNAMELENGTH]; +static char nameSIMS[PVNAMELENGTH]; +static char nameTSE[PVNAMELENGTH]; +static char nameSimmode[PVNAMELENGTH]; +static char nameSimval[PVNAMELENGTH]; +static char nameSimvalNORD[PVNAMELENGTH]; +static char nameSimvalLEN[PVNAMELENGTH]; + +#define SETNAME(field) strcpy(name ## field, name); strcat(name ## field, "." #field) +static +void setNames(const char *name) +{ + SETNAME(VAL); SETNAME(B0); SETNAME(RVAL); SETNAME(SGNL); + SETNAME(SVAL); SETNAME(SIMM); SETNAME(SIML); SETNAME(SIOL); SETNAME(SIMS); + SETNAME(SCAN); SETNAME(PROC); SETNAME(PACT); + SETNAME(STAT); SETNAME(SEVR); SETNAME(TSE); + strcpy(nameSimmode, name); strcat(nameSimmode, ":simmode"); + strcpy(nameSimval, name); strcat(nameSimval, ":simval"); + strcpy(nameSimvalNORD, name); strcat(nameSimvalNORD, ":simval.NORD"); + strcpy(nameSimvalLEN, name); strcat(nameSimvalLEN, ":simval.LEN"); +} + +/* + * Parsing of info items and xsimm structure setting + */ +static +void testSimmSetup(void) +{ + aiRecord *precai; + + testDiag("##### Simm initialization #####"); + + /* no config */ + precai = (aiRecord*)testdbRecordPtr("ai-0"); + testOk(precai->simpvt == NULL, "ai-0.SIMPVT = %p == NULL [no callback]", precai->simpvt); + testOk(precai->sscn == USHRT_MAX, "ai-0.SSCN = %u == USHRT_MAX (not set)", precai->sscn); + testOk(precai->sdly < 0., "ai-0.SDLY = %g < 0.0 (not set)", precai->sdly); + + /* with SCAN */ + precai = (aiRecord*)testdbRecordPtr("ai-1"); + testOk(precai->sscn == 5, "ai-1.SSCN = %u == 5 (2 second)", precai->sscn); + testOk(precai->sdly < 0., "ai-1.SDLY = %g < 0.0 (not set)", precai->sdly); + + /* with DELAY */ + precai = (aiRecord*)testdbRecordPtr("ai-2"); + testOk(precai->sscn == USHRT_MAX, "ai-2.SSCN = %u == USHRT_MAX (not set)", precai->sscn); + testOk(precai->sdly == 0.234, "ai-2.SDLY = %g == 0.234", precai->sdly); + + /* with SCAN and DELAY */ + precai = (aiRecord*)testdbRecordPtr("ai-3"); + testOk(precai->sscn == 4, "ai-3.SSCN = %u == 4 (5 second)", precai->sscn); + testOk(precai->sdly == 0.345, "ai-3.SDLY = %g == 0.345", precai->sdly); +} + +/* + * Invalid SIML link sets LINK/NO_ALARM if in NO_ALARM + */ +static +void testSimlFail(void) +{ + aoRecord *precao; + + testDiag("##### Behavior for failing SIML #####"); + + precao = (aoRecord*)testdbRecordPtr("ao-0"); + /* before anything: UDF INVALID */ + testOk(precao->stat == UDF_ALARM, "ao-0.STAT = %u [%s] == %u [UDF]", + precao->stat, epicsAlarmConditionStrings[precao->stat], UDF_ALARM); + testOk(precao->sevr == INVALID_ALARM, "ao-0.SEVR = %u [%s] == %u [INVALID]", + precao->sevr, epicsAlarmSeverityStrings[precao->sevr], INVALID_ALARM); + + /* legal value: LINK NO_ALARM */ + testdbPutFieldOk("ao-0.VAL", DBR_DOUBLE, 1.0); + testOk(precao->stat == LINK_ALARM, "ao-0.STAT = %u [%s] == %u [LINK]", + precao->stat, epicsAlarmConditionStrings[precao->stat], LINK_ALARM); + testOk(precao->sevr == NO_ALARM, "ao-0.SEVR = %u [%s] == %u [NO_ALARM]", + precao->sevr, epicsAlarmSeverityStrings[precao->sevr], NO_ALARM); + + /* HIGH/MINOR overrides */ + testdbPutFieldOk("ao-0.VAL", DBR_DOUBLE, 2.0); + testOk(precao->stat == HIGH_ALARM, "ao-0.STAT = %u [%s] == %u [HIGH]", + precao->stat, epicsAlarmConditionStrings[precao->stat], HIGH_ALARM); + testOk(precao->sevr == MINOR_ALARM, "ao-0.SEVR = %u [%s] == %u [MINOR]", + precao->sevr, epicsAlarmSeverityStrings[precao->sevr], MINOR_ALARM); +} + +/* + * SIMM triggered SCAN swapping, by writing to SIMM and through SIML + */ + +static +void testSimmToggle(const char *name, epicsEnum16 *psscn) +{ + testDiag("## SIMM toggle and SCAN swapping ##"); + + /* SIMM mode by setting the field */ + + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0); + testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn); + + testDiag("set SIMM to YES"); + testdbPutFieldOk(nameSIMM, DBR_STRING, "YES"); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1); + testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn); + + /* Change simm:SCAN when simmYES */ + testdbPutFieldOk(nameSCAN, DBR_USHORT, 3); + + testDiag("set SIMM to NO"); + testdbPutFieldOk(nameSIMM, DBR_STRING, "NO"); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0); + testOk(*psscn == 3, "SSCN = %u == 3 (10 second)", *psscn); + *psscn = 1; + + if (hasRawSimmSupport(name)) { + testDiag("set SIMM to RAW"); + testdbPutFieldOk(nameSIMM, DBR_STRING, "RAW"); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1); + testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn); + + testDiag("set SIMM to NO"); + testdbPutFieldOk(nameSIMM, DBR_STRING, "NO"); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0); + testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn); + } else { + testDiag("Record type %s has no support for simmRAW", name); + } + + /* SIMM mode through SIML */ + + testdbPutFieldOk(nameSIML, DBR_STRING, nameSimmode); + + testDiag("set SIMM (via SIML -> simmode) to YES"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + + testdbGetFieldEqual(nameSIMM, DBR_USHORT, 1); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1); + testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn); + + testDiag("set SIMM (via SIML -> simmode) to NO"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + + testdbGetFieldEqual(nameSIMM, DBR_USHORT, 0); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0); + testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn); + + if (hasRawSimmSupport(name)) { + testDiag("set SIMM (via SIML -> simmode) to RAW"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 2); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + + testdbGetFieldEqual(nameSIMM, DBR_USHORT, 2); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1); + testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn); + + testDiag("set SIMM (via SIML -> simmode) to NO"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + + testdbGetFieldEqual(nameSIMM, DBR_USHORT, 0); + testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0); + testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn); + } else { + testDiag("Record type %s has no support for simmRAW", name); + } +} + +/* + * Reading from SVAL (direct write or through SIOL link) + */ + +static +void testSvalRead(const char *name, + const epicsTimeStamp *mytime, + const epicsTimeStamp *svtime) +{ + epicsTimeStamp last; + + if (strcmp(name, "histogram") == 0) + strcpy(nameVAL, nameSGNL); + + if (strcmp(name, "aai") != 0 && + strcmp(name, "waveform") != 0 && + strcmp(name, "lsi") != 0) { + + testDiag("## Reading from SVAL ##"); + + testDiag("in simmNO, SVAL must be ignored"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + testdbPutFieldOk(nameVAL, DBR_LONG, 0); + if (strcmp(name, "stringin") == 0) + testdbPutFieldOk(nameSVAL, DBR_STRING, "1"); + else + testdbPutFieldOk(nameSVAL, DBR_USHORT, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameVAL, DBR_USHORT, 0); + + testDiag("in simmYES, SVAL is used for VAL"); + testdbPutFieldOk(nameSIMS, DBR_USHORT, 0); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameVAL, DBR_USHORT, 1); + testDiag("No SIMS setting: STAT/SEVR == NO_ALARM"); + testdbGetFieldEqual(nameSTAT, DBR_STRING, "NO_ALARM"); + testdbGetFieldEqual(nameSEVR, DBR_USHORT, 0); + + if (hasRawSimmSupport(name)) { + testDiag("in simmRAW, SVAL is used for RVAL"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 2); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameRVAL, DBR_USHORT, 1); + } else { + testDiag("Record type %s has no support for simmRAW", name); + } + } + + testDiag("## Reading from SIOL->SVAL ##"); + + /* Set SIOL link to simval */ + testdbPutFieldOk(nameSIOL, DBR_STRING, nameSimval); + + testDiag("in simmNO, SIOL->SVAL must be ignored"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + testdbPutFieldOk(nameVAL, DBR_LONG, 0); + testdbPutFieldOk(nameSimval, DBR_LONG, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameVAL, DBR_USHORT, 0); + + testDiag("in simmYES, SIOL->SVAL is used for VAL"); + testdbPutFieldOk(nameSIMS, DBR_USHORT, 3); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameVAL, DBR_USHORT, 1); + testDiag("SIMS is INVALID: STAT/SEVR == SIMM/INVALID"); + testdbGetFieldEqual(nameSTAT, DBR_STRING, "SIMM"); + testdbGetFieldEqual(nameSEVR, DBR_USHORT, 3); + testdbPutFieldOk(nameSIMS, DBR_USHORT, 0); + + if (hasRawSimmSupport(name)) { + testDiag("in simmRAW, SIOL->SVAL is used for RVAL"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 2); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(nameRVAL, DBR_USHORT, 1); + } else { + testDiag("Record type %s has no support for simmRAW", name); + } + + /* My timestamp must be later than simval's */ + testOk(epicsTimeLessThan(svtime, mytime), "simval time < my time [TSE = 0]"); + + testDiag("for TSE=-2 (from device) and simmYES, take time stamp from IOC or input link"); + + /* Set simmYES */ + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + + /* Set TSE to -2 (from device) and reprocess: timestamps is taken through SIOL from simval */ + testdbPutFieldOk(nameTSE, DBR_SHORT, -2); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testOk(epicsTimeEqual(svtime, mytime), "simval time == my time [TSE = -2]"); + last = *mytime; + + /* With TSE=-2 and no SIOL, timestamp is taken from IOC */ + testdbPutFieldOk(nameSIOL, DBR_STRING, ""); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testOk(epicsTimeLessThan(&last, mytime), "new time stamp from IOC [TSE = -2, no SIOL]"); + + /* Reset TSE */ + testdbPutFieldOk(nameTSE, DBR_SHORT, 0); +} + +/* + * Writing through SIOL link + */ + +static +void testSiolWrite(const char *name, + const epicsTimeStamp *mytime) +{ + epicsTimeStamp now; + + testDiag("## Writing through SIOL ##"); + + /* Set SIOL link to simval */ + testdbPutFieldOk(nameSIOL, DBR_STRING, nameSimval); + + testDiag("in simmNO, SIOL must be ignored"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + if (strcmp(name, "mbboDirect") == 0) + testdbPutFieldOk(nameB0, DBR_LONG, 1); + else + testdbPutFieldOk(nameVAL, DBR_LONG, 1); + + if (strcmp(name, "aao") == 0) + testdbGetFieldEqual(nameSimvalNORD, DBR_USHORT, 0); + else if (strcmp(name, "lso") == 0) + testdbGetFieldEqual(nameSimvalLEN, DBR_USHORT, 0); + else + testdbGetFieldEqual(nameSimval, DBR_USHORT, 0); + + testDiag("in simmYES, SIOL is used to write VAL"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + if (strcmp(name, "mbboDirect") == 0) + testdbPutFieldOk(nameB0, DBR_LONG, 1); + else + testdbPutFieldOk(nameVAL, DBR_LONG, 1); + testdbGetFieldEqual(nameSimval, DBR_USHORT, 1); + + /* Set TSE to -2 (from device) and reprocess: timestamp is taken from IOC */ + epicsTimeGetCurrent(&now); + testdbPutFieldOk(nameTSE, DBR_SHORT, -2); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testOk(epicsTimeLessThan(&now, mytime), "new time stamp from IOC [TSE = -2]"); + + /* Reset TSE */ + testdbPutFieldOk(nameTSE, DBR_SHORT, 0); +} + +/* + * Asynchronous processing using simm:DELAY + */ + +static +void testSimmDelay(const char *name, + epicsFloat64 *psdly, + const epicsTimeStamp *mytime) +{ + epicsTimeStamp now; + const double delay = 0.01; /* 10 ms */ + + testDiag("## Asynchronous processing with simm:DELAY ##"); + + /* Set delay to something just long enough */ + *psdly = delay; + + /* Process in simmNO: synchronous */ + testDiag("simm:DELAY and simmNO processes synchronously"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 0); + epicsTimeGetCurrent(&now); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(namePACT, DBR_USHORT, 0); + testOk(epicsTimeLessThan(&now, mytime), "time stamp is recent"); + + /* Process in simmYES: asynchronous */ + testDiag("simm:DELAY and simmYES processes asynchronously"); + testdbPutFieldOk(nameSimmode, DBR_USHORT, 1); + testdbPutFieldOk(namePROC, DBR_LONG, 0); + testdbGetFieldEqual(namePACT, DBR_USHORT, 1); + epicsTimeGetCurrent(&now); + epicsThreadSleep(1.75*delay); + testdbGetFieldEqual(namePACT, DBR_USHORT, 0); + testOk(epicsTimeLessThan(&now, mytime), "time stamp taken from second pass processing"); + + /* Reset delay */ + *psdly = -1.; +} + +#define RUNALLTESTSREAD(type) \ + testDiag("################################################### Record Type " #type); \ + setNames(#type); \ + testSimmToggle(#type, &((type ## Record*)testdbRecordPtr(#type))->sscn); \ + testSvalRead(#type, &((type ## Record*)testdbRecordPtr(#type))->time, \ + &((type ## Record*)testdbRecordPtr(#type ":simval"))->time); \ + testSimmDelay(#type, &((type ## Record*)testdbRecordPtr(#type))->sdly, \ + &((type ## Record*)testdbRecordPtr(#type))->time) + +#define RUNALLTESTSWRITE(type) \ + testDiag("################################################### Record Type " #type); \ + setNames(#type); \ + testSimmToggle(#type, &((type ## Record*)testdbRecordPtr(#type))->sscn); \ + testSiolWrite(#type, &((type ## Record*)testdbRecordPtr(#type))->time); \ + testSimmDelay(#type, &((type ## Record*)testdbRecordPtr(#type))->sdly, \ + &((type ## Record*)testdbRecordPtr(#type))->time) + +static +void testAllRecTypes(void) +{ + RUNALLTESTSREAD(ai); + RUNALLTESTSWRITE(ao); + RUNALLTESTSREAD(aai); + RUNALLTESTSWRITE(aao); + RUNALLTESTSREAD(bi); + RUNALLTESTSWRITE(bo); + RUNALLTESTSREAD(mbbi); + RUNALLTESTSWRITE(mbbo); + RUNALLTESTSREAD(mbbiDirect); + RUNALLTESTSWRITE(mbboDirect); + RUNALLTESTSREAD(longin); + RUNALLTESTSWRITE(longout); + RUNALLTESTSREAD(int64in); + RUNALLTESTSWRITE(int64out); + RUNALLTESTSREAD(stringin); + RUNALLTESTSWRITE(stringout); + RUNALLTESTSREAD(lsi); + RUNALLTESTSWRITE(lso); + RUNALLTESTSREAD(event); + RUNALLTESTSREAD(waveform); + RUNALLTESTSREAD(histogram); +} + + +MAIN(simmTest) +{ + testPlan(1176); + startSimmTestIoc("simmTest.db"); + + testSimmSetup(); + testSimlFail(); + testAllRecTypes(); + + testIocShutdownOk(); + testdbCleanup(); + return testDone(); +} diff --git a/modules/database/test/std/rec/simmTest.substitutions b/modules/database/test/std/rec/simmTest.substitutions new file mode 100644 index 000000000..a1b3dcbd2 --- /dev/null +++ b/modules/database/test/std/rec/simmTest.substitutions @@ -0,0 +1,32 @@ +file "simmTestSimple.template" { +{ TYPE="ai" } +{ TYPE="ao" } +{ TYPE="bi" } +{ TYPE="bo" } +{ TYPE="mbbi" } +{ TYPE="mbbo" } +{ TYPE="mbbiDirect" } +{ TYPE="mbboDirect" } +{ TYPE="longin" } +{ TYPE="longout" } +{ TYPE="int64in" } +{ TYPE="int64out" } +{ TYPE="stringin" } +{ TYPE="stringout" } +{ TYPE="lsi" } +{ TYPE="lso" } +{ TYPE="event" } +} +file "simmTestArray.template" { +{ TYPE="aai" } +{ TYPE="aao" } +{ TYPE="waveform" } +} +file "simmTestHistogram.template" { +{ TYPE="histogram" } +} +file "simmSetup.db" { +{} +file "simmSimlFail.db" { +{} +} diff --git a/modules/database/test/std/rec/simmTestArray.template b/modules/database/test/std/rec/simmTestArray.template new file mode 100644 index 000000000..36ca2c009 --- /dev/null +++ b/modules/database/test/std/rec/simmTestArray.template @@ -0,0 +1,15 @@ +# Array type records +# Regular simulation mode and simm:SCAN tests +record($(TYPE), "$(TYPE)") { + field(SSCN,"Event") + field(FTVL,"SHORT") + field(NELM,"2") +} +record($(TYPE), "$(TYPE):simval") { + field(FTVL,"SHORT") + field(NELM,"2") +} +record(bo, "$(TYPE):simmode") { + field(ZNAM,"off") + field(ONAM,"on") +} diff --git a/modules/database/test/std/rec/simmTestHistogram.template b/modules/database/test/std/rec/simmTestHistogram.template new file mode 100644 index 000000000..574314f00 --- /dev/null +++ b/modules/database/test/std/rec/simmTestHistogram.template @@ -0,0 +1,12 @@ +# Array type records +# Regular simulation mode and simm:SCAN tests +record($(TYPE), "$(TYPE)") { + field(SSCN,"Event") + field(NELM,"2") +} +record(ai, "$(TYPE):simval") { +} +record(bo, "$(TYPE):simmode") { + field(ZNAM,"off") + field(ONAM,"on") +} diff --git a/modules/database/test/std/rec/simmTestSimple.template b/modules/database/test/std/rec/simmTestSimple.template new file mode 100644 index 000000000..a1a73b5db --- /dev/null +++ b/modules/database/test/std/rec/simmTestSimple.template @@ -0,0 +1,10 @@ +# Regular simulation mode and simm:SCAN tests +record($(TYPE), "$(TYPE)") { + field(SSCN,"Event") +} +record($(TYPE), "$(TYPE):simval") { +} +record(bo, "$(TYPE):simmode") { + field(ZNAM,"off") + field(ONAM,"on") +} diff --git a/modules/database/test/std/rec/softTest.c b/modules/database/test/std/rec/softTest.c new file mode 100644 index 000000000..e9cb319a0 --- /dev/null +++ b/modules/database/test/std/rec/softTest.c @@ -0,0 +1,251 @@ +/*************************************************************************\ +* Copyright (c) 2017 UChicago Argonne LLC, as operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "dbAccess.h" +#include "dbStaticLib.h" +#include "dbTest.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "errlog.h" +#include "registryFunction.h" +#include "subRecord.h" +#include "testMain.h" + +static +void checkDtyp(const char *rec) +{ + char dtyp[16]; + + strcpy(dtyp, rec); + strcat(dtyp, ".DTYP"); + + testdbGetFieldEqual(dtyp, DBR_LONG, 0); + testdbGetFieldEqual(dtyp, DBR_STRING, "Soft Channel"); +} + +static +void doProcess(const char *rec) +{ + char proc[16]; + + strcpy(proc, rec); + strcat(proc, ".PROC"); + + testdbPutFieldOk(proc, DBR_CHAR, 1); +} + +/* Group 0 are soft-channel input records with INP being a DB or CA link + * to the PV 'source'. Their VAL fields all start out with the default + * value for the type, i.e. 0 or an empty string. Triggering record + * processing reads the value from the 'source' PV. + */ + +static +void testGroup0(void) +{ + const char ** rec; + const char * records[] = { + "ai0", "bi0", "di0", "ii0", "li0", "lsi0", "mi0", "si0", + "ai0c", "bi0c", "di0c", "ii0c", "li0c", "lsi0c", "mi0c", "si0c", + NULL + }; + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + testdbPutFieldOk("source", DBR_LONG, 1); + /* The above put sends CA monitors to all of the CA links, but + * doesn't trigger record processing (the links are not CP/CPP). + * How could we wait until all of those monitors have arrived, + * instead of just waiting for an arbitrary time period? + */ + epicsThreadSleep(1.0); /* FIXME: Wait here? */ + for (rec = records; *rec; rec++) { + if (strncmp(*rec, "lsi0", 4) != 0) + testdbGetFieldEqual(*rec, DBR_LONG, 0); + checkDtyp(*rec); + doProcess(*rec); + testdbGetFieldEqual(*rec, DBR_LONG, 1); + } + + testdbPutFieldOk("source", DBR_LONG, 0); + epicsThreadSleep(1.0); /* FIXME: Wait here as above */ + for (rec = records; *rec; rec++) { + doProcess(*rec); + testdbGetFieldEqual(*rec, DBR_LONG, 0); + } +} + +/* Group 1 are all soft-channel input records with INP being a non-zero + * "const" JSON-link, 9 for most records, 1 for the binary. Their VAL + * fields should all be initialized to that constant value. Triggering + * record processing should succeed, but shouldn't change VAL. + */ +static +void testGroup1(void) +{ + const char ** rec; + const char * records[] = { + "bi1", + "ai1", "di1", "ii1", "li1", "lsi1", "mi1", "si1", NULL + }; + int init = 1; /* bi1 initializes to 1 */ + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + for (rec = records; *rec; rec++) { + testdbGetFieldEqual(*rec, DBR_LONG, init); + doProcess(*rec); + testdbGetFieldEqual(*rec, DBR_LONG, init); + init = 9; /* other records initialize to 9 */ + } +} + +/* Group 2 are all soft-channel input records with INP being a CONSTANT + * link with value 9 for most records, 1 for the binary. Their VAL + * fields should all be initialized to that constant value. Triggering + * record processing should succeed, but shouldn't change VAL. + */ +static +void testGroup2(void) +{ + const char ** rec; + const char * records[] = { + "bi2", + "ai2", "di2", "ii2", "li2", "mi2", NULL + }; + int init = 1; /* bi1 initializes to 1 */ + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + for (rec = records; *rec; rec++) { + testdbGetFieldEqual(*rec, DBR_LONG, init); + doProcess(*rec); + testdbGetFieldEqual(*rec, DBR_LONG, init); + init = 9; /* other records initialize to 9 */ + } +} + + + +int dest; +epicsEventId destEvent; + +static +long destSubr(subRecord *prec) +{ + dest = prec->val; + prec->val = -1; + epicsEventMustTrigger(destEvent); + return 0; +} + +static +void checkOutput3(const char *rec, int value) +{ + testDiag("Checking record '%s'", rec); + + testdbPutFieldOk(rec, DBR_LONG, value); + + epicsEventMustWait(destEvent); + testOk(dest == value, "value %d output -> %d", value, dest); +} + +/* Group 3 are all soft-channel output records with OUT being a DB or + * local CA link to the subRecord 'dest'; DB links have the PP flag, + * for CA links the VAL field is marked PP. Putting a value to the + * output record writes that value to 'dest'. + */ +static +void testGroup3(void) +{ + const char ** rec; + const char * records[] = { + "ao3", "bo3", "io3", "lo3", "lso3", "mo3", "so3", + "ao3c", "bo3c", "io3c", "lo3c", "lso3c", "mo3c", "so3c", + NULL, + }; + + destEvent = epicsEventMustCreate(epicsEventEmpty); + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + for (rec = records; *rec; rec++) { + checkOutput3(*rec, 1); + checkDtyp(*rec); + } + checkOutput3("do3.B0", 1); + checkDtyp("do3"); + checkOutput3("do3c.B0", 1); + checkDtyp("do3c"); + + for (rec = records; *rec; rec++) { + checkOutput3(*rec, 0); + } + checkOutput3("do3.B0", 0); + checkOutput3("do3c.B0", 0); +} + + +static +void checkOutput4(const char *rec, int value) +{ + testDiag("Checking record '%s'", rec); + + testdbPutFieldOk(rec, DBR_LONG, value); +} + +/* Group 4 are all soft-channel output records with OUT being empty + * (i.e. a CONSTANT link). Putting a value to the record must succeed. + */ +static +void testGroup4(void) +{ + const char ** rec; + const char * records[] = { + "ao4", "bo4", "do4.B0", "io4", "lo4", "lso4", "mo4", "so4", NULL, + }; + + testDiag("============ Starting %s ============", EPICS_FUNCTION); + + for (rec = records; *rec; rec++) { + checkOutput4(*rec, 0); + } +} + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + + +MAIN(softTest) +{ + testPlan(258); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + registryFunctionAdd("destSubr", (REGISTRYFUNCTION) destSubr); + + testdbReadDatabase("softTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + testGroup0(); + testGroup1(); + testGroup2(); + testGroup3(); + testGroup4(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/softTest.db b/modules/database/test/std/rec/softTest.db new file mode 100644 index 000000000..06dbf2b7e --- /dev/null +++ b/modules/database/test/std/rec/softTest.db @@ -0,0 +1,393 @@ +# Group 0 are all soft-channel input records with INP being a DB link +# to the PV 'source'. Their VAL fields all start out with the default +# value for the type, i.e. 0 or an empty string. Triggering record +# processing should read the integer value from the 'source' PV. + +record(longin, "source") {} + +record(ai, "ai0") { + field(DTYP, "Soft Channel") + field(INP, "source") +} +record(bi, "bi0") { + field(DTYP, "Soft Channel") + field(INP, "source") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii0") { + field(DTYP, "Soft Channel") + field(INP, "source") +} +record(longin, "li0") { + field(DTYP, "Soft Channel") + field(INP, "source") +} +record(mbbiDirect, "di0") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, "source") +} +record(mbbi, "mi0") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, "source") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lsi, "lsi0") { + field(DTYP, "Soft Channel") + field(SIZV, 40) + field(INP, "source") +} +record(stringin, "si0") { + field(DTYP, "Soft Channel") + field(INP, "source") +} + +record(ai, "ai0c") { + field(DTYP, "Soft Channel") + field(INP, "source CA") +} +record(bi, "bi0c") { + field(DTYP, "Soft Channel") + field(INP, "source CA") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii0c") { + field(DTYP, "Soft Channel") + field(INP, "source CA") +} +record(longin, "li0c") { + field(DTYP, "Soft Channel") + field(INP, "source CA") +} +record(mbbiDirect, "di0c") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, "source CA") +} +record(mbbi, "mi0c") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, "source CA") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lsi, "lsi0c") { + field(DTYP, "Soft Channel") + field(SIZV, 40) + field(INP, "source CA") +} +record(stringin, "si0c") { + field(DTYP, "Soft Channel") + field(INP, "source CA") +} + + +# Group 1 are all soft-channel input records with INP being a non-zero +# "const" JSON-link, 9 for most records, 1 for the binary. Their VAL +# fields should all be initialized to that constant value. Triggering +# record processing should succeed, but shouldn't change VAL. + +record(ai, "ai1") { + field(DTYP, "Soft Channel") + field(INP, {const:9}) +} +record(bi, "bi1") { + field(DTYP, "Soft Channel") + field(INP, {const:1}) + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii1") { + field(DTYP, "Soft Channel") + field(INP, {const:9}) +} +record(longin, "li1") { + field(DTYP, "Soft Channel") + field(INP, {const:9}) +} +record(mbbiDirect, "di1") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, {const:9}) +} +record(mbbi, "mi1") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, {const:9}) + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lsi, "lsi1") { + field(DTYP, "Soft Channel") + field(SIZV, 40) + field(INP, {const:"9"}) +} +record(stringin, "si1") { + field(DTYP, "Soft Channel") + field(INP, {const:"9"}) +} + + +# Group 2 are all soft-channel input records with INP being a CONSTANT +# link with value 9 for most records, 1 for the binary. Their VAL +# fields should all be initialized to that constant value. Triggering +# record processing should succeed, but shouldn't change VAL. + +record(ai, "ai2") { + field(DTYP, "Soft Channel") + field(INP, 9) +} +record(bi, "bi2") { + field(DTYP, "Soft Channel") + field(INP, 1) + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64in, "ii2") { + field(DTYP, "Soft Channel") + field(INP, 9) +} +record(longin, "li2") { + field(DTYP, "Soft Channel") + field(INP, 9) +} +record(mbbiDirect, "di2") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, 9) +} +record(mbbi, "mi2") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(INP, 9) + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} + + +# Group 3 are all soft-channel output records with OUT being a DB or +# CA link to the PV 'dest' with PP. Putting a value to the record +# under test writes the value to 'dest' and processes it. + +record(sub, "dest") { + field(SNAM, "destSubr") + field(VAL, -1) +} + +record(ao, "ao3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") +} +record(bo, "bo3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64out, "io3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") +} +record(longout, "lo3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") +} +record(mbboDirect, "do3") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(OUT, "dest PP") +} +record(mbbo, "mo3") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(OUT, "dest PP") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lso, "lso3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") + field(SIZV, 40) +} +record(stringout, "so3") { + field(DTYP, "Soft Channel") + field(OUT, "dest PP") +} + +record(ao, "ao3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") +} +record(bo, "bo3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64out, "io3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") +} +record(longout, "lo3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") +} +record(mbboDirect, "do3c") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(OUT, "dest CA") +} +record(mbbo, "mo3c") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(OUT, "dest CA") + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lso, "lso3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") + field(SIZV, 40) +} +record(stringout, "so3c") { + field(DTYP, "Soft Channel") + field(OUT, "dest CA") +} + + +# Group 4 are all soft-channel output records with OUT being empty +# (i.e. a CONSTANT link). Putting a value to the record must succeed. + +record(ao, "ao4") { + field(DTYP, "Soft Channel") +} +record(bo, "bo4") { + field(DTYP, "Soft Channel") + field(ZNAM, "Zero") + field(ONAM, "One") +} +record(int64out, "io4") { + field(DTYP, "Soft Channel") +} +record(longout, "lo4") { + field(DTYP, "Soft Channel") +} +record(mbboDirect, "do4") { + field(DTYP, "Soft Channel") + field(NOBT, 4) +} +record(mbbo, "mo4") { + field(DTYP, "Soft Channel") + field(NOBT, 4) + field(ZRST, "Zero") + field(ONST, "One") + field(TWST, "Two") + field(THST, "Three") + field(FRST, "Four") + field(FVST, "Five") + field(SXST, "Six") + field(SVST, "Seven") + field(EIST, "Eight") + field(NIST, "Nine") + field(TEST, "Ten") + field(ELST, "Eleven") + field(TWST, "Twelve") + field(TTST, "Thirteen") + field(FTST, "Fourteen") + field(FFST, "Fifteen") +} +record(lso, "lso4") { + field(DTYP, "Soft Channel") + field(SIZV, 40) +} +record(stringout, "so4") { + field(DTYP, "Soft Channel") +} diff --git a/modules/database/test/tools/Base.plt b/modules/database/test/tools/Base.plt new file mode 100644 index 000000000..869b7e463 --- /dev/null +++ b/modules/database/test/tools/Base.plt @@ -0,0 +1,98 @@ +#!/usr/bin/perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 127; + +use DBD::Base; +use DBD::Registrar; + +note "*** Testing DBD::Base class ***"; + +my $base = DBD::Base->new('test', 'Base class'); +isa_ok $base, 'DBD::Base'; +is $base->what, 'Base class', 'DBD Object type'; +is $base->name, 'test', 'Base class name'; + +my $base2 = DBD::Base->new('test2', 'Base class'); +isa_ok $base, 'DBD::Base'; +ok !$base->equals($base2), 'Different names'; + +my $reg = DBD::Registrar->new('test'); +ok !$base->equals($reg), 'Different types'; + +eval { + $base->add_comment('testing'); +}; +ok $@, 'add_comment died'; + +{ + local *STDERR; + my $warning = ''; + open STDERR, '>', \$warning; + $base->add_pod('testing'); + like $warning, qr/^Warning:/, 'add_pod warned'; + # Also proves that warnContext works +} + +note "*** Testing push/pop contexts ***"; +pushContext "a"; +pushContext "b"; +eval { + popContext "b"; +}; +ok !$@, "pop: Expected context didn't die"; + +eval { + popContext "b"; +}; +ok $@, "pop: Incorrect context died"; +# Also proves that dieContext dies properly + +note "*** Testing basic RXs ***"; + +# For help in debugging regex's, wrap tests below inside +# use re 'debugcolor'; +# ... +# no re; + +like($_, qr/^ $RXident $/x, "Good RXident: $_") + foreach qw(a A1 a111 z9 Z9 Z_999); +unlike($_, qr/^ $RXident $/x, "Bad RXident: $_") + foreach qw(. 1 _ : a. _: 9.0); + +like($_, qr/^ $RXname $/x, "Good RXname: $_") + foreach qw(a A1 a1:x _ -; Z[9] Z<999> a{x}b); +unlike($_, qr/^ $RXname $/x, "Bad RXname: $_") + foreach qw({x} a{x} {x}b @A 9.0% $x); + +like($_, qr/^ $RXhex $/x, "Good RXhex: $_") + foreach qw(0x0 0XA 0xAf 0x99 0xfedbca987654321 0XDEADBEEF); +unlike($_, qr/^ $RXhex $/x, "Bad RXhex: $_") + foreach qw(1 x1 0123 0b1010101 -0x12345); + +like($_, qr/^ $RXoct $/x, "Good RXoct: $_") + foreach qw(0 01 07 077 0777 00001 010101 01234567); +unlike($_, qr/^ $RXoct $/x, "Bad RXoct: $_") + foreach qw(1 08 018 0f 0x777 00009 0b1010101); + +like($_, qr/^ $RXuint $/x, "Good RXuint: $_") + foreach qw(0 01 1 9 999 00001 987654321); +unlike($_, qr/^ $RXuint $/x, "Bad RXuint: $_") + foreach qw(-1 0x1 -9 0xf 1.0 1e3 -0x9 0b1010101); + +like($_, qr/^ $RXint $/x, "Good RXint: $_") + foreach qw(0 1 9 -09 999 -90909 00001 010101 123456789); +unlike($_, qr/^ $RXint $/x, "Bad RXint: $_") + foreach qw(0f 0-1 0x777 1.0 1e30 fedcba 0b1010101); + +like($_, qr/^ $RXnum $/x, "Good RXnum: $_") + foreach qw(0 01 0.1 .9 -.9 9.0 -1e2 0.1e+1 .1e1 -.1e1 -1.1E-1 3.1415926535); +unlike($_, qr/^ $RXnum $/x, "Bad RXnum: $_") + foreach qw(0f 0-1 e1 1.e1 1.x -e2 1e3-0 +1 0b1010101); + +# All '\' chars must be doubled inside qr() +like($_, qr/^ $RXdqs $/x, "Good RXdqs: $_") + foreach qw("" "a" "\\"" "\\\\" "\\'" "\\x" "\\\\\\"" "\\"\\\\\\""); +unlike($_, qr/^ $RXdqs $/x, "Bad RXdqs: $_") + foreach qw(x 'x' "x\\" "x\\"x\\"); diff --git a/modules/database/test/tools/Breaktable.plt b/modules/database/test/tools/Breaktable.plt new file mode 100644 index 000000000..e7d9ae904 --- /dev/null +++ b/modules/database/test/tools/Breaktable.plt @@ -0,0 +1,21 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 9; + +use DBD::Breaktable; + +my $bpt = DBD::Breaktable->new('test'); +isa_ok $bpt, 'DBD::Breaktable'; +is $bpt->name, 'test', 'Breakpoint table name'; +is $bpt->points, 0, 'Points == zero'; +$bpt->add_point(0, 0.5); +is $bpt->points, 1, 'First point added'; +is_deeply $bpt->point(0), [0, 0.5], 'First point correct'; +$bpt->add_point(1, 1.5); +is $bpt->points, 2, 'Second point added'; +is_deeply $bpt->point(0), [0, 0.5], 'First point still correct'; +is_deeply $bpt->point(1), [1, 1.5], 'Second point correct'; +is_deeply $bpt->point(2), undef, 'Third point undefined'; + diff --git a/modules/database/test/tools/DBD.plt b/modules/database/test/tools/DBD.plt new file mode 100644 index 000000000..4ecba0c2c --- /dev/null +++ b/modules/database/test/tools/DBD.plt @@ -0,0 +1,59 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 18; + +use DBD; + +my $dbd = DBD->new; +isa_ok $dbd, 'DBD'; + +is keys %{$dbd->breaktables}, 0, 'No breaktables yet'; +my $brk = DBD::Breaktable->new('Brighton'); +$dbd->add($brk); +my %brks = %{$dbd->breaktables}; +is_deeply \%brks, {Brighton => $brk}, 'Added breaktable'; + +is keys %{$dbd->drivers}, 0, 'No drivers yet'; +my $drv = DBD::Driver->new('Danforth'); +$dbd->add($drv); +my %drvs = %{$dbd->drivers}; +is_deeply \%drvs, {Danforth => $drv}, 'Added driver'; + +is keys %{$dbd->functions}, 0, 'No functions yet'; +my $fnc = DBD::Function->new('Frank'); +$dbd->add($fnc); +my %fncs = %{$dbd->functions}; +is_deeply \%fncs, {Frank => $fnc}, 'Added function'; + +is keys %{$dbd->menus}, 0, 'No menus yet'; +my $menu = DBD::Menu->new('Mango'); +$dbd->add($menu); +my %menus = %{$dbd->menus}; +is_deeply \%menus, {Mango => $menu}, 'Added menu'; +is $dbd->menu('Mango'), $menu, 'Named menu'; + +is keys %{$dbd->recordtypes}, 0, 'No recordtypes yet'; +my $rtyp = DBD::Recordtype->new('Rita'); +$dbd->add($rtyp); +my %rtypes = %{$dbd->recordtypes}; +is_deeply \%rtypes, {Rita => $rtyp}, 'Added recordtype'; +is $dbd->recordtype('Rita'), $rtyp, 'Named recordtype'; + +is keys %{$dbd->registrars}, 0, 'No registrars yet'; +my $reg = DBD::Registrar->new('Reggie'); +$dbd->add($reg); +my %regs = %{$dbd->registrars}; +is_deeply \%regs, {Reggie => $reg}, 'Added registrar'; + +is keys %{$dbd->variables}, 0, 'No variables yet'; +my $ivar = DBD::Variable->new('IntVar'); +my $dvar = DBD::Variable->new('DblVar', 'double'); +$dbd->add($ivar); +my %vars = %{$dbd->variables}; +is_deeply \%vars, {IntVar => $ivar}, 'First variable'; +$dbd->add($dvar); +%vars = %{$dbd->variables}; +is_deeply \%vars, {IntVar => $ivar, DblVar => $dvar}, 'Second variable'; + diff --git a/modules/database/test/tools/Device.plt b/modules/database/test/tools/Device.plt new file mode 100644 index 000000000..7ef85e5ce --- /dev/null +++ b/modules/database/test/tools/Device.plt @@ -0,0 +1,32 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 16; + +use DBD::Device; + +my $dev = DBD::Device->new('VME_IO', 'test', 'Device'); +isa_ok $dev, 'DBD::Device'; +is $dev->name, 'test', 'Device name'; +is $dev->link_type, 'VME_IO', 'Link type'; +is $dev->choice, 'Device', 'Choice string'; +ok $dev->legal_addr('#C0xFEED S123 @xxx'), 'Address legal'; +my %dev_addrs = ( + CONSTANT => '12345', + PV_LINK => 'Any:Record.NAME CPP.MS', + VME_IO => '# C1 S2 @Anything', + CAMAC_IO => '# B1 C2 N3 A4 F5 @Anything', + RF_IO => '# R1 M2 D3 E4', + AB_IO => '# L1 A2 C3 S4 @Anything', + GPIB_IO => '# L1 A2 @Anything', + BITBUS_IO => '# L1 N2 P3 S4 @Anything', + BBGPIB_IO => '# L1 B2 G3 @Anything', + VXI_IO => '# V1 C2 S3 @Anything', + INST_IO => '@Anything' +); +while (my ($link, $addr) = each(%dev_addrs)) { + $dev->init($link, 'test', 'Device'); + ok $dev->legal_addr($addr), "$link address"; +} + diff --git a/modules/database/test/tools/Driver.plt b/modules/database/test/tools/Driver.plt new file mode 100644 index 000000000..c1bf062ee --- /dev/null +++ b/modules/database/test/tools/Driver.plt @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 2; + +use DBD::Driver; + +my $drv = DBD::Driver->new('test'); +isa_ok $drv, 'DBD::Driver'; +is $drv->name, 'test', 'Driver name'; + diff --git a/modules/database/test/tools/Function.plt b/modules/database/test/tools/Function.plt new file mode 100644 index 000000000..8c17e27ca --- /dev/null +++ b/modules/database/test/tools/Function.plt @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 2; + +use DBD::Function; + +my $func = DBD::Function->new('test'); +isa_ok $func, 'DBD::Function'; +is $func->name, 'test', 'Function name'; + diff --git a/modules/database/test/tools/Makefile b/modules/database/test/tools/Makefile new file mode 100644 index 000000000..ac7f42071 --- /dev/null +++ b/modules/database/test/tools/Makefile @@ -0,0 +1,26 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* + +TOP = ../.. +include $(TOP)/configure/CONFIG + + +TESTS += Base +TESTS += Breaktable +TESTS += DBD +TESTS += Device +TESTS += Driver +TESTS += Function +TESTS += Menu +TESTS += Recfield +TESTS += Recordtype +TESTS += Registrar +TESTS += Variable + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES diff --git a/modules/database/test/tools/Menu.plt b/modules/database/test/tools/Menu.plt new file mode 100644 index 000000000..3d4f3f951 --- /dev/null +++ b/modules/database/test/tools/Menu.plt @@ -0,0 +1,32 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 14; + +use DBD::Menu; + +my $menu = DBD::Menu->new('test'); +isa_ok $menu, 'DBD::Menu'; +is $menu->name, 'test', 'Menu name'; +is $menu->choices, 0, 'Choices == zero'; +$menu->add_choice('ch1', 'Choice 1'); +is $menu->choices, 1, 'First choice added'; +ok $menu->legal_choice('Choice 1'), 'First choice legal'; +is_deeply $menu->choice(0), ['ch1', 'Choice 1'], 'First choice found'; +$menu->add_choice('ch2', 'Choice 2'); +is $menu->choices, 2, 'Second choice added'; +ok $menu->legal_choice('Choice 1'), 'First choice still legal'; +is_deeply $menu->choice(0), ['ch1', 'Choice 1'], 'First choice still found'; +ok $menu->legal_choice('Choice 2'), 'Second choice legal'; +is_deeply $menu->choice(1), ['ch2', 'Choice 2'], 'Second choice found'; +ok !$menu->legal_choice('Choice 3'), 'Third choice not legal'; +is_deeply $menu->choice(2), undef, 'Third choice undefined'; + +like $menu->toDeclaration, qr/ ^ + \s* typedef \s+ enum \s+ \{ \s* \n + \s* ch1 \s+ \/\* [^*]* \*\/, \s* \n + \s* ch2 \s+ \/\* [^*]* \*\/ \s* \n + \s* \} \s* test \s* ; \s* \n + \s* \# \s* define \s+ test_NUM_CHOICES \s+ 2 \s* \n + \s* $ /x, 'C declaration'; diff --git a/modules/database/test/tools/Recfield.plt b/modules/database/test/tools/Recfield.plt new file mode 100644 index 000000000..7f93e9265 --- /dev/null +++ b/modules/database/test/tools/Recfield.plt @@ -0,0 +1,113 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 76; + +use DBD::Recfield; + +my $fld_string = DBD::Recfield->new('str', 'DBF_STRING'); +isa_ok $fld_string, 'DBD::Recfield'; +isa_ok $fld_string, 'DBD::Recfield::DBF_STRING'; +$fld_string->set_number(0); +is $fld_string->number, 0, 'Field number'; +$fld_string->add_attribute("size", "41"); +is keys %{$fld_string->attributes}, 1, "Size set"; +ok $fld_string->legal_value("Hello, world!"), 'Legal value'; +ok !$fld_string->legal_value("x"x41), 'Illegal string'; +$fld_string->check_valid; +like $fld_string->toDeclaration, qr/^\s*char\s+str\[41\];\s*$/, "C declaration"; + +my $fld_char = DBD::Recfield->new('chr', 'DBF_CHAR'); +isa_ok $fld_char, 'DBD::Recfield'; +isa_ok $fld_char, 'DBD::Recfield::DBF_CHAR'; +is $fld_char->name, 'chr', 'Field name'; +is $fld_char->dbf_type, 'DBF_CHAR', 'Field type'; +ok !$fld_char->legal_value("-129"), 'Illegal - value'; +ok $fld_char->legal_value("-128"), 'Legal - value'; +ok $fld_char->legal_value("127"), 'Legal + value'; +ok !$fld_char->legal_value("0x80"), 'Illegal + hex value'; +$fld_char->check_valid; +like $fld_char->toDeclaration, qr/^\s*epicsInt8\s+chr;\s*$/, "C declaration"; + +my $fld_uchar = DBD::Recfield->new('uchr', 'DBF_UCHAR'); +isa_ok $fld_uchar, 'DBD::Recfield'; +isa_ok $fld_uchar, 'DBD::Recfield::DBF_UCHAR'; +is $fld_uchar->name, 'uchr', 'Field name'; +is $fld_uchar->dbf_type, 'DBF_UCHAR', 'Field type'; +ok !$fld_uchar->legal_value("-1"), 'Illegal - value'; +ok $fld_uchar->legal_value("0"), 'Legal 0 value'; +ok $fld_uchar->legal_value("0377"), 'Legal + value'; +ok !$fld_uchar->legal_value("0400"), 'Illegal + octal value'; +$fld_uchar->check_valid; +like $fld_uchar->toDeclaration, qr/^\s*epicsUInt8\s+uchr;\s*$/, "C declaration"; + +my $fld_short = DBD::Recfield->new('shrt', 'DBF_SHORT'); +isa_ok $fld_short, 'DBD::Recfield'; +isa_ok $fld_short, 'DBD::Recfield::DBF_SHORT'; +is $fld_short->name, 'shrt', 'Field name'; +is $fld_short->dbf_type, 'DBF_SHORT', 'Field type'; +ok !$fld_short->legal_value("-32769"), 'Illegal - value'; +ok $fld_short->legal_value("-32768"), 'Legal - value'; +ok $fld_short->legal_value("32767"), 'Legal + value'; +ok !$fld_short->legal_value("0x8000"), 'Illegal + hex value'; +$fld_short->check_valid; +like $fld_short->toDeclaration, qr/^\s*epicsInt16\s+shrt;\s*$/, "C declaration"; + +my $fld_ushort = DBD::Recfield->new('ushrt', 'DBF_USHORT'); +isa_ok $fld_ushort, 'DBD::Recfield'; +isa_ok $fld_ushort, 'DBD::Recfield::DBF_USHORT'; +is $fld_ushort->name, 'ushrt', 'Field name'; +is $fld_ushort->dbf_type, 'DBF_USHORT', 'Field type'; +ok !$fld_ushort->legal_value("-1"), 'Illegal - value'; +ok $fld_ushort->legal_value("0"), 'Legal 0 value'; +ok $fld_ushort->legal_value("65535"), 'Legal + value'; +ok !$fld_ushort->legal_value("0x10000"), 'Illegal + hex value'; +$fld_ushort->check_valid; +like $fld_ushort->toDeclaration, qr/^\s*epicsUInt16\s+ushrt;\s*$/, "C declaration"; + +my $fld_long = DBD::Recfield->new('lng', 'DBF_LONG'); +isa_ok $fld_long, 'DBD::Recfield'; +isa_ok $fld_long, 'DBD::Recfield::DBF_LONG'; +is $fld_long->name, 'lng', 'Field name'; +is $fld_long->dbf_type, 'DBF_LONG', 'Field type'; +ok $fld_long->legal_value("-12345678"), 'Legal - value'; +ok $fld_long->legal_value("0x12345678"), 'Legal + value'; +ok !$fld_long->legal_value("0xfigure"), 'Illegal value'; +$fld_long->check_valid; +like $fld_long->toDeclaration, qr/^\s*epicsInt32\s+lng;\s*$/, "C declaration"; + +my $fld_ulong = DBD::Recfield->new('ulng', 'DBF_ULONG'); +isa_ok $fld_ulong, 'DBD::Recfield'; +isa_ok $fld_ulong, 'DBD::Recfield::DBF_ULONG'; +is $fld_ulong->name, 'ulng', 'Field name'; +is $fld_ulong->dbf_type, 'DBF_ULONG', 'Field type'; +ok !$fld_ulong->legal_value("-1"), 'Illegal - value'; +ok $fld_ulong->legal_value("00"), 'Legal 0 value'; +ok $fld_ulong->legal_value("0xffffffff"), 'Legal + value'; +ok !$fld_ulong->legal_value("0xfacepaint"), 'Illegal value'; +$fld_ulong->check_valid; +like $fld_ulong->toDeclaration, qr/^\s*epicsUInt32\s+ulng;\s*$/, "C declaration"; + +my $fld_float = DBD::Recfield->new('flt', 'DBF_FLOAT'); +isa_ok $fld_float, 'DBD::Recfield'; +isa_ok $fld_float, 'DBD::Recfield::DBF_FLOAT'; +is $fld_float->name, 'flt', 'Field name'; +is $fld_float->dbf_type, 'DBF_FLOAT', 'Field type'; +ok $fld_float->legal_value("-1.2345678e9"), 'Legal - value'; +ok $fld_float->legal_value("0.12345678e9"), 'Legal + value'; +ok !$fld_float->legal_value("0x1.5"), 'Illegal value'; +$fld_float->check_valid; +like $fld_float->toDeclaration, qr/^\s*epicsFloat32\s+flt;\s*$/, "C declaration"; + +my $fld_double = DBD::Recfield->new('dbl', 'DBF_DOUBLE'); +isa_ok $fld_double, 'DBD::Recfield'; +isa_ok $fld_double, 'DBD::Recfield::DBF_DOUBLE'; +is $fld_double->name, 'dbl', 'Field name'; +is $fld_double->dbf_type, 'DBF_DOUBLE', 'Field type'; +ok $fld_double->legal_value("-12345e-67"), 'Legal - value'; +ok $fld_double->legal_value("12345678e+9"), 'Legal + value'; +ok !$fld_double->legal_value("e5"), 'Illegal value'; +$fld_double->check_valid; +like $fld_double->toDeclaration, qr/^\s*epicsFloat64\s+dbl;\s*$/, "C declaration"; + diff --git a/modules/database/test/tools/Recordtype.plt b/modules/database/test/tools/Recordtype.plt new file mode 100644 index 000000000..ec189a92c --- /dev/null +++ b/modules/database/test/tools/Recordtype.plt @@ -0,0 +1,56 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 17; + +use DBD::Recordtype; +use DBD::Recfield; +use DBD::Device; + +my $rtyp = DBD::Recordtype->new('test'); +isa_ok $rtyp, 'DBD::Recordtype'; +is $rtyp->name, 'test', 'Record name'; +is $rtyp->fields, 0, 'No fields yet'; + +my $fld1 = DBD::Recfield->new('NAME', 'DBF_STRING'); +$fld1->add_attribute("size", "41"); +$fld1->check_valid; + +my $fld2 = DBD::Recfield->new('DTYP', 'DBF_DEVICE'); +$fld2->check_valid; + +$rtyp->add_field($fld1); +is $rtyp->fields, 1, 'First field added'; + +$rtyp->add_field($fld2); +is $rtyp->fields, 2, 'Second field added'; + +my @fields = $rtyp->fields; +is_deeply \@fields, [$fld1, $fld2], 'Field list'; + +my @names = $rtyp->field_names; +is_deeply \@names, ['NAME', 'DTYP'], 'Field name list'; + +is $rtyp->field('NAME'), $fld1, 'Field name lookup'; + +is $fld1->number, 0, 'Field number 0'; +is $fld2->number, 1, 'Field number 1'; + +is $rtyp->devices, 0, 'No devices yet'; + +my $dev1 = DBD::Device->new('INST_IO', 'testDset', 'test device'); +$rtyp->add_device($dev1); +is $rtyp->devices, 1, 'First device added'; + +my @devices = $rtyp->devices; +is_deeply \@devices, [$dev1], 'Device list'; + +is $rtyp->device('test device'), $dev1, 'Device name lookup'; + +is $rtyp->cdefs, 0, 'No cdefs yet'; +$rtyp->add_cdef("cdef"); +is $rtyp->cdefs, 1, 'First cdef added'; + +my @cdefs = $rtyp->cdefs; +is_deeply \@cdefs, ["cdef"], 'cdef list'; diff --git a/modules/database/test/tools/Registrar.plt b/modules/database/test/tools/Registrar.plt new file mode 100644 index 000000000..b5f4d46d0 --- /dev/null +++ b/modules/database/test/tools/Registrar.plt @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 2; + +use DBD::Registrar; + +my $reg = DBD::Registrar->new('test'); +isa_ok $reg, 'DBD::Registrar'; +is $reg->name, 'test', 'Registrar name'; + diff --git a/modules/database/test/tools/Variable.plt b/modules/database/test/tools/Variable.plt new file mode 100644 index 000000000..120732050 --- /dev/null +++ b/modules/database/test/tools/Variable.plt @@ -0,0 +1,14 @@ +#!/usr/bin/env perl + +use lib '@TOP@/lib/perl'; + +use Test::More tests => 4; + +use DBD::Variable; + +my $ivar = DBD::Variable->new('test'); +isa_ok $ivar, 'DBD::Variable'; +is $ivar->name, 'test', 'Variable name'; +is $ivar->var_type, 'int', 'variable defaults to int'; +my $dvar = DBD::Variable->new('test', 'double'); +is $dvar->var_type, 'double', 'double variable';