diff --git a/modules/ca/.ci/travis-build.sh b/modules/ca/.ci/travis-build.sh new file mode 100755 index 000000000..622979b9d --- /dev/null +++ b/modules/ca/.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/ca/.ci/travis-prepare.sh b/modules/ca/.ci/travis-prepare.sh new file mode 100755 index 000000000..85dc8d7ce --- /dev/null +++ b/modules/ca/.ci/travis-prepare.sh @@ -0,0 +1,132 @@ +#!/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}" + +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/ca/.travis.yml b/modules/ca/.travis.yml new file mode 100644 index 000000000..5a99b886a --- /dev/null +++ b/modules/ca/.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 TEST=NO + - CMPLR=clang TEST=NO + - USR_CXXFLAGS=-std=c++11 TEST=NO + - CMPLR=clang USR_CXXFLAGS=-std=c++11 TEST=NO + - 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/ca/Makefile b/modules/ca/Makefile new file mode 100644 index 000000000..13feff6c2 --- /dev/null +++ b/modules/ca/Makefile @@ -0,0 +1,17 @@ +#************************************************************************* +# 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 + +include $(TOP)/configure/RULES_TOP diff --git a/modules/ca/configure/CONFIG b/modules/ca/configure/CONFIG new file mode 100644 index 000000000..331fd7024 --- /dev/null +++ b/modules/ca/configure/CONFIG @@ -0,0 +1,28 @@ +# 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 + +CONFIG = $(RULES)/configure +include $(CONFIG)/CONFIG + +# Override the Base definition: +INSTALL_LOCATION = $(TOP) + +# 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/ca/configure/CONFIG_CA_MODULE b/modules/ca/configure/CONFIG_CA_MODULE new file mode 100644 index 000000000..70ffac19b --- /dev/null +++ b/modules/ca/configure/CONFIG_CA_MODULE @@ -0,0 +1,9 @@ +#************************************************************************* +# 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. +#************************************************************************* + +# Libraries needed to link a host tool +EPICS_BASE_HOST_LIBS = ca Com diff --git a/modules/ca/configure/CONFIG_CA_VERSION b/modules/ca/configure/CONFIG_CA_VERSION new file mode 100644 index 000000000..c50549018 --- /dev/null +++ b/modules/ca/configure/CONFIG_CA_VERSION @@ -0,0 +1,4 @@ +EPICS_CA_MAJOR_VERSION = 4 +EPICS_CA_MINOR_VERSION = 13 +EPICS_CA_MAINTENANCE_VERSION = 2 +EPICS_CA_DEVELOPMENT_FLAG = 1 diff --git a/modules/ca/configure/CONFIG_SITE b/modules/ca/configure/CONFIG_SITE new file mode 100644 index 000000000..d78c7f514 --- /dev/null +++ b/modules/ca/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/ca/configure/Makefile b/modules/ca/configure/Makefile new file mode 100644 index 000000000..e2f373893 --- /dev/null +++ b/modules/ca/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_CA_MODULE +CFG += CONFIG_CA_VERSION + +include $(TOP)/configure/RULES diff --git a/modules/ca/configure/RELEASE b/modules/ca/configure/RELEASE new file mode 100644 index 000000000..a2d9f554a --- /dev/null +++ b/modules/ca/configure/RELEASE @@ -0,0 +1,39 @@ +# 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_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/ca/configure/RULES b/modules/ca/configure/RULES new file mode 100644 index 000000000..6d56e14e8 --- /dev/null +++ b/modules/ca/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/ca/configure/RULES_DIRS b/modules/ca/configure/RULES_DIRS new file mode 100644 index 000000000..3ba269dcc --- /dev/null +++ b/modules/ca/configure/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(CONFIG)/RULES_DIRS diff --git a/modules/ca/configure/RULES_TOP b/modules/ca/configure/RULES_TOP new file mode 100644 index 000000000..2b8cbc6da --- /dev/null +++ b/modules/ca/configure/RULES_TOP @@ -0,0 +1,2 @@ +#RULES_TOP +include $(CONFIG)/RULES_TOP diff --git a/modules/ca/src/Makefile b/modules/ca/src/Makefile new file mode 100644 index 000000000..650b3550f --- /dev/null +++ b/modules/ca/src/Makefile @@ -0,0 +1,25 @@ +#************************************************************************* +# 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 + +# Channel Access Client + +DIRS += client + +DIRS += tools +tools_DEPEND_DIRS = client + +DIRS += perl +perl_DEPEND_DIRS = client + +DIRS += template + +include $(TOP)/configure/RULES_DIRS diff --git a/modules/ca/src/client/CASG.cpp b/modules/ca/src/client/CASG.cpp new file mode 100644 index 000000000..b2dbaac4d --- /dev/null +++ b/modules/ca/src/client/CASG.cpp @@ -0,0 +1,308 @@ +/*************************************************************************\ +* 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 + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "syncGroup.h" +#include "oldAccess.h" +#include "cac.h" +#include "sgAutoPtr.h" + +CASG::CASG ( epicsGuard < epicsMutex > & guard, ca_client_context & cacIn ) : + client ( cacIn ), magic ( CASG_MAGIC ) +{ + client.installCASG ( guard, *this ); +} + +CASG::~CASG () +{ +} + +void CASG::destructor ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + + if ( this->verify ( guard ) ) { + this->reset ( cbGuard, guard ); + this->client.uninstallCASG ( guard, *this ); + this->magic = 0; + } + else { + this->printFormated ( "cac: attempt to destroy invalid sync group ignored\n" ); + } + this->~CASG (); +} + +bool CASG::verify ( epicsGuard < epicsMutex > & ) const +{ + return ( this->magic == CASG_MAGIC ); +} + +/* + * CASG::block () + */ +int CASG::block ( + epicsGuard < epicsMutex > * pcbGuard, + epicsGuard < epicsMutex > & guard, + double timeout ) +{ + epicsTime cur_time; + epicsTime beg_time; + double delay; + double remaining; + int status; + + guard.assertIdenticalMutex ( this->client.mutexRef() ); + + // prevent recursion nightmares by disabling blocking + // for IO from within a CA callback. + if ( epicsThreadPrivateGet ( caClientCallbackThreadId ) ) { + return ECA_EVDISALLOW; + } + + if ( timeout < 0.0 ) { + return ECA_TIMEOUT; + } + + cur_time = epicsTime::getCurrent (); + + this->client.flush ( guard ); + + beg_time = cur_time; + delay = 0.0; + + while ( 1 ) { + if ( this->ioPendingList.count() == 0u ) { + status = ECA_NORMAL; + break; + } + + remaining = timeout - delay; + if ( remaining <= CAC_SIGNIFICANT_DELAY ) { + /* + * Make sure that we take care of + * recv backlog at least once + */ + status = ECA_TIMEOUT; + break; + } + + if ( pcbGuard ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > uncbGuard ( *pcbGuard ); + this->sem.wait ( remaining ); + } + } + else { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->sem.wait ( remaining ); + } + + /* + * force a time update + */ + cur_time = epicsTime::getCurrent (); + + delay = cur_time - beg_time; + } + + return status; +} + +void CASG::reset ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + this->destroyCompletedIO ( cbGuard, guard ); + this->destroyPendingIO ( cbGuard, guard ); +} + +// lock must be applied +void CASG::destroyCompletedIO ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + syncGroupNotify * pNotify; + while ( ( pNotify = this->ioCompletedList.get () ) ) { + pNotify->destroy ( cbGuard, guard ); + } +} + +void CASG::destroyPendingIO ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + while ( syncGroupNotify * pNotify = this->ioPendingList.first () ) { + pNotify->cancel ( cbGuard, guard ); + // cancel must release the guard while + // canceling put callbacks so we + // must double check list membership + if ( pNotify->ioPending ( guard ) ) { + this->ioPendingList.remove ( *pNotify ); + } + else { + this->ioCompletedList.remove ( *pNotify ); + } + pNotify->destroy ( cbGuard, guard ); + } +} + +void CASG::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->client.mutexRef () ); + this->show ( guard, level ); +} + +void CASG::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + ::printf ( "Sync Group: id=%u, magic=%u, opPend=%u\n", + this->getId (), this->magic, this->ioPendingList.count () ); + if ( level ) { + ::printf ( "\tPending" ); + tsDLIterConst < syncGroupNotify > notifyPending = + this->ioPendingList.firstIter (); + while ( notifyPending.valid () ) { + notifyPending->show ( guard, level - 1u ); + notifyPending++; + } + ::printf ( "\tCompleted" ); + tsDLIterConst < syncGroupNotify > notifyCompleted = + this->ioCompletedList.firstIter (); + while ( notifyCompleted.valid () ) { + notifyCompleted->show ( guard, level - 1u ); + notifyCompleted++; + } + } +} + +bool CASG::ioComplete ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + this->destroyCompletedIO ( cbGuard, guard ); + return this->ioPendingList.count () == 0u; +} + +void CASG::put ( epicsGuard < epicsMutex > & guard, chid pChan, + unsigned type, arrayElementCount count, const void * pValue ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + sgAutoPtr < syncGroupWriteNotify > pNotify ( guard, *this ); + pNotify = syncGroupWriteNotify::factory ( + this->freeListWriteOP, *this, & CASG :: recycleWriteNotifyIO, pChan ); + pNotify->begin ( guard, type, count, pValue ); + pNotify.release (); +} + +void CASG::get ( epicsGuard < epicsMutex > & guard, chid pChan, + unsigned type, arrayElementCount count, void *pValue ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + sgAutoPtr < syncGroupReadNotify > pNotify ( guard, *this ); + pNotify = syncGroupReadNotify::factory ( + this->freeListReadOP, *this, & CASG :: recycleReadNotifyIO, pChan, pValue ); + pNotify->begin ( guard, type, count ); + pNotify.release (); +} + +void CASG::completionNotify ( + epicsGuard < epicsMutex > & guard, syncGroupNotify & notify ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + this->ioPendingList.remove ( notify ); + this->ioCompletedList.add ( notify ); + if ( this->ioPendingList.count () == 0u ) { + this->sem.signal (); + } +} + +void CASG :: recycleReadNotifyIO ( epicsGuard < epicsMutex > & guard, + syncGroupReadNotify & io ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + this->freeListReadOP.release ( & io ); +} + +void CASG :: recycleWriteNotifyIO ( epicsGuard < epicsMutex > & guard, + syncGroupWriteNotify & io ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + this->freeListWriteOP.release ( & io ); +} + +int CASG :: printFormated ( const char *pformat, ... ) +{ + va_list theArgs; + int status; + + va_start ( theArgs, pformat ); + + status = this->client.varArgsPrintFormated ( pformat, theArgs ); + + va_end ( theArgs ); + + return status; +} + +void CASG::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char * pContext, + const char * pFileName, unsigned lineNo ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + if ( status != ECA_CHANDESTROY ) { + this->client.exception ( + guard, status, pContext, pFileName, lineNo ); + } +} + +void CASG::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char * pContext, + const char * pFileName, unsigned lineNo, oldChannelNotify & chan, + unsigned type, arrayElementCount count, unsigned op ) +{ + guard.assertIdenticalMutex ( this->client.mutexRef() ); + if ( status != ECA_CHANDESTROY ) { + this->client.exception ( + guard, status, pContext, pFileName, + lineNo, chan, type, count, op ); + } +} + +void CASG::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/ca/src/client/CAref.html b/modules/ca/src/client/CAref.html new file mode 100644 index 000000000..196635e6c --- /dev/null +++ b/modules/ca/src/client/CAref.html @@ -0,0 +1,4489 @@ + + + + + EPICS Channel Access 4.13.1 Reference Manual + + + + +
+ +

EPICS Channel Access 4.13.1 Reference Manual

+
+ Jeffrey O. Hill +
+ +

Los Alamos National +Laboratory, SNS Division

+
+ Ralph Lange +
+ +

Helmholtz-Zentrum +Berlin (BESSY II)

+ +

Copyright © 2009 +Helmholtz-Zentrum Berlin für Materialien und Energie GmbH.
+Copyright © 2002 The University of Chicago, as Operator of Argonne National +Laboratory.
+Copyright © 2002 The Regents of the University of California, as Operator of +Los Alamos National Laboratory.
+Copyright © 2002 Berliner Speicherringgesellschaft für Synchrotronstrahlung +GmbH.

+ +

EPICS BASE is +distributed subject to a Software License Agreement found +in the file LICENSE that is included with this distribution.

+ +
+ +

Table of Contents

+ +

Configuration

+ + +

Building an Application

+ + +

Command Line Utilities

+ + +

Command Line Tools

+ + +

Troubleshooting

+ + +

Function Call Interface Guidelines

+ + +

Functionality Index

+ + +

Function Call Interface Index

+ + +

Deprecated Function Call Interface Function Index

+ + +

Return Codes

+
+ +

Configuration

+ +

Why Reconfigure Channel Access

+ +

Typical reasons to reconfigure EPICS Channel Access:

+
    +
  • Two independent control systems must share a network without fear of + interaction
  • +
  • A test system must not interact with an operational system
  • +
  • Use of address lists instead of broadcasts for name resolution and server + beacons
  • +
  • Control system occupies multiple IP subnets
  • +
  • Nonstandard client disconnect time outs or server beacon intervals
  • +
  • Specify the local time zone
  • +
  • Transport of large arrays
  • +
+ +

EPICS Environment Variables

+ +

All Channel Access (CA) configuration occurs through EPICS environment +variables. When searching for an EPICS environment variable EPICS first looks +in the environment using the ANSI C getenv() call. If no matching variable +exists then the default specified in the EPICS build system configuration files +is used.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameRangeDefault
EPICS_CA_ADDR_LIST{N.N.N.N N.N.N.N:P ...}<none>
EPICS_CA_AUTO_ADDR_LIST{YES, NO}YES
EPICS_CA_NAME_SERVERS{N.N.N.N N.N.N.N:P ...}<none>
EPICS_CA_CONN_TMOr > 0.1 seconds30.0
EPICS_CA_BEACON_PERIODr > 0.1 seconds15.0
EPICS_CA_REPEATER_PORTi > 50005065
EPICS_CA_SERVER_PORTi > 50005064
EPICS_CA_MAX_ARRAY_BYTESi >= 1638416384
EPICS_CA_AUTO_ARRAY_BYTES{YES, NO}YES
EPICS_CA_MAX_SEARCH_PERIODr > 60 seconds300
EPICS_CA_MCAST_TTLr > 11
EPICS_TS_MIN_WEST-720 < i <720 minutes360
+ +

Environment variables are set differently depending on the command line +shell that is in use.

+ + + + + + + + + + + + + + + + + + + + + + + + +
C shellsetenv EPICS_CA_ADDR_LIST 1.2.3.4
bashexport EPICS_CA_ADDR_LIST=1.2.3.4
vxWorks shellputenv ( "EPICS_CA_ADDR_LIST=1.2.3.4" )
DOS command lineset EPICS_CA_ADDR_LIST=1.2.3.4
Windows NT / 2000 / XPcontrol panel / system / environment tab
+ +

CA and Wide Area Networks

+ +

Normally in a local area network (LAN) environment CA discovers the address +of the host for an EPICS process variable by broadcasting frames containing a +list of channel names (CA search messages) and waiting for responses from the +servers that host the channels identified. Likewise CA clients efficiently +discover that CA servers have recently joined the LAN or disconnected from the +LAN by monitoring periodically broadcasted beacons sent out by the servers. +Since hardware broadcasting requires special hardware capabilities, we are +required to provide additional configuration information when EPICS is extended +to operate over a wide area network (WAN).

+ +

IP Network Administration Background Information

+ +

Channel Access is implemented using Internet protocols (IP). IP addresses +are divided into host and network portions. The boundary between each portion +is determined by the IP netmask. Portions of the IP address corresponding to +zeros in the netmask specify the hosts address within an IP subnet. Portions of +the IP address corresponding to binary ones in the netmask specify the address +of a host's IP subnet. Normally the scope of a broadcasted frame will be +limited to one IP subnet. Addresses with the host address portion set to all +zeros or all ones are special. Modern IP kernel implementations reserve +destination addresses with the host portion set to all ones for the purpose of +addressing broadcasts to a particular subnet. In theory we can issue a +broadcast frame on any broadcast capable LAN within the interconnected Internet +by specifying the proper subnet address combined with a host portion set to all +ones. In practice these "directed broadcasts" are frequently limited by the +default router configuration. The proper directed broadcast address required to +reach a particular host can be obtained by logging into that host and typing +the command required by your local operating environment. Ignore the loop back +interface and use the broadcast address associated with an interface connected +to a path through the network to your client. Typically there will be only one +Ethernet interface.

+ + + + + + + + + + + + + + + + +
UNIXifconfig -a
vxWorksifShow
Windowsipconfig
+ +

IP ports are positive integers. The IP address, port number, and protocol +type uniquely identify the source and destination of a particular frame +transmitted between computers. Servers are typically addressed by a well known +port number. Clients are assigned a unique ephemeral port number during +initialization. IP ports below 1024 are reserved for servers that provide +standardized facilities such as mail or file transfer. Port number between 1024 +and 5000 are typically reserved for ephemeral port number assignments.

+ +

IP port numbers

+ +

The two default IP port numbers used by Channel Access may be reconfigured. +This might occur when a site decides to set up two or more completely +independent control systems that will share the same network. For instance, a +site might set up an operational control system and a test control system on +the same network. In this situation it is desirable for the test system and the +operational system to use identical PV names without fear of collision. A site +might also configure the CA port numbers because some other facility is already +using the default port numbers. The default Channel Access port numbers have +been registered with IANA.

+ + + + + + + + + + + + + + + + + + + + +
PurposeDefaultEnvironment Variable
CA Server5064EPICS_CA_SERVER_PORT
CA Beacons (sent to CA repeater daemon)5065EPICS_CA_REPEATER_PORT
+ +

If a client needs to communicate with two servers that are residing at +different port numbers then an extended syntax may be used with the +EPICS_CA_ADDR_LIST environment variable. See WAN +Environment below.

+ +

Firewalls

+ +

If you want channel access clients on a machine to be able to see beacons +and replies to broadcast PV search requests, you need to permit inbound UDP +packets with source port EPICS_CA_SERVER_PORT (default is 5064) or destination +port EPICS_CA_REPEATER_PORT (default is 5065). On systems using iptables this +can be accomplished by rules like

+
     -A INPUT -s 192.168.0.0/22 -p udp --sport 5064 -j ACCEPT
+     -A INPUT -s 192.168.0.0/22 -p udp --dport 5065 -j ACCEPT
+ +

If you want channel access servers (e.g. "soft IOCs") on a machine to be +able to be seen by clients, you need to permit inbound TCP or UDP packets with +destination port EPICS_CA_SERVER_PORT (default is 5064). On systems using +iptables this can be accomplished by rules like

+
     -A INPUT -s 192.168.0.0/22 -p udp --dport 5064 -j ACCEPT
+     -A INPUT -s 192.168.0.0/22 -p tcp --dport 5064 -j ACCEPT
+ +

In all cases the "-s 192.168.0.0/22" specifies the range of addresses from +which you wish to accept packets.

+ +

WAN Environment

+ +

When the CA client library connects a channel it must first determine the IP +address of the server the channels Process Variable resides on. To accomplish +this the client sends name resolution (search) requests to a list of server +destination addresses. These server destination addresses can be IP unicast +addresses (individual host addresses) or IP broadcast addresses. Each name +resolution (search) request contains a list of Process Variable names.If one of +the servers reachable by this address list knows the IP address of a CA server +that can service one or more of the specified Process Variables, then it sends +back a response containing the server's IP address and port number.

+ +

During initialization CA builds the list of server destination addresses +used when sending CA client name resolution (search) requests. This table is +initialized by introspecting the network interfaces attached to the host. For +each interface found that is attached to a broadcast capable IP subnet, the +broadcast address of that subnet is added to the list. For each point to point +interface found, the destination address of that link is added to the list. +This automatic server address list initialization can be disabled if the EPICS +environment variable EPICS_CA_AUTO_ADDR_LIST exists and its value is either +"no" or "NO". The typical default is to enable network interface introspection +driven initialization with EPICS_CA_AUTO_ADDR_LIST set to "YES" or "yes".

+ +

Following network interface introspection, any IP addresses specified in the +EPICS environment variable EPICS_CA_ADDR_LIST are added to the list of +destination addresses for CA client name resolution requests. In an EPICS +system crossing multiple subnets the EPICS_CA_ADDR_LIST must be set so that CA +name resolution (search requests) frames pass from CA clients to the targeted +CA servers unless a CA proxy (gateway) is installed. The addresses in +EPICS_CA_ADDR_LIST may be dotted IP addresses or host names if the local OS has +support for host name to IP address translation. When multiple names are added +to EPICS_CA_ADDR_LIST they must be separated by white space. There is no +requirement that the addresses specified in the EPICS_CA_ADDR_LIST be broadcast +addresses, but this will often be the most convenient choice.

+ +

For any IP addresses specified in the EPICS environment variable +EPICS_CA_NAME_SERVERS, TCP connections are opened and used for CA client name +resolution requests. (Thus, broadcast addresses are not allowed in +EPICS_CA_NAME_SERVERS.) When used in combination with an empty +EPICS_CA_ADDR_LIST and EPICS_CA_AUTO_ADDR_LIST set to "NO", Channel Access can +be run without using UDP for name resolution. Such an TCP-only mode allows for +Channel Access to work e.g. through SSH tunnels.

+ + + + + + + + + + + + + + + + +
C shellsetenv EPICS_CA_ADDR_LIST "1.2.3.255 8.9.10.255"
bashexport EPICS_CA_ADDR_LIST="1.2.3.255 8.9.10.255"
vxWorksputenv ( "EPICS_CA_ADDR_LIST=1.2.3.255 8.9.10.255" )
+ +

If a client needs to communicate with two servers that are residing at +different port numbers then an extended syntax may be used with the +EPICS_CA_ADDR_LIST environment variable. Each host name or IP address in the +EPICS_CA_ADDR_LIST may be immediately followed by a colon and an IP port number +without intervening whitespace. Entries that do not specify a port number will +default to EPICS_CA_SERVER_PORT.

+ + + + + + + + +
C shellsetenv EPICS_CA_ADDR_LIST "1.2.3.255 8.9.10.255:10000"
+ +

Routing Restrictions on vxWorks Systems

+ +

Frequently vxWorks systems boot by default with routes limiting access only +to the local subnet. If a EPICS system is operating in a WAN environment it may +be necessary to configure routes into the vxWorks system which enable a vxWorks +based CA server to respond to requests originating outside its subnet. These +routing restrictions can also apply to vxWorks base CA clients communicating +with off subnet servers. An EPICS system manager can implement an rudimentary, +but robust, form of access control for a particular host by not providing +routes in that host that reach outside of a limited set of subnets. See +"routeLib" in the vxWorks reference manual.

+ +

Disconnect Time Out Interval

+ +

If the CA client library does not see a beacon from a server that it is +connected to for EPICS_CA_CONN_TMO seconds then an state-of-health message is +sent to the server over TCP/IP. If this state-of-health message isn't promptly +replied to then the client library will conclude that channels communicating +with the server are no longer responsive and inform the CA client side +application via function callbacks. The parameter EPICS_CA_CONN_TMO is +specified in floating point seconds. The default is typically 30 seconds. For +efficient operation it is recommended that EPICS_CA_CONN_TMO be set to no less +than twice the value specified for EPICS_CA_BEACON_PERIOD.

+ +

Prior to EPICS R3.14.5 an unresponsive server implied an immediate TCP +circuit disconnect, immediate resumption of UDP based search requests, and +immediate attempts to reconnect. There was concern about excessive levels of +additional activity when servers are operated close to the edge of resource +limitations. Therefore with version R3.14.5 and greater the CA client library +continues to inform client side applications when channels are unresponsive, +but does not immediately disconnect the TCP circuit. Instead the CA client +library postpones circuit shutdown until receiving indication of circuit +disconnect from the IP kernel. This can occur either because a server is +restarted or because the IP kernel's internal TCP circuit inactivity keep alive +timer has expired after a typically long duration (as is appropriate for IP +based systems that need to avoid thrashing during periods of excessive load). +The net result is less search and TCP circuit setup and shutdown activity +during periods of excessive load.

+ +

Dynamic Changes in the CA Client Library Search +Interval

+ +

The CA client library will continuously attempt to connect any CA channels +that an application has created until it is successful. The library +periodically queries the server destination address list described above with +name resolution requests for any unresolved channels. Since this address list +frequently contains broadcast addresses, and because nonexistent process +variable names are frequently configured, or servers may be temporarily +unavailable, then it is necessary for the CA client library internals to +carefully schedule these requests in time to avoid introducing excessive load +on the network and the servers.

+ +

When the CA client library has many channels to connect, and most of its +name resolution requests are responded to, then it sends name resolution +requests at an interval that is twice the estimated round trip interval for the +set of servers responding, or at the minimum delay quantum for the operating +system - whichever is greater. The number of UDP frames per interval is also +dynamically adjusted based on the past success rates.

+ +

If a name resolution request is not responded to, then the client library +doubles the delay between name resolution attempts and reduces the number of +requests per interval. The maximum delay between attempts is limited by +EPICS_CA_MAX_SEARCH_PERIOD (see Configuring the Maximum +Search Period). Note however that prior to R3.14.7, if the client library +did not receive any responses over a long interval it stopped sending name +resolution attempts altogether until a beacon anomaly was detected (see +below).

+ +

The CA client library continually estimates the beacon period of all server +beacons received. If a particular server's beacon period becomes significantly +shorter or longer then the client is said to detect a beacon anomaly. The +library boosts the search interval for unresolved channels when a beacon +anomaly is seen or when any successful search response is received, +but with a longer initial interval between requests than is used when the +application creates a channel. Creation of a new channel does not +(starting with EPICS R3.14.7) change the interval used when searching for +preexisting unresolved channels. The program "casw" prints a message on +standard out for each CA client beacon anomaly detect event.

+ +

See also When a Client Does not See the Server's +Beacon.

+ +

Configuring the Maximum Search +Period

+ +

The rate at which name resolution (search) requests are sent exponentially +backs off to a plateau rate. The value of this plateau has an impact on network +traffic because it determines the rate that clients search for channel names +that are miss-spelled or otherwise don't exist in a server. Furthermore, for +clients that are unable to see the beacon from a new server, the plateau rate +may also determine the maximum interval that the client will wait until +discovering a new server.

+ +

Starting with EPICS R3.14.7 this maximum search rate interval plateau in +seconds is determined by the EPICS_CA_MAX_SEARCH_PERIOD environment +variable.

+ +

See also When a Client Does not See the Server's +Beacon.

+ +

The CA Repeater

+ +

When several client processes run on the same host it is not possible for +all of them to directly receive a copy of the server beacon messages when the +beacon messages are sent to unicast addresses, or when legacy IP kernels are +still in use. To avoid confusion over these restrictions a special UDP server, +the CA Repeater, is automatically spawned by the CA client library when it is +not found to be running. This program listens for server beacons sent to the +UDP port specified in the EPICS_CA_REPEATER_PORT parameter and fans any beacons +received out to any CA client program running on the same host that have +registered themselves with the CA Repeater. If the CA Repeater is not already +running on a workstation, then the "caRepeater" program must be in your path +before using the CA client library for the first time.

+ +

If a host based IOC is run on the same workstation with standalone CA client +processes, then it is probably best to start the caRepeater process when the +workstation is booted. Otherwise it is possible for the standalone CA client +processes to become dependent on a CA repeater started within the confines of +the host based IOC. As long as the host based IOC continues to run there is +nothing wrong with this situation, but problems could arise if this host based +IOC process exits before the standalone client processes which are relying on +its CA repeater for services exit.

+ +

Since the repeater is intended to be shared by multiple clients then it +could be argued that it makes less sense to set up a CA repeater that listens +for beacons on only a subset of available network interfaces. In the worst case +situation the client library might see beacon anomalies from servers that it is +not interested in. Modifications to the CA repeater forcing it to listen only +on a subset of network interfaces might be considered for a future release if +there appear to be situations that require it.

+ +

Configuring the Time Zone

+ +

Note: Starting with EPICS R3.14 all of the libraries in the EPICS base +distribution rely on facilities built into the operating system to determine +the correct time zone. Nevertheless, several programs commonly used with EPICS +still use the original "tssubr" library and therefore they still rely on proper +configuration of EPICS_TS_MIN_WEST.

+ +

While the CA client library does not translate between the local time and +the time zone independent internal storage of EPICS time stamps, many EPICS +client side applications call core EPICS libraries which provide these +services. To set the correct time zone users must compute the number of +positive minutes west of GMT (maximum 720 inclusive) or the negative number of +minutes east of GMT (minimum -720 inclusive). This integer value is then placed +in the variable EPICS_TS_MIN_WEST.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Time ZoneEPICS_TS_MIN_WEST
USA Eastern300
USA Central360
USA Mountain420
USA Pacific480
Alaska540
Hawaii600
Japan-540
China-420
Germany-120
United Kingdom0
+ +

Configuring the Maximum Array Size

+ +

From version R3.16.1, the default setting of EPICS_CA_AUTO_ARRAY_BYTES=YES +will cause the software to ignore EPICS_CA_MAX_ARRAY_BYTES and attempt to +allocate network buffer space as needed by the particular client connection +using malloc. Setting EPICS_CA_AUTO_ARRAY_BYTES=NO will configure the software +to respect the EPICS_CA_MAX_ARRAY_BYTES setting as described below instead.

+ +

Starting with version R3.14 the environment variable +EPICS_CA_MAX_ARRAY_BYTES determines the size of the largest array that may pass +through CA. Prior to this version only arrays smaller than 16k bytes could be +transfered. The CA libraries maintains a free list of 16384 byte network +buffers that are used for ordinary communication. If EPICS_CA_MAX_ARRAY_BYTES +is larger than 16384 then a second free list of larger data buffers is +established and used only after a client send its first large array request.

+ +

The CA client library uses EPICS_CA_MAX_ARRAY_BYTES to determines the +maximum array that it will send or receive. Likewise, the CA server uses +EPICS_CA_MAX_ARRAY_BYTES to determine the maximum array that it may send or +receive. The client does not influence the server's message size quotas and +visa versa. In fact the value of EPICS_CA_MAX_ARRAY_BYTES need not be the same +in the client and the server. If the server receives a request which is too +large to read or respond to in entirety then it sends an exception message to +the client. Likewise, if the CA client library receives a request to send an +array larger than EPICS_CA_MAX_ARRAY_BYTES it will return ECA_TOLARGE.

+ +

A common mistake is to correctly calculate the maximum datum size in bytes +by multiplying the number of elements by the size of a single element, but +neglect to add additional bytes for the compound data types (for example +DBR_GR_DOUBLE) commonly used by the more sophisticated client side +applications.

+ +

Configuring a CA Server

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameRangeDefault
EPICS_CAS_SERVER_PORTi > 5000EPICS_CA_SERVER_PORT
EPICS_CAS_AUTO_BEACON_ADDR_LIST{YES, NO}EPICS_CA_AUTO_ADDR_LIST
EPICS_CAS_BEACON_ADDR_LIST{N.N.N.N N.N.N.N:P ...}EPICS_CA_ADDR_LIST1
EPICS_CAS_BEACON_PERIODr > 0.1 secondsEPICS_CA_BEACON_PERIOD
EPICS_CAS_BEACON_PORTi > 5000EPICS_CA_REPEATER_PORT
EPICS_CAS_INTF_ADDR_LIST{N.N.N.N N.N.N.N:P ...}<none>
EPICS_CAS_IGNORE_ADDR_LIST{N.N.N.N N.N.N.N:P ...}<none>
+ +

Server Port

+ +

The server configures its port number from the EPICS_CAS_SERVER_PORT +environment variable if it is specified. Otherwise the EPICS_CA_SERVER_PORT +environment variable determines the server's port number. Two servers can share +the same UDP port number on the same machine, but there are restrictions - see +a discussion of unicast addresses and two servers sharing +the same UDP port on the same host.

+ +

Server Beacons

+ +

The EPICS_CAS_BEACON_PERIOD parameter determines the server's beacon period +and is specified in floating point seconds. The default is typically 15 +seconds. See also EPICS_CA_CONN_TMO and +Dynamic Changes in the CA Client Library Search +Interval.

+ +

CA servers build a list of addresses to send beacons to during +initialization. If EPICS_CAS_AUTO_BEACON_ADDR_LIST has the value "YES" +(the default) this list will be automatically populated with the broadcast +addresses of all network interfaces. However, if the user also +defines EPICS_CAS_INTF_ADDR_LIST then beacon address list automatic +configuration is constrained to the network interfaces specified therein, and +therefore only the broadcast addresses of the specified LAN interfaces, will be +automatically configured.

+ +

If EPICS_CAS_BEACON_ADDR_LIST is defined then its contents will be used to +augment any automatic configuration of the beacon address list. Individual +entries in EPICS_CAS_BEACON_ADDR_LIST may override the destination port number +if ":nnn" follows the host name or IP address there.

+ +

The EPICS_CAS_BEACON_PORT parameter specifies the destination port for +server beacons. The only exception to this occurs when ports are specified in +EPICS_CAS_BEACON_ADDR_LIST or possibly in EPICS_CA_ADDR_LIST. If +EPICS_CAS_BEACON_PORT is not specified then beacons are sent to the port +specified in EPICS_CA_REPEATER_PORT.

+ +

Binding a Server to a Limited Set of Network Interfaces

+ +

The parameter EPICS_CAS_INTF_ADDR_LIST allows a ca server to bind itself to, +and therefore accept messages received by, a limited set of the local host's +network interfaces (each specified by its IP address). On UNIX systems type +"netstat -ie" (type "ipconfig" on windows) to see a list of the local host's +network interfaces. By default, +the CA server is accessible from all network interfaces configured into its +host.

+ +

Until R3.15.4 the CA server employed by iocCore did not +implement the EPICS_CAS_INTF_ADDR_LIST feature.

+ +

Prior to R3.15.4 CA servers would build the beacon address list +using EPICS_CA_ADDR_LIST if EPICS_CAS_BEACON_ADDR_LIST was no set.

+ +

Ignoring Process Variable Name Resolution Requests From Certain Hosts

+ +

Name resolution requests originating from any of the IP addresses specified +in the EPICS_CAS_IGNORE_ADDR_LIST parameter are not replied to.In R3.14 and +previous releases the CA server employed by iocCore does not implement this +feature.

+ +

Client Configuration that also Applies to Servers

+ +

See also Configuring the Maximum Array Size.

+ +

See also Routing Restrictions on vxWorks Systems.

+
+ +

Building an Application

+ +

Required Header (.h) Files

+ +

An application that uses the CA client library functions described in this +document will need to include the cadef.h header files as follows.

+ +

#include "cadef.h"

+ +

This header file is located at "<EPICS base>/include/". It includes +many other header files (operating system specific and otherwise), and +therefore the application must also specify "<EPICS +base>/include/os/<arch>" in its header file search path.

+ +

Required Libraries

+ +

An application that uses the Channel Access Client Library functions +described in this document will need to link with the EPICS CA Client Library +and also the EPICS Common Library. The EPICS CA Client Library calls the EPICS +Common Library. The following table shows the names of these libraries on UNIX +and Windows systems.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
UNIX ObjectUNIX ShareableWindows ObjectWindows Shareable
EPICS CA Client Librarylibca.alibca.soca.libca.dll
+
+ EPICS Common Library
+
libCom.alibCom.soCom.libCom.dll
+ +

+ +

The above libraries are located in "<EPICS +base>/lib/<architecture>".

+ +

Compiler and System Specific Build +Options

+ +

If you do not use the EPICS build environment (layered make files) then it +may be helpful to run one of the EPICS make files and watch the compile/link +lines. This may be the simplest way to capture the latest system and compiler +specific options required by your build environment. Some snapshots of typical +build lines are shown below, but this information may be out of date.

+ +

Typical Linux Build Options

+ +

gcc -D_GNU_SOURCE -DOSITHREAD_USE_DEFAULT_STACK -D_X86_ -DUNIX -Dlinux +-O3 -g -Wall -I. -I.. -I../../../../include/compiler/gcc +-I../../../../include/os/Linux -I../../../../include -c ../acctst.c

+ +

g++ -o acctst -L/home/user/epics/base-3.15/lib/linux-x86 +-Wl,-rpath,/home/user/epics/base-3.15/lib/linux-x86 +acctstMain.o acctst.o -lca -lCom

+ +

Typical Solaris Build Options

+ +

/opt/SUNWspro/bin/cc -c -D_POSIX_C_SOURCE=199506L -D_XOPEN_SOURCE=500 +-DOSITHREAD_USE_DEFAULT_STACK -DUNIX -DSOLARIS=9 -mt -D__EXTENSIONS__ -Xc -v +-xO4 -I. -I.. -I../../../../include/compiler/solStudio +-I../../../../include/os/solaris -I../../../../include ../acctst.c

+ +

/opt/SUNWspro/bin/CC -o acctst +-L/home/user/epics/base-3.15/lib/solaris-sparc/ -mt -z ignore -z combreloc +-z lazyload -R/home/user/epics/base-3.15/lib/solaris-sparc acctstMain.o +acctst.o -lca -lCom

+ +

Typical Windows Build Options

+ +

cl -c /nologo /D__STDC__=0 /Ox /GL /W3 /w44355 /MD -I. -I.. +-I..\\..\\..\\..\\include\\compiler\\msvc -I..\\..\\..\\..\\include\\os\\WIN32 +-I..\\..\\..\\..\\include ..\\acctst.c

+ +

link -nologo /LTCG /incremental:no /opt:ref /release /version:3.15 +-out:acctst.exe acctstMain.obj acctst.obj +d:/user/epics/base-3.15/lib/win32-x86/ca.lib +d:/user/epics/base-3.15/lib/win32-x86/Com.lib

+ +

Typical vxWorks Build Options

+ +

/usr/local/vxWorks-6.9/gnu/4.3.3-vxworks-6.9/x86-linux2/bin/ccppc +-DCPU=PPC32 -DvxWorks=vxWorks -O2 -Wall -mstrict-align -mlongcall -fno-builtin +-include /usr/local/vxWorks-6.9/vxworks-6.9/target/h/vxWorks.h +-I. -I../O.Common -I.. -I../../../../include/compiler/gcc +-I../../../../include/os/vxWorks -I../../../../include +-I/usr/local/vxWorks-6.9/vxworks-6.9/target/h +-I/usr/local/vxWorks-6.9/vxworks-6.9/target/h/wrn/coreip +-c ../acctst.c

+ +

Other Systems and Compilers

+ +

Contributions gratefully accepted.

+
+ +

Command Line Utilities

+ +

acctst

+
acctst <PV name> [progress logging level] [channel duplication count] 
+                 [test repetition count] [enable preemptive callback]
+ +

Description

+ +

Channel Access Client Library regression test.

+ +

The PV used with the test must be native type DBR_DOUBLE or DBR_FLOAT, and +modified only by acctst while the test is running. Therefore, periodically +scanned hardware attached analog input records do not work well. Test failure +is indicated if the program stops prior to printing "test complete". If +unspecified the progress logging level is zero, and no messages are printed +while the test is progressing. If unspecified, the channel duplication count is +20000. If unspecified, the test repetition count is once only. If unspecified, +preemptive callback is disabled.

+ +

catime

+
catime <PV name> [channel count] [append number to pv name if true]
+ +

Description

+ +

Channel Access Client Library performance test.

+ +

If unspecified, the channel count is 10000. If the "append number to pv name +if true" argument is specified and it is greater than zero then the channel +names in the test are numbered as follows.

+ +

<PV name>000000, <PV name>000001, ... <PV name>nnnnnn

+ +

casw

+
casw [-i <interest level>]
+ +

Description

+ +

CA server "beacon anomaly" logging.

+ +

CA server beacon anomalies occur when a new server joins the network, a +server is rebooted, network connectivity to a server is reestablished, or if a +server's CPU exits a CPU load saturated state.

+ +

CA clients with unresolved channels reset their search request scheduling +timers whenever they see a beacon anomaly.

+ +

This program can be used to detect situations where there are too many +beacon anomalies. IP routing configuration problems may result in false beacon +anomalies that might cause CA clients to use unnecessary additional network +bandwidth and server CPU load when searching for unresolved channels.

+ +

If there are no new CA servers appearing on the network, and network +connectivity remains constant, then casw should print no messages at all. At +higher interest levels the program prints a message for every beacon that is +received, and anomalous entries are flagged with a star.

+ +

caEventRate

+
caEventRate <PV name> [subscription count]
+ +

Description

+ +

Connect to the specified PV, subscribe for monitor updates the specified +number of times (default once), and periodically log the current sampled event +rate, average event rate, and the standard deviation of the event rate in Hertz +to standard out.

+ +

ca_test

+
ca_test <PV name> [value to be written]
+ +

Description

+ +

If a value is specified it is written to the PV. Next, the current value of +the PV is converted to each of the many external data type that can be +specified at the CA client library interface, and each of these is formated and +then output to the console.

+
+ +

Command Line Tools

+ +

caget

+
caget [options] <PV name> ...
+ +

Description

+ +

Get and print value for PV(s).

+ +

The values for one or multiple PVs are read and printed to stdout. The +DBR_... format in which the data is read, the output format, and a number of +details of how integer and float values are represented can be controlled using +command line options.

+ +

When getting multiple PVs, their order on the command line is retained in +the output.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
-hPrint usage information
CA options:
-w <sec>Wait time, specifies longer CA timeout, default is 1.0 second
-cAsynchronous get (use ca_get_callback instead of ca_get)
-p <prio>CA priority (0-99, default 0=lowest)
Format and data type options:
Default output format is "name value"
-tTerse mode - print only value, without name
-aWide mode "name timestamp value stat sevr" (read PVs as + DBR_TIME_xxx)
-d <type>Request specific dbr type; use string (DBR_ prefix may be omitted)
+ or number of one of the following types:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DBR_STRING0DBR_STS_FLOAT9DBR_TIME_LONG19DBR_CTRL_SHORT29
DBR_INT1DBR_STS_ENUM10DBR_TIME_DOUBLE20DBR_CTRL_INT29
DBR_SHORT1DBR_STS_CHAR11DBR_GR_STRING21DBR_CTRL_FLOAT30
DBR_FLOAT2DBR_STS_LONG12DBR_GR_SHORT22DBR_CTRL_ENUM31
DBR_ENUM3DBR_STS_DOUBLE13DBR_GR_INT22DBR_CTRL_CHAR32
DBR_CHAR4DBR_TIME_STRING14DBR_GR_FLOAT23DBR_CTRL_LONG33
DBR_LONG5DBR_TIME_INT15DBR_GR_ENUM24DBR_CTRL_DOUBLE34
DBR_DOUBLE6DBR_TIME_SHORT15DBR_GR_CHAR25DBR_STSACK_STRING37
DBR_STS_STRING7DBR_TIME_FLOAT16DBR_GR_LONG26DBR_CLASS_NAME38
DBR_STS_SHORT8DBR_TIME_ENUM17DBR_GR_DOUBLE27
DBR_STS_INT8DBR_TIME_CHAR18DBR_CTRL_STRING28
+
Enum format:
-nPrint DBF_ENUM value as number (default is enum string)
Arrays:
Value format: Print number of requested values, then list of + values
Default:Print all values
-# <count>Print first <count> elements of an array
-SPrint array of char as a string (long string)
Floating point type format:
Default:Use %g format
-e <nr>Use %e format, with a precision of <nr> digits
-f <nr>Use %f format, with a precision of <nr> digits
-g <nr>Use %g format, with a precision of <nr> digits
-sGet value as string (honors server-side precision)
-lxRound to long integer and print as hex number
-loRound to long integer and print as octal number
-lbRound to long integer and print as binary number
Integer number format:
Default:Print as decimal number
-0xPrint as hex number
-0oPrint as octal number
-0bPrint as binary number
Alternate output field separator:
-F <ofs>Use <ofs> as an alternate output field separator
+ +

camonitor

+
camonitor [options] <PV name> ...
+ +

Description

+ +

Subscribe to and print value updates for PV(s).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
-hPrint usage information
CA options:
-w <sec>Wait time, specifies longer CA timeout, default is 1.0 second
-m <msk>Specify CA event mask to use. <msk> is any combination of
+ 'v' (value), 'a' (alarm), 'l' (log/archive), 'p' (property).
+ Default event mask is 'va'
-p <prio>CA priority (0-99, default 0=lowest)
Timestamps:
Default:Print absolute timestamps (as reported by CA server)
-t <key>Specify timestamp source(s) and type, with <key> containing
+ 's' = CA server (remote) timestamps
+ 'c' = CA client (local) timestamps (shown in '()'s)
+ 'n' = no timestamps
+ 'r' = relative timestamps (time elapsed since start of program)
+ 'i' = incremental timestamps (time elapsed since last update)
+ 'I' = incremental timestamps (time since last update, by channel)
+ 'r', 'i' or 'I' require 's' or 'c' to select the time source
Enum Format:
-nPrint DBF_ENUM values as number (default is enum string)
Arrays:
Array values: Print number of elements, then list of values
Default:Default: Request and print all elements (dynamic arrays supported)
-# <num>Request and print up to <num> elements
-SPrint array of char as a string (long string)
Floating point format:
Default:Use %g format
-e <num>Use %e format, with a precision of <num> digits
-f <num>Use %f format, with a precision of <num> digits
-g <num>Use %g format, with a precision of <num> digits
-sGet value as string (honors server-side precision)
-lxRound to long integer and print as hex number
-loRound to long integer and print as octal number
-lbRound to long integer and print as binary number
Integer number format:
Default:Print as decimal number
-0xPrint as hex number
-0oPrint as octal number
-0bPrint as binary number
+ +

caput

+
caput [options] <PV name> <value> ...
+caput -a [options] <PV name> <no of elements> <value> ...
+ +

Description

+ +

Put value to a PV.

+ +

The specified value is written to the PV (as a string). The PV's value is +read before and after the write operation and printed as "Old" and "New" values +on stdout.

+ +

There are two variants to the arguments for this command. For the scalar +variant without the -a flag, all the value arguments provided after +the PV name are concatenated with a single space character between them, and the +resulting string (up to 40 characters long unless the -S flag is +given) is written to the specified PV.

+ +

The array variant with the -a flag writes an array of string +values to the specified PV. The numeric argument giving the number of array +elements is actually ignored, the array length to be written is actually +controlled by the number of values provided on the command line.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
-hPrint usage information
CA options:
-w <sec>Wait time, specifies longer CA timeout, default is 1.0 second
-cAsynchronous put (use ca_put_callback and wait for completion)
-p <prio>CA priority (0-99, default 0=lowest)
Format options:
-tTerse mode - print only successfully written value, without name
-lLong mode "name timestamp value stat sevr" (read PVs as DBR_TIME_xxx)
Enum Format:
Default:Auto - try value as ENUM string, then as index number
-nForce interpretation of values as numbers
-sForce interpretation of values as strings
Arrays:
Default:Put scalar
Value format: all value arguments concatenated with spaces
-SPut string as an array of chars (long string)
-aPut array
Value format: number of values, then list of values
+ +

cainfo

+
cainfo [options] <PV name> ...
+ +

Description

+ +

Get and print channel and connection information for PV(s).

+ +

All available Channel Access related information about PV(s) is printed to +stdout.

+ +

The -s option allows to specify an interest level for calling Channel +Access' internal report function ca_client_status(), that prints lots of +internal informations on stdout, including environment settings, used CA ports +etc.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
-hPrint usage information
CA options:
-w <sec>Wait time, specifies longer CA timeout, default is 1.0 second
-s <level>Call ca_client_status with the specified interest level
-p <prio>CA priority (0-99, default 0=lowest)
+ +

excas

+ +

excas [options]

+ +

This is an example CA server that is sometimes used for testing purposes. An +example server can be created with the makeBaseApp Perl script, as described in +the application Developer's Guide.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
-d <uuuu>set level uuuu for debug messages, where uuuu is an positive integer + number
-p <aaaa>prefix all of the PV names below with aaaa changing, for example, the + name of "bill" to "xyz:bill"
-t <n.n>set execution time where n.n is a positive real number
-c <uuuu>set the numbered alias count
-s <nnn>the default, nnn is one, enables periodic scanning of the PV + replacing the PV with its value added with a small random change, when + nnn is zero it turns off this type of periodic scanning
-ss <nnn>the default, nnn is one, enables synchronous scanning, and if nnn is + zero it turns on asynchronous scanning
-ad <n.n>set the delay before asynchronous operations complete (defaults to + 0.1 seconds)
-an <nnn>set the maximum number of simultaneous asynchronous operations + (defaults to 1000)
+ +

The example server has a compile time fixed set of example variables.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Process Variable NameNumber of ElementsIO TypeData TypeHigh LimitLow LimitScan Period
jane1Synchronousfloat point, 64 bits10.00.00.1 Seconds, random noise changes
fred1Synchronousfloat point, 64 bits10.0-10.02.0 Seconds, random noise changes
janet1Asynchronousfloat point, 64 bits10.00.00.1 Seconds, random noise changes
freddy1Asynchronousfloat point, 64 bits10.0-10.02.0 Seconds, random noise changes
alan100Synchronousfloat point, 64 bits10.0-10.02.0 Seconds, random noise changes
albert1000Synchronousfloat point, 64 bits10.0-10.020.0 Seconds, random noise changes
boot1Synchronousenumerated, 16 bits10.0-10.0changed only by client
booty1Asynchronousenumerated, 16 bits10.0-10.01.0 Seconds, random noise changes
bill1Synchronousfloat point, 64 bits10.0-10.0changed only by client
billy1Asynchronousfloat point, 64 bits10.0-10.0changed only by client
bloaty100000Synchronousfloat point, 64 bits10.0-10.0changed only by client
+ +

Bugs

+ +

Not all of the options listed above have been tested recently.

+
+ +

Troubleshooting

+ +

When Clients Do Not Connect to Their Server

+ +

Client and Server Broadcast Addresses Don't +Match

+ +

Verify that the broadcast addresses are identical on the server's host and +on the client's host. This can be checked on UNIX with "netstat -i" or +"ifconfig -a"; on vxWorks with ifShow; and on windows with ipconfig. It is +normal for the broadcast addresses to not be identical if the client and server +are not directly attached to the same IP subnet, and in this situation the +EPICS_CA_ADDR_LIST must be set. Otherwise, if the client and server are +intended to be on the same IP subnet, then the problem may be that the IP +netmask is incorrectly set in the network interface configuration. On most +operating systems, when the host's IP address is configured, the host's IP +subnet mask is also configured.

+ +

Client Isn't Configured to Use the Server's Port

+ +

Verify that the client and server are using the same UDP port. Check the +server's port by running "netstat -a | grep nnn" where nnn is the port number +configured in the client. If you do not set EPICS_CA_SERVER_PORT or +EPICS_CAS_SERVER_PORT then the default port will be 5064.

+ +

Unicast Addresses in the EPICS_CA_ADDR_LIST Does not +Reliably Contact Servers Sharing the Same UDP Port on the Same Host

+ +

Two servers can run on the same host with the same server port number, but +there are restrictions. If the host has a modern IP kernel it is possible to +have two or more servers share the same UDP port. It is not possible for these +servers to run on the same host using the same TCP port. If the CA server +library detects that a server is attempting to start on the same port as an +existing CA server then both servers will use the same UDP port, and the 2nd +server will be allocated an ephemeral TCP port. Clients can be configured to +use the same port number for both servers. They will locate the 2nd server via +the shared UDP port, and transparently connect to the 2nd server's ephemeral +TCP port. Be aware however that If there are two server's running on the same +host sharing the same UDP port then they will both receive UDP search requests +sent as broadcasts, but unfortunately (due to a weakness of most IP kernel +implementations) only one of the servers will typically receive UDP search +requests sent to unicast addresses (i.e. a single specific host's ip +address).

+ +

Client Does not See Server's Beacons

+ +

Two conclusions deserve special emphasis. First, if a client does not +see the server's beacons, then it will use additional network and server +resources sending periodic state-of-health messages. Second, if a +client does not see a newly introduced server's beacon, then it will take up to +EPICS_CA_MAX_SEARCH_PERIOD to find that newly introduced server. Also, +starting with EPICS R3.14.7 the client library does not suspend +searching for a channel after 100 unsuccessful attempts until a beacon anomaly +is seen. Therefore, if the client library is from before version R3.14.7 of +EPICS and it timed out attempting to find a server whose beacon can't be seen +by the client library then the client application might need to be restarted in +order to connect to this new beacon-out-of-range server. The typical situation +where a client would not see the server's beacon might be when the client isn't +on the same IP subnet as the server, and the client's EPICS_CA_ADDR_LIST was +modified to include a destination address for the server, but the server's +beacon address list was not modified so that its beacons are received by the +client.

+ +

A Server's IP Address Was Changed

+ +

When communication over a virtual circuit times out, then each channel +attached to the circuit enters a disconnected state and the disconnect callback +handler specified for the channel is called. However, the circuit is not +disconnected until TCP/IP's internal, typically long duration, keep alive timer +expires. The disconnected channels remain attached to the beleaguered circuit +and no attempt is made to search for, or to reestablish, a new circuit. If, at +some time in the future, the circuit becomes responsive again, then the +attached channels enter a connected state again and reconnect callback +handlers are called. Any monitor subscriptions that received an update message +while the channel was disconnected are also refreshed. If at any time the +library receives an indication from the operating system that a beleaguered +circuit has shutdown or was disconnected then the library will immediately +reattempt to find servers for each channel and connect circuits to them.

+ +

A well known negative side effect of the above behavior is that CA clients +will wait the full (typically long) duration of TCP/IP's internal keep alive +timer prior to reconnecting under the following scenario (all of the following +occur):

+
    +
  • An server's (IOC's) operating system crashes (or is abruptly turned off) + or a vxWorks system is stopped by any means
  • +
  • This operating system does not immediately reboot using the same IP + address
  • +
  • A duplicate of the server (IOC) is started appearing at a different IP + address
  • +
+ +

It is unlikely that any rational organization will advocate the above +scenario in a production system. Nevertheless, there are opportunities +for users to become confused during control system development, but it +is felt that the robustness improvements justify isolated confusion during the +system integration and checkout activities where the above scenarios are most +likely to occur.

+ +

Contrast the above behavior with the CA client library behavior of releases +prior to R3.14.5 where the beleaguered circuit was immediately closed when +communication over it timed out. Any attached channels were immediately +searched for, and after successful search responses arrived then attempts were +made to build a new circuit. This behavior could result in undesirable resource +consumption resulting from periodic circuit setup and teardown overhead +(thrashing) during periods of CPU / network / IP kernel buffer congestion.

+ +

Put Requests Just Prior to Process +Termination Appear to be Ignored

+ +

Short lived CA client applications that issue a CA put request and then +immediately exit the process (return from main or call +exit) may find that there request isn't executed. To guarantee +that the request is sent call ca_flush_io() followed by +ca_context_destroy() prior to terminating the process.

+ +

ENOBUFS Messages

+ +

Many Berkley UNIX derived Internet Protocol (IP) kernels use a memory +management scheme with a fixed sized low level memory allocation quantum called +an "mbuf". Messages about "ENOBUFS" are an indication that your IP kernel is +running low on mbuf buffers. An IP kernel mbuf starvation situation may lead to +temporary IP communications stalls or reduced throughput. This issue has to +date been primarily associated with vxWorks systems where mbuf starvation on +earlier vxWorks versions is rumored to lead to permanent IP communications +stalls which are resolved only by a system reboot. IP kernels that use mbufs +frequently allow the initial and maximum number of mbufs to be configured. +Consult your OS's documentation for configuration procedures which vary between +OS and even between different versions of the same OS.

+ +

Contributing Circumstances

+
    +
  • The total number of connected clients is high. Each active socket + requires dedicated mbufs for protocol control blocks, and for any data that + might be pending in the operating system for transmission to Channel Access + or to the network at a given instant. If you increase the vxWorks limit on + the maximum number of file descriptors then it may also be necessary to + increase the size of the mbuf pool.
  • +
+
    +
  • The server has multiple connections where the server's sustained event + (monitor subscription update) production rate is higher than the client's + or the network's sustained event consumption rate. This ties up a per + socket quota of mbufs for data that are pending transmission to the client + via the network. In particular, if there are multiple clients that + subscribe for monitor events but do not call ca_pend_event() + or ca_poll() + to process their CA input queue, then a significant mbuf consuming backlog + can occur in the server.
  • +
+
    +
  • The server does not get a chance to run (because some other higher + priority thread is running) and the CA clients are sending a high volume of + data over TCP or UDP. This ties up a quota of mbufs for each socket in the + server that isn't being reduced by the server's socket read system + calls.
  • +
+
    +
  • The server has multiple stale connections. Stale connections occur when a + client is abruptly turned off or disconnected from the network, and an + internal "keepalive" timer has not yet expired for the virtual circuit in + the operating system, and therefore mbufs may be dedicated to unused + virtual circuits. This situation is made worse if there are active monitor + subscriptions associated with stale connections which will rapidly increase + the number of dedicated mbufs to the quota available for each circuit.
  • +
  • When sites switch to the vxWorks 5.4 IP kernel they frequently run into + network pool exhaustion problems. This may be because the original vxWorks + IP kernel expanded the network pool as needed at runtime while the new + kernel's pool is statically configured at compile time, and does + not expand as needed at runtime. Also, at certain sites problems + related to vxWorks network driver pool exhaustion have also been reported + (this can also result in ENOBUF diagnostic messages).
  • +
+ +

Related Diagnostics

+
    +
  • The EPICS command "casr [interest level]" displays information about the + CA server and how many clients are connected.
  • +
  • The vxWorks command "inetstatShow" indicates how many bytes are pending + in mbufs and indirectly (based on the number of circuits listed) how many + mbuf based protocol control blocks have been consumed. The vxWorks commands + (availability depending on vxWorks version) mbufShow, netStackSysPoolShow, + and netStackDataPoolShow indicate how much space remains in the network + stack pool.
  • +
  • The RTEMS command "netstat [interest level]" displays network information + including mbuf consumption statistics.
  • +
+ +

Server Subscription Update Queuing

+ +

If the subscription update producer in the server produces subscription +updates faster than the subscription update consumer in the client consumes +them, then events have to be discarded if the buffering in the server +isn't allowed to grow to an infinite size. This is a law of nature – +based on queuing theory of course.

+ +

What is done depends on the version of the CA server. All server versions +place quotas on the maximum number of subscription updates allowed on the +subscription update queue at any given time. If this limit is reached, an +intervening update is discarded in favor of a more recent update. Depending on +the version of the server, rapidly updating subscriptions are or are not +allowed to cannibalize the quotas of slow updating subscriptions in limited +ways. Nevertheless, there is always room on the queue for at least one update +for each subscription. This guarantees that the most recent update is always +sent.

+ +

Adding further complication, the CA client library also implements a +primitive type of flow control. If the client library sees that it is reading a +large number of messages one after another w/o intervening delay it knows that +it is not consuming events as fast as they are produced. In that situation it +sends a message telling the server to temporarily stop sending subscription +update messages. When the client catches up it sends another message asking the +server to resume with subscription updates. This prevents slow clients from +getting time warped, but also guarantees that intervening events are discarded +until the slow client catches up.

+ +

There is currently no message on the IOC's console when a particular client +is slow on the uptake. A message of this type used to exist many years ago, but +it was a source of confusion (and what we will call message noise) so it was +removed.

+ +

There is unfortunately no field in the protocol allowing the server to +indicate that an intervening subscription update was discarded. We should +probably add that capability in a future version. Such a feature would, for +example, be beneficial when tuning an archiver installation.

+
+ +

Function Call Interface General Guidelines

+ +

Flushing and Blocking

+ +

Significant performance gains can be realized when the CA client library +doesn't wait for a response to return from the server after each request. All +requests which require interaction with a CA server are accumulated (buffered) +and not forwarded to the IOC until one of ca_flush_io(), +ca_pend_io(), ca_pend_event(), or +ca_sg_block() are called allowing several operations to be +efficiently sent over the network together. Any process variable values written +into your program's variables by ca_get() should not be referenced by your +program until ECA_NORMAL has been received from ca_pend_io().

+ +

Status Codes

+ +

If successful, the routines described here return the status code +ECA_NORMAL. Unsuccessful status codes returned from the client library are +listed with each routine in this manual. Operations that appear to be valid to +the client can still fail in the server. Writing the string "off" to a floating +point field is an example of this type of error. If the server for a channel is +located in a different address space than the client then the ca_xxx() +operations that communicate with the server return status indicating the +validity of the request and whether it was successfully enqueued to the server, +but communication of completion status is deferred until a user callback is +called, or lacking that an exception handler is called. An error number and the +error's severity are embedded in CA status (error) constants. Applications +shouldn't test the success of a CA function call by checking to see if the +returned value is zero as is the UNIX convention. Below are several methods to +test CA function returns. See ca_signal() and +SEVCHK() for more information on this topic.

+
status = ca_XXXX(); 
+SEVCHK( status, "ca_XXXX() returned failure status"); 
+
+if ( status & CA_M_SUCCESS ) { 
+        printf ( "The requested ca_XXXX() operation didn't complete successfully"); 
+} 
+
+if ( status != ECA_NORMAL ) { 
+        printf("The requested ca_XXXX() operation didn't complete successfully because \"%s\"\n",
+                ca_message ( status ) ); 
+}
+ +

Channel Access Data Types

+ +

CA channels form a virtual circuit between a process variable (PV) and a +client side application program. It is possible to connect a wide variety of +data sources into EPICS using the CA server library. When a CA channel +communicates with an EPICS Input Output Controller (IOC) then a field is a +specialization of a PV, and an EPICS record is a plug compatible function block +that contains fields, and the meta data below frequently are mapped onto +specific fields within the EPICS records by the EPICS record support (see the +EPICS Application Developer Guide).

+ +

Arguments of type chtype specifying the data type you wish to transfer. They +expect one of the set of DBR_XXXX data type codes defined in db_access.h. There +are data types for all of the C primitive types, and there are also compound (C +structure) types that include various process variable properties such as +units, limits, time stamp, or alarm status. The primitive C types follow a +naming convention where the C typedef dbr_xxxx_t corresponds to the DBR_XXXX +data type code. The compound (C structure) types follow a naming convention +where the C structure tag dbr_xxxx corresponds to the DBR_XXXX data type code. +The following tables provides more details on the structure of the CA data type +space. Since data addresses are passed to the CA client library as typeless +"void *" pointers then care should be taken to ensure that you have passed the +correct C data type corresponding to the DBR_XXXX type that you have specified. +Architecture independent types are provided in db_access.h to assist +programmers in writing portable code. For example "dbr_short_t" should be used +to send or receive type DBR_SHORT. Be aware that type name DBR_INT has been +deprecated in favor of the less confusing type name DBR_SHORT. In practice, +both the DBR_INT type code and the DBR_SHORT type code refer to a 16 bit +integer type, and are functionally equivalent.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Channel Access Primitive Data Types
CA Type CodePrimitive C Data TypeData Size
DBR_CHARdbr_char_t8 bit character
DBR_SHORTdbr_short_t16 bit integer
DBR_ENUMdbr_enum_t16 bit unsigned integer
DBR_LONGdbr_long_t32 bit signed integer
DBR_FLOATdbr_float_t32 bit IEEE floating point
DBR_DOUBLEdbr_double_t64 bit IEEE floating point
DBR_STRINGdbr_string_t40 character string
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Structure of the Channel Access Data Type Space
CA Type CodeRead / WritePrimitive C Data TypeProcess Variable Properties
DBR_<PRIMITIVE TYPE>RWdbr_<primitive type>_tvalue
DBR_STS_<PRIMITIVE TYPE>Rstruct dbr_sts_<primitive type>value, alarm status, and alarm severity
DBR_TIME_<PRIMITIVE TYPE>Rstruct dbr_time_<primitive type>value, alarm status, alarm severity, and time stamp
DBR_GR_<PRIMITIVE TYPE>Rstruct dbr_gr_<primitive type>value, alarm status, alarm severity, units, display precision, and + graphic limits
DBR_CTRL_<PRIMITIVE TYPE>Rstruct dbr_ctrl_<primitive type>value, alarm status, alarm severity, units, display precision, + graphic limits, and control limits
DBR_PUT_ACKTWdbr_put_ackt_tUsed for global alarm acknowledgement. Do transient alarms have to be + acknowledged? (0,1) means (no, yes).
DBR_PUT_ACKSWdbr_put_acks_tUsed for global alarm acknowledgement. The highest alarm severity to + acknowledge. If the current alarm severity is less then or equal to + this value the alarm is acknowledged.
DBR_STSACK_STRINGRstruct dbr_stsack_stringvalue, alarm status, alarm severity, ackt, acks
DBR_CLASS_NAMERdbr_class_name_tname of enclosing interface (name of the record if channel is + attached to EPICS run time database)
+ +

+ +

Channel value arrays can also be included within the structured CA data +types. If more than one element is requested, then the individual elements can +be accessed in an application program by indexing a pointer to the value field +in the DBR_XXX structure. For example, the following code computes the sum of +the elements in a array process variable and prints its time stamp. The +dbr_size_n() function can be used to determine the correct +number of bytes to reserve when there are more than one value field elements in +a structured CA data type.

+
#include <stdio.h>
+#include <stdlib.h>
+
+#include "cadef.h"
+
+int main ( int argc, char ** argv )
+{
+    struct dbr_time_double * pTD;
+    const dbr_double_t * pValue;
+    unsigned nBytes;
+    unsigned elementCount;
+    char timeString[32];
+    unsigned i;    
+    chid chan;
+    double sum;
+    int status;
+
+    if ( argc != 2 ) {
+        fprintf ( stderr, "usage: %s <channel name>", argv[0] );
+        return -1;
+    }
+
+    status = ca_create_channel ( argv[1], 0, 0, 0, & chan );
+    SEVCHK ( status, "ca_create_channel()" );
+    status = ca_pend_io ( 15.0 );
+    if ( status != ECA_NORMAL ) {
+        fprintf ( stderr, "\"%s\" not found.\n", argv[1] );
+        return -1;
+    }
+
+    elementCount = ca_element_count ( chan );
+    nBytes = dbr_size_n ( DBR_TIME_DOUBLE, elementCount );
+    pTD = ( struct dbr_time_double * ) malloc ( nBytes );
+    if ( ! pTD ) {
+        fprintf ( stderr, "insufficient memory to complete request\n" );
+        return -1;
+    }
+
+    status = ca_array_get ( DBR_TIME_DOUBLE, elementCount, chan, pTD );
+    SEVCHK ( status, "ca_array_get()" );
+    status = ca_pend_io ( 15.0 );
+    if ( status != ECA_NORMAL ) {
+        fprintf ( stderr, "\"%s\" didn't return a value.\n", argv[1] );
+        return -1;
+    }
+
+    pValue = & pTD->value;
+    sum = 0.0;
+    for ( i = 0; i < elementCount; i++ ) {
+        sum += pValue[i];
+    }
+
+    epicsTimeToStrftime ( timeString, sizeof ( timeString ),
+        "%a %b %d %Y %H:%M:%S.%f", & pTD->stamp );
+
+    printf ( "The sum of elements in %s at %s was %f\n", 
+        argv[1], timeString, sum );
+
+    ca_clear_channel ( chan );
+    ca_task_exit ();
+    free ( pTD );
+
+    return 0;
+}
+ +

User Supplied Callback Functions

+ +

Certain CA client initiated requests asynchronously execute an application +supplied callback in the client process when a response arrives. The functions +ca_put_callback(), ca_get_callback(), and +ca_create_subscription() all request notification of +asynchronous completion via this mechanism. The event_handler_args +structure is passed by value to the application supplied +callback. In this structure the dbr field is a void pointer to any +data that might be returned. The status field will be +set to one of the CA error codes in caerr.h and will indicate the status of the +operation performed in the IOC. If the status field isn't set to ECA_NORMAL or +data isn't normally returned from the operation (i.e. put callback) then you +should expect that the dbr field will be set to a null pointer +(zero). The fields usr, chid, and type +are set to the values specified when the request was made by the application. +The dbr pointer, and any data that it points to, are valid only when +executing within the user's callback function.

+
typedef struct event_handler_args {
+    void            *usr;   /* user argument supplied with request */
+    chanId          chid;   /* channel id */
+    long            type;   /* the type of the item returned */ 
+    long            count;  /* the element count of the item returned */
+    const void      *dbr;   /* a pointer to the item returned */
+    int             status; /* ECA_XXX status of the requested op from the server */
+} evargs;
+
+void myCallback ( struct event_handler_args args )
+{
+    if ( args.status != ECA_NORMAL ) {
+    }
+    if ( args.type == DBR_TIME_DOUBLE ) {
+         const struct dbr_time_double * pTD =
+              ( const struct dbr_time_double * ) args.dbr;
+    }
+}
+ +

Channel Access Exceptions

+ +

When the server detects a failure, and there is no client callback function +attached to the request, an exception handler is executed in the client. The +default exception handler prints a message on the console and exits if the +exception condition is severe. Certain internal exceptions within the CA client +library, and failures detected by the SEVCHK macro may also cause the exception +handler to be invoked. To modify this behavior see +ca_add_exception_event().

+ +

Server and Client Share the Same Address Space on The Same +Host

+ +

If the Process Variable's server and it's client are colocated within the +same memory address space and the same host then the ca_xxx() operations bypass +the server and directly interact with the server tool component (commonly the +IOC's function block database). In this situation the ca_xxx() routines +frequently return the completion status of the requested operation directly to +the caller with no opportunity for asynchronous notification of failure via an +exception handler. Likewise, callbacks may be directly invoked by the CA +library functions that request them.

+ +

Arrays

+ +

For routines that require an argument specifying the number of array +elements, no more than the process variable's maximum native element count may +be requested. The process variable's maximum native element count is available +from ca_element_count() when the channel is connected. If fewer elements than +the process variable's native element count are requested, the requested values +will be fetched beginning at element zero. By default CA limits the number of +elements in an array to be no more than approximately 16k divided by the size +of one element in the array. Starting with EPICS R3.14 the maximum array size +may be configured in the client and in the server.

+ +

Connection Management

+ +

Application programs should assume that CA servers may be restarted, and +that network connectivity is transient. When you create a CA channel its +initial connection state will most commonly be disconnected. If the Process +Variable's server is available the library will immediately initiate the +necessary actions to make a connection with it. Otherwise, the client library +will monitor the state of servers on the network and connect or reconnect with +the process variable's server as it becomes available. After the channel +connects the application program can freely perform IO operations through the +channel, but should expect that the channel might disconnect at any time due to +network connectivity disruptions or server restarts.

+ +

Three methods can be used to determine if a channel is connected: the +application program might call ca_state() +to obtain the current connection state, block in +ca_pend_io() until the channel connects, +or install a connection callback handler when it calls +ca_create_channel(). The +ca_pend_io() approach is best suited to +simple command line programs with short runtime duration, and the connection +callback method is best suited to toolkit components with long runtime duration. +Use of ca_state() is appropriate only in +programs that prefer to poll for connection state changes instead of opting for +asynchronous notification. The ca_pend_io() function blocks only +for channels created specifying a null connection handler callback function. The +user's connection state change function will be run immediately from within +ca_create_channel() if the CA +client and CA server are both hosted within the same address space (within the +same process).

+ +

Thread Safety and Preemptive Callback to User Code

+ +

Starting with EPICS R3.14 the CA client libraries are fully thread safe on +all OS (in past releases the library was thread safe only on vxWorks). When the +client library is initialized the programmer may specify if preemptive callback +is to be enabled. Preemptive callback is disabled by default. If preemptive +callback is enabled, then the user's callback functions might be called by CA's +auxiliary threads when the main initiating channel access thread is not inside +of a function in the channel access client library. Otherwise, the user's +callback functions will be called only when the main initiating channel access +thread is executing inside of the CA client library. When the CA client library +invokes a user's callback function, it will always wait for the current +callback to complete prior to executing another callback function. Programmers +enabling preemptive callback should be familiar with using mutex locks to +create a reliable multi-threaded program.

+ +

To set up a traditional single threaded client, you will need code like this +(see ca_context_create() and +CA Client Contexts and Application Specific Auxiliary +Threads) .

+ +

SEVCHK ( ca_context_create(ca_disable_preemptive_callback ), +"application pdq calling ca_context_create" );

+ +

To set up a preemptive callback enabled CA client context you will need code +like this (see ca_context_create() and +CA Client Contexts and Application Specific Auxiliary +Threads).

+ +

SEVCHK ( ca_context_create(ca_enable_preemptive_callback ), +"application pdq calling ca_context_create" );

+ +

CA Client Contexts and Application Specific Auxiliary +Threads

+ +

It is often necessary for several CA client side tools running in the same +address space (process) to be independent of each other. For example, the +database CA links and the sequencer are designed to not use the same CA client +library threads, network circuits, and data structures. Each thread that calls +ca_context_create() for the first time either +directly or implicitly when calling any CA library function for the first time, +creates a CA client library context. A CA client library context contains all +of the threads, network circuits, and data structures required to connect and +communicate with the channels that a CA client application has created. The +priority of auxiliary threads spawned by the CA client library are at fixed +offsets from the priority of the thread that called +ca_context_create(). An application specific +auxiliary thread can join a CA context by calling +ca_attach_context() using the CA context +identifier that was returned from +ca_current_context() when it is called by the +thread that created the context which needs to be joined. A context which is to +be joined must be preemptive - it must be created using +ca_context_create(ca_enable_preemptive_callback). +It is not possible to attach a thread to a non-preemptive CA context created +explicitly or implicitly with +ca_create_context(ca_disable_preemptive_callback). Once a thread has joined +with a CA context it need only make ordinary ca_xxxx() library calls to use the +context.

+ + +

A CA client library context can be shut down and cleaned up, after +destroying any channels or application specific threads that are attached to +it, by calling ca_context_destroy(). The +context may be created and destroyed by different threads as long as they are +both part of the same context.

+ +

Polling the CA Client Library From Single Threaded +Applications

+ +

If preemptive callback is not enabled, then for proper operation CA must +periodically be polled to take care of background activity. This requires that +your application must either wait in one of ca_pend_event(), ca_pend_io(), or +ca_sg_block() or alternatively it should call ca_poll() at least every 100 +milliseconds. In single threaded applications a file descriptor manager like +Xt or the interface described in fdManager.h can be used to monitor both mouse +clicks and also CA's file descriptors so that ca_poll() can be called +immediately when CA server messages arrives over the network.

+ +

Avoid Emulating Bad Practices that May Still be +Common

+ +

With the embryonic releases of EPICS it was a common practice to examine a +channel's connection state, its native type, and its native element count by +directly accessing fields in a structure using a pointer stored in type +chid. Likewise, a user private pointer in the per channel +structure was also commonly set by directly accessing fields in the channel +structure. A number of difficulties arise from this practice, which has long +since been deprecated. For example, prior to release 3.13 it was recognized +that transient changes in certain private fields in the per channel structure +would make it difficult to reliably test the channels connection state using +these private fields directly. Therefore, in release 3.13 the names of certain +fields were changed to discourage this practice. Starting with release 3.14 +codes written this way will not compile. Codes intending to maintain the +highest degree of portability over a wide range of EPICS versions should be +especially careful. For example you should replace all instances off +channel_id->count with +ca_element_count(channel_id). This approach should be reliable on +all versions of EPICS in use today. The construct ca_puser(chid) = +xxxx is particularly problematic. The best mechanisms for setting the +per channel private pointer will be to pass the user private pointer in when +creating the channel. This approach is implemented on all versions. Otherwise, +you can also use ca_set_puser(CHID,PUSER), but this function is +available only after the first official (post beta) release of EPICS 3.13.

+ +

Calling CA Functions from the vxWorks Shell +Thread

+ +

Calling CA functions from the vxWorks shell thread is a somewhat +questionable practice for the following reasons.

+
    +
  • The vxWorks shell thread runs at the very highest priority in the system + and therefore socket calls are made at a priority that is above the + priority of tNetTask. This has caused problems with the WRS IP kernel in + the past. That symptom was observed some time ago, but we don't know if + WRS has fixed the problem.
  • +
+
    +
  • The vxWorks shell thread runs at the very highest priority in the system + and therefore certain CA auxiliary threads will not get the priorities that + are requested for them. This might cause problems only when in a CPU + saturation situations.
  • +
+
    +
  • If the code does not call ca_context_destroy() (named ca_task_exit() in past + releases) then resources are left dangling.
  • +
+
    +
  • In EPICS R3.13 the CA client library installed vxWorks task exit handlers + behaved strangely if CA functions were called from the vxWorks shell, + ca_task_exit() wasn't called, and the vxWorks shell restarted. In + EPICS R3.14 vxWorks task exit handlers are not installed and therefore + cleanup is solely the responsibility of the user. With EPICS R3.14 the user + must call ca_context_destroy() or ca_task_exit() to clean up on vxWorks. This + is the same behavior as on all other OS.
  • +
+ +

Calling CA Functions from POSIX signal +handlers

+ +

As you might expect, it isn't safe to call the CA client library from a POSIX +signal handler. Likewise, it isn't safe to call the CA client library from +interrupt context.

+
+ +

Function Call Reference

+ +

ca_context_create()

+
#include <cadef.h>
+enum ca_preemptive_callback_select
+    { ca_disable_preemptive_callback, ca_enable_preemptive_callback };
+int ca_context_create ( enum ca_preemptive_callback_select SELECT );
+ +

Description

+ +

This function, or ca_attach_context(), +should be called once from each thread prior to making any of the other Channel +Access calls. If one of the above is not called before making other CA calls +then a non-preemptive context is created by default, and future attempts to +create a preemptive context for the current threads will fail.

+ +

If ca_disable_preemptive_callback is specified then additional +threads are not allowed to join the CA context using +ca_context_attach() because allowing other threads to join implies that CA +callbacks will be called preemptively from more than one thread.

+ +

Arguments

+
+
SELECT
+
This argument specifies if preemptive invocation of callback functions + is allowed. If so your callback functions might be called when the thread + that calls this routine is not executing in the CA client library. There + are two implications to consider.
+

First, if preemptive callback mode is enabled the developer must + provide mutual exclusion protection for his data structures. In this mode + it's possible for two threads to touch the application's data structures + at once: this might be the initializing thread (the thread that called + ca_context_create) and also a private thread created by the CA client + library for the purpose of receiving network messages and calling + callbacks. It might be prudent for developers who are unfamiliar with + mutual exclusion locking in a multi-threaded environment to specify + ca_disable_preemptive_callback.

+

Second, if preemptive callback mode is enabled the application is no + longer burdened with the necessity of periodically polling the CA client + library in order that it might take care of its background activities. If + ca_enable_preemptive_callback is specified then CA client + background activities, such as connection management, will proceed even + if the thread that calls this routine is not executing in the CA client + library. Furthermore, in preemptive callback mode callbacks might be + called with less latency because the library is not required to wait + until the initializing thread (the thread that called ca_context_create) + is executing within the CA client library.

+
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_ALLOCMEM - Failed, unable to allocate space in pool

+ +

ECA_NOTTHREADED - Current thread is already a member of a non-preemptive +callback CA context (possibly created implicitly)

+ +

See Also

+ +

ca_context_destroy()

+ +

ca_context_destroy()

+
#include <cadef.h>
+void ca_context_destroy();
+ +

Description

+ +

Shut down the calling thread's channel access client context and free any +resources allocated. Detach the calling thread from any CA client context.

+ +

Any user-created threads that have attached themselves to the CA context +must stop using it prior to its being destroyed. A program running in an IOC +context must delete all of its channels prior to calling ca_context_destroy() +to avoid a crash.

+ +

A CA client application that calls epicsExit() must install an +EPICS exit handler that calls ca_context_destroy() only after first +calling ca_create_context(). This will guarantee that the EPICS exit handlers +get called in the correct order.

+ +

On many OS that execute programs in a process based environment the +resources used by the client library such as sockets and allocated memory are +automatically released by the system when the process exits and +ca_context_destroy() hasn't been called, but on light weight systems such as +vxWorks or RTEMS no cleanup occurs unless the application calls +ca_context_destroy().

+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

See Also

+ +

ca_context_create()

+ +

ca_create_channel()

+
#include <cadef.h>
+typedef void ( caCh ) (struct connection_handler_args);
+int ca_create_channel (const char *PVNAME,
+        caCh *USERFUNC, void *PUSER,
+        capri PRIORITY, chid *PCHID );
+ +

Description

+ +

This function creates a CA channel. The CA client library will attempt to +establish and maintain a virtual circuit between the caller's application and a +named process variable in a CA server. Each call to ca_create_channel() allocates +resources in the CA client library and potentially also a CA server. The +function ca_clear_channel() is used to release these resources. If successful, +the routine writes a channel identifier into the user's variable of type +"chid". This identifier can be used with any channel access call that operates +on a channel.

+ +

The circuit may be initially connected or disconnected depending on the +state of the network and the location of the channel. A channel will only enter +a connected state after the server's address is determined, and only if channel +access successfully establishes a virtual circuit through the network to the +server. Channel access routines that send a request to a server will return +ECA_DISCONNCHID if the channel is currently disconnected.

+ +

There are two ways to obtain asynchronous notification when a channel enters +a connected state.

+
    +
  • The first and simplest method requires that you call ca_pend_io(), and + wait for successful completion, prior to using a channel that was created + specifying a null connection callback function pointer.
  • +
  • The second method requires that you register a connection handler by + supplying a valid connection callback function pointer. This connection + handler is called whenever the connection state of the channel changes. If + you have installed a connection handler then ca_pend_io() will not + block waiting for the channel to enter a connected state.
  • +
+ +

The function ca_state(CHID) can be used to test the connection state of a +channel. Valid connections may be isolated from invalid ones with this function +if ca_pend_io() times out.

+ +

Due to the inherently transient nature of network connections the order of +connection callbacks relative to the order that ca_create_channel() calls are +made by the application can't be guaranteed, and application programs may need +to be prepared for a connected channel to enter a disconnected state at any +time.

+ +

Example

+ +

See caExample.c in the example application created by makeBaseApp.pl.

+ +

Arguments

+
+
PVNAME
+
A nil terminated process variable name string. EPICS process control + function block database variable names are of the form "<record + name>.<field name>". If the field name and the period separator + are omitted then the "VAL" field is implicit. For example "RFHV01" and + "RFHV01.VAL" reference the same EPICS process control function block + database variable.
+
+
+
USERFUNC
+
Optional pointer to the user's callback function to be run when the + connection state changes. Casual users of channel access may decide to + set this field to null or 0 if they do not need to have a callback + function run in response to each connection state change event. +

The following structure is passed by value to the user's + connection callback function. The op field will + be set by the CA client library to CA_OP_CONN_UP when the + channel connects, and to CA_OP_CONN_DOWN when the channel + disconnects. See ca_puser() if the + PUSER argument is required in your callback + handler.

+
struct  ca_connection_handler_args {
+    chanId  chid;  /* channel id */
+    long    op;    /* one of CA_OP_CONN_UP or CA_OP_CONN_DOWN */
+};
+
+
+
+
PUSER
+
The value of this void pointer argument is retained in + storage associated with the specified channel. See the MACROS manual page + for reading and writing this field. Casual users of channel access may + wish to set this field to null or 0.
+
+
+
PRIORITY
+
The priority level for dispatch within the server or network with 0 + specifying the lowest dispatch priority and 99 the highest. This + parameter currently does not impact dispatch priorities within the + client, but this might change in the future. The abstract priority range + specified is mapped into an operating system specific range of priorities + within the server. This parameter is ignored if the server is running on + a network or operating system that does not have native support for + prioritized delivery or execution respectively. Specifying many different + priorities within the same program can increase resource consumption in + the client and the server because an independent virtual circuit, and + associated data structures, is created for each priority that is used on + a particular server.
+
+
+
PCHID
+
The user supplied channel identifier storage is overwritten with a + channel identifier if this routine is successful.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_STRTOBIG - Unusually large string

+ +

ECA_ALLOCMEM - Unable to allocate memory

+ +

ca_clear_channel()

+
#include <cadef.h>
+int ca_clear_channel (chid CHID);
+ +

Description

+ +

Shutdown and reclaim resources associated with a channel created by +ca_create_channel().

+ +

All remote operation requests such as the above are accumulated (buffered) +and not forwarded to the IOC until one of ca_flush_io(), ca_pend_io(), +ca_pend_event(), or ca_sg_block() are called. This allows several requests to be +efficiently sent over the network in one message.

+ +

Clearing a channel does not cause its disconnect handler to be called, but +clearing a channel does shutdown and reclaim any channel state change event +subscriptions (monitors) registered with the channel.

+ +

Arguments

+
+
CHID
+
Identifies the channel to delete.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADCHID - Corrupted CHID

+ +

ca_put()

+
#include <cadef.h>
+int ca_put ( chtype TYPE, 
+        chid CHID, void *PVALUE ); 
+int ca_array_put ( chtype TYPE, unsigned long COUNT, 
+        chid CHID, const void *PVALUE);
+typedef void ( caEventCallBackFunc ) (struct event_handler_args);
+int ca_put_callback ( chtype TYPE, 
+        chid CHID, const void *PVALUE, 
+        caEventCallBackFunc PFUNC, void *USERARG ); 
+int ca_array_put_callback ( chtype TYPE, unsigned long COUNT, 
+        chid CHID, const void *PVALUE, 
+        caEventCallBackFunc PFUNC, void *USERARG );
+ +

Description

+ +

Write a scalar or array value to a process variable.

+ +

When ca_put() or ca_array_put() are invoked the client will receive no response +unless the request can not be fulfilled in the server. If unsuccessful an +exception handler is run on the client side.

+ +

When ca_put_callback() or ca_array_put_callback() are invoked the user supplied +asynchronous callback is called only after the initiated write operation, and +all actions resulting from the initiating write operation, complete.

+ +

If unsuccessful the callback function is invoked indicating failure status. +

+ +

If the channel disconnects before a put callback request can be completed, +then the client's callback function is called with failure status, but this +does not guarantee that the server did not receive and process the request +before the disconnect. If a connection is lost and then resumed outstanding ca +put requests are not automatically reissued following reconnect.

+ +

All of these functions return ECA_DISCONN if the channel is currently +disconnected.

+ +

All put requests are accumulated (buffered) and not forwarded to the IOC +until one of ca_flush_io(), ca_pend_io(), ca_pend_event(), or ca_sg_block() are called. +This allows several requests to be efficiently combined into one message.

+ +

Description (IOC Database Specific)

+ +

A CA put request causes the record to process if the record's SCAN field is +set to passive, and the field being written has its process passive attribute +set to true. If such a record is already processing when a put request is +initiated the specified field is written immediately, and the record is +scheduled to process again as soon as it finishes processing. Earlier instances +of multiple put requests initiated while the record is being processing may be +discarded, but the last put request initiated is always written and +processed.

+ +

A CA put callback request causes the record to process if the +record's SCAN field is set to passive, and the field being written has its +process passive attribute set to true. For such a record, the user's put +callback function is not called until after the record, and any records that +the record links to, finish processing. If such a record is already processing +when a put callback request is initiated the put callback +request is postponed until the record, and any records it links to, finish +processing.

+ +

If the record's SCAN field is not set to passive, or the field being written +has its process passive attribute set to false then the CA put or CA put +callback request cause the specified field to be immediately written, +but they do not cause the record to be processed.

+ +

Arguments

+
+
TYPE
+
The external type of the supplied value to be written. Conversion will + occur if this does not match the native type. Specify one from the set of + DBR_XXXX in db_access.h
+
+
+
COUNT
+
Element count to be written to the specified channel. This must match + the array pointed to by PVALUE.
+
+
+
CHID
+
Channel identifier
+
+
+
PVALUE
+
Pointer to a value or array of values provided by the application to be + written to the channel.
+
+
+
PFUNC
+
Pointer to a user supplied callback function to be + run when the requested operation completes
+
+
+
USERARG
+
pointer sized variable retained and then passed back to user supplied + function above
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADCHID - Corrupted CHID

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_BADCOUNT - Requested count larger than native element count

+ +

ECA_STRTOBIG - Unusually large string supplied

+ +

ECA_NOWTACCESS - Write access denied

+ +

ECA_ALLOCMEM - Unable to allocate memory

+ +

ECA_DISCONN - Channel is disconnected

+ +

See Also

+ +

ca_flush_io()

+ +

ca_pend_event()

+ +

ca_sg_array_put()

+ +

ca_get()

+
#include <cadef.h>
+int ca_get ( chtype TYPE,
+        chid CHID, void *PVALUE );
+int ca_array_get ( chtype TYPE, unsigned long COUNT,
+        chid CHID, void *PVALUE );
+typedef void ( caEventCallBackFunc ) (struct event_handler_args);
+int ca_get_callback ( chtype TYPE,
+        chid CHID,
+        caEventCallBackFunc USERFUNC, void *USERARG);
+int ca_array_get_callback ( chtype TYPE, unsigned long COUNT,
+        chid CHID,
+        caEventCallBackFunc USERFUNC, void *USERARG);
+ +

Description

+ +

Read a scalar or array value from a process variable.

+ +

When ca_get() or ca_array_get() are invoked the returned channel value can't be +assumed to be stable in the application supplied buffer until after ECA_NORMAL +is returned from ca_pend_io(). If a connection is lost outstanding ca get +requests are not automatically reissued following reconnect.

+ +

When ca_get_callback() or ca_array_get_callback() are invoked a value is read +from the channel and then the user's callback is invoked with a pointer to the +retrieved value. Note that ca_pend_io() will not block for the delivery of values +requested by ca_get_callback(). If the channel disconnects before a +ca_get_callback() request can be completed, then the client's callback function is +called with failure status.

+ +

All of these functions return ECA_DISCONN if the channel is currently +disconnected.

+ +

All get requests are accumulated (buffered) and not forwarded to the IOC +until one of ca_flush_io(), ca_pend_io(), ca_pend_event(), or ca_sg_block() are called. +This allows several requests to be efficiently sent over the network in one +message.

+ +

Description (IOC Database Specific)

+ +

A CA get or CA get callback request causes the record's field to be read +immediately independent of whether the record is currently being processed or +not. There is currently no mechanism in place to cause a record to be processed +when a CA get request is initiated.

+ +

Example

+ +

See caExample.c in the example application created by makeBaseApp.pl.

+ +

Arguments

+
+
TYPE
+
The external type of the user variable to return the value into. + Conversion will occur if this does not match the native type. Specify one + from the set of DBR_XXXX in db_access.h
+
+
+
COUNT
+
Element count to be read from the specified channel. Must match the + array pointed to by PVALUE. For ca_array_get_callback() a count of zero + means use the current element count from the server.
+
+
+
CHID
+
Channel identifier
+
+
+
PVALUE
+
Pointer to an application supplied buffer where the current value of + the channel is to be written.
+
+
+
USERFUNC
+
Pointer to a user supplied callback function to be + run when the requested operation completes.
+
+
+
USERARG
+
Pointer sized variable retained and then passed back to user supplied + callback function above.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_BADCHID - Corrupted CHID

+ +

ECA_BADCOUNT - Requested count larger than native element count

+ +

ECA_GETFAIL - A local database get failed

+ +

ECA_NORDACCESS - Read access denied

+ +

ECA_ALLOCMEM - Unable to allocate memory

+ +

ECA_DISCONN - Channel is disconnected

+ +

See Also

+ +

ca_pend_io()

+ +

ca_pend_event()

+ +

ca_sg_array_get()

+ +

ca_create_subscription()

+
#include <cadef.h>
+typedef void ( caEventCallBackFunc ) (struct event_handler_args);
+int ca_create_subscription ( chtype TYPE, unsigned long COUNT,
+        chid CHID, unsigned long MASK,
+        caEventCallBackFunc USERFUNC, void *USERARG, 
+        evid *PEVID );
+ +

Description

+ +

Register a state change subscription and specify a callback function to be +invoked whenever the process variable undergoes significant state changes. A +significant change can be a change in the process variable's value, alarm +status, or alarm severity. In the process control function block database the +deadband field determines the magnitude of a significant change for the +process variable's value. Each call to this function consumes resources in the +client library and potentially a CA server until one of ca_clear_channel() or +ca_clear_subscription() is called.

+ +

Subscriptions may be installed or canceled against both connected and +disconnected channels. The specified USERFUNC is called once immediately after +the subscription is installed with the process variable's current state if the +process variable is connected. Otherwise, the specified USERFUNC is called +immediately after establishing a connection (or reconnection) with the process +variable. The specified USERFUNC is called immediately with the process +variable's current state from within ca_create_subscription() if the client and the +process variable share the same address space.

+ +

If a subscription is installed on a channel in a disconnected state then the +requested count will be set to the native maximum element count of the channel +if the requested count is larger.

+ +

All subscription requests such as the above are accumulated (buffered) and +not forwarded to the IOC until one of ca_flush_io(), ca_pend_io(), ca_pend_event(), +or ca_sg_block() are called. This allows several requests to be efficiently sent +over the network in one message.

+ +

If at any time after subscribing, read access to the specified process +variable is lost, then the callback will be invoked immediately indicating +that read access was lost via the status argument. When read access is restored +normal event processing will resume starting always with at least one update +indicating the current state of the channel.

+ +

A better name for this function might have been ca_subscribe().

+ +

Example

+ +

See caMonitor.c in the example application created by makeBaseApp.pl.

+ +

Arguments

+
+
TYPE
+
The type of value presented to the callback function. Conversion will + occur if it does not match native type. Specify one from the set of + DBR_XXXX in db_access.h
+
+
+
COUNT
+
The element count to be read from the specified channel. A count of + zero means use the current element count from the server.
+
+
+
CHID
+
channel identifier
+
+
+
USERFUNC
+
Pointer to a user supplied callback function to + be invoked with each subscription update.
+
+
+
USERARG
+
pointer sized variable retained and passed back to user callback + function
+
+
+
RESERVED
+
Reserved for future use. Specify 0.0 to remain upwardly compatible.
+
+
+
PEVID
+
This is a pointer to user supplied event id which is overwritten if + successful. This event id can later be used to clear a specific event. + This option may be omitted by passing a null pointer.
+
+
+
MASK
+
A mask with bits set for each of the event trigger types requested. The + event trigger mask must be a bitwise or of one or more of the + following constants. +
    +
  • DBE_VALUE - Trigger events when the channel value exceeds the + monitor dead band
  • +
  • DBE_ARCHIVE (or DBE_LOG) - Trigger events when the channel value + exceeds the archival dead band
  • +
  • DBE_ALARM - Trigger events when the channel alarm state changes
  • +
  • DBE_PROPERTY - Trigger events when a channel property changes.
  • +
+

For functions above that do not include a trigger specification, + events will be triggered when there are significant changes in the + channel's value or when there are changes in the channel's alarm state. + This is the same as "DBE_VALUE | DBE_ALARM."

+
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADCHID - Corrupted CHID

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_ALLOCMEM - Unable to allocate memory

+ +

ECA_ADDFAIL - A local database event add failed

+ +

See Also

+ +

ca_pend_event()

+ +

ca_flush_io()

+ +

ca_clear_subscription()

+
#include <cadef.h>
+int ca_clear_subscription ( evid EVID );
+ +

Description

+ +

Cancel a subscription.

+ +

All cancel-subscription requests such as the above are accumulated (buffered) +and not forwarded to the server until one of ca_flush_io(), ca_pend_io(), +ca_pend_event(), or ca_sg_block() are called. This allows several requests to be +efficiently sent together in one message.

+ +

Arguments

+
+
EVID
+
event id returned by ca_create_subscription()
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADCHID - Corrupted CHID

+ +

See Also

+ +

ca_create_subscription()

+ +

ca_pend_io()

+
#include <cadef.h>
+int ca_pend_io ( double TIMEOUT );
+ +

Description

+ +

This function flushes the send buffer and then blocks until outstanding +ca_get() requests complete, and until channels created +specifying null connection handler function pointers connect for the first +time.

+
    +
  • If ECA_NORMAL is returned then it can be safely assumed that all + outstanding ca_get() requests have completed + successfully and channels created specifying null connection handler + function pointers have connected for the first time.
  • +
  • If ECA_TIMEOUT is returned then it must be assumed for all previous + ca_get() requests and properly qualified first time + channel connects have failed.
  • +
+ +

If ECA_TIMEOUT is returned then get requests may be reissued followed by a +subsequent call to ca_pend_io(). Specifically, the function will block only for +outstanding ca_get() requests issued, and also any channels +created specifying a null connection handler function pointer, after the last +call to ca_pend_io() or ca client context creation whichever is later. Note +that ca_create_channel() requests generally +should not be reissued for the same process variable unless +ca_clear_channel() is called first.

+ +

If no ca_get() or connection state change events are +outstanding then ca_pend_io() will flush the send buffer and return immediately +without processing any outstanding channel access background +activities.

+ +

The delay specified to ca_pend_io() should take into account worst case +network delays such as Ethernet collision exponential back off until +retransmission delays which can be quite long on overloaded networks.

+ +

Unlike ca_pend_event(), this routine +will not process CA's background activities if none of the selected IO requests +are pending.

+ +

Arguments

+
+
TIMEOUT
+
Specifies the time out interval. A TIMEOUT interval of + zero specifies forever.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_TIMEOUT - Selected IO requests didn't complete before specified +timeout

+ +

ECA_EVDISALLOW - Function inappropriate for use within an event handler

+ +

See Also

+ +

ca_get()

+ +

ca_create_channel()

+ +

ca_test_io()

+ +

ca_test_io()

+
#include <cadef.h>
+int ca_test_io();
+ +

Description

+ +

This function tests to see if all ca_get() requests are +complete and channels created specifying a null connection callback function +pointer are connected. It will report the status of outstanding +ca_get() requests issued, and channels created specifying a +null connection callback function pointer, after the last call to ca_pend_io() +or CA context initialization whichever is later.

+ +

Returns

+ +

ECA_IODONE - All IO operations completed

+ +

ECA_IOINPROGRESS - IO operations still in progress

+ +

See Also

+ +

ca_pend_io()

+ +

ca_pend_event()

+
#include <cadef.h>
+int ca_pend_event ( double TIMEOUT );
+int ca_poll ();
+ +

Description

+ +

When ca_pend_event() is invoked the send buffer is flushed and CA background +activity is processed for TIMEOUT seconds.

+ +

When ca_poll() is invoked the send buffer is flushed and any outstanding CA +background activity is processed.

+ +

The ca_pend_event() function will not return before the specified +timeout expires and all unfinished channel access labor has been processed, +and unlike ca_pend_io() returning from the +function does not indicate anything about the status of pending IO +requests.

+ +

Both ca_pend_event() and ca_poll() return ECA_TIMEOUT +when successful. This behavior probably isn't intuitive, but it is preserved to +insure backwards compatibility.

+ +

See also Thread Safety and Preemptive Callback to User +Code.

+ +

Arguments

+
+
TIMEOUT
+
The duration to block in this routine in seconds. A timeout of zero + seconds blocks forever.
+
+ +

Returns

+ +

ECA_TIMEOUT - The operation timed out

+ +

ECA_EVDISALLOW - Function inappropriate for use within a callback +handler

+ +

ca_flush_io()

+
#include <cadef.h>
+int ca_flush_io();
+ +

Description

+ +

Flush outstanding IO requests to the server. This routine might be useful +to users who need to flush requests prior to performing client side labor in +parallel with labor performed in the server.

+ +

Outstanding requests are also sent whenever the buffer which holds them +becomes full.

+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ca_signal()

+
#include <cadef.h>
+int ca_signal ( long CA_STATUS, const char * CONTEXT_STRING ); 
+void SEVCHK( CA_STATUS, CONTEXT_STRING );
+ +

Description

+ +

Provide the error message character string associated with the supplied +channel access error code and the supplied error context to diagnostics. If the +error code indicates an unsuccessful operation a stack dump is printed, if this +capability is available on the local operating system, and execution is +terminated.

+ +

SEVCHK is a macro envelope around ca_signal() which only calls ca_signal() if +the supplied error code indicates an unsuccessful operation. SEVCHK is the +recommended error handler for simple applications which do not wish to write +code testing the status returned from each channel access call.

+ +

Examples

+
status = ca_context_create (...); 
+SEVCHK ( status, "Unable to create a CA client context" );
+ +

If the application only wishes to print the message associated with an error +code or test the severity of an error there are also functions provided for +this purpose.

+ +

Arguments

+
+
CA_STATUS
+
The status (error code) returned from a channel access function.
+
+
+
CONTEXT_STRING
+
A null terminated character string to supply as error context to + diagnostics.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ca_add_exception_event()

+
#include <cadef.h> 
+typedef void (*pCallback) ( struct exception_handler_args HANDLERARGS );
+int ca_add_exception_event ( pCallback  USERFUNC, void *USERARG );
+ +

Description

+ +

Replace the currently installed CA context global exception handler callback.

+ +

When an error occurs in the server asynchronous to the clients thread then +information about this type of error is passed from the server to the client in +an exception message. When the client receives this exception message an +exception handler callback is called.The default exception handler prints a +diagnostic message on the client's standard out and terminates execution if the +error condition is severe.

+ +

Note that certain fields in "struct exception_handler_args" are not +applicable in the context of some error messages. For instance, a failed get +will supply the address in the client task where the returned value was +requested to be written. For other failed operations the value of the addr +field should not be used.

+ +

Arguments

+
+
USERFUNC
+
Pointer to a user callback function to be executed when exceptions + occur. Passing a null value causes the default exception handler to be + reinstalled. The following structure is passed by value to the user's + callback function. Currently, the op field can be one of + CA_OP_GET, CA_OP_PUT, CA_OP_CREATE_CHANNEL, CA_OP_ADD_EVENT, + CA_OP_CLEAR_EVENT, or CA_OP_OTHER. +
struct exception_handler_args {
+    void            *usr;   /* user argument supplied when installed */
+    chanId          chid;   /* channel id (may be null) */
+    long            type;   /* type requested */
+    long            count;  /* count requested */
+    void            *addr;  /* user's address to write results of CA_OP_GET */
+    long            stat;   /* channel access ECA_XXXX status code */
+    long            op;     /* CA_OP_GET, CA_OP_PUT, ..., CA_OP_OTHER */
+    const char      *ctx;   /* a character string containing context info */
+    sonst char      *pFile; /* source file name (may be NULL) */
+    unsigned        lineNo; /* source file line number (may be zero) */
+};
+
+
+
+
USERARG
+
pointer sized variable retained and passed back to user function + above
+
+ +

Example

+
void ca_exception_handler (
+    struct exception_handler_args args)
+{
+    char buf[512];
+    char *pName;
+
+    if ( args.chid ) {
+        pName = ca_name ( args.chid );
+    }
+    else {
+        pName = "?";
+    }
+    sprintf ( buf,
+            "%s - with request chan=%s op=%d data type=%s count=%d",
+            args.ctx, pName, args.op, dbr_type_to_text ( args.type ), args.count );
+    ca_signal ( args.stat, buf );
+}
+ca_add_exception_event ( ca_exception_handler , 0 );
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

+ +

ca_add_fd_registration()

+
#include <cadef.h>
+int ca_add_fd_registration ( void ( USERFUNC * ) ( void *USERARG, int FD, int OPENED ), void * USERARG )
+ +

Description

+ +

For use with the services provided by a file descriptor manager (IO +multiplexor) such as "fdmgr.c". A file descriptor manager is often needed when +two file descriptor IO intensive libraries such as the EPICS channel access +client library and the X window system client library must coexist in the same +UNIX process. This function allows an application code to be notified whenever +the CA client library places a new file descriptor into service and whenever +the CA client library removes a file descriptor from service. Specifying +USERFUNC=NULL disables file descriptor registration (this is the default).

+ +

Arguments

+ +

USERFUNC

+ +

Pointer to a user supplied C function returning null with the above +arguments.

+ +

USERARG

+ +

User supplied pointer sized variable passed to the above function.

+ +

FD

+ +

A file descriptor.

+ +

OPENED

+ +

Boolean argument is true if the file descriptor was opened and false if the +file descriptor was closed.

+ +

Example

+
int s;
+static struct myStruct aStruct;
+
+void fdReg ( struct myStruct *pStruct, int fd, int opened )
+{
+    if ( opened ) printf ( "fd %d was opened\n", fd );
+    else printf ( "fd %d was closed\n", fd );
+}
+s = ca_add_fd_registration ( fdReg, & aStruct );
+SEVCHK ( s, NULL );
+ +

Comments

+ +

When using this function it is advisable to call it only once prior to +calling any other CA function, or once just after creating the CA context (if +you create the context explicitly). Use of this interface can improve latency +slightly in applications that use non preemptive callback mode at the expense +of some additional runtime overhead when compared to the alternative which is +just polling ca_pend_event() periodically. It would probably not be appropriate +to use this function with preemptive callback mode. Starting with R3.14 this +function is implemented in a special backward compatibility mode. if +ca_add_fd_registration() is called, a single pseudo UDP fd is +created which CA pokes whenever something significant happens. Xt and others +can watch this fd so that backwards compatibility is preserved, and so that +they will not need to use preemptive callback mode but they will nevertheless +get the lowest latency response to the arrival of CA messages.

+ +

Returns

+ +

"ECA_NORMAL - Normal successful completion

+ +

+ +

ca_replace_printf_handler +()

+
#include <cadef.h>
+typedef int caPrintfFunc ( const char *pFormat, va_list args );
+int ca_replace_printf_handler ( caPrintfFunc *PFUNC );
+ +

Description

+ +

Replace the default handler for formatted diagnostic message output. The +default handler uses fprintf to send messages to 'stderr'.

+ +

Arguments

+
+
PFUNC
+
A pointer to a user supplied callback handler to be invoked when CA + prints diagnostic messages. Installing a null pointer will cause the + default callback handler to be reinstalled.
+
+ +

Examples

+
int my_printf ( char *pformat, va_list args ) {
+        int status;
+        status = vfprintf( stderr, pformat, args);
+        return status;
+}
+status = ca_replace_printf_handler ( my_printf );
+SEVCHK ( status, "failed to install my printf handler" );
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ca_replace_access_rights_event()

+
#include <cadef.h>
+typedef void ( caEventCallBackFunc )(struct access_rights_handler_args);
+int ca_replace_access_rights_event ( chid CHAN,
+        caEventCallBackFunc PFUNC );
+ +

Description

+ +

Install or replace the access rights state change callback handler for the +specified channel.

+ +

The callback handler is called in the following situations.

+
    +
  • whenever CA connects the channel immediately before the channel's + connection handler is called
  • +
  • whenever CA disconnects the channel immediately after the channel's + disconnect callback is called
  • +
  • once immediately after installation if the channel is connected.
  • +
  • whenever the access rights state of a connected channel changes
  • +
+ +

When a channel is created no access rights handler is installed.

+ +

Arguments

+
+
CHAN
+
The channel identifier.
+
+
+
PFUNC
+
Pointer to a user supplied callback function. A null pointer uninstalls + the current handler. The following arguments are passed by value + to the supplied callback handler. +
typedef struct ca_access_rights {
+    unsigned    read_access:1;
+    unsigned    write_access:1;
+} caar;
+
+/* arguments passed to user access rights handlers */
+struct  access_rights_handler_args {
+    chanId  chid;   /* channel id */
+    caar    ar;     /* new access rights state */
+};
+
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ca_field_type()

+
#include <cadef.h>
+chtype ca_field_type ( CHID );
+ +

Description

+ +

Return the native type in the server of the process variable.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+ +

Returns

+
+
TYPE
+
The data type code will be a member of the set of DBF_XXXX in + db_access.h. The constant TYPENOTCONN is returned if the channel is + disconnected.
+
+ +

ca_element_count()

+
#include <cadef.h>
+unsigned ca_element_count ( CHID );
+ +

Description

+ +

Return the maximum array element count in the server for the specified IO +channel.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+ +

Returns

+
+
COUNT
+
The maximum array element count in the server. An element count of + zero is returned if the channel is disconnected.
+
+ +

ca_name()

+
#include <cadef.h>
+char * ca_name ( CHID );
+ +

Description

+ +

Return the name provided when the supplied channel id was created.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+ +

Returns

+
+
PNAME
+
The channel name. The string returned is valid as long as the channel + specified exists.
+
+ +

ca_set_puser()

+
#include <cadef.h>
+void ca_set_puser ( chid CHID, void *PUSER );
+ +

Description

+ +

Set a user private void pointer variable retained with each channel for use +at the users discretion.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+
+
PUSER
+
user private void pointer
+
+ +

ca_puser()

+
#include <cadef.h>
+void * ca_puser ( CHID );
+ +

Description

+ +

Return a user private void pointer variable retained with each channel for +use at the users discretion.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+ +

Returns

+
+
PUSER
+
user private pointer
+
+ +

ca_state()

+
#include <cadef.h>
+enum channel_state {
+    cs_never_conn, /* valid chid, server not found or unavailable */
+    cs_prev_conn,  /* valid chid, previously connected to server */
+    cs_conn,       /* valid chid, connected to server */
+    cs_closed };   /* channel deleted by user */
+enum channel_state ca_state ( CHID );
+ +

Description

+ +

Returns an enumerated type indicating the current state of the specified IO +channel.

+ +

Arguments

+
+
CHID
+
channel identifier
+
+ +

Returns

+
+
STATE
+
the connection state
+
+ +

ca_message()

+
#include <cadef.h>
+const char * ca_message ( STATUS );
+ +

Description

+ +

return a message character string corresponding to a user specified CA +status code.

+ +

Arguments

+
+
STATUS
+
a CA status code
+
+ +

Returns

+
+
STRING
+
the corresponding error message string
+
+ +

ca_host_name()

+
#include <cadef.h>
+char * ca_host_name ( CHID );
+ +

Description

+ +

Return a character string which contains the name of the host to which a +channel is currently connected.

+ +

Arguments

+
+
CHID
+
the channel identifier
+
+ +

Returns

+
+
STRING
+
The process variable server's host name. If the channel is disconnected + the string "<disconnected>" is returned.
+
+ +

ca_read_access()

+
#include <cadef.h>
+int ca_read_access ( CHID );
+ +

Description

+ +

Returns boolean true if the client currently has read access to the +specified channel and boolean false otherwise.

+ +

Arguments

+
+
CHID
+
the channel identifier
+
+ +

Returns

+
+
STRING
+
boolean true if the client currently has read access to the specified + channel and boolean false otherwise
+
+ +

ca_write_access()

+
#include <cadef.h>
+int ca_write_access ( CHID );
+ +

Description

+ +

Returns boolean true if the client currently has write access to the +specified channel and boolean false otherwise.

+ +

Arguments

+
+
CHID
+
the channel identifier
+
+ +

Returns

+
+
STRING
+
boolean true if the client currently has write access to the specified + channel and boolean false otherwise
+
+ +

dbr_size[]

+
#include <db_access.h>
+extern unsigned dbr_size[/* TYPE */];
+ +

Description

+ +

An array that returns the size in bytes for a DBR_XXXX type.

+ +

Arguments

+
+
TYPE
+
The data type code. A member of the set of DBF_XXXX in db_access.h.
+
+ +

Returns

+
+
SIZE
+
the size in bytes of the specified type
+
+ +

dbr_size_n()

+
#include <db_access.h>
+unsigned dbr_size_n ( TYPE, COUNT );
+ +

Description

+ +

Returns the size in bytes for a DBR_XXXX type with COUNT elements. If the +DBR type is a structure then the value field is the last field in the +structure. If COUNT is greater than one then COUNT-1 elements are appended to +the end of the structure so that they can be addressed as an array through a +pointer to the value field.

+ +

Arguments

+
+
TYPE
+
The data type
+
+
+
COUNT
+
The element count
+
+ +

Returns

+
+
SIZE
+
the size in bytes of the specified type with the specified number of + elements
+
+ +

dbr_value_size[]

+
#include <db_access.h>
+extern unsigned dbr_value_size[/* TYPE */];
+ +

Description

+ +

The array dbr_value_size[TYPE] returns the size in bytes for the value +stored in a DBR_XXXX type. If the type is a structure the size of the value +field is returned otherwise the size of the type is returned.

+ +

Arguments

+
+
TYPE
+
The data type code. A member of the set of DBF_XXXX in db_access.h.
+
+ +

Returns

+
+
SIZE
+
the size in bytes of the value field if the type is a structure and + otherwise the size in bytes of the type
+
+ +

dbr_type_to_text()

+
#include <db_access.h>
+const char * dbr_type_text ( chtype TYPE );
+ +

Description

+ +

Returns a constant null terminated string corresponding to the specified dbr +type.

+ +

Arguments

+
+
TYPE
+
The data type code. A member of the set of DBR_XXXX in db_access.h.
+
+ +

Returns

+
+
STRING
+
+
The const string corresponding to the DBR_XXX type.
+
+ +

ca_test_event()

+
#include <cadef.h>
+ +

Description

+
void ca_test_event ( struct event_handler_args );
+ +

A built-in subscription update callback handler for debugging purposes that +prints diagnostics to standard out.

+ +

Examples

+
void ca_test_event (); 
+status = ca_create_subscription ( type, chid, ca_test_event, NULL, NULL ); 
+SEVCHK ( status, .... );
+ +

See Also

+ +

ca_create_subscription()

+ +

ca_sg_create()

+
#include <cadef.h>
+int ca_sg_create ( CA_SYNC_GID *PGID );
+ +

Description

+ +

Create a synchronous group and return an identifier for it.

+ +

A synchronous group can be used to guarantee that a set of channel access +requests have completed. Once a synchronous group has been created then channel +access get and put requests may be issued within it using +ca_sg_array_get() and ca_sg_array_put() respectively. +The routines ca_sg_block() and ca_sg_test() can be +used to block for and test for completion respectively. The routine +ca_sg_reset() is used to discard knowledge of old requests which +have timed out and in all likelihood will never be satisfied.

+ +

Any number of asynchronous groups can have application requested operations +outstanding within them at any given time.

+ +

Arguments

+
+
PGID
+
Pointer to a user supplied CA_SYNC_GID.
+
+ +

Examples

+
CA_SYNC_GID gid; 
+status = ca_sg_create ( &gid ); 
+SEVCHK ( status, Sync group create failed );
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_ALLOCMEM - Failed, unable to allocate memory

+ +

See Also

+ +

ca_sg_delete()

+ +

ca_sg_block()

+ +

ca_sg_test()

+ +

ca_sg_reset()

+ +

ca_sg_array_put()

+ +

ca_sg_array_get()

+ +

ca_sg_delete()

+
#include <cadef.h>
+int ca_sg_delete ( CA_SYNC_GID GID );
+ +

Description

+ +

Deletes a synchronous group.

+ +

Arguments

+
+
GID
+
Identifier of the synchronous group to be deleted.
+
+ +

Examples

+
CA_SYNC_GID gid; 
+status = ca_sg_delete ( gid ); 
+SEVCHK ( status, Sync group delete failed );
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADSYNCGRP - Invalid synchronous group

+ +

See Also

+ +

ca_sg_create()

+ +

ca_sg_block()

+
#include <cadef.h>
+int ca_sg_block ( CA_SYNC_GID GID, double TIMEOUT );
+ +

Description

+ +

Flushes the send buffer and then waits until outstanding requests complete +or the specified time out expires. At this time outstanding requests include +calls to ca_sg_array_get() and calls to ca_sg_array_put(). If ECA_TIMEOUT is +returned then failure must be assumed for all outstanding queries. Operations +can be reissued followed by another ca_sg_block(). This routine will only block +on outstanding queries issued after the last call to ca_sg_block(), +ca_sg_reset(), or ca_sg_create() whichever occurs later in time. If no queries +are outstanding then ca_sg_block() will return immediately without processing +any pending channel access activities.

+ +

Values written into your program's variables by a channel access synchronous +group request should not be referenced by your program until ECA_NORMAL has +been received from ca_sg_block(). This routine will process pending channel +access background activity while it is waiting.

+ +

Arguments

+
+
GID
+
Identifier of the synchronous group.
+
TIMEOUT
+
The duration to block in this routine in seconds. A timeout of zero + seconds blocks forever.
+
+ +

Examples

+
CA_SYNC_GID gid;
+status = ca_sg_block(gid, 0.0);
+SEVCHK(status, Sync group block failed);
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_TIMEOUT - The operation timed out

+ +

ECA_EVDISALLOW - Function inappropriate for use within an event handler

+ +

ECA_BADSYNCGRP - Invalid synchronous group

+ +

See Also

+ +

ca_sg_test()

+ +

ca_sg_reset()

+ +

ca_sg_test()

+
#include <cadef.h>
+int ca_sg_test ( CA_SYNC_GID GID )
+ +

Description

+ +

Test to see if all requests made within a synchronous group have +completed.

+ +

Arguments

+
+
GID
+
Identifier of the synchronous group.
+
+ +

Description

+ +

Test to see if all requests made within a synchronous group have +completed.

+ +

Examples

+
CA_SYNC_GID gid;
+status = ca_sg_test ( gid );
+ +

Returns

+ +

ECA_IODONE - IO operations completed

+ +

ECA_IOINPROGRESS - Some IO operations still in progress

+ +

ca_sg_reset()

+
#include <cadef.h>
+int ca_sg_reset ( CA_SYNC_GID GID )
+ +

Description

+ +

Reset the number of outstanding requests within the specified synchronous +group to zero so that ca_sg_test() will return ECA_IODONE and ca_sg_block() +will not block unless additional subsequent requests are made.

+ +

Arguments

+
+
GID
+
Identifier of the synchronous group.
+
+ +

Examples

+
CA_SYNC_GID gid; 
+status = ca_sg_reset(gid);
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADSYNCGRP - Invalid synchronous group

+ +

ca_sg_array_put()

+
#include <cadef.h>
+int ca_sg_put ( CA_SYNC_GID GID, chtype TYPE,
+        chid CHID, void *PVALUE );
+int ca_sg_array_put ( CA_SYNC_GID GID, chtype TYPE, 
+        unsigned long COUNT, chid CHID, void *PVALUE );
+ +

Write a value, or array of values, to a channel and increment the outstanding +request count of a synchronous group. The ca_sg_put() and +ca_sg_array_put() functionality is implemented using +ca_array_put_callback().

+ +

All remote operation requests such as the above are accumulated (buffered) +and not forwarded to the server until one of ca_flush_io(), +ca_pend_io(), ca_pend_event(), or +ca_sg_block() are called. This allows several requests to be +efficiently sent in one message.

+ +

If a connection is lost and then resumed outstanding puts are not +reissued.

+ +

Arguments

+
+
GID
+
synchronous group identifier
+
+
+
TYPE
+
The type of supplied value. Conversion will occur if it does not match + the native type. Specify one from the set of DBR_XXXX in db_access.h.
+
+
+
COUNT
+
element count to be written to the specified channel - must match the + array pointed to by PVALUE
+
+
+
CHID
+
channel identifier
+
+
+
PVALUE
+
A pointer to an application supplied buffer containing the value or + array of values returned
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADSYNCGRP - Invalid synchronous group

+ +

ECA_BADCHID - Corrupted CHID

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_BADCOUNT - Requested count larger than native element count

+ +

ECA_STRTOBIG - Unusually large string supplied

+ +

ECA_PUTFAIL - A local database put failed

+ +

See Also

+ +

ca_flush_io()

+ +

ca_sg_array_get()

+
#include <cadef.h>
+int ca_sg_get ( CA_SYNC_GID GID, chtype TYPE,
+        chid CHID, void *PVALUE );
+int ca_sg_array_get ( CA_SYNC_GID GID,
+        chtype TYPE, unsigned long COUNT,
+        chid CHID, void *PVALUE );
+ +

Description

+ +

Read a value from a channel and increment the outstanding request count of a +synchronous group. The ca_sg_get() and +ca_sg_array_get() functionality is implemented using +ca_array_get_callback().

+ +

The values written into your program's variables by ca_sg_get() +or ca_sg_array_get() should not be referenced by your program until +ECA_NORMAL has been received from ca_sg_block(), or until +ca_sg_test() returns ECA_IODONE.

+ +

All remote operation requests such as the above are accumulated (buffered) +and not forwarded to the server until one of ca_flush_io(), +ca_pend_io(), ca_pend_event(), or +ca_sg_block() are called. This allows several requests to be +efficiently sent in one message.

+ +

If a connection is lost and then resumed outstanding gets are not +reissued.

+ +

Arguments

+
+
GID
+
Identifier of the synchronous group.
+
+
+
TYPE
+
External type of returned value. Conversion will occur if this does not + match native type. Specify one from the set of DBR_XXXX in + db_access.h
+
+
+
COUNT
+
Element count to be read from the specified channel. It must match the + array pointed to by PVALUE.
+
+
+
CHID
+
channel identifier
+
+
+
PVALUE
+
Pointer to application supplied buffer that is to contain the value or + array of values to be returned
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_BADSYNCGRP - Invalid synchronous group

+ +

ECA_BADCHID - Corrupted CHID

+ +

ECA_BADCOUNT - Requested count larger than native element count

+ +

ECA_BADTYPE - Invalid DBR_XXXX type

+ +

ECA_GETFAIL - A local database get failed

+ +

See Also

+ +

ca_pend_io()

+ +

ca_flush_io()

+ +

ca_get_callback()

+ +

ca_client_status()

+
int ca_client_status ( unsigned level );
+int ca_context_status ( struct ca_client_context *CONTEXT, 
+       unsigned LEVEL );
+ +

Description

+ +

Prints information about the client context including, at higher interest +levels, status for each channel. Lacking a CA context pointer, +ca_client_status() prints information about the calling threads CA context.

+ +

Arguments

+
+
CONTEXT
+
A pointer to the CA context to examine.
+
LEVEL
+
The interest level. Increasing level produces increasing detail.
+
+ +

ca_current_context()

+
struct ca_client_context * ca_current_context ();
+ +

Description

+ +

Returns a pointer to the current thread's CA context. If none then null is +returned.

+ +

See Also

+ +

ca_attach_context()

+ +

ca_detach_context()

+ +

ca_context_create()

+ +

ca_context_destroy()

+ +

ca_attach_context()

+
int ca_attach_context (struct ca_client_context *CONTEXT);
+ +

Description

+ +

The calling thread becomes a member of the specified CA context. If +ca_disable_preemptive_callback is specified when +ca_context_create() is called (or if ca_task_initialize() is called) then +additional threads are not allowed to join the CA context because +allowing other threads to join implies that CA callbacks will be called +preemptively from more than one thread.

+ +

Arguments

+
+
CONTEXT
+
A pointer to the CA context to join with.
+
+ +

Returns

+ +

ECA_NORMAL - Normal successful completion

+ +

ECA_NOTTHREADED - Context is not preemptive so cannot be joined

+ +

ECA_ISATTACHED - Thread already attached to a CA context

+ +

See Also

+ +

ca_current_context()

+ +

ca_detach_context()

+ +

ca_context_create()

+ +

ca_context_destroy()

+ +

ca_detach_context()

+
void ca_detach_context();
+ +

Description

+ +

Detach from any CA context currently attached to the calling thread. This +does not cleanup or shutdown any currently attached CA context (for +that use ca_context_destroy()).

+ +

See Also

+ +

ca_current_context()

+ +

ca_attach_context()

+ +

ca_context_create()

+ +

ca_context_destroy()

+ +

ca_dump_dbr()

+
void ca_dump_dbr (chtype TYPE, unsigned COUNT, const void * PDBR);
+ +

Description

+ +

Dumps the specified dbr data type to standard out.

+ +

Arguments

+
+
TYPE
+
The data type (from the DBR_XXX set described in db_access.h).
+
COUNT
+
The array element count
+
PDBR
+
A pointer to data of the specified count and number.
+
+
+ +

Return Codes

+
+
ECA_NORMAL
+
Normal successful completion
+
ECA_ALLOCMEM
+
Unable to allocate additional dynamic memory
+
ECA_TOLARGE
+
The requested data transfer is greater than available memory or + EPICS_CA_MAX_ARRAY_BYTES
+
ECA_BADTYPE
+
The data type specified is invalid
+
ECA_BADSTR
+
Invalid string
+
ECA_BADCHID
+
Invalid channel identifier
+
ECA_BADCOUNT
+
Invalid element count requested
+
ECA_PUTFAIL
+
Channel write request failed
+
ECA_GETFAIL
+
Channel read request failed
+
ECA_ADDFAIL
+
unable to install subscription request
+
ECA_TIMEOUT
+
User specified timeout on IO operation expired
+
ECA_EVDISALLOW
+
function called was inappropriate for use within a callback + function
+
ECA_IODONE
+
IO operations have completed
+
ECA_IOINPROGRESS
+
IO operations are in progress
+
ECA_BADSYNCGRP
+
Invalid synchronous group identifier
+
ECA_NORDACCESS
+
Read access denied
+
ECA_NOWTACCESS
+
Write access denied
+
ECA_DISCONN
+
Virtual circuit disconnect"
+
ECA_DBLCHNL
+
Identical process variable name on multiple servers
+
ECA_EVDISALLOW
+
Request inappropriate within subscription (monitor) update callback
+
ECA_BADMONID
+
Bad event subscription (monitor) identifier
+
ECA_BADMASK
+
Invalid event selection mask
+
ECA_PUTCBINPROG
+
Put callback timed out
+
ECA_PUTCBINPROG
+
Put callback timed out
+
ECA_ANACHRONISM
+
Requested feature is no longer supported
+
ECA_NOSEARCHADDR
+
Empty PV search address list
+
ECA_NOCONVERT
+
No reasonable data conversion between client and server types
+
ECA_BADFUNCPTR
+
Invalid function pointer
+
ECA_ISATTACHED
+
Thread is already attached to a client context
+
ECA_UNAVAILINSERV
+
Not supported by attached service
+
ECA_CHANDESTROY
+
User destroyed channel
+
ECA_BADPRIORITY
+
Invalid channel priority
+
ECA_NOTTHREADED
+
Preemptive callback not enabled - additional threads may not join + context
+
ECA_16KARRAYCLIENT
+
Client's protocol revision does not support transfers exceeding 16k + bytes
+
+ +

+ + diff --git a/modules/ca/src/client/Makefile b/modules/ca/src/client/Makefile new file mode 100644 index 000000000..6a7f690e9 --- /dev/null +++ b/modules/ca/src/client/Makefile @@ -0,0 +1,136 @@ +#************************************************************************* +# 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 + +HTMLS += CAref.html + +# +# includes to install from this subproject +# +INC += cadef.h +INC += caerr.h +INC += caeventmask.h +INC += caProto.h +INC += db_access.h +INC += addrList.h +INC += cacIO.h +INC += caDiagnostics.h +INC += net_convert.h +INC += caVersion.h +INC += caVersionNum.h + +LIBSRCS += cac.cpp +LIBSRCS += cacChannel.cpp +LIBSRCS += cacChannelNotify.cpp +LIBSRCS += cacContextNotify.cpp +LIBSRCS += cacReadNotify.cpp +LIBSRCS += cacWriteNotify.cpp +LIBSRCS += cacStateNotify.cpp +LIBSRCS += access.cpp +LIBSRCS += iocinf.cpp +LIBSRCS += convert.cpp +LIBSRCS += test_event.cpp +LIBSRCS += repeater.cpp +LIBSRCS += searchTimer.cpp +LIBSRCS += disconnectGovernorTimer.cpp +LIBSRCS += repeaterSubscribeTimer.cpp +LIBSRCS += baseNMIU.cpp +LIBSRCS += nciu.cpp +LIBSRCS += netiiu.cpp +LIBSRCS += udpiiu.cpp +LIBSRCS += tcpiiu.cpp +LIBSRCS += noopiiu.cpp +LIBSRCS += netReadNotifyIO.cpp +LIBSRCS += netWriteNotifyIO.cpp +LIBSRCS += netSubscription.cpp +LIBSRCS += tcpSendWatchdog.cpp +LIBSRCS += tcpRecvWatchdog.cpp +LIBSRCS += bhe.cpp +LIBSRCS += ca_client_context.cpp +LIBSRCS += oldChannelNotify.cpp +LIBSRCS += oldSubscription.cpp +LIBSRCS += getCallback.cpp +LIBSRCS += getCopy.cpp +LIBSRCS += putCallback.cpp +LIBSRCS += syncgrp.cpp +LIBSRCS += CASG.cpp +LIBSRCS += syncGroupNotify.cpp +LIBSRCS += syncGroupReadNotify.cpp +LIBSRCS += syncGroupWriteNotify.cpp +LIBSRCS += localHostName.cpp +LIBSRCS += comQueRecv.cpp +LIBSRCS += comQueSend.cpp +LIBSRCS += comBuf.cpp +LIBSRCS += hostNameCache.cpp +LIBSRCS += msgForMultiplyDefinedPV.cpp + +LIBRARY=ca + +ca_RCS = ca.rc + +ca_LIBS = Com + +ca_SYS_LIBS_WIN32 = ws2_32 advapi32 user32 + +# libs needed for PROD and TESTPRODUCT +PROD_LIBS = ca Com +# needed when its an object library build +PROD_SYS_LIBS_WIN32 = ws2_32 advapi32 user32 + +PROD_DEFAULT += caRepeater catime acctst caConnTest casw caEventRate +PROD_vxWorks = -nil- +PROD_RTEMS = -nil- +PROD_iOS = -nil- + +OBJS_vxWorks = catime acctst caConnTest casw caEventRate acctstRegister + +caRepeater_SRCS = caRepeater.cpp +catime_SRCS = catimeMain.c catime.c +acctst_SRCS = acctstMain.c acctst.c +caEventRate_SRCS = caEventRateMain.cpp caEventRate.cpp +casw_SRCS = casw.cpp +caConnTest_SRCS = caConnTestMain.cpp caConnTest.cpp + +casw_SYS_LIBS_solaris = socket + +SCRIPTS_HOST = S99caRepeater +SCRIPTS_Linux = caRepeater.service + +EXPAND += S99caRepeater@ +EXPAND += caRepeater.service@ +EXPAND_VARS = INSTALL_BIN=$(abspath $(INSTALL_BIN)) + +SRC_DIRS += $(TOP)/src/client/test +PROD_HOST += ca_test +ca_test_SRCS = ca_test_main.c ca_test.c +ca_test_LIBS = ca Com +ca_test_SYS_LIBS_WIN32 = ws2_32 advapi32 user32 + +OBJS_vxWorks += ca_test + +EXPANDVARS += EPICS_CA_MAJOR_VERSION +EXPANDVARS += EPICS_CA_MINOR_VERSION +EXPANDVARS += EPICS_CA_MAINTENANCE_VERSION +EXPANDVARS += EPICS_CA_DEVELOPMENT_FLAG + +EXPANDFLAGS += $(foreach var,$(EXPANDVARS),-D$(var)="$(strip $($(var)))") + +# shared library ABI version. +SHRLIB_VERSION = 4.13.1 + +include $(TOP)/configure/RULES + + +# Can't use EXPAND as generated headers must appear +# in O.Common, but EXPAND emits rules for O.$(T_A) +../O.Common/caVersionNum.h: ../caVersionNum.h@ + $(EXPAND_TOOL) $(EXPANDFLAGS) $($@_EXPANDFLAGS) $< $@ diff --git a/modules/ca/src/client/S99caRepeater@ b/modules/ca/src/client/S99caRepeater@ new file mode 100644 index 000000000..aabefb72c --- /dev/null +++ b/modules/ca/src/client/S99caRepeater@ @@ -0,0 +1,28 @@ +#!/bin/sh +# +# System-V init script for the EPICS CA Repeater. +# + +INSTALL_BIN=@INSTALL_BIN@ + +# To change the default values for the EPICS environment parameters, +# uncomment and modify the relevant lines below. These are the only +# EPICS environment variables that the CA Repeater makes use of. + +# EPICS_CA_REPEATER_PORT="5065" export EPICS_CA_REPEATER_PORT + +if [ $1 = "start" ]; then + if [ -x $INSTALL_BIN/caRepeater ]; then + echo "Starting EPICS CA Repeater " + $INSTALL_BIN/caRepeater & + fi +else + if [ $1 = "stop" ]; then + pid=`ps -e | sed -ne '/caRepeat/s/^ *\([1-9][0-9]*\).*$/\1/p'` + if [ "${pid}" != "" ]; then + echo "Stopping EPICS CA Repeater " + kill ${pid} + fi + fi +fi + diff --git a/modules/ca/src/client/SearchDest.h b/modules/ca/src/client/SearchDest.h new file mode 100644 index 000000000..c22be7c87 --- /dev/null +++ b/modules/ca/src/client/SearchDest.h @@ -0,0 +1,39 @@ +/*************************************************************************\ + * 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. +\*************************************************************************/ + +#ifndef SearchDest_h +#define SearchDest_h + +#include +#include +#include +#include "caProto.h" + +class channelNode; +class epicsMutex; +template < class T > class epicsGuard; + +struct SearchDest : + public tsDLNode < SearchDest > { + virtual ~SearchDest () {}; + struct Callback { + virtual ~Callback () {}; + virtual void notify ( + const caHdr & msg, const void * pPayload, + const osiSockAddr & addr, const epicsTime & ) = 0; + virtual void show ( + epicsGuard < epicsMutex > &, unsigned level ) const = 0; + }; + virtual void searchRequest ( epicsGuard < epicsMutex > &, + const char * pbuf, size_t len ) = 0; + virtual void show ( epicsGuard < epicsMutex > &, unsigned level ) const = 0; +}; + +#endif // SearchDest_h diff --git a/modules/ca/src/client/access.cpp b/modules/ca/src/client/access.cpp new file mode 100644 index 000000000..a36899c02 --- /dev/null +++ b/modules/ca/src/client/access.cpp @@ -0,0 +1,1107 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeffrey O. Hill + * + */ + +#include +#include + +#include "dbDefs.h" +#include "epicsExit.h" + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + + +/* + * allocate error message string array + * here so I can use sizeof + */ +#define CA_ERROR_GLBLSOURCE + +/* + * allocate header version strings here + */ +#define CAC_VERSION_GLOBAL + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" +#include "cac.h" + +epicsThreadPrivateId caClientContextId; + +const char * ca_message_text [] += +{ +"Normal successful completion", +"Maximum simultaneous IOC connections exceeded", +"Unknown internet host", +"Unknown internet service", +"Unable to allocate a new socket", + +"Unable to connect to internet host or service", +"Unable to allocate additional dynamic memory", +"Unknown IO channel", +"Record field specified inappropriate for channel specified", +"The requested data transfer is greater than available memory or EPICS_CA_MAX_ARRAY_BYTES", + +"User specified timeout on IO operation expired", +"Sorry, that feature is planned but not supported at this time", +"The supplied string is unusually large", +"The request was ignored because the specified channel is disconnected", +"The data type specifed is invalid", + +"Remote Channel not found", +"Unable to locate all user specified channels", +"Channel Access Internal Failure", +"The requested local DB operation failed", +"Channel read request failed", + +"Channel write request failed", +"Channel subscription request failed", +"Invalid element count requested", +"Invalid string", +"Virtual circuit disconnect", + +"Identical process variable names on multiple servers", +"Request inappropriate within subscription (monitor) update callback", +"Database value get for that channel failed during channel search", +"Unable to initialize without the vxWorks VX_FP_TASK task option set", +"Event queue overflow has prevented first pass event after event add", + +"Bad event subscription (monitor) identifier", +"Remote channel has new network address", +"New or resumed network connection", +"Specified task isnt a member of a CA context", +"Attempt to use defunct CA feature failed", + +"The supplied string is empty", +"Unable to spawn the CA repeater thread- auto reconnect will fail", +"No channel id match for search reply- search reply ignored", +"Reseting dead connection- will try to reconnect", +"Server (IOC) has fallen behind or is not responding- still waiting", + +"No internet interface with broadcast available", +"Invalid event selection mask", +"IO operations have completed", +"IO operations are in progress", +"Invalid synchronous group identifier", + +"Put callback timed out", +"Read access denied", +"Write access denied", +"Requested feature is no longer supported", +"Empty PV search address list", + +"No reasonable data conversion between client and server types", +"Invalid channel identifier", +"Invalid function pointer", +"Thread is already attached to a client context", +"Not supported by attached service", + +"User destroyed channel", +"Invalid channel priority", +"Preemptive callback not enabled - additional threads may not join context", +"Client's protocol revision does not support transfers exceeding 16k bytes", +"Virtual circuit connection sequence aborted", + +"Virtual circuit unresponsive" +}; + +static epicsThreadOnceId caClientContextIdOnce = EPICS_THREAD_ONCE_INIT; + +// runs once only for each process +extern "C" void ca_init_client_context ( void * ) +{ + caClientContextId = epicsThreadPrivateCreate (); +} + +/* + * fetchClientContext (); + */ +int fetchClientContext ( ca_client_context **ppcac ) +{ + epicsThreadOnce ( &caClientContextIdOnce, ca_init_client_context, 0 ); + if ( caClientContextId == 0 ) { + return ECA_ALLOCMEM; + } + + int status; + *ppcac = ( ca_client_context * ) epicsThreadPrivateGet ( caClientContextId ); + if ( *ppcac ) { + status = ECA_NORMAL; + } + else { + status = ca_task_initialize (); + if ( status == ECA_NORMAL ) { + *ppcac = (ca_client_context *) epicsThreadPrivateGet ( caClientContextId ); + if ( ! *ppcac ) { + status = ECA_INTERNAL; + } + } + } + + return status; +} + +/* + * ca_task_initialize () + */ +// extern "C" +int epicsShareAPI ca_task_initialize ( void ) +{ + return ca_context_create ( ca_disable_preemptive_callback ); +} + +// extern "C" +int epicsShareAPI ca_context_create ( + ca_preemptive_callback_select premptiveCallbackSelect ) +{ + ca_client_context *pcac; + + try { + epicsThreadOnce ( & caClientContextIdOnce, ca_init_client_context, 0); + if ( caClientContextId == 0 ) { + return ECA_ALLOCMEM; + } + + pcac = ( ca_client_context * ) epicsThreadPrivateGet ( caClientContextId ); + if ( pcac ) { + if ( premptiveCallbackSelect == ca_enable_preemptive_callback && + ! pcac->preemptiveCallbakIsEnabled() ) { + return ECA_NOTTHREADED; + } + return ECA_NORMAL; + } + + pcac = new ca_client_context ( + premptiveCallbackSelect == ca_enable_preemptive_callback ); + if ( ! pcac ) { + return ECA_ALLOCMEM; + } + + epicsThreadPrivateSet ( caClientContextId, (void *) pcac ); + } + catch ( ... ) { + return ECA_ALLOCMEM; + } + return ECA_NORMAL; +} + +// +// ca_modify_host_name () +// +// defunct +// +// extern "C" +int epicsShareAPI ca_modify_host_name ( const char * ) +{ + return ECA_NORMAL; +} + +// +// ca_modify_user_name() +// +// defunct +// +// extern "C" +int epicsShareAPI ca_modify_user_name ( const char * ) +{ + return ECA_NORMAL; +} + +// +// ca_context_destroy () +// +// extern "C" +void epicsShareAPI ca_context_destroy () +{ + ca_client_context *pcac; + + if ( caClientContextId != NULL ) { + pcac = (ca_client_context *) epicsThreadPrivateGet ( caClientContextId ); + if ( pcac ) { + delete pcac; + epicsThreadPrivateSet ( caClientContextId, 0 ); + } + } +} + +/* + * ca_task_exit() + * + * releases all resources alloc to a channel access client + */ +// extern "C" +int epicsShareAPI ca_task_exit () +{ + ca_context_destroy (); + return ECA_NORMAL; +} + +/* + * + * CA_BUILD_AND_CONNECT + * + * backwards compatible entry point to ca_search_and_connect() + */ +// extern "C" +int epicsShareAPI ca_build_and_connect ( const char *name_str, chtype get_type, + arrayElementCount get_count, chid * chan, void *pvalue, + caCh *conn_func, void *puser ) +{ + if ( get_type != TYPENOTCONN && pvalue != 0 && get_count != 0 ) { + return ECA_ANACHRONISM; + } + + return ca_search_and_connect ( name_str, chan, conn_func, puser ); +} + +/* + * ca_search_and_connect() + */ +// extern "C" +int epicsShareAPI ca_search_and_connect ( + const char * name_str, chid * chanptr, + caCh * conn_func, void * puser ) +{ + return ca_create_channel ( name_str, conn_func, + puser, CA_PRIORITY_DEFAULT, chanptr ); +} + +// extern "C" +int epicsShareAPI ca_create_channel ( + const char * name_str, caCh * conn_func, void * puser, + capri priority, chid * chanptr ) +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( & pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + { + CAFDHANDLER * pFunc = 0; + void * pArg = 0; + { + epicsGuard < epicsMutex > + guard ( pcac->mutex ); + if ( pcac->fdRegFuncNeedsToBeCalled ) { + pFunc = pcac->fdRegFunc; + pArg = pcac->fdRegArg; + pcac->fdRegFuncNeedsToBeCalled = false; + } + } + if ( pFunc ) { + ( *pFunc ) ( pArg, pcac->sock, true ); + } + } + + try { + epicsGuard < epicsMutex > guard ( pcac->mutex ); + oldChannelNotify * pChanNotify = + new ( pcac->oldChannelNotifyFreeList ) + oldChannelNotify ( guard, *pcac, name_str, + conn_func, puser, priority ); + // make sure that their chan pointer is set prior to + // calling connection call backs + *chanptr = pChanNotify; + pChanNotify->initiateConnect ( guard ); + // no need to worry about a connect preempting here because + // the connect sequence will not start untill initiateConnect() + // is called + } + catch ( cacChannel::badString & ) { + return ECA_BADSTR; + } + catch ( std::bad_alloc & ) { + return ECA_ALLOCMEM; + } + catch ( cacChannel::badPriority & ) { + return ECA_BADPRIORITY; + } + catch ( cacChannel::unsupportedByService & ) { + return ECA_UNAVAILINSERV; + } + catch ( std :: exception & except ) { + pcac->printFormated ( + "ca_create_channel: " + "unexpected exception was \"%s\"", + except.what () ); + return ECA_INTERNAL; + } + catch ( ... ) { + return ECA_INTERNAL; + } + + return ECA_NORMAL; +} + +/* + * ca_clear_channel () + * + * a known defect here is that there will be a + * crash if they destroy the channel after destroying + * its context + */ +// extern "C" +int epicsShareAPI ca_clear_channel ( chid pChan ) +{ + ca_client_context & cac = pChan->getClientCtx (); + { + epicsGuard < epicsMutex > guard ( cac.mutex ); + try { + pChan->eliminateExcessiveSendBacklog ( guard ); + } + catch ( cacChannel::notConnected & ) { + // intentionally ignored + } + } + if ( cac.pCallbackGuard.get() && + cac.createdByThread == epicsThreadGetIdSelf () ) { + epicsGuard < epicsMutex > guard ( cac.mutex ); + pChan->destructor ( *cac.pCallbackGuard.get(), guard ); + cac.oldChannelNotifyFreeList.release ( pChan ); + } + else { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( cac.cbMutex ); + epicsGuard < epicsMutex > guard ( cac.mutex ); + pChan->destructor ( *cac.pCallbackGuard.get(), guard ); + cac.oldChannelNotifyFreeList.release ( pChan ); + } + return ECA_NORMAL; +} + +/* + * Specify an event subroutine to be run for asynch exceptions + */ +// extern "C" +int epicsShareAPI ca_add_exception_event ( caExceptionHandler *pfunc, void *arg ) +{ + ca_client_context *pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + pcac->changeExceptionEvent ( pfunc, arg ); + + return ECA_NORMAL; +} + +/* + * ca_add_masked_array_event + */ +int epicsShareAPI ca_add_masked_array_event ( + chtype type, arrayElementCount count, chid pChan, + caEventCallBackFunc *pCallBack, void *pCallBackArg, + ca_real, ca_real, ca_real, + evid *monixptr, long mask ) +{ + return ca_create_subscription ( type, count, pChan, mask, + pCallBack, pCallBackArg, monixptr ); +} + +/* + * ca_clear_event () + */ +int epicsShareAPI ca_clear_event ( evid pMon ) +{ + return ca_clear_subscription ( pMon ); +} + +// extern "C" +chid epicsShareAPI ca_evid_to_chid ( evid pMon ) +{ + return & pMon->channel (); +} + +// extern "C" +int epicsShareAPI ca_pend ( ca_real timeout, int early ) +{ + if ( early ) { + return ca_pend_io ( timeout ); + } + else { + return ca_pend_event ( timeout ); + } +} + +/* + * ca_pend_event () + */ +// extern "C" +int epicsShareAPI ca_pend_event ( ca_real timeout ) +{ + ca_client_context *pcac; + int status = fetchClientContext ( &pcac ); + if ( status != ECA_NORMAL ) { + return status; + } + + try { + // preserve past odd ball behavior of waiting forever when + // the delay is zero + if ( timeout == 0.0 ) { + while ( true ) { + pcac->pendEvent ( 60.0 ); + } + } + return pcac->pendEvent ( timeout ); + } + catch ( ... ) { + return ECA_INTERNAL; + } +} + +/* + * ca_pend_io () + */ +// extern "C" +int epicsShareAPI ca_pend_io ( ca_real timeout ) +{ + ca_client_context *pcac; + int status = fetchClientContext ( &pcac ); + if ( status != ECA_NORMAL ) { + return status; + } + + try { + // preserve past odd ball behavior of waiting forever when + // the delay is zero + if ( timeout == 0.0 ) { + return pcac->pendIO ( DBL_MAX ); + } + + return pcac->pendIO ( timeout ); + } + catch ( ... ) { + return ECA_INTERNAL; + } +} + +/* + * ca_flush_io () + */ +int epicsShareAPI ca_flush_io () +{ + ca_client_context * pcac; + int caStatus = fetchClientContext (&pcac); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + epicsGuard < epicsMutex > guard ( pcac->mutex ); + pcac->flush ( guard ); + + return ECA_NORMAL; +} + +/* + * CA_TEST_IO () + */ +int epicsShareAPI ca_test_io () +{ + ca_client_context *pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + if ( pcac->ioComplete () ) { + return ECA_IODONE; + } + else{ + return ECA_IOINPROGRESS; + } +} + +/* + * CA_SIGNAL() + */ +// extern "C" +void epicsShareAPI ca_signal ( long ca_status, const char *message ) +{ + ca_signal_with_file_and_lineno ( ca_status, message, NULL, 0 ); +} + +/* + * ca_message (long ca_status) + * + * - if it is an unknown error code then it possible + * that the error string generated below + * will be overwritten before (or while) the caller + * of this routine is calling this routine + * (if they call this routine again). + */ +// extern "C" +const char * epicsShareAPI ca_message ( long ca_status ) +{ + unsigned msgNo = CA_EXTRACT_MSG_NO ( ca_status ); + + if ( msgNo < NELEMENTS (ca_message_text) ) { + return ca_message_text[msgNo]; + } + else { + return "new CA message number known only by server - see caerr.h"; + } +} + +/* + * ca_signal_with_file_and_lineno() + */ +// extern "C" +void epicsShareAPI ca_signal_with_file_and_lineno ( long ca_status, + const char *message, const char *pfilenm, int lineno ) +{ + ca_signal_formated ( ca_status, pfilenm, lineno, message ); +} + +/* + * ca_signal_formated() + */ +// extern "C" +void epicsShareAPI ca_signal_formated ( long ca_status, const char *pfilenm, + int lineno, const char *pFormat, ... ) +{ + ca_client_context *pcac; + + if ( caClientContextId ) { + pcac = ( ca_client_context * ) epicsThreadPrivateGet ( caClientContextId ); + } + else { + pcac = 0; + } + + va_list theArgs; + va_start ( theArgs, pFormat ); + if ( pcac ) { + pcac->vSignal ( ca_status, pfilenm, lineno, pFormat, theArgs ); + } + else { + fprintf ( stderr, "CA exception in thread w/o CA ctx: status=%s file=%s line=%d: \n", + ca_message ( ca_status ), pfilenm, lineno ); + if ( pFormat ) { + vfprintf ( stderr, pFormat, theArgs ); + } + } + va_end ( theArgs ); +} + +/* + * CA_ADD_FD_REGISTRATION + * + * call their function with their argument whenever + * a new fd is added or removed + * (for a manager of the select system call under UNIX) + * + */ +// extern "C" +int epicsShareAPI ca_add_fd_registration ( CAFDHANDLER * func, void * arg ) +{ + ca_client_context *pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + pcac->registerForFileDescriptorCallBack ( func, arg ); + + return ECA_NORMAL; +} + +/* + * ca_version() + * function that returns the CA version string + */ +// extern "C" +const char * epicsShareAPI ca_version () +{ + return CA_VERSION_STRING ( CA_MINOR_PROTOCOL_REVISION ); +} + +/* + * ca_replace_printf_handler () + */ +// extern "C" +int epicsShareAPI ca_replace_printf_handler ( caPrintfFunc *ca_printf_func ) +{ + ca_client_context *pcac; + int caStatus = fetchClientContext (&pcac); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + pcac->replaceErrLogHandler ( ca_printf_func ); + + return ECA_NORMAL; +} + +/* + * ca_get_ioc_connection_count() + * + * returns the number of IOC's that CA is connected to + * (for testing purposes only) + */ +// extern "C" +unsigned epicsShareAPI ca_get_ioc_connection_count () +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( & pcac ); + if ( caStatus != ECA_NORMAL ) { + return 0u; + } + + return pcac->circuitCount (); +} + +unsigned epicsShareAPI ca_beacon_anomaly_count () +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( & pcac ); + if ( caStatus != ECA_NORMAL ) { + return 0u; + } + + return pcac->beaconAnomaliesSinceProgramStart (); +} + +// extern "C" +int epicsShareAPI ca_channel_status ( epicsThreadId /* tid */ ) +{ + ::printf ("The R3.14 EPICS OS abstraction API does not allow peeking at thread private storage of another thread.\n"); + ::printf ("Please call \"ca_client_status ( unsigned level )\" from the subsystem specific diagnostic code.\n"); + return ECA_ANACHRONISM; +} + +// extern "C" +int epicsShareAPI ca_client_status ( unsigned level ) +{ + ca_client_context *pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + pcac->show ( level ); + return ECA_NORMAL; +} + +int epicsShareAPI ca_context_status ( ca_client_context * pcac, unsigned level ) +{ + pcac->show ( level ); + return ECA_NORMAL; +} + +/* + * ca_current_context () + * + * used when an auxillary thread needs to join a CA client context started + * by another thread + */ +// extern "C" +struct ca_client_context * epicsShareAPI ca_current_context () +{ + struct ca_client_context *pCtx; + if ( caClientContextId ) { + pCtx = ( struct ca_client_context * ) + epicsThreadPrivateGet ( caClientContextId ); + } + else { + pCtx = 0; + } + return pCtx; +} + +/* + * ca_attach_context () + * + * used when an auxillary thread needs to join a CA client context started + * by another thread + */ +// extern "C" +int epicsShareAPI ca_attach_context ( struct ca_client_context * pCtx ) +{ + ca_client_context *pcac = (ca_client_context *) epicsThreadPrivateGet ( caClientContextId ); + if ( pcac && pCtx != 0 ) { + return ECA_ISATTACHED; + } + if ( ! pCtx->preemptiveCallbakIsEnabled() ) { + return ECA_NOTTHREADED; + } + epicsThreadPrivateSet ( caClientContextId, pCtx ); + return ECA_NORMAL; +} + +void epicsShareAPI ca_detach_context () +{ + if ( caClientContextId ) { + epicsThreadPrivateSet ( caClientContextId, 0 ); + } +} + +int epicsShareAPI ca_preemtive_callback_is_enabled () +{ + ca_client_context *pcac = (ca_client_context *) epicsThreadPrivateGet ( caClientContextId ); + if ( ! pcac ) { + return 0; + } + return pcac->preemptiveCallbakIsEnabled (); +} + + +// extern "C" +void epicsShareAPI ca_self_test () +{ + ca_client_context *pcac = (ca_client_context *) epicsThreadPrivateGet ( caClientContextId ); + if ( ! pcac ) { + return; + } + pcac->selfTest (); +} + +// extern "C" +epicsShareDef const int epicsTypeToDBR_XXXX [lastEpicsType+1] = { + DBR_SHORT, /* forces conversion fronm uint8 to int16 */ + DBR_CHAR, + DBR_SHORT, + DBR_LONG, /* forces conversion fronm uint16 to int32 */ + DBR_ENUM, + DBR_LONG, + DBR_LONG, /* very large unsigned number will not map */ + DBR_FLOAT, + DBR_DOUBLE, + DBR_STRING, + DBR_STRING +}; + +// extern "C" +epicsShareDef const epicsType DBR_XXXXToEpicsType [LAST_BUFFER_TYPE+1] = { + epicsOldStringT, + epicsInt16T, + epicsFloat32T, + epicsEnum16T, + epicsUInt8T, + epicsInt32T, + epicsFloat64T, + + epicsOldStringT, + epicsInt16T, + epicsFloat32T, + epicsEnum16T, + epicsUInt8T, + epicsInt32T, + epicsFloat64T, + + epicsOldStringT, + epicsInt16T, + epicsFloat32T, + epicsEnum16T, + epicsUInt8T, + epicsInt32T, + epicsFloat64T, + + epicsOldStringT, + epicsInt16T, + epicsFloat32T, + epicsEnum16T, + epicsUInt8T, + epicsInt32T, + epicsFloat64T, + + epicsOldStringT, + epicsInt16T, + epicsFloat32T, + epicsEnum16T, + epicsUInt8T, + epicsInt32T, + epicsFloat64T, + + epicsUInt16T, + epicsUInt16T, + epicsOldStringT, + epicsOldStringT +}; + +// extern "C" +epicsShareDef const unsigned short dbr_size[LAST_BUFFER_TYPE+1] = { + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_short_t), /* short */ + sizeof(dbr_float_t), /* IEEE Float */ + sizeof(dbr_enum_t), /* item number */ + sizeof(dbr_char_t), /* character */ + + sizeof(dbr_long_t), /* long */ + sizeof(dbr_double_t), /* double */ + sizeof(struct dbr_sts_string), /* string field with status */ + sizeof(struct dbr_sts_short), /* short field with status */ + sizeof(struct dbr_sts_float), /* float field with status */ + + sizeof(struct dbr_sts_enum), /* item number with status */ + sizeof(struct dbr_sts_char), /* char field with status */ + sizeof(struct dbr_sts_long), /* long field with status */ + sizeof(struct dbr_sts_double), /* double field with time */ + sizeof(struct dbr_time_string), /* string field with time */ + + sizeof(struct dbr_time_short), /* short field with time */ + sizeof(struct dbr_time_float), /* float field with time */ + sizeof(struct dbr_time_enum), /* item number with time */ + sizeof(struct dbr_time_char), /* char field with time */ + sizeof(struct dbr_time_long), /* long field with time */ + + sizeof(struct dbr_time_double), /* double field with time */ + sizeof(struct dbr_sts_string), /* graphic string info */ + sizeof(struct dbr_gr_short), /* graphic short info */ + sizeof(struct dbr_gr_float), /* graphic float info */ + sizeof(struct dbr_gr_enum), /* graphic item info */ + + sizeof(struct dbr_gr_char), /* graphic char info */ + sizeof(struct dbr_gr_long), /* graphic long info */ + sizeof(struct dbr_gr_double), /* graphic double info */ + sizeof(struct dbr_sts_string), /* control string info */ + sizeof(struct dbr_ctrl_short), /* control short info */ + + sizeof(struct dbr_ctrl_float), /* control float info */ + sizeof(struct dbr_ctrl_enum), /* control item info */ + sizeof(struct dbr_ctrl_char), /* control char info */ + sizeof(struct dbr_ctrl_long), /* control long info */ + sizeof(struct dbr_ctrl_double), /* control double info */ + + sizeof(dbr_put_ackt_t), /* put ackt */ + sizeof(dbr_put_acks_t), /* put acks */ + sizeof(struct dbr_stsack_string),/* string field with status/ack*/ + sizeof(dbr_string_t), /* string max size */ +}; + +// extern "C" +epicsShareDef const unsigned short dbr_value_size[LAST_BUFFER_TYPE+1] = { + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_short_t), /* short */ + sizeof(dbr_float_t), /* IEEE Float */ + sizeof(dbr_enum_t), /* item number */ + sizeof(dbr_char_t), /* character */ + + sizeof(dbr_long_t), /* long */ + sizeof(dbr_double_t), /* double */ + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_short_t), /* short */ + sizeof(dbr_float_t), /* IEEE Float */ + + sizeof(dbr_enum_t), /* item number */ + sizeof(dbr_char_t), /* character */ + sizeof(dbr_long_t), /* long */ + sizeof(dbr_double_t), /* double */ + sizeof(dbr_string_t), /* string max size */ + + sizeof(dbr_short_t), /* short */ + sizeof(dbr_float_t), /* IEEE Float */ + sizeof(dbr_enum_t), /* item number */ + sizeof(dbr_char_t), /* character */ + sizeof(dbr_long_t), /* long */ + + sizeof(dbr_double_t), /* double */ + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_short_t), /* short */ + sizeof(dbr_float_t), /* IEEE Float */ + sizeof(dbr_enum_t), /* item number */ + + sizeof(dbr_char_t), /* character */ + sizeof(dbr_long_t), /* long */ + sizeof(dbr_double_t), /* double */ + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_short_t), /* short */ + + sizeof(dbr_float_t), /* IEEE Float */ + sizeof(dbr_enum_t), /* item number */ + sizeof(dbr_char_t), /* character */ + sizeof(dbr_long_t), /* long */ + sizeof(dbr_double_t), /* double */ + + sizeof(dbr_ushort_t), /* put_ackt */ + sizeof(dbr_ushort_t), /* put_acks */ + sizeof(dbr_string_t), /* string max size */ + sizeof(dbr_string_t), /* string max size */ +}; + +//extern "C" +epicsShareDef const enum dbr_value_class dbr_value_class[LAST_BUFFER_TYPE+1] = { + dbr_class_string, /* string max size */ + dbr_class_int, /* short */ + dbr_class_float, /* IEEE Float */ + dbr_class_int, /* item number */ + dbr_class_int, /* character */ + dbr_class_int, /* long */ + dbr_class_float, /* double */ + + dbr_class_string, /* string max size */ + dbr_class_int, /* short */ + dbr_class_float, /* IEEE Float */ + dbr_class_int, /* item number */ + dbr_class_int, /* character */ + dbr_class_int, /* long */ + dbr_class_float, /* double */ + + dbr_class_string, /* string max size */ + dbr_class_int, /* short */ + dbr_class_float, /* IEEE Float */ + dbr_class_int, /* item number */ + dbr_class_int, /* character */ + dbr_class_int, /* long */ + dbr_class_float, /* double */ + + dbr_class_string, /* string max size */ + dbr_class_int, /* short */ + dbr_class_float, /* IEEE Float */ + dbr_class_int, /* item number */ + dbr_class_int, /* character */ + dbr_class_int, /* long */ + dbr_class_float, /* double */ + + dbr_class_string, /* string max size */ + dbr_class_int, /* short */ + dbr_class_float, /* IEEE Float */ + dbr_class_int, /* item number */ + dbr_class_int, /* character */ + dbr_class_int, /* long */ + dbr_class_float, /* double */ + dbr_class_int, + dbr_class_int, + dbr_class_string, + dbr_class_string, /* string max size */ +}; + +// extern "C" +epicsShareDef const unsigned short dbr_value_offset[LAST_BUFFER_TYPE+1] = { + 0, /* string */ + 0, /* short */ + 0, /* IEEE Float */ + 0, /* item number */ + 0, /* character */ + 0, /* long */ + 0, /* IEEE double */ + (unsigned short) offsetof(dbr_sts_string,value[0]),/* string field with status */ + (unsigned short) offsetof(dbr_sts_short,value), /* short field with status */ + (unsigned short) offsetof(dbr_sts_float,value), /* float field with status */ + (unsigned short) offsetof(dbr_sts_enum,value), /* item number with status */ + (unsigned short) offsetof(dbr_sts_char,value), /* char field with status */ + (unsigned short) offsetof(dbr_sts_long,value), /* long field with status */ + (unsigned short) offsetof(dbr_sts_double,value), /* double field with time */ + (unsigned short) offsetof(dbr_time_string,value[0] ),/* string field with time */ + (unsigned short) offsetof(dbr_time_short,value), /* short field with time */ + (unsigned short) offsetof(dbr_time_float,value), /* float field with time */ + (unsigned short) offsetof(dbr_time_enum,value), /* item number with time */ + (unsigned short) offsetof(dbr_time_char,value), /* char field with time */ + (unsigned short) offsetof(dbr_time_long,value), /* long field with time */ + (unsigned short) offsetof(dbr_time_double,value), /* double field with time */ + (unsigned short) offsetof(dbr_sts_string,value[0]),/* graphic string info */ + (unsigned short) offsetof(dbr_gr_short,value), /* graphic short info */ + (unsigned short) offsetof(dbr_gr_float,value), /* graphic float info */ + (unsigned short) offsetof(dbr_gr_enum,value), /* graphic item info */ + (unsigned short) offsetof(dbr_gr_char,value), /* graphic char info */ + (unsigned short) offsetof(dbr_gr_long,value), /* graphic long info */ + (unsigned short) offsetof(dbr_gr_double,value), /* graphic double info */ + (unsigned short) offsetof(dbr_sts_string,value[0]),/* control string info */ + (unsigned short) offsetof(dbr_ctrl_short,value), /* control short info */ + (unsigned short) offsetof(dbr_ctrl_float,value), /* control float info */ + (unsigned short) offsetof(dbr_ctrl_enum,value), /* control item info */ + (unsigned short) offsetof(dbr_ctrl_char,value), /* control char info */ + (unsigned short) offsetof(dbr_ctrl_long,value), /* control long info */ + (unsigned short) offsetof(dbr_ctrl_double,value), /* control double info */ + 0, /* put ackt */ + 0, /* put acks */ + (unsigned short) offsetof(dbr_stsack_string,value[0]),/* string field with status */ + 0, /* string */ +}; + +// extern "C" +epicsShareDef const char *dbf_text[LAST_TYPE+3] = { + "TYPENOTCONN", + "DBF_STRING", + "DBF_SHORT", + "DBF_FLOAT", + "DBF_ENUM", + "DBF_CHAR", + "DBF_LONG", + "DBF_DOUBLE", + "DBF_NO_ACCESS" +}; + +// extern "C" +epicsShareDef const char *dbf_text_invalid = "DBF_invalid"; + +// extern "C" +epicsShareDef const short dbf_text_dim = (sizeof dbf_text)/(sizeof (char *)); + +// extern "C" +epicsShareDef const char *dbr_text[LAST_BUFFER_TYPE+1] = { + "DBR_STRING", + "DBR_SHORT", + "DBR_FLOAT", + "DBR_ENUM", + "DBR_CHAR", + "DBR_LONG", + "DBR_DOUBLE", + "DBR_STS_STRING", + "DBR_STS_SHORT", + "DBR_STS_FLOAT", + "DBR_STS_ENUM", + "DBR_STS_CHAR", + "DBR_STS_LONG", + "DBR_STS_DOUBLE", + "DBR_TIME_STRING", + "DBR_TIME_SHORT", + "DBR_TIME_FLOAT", + "DBR_TIME_ENUM", + "DBR_TIME_CHAR", + "DBR_TIME_LONG", + "DBR_TIME_DOUBLE", + "DBR_GR_STRING", + "DBR_GR_SHORT", + "DBR_GR_FLOAT", + "DBR_GR_ENUM", + "DBR_GR_CHAR", + "DBR_GR_LONG", + "DBR_GR_DOUBLE", + "DBR_CTRL_STRING", + "DBR_CTRL_SHORT", + "DBR_CTRL_FLOAT", + "DBR_CTRL_ENUM", + "DBR_CTRL_CHAR", + "DBR_CTRL_LONG", + "DBR_CTRL_DOUBLE", + "DBR_PUT_ACKT", + "DBR_PUT_ACKS", + "DBR_STSACK_STRING", + "DBR_CLASS_NAME" +}; + +// extern "C" +epicsShareDef const char *dbr_text_invalid = "DBR_invalid"; + +// extern "C" +epicsShareDef const short dbr_text_dim = (sizeof dbr_text) / (sizeof (char *)) + 1; diff --git a/modules/ca/src/client/acctst.c b/modules/ca/src/client/acctst.c new file mode 100644 index 000000000..fd18ae4b1 --- /dev/null +++ b/modules/ca/src/client/acctst.c @@ -0,0 +1,3555 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * CA regression test + * Authors: + * Jeff Hill + * Murali Shankar - initial versions of verifyMultithreadSubscr + * Michael Abbott - initial versions of multiSubscrDestroyNoLateCallbackTest + * + */ + +/* + * ANSI + */ +#include +#include +#include +#include +#include + +/* + * EPICS + */ +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" +#include "epicsAssert.h" +#include "epicsMutex.h" +#include "epicsEvent.h" +#include "epicsTime.h" +#include "dbDefs.h" +#include "envDefs.h" +#include "caDiagnostics.h" +#include "cadef.h" +#include "fdmgr.h" +#include "epicsExit.h" + +typedef struct appChan { + char name[64]; + chid channel; + evid subscription; + unsigned char connected; + unsigned char accessRightsHandlerInstalled; + unsigned subscriptionUpdateCount; + unsigned accessUpdateCount; + unsigned connectionUpdateCount; + unsigned getCallbackCount; +} appChan; + +unsigned subscriptionUpdateCount; +unsigned accessUpdateCount; +unsigned connectionUpdateCount; +unsigned getCallbackCount; + +static epicsTimeStamp showProgressBeginTime; + +static const double timeoutToPendIO = 1e20; + +#define verify(exp) ((exp) ? (void)0 : \ + epicsAssert(__FILE__, __LINE__, #exp, epicsAssertAuthor)) + +void showProgressBegin ( const char *pTestName, unsigned interestLevel ) +{ + + if ( interestLevel > 0 ) { + if ( interestLevel > 1 ) { + printf ( "%s ", pTestName ); + epicsTimeGetCurrent ( & showProgressBeginTime ); + } + printf ( "{" ); + } + fflush ( stdout ); +} + +void showProgressEnd ( unsigned interestLevel ) +{ + if ( interestLevel > 0 ) { + printf ( "}" ); + if ( interestLevel > 1 ) { + epicsTimeStamp showProgressEndTime; + double delay; + epicsTimeGetCurrent ( & showProgressEndTime ); + delay = epicsTimeDiffInSeconds ( &showProgressEndTime, &showProgressBeginTime ); + printf ( " %f sec\n", delay ); + } + else { + fflush ( stdout ); + } + } +} + +void showProgress ( unsigned interestLevel ) +{ + if ( interestLevel > 0 ) { + printf ( "." ); + fflush ( stdout ); + } +} + +void nUpdatesTester ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + unsigned *pCtr = (unsigned *) args.usr; + ( *pCtr ) ++; + } + else { + printf ( "subscription update failed for \"%s\" because \"%s\"", + ca_name ( args.chid ), ca_message ( args.status ) ); + } +} + +void monitorSubscriptionFirstUpdateTest ( const char *pName, chid chan, unsigned interestLevel ) +{ + int status; + struct dbr_ctrl_double currentVal; + double delta; + unsigned eventCount = 0u; + unsigned waitCount = 0u; + evid id; + chid chan2; + + showProgressBegin ( "monitorSubscriptionFirstUpdateTest", interestLevel ); + + /* + * verify that the first event arrives (with evid) + * and channel connected + */ + status = ca_add_event ( DBR_FLOAT, + chan, nUpdatesTester, &eventCount, &id ); + SEVCHK ( status, 0 ); + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + while ( eventCount < 1 && waitCount++ < 100 ) { + printf ( "e" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + verify ( eventCount > 0 ); + + /* clear any knowledge of old gets */ + ca_pend_io ( 1e-5 ); + + /* verify that a ca_put() produces an update, but */ + /* this may fail if there is an unusual deadband */ + status = ca_get ( DBR_CTRL_DOUBLE, chan, ¤tVal ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + eventCount = 0u; + waitCount = 0u; + delta = ( currentVal.upper_ctrl_limit - currentVal.lower_ctrl_limit ) / 4.0; + if ( delta <= 0.0 ) { + delta = 100.0; + } + if ( currentVal.value + delta < currentVal.upper_ctrl_limit ) { + currentVal.value += delta; + } + else { + currentVal.value -= delta; + } + status = ca_put ( DBR_DOUBLE, chan, ¤tVal.value ); + SEVCHK ( status, NULL ); + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + while ( eventCount < 1 && waitCount++ < 100 ) { + printf ( "p" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + verify ( eventCount > 0 ); + + status = ca_clear_event ( id ); + SEVCHK (status, 0); + + /* + * verify that the first event arrives (w/o evid) + * and when channel initially disconnected + */ + eventCount = 0u; + waitCount = 0u; + status = ca_search ( pName, &chan2 ); + SEVCHK ( status, 0 ); + status = ca_add_event ( DBR_FLOAT, chan2, + nUpdatesTester, &eventCount, 0 ); + SEVCHK ( status, 0 ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK (status, 0); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( eventCount < 1 && waitCount++ < 100 ) { + printf ( "w" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + verify ( eventCount > 0 ); + + /* verify that a ca_put() produces an update, but */ + /* this may fail if there is an unusual deadband */ + status = ca_get ( DBR_CTRL_DOUBLE, chan2, ¤tVal ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + eventCount = 0u; + waitCount = 0u; + delta = ( currentVal.upper_ctrl_limit - currentVal.lower_ctrl_limit ) / 4.0; + if ( delta <= 0.0 ) { + delta = 100.0; + } + if ( currentVal.value + delta < currentVal.upper_ctrl_limit ) { + currentVal.value += delta; + } + else { + currentVal.value -= delta; + } + status = ca_put ( DBR_DOUBLE, chan2, ¤tVal.value ); + SEVCHK ( status, NULL ); + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( eventCount < 1 && waitCount++ < 100 ) { + printf ( "t" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + verify ( eventCount > 0 ); + + /* clean up */ + status = ca_clear_channel ( chan2 ); + SEVCHK ( status, 0 ); + + showProgressEnd ( interestLevel ); +} + +void ioTesterGet ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + unsigned *pCtr = (unsigned *) args.usr; + ( *pCtr ) ++; + } + else { + printf("get call back failed for \"%s\" because \"%s\"", + ca_name ( args.chid ), ca_message ( args.status ) ); + } +} + +void ioTesterEvent ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + int status; + status = ca_get_callback ( DBR_STS_STRING, args.chid, ioTesterGet, args.usr ); + SEVCHK ( status, 0 ); + } + else { + printf ( "subscription update failed for \"%s\" because \"%s\"", + ca_name ( args.chid ), ca_message ( args.status ) ); + } +} + +void verifyMonitorSubscriptionFlushIO ( chid chan, unsigned interestLevel ) +{ + int status; + unsigned eventCount = 0u; + unsigned waitCount = 0u; + evid id; + + showProgressBegin ( "verifyMonitorSubscriptionFlushIO", interestLevel ); + + /* + * verify that the first event arrives + */ + status = ca_add_event ( DBR_FLOAT, + chan, nUpdatesTester, &eventCount, &id ); + SEVCHK (status, 0); + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( eventCount < 1 && waitCount++ < 100 ) { + printf ( "-" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + verify ( eventCount > 0 ); + status = ca_clear_event ( id ); + SEVCHK (status, 0); + + showProgressEnd ( interestLevel ); +} + +void accessRightsStateChange ( struct access_rights_handler_args args ) +{ + appChan *pChan = (appChan *) ca_puser ( args.chid ); + + verify ( pChan->channel == args.chid ); + verify ( args.ar.read_access == ca_read_access ( args.chid ) ); + verify ( args.ar.write_access == ca_write_access ( args.chid ) ); + accessUpdateCount++; + pChan->accessUpdateCount++; +} + +void getCallbackStateChange ( struct event_handler_args args ) +{ + appChan *pChan = (appChan *) args.usr; + + verify ( pChan->channel == args.chid ); + verify ( pChan->connected ); + if ( args.status != ECA_NORMAL ) { + printf ( "getCallbackStateChange abnormal status was \"%s\"\n", + ca_message ( args.status ) ); + verify ( args.status == ECA_NORMAL ); + } + + getCallbackCount++; + pChan->getCallbackCount++; +} + +void connectionStateChange ( struct connection_handler_args args ) +{ + int status; + + appChan *pChan = (appChan *) ca_puser ( args.chid ); + + verify ( pChan->channel == args.chid ); + + if ( args.op == CA_OP_CONN_UP ) { + if ( pChan->accessRightsHandlerInstalled ) { + verify ( pChan->accessUpdateCount > 0u ); + } + verify ( ! pChan->connected ); + pChan->connected = 1; + status = ca_get_callback ( DBR_STS_STRING, args.chid, getCallbackStateChange, pChan ); + SEVCHK (status, 0); + } + else if ( args.op == CA_OP_CONN_DOWN ) { + verify ( pChan->connected ); + pChan->connected = 0u; + verify ( ! ca_read_access ( args.chid ) ); + verify ( ! ca_write_access ( args.chid ) ); + } + else { + verify ( 0 ); + } + pChan->connectionUpdateCount++; + connectionUpdateCount++; +} + +void subscriptionStateChange ( struct event_handler_args args ) +{ + struct dbr_sts_string * pdbrgs = ( struct dbr_sts_string * ) args.dbr; + appChan *pChan = (appChan *) args.usr; + + verify ( args.status == ECA_NORMAL ); + verify ( pChan->channel == args.chid ); + verify ( pChan->connected ); + verify ( args.type == DBR_STS_STRING ); + verify ( strlen ( pdbrgs->value ) <= MAX_STRING_SIZE ); + pChan->subscriptionUpdateCount++; + subscriptionUpdateCount++; +} + +void noopSubscriptionStateChange ( struct event_handler_args args ) +{ + if ( args.status != ECA_NORMAL ) { + printf ( "noopSubscriptionStateChange: subscription update failed for \"%s\" because \"%s\"", + ca_name ( args.chid ), ca_message ( args.status ) ); + } +} + +/* + * verifyConnectionHandlerConnect () + * + * 1) verify that connection handler runs during connect + * + * 2) verify that access rights handler runs during connect + * + * 3) verify that get call back runs from connection handler + * (and that they are not required to flush in the connection handler) + * + * 4) verify that first event callback arrives after connect + * + * 5) verify subscription can be cleared before channel is cleared + * + * 6) verify subscription can be cleared by clearing the channel + * + * 7) verify that a nill access rights handler can be installed + */ +void verifyConnectionHandlerConnect ( appChan *pChans, unsigned chanCount, + unsigned repetitionCount, unsigned interestLevel ) +{ + int status; + unsigned i, j; + + showProgressBegin ( "verifyConnectionHandlerConnect", interestLevel ); + + for ( i = 0; i < repetitionCount; i++ ) { + subscriptionUpdateCount = 0u; + accessUpdateCount = 0u; + connectionUpdateCount = 0u; + getCallbackCount = 0u; + + for ( j = 0u; j < chanCount; j++ ) { + + pChans[j].subscriptionUpdateCount = 0u; + pChans[j].accessUpdateCount = 0u; + pChans[j].accessRightsHandlerInstalled = 0; + pChans[j].connectionUpdateCount = 0u; + pChans[j].getCallbackCount = 0u; + pChans[j].connected = 0u; + + status = ca_search_and_connect ( pChans[j].name, + &pChans[j].channel, connectionStateChange, &pChans[j] ); + SEVCHK ( status, NULL ); + + status = ca_replace_access_rights_event ( + pChans[j].channel, accessRightsStateChange ); + SEVCHK ( status, NULL ); + pChans[j].accessRightsHandlerInstalled = 1; + + status = ca_add_event ( DBR_STS_STRING, pChans[j].channel, + subscriptionStateChange, &pChans[j], &pChans[j].subscription ); + SEVCHK ( status, NULL ); + + verify ( ca_test_io () == ECA_IODONE ); + } + + ca_flush_io (); + + showProgress ( interestLevel ); + + while ( connectionUpdateCount < chanCount || + getCallbackCount < chanCount ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + + for ( j = 0u; j < chanCount; j++ ) { + verify ( pChans[j].getCallbackCount == 1u); + verify ( pChans[j].connectionUpdateCount > 0 ); + if ( pChans[j].connectionUpdateCount > 1u ) { + printf ("Unusual connection activity count = %u on channel %s?\n", + pChans[j].connectionUpdateCount, pChans[j].name ); + } + verify ( pChans[j].accessUpdateCount > 0 ); + if ( pChans[j].accessUpdateCount > 1u ) { + printf ("Unusual access rights activity count = %u on channel %s?\n", + pChans[j].connectionUpdateCount, pChans[j].name ); + } + } + + ca_self_test (); + + showProgress ( interestLevel ); + + for ( j = 0u; j < chanCount; j += 2 ) { + status = ca_clear_event ( pChans[j].subscription ); + SEVCHK ( status, NULL ); + } + + ca_self_test (); + + showProgress ( interestLevel ); + + for ( j = 0u; j < chanCount; j++ ) { + status = ca_replace_access_rights_event ( + pChans[j].channel, 0 ); + SEVCHK ( status, NULL ); + } + + for ( j = 0u; j < chanCount; j++ ) { + status = ca_clear_channel ( pChans[j].channel ); + SEVCHK ( status, NULL ); + } + + ca_self_test (); + + showProgress ( interestLevel ); + + } + showProgressEnd ( interestLevel ); +} + +/* + * verifyBlockingConnect () + * + * 1) verify that we dont print a disconnect message when + * we delete the last channel + * + * 2) verify that we delete the connection to the IOC + * when the last channel is deleted. + * + * 3) verify channel connection state variables + * + * 4) verify ca_test_io () and ca_pend_io () work with + * channels w/o connection handlers + * + * 5) verify that the pending IO count is properly + * maintained when we are add/removing a connection + * handler + * + * 6) verify that the pending IO count goes to zero + * if the channel is deleted before it connects. + */ +void verifyBlockingConnect ( appChan *pChans, unsigned chanCount, + unsigned repetitionCount, unsigned interestLevel ) +{ + int status; + unsigned i, j; + unsigned connections; + unsigned backgroundConnCount = ca_get_ioc_connection_count (); + + showProgressBegin ( "verifyBlockingConnect", interestLevel ); + + i = 0; + while ( backgroundConnCount > 1u ) { + backgroundConnCount = ca_get_ioc_connection_count (); + verify ( i++ < 10 ); + printf ( "Z" ); + fflush ( stdout ); + epicsThreadSleep ( 1.0 ); + } + + for ( i = 0; i < repetitionCount; i++ ) { + + for ( j = 0u; j < chanCount; j++ ) { + pChans[j].subscriptionUpdateCount = 0u; + pChans[j].accessUpdateCount = 0u; + pChans[j].accessRightsHandlerInstalled = 0; + pChans[j].connectionUpdateCount = 0u; + pChans[j].getCallbackCount = 0u; + pChans[j].connected = 0u; + + status = ca_search_and_connect ( pChans[j].name, &pChans[j].channel, NULL, &pChans[j] ); + SEVCHK ( status, NULL ); + + if ( ca_state ( pChans[j].channel ) == cs_conn ) { + verify ( VALID_DB_REQ ( ca_field_type ( pChans[j].channel ) ) ); + } + else { + verify ( INVALID_DB_REQ ( ca_field_type ( pChans[j].channel ) ) ); + verify ( ca_test_io () == ECA_IOINPROGRESS ); + } + + status = ca_replace_access_rights_event ( + pChans[j].channel, accessRightsStateChange ); + SEVCHK ( status, NULL ); + pChans[j].accessRightsHandlerInstalled = 1; + } + + showProgress ( interestLevel ); + + for ( j = 0u; j < chanCount; j += 2 ) { + status = ca_change_connection_event ( pChans[j].channel, connectionStateChange ); + SEVCHK ( status, NULL ); + } + + for ( j = 0u; j < chanCount; j += 2 ) { + status = ca_change_connection_event ( pChans[j].channel, NULL ); + SEVCHK ( status, NULL ); + } + + for ( j = 0u; j < chanCount; j += 2 ) { + status = ca_change_connection_event ( pChans[j].channel, connectionStateChange ); + SEVCHK ( status, NULL ); + } + + for ( j = 0u; j < chanCount; j += 2 ) { + status = ca_change_connection_event ( pChans[j].channel, NULL ); + SEVCHK ( status, NULL ); + } + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + + ca_self_test (); + + showProgress ( interestLevel ); + + verify ( ca_test_io () == ECA_IODONE ); + + connections = ca_get_ioc_connection_count (); + verify ( connections == backgroundConnCount ); + + for ( j = 0u; j < chanCount; j++ ) { + verify ( VALID_DB_REQ ( ca_field_type ( pChans[j].channel ) ) ); + verify ( ca_state ( pChans[j].channel ) == cs_conn ); + SEVCHK ( ca_clear_channel ( pChans[j].channel ), NULL ); + } + + ca_self_test (); + + ca_flush_io (); + + showProgress ( interestLevel ); + + /* + * verify that connections to IOC's that are + * not in use are dropped + */ + if ( ca_get_ioc_connection_count () != backgroundConnCount ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); + j=0; + while ( ca_get_ioc_connection_count () != backgroundConnCount ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + verify ( ++j < 100 ); + } + } + showProgress ( interestLevel ); + } + + for ( j = 0u; j < chanCount; j++ ) { + status = ca_search ( pChans[j].name, &pChans[j].channel ); + SEVCHK ( status, NULL ); + } + + for ( j = 0u; j < chanCount; j++ ) { + status = ca_clear_channel ( pChans[j].channel ); + SEVCHK ( status, NULL ); + } + + verify ( ca_test_io () == ECA_IODONE ); + + /* + * verify ca_pend_io() does not see old search requests + * (that did not specify a connection handler) + */ + status = ca_search_and_connect ( pChans[0].name, &pChans[0].channel, NULL, NULL); + SEVCHK ( status, NULL ); + + if ( ca_state ( pChans[0].channel ) == cs_never_conn ) { + /* force an early timeout */ + status = ca_pend_io ( 1e-16 ); + if ( status == ECA_TIMEOUT ) { + /* + * we end up here if the channel isnt on the same host + */ + epicsThreadSleep ( 0.1 ); + ca_poll (); + if ( ca_state( pChans[0].channel ) != cs_conn ) { + while ( ca_state ( pChans[0].channel ) != cs_conn ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + } + + status = ca_search_and_connect ( pChans[1].name, &pChans[1].channel, NULL, NULL ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( 1e-16 ); + if ( status != ECA_TIMEOUT ) { + verify ( ca_state ( pChans[1].channel ) == cs_conn ); + } + status = ca_clear_channel ( pChans[1].channel ); + SEVCHK ( status, NULL ); + } + else { + verify ( ca_state( pChans[0].channel ) == cs_conn ); + } + } + status = ca_clear_channel( pChans[0].channel ); + SEVCHK ( status, NULL ); + + ca_self_test (); + + showProgressEnd ( interestLevel ); +} + +/* + * 1) verify that use of NULL evid does not cause problems + * 2) verify clear before connect + */ +void verifyClear ( appChan *pChans, unsigned interestLevel ) +{ + int status; + + showProgressBegin ( "verifyClear", interestLevel ); + + /* + * verify channel clear before connect + */ + status = ca_search ( pChans[0].name, &pChans[0].channel ); + SEVCHK ( status, NULL ); + + status = ca_clear_channel ( pChans[0].channel ); + SEVCHK ( status, NULL ); + + /* + * verify subscription clear before connect + * and verify that NULL evid does not cause failure + */ + status = ca_search ( pChans[0].name, &pChans[0].channel ); + SEVCHK ( status, NULL ); + + SEVCHK ( status, NULL ); + status = ca_add_event ( DBR_GR_DOUBLE, + pChans[0].channel, noopSubscriptionStateChange, NULL, NULL ); + SEVCHK ( status, NULL ); + + status = ca_clear_channel ( pChans[0].channel ); + SEVCHK ( status, NULL ); + showProgressEnd ( interestLevel ); +} + +/* + * grEnumTest + */ +void grEnumTest ( chid chan, unsigned interestLevel ) +{ + struct dbr_gr_enum ge; + unsigned count; + int status; + unsigned i; + + showProgressBegin ( "grEnumTest", interestLevel ); + + ge.no_str = -1; + + status = ca_get (DBR_GR_ENUM, chan, &ge); + SEVCHK (status, "DBR_GR_ENUM ca_get()"); + + status = ca_pend_io (timeoutToPendIO); + verify (status == ECA_NORMAL); + + verify ( ge.no_str >= 0 && ge.no_str < NELEMENTS(ge.strs) ); + if ( ge.no_str > 0 ) { + printf ("Enum state str = {"); + count = (unsigned) ge.no_str; + for (i=0; i epsilon ) { + printf ( "float test failed val written %f\n", fval ); + printf ( "float test failed val read %f\n", fretval ); + verify (0); + } + fval += increment; + } +} + +/* + * doubleTest () + */ +void doubleTest ( chid chan, dbr_double_t beginValue, + dbr_double_t increment, dbr_double_t epsilon, + unsigned iterations) +{ + unsigned i; + dbr_double_t fval; + dbr_double_t fretval; + int status; + + fval = beginValue; + for ( i = 0; i < iterations; i++ ) { + fretval = DBL_MAX; + status = ca_put ( DBR_DOUBLE, chan, &fval ); + SEVCHK ( status, NULL ); + status = ca_get ( DBR_DOUBLE, chan, &fretval ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + if ( fabs ( fval - fretval ) > epsilon ) { + printf ( "double test failed val written %f\n", fval ); + printf ( "double test failed val read %f\n", fretval ); + verify ( 0 ); + } + fval += increment; + } +} + +/* + * Verify that we can write and then read back + * the same analog value + */ +void verifyAnalogIO ( chid chan, int dataType, double min, double max, + int minExp, int maxExp, double epsilon, unsigned interestLevel ) +{ + int i; + double incr; + double epsil; + double base; + unsigned iter; + + + if ( ! ca_write_access ( chan ) ) { + printf ("skipped analog test - no write access\n"); + return; + } + + if ( dbr_value_class[ca_field_type ( chan )] != dbr_class_float ) { + printf ("skipped analog test - not an analog type\n"); + return; + } + + showProgressBegin ( "verifyAnalogIO", interestLevel ); + + epsil = epsilon * 4.0; + base = min; + for ( i = minExp; i <= maxExp; i += maxExp / 10 ) { + incr = ldexp ( 0.5, i ); + if ( fabs (incr) > max /10.0 ) { + iter = ( unsigned ) ( max / fabs (incr) ); + } + else { + iter = 10u; + } + if ( dataType == DBR_FLOAT ) { + floatTest ( chan, (dbr_float_t) base, (dbr_float_t) incr, + (dbr_float_t) epsil, iter ); + } + else if (dataType == DBR_DOUBLE ) { + doubleTest ( chan, (dbr_double_t) base, (dbr_double_t) incr, + (dbr_double_t) epsil, iter ); + } + else { + verify ( 0 ); + } + } + base = max; + for ( i = minExp; i <= maxExp; i += maxExp / 10 ) { + incr = - ldexp ( 0.5, i ); + if ( fabs (incr) > max / 10.0 ) { + iter = (unsigned) ( max / fabs (incr) ); + } + else { + iter = 10u; + } + if ( dataType == DBR_FLOAT ) { + floatTest ( chan, (dbr_float_t) base, (dbr_float_t) incr, + (dbr_float_t) epsil, iter ); + } + else if (dataType == DBR_DOUBLE ) { + doubleTest ( chan, (dbr_double_t) base, (dbr_double_t) incr, + (dbr_double_t) epsil, iter ); + } + else { + verify ( 0 ); + } + } + base = - max; + for ( i = minExp; i <= maxExp; i += maxExp / 10 ) { + incr = ldexp ( 0.5, i ); + if ( fabs (incr) > max / 10.0 ) { + iter = (unsigned) ( max / fabs ( incr ) ); + } + else { + iter = 10l; + } + if ( dataType == DBR_FLOAT ) { + floatTest ( chan, (dbr_float_t) base, (dbr_float_t) incr, + (dbr_float_t) epsil, iter ); + } + else if (dataType == DBR_DOUBLE ) { + doubleTest ( chan, (dbr_double_t) base, (dbr_double_t) incr, + (dbr_double_t) epsil, iter ); + } + else { + verify ( 0 ); + } + } + showProgressEnd ( interestLevel ); +} + +/* + * Verify that we can write and then read back + * the same DBR_LONG value + */ +void verifyLongIO ( chid chan, unsigned interestLevel ) +{ + int status; + + dbr_long_t iter, rdbk, incr; + struct dbr_ctrl_long cl; + + if ( ca_write_access ( chan ) ) { + return; + } + + status = ca_get ( DBR_CTRL_LONG, chan, &cl ); + SEVCHK ( status, "control long fetch failed\n" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "control long pend failed\n" ); + + incr = ( cl.upper_ctrl_limit - cl.lower_ctrl_limit ); + if ( incr >= 1 ) { + showProgressBegin ( "verifyLongIO", interestLevel ); + incr /= 1000; + if ( incr == 0 ) { + incr = 1; + } + for ( iter = cl.lower_ctrl_limit; + iter <= cl.upper_ctrl_limit; iter+=incr ) { + + ca_put ( DBR_LONG, chan, &iter ); + ca_get ( DBR_LONG, chan, &rdbk ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "get pend failed\n" ); + verify ( iter == rdbk ); + } + showProgressEnd ( interestLevel ); + } + else { + printf ( "strange limits configured for channel \"%s\"\n", ca_name (chan) ); + } +} + +/* + * Verify that we can write and then read back + * the same DBR_SHORT value + */ +void verifyShortIO ( chid chan, unsigned interestLevel ) +{ + int status; + + dbr_short_t iter, rdbk, incr; + struct dbr_ctrl_short cl; + + if ( ca_write_access ( chan ) ) { + return; + } + + status = ca_get ( DBR_CTRL_SHORT, chan, &cl ); + SEVCHK ( status, "control short fetch failed\n" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "control short pend failed\n" ); + + incr = (dbr_short_t) ( cl.upper_ctrl_limit - cl.lower_ctrl_limit ); + if ( incr >= 1 ) { + showProgressBegin ( "verifyShortIO", interestLevel ); + + incr /= 1000; + if ( incr == 0 ) { + incr = 1; + } + for ( iter = (dbr_short_t) cl.lower_ctrl_limit; + iter <= (dbr_short_t) cl.upper_ctrl_limit; + iter = (dbr_short_t) (iter + incr) ) { + + ca_put ( DBR_SHORT, chan, &iter ); + ca_get ( DBR_SHORT, chan, &rdbk ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "get pend failed\n" ); + verify ( iter == rdbk ); + } + showProgressEnd ( interestLevel ); + } + else { + printf ( "Strange limits configured for channel \"%s\"\n", ca_name (chan) ); + } +} + +void verifyHighThroughputRead ( chid chan, unsigned interestLevel ) +{ + int status; + unsigned i; + + /* + * verify we dont jam up on many uninterrupted + * solicitations + */ + if ( ca_read_access (chan) ) { + dbr_float_t temp; + showProgressBegin ( "verifyHighThroughputRead", interestLevel ); + for ( i=0; i<10000; i++ ) { + status = ca_get ( DBR_FLOAT, chan, &temp ); + SEVCHK ( status ,NULL ); + } + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + showProgressEnd ( interestLevel ); + } + else { + printf ( "Skipped highthroughput read test - no read access\n" ); + } +} + +void verifyHighThroughputWrite ( chid chan, unsigned interestLevel ) +{ + int status; + unsigned i; + + if (ca_write_access ( chan ) ) { + showProgressBegin ( "verifyHighThroughputWrite", interestLevel ); + for ( i=0; i<10000; i++ ) { + dbr_double_t fval = 3.3; + status = ca_put ( DBR_DOUBLE, chan, &fval ); + SEVCHK ( status, NULL ); + } + SEVCHK ( ca_pend_io (timeoutToPendIO), NULL ); + showProgressEnd ( interestLevel ); + } + else{ + printf("Skipped multiple put test - no write access\n"); + } +} + +/* + * verify we dont jam up on many uninterrupted + * get callback requests + */ +void verifyHighThroughputReadCallback ( chid chan, unsigned interestLevel ) +{ + unsigned i; + int status; + + if ( ca_read_access ( chan ) ) { + unsigned count = 0u; + showProgressBegin ( "verifyHighThroughputReadCallback", interestLevel ); + for ( i=0; i<10000; i++ ) { + status = ca_array_get_callback ( + DBR_FLOAT, 1, chan, nUpdatesTester, &count ); + SEVCHK ( status, NULL ); + } + SEVCHK ( ca_flush_io (), NULL ); + while ( count < 10000u ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + showProgressEnd ( interestLevel ); + } + else { + printf ( "Skipped multiple get cb test - no read access\n" ); + } +} + +/* + * verify we dont jam up on many uninterrupted + * put callback request + */ +void verifyHighThroughputWriteCallback ( chid chan, unsigned interestLevel ) +{ + unsigned i; + int status; + + if ( ca_write_access (chan) && ca_v42_ok (chan) ) { + unsigned count = 0u; + dbr_double_t dval; + showProgressBegin ( "verifyHighThroughputWriteCallback", interestLevel ); + for ( i=0; i<10000; i++ ) { + dval = i + 1; + status = ca_array_put_callback ( + DBR_DOUBLE, 1, chan, &dval, + nUpdatesTester, &count ); + SEVCHK ( status, NULL ); + } + SEVCHK ( ca_flush_io (), NULL ); + dval = 0.0; + status = ca_get ( DBR_DOUBLE, chan, &dval ); + SEVCHK ( status, + "verifyHighThroughputWriteCallback, verification get" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, + "verifyHighThroughputWriteCallback, verification get pend" ); + verify ( dval == i ); + showProgressEnd ( interestLevel ); + } + else { + printf ( "Skipped multiple put cb test - no write access\n" ); + } +} + +void verifyBadString ( chid chan, unsigned interestLevel ) +{ + int status; + + /* + * verify that we detect that a large string has been written + */ + if ( ca_write_access (chan) ) { + dbr_string_t stimStr; + dbr_string_t respStr; + showProgressBegin ( "verifyBadString", interestLevel ); + memset (stimStr, 'a', sizeof (stimStr) ); + status = ca_array_put ( DBR_STRING, 1u, chan, stimStr ); + verify ( status != ECA_NORMAL ); + sprintf ( stimStr, "%u", 8u ); + status = ca_array_put ( DBR_STRING, 1u, chan, stimStr ); + verify ( status == ECA_NORMAL ); + status = ca_array_get ( DBR_STRING, 1u, chan, respStr ); + verify ( status == ECA_NORMAL ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + if ( strcmp ( stimStr, respStr ) ) { + printf ( + "Test fails if stim \"%s\" isnt roughly equiv to resp \"%s\"\n", + stimStr, respStr); + } + showProgressEnd ( interestLevel ); + } + else { + printf ( "Skipped bad string test - no write access\n" ); + } +} + +/* + * multiple_sg_requests() + */ +void multiple_sg_requests ( chid chix, CA_SYNC_GID gid ) +{ + int status; + unsigned i; + static dbr_float_t fvalput = 3.3F; + static dbr_float_t fvalget; + + for ( i=0; i < 1000; i++ ) { + if ( ca_write_access (chix) ){ + status = ca_sg_array_put ( gid, DBR_FLOAT, 1, + chix, &fvalput); + SEVCHK ( status, NULL ); + } + + if ( ca_read_access (chix) ) { + status = ca_sg_array_get ( gid, DBR_FLOAT, 1, + chix, &fvalget); + SEVCHK ( status, NULL ); + } + } +} + +/* + * test_sync_groups() + */ +void test_sync_groups ( chid chan, unsigned interestLevel ) +{ + int status; + CA_SYNC_GID gid1=0; + CA_SYNC_GID gid2=0; + + if ( ! ca_v42_ok ( chan ) ) { + printf ( "skipping sync group test - server is on wrong version\n" ); + } + + showProgressBegin ( "test_sync_groups", interestLevel ); + + status = ca_sg_create ( &gid1 ); + SEVCHK ( status, NULL ); + + multiple_sg_requests ( chan, gid1 ); + status = ca_sg_reset ( gid1 ); + SEVCHK ( status, NULL ); + + status = ca_sg_create ( &gid2 ); + SEVCHK ( status, NULL ); + + multiple_sg_requests ( chan, gid2 ); + multiple_sg_requests ( chan, gid1 ); + status = ca_sg_test ( gid2 ); + SEVCHK ( status, "SYNC GRP2" ); + status = ca_sg_test ( gid1 ); + SEVCHK ( status, "SYNC GRP1" ); + status = ca_sg_block ( gid1, 500.0 ); + SEVCHK ( status, "SYNC GRP1" ); + status = ca_sg_block ( gid2, 500.0 ); + SEVCHK ( status, "SYNC GRP2" ); + + status = ca_sg_delete ( gid2 ); + SEVCHK (status, NULL); + status = ca_sg_create ( &gid2 ); + SEVCHK (status, NULL); + + multiple_sg_requests ( chan, gid1 ); + multiple_sg_requests ( chan, gid2 ); + status = ca_sg_block ( gid1, 500.0 ); + SEVCHK ( status, "SYNC GRP1" ); + status = ca_sg_block ( gid2, 500.0 ); + SEVCHK ( status, "SYNC GRP2" ); + status = ca_sg_delete ( gid1 ); + SEVCHK ( status, NULL ); + status = ca_sg_delete ( gid2 ); + SEVCHK ( status, NULL ); + + showProgressEnd ( interestLevel ); +} + +#define multiSubscrDestroyNoLateCallbackEventCount 500 + +struct MultiSubscrDestroyNoLateCallbackEventData { + evid m_id; + size_t m_nCallback; + int m_callbackIsOk; + struct MultiSubscrDestroyNoLateCallbackTestData * m_pTestData; +}; + +struct MultiSubscrDestroyNoLateCallbackTestData { + const char * m_pChanName; + chid m_chan; + epicsMutexId m_mutex; + epicsEventId m_testDoneEvent; + unsigned m_interestLevel; + struct MultiSubscrDestroyNoLateCallbackEventData + m_eventData [multiSubscrDestroyNoLateCallbackEventCount]; +}; + +static void noLateCallbackDetect ( struct event_handler_args args ) +{ + int callbackIsOk; + struct MultiSubscrDestroyNoLateCallbackEventData * const pEventData = args.usr; + epicsMutexLockStatus lockStatus = epicsMutexLock ( pEventData->m_pTestData->m_mutex ); + callbackIsOk = pEventData->m_callbackIsOk; + pEventData->m_nCallback++; + epicsMutexUnlock ( pEventData->m_pTestData->m_mutex ); + verify ( lockStatus == epicsMutexLockOK ); + verify ( callbackIsOk ); +} + +static void multiSubscrDestroyNoLateCallbackThread ( void * pParm ) +{ + struct MultiSubscrDestroyNoLateCallbackTestData * const pTestData = + ( struct MultiSubscrDestroyNoLateCallbackTestData * ) pParm; + unsigned i, j; + int status; + + status = ca_context_create ( ca_enable_preemptive_callback ); + verify ( status == ECA_NORMAL ); + + status = ca_create_channel ( pTestData->m_pChanName, 0, 0, + CA_PRIORITY_DEFAULT, &pTestData->m_chan ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "multiSubscrDestroyLateNoCallbackTest: channel connect failed" ); + verify ( status == ECA_NORMAL ); + + /* + * create a set of subscriptions + */ + for ( i=0; i < 10000; i++ ) { + unsigned int priorityOfTestThread; + for ( j=0; j < multiSubscrDestroyNoLateCallbackEventCount; j++ ) { + epicsMutexLockStatus lockStatus = epicsMutexLock ( pTestData->m_mutex ); + verify ( lockStatus == epicsMutexLockOK ); + pTestData->m_eventData[j].m_nCallback = 0; + pTestData->m_eventData[j].m_callbackIsOk = TRUE; + pTestData->m_eventData[j].m_pTestData = pTestData; + epicsMutexUnlock ( pTestData->m_mutex ); + SEVCHK ( ca_add_event ( DBR_GR_FLOAT, pTestData->m_chan, noLateCallbackDetect, + &pTestData->m_eventData[j], &pTestData->m_eventData[j].m_id ) , NULL ); + } + SEVCHK ( ca_flush_io(), NULL ); + + /* + * raise the priority of the current thread hoping to improve our + * likelyhood of detecting a bug + */ + priorityOfTestThread = epicsThreadGetPrioritySelf (); + epicsThreadSetPriority ( epicsThreadGetIdSelf(), epicsThreadPriorityHigh ); + + + /* + * wait for the first subscription update to arrive + */ + { + epicsMutexLockStatus lockStatus = epicsMutexLock ( pTestData->m_mutex ); + verify ( lockStatus == epicsMutexLockOK ); + while ( pTestData->m_eventData[0].m_nCallback == 0 ) { + epicsMutexUnlock ( pTestData->m_mutex ); + epicsThreadSleep ( 50e-6 ); + lockStatus = epicsMutexLock ( pTestData->m_mutex ); + verify ( lockStatus == epicsMutexLockOK ); + } + epicsMutexUnlock ( pTestData->m_mutex ); + } + /* + * try to destroy all of the subscriptions at precisely the same time that + * their first callbacks are running + */ + for ( j=0; j < multiSubscrDestroyNoLateCallbackEventCount; j++ ) { + epicsMutexLockStatus lockStatus; + SEVCHK ( ca_clear_event ( pTestData->m_eventData[j].m_id ) , NULL ); + lockStatus = epicsMutexLock ( pTestData->m_mutex ); + verify ( lockStatus == epicsMutexLockOK ); + pTestData->m_eventData[j].m_callbackIsOk = FALSE; + epicsMutexUnlock ( pTestData->m_mutex ); + } + /* + * return to the original priority + */ + epicsThreadSetPriority ( epicsThreadGetIdSelf(), priorityOfTestThread ); + + if ( i % 1000 == 0 ) { + showProgress ( pTestData->m_interestLevel ); + } + } + + SEVCHK ( ca_clear_channel ( pTestData->m_chan ), NULL ); + + ca_context_destroy (); + + epicsEventMustTrigger ( pTestData->m_testDoneEvent ); +} + +/* + * verify that, in a preemtive callback mode client, a subscription callback never + * comes after the subscription is destroyed + */ +static void multiSubscrDestroyNoLateCallbackTest ( const char *pName, unsigned interestLevel ) +{ + struct MultiSubscrDestroyNoLateCallbackTestData * pTestData; + + showProgressBegin ( "multiSubscrDestroyNoLateCallbackTest", interestLevel ); + + pTestData = calloc ( 1u, sizeof ( struct MultiSubscrDestroyNoLateCallbackTestData ) ); + verify ( pTestData ); + pTestData->m_mutex = epicsMutexMustCreate (); + pTestData->m_testDoneEvent = epicsEventMustCreate ( epicsEventEmpty ); + pTestData->m_pChanName = pName; + pTestData->m_interestLevel = interestLevel; + epicsThreadMustCreate ( + "multiSubscrDestroyNoLateCallbackTest", + epicsThreadPriorityLow, + epicsThreadGetStackSize ( epicsThreadStackMedium ), + multiSubscrDestroyNoLateCallbackThread, + pTestData ); + + /* + * wait for test to complete + */ + epicsEventMustWait ( pTestData->m_testDoneEvent ); + + /* + * cleanup + */ + epicsMutexDestroy ( pTestData->m_mutex ); + epicsEventDestroy ( pTestData->m_testDoneEvent ); + free ( pTestData ); + + showProgressEnd ( interestLevel ); +} + +/* + * multiSubscriptionDeleteTest + * + * 1) verify we can add many monitors at once + * 2) verify that under heavy load the last monitor + * returned is the last modification sent + * 3) attempt to delete monitors while many monitors + * are running + */ +void multiSubscriptionDeleteTest ( chid chan, unsigned interestLevel ) +{ + unsigned count = 0u; + evid mid[1000]; + dbr_float_t temp, getResp; + unsigned i; + + showProgressBegin ( "multiSubscriptionDeleteTest", interestLevel ); + + for ( i=0; i < NELEMENTS (mid); i++ ) { + SEVCHK ( ca_add_event ( DBR_GR_FLOAT, chan, noopSubscriptionStateChange, + &count, &mid[i]) , NULL ); + } + + /* + * force all of the monitors subscription requests to + * complete + * + * NOTE: this hopefully demonstrates that when the + * server is very busy with monitors the client + * is still able to punch through with a request. + */ + SEVCHK ( ca_get ( DBR_FLOAT,chan,&getResp ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + + showProgress ( interestLevel ); + + /* + * attempt to generate heavy event traffic before initiating + * the monitor delete + */ + if ( ca_write_access (chan) ) { + for ( i=0; i < NELEMENTS (mid); i++ ) { + temp = (float) i; + SEVCHK ( ca_put (DBR_FLOAT, chan, &temp), NULL); + } + } + + showProgress ( interestLevel ); + + /* + * without pausing begin deleting the event subscriptions + * while the queue is full + * + * continue attempting to generate heavy event traffic + * while deleting subscriptions with the hope that we will + * deleting an event at the instant that its callback is + * occurring + */ + for ( i=0; i < NELEMENTS (mid); i++ ) { + if ( ca_write_access (chan) ) { + temp = (float) i; + SEVCHK ( ca_put (DBR_FLOAT, chan, &temp), NULL); + } + SEVCHK ( ca_clear_event ( mid[i]), NULL ); + } + + showProgress ( interestLevel ); + + /* + * force all of the clear event requests to complete + */ + SEVCHK ( ca_get (DBR_FLOAT,chan,&temp), NULL ); + SEVCHK ( ca_pend_io (timeoutToPendIO), NULL ); + + showProgressEnd ( interestLevel ); +} + + +/* + * singleSubscriptionDeleteTest + * + * verify that we dont fail when we repeatedly create + * and delete only one subscription with a high level of + * traffic on it + */ +void singleSubscriptionDeleteTest ( chid chan, unsigned interestLevel ) +{ + unsigned count = 0u; + evid sid; + dbr_float_t temp, getResp; + unsigned i; + + showProgressBegin ( "singleSubscriptionDeleteTest", interestLevel ); + + for ( i=0; i < 1000; i++ ){ + count = 0u; + SEVCHK ( ca_add_event ( DBR_GR_FLOAT, chan, noopSubscriptionStateChange, + &count, &sid) , NULL ); + + if ( i % 100 == 0 ) { + showProgress ( interestLevel ); + } + + /* + * force the subscription request to complete + */ + SEVCHK ( ca_get ( DBR_FLOAT, chan, &getResp ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + + /* + * attempt to generate heavy event traffic before initiating + * the monitor delete + * + * try to interrupt the recv thread while it is processing + * incoming subscription updates + */ + if ( ca_write_access (chan) ) { + unsigned j = 0; + while ( j < i ) { + temp = (float) j++; + SEVCHK ( ca_put (DBR_FLOAT, chan, &temp), + "singleSubscriptionDeleteTest - one of multiple puts" ); + } + ca_flush_io (); + } + + SEVCHK ( ca_clear_event ( sid ), NULL ); + } + + showProgressEnd ( interestLevel ); +} + +/* + * channelClearWithEventTrafficTest + * + * verify that we can delete a channel that has subcriptions + * attached with heavy update traffic + */ +void channelClearWithEventTrafficTest ( const char *pName, unsigned interestLevel ) +{ + unsigned count = 0u; + evid sid; + dbr_float_t temp, getResp; + unsigned i; + + showProgressBegin ( "channelClearWithEventTrafficTest", interestLevel ); + + for ( i=0; i < 1000; i++ ) { + chid chan; + + int status = ca_create_channel ( pName, 0, 0, + CA_PRIORITY_DEFAULT, &chan ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "channelClearWithEventTrafficTest: channel connect failed" ); + verify ( status == ECA_NORMAL ); + + count = 0u; + SEVCHK ( ca_add_event ( DBR_GR_FLOAT, chan, noopSubscriptionStateChange, + &count, &sid ) , NULL ); + + /* + * force the subscription request to complete + */ + SEVCHK ( ca_get ( DBR_FLOAT, chan, &getResp ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + + /* + * attempt to generate heavy event traffic before initiating + * the channel delete + * + * try to interrupt the recv thread while it is processing + * incoming subscription updates + */ + if ( ca_write_access (chan) ) { + unsigned j = 0; + while ( j < i ) { + temp = (float) j++; + SEVCHK ( ca_put (DBR_FLOAT, chan, &temp), NULL); + } + ca_flush_io (); + epicsThreadSleep ( 0.001 ); + } + + SEVCHK ( ca_clear_channel ( chan ), NULL ); + } + + showProgressEnd ( interestLevel ); +} + + + +evid globalEventID; + +void selfDeleteEvent ( struct event_handler_args args ) +{ + int status; + status = ca_clear_event ( globalEventID ); + verify ( status == ECA_NORMAL ); +} + +void eventClearTest ( chid chan ) +{ + int status; + evid monix1, monix2, monix3; + + status = ca_add_event ( DBR_FLOAT, chan, noopSubscriptionStateChange, + NULL, &monix1 ); + SEVCHK ( status, NULL ); + + status = ca_clear_event ( monix1 ); + SEVCHK ( status, NULL ); + + status = ca_add_event ( DBR_FLOAT, chan, noopSubscriptionStateChange, + NULL, &monix1 ); + SEVCHK ( status, NULL ); + + status = ca_add_event ( DBR_FLOAT, chan, noopSubscriptionStateChange, + NULL, &monix2); + SEVCHK (status, NULL); + + status = ca_clear_event ( monix2 ); + SEVCHK ( status, NULL); + + status = ca_add_event ( DBR_FLOAT, chan, noopSubscriptionStateChange, + NULL, &monix2); + SEVCHK ( status, NULL ); + + status = ca_add_event ( DBR_FLOAT, chan, noopSubscriptionStateChange, + NULL, &monix3); + SEVCHK ( status, NULL ); + + status = ca_clear_event ( monix2 ); + SEVCHK ( status, NULL); + + status = ca_clear_event ( monix1 ); + SEVCHK ( status, NULL); + + status = ca_clear_event ( monix3 ); + SEVCHK ( status, NULL); + + status = ca_add_event ( DBR_FLOAT, chan, selfDeleteEvent, + 0, &globalEventID ); + SEVCHK ( status, NULL ); +} + +unsigned acctstExceptionCount = 0u; +void acctstExceptionNotify ( struct exception_handler_args args ) +{ + acctstExceptionCount++; +} + +static unsigned arrayEventExceptionNotifyComplete = 0; +void arrayEventExceptionNotify ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + printf ( + "arrayEventExceptionNotify: expected " + "exception but didnt receive one against \"%s\" \n", + ca_name ( args.chid ) ); + } + else { + arrayEventExceptionNotifyComplete = 1; + } +} + +void exceptionTest ( chid chan, unsigned interestLevel ) +{ + int status; + + showProgressBegin ( "exceptionTest", interestLevel ); + + /* + * force a get exception to occur + */ + { + dbr_put_ackt_t *pRS; + + acctstExceptionCount = 0u; + status = ca_add_exception_event ( acctstExceptionNotify, 0 ); + SEVCHK ( status, "exception notify install failed" ); + + pRS = malloc ( ca_element_count (chan) * sizeof (*pRS) ); + verify ( pRS ); + status = ca_array_get ( DBR_PUT_ACKT, + ca_element_count (chan), chan, pRS ); + SEVCHK ( status, "array read request failed" ); + ca_pend_io ( 1e-5 ); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( acctstExceptionCount < 1u ) { + printf ( "G" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + status = ca_add_exception_event ( 0, 0 ); + SEVCHK ( status, "exception notify install failed" ); + free ( pRS ); + } + + /* + * force a get call back exception to occur + */ + { + arrayEventExceptionNotifyComplete = 0u; + status = ca_array_get_callback ( DBR_PUT_ACKT, + ca_element_count (chan), chan, arrayEventExceptionNotify, 0 ); + if ( status != ECA_NORMAL ) { + verify ( status == ECA_BADTYPE || status == ECA_GETFAIL ); + arrayEventExceptionNotifyComplete = 1; + } + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( ! arrayEventExceptionNotifyComplete ) { + printf ( "GCB" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + } + + /* + * force a subscription exception to occur + */ + { + evid id; + + arrayEventExceptionNotifyComplete = 0u; + status = ca_add_array_event ( DBR_PUT_ACKT, ca_element_count ( chan ), + chan, arrayEventExceptionNotify, 0, 0.0, 0.0, 0.0, &id ); + if ( status != ECA_NORMAL ) { + verify ( status == ECA_BADTYPE || status == ECA_GETFAIL ); + arrayEventExceptionNotifyComplete = 1; + } + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( ! arrayEventExceptionNotifyComplete ) { + printf ( "S" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + status = ca_clear_event ( id ); + SEVCHK ( status, "subscription clear failed" ); + } + + /* + * force a put exception to occur + */ + { + dbr_string_t * pWS; + unsigned i; + + acctstExceptionCount = 0u; + status = ca_add_exception_event ( acctstExceptionNotify, 0 ); + SEVCHK ( status, "exception notify install failed" ); + + pWS = malloc ( ca_element_count (chan) * MAX_STRING_SIZE ); + verify ( pWS ); + for ( i = 0; i < ca_element_count (chan); i++ ) { + strcpy ( pWS[i], "@#$%" ); + } + status = ca_array_put ( DBR_STRING, + ca_element_count (chan), chan, pWS ); + if ( status != ECA_NORMAL ) { + verify ( status == ECA_BADTYPE || status == ECA_PUTFAIL ); + acctstExceptionCount++; /* local PV case */ + } + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( acctstExceptionCount < 1u ) { + printf ( "P" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + status = ca_add_exception_event ( 0, 0 ); + SEVCHK ( status, "exception notify install failed" ); + free ( pWS ); + } + + /* + * force a put callback exception to occur + */ + { + dbr_string_t *pWS; + unsigned i; + + pWS = malloc ( ca_element_count (chan) * MAX_STRING_SIZE ); + verify ( pWS ); + for ( i = 0; i < ca_element_count (chan); i++ ) { + strcpy ( pWS[i], "@#$%" ); + } + arrayEventExceptionNotifyComplete = 0u; + status = ca_array_put_callback ( DBR_STRING, + ca_element_count (chan), chan, pWS, + arrayEventExceptionNotify, 0); + if ( status != ECA_NORMAL ) { + arrayEventExceptionNotifyComplete = 1; + } + ca_flush_io (); + epicsThreadSleep ( 0.1 ); + ca_poll (); + while ( ! arrayEventExceptionNotifyComplete ) { + printf ( "PCB" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + free ( pWS ); + } + + showProgressEnd ( interestLevel ); +} + +/* + * array test + * + * verify that we can at least write and read back the same array + * if multiple elements are present + */ +static unsigned arrayReadNotifyComplete = 0; +static unsigned arrayWriteNotifyComplete = 0; +void arrayReadNotify ( struct event_handler_args args ) +{ + dbr_double_t *pWF = ( dbr_double_t * ) ( args.usr ); + dbr_double_t *pRF = ( dbr_double_t * ) ( args.dbr ); + int i; + for ( i = 0; i < args.count; i++ ) { + verify ( pWF[i] == pRF[i] ); + } + arrayReadNotifyComplete = 1; +} +void arrayWriteNotify ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + arrayWriteNotifyComplete = 1; + } + else { + printf ( "arrayWriteNotify: update failed for \"%s\" because \"%s\"", + ca_name ( args.chid ), ca_message ( args.status ) ); + } +} + +void arrayTest ( chid chan, unsigned maxArrayBytes, unsigned interestLevel ) +{ + dbr_double_t *pRF, *pWF; + unsigned i; + int status; + evid id; + + if ( ! ca_write_access ( chan ) ) { + printf ( "skipping array test - no write access\n" ); + return; + } + + showProgressBegin ( "arrayTest", interestLevel ); + + pRF = (dbr_double_t *) calloc ( ca_element_count (chan), sizeof (*pRF) ); + verify ( pRF != NULL ); + + pWF = (dbr_double_t *) calloc ( ca_element_count (chan), sizeof (*pWF) ); + verify ( pWF != NULL ); + + /* + * write some random numbers into the array + */ + for ( i = 0; i < ca_element_count (chan); i++ ) { + pWF[i] = rand (); + pRF[i] = - pWF[i]; + } + status = ca_array_put ( DBR_DOUBLE, ca_element_count ( chan ), + chan, pWF ); + SEVCHK ( status, "array write request failed" ); + + /* + * read back the array + */ + status = ca_array_get ( DBR_DOUBLE, ca_element_count (chan), + chan, pRF ); + SEVCHK ( status, "array read request failed" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "array read failed" ); + + /* + * verify read response matches values written + */ + for ( i = 0; i < ca_element_count ( chan ); i++ ) { + if ( pWF[i] != pRF[i] ) { + printf ( "i=%u, pWF[i]=%f, pRF[i]=%f\n", + i, pWF[i], pRF[i]); + } + verify ( pWF[i] == pRF[i] ); + } + + /* + * read back the array as strings + */ + { + char *pRS; + unsigned size = ca_element_count (chan) * MAX_STRING_SIZE; + + if ( size <= maxArrayBytes ) { + + pRS = malloc ( ca_element_count (chan) * MAX_STRING_SIZE ); + verify ( pRS ); + status = ca_array_get ( DBR_STRING, + ca_element_count (chan), chan, pRS ); + SEVCHK ( status, "array read request failed" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "array read failed" ); + free ( pRS ); + } + else { + printf ( "skipping the fetch array in string data type test - does not fit\n" ); + } + } + + /* + * write some random numbers into the array + */ + for ( i = 0; i < ca_element_count (chan); i++ ) { + pWF[i] = rand (); + pRF[i] = - pWF[i]; + } + status = ca_array_put_callback ( DBR_DOUBLE, ca_element_count (chan), + chan, pWF, arrayWriteNotify, 0 ); + SEVCHK ( status, "array write notify request failed" ); + status = ca_array_get_callback ( DBR_DOUBLE, ca_element_count (chan), + chan, arrayReadNotify, pWF ); + SEVCHK ( status, "array read notify request failed" ); + ca_flush_io (); + while ( ! arrayWriteNotifyComplete || ! arrayReadNotifyComplete ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + + /* + * write some random numbers into the array + */ + for ( i = 0; i < ca_element_count (chan); i++ ) { + pWF[i] = rand (); + pRF[i] = - pWF[i]; + } + arrayReadNotifyComplete = 0; + status = ca_array_put ( DBR_DOUBLE, ca_element_count ( chan ), + chan, pWF ); + SEVCHK ( status, "array write notify request failed" ); + status = ca_add_array_event ( DBR_DOUBLE, ca_element_count ( chan ), + chan, arrayReadNotify, pWF, 0.0, 0.0, 0.0, &id ); + SEVCHK ( status, "array subscription request failed" ); + ca_flush_io (); + while ( ! arrayReadNotifyComplete ) { + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + status = ca_clear_event ( id ); + SEVCHK ( status, "clear event request failed" ); + + /* + * a get request should fail or fill with zeros + * when the array size is too large + */ + { + acctstExceptionCount = 0u; + status = ca_add_exception_event ( acctstExceptionNotify, 0 ); + SEVCHK ( status, "exception notify install failed" ); + status = ca_array_get ( DBR_DOUBLE, + ca_element_count (chan)+1, chan, pRF ); + if ( status == ECA_NORMAL ) { + ca_poll (); + while ( acctstExceptionCount < 1u ) { + printf ( "N" ); + fflush ( stdout ); + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + } + } + else { + verify ( status == ECA_BADCOUNT ); + } + status = ca_add_exception_event ( 0, 0 ); + SEVCHK ( status, "exception notify install failed" ); + } + + free ( pRF ); + free ( pWF ); + + showProgressEnd ( interestLevel ); +} + +/* + * verify that unequal send/recv buffer sizes work + * (a bug related to this test was detected in early R3.14) + * + * this test must be run when no other channels are connected + */ +void unequalServerBufferSizeTest ( const char * pName, unsigned interestLevel ) +{ + dbr_double_t *pRF, *pWF; + unsigned connections; + chid newChan; + int status; + + showProgressBegin ( "unequalServerBufferSizeTest", interestLevel ); + + /* this test must be run when no channels are connected */ + connections = ca_get_ioc_connection_count (); + verify ( connections == 0u ); + + status = ca_create_channel ( pName, 0, 0, 0, & newChan ); + verify ( status == ECA_NORMAL ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + + showProgress ( interestLevel ); + + if ( ! ca_write_access ( newChan ) ) { + printf ( "skipping unequal buffer size test - no write access\n" ); + status = ca_clear_channel ( newChan ); + verify ( status == ECA_NORMAL ); + return; + } + + pRF = (dbr_double_t *) calloc ( ca_element_count (newChan), sizeof (*pRF) ); + verify ( pRF != NULL ); + + pWF = (dbr_double_t *) calloc ( ca_element_count (newChan), sizeof (*pWF) ); + verify ( pWF != NULL ); + + status = ca_array_get ( DBR_DOUBLE, ca_element_count ( newChan ), + newChan, pRF ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + status = ca_clear_channel ( newChan ); + verify ( status == ECA_NORMAL ); + + showProgress ( interestLevel ); + + status = ca_create_channel ( pName, 0, 0, 0, &newChan ); + verify ( status == ECA_NORMAL ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + + showProgress ( interestLevel ); + + status = ca_array_put ( DBR_DOUBLE, ca_element_count ( newChan ), + newChan, pWF ); + verify ( status == ECA_NORMAL ); + status = ca_array_get ( DBR_DOUBLE, 1, + newChan, pRF ); + verify ( status == ECA_NORMAL ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + status = ca_clear_channel ( newChan ); + verify ( status == ECA_NORMAL ); + + free ( pRF ); + free ( pWF ); + + showProgressEnd ( interestLevel ); +} + +/* + * pend_event_delay_test() + */ +void pend_event_delay_test ( dbr_double_t request ) +{ + int status; + epicsTimeStamp end_time; + epicsTimeStamp start_time; + dbr_double_t delay; + dbr_double_t accuracy; + + epicsTimeGetCurrent ( &start_time ); + status = ca_pend_event ( request ); + if (status != ECA_TIMEOUT) { + SEVCHK(status, NULL); + } + epicsTimeGetCurrent(&end_time); + delay = epicsTimeDiffInSeconds ( &end_time, &start_time ); + accuracy = 100.0*(delay-request)/request; + printf ( "CA pend event delay = %f sec results in error = %f %%\n", + request, accuracy ); + verify ( fabs(accuracy) < 10.0 ); +} + +void caTaskExitTest ( unsigned interestLevel ) +{ + int status; + + showProgressBegin ( "caTaskExitTest", interestLevel ); + + status = ca_task_exit (); + SEVCHK ( status, NULL ); + + showProgressEnd ( interestLevel ); +} + +void verifyDataTypeMacros (void) +{ + int type; + + type = dbf_type_to_DBR ( DBF_SHORT ); + verify ( type == DBR_SHORT ); + type = dbf_type_to_DBR_STS ( DBF_SHORT ); + verify ( type == DBR_STS_SHORT ); + type = dbf_type_to_DBR_GR ( DBF_SHORT ); + verify ( type == DBR_GR_SHORT ); + type = dbf_type_to_DBR_CTRL ( DBF_SHORT ); + verify ( type == DBR_CTRL_SHORT ); + type = dbf_type_to_DBR_TIME ( DBF_SHORT ); + verify ( type == DBR_TIME_SHORT ); + verify ( strcmp ( dbr_type_to_text( DBR_SHORT ), "DBR_SHORT" ) == 0 ); + verify ( strcmp ( dbf_type_to_text( DBF_SHORT ), "DBF_SHORT" ) == 0 ); + verify ( dbr_type_is_SHORT ( DBR_SHORT ) ); + verify ( dbr_type_is_valid ( DBR_SHORT ) ); + verify ( dbf_type_is_valid ( DBF_SHORT ) ); + { + int dataType = -1; + dbf_text_to_type ( "DBF_SHORT", dataType ); + verify ( dataType == DBF_SHORT ); + dbr_text_to_type ( "DBR_CLASS_NAME", dataType ); + verify ( dataType == DBR_CLASS_NAME ); + } +} + +typedef struct { + evid id; + dbr_float_t lastValue; + unsigned count; +} eventTest; + +/* + * updateTestEvent () + */ +void updateTestEvent ( struct event_handler_args args ) +{ + eventTest *pET = (eventTest *) args.usr; + struct dbr_gr_float *pGF = (struct dbr_gr_float *) args.dbr; + pET->lastValue = pGF->value; + pET->count++; +} + +dbr_float_t monitorUpdateTestPattern ( unsigned iter ) +{ + return ( (float) iter ) * 10.12345f + 10.7f; +} + +void callbackClearsChannel ( struct event_handler_args args ) +{ + int status; + status = ca_clear_channel ( args.chid ); + SEVCHK ( status, "clearChannelInXxxxCallbackTest clear channel" ); +} + +void clearChannelInGetCallbackTest ( const char *pName, unsigned level ) +{ + unsigned i; + chid chan; + int status; + + showProgressBegin ( "clearChannelInGetCallbackTest", level ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + SEVCHK ( status, "clearChannelInGetCallbackTest create channel" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "clearChannelInGetCallbackTest connect channel" ); + + status = ca_get_callback ( DBR_DOUBLE, chan, callbackClearsChannel, 0 ); + SEVCHK ( status, "clearChannelInGetCallbackTest get callback" ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + showProgressEnd ( level ); +} + +void clearChannelInPutCallbackTest ( const char *pName, unsigned level ) +{ + unsigned i; + const dbr_double_t value = 1.1; + chid chan; + int status; + + showProgressBegin ( "clearChannelInPutCallbackTest", level ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + SEVCHK ( status, "clearChannelInPutCallbackTest create channel" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "clearChannelInPutCallbackTest connect channel" ); + + status = ca_put_callback ( DBR_DOUBLE, chan, & value, + callbackClearsChannel, 0 ); + SEVCHK ( status, "clearChannelInPutCallbackTest get callback" ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + showProgressEnd ( level ); +} + +void clearChannelInSubscrCallbackTest ( const char *pName, unsigned level ) +{ + unsigned i; + chid chan; + int status; + + showProgressBegin ( "clearChannelInSubscrCallbackTest", level ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + SEVCHK ( status, "clearChannelInSubscrCallbackTest create channel" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "clearChannelInSubscrCallbackTest connect channel" ); + + status = ca_create_subscription ( DBR_DOUBLE, 1, chan, + DBE_VALUE, callbackClearsChannel, 0, 0 ); + SEVCHK ( status, "clearChannelInSubscrCallbackTest subscribe" ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + showProgressEnd ( level ); +} + +void monitorAddConnectionCallback ( struct connection_handler_args args ) +{ + if ( args.op == CA_OP_CONN_UP ) { + unsigned * pEventCount = ( unsigned * ) ca_puser ( args.chid ); + int status; + verify ( *pEventCount == 0u ); + (*pEventCount)++; + status = ca_create_subscription ( DBR_DOUBLE, 1, + args.chid, DBE_VALUE, nUpdatesTester, ca_puser ( args.chid ), 0 ); + SEVCHK ( status, "monitorAddConnectionCallback create subscription" ); + } + else { + fprintf ( stderr, "disconnect during monitorAddConnectionCallbackTest?\n" ); + } +} + +/* + * monitorAddConnectionCallbackTest + * 1) subscription add from within connection callback needs to work + * 2) check for problems where subscription is installed twice if + * its installed within the connection callback handler + */ +void monitorAddConnectionCallbackTest ( const char *pName, unsigned interestLevel ) +{ + unsigned i; + chid chan; + int status; + unsigned eventCount = 0u; + unsigned getCallbackCount = 0u; + + showProgressBegin ( "monitorAddConnectionCallbackTest", interestLevel ); + + for ( i = 0; ca_get_ioc_connection_count () > 0 ; i++ ) { + ca_pend_event ( 0.1 ); + verify ( i < 100 ); + } + + status = ca_create_channel ( pName, + monitorAddConnectionCallback, &eventCount, 0, & chan ); + SEVCHK ( status, "monitorAddConnectionCallbackTest create channel" ); + + while ( eventCount < 2 ) { + ca_pend_event ( 0.1 ); + } + verify ( eventCount >= 2u ); + + status = ca_get_callback ( DBR_DOUBLE, chan, nUpdatesTester, &getCallbackCount ); + SEVCHK ( status, "monitorAddConnectionCallback get callback" ); + while ( getCallbackCount == 0 ) { + ca_pend_event ( 0.1 ); + } + verify ( eventCount >= 2u ); + verify ( getCallbackCount == 1u ); + + status = ca_clear_channel ( chan ); + SEVCHK ( status, "monitorAddConnectionCallbackTest clear channel" ); + + status = ca_flush_io (); + SEVCHK ( status, "monitorAddConnectionCallbackTest flush" ); + + showProgressEnd ( interestLevel ); +} + + +/* + * monitorUpdateTest + * + * 1) verify we can add many monitors at once + * 2) verify that under heavy load the last monitor + * returned is the last modification sent + */ +void monitorUpdateTest ( chid chan, unsigned interestLevel ) +{ + eventTest test[100]; + dbr_float_t temp, getResp; + unsigned i, j; + unsigned flowCtrlCount = 0u; + unsigned prevPassCount; + unsigned tries; + + if ( ! ca_write_access ( chan ) ) { + printf ("skipped monitorUpdateTest test - no write access\n"); + return; + } + + if ( dbr_value_class[ca_field_type ( chan )] != dbr_class_float ) { + printf ("skipped monitorUpdateTest test - not an analog type\n"); + return; + } + + showProgressBegin ( "monitorUpdateTest", interestLevel ); + + /* + * set channel to known value + */ + temp = 1; + SEVCHK ( ca_put ( DBR_FLOAT, chan, &temp ), NULL ); + + for ( i = 0; i < NELEMENTS(test); i++ ) { + test[i].count = 0; + test[i].lastValue = -1.0f; + SEVCHK(ca_add_event(DBR_GR_FLOAT, chan, updateTestEvent, + &test[i], &test[i].id),NULL); + } + + /* + * force all of the monitors subscription requests to + * complete + * + * NOTE: this hopefully demonstrates that when the + * server is very busy with monitors the client + * is still able to punch through with a request. + */ + SEVCHK ( ca_get ( DBR_FLOAT, chan, &getResp) ,NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ) ,NULL ); + + showProgress ( interestLevel ); + + /* + * pass the test only if we get the first monitor update + */ + tries = 0; + while ( 1 ) { + unsigned nFailed = 0u; + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + for ( i = 0; i < NELEMENTS ( test ); i++ ) { + if ( test[i].count > 0 ) { + if ( test[i].lastValue != temp ) { + nFailed++; + } + } + else { + nFailed++; + } + } + if ( nFailed == 0u ) { + break; + } + printf ( "-" ); + fflush ( stdout ); + verify ( tries++ < 50 ); + } + + showProgress ( interestLevel ); + + /* + * attempt to uncover problems where the last event isnt sent + * and hopefully get into a flow control situation + */ + prevPassCount = 0u; + for ( i = 0; i < 10; i++ ) { + for ( j = 0; j < NELEMENTS(test); j++ ) { + SEVCHK ( ca_clear_event ( test[j].id ), NULL ); + test[j].count = 0; + test[j].lastValue = -1.0f; + SEVCHK ( ca_add_event ( DBR_GR_FLOAT, chan, updateTestEvent, + &test[j], &test[j].id ) , NULL ); + } + + for ( j = 0; j <= i; j++ ) { + temp = monitorUpdateTestPattern ( j ); + SEVCHK ( ca_put ( DBR_FLOAT, chan, &temp ), NULL ); + } + + /* + * wait for the above to complete + */ + getResp = -1; + SEVCHK ( ca_get ( DBR_FLOAT, chan, &getResp ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + + if ( getResp != temp ) { + printf ( "getResp=%f, temp=%f\n", getResp, temp ); + verify ( getResp == temp ); + } + + /* + * wait for all of the monitors to have correct values + */ + tries = 0; + while (1) { + unsigned passCount = 0; + unsigned tmpFlowCtrlCount = 0u; + epicsThreadSleep ( 0.1 ); + ca_poll (); /* emulate typical GUI */ + for ( j = 0; j < NELEMENTS ( test ); j++ ) { + /* + * we shouldnt see old monitors because + * we resubscribed + */ + verify ( test[j].count <= i + 2 ); + if ( test[j].lastValue == temp ) { + if ( test[j].count < i + 1 ) { + tmpFlowCtrlCount++; + } + passCount++; + } + } + if ( passCount == NELEMENTS ( test ) ) { + flowCtrlCount += tmpFlowCtrlCount; + break; + } + if ( passCount == prevPassCount ) { + verify ( tries++ < 500 ); + if ( tries % 50 == 0 ) { + for ( j = 0; j <= i; j++ ) { + dbr_float_t pat = monitorUpdateTestPattern ( j ); + if ( pat == test[0].lastValue ) { + break; + } + } + if ( j <= i) { + printf ( "-(%d)", i-j ); + } + else { + printf ( "." ); + } + fflush ( stdout ); + } + } + prevPassCount = passCount; + } + } + + showProgress ( interestLevel ); + + /* + * delete the event subscriptions + */ + for ( i = 0; i < NELEMENTS ( test ); i++ ) { + SEVCHK ( ca_clear_event ( test[i].id ), NULL ); + } + + /* + * force all of the clear event requests to + * complete + */ + SEVCHK ( ca_get ( DBR_FLOAT, chan, &temp ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + + /* printf ( "flow control bypassed %u events\n", flowCtrlCount ); */ + + showProgressEnd ( interestLevel ); +} + +void verifyReasonableBeaconPeriod ( chid chan, unsigned interestLevel ) +{ + if ( ca_get_ioc_connection_count () > 0 ) { + double beaconPeriod; + double watchDogDelay; + unsigned i; + + showProgressBegin ( "verifyReasonableBeaconPeriod", interestLevel ); + + + printf ( "Beacon anomalies detected since program start %u\n", + ca_beacon_anomaly_count () ); + + beaconPeriod = ca_beacon_period ( chan ); + printf ( "Estimated beacon period for channel %s = %g sec.\n", + ca_name ( chan ), beaconPeriod ); + + watchDogDelay = ca_receive_watchdog_delay ( chan ); + verify ( watchDogDelay >= 0.0 ); + + printf ( "busy: receive watchdog for \"%s\" expires in %g sec.\n", + ca_name ( chan ), watchDogDelay ); + + /* + * let one default connection timeout go by w/o receive activity + * so we can see if beacons reset the watchdog + */ + for ( i = 0u; i < 15u; i++ ) { + ca_pend_event ( 2.0 ); + showProgress ( interestLevel ); + } + if ( interestLevel > 0 ) { + printf ( "\n" ); + } + + watchDogDelay = ca_receive_watchdog_delay ( chan ); + verify ( watchDogDelay >= 0.0 ); + + printf ( "inactive: receive watchdog for \"%s\" expires in %g sec.\n", + ca_name ( chan ), watchDogDelay ); + + showProgressEnd ( interestLevel ); + } +} + +void verifyOldPend ( unsigned interestLevel ) +{ + int status; + + showProgressBegin ( "verifyOldPend", interestLevel ); + + /* + * verify that the old ca_pend() is in the symbol table + */ + status = ca_pend ( 100000.0, 1 ); + verify ( status == ECA_NORMAL ); + status = ca_pend ( 1e-12, 0 ); + verify ( status == ECA_TIMEOUT ); + + showProgressEnd ( interestLevel ); +} + +void verifyTimeStamps ( chid chan, unsigned interestLevel ) +{ + struct dbr_time_double first; + struct dbr_time_double last; + epicsTimeStamp localTime; + char buf[128]; + size_t length; + double diff; + int status; + + showProgressBegin ( "verifyTimeStamps", interestLevel ); + + status = epicsTimeGetCurrent ( & localTime ); + verify ( status >= 0 ); + + status = ca_get ( DBR_TIME_DOUBLE, chan, & first ); + SEVCHK ( status, "fetch of dbr time double failed\n" ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + + status = ca_get ( DBR_TIME_DOUBLE, chan, & last ); + SEVCHK ( status, "fetch of dbr time double failed\n" ); + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + + length = epicsTimeToStrftime ( buf, sizeof ( buf ), + "%a %b %d %Y %H:%M:%S.%f", & first.stamp ); + verify ( length ); + printf ("Processing time of channel \"%s\" was \"%s\"\n", + ca_name ( chan ), buf ); + + diff = epicsTimeDiffInSeconds ( & last.stamp, & first.stamp ); + printf ("Time difference between two successive reads was %g sec\n", + diff ); + + diff = epicsTimeDiffInSeconds ( & first.stamp, & localTime ); + printf ("Time difference between client and server %g sec\n", + diff ); + + showProgressEnd ( interestLevel ); +} + +/* + * attempts to verify from the client side that + * channel priorities work correctly + */ +void verifyChannelPriorities ( const char *pName, unsigned interestLevel ) +{ + static const unsigned nPrio = 30; + unsigned i; + + showProgressBegin ( "verifyChannelPriorities", interestLevel ); + + for ( i = 0u; i < nPrio; i++ ) { + int status; + double value; + chid chan0, chan1; + unsigned priority0, priority1; + + priority0 = ( i * ( CA_PRIORITY_MAX - CA_PRIORITY_MIN ) ) / nPrio; + priority0 += CA_PRIORITY_MIN; + if ( priority0 > CA_PRIORITY_MAX ) { + priority0 = CA_PRIORITY_MAX; + } + + priority1 = ( (i+1) * ( CA_PRIORITY_MAX - CA_PRIORITY_MIN ) ) / nPrio; + priority1 += CA_PRIORITY_MIN; + if ( priority1 > CA_PRIORITY_MAX ) { + priority1 = CA_PRIORITY_MAX; + } + + status = ca_create_channel ( pName, 0, 0, + priority0, &chan0 ); + SEVCHK ( status, "prioritized channel create failed" ); + + status = ca_create_channel ( pName, 0, 0, + priority1, &chan1 ); + SEVCHK ( status, "prioritized channel create failed" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "prioritized channel connect failed" ); + verify ( status == ECA_NORMAL ); + + value = i; + status = ca_put ( DBR_DOUBLE, chan0, &value ); + SEVCHK ( status, "prioritized channel put failed" ); + status = ca_put ( DBR_DOUBLE, chan1, &value ); + SEVCHK ( status, "prioritized channel put failed" ); + + status = ca_flush_io (); + SEVCHK ( status, "prioritized channel flush failed" ); + + status = ca_get ( DBR_DOUBLE, chan0, &value ); + SEVCHK ( status, "prioritized channel get failed" ); + status = ca_get ( DBR_DOUBLE, chan1, &value ); + SEVCHK ( status, "prioritized channel get failed" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "prioritized channel pend io failed" ); + + status = ca_clear_channel ( chan0 ); + SEVCHK ( status, "prioritized channel clear failed" ); + status = ca_clear_channel ( chan1 ); + SEVCHK ( status, "prioritized channel clear failed" ); + } + + showProgressEnd ( interestLevel ); +} + +void verifyTearDownWhenChannelConnected ( const char * pName, + enum ca_preemptive_callback_select select, + unsigned interestLevel ) +{ + static const unsigned chanCount = 20; + static const unsigned loopCount = 100; + + chid * const pChans = (chid * const) calloc ( chanCount, sizeof ( *pChans ) ); + double * const pValues = (double * const) calloc ( chanCount, sizeof ( *pValues ) ); + unsigned i, j; + + verify ( pChans && pValues ); + + showProgressBegin ( "verifyTearDownWhenChannelConnected", interestLevel ); + + for ( i = 0u; i < loopCount; i++ ) { + int status; + ca_context_create ( select ); + + for ( j = 0; j < chanCount; j++ ) { + status = ca_create_channel ( pName, 0, 0, 0, & pChans[j] ); + SEVCHK ( status, "immediate tear down channel create failed" ); + } + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "immediate tear down channel connect failed" ); + verify ( status == ECA_NORMAL ); + + for ( j = 0; j < chanCount; j++ ) { + status = ca_get ( DBR_DOUBLE, pChans[j], &pValues[j] ); + SEVCHK ( status, "immediate tear down channel get failed" ); + } + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "immediate tear down get pend io failed" ); + + ca_context_destroy (); + } + + ca_context_create ( select ); + + free ( pChans ); + free ( pValues ); + + showProgressEnd ( interestLevel ); +} + + +void verifyImmediateTearDown ( const char * pName, + enum ca_preemptive_callback_select select, + unsigned interestLevel ) +{ + int i; + + showProgressBegin ( "verifyImmediateTearDown", interestLevel ); + + for ( i = 0u; i < 100; i++ ) { + chid chan; + int status; + dbr_long_t value = i % 8; + ca_context_create ( select ); + ca_task_initialize (); + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + SEVCHK ( status, "immediate tear down channel create failed" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "immediate tear down channel connect failed" ); + verify ( status == ECA_NORMAL ); + /* + * verify that puts pending when we call ca_task_exit() + * get flushed out + */ + if ( i > 0 ) { + dbr_long_t currentValue = value; + status = ca_get ( DBR_LONG, chan, & currentValue ); + SEVCHK ( status, "immediate tear down channel get failed" ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "immediate tear down channel get failed" ); + if ( currentValue != ( (i - 1) % 8 ) ) { + printf ( "currentValue = %i, i = %i\n", currentValue, i ); + verify ( currentValue == ( (i - 1) % 8 ) ); + } + } + status = ca_put ( DBR_LONG, chan, & value ); + SEVCHK ( status, "immediate tear down channel put failed" ); + status = ca_clear_channel ( chan ); + SEVCHK ( status, "immediate tear down channel clear failed" ); + ca_context_destroy (); + /* epicsThreadSleep ( 1e-15 ); */ + if ( i % 10 == 0 ) { + showProgress ( interestLevel ); + } + } + + ca_context_create ( select ); + + showProgressEnd ( interestLevel ); +} + +/* + * keeping these tests together detects a bug + */ +void eventClearAndMultipleMonitorTest ( chid chan, unsigned interestLevel ) +{ + eventClearTest ( chan ); + monitorUpdateTest ( chan, interestLevel ); +} + +void fdcb ( void * parg ) +{ + ca_poll (); +} + +void fdRegCB ( void * parg, int fd, int opened ) +{ + int status; + + fdctx * mgrCtx = ( fdctx * ) parg; + if ( opened ) { + status = fdmgr_add_callback ( + mgrCtx, fd, fdi_read, fdcb, 0 ); + verify ( status >= 0 ); + } + else { + status = fdmgr_clear_callback ( + mgrCtx, fd, fdi_read ); + verify ( status >= 0 ); + } +} + +typedef struct { + char m_chanName[100u]; + struct ca_client_context * m_pCtx; + chid m_chan; + epicsMutexId m_mutex; + epicsEventId m_testCompleteEvent; + epicsEventId m_threadExitEvent; + size_t m_nUpdatesReceived; + size_t m_nUpdatesRequired; + int m_testInitiated; + int m_testComplete; + unsigned m_interestLevel; +} MultiThreadSubscrTest; + +static void testMultithreadSubscrSubscrCallback + ( struct event_handler_args eha ) +{ + const epicsEventId firstUpdateEvent = ( epicsEventId ) eha.usr; + epicsEventSignal ( firstUpdateEvent ); +} + +static void testMultithreadSubscrCreateSubscr ( void * pParm ) +{ + static unsigned nElem = 0; + int testComplete = FALSE; + evid id; + epicsEventId firstUpdateEvent; + epicsEventWaitStatus eventWaitStatus; + MultiThreadSubscrTest * const pMultiThreadSubscrTest = + ( MultiThreadSubscrTest * ) pParm; + + /* this is required for the ca_flush below to work correctly */ + int status = ca_attach_context ( pMultiThreadSubscrTest->m_pCtx ); + verify ( status == ECA_NORMAL ); + firstUpdateEvent = epicsEventMustCreate ( epicsEventEmpty ); + verify ( firstUpdateEvent ); + status = ca_create_subscription ( + DBR_TIME_LONG, + nElem, + pMultiThreadSubscrTest->m_chan, + DBE_VALUE, + testMultithreadSubscrSubscrCallback, + firstUpdateEvent, + & id ); + verify ( status == ECA_NORMAL ); + status = ca_flush_io (); + verify ( status == ECA_NORMAL ); + /* wait for first update */ + eventWaitStatus = epicsEventWaitWithTimeout ( + firstUpdateEvent, 60.0 * 10 ); + verify ( eventWaitStatus == epicsEventWaitOK ); + epicsEventDestroy ( firstUpdateEvent ); + status = ca_clear_subscription ( id ); + verify ( status == ECA_NORMAL ); + epicsMutexMustLock ( pMultiThreadSubscrTest->m_mutex ); + pMultiThreadSubscrTest->m_nUpdatesReceived++; + testComplete = ( pMultiThreadSubscrTest->m_nUpdatesReceived == + pMultiThreadSubscrTest->m_nUpdatesRequired ); + pMultiThreadSubscrTest->m_testComplete = testComplete; + epicsMutexUnlock ( pMultiThreadSubscrTest->m_mutex ); + if ( testComplete ) { + epicsEventSignal ( pMultiThreadSubscrTest->m_testCompleteEvent ); + } +} + +void testMultithreadSubscrConnHandler ( struct connection_handler_args args ) +{ + MultiThreadSubscrTest * const pMultiThreadSubscrTest = + ( MultiThreadSubscrTest * ) ca_puser ( args.chid ); + epicsMutexMustLock ( pMultiThreadSubscrTest->m_mutex ); + if ( !pMultiThreadSubscrTest->m_testInitiated && + args.op == CA_OP_CONN_UP ) { + int i; + pMultiThreadSubscrTest->m_testInitiated = TRUE; + for ( i = 0; i < pMultiThreadSubscrTest->m_nUpdatesRequired; i++ ) { + char threadname[64]; + epicsThreadId threadId; + sprintf(threadname, "testSubscr%06u", i); + threadId = epicsThreadCreate ( threadname, + epicsThreadPriorityMedium, + epicsThreadGetStackSize(epicsThreadStackSmall), + testMultithreadSubscrCreateSubscr, + pMultiThreadSubscrTest ); + verify ( threadId ); + } + } + epicsMutexUnlock ( pMultiThreadSubscrTest->m_mutex ); +} + +void testMultithreadSubscr ( void * pParm ) +{ + MultiThreadSubscrTest * const pMultiThreadSubscrTest = + ( MultiThreadSubscrTest * ) pParm; + int status; + unsigned i; + + status = ca_context_create ( ca_enable_preemptive_callback ); + verify ( status == ECA_NORMAL ); + pMultiThreadSubscrTest->m_pCtx = ca_current_context (); + verify ( pMultiThreadSubscrTest->m_pCtx ); + status = ca_create_channel ( + pMultiThreadSubscrTest->m_chanName, + testMultithreadSubscrConnHandler, + pMultiThreadSubscrTest, + CA_PRIORITY_MIN, + & pMultiThreadSubscrTest->m_chan ); + verify ( status == ECA_NORMAL ); + + showProgressBegin ( "verifyMultithreadSubscr", + pMultiThreadSubscrTest->m_interestLevel ); + i = 0; + while ( TRUE ) { + int success = FALSE; + epicsEventWaitStatus eventWaitStatus; + epicsMutexMustLock ( pMultiThreadSubscrTest->m_mutex ); + success = pMultiThreadSubscrTest->m_testComplete; + epicsMutexUnlock ( pMultiThreadSubscrTest->m_mutex ); + if ( success ) { + break; + } + eventWaitStatus = epicsEventWaitWithTimeout ( + pMultiThreadSubscrTest->m_testCompleteEvent, 0.1 ); + verify ( eventWaitStatus == epicsEventWaitOK || + eventWaitStatus == epicsEventWaitTimeout ); + if ( i++ % 100 == 0u ) + showProgress ( pMultiThreadSubscrTest->m_interestLevel ); + verify ( i < 1000 ); + } + showProgressEnd ( pMultiThreadSubscrTest->m_interestLevel ); + + status = ca_clear_channel ( pMultiThreadSubscrTest->m_chan ); + verify ( status == ECA_NORMAL ); + ca_context_destroy (); + epicsEventSignal ( pMultiThreadSubscrTest->m_threadExitEvent ); +} + +/* + * test installation of subscriptions similar to usage paterns + * employed by modern versions of the sequencer + */ +void verifyMultithreadSubscr ( const char * pName, unsigned interestLevel ) +{ + static unsigned nSubscr = 3000; + epicsThreadId threadId; + MultiThreadSubscrTest * const pMultiThreadSubscrTest = + (MultiThreadSubscrTest*) calloc ( 1, + sizeof ( MultiThreadSubscrTest ) ); + verify ( pMultiThreadSubscrTest); + pMultiThreadSubscrTest->m_mutex = epicsMutexMustCreate (); + verify ( pMultiThreadSubscrTest->m_mutex ); + pMultiThreadSubscrTest->m_testCompleteEvent = + epicsEventMustCreate ( epicsEventEmpty ); + verify ( pMultiThreadSubscrTest->m_testCompleteEvent ); + pMultiThreadSubscrTest->m_threadExitEvent = + epicsEventMustCreate ( epicsEventEmpty ); + verify ( pMultiThreadSubscrTest->m_threadExitEvent ); + strncpy ( pMultiThreadSubscrTest->m_chanName, pName, + sizeof ( pMultiThreadSubscrTest->m_chanName ) ); + pMultiThreadSubscrTest->m_chanName + [ sizeof ( pMultiThreadSubscrTest->m_chanName ) - 1u ] = '\0'; + pMultiThreadSubscrTest->m_nUpdatesRequired = nSubscr; + pMultiThreadSubscrTest->m_interestLevel = interestLevel; + threadId = epicsThreadCreate ( + "testMultithreadSubscr", + epicsThreadPriorityMedium, + epicsThreadGetStackSize(epicsThreadStackSmall), + testMultithreadSubscr, pMultiThreadSubscrTest ); + verify ( threadId ); + { + epicsEventWaitStatus eventWaitStatus; + eventWaitStatus = epicsEventWaitWithTimeout ( + pMultiThreadSubscrTest->m_threadExitEvent, 1000.0 ); + verify ( eventWaitStatus == epicsEventWaitOK ); + } + epicsEventDestroy ( pMultiThreadSubscrTest->m_testCompleteEvent ); + epicsEventDestroy ( pMultiThreadSubscrTest->m_threadExitEvent ); + epicsMutexDestroy ( pMultiThreadSubscrTest->m_mutex ); + free ( pMultiThreadSubscrTest ); +} + +void fdManagerVerify ( const char * pName, unsigned interestLevel ) +{ + int status; + fdctx * mgrCtx; + struct timeval tmo; + chid newChan; + evid subscription; + unsigned eventCount = 0u; + epicsTimeStamp begin, end; + + mgrCtx = fdmgr_init (); + verify ( mgrCtx ); + + showProgressBegin ( "fdManagerVerify", interestLevel ); + + status = ca_add_fd_registration ( fdRegCB, mgrCtx ); + verify ( status == ECA_NORMAL ); + + status = ca_create_channel ( pName, 0, 0, 0, & newChan ); + verify ( status == ECA_NORMAL ); + + while ( ca_state ( newChan ) != cs_conn ) { + tmo.tv_sec = 6000; + tmo.tv_usec = 0; + status = fdmgr_pend_event ( mgrCtx, & tmo ); + verify ( status >= 0 ); + } + + showProgress ( interestLevel ); + + status = ca_add_event ( DBR_FLOAT, newChan, + nUpdatesTester, & eventCount, & subscription ); + verify ( status == ECA_NORMAL ); + + status = ca_flush_io (); + verify ( status == ECA_NORMAL ); + + while ( eventCount < 1 ) { + tmo.tv_sec = 6000; + tmo.tv_usec = 0; + status = fdmgr_pend_event ( mgrCtx, & tmo ); + verify ( status >= 0 ); + } + + showProgress ( interestLevel ); + + status = ca_clear_event ( subscription ); + verify ( status == ECA_NORMAL ); + + status = ca_flush_io (); + verify ( status == ECA_NORMAL ); + + /* look for infinite loop in fd manager schedualing */ + epicsTimeGetCurrent ( & begin ); + eventCount = 0u; + while ( 1 ) { + double delay; + tmo.tv_sec = 1; + tmo.tv_usec = 0; + status = fdmgr_pend_event ( mgrCtx, & tmo ); + verify ( status >= 0 ); + epicsTimeGetCurrent ( & end ); + delay = epicsTimeDiffInSeconds ( & end, & begin ); + if ( delay >= 1.0 ) { + break; + } + verify ( eventCount++ < 100 ); + } + + showProgress ( interestLevel ); + + status = ca_clear_channel ( newChan ); + verify ( status == ECA_NORMAL ); + + status = ca_add_fd_registration ( 0, 0 ); + verify ( status == ECA_NORMAL ); + + status = fdmgr_delete ( mgrCtx ); + verify ( status >= 0 ); + + showProgressEnd ( interestLevel ); +} + +void verifyConnectWithDisconnectedChannels ( + const char *pName, unsigned interestLevel ) +{ + int status; + chid bogusChan[300]; + chid validChan; + unsigned i; + + showProgressBegin ( "verifyConnectWithDisconnectedChannels", interestLevel ); + + for ( i= 0u; i < NELEMENTS ( bogusChan ); i++ ) { + char buf[256]; + sprintf ( buf, "aChannelThatShouldNeverNeverNeverExist%u", i ); + status = ca_create_channel ( buf, 0, 0, 0, & bogusChan[i] ); + verify ( status == ECA_NORMAL ); + } + + status = ca_pend_io ( 0.001 ); + verify ( status == ECA_TIMEOUT ); + + /* wait a long time for the search interval to increase */ + for ( i= 0u; i < 10; i++ ) { + epicsThreadSleep ( 1.0 ); + showProgress ( interestLevel ); + } + + status = ca_create_channel ( pName, 0, 0, 0, & validChan ); + verify ( status == ECA_NORMAL ); + + /* + * we should be able to connect to a valid + * channel within a reasonable delay even + * though there is one permanently + * diasconnected channel + */ + status = ca_pend_io ( timeoutToPendIO ); + verify ( status == ECA_NORMAL ); + + status = ca_clear_channel ( validChan ); + verify ( status == ECA_NORMAL ); + + for ( i= 0u; i < NELEMENTS ( bogusChan ); i++ ) { + status = ca_clear_channel ( bogusChan[i] ); + verify ( status == ECA_NORMAL ); + } + + showProgressEnd ( interestLevel ); +} + +void verifyClearChannelOnDisconnectCallback ( + struct connection_handler_args args ) +{ + int * pDisconnectFlag = ca_puser ( args.chid ); + if ( args.op == CA_OP_CONN_DOWN ) { + ca_clear_channel ( args.chid ); + *pDisconnectFlag = 1; + } +} + +void noopExceptionCallback ( struct exception_handler_args args ) +{ +} + +void verifyDisconnect ( + const char * pName, unsigned interestLevel ) +{ + int disconnectFlag = 0; + unsigned count = 0; + chid chan0; + chid chan1; + int status; + + status = ca_create_channel ( + pName, verifyClearChannelOnDisconnectCallback, + & disconnectFlag, 0, & chan0 ); + SEVCHK ( status, NULL ); + + fprintf ( stdout, "Waiting for test channel to connect." ); + fflush ( stdout ); + do { + ca_pend_event ( 0.1 ); + if ( count++%50 == 0 ) { + fprintf ( stdout, "." ); + fflush ( stdout ); + } + } while ( ca_state ( chan0 ) != cs_conn ); + fprintf ( stdout, "confirmed.\n" ); + + /* + * if its a local channel and will never disconnect + * then skip the portions of this test that cant be + * completed. + */ + if ( ca_get_ioc_connection_count () == 0 ) { + status = ca_clear_channel ( chan0 ); + SEVCHK ( status, NULL ); + return; + } + + status = ca_add_exception_event ( noopExceptionCallback, 0 ); + SEVCHK ( status, NULL ); + + fprintf ( stdout, "Please force test channel to disconnect." ); + fflush ( stdout ); + do { + ca_pend_event ( 0.1 );; + if ( count++%50 == 0 ) { + fprintf ( stdout, "." ); + fflush ( stdout ); + } + } while ( ! disconnectFlag ); + fprintf ( stdout, "confirmed.\n" ); + /* channel cleared by disconnect handler */ + + status = ca_create_channel ( + pName, 0, 0, 0, & chan1 ); + SEVCHK ( status, NULL ); + + fprintf ( stdout, "Waiting for test channel to connect." ); + fflush ( stdout ); + while ( ca_state ( chan1 ) != cs_conn ) { + ca_pend_event ( 5.0 ); + fprintf ( stdout, "." ); + fflush ( stdout ); + } + status = ca_clear_channel ( chan1 ); + SEVCHK ( status, NULL ); + fprintf ( stdout, "confirmed.\n" ); + + status = ca_add_exception_event ( 0, 0 ); + SEVCHK ( status, NULL ); +} + +void verifyName ( + const char * pName, unsigned interestLevel ) +{ + chid chan; + int status = ca_create_channel ( + pName, 0, 0, 0, & chan ); + SEVCHK ( status, NULL ); + if ( strcmp ( pName, ca_name ( chan ) ) != 0 ) { + printf ( "Canonical name for channel was \"%s\"\n", ca_name ( chan ) ); + } + status = ca_clear_channel ( chan ); + SEVCHK ( status, NULL ); +} + +void verifyContextRundownFlush ( const char * pName, unsigned interestLevel ) +{ + unsigned i; + + showProgressBegin ( "verifyContextRundownFlush", interestLevel ); + + for ( i=0u; i < 1000; i++ ) { + const dbr_double_t stim = i; + + { + chid chan; + int status; + status = ca_context_create ( ca_disable_preemptive_callback ); + SEVCHK ( status, "context create failed" ); + + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + /* + * currently in-memory channels cant be used with this test + * !!!! FIX ME, FIX ME, FIX ME, FIX ME !!!! + */ + if ( status != ECA_UNAVAILINSERV ) { + SEVCHK ( status, NULL ); + + status = ca_pend_io( timeoutToPendIO ); + SEVCHK ( status, "channel connect failed" ); + + status = ca_put ( DBR_DOUBLE, chan, & stim ); + SEVCHK ( status, "channel put failed" ); + + status = ca_clear_channel ( chan ); + SEVCHK ( status, NULL ); + } + ca_context_destroy (); + } + + { + chid chan; + int status; + dbr_double_t resp; + status = ca_context_create ( ca_disable_preemptive_callback ); + SEVCHK ( status, "context create failed" ); + + status = ca_create_channel ( pName, 0, 0, 0, & chan ); + SEVCHK ( status, NULL ); + /* + * currently in-memory channels cant be used with this test + * !!!! FIX ME, FIX ME, FIX ME, FIX ME !!!! + */ + if ( status != ECA_UNAVAILINSERV ) { + status = ca_pend_io( timeoutToPendIO ); + SEVCHK ( status, "channel connect failed" ); + + status = ca_get ( DBR_DOUBLE, chan, & resp ); + SEVCHK ( status, "channel get failed" ); + + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, "get, pend io failed" ); + + verify ( stim == resp ); + + status = ca_clear_channel ( chan ); + SEVCHK ( status, NULL ); + } + ca_context_destroy (); + } + + if ( i % 100 == 0 ) { + showProgress ( interestLevel ); + } + } + + showProgressEnd ( interestLevel ); +} + +void verifyContextRundownChanStillExist ( + const char * pName, unsigned interestLevel ) +{ + chid chan[10000]; + int status; + unsigned i; + + showProgressBegin ( "verifyContextRundownChanStillExist", interestLevel ); + + status = ca_context_create ( ca_disable_preemptive_callback ); + SEVCHK ( status, "context create failed" ); + + for ( i = 0; i < NELEMENTS ( chan ); i++ ) { + status = ca_create_channel ( pName, 0, 0, 0, & chan[i] ); + /* + * currently in-memory channels cant be used with this test + * !!!! FIX ME, FIX ME, FIX ME, FIX ME !!!! + */ + if ( status == ECA_UNAVAILINSERV ) { + break; + } + SEVCHK ( status, NULL ); + } + + status = ca_pend_io( timeoutToPendIO ); + SEVCHK ( status, "channel connect failed" ); + + ca_context_destroy (); + + showProgressEnd ( interestLevel ); +} + +int acctst ( const char * pName, unsigned interestLevel, unsigned channelCount, + unsigned repetitionCount, enum ca_preemptive_callback_select select ) +{ + chid chan; + int status; + unsigned i; + appChan *pChans; + unsigned connections; + unsigned maxArrayBytes = 10000000; + + printf ( "CA Client V%s, channel name \"%s\", timeout %g\n", + ca_version (), pName, timeoutToPendIO ); + if ( select == ca_enable_preemptive_callback ) { + printf ( "Preemptive call back is enabled.\n" ); + } + + { + char tmpString[32]; + sprintf ( tmpString, "%u", maxArrayBytes ); + epicsEnvSet ( "EPICS_CA_MAX_ARRAY_BYTES", tmpString ); + } + + /* + * this test creates, and then destroys, a private CA context + */ + multiSubscrDestroyNoLateCallbackTest ( pName, interestLevel ); + + status = ca_context_create ( select ); + SEVCHK ( status, NULL ); + + verifyDisconnect ( pName, interestLevel ); + verifyImmediateTearDown ( pName, select, interestLevel ); + verifyTearDownWhenChannelConnected ( pName, select, interestLevel ); + + verifyDataTypeMacros (); + + connections = ca_get_ioc_connection_count (); + verify ( connections == 0u ); + unequalServerBufferSizeTest ( pName, interestLevel ); + clearChannelInGetCallbackTest ( pName, interestLevel ); + clearChannelInPutCallbackTest ( pName, interestLevel ); + clearChannelInSubscrCallbackTest ( pName, interestLevel ); + monitorAddConnectionCallbackTest ( pName, interestLevel ); + + showProgressBegin ( "connecting to test channel", interestLevel ); + status = ca_search ( pName, & chan ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( timeoutToPendIO ); + SEVCHK ( status, NULL ); + showProgressEnd ( interestLevel ); + + printf ( "native type was %s, native count was %lu\n", + dbf_type_to_text ( ca_field_type ( chan ) ), + ca_element_count ( chan ) ); + + connections = ca_get_ioc_connection_count (); + verify ( connections == 1u || connections == 0u ); + if ( connections == 0u ) { + printf ( "testing with a local channel\n" ); + } + + verifyName ( pName, interestLevel ); + verifyConnectWithDisconnectedChannels ( pName, interestLevel ); + grEnumTest ( chan, interestLevel ); + test_sync_groups ( chan, interestLevel ); + verifyChannelPriorities ( pName, interestLevel ); + verifyTimeStamps ( chan, interestLevel ); + verifyOldPend ( interestLevel ); + exceptionTest ( chan, interestLevel ); + arrayTest ( chan, maxArrayBytes, interestLevel ); + verifyMonitorSubscriptionFlushIO ( chan, interestLevel ); + monitorSubscriptionFirstUpdateTest ( pName, chan, interestLevel ); + ctrlDoubleTest ( chan, interestLevel ); + verifyBlockInPendIO ( chan, interestLevel ); + verifyAnalogIO ( chan, DBR_FLOAT, FLT_MIN, FLT_MAX, + FLT_MIN_EXP, FLT_MAX_EXP, FLT_EPSILON, interestLevel ); + verifyAnalogIO ( chan, DBR_DOUBLE, DBL_MIN, DBL_MAX, + DBL_MIN_EXP, DBL_MAX_EXP, DBL_EPSILON, interestLevel ); + verifyLongIO ( chan, interestLevel ); + verifyShortIO ( chan, interestLevel ); + multiSubscriptionDeleteTest ( chan, interestLevel ); + singleSubscriptionDeleteTest ( chan, interestLevel ); + channelClearWithEventTrafficTest ( pName, interestLevel ); + eventClearAndMultipleMonitorTest ( chan, interestLevel ); + verifyHighThroughputRead ( chan, interestLevel ); + verifyHighThroughputWrite ( chan, interestLevel ); + verifyHighThroughputReadCallback ( chan, interestLevel ); + verifyHighThroughputWriteCallback ( chan, interestLevel ); + verifyBadString ( chan, interestLevel ); + verifyMultithreadSubscr ( pName, interestLevel ); + if ( select != ca_enable_preemptive_callback ) { + fdManagerVerify ( pName, interestLevel ); + } + + /* + * CA pend event delay accuracy test + * (CA asssumes that search requests can be sent + * at least every 25 mS on all supported os) + */ + printf ( "\n" ); + pend_event_delay_test ( 1.0 ); + pend_event_delay_test ( 0.1 ); + pend_event_delay_test ( 0.25 ); + + /* ca_channel_status ( 0 ); */ + ca_client_status ( 0 ); + /* ca_client_status ( 6u ); info about each channel */ + + pChans = calloc ( channelCount, sizeof ( *pChans ) ); + verify ( pChans ); + + for ( i = 0; i < channelCount; i++ ) { + strncpy ( pChans[ i ].name, pName, sizeof ( pChans[ i ].name ) ); + pChans[ i ].name[ sizeof ( pChans[i].name ) - 1 ] = '\0'; + } + + verifyConnectionHandlerConnect ( pChans, channelCount, repetitionCount, interestLevel ); + verifyBlockingConnect ( pChans, channelCount, repetitionCount, interestLevel ); + verifyClear ( pChans, interestLevel ); + + verifyReasonableBeaconPeriod ( chan, interestLevel ); + + /* + * Verify that we can do IO with the new types for ALH + */ +#if 0 + if ( ca_read_access (chan) && ca_write_access (chan) ) { + { + dbr_put_ackt_t acktIn = 1u; + dbr_put_acks_t acksIn = 1u; + struct dbr_stsack_string stsackOut; + + SEVCHK ( ca_put ( DBR_PUT_ACKT, chan, &acktIn ), NULL ); + SEVCHK ( ca_put ( DBR_PUT_ACKS, chan, &acksIn ), NULL ); + SEVCHK ( ca_get ( DBR_STSACK_STRING, chan, &stsackOut ), NULL ); + SEVCHK ( ca_pend_io ( timeoutToPendIO ), NULL ); + } +#endif + + /* test that ca_task_exit () works when there is still one channel remaining */ + /* status = ca_clear_channel ( chan ); */ + /* SEVCHK ( status, NULL ); */ + + caTaskExitTest ( interestLevel ); + + verifyContextRundownFlush ( pName, interestLevel ); + verifyContextRundownChanStillExist ( pName, interestLevel ); + + free ( pChans ); + + printf ( "\nTest Complete\n" ); + + epicsExit ( EXIT_SUCCESS ); + + return 0; +} + + diff --git a/modules/ca/src/client/acctstMain.c b/modules/ca/src/client/acctstMain.c new file mode 100644 index 000000000..4c700a30b --- /dev/null +++ b/modules/ca/src/client/acctstMain.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* 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 "cadef.h" +#include "caDiagnostics.h" + +int main ( int argc, char **argv ) +{ + unsigned progressLoggingLevel; + unsigned channelCount; + unsigned repetitionCount; + enum ca_preemptive_callback_select preempt; + int aBoolean; + + + if ( argc < 2 || argc > 6 ) { + printf ("usage: %s [progress logging level] [channel count] " + "[repetition count] [enable preemptive callback]\n", + argv[0] ); + return 1; + } + + if ( argc >= 3 ) { + progressLoggingLevel = atoi ( argv[2] ); + } + else { + progressLoggingLevel = 0; + } + + if ( argc >= 4 ) { + channelCount = atoi ( argv[3] ); + } + else { + channelCount = 20000; + } + + if ( argc >= 5 ) { + repetitionCount = atoi ( argv[4] ); + } + else { + repetitionCount = 1; + } + + if ( argc >= 6 ) { + aBoolean = atoi ( argv[5] ); + } + else { + aBoolean = 0; + } + if ( aBoolean ) { + preempt = ca_enable_preemptive_callback; + } + else { + preempt = ca_disable_preemptive_callback; + } + + acctst ( argv[1], progressLoggingLevel, channelCount, repetitionCount, preempt ); + + return 0; +} diff --git a/modules/ca/src/client/acctstRegister.cpp b/modules/ca/src/client/acctstRegister.cpp new file mode 100644 index 000000000..e75ed7fe3 --- /dev/null +++ b/modules/ca/src/client/acctstRegister.cpp @@ -0,0 +1,69 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * CA client library diagnostics IOC shell registration + * Authors: + * Jeff Hill + */ + +#include +#include "caDiagnostics.h" + +/* Information needed by iocsh */ +static const iocshArg acctstArg0 = { "channel name", iocshArgString }; +static const iocshArg acctstArg1 = { "interest level", iocshArgInt }; +static const iocshArg acctstArg2 = { "channel count", iocshArgInt }; +static const iocshArg acctstArg3 = { "repetition count", iocshArgInt }; +static const iocshArg acctstArg4 = { "preemptive callback select", iocshArgInt }; + +static const iocshArg *acctstArgs[] = +{ + &acctstArg0, + &acctstArg1, + &acctstArg2, + &acctstArg3, + &acctstArg4 +}; +static const iocshFuncDef acctstFuncDef = {"acctst", 5, acctstArgs}; + + +/* Wrapper called by iocsh, selects the argument types that print needs */ +static void acctstCallFunc(const iocshArgBuf *args) { + if ( args[1].ival < 0 ) { + printf ( "negative interest level not allowed\n" ); + return; + } + if ( args[2].ival < 0 ) { + printf ( "negative channel count not allowed\n" ); + return; + } + if ( args[3].ival < 0 ) { + printf ( "negative repetition count not allowed\n" ); + return; + } + acctst ( + args[0].sval, /* channel name */ + ( unsigned ) args[1].ival, /* interest level */ + ( unsigned ) args[2].ival, /* channel count */ + ( unsigned ) args[3].ival, /* repetition count */ + ( ca_preemptive_callback_select ) args[4].ival ); /* preemptive callback select */ +} + +struct AutoInit { + AutoInit (); +}; + +AutoInit :: AutoInit () +{ + iocshRegister ( &acctstFuncDef, acctstCallFunc ); +} + +AutoInit autoInit; + diff --git a/modules/ca/src/client/addrList.h b/modules/ca/src/client/addrList.h new file mode 100644 index 000000000..c06c8b2bc --- /dev/null +++ b/modules/ca/src/client/addrList.h @@ -0,0 +1,40 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +#ifndef addrListh +#define addrListh + +#include "shareLib.h" +#include "envDefs.h" +#include "osiSock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc void epicsShareAPI configureChannelAccessAddressList + ( struct ELLLIST *pList, SOCKET sock, unsigned short port ); + +epicsShareFunc int epicsShareAPI addAddrToChannelAccessAddressList + ( struct ELLLIST *pList, const ENV_PARAM *pEnv, + unsigned short port, int ignoreNonDefaultPort ); + +epicsShareFunc void epicsShareAPI printChannelAccessAddressList + ( const struct ELLLIST *pList ); + +epicsShareFunc void epicsShareAPI removeDuplicateAddresses + ( struct ELLLIST *pDestList, ELLLIST *pSrcList, int silent); + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef addrListh */ + diff --git a/modules/ca/src/client/autoPtrFreeList.h b/modules/ca/src/client/autoPtrFreeList.h new file mode 100644 index 000000000..7dc73609a --- /dev/null +++ b/modules/ca/src/client/autoPtrFreeList.h @@ -0,0 +1,104 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef autoPtrFreeListh +#define autoPtrFreeListh + +#ifdef epicsExportSharedSymbols +# define autoPtrFreeListh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsFreeList.h" +#include "compilerDependencies.h" + +#ifdef autoPtrFreeListh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +#endif + +template < class T, unsigned N = 0x400, class MUTEX = epicsMutex > +class autoPtrFreeList { +public: + autoPtrFreeList ( tsFreeList < T, N, MUTEX > &, T * ); + ~autoPtrFreeList (); + T & operator * () const; + T * operator -> () const; + T * get () const; + T * release (); +private: + T * p; + tsFreeList < T, N, MUTEX > & freeList; + // not implemented + autoPtrFreeList & operator = ( const autoPtrFreeList & ); + autoPtrFreeList ( const autoPtrFreeList < T, N, MUTEX > & ); +}; + +template < class T, unsigned N, class MUTEX > +inline autoPtrFreeList < T, N, MUTEX >::autoPtrFreeList ( + tsFreeList < T, N, MUTEX > & freeListIn, T * pIn ) : + p ( pIn ), freeList ( freeListIn ) {} + +template < class T, unsigned N, class MUTEX > +inline autoPtrFreeList < T, N, MUTEX >::~autoPtrFreeList () +{ + if ( this->p ) { + this->p->~T(); + // its probably a good idea to require that the class has placement delete + // by calling it during cleanup if the compiler supports it +# if defined ( CXX_PLACEMENT_DELETE ) + T::operator delete ( this->p, this->freeList ); +# else + this->freeList.release ( this->p ); +# endif + } +} + +template < class T, unsigned N, class MUTEX > +inline T & autoPtrFreeList < T, N, MUTEX >::operator * () const +{ + return * this->p; +} + +template < class T, unsigned N, class MUTEX > +inline T * autoPtrFreeList < T, N, MUTEX >::operator -> () const +{ + return this->p; +} + +template < class T, unsigned N, class MUTEX > +inline T * autoPtrFreeList < T, N, MUTEX >::get () const +{ + return this->p; +} + +template < class T, unsigned N, class MUTEX > +inline T * autoPtrFreeList < T, N, MUTEX >::release () +{ + T *pTmp = this->p; + this->p = 0; + return pTmp; +} + +#endif // #ifdef autoPtrFreeListh diff --git a/modules/ca/src/client/autoPtrRecycle.h b/modules/ca/src/client/autoPtrRecycle.h new file mode 100644 index 000000000..173e148b8 --- /dev/null +++ b/modules/ca/src/client/autoPtrRecycle.h @@ -0,0 +1,92 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef autoPtrRecycleh +#define autoPtrRecycleh + +template < class T > +class autoPtrRecycle { +public: + autoPtrRecycle ( + epicsGuard < epicsMutex > &, chronIntIdResTable < baseNMIU > &, + cacRecycle &, T * ); + ~autoPtrRecycle (); + T & operator * () const; + T * operator -> () const; + T * get () const; + T * release (); +private: + T * p; + cacRecycle & r; + chronIntIdResTable < baseNMIU > & ioTable; + epicsGuard < epicsMutex > & guard; + // not implemented + autoPtrRecycle ( const autoPtrRecycle & ); + autoPtrRecycle & operator = ( const autoPtrRecycle & ); +}; + +template < class T > +inline autoPtrRecycle::autoPtrRecycle ( + epicsGuard < epicsMutex > & guardIn, chronIntIdResTable < baseNMIU > & tbl, + cacRecycle & rIn, T * pIn ) : + p ( pIn ), r ( rIn ), ioTable ( tbl ), guard ( guardIn ) {} + +template < class T > +inline autoPtrRecycle::~autoPtrRecycle () +{ + if ( this->p ) { + baseNMIU *pb = this->p; + this->ioTable.remove ( *pb ); + pb->destroy ( this->guard, this->r ); + } +} + +template < class T > +inline T & autoPtrRecycle::operator * () const +{ + return * this->p; +} + +template < class T > +inline T * autoPtrRecycle::operator -> () const +{ + return this->p; +} + +template < class T > +inline T * autoPtrRecycle::get () const +{ + return this->p; +} + +template < class T > +inline T * autoPtrRecycle::release () +{ + T *pTmp = this->p; + this->p = 0; + return pTmp; +} + +#endif // #ifdef autoPtrRecycleh diff --git a/modules/ca/src/client/baseNMIU.cpp b/modules/ca/src/client/baseNMIU.cpp new file mode 100644 index 000000000..20f153b0a --- /dev/null +++ b/modules/ca/src/client/baseNMIU.cpp @@ -0,0 +1,39 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, the Regents of the University of California. + * + * Author: Jeff Hill + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "nciu.h" +#include "netIO.h" + +baseNMIU::~baseNMIU () +{ +} + +void baseNMIU::forceSubscriptionUpdate ( + epicsGuard < epicsMutex > &, nciu & ) +{ +} + + + + diff --git a/modules/ca/src/client/bhe.cpp b/modules/ca/src/client/bhe.cpp new file mode 100644 index 000000000..d6f1796be --- /dev/null +++ b/modules/ca/src/client/bhe.cpp @@ -0,0 +1,358 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "virtualCircuit.h" +#include "bhe.h" + +/* + * set average to -1.0 so that when the next beacon + * occurs we can distinguish between: + * o new server + * o existing server's beacon we are seeing + * for the first time shortly after program + * start up + * + * if creating this in response to a search reply + * and not in response to a beacon then + * we set the beacon time stamp to + * zero (so we can correctly compute the period + * between the 1st and 2nd beacons) + */ +bhe::bhe ( epicsMutex & mutexIn, const epicsTime & initialTimeStamp, + unsigned initialBeaconNumber, const inetAddrID & addr ) : + inetAddrID ( addr ), timeStamp ( initialTimeStamp ), averagePeriod ( - DBL_MAX ), + mutex ( mutexIn ), pIIU ( 0 ), lastBeaconNumber ( initialBeaconNumber ) +{ +# ifdef DEBUG + { + char name[64]; + addr.name ( name, sizeof ( name ) ); + ::printf ( "created beacon entry for %s\n", name ); + } +# endif +} + +bhe::~bhe () +{ +} + +void bhe::beaconAnomalyNotify ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pIIU ) { + this->pIIU->beaconAnomalyNotify ( guard ); + } +} + +#ifdef DEBUG +void bhe::logBeacon ( const char * pDiagnostic, + const double & currentPeriod, + const epicsTime & currentTime ) +{ + if ( this->pIIU ) { + char name[64]; + this->name ( name, sizeof ( name ) ); + char date[64]; + currentTime.strftime ( date, sizeof ( date ), + "%a %b %d %Y %H:%M:%S.%f"); + ::printf ( "%s cp=%g ap=%g %s %s\n", + pDiagnostic, currentPeriod, + this->averagePeriod, name, date ); + } +} +#else +inline void bhe::logBeacon ( const char * /* pDiagnostic */, + const double & /* currentPeriod */, + const epicsTime & /* currentTime */ ) +{ +} +#endif + +#ifdef DEBUG +void bhe::logBeaconDiscard ( unsigned beaconAdvance, + const epicsTime & currentTime ) +{ + if ( this->pIIU ) { + char name[64]; + this->name ( name, sizeof ( name ) ); + char date[64]; + currentTime.strftime ( date, sizeof ( date ), + "%a %b %d %Y %H:%M:%S.%f"); + ::printf ( "bb %u %s %s\n", + beaconAdvance, name, date ); + } +} +#else +void bhe::logBeaconDiscard ( unsigned /* beaconAdvance */, + const epicsTime & /* currentTime */ ) +{ +} +#endif + +/* + * update beacon period + * + * updates beacon period, and looks for beacon anomalies + */ +bool bhe::updatePeriod ( + epicsGuard < epicsMutex > & guard, const epicsTime & programBeginTime, + const epicsTime & currentTime, ca_uint32_t beaconNumber, + unsigned protocolRevision ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + // + // this block is enetered if the beacon was created as a side effect of + // creating a connection and so we dont yet know the first beacon time + // and sequence number + // + if ( this->timeStamp == epicsTime () ) { + if ( CA_V410 ( protocolRevision ) ) { + this->lastBeaconNumber = beaconNumber; + } + + this->beaconAnomalyNotify ( guard ); + + /* + * this is the 1st beacon seen - the beacon time stamp + * was not initialized during BHE create because + * a TCP/IP connection created the beacon. + * (nothing to do but set the beacon time stamp and return) + */ + this->timeStamp = currentTime; + + logBeacon ( "fb", - DBL_MAX, currentTime ); + + return false; + } + + // 1) detect beacon duplications due to redundant routes + // 2) detect lost beacons due to input queue overrun or damage + if ( CA_V410 ( protocolRevision ) ) { + unsigned beaconSeqAdvance; + if ( beaconNumber >= this->lastBeaconNumber ) { + beaconSeqAdvance = beaconNumber - this->lastBeaconNumber; + } + else { + beaconSeqAdvance = ( ca_uint32_max - this->lastBeaconNumber ) + beaconNumber; + } + this->lastBeaconNumber = beaconNumber; + + // throw out sequence numbers just prior to, or the same as, the last one received + // (this situation is probably caused by a temporary duplicate route ) + if ( beaconSeqAdvance == 0 || beaconSeqAdvance > ca_uint32_max - 256 ) { + logBeaconDiscard ( beaconSeqAdvance, currentTime ); + return false; + } + + // throw out sequence numbers that jump forward by only a few numbers + // (this situation is probably caused by a duplicate route + // or a beacon due to input queue overun) + if ( beaconSeqAdvance > 1 && beaconSeqAdvance < 4 ) { + logBeaconDiscard ( beaconSeqAdvance, currentTime ); + return false; + } + } + + // compute the beacon period (if we have seen at least two beacons) + bool netChange = false; + double currentPeriod = currentTime - this->timeStamp; + + if ( this->averagePeriod < 0.0 ) { + double totalRunningTime; + + this->beaconAnomalyNotify ( guard ); + + /* + * this is the 2nd beacon seen. We cant tell about + * the change in period at this point so we just + * initialize the average period and return. + */ + this->averagePeriod = currentPeriod; + + logBeacon ( "fp", currentPeriod, currentTime ); + + + /* + * ignore beacons seen for the first time shortly after + * init, but do not ignore beacons arriving with a short + * period because the IOC was rebooted soon after the + * client starts up. + */ + totalRunningTime = this->timeStamp - programBeginTime; + if ( currentPeriod <= totalRunningTime ) { + netChange = true; + } + } + else { + + /* + * Is this an IOC seen because of a restored + * network segment? + * + * It may be possible to get false triggers here + * if the client is busy, but this does not cause + * problems because the echo response will tell us + * that the server is available + */ + if ( currentPeriod >= this->averagePeriod * 1.25 ) { + + /* + * trigger on any missing beacon + * if connected to this server + */ + this->beaconAnomalyNotify ( guard ); + + if ( currentPeriod >= this->averagePeriod * 3.25 ) { + /* + * trigger on any 3 contiguous missing beacons + * if not connected to this server + */ + netChange = true; + } + logBeacon ( "bah", currentPeriod, currentTime ); + } + + /* + * Is this an IOC seen because of an IOC reboot + * (beacon come at a higher rate just after the + * IOC reboots). Lower tolarance here because we + * dont have to worry about lost beacons. + * + * It may be possible to get false triggers here + * if the client is busy, but this does not cause + * problems because the echo response will tell us + * that the server is available + */ + else if ( currentPeriod <= this->averagePeriod * 0.80 ) { + this->beaconAnomalyNotify ( guard ); + netChange = true; + logBeacon ( "bal", currentPeriod, currentTime ); + } + else if ( this->pIIU ) { + // update state of health for active virtual circuits + // if the beacon looks ok + this->pIIU->beaconArrivalNotify ( guard ); + logBeacon ( "vb", currentPeriod, currentTime ); + } + + // update a running average period + this->averagePeriod = currentPeriod * 0.125 + + this->averagePeriod * 0.875; + } + + this->timeStamp = currentTime; + + return netChange; +} + +void bhe::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->show ( guard, level ); +} + +void bhe::show ( epicsGuard < epicsMutex > &, unsigned level ) const +{ + char host [64]; + this->name ( host, sizeof ( host ) ); + if ( this->averagePeriod == -DBL_MAX ) { + ::printf ( "CA beacon hash entry for %s \n", + host ); + } + else { + ::printf ( "CA beacon hash entry for %s with period estimate %f\n", + host, this->averagePeriod ); + } + if ( level > 0u ) { + char date[64]; + this->timeStamp.strftime ( date, sizeof ( date ), "%a %b %d %Y %H:%M:%S"); + ::printf ( "\tbeacon number %u, on %s\n", + this->lastBeaconNumber, date ); + } +} + +double bhe::period ( epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->averagePeriod; +} + +epicsTime bhe::updateTime ( epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->timeStamp; +} + +void bhe::registerIIU ( + epicsGuard < epicsMutex > & guard, tcpiiu & iiu ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->pIIU = & iiu; +} + +void bhe::unregisterIIU ( + epicsGuard < epicsMutex > & guard, tcpiiu & iiu ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->pIIU == & iiu ) { + this->pIIU = 0; + this->timeStamp = epicsTime(); + this->averagePeriod = - DBL_MAX; + logBeacon ( "ui", this->averagePeriod, epicsTime::getCurrent () ); + } +} + +void bhe::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 * bheFreeStore::allocate ( size_t size ) +{ + return freeList.allocate ( size ); +} + +void bheFreeStore::release ( void * pCadaver ) +{ + freeList.release ( pCadaver ); +} + +bheMemoryManager::~bheMemoryManager () {} + + diff --git a/modules/ca/src/client/bhe.h b/modules/ca/src/client/bhe.h new file mode 100644 index 000000000..4da95202a --- /dev/null +++ b/modules/ca/src/client/bhe.h @@ -0,0 +1,122 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#ifndef bheh +#define bheh + +#ifdef epicsExportSharedSymbols +# define bhehEpicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsDLList.h" +#include "tsFreeList.h" +#include "epicsTime.h" +#include "compilerDependencies.h" + +#ifdef bhehEpicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "inetAddrID.h" +#include "caProto.h" + +class tcpiiu; +class bheMemoryManager; + +// using a pure abstract wrapper class around the free list avoids +// Tornado 2.0.1 GNU compiler bugs +class epicsShareClass bheMemoryManager { +public: + virtual ~bheMemoryManager (); + virtual void * allocate ( size_t ) = 0; + virtual void release ( void * ) = 0; +}; + +class bhe : public tsSLNode < bhe >, public inetAddrID { +public: + epicsShareFunc bhe ( + epicsMutex &, const epicsTime & initialTimeStamp, + unsigned initialBeaconNumber, const inetAddrID & addr ); + epicsShareFunc ~bhe (); + epicsShareFunc bool updatePeriod ( + epicsGuard < epicsMutex > &, + const epicsTime & programBeginTime, + const epicsTime & currentTime, ca_uint32_t beaconNumber, + unsigned protocolRevision ); + epicsShareFunc double period ( epicsGuard < epicsMutex > & ) const; + epicsShareFunc epicsTime updateTime ( epicsGuard < epicsMutex > & ) const; + epicsShareFunc void show ( unsigned level ) const; + epicsShareFunc void show ( epicsGuard < epicsMutex > &, unsigned /* level */ ) const; + epicsShareFunc void registerIIU ( epicsGuard < epicsMutex > &, tcpiiu & ); + epicsShareFunc void unregisterIIU ( epicsGuard < epicsMutex > &, tcpiiu & ); + epicsShareFunc void * operator new ( size_t size, bheMemoryManager & ); +#ifdef CXX_PLACEMENT_DELETE + epicsShareFunc void operator delete ( void *, bheMemoryManager & ); +#endif +private: + epicsTime timeStamp; + double averagePeriod; + epicsMutex & mutex; + tcpiiu * pIIU; + ca_uint32_t lastBeaconNumber; + void beaconAnomalyNotify ( epicsGuard < epicsMutex > & ); + void logBeacon ( const char * pDiagnostic, + const double & currentPeriod, + const epicsTime & currentTime ); + void logBeaconDiscard ( unsigned beaconAdvance, + const epicsTime & currentTime ); + bhe ( const bhe & ); + bhe & operator = ( const bhe & ); + epicsShareFunc void operator delete ( void * ); +}; + +// using a wrapper class around the free list avoids +// Tornado 2.0.1 GNU compiler bugs +class bheFreeStore : public bheMemoryManager { +public: + bheFreeStore () {} + void * allocate ( size_t ); + void release ( void * ); +private: + tsFreeList < bhe, 0x100 > freeList; + bheFreeStore ( const bheFreeStore & ); + bheFreeStore & operator = ( const bheFreeStore & ); +}; + +inline void * bhe::operator new ( size_t size, + bheMemoryManager & mgr ) +{ + return mgr.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void bhe::operator delete ( void * pCadaver, + bheMemoryManager & mgr ) +{ + mgr.release ( pCadaver ); +} +#endif + +#endif // ifdef bheh + + diff --git a/modules/ca/src/client/ca.rc b/modules/ca/src/client/ca.rc new file mode 100644 index 000000000..b1f87acb4 --- /dev/null +++ b/modules/ca/src/client/ca.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","Channel Access Library for EPICS\0" + VALUE "CompanyName", "The EPICS collaboration\0" + VALUE "FileDescription", "Channel Access Library\0" + VALUE "FileVersion", EPICS_VERSION_STRING "\0" + VALUE "InternalName", "ca\0" + VALUE "LegalCopyright", "Copyright (C) Univ. of California, Univ. of Chicago\0" + VALUE "OriginalFilename", "ca.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/ca/src/client/caConnTest.cpp b/modules/ca/src/client/caConnTest.cpp new file mode 100644 index 000000000..21077398a --- /dev/null +++ b/modules/ca/src/client/caConnTest.cpp @@ -0,0 +1,109 @@ +/*************************************************************************\ +* 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 + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "cadef.h" +#include "epicsTime.h" + +static unsigned channelCount = 0u; +static unsigned connCount = 0u; +static bool subsequentConnect = false; + +epicsTime begin; + +extern "C" void caConnTestConnHandler ( struct connection_handler_args args ) +{ + if ( args.op == CA_OP_CONN_UP ) { + if ( connCount == 0u ) { + if ( subsequentConnect ) { + printf ("the first channel connected\n"); + begin = epicsTime::getCurrent (); + } + } + connCount++; + // printf ( "." ); + // fflush ( stdout ); + if ( connCount == channelCount ) { + epicsTime current = epicsTime::getCurrent (); + double delay = current - begin; + printf ( "all channels connected after %f sec ( %f sec per channel)\n", + delay, delay / channelCount ); + } + } + else if ( args.op == CA_OP_CONN_DOWN ) { + if ( connCount == channelCount ) { + printf ( "channels are disconnected\n" ); + subsequentConnect = true; + } + connCount--; + if ( connCount == 0u ) { + printf ( "all channels are disconnected\n" ); + } + } + else { + assert ( 0 ); + } +} + +void caConnTest ( const char *pNameIn, unsigned channelCountIn, double delayIn ) +{ + unsigned iteration = 0u; + int status; + unsigned i; + chid *pChans; + + channelCount = channelCountIn; + + pChans = new chid [channelCount]; + + while ( 1 ) { + connCount = 0u; + subsequentConnect = false; + begin = epicsTime::getCurrent (); + + printf ( "initializing CA client library\n" ); + + status = ca_task_initialize(); + SEVCHK ( status, "CA init failed" ); + + printf ( "creating channels\n" ); + + for ( i = 0u; i < channelCount; i++ ) { + status = ca_search_and_connect ( pNameIn, + &pChans[i], caConnTestConnHandler, 0 ); + SEVCHK ( status, "CA search problems" ); + } + + printf ( "all channels were created\n" ); + + ca_pend_event ( delayIn ); + + if ( iteration & 1 ) { + for ( i = 0u; i < channelCount; i++ ) { + status = ca_clear_channel ( pChans[i] ); + SEVCHK ( status, "ca_clear_channel() problems" ); + } + printf ( "all channels were destroyed\n" ); + } + + printf ( "shutting down CA client library\n" ); + + status = ca_task_exit (); + SEVCHK ( status, "task exit problems" ); + + iteration++; + } + + //delete [] pChans; +} diff --git a/modules/ca/src/client/caConnTestMain.cpp b/modules/ca/src/client/caConnTestMain.cpp new file mode 100644 index 000000000..f3985801a --- /dev/null +++ b/modules/ca/src/client/caConnTestMain.cpp @@ -0,0 +1,45 @@ +/*************************************************************************\ +* 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 "caDiagnostics.h" + +int main ( int argc, char **argv ) +{ + double delay = 60.0 * 5.0; + unsigned count = 2000; + + if ( argc < 2 || argc > 4 ) { + printf ( "usage: %s < channel name > [ < count > ] [ < delay sec > ]\n", argv[0] ); + return -1; + } + + if ( argc >= 3 ) { + int nConverted = sscanf ( argv[2], "%u", &count ); + if ( nConverted != 1 ) { + printf ( "conversion failed, changing channel count arg \"%s\" to %u\n", + argv[1], count ); + } + } + + if ( argc >= 4 ) { + int nConverted = epicsScanDouble( argv[3], &delay ); + if ( nConverted != 1 ) { + printf ( "conversion failed, changing delay arg \"%s\" to %f\n", + argv[2], delay ); + } + } + + caConnTest ( argv[1], count, delay ); + + return 0; +} diff --git a/modules/ca/src/client/caDiagnostics.h b/modules/ca/src/client/caDiagnostics.h new file mode 100644 index 000000000..90221e1ed --- /dev/null +++ b/modules/ca/src/client/caDiagnostics.h @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef caDiagnosticsh +#define caDiagnosticsh + +#include "cadef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum appendNumberFlag {appendNumber, dontAppendNumber}; +int catime ( const char *channelName, unsigned channelCount, enum appendNumberFlag appNF ); + +int acctst ( const char *pname, unsigned logggingInterestLevel, + unsigned channelCount, unsigned repetitionCount, + enum ca_preemptive_callback_select select ); + +#define CATIME_OK 0 +#define CATIME_ERROR -1 + +#ifdef __cplusplus +} +#endif + +void caConnTest ( const char *pNameIn, unsigned channelCountIn, double delayIn ); + +#endif /* caDiagnosticsh */ + + diff --git a/modules/ca/src/client/caEventRate.cpp b/modules/ca/src/client/caEventRate.cpp new file mode 100644 index 000000000..8beb16333 --- /dev/null +++ b/modules/ca/src/client/caEventRate.cpp @@ -0,0 +1,139 @@ +/*************************************************************************\ +* 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 "cadef.h" +#include "dbDefs.h" +#include "epicsTime.h" +#include "errlog.h" + +/* + * event_handler() + */ +extern "C" void eventCallBack ( struct event_handler_args args ) +{ + unsigned *pCount = static_cast < unsigned * > ( args.usr ); + (*pCount)++; +} + +/* + * caEventRate () + */ +void caEventRate ( const char *pName, unsigned count ) +{ + static const double initialSamplePeriod = 1.0; + static const double maxSamplePeriod = 60.0 * 5.0; + unsigned eventCount = 0u; + + chid * pChidTable = new chid [ count ]; + + { + printf ( "Connecting to CA Channel \"%s\" %u times.", + pName, count ); + fflush ( stdout ); + + epicsTime begin = epicsTime::getCurrent (); + for ( unsigned i = 0u; i < count; i++ ) { + int status = ca_search ( pName, & pChidTable[i] ); + SEVCHK ( status, NULL ); + } + + int status = ca_pend_io ( 10000.0 ); + if ( status != ECA_NORMAL ) { + fprintf ( stderr, " not found.\n" ); + return; + } + epicsTime end = epicsTime::getCurrent (); + + printf ( " done(%f sec).\n", end - begin ); + } + + { + printf ( "Subscribing %u times.", count ); + fflush ( stdout ); + + epicsTime begin = epicsTime::getCurrent (); + for ( unsigned i = 0u; i < count; i++ ) { + int addEventStatus = ca_add_event ( DBR_FLOAT, + pChidTable[i], eventCallBack, &eventCount, NULL); + SEVCHK ( addEventStatus, __FILE__ ); + } + + int status = ca_flush_io (); + SEVCHK ( status, __FILE__ ); + + epicsTime end = epicsTime::getCurrent (); + + printf ( " done(%f sec).\n", end - begin ); + } + + { + printf ( "Waiting for initial value events." ); + fflush ( stdout ); + + // let the first one go by + epicsTime begin = epicsTime::getCurrent (); + while ( eventCount < count ) { + int status = ca_pend_event ( 0.01 ); + if ( status != ECA_TIMEOUT ) { + SEVCHK ( status, NULL ); + } + } + epicsTime end = epicsTime::getCurrent (); + + printf ( " done(%f sec).\n", end - begin ); + } + + double samplePeriod = initialSamplePeriod; + double X = 0.0; + double XX = 0.0; + unsigned N = 0u; + while ( true ) { + unsigned nEvents, lastEventCount, curEventCount; + + epicsTime beginPend = epicsTime::getCurrent (); + lastEventCount = eventCount; + int status = ca_pend_event ( samplePeriod ); + curEventCount = eventCount; + epicsTime endPend = epicsTime::getCurrent (); + if ( status != ECA_TIMEOUT ) { + SEVCHK ( status, NULL ); + } + + if ( curEventCount >= lastEventCount ) { + nEvents = curEventCount - lastEventCount; + } + else { + nEvents = ( UINT_MAX - lastEventCount ) + curEventCount + 1u; + } + + N++; + + double period = endPend - beginPend; + double Hz = nEvents / period; + + X += Hz; + XX += Hz * Hz; + + double mean = X / N; + double stdDev = sqrt ( XX / N - mean * mean ); + + printf ( "CA Event Rate (Hz): current %g mean %g std dev %g\n", + Hz, mean, stdDev ); + + if ( samplePeriod < maxSamplePeriod ) { + samplePeriod += samplePeriod; + } + } +} + diff --git a/modules/ca/src/client/caEventRateMain.cpp b/modules/ca/src/client/caEventRateMain.cpp new file mode 100644 index 000000000..725e66102 --- /dev/null +++ b/modules/ca/src/client/caEventRateMain.cpp @@ -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 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 + +void caEventRate ( const char *pName, unsigned count ); + +int main ( int argc, char **argv ) +{ + if ( argc < 2 || argc > 3 ) { + fprintf ( stderr, "usage: %s < PV name > [subscription count]\n", argv[0] ); + return 0; + } + + unsigned count; + if ( argc == 3 ) { + int status = sscanf ( argv[2], " %u ", & count ); + if ( status != 1 ) { + fprintf ( stderr, "expected unsigned integer 2nd argument\n" ); + return 0; + } + } + else { + count = 1; + } + + caEventRate ( argv[1], count ); + + return 0; +} diff --git a/modules/ca/src/client/caProto.h b/modules/ca/src/client/caProto.h new file mode 100644 index 000000000..781c89b34 --- /dev/null +++ b/modules/ca/src/client/caProto.h @@ -0,0 +1,187 @@ +/*************************************************************************\ +* 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 __CAPROTO__ +#define __CAPROTO__ + +#define capStrOf(A) #A +#define capStrOfX(A) capStrOf ( A ) + +/* + * CA protocol revision + * TCP/UDP port number (bumped each major protocol change) + */ +#define CA_MAJOR_PROTOCOL_REVISION 4 +#define CA_VERSION_STRING( MINOR_REVISION ) \ +( capStrOfX ( CA_MAJOR_PROTOCOL_REVISION ) "." capStrOfX ( MINOR_REVISION ) ) +#define CA_UKN_MINOR_VERSION 0u /* unknown minor version */ +#define CA_MINIMUM_SUPPORTED_VERSION 4u +# define CA_VSUPPORTED(MINOR) ((MINOR)>=CA_MINIMUM_SUPPORTED_VERSION) +# define CA_V41(MINOR) ((MINOR)>=1u) +# define CA_V42(MINOR) ((MINOR)>=2u) +# define CA_V43(MINOR) ((MINOR)>=3u) +# define CA_V44(MINOR) ((MINOR)>=4u) +# define CA_V45(MINOR) ((MINOR)>=5u) +# define CA_V46(MINOR) ((MINOR)>=6u) +# define CA_V47(MINOR) ((MINOR)>=7u) +# define CA_V48(MINOR) ((MINOR)>=8u) +# define CA_V49(MINOR) ((MINOR)>=9u) /* large arrays, dispatch priorities */ +# define CA_V410(MINOR) ((MINOR)>=10u) /* beacon counter */ +# define CA_V411(MINOR) ((MINOR)>=11u) /* sequence numbers in UDP version command */ +# define CA_V412(MINOR) ((MINOR)>=12u) /* TCP-based search requests */ +# define CA_V413(MINOR) ((MINOR)>=13u) /* Allow zero length in requests. */ + +/* + * These port numbers are only used if the CA repeater and + * CA server port numbers cant be obtained from the EPICS + * environment variables "EPICS_CA_REPEATER_PORT" and + * "EPICS_CA_SERVER_PORT" + */ +#define CA_PORT_BASE IPPORT_USERRESERVED + 56U +#define CA_SERVER_PORT (CA_PORT_BASE+CA_MAJOR_PROTOCOL_REVISION*2u) +#define CA_REPEATER_PORT (CA_PORT_BASE+CA_MAJOR_PROTOCOL_REVISION*2u+1u) + +/* + * 1500 (max of ethernet and 802.{2,3} MTU) - 20(IP) - 8(UDP) + * (the MTU of Ethernet is currently independent of its speed varient) + */ +#define ETHERNET_MAX_UDP ( 1500u - 20u - 8u ) +#define MAX_UDP_RECV ( 0xffff + 16u ) /* allow large frames to be received in the future */ +#define MAX_UDP_SEND 1024u /* original MAX_UDP */ +#define MAX_TCP ( 1024 * 16u ) /* so waveforms fit */ +#define MAX_MSG_SIZE ( MAX_TCP ) /* the larger of tcp and udp max */ + +#define CA_PROTO_PRIORITY_MIN 0u +#define CA_PROTO_PRIORITY_MAX 99u + +/* + * architecture independent types + * + * (so far this works on all archs we have ported to) + */ +typedef unsigned char ca_uint8_t; +typedef unsigned short ca_uint16_t; +typedef unsigned int ca_uint32_t; +typedef float ca_float32_t; +typedef ca_uint32_t caResId; + +#define ca_uint32_max 0xffffffff + + /* values for m_cmmd */ +#define CA_PROTO_VERSION 0u /* set minor version and priority (used to be NOOP cmd) */ +#define CA_PROTO_EVENT_ADD 1u /* add an event */ +#define CA_PROTO_EVENT_CANCEL 2u /* cancel an event */ +#define CA_PROTO_READ 3u /* read and return a channel value*/ +#define CA_PROTO_WRITE 4u /* write a channel value */ +#define CA_PROTO_SNAPSHOT 5u /* snapshot of the system */ +#define CA_PROTO_SEARCH 6u /* IOC channel search */ +#define CA_PROTO_BUILD 7u /* build - obsolete */ +#define CA_PROTO_EVENTS_OFF 8u /* flow control */ +#define CA_PROTO_EVENTS_ON 9u /* flow control */ +#define CA_PROTO_READ_SYNC 10u /* purge old reads */ +#define CA_PROTO_ERROR 11u /* an operation failed */ +#define CA_PROTO_CLEAR_CHANNEL 12u /* free chan resources */ +#define CA_PROTO_RSRV_IS_UP 13u /* CA server has joined the net */ +#define CA_PROTO_NOT_FOUND 14u /* channel not found */ +#define CA_PROTO_READ_NOTIFY 15u /* add a one shot event */ +#define CA_PROTO_READ_BUILD 16u /* read and build - obsolete */ +#define REPEATER_CONFIRM 17u /* registration confirmation */ +#define CA_PROTO_CREATE_CHAN 18u /* client creates channel in server */ +#define CA_PROTO_WRITE_NOTIFY 19u /* notify after write chan value */ +#define CA_PROTO_CLIENT_NAME 20u /* CA V4.1 identify client */ +#define CA_PROTO_HOST_NAME 21u /* CA V4.1 identify client */ +#define CA_PROTO_ACCESS_RIGHTS 22u /* CA V4.2 asynch access rights chg */ +#define CA_PROTO_ECHO 23u /* CA V4.3 connection verify */ +#define REPEATER_REGISTER 24u /* register for repeater fan out */ +#define CA_PROTO_SIGNAL 25u /* knock the server out of select */ +#define CA_PROTO_CREATE_CH_FAIL 26u /* unable to create chan resource in server */ +#define CA_PROTO_SERVER_DISCONN 27u /* server deletes PV (or channel) */ + +#define CA_PROTO_LAST_CMMD CA_PROTO_SERVER_DISCONN + +/* + * for use with search and not_found (if search fails and + * its not a broadcast tell the client to look elesewhere) + */ +#define DOREPLY 10u +#define DONTREPLY 5u + +/* + * for use with the m_dataType field in UDP messages emitted by servers + */ +#define sequenceNoIsValid 1 + +/* size of object in bytes rounded up to nearest oct word */ +#define OCT_ROUND(A) (((A)+7)/8) +#define OCT_SIZEOF(A) (OCT_ROUND(sizeof(A))) + +/* size of object in bytes rounded up to nearest long word */ +#define QUAD_ROUND(A) ((A)+3)/4) +#define QUAD_SIZEOF(A) (QUAD_ROUND(sizeof(A))) + +/* size of object in bytes rounded up to nearest short word */ +#define BI_ROUND(A) (((A)+1)/2) +#define BI_SIZEOF(A) (BI_ROUND(sizeof(A))) + +/* + * For communicating access rights to the clients + * + * (placed in m_available hdr field of CA_PROTO_ACCESS_RIGHTS cmmd + */ +#define CA_PROTO_ACCESS_RIGHT_READ (1u<<0u) +#define CA_PROTO_ACCESS_RIGHT_WRITE (1u<<1u) + +/* + * All structures passed in the protocol must have individual + * fields aligned on natural boundaries. + * + * NOTE: all structures declared in this file must have a + * byte count which is evenly divisible by 8 matching + * the largest atomic data type in db_access.h. + */ +#define CA_MESSAGE_ALIGN(A) (OCT_ROUND(A)<<3u) + +/* + * the common part of each message sent/recv by the + * CA server. + */ +typedef struct ca_hdr { + ca_uint16_t m_cmmd; /* operation to be performed */ + ca_uint16_t m_postsize; /* size of payload */ + ca_uint16_t m_dataType; /* operation data type */ + ca_uint16_t m_count; /* operation data count */ + ca_uint32_t m_cid; /* channel identifier */ + ca_uint32_t m_available; /* protocol stub dependent */ +} caHdr; + +/* + * for monitor (event) message extension + */ +struct mon_info { + ca_float32_t m_lval; /* low delta */ + ca_float32_t m_hval; /* high delta */ + ca_float32_t m_toval; /* period btween samples */ + ca_uint16_t m_mask; /* event select mask */ + ca_uint16_t m_pad; /* extend to 32 bits */ +}; + +/* + * PV names greater than this length assumed to be invalid + */ +#define unreasonablePVNameSize 500u + +#endif /* __CAPROTO__ */ + diff --git a/modules/ca/src/client/caRepeater.cpp b/modules/ca/src/client/caRepeater.cpp new file mode 100644 index 000000000..9879af2b4 --- /dev/null +++ b/modules/ca/src/client/caRepeater.cpp @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * + * CA UDP repeater standalone executable + * + * Author: Jeff Hill + * Date: 3-27-90 + * + * PURPOSE: + * Broadcasts fan out over the LAN, but old IP kernels do not allow + * two processes on the same machine to get the same broadcast + * (and modern IP kernels do not allow two processes on the same machine + * to receive the same unicast). + * + * This code fans out UDP messages sent to the CA repeater port + * to all CA client processes that have subscribed. + * + * NOTES: + * + * see repeater.c + * + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "epicsAssert.h" +#include "udpiiu.h" + +int main() +{ + ca_repeater (); + return ( 0 ); +} + diff --git a/modules/ca/src/client/caRepeater.service@ b/modules/ca/src/client/caRepeater.service@ new file mode 100644 index 000000000..ee50305a1 --- /dev/null +++ b/modules/ca/src/client/caRepeater.service@ @@ -0,0 +1,25 @@ +# +# Linux systemd service file for the EPICS CA Repeater +# +# To install this file, as root: +# cp caRepeater.service /etc/systemd/system +# chmod 664 /etc/systemd/system/caRepeater.service +# systemctl daemon-reload +# systemctl enable caRepeater +# systemctl start caRepeater +# +# To check the status: +# systemctl status caRepeater + +[Unit] +Description=EPICS CA Repeater +Requires=network.target +After=network.target + +[Service] +ExecStart=@INSTALL_BIN@/caRepeater +Restart=always +User=daemon + +[Install] +WantedBy=multi-user.target diff --git a/modules/ca/src/client/caServerID.h b/modules/ca/src/client/caServerID.h new file mode 100644 index 000000000..08bfdd5df --- /dev/null +++ b/modules/ca/src/client/caServerID.h @@ -0,0 +1,88 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#ifndef caServerIDh +#define caServerIDh + +#include "osiSock.h" +#include "resourceLib.h" +#include "caProto.h" + +class caServerID { +public: + caServerID ( const struct sockaddr_in & addrIn, unsigned priority ); + bool operator == ( const caServerID & ) const; + resTableIndex hash () const; + osiSockAddr address () const; + unsigned priority () const; +private: + struct sockaddr_in addr; + ca_uint8_t pri; +}; + +inline caServerID::caServerID ( + const struct sockaddr_in & addrIn, unsigned priorityIn ) : + addr ( addrIn ), pri ( static_cast ( priorityIn ) ) +{ + assert ( priorityIn <= 0xff ); +} + +inline bool caServerID::operator == ( const caServerID & rhs ) const +{ + if ( this->addr.sin_addr.s_addr == rhs.addr.sin_addr.s_addr && + this->addr.sin_port == rhs.addr.sin_port && + this->pri == rhs.pri ) { + return true; + } + return false; +} + +inline resTableIndex caServerID::hash () const +{ + // start with a very small server table to speed + // up the flush traverse for the frequent case - + // a small numbers of servers + const unsigned caServerMinIndexBitWidth = 2u; + const unsigned caServerMaxIndexBitWidth = 32u; + + unsigned index; + index = this->addr.sin_addr.s_addr; + index ^= this->addr.sin_port; + index ^= this->addr.sin_port >> 8u; + index ^= this->pri; + return integerHash ( caServerMinIndexBitWidth, + caServerMaxIndexBitWidth, index ); +} + +inline osiSockAddr caServerID::address () const +{ + osiSockAddr tmp; + tmp.ia = this->addr; + return tmp; +} + +inline unsigned caServerID::priority () const +{ + return this->pri; +} + +#endif // ifdef caServerID + + diff --git a/modules/ca/src/client/caVersion.h b/modules/ca/src/client/caVersion.h new file mode 100644 index 000000000..cdae17dce --- /dev/null +++ b/modules/ca/src/client/caVersion.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* 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 CAVERSION_H +#define CAVERSION_H + +#include +#include + +#ifndef VERSION_INT +# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P)) +#endif + +/* include generated headers with: + * EPICS_CA_MAJOR_VERSION + * EPICS_CA_MINOR_VERSION + * EPICS_CA_MAINTENANCE_VERSION + * EPICS_CA_DEVELOPMENT_FLAG + */ +#include "caVersionNum.h" + +#define CA_VERSION_INT VERSION_INT(EPICS_CA_MAJOR_VERSION, EPICS_CA_MINOR_VERSION, EPICS_CA_MAINTENANCE_VERSION, 0) + +#endif // CAVERSION_H diff --git a/modules/ca/src/client/caVersionNum.h@ b/modules/ca/src/client/caVersionNum.h@ new file mode 100644 index 000000000..26ce6e1af --- /dev/null +++ b/modules/ca/src/client/caVersionNum.h@ @@ -0,0 +1,7 @@ +#ifndef CAVERSION_H +# error include caVersion.h, not this header +#endif +#define EPICS_CA_MAJOR_VERSION @EPICS_CA_MAJOR_VERSION@ +#define EPICS_CA_MINOR_VERSION @EPICS_CA_MINOR_VERSION@ +#define EPICS_CA_MAINTENANCE_VERSION @EPICS_CA_MAINTENANCE_VERSION@ +#define EPICS_CA_DEVELOPMENT_FLAG @EPICS_CA_DEVELOPMENT_FLAG@ diff --git a/modules/ca/src/client/ca_client_context.cpp b/modules/ca/src/client/ca_client_context.cpp new file mode 100644 index 000000000..2bb3e24a8 --- /dev/null +++ b/modules/ca/src/client/ca_client_context.cpp @@ -0,0 +1,811 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifdef _MSC_VER +# pragma warning(disable:4355) +#endif + +#include +#include // vxWorks 6.0 requires this include +#include + +#include "epicsExit.h" +#include "errlog.h" +#include "locationException.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" +#include "cac.h" + +epicsShareDef epicsThreadPrivateId caClientCallbackThreadId; + +static epicsThreadOnceId cacOnce = EPICS_THREAD_ONCE_INIT; + +const unsigned ca_client_context :: flushBlockThreshold = 0x58000; + +extern "C" void cacExitHandler ( void *) +{ + epicsThreadPrivateDelete ( caClientCallbackThreadId ); + caClientCallbackThreadId = 0; + delete ca_client_context::pDefaultServiceInstallMutex; +} + +// runs once only for each process +extern "C" void cacOnceFunc ( void * ) +{ + caClientCallbackThreadId = epicsThreadPrivateCreate (); + assert ( caClientCallbackThreadId ); + ca_client_context::pDefaultServiceInstallMutex = newEpicsMutex; + epicsAtExit ( cacExitHandler,0 ); +} + +extern epicsThreadPrivateId caClientContextId; + +cacService * ca_client_context::pDefaultService = 0; +epicsMutex * ca_client_context::pDefaultServiceInstallMutex; + +ca_client_context::ca_client_context ( bool enablePreemptiveCallback ) : + createdByThread ( epicsThreadGetIdSelf () ), + ca_exception_func ( 0 ), ca_exception_arg ( 0 ), + pVPrintfFunc ( errlogVprintf ), fdRegFunc ( 0 ), fdRegArg ( 0 ), + pndRecvCnt ( 0u ), ioSeqNo ( 0u ), callbackThreadsPending ( 0u ), + localPort ( 0 ), fdRegFuncNeedsToBeCalled ( false ), + noWakeupSincePend ( true ) +{ + static const unsigned short PORT_ANY = 0u; + + if ( ! osiSockAttach () ) { + throwWithLocation ( noSocket () ); + } + + epicsThreadOnce ( & cacOnce, cacOnceFunc, 0 ); + { + epicsGuard < epicsMutex > guard ( *ca_client_context::pDefaultServiceInstallMutex ); + if ( ca_client_context::pDefaultService ) { + this->pServiceContext.reset ( + & ca_client_context::pDefaultService->contextCreate ( + this->mutex, this->cbMutex, *this ) ); + } + else { + this->pServiceContext.reset ( new cac ( this->mutex, this->cbMutex, *this ) ); + } + } + + this->sock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if ( this->sock == INVALID_SOCKET ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + this->printFormated ( + "ca_client_context: unable to create " + "datagram socket because = \"%s\"\n", + sockErrBuf ); + throwWithLocation ( noSocket () ); + } + + { + osiSockIoctl_t yes = true; + int status = socket_ioctl ( this->sock, + FIONBIO, & yes); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( this->sock ); + this->printFormated ( + "%s: non blocking IO set fail because \"%s\"\n", + __FILE__, sockErrBuf ); + throwWithLocation ( noSocket () ); + } + } + + // force a bind to an unconstrained address so we can obtain + // the local port number below + { + osiSockAddr addr; + memset ( (char *)&addr, 0 , sizeof ( addr ) ); + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + addr.ia.sin_port = htons ( PORT_ANY ); + int status = bind (this->sock, &addr.sa, sizeof (addr) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy (this->sock); + this->printFormated ( + "CAC: unable to bind to an unconstrained " + "address because = \"%s\"\n", + sockErrBuf ); + throwWithLocation ( noSocket () ); + } + } + + { + osiSockAddr tmpAddr; + osiSocklen_t saddr_length = sizeof ( tmpAddr ); + int status = getsockname ( this->sock, & tmpAddr.sa, & saddr_length ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( this->sock ); + this->printFormated ( "CAC: getsockname () error was \"%s\"\n", sockErrBuf ); + throwWithLocation ( noSocket () ); + } + if ( tmpAddr.sa.sa_family != AF_INET) { + epicsSocketDestroy ( this->sock ); + this->printFormated ( "CAC: UDP socket was not inet addr family\n" ); + throwWithLocation ( noSocket () ); + } + this->localPort = htons ( tmpAddr.ia.sin_port ); + } + + std::auto_ptr < CallbackGuard > pCBGuard; + if ( ! enablePreemptiveCallback ) { + pCBGuard.reset ( new CallbackGuard ( this->cbMutex ) ); + } + + // multiple steps ensure exception safety + this->pCallbackGuard = pCBGuard; +} + +ca_client_context::~ca_client_context () +{ + if ( this->fdRegFunc ) { + ( *this->fdRegFunc ) + ( this->fdRegArg, this->sock, false ); + } + epicsSocketDestroy ( this->sock ); + + osiSockRelease (); + + // force a logical shutdown order + // so that the cac class does not hang its + // receive threads during their shutdown sequence + // and so that classes using this classes mutex + // are destroyed before the mutex is destroyed + if ( this->pCallbackGuard.get() ) { + epicsGuardRelease < epicsMutex > unguard ( *this->pCallbackGuard ); + this->pServiceContext.reset ( 0 ); + } + else { + this->pServiceContext.reset ( 0 ); + } +} + +void ca_client_context::destroyGetCopy ( + epicsGuard < epicsMutex > & guard, getCopy & gc ) +{ + guard.assertIdenticalMutex ( this->mutex ); + gc.~getCopy (); + this->getCopyFreeList.release ( & gc ); +} + +void ca_client_context::destroyGetCallback ( + epicsGuard < epicsMutex > & guard, getCallback & gcb ) +{ + guard.assertIdenticalMutex ( this->mutex ); + gcb.~getCallback (); + this->getCallbackFreeList.release ( & gcb ); +} + +void ca_client_context::destroyPutCallback ( + epicsGuard < epicsMutex > & guard, putCallback & pcb ) +{ + guard.assertIdenticalMutex ( this->mutex ); + pcb.~putCallback (); + this->putCallbackFreeList.release ( & pcb ); +} + +void ca_client_context::destroySubscription ( + epicsGuard < epicsMutex > & guard, oldSubscription & os ) +{ + guard.assertIdenticalMutex ( this->mutex ); + os.~oldSubscription (); + this->subscriptionFreeList.release ( & os ); +} + +void ca_client_context::changeExceptionEvent ( + caExceptionHandler * pfunc, void * arg ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->ca_exception_func = pfunc; + this->ca_exception_arg = arg; +// should block here until releated callback in progress completes +} + +void ca_client_context::replaceErrLogHandler ( + caPrintfFunc * ca_printf_func ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( ca_printf_func ) { + this->pVPrintfFunc = ca_printf_func; + } + else { + this->pVPrintfFunc = epicsVprintf; + } +// should block here until releated callback in progress completes +} + +void ca_client_context::registerForFileDescriptorCallBack ( + CAFDHANDLER *pFunc, void *pArg ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->fdRegFunc = pFunc; + this->fdRegArg = pArg; + this->fdRegFuncNeedsToBeCalled = true; + if ( pFunc ) { + // the receive thread might already be blocking + // w/o having sent the wakeup message + this->_sendWakeupMsg (); + } +// should block here until releated callback in progress completes +} + +int ca_client_context :: printFormated ( + const char *pformat, ... ) const +{ + va_list theArgs; + int status; + + va_start ( theArgs, pformat ); + + status = this->ca_client_context :: varArgsPrintFormated ( pformat, theArgs ); + + va_end ( theArgs ); + + return status; +} + +int ca_client_context :: varArgsPrintFormated ( + const char *pformat, va_list args ) const +{ + caPrintfFunc * pFunc; + { + epicsGuard < epicsMutex > guard ( this->mutex ); + pFunc = this->pVPrintfFunc; + } + if ( pFunc ) { + return ( *pFunc ) ( pformat, args ); + } + else { + return :: vfprintf ( stderr, pformat, args ); + } +} + +void ca_client_context::exception ( + epicsGuard < epicsMutex > & guard, int stat, const char * pCtx, + const char * pFile, unsigned lineNo ) +{ + struct exception_handler_args args; + caExceptionHandler * pFunc = this->ca_exception_func; + void * pArg = this->ca_exception_arg; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + // NOOP if they disable exceptions + if ( pFunc ) { + args.chid = NULL; + args.type = TYPENOTCONN; + args.count = 0; + args.addr = NULL; + args.stat = stat; + args.op = CA_OP_OTHER; + args.ctx = pCtx; + args.pFile = pFile; + args.lineNo = lineNo; + args.usr = pArg; + ( *pFunc ) ( args ); + } + else { + this->signal ( stat, pFile, lineNo, pCtx ); + } + } +} + +void ca_client_context::exception ( + epicsGuard < epicsMutex > & guard, int status, const char * pContext, + const char * pFileName, unsigned lineNo, oldChannelNotify & chan, + unsigned type, arrayElementCount count, unsigned op ) +{ + struct exception_handler_args args; + caExceptionHandler * pFunc = this->ca_exception_func; + void * pArg = this->ca_exception_arg; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + // NOOP if they disable exceptions + if ( pFunc ) { + args.chid = &chan; + args.type = type; + args.count = count; + args.addr = NULL; + args.stat = status; + args.op = op; + args.ctx = pContext; + args.pFile = pFileName; + args.lineNo = lineNo; + args.usr = pArg; + ( *pFunc ) ( args ); + } + else { + this->signal ( status, pFileName, lineNo, + "op=%u, channel=%s, type=%s, count=%lu, ctx=\"%s\"", + op, ca_name ( &chan ), + dbr_type_to_text ( static_cast ( type ) ), + count, pContext ); + } + } +} + +void ca_client_context::signal ( int ca_status, const char * pfilenm, + int lineno, const char * pFormat, ... ) +{ + va_list theArgs; + va_start ( theArgs, pFormat ); + this->vSignal ( ca_status, pfilenm, lineno, pFormat, theArgs); + va_end ( theArgs ); +} + +void ca_client_context :: vSignal ( + int ca_status, const char *pfilenm, + int lineno, const char *pFormat, va_list args ) +{ + static const char *severity[] = + { + "Warning", + "Success", + "Error", + "Info", + "Fatal", + "Fatal", + "Fatal", + "Fatal" + }; + + this->printFormated ( "CA.Client.Exception...............................................\n" ); + + this->printFormated ( " %s: \"%s\"\n", + severity[ CA_EXTRACT_SEVERITY ( ca_status ) ], + ca_message ( ca_status ) ); + + if ( pFormat ) { + this->printFormated ( " Context: \"" ); + this->varArgsPrintFormated ( pFormat, args ); + this->printFormated ( "\"\n" ); + } + + if ( pfilenm ) { + this->printFormated ( " Source File: %s line %d\n", + pfilenm, lineno ); + } + + epicsTime current = epicsTime::getCurrent (); + char date[64]; + current.strftime ( date, sizeof ( date ), "%a %b %d %Y %H:%M:%S.%f"); + this->printFormated ( " Current Time: %s\n", date ); + + /* + * Terminate execution if unsuccessful + */ + if( ! ( ca_status & CA_M_SUCCESS ) && + CA_EXTRACT_SEVERITY ( ca_status ) != CA_K_WARNING ){ + errlogFlush (); + abort (); + } + + this->printFormated ( "..................................................................\n" ); +} + +void ca_client_context::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + + ::printf ( "ca_client_context at %p pndRecvCnt=%u ioSeqNo=%u\n", + static_cast ( this ), + this->pndRecvCnt, this->ioSeqNo ); + + if ( level > 0u ) { + this->pServiceContext->show ( guard, level - 1u ); + ::printf ( "\tpreemptive callback is %s\n", + this->pCallbackGuard.get() ? "disabled" : "enabled" ); + ::printf ( "\tthere are %u unsatisfied IO operations blocking ca_pend_io()\n", + this->pndRecvCnt ); + ::printf ( "\tthe current io sequence number is %u\n", + this->ioSeqNo ); + ::printf ( "IO done event:\n"); + this->ioDone.show ( level - 1u ); + ::printf ( "Synchronous group identifier hash table:\n" ); + this->sgTable.show ( level - 1u ); + } +} + +void ca_client_context::attachToClientCtx () +{ + assert ( ! epicsThreadPrivateGet ( caClientContextId ) ); + epicsThreadPrivateSet ( caClientContextId, this ); +} + +void ca_client_context::incrementOutstandingIO ( + epicsGuard < epicsMutex > & guard, unsigned ioSeqNoIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->ioSeqNo == ioSeqNoIn ) { + assert ( this->pndRecvCnt < UINT_MAX ); + this->pndRecvCnt++; + } +} + +void ca_client_context::decrementOutstandingIO ( + epicsGuard < epicsMutex > & guard, unsigned ioSeqNoIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->ioSeqNo == ioSeqNoIn ) { + assert ( this->pndRecvCnt > 0u ); + this->pndRecvCnt--; + if ( this->pndRecvCnt == 0u ) { + this->ioDone.signal (); + } + } +} + +// !!!! This routine is only visible in the old interface - or in a new ST interface. +// !!!! In the old interface we restrict thread attach so that calls from threads +// !!!! other than the initializing thread are not allowed if preemptive callback +// !!!! is disabled. This prevents the preemptive callback lock from being released +// !!!! by other threads than the one that locked it. +// +int ca_client_context::pendIO ( const double & timeout ) +{ + // prevent recursion nightmares by disabling calls to + // pendIO () from within a CA callback. + if ( epicsThreadPrivateGet ( caClientCallbackThreadId ) ) { + return ECA_EVDISALLOW; + } + + int status = ECA_NORMAL; + epicsTime beg_time = epicsTime::getCurrent (); + double remaining = timeout; + + epicsGuard < epicsMutex > guard ( this->mutex ); + + this->flush ( guard ); + + while ( this->pndRecvCnt > 0 ) { + if ( remaining < CAC_SIGNIFICANT_DELAY ) { + status = ECA_TIMEOUT; + break; + } + + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->blockForEventAndEnableCallbacks ( this->ioDone, remaining ); + } + + double delay = epicsTime::getCurrent () - beg_time; + if ( delay < timeout ) { + remaining = timeout - delay; + } + else { + remaining = 0.0; + } + } + + this->ioSeqNo++; + this->pndRecvCnt = 0u; + + return status; +} + +// !!!! This routine is only visible in the old interface - or in a new ST interface. +// !!!! In the old interface we restrict thread attach so that calls from threads +// !!!! other than the initializing thread are not allowed if preemptive callback +// !!!! is disabled. This prevents the preemptive callback lock from being released +// !!!! by other threads than the one that locked it. +// +int ca_client_context::pendEvent ( const double & timeout ) +{ + // prevent recursion nightmares by disabling calls to + // pendIO () from within a CA callback. + if ( epicsThreadPrivateGet ( caClientCallbackThreadId ) ) { + return ECA_EVDISALLOW; + } + + epicsTime current = epicsTime::getCurrent (); + + { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->flush ( guard ); + } + + // process at least once if preemptive callback is disabled + if ( this->pCallbackGuard.get() ) { + epicsGuardRelease < epicsMutex > cbUnguard ( *this->pCallbackGuard ); + epicsGuard < epicsMutex > guard ( this->mutex ); + + // + // This is needed because in non-preemptive callback mode + // legacy applications that use file descriptor managers + // will register for ca receive thread activity and keep + // calling ca_pend_event until all of the socket data has + // been read. We must guarantee that other threads get a + // chance to run if there is data in any of the sockets. + // + if ( this->fdRegFunc ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + + // remove short udp message sent to wake + // up a file descriptor manager + osiSockAddr tmpAddr; + osiSocklen_t addrSize = sizeof ( tmpAddr.sa ); + char buf = 0; + int status = 0; + do { + status = recvfrom ( this->sock, & buf, sizeof ( buf ), + 0, & tmpAddr.sa, & addrSize ); + } while ( status > 0 ); + } + while ( this->callbackThreadsPending > 0 ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->callbackThreadActivityComplete.wait ( 30.0 ); + } + this->noWakeupSincePend = true; + } + + double elapsed = epicsTime::getCurrent() - current; + double delay; + + if ( timeout > elapsed ) { + delay = timeout - elapsed; + } + else { + delay = 0.0; + } + + if ( delay >= CAC_SIGNIFICANT_DELAY ) { + if ( this->pCallbackGuard.get() ) { + epicsGuardRelease < epicsMutex > unguard ( *this->pCallbackGuard ); + epicsThreadSleep ( delay ); + } + else { + epicsThreadSleep ( delay ); + } + } + + return ECA_TIMEOUT; +} + +void ca_client_context::blockForEventAndEnableCallbacks ( + epicsEvent & event, const double & timeout ) +{ + if ( this->pCallbackGuard.get() ) { + epicsGuardRelease < epicsMutex > unguard ( *this->pCallbackGuard ); + event.wait ( timeout ); + } + else { + event.wait ( timeout ); + } +} + +void ca_client_context::callbackProcessingInitiateNotify () +{ + // if preemptive callback is enabled then this is a noop + if ( this->pCallbackGuard.get() ) { + bool sendNeeded = false; + { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->callbackThreadsPending++; + if ( this->fdRegFunc && this->noWakeupSincePend ) { + this->noWakeupSincePend = false; + sendNeeded = true; + } + } + if ( sendNeeded ) { + _sendWakeupMsg (); + } + } +} + +void ca_client_context :: _sendWakeupMsg () +{ + // send short udp message to wake up a file descriptor manager + // when a message arrives + osiSockAddr tmpAddr; + tmpAddr.ia.sin_family = AF_INET; + tmpAddr.ia.sin_addr.s_addr = htonl ( INADDR_LOOPBACK ); + tmpAddr.ia.sin_port = htons ( this->localPort ); + char buf = 0; + sendto ( this->sock, & buf, sizeof ( buf ), + 0, & tmpAddr.sa, sizeof ( tmpAddr.sa ) ); +} + +void ca_client_context::callbackProcessingCompleteNotify () +{ + // if preemptive callback is enabled then this is a noop + if ( this->pCallbackGuard.get() ) { + bool signalNeeded = false; + { + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->callbackThreadsPending <= 1 ) { + if ( this->callbackThreadsPending == 1 ) { + this->callbackThreadsPending = 0; + signalNeeded = true; + } + } + else { + this->callbackThreadsPending--; + } + } + if ( signalNeeded ) { + this->callbackThreadActivityComplete.signal (); + } + } +} + +cacChannel & ca_client_context::createChannel ( + epicsGuard < epicsMutex > & guard, const char * pChannelName, + cacChannelNotify & chan, cacChannel::priLev pri ) +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->pServiceContext->createChannel ( + guard, pChannelName, chan, pri ); +} + +void ca_client_context::flush ( epicsGuard < epicsMutex > & guard ) +{ + this->pServiceContext->flush ( guard ); +} + +unsigned ca_client_context::circuitCount () const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + return this->pServiceContext->circuitCount ( guard ); +} + +unsigned ca_client_context::beaconAnomaliesSinceProgramStart () const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + return this->pServiceContext->beaconAnomaliesSinceProgramStart ( guard ); +} + +void ca_client_context::installCASG ( + epicsGuard < epicsMutex > & guard, CASG & sg ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->sgTable.idAssignAdd ( sg ); +} + +void ca_client_context::uninstallCASG ( + epicsGuard < epicsMutex > & guard, CASG & sg ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->sgTable.remove ( sg ); +} + +CASG * ca_client_context::lookupCASG ( + epicsGuard < epicsMutex > & guard, unsigned idIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + CASG * psg = this->sgTable.lookup ( idIn ); + if ( psg ) { + if ( ! psg->verify ( guard ) ) { + psg = 0; + } + } + return psg; +} + +void ca_client_context::selfTest () const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + this->sgTable.verify (); + this->pServiceContext->selfTest ( guard ); +} + +epicsMutex & ca_client_context::mutexRef () const +{ + return this->mutex; +} + +cacContext & ca_client_context::createNetworkContext ( + epicsMutex & mutexIn, epicsMutex & cbMutexIn ) +{ + return * new cac ( mutexIn, cbMutexIn, *this ); +} + +void ca_client_context::installDefaultService ( cacService & service ) +{ + epicsThreadOnce ( & cacOnce, cacOnceFunc, 0 ); + + epicsGuard < epicsMutex > guard ( *ca_client_context::pDefaultServiceInstallMutex ); + if ( ca_client_context::pDefaultService ) { + throw std::logic_error + ( "CA in-memory service already installed and can't be replaced"); + } + ca_client_context::pDefaultService = & service; +} + +void epicsShareAPI caInstallDefaultService ( cacService & service ) +{ + ca_client_context::installDefaultService ( service ); +} + +epicsShareFunc int epicsShareAPI ca_clear_subscription ( evid pMon ) +{ + oldChannelNotify & chan = pMon->channel (); + ca_client_context & cac = chan.getClientCtx (); + // !!!! the order in which we take the mutex here prevents deadlocks + { + epicsGuard < epicsMutex > guard ( cac.mutex ); + try { + // if this stalls out on a live circuit then an exception + // can be forthcoming which we must ignore as the clear + // request must always be successful + chan.eliminateExcessiveSendBacklog ( guard ); + } + catch ( cacChannel::notConnected & ) { + // intentionally ignored + } + } + if ( cac.pCallbackGuard.get() && + cac.createdByThread == epicsThreadGetIdSelf () ) { + epicsGuard < epicsMutex > guard ( cac.mutex ); + pMon->cancel ( *cac.pCallbackGuard.get(), guard ); + } + else { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( cac.cbMutex ); + epicsGuard < epicsMutex > guard ( cac.mutex ); + pMon->cancel ( cbGuard, guard ); + } + return ECA_NORMAL; +} + +void ca_client_context :: eliminateExcessiveSendBacklog ( + epicsGuard < epicsMutex > & guard, cacChannel & chan ) +{ + if ( chan.requestMessageBytesPending ( guard ) > + ca_client_context :: flushBlockThreshold ) { + if ( this->pCallbackGuard.get() && + this->createdByThread == epicsThreadGetIdSelf () ) { + // we need to be very careful about lock hierarchy + // inversion in this situation + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbunguard ( + * this->pCallbackGuard.get() ); + { + epicsGuard < epicsMutex > nestedGuard ( this->mutex ); + chan.flush ( nestedGuard ); + } + } + } + else { + chan.flush ( guard ); + } + } +} diff --git a/modules/ca/src/client/cac.cpp b/modules/ca/src/client/cac.cpp new file mode 100644 index 000000000..5748353bd --- /dev/null +++ b/modules/ca/src/client/cac.cpp @@ -0,0 +1,1336 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + * + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include +#include +#include // vxWorks 6.0 requires this include + +#include "dbDefs.h" +#include "epicsGuard.h" +#include "epicsVersion.h" +#include "osiProcess.h" +#include "epicsSignal.h" +#include "envDefs.h" +#include "locationException.h" +#include "errlog.h" +#include "epicsExport.h" + +#define epicsExportSharedSymbols +#include "addrList.h" +#include "iocinf.h" +#include "cac.h" +#include "inetAddrID.h" +#include "caServerID.h" +#include "virtualCircuit.h" +#include "syncGroup.h" +#include "nciu.h" +#include "autoPtrRecycle.h" +#include "msgForMultiplyDefinedPV.h" +#include "udpiiu.h" +#include "bhe.h" +#include "net_convert.h" +#include "autoPtrFreeList.h" +#include "noopiiu.h" + +static const char pVersionCAC[] = + "@(#) " EPICS_VERSION_STRING + ", CA Client Library " __DATE__; + +// TCP response dispatch table +const cac::pProtoStubTCP cac::tcpJumpTableCAC [] = +{ + &cac::versionAction, + &cac::eventRespAction, + &cac::badTCPRespAction, + &cac::readRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::searchRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + // legacy CA_PROTO_READ_SYNC used as an echo with legacy server + &cac::echoRespAction, + &cac::exceptionRespAction, + &cac::clearChannelRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::readNotifyRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::createChannelRespAction, + &cac::writeNotifyRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::accessRightsRespAction, + &cac::echoRespAction, + &cac::badTCPRespAction, + &cac::badTCPRespAction, + &cac::verifyAndDisconnectChan, + &cac::verifyAndDisconnectChan +}; + +// TCP exception dispatch table +const cac::pExcepProtoStubTCP cac::tcpExcepJumpTableCAC [] = +{ + &cac::defaultExcep, // CA_PROTO_VERSION + &cac::eventAddExcep, // CA_PROTO_EVENT_ADD + &cac::defaultExcep, // CA_PROTO_EVENT_CANCEL + &cac::readExcep, // CA_PROTO_READ + &cac::writeExcep, // CA_PROTO_WRITE + &cac::defaultExcep, // CA_PROTO_SNAPSHOT + &cac::defaultExcep, // CA_PROTO_SEARCH + &cac::defaultExcep, // CA_PROTO_BUILD + &cac::defaultExcep, // CA_PROTO_EVENTS_OFF + &cac::defaultExcep, // CA_PROTO_EVENTS_ON + &cac::defaultExcep, // CA_PROTO_READ_SYNC + &cac::defaultExcep, // CA_PROTO_ERROR + &cac::defaultExcep, // CA_PROTO_CLEAR_CHANNEL + &cac::defaultExcep, // CA_PROTO_RSRV_IS_UP + &cac::defaultExcep, // CA_PROTO_NOT_FOUND + &cac::readNotifyExcep, // CA_PROTO_READ_NOTIFY + &cac::defaultExcep, // CA_PROTO_READ_BUILD + &cac::defaultExcep, // REPEATER_CONFIRM + &cac::defaultExcep, // CA_PROTO_CREATE_CHAN + &cac::writeNotifyExcep, // CA_PROTO_WRITE_NOTIFY + &cac::defaultExcep, // CA_PROTO_CLIENT_NAME + &cac::defaultExcep, // CA_PROTO_HOST_NAME + &cac::defaultExcep, // CA_PROTO_ACCESS_RIGHTS + &cac::defaultExcep, // CA_PROTO_ECHO + &cac::defaultExcep, // REPEATER_REGISTER + &cac::defaultExcep, // CA_PROTO_SIGNAL + &cac::defaultExcep, // CA_PROTO_CREATE_CH_FAIL + &cac::defaultExcep // CA_PROTO_SERVER_DISCONN +}; + +// +// cac::cac () +// +cac::cac ( + epicsMutex & mutualExclusionIn, + epicsMutex & callbackControlIn, + cacContextNotify & notifyIn ) : + _refLocalHostName ( localHostNameCache.getReference () ), + programBeginTime ( epicsTime::getCurrent() ), + connTMO ( CA_CONN_VERIFY_PERIOD ), + mutex ( mutualExclusionIn ), + cbMutex ( callbackControlIn ), + ipToAEngine ( ipAddrToAsciiEngine::allocate () ), + timerQueue ( epicsTimerQueueActive::allocate ( false, + lowestPriorityLevelAbove(epicsThreadGetPrioritySelf()) ) ), + pUserName ( 0 ), + pudpiiu ( 0 ), + tcpSmallRecvBufFreeList ( 0 ), + tcpLargeRecvBufFreeList ( 0 ), + notify ( notifyIn ), + initializingThreadsId ( epicsThreadGetIdSelf() ), + initializingThreadsPriority ( epicsThreadGetPrioritySelf() ), + maxRecvBytesTCP ( MAX_TCP ), + maxContigFrames ( contiguousMsgCountWhichTriggersFlowControl ), + beaconAnomalyCount ( 0u ), + iiuExistenceCount ( 0u ), + cacShutdownInProgress ( false ) +{ + if ( ! osiSockAttach () ) { + throwWithLocation ( udpiiu :: noSocket () ); + } + + try { + long status; + + /* + * Certain os, such as HPUX, do not unblock a socket system call + * when another thread asynchronously calls both shutdown() and + * close(). To solve this problem we need to employ OS specific + * mechanisms. + */ + epicsSignalInstallSigAlarmIgnore (); + epicsSignalInstallSigPipeIgnore (); + + { + char tmp[256]; + size_t len; + osiGetUserNameReturn gunRet; + + gunRet = osiGetUserName ( tmp, sizeof (tmp) ); + if ( gunRet != osiGetUserNameSuccess ) { + tmp[0] = '\0'; + } + len = strlen ( tmp ) + 1; + this->pUserName = new char [ len ]; + strncpy ( this->pUserName, tmp, len ); + } + + this->_serverPort = + envGetInetPortConfigParam ( &EPICS_CA_SERVER_PORT, + static_cast (CA_SERVER_PORT) ); + + status = envGetDoubleConfigParam ( &EPICS_CA_CONN_TMO, &this->connTMO ); + if ( status ) { + this->connTMO = CA_CONN_VERIFY_PERIOD; + epicsGuard < epicsMutex > cbGuard ( this->cbMutex ); + errlogPrintf ( "EPICS \"%s\" double fetch failed\n", EPICS_CA_CONN_TMO.name ); + errlogPrintf ( "Defaulting \"%s\" = %f\n", EPICS_CA_CONN_TMO.name, this->connTMO ); + } + + long maxBytesAsALong; + status = envGetLongConfigParam ( &EPICS_CA_MAX_ARRAY_BYTES, &maxBytesAsALong ); + if ( status || maxBytesAsALong < 0 ) { + errlogPrintf ( "cac: EPICS_CA_MAX_ARRAY_BYTES was not a positive integer\n" ); + } + 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 ( "cac: EPICS_CA_MAX_ARRAY_BYTES was rounded up to %u\n", MAX_TCP ); + } + else { + this->maxRecvBytesTCP = maxBytes; + } + } + freeListInitPvt ( &this->tcpSmallRecvBufFreeList, MAX_TCP, 1 ); + if ( ! this->tcpSmallRecvBufFreeList ) { + throw std::bad_alloc (); + } + + int autoMaxBytes; + if(envGetBoolConfigParam(&EPICS_CA_AUTO_ARRAY_BYTES, &autoMaxBytes)) + autoMaxBytes = 1; + + if(!autoMaxBytes) { + freeListInitPvt ( &this->tcpLargeRecvBufFreeList, this->maxRecvBytesTCP, 1 ); + if ( ! this->tcpLargeRecvBufFreeList ) { + throw std::bad_alloc (); + } + } + unsigned bufsPerArray = this->maxRecvBytesTCP / comBuf::capacityBytes (); + if ( bufsPerArray > 1u ) { + maxContigFrames = bufsPerArray * + contiguousMsgCountWhichTriggersFlowControl; + } + } + catch ( ... ) { + osiSockRelease (); + delete [] this->pUserName; + freeListCleanup ( this->tcpSmallRecvBufFreeList ); + if ( this->tcpLargeRecvBufFreeList ) { + freeListCleanup ( this->tcpLargeRecvBufFreeList ); + } + this->timerQueue.release (); + throw; + } + + /* + * load user configured tcp name server address list, + * create virtual circuits, and add them to server table + */ + ELLLIST dest, tmpList; + + ellInit ( & dest ); + ellInit ( & tmpList ); + + addAddrToChannelAccessAddressList ( &tmpList, &EPICS_CA_NAME_SERVERS, this->_serverPort, false ); + removeDuplicateAddresses ( &dest, &tmpList, 0 ); + + epicsGuard < epicsMutex > guard ( this->mutex ); + + while ( osiSockAddrNode * + pNode = reinterpret_cast < osiSockAddrNode * > ( ellGet ( & dest ) ) ) { + tcpiiu * piiu = NULL; + SearchDestTCP * pdst = new SearchDestTCP ( *this, pNode->addr ); + this->registerSearchDest ( guard, * pdst ); + /* Initially assume that servers listed in EPICS_CA_NAME_SERVERS support at least minor + * version 11. This causes tcpiiu to send the user and host name authentication + * messages. When the actual Version message is received from the server it will + * be overwrite this assumption. + */ + bool newIIU = findOrCreateVirtCircuit ( + guard, pNode->addr, cacChannel::priorityDefault, + piiu, 11, pdst ); + free ( pNode ); + if ( newIIU ) { + piiu->start ( guard ); + } + } +} + +cac::~cac () +{ + // this blocks until the UDP thread exits so that + // it will not sneak in any new clients + // + // lock intentionally not held here so that we dont deadlock + // waiting for the UDP thread to exit while it is waiting to + // get the lock. + { + epicsGuard < epicsMutex > cbGuard ( this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->pudpiiu ) { + this->pudpiiu->shutdown ( cbGuard, guard ); + + // make sure no new tcp circuits are created + this->cacShutdownInProgress = true; + + // + // shutdown all tcp circuits + // + tsDLIter < tcpiiu > iter = this->circuitList.firstIter (); + while ( iter.valid() ) { + // this causes a clean shutdown to occur + iter->unlinkAllChannels ( cbGuard, guard ); + iter++; + } + } + } + + // + // wait for all tcp threads to exit + // + // this will block for oustanding sends to go out so dont + // hold a lock while waiting + // + { + epicsGuard < epicsMutex > guard ( this->mutex ); + while ( this->iiuExistenceCount > 0 ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->iiuUninstall.wait (); + } + } + + if ( this->pudpiiu ) { + delete this->pudpiiu; + } + + freeListCleanup ( this->tcpSmallRecvBufFreeList ); + if ( this->tcpLargeRecvBufFreeList ) { + freeListCleanup ( this->tcpLargeRecvBufFreeList ); + } + + delete [] this->pUserName; + + tsSLList < bhe > tmpBeaconList; + this->beaconTable.removeAll ( tmpBeaconList ); + while ( bhe * pBHE = tmpBeaconList.get() ) { + pBHE->~bhe (); + this->bheFreeList.release ( pBHE ); + } + + this->timerQueue.release (); + + this->ipToAEngine.release (); + + // clean-up the list of un-notified msg objects + while ( msgForMultiplyDefinedPV * msg = this->msgMultiPVList.get() ) { + msg->~msgForMultiplyDefinedPV (); + this->mdpvFreeList.release ( msg ); + } + + errlogFlush (); + + osiSockRelease (); + + // its ok for channels and subscriptions to still + // exist at this point. The user created them and + // its his responsibility to clean them up. +} + +unsigned cac::lowestPriorityLevelAbove ( unsigned priority ) +{ + unsigned abovePriority; + epicsThreadBooleanStatus tbs; + tbs = epicsThreadLowestPriorityLevelAbove ( + priority, & abovePriority ); + if ( tbs != epicsThreadBooleanStatusSuccess ) { + abovePriority = priority; + } + return abovePriority; +} + +unsigned cac::highestPriorityLevelBelow ( unsigned priority ) +{ + unsigned belowPriority; + epicsThreadBooleanStatus tbs; + tbs = epicsThreadHighestPriorityLevelBelow ( + priority, & belowPriority ); + if ( tbs != epicsThreadBooleanStatusSuccess ) { + belowPriority = priority; + } + return belowPriority; +} + +// +// set the push pending flag on all virtual circuits +// +void cac::flush ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + tsDLIter < tcpiiu > iter = this->circuitList.firstIter (); + while ( iter.valid () ) { + iter->flushRequest ( guard ); + iter++; + } +} + +unsigned cac::circuitCount ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->circuitList.count (); +} + +void cac::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + + ::printf ( "Channel Access Client Context at %p for user %s\n", + static_cast ( this ), this->pUserName ); + // this also supresses the "defined, but not used" + // warning message + ::printf ( "\trevision \"%s\"\n", pVersionCAC ); + + if ( level > 0u ) { + this->serverTable.show ( level - 1u ); + ::printf ( "\tconnection time out watchdog period %f\n", this->connTMO ); + } + + if ( level > 1u ) { + if ( this->pudpiiu ) { + this->pudpiiu->show ( level - 2u ); + } + } + + if ( level > 2u ) { + ::printf ( "Program begin time:\n"); + this->programBeginTime.show ( level - 3u ); + ::printf ( "Channel identifier hash table:\n" ); + this->chanTable.show ( level - 3u ); + ::printf ( "IO identifier hash table:\n" ); + this->ioTable.show ( level - 3u ); + ::printf ( "Beacon source identifier hash table:\n" ); + this->beaconTable.show ( level - 3u ); + ::printf ( "Timer queue:\n" ); + this->timerQueue.show ( level - 3u ); + ::printf ( "IP address to name conversion engine:\n" ); + this->ipToAEngine.show ( level - 3u ); + } + + if ( level > 3u ) { + ::printf ( "Default mutex:\n"); + this->mutex.show ( level - 4u ); + ::printf ( "mutex:\n" ); + this->mutex.show ( level - 4u ); + } +} + +/* + * cac::beaconNotify + */ +void cac::beaconNotify ( const inetAddrID & addr, const epicsTime & currentTime, + ca_uint32_t beaconNumber, unsigned protocolRevision ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + + if ( ! this->pudpiiu ) { + return; + } + + /* + * look for it in the hash table + */ + bhe *pBHE = this->beaconTable.lookup ( addr ); + if ( pBHE ) { + /* + * return if the beacon period has not changed significantly + */ + if ( ! pBHE->updatePeriod ( guard, this->programBeginTime, + currentTime, beaconNumber, protocolRevision ) ) { + return; + } + } + else { + /* + * This is the first beacon seen from this server. + * Wait until 2nd beacon is seen before deciding + * if it is a new server (or just the first + * time that we have seen a server's beacon + * shortly after the program started up) + */ + pBHE = new ( this->bheFreeList ) + bhe ( this->mutex, currentTime, beaconNumber, addr ); + if ( pBHE ) { + if ( this->beaconTable.add ( *pBHE ) < 0 ) { + pBHE->~bhe (); + this->bheFreeList.release ( pBHE ); + } + } + return; + } + + this->beaconAnomalyCount++; + + this->pudpiiu->beaconAnomalyNotify ( guard ); + +# ifdef DEBUG + { + char buf[128]; + addr.name ( buf, sizeof ( buf ) ); + ::printf ( "New server available: %s\n", buf ); + } +# endif +} + +cacChannel & cac::createChannel ( + epicsGuard < epicsMutex > & guard, const char * pName, + cacChannelNotify & chan, cacChannel::priLev pri ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( pri > cacChannel::priorityMax ) { + throw cacChannel::badPriority (); + } + + if ( pName == 0 || pName[0] == '\0' ) { + throw cacChannel::badString (); + } + + if ( ! this->pudpiiu ) { + this->pudpiiu = new udpiiu ( + guard, this->timerQueue, this->cbMutex, + this->mutex, this->notify, *this, this->_serverPort, + this->searchDestList ); + } + + nciu * pNetChan = new ( this->channelFreeList ) + nciu ( *this, noopIIU, chan, pName, pri ); + this->chanTable.idAssignAdd ( *pNetChan ); + return *pNetChan; +} + +bool cac::findOrCreateVirtCircuit ( + epicsGuard < epicsMutex > & guard, const osiSockAddr & addr, + unsigned priority, tcpiiu *& piiu, unsigned minorVersionNumber, + SearchDestTCP * pSearchDest ) +{ + guard.assertIdenticalMutex ( this->mutex ); + bool newIIU = false; + + if ( piiu ) { + if ( ! piiu->alive ( guard ) ) { + return newIIU; + } + } + else { + try { + autoPtrFreeList < tcpiiu, 32, epicsMutexNOOP > pnewiiu ( + this->freeListVirtualCircuit, + new ( this->freeListVirtualCircuit ) tcpiiu ( + *this, this->mutex, this->cbMutex, this->notify, this->connTMO, + this->timerQueue, addr, this->comBufMemMgr, minorVersionNumber, + this->ipToAEngine, priority, pSearchDest ) ); + + bhe * pBHE = this->beaconTable.lookup ( addr.ia ); + if ( ! pBHE ) { + pBHE = new ( this->bheFreeList ) + bhe ( this->mutex, epicsTime (), 0u, addr.ia ); + if ( this->beaconTable.add ( *pBHE ) < 0 ) { + return newIIU; + } + } + this->serverTable.add ( *pnewiiu ); + this->circuitList.add ( *pnewiiu ); + this->iiuExistenceCount++; + pBHE->registerIIU ( guard, *pnewiiu ); + piiu = pnewiiu.release (); + newIIU = true; + } + catch ( std :: exception & except ) { + errlogPrintf ( + "CAC: exception during virtual circuit creation \"%s\"\n", + except.what () ); + return newIIU; + } + catch ( ... ) { + errlogPrintf ( + "CAC: Nonstandard exception during virtual circuit creation\n" ); + return newIIU; + } + } + return newIIU; +} + +void cac::transferChanToVirtCircuit ( + unsigned cid, unsigned sid, + ca_uint16_t typeCode, arrayElementCount count, + unsigned minorVersionNumber, const osiSockAddr & addr, + const epicsTime & currentTime ) +{ + if ( addr.sa.sa_family != AF_INET ) { + return; + } + + epicsGuard < epicsMutex > guard ( this->mutex ); + + /* + * Do not open new circuits while the cac is shutting down + */ + if ( this->cacShutdownInProgress ) { + return; + } + + /* + * ignore search replies for deleted channels + */ + nciu * pChan = this->chanTable.lookup ( cid ); + if ( ! pChan ) { + return; + } + + /* + * Ignore duplicate search replies + */ + osiSockAddr chanAddr = pChan->getPIIU(guard)->getNetworkAddress (guard); + + if ( chanAddr.sa.sa_family != AF_UNSPEC ) { + if ( ! sockAddrAreIdentical ( &addr, &chanAddr ) ) { + char acc[64]; + pChan->getPIIU(guard)->getHostName ( guard, acc, sizeof ( acc ) ); + msgForMultiplyDefinedPV * pMsg = new ( this->mdpvFreeList ) + msgForMultiplyDefinedPV ( this->ipToAEngine, + *this, pChan->pName ( guard ), acc ); + // cac keeps a list of these objects for proper clean-up in ~cac + this->msgMultiPVList.add ( *pMsg ); + // It is possible for the ioInitiate call below to + // call the callback directly if queue quota is exceeded. + // This callback takes the callback lock and therefore we + // must release the primary mutex here to avoid a lock + // hierarchy inversion. + epicsGuardRelease < epicsMutex > unguard ( guard ); + pMsg->ioInitiate ( addr ); + } + return; + } + + caServerID servID ( addr.ia, pChan->getPriority(guard) ); + tcpiiu * piiu = this->serverTable.lookup ( servID ); + + bool newIIU = findOrCreateVirtCircuit ( + guard, addr, + pChan->getPriority(guard), piiu, minorVersionNumber ); + + // must occur before moving to new iiu + pChan->getPIIU(guard)->uninstallChanDueToSuccessfulSearchResponse ( + guard, *pChan, currentTime ); + if ( piiu ) { + piiu->installChannel ( + guard, *pChan, sid, typeCode, count ); + + if ( newIIU ) { + piiu->start ( guard ); + } + } +} + +void cac::destroyChannel ( + epicsGuard < epicsMutex > & guard, + nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + // uninstall channel so that recv threads + // will not start a new callback for this channel's IO. + if ( this->chanTable.remove ( chan ) != & chan ) { + throw std::logic_error ( "Invalid channel identifier" ); + } + chan.~nciu (); + this->channelFreeList.release ( & chan ); +} + +void cac::disconnectAllIO ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, + nciu & chan, tsDLList < baseNMIU > & ioList ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + char buf[128]; + chan.getHostName ( guard, buf, sizeof ( buf ) ); + + tsDLIter < baseNMIU > pNetIO = ioList.firstIter(); + while ( pNetIO.valid () ) { + tsDLIter < baseNMIU > pNext = pNetIO; + pNext++; + if ( ! pNetIO->isSubscription() ) { + this->ioTable.remove ( pNetIO->getId () ); + } + pNetIO->exception ( guard, *this, ECA_DISCONN, buf ); + pNetIO = pNext; + } +} + +int cac :: printFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char * pformat, ... ) const +{ + va_list theArgs; + va_start ( theArgs, pformat ); + int status = this->varArgsPrintFormated ( callbackControl, pformat, theArgs ); + va_end ( theArgs ); + return status; +} + +netWriteNotifyIO & cac::writeNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, privateInterfaceForIO & icni, + unsigned type, arrayElementCount nElem, const void * pValue, cacWriteNotify & notifyIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + autoPtrRecycle < netWriteNotifyIO > pIO ( + guard, this->ioTable, *this, + netWriteNotifyIO::factory ( this->freeListWriteNotifyIO, icni, notifyIn ) ); + this->ioTable.idAssignAdd ( *pIO ); + chan.getPIIU(guard)->writeNotifyRequest ( + guard, chan, *pIO, type, nElem, pValue ); + return *pIO.release(); +} + +netReadNotifyIO & cac::readNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, privateInterfaceForIO & icni, + unsigned type, arrayElementCount nElem, cacReadNotify & notifyIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + autoPtrRecycle < netReadNotifyIO > pIO ( + guard, this->ioTable, *this, + netReadNotifyIO::factory ( this->freeListReadNotifyIO, icni, notifyIn ) ); + this->ioTable.idAssignAdd ( *pIO ); + chan.getPIIU(guard)->readNotifyRequest ( guard, chan, *pIO, type, nElem ); + return *pIO.release(); +} + +bool cac::destroyIO ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid & idIn, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + baseNMIU * pIO = this->ioTable.remove ( idIn ); + if ( pIO ) { + class netSubscription * pSubscr = pIO->isSubscription (); + if ( pSubscr ) { + pSubscr->unsubscribeIfRequired ( guard, chan ); + } + + // this uninstalls from the list and destroys the IO + pIO->exception ( guard, *this, + ECA_CHANDESTROY, chan.pName ( guard ) ); + return true; + } + return false; +} + +void cac::ioShow ( + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid & idIn, unsigned level ) const +{ + baseNMIU * pmiu = this->ioTable.lookup ( idIn ); + if ( pmiu ) { + pmiu->show ( guard, level ); + } +} + +void cac::ioExceptionNotify ( + unsigned idIn, int status, const char * pContext, + unsigned type, arrayElementCount count ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + baseNMIU * pmiu = this->ioTable.lookup ( idIn ); + if ( pmiu ) { + pmiu->exception ( guard, *this, status, pContext, type, count ); + } +} + +void cac::ioExceptionNotifyAndUninstall ( + unsigned idIn, int status, const char * pContext, + unsigned type, arrayElementCount count ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + baseNMIU * pmiu = this->ioTable.remove ( idIn ); + if ( pmiu ) { + pmiu->exception ( guard, *this, status, pContext, type, count ); + } +} + +void cac::recycleReadNotifyIO ( + epicsGuard < epicsMutex > & guard, netReadNotifyIO & io ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->freeListReadNotifyIO.release ( & io ); +} + +void cac::recycleWriteNotifyIO ( + epicsGuard < epicsMutex > & guard, netWriteNotifyIO & io ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->freeListWriteNotifyIO.release ( & io ); +} + +void cac::recycleSubscription ( + epicsGuard < epicsMutex > & guard, netSubscription & io ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->freeListSubscription.release ( & io ); +} + +netSubscription & cac::subscriptionRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, privateInterfaceForIO & privChan, + unsigned type, + arrayElementCount nElem, unsigned mask, + cacStateNotify & notifyIn, + bool chanIsInstalled ) +{ + guard.assertIdenticalMutex ( this->mutex ); + autoPtrRecycle < netSubscription > pIO ( + guard, this->ioTable, *this, + netSubscription::factory ( this->freeListSubscription, + privChan, type, nElem, mask, notifyIn ) ); + this->ioTable.idAssignAdd ( *pIO ); + if ( chanIsInstalled ) { + pIO->subscribeIfRequired ( guard, chan ); + } + return *pIO.release (); +} + +bool cac::versionAction ( callbackManager &, tcpiiu & iiu, + const epicsTime &, const caHdrLargeArray & msg, void * ) +{ + iiu.versionRespNotify ( msg ); + return true; +} + +bool cac::echoRespAction ( + callbackManager & mgr, tcpiiu & iiu, + const epicsTime & /* current */, const caHdrLargeArray &, void * ) +{ + iiu.probeResponseNotify ( mgr.cbGuard ); + return true; +} + +bool cac::writeNotifyRespAction ( + callbackManager &, tcpiiu &, + const epicsTime &, const caHdrLargeArray & hdr, void * ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + baseNMIU * pmiu = this->ioTable.remove ( hdr.m_available ); + if ( pmiu ) { + if ( hdr.m_cid == ECA_NORMAL ) { + pmiu->completion ( guard, *this ); + } + else { + pmiu->exception ( guard, *this, + hdr.m_cid, "write notify request rejected" ); + } + } + return true; +} + +bool cac::readNotifyRespAction ( callbackManager &, tcpiiu & iiu, + const epicsTime &, const caHdrLargeArray & hdr, void * pMsgBdy ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + + /* + * the channel id field is abused for + * read notify status starting with CA V4.1 + */ + int caStatus; + if ( iiu.ca_v41_ok ( guard ) ) { + caStatus = hdr.m_cid; + } + else { + caStatus = ECA_NORMAL; + } + + baseNMIU * pmiu = this->ioTable.remove ( hdr.m_available ); + // + // The IO destroy routines take the call back mutex + // when uninstalling and deleting the baseNMIU so there is + // no need to worry here about the baseNMIU being deleted while + // it is in use here. + // + if ( pmiu ) { + // if its a circuit-becomes-responsive subscription update + // then we need to reinstall the IO into the table + netSubscription * pSubscr = pmiu->isSubscription (); + if ( pSubscr ) { + // this does *not* assign a new resource id + this->ioTable.add ( *pmiu ); + } + if ( caStatus == ECA_NORMAL ) { + /* + * convert the data buffer from net + * format to host format + */ + caStatus = caNetConvert ( + hdr.m_dataType, pMsgBdy, pMsgBdy, false, hdr.m_count ); + } + if ( caStatus == ECA_NORMAL ) { + pmiu->completion ( guard, *this, + hdr.m_dataType, hdr.m_count, pMsgBdy ); + } + else { + pmiu->exception ( guard, *this, + caStatus, "read failed", + hdr.m_dataType, hdr.m_count ); + } + } + return true; +} + +bool cac::searchRespAction ( callbackManager &, tcpiiu & iiu, + const epicsTime & currentTime, const caHdrLargeArray & msg, + void * /* pMsgBdy */ ) +{ + assert ( this->pudpiiu ); + iiu.searchRespNotify ( currentTime, msg ); + return true; +} + +bool cac::eventRespAction ( callbackManager &, tcpiiu &iiu, + const epicsTime &, const caHdrLargeArray & hdr, void * pMsgBdy ) +{ + int caStatus; + + /* + * m_postsize = 0 used to be a subscription cancel confirmation, + * but is now a noop because the IO block is immediately deleted + */ + if ( ! hdr.m_postsize ) { + return true; + } + + epicsGuard < epicsMutex > guard ( this->mutex ); + + /* + * the channel id field is abused for + * read notify status starting with CA V4.1 + */ + if ( iiu.ca_v41_ok ( guard ) ) { + caStatus = hdr.m_cid; + } + else { + caStatus = ECA_NORMAL; + } + + // + // The IO destroy routines take the call back mutex + // when uninstalling and deleting the baseNMIU so there is + // no need to worry here about the baseNMIU being deleted while + // it is in use here. + // + baseNMIU * pmiu = this->ioTable.lookup ( hdr.m_available ); + if ( pmiu ) { + /* + * convert the data buffer from net format to host format + */ + if ( caStatus == ECA_NORMAL ) { + caStatus = caNetConvert ( + hdr.m_dataType, pMsgBdy, pMsgBdy, false, hdr.m_count ); + } + if ( caStatus == ECA_NORMAL ) { + pmiu->completion ( guard, *this, + hdr.m_dataType, hdr.m_count, pMsgBdy ); + } + else { + pmiu->exception ( guard, *this, caStatus, + "subscription update read failed", + hdr.m_dataType, hdr.m_count ); + } + } + return true; +} + +bool cac::readRespAction ( callbackManager &, tcpiiu &, + const epicsTime &, const caHdrLargeArray & hdr, void * pMsgBdy ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + baseNMIU * pmiu = this->ioTable.remove ( hdr.m_available ); + // + // The IO destroy routines take the call back mutex + // when uninstalling and deleting the baseNMIU so there is + // no need to worry here about the baseNMIU being deleted while + // it is in use here. + // + if ( pmiu ) { + pmiu->completion ( guard, *this, + hdr.m_dataType, hdr.m_count, pMsgBdy ); + } + return true; +} + +bool cac::clearChannelRespAction ( callbackManager &, tcpiiu &, + const epicsTime &, const caHdrLargeArray &, void * /* pMsgBody */ ) +{ + return true; // currently a noop +} + +bool cac::defaultExcep ( + callbackManager &, tcpiiu & iiu, + const caHdrLargeArray &, const char * pCtx, unsigned status ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + char buf[512]; + char hostName[64]; + iiu.getHostName ( guard, hostName, sizeof ( hostName ) ); + sprintf ( buf, "host=%s ctx=%.400s", hostName, pCtx ); + this->notify.exception ( guard, status, buf, 0, 0u ); + return true; +} + +void cac::exception ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, int status, + const char * pContext, const char * pFileName, unsigned lineNo ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + this->notify.exception ( guard, status, pContext, + pFileName, lineNo ); +} + +bool cac::eventAddExcep ( + callbackManager &, tcpiiu &, + const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ) +{ + this->ioExceptionNotify ( hdr.m_available, status, pCtx, + hdr.m_dataType, hdr.m_count ); + return true; +} + +bool cac::readExcep ( callbackManager &, tcpiiu &, + const caHdrLargeArray & hdr, + const char * pCtx, unsigned status ) +{ + this->ioExceptionNotifyAndUninstall ( hdr.m_available, + status, pCtx, hdr.m_dataType, hdr.m_count ); + return true; +} + +bool cac::writeExcep ( + callbackManager & mgr, + tcpiiu &, const caHdrLargeArray & hdr, + const char * pCtx, unsigned status ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + nciu * pChan = this->chanTable.lookup ( hdr.m_available ); + if ( pChan ) { + pChan->writeException ( mgr.cbGuard, guard, status, pCtx, + hdr.m_dataType, hdr.m_count ); + } + return true; +} + +bool cac::readNotifyExcep ( callbackManager &, tcpiiu &, + const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ) +{ + this->ioExceptionNotifyAndUninstall ( hdr.m_available, + status, pCtx, hdr.m_dataType, hdr.m_count ); + return true; +} + +bool cac::writeNotifyExcep ( callbackManager &, tcpiiu &, + const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ) +{ + this->ioExceptionNotifyAndUninstall ( hdr.m_available, + status, pCtx, hdr.m_dataType, hdr.m_count ); + return true; +} + +bool cac::exceptionRespAction ( callbackManager & cbMutexIn, tcpiiu & iiu, + const epicsTime &, const caHdrLargeArray & hdr, void * pMsgBdy ) +{ + const caHdr * pReq = reinterpret_cast < const caHdr * > ( pMsgBdy ); + unsigned bytesSoFar = sizeof ( *pReq ); + if ( hdr.m_postsize < bytesSoFar ) { + return false; + } + caHdrLargeArray req; + req.m_cmmd = AlignedWireRef < const epicsUInt16 > ( pReq->m_cmmd ); + req.m_postsize = AlignedWireRef < const epicsUInt16 > ( pReq->m_postsize ); + req.m_dataType = AlignedWireRef < const epicsUInt16 > ( pReq->m_dataType ); + req.m_count = AlignedWireRef < const epicsUInt16 > ( pReq->m_count ); + req.m_cid = AlignedWireRef < const epicsUInt32 > ( pReq->m_cid ); + req.m_available = AlignedWireRef < const epicsUInt32 > ( pReq->m_available ); + const ca_uint32_t * pLW = reinterpret_cast < const ca_uint32_t * > ( pReq + 1 ); + if ( req.m_postsize == 0xffff ) { + static const unsigned annexSize = + sizeof ( req.m_postsize ) + sizeof ( req.m_count ); + bytesSoFar += annexSize; + if ( hdr.m_postsize < bytesSoFar ) { + return false; + } + req.m_postsize = AlignedWireRef < const epicsUInt32 > ( pLW[0] ); + req.m_count = AlignedWireRef < const epicsUInt32 > ( pLW[1] ); + pLW += 2u; + } + + // execute the exception message + pExcepProtoStubTCP pStub; + if ( hdr.m_cmmd >= NELEMENTS ( cac::tcpExcepJumpTableCAC ) ) { + pStub = &cac::defaultExcep; + } + else { + pStub = cac::tcpExcepJumpTableCAC [req.m_cmmd]; + } + const char *pCtx = reinterpret_cast < const char * > ( pLW ); + return ( this->*pStub ) ( cbMutexIn, iiu, req, pCtx, hdr.m_available ); +} + +bool cac::accessRightsRespAction ( + callbackManager & mgr, tcpiiu &, + const epicsTime &, const caHdrLargeArray & hdr, void * /* pMsgBody */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + nciu * pChan = this->chanTable.lookup ( hdr.m_cid ); + if ( pChan ) { + unsigned ar = hdr.m_available; + caAccessRights accessRights ( + ( ar & CA_PROTO_ACCESS_RIGHT_READ ) ? true : false, + ( ar & CA_PROTO_ACCESS_RIGHT_WRITE ) ? true : false); + pChan->accessRightsStateChange ( accessRights, mgr.cbGuard, guard ); + } + + return true; +} + +bool cac::createChannelRespAction ( + callbackManager & mgr, tcpiiu & iiu, + const epicsTime &, const caHdrLargeArray & hdr, void * /* pMsgBody */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + nciu * pChan = this->chanTable.lookup ( hdr.m_cid ); + if ( pChan ) { + unsigned sidTmp; + if ( iiu.ca_v44_ok ( guard ) ) { + sidTmp = hdr.m_available; + } + else { + sidTmp = pChan->getSID (guard); + } + bool wasExpected = iiu.connectNotify ( guard, *pChan ); + if ( wasExpected ) { + pChan->connect ( hdr.m_dataType, hdr.m_count, sidTmp, + mgr.cbGuard, guard ); + } + else { + errlogPrintf ( + "CA Client Library: Ignored duplicate create channel " + "response from CA server?\n" ); + } + } + else if ( iiu.ca_v44_ok ( guard ) ) { + // this indicates a claim response for a resource that does + // not exist in the client - so just remove it from the server + iiu.clearChannelRequest ( guard, hdr.m_available, hdr.m_cid ); + } + + return true; +} + +bool cac::verifyAndDisconnectChan ( + callbackManager & mgr, tcpiiu &, + const epicsTime &, const caHdrLargeArray & hdr, void * /* pMsgBody */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + nciu * pChan = this->chanTable.lookup ( hdr.m_cid ); + if ( ! pChan ) { + return true; + } + this->disconnectChannel ( mgr.cbGuard, guard, *pChan ); + return true; +} + +void cac::disconnectChannel ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + assert ( this->pudpiiu ); + chan.disconnectAllIO ( cbGuard, guard ); + chan.getPIIU(guard)->uninstallChan ( guard, chan ); + this->pudpiiu->installDisconnectedChannel ( guard, chan ); + chan.unresponsiveCircuitNotify ( cbGuard, guard ); +} + +bool cac::badTCPRespAction ( callbackManager &, tcpiiu & iiu, + const epicsTime &, const caHdrLargeArray & hdr, void * /* pMsgBody */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + char hostName[64]; + iiu.getHostName ( guard, hostName, sizeof ( hostName ) ); + errlogPrintf ( "CAC: Undecipherable TCP message ( bad response type %u ) from %s\n", + hdr.m_cmmd, hostName ); + return false; +} + +bool cac::executeResponse ( callbackManager & mgr, tcpiiu & iiu, + const epicsTime & currentTime, caHdrLargeArray & hdr, char * pMshBody ) +{ + // execute the response message + pProtoStubTCP pStub; + if ( hdr.m_cmmd >= NELEMENTS ( cac::tcpJumpTableCAC ) ) { + pStub = &cac::badTCPRespAction; + } + else { + pStub = cac::tcpJumpTableCAC [hdr.m_cmmd]; + } + return ( this->*pStub ) ( mgr, iiu, currentTime, hdr, pMshBody ); +} + +void cac::selfTest ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + this->chanTable.verify (); + this->ioTable.verify (); + this->beaconTable.verify (); +} + +void cac::destroyIIU ( tcpiiu & iiu ) +{ + { + callbackManager mgr ( this->notify, this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->mutex ); + + if ( iiu.channelCount ( guard ) ) { + char hostNameTmp[64]; + iiu.getHostName ( guard, hostNameTmp, sizeof ( hostNameTmp ) ); + genLocalExcep ( mgr.cbGuard, guard, *this, ECA_DISCONN, hostNameTmp ); + } + osiSockAddr addr = iiu.getNetworkAddress ( guard ); + if ( addr.sa.sa_family == AF_INET ) { + inetAddrID tmp ( addr.ia ); + bhe * pBHE = this->beaconTable.lookup ( tmp ); + if ( pBHE ) { + pBHE->unregisterIIU ( guard, iiu ); + } + } + + assert ( this->pudpiiu ); + iiu.disconnectAllChannels ( mgr.cbGuard, guard, *this->pudpiiu ); + + this->serverTable.remove ( iiu ); + this->circuitList.remove ( iiu ); + } + + // this destroys a timer that takes the primary mutex + // so we must not hold the primary mutex here + // + // this waits for send/recv threads to exit + // this also uses the cac free lists so cac must wait + // for this to finish before it shuts down + + iiu.~tcpiiu (); + + { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->freeListVirtualCircuit.release ( & iiu ); + this->iiuExistenceCount--; + // signal iiu uninstall event so that cac can properly shut down + this->iiuUninstall.signal(); + } + // do not touch "this" after lock is released above +} + +double cac::beaconPeriod ( + epicsGuard < epicsMutex > & guard, + const nciu & chan ) const +{ + const netiiu * pIIU = chan.getConstPIIU ( guard ); + if ( pIIU ) { + osiSockAddr addr = pIIU->getNetworkAddress ( guard ); + if ( addr.sa.sa_family == AF_INET ) { + inetAddrID tmp ( addr.ia ); + bhe *pBHE = this->beaconTable.lookup ( tmp ); + if ( pBHE ) { + return pBHE->period ( guard ); + } + } + } + return - DBL_MAX; +} + +void cac::initiateConnect ( + epicsGuard < epicsMutex > & guard, + nciu & chan, netiiu * & piiu ) +{ + guard.assertIdenticalMutex ( this->mutex ); + assert ( this->pudpiiu ); + this->pudpiiu->installNewChannel ( guard, chan, piiu ); +} + +void *cacComBufMemoryManager::allocate ( size_t size ) +{ + return this->freeList.allocate ( size ); +} + +void cacComBufMemoryManager::release ( void * pCadaver ) +{ + this->freeList.release ( pCadaver ); +} + +void cac::pvMultiplyDefinedNotify ( msgForMultiplyDefinedPV & mfmdpv, + const char * pChannelName, const char * pAcc, const char * pRej ) +{ + char buf[256]; + sprintf ( buf, "Channel: \"%.64s\", Connecting to: %.64s, Ignored: %.64s", + pChannelName, pAcc, pRej ); + { + callbackManager mgr ( this->notify, this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->mutex ); + this->exception ( mgr.cbGuard, guard, ECA_DBLCHNL, buf, __FILE__, __LINE__ ); + + // remove from the list under lock + this->msgMultiPVList.remove ( mfmdpv ); + } + // delete msg object + mfmdpv.~msgForMultiplyDefinedPV (); + this->mdpvFreeList.release ( & mfmdpv ); +} + +void cac::registerSearchDest ( + epicsGuard < epicsMutex > & guard, + SearchDest & req ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->searchDestList.add ( req ); +} diff --git a/modules/ca/src/client/cac.h b/modules/ca/src/client/cac.h new file mode 100644 index 000000000..7db5c6ddc --- /dev/null +++ b/modules/ca/src/client/cac.h @@ -0,0 +1,435 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author: Jeff Hill + * + */ + +#ifndef cach +#define cach + +#ifdef epicsExportSharedSymbols +# define cach_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "compilerDependencies.h" +#include "ipAddrToAsciiAsynchronous.h" +#include "msgForMultiplyDefinedPV.h" +#include "epicsTimer.h" +#include "epicsEvent.h" +#include "freeList.h" +#include "localHostName.h" + +#ifdef cach_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "nciu.h" +#include "comBuf.h" +#include "bhe.h" +#include "cacIO.h" +#include "netIO.h" +#include "localHostName.h" +#include "virtualCircuit.h" + +class netWriteNotifyIO; +class netReadNotifyIO; +class netSubscription; +class tcpiiu; + +// used to control access to cac's recycle routines which +// should only be indirectly invoked by CAC when its lock +// is applied +class cacRecycle { +public: + virtual void recycleReadNotifyIO ( + epicsGuard < epicsMutex > &, netReadNotifyIO &io ) = 0; + virtual void recycleWriteNotifyIO ( + epicsGuard < epicsMutex > &, netWriteNotifyIO &io ) = 0; + virtual void recycleSubscription ( + epicsGuard < epicsMutex > &, netSubscription &io ) = 0; +protected: + virtual ~cacRecycle() {} +}; + +struct CASG; +class inetAddrID; +class caServerID; +struct caHdrLargeArray; + +class cacComBufMemoryManager : public comBufMemoryManager +{ +public: + cacComBufMemoryManager () {} + void * allocate ( size_t ); + void release ( void * ); +private: + tsFreeList < comBuf, 0x20 > freeList; + cacComBufMemoryManager ( const cacComBufMemoryManager & ); + cacComBufMemoryManager & operator = ( const cacComBufMemoryManager & ); +}; + +class notifyGuard { +public: + notifyGuard ( cacContextNotify & ); + ~notifyGuard (); +private: + cacContextNotify & notify; + notifyGuard ( const notifyGuard & ); + notifyGuard & operator = ( const notifyGuard & ); +}; + +class callbackManager : public notifyGuard { +public: + callbackManager ( + cacContextNotify &, + epicsMutex & callbackControl ); + epicsGuard < epicsMutex > cbGuard; +}; + +class cac : + public cacContext, + private cacRecycle, + private callbackForMultiplyDefinedPV +{ +public: + cac ( + epicsMutex & mutualExclusion, + epicsMutex & callbackControl, + cacContextNotify & ); + virtual ~cac (); + + // beacon management + void beaconNotify ( const inetAddrID & addr, const epicsTime & currentTime, + ca_uint32_t beaconNumber, unsigned protocolRevision ); + unsigned beaconAnomaliesSinceProgramStart ( + epicsGuard < epicsMutex > & ) const; + + // IO management + void flush ( epicsGuard < epicsMutex > & guard ); + bool executeResponse ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, caHdrLargeArray &, char *pMsgBody ); + + // channel routines + void transferChanToVirtCircuit ( + unsigned cid, unsigned sid, + ca_uint16_t typeCode, arrayElementCount count, + unsigned minorVersionNumber, const osiSockAddr &, + const epicsTime & currentTime ); + cacChannel & createChannel ( + epicsGuard < epicsMutex > & guard, const char * pChannelName, + cacChannelNotify &, cacChannel::priLev ); + void destroyChannel ( + epicsGuard < epicsMutex > &, nciu & ); + void initiateConnect ( + epicsGuard < epicsMutex > &, nciu &, netiiu * & ); + nciu * lookupChannel ( + epicsGuard < epicsMutex > &, const cacChannel::ioid & ); + + // IO requests + netWriteNotifyIO & writeNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, privateInterfaceForIO &, + unsigned type, arrayElementCount nElem, const void * pValue, + cacWriteNotify & ); + netReadNotifyIO & readNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, privateInterfaceForIO &, + unsigned type, arrayElementCount nElem, + cacReadNotify & ); + netSubscription & subscriptionRequest ( + epicsGuard < epicsMutex > &, nciu &, privateInterfaceForIO &, + unsigned type, arrayElementCount nElem, unsigned mask, + cacStateNotify &, bool channelIsInstalled ); + bool destroyIO ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const cacChannel::ioid & idIn, + nciu & chan ); + void disconnectAllIO ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, + nciu &, tsDLList < baseNMIU > & ioList ); + + void ioShow ( + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid &id, unsigned level ) const; + + // exception generation + void exception ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, + int status, const char * pContext, + const char * pFileName, unsigned lineNo ); + + // search destination management + void registerSearchDest ( + epicsGuard < epicsMutex > &, SearchDest & req ); + bool findOrCreateVirtCircuit ( + epicsGuard < epicsMutex > &, const osiSockAddr &, + unsigned, tcpiiu *&, unsigned, SearchDestTCP * pSearchDest = NULL ); + + // diagnostics + unsigned circuitCount ( epicsGuard < epicsMutex > & ) const; + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; + int printFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char *pformat, ... ) const; + int varArgsPrintFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char *pformat, va_list args ) const; + double connectionTimeout ( epicsGuard < epicsMutex > & ); + + unsigned maxContiguousFrames ( epicsGuard < epicsMutex > & ) const; + + // misc + const char * userNamePointer () const; + unsigned getInitializingThreadsPriority () const; + epicsMutex & mutexRef (); + void attachToClientCtx (); + void selfTest ( + epicsGuard < epicsMutex > & ) const; + double beaconPeriod ( + epicsGuard < epicsMutex > &, + const nciu & chan ) const; + static unsigned lowestPriorityLevelAbove ( unsigned priority ); + static unsigned highestPriorityLevelBelow ( unsigned priority ); + void destroyIIU ( tcpiiu & iiu ); + + const char * pLocalHostName (); + +private: + epicsSingleton < localHostName > :: reference _refLocalHostName; + chronIntIdResTable < nciu > chanTable; + // + // !!!! There is at this point no good reason + // !!!! to maintain one IO table for all types of + // !!!! IO. It would probably be better to maintain + // !!!! an independent table for each IO type. The + // !!!! new adaptive sized hash table will not + // !!!! waste memory. Making this change will + // !!!! avoid virtual function overhead when + // !!!! accessing the different types of IO. This + // !!!! approach would also probably be safer in + // !!!! terms of detecting damaged protocol. + // + chronIntIdResTable < baseNMIU > ioTable; + resTable < bhe, inetAddrID > beaconTable; + resTable < tcpiiu, caServerID > serverTable; + tsDLList < tcpiiu > circuitList; + tsDLList < SearchDest > searchDestList; + tsDLList < msgForMultiplyDefinedPV > msgMultiPVList; + tsFreeList + < class tcpiiu, 32, epicsMutexNOOP > + freeListVirtualCircuit; + tsFreeList + < class netReadNotifyIO, 1024, epicsMutexNOOP > + freeListReadNotifyIO; + tsFreeList + < class netWriteNotifyIO, 1024, epicsMutexNOOP > + freeListWriteNotifyIO; + tsFreeList + < class netSubscription, 1024, epicsMutexNOOP > + freeListSubscription; + tsFreeList + < class nciu, 1024, epicsMutexNOOP > + channelFreeList; + tsFreeList + < class msgForMultiplyDefinedPV, 16 > + mdpvFreeList; + cacComBufMemoryManager comBufMemMgr; + bheFreeStore bheFreeList; + epicsTime programBeginTime; + double connTMO; + // **** lock hierarchy **** + // 1) callback lock must always be acquired before + // the primary mutex if both locks are needed + epicsMutex & mutex; + epicsMutex & cbMutex; + epicsEvent iiuUninstall; + ipAddrToAsciiEngine & ipToAEngine; + epicsTimerQueueActive & timerQueue; + char * pUserName; + class udpiiu * pudpiiu; + void * tcpSmallRecvBufFreeList; + void * tcpLargeRecvBufFreeList; + cacContextNotify & notify; + epicsThreadId initializingThreadsId; + unsigned initializingThreadsPriority; + unsigned maxRecvBytesTCP; + unsigned maxContigFrames; + unsigned beaconAnomalyCount; + unsigned short _serverPort; + unsigned iiuExistenceCount; + bool cacShutdownInProgress; + + void recycleReadNotifyIO ( + epicsGuard < epicsMutex > &, netReadNotifyIO &io ); + void recycleWriteNotifyIO ( + epicsGuard < epicsMutex > &, netWriteNotifyIO &io ); + void recycleSubscription ( + epicsGuard < epicsMutex > &, netSubscription &io ); + + void disconnectChannel ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, nciu & chan ); + + void ioExceptionNotify ( unsigned id, int status, + const char * pContext, unsigned type, arrayElementCount count ); + void ioExceptionNotifyAndUninstall ( unsigned id, int status, + const char * pContext, unsigned type, arrayElementCount count ); + + void pvMultiplyDefinedNotify ( msgForMultiplyDefinedPV & mfmdpv, + const char * pChannelName, const char * pAcc, const char * pRej ); + + // recv protocol stubs + bool versionAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool echoRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool writeNotifyRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool searchRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool readNotifyRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool eventRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool readRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool clearChannelRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool exceptionRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool accessRightsRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool createChannelRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool verifyAndDisconnectChan ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + bool badTCPRespAction ( callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + + typedef bool ( cac::*pProtoStubTCP ) ( + callbackManager &, tcpiiu &, + const epicsTime & currentTime, const caHdrLargeArray &, void *pMsgBdy ); + static const pProtoStubTCP tcpJumpTableCAC []; + + bool defaultExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool eventAddExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool readExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool writeExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool clearChanExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool readNotifyExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + bool writeNotifyExcep ( callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + typedef bool ( cac::*pExcepProtoStubTCP ) ( + callbackManager &, tcpiiu &iiu, const caHdrLargeArray &hdr, + const char *pCtx, unsigned status ); + static const pExcepProtoStubTCP tcpExcepJumpTableCAC []; + + cac ( const cac & ); + cac & operator = ( const cac & ); + + friend class tcpiiu; +}; + +inline const char * cac::userNamePointer () const +{ + return this->pUserName; +} + +inline unsigned cac::getInitializingThreadsPriority () const +{ + return this->initializingThreadsPriority; +} + +inline epicsMutex & cac::mutexRef () +{ + return this->mutex; +} + +inline int cac :: varArgsPrintFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char *pformat, va_list args ) const +{ + callbackControl.assertIdenticalMutex ( this->cbMutex ); + return this->notify.varArgsPrintFormated ( pformat, args ); +} + +inline void cac::attachToClientCtx () +{ + this->notify.attachToClientCtx (); +} + +inline unsigned cac::beaconAnomaliesSinceProgramStart ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->beaconAnomalyCount; +} + +inline notifyGuard::notifyGuard ( cacContextNotify & notifyIn ) : + notify ( notifyIn ) +{ + this->notify.callbackProcessingInitiateNotify (); +} + +inline notifyGuard::~notifyGuard () +{ + this->notify.callbackProcessingCompleteNotify (); +} + +inline callbackManager::callbackManager ( + cacContextNotify & notify, epicsMutex & callbackControl ) : + notifyGuard ( notify ), cbGuard ( callbackControl ) +{ +} + +inline nciu * cac::lookupChannel ( + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid & idIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->chanTable.lookup ( idIn ); +} + +inline const char * cac :: pLocalHostName () +{ + return _refLocalHostName->pointer (); +} + +inline unsigned cac :: + maxContiguousFrames ( epicsGuard < epicsMutex > & ) const +{ + return maxContigFrames; +} + +inline double cac :: + connectionTimeout ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->connTMO; +} + +#endif // ifdef cach diff --git a/modules/ca/src/client/cacChannel.cpp b/modules/ca/src/client/cacChannel.cpp new file mode 100644 index 000000000..c1a52a002 --- /dev/null +++ b/modules/ca/src/client/cacChannel.cpp @@ -0,0 +1,145 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "localHostName.h" +#include "cacIO.h" + +class CACChannelPrivate { +public: + CACChannelPrivate (); + unsigned getHostName ( char * pBuf, unsigned bufLength ); + const char * pHostName (); +private: + epicsSingleton < localHostName > :: reference + _refLocalHostName; +}; + +static epicsThreadOnceId cacChannelIdOnce = EPICS_THREAD_ONCE_INIT; + +const cacChannel::priLev cacChannel::priorityMax = 99u; +const cacChannel::priLev cacChannel::priorityMin = 0u; +const cacChannel::priLev cacChannel::priorityDefault = priorityMin; +const cacChannel::priLev cacChannel::priorityLinksDB = priorityMax; +const cacChannel::priLev cacChannel::priorityArchive = ( priorityMax - priorityMin ) / 2; +const cacChannel::priLev cacChannel::priorityOPI = priorityMin; + +cacChannel::~cacChannel () +{ +} + +caAccessRights cacChannel::accessRights ( + epicsGuard < epicsMutex > & ) const +{ + static caAccessRights ar ( true, true ); + return ar; +} + +unsigned cacChannel::searchAttempts ( + epicsGuard < epicsMutex > & ) const +{ + return 0u; +} + +double cacChannel::beaconPeriod ( + epicsGuard < epicsMutex > & ) const +{ + return - DBL_MAX; +} + +double cacChannel::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const +{ + return - DBL_MAX; +} + +bool cacChannel::ca_v42_ok ( + epicsGuard < epicsMutex > & ) const +{ + return true; +} + +bool cacChannel::connected ( + epicsGuard < epicsMutex > & ) const +{ + return true; +} + +CACChannelPrivate :: + CACChannelPrivate() : + _refLocalHostName ( localHostNameCache.getReference () ) +{ +} + +inline unsigned CACChannelPrivate :: + getHostName ( char * pBuf, unsigned bufLength ) +{ + return _refLocalHostName->getName ( pBuf, bufLength ); +} + +inline const char * CACChannelPrivate :: + pHostName () +{ + return _refLocalHostName->pointer (); +} + +static CACChannelPrivate * pCACChannelPrivate = 0; + +// runs once only for each process +extern "C" void cacChannelSetup ( void * ) +{ + pCACChannelPrivate = new CACChannelPrivate (); +} + +// the default is to assume that it is a locally hosted channel +unsigned cacChannel::getHostName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLength ) const throw () +{ + if ( bufLength ) { + epicsThreadOnce ( & cacChannelIdOnce, cacChannelSetup, 0); + return pCACChannelPrivate->getHostName ( pBuf, bufLength ); + } + return 0u; +} + +// the default is to assume that it is a locally hosted channel +const char * cacChannel::pHostName ( + epicsGuard < epicsMutex > & ) const throw () +{ + epicsThreadOnce ( & cacChannelIdOnce, cacChannelSetup, 0); + return pCACChannelPrivate->pHostName (); +} + +cacContext::~cacContext () {} + +cacService::~cacService () {} + + diff --git a/modules/ca/src/client/cacChannelNotify.cpp b/modules/ca/src/client/cacChannelNotify.cpp new file mode 100644 index 000000000..08d2cab94 --- /dev/null +++ b/modules/ca/src/client/cacChannelNotify.cpp @@ -0,0 +1,34 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include "iocinf.h" + +#define epicsExportSharedSymbols +#include "cacIO.h" +#undef epicsExportSharedSymbols + +cacChannelNotify::~cacChannelNotify () +{ +} + diff --git a/modules/ca/src/client/cacContextNotify.cpp b/modules/ca/src/client/cacContextNotify.cpp new file mode 100644 index 000000000..a4498ac04 --- /dev/null +++ b/modules/ca/src/client/cacContextNotify.cpp @@ -0,0 +1,43 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include + +#include "iocinf.h" + +#define epicsExportSharedSymbols +#include "cacIO.h" +#undef epicsExportSharedSymbols + +cacContextNotify::~cacContextNotify () +{ +} + +void cacContextNotify::callbackProcessingInitiateNotify () +{ +} + +void cacContextNotify::callbackProcessingCompleteNotify () +{ +} + + + diff --git a/modules/ca/src/client/cacIO.h b/modules/ca/src/client/cacIO.h new file mode 100644 index 000000000..4e8af4a05 --- /dev/null +++ b/modules/ca/src/client/cacIO.h @@ -0,0 +1,392 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef cacIOh +#define cacIOh + +// +// Open Issues +// ----------- +// +// 1) A status code from the old client side interface is passed +// to the exception notify callback. Should we just pass a string? +// If so, then how do they detect the type of error and recover. +// Perhaps we should call a different vf for each type of exception. +// +// 2) Some exception types are present here but there is no common +// exception base class in use. +// +// 3) Should I be passing the channel reference in cacChannelNotify? +// +// 4) Should the code for caAccessRights not be inline so that this +// interface is version independent. +// +// + +#include +#include + +#ifdef epicsExportSharedSymbols +# define cacIOh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsDLList.h" +#include "epicsMutex.h" +#include "epicsGuard.h" +#include "epicsThread.h" + +#ifdef cacIOh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + + +class cacChannel; + +typedef unsigned long arrayElementCount; + +// 1) this should not be passing caerr.h status to the exception callback +// 2) needless-to-say the data should be passed here using the new data access API +class epicsShareClass cacWriteNotify { +public: + virtual ~cacWriteNotify () = 0; + virtual void completion ( epicsGuard < epicsMutex > & ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void exception ( + epicsGuard < epicsMutex > &, + int status, const char * pContext, + unsigned type, arrayElementCount count ) = 0; +}; + +// 1) this should not be passing caerr.h status to the exception callback +// 2) needless-to-say the data should be passed here using the new data access API +class epicsShareClass cacReadNotify { +public: + virtual ~cacReadNotify () = 0; + virtual void completion ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void * pData ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void exception ( + epicsGuard < epicsMutex > &, int status, + const char * pContext, unsigned type, + arrayElementCount count ) = 0; +}; + +// 1) this should not be passing caerr.h status to the exception callback +// 2) needless-to-say the data should be passed here using the new data access API +class epicsShareClass cacStateNotify { +public: + virtual ~cacStateNotify () = 0; + virtual void current ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void * pData ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void exception ( + epicsGuard < epicsMutex > &, int status, + const char *pContext, unsigned type, + arrayElementCount count ) = 0; +}; + +class caAccessRights { +public: + caAccessRights ( + bool readPermit = false, + bool writePermit = false, + bool operatorConfirmationRequest = false); + void setReadPermit (); + void setWritePermit (); + void setOperatorConfirmationRequest (); + void clrReadPermit (); + void clrWritePermit (); + void clrOperatorConfirmationRequest (); + bool readPermit () const; + bool writePermit () const; + bool operatorConfirmationRequest () const; +private: + bool f_readPermit:1; + bool f_writePermit:1; + bool f_operatorConfirmationRequest:1; +}; + +class epicsShareClass cacChannelNotify { +public: + virtual ~cacChannelNotify () = 0; + virtual void connectNotify ( epicsGuard < epicsMutex > & ) = 0; + virtual void disconnectNotify ( epicsGuard < epicsMutex > & ) = 0; + virtual void serviceShutdownNotify ( + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual void accessRightsNotify ( + epicsGuard < epicsMutex > &, const caAccessRights & ) = 0; + virtual void exception ( + epicsGuard < epicsMutex > &, int status, const char *pContext ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void readException ( + epicsGuard < epicsMutex > &, int status, const char *pContext, + unsigned type, arrayElementCount count, void *pValue ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void writeException ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + unsigned type, arrayElementCount count ) = 0; +}; + +class CallbackGuard : + public epicsGuard < epicsMutex > { +public: + CallbackGuard ( epicsMutex & mutex ) : + epicsGuard < epicsMutex > ( mutex ) {} +private: + CallbackGuard ( const CallbackGuard & ); + CallbackGuard & operator = ( const CallbackGuard & ); +}; + +// +// Notes +// 1) This interface assumes that when a channel is deleted then all +// attached IO is deleted. This is left over from the old interface, +// but perhaps is a bad practice that should be eliminated? If so, +// then the IO should not store or use a pointer to the channel. +// +class epicsShareClass cacChannel { +public: + typedef unsigned priLev; + static const priLev priorityMax; + static const priLev priorityMin; + static const priLev priorityDefault; + static const priLev priorityLinksDB; + static const priLev priorityArchive; + static const priLev priorityOPI; + + typedef unsigned ioid; + enum ioStatus { iosSynch, iosAsynch }; + + cacChannel ( cacChannelNotify & ); + virtual void destroy ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + cacChannelNotify & notify () const; // required ????? + virtual unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () = 0; + // !! deprecated, avoid use !! + virtual const char * pName ( + epicsGuard < epicsMutex > & guard ) const throw () = 0; + virtual void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const = 0; + virtual void initiateConnect ( + epicsGuard < epicsMutex > & ) = 0; + virtual unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual ioStatus read ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + cacReadNotify &, ioid * = 0 ) = 0; + virtual void write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue ) = 0; + virtual ioStatus write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue, cacWriteNotify &, ioid * = 0 ) = 0; + virtual void subscribe ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, unsigned mask, cacStateNotify &, + ioid * = 0 ) = 0; + // The primary mutex must be released when calling the user's + // callback, and therefore a finite interval exists when we are + // moving forward with the intent to call the users callback + // but the users IO could be deleted during this interval. + // To prevent the user's callback from being called after + // destroying his IO we must past a guard for the callback + // mutex here. + virtual void ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & ) = 0; + virtual void ioShow ( + epicsGuard < epicsMutex > &, + const ioid &, unsigned level ) const = 0; + virtual short nativeType ( + epicsGuard < epicsMutex > & ) const = 0; + virtual arrayElementCount nativeElementCount ( + epicsGuard < epicsMutex > & ) const = 0; + virtual caAccessRights accessRights ( + epicsGuard < epicsMutex > & ) const; + virtual unsigned searchAttempts ( + epicsGuard < epicsMutex > & ) const; + virtual double beaconPeriod ( + epicsGuard < epicsMutex > & ) const; // negative DBL_MAX if UKN + virtual double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const; // negative DBL_MAX if UKN + virtual bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const; + virtual bool connected ( + epicsGuard < epicsMutex > & ) const; + virtual unsigned getHostName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLength ) const throw (); + // !! deprecated, avoid use !! + virtual const char * pHostName ( + epicsGuard < epicsMutex > & guard ) const throw (); + + // exceptions + class badString {}; + class badType {}; + class badPriority {}; + class outOfBounds {}; + class badEventSelection {}; + class noWriteAccess {}; + class noReadAccess {}; + class notConnected {}; + class unsupportedByService {}; + class msgBodyCacheTooSmall {}; // hopefully this one goes away in the future + class requestTimedOut {}; + +protected: + virtual ~cacChannel () = 0; + +private: + cacChannelNotify & callback; + cacChannel ( const cacChannel & ); + cacChannel & operator = ( const cacChannel & ); +}; + +class epicsShareClass cacContext { +public: + virtual ~cacContext (); + virtual cacChannel & createChannel ( + epicsGuard < epicsMutex > &, + const char * pChannelName, cacChannelNotify &, + cacChannel::priLev = cacChannel::priorityDefault ) = 0; + virtual void flush ( + epicsGuard < epicsMutex > & ) = 0; + virtual unsigned circuitCount ( + epicsGuard < epicsMutex > & ) const = 0; + virtual void selfTest ( + epicsGuard < epicsMutex > & ) const = 0; + virtual unsigned beaconAnomaliesSinceProgramStart ( + epicsGuard < epicsMutex > & ) const = 0; + virtual void show ( + epicsGuard < epicsMutex > &, unsigned level ) const = 0; +}; + +class epicsShareClass cacContextNotify { +public: + virtual ~cacContextNotify () = 0; + virtual cacContext & createNetworkContext ( + epicsMutex & mutualExclusion, epicsMutex & callbackControl ) = 0; +// we should probably have a different vf for each type of exception ???? + virtual void exception ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + const char * pFileName, unsigned lineNo ) = 0; +// perhaps this should be phased out in deference to the exception mechanism + virtual int varArgsPrintFormated ( const char * pformat, va_list args ) const = 0; +// backwards compatibility (from here down) + virtual void attachToClientCtx () = 0; + virtual void callbackProcessingInitiateNotify () = 0; + virtual void callbackProcessingCompleteNotify () = 0; +}; + +// **** Lock Hierarchy **** +// callbackControl must be taken before mutualExclusion if both are held at +// the same time +class epicsShareClass cacService { +public: + virtual ~cacService () = 0; + virtual cacContext & contextCreate ( + epicsMutex & mutualExclusion, + epicsMutex & callbackControl, + cacContextNotify & ) = 0; +}; + +epicsShareFunc void epicsShareAPI caInstallDefaultService ( cacService & service ); + +epicsShareExtern epicsThreadPrivateId caClientCallbackThreadId; + +inline cacChannel::cacChannel ( cacChannelNotify & notify ) : + callback ( notify ) +{ +} + +inline cacChannelNotify & cacChannel::notify () const +{ + return this->callback; +} + +inline caAccessRights::caAccessRights ( + bool readPermit, bool writePermit, bool operatorConfirmationRequest) : + f_readPermit ( readPermit ), f_writePermit ( writePermit ), + f_operatorConfirmationRequest ( operatorConfirmationRequest ) {} + +inline void caAccessRights::setReadPermit () +{ + this->f_readPermit = true; +} + +inline void caAccessRights::setWritePermit () +{ + this->f_writePermit = true; +} + +inline void caAccessRights::setOperatorConfirmationRequest () +{ + this->f_operatorConfirmationRequest = true; +} + +inline void caAccessRights::clrReadPermit () +{ + this->f_readPermit = false; +} + +inline void caAccessRights::clrWritePermit () +{ + this->f_writePermit = false; +} + +inline void caAccessRights::clrOperatorConfirmationRequest () +{ + this->f_operatorConfirmationRequest = false; +} + +inline bool caAccessRights::readPermit () const +{ + return this->f_readPermit; +} + +inline bool caAccessRights::writePermit () const +{ + return this->f_writePermit; +} + +inline bool caAccessRights::operatorConfirmationRequest () const +{ + return this->f_operatorConfirmationRequest; +} + +#endif // ifndef cacIOh diff --git a/modules/ca/src/client/cacReadNotify.cpp b/modules/ca/src/client/cacReadNotify.cpp new file mode 100644 index 000000000..23284c8df --- /dev/null +++ b/modules/ca/src/client/cacReadNotify.cpp @@ -0,0 +1,30 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include "iocinf.h" + +#define epicsExportSharedSymbols +#include "cacIO.h" +#undef epicsExportSharedSymbols + +cacReadNotify::~cacReadNotify () +{ +} diff --git a/modules/ca/src/client/cacStateNotify.cpp b/modules/ca/src/client/cacStateNotify.cpp new file mode 100644 index 000000000..08852489a --- /dev/null +++ b/modules/ca/src/client/cacStateNotify.cpp @@ -0,0 +1,30 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include "iocinf.h" + +#define epicsExportSharedSymbols +#include "cacIO.h" +#undef epicsExportSharedSymbols + +cacStateNotify::~cacStateNotify () +{ +} diff --git a/modules/ca/src/client/cacWriteNotify.cpp b/modules/ca/src/client/cacWriteNotify.cpp new file mode 100644 index 000000000..13d47cd45 --- /dev/null +++ b/modules/ca/src/client/cacWriteNotify.cpp @@ -0,0 +1,30 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include "iocinf.h" + +#define epicsExportSharedSymbols +#include "cacIO.h" +#undef epicsExportSharedSymbols + +cacWriteNotify::~cacWriteNotify () +{ +} diff --git a/modules/ca/src/client/cadef.h b/modules/ca/src/client/cadef.h new file mode 100644 index 000000000..e62dd7249 --- /dev/null +++ b/modules/ca/src/client/cadef.h @@ -0,0 +1,904 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + * + */ + +#ifndef INCLcadefh +#define INCLcadefh + +/* + * done in two ifdef steps so that we will remain compatible with + * traditional C + */ +#ifndef CA_DONT_INCLUDE_STDARGH +# include +#endif + +#ifdef epicsExportSharedSymbols +# define INCLcadefh_accessh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsThread.h" + +#ifdef INCLcadefh_accessh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + + +#include "caerr.h" +#include "db_access.h" +#include "caeventmask.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct oldChannelNotify *chid; +typedef chid chanId; /* for when the structures field name is "chid" */ +typedef long chtype; +typedef struct oldSubscription *evid; +typedef double ca_real; + +/* arguments passed to user connection handlers */ +struct connection_handler_args { + chanId chid; /* channel id */ + long op; /* one of CA_OP_CONN_UP or CA_OP_CONN_DOWN */ +}; + +typedef void caCh (struct connection_handler_args args); + +typedef struct ca_access_rights { + unsigned read_access:1; + unsigned write_access:1; +} caar; + +/* arguments passed to user access rights handlers */ +struct access_rights_handler_args { + chanId chid; /* channel id */ + caar ar; /* new access rights state */ +}; + +typedef void caArh (struct access_rights_handler_args args); + +/* The conversion routine to call for each type */ +#define VALID_TYPE(TYPE) (((unsigned short)TYPE)<=LAST_BUFFER_TYPE) + +/* + * Arguments passed to event handlers and get/put call back handlers. + * + * The status field below is the CA ECA_XXX status of the requested + * operation which is saved from when the operation was attempted in the + * server and copied back to the clients call back routine. + * If the status is not ECA_NORMAL then the dbr pointer will be NULL + * and the requested operation can not be assumed to be successful. + */ +typedef struct event_handler_args { + void *usr; /* user argument supplied with request */ + chanId chid; /* channel id */ + long type; /* the type of the item returned */ + long count; /* the element count of the item returned */ + const void *dbr; /* a pointer to the item returned */ + int status; /* ECA_XXX status of the requested op from the server */ +} evargs; +typedef void caEventCallBackFunc (struct event_handler_args); + +epicsShareFunc void epicsShareAPI ca_test_event +( + struct event_handler_args +); + +/* arguments passed to user exception handlers */ +struct exception_handler_args { + void *usr; /* user argument supplied when installed */ + chanId chid; /* channel id (may be nill) */ + long type; /* type requested */ + long count; /* count requested */ + void *addr; /* user's address to write results of CA_OP_GET */ + long stat; /* channel access ECA_XXXX status code */ + long op; /* CA_OP_GET, CA_OP_PUT, ..., CA_OP_OTHER */ + const char *ctx; /* a character string containing context info */ + const char *pFile; /* source file name (may be NULL) */ + unsigned lineNo; /* source file line number (may be zero) */ +}; + +typedef unsigned CA_SYNC_GID; + +/* + * External OP codes for CA operations + */ +#define CA_OP_GET 0 +#define CA_OP_PUT 1 +#define CA_OP_CREATE_CHANNEL 2 +#define CA_OP_ADD_EVENT 3 +#define CA_OP_CLEAR_EVENT 4 +#define CA_OP_OTHER 5 + +/* + * used with connection_handler_args + */ +#define CA_OP_CONN_UP 6 +#define CA_OP_CONN_DOWN 7 + +/* depricated */ +#define CA_OP_SEARCH 2 + +/* + * provides efficient test and display of channel access errors + */ +#define SEVCHK(CA_ERROR_CODE, MESSAGE_STRING) \ +{ \ + int ca_unique_status_name = (CA_ERROR_CODE); \ + if(!(ca_unique_status_name & CA_M_SUCCESS)) \ + ca_signal_with_file_and_lineno( \ + ca_unique_status_name, \ + (MESSAGE_STRING), \ + __FILE__, \ + __LINE__); \ +} + + +#define TYPENOTCONN (-1) /* the channel's native type when disconnected */ +epicsShareFunc short epicsShareAPI ca_field_type (chid chan); +epicsShareFunc unsigned long epicsShareAPI ca_element_count (chid chan); +epicsShareFunc const char * epicsShareAPI ca_name (chid chan); +epicsShareFunc void epicsShareAPI ca_set_puser (chid chan, void *puser); +epicsShareFunc void * epicsShareAPI ca_puser (chid chan); +epicsShareFunc unsigned epicsShareAPI ca_read_access (chid chan); +epicsShareFunc unsigned epicsShareAPI ca_write_access (chid chan); + +/* + * cs_ - `channel state' + * + * cs_never_conn valid chid, IOC not found + * cs_prev_conn valid chid, IOC was found, but unavailable + * cs_conn valid chid, IOC was found, still available + * cs_closed channel deleted by user + */ +enum channel_state {cs_never_conn, cs_prev_conn, cs_conn, cs_closed}; +epicsShareFunc enum channel_state epicsShareAPI ca_state (chid chan); + +/************************************************************************/ +/* Perform Library Initialization */ +/* */ +/* Must be called once before calling any of the other routines */ +/************************************************************************/ +epicsShareFunc int epicsShareAPI ca_task_initialize (void); +enum ca_preemptive_callback_select +{ ca_disable_preemptive_callback, ca_enable_preemptive_callback }; +epicsShareFunc int epicsShareAPI + ca_context_create (enum ca_preemptive_callback_select select); +epicsShareFunc void epicsShareAPI ca_detach_context (); + +/************************************************************************/ +/* Remove CA facility from your task */ +/* */ +/* Normally called automatically at task exit */ +/************************************************************************/ +epicsShareFunc int epicsShareAPI ca_task_exit (void); +epicsShareFunc void epicsShareAPI ca_context_destroy (void); + +typedef unsigned capri; +#define CA_PRIORITY_MAX 99 +#define CA_PRIORITY_MIN 0 +#define CA_PRIORITY_DEFAULT CA_PRIORITY_MIN + +#define CA_PRIORITY_DB_LINKS 80 +#define CA_PRIORITY_ARCHIVE 20 +#define CA_PRIORITY_OPI 0 + +/* + * ca_create_channel () + * + * pChanName R channel name string + * pConnStateCallback R address of connection state change + * callback function + * pUserPrivate R placed in the channel's user private field + * o can be fetched later by ca_puser(CHID) + * o passed as void * arg to *pConnectCallback above + * priority R priority level in the server 0 - 100 + * pChanID RW channel id written here + */ +epicsShareFunc int epicsShareAPI ca_create_channel +( + const char *pChanName, + caCh *pConnStateCallback, + void *pUserPrivate, + capri priority, + chid *pChanID +); + +/* + * ca_change_connection_event() + * + * chan R channel identifier + * pfunc R address of connection call-back function + */ +epicsShareFunc int epicsShareAPI ca_change_connection_event +( + chid chan, + caCh * pfunc +); + +/* + * ca_replace_access_rights_event () + * + * chan R channel identifier + * pfunc R address of access rights call-back function + */ +epicsShareFunc int epicsShareAPI ca_replace_access_rights_event ( + chid chan, + caArh *pfunc +); + +/* + * ca_add_exception_event () + * + * replace the default exception handler + * + * pfunc R address of exception call-back function + * pArg R copy of this pointer passed to exception + * call-back function + */ +typedef void caExceptionHandler (struct exception_handler_args); +epicsShareFunc int epicsShareAPI ca_add_exception_event +( + caExceptionHandler *pfunc, + void *pArg +); + +/* + * ca_clear_channel() + * - deallocate resources reserved for a channel + * + * chanId R channel ID + */ +epicsShareFunc int epicsShareAPI ca_clear_channel +( + chid chanId +); + +/************************************************************************/ +/* Write a value to a channel */ +/************************************************************************/ +/* + * ca_bput() + * + * WARNING: this copies the new value from a string (dbr_string_t) + * (and not as an integer) + * + * chan R channel identifier + * pValue R new channel value string copied from this location + */ +#define ca_bput(chan, pValue) \ +ca_array_put(DBR_STRING, 1u, chan, (const dbr_string_t *) (pValue)) + +/* + * ca_rput() + * + * WARNING: this copies the new value from a dbr_float_t + * + * chan R channel identifier + * pValue R new channel value copied from this location + */ +#define ca_rput(chan,pValue) \ +ca_array_put(DBR_FLOAT, 1u, chan, (const dbr_float_t *) pValue) + +/* + * ca_put() + * + * type R data type from db_access.h + * chan R channel identifier + * pValue R new channel value copied from this location + */ +#define ca_put(type, chan, pValue) ca_array_put (type, 1u, chan, pValue) + +/* + * ca_array_put() + * + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pValue R new channel value copied from this location + */ +epicsShareFunc int epicsShareAPI ca_array_put +( + chtype type, + unsigned long count, + chid chanId, + const void * pValue +); + +/* + * ca_array_put_callback() + * + * This routine functions identically to the original ca put request + * with the addition of a callback to the user supplied function + * after recod processing completes in the IOC. The arguments + * to the user supplied callback function are declared in + * the structure event_handler_args and include the pointer + * sized user argument supplied when ca_array_put_callback() is called. + * + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pValue R new channel value copied from this location + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + */ +epicsShareFunc int epicsShareAPI ca_array_put_callback +( + chtype type, + unsigned long count, + chid chanId, + const void * pValue, + caEventCallBackFunc * pFunc, + void * pArg +); + +#define ca_put_callback(type, chan, pValue, pFunc, pArg) \ + ca_array_put_callback(type, 1u, chan, pValue, pFunc, pArg) + +/************************************************************************/ +/* Read a value from a channel */ +/************************************************************************/ + +/* + * ca_bget() + * + * WARNING: this copies the new value into a string (dbr_string_t) + * (and not into an integer) + * + * chan R channel identifier + * pValue W channel value copied to this location + */ +#define ca_bget(chan, pValue) \ +ca_array_get(DBR_STRING, 1u, chan, (dbr_string_t *)(pValue)) + +/* + * ca_rget() + * + * WARNING: this copies the new value into a 32 bit float (dbr_float_t) + * + * chan R channel identifier + * pValue W channel value copied to this location + */ +#define ca_rget(chan, pValue) \ +ca_array_get(DBR_FLOAT, 1u, chan, (dbr_float_t *)(pValue)) + +/* + * ca_rget() + * + * type R data type from db_access.h + * chan R channel identifier + * pValue W channel value copied to this location + */ +#define ca_get(type, chan, pValue) ca_array_get(type, 1u, chan, pValue) + +/* + * ca_array_get() + * + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pValue W channel value copied to this location + */ +epicsShareFunc int epicsShareAPI ca_array_get +( + chtype type, + unsigned long count, + chid chanId, + void * pValue +); + +/************************************************************************/ +/* Read a value from a channel and run a callback when the value */ +/* returns */ +/* */ +/* */ +/************************************************************************/ +/* + * ca_bget_callback() + * + * WARNING: this returns the new value as a string (dbr_string_t) + * (and not as an integer) + * + * chan R channel identifier + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + */ +#define ca_bget_callback(chan, pFunc, pArg)\ +ca_array_get_callback (DBR_STRING, 1u, chan, pFunc, pArg) + +/* + * ca_rget_callback() + * + * WARNING: this returns the new value as a float (dbr_float_t) + * + * chan R channel identifier + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + */ +#define ca_rget_callback(chan, pFunc, pArg)\ +ca_array_get_callback (DBR_FLOAT, 1u, chan, pFunc, pArg) + +/* + * ca_get_callback() + * + * type R data type from db_access.h + * chan R channel identifier + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + */ +#define ca_get_callback(type, chan, pFunc, pArg)\ +ca_array_get_callback (type, 1u, chan, pFunc, pArg) + +/* + * ca_array_get_callback() + * + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + */ +epicsShareFunc int epicsShareAPI ca_array_get_callback +( + chtype type, + unsigned long count, + chid chanId, + caEventCallBackFunc * pFunc, + void * pArg +); + +/************************************************************************/ +/* Specify a function to be executed whenever significant changes */ +/* occur to a channel. */ +/* NOTES: */ +/* 1) Evid may be omited by passing a NULL pointer */ +/* */ +/* 2) An array count of zero specifies the native db count */ +/* */ +/************************************************************************/ + +/* + * ca_create_subscription () + * + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * mask R event mask - one of {DBE_VALUE, DBE_ALARM, DBE_LOG} + * pFunc R pointer to call-back function + * pArg R copy of this pointer passed to pFunc + * pEventID W event id written at specified address + */ +epicsShareFunc int epicsShareAPI ca_create_subscription +( + chtype type, + unsigned long count, + chid chanId, + long mask, + caEventCallBackFunc * pFunc, + void * pArg, + evid * pEventID +); + +/************************************************************************/ +/* Remove a function from a list of those specified to run */ +/* whenever significant changes occur to a channel */ +/* */ +/************************************************************************/ +/* + * ca_clear_subscription() + * + * eventID R event id + */ +epicsShareFunc int epicsShareAPI ca_clear_subscription +( + evid eventID +); + +epicsShareFunc chid epicsShareAPI ca_evid_to_chid ( evid id ); + + +/************************************************************************/ +/* */ +/* Requested data is not necessarily stable prior to */ +/* return from called subroutine. Call ca_pend_io() */ +/* to guarantee that requested data is stable. Call the routine */ +/* ca_flush_io() to force all outstanding requests to be */ +/* sent out over the network. Significant increases in */ +/* performance have been measured when batching several remote */ +/* requests together into one message. Additional */ +/* improvements can be obtained by performing local processing */ +/* in parallel with outstanding remote processing. */ +/* */ +/* FLOW OF TYPICAL APPLICATION */ +/* */ +/* search() ! Obtain Channel ids */ +/* . ! " */ +/* . ! " */ +/* pend_io ! wait for channels to connect */ +/* */ +/* get() ! several requests for remote info */ +/* get() ! " */ +/* add_event() ! " */ +/* get() ! " */ +/* . */ +/* . */ +/* . */ +/* flush_io() ! send get requests */ +/* ! optional parallel processing */ +/* . ! " */ +/* . ! " */ +/* pend_io() ! wait for replies from get requests */ +/* . ! access to requested data */ +/* . ! " */ +/* pend_event() ! wait for requested events */ +/* */ +/************************************************************************/ + +/************************************************************************/ +/* These routines wait for channel subscription events and call the */ +/* functions specified with add_event when events occur. If the */ +/* timeout is specified as 0 an infinite timeout is assumed. */ +/* ca_flush_io() is called by this routine. If ca_pend_io () */ +/* is called when no IO is outstanding then it will return immediately */ +/* without processing. */ +/************************************************************************/ + +/* + * ca_pend_event() + * + * timeOut R wait for this delay in seconds + */ +epicsShareFunc int epicsShareAPI ca_pend_event (ca_real timeOut); +#define ca_poll() ca_pend_event(1e-12) + +/* + * ca_pend_io() + * + * timeOut R wait for this delay in seconds but return early + * if all get requests (or search requests with null + * connection handler pointer have completed) + */ +epicsShareFunc int epicsShareAPI ca_pend_io (ca_real timeOut); + +/* calls ca_pend_io() if early is true otherwise ca_pend_event() is called */ +epicsShareFunc int epicsShareAPI ca_pend (ca_real timeout, int early); + +/* + * ca_test_io() + * + * returns TRUE when get requests (or search requests with null + * connection handler pointer) are outstanding + */ +epicsShareFunc int epicsShareAPI ca_test_io (void); + +/************************************************************************/ +/* Send out all outstanding messages in the send queue */ +/************************************************************************/ +/* + * ca_flush_io() + */ +epicsShareFunc int epicsShareAPI ca_flush_io (void); + + +/* + * ca_signal() + * + * errorCode R status returned from channel access function + * pCtxStr R context string included with error print out + */ +epicsShareFunc void epicsShareAPI ca_signal +( + long errorCode, + const char *pCtxStr +); + +/* + * ca_signal_with_file_and_lineno() + * errorCode R status returned from channel access function + * pCtxStr R context string included with error print out + * pFileStr R file name string included with error print out + * lineNo R line number included with error print out + * + */ +epicsShareFunc void epicsShareAPI ca_signal_with_file_and_lineno +( + long errorCode, + const char *pCtxStr, + const char *pFileStr, + int lineNo +); + +/* + * ca_signal_formated() + * errorCode R status returned from channel access function + * pFileStr R file name string included with error print out + * lineNo R line number included with error print out + * pFormat R printf dtyle format string (and optional arguments) + * + */ +epicsShareFunc void epicsShareAPI ca_signal_formated (long ca_status, const char *pfilenm, + int lineno, const char *pFormat, ...); + +/* + * ca_host_name_function() + * + * channel R channel identifier + * + * !!!! this function is _not_ thread safe !!!! + */ +epicsShareFunc const char * epicsShareAPI ca_host_name (chid channel); +/* thread safe version */ +epicsShareFunc unsigned epicsShareAPI ca_get_host_name ( chid pChan, + char *pBuf, unsigned bufLength ); + +/* + * CA_ADD_FD_REGISTRATION + * + * call their function with their argument whenever + * a new fd is added or removed + * (for use with a manager of the select system call under UNIX) + * + * if (opened) then fd was created + * if (!opened) then fd was deleted + * + */ +typedef void CAFDHANDLER (void *parg, int fd, int opened); + +/* + * ca_add_fd_registration() + * + * pHandler R pointer to function which is to be called + * when an fd is created or deleted + * pArg R argument passed to above function + */ +epicsShareFunc int epicsShareAPI ca_add_fd_registration +( + CAFDHANDLER *pHandler, + void *pArg +); + + +/* + * CA synch groups + * + * This facility will allow the programmer to create + * any number of synchronization groups. The programmer might then + * interleave IO requests within any of the groups. Once The + * IO operations are initiated then the programmer is free to + * block for IO completion within any one of the groups as needed. + */ + +/* + * ca_sg_create() + * + * create a sync group + * + * pgid W pointer to sync group id that will be written + */ +epicsShareFunc int epicsShareAPI ca_sg_create (CA_SYNC_GID * pgid); + +/* + * ca_sg_delete() + * + * delete a sync group + * + * gid R sync group id + */ +epicsShareFunc int epicsShareAPI ca_sg_delete (const CA_SYNC_GID gid); + +/* + * ca_sg_block() + * + * block for IO performed within a sync group to complete + * + * gid R sync group id + * timeout R wait for this duration prior to timing out + * and returning ECA_TIMEOUT + */ +epicsShareFunc int epicsShareAPI ca_sg_block (const CA_SYNC_GID gid, ca_real timeout); + +/* + * ca_sg_test() + * + * test for sync group IO operations in progress + * + * gid R sync group id + * + * returns one of ECA_BADSYNCGRP, ECA_IOINPROGRESS, ECA_IODONE + */ +epicsShareFunc int epicsShareAPI ca_sg_test (const CA_SYNC_GID gid); + +/* + * ca_sg_reset + * + * gid R sync group id + */ +epicsShareFunc int epicsShareAPI ca_sg_reset(const CA_SYNC_GID gid); + +/* + * ca_sg_array_get() + * + * initiate a get within a sync group + * (essentially a ca_array_get() with a sync group specified) + * + * gid R sync group id + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pValue W channel value copied to this location + */ +epicsShareFunc int epicsShareAPI ca_sg_array_get +( + const CA_SYNC_GID gid, + chtype type, + unsigned long count, + chid chan, + void *pValue +); + +#define ca_sg_get(gid, type, chan, pValue) \ +ca_sg_array_get (gid, type, 1u, chan, pValue) + +/* + * ca_sg_array_put() + * + * initiate a put within a sync group + * (essentially a ca_array_put() with a sync group specified) + * + * gid R sync group id + * type R data type from db_access.h + * count R array element count + * chan R channel identifier + * pValue R new channel value copied from this location + */ +epicsShareFunc int epicsShareAPI ca_sg_array_put +( + const CA_SYNC_GID gid, + chtype type, + unsigned long count, + chid chan, + const void *pValue +); + +#define ca_sg_put(gid, type, chan, pValue) \ +ca_sg_array_put (gid, type, 1u, chan, pValue) + +/* + * ca_sg_stat() + * + * print status of a sync group + * + * gid R sync group id + */ +epicsShareFunc int epicsShareAPI ca_sg_stat (CA_SYNC_GID gid); + +epicsShareFunc void epicsShareAPI ca_dump_dbr (chtype type, unsigned count, const void * pbuffer); + + +/* + * ca_v42_ok() + * + * Put call back is available if the CA server is on version is 4.2 + * or higher. + * + * chan R channel identifier + * + * (returns true or false) + */ +epicsShareFunc int epicsShareAPI ca_v42_ok (chid chan); + +/* + * ca_version() + * + * returns the CA version string + */ +epicsShareFunc const char * epicsShareAPI ca_version (void); + +/* + * ca_replace_printf_handler () + * + * for apps that want to change where ca formatted + * text output goes + * + * use two ifdef's for trad C compatibility + * + * ca_printf_func R pointer to new function called when + * CA prints an error message + */ +#ifndef CA_DONT_INCLUDE_STDARGH +typedef int caPrintfFunc (const char *pformat, va_list args); +epicsShareFunc int epicsShareAPI ca_replace_printf_handler ( + caPrintfFunc *ca_printf_func +); +#endif /*CA_DONT_INCLUDE_STDARGH*/ + +/* + * (for testing purposes only) + */ +epicsShareFunc unsigned epicsShareAPI ca_get_ioc_connection_count (void); +epicsShareFunc int epicsShareAPI ca_preemtive_callback_is_enabled (void); +epicsShareFunc void epicsShareAPI ca_self_test (void); +epicsShareFunc unsigned epicsShareAPI ca_beacon_anomaly_count (void); +epicsShareFunc unsigned epicsShareAPI ca_search_attempts (chid chan); +epicsShareFunc double epicsShareAPI ca_beacon_period (chid chan); +epicsShareFunc double epicsShareAPI ca_receive_watchdog_delay (chid chan); + +/* + * used when an auxillary thread needs to join a CA client context started + * by another thread + */ +epicsShareFunc struct ca_client_context * epicsShareAPI ca_current_context (); +epicsShareFunc int epicsShareAPI ca_attach_context ( struct ca_client_context * context ); + + +epicsShareFunc int epicsShareAPI ca_client_status ( unsigned level ); +epicsShareFunc int epicsShareAPI ca_context_status ( struct ca_client_context *, unsigned level ); + +/* + * deprecated + */ +#define ca_build_channel(NAME,XXXXX,CHIDPTR,YYYYY)\ +ca_build_and_connect(NAME, XXXXX, 1, CHIDPTR, YYYYY, 0, 0) +#define ca_array_build(NAME,XXXXX, ZZZZZZ, CHIDPTR,YYYYY)\ +ca_build_and_connect(NAME, XXXXX, ZZZZZZ, CHIDPTR, YYYYY, 0, 0) +epicsShareFunc int epicsShareAPI ca_build_and_connect + ( const char *pChanName, chtype, unsigned long, + chid * pChanID, void *, caCh * pFunc, void * pArg ); +#define ca_search(pChanName, pChanID)\ +ca_search_and_connect (pChanName, pChanID, 0, 0) +epicsShareFunc int epicsShareAPI ca_search_and_connect + ( const char * pChanName, chid * pChanID, + caCh *pFunc, void * pArg ); +epicsShareFunc int epicsShareAPI ca_channel_status (epicsThreadId tid); +epicsShareFunc int epicsShareAPI ca_clear_event ( evid eventID ); +#define ca_add_event(type,chan,pFunc,pArg,pEventID)\ +ca_add_array_event(type,1u,chan,pFunc,pArg,0.0,0.0,0.0,pEventID) +#define ca_add_delta_event(TYPE,CHID,ENTRY,ARG,DELTA,EVID)\ + ca_add_array_event(TYPE,1,CHID,ENTRY,ARG,DELTA,DELTA,0.0,EVID) +#define ca_add_general_event(TYPE,CHID,ENTRY,ARG,P_DELTA,N_DELTA,TO,EVID)\ +ca_add_array_event(TYPE,1,CHID,ENTRY,ARG,P_DELTA,N_DELTA,TO,EVID) +#define ca_add_array_event(TYPE,COUNT,CHID,ENTRY,ARG,P_DELTA,N_DELTA,TO,EVID)\ +ca_add_masked_array_event(TYPE,COUNT,CHID,ENTRY,ARG,P_DELTA,N_DELTA,TO,EVID, DBE_VALUE | DBE_ALARM) +epicsShareFunc int epicsShareAPI ca_add_masked_array_event + ( chtype type, unsigned long count, chid chanId, caEventCallBackFunc * pFunc, + void * pArg, ca_real p_delta, ca_real n_delta, ca_real timeout, + evid * pEventID, long mask ); + +/* + * defunct + */ +epicsShareFunc int epicsShareAPI ca_modify_user_name ( const char *pUserName ); +epicsShareFunc int epicsShareAPI ca_modify_host_name ( const char *pHostName ); + +#ifdef __cplusplus +} +#endif + +/* + * no additions below this endif + */ +#endif /* ifndef INCLcadefh */ + diff --git a/modules/ca/src/client/caerr.h b/modules/ca/src/client/caerr.h new file mode 100644 index 000000000..53930962d --- /dev/null +++ b/modules/ca/src/client/caerr.h @@ -0,0 +1,160 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeffrey O. Hill + * + */ + + +#ifndef INCLcaerrh +#define INCLcaerrh + +#ifdef epicsExportSharedSymbols +# define INCLcaerrh_accessh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +# include "epicsTypes.h" + +#ifdef INCLcaerrh_accessh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +/* CA Status Code Definitions */ + +#define CA_K_INFO 3 /* successful */ +#define CA_K_ERROR 2 /* failed- continue */ +#define CA_K_SUCCESS 1 /* successful */ +#define CA_K_WARNING 0 /* unsuccessful */ +#define CA_K_SEVERE 4 /* failed- quit */ +#define CA_K_FATAL CA_K_ERROR | CA_K_SEVERE + +#define CA_M_MSG_NO 0x0000FFF8 +#define CA_M_SEVERITY 0x00000007 +#define CA_M_LEVEL 0x00000003 +#define CA_M_SUCCESS 0x00000001 +#define CA_M_ERROR 0x00000002 +#define CA_M_SEVERE 0x00000004 + +#define CA_S_MSG_NO 0x0D +#define CA_S_SEVERITY 0x03 + +#define CA_V_MSG_NO 0x03 +#define CA_V_SEVERITY 0x00 +#define CA_V_SUCCESS 0x00 + +/* Define MACROS to extract/insert individual fields from a status value */ + +#define CA_EXTRACT_MSG_NO(code)\ +( ( (code) & CA_M_MSG_NO ) >> CA_V_MSG_NO ) +#define CA_EXTRACT_SEVERITY(code)\ +( ( (code) & CA_M_SEVERITY ) >> CA_V_SEVERITY ) +#define CA_EXTRACT_SUCCESS(code)\ +( ( (code) & CA_M_SUCCESS ) >> CA_V_SUCCESS ) + +#define CA_INSERT_MSG_NO(code)\ +( ((code)<< CA_V_MSG_NO) & CA_M_MSG_NO ) +#define CA_INSERT_SEVERITY(code)\ +( ((code)<< CA_V_SEVERITY)& CA_M_SEVERITY ) +#define CA_INSERT_SUCCESS(code)\ +( ((code)<< CA_V_SUCCESS) & CA_M_SUCCESS ) + +#define DEFMSG(SEVERITY,NUMBER)\ +(CA_INSERT_MSG_NO(NUMBER) | CA_INSERT_SEVERITY(SEVERITY)) + +/* + * In the lines below "defunct" indicates that current release + * servers and client library will not return this error code, but + * servers on earlier releases that communicate with current clients + * might still generate exceptions with these error constants + */ +#define ECA_NORMAL DEFMSG(CA_K_SUCCESS, 0) /* success */ +#define ECA_MAXIOC DEFMSG(CA_K_ERROR, 1) /* defunct */ +#define ECA_UKNHOST DEFMSG(CA_K_ERROR, 2) /* defunct */ +#define ECA_UKNSERV DEFMSG(CA_K_ERROR, 3) /* defunct */ +#define ECA_SOCK DEFMSG(CA_K_ERROR, 4) /* defunct */ +#define ECA_CONN DEFMSG(CA_K_WARNING, 5) /* defunct */ +#define ECA_ALLOCMEM DEFMSG(CA_K_WARNING, 6) +#define ECA_UKNCHAN DEFMSG(CA_K_WARNING, 7) /* defunct */ +#define ECA_UKNFIELD DEFMSG(CA_K_WARNING, 8) /* defunct */ +#define ECA_TOLARGE DEFMSG(CA_K_WARNING, 9) +#define ECA_TIMEOUT DEFMSG(CA_K_WARNING, 10) +#define ECA_NOSUPPORT DEFMSG(CA_K_WARNING, 11) /* defunct */ +#define ECA_STRTOBIG DEFMSG(CA_K_WARNING, 12) /* defunct */ +#define ECA_DISCONNCHID DEFMSG(CA_K_ERROR, 13) /* defunct */ +#define ECA_BADTYPE DEFMSG(CA_K_ERROR, 14) +#define ECA_CHIDNOTFND DEFMSG(CA_K_INFO, 15) /* defunct */ +#define ECA_CHIDRETRY DEFMSG(CA_K_INFO, 16) /* defunct */ +#define ECA_INTERNAL DEFMSG(CA_K_FATAL, 17) +#define ECA_DBLCLFAIL DEFMSG(CA_K_WARNING, 18) /* defunct */ +#define ECA_GETFAIL DEFMSG(CA_K_WARNING, 19) +#define ECA_PUTFAIL DEFMSG(CA_K_WARNING, 20) +#define ECA_ADDFAIL DEFMSG(CA_K_WARNING, 21) /* defunct */ +#define ECA_BADCOUNT DEFMSG(CA_K_WARNING, 22) +#define ECA_BADSTR DEFMSG(CA_K_ERROR, 23) +#define ECA_DISCONN DEFMSG(CA_K_WARNING, 24) +#define ECA_DBLCHNL DEFMSG(CA_K_WARNING, 25) +#define ECA_EVDISALLOW DEFMSG(CA_K_ERROR, 26) +#define ECA_BUILDGET DEFMSG(CA_K_WARNING, 27) /* defunct */ +#define ECA_NEEDSFP DEFMSG(CA_K_WARNING, 28) /* defunct */ +#define ECA_OVEVFAIL DEFMSG(CA_K_WARNING, 29) /* defunct */ +#define ECA_BADMONID DEFMSG(CA_K_ERROR, 30) +#define ECA_NEWADDR DEFMSG(CA_K_WARNING, 31) /* defunct */ +#define ECA_NEWCONN DEFMSG(CA_K_INFO, 32) /* defunct */ +#define ECA_NOCACTX DEFMSG(CA_K_WARNING, 33) /* defunct */ +#define ECA_DEFUNCT DEFMSG(CA_K_FATAL, 34) /* defunct */ +#define ECA_EMPTYSTR DEFMSG(CA_K_WARNING, 35) /* defunct */ +#define ECA_NOREPEATER DEFMSG(CA_K_WARNING, 36) /* defunct */ +#define ECA_NOCHANMSG DEFMSG(CA_K_WARNING, 37) /* defunct */ +#define ECA_DLCKREST DEFMSG(CA_K_WARNING, 38) /* defunct */ +#define ECA_SERVBEHIND DEFMSG(CA_K_WARNING, 39) /* defunct */ +#define ECA_NOCAST DEFMSG(CA_K_WARNING, 40) /* defunct */ +#define ECA_BADMASK DEFMSG(CA_K_ERROR, 41) +#define ECA_IODONE DEFMSG(CA_K_INFO, 42) +#define ECA_IOINPROGRESS DEFMSG(CA_K_INFO, 43) +#define ECA_BADSYNCGRP DEFMSG(CA_K_ERROR, 44) +#define ECA_PUTCBINPROG DEFMSG(CA_K_ERROR, 45) +#define ECA_NORDACCESS DEFMSG(CA_K_WARNING, 46) +#define ECA_NOWTACCESS DEFMSG(CA_K_WARNING, 47) +#define ECA_ANACHRONISM DEFMSG(CA_K_ERROR, 48) +#define ECA_NOSEARCHADDR DEFMSG(CA_K_WARNING, 49) +#define ECA_NOCONVERT DEFMSG(CA_K_WARNING, 50) +#define ECA_BADCHID DEFMSG(CA_K_ERROR, 51) +#define ECA_BADFUNCPTR DEFMSG(CA_K_ERROR, 52) +#define ECA_ISATTACHED DEFMSG(CA_K_WARNING, 53) +#define ECA_UNAVAILINSERV DEFMSG(CA_K_WARNING, 54) +#define ECA_CHANDESTROY DEFMSG(CA_K_WARNING, 55) +#define ECA_BADPRIORITY DEFMSG(CA_K_ERROR, 56) +#define ECA_NOTTHREADED DEFMSG(CA_K_ERROR, 57) +#define ECA_16KARRAYCLIENT DEFMSG(CA_K_WARNING, 58) +#define ECA_CONNSEQTMO DEFMSG(CA_K_WARNING, 59) +#define ECA_UNRESPTMO DEFMSG(CA_K_WARNING, 60) + +#ifdef __cplusplus +extern "C" { +#endif + +epicsShareFunc const char * epicsShareAPI ca_message(long ca_status); + +epicsShareExtern const char * ca_message_text []; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/modules/ca/src/client/caeventmask.h b/modules/ca/src/client/caeventmask.h new file mode 100644 index 000000000..5f0914509 --- /dev/null +++ b/modules/ca/src/client/caeventmask.h @@ -0,0 +1,44 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +#ifndef INCLcaeventmaskh +#define INCLcaeventmaskh + +/* + event selections + (If any more than 8 of these are needed then update the + select field in the event_block struct in db_event.c from + unsigned char to unsigned short) + + + DBE_VALUE + Trigger an event when a significant change in the channel's value + occurs. Relies on the monitor deadband field under DCT. + + DBE_ARCHIVE (DBE_LOG) + Trigger an event when an archive significant change in the channel's + value occurs. Relies on the archiver monitor deadband field under DCT. + + DBE_ALARM + Trigger an event when the alarm state changes + + DBE_PROPERTY + Trigger an event when a property change (control limit, graphical + limit, status string, enum string ...) occurs. + +*/ + +#define DBE_VALUE (1<<0) +#define DBE_ARCHIVE (1<<1) +#define DBE_LOG DBE_ARCHIVE +#define DBE_ALARM (1<<2) +#define DBE_PROPERTY (1<<3) + +#endif diff --git a/modules/ca/src/client/casw.cpp b/modules/ca/src/client/casw.cpp new file mode 100644 index 000000000..f69632c13 --- /dev/null +++ b/modules/ca/src/client/casw.cpp @@ -0,0 +1,306 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "envDefs.h" +#include "errlog.h" +#include "osiWireFormat.h" + +#include "bhe.h" +#include "udpiiu.h" +#include "inetAddrID.h" + +// using a wrapper class around the free list avoids +// Tornado 2.0.1 GNU compiler bugs +class bheFreeStoreMgr : public bheMemoryManager { +public: + bheFreeStoreMgr () {} + void * allocate ( size_t ); + void release ( void * ); +private: + tsFreeList < class bhe, 0x100 > freeList; + bheFreeStoreMgr ( const bheFreeStoreMgr & ); + bheFreeStoreMgr & operator = ( const bheFreeStoreMgr & ); +}; + +void * bheFreeStoreMgr::allocate ( size_t size ) +{ + return freeList.allocate ( size ); +} + +void bheFreeStoreMgr::release ( void * pCadaver ) +{ + freeList.release ( pCadaver ); +} + +int main ( int argc, char ** argv ) +{ + epicsMutex mutex; + epicsGuard < epicsMutex > guard ( mutex ); + bheFreeStoreMgr bheFreeList; + epicsTime programBeginTime = epicsTime::getCurrent (); + bool validCommandLine = false; + unsigned interest = 0u; + SOCKET sock; + osiSockAddr addr; + osiSocklen_t addrSize; + char buf [0x4000]; + const char *pCurBuf; + const caHdr *pCurMsg; + ca_uint16_t serverPort; + ca_uint16_t repeaterPort; + int status; + + if ( argc == 1 ) { + validCommandLine = true; + } + else if ( argc == 2 ) { + status = sscanf ( argv[1], " -i%u ", & interest ); + if ( status == 1 ) { + validCommandLine = true; + } + } + else if ( argc == 3 ) { + if ( strcmp ( argv[1], "-i" ) == 0 ) { + status = sscanf ( argv[2], " %u ", & interest ); + if ( status == 1 ) { + validCommandLine = true; + } + } + } + + if ( ! validCommandLine ) { + printf ( "usage: casw <-i interestLevel>\n" ); + return 0; + } + + serverPort = + envGetInetPortConfigParam ( &EPICS_CA_SERVER_PORT, + static_cast (CA_SERVER_PORT) ); + + repeaterPort = + envGetInetPortConfigParam ( &EPICS_CA_REPEATER_PORT, + static_cast (CA_REPEATER_PORT) ); + + caStartRepeaterIfNotInstalled ( repeaterPort ); + + sock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if ( sock == INVALID_SOCKET ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("casw: unable to create datagram socket because = \"%s\"\n", + sockErrBuf ); + return -1; + } + + memset ( (char *) &addr, 0 , sizeof (addr) ); + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + addr.ia.sin_port = htons ( 0 ); // any port + status = bind ( sock, &addr.sa, sizeof (addr) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( sock ); + errlogPrintf ( "casw: unable to bind to an unconstrained address because = \"%s\"\n", + sockErrBuf ); + return -1; + } + + osiSockIoctl_t yes = true; + status = socket_ioctl ( sock, FIONBIO, &yes ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( sock ); + errlogPrintf ( "casw: unable to set socket to nonblocking state because \"%s\"\n", + sockErrBuf ); + return -1; + } + + unsigned attemptNumber = 0u; + while ( true ) { + caRepeaterRegistrationMessage ( sock, repeaterPort, attemptNumber ); + epicsThreadSleep ( 0.1 ); + addrSize = ( osiSocklen_t ) sizeof ( addr ); + status = recvfrom ( sock, buf, sizeof ( buf ), 0, + &addr.sa, &addrSize ); + if ( status >= static_cast ( sizeof ( *pCurMsg ) ) ) { + pCurMsg = reinterpret_cast < caHdr * > ( buf ); + epicsUInt16 cmmd = AlignedWireRef < const epicsUInt16 > ( pCurMsg->m_cmmd ); + if ( cmmd == REPEATER_CONFIRM ) { + break; + } + } + + attemptNumber++; + if ( attemptNumber > 100 ) { + epicsSocketDestroy ( sock ); + errlogPrintf ( "casw: unable to register with the CA repeater\n" ); + return -1; + } + } + + osiSockIoctl_t no = false; + status = socket_ioctl ( sock, FIONBIO, &no ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( sock ); + errlogPrintf ( "casw: unable to set socket to blocking state because \"%s\"\n", + sockErrBuf ); + return -1; + } + + resTable < bhe, inetAddrID > beaconTable; + while ( true ) { + + addrSize = ( osiSocklen_t ) sizeof ( addr ); + status = recvfrom ( sock, buf, sizeof ( buf ), 0, + &addr.sa, &addrSize ); + if ( status <= 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( sock ); + errlogPrintf ("casw: error from recv was = \"%s\"\n", + sockErrBuf ); + return -1; + } + + if ( addr.sa.sa_family != AF_INET ) { + continue; + } + + unsigned byteCount = static_cast ( status ); + pCurMsg = reinterpret_cast < const caHdr * > ( ( pCurBuf = buf ) ); + while ( byteCount ) { + AlignedWireRef < const epicsUInt16 > pstSize ( pCurMsg->m_postsize ); + size_t msgSize = pstSize + sizeof ( *pCurMsg ) ; + if ( msgSize > byteCount ) { + errlogPrintf ( "CASW: udp input protocol violation\n" ); + break; + } + + epicsUInt16 cmmd = AlignedWireRef < const epicsUInt16 > ( pCurMsg->m_cmmd ); + if ( cmmd == CA_PROTO_RSRV_IS_UP ) { + bool anomaly = false; + epicsTime previousTime; + struct sockaddr_in ina; + + /* + * this allows a fan-out server to potentially + * insert the true address of the CA server + * + * old servers: + * 1) set this field to one of the ip addresses of the host _or_ + * 2) set this field to INADDR_ANY + * new servers: + * always set this field to INADDR_ANY + * + * clients always assume that if this + * field is set to something that isnt INADDR_ANY + * then it is the overriding IP address of the server. + */ + ina.sin_family = AF_INET; + ina.sin_addr.s_addr = pCurMsg->m_available; + + if ( pCurMsg->m_count != 0 ) { + ina.sin_port = pCurMsg->m_count; + } + else { + /* + * old servers dont supply this and the + * default port must be assumed + */ + ina.sin_port = htons ( serverPort ); + } + + ca_uint32_t beaconNumber = ntohl ( pCurMsg->m_cid ); + unsigned protocolRevision = ntohs ( pCurMsg->m_dataType ); + + epicsTime currentTime = epicsTime::getCurrent(); + + /* + * look for it in the hash table + */ + bhe *pBHE = beaconTable.lookup ( ina ); + if ( pBHE ) { + previousTime = pBHE->updateTime ( guard ); + anomaly = pBHE->updatePeriod ( + guard, programBeginTime, + currentTime, beaconNumber, protocolRevision ); + } + else { + /* + * This is the first beacon seen from this server. + * Wait until 2nd beacon is seen before deciding + * if it is a new server (or just the first + * time that we have seen a server's beacon + * shortly after the program started up) + */ + pBHE = new ( bheFreeList ) + bhe ( mutex, currentTime, beaconNumber, ina ); + if ( pBHE ) { + if ( beaconTable.add ( *pBHE ) < 0 ) { + pBHE->~bhe (); + bheFreeList.release ( pBHE ); + } + } + } + if ( anomaly || interest > 1 ) { + char date[64]; + currentTime.strftime ( date, sizeof ( date ), + "%Y-%m-%d %H:%M:%S.%09f"); + char host[64]; + ipAddrToA ( &ina, host, sizeof ( host ) ); + const char * pPrefix = ""; + if ( interest > 1 ) { + if ( anomaly ) { + pPrefix = "* "; + } + else { + pPrefix = " "; + } + } + printf ( "%s%-40s %s\n", + pPrefix, host, date ); + if ( anomaly && interest > 0 ) { + printf ( "\testimate=%f current=%f\n", + pBHE->period ( guard ), + currentTime - previousTime ); + } + fflush(stdout); + } + } + pCurBuf += msgSize; + pCurMsg = reinterpret_cast < const caHdr * > ( pCurBuf ); + byteCount -= msgSize; + } + } +} diff --git a/modules/ca/src/client/catime.c b/modules/ca/src/client/catime.c new file mode 100644 index 000000000..cccd940bc --- /dev/null +++ b/modules/ca/src/client/catime.c @@ -0,0 +1,685 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * CA performance test + * + * History + * joh 09-12-89 Initial release + * joh 12-20-94 portability + * + * + */ + +#include +#include +#include +#include +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "epicsAssert.h" +#include "epicsTime.h" +#include "cadef.h" +#include "caProto.h" + +#include "caDiagnostics.h" + +#ifndef NULL +#define NULL 0 +#endif + +#define WAIT_FOR_ACK + +typedef struct testItem { + chid chix; + char name[128]; + int type; + int count; + void * pValue; +} ti; + +typedef void tf ( ti *pItems, unsigned iterations, unsigned *pInlineIter ); + +/* + * test_pend() + */ +static void test_pend( +ti *pItems, +unsigned iterations, +unsigned *pInlineIter +) +{ + unsigned i; + int status; + + for (i=0; itype, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_put( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + } +#ifdef WAIT_FOR_ACK + status = ca_array_get (DBR_INT, 1, pItems[0].chix, &val); + SEVCHK (status, NULL); + ca_pend_io(100.0); +#endif + status = ca_array_put( + pItems[0].type, + pItems[0].count, + pItems[0].chix, + pItems[0].pValue); + SEVCHK (status, NULL); + status = ca_flush_io(); + SEVCHK (status, NULL); + + *pInlineIter = 10; +} + +/* + * test_get () + */ +static void test_get( +ti *pItems, +unsigned iterations, +unsigned *pInlineIter +) +{ + ti *pi; + int status; + + for (pi=pItems; pi<&pItems[iterations]; pi++) { + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + } + status = ca_pend_io(1e20); + SEVCHK (status, NULL); + + *pInlineIter = 10; +} + +/* + * test_wait () + */ +static void test_wait ( +ti *pItems, +unsigned iterations, +unsigned *pInlineIter +) +{ + ti *pi; + int status; + + for (pi=pItems; pi<&pItems[iterations]; pi++) { + status = ca_array_get( + pi->type, + pi->count, + pi->chix, + pi->pValue); + SEVCHK (status, NULL); + status = ca_pend_io(100.0); + SEVCHK (status, NULL); + } + + *pInlineIter = 1; +} + +/* + * measure_get_latency + */ +static void measure_get_latency (ti *pItems, unsigned iterations) +{ + epicsTimeStamp end_time; + epicsTimeStamp start_time; + double delay; + double X = 0u; + double XX = 0u; + double max = DBL_MIN; + double min = DBL_MAX; + double mean; + double stdDev; + ti *pi; + int status; + + for ( pi = pItems; pi < &pItems[iterations]; pi++ ) { + epicsTimeGetCurrent ( &start_time ); + status = ca_array_get ( pi->type, pi->count, + pi->chix, pi->pValue ); + SEVCHK ( status, NULL ); + status = ca_pend_io ( 100.0 ); + SEVCHK ( status, NULL ); + + epicsTimeGetCurrent ( &end_time ); + + delay = epicsTimeDiffInSeconds ( &end_time,&start_time ); + + X += delay; + XX += delay*delay; + + if ( delay > max ) { + max = delay; + } + + if ( delay < min ) { + min = delay; + } + } + + mean = X/iterations; + stdDev = sqrt ( XX/iterations - mean*mean ); + printf ( + "Get Latency - " + "mean = %3.1f uS, " + "std dev = %3.1f uS, " + "min = %3.1f uS " + "max = %3.1f uS\n", + mean * 1e6, stdDev * 1e6, + min * 1e6, max * 1e6 ); +} + +/* + * printSearchStat() + */ +static void printSearchStat ( const ti * pi, unsigned iterations ) +{ + unsigned i; + double X = 0u; + double XX = 0u; + double max = DBL_MIN; + double min = DBL_MAX; + double mean; + double stdDev; + + for ( i = 0; i < iterations; i++ ) { + double retry = ca_search_attempts ( pi[i].chix ); + X += retry; + XX += retry * retry; + if ( retry > max ) { + max = retry; + } + if ( retry < min ) { + min = retry; + } + } + + mean = X / iterations; + stdDev = sqrt( XX / iterations - mean * mean ); + printf ( + "Search tries per chan - " + "mean = %3.1f " + "std dev = %3.1f " + "min = %3.1f " + "max = %3.1f\n", + mean, stdDev, min, max); +} + +/* + * timeIt () + */ +void timeIt ( tf *pfunc, ti *pItems, unsigned iterations, + unsigned nBytesSent, unsigned nBytesRecv ) +{ + epicsTimeStamp end_time; + epicsTimeStamp start_time; + double delay; + unsigned inlineIter; + + epicsTimeGetCurrent ( &start_time ); + (*pfunc) ( pItems, iterations, &inlineIter ); + epicsTimeGetCurrent ( &end_time ); + delay = epicsTimeDiffInSeconds ( &end_time, &start_time ); + if ( delay > 0.0 ) { + double freq = ( iterations * inlineIter ) / delay; + printf ( "Per Op, %8.4f uS ( %8.4f MHz )", + 1e6 / freq, freq / 1e6 ); + if ( pItems != NULL ) { + printf(", %8.4f snd Mbps, %8.4f rcv Mbps\n", + (inlineIter*nBytesSent*CHAR_BIT)/(delay*1e6), + (inlineIter*nBytesRecv*CHAR_BIT)/(delay*1e6) ); + } + else { + printf ("\n"); + } + } +} + +/* + * test () + */ +static void test ( ti *pItems, unsigned iterations ) +{ + unsigned payloadSize, dblPayloadSize; + unsigned nBytesSent, nBytesRecv; + + payloadSize = + dbr_size_n ( pItems[0].type, pItems[0].count ); + payloadSize = CA_MESSAGE_ALIGN ( payloadSize ); + + dblPayloadSize = dbr_size [ DBR_DOUBLE ]; + dblPayloadSize = CA_MESSAGE_ALIGN ( dblPayloadSize ); + + if ( payloadSize > dblPayloadSize ) { + unsigned factor = payloadSize / dblPayloadSize; + while ( factor ) { + if ( iterations > 10 * factor ) { + iterations /= factor; + break; + } + factor /= 2; + } + } + + printf ( "\t### async put test ###\n"); + nBytesSent = sizeof ( caHdr ) + CA_MESSAGE_ALIGN( payloadSize ); + nBytesRecv = 0u; + timeIt ( test_put, pItems, iterations, + nBytesSent * iterations, + nBytesRecv * iterations ); + + printf ( "\t### async get test ###\n"); + nBytesSent = sizeof ( caHdr ); + nBytesRecv = sizeof ( caHdr ) + CA_MESSAGE_ALIGN ( payloadSize ); + timeIt ( test_get, pItems, iterations, + nBytesSent * ( iterations ), + nBytesRecv * ( iterations ) ); + + printf ("\t### synch get test ###\n"); + nBytesSent = sizeof ( caHdr ); + nBytesRecv = sizeof ( caHdr ) + CA_MESSAGE_ALIGN ( payloadSize ); + if ( iterations > 100 ) { + iterations /= 100; + } + else if ( iterations > 10 ) { + iterations /= 10; + } + timeIt ( test_wait, pItems, iterations, + nBytesSent * iterations, + nBytesRecv * iterations ); +} + +/* + * catime () + */ +int catime ( const char * channelName, + unsigned channelCount, enum appendNumberFlag appNF ) +{ + unsigned i; + int j; + unsigned strsize; + unsigned nBytesSent, nBytesRecv; + ti *pItemList; + + if ( channelCount == 0 ) { + printf ( "channel count was zero\n" ); + return 0; + } + + pItemList = calloc ( channelCount, sizeof (ti) ); + if ( ! pItemList ) { + return -1; + } + + SEVCHK ( ca_context_create ( ca_disable_preemptive_callback ), + "Unable to initialize" ); + + if ( appNF == appendNumber ) { + printf ( "Testing with %u channels named %snnn\n", + channelCount, channelName ); + } + else { + printf ( "Testing with %u channels named %s\n", + channelCount, channelName ); + } + + strsize = sizeof ( pItemList[0].name ) - 1; + nBytesSent = 0; + nBytesRecv = 0; + for ( i=0; i < channelCount; i++ ) { + if ( appNF == appendNumber ) { + sprintf ( pItemList[i].name,"%.*s%.6u", + (int) (strsize - 15u), channelName, i ); + } + else { + strncpy ( pItemList[i].name, channelName, strsize); + } + pItemList[i].name[strsize]= '\0'; + pItemList[i].count = 0; + pItemList[i].pValue = 0; + nBytesSent += 2 * ( CA_MESSAGE_ALIGN ( strlen ( pItemList[i].name ) ) + + sizeof (caHdr) ); + nBytesRecv += 2 * sizeof (caHdr); + } + + printf ( "Channel Connect Test\n" ); + printf ( "--------------------\n" ); + timeIt ( test_search, pItemList, channelCount, nBytesSent, nBytesRecv ); + printSearchStat ( pItemList, channelCount ); + + for ( i = 0; i < channelCount; i++ ) { + size_t count = ca_element_count ( pItemList[i].chix ); + size_t size = sizeof ( dbr_string_t ) * count; + pItemList[i].count = count; + pItemList[i].pValue = malloc ( size ); + assert ( pItemList[i].pValue ); + } + + printf ( + "channel name=%s, native type=%d, native count=%u\n", + ca_name (pItemList[0].chix), + ca_field_type (pItemList[0].chix), + pItemList[0].count ); + + printf ("Pend Event Test\n"); + printf ( "----------------\n" ); + timeIt ( test_pend, NULL, 100, 0, 0 ); + + for ( i = 0; i < channelCount; i++ ) { + dbr_float_t * pFltVal = ( dbr_float_t * ) pItemList[i].pValue; + double val = i; + val /= channelCount; + for ( j = 0; j < pItemList[i].count; j++ ) { + pFltVal[j] = (dbr_float_t) val; + } + pItemList[i].type = DBR_FLOAT; + } + printf ( "DBR_FLOAT Test\n" ); + printf ( "--------------\n" ); + test ( pItemList, channelCount ); + + for ( i = 0; i < channelCount; i++ ) { + dbr_double_t * pDblVal = ( dbr_double_t * ) pItemList[i].pValue; + double val = i; + val /= channelCount; + for ( j = 0; j < pItemList[i].count; j++ ) { + pDblVal[j] = (dbr_double_t) val; + } + pItemList[i].type = DBR_DOUBLE; + } + printf ( "DBR_DOUBLE Test\n" ); + printf ( "---------------\n" ); + test ( pItemList, channelCount ); + + + for ( i = 0; i < channelCount; i++ ) { + dbr_string_t * pStrVal = ( dbr_string_t * ) pItemList[i].pValue; + double val = i; + val /= channelCount; + for ( j = 0; j < pItemList[i].count; j++ ) { + sprintf ( pStrVal[j], "%f", val ); + } + pItemList[i].type = DBR_STRING; + } + printf ( "DBR_STRING Test\n" ); + printf ( "---------------\n" ); + test ( pItemList, channelCount ); + + for ( i = 0; i < channelCount; i++ ) { + dbr_int_t * pIntVal = ( dbr_int_t * ) pItemList[i].pValue; + double val = i; + val /= channelCount; + for ( j = 0; j < pItemList[i].count; j++ ) { + pIntVal[j] = (dbr_int_t) val; + } + pItemList[i].type = DBR_INT; + } + printf ( "DBR_INT Test\n" ); + printf ( "------------\n" ); + test ( pItemList, channelCount ); + + printf ( "Get Latency Test\n" ); + printf ( "----------------\n" ); + for ( i = 0; i < channelCount; i++ ) { + dbr_double_t * pDblVal = ( dbr_double_t * ) pItemList[i].pValue; + for ( j = 0; j < pItemList[i].count; j++ ) { + pDblVal[j] = 0; + } + pItemList[i].type = DBR_DOUBLE; + } + measure_get_latency ( pItemList, channelCount ); + + printf ( "Free Channel Test\n" ); + printf ( "-----------------\n" ); + timeIt ( test_free, pItemList, channelCount, 0, 0 ); + + SEVCHK ( ca_task_exit (), "Unable to free resources at exit" ); + + for ( i = 0; i < channelCount; i++ ) { + free ( pItemList[i].pValue ); + } + + free ( pItemList ); + + return CATIME_OK; +} + + + + diff --git a/modules/ca/src/client/catimeMain.c b/modules/ca/src/client/catimeMain.c new file mode 100644 index 000000000..598ec453b --- /dev/null +++ b/modules/ca/src/client/catimeMain.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. +\*************************************************************************/ + +#include +#include + +#include "caDiagnostics.h" + +static const unsigned defaultIterations = 10000u; + +int main ( int argc, char **argv ) +{ + const char *pUsage = " [ []]"; + + if ( argc > 1 ) { + char *pname = argv[1]; + if ( argc > 2 ) { + int iterations = atoi (argv[2]); + if ( iterations > 0) { + if ( argc > 3 ) { + if ( argc == 4 ) { + int status; + unsigned appendNumberBool; + status = sscanf ( argv[3], " %u ", &appendNumberBool ); + if ( status == 1 ) { + if ( appendNumberBool ) { + return catime ( pname, (unsigned) iterations, appendNumber ); + } + else { + return catime ( pname, (unsigned) iterations, dontAppendNumber ); + } + } + } + } + else { + return catime ( pname, (unsigned) iterations, dontAppendNumber ); + } + } + } + else { + return catime ( pname, defaultIterations, dontAppendNumber ); + } + } + printf ( "usage: %s %s\n", argv[0], pUsage); + return -1; +} diff --git a/modules/ca/src/client/comBuf.cpp b/modules/ca/src/client/comBuf.cpp new file mode 100644 index 000000000..94245d387 --- /dev/null +++ b/modules/ca/src/client/comBuf.cpp @@ -0,0 +1,66 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "comBuf.h" +#include "errlog.h" + +bool comBuf::flushToWire ( wireSendAdapter & wire, const epicsTime & currentTime ) +{ + unsigned index = this->nextReadIndex; + unsigned finalIndex = this->commitIndex; + while ( index < finalIndex ) { + unsigned nBytes = wire.sendBytes ( + &this->buf[index], finalIndex - index, currentTime ); + if ( nBytes == 0u ) { + this->nextReadIndex = index; + return false; + } + index += nBytes; + } + this->nextReadIndex = index; + return true; +} + +// throwing the exception from a function that isnt inline +// shrinks the GNU compiled object code +void comBuf::throwInsufficentBytesException () +{ + throw comBuf::insufficentBytesAvailable (); +} + +void comBuf::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__ ); +} + +comBufMemoryManager::~comBufMemoryManager () {} diff --git a/modules/ca/src/client/comBuf.h b/modules/ca/src/client/comBuf.h new file mode 100644 index 000000000..59e38780b --- /dev/null +++ b/modules/ca/src/client/comBuf.h @@ -0,0 +1,335 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +#ifndef comBufh +#define comBufh + +#include +#include + +#include "epicsAssert.h" +#include "epicsTypes.h" +#include "tsFreeList.h" +#include "tsDLList.h" +#include "osiWireFormat.h" +#include "compilerDependencies.h" + +static const unsigned comBufSize = 0x4000; + +// this wrapper avoids Tornado 2.0.1 compiler bugs +class comBufMemoryManager { +public: + virtual ~comBufMemoryManager (); + virtual void * allocate ( size_t ) = 0; + virtual void release ( void * ) = 0; +}; + +class wireSendAdapter { +public: + virtual unsigned sendBytes ( const void * pBuf, + unsigned nBytesInBuf, + const class epicsTime & currentTime ) = 0; +protected: + virtual ~wireSendAdapter() {} +}; + +enum swioCircuitState { + swioConnected, + swioPeerHangup, + swioPeerAbort, + swioLinkFailure, + swioLocalAbort +}; +struct statusWireIO { + unsigned bytesCopied; + swioCircuitState circuitState; +}; + +class wireRecvAdapter { +public: + virtual void recvBytes ( void * pBuf, + unsigned nBytesInBuf, statusWireIO & ) = 0; +protected: + virtual ~wireRecvAdapter() {} +}; + +class comBuf : public tsDLNode < comBuf > { +public: + class insufficentBytesAvailable {}; + comBuf (); + unsigned unoccupiedBytes () const; + unsigned occupiedBytes () const; + unsigned uncommittedBytes () const; + static unsigned capacityBytes (); + void clear (); + unsigned copyInBytes ( const void *pBuf, unsigned nBytes ); + unsigned push ( comBuf & ); + template < class T > + bool push ( const T & value ); + template < class T > + unsigned push ( const T * pValue, unsigned nElem ); + unsigned push ( const epicsInt8 * pValue, unsigned nElem ); + unsigned push ( const epicsUInt8 * pValue, unsigned nElem ); + unsigned push ( const epicsOldString * pValue, unsigned nElem ); + void commitIncomming (); + void clearUncommittedIncomming (); + bool copyInAllBytes ( const void *pBuf, unsigned nBytes ); + unsigned copyOutBytes ( void *pBuf, unsigned nBytes ); + bool copyOutAllBytes ( void *pBuf, unsigned nBytes ); + unsigned removeBytes ( unsigned nBytes ); + bool flushToWire ( wireSendAdapter &, const epicsTime & currentTime ); + void fillFromWire ( wireRecvAdapter &, statusWireIO & ); + struct popStatus { + bool success; + bool nowEmpty; + }; + template < class T > + popStatus pop ( T & ); + static void throwInsufficentBytesException (); + void * operator new ( size_t size, + comBufMemoryManager & ); + epicsPlacementDeleteOperator (( void *, comBufMemoryManager & )) +private: + unsigned commitIndex; + unsigned nextWriteIndex; + unsigned nextReadIndex; + epicsUInt8 buf [ comBufSize ]; + void operator delete ( void * ); + template < class T > + bool push ( const T * ); // disabled +}; + +inline void * comBuf::operator new ( size_t size, + comBufMemoryManager & mgr ) +{ + return mgr.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void comBuf::operator delete ( void * pCadaver, + comBufMemoryManager & mgr ) +{ + mgr.release ( pCadaver ); +} +#endif + +inline comBuf::comBuf () : commitIndex ( 0u ), + nextWriteIndex ( 0u ), nextReadIndex ( 0u ) +{ +} + +inline void comBuf :: clear () +{ + this->commitIndex = 0u; + this->nextWriteIndex = 0u; + this->nextReadIndex = 0u; +} + +inline unsigned comBuf :: unoccupiedBytes () const +{ + return sizeof ( this->buf ) - this->nextWriteIndex; +} + +inline unsigned comBuf :: occupiedBytes () const +{ + return this->commitIndex - this->nextReadIndex; +} + +inline unsigned comBuf :: uncommittedBytes () const +{ + return this->nextWriteIndex - this->commitIndex; +} + +inline unsigned comBuf :: push ( comBuf & bufIn ) +{ + unsigned nBytes = this->copyInBytes ( + & bufIn.buf[ bufIn.nextReadIndex ], + bufIn.commitIndex - bufIn.nextReadIndex ); + bufIn.nextReadIndex += nBytes; + return nBytes; +} + +inline unsigned comBuf :: capacityBytes () +{ + return comBufSize; +} + +inline void comBuf :: fillFromWire ( + wireRecvAdapter & wire, statusWireIO & stat ) +{ + wire.recvBytes ( + & this->buf[this->nextWriteIndex], + sizeof ( this->buf ) - this->nextWriteIndex, stat ); + if ( stat.circuitState == swioConnected ) { + this->nextWriteIndex += stat.bytesCopied; + } +} + +template < class T > +inline bool comBuf :: push ( const T & value ) +{ + unsigned index = this->nextWriteIndex; + unsigned available = sizeof ( this->buf ) - index; + if ( sizeof ( value ) > available ) { + return false; + } + WireSet ( value, & this->buf[index] ); + this->nextWriteIndex = index + sizeof ( value ); + return true; +} + +inline unsigned comBuf :: push ( const epicsInt8 *pValue, unsigned nElem ) +{ + return copyInBytes ( pValue, nElem ); +} + +inline unsigned comBuf :: push ( const epicsUInt8 *pValue, unsigned nElem ) +{ + return copyInBytes ( pValue, nElem ); +} + +inline unsigned comBuf :: push ( const epicsOldString * pValue, unsigned nElem ) +{ + unsigned index = this->nextWriteIndex; + unsigned available = sizeof ( this->buf ) - index; + unsigned nBytes = sizeof ( *pValue ) * nElem; + if ( nBytes > available ) { + nElem = available / sizeof ( *pValue ); + nBytes = nElem * sizeof ( *pValue ); + } + memcpy ( &this->buf[ index ], pValue, nBytes ); + this->nextWriteIndex = index + nBytes; + return nElem; +} + +template < class T > +unsigned comBuf :: push ( const T * pValue, unsigned nElem ) +{ + unsigned index = this->nextWriteIndex; + unsigned available = sizeof ( this->buf ) - index; + unsigned nBytes = sizeof ( *pValue ) * nElem; + if ( nBytes > available ) { + nElem = available / sizeof ( *pValue ); + } + for ( unsigned i = 0u; i < nElem; i++ ) { + // allow native floating point formats to be converted to IEEE + WireSet( pValue[i], &this->buf[index] ); + index += sizeof ( *pValue ); + } + this->nextWriteIndex = index; + return nElem; +} + +inline void comBuf :: commitIncomming () +{ + this->commitIndex = this->nextWriteIndex; +} + +inline void comBuf :: clearUncommittedIncomming () +{ + this->nextWriteIndex = this->commitIndex; +} + +inline bool comBuf :: copyInAllBytes ( const void *pBuf, unsigned nBytes ) +{ + unsigned index = this->nextWriteIndex; + unsigned available = sizeof ( this->buf ) - index; + if ( nBytes <= available ) { + memcpy ( & this->buf[index], pBuf, nBytes ); + this->nextWriteIndex = index + nBytes; + return true; + } + return false; +} + +inline unsigned comBuf :: copyInBytes ( const void * pBuf, unsigned nBytes ) +{ + unsigned index = this->nextWriteIndex; + unsigned available = sizeof ( this->buf ) - index; + if ( nBytes > available ) { + nBytes = available; + } + memcpy ( & this->buf[index], pBuf, nBytes ); + this->nextWriteIndex = index + nBytes; + return nBytes; +} + +inline bool comBuf :: copyOutAllBytes ( void * pBuf, unsigned nBytes ) +{ + unsigned index = this->nextReadIndex; + unsigned occupied = this->commitIndex - index; + if ( nBytes <= occupied ) { + memcpy ( pBuf, &this->buf[index], nBytes); + this->nextReadIndex = index + nBytes; + return true; + } + return false; +} + +inline unsigned comBuf :: copyOutBytes ( void *pBuf, unsigned nBytes ) +{ + unsigned index = this->nextReadIndex; + unsigned occupied = this->commitIndex - index; + if ( nBytes > occupied ) { + nBytes = occupied; + } + memcpy ( pBuf, &this->buf[index], nBytes); + this->nextReadIndex = index + nBytes; + return nBytes; +} + +inline unsigned comBuf :: removeBytes ( unsigned nBytes ) +{ + unsigned index = this->nextReadIndex; + unsigned occupied = this->commitIndex - index; + if ( nBytes > occupied ) { + nBytes = occupied; + } + this->nextReadIndex = index + nBytes; + return nBytes; +} + +template < class T > +comBuf :: popStatus comBuf :: pop ( T & returnVal ) +{ + unsigned nrIndex = this->nextReadIndex; + unsigned popIndex = nrIndex + sizeof ( returnVal ); + unsigned cIndex = this->commitIndex; + popStatus status; + status.success = true; + status.nowEmpty = false; + if ( popIndex >= cIndex ) { + if ( popIndex == cIndex ) { + status.nowEmpty = true; + } + else { + status.success = false; + return status; + } + } + WireGet ( & this->buf[ nrIndex ], returnVal ); + this->nextReadIndex = popIndex; + return status; +} + +#endif // ifndef comBufh diff --git a/modules/ca/src/client/comQueRecv.cpp b/modules/ca/src/client/comQueRecv.cpp new file mode 100644 index 000000000..88263544d --- /dev/null +++ b/modules/ca/src/client/comQueRecv.cpp @@ -0,0 +1,269 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "virtualCircuit.h" + +comQueRecv::comQueRecv ( comBufMemoryManager & comBufMemoryManagerIn ): + comBufMemMgr ( comBufMemoryManagerIn ), nBytesPending ( 0u ) +{ +} + +comQueRecv::~comQueRecv () +{ + this->clear (); +} + +void comQueRecv::clear () +{ + comBuf *pBuf; + while ( ( pBuf = this->bufs.get () ) ) { + pBuf->~comBuf (); + this->comBufMemMgr.release ( pBuf ); + } + this->nBytesPending = 0u; +} + +unsigned comQueRecv::copyOutBytes ( epicsInt8 *pBuf, unsigned nBytes ) +{ + unsigned totalBytes = 0u; + do { + comBuf * pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + this->nBytesPending -= totalBytes; + return totalBytes; + } + totalBytes += pComBuf->copyOutBytes ( &pBuf[totalBytes], nBytes - totalBytes ); + if ( pComBuf->occupiedBytes () == 0u ) { + this->bufs.remove ( *pComBuf ); + pComBuf->~comBuf (); + this->comBufMemMgr.release ( pComBuf ); + } + } + while ( totalBytes < nBytes ); + this->nBytesPending -= totalBytes; + return totalBytes; +} + +unsigned comQueRecv::removeBytes ( unsigned nBytes ) +{ + unsigned totalBytes = 0u; + unsigned bytesLeft = nBytes; + while ( bytesLeft ) { + comBuf * pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + this->nBytesPending -= totalBytes; + return totalBytes; + } + unsigned nBytesThisTime = pComBuf->removeBytes ( bytesLeft ); + if ( pComBuf->occupiedBytes () == 0u ) { + this->bufs.remove ( *pComBuf ); + pComBuf->~comBuf (); + this->comBufMemMgr.release ( pComBuf ); + } + if ( nBytesThisTime == 0u) { + break; + } + totalBytes += nBytesThisTime; + bytesLeft = nBytes - totalBytes; + } + this->nBytesPending -= totalBytes; + return totalBytes; +} + +void comQueRecv::popString ( epicsOldString *pStr ) +{ + for ( unsigned i = 0u; i < sizeof ( *pStr ); i++ ) { + pStr[0][i] = this->popInt8 (); + } +} + +void comQueRecv::pushLastComBufReceived ( comBuf & bufIn ) + +{ + bufIn.commitIncomming (); + comBuf * pComBuf = this->bufs.last (); + if ( pComBuf ) { + if ( pComBuf->unoccupiedBytes() ) { + this->nBytesPending += pComBuf->push ( bufIn ); + pComBuf->commitIncomming (); + } + } + unsigned bufBytes = bufIn.occupiedBytes(); + if ( bufBytes ) { + this->nBytesPending += bufBytes; + this->bufs.add ( bufIn ); + } + else { + bufIn.~comBuf (); + this->comBufMemMgr.release ( & bufIn ); + } +} + +// 1) split between buffers expected to run slower +// 2) using canonical unsigned tmp avoids ANSI C conversions to int +// 3) cast required because sizeof(unsigned) >= sizeof(epicsUInt32) +epicsUInt16 comQueRecv::multiBufferPopUInt16 () +{ + epicsUInt16 tmp; + if ( this->occupiedBytes() >= sizeof (tmp) ) { + unsigned byte1 = this->popUInt8 (); + unsigned byte2 = this->popUInt8 (); + tmp = static_cast ( ( byte1 << 8u ) | byte2 ); + } + else { + comBuf::throwInsufficentBytesException (); + tmp = 0u; + } + return tmp; +} + +// 1) split between buffers expected to run slower +// 2) using canonical unsigned temporary avoids ANSI C conversions to int +// 3) cast required because sizeof(unsigned) >= sizeof(epicsUInt32) +epicsUInt32 comQueRecv::multiBufferPopUInt32 () +{ + epicsUInt32 tmp; + if ( this->occupiedBytes() >= sizeof (tmp) ) { + // 1) split between buffers expected to run slower + // 2) using canonical unsigned temporary avoids ANSI C conversions to int + // 3) cast required because sizeof(unsigned) >= sizeof(epicsUInt32) + unsigned byte1 = this->popUInt8(); + unsigned byte2 = this->popUInt8(); + unsigned byte3 = this->popUInt8(); + unsigned byte4 = this->popUInt8(); + tmp = static_cast + ( ( byte1 << 24u ) | ( byte2 << 16u ) | + ( byte3 << 8u ) | byte4 ); + } + else { + comBuf::throwInsufficentBytesException (); + tmp = 0u; // avoid compiler warnings + } + return tmp; +} + +void comQueRecv::removeAndDestroyBuf ( comBuf & buf ) +{ + this->bufs.remove ( buf ); + buf.~comBuf (); + this->comBufMemMgr.release ( & buf ); +} + +epicsUInt8 comQueRecv::popUInt8 () +{ + comBuf * pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + comBuf::throwInsufficentBytesException (); + } + epicsUInt8 tmp = '\0'; + comBuf::popStatus status = pComBuf->pop ( tmp ); + if ( ! status.success ) { + comBuf::throwInsufficentBytesException (); + } + if ( status.nowEmpty ) { + this->removeAndDestroyBuf ( *pComBuf ); + } + this->nBytesPending--; + return tmp; +} + +epicsUInt16 comQueRecv::popUInt16 () +{ + comBuf * pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + comBuf::throwInsufficentBytesException (); + } + // try first for all in one buffer efficent version + epicsUInt16 tmp = 0; + comBuf::popStatus status = pComBuf->pop ( tmp ); + if ( status.success ) { + this->nBytesPending -= sizeof ( epicsUInt16 ); + if ( status.nowEmpty ) { + this->removeAndDestroyBuf ( *pComBuf ); + } + return tmp; + } + return this->multiBufferPopUInt16 (); +} + +epicsUInt32 comQueRecv::popUInt32 () +{ + comBuf *pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + comBuf::throwInsufficentBytesException (); + } + // try first for all in one buffer efficent version + epicsUInt32 tmp = 0; + comBuf::popStatus status = pComBuf->pop ( tmp ); + if ( status.success ) { + this->nBytesPending -= sizeof ( epicsUInt32 ); + if ( status.nowEmpty ) { + this->removeAndDestroyBuf ( *pComBuf ); + } + return tmp; + } + return this->multiBufferPopUInt32 (); +} + +bool comQueRecv::popOldMsgHeader ( caHdrLargeArray & msg ) +{ + // try first for all in one buffer efficent version + comBuf * pComBuf = this->bufs.first (); + if ( ! pComBuf ) { + return false; + } + unsigned avail = pComBuf->occupiedBytes (); + if ( avail >= sizeof ( caHdr ) ) { + pComBuf->pop ( msg.m_cmmd ); + ca_uint16_t smallPostsize = 0; + pComBuf->pop ( smallPostsize ); + msg.m_postsize = smallPostsize; + pComBuf->pop ( msg.m_dataType ); + ca_uint16_t smallCount = 0; + pComBuf->pop ( smallCount ); + msg.m_count = smallCount; + pComBuf->pop ( msg.m_cid ); + pComBuf->pop ( msg.m_available ); + this->nBytesPending -= sizeof ( caHdr ); + if ( avail == sizeof ( caHdr ) ) { + this->removeAndDestroyBuf ( *pComBuf ); + } + return true; + } + else if ( this->occupiedBytes () >= sizeof ( caHdr ) ) { + msg.m_cmmd = this->popUInt16 (); + msg.m_postsize = this->popUInt16 (); + msg.m_dataType = this->popUInt16 (); + msg.m_count = this->popUInt16 (); + msg.m_cid = this->popUInt32 (); + msg.m_available = this->popUInt32 (); + return true; + } + else { + return false; + } +} + diff --git a/modules/ca/src/client/comQueRecv.h b/modules/ca/src/client/comQueRecv.h new file mode 100644 index 000000000..7f3153d8d --- /dev/null +++ b/modules/ca/src/client/comQueRecv.h @@ -0,0 +1,111 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef comQueRecvh +#define comQueRecvh + +#include "comBuf.h" + +class comQueRecv { +public: + comQueRecv ( comBufMemoryManager & ); + ~comQueRecv (); + unsigned occupiedBytes () const; + unsigned copyOutBytes ( epicsInt8 *pBuf, unsigned nBytes ); + unsigned removeBytes ( unsigned nBytes ); + void pushLastComBufReceived ( comBuf & ); + void clear (); + bool popOldMsgHeader ( struct caHdrLargeArray & ); + epicsInt8 popInt8 (); + epicsUInt8 popUInt8 (); + epicsInt16 popInt16 (); + epicsUInt16 popUInt16 (); + epicsInt32 popInt32 (); + epicsUInt32 popUInt32 (); + epicsFloat32 popFloat32 (); + epicsFloat64 popFloat64 (); + void popString ( epicsOldString * ); +private: + tsDLList < comBuf > bufs; + comBufMemoryManager & comBufMemMgr; + unsigned nBytesPending; + epicsUInt16 multiBufferPopUInt16 (); + epicsUInt32 multiBufferPopUInt32 (); + void removeAndDestroyBuf ( comBuf & ); + comQueRecv ( const comQueRecv & ); + comQueRecv & operator = ( const comQueRecv & ); +}; + +inline unsigned comQueRecv::occupiedBytes () const +{ + return this->nBytesPending; +} + +inline epicsInt8 comQueRecv::popInt8 () +{ + return static_cast < epicsInt8 > ( this->popUInt8() ); +} + +inline epicsInt16 comQueRecv::popInt16 () +{ + return static_cast < epicsInt16 > ( this->popUInt16() ); +} + +inline epicsInt32 comQueRecv::popInt32 () +{ + return static_cast < epicsInt32 > ( this->popUInt32() ); +} + +// this has been optimized to aligned convert, maybe more could be done, +// but since it is currently not used ... +inline epicsFloat32 comQueRecv::popFloat32 () +{ + union { + epicsUInt8 _wire[ sizeof ( epicsFloat32 ) ]; + epicsFloat32 _fp; + } tmp; + // optimizer will unroll this loop + for ( unsigned i = 0u; i < sizeof ( tmp._wire ); i++ ) { + tmp._wire[i] = this->popUInt8 (); + } + return AlignedWireRef < epicsFloat32 > ( tmp._fp ); +} + +// this has been optimized to aligned convert, maybe more could be done, +// but since it is currently not used ... +inline epicsFloat64 comQueRecv::popFloat64 () +{ + union { + epicsUInt8 _wire[ sizeof ( epicsFloat64 ) ]; + epicsFloat64 _fp; + } tmp; + // optimizer will unroll this loop + for ( unsigned i = 0u; i < sizeof ( tmp._wire ); i++ ) { + tmp._wire[i] = this->popUInt8 (); + } + return AlignedWireRef < epicsFloat64 > ( tmp._fp ); +} + +#endif // ifndef comQueRecvh diff --git a/modules/ca/src/client/comQueSend.cpp b/modules/ca/src/client/comQueSend.cpp new file mode 100644 index 000000000..6ed516bb3 --- /dev/null +++ b/modules/ca/src/client/comQueSend.cpp @@ -0,0 +1,411 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +// +// Requirements: +// 1) Allow sufficent headroom so that users will be able to perform +// a reasonable amount of IO within CA callbacks without experiencing +// a push/pull deadlock. If a potential push/pull deadlock situation +// occurs then detect and avoid it and provide diagnotic to the user +// via special status. +// 2) Return status to the user when there is insufficent memory to +// queue a complete message. +// 3) return status to the user when a message cant be flushed because +// a connection dropped. +// 4) Do not allocate too much memory in exception situatons (such as +// after a circuit disconnect). +// 5) Avoid allocating more memory than is absolutely necessary to meet +// the above requirements. +// 6) Message fragments must never be sent to the IOC when there isnt +// enough memory to queue part of a message (we also must not force +// a disconnect because the client is starved for memory). +// 7) avoid the need to check status for each byte pushed into the +// protocol stream. +// +// Implementation: +// 1) When queuing a complete message, first test to see if a flush is +// required. If it is a receive thread scheduals the flush with the +// send thread, and otherwise directly execute the system call. The +// send thread must run at a higher priority than the receive thread +// if we are to minimize memory consumption. +// 2) Preallocate space for the entire message prior to copying in the +// message so that message fragments are not flushed out just prior +// to detecting that memory is unavailable. +// 3) Return a special error constant when the following situations +// are detected when the user is attempting to queue a request +// from within a user callback executed by a receive thread: +// a) A user is queuing more requests that demand a response from a +// callback than are removed by the response that initiated the +// callback, and this situation persists for many callbacks until +// all buffering in the system is exausted. +// b) A user is queuing many requests that demand a response from one +// callback until all buffering in the system is exausted. +// c) Some combination of both (a) nad (b). +// +// + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "virtualCircuit.h" +#include "db_access.h" // for dbr_short_t etc + +// nill message alignment pad bytes +const char cacNillBytes [] = +{ + 0, 0, 0, 0, + 0, 0, 0, 0 +}; + +comQueSend::comQueSend ( wireSendAdapter & wireIn, + comBufMemoryManager & comBufMemMgrIn ): + comBufMemMgr ( comBufMemMgrIn ), wire ( wireIn ), + nBytesPending ( 0u ) +{ +} + +comQueSend::~comQueSend () +{ + this->clear (); +} + +void comQueSend::clear () +{ + comBuf *pBuf; + + while ( ( pBuf = this->bufs.get () ) ) { + this->nBytesPending -= pBuf->occupiedBytes (); + pBuf->~comBuf (); + this->comBufMemMgr.release ( pBuf ); + } + this->pFirstUncommited = tsDLIter < comBuf > (); + assert ( this->nBytesPending == 0 ); +} + +void comQueSend::copy_dbr_string ( const void * pValue ) +{ + this->push ( static_cast < const char * > ( pValue ), MAX_STRING_SIZE ); +} + +void comQueSend::copy_dbr_short ( const void * pValue ) +{ + this->push ( * static_cast ( pValue ) ); +} + +void comQueSend::copy_dbr_float ( const void * pValue ) +{ + this->push ( * static_cast ( pValue ) ); +} + +void comQueSend::copy_dbr_char ( const void * pValue ) +{ + this->push ( * static_cast ( pValue ) ); +} + +void comQueSend::copy_dbr_long ( const void * pValue ) +{ + this->push ( * static_cast ( pValue ) ); +} + +void comQueSend::copy_dbr_double ( const void * pValue ) +{ + this->push ( * static_cast ( pValue ) ); +} + +void comQueSend::copy_dbr_invalid ( const void * ) +{ + throw cacChannel::badType (); +} + +const comQueSend::copyScalarFunc_t comQueSend::dbrCopyScalar [39] = { + &comQueSend::copy_dbr_string, + &comQueSend::copy_dbr_short, + &comQueSend::copy_dbr_float, + &comQueSend::copy_dbr_short, // DBR_ENUM + &comQueSend::copy_dbr_char, + &comQueSend::copy_dbr_long, + &comQueSend::copy_dbr_double, + &comQueSend::copy_dbr_invalid, // DBR_STS_SHORT + &comQueSend::copy_dbr_invalid, // DBR_STS_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_STS_ENUM + &comQueSend::copy_dbr_invalid, // DBR_STS_CHAR + &comQueSend::copy_dbr_invalid, // DBR_STS_LONG + &comQueSend::copy_dbr_invalid, // DBR_STS_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_TIME_STRING + &comQueSend::copy_dbr_invalid, // DBR_TIME_INT + &comQueSend::copy_dbr_invalid, // DBR_TIME_SHORT + &comQueSend::copy_dbr_invalid, // DBR_TIME_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_TIME_ENUM + &comQueSend::copy_dbr_invalid, // DBR_TIME_CHAR + &comQueSend::copy_dbr_invalid, // DBR_TIME_LONG + &comQueSend::copy_dbr_invalid, // DBR_TIME_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_GR_STRING + &comQueSend::copy_dbr_invalid, // DBR_GR_SHORT + &comQueSend::copy_dbr_invalid, // DBR_GR_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_GR_ENUM + &comQueSend::copy_dbr_invalid, // DBR_GR_CHAR + &comQueSend::copy_dbr_invalid, // DBR_GR_LONG + &comQueSend::copy_dbr_invalid, // DBR_GR_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_CTRL_STRING + &comQueSend::copy_dbr_invalid, // DBR_CTRL_SHORT + &comQueSend::copy_dbr_invalid, // DBR_CTRL_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_CTRL_ENUM + &comQueSend::copy_dbr_invalid, // DBR_CTRL_CHAR + &comQueSend::copy_dbr_invalid, // DBR_CTRL_LONG + &comQueSend::copy_dbr_invalid, // DBR_CTRL_DOUBLE + &comQueSend::copy_dbr_short, // DBR_PUT_ACKT + &comQueSend::copy_dbr_short, // DBR_PUT_ACKS + &comQueSend::copy_dbr_invalid, // DBR_STSACK_STRING + &comQueSend::copy_dbr_invalid // DBR_CLASS_NAME +}; + +void comQueSend::copy_dbr_string ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast < const char * > ( pValue ), nElem * MAX_STRING_SIZE ); +} + +void comQueSend::copy_dbr_short ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast ( pValue ), nElem ); +} + +void comQueSend::copy_dbr_float ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast ( pValue ), nElem ); +} + +void comQueSend::copy_dbr_char ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast ( pValue ), nElem ); +} + +void comQueSend::copy_dbr_long ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast ( pValue ), nElem ); +} + +void comQueSend::copy_dbr_double ( const void *pValue, unsigned nElem ) +{ + this->push ( static_cast ( pValue ), nElem ); +} + +void comQueSend::copy_dbr_invalid ( const void *, unsigned ) +{ + throw cacChannel::badType (); +} + +const comQueSend::copyVectorFunc_t comQueSend::dbrCopyVector [39] = { + &comQueSend::copy_dbr_string, + &comQueSend::copy_dbr_short, + &comQueSend::copy_dbr_float, + &comQueSend::copy_dbr_short, // DBR_ENUM + &comQueSend::copy_dbr_char, + &comQueSend::copy_dbr_long, + &comQueSend::copy_dbr_double, + &comQueSend::copy_dbr_invalid, // DBR_STS_SHORT + &comQueSend::copy_dbr_invalid, // DBR_STS_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_STS_ENUM + &comQueSend::copy_dbr_invalid, // DBR_STS_CHAR + &comQueSend::copy_dbr_invalid, // DBR_STS_LONG + &comQueSend::copy_dbr_invalid, // DBR_STS_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_TIME_STRING + &comQueSend::copy_dbr_invalid, // DBR_TIME_INT + &comQueSend::copy_dbr_invalid, // DBR_TIME_SHORT + &comQueSend::copy_dbr_invalid, // DBR_TIME_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_TIME_ENUM + &comQueSend::copy_dbr_invalid, // DBR_TIME_CHAR + &comQueSend::copy_dbr_invalid, // DBR_TIME_LONG + &comQueSend::copy_dbr_invalid, // DBR_TIME_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_GR_STRING + &comQueSend::copy_dbr_invalid, // DBR_GR_SHORT + &comQueSend::copy_dbr_invalid, // DBR_GR_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_GR_ENUM + &comQueSend::copy_dbr_invalid, // DBR_GR_CHAR + &comQueSend::copy_dbr_invalid, // DBR_GR_LONG + &comQueSend::copy_dbr_invalid, // DBR_GR_DOUBLE + &comQueSend::copy_dbr_invalid, // DBR_CTRL_STRING + &comQueSend::copy_dbr_invalid, // DBR_CTRL_SHORT + &comQueSend::copy_dbr_invalid, // DBR_CTRL_FLOAT + &comQueSend::copy_dbr_invalid, // DBR_CTRL_ENUM + &comQueSend::copy_dbr_invalid, // DBR_CTRL_CHAR + &comQueSend::copy_dbr_invalid, // DBR_CTRL_LONG + &comQueSend::copy_dbr_invalid, // DBR_CTRL_DOUBLE + &comQueSend::copy_dbr_short, // DBR_PUT_ACKT + &comQueSend::copy_dbr_short, // DBR_PUT_ACKS + &comQueSend::copy_dbr_invalid, // DBR_STSACK_STRING + &comQueSend::copy_dbr_invalid // DBR_CLASS_NAME +}; + +comBuf * comQueSend::popNextComBufToSend () +{ + comBuf *pBuf = this->bufs.get (); + if ( pBuf ) { + unsigned nBytesThisBuf = pBuf->occupiedBytes (); + if ( nBytesThisBuf ) { + assert ( this->nBytesPending >= nBytesThisBuf ); + this->nBytesPending -= nBytesThisBuf; + } + else { + this->bufs.push ( *pBuf ); + pBuf = 0; + } + } + else { + assert ( this->nBytesPending == 0u ); + } + return pBuf; +} + +void comQueSend::insertRequestHeader ( + ca_uint16_t request, ca_uint32_t payloadSize, + ca_uint16_t dataType, ca_uint32_t nElem, ca_uint32_t cid, + ca_uint32_t requestDependent, bool v49Ok ) +{ + if ( payloadSize < 0xffff && nElem < 0xffff ) { + comBuf * pComBuf = this->bufs.last (); + if ( ! pComBuf || pComBuf->unoccupiedBytes() < 16u ) { + pComBuf = newComBuf (); + this->pushComBuf ( *pComBuf ); + } + pComBuf->push ( request ); + pComBuf->push ( static_cast < ca_uint16_t > ( payloadSize ) ); + pComBuf->push ( dataType ); + pComBuf->push ( static_cast < ca_uint16_t > ( nElem ) ); + pComBuf->push ( cid ); + pComBuf->push ( requestDependent ); + } + else if ( v49Ok ) { + comBuf * pComBuf = this->bufs.last (); + if ( ! pComBuf || pComBuf->unoccupiedBytes() < 24u ) { + pComBuf = newComBuf (); + this->pushComBuf ( *pComBuf ); + } + pComBuf->push ( request ); + pComBuf->push ( static_cast < ca_uint16_t > ( 0xffff ) ); + pComBuf->push ( dataType ); + pComBuf->push ( static_cast < ca_uint16_t > ( 0u ) ); + pComBuf->push ( cid ); + pComBuf->push ( requestDependent ); + pComBuf->push ( payloadSize ); + pComBuf->push ( nElem ); + } + else { + throw cacChannel::outOfBounds (); + } +} + +void comQueSend::insertRequestWithPayLoad ( + ca_uint16_t request, unsigned dataType, arrayElementCount nElem, + ca_uint32_t cid, ca_uint32_t requestDependent, + const void * pPayload, bool v49Ok ) +{ + if ( INVALID_DB_REQ ( dataType ) ) { + throw cacChannel::badType (); + } + if ( dataType >= comQueSendCopyDispatchSize ) { + throw cacChannel::badType(); + } + ca_uint32_t size = 0u; + ca_uint32_t payloadSize = 0u; + if ( nElem == 1 ) { + if ( dataType == DBR_STRING ) { + const char * pStr = static_cast < const char * > ( pPayload ); + size = strlen ( pStr ) + 1u; + if ( size > MAX_STRING_SIZE ) { + throw cacChannel::outOfBounds(); + } + payloadSize = CA_MESSAGE_ALIGN ( size ); + this->insertRequestHeader ( request, payloadSize, + static_cast ( dataType ), + nElem, cid, requestDependent, v49Ok ); + this->pushString ( pStr, size ); + } + else { + size = dbr_size[dataType]; + payloadSize = CA_MESSAGE_ALIGN ( size ); + this->insertRequestHeader ( request, payloadSize, + static_cast ( dataType ), + nElem, cid, requestDependent, v49Ok ); + ( this->*dbrCopyScalar [dataType] ) ( pPayload ); + } + } + else { + arrayElementCount maxBytes; + if ( v49Ok ) { + maxBytes = 0xffffffff; + } + else { + maxBytes = MAX_TCP - sizeof ( caHdr ); + } + arrayElementCount maxElem = + ( maxBytes - sizeof (dbr_double_t) - dbr_size[dataType] ) / + dbr_value_size[dataType]; + if ( nElem >= maxElem ) { + throw cacChannel::outOfBounds(); + } + // the above checks verify that the total size + // is lest that 0xffffffff + size = static_cast < ca_uint32_t > + ( dbr_size_n ( dataType, nElem ) ); + payloadSize = CA_MESSAGE_ALIGN ( size ); + this->insertRequestHeader ( request, payloadSize, + static_cast ( dataType ), + static_cast < ca_uint32_t > ( nElem ), + cid, requestDependent, v49Ok ); + ( this->*dbrCopyVector [dataType] ) ( pPayload, nElem ); + } + // set pad bytes to nill + unsigned padSize = payloadSize - size; + if ( padSize ) { + this->pushString ( cacNillBytes, payloadSize - size ); + } +} + +void comQueSend::commitMsg () +{ + while ( this->pFirstUncommited.valid() ) { + this->nBytesPending += this->pFirstUncommited->uncommittedBytes (); + this->pFirstUncommited->commitIncomming (); + this->pFirstUncommited++; + } + // printf ( "NBP: %u\n", this->nBytesPending ); +} + + +void comQueSend::clearUncommitedMsg () +{ + while ( this->pFirstUncommited.valid() ) { + tsDLIter < comBuf > next = this->pFirstUncommited; + next++; + this->pFirstUncommited->clearUncommittedIncomming (); + if ( this->pFirstUncommited->occupiedBytes() == 0u ) { + this->bufs.remove ( *this->pFirstUncommited ); + this->pFirstUncommited->~comBuf (); + this->comBufMemMgr.release ( this->pFirstUncommited.pointer() ); + } + this->pFirstUncommited = next; + } +} + diff --git a/modules/ca/src/client/comQueSend.h b/modules/ca/src/client/comQueSend.h new file mode 100644 index 000000000..808285e7a --- /dev/null +++ b/modules/ca/src/client/comQueSend.h @@ -0,0 +1,238 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef comQueSendh +#define comQueSendh + +#include + +#include "tsDLList.h" +#include "comBuf.h" + +#define comQueSendCopyDispatchSize 39 + +class epicsMutex; +template < class T > class epicsGuard; + +class comQueSendMsgMinder { +public: + comQueSendMsgMinder ( + class comQueSend &, epicsGuard < epicsMutex > & ); + ~comQueSendMsgMinder (); + void commit (); +private: + class comQueSend * pSendQue; +}; + +// +// Notes. +// o calling popNextComBufToSend() will clear any uncommitted bytes +// +class comQueSend { +public: + comQueSend ( wireSendAdapter &, comBufMemoryManager & ); + ~comQueSend (); + void clear (); + unsigned occupiedBytes () const; + bool flushEarlyThreshold ( unsigned nBytesThisMsg ) const; + bool flushBlockThreshold () const; + void pushUInt16 ( const ca_uint16_t value ); + void pushUInt32 ( const ca_uint32_t value ); + void pushFloat32 ( const ca_float32_t value ); + void pushString ( const char *pVal, unsigned nChar ); + void insertRequestHeader ( + ca_uint16_t request, ca_uint32_t payloadSize, + ca_uint16_t dataType, ca_uint32_t nElem, ca_uint32_t cid, + ca_uint32_t requestDependent, bool v49Ok ); + void insertRequestWithPayLoad ( + ca_uint16_t request, unsigned dataType, arrayElementCount nElem, + ca_uint32_t cid, ca_uint32_t requestDependent, + const void * pPayload, bool v49Ok ); + comBuf * popNextComBufToSend (); +private: + comBufMemoryManager & comBufMemMgr; + tsDLList < comBuf > bufs; + tsDLIter < comBuf > pFirstUncommited; + wireSendAdapter & wire; + unsigned nBytesPending; + + typedef void ( comQueSend::*copyScalarFunc_t ) ( + const void * pValue ); + static const copyScalarFunc_t dbrCopyScalar [comQueSendCopyDispatchSize]; + void copy_dbr_string ( const void * pValue ); + void copy_dbr_short ( const void * pValue ); + void copy_dbr_float ( const void * pValue ); + void copy_dbr_char ( const void * pValue ); + void copy_dbr_long ( const void * pValue ); + void copy_dbr_double ( const void * pValue ); + void copy_dbr_invalid ( const void * pValue ); + + typedef void ( comQueSend::*copyVectorFunc_t ) ( + const void * pValue, unsigned nElem ); + static const copyVectorFunc_t dbrCopyVector [comQueSendCopyDispatchSize]; + void copy_dbr_string ( const void *pValue, unsigned nElem ); + void copy_dbr_short ( const void *pValue, unsigned nElem ); + void copy_dbr_float ( const void *pValue, unsigned nElem ); + void copy_dbr_char ( const void *pValue, unsigned nElem ); + void copy_dbr_long ( const void *pValue, unsigned nElem ); + void copy_dbr_double ( const void *pValue, unsigned nElem ); + void copy_dbr_invalid ( const void * pValue, unsigned nElem ); + + void pushComBuf ( comBuf & ); + comBuf * newComBuf (); + + void beginMsg (); + void commitMsg (); + void clearUncommitedMsg (); + + friend class comQueSendMsgMinder; + + // + // visual C++ versions 6 & 7 do not allow out of + // class member template function definition + // + template < class T > + inline void push ( const T *pVal, const unsigned nElem ) + { + comBuf * pLastBuf = this->bufs.last (); + unsigned nCopied; + if ( pLastBuf ) { + nCopied = pLastBuf->push ( pVal, nElem ); + } + else { + nCopied = 0u; + } + while ( nElem > nCopied ) { + comBuf * pComBuf = newComBuf (); + nCopied += pComBuf->push + ( &pVal[nCopied], nElem - nCopied ); + this->pushComBuf ( *pComBuf ); + } + } + + // + // visual C++ versions 6 and 7 do not allow out of + // class member template function definition + // + template < class T > + inline void push ( const T & val ) + { + comBuf * pComBuf = this->bufs.last (); + if ( pComBuf && pComBuf->push ( val ) ) { + return; + } + pComBuf = newComBuf (); + bool success = pComBuf->push ( val ); + assert ( success ); + this->pushComBuf ( *pComBuf ); + } + + template < class T > + inline void push ( const T * ); // disabled + + comQueSend ( const comQueSend & ); + comQueSend & operator = ( const comQueSend & ); +}; + +extern const char cacNillBytes[]; + +inline comQueSendMsgMinder::comQueSendMsgMinder ( + class comQueSend & sendQueIn, epicsGuard < epicsMutex > & ) : + pSendQue ( & sendQueIn ) +{ + sendQueIn.beginMsg (); +} + +inline comQueSendMsgMinder::~comQueSendMsgMinder () +{ + if ( this->pSendQue ) { + this->pSendQue->clearUncommitedMsg (); + } +} + +inline void comQueSendMsgMinder::commit () +{ + if ( this->pSendQue ) { + this->pSendQue->commitMsg (); + this->pSendQue = 0; + } +} + +inline void comQueSend::beginMsg () +{ + this->pFirstUncommited = this->bufs.lastIter (); +} + +inline void comQueSend::pushUInt16 ( const ca_uint16_t value ) +{ + this->push ( value ); +} + +inline void comQueSend::pushUInt32 ( const ca_uint32_t value ) +{ + this->push ( value ); +} + +inline void comQueSend::pushFloat32 ( const ca_float32_t value ) +{ + this->push ( value ); +} + +inline void comQueSend::pushString ( const char *pVal, unsigned nChar ) +{ + this->push ( pVal, nChar ); +} + +inline void comQueSend::pushComBuf ( comBuf & cb ) +{ + this->bufs.add ( cb ); + if ( ! this->pFirstUncommited.valid() ) { + this->pFirstUncommited = this->bufs.lastIter (); + } +} + +inline unsigned comQueSend::occupiedBytes () const +{ + return this->nBytesPending; +} + +inline bool comQueSend::flushBlockThreshold () const +{ + return ( this->nBytesPending > 16 * comBuf::capacityBytes () ); +} + +inline bool comQueSend::flushEarlyThreshold ( unsigned nBytesThisMsg ) const +{ + return ( this->nBytesPending + nBytesThisMsg > 4 * comBuf::capacityBytes () ); +} + +// wrapping this with a function avoids WRS T2.2 Cygnus GNU compiler bugs +inline comBuf * comQueSend::newComBuf () +{ + return new ( this->comBufMemMgr ) comBuf; +} + +#endif // ifndef comQueSendh diff --git a/modules/ca/src/client/convert.cpp b/modules/ca/src/client/convert.cpp new file mode 100644 index 000000000..851711774 --- /dev/null +++ b/modules/ca/src/client/convert.cpp @@ -0,0 +1,1440 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * C O N V E R T . C + * + * Author: D. Kersteins + * + * + * NOTES: + * + * 1) All routines in this file have an encode argument which + * determines if we are converting from the standard format to + * the local format or vise versa. To date only float and double data + * types must be converted differently depending on the encode + * argument - joh + * + */ + +#include + +#include "dbDefs.h" +#include "osiSock.h" +#include "osiWireFormat.h" + +#define epicsExportSharedSymbols +#include "net_convert.h" +#include "iocinf.h" +#include "caProto.h" +#include "caerr.h" + +/* + * NOOP if this isnt required + */ +#ifdef EPICS_CONVERSION_REQUIRED + +/* + * if hton is true then it is a host to network conversion + * otherwise vise-versa + * + * net format: big endian and IEEE float + */ +typedef void ( * CACVRTFUNCPTR ) ( + const void *pSrc, void *pDest, int hton, arrayElementCount count ); + +inline void dbr_htond ( + const dbr_double_t * pHost, dbr_double_t * pNet ) +{ + AlignedWireRef < epicsFloat64 > tmp ( *pNet ); + tmp = *pHost; +} +inline void dbr_ntohd ( + const dbr_double_t * pNet, dbr_double_t * pHost ) +{ + *pHost = AlignedWireRef < const epicsFloat64 > ( *pNet ); +} +inline void dbr_htonf ( + const dbr_float_t * pHost, dbr_float_t * pNet ) +{ + AlignedWireRef < epicsFloat32 > tmp ( *pNet ); + tmp = *pHost; +} +inline void dbr_ntohf ( + const dbr_float_t * pNet, dbr_float_t * pHost ) +{ + *pHost = AlignedWireRef < const epicsFloat32 > ( *pNet ); +} + +inline epicsUInt16 dbr_ntohs( const epicsUInt16 & net ) +{ + return AlignedWireRef < const epicsUInt16 > ( net ); +} + +inline epicsUInt16 dbr_htons ( const epicsUInt16 & host ) +{ + epicsUInt16 tmp; + AlignedWireRef < epicsUInt16 > awr ( tmp ); + awr = host; + return tmp; +} + +inline epicsUInt32 dbr_ntohl( const epicsUInt32 & net ) +{ + return AlignedWireRef < const epicsUInt32 > ( net ); +} + +inline epicsUInt32 dbr_htonl ( const epicsUInt32 & host ) +{ + epicsUInt32 tmp; + AlignedWireRef < epicsUInt32 > awr ( tmp ); + awr = host; + return tmp; +} + +/* + * if hton is true then it is a host to network conversion + * otherwise vise-versa + * + * net format: big endian and IEEE float + * + */ + +/* + * CVRT_STRING() + */ +static void cvrt_string( +const void *s, /* source */ +void *d, /* destination */ +int /* encode */, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + char *pSrc = (char *) s; + char *pDest = (char *) d; + + /* convert "in place" -> nothing to do */ + if (s == d) + return; + memcpy ( pDest, pSrc, num*MAX_STRING_SIZE ); +} + +/* + * CVRT_SHORT() + */ +static void cvrt_short( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + dbr_short_t *pSrc = (dbr_short_t *) s; + dbr_short_t *pDest = (dbr_short_t *) d; + + if(encode){ + for(arrayElementCount i=0; i nothing to do */ + if (s == d) + return; + for( arrayElementCount i=0; istatus = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + /* convert "in place" -> nothing else to do */ + if (s == d) + return; + + memcpy ( pDest->value, pSrc->value, (MAX_STRING_SIZE * num) ); + +} + +/**************************************************************************** +** cvrt_sts_short(s,d) +** struct dbr_sts_int *s pointer to source struct +** struct dbr_sts_int *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_sts_short( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_int *pSrc = (struct dbr_sts_int *) s; + struct dbr_sts_int *pDest = (struct dbr_sts_int *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + if (num == 1) /* single value */ + pDest->value = dbr_ntohs(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_short(&pSrc->value, &pDest->value, encode, num); + } +} +/**************************************************************************** +** cvrt_sts_float(s,d) +** struct dbr_sts_float *s pointer to source struct +** struct dbr_sts_float *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_sts_float( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_float *pSrc = (struct dbr_sts_float *) s; + struct dbr_sts_float *pDest = (struct dbr_sts_float *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + cvrt_float(&pSrc->value, &pDest->value, encode, num); +} + +/**************************************************************************** +** cvrt_sts_double(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_sts_double( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_double *pSrc = (struct dbr_sts_double *) s; + struct dbr_sts_double *pDest = (struct dbr_sts_double *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + cvrt_double(&pSrc->value, &pDest->value, encode, num); +} + +/**************************************************************************** +** cvrt_sts_enum(s,d) +** struct dbr_sts_enum *s pointer to source struct +** struct dbr_sts_enum *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_sts_enum( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_enum *pSrc = (struct dbr_sts_enum *) s; + struct dbr_sts_enum *pDest = (struct dbr_sts_enum *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + if (num == 1) + pDest->value = dbr_ntohs(pSrc->value); + else { + cvrt_enum(&pSrc->value,&pDest->value,encode,num); + } +} + +/**************************************************************************** +** cvrt_gr_short() +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_gr_short( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_int *pSrc = (struct dbr_gr_int *) s; + struct dbr_gr_int *pDest = (struct dbr_gr_int *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + pDest->upper_disp_limit = dbr_ntohs(pSrc->upper_disp_limit); + pDest->lower_disp_limit = dbr_ntohs(pSrc->lower_disp_limit); + pDest->upper_alarm_limit = dbr_ntohs(pSrc->upper_alarm_limit); + pDest->upper_warning_limit = dbr_ntohs(pSrc->upper_warning_limit); + pDest->lower_alarm_limit = dbr_ntohs(pSrc->lower_alarm_limit); + pDest->lower_warning_limit = dbr_ntohs(pSrc->lower_warning_limit); + + if (num == 1) + pDest->value = dbr_ntohs(pSrc->value); + else { + cvrt_short(&pSrc->value, &pDest->value, encode,num); + } +} + +/**************************************************************************** +** cvrt_gr_char() +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_gr_char( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_char *pSrc = (struct dbr_gr_char *) s; + struct dbr_gr_char *pDest = (struct dbr_gr_char *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + if (s == d) /* source == dest -> no more conversions */ + return; + + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + + pDest->upper_disp_limit = pSrc->upper_disp_limit; + pDest->lower_disp_limit = pSrc->lower_disp_limit; + pDest->upper_alarm_limit = pSrc->upper_alarm_limit; + pDest->upper_warning_limit = pSrc->upper_warning_limit; + pDest->lower_alarm_limit = pSrc->lower_alarm_limit; + pDest->lower_warning_limit = pSrc->lower_warning_limit; + + if (num == 1) + pDest->value = pSrc->value; + else { + memcpy((char *)&pDest->value, (char *)&pSrc->value, num); + } +} + +/**************************************************************************** +** cvrt_gr_long() +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_gr_long( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_long *pSrc = (struct dbr_gr_long *) s; + struct dbr_gr_long *pDest = (struct dbr_gr_long *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + pDest->upper_disp_limit = dbr_ntohl(pSrc->upper_disp_limit); + pDest->lower_disp_limit = dbr_ntohl(pSrc->lower_disp_limit); + pDest->upper_alarm_limit = dbr_ntohl(pSrc->upper_alarm_limit); + pDest->upper_warning_limit = dbr_ntohl(pSrc->upper_warning_limit); + pDest->lower_alarm_limit = dbr_ntohl(pSrc->lower_alarm_limit); + pDest->lower_warning_limit = dbr_ntohl(pSrc->lower_warning_limit); + + if (num == 1) + pDest->value = dbr_ntohl(pSrc->value); + else { + cvrt_long(&pSrc->value, &pDest->value, encode, num); + } +} + +/**************************************************************************** +** cvrt_gr_enum(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_gr_enum( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_enum *pSrc = (struct dbr_gr_enum *) s; + struct dbr_gr_enum *pDest = (struct dbr_gr_enum *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->no_str = dbr_ntohs(pSrc->no_str); + if ( s != d ) { + memcpy((void *)pDest->strs,(void *)pSrc->strs,sizeof(pSrc->strs)); + } + + if (num == 1) /* single value */ + pDest->value = dbr_ntohs(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_enum(&(pSrc->value), &(pDest->value), encode, num); + } +} + +/**************************************************************************** +** cvrt_gr_double(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_gr_double( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_double *pSrc = (struct dbr_gr_double *) s; + struct dbr_gr_double *pDest = (struct dbr_gr_double *) d; + + /* these are same for vax to ieee or ieee to vax */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->precision = dbr_ntohs(pSrc->precision); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + if (encode) /* vax to ieee convert */ + { + if (num == 1){ + dbr_htond(&pSrc->value, &pDest->value); + } + else { + cvrt_double(&pSrc->value, &pDest->value, encode,num); + } + dbr_htond(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_htond(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_htond(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_htond(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_htond(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_htond(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + } + else /* ieee to vax convert */ + { + if (num == 1){ + dbr_ntohd(&pSrc->value, &pDest->value); + } + else { + cvrt_double(&pSrc->value, &pDest->value, encode,num); + } + dbr_ntohd(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_ntohd(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_ntohd(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_ntohd(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_ntohd(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_ntohd(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + } +} + + +/**************************************************************************** +** cvrt_gr_float(s,d) +** struct dbr_gr_float *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_gr_float( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_gr_float *pSrc = (struct dbr_gr_float *) s; + struct dbr_gr_float *pDest = (struct dbr_gr_float *) d; + + /* these are same for vax to ieee or ieee to vax */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->precision = dbr_ntohs(pSrc->precision); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + if (encode) /* vax to ieee convert */ + { + if (num == 1){ + dbr_htonf(&pSrc->value, &pDest->value); + } + else { + cvrt_float(&pSrc->value, &pDest->value, encode,num); + } + dbr_htonf(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_htonf(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_htonf(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_htonf(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_htonf(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_htonf(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + } + else /* ieee to vax convert */ + { + if (num == 1){ + dbr_ntohf(&pSrc->value, &pDest->value); + } + else { + cvrt_float(&pSrc->value, &pDest->value, encode,num); + } + dbr_ntohf(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_ntohf(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_ntohf(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_ntohf(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_ntohf(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_ntohf(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + } +} + + + +/**************************************************************************** +** cvrt_ctrl_short(s,d) +** struct dbr_gr_int *s pointer to source struct +** struct dbr_gr_int *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_ctrl_short( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_int *pSrc = (struct dbr_ctrl_int *) s; + struct dbr_ctrl_int *pDest = (struct dbr_ctrl_int *) d; + + /* vax to ieee or ieee to vax -- same code */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + pDest->upper_disp_limit = dbr_ntohs(pSrc->upper_disp_limit); + pDest->lower_disp_limit = dbr_ntohs(pSrc->lower_disp_limit); + pDest->upper_alarm_limit = dbr_ntohs(pSrc->upper_alarm_limit); + pDest->upper_warning_limit = dbr_ntohs(pSrc->upper_warning_limit); + pDest->lower_alarm_limit = dbr_ntohs(pSrc->lower_alarm_limit); + pDest->lower_warning_limit = dbr_ntohs(pSrc->lower_warning_limit); + pDest->lower_ctrl_limit = dbr_ntohs(pSrc->lower_ctrl_limit); + pDest->upper_ctrl_limit = dbr_ntohs(pSrc->upper_ctrl_limit); + + if (num == 1) + pDest->value = dbr_ntohs(pSrc->value); + else { + cvrt_short(&pSrc->value, &pDest->value, encode, num); + } +} + +/**************************************************************************** +** cvrt_ctrl_long(s,d) +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_ctrl_long( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_long *pSrc = (struct dbr_ctrl_long*) s; + struct dbr_ctrl_long *pDest = (struct dbr_ctrl_long *) d; + + /* vax to ieee or ieee to vax -- same code */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + + pDest->upper_disp_limit = dbr_ntohl(pSrc->upper_disp_limit); + pDest->lower_disp_limit = dbr_ntohl(pSrc->lower_disp_limit); + pDest->upper_alarm_limit = dbr_ntohl(pSrc->upper_alarm_limit); + pDest->upper_warning_limit = dbr_ntohl(pSrc->upper_warning_limit); + pDest->lower_alarm_limit = dbr_ntohl(pSrc->lower_alarm_limit); + pDest->lower_warning_limit = dbr_ntohl(pSrc->lower_warning_limit); + pDest->lower_ctrl_limit = dbr_ntohl(pSrc->lower_ctrl_limit); + pDest->upper_ctrl_limit = dbr_ntohl(pSrc->upper_ctrl_limit); + + if (num == 1) + pDest->value = dbr_ntohl(pSrc->value); + else { + cvrt_long(&pSrc->value, &pDest->value, encode, num); + } +} + +/**************************************************************************** +** cvrt_ctrl_short(s,d) +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_ctrl_char( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_char *pSrc = (struct dbr_ctrl_char *) s; + struct dbr_ctrl_char *pDest = (struct dbr_ctrl_char *) d; + + /* vax to ieee or ieee to vax -- same code */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + if ( s == d ) + return; + + pDest->upper_disp_limit = pSrc->upper_disp_limit; + pDest->lower_disp_limit = pSrc->lower_disp_limit; + pDest->upper_alarm_limit = pSrc->upper_alarm_limit; + pDest->upper_warning_limit = pSrc->upper_warning_limit; + pDest->lower_ctrl_limit = pSrc->lower_ctrl_limit; + pDest->upper_ctrl_limit = pSrc->upper_ctrl_limit; + + if (num == 1) + pDest->value = pSrc->value; + else { + memcpy((void *)&pDest->value, (void *)&pSrc->value, num); + } +} + +/**************************************************************************** +** cvrt_ctrl_double(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_ctrl_double( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_double *pSrc = (struct dbr_ctrl_double *) s; + struct dbr_ctrl_double *pDest = (struct dbr_ctrl_double *) d; + + /* these are the same for ieee to vax or vax to ieee */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->precision = dbr_ntohs(pSrc->precision); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + if (encode) /* vax to ieee convert */ + { + if (num == 1){ + dbr_htond(&pSrc->value, &pDest->value); + } + else { + cvrt_double(&pSrc->value, &pDest->value, encode, num); + } + dbr_htond(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_htond(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_htond(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_htond(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_htond(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_htond(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + dbr_htond(&pSrc->lower_ctrl_limit, &pDest->lower_ctrl_limit); + dbr_htond(&pSrc->upper_ctrl_limit, &pDest->upper_ctrl_limit); + } + else /* ieee to vax convert */ + { + if (num == 1){ + dbr_ntohd(&pSrc->value, &pDest->value); + } + else { + cvrt_double(&pSrc->value, &pDest->value, encode, num); + } + dbr_ntohd(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_ntohd(&pSrc->upper_disp_limit, &pDest->upper_disp_limit); + dbr_ntohd(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_ntohd(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_ntohd(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_ntohd(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + dbr_ntohd(&pSrc->lower_ctrl_limit, &pDest->lower_ctrl_limit); + dbr_ntohd(&pSrc->upper_ctrl_limit, &pDest->upper_ctrl_limit); + } + +} + + + +/**************************************************************************** +** cvrt_ctrl_float(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_ctrl_float( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_float *pSrc = (struct dbr_ctrl_float *) s; + struct dbr_ctrl_float *pDest = (struct dbr_ctrl_float *) d; + + /* these are the same for ieee to vaax or vax to ieee */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->precision = dbr_ntohs(pSrc->precision); + if ( s != d ) { + memcpy(pDest->units,pSrc->units,sizeof(pSrc->units)); + } + if (encode) /* vax to ieee convert */ + { + if (num == 1){ + dbr_htonf(&pSrc->value, &pDest->value); + } + else { + cvrt_float(&pSrc->value, &pDest->value, encode, num); + } + dbr_htonf(&pSrc->upper_disp_limit,&pDest->upper_disp_limit); + dbr_htonf(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_htonf(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_htonf(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_htonf(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_htonf(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + dbr_htonf(&pSrc->lower_ctrl_limit, &pDest->lower_ctrl_limit); + dbr_htonf(&pSrc->upper_ctrl_limit, &pDest->upper_ctrl_limit); + } + else /* ieee to vax convert */ + { + if (num == 1){ + dbr_ntohf(&pSrc->value, &pDest->value); + } + else { + cvrt_float(&pSrc->value, &pDest->value, encode, num); + } + dbr_ntohf(&pSrc->lower_disp_limit, &pDest->lower_disp_limit); + dbr_ntohf(&pSrc->upper_disp_limit, &pDest->upper_disp_limit); + dbr_ntohf(&pSrc->upper_alarm_limit, &pDest->upper_alarm_limit); + dbr_ntohf(&pSrc->upper_warning_limit, &pDest->upper_warning_limit); + dbr_ntohf(&pSrc->lower_alarm_limit, &pDest->lower_alarm_limit); + dbr_ntohf(&pSrc->lower_warning_limit, &pDest->lower_warning_limit); + dbr_ntohf(&pSrc->lower_ctrl_limit, &pDest->lower_ctrl_limit); + dbr_ntohf(&pSrc->upper_ctrl_limit, &pDest->upper_ctrl_limit); + } + +} + + +/**************************************************************************** +** cvrt_ctrl_enum(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_ctrl_enum( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_ctrl_enum *pSrc = (struct dbr_ctrl_enum *) s; + struct dbr_ctrl_enum *pDest = (struct dbr_ctrl_enum *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->no_str = dbr_ntohs(pSrc->no_str); + if ( s != d ) { + memcpy((void *)pDest->strs,(void *)pSrc->strs,sizeof(pSrc->strs)); + } + + if (num == 1) /* single value */ + pDest->value = dbr_ntohs(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_enum(&(pSrc->value), &(pDest->value), encode, num); + } +} + +/**************************************************************************** +** cvrt_sts_char(s,d) +** struct dbr_sts_int *s pointer to source struct +** struct dbr_sts_int *d pointer to destination struct +** int encode; boolean, if true vax to ieee +** else ieee to vax +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_sts_char( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_char *pSrc = (struct dbr_sts_char *) s; + struct dbr_sts_char *pDest = (struct dbr_sts_char *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + if ( s == d ) + return; + + if (num == 1) /* single value */ + pDest->value = pSrc->value; + else /* array chan-- multiple pts */ + { + memcpy((void *)&pDest->value, (void *)&pSrc->value, num); + } +} + +/**************************************************************************** +** cvrt_sts_long(s,d) +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_sts_long( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_sts_long *pSrc = (struct dbr_sts_long *) s; + struct dbr_sts_long *pDest = (struct dbr_sts_long *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + + if (num == 1) /* single value */ + pDest->value = dbr_ntohl(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_long(&pDest->value, &pSrc->value, encode, num); + } +} + + +/**************************************************************************** +** cvrt_time_string(s,d) +** +** converts fields of struct in HOST format to NET format +** or +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_time_string( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_string *pSrc = (struct dbr_time_string *) s; + struct dbr_time_string *pDest = (struct dbr_time_string *) d; + + /* convert ieee to vax format or vax to ieee */ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + if ( s != d ) { + memcpy(pDest->value, pSrc->value, (MAX_STRING_SIZE * num)); + } +} + +/**************************************************************************** +** cvrt_time_short(s,d) +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_time_short( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_short *pSrc = (struct dbr_time_short *) s; + struct dbr_time_short *pDest = (struct dbr_time_short *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + if (num == 1) /* single value */ + pDest->value = dbr_ntohs(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_short(&pSrc->value, &pDest->value, encode, num); + } +} + +/**************************************************************************** +** cvrt_time_float(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_time_float( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_float *pSrc = (struct dbr_time_float *) s; + struct dbr_time_float *pDest = (struct dbr_time_float *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + cvrt_float(&pSrc->value, &pDest->value, encode, num); +} + +/**************************************************************************** +** cvrt_time_double(s,d) +** +** if encode +** converts struct in HOST format to ieee format +** else +** converts fields of struct in NET format to fields with HOST +** format; +****************************************************************************/ + +static void cvrt_time_double( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_double *pSrc = (struct dbr_time_double *) s; + struct dbr_time_double *pDest = (struct dbr_time_double *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + cvrt_double(&pSrc->value, &pDest->value, encode, num); +} + + + +/**************************************************************************** +** cvrt_time_enum(s,d) +** +** converts fields of struct in NET format to fields with HOST format +** or +** converts fields of struct in HOST format to fields with NET format +** +****************************************************************************/ + +static void cvrt_time_enum( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_enum *pSrc = (struct dbr_time_enum *) s; + struct dbr_time_enum *pDest = (struct dbr_time_enum *) d; + + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + if (num == 1) + pDest->value = dbr_ntohs(pSrc->value); + else { + cvrt_enum(&pSrc->value,&pDest->value,encode,num); + } +} + +/**************************************************************************** +** cvrt_sts_char(s,d) +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_time_char( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_char *pSrc = (struct dbr_time_char *) s; + struct dbr_time_char *pDest = (struct dbr_time_char *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + if ( s == d ) + return; + + if (num == 1) /* single value */ + pDest->value = pSrc->value; + else /* array chan-- multiple pts */ + { + memcpy((void *)&pDest->value, (void *)&pSrc->value, num); + } +} +/**************************************************************************** +** cvrt_time_long(s,d) +** +** converts fields ofstruct in HOST format to ieee format +** or +** converts fields of struct in NET format to fields with HOST +** format +****************************************************************************/ + +static void cvrt_time_long( +const void *s, /* source */ +void *d, /* destination */ +int encode, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + struct dbr_time_long *pSrc = (struct dbr_time_long *) s; + struct dbr_time_long *pDest = (struct dbr_time_long *) d; + + /* convert vax to ieee or ieee to vax format -- same code*/ + pDest->status = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->stamp.secPastEpoch = dbr_ntohl(pSrc->stamp.secPastEpoch); + pDest->stamp.nsec = dbr_ntohl(pSrc->stamp.nsec); + + if (num == 1) /* single value */ + pDest->value = dbr_ntohl(pSrc->value); + else /* array chan-- multiple pts */ + { + cvrt_long(&pDest->value, &pSrc->value, encode, num); + } +} + +/* + * cvrt_put_ackt() + * + * + * + * + */ +static void cvrt_put_ackt( +const void *s, /* source */ +void *d, /* destination */ +int /*encode*/, /* cvrt HOST to NET if T */ +arrayElementCount num /* number of values */ +) +{ + dbr_put_ackt_t *pSrc = (dbr_put_ackt_t *) s; + dbr_put_ackt_t *pDest = (dbr_put_ackt_t *) d; + arrayElementCount i; + + for(i=0; istatus = dbr_ntohs(pSrc->status); + pDest->severity = dbr_ntohs(pSrc->severity); + pDest->ackt = dbr_ntohs(pSrc->ackt); + pDest->acks = dbr_ntohs(pSrc->acks); + + /* convert "in place" -> nothing else to do */ + if (s == d) + return; + + memcpy(pDest->value, pSrc->value, (MAX_STRING_SIZE * num)); +} + +/* cvrt is (array of) (pointer to) (function returning) int */ +static CACVRTFUNCPTR cac_dbr_cvrt[] = { + cvrt_string, + cvrt_short, + cvrt_float, + cvrt_enum, + cvrt_char, + cvrt_long, + cvrt_double, + + cvrt_sts_string, + cvrt_sts_short, + cvrt_sts_float, + cvrt_sts_enum, + cvrt_sts_char, + cvrt_sts_long, + cvrt_sts_double, + + cvrt_time_string, + cvrt_time_short, + cvrt_time_float, + cvrt_time_enum, + cvrt_time_char, + cvrt_time_long, + cvrt_time_double, + + cvrt_sts_string, /* DBR_GR_STRING identical to dbr_sts_string */ + cvrt_gr_short, + cvrt_gr_float, + cvrt_gr_enum, + cvrt_gr_char, + cvrt_gr_long, + cvrt_gr_double, + + cvrt_sts_string, /* DBR_CTRL_STRING identical to dbr_sts_string */ + cvrt_ctrl_short, + cvrt_ctrl_float, + cvrt_ctrl_enum, + cvrt_ctrl_char, + cvrt_ctrl_long, + cvrt_ctrl_double, + + cvrt_put_ackt, + cvrt_put_ackt, /* DBR_PUT_ACKS identical to DBR_PUT_ACKT */ + cvrt_stsack_string, + cvrt_string +}; + +#endif /* EPICS_CONVERSION_REQUIRED */ + +int caNetConvert ( unsigned type, const void *pSrc, void *pDest, + int hton, arrayElementCount count ) +{ +# ifdef EPICS_CONVERSION_REQUIRED + if ( type >= NELEMENTS ( cac_dbr_cvrt ) ) { + return ECA_BADTYPE; + } + ( * cac_dbr_cvrt [ type ] ) ( pSrc, pDest, hton, count ); +# else + if ( INVALID_DB_REQ ( type ) ) { + return ECA_BADTYPE; + } + if ( pSrc != pDest ) { + memcpy ( pDest, pSrc, dbr_size_n ( type, count ) ); + } +# endif + return ECA_NORMAL; +} + diff --git a/modules/ca/src/client/db_access.h b/modules/ca/src/client/db_access.h new file mode 100644 index 000000000..92aa5d011 --- /dev/null +++ b/modules/ca/src/client/db_access.h @@ -0,0 +1,740 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* base/include/db_access.h */ +/* Author: Bob Dalesio + * Date: 4-4-88 +*/ + +#ifndef INCLdb_accessh +#define INCLdb_accessh + +#include + +#ifdef epicsExportSharedSymbols +# define INCLdb_accessh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsTypes.h" +#include "epicsTime.h" + +#ifdef INCLdb_accessh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_UNITS_SIZE 8 +#define MAX_ENUM_STRING_SIZE 26 +#define MAX_ENUM_STATES 16 + +/* + * architecture independent types + * + * (so far this is sufficient for all archs we have ported to) + */ +typedef epicsOldString dbr_string_t; +typedef epicsUInt8 dbr_char_t; +typedef epicsInt16 dbr_short_t; +typedef epicsUInt16 dbr_ushort_t; +typedef epicsInt16 dbr_int_t; +typedef epicsUInt16 dbr_enum_t; +typedef epicsInt32 dbr_long_t; +typedef epicsUInt32 dbr_ulong_t; +typedef epicsFloat32 dbr_float_t; +typedef epicsFloat64 dbr_double_t; +typedef epicsUInt16 dbr_put_ackt_t; +typedef epicsUInt16 dbr_put_acks_t; +typedef epicsOldString dbr_stsack_string_t; +typedef epicsOldString dbr_class_name_t; + +#ifndef db_accessHFORdb_accessC +/* database field types */ +#define DBF_STRING 0 +#define DBF_INT 1 +#define DBF_SHORT 1 +#define DBF_FLOAT 2 +#define DBF_ENUM 3 +#define DBF_CHAR 4 +#define DBF_LONG 5 +#define DBF_DOUBLE 6 +#define DBF_NO_ACCESS 7 +#define LAST_TYPE DBF_DOUBLE +#define VALID_DB_FIELD(x) ((x >= 0) && (x <= LAST_TYPE)) +#define INVALID_DB_FIELD(x) ((x < 0) || (x > LAST_TYPE)) + +/* data request buffer types */ +#define DBR_STRING DBF_STRING +#define DBR_INT DBF_INT +#define DBR_SHORT DBF_INT +#define DBR_FLOAT DBF_FLOAT +#define DBR_ENUM DBF_ENUM +#define DBR_CHAR DBF_CHAR +#define DBR_LONG DBF_LONG +#define DBR_DOUBLE DBF_DOUBLE +#define DBR_STS_STRING 7 +#define DBR_STS_SHORT 8 +#define DBR_STS_INT DBR_STS_SHORT +#define DBR_STS_FLOAT 9 +#define DBR_STS_ENUM 10 +#define DBR_STS_CHAR 11 +#define DBR_STS_LONG 12 +#define DBR_STS_DOUBLE 13 +#define DBR_TIME_STRING 14 +#define DBR_TIME_INT 15 +#define DBR_TIME_SHORT 15 +#define DBR_TIME_FLOAT 16 +#define DBR_TIME_ENUM 17 +#define DBR_TIME_CHAR 18 +#define DBR_TIME_LONG 19 +#define DBR_TIME_DOUBLE 20 +#define DBR_GR_STRING 21 +#define DBR_GR_SHORT 22 +#define DBR_GR_INT DBR_GR_SHORT +#define DBR_GR_FLOAT 23 +#define DBR_GR_ENUM 24 +#define DBR_GR_CHAR 25 +#define DBR_GR_LONG 26 +#define DBR_GR_DOUBLE 27 +#define DBR_CTRL_STRING 28 +#define DBR_CTRL_SHORT 29 +#define DBR_CTRL_INT DBR_CTRL_SHORT +#define DBR_CTRL_FLOAT 30 +#define DBR_CTRL_ENUM 31 +#define DBR_CTRL_CHAR 32 +#define DBR_CTRL_LONG 33 +#define DBR_CTRL_DOUBLE 34 +#define DBR_PUT_ACKT DBR_CTRL_DOUBLE + 1 +#define DBR_PUT_ACKS DBR_PUT_ACKT + 1 +#define DBR_STSACK_STRING DBR_PUT_ACKS + 1 +#define DBR_CLASS_NAME DBR_STSACK_STRING + 1 +#define LAST_BUFFER_TYPE DBR_CLASS_NAME +#define VALID_DB_REQ(x) ((x >= 0) && (x <= LAST_BUFFER_TYPE)) +#define INVALID_DB_REQ(x) ((x < 0) || (x > LAST_BUFFER_TYPE)) + +/* + * The enumeration "epicsType" is an index to this array + * of type DBR types. In some cases we select the a + * larger type to avoid loss of information + */ +epicsShareExtern const int epicsTypeToDBR_XXXX [lastEpicsType+1]; + +/* + * The DBR_XXXX types are indicies into this array + */ +epicsShareExtern const epicsType DBR_XXXXToEpicsType [LAST_BUFFER_TYPE+1]; + +/* values returned for each field type + * DBR_STRING returns a NULL terminated string + * DBR_SHORT returns an unsigned short + * DBR_INT returns an unsigned short + * DBR_FLOAT returns an IEEE floating point value + * DBR_ENUM returns an unsigned short which is the enum item + * DBR_CHAR returns an unsigned char + * DBR_LONG returns an unsigned long + * DBR_DOUBLE returns a double precision floating point number + * DBR_STS_STRING returns a string status structure (dbr_sts_string) + * DBR_STS_SHORT returns a short status structure (dbr_sts_short) + * DBR_STS_INT returns a short status structure (dbr_sts_int) + * DBR_STS_FLOAT returns a float status structure (dbr_sts_float) + * DBR_STS_ENUM returns an enum status structure (dbr_sts_enum) + * DBR_STS_CHAR returns a char status structure (dbr_sts_char) + * DBR_STS_LONG returns a long status structure (dbr_sts_long) + * DBR_STS_DOUBLE returns a double status structure (dbr_sts_double) + * DBR_TIME_STRING returns a string time structure (dbr_time_string) + * DBR_TIME_SHORT returns a short time structure (dbr_time_short) + * DBR_TIME_INT returns a short time structure (dbr_time_short) + * DBR_TIME_FLOAT returns a float time structure (dbr_time_float) + * DBR_TIME_ENUM returns an enum time structure (dbr_time_enum) + * DBR_TIME_CHAR returns a char time structure (dbr_time_char) + * DBR_TIME_LONG returns a long time structure (dbr_time_long) + * DBR_TIME_DOUBLE returns a double time structure (dbr_time_double) + * DBR_GR_STRING returns a graphic string structure (dbr_gr_string) + * DBR_GR_SHORT returns a graphic short structure (dbr_gr_short) + * DBR_GR_INT returns a graphic short structure (dbr_gr_int) + * DBR_GR_FLOAT returns a graphic float structure (dbr_gr_float) + * DBR_GR_ENUM returns a graphic enum structure (dbr_gr_enum) + * DBR_GR_CHAR returns a graphic char structure (dbr_gr_char) + * DBR_GR_LONG returns a graphic long structure (dbr_gr_long) + * DBR_GR_DOUBLE returns a graphic double structure (dbr_gr_double) + * DBR_CTRL_STRING returns a control string structure (dbr_ctrl_int) + * DBR_CTRL_SHORT returns a control short structure (dbr_ctrl_short) + * DBR_CTRL_INT returns a control short structure (dbr_ctrl_int) + * DBR_CTRL_FLOAT returns a control float structure (dbr_ctrl_float) + * DBR_CTRL_ENUM returns a control enum structure (dbr_ctrl_enum) + * DBR_CTRL_CHAR returns a control char structure (dbr_ctrl_char) + * DBR_CTRL_LONG returns a control long structure (dbr_ctrl_long) + * DBR_CTRL_DOUBLE returns a control double structure (dbr_ctrl_double) + */ +#endif /*db_accessHFORdb_accessC*/ + +/* VALUES WITH STATUS STRUCTURES */ + +/* structure for a string status field */ +struct dbr_sts_string { + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_string_t value; /* current value */ +}; + +/* structure for a string status and ack field */ +struct dbr_stsack_string{ + dbr_ushort_t status; /* status of value */ + dbr_ushort_t severity; /* severity of alarm */ + dbr_ushort_t ackt; /* ack transient? */ + dbr_ushort_t acks; /* ack severity */ + dbr_string_t value; /* current value */ +}; +/* structure for an short status field */ +struct dbr_sts_int{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t value; /* current value */ +}; +struct dbr_sts_short{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t value; /* current value */ +}; + +/* structure for a float status field */ +struct dbr_sts_float{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_float_t value; /* current value */ +}; + +/* structure for a enum status field */ +struct dbr_sts_enum{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_enum_t value; /* current value */ +}; + +/* structure for a char status field */ +struct dbr_sts_char{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_char_t RISC_pad; /* RISC alignment */ + dbr_char_t value; /* current value */ +}; + +/* structure for a long status field */ +struct dbr_sts_long{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_long_t value; /* current value */ +}; + +/* structure for a double status field */ +struct dbr_sts_double{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_long_t RISC_pad; /* RISC alignment */ + dbr_double_t value; /* current value */ +}; + +/* VALUES WITH STATUS AND TIME STRUCTURES */ + +/* structure for a string time field */ +struct dbr_time_string{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_string_t value; /* current value */ +}; + +/* structure for an short time field */ +struct dbr_time_short{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_short_t RISC_pad; /* RISC alignment */ + dbr_short_t value; /* current value */ +}; + +/* structure for a float time field */ +struct dbr_time_float{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_float_t value; /* current value */ +}; + +/* structure for a enum time field */ +struct dbr_time_enum{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_short_t RISC_pad; /* RISC alignment */ + dbr_enum_t value; /* current value */ +}; + +/* structure for a char time field */ +struct dbr_time_char{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_short_t RISC_pad0; /* RISC alignment */ + dbr_char_t RISC_pad1; /* RISC alignment */ + dbr_char_t value; /* current value */ +}; + +/* structure for a long time field */ +struct dbr_time_long{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_long_t value; /* current value */ +}; + +/* structure for a double time field */ +struct dbr_time_double{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + epicsTimeStamp stamp; /* time stamp */ + dbr_long_t RISC_pad; /* RISC alignment */ + dbr_double_t value; /* current value */ +}; + +/* VALUES WITH STATUS AND GRAPHIC STRUCTURES */ + +/* structure for a graphic string */ + /* not implemented; use struct_dbr_sts_string */ + +/* structure for a graphic short field */ +struct dbr_gr_int{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_short_t upper_disp_limit; /* upper limit of graph */ + dbr_short_t lower_disp_limit; /* lower limit of graph */ + dbr_short_t upper_alarm_limit; + dbr_short_t upper_warning_limit; + dbr_short_t lower_warning_limit; + dbr_short_t lower_alarm_limit; + dbr_short_t value; /* current value */ +}; +struct dbr_gr_short{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_short_t upper_disp_limit; /* upper limit of graph */ + dbr_short_t lower_disp_limit; /* lower limit of graph */ + dbr_short_t upper_alarm_limit; + dbr_short_t upper_warning_limit; + dbr_short_t lower_warning_limit; + dbr_short_t lower_alarm_limit; + dbr_short_t value; /* current value */ +}; + +/* structure for a graphic floating point field */ +struct dbr_gr_float{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t precision; /* number of decimal places */ + dbr_short_t RISC_pad0; /* RISC alignment */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_float_t upper_disp_limit; /* upper limit of graph */ + dbr_float_t lower_disp_limit; /* lower limit of graph */ + dbr_float_t upper_alarm_limit; + dbr_float_t upper_warning_limit; + dbr_float_t lower_warning_limit; + dbr_float_t lower_alarm_limit; + dbr_float_t value; /* current value */ +}; + +/* structure for a graphic enumeration field */ +struct dbr_gr_enum{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t no_str; /* number of strings */ + char strs[MAX_ENUM_STATES][MAX_ENUM_STRING_SIZE]; + /* state strings */ + dbr_enum_t value; /* current value */ +}; + +/* structure for a graphic char field */ +struct dbr_gr_char{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_char_t upper_disp_limit; /* upper limit of graph */ + dbr_char_t lower_disp_limit; /* lower limit of graph */ + dbr_char_t upper_alarm_limit; + dbr_char_t upper_warning_limit; + dbr_char_t lower_warning_limit; + dbr_char_t lower_alarm_limit; + dbr_char_t RISC_pad; /* RISC alignment */ + dbr_char_t value; /* current value */ +}; + +/* structure for a graphic long field */ +struct dbr_gr_long{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_long_t upper_disp_limit; /* upper limit of graph */ + dbr_long_t lower_disp_limit; /* lower limit of graph */ + dbr_long_t upper_alarm_limit; + dbr_long_t upper_warning_limit; + dbr_long_t lower_warning_limit; + dbr_long_t lower_alarm_limit; + dbr_long_t value; /* current value */ +}; + +/* structure for a graphic double field */ +struct dbr_gr_double{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t precision; /* number of decimal places */ + dbr_short_t RISC_pad0; /* RISC alignment */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_double_t upper_disp_limit; /* upper limit of graph */ + dbr_double_t lower_disp_limit; /* lower limit of graph */ + dbr_double_t upper_alarm_limit; + dbr_double_t upper_warning_limit; + dbr_double_t lower_warning_limit; + dbr_double_t lower_alarm_limit; + dbr_double_t value; /* current value */ +}; + +/* VALUES WITH STATUS, GRAPHIC and CONTROL STRUCTURES */ + +/* structure for a control string */ + /* not implemented; use struct_dbr_sts_string */ + +/* structure for a control integer */ +struct dbr_ctrl_int{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_short_t upper_disp_limit; /* upper limit of graph */ + dbr_short_t lower_disp_limit; /* lower limit of graph */ + dbr_short_t upper_alarm_limit; + dbr_short_t upper_warning_limit; + dbr_short_t lower_warning_limit; + dbr_short_t lower_alarm_limit; + dbr_short_t upper_ctrl_limit; /* upper control limit */ + dbr_short_t lower_ctrl_limit; /* lower control limit */ + dbr_short_t value; /* current value */ +}; +struct dbr_ctrl_short{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_short_t upper_disp_limit; /* upper limit of graph */ + dbr_short_t lower_disp_limit; /* lower limit of graph */ + dbr_short_t upper_alarm_limit; + dbr_short_t upper_warning_limit; + dbr_short_t lower_warning_limit; + dbr_short_t lower_alarm_limit; + dbr_short_t upper_ctrl_limit; /* upper control limit */ + dbr_short_t lower_ctrl_limit; /* lower control limit */ + dbr_short_t value; /* current value */ +}; + +/* structure for a control floating point field */ +struct dbr_ctrl_float{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t precision; /* number of decimal places */ + dbr_short_t RISC_pad; /* RISC alignment */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_float_t upper_disp_limit; /* upper limit of graph */ + dbr_float_t lower_disp_limit; /* lower limit of graph */ + dbr_float_t upper_alarm_limit; + dbr_float_t upper_warning_limit; + dbr_float_t lower_warning_limit; + dbr_float_t lower_alarm_limit; + dbr_float_t upper_ctrl_limit; /* upper control limit */ + dbr_float_t lower_ctrl_limit; /* lower control limit */ + dbr_float_t value; /* current value */ +}; + +/* structure for a control enumeration field */ +struct dbr_ctrl_enum{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t no_str; /* number of strings */ + char strs[MAX_ENUM_STATES][MAX_ENUM_STRING_SIZE]; + /* state strings */ + dbr_enum_t value; /* current value */ +}; + +/* structure for a control char field */ +struct dbr_ctrl_char{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_char_t upper_disp_limit; /* upper limit of graph */ + dbr_char_t lower_disp_limit; /* lower limit of graph */ + dbr_char_t upper_alarm_limit; + dbr_char_t upper_warning_limit; + dbr_char_t lower_warning_limit; + dbr_char_t lower_alarm_limit; + dbr_char_t upper_ctrl_limit; /* upper control limit */ + dbr_char_t lower_ctrl_limit; /* lower control limit */ + dbr_char_t RISC_pad; /* RISC alignment */ + dbr_char_t value; /* current value */ +}; + +/* structure for a control long field */ +struct dbr_ctrl_long{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_long_t upper_disp_limit; /* upper limit of graph */ + dbr_long_t lower_disp_limit; /* lower limit of graph */ + dbr_long_t upper_alarm_limit; + dbr_long_t upper_warning_limit; + dbr_long_t lower_warning_limit; + dbr_long_t lower_alarm_limit; + dbr_long_t upper_ctrl_limit; /* upper control limit */ + dbr_long_t lower_ctrl_limit; /* lower control limit */ + dbr_long_t value; /* current value */ +}; + +/* structure for a control double field */ +struct dbr_ctrl_double{ + dbr_short_t status; /* status of value */ + dbr_short_t severity; /* severity of alarm */ + dbr_short_t precision; /* number of decimal places */ + dbr_short_t RISC_pad0; /* RISC alignment */ + char units[MAX_UNITS_SIZE]; /* units of value */ + dbr_double_t upper_disp_limit; /* upper limit of graph */ + dbr_double_t lower_disp_limit; /* lower limit of graph */ + dbr_double_t upper_alarm_limit; + dbr_double_t upper_warning_limit; + dbr_double_t lower_warning_limit; + dbr_double_t lower_alarm_limit; + dbr_double_t upper_ctrl_limit; /* upper control limit */ + dbr_double_t lower_ctrl_limit; /* lower control limit */ + dbr_double_t value; /* current value */ +}; + +#define dbr_size_n(TYPE,COUNT)\ +((unsigned)((COUNT)<=0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE])) + +/* size for each type - array indexed by the DBR_ type code */ +epicsShareExtern const unsigned short dbr_size[]; + +/* size for each type's value - array indexed by the DBR_ type code */ +epicsShareExtern const unsigned short dbr_value_size[]; + +#ifndef db_accessHFORdb_accessC +/* class for each type's value */ +enum dbr_value_class { + dbr_class_int, + dbr_class_float, + dbr_class_string, + dbr_class_max}; + +epicsShareExtern const enum dbr_value_class dbr_value_class[LAST_BUFFER_TYPE+1]; + +/* + * ptr to value given a pointer to the structure and the DBR type + */ +#define dbr_value_ptr(PDBR, DBR_TYPE) \ +((void *)(((char *)PDBR)+dbr_value_offset[DBR_TYPE])) + +/* + * ptr to value given a pointer to the structure and the structure declaration + */ +#define dbr_value_ptr_from_structure(PDBR, STRUCTURE)\ +((void *)(((char *)PDBR)+BYTE_OS(STRUCTURE, value))) + +epicsShareExtern const unsigned short dbr_value_offset[LAST_BUFFER_TYPE+1]; + + +/* union for each fetch buffers */ +union db_access_val{ + dbr_string_t strval; /* string max size */ + dbr_short_t shrtval; /* short */ + dbr_short_t intval; /* short */ + dbr_float_t fltval; /* IEEE Float */ + dbr_enum_t enmval; /* item number */ + dbr_char_t charval; /* character */ + dbr_long_t longval; /* long */ + dbr_double_t doubleval; /* double */ + struct dbr_sts_string sstrval; /* string field with status */ + struct dbr_sts_short sshrtval; /* short field with status */ + struct dbr_sts_float sfltval; /* float field with status */ + struct dbr_sts_enum senmval; /* item number with status */ + struct dbr_sts_char schrval; /* char field with status */ + struct dbr_sts_long slngval; /* long field with status */ + struct dbr_sts_double sdblval; /* double field with time */ + struct dbr_time_string tstrval; /* string field with time */ + struct dbr_time_short tshrtval; /* short field with time */ + struct dbr_time_float tfltval; /* float field with time */ + struct dbr_time_enum tenmval; /* item number with time */ + struct dbr_time_char tchrval; /* char field with time */ + struct dbr_time_long tlngval; /* long field with time */ + struct dbr_time_double tdblval; /* double field with time */ + struct dbr_sts_string gstrval; /* graphic string info */ + struct dbr_gr_short gshrtval; /* graphic short info */ + struct dbr_gr_float gfltval; /* graphic float info */ + struct dbr_gr_enum genmval; /* graphic item info */ + struct dbr_gr_char gchrval; /* graphic char info */ + struct dbr_gr_long glngval; /* graphic long info */ + struct dbr_gr_double gdblval; /* graphic double info */ + struct dbr_sts_string cstrval; /* control string info */ + struct dbr_ctrl_short cshrtval; /* control short info */ + struct dbr_ctrl_float cfltval; /* control float info */ + struct dbr_ctrl_enum cenmval; /* control item info */ + struct dbr_ctrl_char cchrval; /* control char info */ + struct dbr_ctrl_long clngval; /* control long info */ + struct dbr_ctrl_double cdblval; /* control double info */ + dbr_put_ackt_t putackt; /* item number */ + dbr_put_acks_t putacks; /* item number */ + struct dbr_sts_string sastrval; /* string field with status */ + dbr_string_t classname; /* string max size */ +}; + +/*---------------------------------------------------------------------------- +* repository for some useful PV database constants and utilities +* +* item dimensions +* db_strval_dim dimension for string values +* db_units_dim dimension for record units text +* db_desc_dim dimension for record description text +* db_name_dim dimension for channel names (record.field\0) +* db_state_dim number of states possible in a state table +* db_state_text_dim dimension for a state text string +* usage: char state_table[db_state_dim][db_state_text_dim] +* +* type checking macros -- return non-zero if condition is true, zero otherwise +* +* int dbf_type_is_valid(type) type is a valid DBF_xxx +* int dbr_type_is_valid(type) type is a valid DBR_xxx +* int dbr_type_is_plain(type) type is a valid plain DBR_xxx +* int dbr_type_is_STS(type) type is a valid DBR_STS_xxx +* int dbr_type_is_TIME(type) type is a valid DBR_TIME_xxx +* int dbr_type_is_GR(type) type is a valid DBR_GR_xxx +* int dbr_type_is_CTRL(type) type is a valid DBR_CTRL_xxx +* int dbr_type_is_STRING(type) type is a valid DBR_STRING_xxx +* int dbr_type_is_SHORT(type) type is a valid DBR_SHORT_xxx +* int dbr_type_is_FLOAT(type) type is a valid DBR_FLOAT_xxx +* int dbr_type_is_ENUM(type) type is a valid DBR_ENUM_xxx +* int dbr_type_is_CHAR(type) type is a valid DBR_CHAR_xxx +* int dbr_type_is_LONG(type) type is a valid DBR_LONG_xxx +* int dbr_type_is_DOUBLE(type) type is a valid DBR_DOUBLE_xxx +* +* type conversion macros +* +* char *dbf_type_to_text(type) returns text matching DBF_xxx +* void dbf_text_to_type(text, type) finds DBF_xxx matching text +* int dbf_type_to_DBR(type) returns DBR_xxx matching DBF_xxx +* int dbf_type_to_DBR_TIME(type) returns DBR_TIME_xxx matching DBF_xxx +* int dbf_type_to_DBR_GR(type) returns DBR_GR_xxx matching DBF_xxx +* int dbf_type_to_DBR_CTRL(type) returns DBR_CTRL_xxx matching DBF_xxx +* char *dbr_type_to_text(type) returns text matching DBR_xxx +* void dbr_text_to_type(text, type) finds DBR_xxx matching text +*---------------------------------------------------------------------------*/ +#define db_strval_dim MAX_STRING_SIZE +#define db_units_dim MAX_UNITS_SIZE +#define db_desc_dim 24 +#define db_name_dim 36 +#define db_state_dim MAX_ENUM_STATES +#define db_state_text_dim MAX_ENUM_STRING_SIZE + +#define dbf_type_is_valid(type) ((type) >= 0 && (type) <= LAST_TYPE) +#define dbr_type_is_valid(type) ((type) >= 0 && (type) <= LAST_BUFFER_TYPE) +#define dbr_type_is_plain(type) \ + ((type) >= DBR_STRING && (type) <= DBR_DOUBLE) +#define dbr_type_is_STS(type) \ + ((type) >= DBR_STS_STRING && (type) <= DBR_STS_DOUBLE) +#define dbr_type_is_TIME(type) \ + ((type) >= DBR_TIME_STRING && (type) <= DBR_TIME_DOUBLE) +#define dbr_type_is_GR(type) \ + ((type) >= DBR_GR_STRING && (type) <= DBR_GR_DOUBLE) +#define dbr_type_is_CTRL(type) \ + ((type) >= DBR_CTRL_STRING && (type) <= DBR_CTRL_DOUBLE) +#define dbr_type_is_STRING(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_STRING) +#define dbr_type_is_SHORT(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_SHORT) +#define dbr_type_is_FLOAT(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_FLOAT) +#define dbr_type_is_ENUM(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_ENUM) +#define dbr_type_is_CHAR(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_CHAR) +#define dbr_type_is_LONG(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_LONG) +#define dbr_type_is_DOUBLE(type) \ + ((type) >= 0 && (type) <= LAST_BUFFER_TYPE && \ + (type)%(LAST_TYPE+1) == DBR_DOUBLE) + +#define dbf_type_to_text(type) \ + ( ((type) >= -1 && (type) < dbf_text_dim-2) ? \ + dbf_text[type+1] : dbf_text_invalid ) + +#define dbf_text_to_type(text, type) \ + for (type=dbf_text_dim-3; type>=0; type--) { \ + if (strcmp(text, dbf_text[type+1]) == 0) \ + break; \ + } + +#define dbr_type_to_text(type) \ + ( ((type) >= 0 && (type) < dbr_text_dim) ? \ + dbr_text[(type)] : dbr_text_invalid ) + +#define dbr_text_to_type(text, type) \ + for (type=dbr_text_dim-2; type>=0; type--) { \ + if (strcmp(text, dbr_text[type]) == 0) \ + break; \ + } + +#define dbf_type_to_DBR(type) \ + (((type) >= 0 && (type) <= dbf_text_dim-3) ? \ + (type) : -1 ) + +#define dbf_type_to_DBR_STS(type) \ + (((type) >= 0 && (type) <= dbf_text_dim-3) ? \ + (type) + (dbf_text_dim-2) : -1 ) + +#define dbf_type_to_DBR_TIME(type) \ + (((type) >= 0 && (type) <= dbf_text_dim-3) ? \ + (type) + 2*(dbf_text_dim-2) : -1 ) + +#define dbf_type_to_DBR_GR(type) \ + (((type) >= 0 && (type) <= dbf_text_dim-3) ? \ + (type) + 3*(dbf_text_dim-2) : -1 ) + +#define dbf_type_to_DBR_CTRL(type) \ + (((type) >= 0 && (type) <= dbf_text_dim-3) ? \ + (type) + 4*(dbf_text_dim-2) : -1 ) + + +epicsShareExtern const char *dbf_text[LAST_TYPE+3]; +epicsShareExtern const short dbf_text_dim; +epicsShareExtern const char *dbf_text_invalid; + +epicsShareExtern const char *dbr_text[LAST_BUFFER_TYPE+1]; +epicsShareExtern const short dbr_text_dim; +epicsShareExtern const char *dbr_text_invalid; +#endif /*db_accessHFORdb_accessC*/ + +#ifdef __cplusplus +} +#endif + +#endif /* INCLdb_accessh */ diff --git a/modules/ca/src/client/disconnectGovernorTimer.cpp b/modules/ca/src/client/disconnectGovernorTimer.cpp new file mode 100644 index 000000000..f1d517f07 --- /dev/null +++ b/modules/ca/src/client/disconnectGovernorTimer.cpp @@ -0,0 +1,110 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +// +// +// L O S A L A M O S +// Los Alamos National Laboratory +// Los Alamos, New Mexico 87545 +// +// Copyright, 1986, The Regents of the University of California. +// +// Author: Jeff Hill +// + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "disconnectGovernorTimer.h" +#include "udpiiu.h" +#include "nciu.h" + +static const double disconnectGovernorPeriod = 10.0; // sec + +disconnectGovernorTimer::disconnectGovernorTimer ( + disconnectGovernorNotify & iiuIn, + epicsTimerQueue & queueIn, + epicsMutex & mutexIn ) : + mutex ( mutexIn ), timer ( queueIn.createTimer () ), + iiu ( iiuIn ) +{ +} + +disconnectGovernorTimer::~disconnectGovernorTimer () +{ + this->timer.destroy (); +} + +void disconnectGovernorTimer:: start () +{ + this->timer.start ( *this, disconnectGovernorPeriod ); +} + +void disconnectGovernorTimer::shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbUnguard ( cbGuard ); + this->timer.cancel (); + } + } + while ( nciu * pChan = this->chanList.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->serviceShutdownNotify ( cbGuard, guard ); + } +} + +epicsTimerNotify::expireStatus disconnectGovernorTimer::expire ( + const epicsTime & /* currentTime */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + while ( nciu * pChan = chanList.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + this->iiu.govExpireNotify ( guard, *pChan ); + } + return expireStatus ( restart, disconnectGovernorPeriod ); +} + +void disconnectGovernorTimer::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + ::printf ( "disconnect governor timer: with %u channels pending\n", + this->chanList.count () ); + if ( level > 0u ) { + tsDLIterConst < nciu > pChan = this->chanList.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 1u ); + pChan++; + } + } +} + +void disconnectGovernorTimer::installChan ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->chanList.add ( chan ); + chan.channelNode::listMember = channelNode::cs_disconnGov; +} + +void disconnectGovernorTimer::uninstallChan ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->chanList.remove ( chan ); + chan.channelNode::listMember = channelNode::cs_none; +} + +disconnectGovernorNotify::~disconnectGovernorNotify () {} + diff --git a/modules/ca/src/client/disconnectGovernorTimer.h b/modules/ca/src/client/disconnectGovernorTimer.h new file mode 100644 index 000000000..f636d6260 --- /dev/null +++ b/modules/ca/src/client/disconnectGovernorTimer.h @@ -0,0 +1,77 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +// +// +// +// L O S A L A M O S +// Los Alamos National Laboratory +// Los Alamos, New Mexico 87545 +// +// Copyright, 1986, The Regents of the University of California. +// +// +// Author Jeffrey O. Hill +// johill@lanl.gov +// 505 665 1831 +// + +#ifndef disconnectGovernorTimerh +#define disconnectGovernorTimerh + +#ifdef epicsExportSharedSymbols +# define searchTimerh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsMutex.h" +#include "epicsGuard.h" +#include "epicsTimer.h" + +#ifdef searchTimerh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "caProto.h" +#include "netiiu.h" + +class disconnectGovernorNotify { +public: + virtual ~disconnectGovernorNotify () = 0; + virtual void govExpireNotify ( + epicsGuard < epicsMutex > &, nciu & ) = 0; +}; + +class disconnectGovernorTimer : private epicsTimerNotify { +public: + disconnectGovernorTimer ( + class disconnectGovernorNotify &, epicsTimerQueue &, epicsMutex & ); + virtual ~disconnectGovernorTimer (); + void start (); + void shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void installChan ( + epicsGuard < epicsMutex > &, nciu & ); + void uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ); + void show ( unsigned level ) const; +private: + tsDLList < nciu > chanList; + epicsMutex & mutex; + epicsTimer & timer; + class disconnectGovernorNotify & iiu; + epicsTimerNotify::expireStatus expire ( const epicsTime & currentTime ); + disconnectGovernorTimer ( const disconnectGovernorTimer & ); + disconnectGovernorTimer & operator = ( const disconnectGovernorTimer & ); +}; + +#endif // ifdef disconnectGovernorTimerh diff --git a/modules/ca/src/client/evtime.c b/modules/ca/src/client/evtime.c new file mode 100644 index 000000000..d5cf58382 --- /dev/null +++ b/modules/ca/src/client/evtime.c @@ -0,0 +1,95 @@ +/*************************************************************************\ +* 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 "dbDefs.h" +#include "epicsTime.h" +#include "cadef.h" + +void event_handler (struct event_handler_args args); +int evtime (char *pname); + +static unsigned iteration_count; +static epicsUInt32 last_time; + +#ifndef iocCore +int main(int argc, char **argv) +{ + char *pname; + + if(argc == 2){ + pname = argv[1]; + evtime(pname); + } + else{ + printf("usage: %s ", argv[0]); + } + return(0); +} +#endif + +/* + * evtime() + */ +int evtime(char *pname) +{ + chid chan; + int status; + + status = ca_search(pname, &chan); + SEVCHK(status, NULL); + + status = ca_pend_io(10.0); + if(status != ECA_NORMAL){ + printf("%s not found\n", pname); + return 0; + } + + status = ca_add_event( + DBR_FLOAT, + chan, + event_handler, + NULL, + NULL); + SEVCHK(status, __FILE__); + + status = ca_pend_event(0.0); + SEVCHK(status, NULL); +} + + +/* + * event_handler() + * + */ +void event_handler(struct event_handler_args args) +{ + epicsUInt32 current_time; +# define COUNT 0x8000 + double interval; + double delay; + epicsTimeStamp ts; + + if(iteration_count%COUNT == 0){ + epicsTimeGetCurrent(&ts); + current_time = ts.secPastEpoch; + if(last_time != 0){ + interval = current_time - last_time; + delay = interval/COUNT; + printf("Delay = %f sec per event\n", + delay); + } + last_time = current_time; + } + + iteration_count++; +} + diff --git a/modules/ca/src/client/future_work.txt b/modules/ca/src/client/future_work.txt new file mode 100644 index 000000000..3f3cb3317 --- /dev/null +++ b/modules/ca/src/client/future_work.txt @@ -0,0 +1,38 @@ + +Potential upgrades to Channel Access + +o generalized access to abstract data +o use multicast for broadcasting and ax the repeater +o improve client API +o improve security +o name service (wildcard queries) +o PV name prefix (for all PVs) +o client access to process passive and for maximize severity +o detect name conflicts at boot time +o multi priority connections (quality of service) +o reduce protocol overhead +o compressed protocol + + +o If there is a beacon anomaly then this indicates that +the server is _not_ available. Perhaps we should only +reset the search timer interval when we see a beacon +anomaly transition into a sure steady beacon. + +o make certain that a monitor callback canceling itself +does not deadlock. + +o the free list library does not now cause exceptions to +occur (it uses new ( nothrow )), and therefore we should +change the new handlers to be nothrow also if this is +the design goal. + +o test the library when an IOC is running low on memory. + +o The new CA interface should be multi-threaded by default. +We should continue to support a single-threaded interface, +but this would should be restricted so that it always +runs with preemptive callback disable and no threads +may join in. Alternatively, the new CA interface could +be 100% preemptive multi-threaded and the old interface +could be layered on this. diff --git a/modules/ca/src/client/getCallback.cpp b/modules/ca/src/client/getCallback.cpp new file mode 100644 index 000000000..0fc050043 --- /dev/null +++ b/modules/ca/src/client/getCallback.cpp @@ -0,0 +1,104 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" + +getCallback::getCallback ( oldChannelNotify & chanIn, + caEventCallBackFunc *pFuncIn, void *pPrivateIn ) : + chan ( chanIn ), pFunc ( pFuncIn ), pPrivate ( pPrivateIn ) +{ +} + +getCallback::~getCallback () +{ +} + +void getCallback::completion ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount count, const void *pData ) +{ + struct event_handler_args args; + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = type; + args.count = count; + args.status = ECA_NORMAL; + args.dbr = pData; + caEventCallBackFunc * pFuncTmp = this->pFunc; + // fetch client context and destroy prior to releasing + // the lock and calling cb in case they destroy channel there + this->chan.getClientCtx().destroyGetCallback ( guard, *this ); + if ( pFuncTmp ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + pFuncTmp ( args ); + } +} + +void getCallback::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char * /* pContext */, + unsigned type, arrayElementCount count ) +{ + if ( status != ECA_CHANDESTROY ) { + struct event_handler_args args; + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = type; + args.count = count; + args.status = status; + args.dbr = 0; + caEventCallBackFunc * pFuncTmp = this->pFunc; + // fetch client context and destroy prior to releasing + // the lock and calling cb in case they destroy channel there + this->chan.getClientCtx().destroyGetCallback ( guard, *this ); + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFuncTmp ) ( args ); + } + } + else { + this->chan.getClientCtx().destroyGetCallback ( guard, *this ); + } +} + +void getCallback::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/ca/src/client/getCopy.cpp b/modules/ca/src/client/getCopy.cpp new file mode 100644 index 000000000..23a508d9a --- /dev/null +++ b/modules/ca/src/client/getCopy.cpp @@ -0,0 +1,118 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include "errlog.h" + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" +#include "cac.h" + +getCopy::getCopy ( + epicsGuard < epicsMutex > & guard, ca_client_context & cacCtxIn, + oldChannelNotify & chanIn, unsigned typeIn, + arrayElementCount countIn, void * pValueIn ) : + count ( countIn ), cacCtx ( cacCtxIn ), chan ( chanIn ), pValue ( pValueIn ), + ioSeqNo ( 0 ), type ( typeIn ) +{ + this->ioSeqNo = cacCtxIn.sequenceNumberOfOutstandingIO ( guard ); + cacCtxIn.incrementOutstandingIO ( guard, this->ioSeqNo ); +} + +getCopy::~getCopy () +{ +} + +void getCopy::cancel () +{ + epicsGuard < epicsMutex > guard ( this->cacCtx.mutexRef () ); + this->cacCtx.decrementOutstandingIO ( guard, this->ioSeqNo ); +} + +void getCopy::completion ( + epicsGuard < epicsMutex > & guard, unsigned typeIn, + arrayElementCount countIn, const void *pDataIn ) +{ + if ( this->type == typeIn ) { + unsigned size = dbr_size_n ( typeIn, countIn ); + memcpy ( this->pValue, pDataIn, size ); + this->cacCtx.decrementOutstandingIO ( guard, this->ioSeqNo ); + this->cacCtx.destroyGetCopy ( guard, *this ); + // this object destroyed by preceding function call + } + else { + this->exception ( guard, ECA_INTERNAL, + "bad data type match in get copy back response", + typeIn, countIn); + // this object destroyed by preceding function call + } +} + +void getCopy::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char *pContext, + unsigned /* typeIn */, arrayElementCount /* countIn */ ) +{ + oldChannelNotify & chanTmp ( this->chan ); + unsigned typeTmp ( this->type ); + arrayElementCount countTmp ( this->count ); + ca_client_context & caClientCtx ( this->cacCtx ); + // fetch client context and destroy prior to releasing + // the lock and calling cb in case they destroy channel there + this->cacCtx.destroyGetCopy ( guard, *this ); + if ( status != ECA_CHANDESTROY ) { + caClientCtx.exception ( guard, status, pContext, + __FILE__, __LINE__, chanTmp, typeTmp, + countTmp, CA_OP_GET ); + } +} + +void getCopy::show ( unsigned level ) const +{ + int tmpType = static_cast ( this->type ); + ::printf ( "read copy IO at %p, type %s, element count %lu\n", + static_cast ( this ), dbf_type_to_text ( tmpType ), this->count ); + if ( level > 0u ) { + ::printf ( "\tIO sequence number %u, user's storage %p\n", + this->ioSeqNo, static_cast ( this->pValue ) ); + } +} + +void getCopy::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/ca/src/client/hostNameCache.cpp b/modules/ca/src/client/hostNameCache.cpp new file mode 100644 index 000000000..c3d105c06 --- /dev/null +++ b/modules/ca/src/client/hostNameCache.cpp @@ -0,0 +1,92 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + */ + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" +#include "hostNameCache.h" +#include "epicsGuard.h" + +hostNameCache::hostNameCache ( + const osiSockAddr & addr, ipAddrToAsciiEngine & engine ) : + dnsTransaction ( engine.createTransaction() ), nameLength ( 0 ) +{ + sockAddrToDottedIP ( &addr.sa, hostNameBuf, sizeof ( hostNameBuf ) ); + hostNameBuf[ sizeof ( hostNameBuf ) - 1 ] = '\0'; + nameLength = strlen ( hostNameBuf ); + this->dnsTransaction.ipAddrToAscii ( addr, *this ); +} + +void hostNameCache::destroy () +{ + delete this; +} + +hostNameCache::~hostNameCache () +{ + this->dnsTransaction.release (); +} + +void hostNameCache::transactionComplete ( const char * pHostNameIn ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + // a few legacy clients have a direct pointer to this buffer so we + // set the entrire string to nill terminators before we start copying + // in the name (this reduces the chance that another thread will see + // garbage characters). + size_t newNameLen = strlen ( pHostNameIn ); + if ( newNameLen > sizeof ( this->hostNameBuf ) - 1u ) { + newNameLen = sizeof ( this->hostNameBuf ) - 1u; + } + strncpy ( this->hostNameBuf, "", sizeof ( this->hostNameBuf ) ); + strncpy ( this->hostNameBuf, pHostNameIn, sizeof ( this->hostNameBuf ) - 1 ); + this->nameLength = newNameLen; +} + +unsigned hostNameCache::getName ( + char * pBuf, unsigned bufSize ) const +{ + if ( bufSize == 0u ) { + return 0u; + } + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->nameLength > 0u ) { + if ( this->nameLength < bufSize ) { + strcpy ( pBuf, this->hostNameBuf ); + return this->nameLength; + } + else { + unsigned reducedSize = bufSize - 1u; + strncpy ( pBuf, this->hostNameBuf, reducedSize ); + pBuf [ reducedSize ] = '\0'; + return reducedSize; + } + } + else { + osiSockAddr tmpAddr = this->dnsTransaction.address (); + return sockAddrToDottedIP ( &tmpAddr.sa, pBuf, bufSize ); + } +} + diff --git a/modules/ca/src/client/hostNameCache.h b/modules/ca/src/client/hostNameCache.h new file mode 100644 index 000000000..a4eacfbb3 --- /dev/null +++ b/modules/ca/src/client/hostNameCache.h @@ -0,0 +1,61 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef hostNameCacheh +#define hostNameCacheh + +#ifdef epicsExportSharedSymbols +# define hostNameCache_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "ipAddrToAsciiAsynchronous.h" +#include "epicsMutex.h" + +#ifdef hostNameCache_epicsExportSharedSymbols +# define epicsExportSharedSymbols +#endif + +class hostNameCache : public ipAddrToAsciiCallBack { +public: + hostNameCache ( const osiSockAddr & addr, ipAddrToAsciiEngine & engine ); + ~hostNameCache (); + void destroy (); + void transactionComplete ( const char * pHostName ); + unsigned getName ( char *pBuf, unsigned bufLength ) const; + const char * pointer () const; +private: + char hostNameBuf [128]; + mutable epicsMutex mutex; + ipAddrToAsciiTransaction & dnsTransaction; + unsigned nameLength; +}; + +inline const char * hostNameCache::pointer () const +{ + return this->hostNameBuf; +} + +#endif // #ifndef hostNameCacheh diff --git a/modules/ca/src/client/inetAddrID.h b/modules/ca/src/client/inetAddrID.h new file mode 100644 index 000000000..978787599 --- /dev/null +++ b/modules/ca/src/client/inetAddrID.h @@ -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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#ifndef inetAddrIDh +#define inetAddrIDh + +#include "osiSock.h" +#include "resourceLib.h" + +class inetAddrID { +public: + inetAddrID ( const struct sockaddr_in & addrIn ); + bool operator == ( const inetAddrID & ) const; + resTableIndex hash () const; + void name ( char *pBuf, unsigned bufSize ) const; +private: + struct sockaddr_in addr; +}; + +inline inetAddrID::inetAddrID ( const struct sockaddr_in & addrIn ) : + addr ( addrIn ) +{ +} + +inline bool inetAddrID::operator == ( const inetAddrID &rhs ) const +{ + if ( this->addr.sin_addr.s_addr == rhs.addr.sin_addr.s_addr ) { + if ( this->addr.sin_port == rhs.addr.sin_port ) { + return true; + } + } + return false; +} + +inline resTableIndex inetAddrID::hash () const +{ + const unsigned inetAddrMinIndexBitWidth = 8u; + const unsigned inetAddrMaxIndexBitWidth = 32u; + unsigned index; + index = this->addr.sin_addr.s_addr; + index ^= this->addr.sin_port; + index ^= this->addr.sin_port >> 8u; + return integerHash ( inetAddrMinIndexBitWidth, + inetAddrMaxIndexBitWidth, index ); +} + +inline void inetAddrID::name ( char *pBuf, unsigned bufSize ) const +{ + ipAddrToDottedIP ( &this->addr, pBuf, bufSize ); +} + +#endif // ifdef inetAddrID + + diff --git a/modules/ca/src/client/iocinf.cpp b/modules/ca/src/client/iocinf.cpp new file mode 100644 index 000000000..09eea292b --- /dev/null +++ b/modules/ca/src/client/iocinf.cpp @@ -0,0 +1,265 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include +#include +#include +#include + +#include "envDefs.h" +#include "epicsAssert.h" +#include "epicsStdioRedirect.h" +#include "errlog.h" +#include "osiWireFormat.h" + +#define epicsExportSharedSymbols +#include "addrList.h" +#undef epicsExportSharedSymbols + +#include "iocinf.h" + +/* + * getToken() + */ +static char *getToken ( const char **ppString, char *pBuf, unsigned bufSIze ) +{ + bool tokenFound = false; + const char *pToken; + unsigned i; + + pToken = *ppString; + while ( isspace (*pToken) && *pToken ){ + pToken++; + } + + for ( i=0u; iname); + fprintf ( stderr, "\tBad internet address or host name: '%s'\n", pToken); + continue; + } + + if ( ignoreNonDefaultPort && ntohs ( addr.sin_port ) != port ) { + continue; + } + + pNewNode = (osiSockAddrNode *) calloc (1, sizeof(*pNewNode)); + if (pNewNode==NULL) { + fprintf ( stderr, "addAddrToChannelAccessAddressList(): no memory available for configuration\n"); + break; + } + + pNewNode->addr.ia = addr; + + /* + * LOCK applied externally + */ + ellAdd (pList, &pNewNode->node); + ret = 0; /* success if anything is added to the list */ + } + + return ret; +} + +/* + * removeDuplicateAddresses () + */ +extern "C" void epicsShareAPI removeDuplicateAddresses + ( ELLLIST *pDestList, ELLLIST *pSrcList, int silent ) +{ + ELLNODE *pRawNode; + + while ( (pRawNode = ellGet ( pSrcList ) ) ) { + STATIC_ASSERT ( offsetof (osiSockAddrNode, node) == 0 ); + osiSockAddrNode *pNode = reinterpret_cast ( pRawNode ); + osiSockAddrNode *pTmpNode; + + if ( pNode->addr.sa.sa_family == AF_INET ) { + + pTmpNode = (osiSockAddrNode *) ellFirst (pDestList); + while ( pTmpNode ) { + if (pTmpNode->addr.sa.sa_family == AF_INET) { + if ( pNode->addr.ia.sin_addr.s_addr == pTmpNode->addr.ia.sin_addr.s_addr && + pNode->addr.ia.sin_port == pTmpNode->addr.ia.sin_port ) { + if ( ! silent ) { + char buf[64]; + ipAddrToDottedIP ( &pNode->addr.ia, buf, sizeof (buf) ); + fprintf ( stderr, + "Warning: Duplicate EPICS CA Address list entry \"%s\" discarded\n", buf ); + } + free (pNode); + pNode = NULL; + break; + } + } + pTmpNode = (osiSockAddrNode *) ellNext (&pTmpNode->node); + } + if (pNode) { + ellAdd (pDestList, &pNode->node); + } + } + else { + ellAdd (pDestList, &pNode->node); + } + } +} + +/* + * forcePort () + */ +static void forcePort ( ELLLIST *pList, unsigned short port ) +{ + osiSockAddrNode *pNode; + + pNode = ( osiSockAddrNode * ) ellFirst ( pList ); + while ( pNode ) { + if ( pNode->addr.sa.sa_family == AF_INET ) { + pNode->addr.ia.sin_port = htons ( port ); + } + pNode = ( osiSockAddrNode * ) ellNext ( &pNode->node ); + } +} + + +/* + * configureChannelAccessAddressList () + */ +extern "C" void epicsShareAPI configureChannelAccessAddressList + ( ELLLIST *pList, SOCKET sock, unsigned short port ) +{ + ELLLIST tmpList; + char *pstr; + char yesno[32u]; + int yes; + + /* + * dont load the list twice + */ + assert ( ellCount (pList) == 0 ); + + ellInit ( &tmpList ); + + /* + * Check to see if the user has disabled + * initializing the search b-cast list + * from the interfaces found. + */ + yes = true; + pstr = envGetConfigParam ( &EPICS_CA_AUTO_ADDR_LIST, + sizeof (yesno), yesno ); + if ( pstr ) { + if ( strstr ( pstr, "no" ) || strstr ( pstr, "NO" ) ) { + yes = false; + } + } + + /* + * LOCK is for piiu->destAddr list + * (lock outside because this is used by the server also) + */ + if (yes) { + ELLLIST bcastList; + osiSockAddr addr; + ellInit ( &bcastList ); + addr.ia.sin_family = AF_UNSPEC; + osiSockDiscoverBroadcastAddresses ( &bcastList, sock, &addr ); + forcePort ( &bcastList, port ); + removeDuplicateAddresses ( &tmpList, &bcastList, 1 ); + if ( ellCount ( &tmpList ) == 0 ) { + osiSockAddrNode *pNewNode; + pNewNode = (osiSockAddrNode *) calloc ( 1, sizeof (*pNewNode) ); + if ( pNewNode ) { + /* + * if no interfaces found then look for local channels + * with the loop back interface + */ + pNewNode->addr.ia.sin_family = AF_INET; + pNewNode->addr.ia.sin_addr.s_addr = htonl ( INADDR_LOOPBACK ); + pNewNode->addr.ia.sin_port = htons ( port ); + ellAdd ( &tmpList, &pNewNode->node ); + } + else { + errlogPrintf ( "configureChannelAccessAddressList(): no memory available for configuration\n" ); + } + } + } + addAddrToChannelAccessAddressList ( &tmpList, &EPICS_CA_ADDR_LIST, port, false ); + + removeDuplicateAddresses ( pList, &tmpList, 0 ); +} + + +/* + * printChannelAccessAddressList () + */ +extern "C" void epicsShareAPI printChannelAccessAddressList ( const ELLLIST *pList ) +{ + osiSockAddrNode *pNode; + + ::printf ( "Channel Access Address List\n" ); + pNode = (osiSockAddrNode *) ellFirst ( pList ); + while (pNode) { + char buf[64]; + ipAddrToA ( &pNode->addr.ia, buf, sizeof ( buf ) ); + ::printf ( "%s\n", buf ); + pNode = (osiSockAddrNode *) ellNext ( &pNode->node ); + } +} diff --git a/modules/ca/src/client/iocinf.h b/modules/ca/src/client/iocinf.h new file mode 100644 index 000000000..0d3b57db1 --- /dev/null +++ b/modules/ca/src/client/iocinf.h @@ -0,0 +1,70 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef INCiocinfh +#define INCiocinfh + +#ifdef DEBUG +# define debugPrintf(argsInParen) ::printf argsInParen +#else +# define debugPrintf(argsInParen) +#endif + +#if defined ( CLOCKS_PER_SEC ) +# define CAC_SIGNIFICANT_DELAY ( 1.0 / CLOCKS_PER_SEC ) +#else +# define CAC_SIGNIFICANT_DELAY (1.0 / 1000000u) +#endif + +/* + * these two control the period of connection verifies + * (echo requests) - CA_CONN_VERIFY_PERIOD - and how + * long we will wait for an echo reply before we + * give up and flag the connection for disconnect + * - CA_ECHO_TIMEOUT. + * + * CA_CONN_VERIFY_PERIOD is normally obtained from an + * EPICS environment variable. + */ +static const double CA_ECHO_TIMEOUT = 5.0; /* (sec) disconn no echo reply tmo */ +static const double CA_CONN_VERIFY_PERIOD = 30.0; /* (sec) how often to request echo */ + +/* + * this determines the number of messages received + * without a delay in between before we go into + * monitor flow control + * + * turning this down effects maximum throughput + * because we dont get an optimal number of bytes + * per network frame + */ +static const unsigned contiguousMsgCountWhichTriggersFlowControl = 10u; + +/* + * CA internal functions + */ +#define genLocalExcep( CBGUARD, GUARD, CAC, STAT, PCTX ) \ +(CAC).exception ( CBGUARD, GUARD, STAT, PCTX, __FILE__, __LINE__ ) + +#endif // ifdef INCiocinfh diff --git a/modules/ca/src/client/localHostName.cpp b/modules/ca/src/client/localHostName.cpp new file mode 100644 index 000000000..b0b96bb47 --- /dev/null +++ b/modules/ca/src/client/localHostName.cpp @@ -0,0 +1,66 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include "osiSock.h" + +#include "localHostName.h" + +epicsSingleton < localHostName > localHostNameCache; + +localHostName::localHostName () : + attachedToSockLib ( osiSockAttach () != 0 ), length ( 0u ) +{ + const char * pErrStr = ""; + int status = -1; + if ( this->attachedToSockLib ) { + status = gethostname ( + this->cache, sizeof ( this->cache ) ); + } + if ( status ) { + strncpy ( this->cache, pErrStr, sizeof ( this->cache ) ); + } + this->cache [ sizeof ( this->cache ) - 1u ] = '\0'; + this->length = strlen ( this->cache ); +} + +localHostName::~localHostName () +{ + if ( this->attachedToSockLib ) { + osiSockRelease (); + } +} + +unsigned localHostName::getName ( + char * pBuf, unsigned bufLength ) const +{ + if ( bufLength ) { + strncpy ( pBuf, this->cache, bufLength ); + if ( this->length < bufLength ) { + return this->length; + } + else { + unsigned reducedSize = bufLength - 1; + pBuf [ reducedSize ] = '\0'; + return reducedSize; + } + } + return 0u; +} diff --git a/modules/ca/src/client/localHostName.h b/modules/ca/src/client/localHostName.h new file mode 100644 index 000000000..f116b8140 --- /dev/null +++ b/modules/ca/src/client/localHostName.h @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#ifndef localHostNameh +#define localHostNameh + +#include + +#ifdef epicsExportSharedSymbols +# define localHostNameh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsSingleton.h" + +#ifdef localHostNameh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +#endif + +class localHostName { +public: + localHostName (); + ~localHostName (); + const char * pointer () const; + unsigned getName ( char * pBuf, unsigned bufLength ) const; + unsigned nameLength () const; +private: + bool attachedToSockLib; + unsigned length; + char cache [128]; +}; + +extern epicsSingleton < localHostName > localHostNameCache; + +inline unsigned localHostName::nameLength () const +{ + return this->length; +} + +inline const char * localHostName::pointer () const +{ + return this->cache; +} + +#endif // ifndef localHostNameh + + diff --git a/modules/ca/src/client/msgForMultiplyDefinedPV.cpp b/modules/ca/src/client/msgForMultiplyDefinedPV.cpp new file mode 100644 index 000000000..7002dd4b9 --- /dev/null +++ b/modules/ca/src/client/msgForMultiplyDefinedPV.cpp @@ -0,0 +1,93 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "msgForMultiplyDefinedPV.h" +#include "cac.h" +#include "caerr.h" // for ECA_DBLCHNL + +msgForMultiplyDefinedPV::msgForMultiplyDefinedPV ( + ipAddrToAsciiEngine & engine, + callbackForMultiplyDefinedPV & cbIn, + const char * pChannelName, const char * pAcc ) : + dnsTransaction ( engine.createTransaction () ), cb ( cbIn ) +{ + strncpy ( this->acc, pAcc, sizeof ( this->acc ) ); + this->acc[ sizeof ( this->acc ) - 1 ] = '\0'; + strncpy ( this->channel, pChannelName, sizeof ( this->channel ) ); + this->channel[ sizeof ( this->channel ) - 1 ] = '\0'; +} + +msgForMultiplyDefinedPV::~msgForMultiplyDefinedPV () +{ + this->dnsTransaction.release (); +} + +void msgForMultiplyDefinedPV::transactionComplete ( const char * pHostNameRej ) +{ + // calls into cac for the notification + // the msg object (= this) is being deleted as part of the notification + this->cb.pvMultiplyDefinedNotify ( *this, this->channel, this->acc, pHostNameRej ); + // !! dont touch 'this' pointer after this point because object has been deleted !! +} + +void * msgForMultiplyDefinedPV::operator new ( size_t size, + tsFreeList < class msgForMultiplyDefinedPV, 16 > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +void msgForMultiplyDefinedPV::operator delete ( void *pCadaver, + tsFreeList < class msgForMultiplyDefinedPV, 16 > & freeList ) +{ + freeList.release ( pCadaver, sizeof ( msgForMultiplyDefinedPV ) ); +} +#endif + +void msgForMultiplyDefinedPV::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__ ); +} + +callbackForMultiplyDefinedPV::~callbackForMultiplyDefinedPV () +{ +} + + diff --git a/modules/ca/src/client/msgForMultiplyDefinedPV.h b/modules/ca/src/client/msgForMultiplyDefinedPV.h new file mode 100644 index 000000000..3f1e771c0 --- /dev/null +++ b/modules/ca/src/client/msgForMultiplyDefinedPV.h @@ -0,0 +1,79 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef msgForMultiplyDefinedPVh +#define msgForMultiplyDefinedPVh + +#ifdef epicsExportSharedSymbols +# define msgForMultiplyDefinedPVh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "ipAddrToAsciiAsynchronous.h" +#include "tsFreeList.h" +#include "tsDLList.h" +#include "compilerDependencies.h" + +#ifdef msgForMultiplyDefinedPVh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +#endif + +class callbackForMultiplyDefinedPV { +public: + virtual ~callbackForMultiplyDefinedPV () = 0; + virtual void pvMultiplyDefinedNotify ( + class msgForMultiplyDefinedPV &, const char * pChannelName, + const char * pAcc, const char * pRej ) = 0; +}; + +class msgForMultiplyDefinedPV : + public ipAddrToAsciiCallBack, + public tsDLNode < msgForMultiplyDefinedPV > { +public: + msgForMultiplyDefinedPV ( ipAddrToAsciiEngine & engine, + callbackForMultiplyDefinedPV &, const char * pChannelName, + const char * pAcc ); + virtual ~msgForMultiplyDefinedPV (); + void ioInitiate ( const osiSockAddr & rej ); + void * operator new ( size_t size, tsFreeList < class msgForMultiplyDefinedPV, 16 > & ); + epicsPlacementDeleteOperator (( void *, tsFreeList < class msgForMultiplyDefinedPV, 16 > & )) +private: + char acc[64]; + char channel[64]; + ipAddrToAsciiTransaction & dnsTransaction; + callbackForMultiplyDefinedPV & cb; + void transactionComplete ( const char * pHostName ); + msgForMultiplyDefinedPV ( const msgForMultiplyDefinedPV & ); + msgForMultiplyDefinedPV & operator = ( const msgForMultiplyDefinedPV & ); + void operator delete ( void * ); +}; + +inline void msgForMultiplyDefinedPV::ioInitiate ( const osiSockAddr & rej ) +{ + this->dnsTransaction.ipAddrToAscii ( rej, *this ); +} + +#endif // ifdef msgForMultiplyDefinedPVh + diff --git a/modules/ca/src/client/nciu.cpp b/modules/ca/src/client/nciu.cpp new file mode 100644 index 000000000..8eb89c5c3 --- /dev/null +++ b/modules/ca/src/client/nciu.cpp @@ -0,0 +1,626 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + * + */ + +#include +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "epicsAlgorithm.h" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "cac.h" +#include "osiWireFormat.h" +#include "udpiiu.h" +#include "virtualCircuit.h" +#include "cadef.h" +#include "db_access.h" // for INVALID_DB_REQ +#include "noopiiu.h" + +nciu::nciu ( cac & cacIn, netiiu & iiuIn, cacChannelNotify & chanIn, + const char *pNameIn, cacChannel::priLev pri ) : + cacChannel ( chanIn ), + cacCtx ( cacIn ), + piiu ( & iiuIn ), + sid ( UINT_MAX ), + count ( 0 ), + retry ( 0u ), + nameLength ( 0u ), + typeCode ( USHRT_MAX ), + priority ( static_cast ( pri ) ) +{ + size_t nameLengthTmp = strlen ( pNameIn ) + 1; + + // second constraint is imposed by size field in protocol header + if ( nameLengthTmp > MAX_UDP_SEND - sizeof ( caHdr ) || nameLengthTmp > USHRT_MAX ) { + throw cacChannel::badString (); + } + + if ( pri > 0xff ) { + throw cacChannel::badPriority (); + } + + this->nameLength = static_cast ( nameLengthTmp ); + + this->pNameStr = new char [ this->nameLength ]; + strcpy ( this->pNameStr, pNameIn ); +} + +nciu::~nciu () +{ + delete [] this->pNameStr; +} + +// channels are created by the user, and only destroyed by the user +// using this routine +void nciu::destroy ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExcusionGuard ) +{ + while ( baseNMIU * pNetIO = this->eventq.first () ) { + bool success = this->cacCtx.destroyIO ( callbackGuard, mutualExcusionGuard, + pNetIO->getId (), *this ); + assert ( success ); + } + + // if the claim reply has not returned yet then we will issue + // the clear channel request to the server when the claim reply + // arrives and there is no matching nciu in the client + if ( this->channelNode::isInstalledInServer ( mutualExcusionGuard ) ) { + this->getPIIU(mutualExcusionGuard)->clearChannelRequest ( + mutualExcusionGuard, this->sid, this->id ); + } + this->piiu->uninstallChan ( mutualExcusionGuard, *this ); + this->cacCtx.destroyChannel ( mutualExcusionGuard, *this ); +} + +void nciu::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 nciu::initiateConnect ( + epicsGuard < epicsMutex > & guard ) +{ + this->cacCtx.initiateConnect ( guard, *this, this->piiu ); +} + +void nciu::connect ( unsigned nativeType, + unsigned nativeCount, unsigned sidIn, + epicsGuard < epicsMutex > & /* cbGuard */, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + if ( ! dbf_type_is_valid ( nativeType ) ) { + throw std::logic_error ( "Ignored conn resp with bad native data type" ); + } + + this->typeCode = static_cast < unsigned short > ( nativeType ); + this->count = nativeCount; + this->sid = sidIn; + + /* + * if less than v4.1 then the server will never + * send access rights and there will always be access + */ + bool v41Ok = this->piiu->ca_v41_ok ( guard ); + if ( ! v41Ok ) { + this->accessRightState.setReadPermit(); + this->accessRightState.setWritePermit(); + } + + /* + * if less than v4.1 then the server will never + * send access rights and we know that there + * will always be access and also need to call + * their call back here + */ + if ( ! v41Ok ) { + this->notify().accessRightsNotify ( + guard, this->accessRightState ); + } + + // channel uninstal routine grabs the callback lock so + // a channel will not be deleted while a call back is + // in progress + // + // the callback lock is also taken when a channel + // disconnects to prevent a race condition with the + // code below - ie we hold the callback lock here + // so a chanel cant be destroyed out from under us. + this->notify().connectNotify ( guard ); +} + +void nciu::unresponsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + ioid tmpId = this->getId (); + cac & caRefTmp = this->cacCtx; + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + this->cacCtx.disconnectAllIO ( cbGuard, guard, + *this, this->eventq ); + this->notify().disconnectNotify ( guard ); + // if they destroy the channel in their disconnect + // handler then we have to be very careful to not + // touch this object if it has been destroyed + nciu * pChan = caRefTmp.lookupChannel ( guard, tmpId ); + if ( pChan ) { + caAccessRights noRights; + pChan->notify().accessRightsNotify ( guard, noRights ); + // likewise, they might destroy the channel in their access rights + // handler so we have to be very careful to not touch this + // object from here on down + } +} + +void nciu::setServerAddressUnknown ( netiiu & newiiu, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + this->piiu = & newiiu; + this->retry = 0; + this->typeCode = USHRT_MAX; + this->count = 0u; + this->sid = UINT_MAX; + this->accessRightState.clrReadPermit(); + this->accessRightState.clrWritePermit(); +} + +void nciu::accessRightsStateChange ( + const caAccessRights & arIn, epicsGuard < epicsMutex > & /* cbGuard */, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + this->accessRightState = arIn; + + // + // the channel delete routine takes the call back lock so + // that this will not be called when the channel is being + // deleted. + // + this->notify().accessRightsNotify ( guard, this->accessRightState ); +} + +/* + * nciu::searchMsg () + */ +bool nciu::searchMsg ( epicsGuard < epicsMutex > & guard ) +{ + bool success = this->piiu->searchMsg ( + guard, this->getId (), this->pNameStr, this->nameLength ); + if ( success ) { + if ( this->retry < UINT_MAX ) { + this->retry++; + } + } + return success; +} + +const char *nciu::pName ( + epicsGuard < epicsMutex > & guard ) const throw () +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + return this->pNameStr; +} + +unsigned nciu::getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () +{ + if ( bufLen == 0u ) { + return 0u; + } + if ( this->nameLength < bufLen ) { + strcpy ( pBuf, this->pNameStr ); + return this->nameLength; + } + else { + unsigned reducedSize = bufLen - 1u; + strncpy ( pBuf, this->pNameStr, bufLen ); + pBuf[reducedSize] = '\0'; + return reducedSize; + } +} + +unsigned nciu::nameLen ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + return this->nameLength; +} + +unsigned nciu::requestMessageBytesPending ( + epicsGuard < epicsMutex > & guard ) +{ + return piiu->requestMessageBytesPending ( guard ); +} + +void nciu::flush ( + epicsGuard < epicsMutex > & guard ) +{ + piiu->flush ( guard ); +} + +cacChannel::ioStatus nciu::read ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount countIn, + cacReadNotify ¬ify, ioid *pId ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + + if ( ! this->connected ( guard ) ) { + throw cacChannel::notConnected (); + } + if ( ! this->accessRightState.readPermit () ) { + throw cacChannel::noReadAccess (); + } + if ( countIn > this->count ) { + throw cacChannel::outOfBounds (); + } + + // + // fail out if their arguments are invalid + // + if ( INVALID_DB_REQ ( type ) ) { + throw cacChannel::badType (); + } + + netReadNotifyIO & io = this->cacCtx.readNotifyRequest ( + guard, *this, *this, type, countIn, notify ); + if ( pId ) { + *pId = io.getId (); + } + this->eventq.add ( io ); + return cacChannel::iosAsynch; +} + +void nciu::stringVerify ( const char *pStr, const unsigned count ) +{ + for ( unsigned i = 0; i < count; i++ ) { + unsigned int strsize = 0; + while ( pStr[strsize++] != '\0' ) { + if ( strsize >= MAX_STRING_SIZE ) { + throw badString(); + } + } + pStr += MAX_STRING_SIZE; + } +} + +void nciu::write ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount countIn, const void * pValue ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + + // make sure that they get this and not "no write access" + // if disconnected + if ( ! this->connected ( guard ) ) { + throw cacChannel::notConnected(); + } + if ( ! this->accessRightState.writePermit() ) { + throw cacChannel::noWriteAccess(); + } + if ( countIn > this->count || countIn == 0 ) { + throw cacChannel::outOfBounds(); + } + if ( type == DBR_STRING ) { + nciu::stringVerify ( (char *) pValue, countIn ); + } + this->piiu->writeRequest ( guard, *this, type, countIn, pValue ); +} + +cacChannel::ioStatus nciu::write ( + epicsGuard < epicsMutex > & guard, unsigned type, arrayElementCount countIn, + const void * pValue, cacWriteNotify & notify, ioid * pId ) +{ + // make sure that they get this and not "no write access" + // if disconnected + if ( ! this->connected ( guard ) ) { + throw cacChannel::notConnected(); + } + if ( ! this->accessRightState.writePermit() ) { + throw cacChannel::noWriteAccess(); + } + if ( countIn > this->count || countIn == 0 ) { + throw cacChannel::outOfBounds(); + } + if ( type == DBR_STRING ) { + nciu::stringVerify ( (char *) pValue, countIn ); + } + + netWriteNotifyIO & io = this->cacCtx.writeNotifyRequest ( + guard, *this, *this, type, countIn, pValue, notify ); + if ( pId ) { + *pId = io.getId (); + } + this->eventq.add ( io ); + return cacChannel::iosAsynch; +} + +void nciu::subscribe ( + epicsGuard < epicsMutex > & guard, unsigned type, + arrayElementCount nElem, unsigned mask, + cacStateNotify & notify, ioid *pId ) +{ + netSubscription & io = this->cacCtx.subscriptionRequest ( + guard, *this, *this, type, nElem, mask, notify, + this->channelNode::isInstalledInServer ( guard ) ); + this->eventq.add ( io ); + if ( pId ) { + *pId = io.getId (); + } +} + +void nciu::ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & idIn ) +{ + this->cacCtx.destroyIO ( callbackGuard, + mutualExclusionGuard, idIn, *this ); +} + +void nciu::ioShow ( + epicsGuard < epicsMutex > & guard, + const ioid &idIn, unsigned level ) const +{ + this->cacCtx.ioShow ( guard, idIn, level ); +} + +unsigned nciu::getHostName ( + epicsGuard < epicsMutex > & guard, + char *pBuf, unsigned bufLength ) const throw () +{ + return this->piiu->getHostName ( + guard, pBuf, bufLength ); +} + +const char * nciu::pHostName ( + epicsGuard < epicsMutex > & guard ) const throw () +{ + return this->piiu->pHostName ( guard ); +} + +bool nciu::ca_v42_ok ( + epicsGuard < epicsMutex > & guard ) const +{ + return this->piiu->ca_v42_ok ( guard ); +} + +short nciu::nativeType ( + epicsGuard < epicsMutex > & guard ) const +{ + short type = TYPENOTCONN; + if ( this->connected ( guard ) ) { + if ( this->typeCode < SHRT_MAX ) { + type = static_cast ( this->typeCode ); + } + } + return type; +} + +arrayElementCount nciu::nativeElementCount ( + epicsGuard < epicsMutex > & guard ) const +{ + arrayElementCount countOut = 0ul; + if ( this->connected ( guard ) ) { + countOut = this->count; + } + return countOut; +} + +caAccessRights nciu::accessRights ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + return this->accessRightState; +} + +unsigned nciu::searchAttempts ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + return this->retry; +} + +double nciu::beaconPeriod ( + epicsGuard < epicsMutex > & guard ) const +{ + return this->cacCtx.beaconPeriod ( guard, *this ); +} + +double nciu::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & guard ) const +{ + return this->piiu->receiveWatchdogDelay ( guard ); +} + +bool nciu::connected ( epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + return this->channelNode::isConnected ( guard ); +} + +void nciu::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->cacCtx.mutexRef() ); + this->show ( guard, level ); +} + +void nciu::show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + if ( this->connected ( guard ) ) { + char hostNameTmp [256]; + this->getHostName ( guard, hostNameTmp, sizeof ( hostNameTmp ) ); + ::printf ( "Channel \"%s\", connected to server %s", + this->pNameStr, hostNameTmp ); + if ( level > 1u ) { + int tmpTypeCode = static_cast < int > ( this->typeCode ); + ::printf ( ", native type %s, native element count %u", + dbf_type_to_text ( tmpTypeCode ), this->count ); + ::printf ( ", %sread access, %swrite access", + this->accessRightState.readPermit() ? "" : "no ", + this->accessRightState.writePermit() ? "" : "no "); + } + ::printf ( "\n" ); + } + else { + ::printf ( "Channel \"%s\" is disconnected\n", this->pNameStr ); + } + + if ( level > 2u ) { + ::printf ( "\tnetwork IO pointer = %p\n", + static_cast ( this->piiu ) ); + ::printf ( "\tserver identifier %u\n", this->sid ); + ::printf ( "\tsearch retry number=%u\n", this->retry ); + ::printf ( "\tname length=%u\n", this->nameLength ); + } +} + +void nciu::ioCompletionNotify ( + epicsGuard < epicsMutex > &, class baseNMIU & io ) +{ + this->eventq.remove ( io ); +} + +void nciu::resubscribe ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + tsDLIter < baseNMIU > pNetIO = this->eventq.firstIter (); + while ( pNetIO.valid () ) { + tsDLIter < baseNMIU > next = pNetIO; + next++; + class netSubscription * pSubscr = pNetIO->isSubscription (); + // Its normal for other types of IO to exist after the channel connects, + // but before all of the resubscription requests go out. We must ignore + // them here. + if ( pSubscr ) { + try { + pSubscr->subscribeIfRequired ( guard, *this ); + } + catch ( ... ) { + errlogPrintf ( "CAC: failed to send subscription request " + "during channel connect\n" ); + } + } + pNetIO = next; + } +} + +void nciu::sendSubscriptionUpdateRequests ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + tsDLIter < baseNMIU > pNetIO = this->eventq.firstIter (); + while ( pNetIO.valid () ) { + tsDLIter < baseNMIU > next = pNetIO; + next++; + try { + pNetIO->forceSubscriptionUpdate ( guard, *this ); + } + catch ( ... ) { + errlogPrintf ( + "CAC: failed to send subscription update request " + "during channel connect\n" ); + } + pNetIO = next; + } +} + +void nciu::disconnectAllIO ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + this->cacCtx.disconnectAllIO ( cbGuard, guard, + *this, this->eventq ); +} + +void nciu::serviceShutdownNotify ( + epicsGuard < epicsMutex > & callbackControlGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ) +{ + this->setServerAddressUnknown ( noopIIU, mutualExclusionGuard ); + this->notify().serviceShutdownNotify ( mutualExclusionGuard ); +} + +void channelNode::setRespPendingState ( + epicsGuard < epicsMutex > &, unsigned index ) +{ + this->listMember = + static_cast < channelNode::channelState > + ( channelNode::cs_searchRespPending0 + index ); + if ( this->listMember > cs_searchRespPending17 ) { + throw std::runtime_error ( + "resp search timer index out of bounds" ); + } +} + +void channelNode::setReqPendingState ( + epicsGuard < epicsMutex > &, unsigned index ) +{ + this->listMember = + static_cast < channelNode::channelState > + ( channelNode::cs_searchReqPending0 + index ); + if ( this->listMember > cs_searchReqPending17 ) { + throw std::runtime_error ( + "req search timer index out of bounds" ); + } +} + +unsigned channelNode::getMaxSearchTimerCount () +{ + return epicsMin ( + cs_searchReqPending17 - cs_searchReqPending0, + cs_searchRespPending17 - cs_searchRespPending0 ) + 1u; +} + +unsigned channelNode::getSearchTimerIndex ( + epicsGuard < epicsMutex > & ) +{ + channelNode::channelState chanState = this->listMember; + unsigned index = 0u; + if ( chanState >= cs_searchReqPending0 && + chanState <= cs_searchReqPending17 ) { + index = chanState - cs_searchReqPending0; + } + else if ( chanState >= cs_searchRespPending0 && + chanState <= cs_searchRespPending17 ) { + index = chanState - cs_searchRespPending0; + } + else { + throw std::runtime_error ( + "channel was expected to be in a search timer, but wasnt" );; + } + return index; +} + diff --git a/modules/ca/src/client/nciu.h b/modules/ca/src/client/nciu.h new file mode 100644 index 000000000..7cba6e82b --- /dev/null +++ b/modules/ca/src/client/nciu.h @@ -0,0 +1,385 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef nciuh +#define nciuh + +#ifdef epicsExportSharedSymbols +# define nciuh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "resourceLib.h" +#include "tsDLList.h" +#include "tsFreeList.h" +#include "epicsMutex.h" +#include "compilerDependencies.h" + +#ifdef nciuh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#define CA_MINOR_PROTOCOL_REVISION 13 +#include "caProto.h" + +#include "cacIO.h" + +class cac; +class netiiu; + +// The node and the state which tracks the list membership +// are in the channel, but belong to the circuit. +// Protected by the callback mutex +class channelNode : public tsDLNode < class nciu > +{ +protected: + channelNode (); + bool isInstalledInServer ( epicsGuard < epicsMutex > & ) const; + bool isConnected ( epicsGuard < epicsMutex > & ) const; +public: + static unsigned getMaxSearchTimerCount (); +private: + enum channelState { + cs_none, + cs_disconnGov, + // note: indexing is used here + // so these must be contiguous + cs_searchReqPending0, + cs_searchReqPending1, + cs_searchReqPending2, + cs_searchReqPending3, + cs_searchReqPending4, + cs_searchReqPending5, + cs_searchReqPending6, + cs_searchReqPending7, + cs_searchReqPending8, + cs_searchReqPending9, + cs_searchReqPending10, + cs_searchReqPending11, + cs_searchReqPending12, + cs_searchReqPending13, + cs_searchReqPending14, + cs_searchReqPending15, + cs_searchReqPending16, + cs_searchReqPending17, + // note: indexing is used here + // so these must be contiguous + cs_searchRespPending0, + cs_searchRespPending1, + cs_searchRespPending2, + cs_searchRespPending3, + cs_searchRespPending4, + cs_searchRespPending5, + cs_searchRespPending6, + cs_searchRespPending7, + cs_searchRespPending8, + cs_searchRespPending9, + cs_searchRespPending10, + cs_searchRespPending11, + cs_searchRespPending12, + cs_searchRespPending13, + cs_searchRespPending14, + cs_searchRespPending15, + cs_searchRespPending16, + cs_searchRespPending17, + cs_createReqPend, + cs_createRespPend, + cs_v42ConnCallbackPend, + cs_subscripReqPend, + cs_connected, + cs_unrespCircuit, + cs_subscripUpdateReqPend + } listMember; + void setRespPendingState ( epicsGuard < epicsMutex > &, unsigned index ); + void setReqPendingState ( epicsGuard < epicsMutex > &, unsigned index ); + unsigned getSearchTimerIndex ( epicsGuard < epicsMutex > & ); + friend class tcpiiu; + friend class udpiiu; + friend class tcpSendThread; + friend class searchTimer; + friend class disconnectGovernorTimer; +}; + +class privateInterfaceForIO { +public: + virtual void ioCompletionNotify ( + epicsGuard < epicsMutex > &, class baseNMIU & ) = 0; + virtual arrayElementCount nativeElementCount ( + epicsGuard < epicsMutex > & ) const = 0; + virtual bool connected ( epicsGuard < epicsMutex > & ) const = 0; +protected: + virtual ~privateInterfaceForIO() {} +}; + +class nciu : + public cacChannel, + public chronIntIdRes < nciu >, + public channelNode, + private privateInterfaceForIO { +public: + nciu ( cac &, netiiu &, cacChannelNotify &, + const char * pNameIn, cacChannel::priLev ); + ~nciu (); + void connect ( unsigned nativeType, + unsigned nativeCount, unsigned sid, + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void connect ( epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void unresponsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void circuitHangupNotify ( class udpiiu &, + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void setServerAddressUnknown ( + netiiu & newiiu, epicsGuard < epicsMutex > & guard ); + bool searchMsg ( + epicsGuard < epicsMutex > & ); + void serviceShutdownNotify ( + epicsGuard < epicsMutex > & callbackControlGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void accessRightsStateChange ( const caAccessRights &, + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + ca_uint32_t getSID ( + epicsGuard < epicsMutex > & ) const; + ca_uint32_t getCID ( + epicsGuard < epicsMutex > & ) const; + netiiu * getPIIU ( + epicsGuard < epicsMutex > & ); + const netiiu * getConstPIIU ( + epicsGuard < epicsMutex > & ) const; + cac & getClient (); + void searchReplySetUp ( netiiu &iiu, unsigned sidIn, + ca_uint16_t typeIn, arrayElementCount countIn, + epicsGuard < epicsMutex > & ); + void show ( + unsigned level ) const; + void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const; + unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw (); + const char * pName ( + epicsGuard < epicsMutex > & ) const throw (); + unsigned nameLen ( + epicsGuard < epicsMutex > & ) const; + unsigned getHostName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw (); + void writeException ( + epicsGuard < epicsMutex > &, epicsGuard < epicsMutex > &, + int status, const char *pContext, unsigned type, arrayElementCount count ); + cacChannel::priLev getPriority ( + epicsGuard < epicsMutex > & ) const; + void * operator new ( + size_t size, tsFreeList < class nciu, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator ( + ( void *, tsFreeList < class nciu, 1024, epicsMutexNOOP > & )) + //arrayElementCount nativeElementCount ( epicsGuard < epicsMutex > & ) const; + void resubscribe ( epicsGuard < epicsMutex > & ); + void sendSubscriptionUpdateRequests ( epicsGuard < epicsMutex > & ); + void disconnectAllIO ( + epicsGuard < epicsMutex > &, epicsGuard < epicsMutex > & ); + bool connected ( epicsGuard < epicsMutex > & ) const; + unsigned getcount() const { return count; } + +private: + tsDLList < class baseNMIU > eventq; + caAccessRights accessRightState; + cac & cacCtx; + char * pNameStr; + netiiu * piiu; + ca_uint32_t sid; // server id + unsigned count; + unsigned retry; // search retry number + unsigned short nameLength; // channel name length + ca_uint16_t typeCode; + ca_uint8_t priority; + virtual void destroy ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void initiateConnect ( + epicsGuard < epicsMutex > & ); + unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + ioStatus read ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + cacReadNotify &, ioid * ); + void write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue ); + ioStatus write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + const void *pValue, cacWriteNotify &, ioid * ); + void subscribe ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount nElem, + unsigned mask, cacStateNotify ¬ify, ioid * ); + // The primary mutex must be released when calling the user's + // callback, and therefore a finite interval exists when we are + // moving forward with the intent to call the users callback + // but the users IO could be deleted during this interval. + // To prevent the user's callback from being called after + // destroying his IO we must past a guard for the callback + // mutex here. + virtual void ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const ioid & ); + void ioShow ( + epicsGuard < epicsMutex > &, + const ioid &, unsigned level ) const; + short nativeType ( + epicsGuard < epicsMutex > & ) const; + caAccessRights accessRights ( + epicsGuard < epicsMutex > & ) const; + unsigned searchAttempts ( + epicsGuard < epicsMutex > & ) const; + double beaconPeriod ( + epicsGuard < epicsMutex > & ) const; + double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const; + bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const; + arrayElementCount nativeElementCount ( + epicsGuard < epicsMutex > & ) const; + static void stringVerify ( const char *pStr, const unsigned count ); + void ioCompletionNotify ( + epicsGuard < epicsMutex > &, class baseNMIU & ); + const char * pHostName ( + epicsGuard < epicsMutex > & guard ) const throw (); + nciu ( const nciu & ); + nciu & operator = ( const nciu & ); + void operator delete ( void * ); +}; + +inline void * nciu::operator new ( size_t size, + tsFreeList < class nciu, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void nciu::operator delete ( void * pCadaver, + tsFreeList < class nciu, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver, sizeof ( nciu ) ); +} +#endif + +inline ca_uint32_t nciu::getSID ( + epicsGuard < epicsMutex > & ) const +{ + return this->sid; +} + +inline ca_uint32_t nciu::getCID ( + epicsGuard < epicsMutex > & ) const +{ + return this->id; +} + +// this is to only be used by early protocol revisions +inline void nciu::connect ( epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + this->connect ( this->typeCode, this->count, + this->sid, cbGuard, guard ); +} + +inline void nciu::searchReplySetUp ( netiiu &iiu, unsigned sidIn, + ca_uint16_t typeIn, arrayElementCount countIn, + epicsGuard < epicsMutex > & ) +{ + this->piiu = & iiu; + this->typeCode = typeIn; + this->count = countIn; + this->sid = sidIn; +} + +inline netiiu * nciu::getPIIU ( + epicsGuard < epicsMutex > & ) +{ + return this->piiu; +} + +inline void nciu::writeException ( + epicsGuard < epicsMutex > & /* cbGuard */, + epicsGuard < epicsMutex > & guard, + int status, const char * pContext, + unsigned typeIn, arrayElementCount countIn ) +{ + this->notify().writeException ( guard, + status, pContext, typeIn, countIn ); +} + +inline const netiiu * nciu::getConstPIIU ( + epicsGuard < epicsMutex > & ) const +{ + return this->piiu; +} + +inline cac & nciu::getClient () +{ + return this->cacCtx; +} + +inline cacChannel::priLev nciu::getPriority ( + epicsGuard < epicsMutex > & ) const +{ + return this->priority; +} + +inline channelNode::channelNode () : + listMember ( cs_none ) +{ +} + +inline bool channelNode::isConnected ( epicsGuard < epicsMutex > & ) const +{ + return + this->listMember == cs_connected || + this->listMember == cs_subscripReqPend || + this->listMember == cs_subscripUpdateReqPend; +} + +inline bool channelNode::isInstalledInServer ( epicsGuard < epicsMutex > & ) const +{ + return + this->listMember == cs_connected || + this->listMember == cs_subscripReqPend || + this->listMember == cs_unrespCircuit || + this->listMember == cs_subscripUpdateReqPend; +} + +#endif // ifdef nciuh diff --git a/modules/ca/src/client/netIO.h b/modules/ca/src/client/netIO.h new file mode 100644 index 000000000..e728d2a1a --- /dev/null +++ b/modules/ca/src/client/netIO.h @@ -0,0 +1,306 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef netIOh +#define netIOh + +#include "nciu.h" +#include "compilerDependencies.h" + +// SUN PRO generates multiply defined symbols if the baseNMIU +// destructor is virtual (therefore it is protected). +// I assume that SUNPRO will fix this in future versions. +// With other compilers we get warnings (and +// potential problems) if we dont make the baseNMIU +// destructor virtual. +#if defined ( __SUNPRO_CC ) && ( __SUNPRO_CC <= 0x540 ) +# define NETIO_VIRTUAL_DESTRUCTOR +#else +# define NETIO_VIRTUAL_DESTRUCTOR virtual +#endif + +class privateInterfaceForIO; + +class baseNMIU : public tsDLNode < baseNMIU >, + public chronIntIdRes < baseNMIU > { +public: + virtual void destroy ( + epicsGuard < epicsMutex > &, class cacRecycle & ) = 0; // only called by cac + virtual void completion ( + epicsGuard < epicsMutex > &, cacRecycle & ) = 0; + virtual void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext ) = 0; + virtual void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext, unsigned type, + arrayElementCount count ) = 0; + virtual void completion ( + epicsGuard < epicsMutex > &, cacRecycle &, + unsigned type, arrayElementCount count, + const void * pData ) = 0; + virtual void forceSubscriptionUpdate ( + epicsGuard < epicsMutex > & guard, nciu & chan ) = 0; + virtual class netSubscription * isSubscription () = 0; + virtual void show ( + unsigned level ) const = 0; + virtual void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const = 0; +protected: + NETIO_VIRTUAL_DESTRUCTOR ~baseNMIU (); +}; + +class netSubscription : public baseNMIU { +public: + static netSubscription * factory ( + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > &, + class privateInterfaceForIO &, unsigned type, arrayElementCount count, + unsigned mask, cacStateNotify & ); + void show ( + unsigned level ) const; + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; + arrayElementCount getCount ( + epicsGuard < epicsMutex > &, bool allow_zero ) const; + unsigned getType ( + epicsGuard < epicsMutex > & ) const; + unsigned getMask ( + epicsGuard < epicsMutex > & ) const; + void subscribeIfRequired ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + void unsubscribeIfRequired ( + epicsGuard < epicsMutex > & guard, nciu & chan ); +protected: + netSubscription ( + class privateInterfaceForIO &, unsigned type, + arrayElementCount count, + unsigned mask, cacStateNotify & ); + ~netSubscription (); +private: + const arrayElementCount count; + class privateInterfaceForIO & privateChanForIO; + cacStateNotify & notify; + const unsigned type; + const unsigned mask; + bool subscribed; + class netSubscription * isSubscription (); + void operator delete ( void * ); + void * operator new ( size_t, + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > & )) + void destroy ( + epicsGuard < epicsMutex > &, class cacRecycle & ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle & ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle &, + unsigned type, arrayElementCount count, const void * pData ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext, unsigned type, + arrayElementCount count ); + void forceSubscriptionUpdate ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + netSubscription ( const netSubscription & ); + netSubscription & operator = ( const netSubscription & ); +}; + +class netReadNotifyIO : public baseNMIU { +public: + static netReadNotifyIO * factory ( + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > &, + privateInterfaceForIO &, cacReadNotify & ); + void show ( + unsigned level ) const; + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; +protected: + netReadNotifyIO ( privateInterfaceForIO &, cacReadNotify & ); + ~netReadNotifyIO (); +private: + cacReadNotify & notify; + class privateInterfaceForIO & privateChanForIO; + void operator delete ( void * ); + void * operator new ( size_t, + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > & )) + void destroy ( + epicsGuard < epicsMutex > &, class cacRecycle & ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle & ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle &, + unsigned type, arrayElementCount count, + const void * pData ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext, + unsigned type, arrayElementCount count ); + class netSubscription * isSubscription (); + void forceSubscriptionUpdate ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + netReadNotifyIO ( const netReadNotifyIO & ); + netReadNotifyIO & operator = ( const netReadNotifyIO & ); +}; + +class netWriteNotifyIO : public baseNMIU { +public: + static netWriteNotifyIO * factory ( + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > &, + privateInterfaceForIO &, cacWriteNotify & ); + void show ( + unsigned level ) const; + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; +protected: + netWriteNotifyIO ( privateInterfaceForIO &, cacWriteNotify & ); + ~netWriteNotifyIO (); +private: + cacWriteNotify & notify; + privateInterfaceForIO & privateChanForIO; + void operator delete ( void * ); + void * operator new ( size_t, + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > & )) + class netSubscription * isSubscription (); + void destroy ( + epicsGuard < epicsMutex > &, class cacRecycle & ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle & ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext ); + void completion ( + epicsGuard < epicsMutex > &, cacRecycle &, + unsigned type, arrayElementCount count, + const void * pData ); + void exception ( + epicsGuard < epicsMutex > &, cacRecycle &, + int status, const char * pContext, unsigned type, + arrayElementCount count ); + void forceSubscriptionUpdate ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + netWriteNotifyIO ( const netWriteNotifyIO & ); + netWriteNotifyIO & operator = ( const netWriteNotifyIO & ); +}; + +inline void * netSubscription::operator new ( size_t size, + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > &freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) + inline void netSubscription::operator delete ( void *pCadaver, + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > &freeList ) + { + freeList.release ( pCadaver ); + } +#endif + +inline netSubscription * netSubscription::factory ( + tsFreeList < class netSubscription, 1024, epicsMutexNOOP > & freeList, + class privateInterfaceForIO & chan, unsigned type, arrayElementCount count, + unsigned mask, cacStateNotify ¬ify ) +{ + return new ( freeList ) netSubscription ( chan, type, + count, mask, notify ); +} + +inline arrayElementCount netSubscription::getCount ( + epicsGuard < epicsMutex > & guard, bool allow_zero ) const +{ + //guard.assertIdenticalMutex ( this->mutex ); + arrayElementCount nativeCount = this->privateChanForIO.nativeElementCount ( guard ); + if ( (this->count == 0u && !allow_zero) || this->count > nativeCount ) { + return nativeCount; + } + else { + return this->count; + } +} + +inline unsigned netSubscription::getType ( epicsGuard < epicsMutex > & ) const +{ + return this->type; +} + +inline unsigned netSubscription::getMask ( epicsGuard < epicsMutex > & ) const +{ + return this->mask; +} + +inline netReadNotifyIO * netReadNotifyIO::factory ( + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > & freeList, + privateInterfaceForIO & ioComplNotifIntf, cacReadNotify & notify ) +{ + return new ( freeList ) netReadNotifyIO ( ioComplNotifIntf, notify ); +} + +inline void * netReadNotifyIO::operator new ( size_t size, + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) + inline void netReadNotifyIO::operator delete ( void *pCadaver, + tsFreeList < class netReadNotifyIO, 1024, epicsMutexNOOP > & freeList ) + { + freeList.release ( pCadaver ); + } +#endif + +inline netWriteNotifyIO * netWriteNotifyIO::factory ( + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > & freeList, + privateInterfaceForIO & ioComplNotifyIntf, cacWriteNotify & notify ) +{ + return new ( freeList ) netWriteNotifyIO ( ioComplNotifyIntf, notify ); +} + +inline void * netWriteNotifyIO::operator new ( size_t size, + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) + inline void netWriteNotifyIO::operator delete ( void *pCadaver, + tsFreeList < class netWriteNotifyIO, 1024, epicsMutexNOOP > & freeList ) + { + freeList.release ( pCadaver ); + } +#endif + +#endif // ifdef netIOh diff --git a/modules/ca/src/client/netReadNotifyIO.cpp b/modules/ca/src/client/netReadNotifyIO.cpp new file mode 100644 index 000000000..2e4b8ead6 --- /dev/null +++ b/modules/ca/src/client/netReadNotifyIO.cpp @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include + +#include "errlog.h" + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "nciu.h" +#include "cac.h" + +netReadNotifyIO::netReadNotifyIO ( + privateInterfaceForIO & ioComplIntfIn, + cacReadNotify & notify ) : + notify ( notify ), privateChanForIO ( ioComplIntfIn ) +{ +} + +netReadNotifyIO::~netReadNotifyIO () +{ +} + +void netReadNotifyIO::show ( unsigned /* level */ ) const +{ + ::printf ( "netReadNotifyIO at %p\n", + static_cast < const void * > ( this ) ); +} + +void netReadNotifyIO::show ( + epicsGuard < epicsMutex > &, unsigned level ) const +{ + this->show ( level ); +} + +void netReadNotifyIO::destroy ( + epicsGuard < epicsMutex > & guard, cacRecycle & recycle ) +{ + this->~netReadNotifyIO (); + recycle.recycleReadNotifyIO ( guard, *this ); +} + +void netReadNotifyIO::completion ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, unsigned type, + arrayElementCount count, const void * pData ) +{ + //guard.assertIdenticalMutex ( this->mutex ); + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.completion ( guard, type, count, pData ); + this->~netReadNotifyIO (); + recycle.recycleReadNotifyIO ( guard, *this ); +} + +void netReadNotifyIO::completion ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle ) +{ + //guard.assertIdenticalMutex ( this->mutex ); + //this->chan.getClient().printf ( "Read response w/o data ?\n" ); + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->~netReadNotifyIO (); + recycle.recycleReadNotifyIO ( guard, *this ); +} + +void netReadNotifyIO::exception ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, + int status, const char *pContext ) +{ + //guard.assertIdenticalMutex ( this->mutex ); + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, UINT_MAX, 0u ); + this->~netReadNotifyIO (); + recycle.recycleReadNotifyIO ( guard, *this ); +} + +void netReadNotifyIO::exception ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, + int status, const char *pContext, + unsigned type, arrayElementCount count ) +{ + //guard.assertIdenticalMutex ( this->mutex ) + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, type, count ); + this->~netReadNotifyIO (); + recycle.recycleReadNotifyIO ( guard, *this ); +} + +class netSubscription * netReadNotifyIO::isSubscription () +{ + return 0; +} + +void netReadNotifyIO::forceSubscriptionUpdate ( + epicsGuard < epicsMutex > &, nciu & ) +{ +} + +void netReadNotifyIO::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/ca/src/client/netSubscription.cpp b/modules/ca/src/client/netSubscription.cpp new file mode 100644 index 000000000..fe2426a89 --- /dev/null +++ b/modules/ca/src/client/netSubscription.cpp @@ -0,0 +1,189 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include + +#include "errlog.h" + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "nciu.h" +#include "cac.h" +#include "db_access.h" // for dbf_type_to_text +#include "caerr.h" + +netSubscription::netSubscription ( + privateInterfaceForIO & chanIn, + unsigned typeIn, arrayElementCount countIn, + unsigned maskIn, cacStateNotify & notifyIn ) : + count ( countIn ), privateChanForIO ( chanIn ), + notify ( notifyIn ), type ( typeIn ), mask ( maskIn ), + subscribed ( false ) +{ + if ( ! dbr_type_is_valid ( typeIn ) ) { + throw cacChannel::badType (); + } + if ( this->mask == 0u ) { + throw cacChannel::badEventSelection (); + } +} + +netSubscription::~netSubscription () +{ +} + +void netSubscription::destroy ( + epicsGuard < epicsMutex > & guard, cacRecycle & recycle ) +{ + this->~netSubscription (); + recycle.recycleSubscription ( guard, *this ); +} + +class netSubscription * netSubscription::isSubscription () +{ + return this; +} + +void netSubscription::show ( unsigned /* level */ ) const +{ + ::printf ( "event subscription IO at %p, type %s, element count %lu, mask %u\n", + static_cast < const void * > ( this ), + dbf_type_to_text ( static_cast < int > ( this->type ) ), + this->count, this->mask ); +} + +void netSubscription::show ( + epicsGuard < epicsMutex > &, unsigned level ) const +{ + this->show ( level ); +} + +void netSubscription::completion ( + epicsGuard < epicsMutex > &, cacRecycle & ) +{ + errlogPrintf ( "subscription update w/o data ?\n" ); +} + +void netSubscription::exception ( + epicsGuard < epicsMutex > & guard, cacRecycle & recycle, + int status, const char * pContext ) +{ + if ( status == ECA_DISCONN ) { + this->subscribed = false; + } + if ( status == ECA_CHANDESTROY ) { + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, UINT_MAX, 0 ); + this->~netSubscription (); + recycle.recycleSubscription ( guard, *this ); + } + else { + // guard.assertIdenticalMutex ( this->mutex ); + if ( this->privateChanForIO.connected ( guard ) ) { + this->notify.exception ( + guard, status, pContext, UINT_MAX, 0 ); + } + } +} + +void netSubscription::exception ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, int status, const char * pContext, + unsigned typeIn, arrayElementCount countIn ) +{ + if ( status == ECA_DISCONN ) { + this->subscribed = false; + } + if ( status == ECA_CHANDESTROY ) { + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, UINT_MAX, 0 ); + this->~netSubscription (); + recycle.recycleSubscription ( guard, *this ); + } + else { + //guard.assertIdenticalMutex ( this->mutex ); + if ( this->privateChanForIO.connected ( guard ) ) { + this->notify.exception ( + guard, status, pContext, typeIn, countIn ); + } + } +} + +void netSubscription::completion ( + epicsGuard < epicsMutex > & guard, cacRecycle &, + unsigned typeIn, arrayElementCount countIn, + const void * pDataIn ) +{ + // guard.assertIdenticalMutex ( this->mutex ); + if ( this->privateChanForIO.connected ( guard ) ) { + this->notify.current ( + guard, typeIn, countIn, pDataIn ); + } +} + +void netSubscription::subscribeIfRequired ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + if ( ! this->subscribed ) { + chan.getPIIU(guard)->subscriptionRequest ( + guard, chan, *this ); + this->subscribed = true; + } +} + +void netSubscription::unsubscribeIfRequired ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + if ( this->subscribed ) { + chan.getPIIU(guard)->subscriptionCancelRequest ( + guard, chan, *this ); + this->subscribed = false; + } +} + +void netSubscription::forceSubscriptionUpdate ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + chan.getPIIU(guard)->subscriptionUpdateRequest ( + guard, chan, *this ); +} + +void netSubscription::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/ca/src/client/netWriteNotifyIO.cpp b/modules/ca/src/client/netWriteNotifyIO.cpp new file mode 100644 index 000000000..afa9996d5 --- /dev/null +++ b/modules/ca/src/client/netWriteNotifyIO.cpp @@ -0,0 +1,130 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include + +#include "errlog.h" + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "nciu.h" +#include "cac.h" + +netWriteNotifyIO::netWriteNotifyIO ( + privateInterfaceForIO & ioComplIntf, cacWriteNotify & notifyIn ) : + notify ( notifyIn ), privateChanForIO ( ioComplIntf ) +{ +} + +netWriteNotifyIO::~netWriteNotifyIO () +{ +} + +void netWriteNotifyIO::show ( unsigned /* level */ ) const +{ + ::printf ( "read write notify IO at %p\n", + static_cast < const void * > ( this ) ); +} + +void netWriteNotifyIO::show ( + epicsGuard < epicsMutex > &, + unsigned level ) const +{ + this->show ( level ); +} + +void netWriteNotifyIO::destroy ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle ) +{ + this->~netWriteNotifyIO (); + recycle.recycleWriteNotifyIO ( guard, *this ); +} + +void netWriteNotifyIO::completion ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle ) +{ + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.completion ( guard ); + this->~netWriteNotifyIO (); + recycle.recycleWriteNotifyIO ( guard, *this ); +} + +void netWriteNotifyIO::completion ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, + unsigned /* type */, arrayElementCount /* count */, + const void * /* pData */ ) +{ + //this->chan.getClient().printf ( "Write response with data ?\n" ); + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->~netWriteNotifyIO (); + recycle.recycleWriteNotifyIO ( guard, *this ); +} + +void netWriteNotifyIO::exception ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, + int status, const char * pContext ) +{ + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, UINT_MAX, 0u ); + this->~netWriteNotifyIO (); + recycle.recycleWriteNotifyIO ( guard, *this ); +} + +void netWriteNotifyIO::exception ( + epicsGuard < epicsMutex > & guard, + cacRecycle & recycle, + int status, const char *pContext, + unsigned type, arrayElementCount count ) +{ + this->privateChanForIO.ioCompletionNotify ( guard, *this ); + this->notify.exception ( + guard, status, pContext, type, count ); + this->~netWriteNotifyIO (); + recycle.recycleWriteNotifyIO ( guard, *this ); +} + +class netSubscription * netWriteNotifyIO::isSubscription () +{ + return 0; +} + +void netWriteNotifyIO::forceSubscriptionUpdate ( + epicsGuard < epicsMutex > &, nciu & ) +{ +} + +void netWriteNotifyIO::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/ca/src/client/net_convert.h b/modules/ca/src/client/net_convert.h new file mode 100644 index 000000000..bee51c0c4 --- /dev/null +++ b/modules/ca/src/client/net_convert.h @@ -0,0 +1,36 @@ +/*************************************************************************\ +* 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: J. Hill + * + */ + +#ifndef _NET_CONVERT_H +#define _NET_CONVERT_H + +#include "db_access.h" +#include "shareLib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long arrayElementCount; + +epicsShareFunc int caNetConvert ( + unsigned type, const void *pSrc, void *pDest, + int hton, arrayElementCount count ); + +#ifdef __cplusplus +} +#endif + +#endif /* define _NET_CONVERT_H */ diff --git a/modules/ca/src/client/netiiu.cpp b/modules/ca/src/client/netiiu.cpp new file mode 100644 index 000000000..a81b15533 --- /dev/null +++ b/modules/ca/src/client/netiiu.cpp @@ -0,0 +1,174 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include +#include // vxWorks 6.0 requires this include + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "cac.h" +#include "netiiu.h" + +netiiu::~netiiu () +{ +} + +bool netiiu::ca_v42_ok ( + epicsGuard < epicsMutex > & ) const +{ + return false; +} + +bool netiiu::ca_v41_ok ( + epicsGuard < epicsMutex > & ) const +{ + return false; +} + +void netiiu::writeRequest ( + epicsGuard < epicsMutex > &, nciu &, + unsigned, arrayElementCount, const void * ) +{ + throw cacChannel::notConnected(); +} + +void netiiu::writeNotifyRequest ( + epicsGuard < epicsMutex > &, + nciu &, netWriteNotifyIO &, unsigned, + arrayElementCount, const void * ) +{ + throw cacChannel::notConnected(); +} + +void netiiu::readNotifyRequest ( + epicsGuard < epicsMutex > &, + nciu &, netReadNotifyIO &, unsigned, arrayElementCount ) +{ + throw cacChannel::notConnected(); +} + +void netiiu::clearChannelRequest ( + epicsGuard < epicsMutex > &, ca_uint32_t, ca_uint32_t ) +{ +} + +void netiiu::subscriptionRequest ( + epicsGuard < epicsMutex > &, nciu &, netSubscription & ) +{ +} + +void netiiu::subscriptionCancelRequest ( + epicsGuard < epicsMutex > &, nciu &, netSubscription & ) +{ +} + +void netiiu::subscriptionUpdateRequest ( + epicsGuard < epicsMutex > &, nciu &, netSubscription & ) +{ +} + +static const char * const pHostNameNetIIU = ""; + +unsigned netiiu::getHostName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw () +{ + if ( bufLen ) { + unsigned len = strlen ( pHostNameNetIIU ); + strncpy ( pBuf, pHostNameNetIIU, bufLen ); + if ( len < bufLen ) { + return len; + } + else { + unsigned reducedSize = bufLen - 1u; + pBuf[reducedSize] = '\0'; + return reducedSize; + } + } + return 0u; +} + +const char * netiiu::pHostName ( + epicsGuard < epicsMutex > & ) const throw () +{ + return pHostNameNetIIU; +} + +osiSockAddr netiiu::getNetworkAddress ( + epicsGuard < epicsMutex > & ) const +{ + osiSockAddr addr; + addr.sa.sa_family = AF_UNSPEC; + return addr; +} + +void netiiu::flushRequest ( + epicsGuard < epicsMutex > & ) +{ +} + +unsigned netiiu::requestMessageBytesPending ( + epicsGuard < epicsMutex > & ) +{ + return 0u; +} + +void netiiu::flush ( + epicsGuard < epicsMutex > & ) +{ +} + +void netiiu::requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & ) +{ +} + +void netiiu::uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ) +{ + throw cacChannel::notConnected(); +} + +double netiiu::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const +{ + return - DBL_MAX; +} + +void netiiu::uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, const epicsTime & ) +{ + throw std::runtime_error ( + "search response occured when not attached to udpiiu?" ); +} + +bool netiiu::searchMsg ( + epicsGuard < epicsMutex > &, ca_uint32_t /* id */, + const char * /* pName */, unsigned /* nameLength */ ) +{ + return false; +} + + diff --git a/modules/ca/src/client/netiiu.h b/modules/ca/src/client/netiiu.h new file mode 100644 index 000000000..2caa3d0fa --- /dev/null +++ b/modules/ca/src/client/netiiu.h @@ -0,0 +1,97 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef netiiuh +#define netiiuh + +#include "cacIO.h" +#include "caProto.h" + +class netWriteNotifyIO; +class netReadNotifyIO; +class netSubscription; +union osiSockAddr; +class cac; +class nciu; + +class netiiu { +public: + virtual ~netiiu () = 0; + virtual unsigned getHostName ( + epicsGuard < epicsMutex > &, char * pBuf, + unsigned bufLength ) const throw () = 0; + virtual const char * pHostName ( + epicsGuard < epicsMutex > & ) const throw () = 0; + virtual bool ca_v41_ok ( + epicsGuard < epicsMutex > & ) const = 0; + virtual bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const = 0; + virtual unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual void writeRequest ( + epicsGuard < epicsMutex > &, nciu &, + unsigned type, arrayElementCount nElem, + const void *pValue ) = 0; + virtual void writeNotifyRequest ( + epicsGuard < epicsMutex > &, + nciu &, netWriteNotifyIO &, + unsigned type, arrayElementCount nElem, + const void *pValue ) = 0; + virtual void readNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, + netReadNotifyIO &, unsigned type, + arrayElementCount nElem ) = 0; + virtual void clearChannelRequest ( + epicsGuard < epicsMutex > &, + ca_uint32_t sid, ca_uint32_t cid ) = 0; + virtual void subscriptionRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ) = 0; + virtual void subscriptionUpdateRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ) = 0; + virtual void subscriptionCancelRequest ( + epicsGuard < epicsMutex > &, + nciu & chan, netSubscription & subscr ) = 0; + virtual void flushRequest ( + epicsGuard < epicsMutex > & ) = 0; + virtual void requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & ) = 0; + virtual osiSockAddr getNetworkAddress ( + epicsGuard < epicsMutex > & ) const = 0; + virtual void uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ) = 0; + virtual void uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, + const class epicsTime & currentTime ) = 0; + virtual double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const = 0; + virtual bool searchMsg ( + epicsGuard < epicsMutex > &, ca_uint32_t id, + const char * pName, unsigned nameLength ) = 0; +}; + +#endif // netiiuh diff --git a/modules/ca/src/client/noopiiu.cpp b/modules/ca/src/client/noopiiu.cpp new file mode 100644 index 000000000..a3d20b59b --- /dev/null +++ b/modules/ca/src/client/noopiiu.cpp @@ -0,0 +1,170 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include "osiSock.h" + +#define epicsExportSharedSymbols +#include "noopiiu.h" + +noopiiu noopIIU; + +noopiiu::~noopiiu () +{ +} + +unsigned noopiiu::getHostName ( + epicsGuard < epicsMutex > & cacGuard, + char * pBuf, unsigned bufLength ) const throw () +{ + return netiiu::getHostName ( cacGuard, pBuf, bufLength ); +} + +const char * noopiiu::pHostName ( + epicsGuard < epicsMutex > & cacGuard ) const throw () +{ + return netiiu::pHostName ( cacGuard ); +} + +bool noopiiu::ca_v42_ok ( + epicsGuard < epicsMutex > & cacGuard ) const +{ + return netiiu::ca_v42_ok ( cacGuard ); +} + +bool noopiiu::ca_v41_ok ( + epicsGuard < epicsMutex > & cacGuard ) const +{ + return netiiu::ca_v41_ok ( cacGuard ); +} + +void noopiiu::writeRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, unsigned type, + arrayElementCount nElem, const void * pValue ) +{ + netiiu::writeRequest ( guard, chan, type, nElem, pValue ); +} + +void noopiiu::writeNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netWriteNotifyIO & io, unsigned type, + arrayElementCount nElem, const void *pValue ) +{ + netiiu::writeNotifyRequest ( guard, chan, io, type, nElem, pValue ); +} + +void noopiiu::readNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netReadNotifyIO & io, unsigned type, arrayElementCount nElem ) +{ + netiiu::readNotifyRequest ( guard, chan, io, type, nElem ); +} + +void noopiiu::clearChannelRequest ( + epicsGuard < epicsMutex > & guard, + ca_uint32_t sid, ca_uint32_t cid ) +{ + netiiu::clearChannelRequest ( guard, sid, cid ); +} + +void noopiiu::subscriptionRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netSubscription & subscr ) +{ + netiiu::subscriptionRequest ( guard, chan, subscr ); +} + +void noopiiu::subscriptionUpdateRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netSubscription & subscr ) +{ + netiiu::subscriptionUpdateRequest ( + guard, chan, subscr ); +} + +void noopiiu::subscriptionCancelRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, netSubscription & subscr ) +{ + netiiu::subscriptionCancelRequest ( guard, chan, subscr ); +} + +void noopiiu::flushRequest ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::flushRequest ( guard ); +} + +unsigned noopiiu::requestMessageBytesPending ( + epicsGuard < epicsMutex > & guard ) +{ + return netiiu::requestMessageBytesPending ( guard ); +} + +void noopiiu::flush ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::flush ( guard ); +} + +void noopiiu::requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::requestRecvProcessPostponedFlush ( guard ); +} + +osiSockAddr noopiiu::getNetworkAddress ( + epicsGuard < epicsMutex > & guard ) const +{ + return netiiu::getNetworkAddress ( guard ); +} + +double noopiiu::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & guard ) const +{ + return netiiu::receiveWatchdogDelay ( guard ); +} + +void noopiiu::uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ) +{ + // intentionally does not call default in netiiu +} + +void noopiiu::uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > & guard, nciu & chan, + const class epicsTime & currentTime ) +{ + netiiu::uninstallChanDueToSuccessfulSearchResponse ( + guard, chan, currentTime ); +} + +bool noopiiu::searchMsg ( + epicsGuard < epicsMutex > & guard, ca_uint32_t id, + const char * pName, unsigned nameLength ) +{ + return netiiu::searchMsg ( + guard, id, pName, nameLength ); +} + diff --git a/modules/ca/src/client/noopiiu.h b/modules/ca/src/client/noopiiu.h new file mode 100644 index 000000000..f373edec8 --- /dev/null +++ b/modules/ca/src/client/noopiiu.h @@ -0,0 +1,92 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef noopiiuh +#define noopiiuh + +#include "netiiu.h" + +class noopiiu : public netiiu { +public: + ~noopiiu (); + unsigned getHostName ( + epicsGuard < epicsMutex > &, char * pBuf, + unsigned bufLength ) const throw (); + const char * pHostName ( + epicsGuard < epicsMutex > & ) const throw (); + bool ca_v41_ok ( + epicsGuard < epicsMutex > & ) const; + bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const; + unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void writeRequest ( + epicsGuard < epicsMutex > &, nciu &, + unsigned type, arrayElementCount nElem, + const void *pValue ); + void writeNotifyRequest ( + epicsGuard < epicsMutex > &, + nciu &, netWriteNotifyIO &, + unsigned type, arrayElementCount nElem, + const void *pValue ); + void readNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, + netReadNotifyIO &, unsigned type, + arrayElementCount nElem ); + void clearChannelRequest ( + epicsGuard < epicsMutex > &, + ca_uint32_t sid, ca_uint32_t cid ); + void subscriptionRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ); + void subscriptionUpdateRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ); + void subscriptionCancelRequest ( + epicsGuard < epicsMutex > &, + nciu & chan, netSubscription & subscr ); + void flushRequest ( + epicsGuard < epicsMutex > & ); + void requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & ); + osiSockAddr getNetworkAddress ( + epicsGuard < epicsMutex > & ) const; + void uninstallChan ( + epicsGuard < epicsMutex > & mutex, + nciu & ); + void uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, + const class epicsTime & currentTime ); + double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const; + bool searchMsg ( + epicsGuard < epicsMutex > &, ca_uint32_t id, + const char * pName, unsigned nameLength ); +}; + +extern noopiiu noopIIU; + +#endif // ifndef noopiiuh diff --git a/modules/ca/src/client/oldAccess.h b/modules/ca/src/client/oldAccess.h new file mode 100644 index 000000000..c893eeb68 --- /dev/null +++ b/modules/ca/src/client/oldAccess.h @@ -0,0 +1,612 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef oldAccessh +#define oldAccessh + +#include + +#ifdef epicsExportSharedSymbols +# define oldAccessh_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsFreeList.h" +#include "compilerDependencies.h" +#include "osiSock.h" + +#ifdef oldAccessh_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "caProto.h" +#include "cacIO.h" +#include "cadef.h" +#include "syncGroup.h" + +struct oldChannelNotify : private cacChannelNotify { +public: + oldChannelNotify ( + epicsGuard < epicsMutex > &, struct ca_client_context &, + const char * pName, caCh * pConnCallBackIn, + void * pPrivateIn, capri priority ); + void destructor ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & mutexGuard ); + + // legacy C API + friend unsigned epicsShareAPI ca_get_host_name ( + chid pChan, char * pBuf, unsigned bufLength ); + friend const char * epicsShareAPI ca_host_name ( + chid pChan ); + friend const char * epicsShareAPI ca_name ( + chid pChan ); + friend void epicsShareAPI ca_set_puser ( + chid pChan, void * puser ); + friend void * epicsShareAPI ca_puser ( + chid pChan ); + friend int epicsShareAPI ca_change_connection_event ( + chid pChan, caCh * pfunc ); + friend int epicsShareAPI ca_replace_access_rights_event ( + chid pChan, caArh *pfunc ); + friend int epicsShareAPI ca_array_get ( chtype type, + arrayElementCount count, chid pChan, void * pValue ); + friend int epicsShareAPI ca_array_get_callback ( chtype type, + arrayElementCount count, chid pChan, + caEventCallBackFunc *pfunc, void *arg ); + friend int epicsShareAPI ca_array_put ( + chtype type, arrayElementCount count, + chid pChan, const void * pValue ); + friend int epicsShareAPI ca_array_put_callback ( + chtype type, arrayElementCount count, + chid pChan, const void *pValue, + caEventCallBackFunc *pfunc, void *usrarg ); + friend double epicsShareAPI ca_beacon_period ( + chid pChan ); + friend unsigned epicsShareAPI ca_search_attempts ( + chid pChan ); + friend unsigned epicsShareAPI ca_write_access ( + chid pChan ); + friend unsigned epicsShareAPI ca_read_access ( + chid pChan ); + friend short epicsShareAPI ca_field_type ( + chid pChan ); + friend arrayElementCount epicsShareAPI ca_element_count ( + chid pChan ); + friend int epicsShareAPI ca_v42_ok ( + chid pChan ); + friend int epicsShareAPI ca_create_subscription ( + chtype type, arrayElementCount count, chid pChan, + long mask, caEventCallBackFunc * pCallBack, + void * pCallBackArg, evid * monixptr ); + friend enum channel_state epicsShareAPI ca_state ( + chid pChan ); + friend double epicsShareAPI ca_receive_watchdog_delay ( + chid pChan ); + + unsigned getName ( + epicsGuard < epicsMutex > &, + char * pBuf, unsigned bufLen ) const throw (); + void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const; + void initiateConnect ( + epicsGuard < epicsMutex > & ); + void read ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, + cacReadNotify ¬ify, cacChannel::ioid *pId = 0 ); + void write ( + epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count, const void *pValue, + cacWriteNotify &, cacChannel::ioid *pId = 0 ); + void ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const cacChannel::ioid & ); + void ioShow ( + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid &, unsigned level ) const; + ca_client_context & getClientCtx (); + void eliminateExcessiveSendBacklog ( + epicsGuard < epicsMutex > & ); + + void * operator new ( size_t size, + tsFreeList < struct oldChannelNotify, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void * , + tsFreeList < struct oldChannelNotify, 1024, epicsMutexNOOP > & )) +protected: + ~oldChannelNotify (); +private: + ca_client_context & cacCtx; + cacChannel & io; + caCh * pConnCallBack; + void * pPrivate; + caArh * pAccessRightsFunc; + unsigned ioSeqNo; + bool currentlyConnected; + bool prevConnected; + void connectNotify ( epicsGuard < epicsMutex > & ); + void disconnectNotify ( epicsGuard < epicsMutex > & ); + void serviceShutdownNotify ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void accessRightsNotify ( + epicsGuard < epicsMutex > &, const caAccessRights & ); + void exception ( epicsGuard < epicsMutex > &, + int status, const char * pContext ); + void readException ( epicsGuard < epicsMutex > &, + int status, const char * pContext, + unsigned type, arrayElementCount count, void *pValue ); + void writeException ( epicsGuard < epicsMutex > &, + int status, const char * pContext, + unsigned type, arrayElementCount count ); + oldChannelNotify ( const oldChannelNotify & ); + oldChannelNotify & operator = ( const oldChannelNotify & ); + void operator delete ( void * ); +}; + +class getCopy : public cacReadNotify { +public: + getCopy ( + epicsGuard < epicsMutex > & guard, + ca_client_context & cacCtx, + oldChannelNotify &, unsigned type, + arrayElementCount count, void *pValue ); + ~getCopy (); + void show ( unsigned level ) const; + void cancel (); + void * operator new ( size_t size, + tsFreeList < class getCopy, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class getCopy, 1024, epicsMutexNOOP > & )) +private: + arrayElementCount count; + ca_client_context & cacCtx; + oldChannelNotify & chan; + void * pValue; + unsigned ioSeqNo; + unsigned type; + void completion ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void *pData ); + void exception ( + epicsGuard < epicsMutex > &, int status, + const char *pContext, unsigned type, arrayElementCount count ); + getCopy ( const getCopy & ); + getCopy & operator = ( const getCopy & ); + void operator delete ( void * ); +}; + +class getCallback : public cacReadNotify { +public: + getCallback ( + oldChannelNotify & chanIn, + caEventCallBackFunc *pFunc, void *pPrivate ); + ~getCallback (); + void * operator new ( size_t size, + tsFreeList < class getCallback, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class getCallback, 1024, epicsMutexNOOP > & )) +private: + oldChannelNotify & chan; + caEventCallBackFunc * pFunc; + void * pPrivate; + void completion ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void *pData); + void exception ( + epicsGuard < epicsMutex > &, int status, + const char * pContext, unsigned type, arrayElementCount count ); + getCallback ( const getCallback & ); + getCallback & operator = ( const getCallback & ); + void operator delete ( void * ); +}; + +class putCallback : public cacWriteNotify { +public: + putCallback ( + oldChannelNotify &, + caEventCallBackFunc *pFunc, void *pPrivate ); + ~putCallback (); + void * operator new ( size_t size, + tsFreeList < class putCallback, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class putCallback, 1024, epicsMutexNOOP > & )) +private: + oldChannelNotify & chan; + caEventCallBackFunc * pFunc; + void *pPrivate; + void completion ( epicsGuard < epicsMutex > & ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char *pContext, + unsigned type, arrayElementCount count ); + putCallback ( const putCallback & ); + putCallback & operator = ( const putCallback & ); + void operator delete ( void * ); +}; + +struct oldSubscription : private cacStateNotify { +public: + oldSubscription ( + epicsGuard < epicsMutex > & guard, + oldChannelNotify & chanIn, cacChannel & io, + unsigned type, arrayElementCount nElem, unsigned mask, + caEventCallBackFunc * pFuncIn, void * pPrivateIn, + evid * ); + ~oldSubscription (); + oldChannelNotify & channel () const; + // The primary mutex must be released when calling the user's + // callback, and therefore a finite interval exists when we are + // moving forward with the intent to call the users callback + // but the users IO could be deleted during this interval. + // To prevent the user's callback from being called after + // destroying his IO we must past a guard for the callback + // mutex here. + void cancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void * operator new ( size_t size, + tsFreeList < struct oldSubscription, 1024, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < struct oldSubscription, 1024, epicsMutexNOOP > & )) +private: + oldChannelNotify & chan; + cacChannel::ioid id; + caEventCallBackFunc * pFunc; + void * pPrivate; + void current ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void *pData ); + void exception ( + epicsGuard < epicsMutex > &, int status, + const char *pContext, unsigned type, arrayElementCount count ); + oldSubscription ( const oldSubscription & ); + oldSubscription & operator = ( const oldSubscription & ); + void operator delete ( void * ); +}; + +extern "C" void cacOnceFunc ( void * ); +extern "C" void cacExitHandler ( void *); + +struct ca_client_context : public cacContextNotify +{ +public: + ca_client_context ( bool enablePreemptiveCallback = false ); + virtual ~ca_client_context (); + void changeExceptionEvent ( + caExceptionHandler * pfunc, void * arg ); + void registerForFileDescriptorCallBack ( + CAFDHANDLER * pFunc, void * pArg ); + void replaceErrLogHandler ( caPrintfFunc * ca_printf_func ); + cacChannel & createChannel ( + epicsGuard < epicsMutex > &, const char * pChannelName, + cacChannelNotify &, cacChannel::priLev pri ); + void flush ( epicsGuard < epicsMutex > & ); + void eliminateExcessiveSendBacklog ( + epicsGuard < epicsMutex > &, cacChannel & ); + int pendIO ( const double & timeout ); + int pendEvent ( const double & timeout ); + bool ioComplete () const; + void show ( unsigned level ) const; + unsigned circuitCount () const; + unsigned sequenceNumberOfOutstandingIO ( + epicsGuard < epicsMutex > & ) const; + unsigned beaconAnomaliesSinceProgramStart () const; + void incrementOutstandingIO ( + epicsGuard < epicsMutex > &, unsigned ioSeqNo ); + void decrementOutstandingIO ( + epicsGuard < epicsMutex > &, unsigned ioSeqNo ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + const char * pFileName, unsigned lineNo ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + const char * pFileName, unsigned lineNo, oldChannelNotify & chan, + unsigned type, arrayElementCount count, unsigned op ); + void blockForEventAndEnableCallbacks ( + epicsEvent & event, const double & timeout ); + CASG * lookupCASG ( epicsGuard < epicsMutex > &, unsigned id ); + static void installDefaultService ( cacService & ); + void installCASG ( epicsGuard < epicsMutex > &, CASG & ); + void uninstallCASG ( epicsGuard < epicsMutex > &, CASG & ); + void selfTest () const; +// perhaps these should be eliminated in deference to the exception mechanism + int printFormated ( const char * pformat, ... ) const; + int varArgsPrintFormated ( const char * pformat, va_list args ) const; + void signal ( int ca_status, const char * pfilenm, + int lineno, const char * pFormat, ... ); + void vSignal ( int ca_status, const char * pfilenm, + int lineno, const char *pFormat, va_list args ); + bool preemptiveCallbakIsEnabled () const; + void destroyGetCopy ( epicsGuard < epicsMutex > &, getCopy & ); + void destroyGetCallback ( epicsGuard < epicsMutex > &, getCallback & ); + void destroyPutCallback ( epicsGuard < epicsMutex > &, putCallback & ); + void destroySubscription ( epicsGuard < epicsMutex > &, oldSubscription & ); + epicsMutex & mutexRef () const; + + template < class T > + void whenThereIsAnExceptionDestroySyncGroupIO ( epicsGuard < epicsMutex > &, T & ); + + // legacy C API + friend int epicsShareAPI ca_create_channel ( + const char * name_str, caCh * conn_func, void * puser, + capri priority, chid * chanptr ); + friend int epicsShareAPI ca_clear_channel ( chid pChan ); + friend int epicsShareAPI ca_array_get ( chtype type, + arrayElementCount count, chid pChan, void * pValue ); + friend int epicsShareAPI ca_array_get_callback ( chtype type, + arrayElementCount count, chid pChan, + caEventCallBackFunc *pfunc, void *arg ); + friend int epicsShareAPI ca_array_put ( chtype type, + arrayElementCount count, chid pChan, const void * pValue ); + friend int epicsShareAPI ca_array_put_callback ( chtype type, + arrayElementCount count, chid pChan, const void * pValue, + caEventCallBackFunc *pfunc, void *usrarg ); + friend int epicsShareAPI ca_create_subscription ( + chtype type, arrayElementCount count, chid pChan, + long mask, caEventCallBackFunc * pCallBack, void * pCallBackArg, + evid *monixptr ); + friend int epicsShareAPI ca_flush_io (); + friend int epicsShareAPI ca_clear_subscription ( evid pMon ); + friend int epicsShareAPI ca_sg_create ( CA_SYNC_GID * pgid ); + friend int epicsShareAPI ca_sg_delete ( const CA_SYNC_GID gid ); + friend int epicsShareAPI ca_sg_block ( const CA_SYNC_GID gid, ca_real timeout ); + friend int epicsShareAPI ca_sg_reset ( const CA_SYNC_GID gid ); + friend int epicsShareAPI ca_sg_test ( const CA_SYNC_GID gid ); + friend int epicsShareAPI ca_sg_array_get ( const CA_SYNC_GID gid, + chtype type, arrayElementCount count, + chid pChan, void *pValue ); + friend int epicsShareAPI ca_sg_array_put ( const CA_SYNC_GID gid, + chtype type, arrayElementCount count, + chid pChan, const void *pValue ); + friend int ca_sync_group_destroy ( CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard, + ca_client_context & cac, const CA_SYNC_GID gid ); + friend void sync_group_reset ( ca_client_context & client, + CASG & sg ); + + // exceptions + class noSocket {}; +private: + chronIntIdResTable < CASG > sgTable; + tsFreeList < struct oldChannelNotify, 1024, epicsMutexNOOP > oldChannelNotifyFreeList; + tsFreeList < class getCopy, 1024, epicsMutexNOOP > getCopyFreeList; + tsFreeList < class getCallback, 1024, epicsMutexNOOP > getCallbackFreeList; + tsFreeList < class putCallback, 1024, epicsMutexNOOP > putCallbackFreeList; + tsFreeList < struct oldSubscription, 1024, epicsMutexNOOP > subscriptionFreeList; + tsFreeList < struct CASG, 128, epicsMutexNOOP > casgFreeList; + mutable epicsMutex mutex; + mutable epicsMutex cbMutex; + epicsEvent ioDone; + epicsEvent callbackThreadActivityComplete; + epicsThreadId createdByThread; + std::auto_ptr < CallbackGuard > pCallbackGuard; + std::auto_ptr < cacContext > pServiceContext; + caExceptionHandler * ca_exception_func; + void * ca_exception_arg; + caPrintfFunc * pVPrintfFunc; + CAFDHANDLER * fdRegFunc; + void * fdRegArg; + SOCKET sock; + unsigned pndRecvCnt; + unsigned ioSeqNo; + unsigned callbackThreadsPending; + ca_uint16_t localPort; + bool fdRegFuncNeedsToBeCalled; + bool noWakeupSincePend; + + void attachToClientCtx (); + void callbackProcessingInitiateNotify (); + void callbackProcessingCompleteNotify (); + cacContext & createNetworkContext ( + epicsMutex & mutualExclusion, epicsMutex & callbackControl ); + void _sendWakeupMsg (); + + ca_client_context ( const ca_client_context & ); + ca_client_context & operator = ( const ca_client_context & ); + + friend void cacOnceFunc ( void * ); + friend void cacExitHandler ( void *); + static cacService * pDefaultService; + static epicsMutex * pDefaultServiceInstallMutex; + static const unsigned flushBlockThreshold; +}; + +int fetchClientContext ( ca_client_context * * ppcac ); + +inline ca_client_context & oldChannelNotify::getClientCtx () +{ + return this->cacCtx; +} + +inline unsigned oldChannelNotify::getName ( + epicsGuard < epicsMutex > & guard, + char * pBuf, unsigned bufLen ) const throw () +{ + return this->io.getName ( guard, pBuf, bufLen ); +} + +inline void oldChannelNotify::show ( + epicsGuard < epicsMutex > & guard, + unsigned level ) const +{ + this->io.show ( guard, level ); +} + +inline void oldChannelNotify::initiateConnect ( + epicsGuard < epicsMutex > & guard ) +{ + this->io.initiateConnect ( guard ); +} + +inline void oldChannelNotify::ioCancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard, + const cacChannel::ioid & id ) +{ + this->io.ioCancel ( callbackGuard, mutualExclusionGuard, id ); +} + +inline void oldChannelNotify::ioShow ( + epicsGuard < epicsMutex > & guard, + const cacChannel::ioid & id, unsigned level ) const +{ + this->io.ioShow ( guard, id, level ); +} + +inline void oldChannelNotify::eliminateExcessiveSendBacklog ( + epicsGuard < epicsMutex > & guard ) +{ + this->cacCtx.eliminateExcessiveSendBacklog ( guard, this->io ); +} + +inline void * oldChannelNotify::operator new ( size_t size, + tsFreeList < struct oldChannelNotify, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void oldChannelNotify::operator delete ( void *pCadaver, + tsFreeList < struct oldChannelNotify, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline void * oldSubscription::operator new ( size_t size, + tsFreeList < struct oldSubscription, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void oldSubscription::operator delete ( void *pCadaver, + tsFreeList < struct oldSubscription, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline void oldSubscription::cancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ) +{ + this->chan.ioCancel ( callbackGuard, mutualExclusionGuard, this->id ); +} + +inline oldChannelNotify & oldSubscription::channel () const +{ + return this->chan; +} + +inline void * getCopy::operator new ( size_t size, + tsFreeList < class getCopy, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void getCopy::operator delete ( void *pCadaver, + tsFreeList < class getCopy, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline void * putCallback::operator new ( size_t size, + tsFreeList < class putCallback, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void putCallback::operator delete ( void * pCadaver, + tsFreeList < class putCallback, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline void * getCallback::operator new ( size_t size, + tsFreeList < class getCallback, 1024, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void getCallback::operator delete ( void * pCadaver, + tsFreeList < class getCallback, 1024, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline bool ca_client_context::preemptiveCallbakIsEnabled () const +{ + return this->pCallbackGuard.get () == 0; +} + +inline bool ca_client_context::ioComplete () const +{ + return ( this->pndRecvCnt == 0u ); +} + +inline unsigned ca_client_context::sequenceNumberOfOutstandingIO ( + epicsGuard < epicsMutex > & ) const +{ + // perhaps on SMP systems THERE should be lock/unlock around this + return this->ioSeqNo; +} + +template < class T > +void ca_client_context :: whenThereIsAnExceptionDestroySyncGroupIO ( + epicsGuard < epicsMutex > & guard, T & io ) +{ + if ( this->pCallbackGuard.get() && + this->createdByThread == epicsThreadGetIdSelf () ) { + io.destroy ( *this->pCallbackGuard.get(), guard ); + } + else { + // dont reverse the lock hierarchy + epicsGuardRelease < epicsMutex > guardRelease ( guard ); + { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->mutex ); + io.destroy ( cbGuard, guard ); + } + } +} + +#endif // ifndef oldAccessh diff --git a/modules/ca/src/client/oldChannelNotify.cpp b/modules/ca/src/client/oldChannelNotify.cpp new file mode 100644 index 000000000..701f51fc1 --- /dev/null +++ b/modules/ca/src/client/oldChannelNotify.cpp @@ -0,0 +1,709 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#ifdef _MSC_VER +# pragma warning(disable:4355) +#endif + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" +#include "cac.h" +#include "autoPtrFreeList.h" + +extern "C" void cacNoopAccesRightsHandler ( struct access_rights_handler_args ) +{ +} + +oldChannelNotify::oldChannelNotify ( + epicsGuard < epicsMutex > & guard, ca_client_context & cacIn, + const char *pName, caCh * pConnCallBackIn, + void * pPrivateIn, capri priority ) : + cacCtx ( cacIn ), + io ( cacIn.createChannel ( guard, pName, *this, priority ) ), + pConnCallBack ( pConnCallBackIn ), + pPrivate ( pPrivateIn ), pAccessRightsFunc ( cacNoopAccesRightsHandler ), + ioSeqNo ( 0 ), currentlyConnected ( false ), prevConnected ( false ) +{ + guard.assertIdenticalMutex ( cacIn.mutexRef () ); + this->ioSeqNo = cacIn.sequenceNumberOfOutstandingIO ( guard ); + if ( pConnCallBackIn == 0 ) { + cacIn.incrementOutstandingIO ( guard, this->ioSeqNo ); + } +} + +oldChannelNotify::~oldChannelNotify () +{ +} + +void oldChannelNotify::destructor ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & mutexGuard ) +{ + mutexGuard.assertIdenticalMutex ( this->cacCtx.mutexRef () ); + this->io.destroy ( cbGuard, mutexGuard ); + // no need to worry about a connect preempting here because + // the io (the nciu) has been destroyed above + if ( this->pConnCallBack == 0 && ! this->currentlyConnected ) { + this->cacCtx.decrementOutstandingIO ( mutexGuard, this->ioSeqNo ); + } + this->~oldChannelNotify (); +} + +void oldChannelNotify::connectNotify ( + epicsGuard < epicsMutex > & guard ) +{ + this->currentlyConnected = true; + this->prevConnected = true; + if ( this->pConnCallBack ) { + struct connection_handler_args args; + args.chid = this; + args.op = CA_OP_CONN_UP; + caCh * pFunc = this->pConnCallBack; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFunc ) ( args ); + } + } + else { + this->cacCtx.decrementOutstandingIO ( guard, this->ioSeqNo ); + } +} + +void oldChannelNotify::disconnectNotify ( + epicsGuard < epicsMutex > & guard ) +{ + this->currentlyConnected = false; + if ( this->pConnCallBack ) { + struct connection_handler_args args; + args.chid = this; + args.op = CA_OP_CONN_DOWN; + caCh * pFunc = this->pConnCallBack; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFunc ) ( args ); + } + } + else { + this->cacCtx.incrementOutstandingIO ( + guard, this->ioSeqNo ); + } +} + +void oldChannelNotify::serviceShutdownNotify ( + epicsGuard < epicsMutex > & guard ) +{ + this->disconnectNotify ( guard ); +} + +void oldChannelNotify::accessRightsNotify ( + epicsGuard < epicsMutex > & guard, const caAccessRights & ar ) +{ + struct access_rights_handler_args args; + args.chid = this; + args.ar.read_access = ar.readPermit(); + args.ar.write_access = ar.writePermit(); + caArh * pFunc = this->pAccessRightsFunc; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFunc ) ( args ); + } +} + +void oldChannelNotify::exception ( + epicsGuard < epicsMutex > & guard, int status, const char * pContext ) +{ + this->cacCtx.exception ( guard, status, pContext, __FILE__, __LINE__ ); +} + +void oldChannelNotify::readException ( + epicsGuard < epicsMutex > & guard, int status, const char *pContext, + unsigned type, arrayElementCount count, void * /* pValue */ ) +{ + this->cacCtx.exception ( guard, status, pContext, + __FILE__, __LINE__, *this, type, count, CA_OP_GET ); +} + +void oldChannelNotify::writeException ( + epicsGuard < epicsMutex > & guard, int status, const char *pContext, + unsigned type, arrayElementCount count ) +{ + this->cacCtx.exception ( guard, status, pContext, + __FILE__, __LINE__, *this, type, count, CA_OP_PUT ); +} + +void oldChannelNotify::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__ ); +} + +/* + * ca_get_host_name () + */ +unsigned epicsShareAPI ca_get_host_name ( + chid pChan, char * pBuf, unsigned bufLength ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef() ); + return pChan->io.getHostName ( guard, pBuf, bufLength ); +} + +/* + * ca_host_name () + * + * !!!! not thread safe !!!! + * + */ +const char * epicsShareAPI ca_host_name ( + chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.pHostName ( guard ); +} + +/* + * ca_set_puser () + */ +void epicsShareAPI ca_set_puser ( + chid pChan, void * puser ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + pChan->pPrivate = puser; +} + +/* + * ca_get_puser () + */ +void * epicsShareAPI ca_puser ( + chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->pPrivate; +} + +/* + * Specify an event subroutine to be run for connection events + */ +int epicsShareAPI ca_change_connection_event ( chid pChan, caCh * pfunc ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + if ( ! pChan->currentlyConnected ) { + if ( pfunc ) { + if ( ! pChan->pConnCallBack ) { + pChan->cacCtx.decrementOutstandingIO ( guard, pChan->ioSeqNo ); + } + } + else { + if ( pChan->pConnCallBack ) { + pChan->cacCtx.incrementOutstandingIO ( guard, pChan->ioSeqNo ); + } + } + } + pChan->pConnCallBack = pfunc; + return ECA_NORMAL; +} + +/* + * ca_replace_access_rights_event + */ +int epicsShareAPI ca_replace_access_rights_event ( + chid pChan, caArh *pfunc ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + + // The order of the following is significant to guarantee that the + // access rights handler is always gets called even if the channel connects + // while this is running. There is some very small chance that the + // handler could be called twice here with the same access rights state, but + // that will not upset the application. + pChan->pAccessRightsFunc = pfunc ? pfunc : cacNoopAccesRightsHandler; + caAccessRights tmp = pChan->io.accessRights ( guard ); + + if ( pChan->currentlyConnected ) { + struct access_rights_handler_args args; + args.chid = pChan; + args.ar.read_access = tmp.readPermit (); + args.ar.write_access = tmp.writePermit (); + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pChan->pAccessRightsFunc ) ( args ); + } + return ECA_NORMAL; +} + +/* + * ca_array_get () + */ +int epicsShareAPI ca_array_get ( chtype type, + arrayElementCount count, chid pChan, void *pValue ) +{ + int caStatus; + try { + if ( type < 0 ) { + return ECA_BADTYPE; + } + if ( count == 0 ) + return ECA_BADCOUNT; + + unsigned tmpType = static_cast < unsigned > ( type ); + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + pChan->eliminateExcessiveSendBacklog ( guard ); + autoPtrFreeList < getCopy, 0x400, epicsMutexNOOP > pNotify + ( pChan->getClientCtx().getCopyFreeList, + new ( pChan->getClientCtx().getCopyFreeList ) + getCopy ( guard, pChan->getClientCtx(), *pChan, + tmpType, count, pValue ) ); + pChan->io.read ( guard, type, count, *pNotify, 0 ); + pNotify.release (); + caStatus = ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + caStatus = ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + caStatus = ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + caStatus = ECA_BADCOUNT; + } + catch ( cacChannel::noReadAccess & ) + { + caStatus = ECA_NORDACCESS; + } + catch ( cacChannel::notConnected & ) + { + caStatus = ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + caStatus = ECA_UNAVAILINSERV; + } + catch ( cacChannel::requestTimedOut & ) + { + caStatus = ECA_TIMEOUT; + } + catch ( std::bad_alloc & ) + { + caStatus = ECA_ALLOCMEM; + } + catch ( cacChannel::msgBodyCacheTooSmall & ) { + caStatus = ECA_TOLARGE; + } + catch ( ... ) + { + caStatus = ECA_GETFAIL; + } + return caStatus; +} + +/* + * ca_array_get_callback () + */ +int epicsShareAPI ca_array_get_callback ( chtype type, + arrayElementCount count, chid pChan, + caEventCallBackFunc *pfunc, void *arg ) +{ + int caStatus; + try { + if ( type < 0 ) { + return ECA_BADTYPE; + } + if ( pfunc == NULL ) { + return ECA_BADFUNCPTR; + } + unsigned tmpType = static_cast < unsigned > ( type ); + + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + pChan->eliminateExcessiveSendBacklog ( guard ); + autoPtrFreeList < getCallback, 0x400, epicsMutexNOOP > pNotify + ( pChan->getClientCtx().getCallbackFreeList, + new ( pChan->getClientCtx().getCallbackFreeList ) + getCallback ( *pChan, pfunc, arg ) ); + pChan->io.read ( guard, tmpType, count, *pNotify, 0 ); + pNotify.release (); + caStatus = ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + caStatus = ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + caStatus = ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + caStatus = ECA_BADCOUNT; + } + catch ( cacChannel::noReadAccess & ) + { + caStatus = ECA_NORDACCESS; + } + catch ( cacChannel::notConnected & ) + { + caStatus = ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + caStatus = ECA_UNAVAILINSERV; + } + catch ( cacChannel::requestTimedOut & ) + { + caStatus = ECA_TIMEOUT; + } + catch ( std::bad_alloc & ) + { + caStatus = ECA_ALLOCMEM; + } + catch ( cacChannel::msgBodyCacheTooSmall & ) { + caStatus = ECA_TOLARGE; + } + catch ( ... ) + { + caStatus = ECA_GETFAIL; + } + return caStatus; +} + +void oldChannelNotify::read ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount count, + cacReadNotify & notify, cacChannel::ioid * pId ) +{ + this->io.read ( guard, type, count, notify, pId ); +} + +/* + * ca_array_put_callback () + */ +int epicsShareAPI ca_array_put_callback ( chtype type, arrayElementCount count, + chid pChan, const void *pValue, caEventCallBackFunc *pfunc, void *usrarg ) +{ + int caStatus; + try { + if ( type < 0 ) { + return ECA_BADTYPE; + } + if ( pfunc == NULL ) { + return ECA_BADFUNCPTR; + } + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + pChan->eliminateExcessiveSendBacklog ( guard ); + unsigned tmpType = static_cast < unsigned > ( type ); + autoPtrFreeList < putCallback, 0x400, epicsMutexNOOP > pNotify + ( pChan->getClientCtx().putCallbackFreeList, + new ( pChan->getClientCtx().putCallbackFreeList ) + putCallback ( *pChan, pfunc, usrarg ) ); + pChan->io.write ( guard, tmpType, count, pValue, *pNotify, 0 ); + pNotify.release (); + caStatus = ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + caStatus = ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + caStatus = ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + caStatus = ECA_BADCOUNT; + } + catch ( cacChannel::noWriteAccess & ) + { + caStatus = ECA_NOWTACCESS; + } + catch ( cacChannel::notConnected & ) + { + caStatus = ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + caStatus = ECA_UNAVAILINSERV; + } + catch ( cacChannel::requestTimedOut & ) + { + caStatus = ECA_TIMEOUT; + } + catch ( std::bad_alloc & ) + { + caStatus = ECA_ALLOCMEM; + } + catch ( ... ) + { + caStatus = ECA_PUTFAIL; + } + return caStatus; +} + +/* + * ca_array_put () + */ +int epicsShareAPI ca_array_put ( chtype type, arrayElementCount count, + chid pChan, const void * pValue ) +{ + if ( type < 0 ) { + return ECA_BADTYPE; + } + unsigned tmpType = static_cast < unsigned > ( type ); + + int caStatus; + try { + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + pChan->eliminateExcessiveSendBacklog ( guard ); + pChan->io.write ( guard, tmpType, count, pValue ); + caStatus = ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + caStatus = ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + caStatus = ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + caStatus = ECA_BADCOUNT; + } + catch ( cacChannel::noWriteAccess & ) + { + caStatus = ECA_NOWTACCESS; + } + catch ( cacChannel::notConnected & ) + { + caStatus = ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + caStatus = ECA_UNAVAILINSERV; + } + catch ( cacChannel::requestTimedOut & ) + { + caStatus = ECA_TIMEOUT; + } + catch ( std::bad_alloc & ) + { + caStatus = ECA_ALLOCMEM; + } + catch ( ... ) + { + caStatus = ECA_PUTFAIL; + } + return caStatus; +} + +int epicsShareAPI ca_create_subscription ( + chtype type, arrayElementCount count, chid pChan, + long mask, caEventCallBackFunc * pCallBack, void * pCallBackArg, + evid * monixptr ) +{ + if ( type < 0 ) { + return ECA_BADTYPE; + } + unsigned tmpType = static_cast < unsigned > ( type ); + + if ( INVALID_DB_REQ (type) ) { + return ECA_BADTYPE; + } + + if ( pCallBack == NULL ) { + return ECA_BADFUNCPTR; + } + + static const long maskMask = 0xffff; + if ( ( mask & maskMask ) == 0) { + return ECA_BADMASK; + } + + if ( mask & ~maskMask ) { + return ECA_BADMASK; + } + + try { + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + try { + // if this stalls out on a live circuit then an exception + // can be forthcoming which we must ignore (this is a + // special case preserving legacy ca_create_subscription + // behavior) + pChan->eliminateExcessiveSendBacklog ( guard ); + } + catch ( cacChannel::notConnected & ) { + // intentionally ignored (its ok to subscribe when not connected) + } + new ( pChan->getClientCtx().subscriptionFreeList ) + oldSubscription ( + guard, *pChan, pChan->io, tmpType, count, mask, + pCallBack, pCallBackArg, monixptr ); + // dont touch object created after above new because + // the first callback might have canceled, and therefore + // destroyed, it + return ECA_NORMAL; + } + catch ( cacChannel::badType & ) + { + return ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + return ECA_BADCOUNT; + } + catch ( cacChannel::badEventSelection & ) + { + return ECA_BADMASK; + } + catch ( cacChannel::noReadAccess & ) + { + return ECA_NORDACCESS; + } + catch ( cacChannel::unsupportedByService & ) + { + return ECA_UNAVAILINSERV; + } + catch ( std::bad_alloc & ) + { + return ECA_ALLOCMEM; + } + catch ( cacChannel::msgBodyCacheTooSmall & ) { + return ECA_TOLARGE; + } + catch ( ... ) + { + return ECA_INTERNAL; + } +} + +void oldChannelNotify::write ( + epicsGuard < epicsMutex > & guard, unsigned type, arrayElementCount count, + const void * pValue, cacWriteNotify & notify, cacChannel::ioid * pId ) +{ + this->io.write ( guard, type, count, pValue, notify, pId ); +} + +/* + * ca_field_type() + */ +short epicsShareAPI ca_field_type ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.nativeType ( guard ); +} + +/* + * ca_element_count () + */ +arrayElementCount epicsShareAPI ca_element_count ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.nativeElementCount ( guard ); +} + +/* + * ca_state () + */ +enum channel_state epicsShareAPI ca_state ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + if ( pChan->io.connected ( guard ) ) { + return cs_conn; + } + else if ( pChan->prevConnected ){ + return cs_prev_conn; + } + else { + return cs_never_conn; + } +} + +/* + * ca_read_access () + */ +unsigned epicsShareAPI ca_read_access ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.accessRights(guard).readPermit(); +} + +/* + * ca_write_access () + */ +unsigned epicsShareAPI ca_write_access ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.accessRights(guard).writePermit(); +} + +/* + * ca_name () + */ +const char * epicsShareAPI ca_name ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.pName ( guard ); +} + +unsigned epicsShareAPI ca_search_attempts ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.searchAttempts ( guard ); +} + +double epicsShareAPI ca_beacon_period ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.beaconPeriod ( guard ); +} + +double epicsShareAPI ca_receive_watchdog_delay ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.receiveWatchdogDelay ( guard ); +} + +/* + * ca_v42_ok(chid chan) + */ +int epicsShareAPI ca_v42_ok ( chid pChan ) +{ + epicsGuard < epicsMutex > guard ( pChan->cacCtx.mutexRef () ); + return pChan->io.ca_v42_ok ( guard ); +} + + diff --git a/modules/ca/src/client/oldSubscription.cpp b/modules/ca/src/client/oldSubscription.cpp new file mode 100644 index 000000000..34b58a48d --- /dev/null +++ b/modules/ca/src/client/oldSubscription.cpp @@ -0,0 +1,107 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#include + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" + +oldSubscription::oldSubscription ( + epicsGuard < epicsMutex > & guard, + oldChannelNotify & chanIn, cacChannel & io, + unsigned type, arrayElementCount nElem, unsigned mask, + caEventCallBackFunc * pFuncIn, void * pPrivateIn, + evid * pEventId ) : + chan ( chanIn ), id ( UINT_MAX ), pFunc ( pFuncIn ), + pPrivate ( pPrivateIn ) +{ + // The users event id *must* be set prior to potentially + // calling his callback from within subscribe. + if ( pEventId ) { + *pEventId = this; + } + io.subscribe ( guard, type, nElem, mask, *this, &this->id ); + // Dont touch this pointer after this point because the + // 1st update callback might cancel the subscription and + // thereby destroy this object. +} + +oldSubscription::~oldSubscription () +{ +} + +void oldSubscription::current ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount count, const void * pData ) +{ + struct event_handler_args args; + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = static_cast < long > ( type ); + args.count = static_cast < long > ( count ); + args.status = ECA_NORMAL; + args.dbr = pData; + caEventCallBackFunc * pFuncTmp = this->pFunc; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFuncTmp ) ( args ); + } +} + +void oldSubscription::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char * /* pContext */, + unsigned type, arrayElementCount count ) +{ + if ( status == ECA_CHANDESTROY ) { + ca_client_context & cac = this->chan.getClientCtx (); + cac.destroySubscription ( guard, *this ); + } + else if ( status != ECA_DISCONN ) { + struct event_handler_args args; + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = type; + args.count = count; + args.status = status; + args.dbr = 0; + caEventCallBackFunc * pFuncTmp = this->pFunc; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFuncTmp ) ( args ); + } + } +} + +void oldSubscription::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/ca/src/client/putCallback.cpp b/modules/ca/src/client/putCallback.cpp new file mode 100644 index 000000000..85fcaeb7f --- /dev/null +++ b/modules/ca/src/client/putCallback.cpp @@ -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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#include +#include + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" + +putCallback::putCallback ( + oldChannelNotify & chanIn, caEventCallBackFunc * pFuncIn, + void * pPrivateIn ) : + chan ( chanIn ), pFunc ( pFuncIn ), pPrivate ( pPrivateIn ) +{ +} + +putCallback::~putCallback () +{ +} + +void putCallback::completion ( epicsGuard < epicsMutex > & guard ) +{ + struct event_handler_args args; + + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = TYPENOTCONN; + args.count = 0; + args.status = ECA_NORMAL; + args.dbr = 0; + caEventCallBackFunc * pFuncTmp = this->pFunc; + // fetch client context and destroy prior to releasing + // the lock and calling cb in case they destroy channel there + this->chan.getClientCtx().destroyPutCallback ( guard, *this ); + if ( pFuncTmp ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + pFuncTmp ( args ); + } +} + +void putCallback::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char * /* pContext */, + unsigned type, arrayElementCount count ) +{ + if ( status != ECA_CHANDESTROY ) { + struct event_handler_args args; + args.usr = this->pPrivate; + args.chid = & this->chan; + args.type = type; + args.count = count; + args.status = status; + args.dbr = 0; + caEventCallBackFunc * pFuncTmp = this->pFunc; + // fetch client context and destroy prior to releasing + // the lock and calling cb in case they destroy channel there + this->chan.getClientCtx().destroyPutCallback ( guard, *this ); + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + ( *pFuncTmp ) ( args ); + } + } + else { + this->chan.getClientCtx().destroyPutCallback ( guard, *this ); + } +} + +void putCallback::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/ca/src/client/repeater.cpp b/modules/ca/src/client/repeater.cpp new file mode 100644 index 000000000..61ba33fc5 --- /dev/null +++ b/modules/ca/src/client/repeater.cpp @@ -0,0 +1,587 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * REPEATER.cpp + * + * CA broadcast repeater + * + * Author: Jeff Hill + * Date: 3-27-90 + * + * PURPOSE: + * Broadcasts fan out over the LAN, but old IP kernels do not allow + * two processes on the same machine to get the same broadcast + * (and modern IP kernels do not allow two processes on the same machine + * to receive the same unicast). + * + * This code fans out UDP messages sent to the CA repeater port + * to all CA client processes that have subscribed. + * + */ + +/* + * It would be preferable to avoid using the repeater on multicast enhanced + * IP kernels, but this is not going to work in all situations because + * (according to Steven's TCP/IP illustrated volume I) if a broadcast is + * received it goes to all sockets on the same port, but if a unicast is + * received it goes to only one of the sockets on the same port (we can only + * guess at which one it will be). + * + * I have observed this behavior under winsock II: + * o only one of the sockets on the same port receives the message if we + * send to the loopback address + * o both of the sockets on the same port receives the message if we send + * to the broadcast address + */ + +/* verifyClients() Mechanism + * + * This is required because Solaris and HPUX have half baked versions + * of sockets. + * + * As written, the repeater should be robust against situations where the + * IP kernel doesn't implement UDP disconnect on receiving ICMP port + * unreachable errors from the destination process. As I recall, this + * change was required in the repeater code when we ported from sunos4 to + * Solaris. To avoid unreasonable overhead, I decided at the time to check + * the validity of all existing connections only when a new client + * registers with the repeater (and not when fanning out each beacon + * received). -- Jeff + */ + +#include +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "tsDLList.h" +#include "envDefs.h" +#include "tsFreeList.h" +#include "osiWireFormat.h" +#include "taskwd.h" +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "caProto.h" +#include "udpiiu.h" +#include "repeaterClient.h" + + +/* + * these can be external since there is only one instance + * per machine so we dont care about reentrancy + */ +static tsDLList < repeaterClient > client_list; + +static const unsigned short PORT_ANY = 0u; + +/* + * makeSocket() + */ +static int makeSocket ( unsigned short port, bool reuseAddr, SOCKET * pSock ) +{ + SOCKET sock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, 0 ); + + if ( sock == INVALID_SOCKET ) { + *pSock = sock; + return SOCKERRNO; + } + + /* + * no need to bind if unconstrained + */ + if ( port != PORT_ANY ) { + int status; + union { + struct sockaddr_in ia; + struct sockaddr sa; + } bd; + + memset ( (char *) &bd, 0, sizeof (bd) ); + bd.ia.sin_family = AF_INET; + bd.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + bd.ia.sin_port = htons ( port ); + status = bind ( sock, &bd.sa, (int) sizeof(bd) ); + if ( status < 0 ) { + status = SOCKERRNO; + epicsSocketDestroy ( sock ); + return status; + } + if ( reuseAddr ) { + epicsSocketEnableAddressReuseDuringTimeWaitState ( sock ); + } + } + *pSock = sock; + return 0; +} + +repeaterClient::repeaterClient ( const osiSockAddr &fromIn ) : + from ( fromIn ), sock ( INVALID_SOCKET ) +{ +#ifdef DEBUG + unsigned port = ntohs ( from.ia.sin_port ); + debugPrintf ( ( "new client %u\n", port ) ); +#endif +} + +bool repeaterClient::connect () +{ + int status; + + if ( int sockerrno = makeSocket ( PORT_ANY, false, & this->sock ) ) { + char sockErrBuf[64]; + epicsSocketConvertErrorToString ( + sockErrBuf, sizeof ( sockErrBuf ), sockerrno ); + fprintf ( stderr, "%s: no client sock because \"%s\"\n", + __FILE__, sockErrBuf ); + return false; + } + + status = ::connect ( this->sock, &this->from.sa, sizeof ( this->from.sa ) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + fprintf ( stderr, "%s: unable to connect client sock because \"%s\"\n", + __FILE__, sockErrBuf ); + return false; + } + + return true; +} + +bool repeaterClient::sendConfirm () +{ + int status; + + caHdr confirm; + memset ( (char *) &confirm, '\0', sizeof (confirm) ); + AlignedWireRef < epicsUInt16 > ( confirm.m_cmmd ) = REPEATER_CONFIRM; + confirm.m_available = this->from.ia.sin_addr.s_addr; + status = send ( this->sock, (char *) &confirm, + sizeof (confirm), 0 ); + if ( status >= 0 ) { + assert ( status == sizeof ( confirm ) ); + return true; + } + else if ( SOCKERRNO == SOCK_ECONNREFUSED ) { + return false; + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + debugPrintf ( ( "CA Repeater: confirm req err was \"%s\"\n", sockErrBuf) ); + return false; + } +} + +bool repeaterClient::sendMessage ( const void *pBuf, unsigned bufSize ) +{ + int status; + + status = send ( this->sock, (char *) pBuf, bufSize, 0 ); + if ( status >= 0 ) { + assert ( static_cast ( status ) == bufSize ); +#ifdef DEBUG + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ("Sent to %u\n", port ) ); +#endif + return true; + } + else { + int errnoCpy = SOCKERRNO; + if ( errnoCpy == SOCK_ECONNREFUSED ) { +#ifdef DEBUG + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ("Client refused message %u\n", port ) ); +#endif + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + debugPrintf ( ( "CA Repeater: UDP send err was \"%s\"\n", sockErrBuf) ); + } + return false; + } +} + +repeaterClient::~repeaterClient () +{ + if ( this->sock != INVALID_SOCKET ) { + epicsSocketDestroy ( this->sock ); + } +#ifdef DEBUG + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ( "Deleted client %u\n", port ) ); +#endif +} + +void repeaterClient::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 * repeaterClient::operator new ( size_t size, + tsFreeList < repeaterClient, 0x20 > & freeList ) +{ + return freeList.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +void repeaterClient::operator delete ( void *pCadaver, + tsFreeList < repeaterClient, 0x20 > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline unsigned short repeaterClient::port () const +{ + return ntohs ( this->from.ia.sin_port ); +} + +inline bool repeaterClient::identicalAddress ( const osiSockAddr &fromIn ) +{ + if ( fromIn.sa.sa_family == this->from.sa.sa_family ) { + if ( fromIn.ia.sin_port == this->from.ia.sin_port) { + if ( fromIn.ia.sin_addr.s_addr == this->from.ia.sin_addr.s_addr ) { + return true; + } + } + } + return false; +} + +inline bool repeaterClient::identicalPort ( const osiSockAddr &fromIn ) +{ + if ( fromIn.sa.sa_family == this->from.sa.sa_family ) { + if ( fromIn.ia.sin_port == this->from.ia.sin_port) { + return true; + } + } + return false; +} + +bool repeaterClient::verify () +{ + SOCKET tmpSock; + int sockerrno = makeSocket ( this->port (), false, & tmpSock ); + + if ( sockerrno == SOCK_EADDRINUSE ) { + // Normal result, client using port + return true; + } + + if ( sockerrno == 0 ) { + // Client went away, released port + epicsSocketDestroy ( tmpSock ); + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrorToString ( + sockErrBuf, sizeof ( sockErrBuf ), sockerrno ); + fprintf ( stderr, "CA Repeater: Bind test error \"%s\"\n", + sockErrBuf ); + } + return false; +} + + +/* + * verifyClients() + */ +static void verifyClients ( tsFreeList < repeaterClient, 0x20 > & freeList ) +{ + static tsDLList < repeaterClient > theClients; + repeaterClient *pclient; + + while ( ( pclient = client_list.get () ) ) { + if ( pclient->verify () ) { + theClients.add ( *pclient ); + } + else { + pclient->~repeaterClient (); + freeList.release ( pclient ); + } + } + client_list.add ( theClients ); +} + +/* + * fanOut() + */ +static void fanOut ( const osiSockAddr & from, const void * pMsg, + unsigned msgSize, tsFreeList < repeaterClient, 0x20 > & freeList ) +{ + static tsDLList < repeaterClient > theClients; + repeaterClient *pclient; + + while ( ( pclient = client_list.get () ) ) { + theClients.add ( *pclient ); + /* Dont reflect back to sender */ + if ( pclient->identicalAddress ( from ) ) { + continue; + } + + if ( ! pclient->sendMessage ( pMsg, msgSize ) ) { + if ( ! pclient->verify () ) { + theClients.remove ( *pclient ); + pclient->~repeaterClient (); + freeList.release ( pclient ); + } + } + } + + client_list.add ( theClients ); +} + +/* + * register_new_client() + */ +static void register_new_client ( osiSockAddr & from, + tsFreeList < repeaterClient, 0x20 > & freeList ) +{ + bool newClient = false; + int status; + + if ( from.sa.sa_family != AF_INET ) { + return; + } + + /* + * the repeater and its clients must be on the same host + */ + if ( INADDR_LOOPBACK != ntohl ( from.ia.sin_addr.s_addr ) ) { + static SOCKET testSock = INVALID_SOCKET; + static bool init = false; + + if ( ! init ) { + SOCKET sock; + if ( int sockerrno = makeSocket ( PORT_ANY, true, & sock ) ) { + char sockErrBuf[64]; + epicsSocketConvertErrorToString ( + sockErrBuf, sizeof ( sockErrBuf ), sockerrno ); + fprintf ( stderr, "%s: Unable to create repeater bind test socket because \"%s\"\n", + __FILE__, sockErrBuf ); + } + else { + testSock = sock; + } + init = true; + } + + /* + * Unfortunately on 3.13 beta 11 and before the + * repeater would not always allow the loopback address + * as a local client address so current clients alternate + * between the address of the first non-loopback interface + * found and the loopback addresss when subscribing with + * the CA repeater until all CA repeaters have been updated + * to current code. + */ + if ( testSock != INVALID_SOCKET ) { + osiSockAddr addr; + + addr = from; + addr.ia.sin_port = PORT_ANY; + + /* we can only bind to a local address */ + status = bind ( testSock, &addr.sa, sizeof ( addr ) ); + if ( status ) { + return; + } + } + else { + return; + } + } + + tsDLIter < repeaterClient > pclient = client_list.firstIter (); + while ( pclient.valid () ) { + if ( pclient->identicalPort ( from ) ) { + break; + } + pclient++; + } + + repeaterClient *pNewClient; + if ( pclient.valid () ) { + pNewClient = pclient.pointer (); + } + else { + pNewClient = new ( freeList ) repeaterClient ( from ); + if ( ! pNewClient ) { + fprintf ( stderr, "%s: no memory for new client\n", __FILE__ ); + return; + } + if ( ! pNewClient->connect () ) { + pNewClient->~repeaterClient (); + freeList.release ( pNewClient ); + return; + } + client_list.add ( *pNewClient ); + newClient = true; + } + + if ( ! pNewClient->sendConfirm () ) { + client_list.remove ( *pNewClient ); + pNewClient->~repeaterClient (); + freeList.release ( pNewClient ); +# ifdef DEBUG + epicsUInt16 port = ntohs ( from.ia.sin_port ); + debugPrintf ( ( "Deleted repeater client=%u (error while sending ack)\n", + port ) ); +# endif + } + + /* + * send a noop message to all other clients so that we dont + * accumulate sockets when there are no beacons + */ + caHdr noop; + memset ( (char *) &noop, '\0', sizeof ( noop ) ); + AlignedWireRef < epicsUInt16 > ( noop.m_cmmd ) = CA_PROTO_VERSION; + fanOut ( from, &noop, sizeof ( noop ), freeList ); + + if ( newClient ) { + /* + * For HPUX and Solaris we need to verify that the clients + * have not gone away - because an ICMP error return does not + * get through to send(), which returns no error code. + * + * This is done each time that a new client is created. + * See also the note in the file header. + * + * This is done here in order to avoid deleting a client + * prior to sending its confirm message. + */ + verifyClients ( freeList ); + } +} + + +/* + * ca_repeater () + */ +void ca_repeater () +{ + tsFreeList < repeaterClient, 0x20 > freeList; + int size; + SOCKET sock; + osiSockAddr from; + unsigned short port; + char * pBuf; + + pBuf = new char [MAX_UDP_RECV]; + + { + bool success = osiSockAttach(); + assert ( success ); + } + + port = envGetInetPortConfigParam ( & EPICS_CA_REPEATER_PORT, + static_cast (CA_REPEATER_PORT) ); + if ( int sockerrno = makeSocket ( port, true, & sock ) ) { + /* + * test for server was already started + */ + if ( sockerrno == SOCK_EADDRINUSE ) { + osiSockRelease (); + debugPrintf ( ( "CA Repeater: exiting because a repeater is already running\n" ) ); + delete [] pBuf; + return; + } + char sockErrBuf[64]; + epicsSocketConvertErrorToString ( + sockErrBuf, sizeof ( sockErrBuf ), sockerrno ); + fprintf ( stderr, "%s: Unable to create repeater socket because \"%s\" - fatal\n", + __FILE__, sockErrBuf ); + osiSockRelease (); + delete [] pBuf; + return; + } + + debugPrintf ( ( "CA Repeater: Attached and initialized\n" ) ); + + while ( true ) { + osiSocklen_t from_size = sizeof ( from ); + size = recvfrom ( sock, pBuf, MAX_UDP_RECV, 0, + &from.sa, &from_size ); + if ( size < 0 ) { + int errnoCpy = SOCKERRNO; + // Avoid spurious ECONNREFUSED bug in linux + if ( errnoCpy == SOCK_ECONNREFUSED ) { + continue; + } + // Avoid ECONNRESET from connected socket in windows + if ( errnoCpy == SOCK_ECONNRESET ) { + continue; + } + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + fprintf ( stderr, "CA Repeater: unexpected UDP recv err: %s\n", + sockErrBuf ); + continue; + } + + caHdr * pMsg = ( caHdr * ) pBuf; + + /* + * both zero length message and a registration message + * will register a new client + */ + if ( ( (size_t) size) >= sizeof (*pMsg) ) { + if ( AlignedWireRef < epicsUInt16 > ( pMsg->m_cmmd ) == REPEATER_REGISTER ) { + register_new_client ( from, freeList ); + + /* + * strip register client message + */ + pMsg++; + size -= sizeof ( *pMsg ); + if ( size==0 ) { + continue; + } + } + else if ( AlignedWireRef < epicsUInt16 > ( pMsg->m_cmmd ) == CA_PROTO_RSRV_IS_UP ) { + if ( pMsg->m_available == 0u ) { + pMsg->m_available = from.ia.sin_addr.s_addr; + } + } + } + else if ( size == 0 ) { + register_new_client ( from, freeList ); + continue; + } + + fanOut ( from, pMsg, size, freeList ); + } +} + +/* + * caRepeaterThread () + */ +extern "C" void caRepeaterThread ( void * /* pDummy */ ) +{ + taskwdInsert ( epicsThreadGetIdSelf(), NULL, NULL ); + ca_repeater (); +} + + diff --git a/modules/ca/src/client/repeaterClient.h b/modules/ca/src/client/repeaterClient.h new file mode 100644 index 000000000..faaf0809f --- /dev/null +++ b/modules/ca/src/client/repeaterClient.h @@ -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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef repeaterClienth +#define repeaterClienth + +#ifdef epicsExportSharedSymbols +# define repeaterClienth_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsDLList.h" +#include "tsFreeList.h" +#include "compilerDependencies.h" + +#ifdef repeaterClienth_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +union osiSockAddr; + +/* + * one socket per client so we will get the ECONNREFUSED + * error code (and then delete the client) + */ +class repeaterClient : public tsDLNode < repeaterClient > { +public: + repeaterClient ( const osiSockAddr & from ); + ~repeaterClient (); + bool connect (); + bool sendConfirm (); + bool sendMessage ( const void *pBuf, unsigned bufSize ); + bool verify (); + bool identicalAddress ( const osiSockAddr &from ); + bool identicalPort ( const osiSockAddr &from ); + void * operator new ( size_t size, + tsFreeList < repeaterClient, 0x20 > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < repeaterClient, 0x20 > & )) +private: + osiSockAddr from; + SOCKET sock; + unsigned short port () const; + void operator delete ( void * ); +}; + +#endif // repeaterClienth + + diff --git a/modules/ca/src/client/repeaterSubscribeTimer.cpp b/modules/ca/src/client/repeaterSubscribeTimer.cpp new file mode 100644 index 000000000..948ccebd5 --- /dev/null +++ b/modules/ca/src/client/repeaterSubscribeTimer.cpp @@ -0,0 +1,109 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + * + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "cac.h" +#include "iocinf.h" +#include "repeaterSubscribeTimer.h" + +#define epicsExportSharedSymbols +#include "udpiiu.h" +#undef epicsExportSharedSymbols + +static const double repeaterSubscribeTimerInitialPeriod = 10.0; // sec +static const double repeaterSubscribeTimerPeriod = 1.0; // sec + +repeaterSubscribeTimer::repeaterSubscribeTimer ( + repeaterTimerNotify & iiuIn, epicsTimerQueue & queueIn, + epicsMutex & cbMutexIn, cacContextNotify & ctxNotifyIn ) : + timer ( queueIn.createTimer () ), iiu ( iiuIn ), + cbMutex ( cbMutexIn ),ctxNotify ( ctxNotifyIn ), + attempts ( 0 ), registered ( false ), once ( false ) +{ +} + +repeaterSubscribeTimer::~repeaterSubscribeTimer () +{ + this->timer.destroy (); +} + +void repeaterSubscribeTimer::start () +{ + this->timer.start ( + *this, repeaterSubscribeTimerInitialPeriod ); +} + +void repeaterSubscribeTimer::shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbUnguard ( cbGuard ); + this->timer.cancel (); + } +} + +epicsTimerNotify::expireStatus repeaterSubscribeTimer:: + expire ( const epicsTime & /* currentTime */ ) +{ + epicsGuard < epicsMutex > guard ( this->stateMutex ); + + static const unsigned nTriesToMsg = 50; + if ( this->attempts > nTriesToMsg && ! this->once ) { + callbackManager mgr ( this->ctxNotify, this->cbMutex ); + this->iiu.printFormated ( mgr.cbGuard, + "CA client library is unable to contact CA repeater after %u tries.\n", + nTriesToMsg ); + this->iiu.printFormated ( mgr.cbGuard, + "Silence this message by starting a CA repeater daemon\n") ; + this->iiu.printFormated ( mgr.cbGuard, + "or by calling ca_pend_event() and or ca_poll() more often.\n" ); + this->once = true; + } + + this->iiu.repeaterRegistrationMessage ( this->attempts ); + this->attempts++; + + if ( this->registered ) { + return noRestart; + } + else { + return expireStatus ( restart, repeaterSubscribeTimerPeriod ); + } +} + +void repeaterSubscribeTimer::show ( unsigned /* level */ ) const +{ + epicsGuard < epicsMutex > guard ( this->stateMutex ); + + ::printf ( "repeater subscribe timer: attempts=%u registered=%u once=%u\n", + this->attempts, this->registered, this->once ); +} + +void repeaterSubscribeTimer::confirmNotify () +{ + epicsGuard < epicsMutex > guard ( this->stateMutex ); + this->registered = true; +} + +repeaterTimerNotify::~repeaterTimerNotify () {} diff --git a/modules/ca/src/client/repeaterSubscribeTimer.h b/modules/ca/src/client/repeaterSubscribeTimer.h new file mode 100644 index 000000000..fa4768499 --- /dev/null +++ b/modules/ca/src/client/repeaterSubscribeTimer.h @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef repeaterSubscribeTimerh +#define repeaterSubscribeTimerh + +#include "epicsTimer.h" + +#ifdef epicsExportSharedSymbols +# define repeaterSubscribeTimerh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsTimer.h" + +#ifdef repeaterSubscribeTimerh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +class epicsMutex; +class cacContextNotify; + +class repeaterTimerNotify { +public: + virtual ~repeaterTimerNotify () = 0; + virtual void repeaterRegistrationMessage ( + unsigned attemptNumber ) = 0; + virtual int printFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char * pformat, ... ) = 0; +}; + +class repeaterSubscribeTimer : private epicsTimerNotify { +public: + repeaterSubscribeTimer ( + repeaterTimerNotify &, epicsTimerQueue &, + epicsMutex & cbMutex, cacContextNotify & ctxNotify ); + virtual ~repeaterSubscribeTimer (); + void start (); + void shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void confirmNotify (); + void show ( unsigned level ) const; +private: + epicsTimer & timer; + repeaterTimerNotify & iiu; + epicsMutex & cbMutex; + cacContextNotify & ctxNotify; + mutable epicsMutex stateMutex; + unsigned attempts; + bool registered; + bool once; + expireStatus expire ( const epicsTime & currentTime ); + repeaterSubscribeTimer ( const repeaterSubscribeTimer & ); + repeaterSubscribeTimer & operator = ( const repeaterSubscribeTimer & ); +}; + +#endif // ifdef repeaterSubscribeTimerh diff --git a/modules/ca/src/client/searchTimer.cpp b/modules/ca/src/client/searchTimer.cpp new file mode 100644 index 000000000..e55e49eab --- /dev/null +++ b/modules/ca/src/client/searchTimer.cpp @@ -0,0 +1,399 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +// +// +// L O S A L A M O S +// Los Alamos National Laboratory +// Los Alamos, New Mexico 87545 +// +// Copyright, 1986, The Regents of the University of California. +// +// Author: Jeff Hill +// + +#include +#include // vxWorks 6.0 requires this include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "envDefs.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "udpiiu.h" +#include "nciu.h" + +static const unsigned initialTriesPerFrame = 1u; // initial UDP frames per search try +static const unsigned maxTriesPerFrame = 64u; // max UDP frames per search try + +// +// searchTimer::searchTimer () +// +searchTimer::searchTimer ( + searchTimerNotify & iiuIn, + epicsTimerQueue & queueIn, + const unsigned indexIn, + epicsMutex & mutexIn, + bool boostPossibleIn ) : + timeAtLastSend ( epicsTime::getCurrent () ), + timer ( queueIn.createTimer () ), + iiu ( iiuIn ), + mutex ( mutexIn ), + framesPerTry ( initialTriesPerFrame ), + framesPerTryCongestThresh ( DBL_MAX ), + retry ( 0 ), + searchAttempts ( 0u ), + searchResponses ( 0u ), + index ( indexIn ), + dgSeqNoAtTimerExpireBegin ( 0u ), + dgSeqNoAtTimerExpireEnd ( 0u ), + boostPossible ( boostPossibleIn ), + stopped ( false ) +{ +} + +void searchTimer::start ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->timer.start ( *this, this->period ( guard ) ); +} + +searchTimer::~searchTimer () +{ + assert ( this->chanListReqPending.count() == 0 ); + assert ( this->chanListRespPending.count() == 0 ); + this->timer.destroy (); +} + +void searchTimer::shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + this->stopped = true; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbUnguard ( cbGuard ); + this->timer.cancel (); + } + } + + while ( nciu * pChan = this->chanListReqPending.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + while ( nciu * pChan = this->chanListRespPending.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->serviceShutdownNotify ( cbGuard, guard ); + } +} + +void searchTimer::installChannel ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + this->chanListReqPending.add ( chan ); + chan.channelNode::setReqPendingState ( guard, this->index ); +} + +void searchTimer::moveChannels ( + epicsGuard < epicsMutex > & guard, searchTimer & dest ) +{ + while ( nciu * pChan = this->chanListRespPending.get () ) { + if ( this->searchAttempts > 0 ) { + this->searchAttempts--; + } + dest.installChannel ( guard, *pChan ); + } + while ( nciu * pChan = this->chanListReqPending.get () ) { + dest.installChannel ( guard, *pChan ); + } +} + +// +// searchTimer::expire () +// +epicsTimerNotify::expireStatus searchTimer::expire ( + const epicsTime & currentTime ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + + while ( nciu * pChan = this->chanListRespPending.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + this->iiu.noSearchRespNotify ( + guard, *pChan, this->index ); + } + + this->timeAtLastSend = currentTime; + + // boost search period for channels not recently + // searched for if there was some success + if ( this->searchResponses && this->boostPossible ) { + while ( nciu * pChan = this->chanListReqPending.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + this->iiu.boostChannel ( guard, *pChan ); + } + } + + if ( this->searchAttempts ) { +#if 0 + // + // dynamically adjust the number of UDP frames per + // try depending how many search requests are not + // replied to + // + // The variable this->framesPerTry + // determines the number of UDP frames to be sent + // each time that expire() is called. + // If this value is too high we will waste some + // network bandwidth. If it is too low we will + // use very little of the incoming UDP message + // buffer associated with the server's port and + // will therefore take longer to connect. We + // initialize this->framesPerTry to a prime number + // so that it is less likely that the + // same channel is in the last UDP frame + // sent every time that this is called (and + // potentially discarded by a CA server with + // a small UDP input queue). + // + // increase frames per try only if we see better than + // a 93.75% success rate for one pass through the list + // + if ( this->searchResponses > + ( this->searchAttempts - (this->searchAttempts/16u) ) ) { + // increase UDP frames per try if we have a good score + if ( this->framesPerTry < maxTriesPerFrame ) { + // a congestion avoidance threshold similar to TCP is now used + if ( this->framesPerTry < this->framesPerTryCongestThresh ) { + this->framesPerTry += this->framesPerTry; + } + else { + this->framesPerTry += (this->framesPerTry/8) + 1; + } + debugPrintf ( ("Increasing frame count to %u t=%u r=%u\n", + this->framesPerTry, this->searchAttempts, this->searchResponses) ); + } + } + // if we detect congestion because we have less than a 87.5% success + // rate then gradually reduce the frames per try + else if ( this->searchResponses < + ( this->searchAttempts - (this->searchAttempts/8u) ) ) { + if ( this->framesPerTry > 1 ) { + this->framesPerTry--; + } + this->framesPerTryCongestThresh = this->framesPerTry/2 + 1; + debugPrintf ( ("Congestion detected - set frames per try to %f t=%u r=%u\n", + this->framesPerTry, this->searchAttempts, this->searchResponses) ); + } +#else + if ( this->searchResponses == this->searchAttempts ) { + // increase UDP frames per try if we have a good score + if ( this->framesPerTry < maxTriesPerFrame ) { + // a congestion avoidance threshold similar to TCP is now used + if ( this->framesPerTry < this->framesPerTryCongestThresh ) { + double doubled = 2 * this->framesPerTry; + if ( doubled > this->framesPerTryCongestThresh ) { + this->framesPerTry = this->framesPerTryCongestThresh; + } + else { + this->framesPerTry = doubled; + } + } + else { + this->framesPerTry += 1.0 / this->framesPerTry; + } + debugPrintf ( ("Increasing frame count to %g t=%u r=%u\n", + this->framesPerTry, this->searchAttempts, this->searchResponses) ); + } + } + else { + this->framesPerTryCongestThresh = this->framesPerTry / 2.0; + this->framesPerTry = 1u; + debugPrintf ( ("Congestion detected - set frames per try to %g t=%u r=%u\n", + this->framesPerTry, this->searchAttempts, this->searchResponses) ); + } +#endif + } + + this->dgSeqNoAtTimerExpireBegin = + this->iiu.datagramSeqNumber ( guard ); + + this->searchAttempts = 0; + this->searchResponses = 0; + + unsigned nFrameSent = 0u; + while ( true ) { + nciu * pChan = this->chanListReqPending.get (); + if ( ! pChan ) { + break; + } + + pChan->channelNode::listMember = + channelNode::cs_none; + + bool success = pChan->searchMsg ( guard ); + if ( ! success ) { + if ( this->iiu.datagramFlush ( guard, currentTime ) ) { + nFrameSent++; + if ( nFrameSent < this->framesPerTry ) { + success = pChan->searchMsg ( guard ); + } + } + if ( ! success ) { + this->chanListReqPending.push ( *pChan ); + pChan->channelNode::setReqPendingState ( + guard, this->index ); + break; + } + } + + this->chanListRespPending.add ( *pChan ); + pChan->channelNode::setRespPendingState ( + guard, this->index ); + + if ( this->searchAttempts < UINT_MAX ) { + this->searchAttempts++; + } + } + + // flush out the search request buffer + if ( this->iiu.datagramFlush ( guard, currentTime ) ) { + nFrameSent++; + } + + this->dgSeqNoAtTimerExpireEnd = + this->iiu.datagramSeqNumber ( guard ) - 1u; + +# ifdef DEBUG + if ( this->searchAttempts ) { + char buf[64]; + currentTime.strftime ( buf, sizeof(buf), "%M:%S.%09f"); + debugPrintf ( ("sent %u delay sec=%f Rts=%s\n", + nFrameSent, this->period(), buf ) ); + } +# endif + + return expireStatus ( restart, this->period ( guard ) ); +} + +void searchTimer :: show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + ::printf ( "searchTimer with period %f\n", this->period ( guard ) ); + if ( level > 0 ) { + ::printf ( "channels with search request pending = %u\n", + this->chanListReqPending.count () ); + if ( level > 1u ) { + tsDLIterConst < nciu > pChan = + this->chanListReqPending.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + ::printf ( "channels with search response pending = %u\n", + this->chanListRespPending.count () ); + if ( level > 1u ) { + tsDLIterConst < nciu > pChan = + this->chanListRespPending.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + } +} + +// +// Reset the delay to the next search request if we get +// at least one response. However, dont reset this delay if we +// get a delayed response to an old search request. +// +void searchTimer::uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > & guard, nciu & chan, + ca_uint32_t respDatagramSeqNo, bool seqNumberIsValid, + const epicsTime & currentTime ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->uninstallChan ( guard, chan ); + + if ( this->stopped ) { + return; + } + + bool validResponse = true; + if ( seqNumberIsValid ) { + validResponse = + this->dgSeqNoAtTimerExpireBegin <= respDatagramSeqNo && + this->dgSeqNoAtTimerExpireEnd >= respDatagramSeqNo; + } + + // if we receive a successful response then reset to a + // reasonable timer period + if ( validResponse ) { + double measured = currentTime - this->timeAtLastSend; + this->iiu.updateRTTE ( guard, measured ); + + if ( this->searchResponses < UINT_MAX ) { + this->searchResponses++; + if ( this->searchResponses == this->searchAttempts ) { + if ( this->chanListReqPending.count () ) { + // + // when we get 100% success immediately + // send another search request + // + debugPrintf ( ( "All requests succesful, set timer delay to zero\n" ) ); + this->timer.start ( *this, currentTime ); + } + } + } + } +} + +void searchTimer::uninstallChan ( + epicsGuard < epicsMutex > & cacGuard, nciu & chan ) +{ + cacGuard.assertIdenticalMutex ( this->mutex ); + unsigned ulistmem = + static_cast ( chan.channelNode::listMember ); + unsigned uReqBase = + static_cast ( channelNode::cs_searchReqPending0 ); + if ( ulistmem == this->index + uReqBase ) { + this->chanListReqPending.remove ( chan ); + } + else { + unsigned uRespBase = + static_cast ( + channelNode::cs_searchRespPending0 ); + if ( ulistmem == this->index + uRespBase ) { + this->chanListRespPending.remove ( chan ); + } + else { + throw std::runtime_error ( + "uninstalling channel search timer, but channel " + "state is wrong" ); + } + } + chan.channelNode::listMember = channelNode::cs_none; +} + +double searchTimer::period ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return (1 << this->index ) * this->iiu.getRTTE ( guard ); +} + +searchTimerNotify::~searchTimerNotify () {} diff --git a/modules/ca/src/client/searchTimer.h b/modules/ca/src/client/searchTimer.h new file mode 100644 index 000000000..7b9fe1717 --- /dev/null +++ b/modules/ca/src/client/searchTimer.h @@ -0,0 +1,108 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +// +// +// +// L O S A L A M O S +// Los Alamos National Laboratory +// Los Alamos, New Mexico 87545 +// +// Copyright, 1986, The Regents of the University of California. +// +// +// Author Jeffrey O. Hill +// johill@lanl.gov +// 505 665 1831 +// + +#ifndef searchTimerh +#define searchTimerh + +#ifdef epicsExportSharedSymbols +# define searchTimerh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsMutex.h" +#include "epicsGuard.h" +#include "epicsTimer.h" + +#ifdef searchTimerh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "caProto.h" +#include "netiiu.h" + +class searchTimerNotify { +public: + virtual ~searchTimerNotify () = 0; + virtual void boostChannel ( + epicsGuard < epicsMutex > &, nciu & ) = 0; + virtual void noSearchRespNotify ( + epicsGuard < epicsMutex > &, nciu &, unsigned ) = 0; + virtual double getRTTE ( epicsGuard < epicsMutex > & ) const = 0; + virtual void updateRTTE ( epicsGuard < epicsMutex > &, double rtte ) = 0; + virtual bool datagramFlush ( + epicsGuard < epicsMutex > &, + const epicsTime & currentTime ) = 0; + virtual ca_uint32_t datagramSeqNumber ( + epicsGuard < epicsMutex > & ) const = 0; +}; + +class searchTimer : private epicsTimerNotify { +public: + searchTimer ( + class searchTimerNotify &, epicsTimerQueue &, + const unsigned index, epicsMutex &, + bool boostPossible ); + virtual ~searchTimer (); + void start ( epicsGuard < epicsMutex > & ); + void shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void moveChannels ( + epicsGuard < epicsMutex > &, searchTimer & dest ); + void installChannel ( + epicsGuard < epicsMutex > &, nciu & ); + void uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ); + void uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, + ca_uint32_t respDatagramSeqNo, bool seqNumberIsValid, + const epicsTime & currentTime ); + void show ( unsigned level ) const; +private: + tsDLList < nciu > chanListReqPending; + tsDLList < nciu > chanListRespPending; + epicsTime timeAtLastSend; + epicsTimer & timer; + searchTimerNotify & iiu; + epicsMutex & mutex; + double framesPerTry; /* # of UDP frames per search try */ + double framesPerTryCongestThresh; /* one half N tries w congest */ + unsigned retry; + unsigned searchAttempts; /* num search tries after last timer experation */ + unsigned searchResponses; /* num search resp after last timer experation */ + const unsigned index; + ca_uint32_t dgSeqNoAtTimerExpireBegin; + ca_uint32_t dgSeqNoAtTimerExpireEnd; + const bool boostPossible; + bool stopped; + + expireStatus expire ( const epicsTime & currentTime ); + double period ( epicsGuard < epicsMutex > & ) const; + searchTimer ( const searchTimer & ); // not implemented + searchTimer & operator = ( const searchTimer & ); // not implemented +}; + +#endif // ifdef searchTimerh diff --git a/modules/ca/src/client/sgAutoPtr.h b/modules/ca/src/client/sgAutoPtr.h new file mode 100644 index 000000000..e2899468c --- /dev/null +++ b/modules/ca/src/client/sgAutoPtr.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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef sgAutoPtrh +#define sgAutoPtrh + +template < class T > +class sgAutoPtr { +public: + sgAutoPtr ( epicsGuard < epicsMutex > &, struct CASG & ); + ~sgAutoPtr (); + sgAutoPtr < T > & operator = ( T * ); + T * operator -> (); + T & operator * (); + T * get (); + T * release (); +private: + T * pNotify; + struct CASG & sg; + epicsGuard < epicsMutex > & guard; + sgAutoPtr & operator = ( const sgAutoPtr & ); +}; + +template < class T > +inline sgAutoPtr < T > :: sgAutoPtr ( + epicsGuard < epicsMutex > & guardIn, struct CASG & sgIn ) : + pNotify ( 0 ), sg ( sgIn ), guard ( guardIn ) +{ +} + +template < class T > +inline sgAutoPtr < T > :: ~sgAutoPtr () +{ + if ( this->pNotify ) { + this->sg.ioPendingList.remove ( *this->pNotify ); + this->sg.client. + whenThereIsAnExceptionDestroySyncGroupIO ( this->guard, *this->pNotify ); + } +} + +template < class T > +inline sgAutoPtr < T > & sgAutoPtr < T > :: operator = ( T * pNotifyIn ) +{ + if ( this->pNotify ) { + this->sg.ioPendingList.remove ( *this->pNotify ); + this->sg.client. + whenThereIsAnExceptionDestroySyncGroupIO ( this->guard, *this->pNotify ); + } + this->pNotify = pNotifyIn; + this->sg.ioPendingList.add ( *this->pNotify ); + return *this; +} + +template < class T > +inline T * sgAutoPtr < T > :: operator -> () +{ + return this->pNotify; +} + +template < class T > +inline T & sgAutoPtr < T > :: operator * () +{ + assert ( this->pNotify ); + return * this->pNotify; +} + +template < class T > +inline T * sgAutoPtr < T > :: release () +{ + T * pTmp = this->pNotify; + this->pNotify = 0; + return pTmp; +} + +template < class T > +inline T * sgAutoPtr < T > :: get () +{ + return this->pNotify; +} + +#endif // sgAutoPtrh diff --git a/modules/ca/src/client/syncGroup.h b/modules/ca/src/client/syncGroup.h new file mode 100644 index 000000000..3b9c3cd4f --- /dev/null +++ b/modules/ca/src/client/syncGroup.h @@ -0,0 +1,280 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef syncGrouph +#define syncGrouph + +#ifdef epicsExportSharedSymbols +# define syncGrouph_restore_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "tsDLList.h" +#include "tsFreeList.h" +#include "resourceLib.h" +#include "epicsEvent.h" +#include "compilerDependencies.h" + +#ifdef syncGrouph_restore_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "cadef.h" +#include "cacIO.h" + +static const unsigned CASG_MAGIC = 0xFAB4CAFE; + +class syncGroupNotify : public tsDLNode < syncGroupNotify > { +public: + syncGroupNotify (); + virtual void destroy ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ) = 0; + virtual bool ioPending ( + epicsGuard < epicsMutex > & guard ) = 0; + virtual void cancel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & mutualExclusionGuard ) = 0; + virtual void show ( + epicsGuard < epicsMutex > &, + unsigned level ) const = 0; +protected: + virtual ~syncGroupNotify (); + syncGroupNotify ( const syncGroupNotify & ); + syncGroupNotify & operator = ( const syncGroupNotify & ); +}; + +struct CASG; + +class syncGroupReadNotify : public syncGroupNotify, public cacReadNotify { +public: + typedef void ( CASG :: * PRecycleFunc ) + ( epicsGuard < epicsMutex > &, syncGroupReadNotify & ); + static syncGroupReadNotify * factory ( + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > &, + CASG &, PRecycleFunc, chid, void *pValueIn ); + void destroy ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + bool ioPending ( + epicsGuard < epicsMutex > & guard ); + void begin ( epicsGuard < epicsMutex > &, + unsigned type, arrayElementCount count ); + void cancel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; +protected: + syncGroupReadNotify ( CASG & sgIn, PRecycleFunc, chid, void * pValueIn ); + virtual ~syncGroupReadNotify (); +private: + chid chan; + PRecycleFunc pRecycleFunc; + CASG & sg; + void * pValue; + const unsigned magic; + cacChannel::ioid id; + bool idIsValid; + bool ioComplete; + void operator delete ( void * ); + void * operator new ( size_t, + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > & )) + void completion ( + epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void * pData ); + void exception ( + epicsGuard < epicsMutex > &, int status, + const char * pContext, unsigned type, arrayElementCount count ); + syncGroupReadNotify ( const syncGroupReadNotify & ); + syncGroupReadNotify & operator = ( const syncGroupReadNotify & ); +}; + +class syncGroupWriteNotify : public syncGroupNotify, public cacWriteNotify { +public: + typedef void ( CASG :: * PRecycleFunc ) + ( epicsGuard < epicsMutex > &, syncGroupWriteNotify & ); + static syncGroupWriteNotify * factory ( + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > &, + CASG &, PRecycleFunc, chid ); + void destroy ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + bool ioPending ( + epicsGuard < epicsMutex > & guard ); + void begin ( epicsGuard < epicsMutex > &, unsigned type, + arrayElementCount count, const void * pValueIn ); + void cancel ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; +protected: + syncGroupWriteNotify ( struct CASG &, PRecycleFunc, chid ); + virtual ~syncGroupWriteNotify (); // allocate only from pool +private: + chid chan; + PRecycleFunc pRecycleFunc; + CASG & sg; + const unsigned magic; + cacChannel::ioid id; + bool idIsValid; + bool ioComplete; + void operator delete ( void * ); + void * operator new ( size_t, + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > & )) + void completion ( epicsGuard < epicsMutex > & ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char *pContext, + unsigned type, arrayElementCount count ); + syncGroupWriteNotify ( const syncGroupWriteNotify & ); + syncGroupWriteNotify & operator = ( const syncGroupWriteNotify & ); +}; + +struct ca_client_context; + +template < class T > class sgAutoPtr; + +struct CASG : public chronIntIdRes < CASG > { +public: + CASG ( epicsGuard < epicsMutex > &, ca_client_context & cacIn ); + void destructor ( + CallbackGuard &, + epicsGuard < epicsMutex > & guard ); + bool ioComplete ( + CallbackGuard &, + epicsGuard < epicsMutex > & guard ); + bool verify ( epicsGuard < epicsMutex > & ) const; + int block ( epicsGuard < epicsMutex > * pcbGuard, + epicsGuard < epicsMutex > & guard, double timeout ); + void reset ( CallbackGuard &, epicsGuard < epicsMutex > & ); + void show ( epicsGuard < epicsMutex > &, unsigned level ) const; + void show ( unsigned level ) const; + void get ( epicsGuard < epicsMutex > &, chid pChan, + unsigned type, arrayElementCount count, void * pValue ); + void put ( epicsGuard < epicsMutex > &, chid pChan, + unsigned type, arrayElementCount count, const void * pValue ); + void completionNotify ( + epicsGuard < epicsMutex > &, syncGroupNotify & ); + int printFormated ( const char * pFormat, ... ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + const char * pFileName, unsigned lineNo ); + void exception ( + epicsGuard < epicsMutex > &, int status, const char * pContext, + const char * pFileName, unsigned lineNo, oldChannelNotify & chan, + unsigned type, arrayElementCount count, unsigned op ); + void * operator new ( size_t size, + tsFreeList < struct CASG, 128, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < struct CASG, 128, epicsMutexNOOP > & )) + +private: + tsDLList < syncGroupNotify > ioPendingList; + tsDLList < syncGroupNotify > ioCompletedList; + epicsEvent sem; + ca_client_context & client; + unsigned magic; + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > freeListReadOP; + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > freeListWriteOP; + + void destroyPendingIO ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + void destroyCompletedIO ( + CallbackGuard & cbGuard, + epicsGuard < epicsMutex > & guard ); + void recycleReadNotifyIO ( epicsGuard < epicsMutex > &, + syncGroupReadNotify & ); + void recycleWriteNotifyIO ( epicsGuard < epicsMutex > &, + syncGroupWriteNotify & ); + + CASG ( const CASG & ); + CASG & operator = ( const CASG & ); + + void operator delete ( void * ); + + ~CASG (); + + friend class sgAutoPtr < syncGroupWriteNotify >; + friend class sgAutoPtr < syncGroupReadNotify >; +}; + +class boolFlagManager { +public: + boolFlagManager ( bool & flag ); + ~boolFlagManager (); + void release (); +private: + bool * pBool; +}; + +inline boolFlagManager::boolFlagManager ( bool & flagIn ) : + pBool ( & flagIn ) +{ + *this->pBool = true; +} + +inline boolFlagManager::~boolFlagManager () +{ + if ( this->pBool ) { + *this->pBool = false; + } +} + +inline void boolFlagManager::release () +{ + this->pBool = 0; +} + +inline void * CASG::operator new ( size_t size, + tsFreeList < struct CASG, 128, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) +inline void CASG::operator delete ( void * pCadaver, + tsFreeList < struct CASG, 128, epicsMutexNOOP > & freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + +inline bool syncGroupWriteNotify::ioPending ( + epicsGuard < epicsMutex > & /* guard */ ) +{ + return ! this->ioComplete; +} + +inline bool syncGroupReadNotify::ioPending ( + epicsGuard < epicsMutex > & /* guard */ ) +{ + return ! this->ioComplete; +} + +#endif // ifdef syncGrouph diff --git a/modules/ca/src/client/syncGroupNotify.cpp b/modules/ca/src/client/syncGroupNotify.cpp new file mode 100644 index 000000000..2780fbe89 --- /dev/null +++ b/modules/ca/src/client/syncGroupNotify.cpp @@ -0,0 +1,34 @@ +/*************************************************************************\ +* 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 + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "syncGroup.h" +#include "oldAccess.h" + +syncGroupNotify::syncGroupNotify () +{ +} + +syncGroupNotify::~syncGroupNotify () +{ +} + + + + diff --git a/modules/ca/src/client/syncGroupReadNotify.cpp b/modules/ca/src/client/syncGroupReadNotify.cpp new file mode 100644 index 000000000..711a2fae4 --- /dev/null +++ b/modules/ca/src/client/syncGroupReadNotify.cpp @@ -0,0 +1,153 @@ +/*************************************************************************\ +* 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 + */ + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "syncGroup.h" +#include "oldAccess.h" + +syncGroupReadNotify::syncGroupReadNotify ( + CASG & sgIn, PRecycleFunc pRecycleFuncIn, + chid pChan, void * pValueIn ) : + chan ( pChan ), pRecycleFunc ( pRecycleFuncIn ), + sg ( sgIn ), pValue ( pValueIn ), + magic ( CASG_MAGIC ), id ( 0u ), + idIsValid ( false ), ioComplete ( false ) +{ +} + +void syncGroupReadNotify::begin ( + epicsGuard < epicsMutex > & guard, + unsigned type, arrayElementCount count ) +{ + this->chan->eliminateExcessiveSendBacklog ( guard ); + this->ioComplete = false; + boolFlagManager mgr ( this->idIsValid ); + this->chan->read ( guard, type, count, *this, &this->id ); + mgr.release (); +} + +void syncGroupReadNotify::cancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExcusionGuard ) +{ + if ( this->idIsValid ) { + this->chan->ioCancel ( callbackGuard, mutualExcusionGuard, this->id ); + this->idIsValid = false; + } +} + +syncGroupReadNotify * syncGroupReadNotify::factory ( + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > & freeList, + struct CASG & sg, PRecycleFunc pRecycleFunc, chid chan, void * pValueIn ) +{ + return new ( freeList ) + syncGroupReadNotify ( sg, pRecycleFunc, chan, pValueIn ); +} + +void syncGroupReadNotify::destroy ( + CallbackGuard &, + epicsGuard < epicsMutex > & guard ) +{ + CASG & sgRef ( this->sg ); + this->~syncGroupReadNotify (); + ( sgRef.*pRecycleFunc ) ( guard, *this ); +} + +syncGroupReadNotify::~syncGroupReadNotify () +{ + assert ( ! this->idIsValid ); +} + +void syncGroupReadNotify::completion ( + epicsGuard < epicsMutex > & guard, unsigned type, + arrayElementCount count, const void * pData ) +{ + if ( this->magic != CASG_MAGIC ) { + this->sg.printFormated ( + "cac: sync group io_complete(): bad sync grp op magic number?\n" ); + return; + } + + if ( this->pValue ) { + size_t size = dbr_size_n ( type, count ); + memcpy ( this->pValue, pData, size ); + } + this->sg.completionNotify ( guard, *this ); + this->idIsValid = false; + this->ioComplete = true; +} + +void syncGroupReadNotify::exception ( + epicsGuard < epicsMutex > & guard, int status, const char * pContext, + unsigned type, arrayElementCount count ) +{ + if ( this->magic != CASG_MAGIC ) { + this->sg.printFormated ( + "cac: sync group io_complete(): bad sync grp op magic number?\n" ); + return; + } + this->idIsValid = false; + this->sg.exception ( guard, status, pContext, + __FILE__, __LINE__, *this->chan, type, count, CA_OP_GET ); + // + // This notify is left installed at this point as a place holder indicating that + // all requests have not been completed. This notify is not uninstalled until + // CASG::block() times out or unit they call CASG::reset(). + // +} + +void syncGroupReadNotify::show ( + epicsGuard < epicsMutex > &, unsigned level ) const +{ + ::printf ( "pending sg read op: pVal=%p\n", this->pValue ); + if ( level > 0u ) { + ::printf ( "pending sg op: magic=%u sg=%p\n", + this->magic, static_cast < void * > ( & this->sg ) ); + } +} + +void syncGroupReadNotify::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 * syncGroupReadNotify::operator new ( size_t size, + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) +void syncGroupReadNotify::operator delete ( void *pCadaver, + tsFreeList < class syncGroupReadNotify, 128, epicsMutexNOOP > &freeList ) +{ + freeList.release ( pCadaver ); +} +#endif diff --git a/modules/ca/src/client/syncGroupWriteNotify.cpp b/modules/ca/src/client/syncGroupWriteNotify.cpp new file mode 100644 index 000000000..f0bf42753 --- /dev/null +++ b/modules/ca/src/client/syncGroupWriteNotify.cpp @@ -0,0 +1,144 @@ +/*************************************************************************\ +* 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 + */ + +#include +#include + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "syncGroup.h" +#include "oldAccess.h" + +syncGroupWriteNotify::syncGroupWriteNotify ( CASG & sgIn, + PRecycleFunc pRecycleFuncIn, chid pChan ) : + chan ( pChan ), pRecycleFunc ( pRecycleFuncIn ), + sg ( sgIn ), magic ( CASG_MAGIC ), + id ( 0u ), idIsValid ( false ), ioComplete ( false ) +{ +} + +void syncGroupWriteNotify::begin ( + epicsGuard < epicsMutex > & guard, unsigned type, + arrayElementCount count, const void * pValueIn ) +{ + this->chan->eliminateExcessiveSendBacklog ( guard ); + this->ioComplete = false; + boolFlagManager mgr ( this->idIsValid ); + this->chan->write ( guard, type, count, + pValueIn, *this, &this->id ); + mgr.release (); +} + +void syncGroupWriteNotify::cancel ( + CallbackGuard & callbackGuard, + epicsGuard < epicsMutex > & mutualExcusionGuard ) +{ + if ( this->idIsValid ) { + this->chan->ioCancel ( callbackGuard, mutualExcusionGuard, this->id ); + this->idIsValid = false; + } +} + +syncGroupWriteNotify * syncGroupWriteNotify::factory ( + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > &freeList, + struct CASG & sg, PRecycleFunc pRecycleFunc, chid chan ) +{ + return new ( freeList ) syncGroupWriteNotify ( sg, pRecycleFunc, chan ); +} + +void syncGroupWriteNotify::destroy ( + CallbackGuard &, + epicsGuard < epicsMutex > & guard ) +{ + CASG & sgRef ( this->sg ); + this->~syncGroupWriteNotify (); + ( sgRef.*pRecycleFunc ) ( guard, *this ); +} + +syncGroupWriteNotify::~syncGroupWriteNotify () +{ + assert ( ! this->idIsValid ); +} + +void syncGroupWriteNotify::completion ( + epicsGuard < epicsMutex > & guard ) +{ + if ( this->magic != CASG_MAGIC ) { + this->sg.printFormated ( "cac: sync group io_complete(): bad sync grp op magic number?\n" ); + return; + } + this->sg.completionNotify ( guard, *this ); + this->idIsValid = false; + this->ioComplete = true; +} + +void syncGroupWriteNotify::exception ( + epicsGuard < epicsMutex > & guard, + int status, const char *pContext, unsigned type, arrayElementCount count ) +{ + if ( this->magic != CASG_MAGIC ) { + this->sg.printFormated ( "cac: sync group io_complete(): bad sync grp op magic number?\n" ); + return; + } + this->sg.exception ( guard, status, pContext, + __FILE__, __LINE__, *this->chan, type, count, CA_OP_PUT ); + this->idIsValid = false; + // + // This notify is left installed at this point as a place holder indicating that + // all requests have not been completed. This notify is not uninstalled until + // CASG::block() times out or unit they call CASG::reset(). + // +} + +void syncGroupWriteNotify::show ( + epicsGuard < epicsMutex > &, unsigned level ) const +{ + ::printf ( "pending write sg op\n" ); + if ( level > 0u ) { + ::printf ( "pending sg op: magic=%u sg=%p\n", + this->magic, static_cast < void * > ( &this->sg ) ); + } +} + +void syncGroupWriteNotify::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 * syncGroupWriteNotify::operator new ( size_t size, + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > & freeList ) +{ + return freeList.allocate ( size ); +} + +#if defined ( CXX_PLACEMENT_DELETE ) +void syncGroupWriteNotify::operator delete ( void *pCadaver, + tsFreeList < class syncGroupWriteNotify, 128, epicsMutexNOOP > &freeList ) +{ + freeList.release ( pCadaver ); +} +#endif + diff --git a/modules/ca/src/client/syncgrp.cpp b/modules/ca/src/client/syncgrp.cpp new file mode 100644 index 000000000..9727ae8df --- /dev/null +++ b/modules/ca/src/client/syncgrp.cpp @@ -0,0 +1,363 @@ +/*************************************************************************\ +* 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 + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#define epicsExportSharedSymbols +#include "iocinf.h" +#include "oldAccess.h" +#include "syncGroup.h" + +/* + * ca_sg_create() + */ +extern "C" int epicsShareAPI ca_sg_create ( CA_SYNC_GID * pgid ) +{ + ca_client_context * pcac; + int caStatus; + CASG * pcasg; + + caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + try { + epicsGuard < epicsMutex > guard ( pcac->mutexRef() ); + pcasg = new ( pcac->casgFreeList ) CASG ( guard, *pcac ); + *pgid = pcasg->getId (); + return ECA_NORMAL; + } + catch ( std::bad_alloc & ) { + return ECA_ALLOCMEM; + } + catch ( ... ) { + return ECA_INTERNAL; + } +} + +int ca_sync_group_destroy ( CallbackGuard & cbGuard, epicsGuard < epicsMutex > & guard, + ca_client_context & cac, const CA_SYNC_GID gid ) +{ + int caStatus; + CASG * pcasg = cac.lookupCASG ( guard, gid ); + if ( pcasg ) { + pcasg->destructor ( cbGuard, guard ); + cac.casgFreeList.release ( pcasg ); + caStatus = ECA_NORMAL; + } + else { + caStatus = ECA_BADSYNCGRP; + } + return caStatus; +} + +/* + * ca_sg_delete() + */ +extern "C" int epicsShareAPI ca_sg_delete ( const CA_SYNC_GID gid ) +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( & pcac ); + if ( caStatus == ECA_NORMAL ) { + if ( pcac->pCallbackGuard.get() && + pcac->createdByThread == epicsThreadGetIdSelf () ) { + epicsGuard < epicsMutex > guard ( pcac->mutex ); + caStatus = ca_sync_group_destroy ( *pcac->pCallbackGuard.get(), + guard, *pcac, gid ); + } + else { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( pcac->cbMutex ); + epicsGuard < epicsMutex > guard ( pcac->mutex ); + caStatus = ca_sync_group_destroy ( cbGuard, guard, *pcac, gid ); + } + } + return caStatus; +} + +void sync_group_reset ( ca_client_context & client, CASG & sg ) +{ + if ( client.pCallbackGuard.get() && + client.createdByThread == epicsThreadGetIdSelf () ) { + epicsGuard < epicsMutex > guard ( client.mutex ); + sg.reset ( *client.pCallbackGuard.get(), guard ); + } + else { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( client.cbMutex ); + epicsGuard < epicsMutex > guard ( client.mutex ); + sg.reset ( cbGuard, guard ); + } +} + +// +// ca_sg_block () +// +// !!!! This routine is only visible in the old interface - or in a new ST interface. +// !!!! In the old interface we restrict thread attach so that calls from threads +// !!!! other than the initializing thread are not allowed if preemptive callback +// !!!! is disabled. This prevents the preemptive callback lock from being released +// !!!! by other threads than the one that locked it. +// +extern "C" int epicsShareAPI ca_sg_block ( + const CA_SYNC_GID gid, ca_real timeout ) +{ + ca_client_context *pcac; + int status = fetchClientContext ( &pcac ); + if ( status == ECA_NORMAL ) { + CASG * pcasg; + { + epicsGuard < epicsMutex > guard ( pcac->mutex ); + pcasg = pcac->lookupCASG ( guard, gid ); + if ( pcasg ) { + status = pcasg->block ( + pcac->pCallbackGuard.get (), guard, timeout ); + } + else { + status = ECA_BADSYNCGRP; + } + } + if ( pcasg ) { + sync_group_reset ( *pcac, *pcasg ); + } + } + return status; +} + +/* + * ca_sg_reset + */ +extern "C" int epicsShareAPI ca_sg_reset ( const CA_SYNC_GID gid ) +{ + ca_client_context *pcac; + int caStatus = fetchClientContext (&pcac); + if ( caStatus == ECA_NORMAL ) { + CASG * pcasg; + { + epicsGuard < epicsMutex > guard ( pcac->mutex ); + pcasg = pcac->lookupCASG ( guard, gid ); + } + if ( pcasg ) { + sync_group_reset ( *pcac, *pcasg ); + caStatus = ECA_NORMAL; + } + else { + caStatus = ECA_BADSYNCGRP; + } + } + return caStatus; +} + +/* + * ca_sg_stat + */ +extern "C" int epicsShareAPI ca_sg_stat ( const CA_SYNC_GID gid ) +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + epicsGuard < epicsMutex > guard ( pcac->mutexRef() ); + + CASG * pcasg = pcac->lookupCASG ( guard, gid ); + if ( ! pcasg ) { + ::printf ( "Bad Sync Group Id\n"); + return ECA_BADSYNCGRP; + } + pcasg->show ( guard, 1000u ); + + return ECA_NORMAL; +} + +/* + * ca_sg_test + */ +extern "C" int epicsShareAPI ca_sg_test ( const CA_SYNC_GID gid ) +{ + ca_client_context * pcac; + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus == ECA_NORMAL ) { + epicsGuard < epicsMutex > guard ( pcac->mutexRef() ); + CASG * pcasg = pcac->lookupCASG ( guard, gid ); + if ( pcasg ) { + bool isComplete; + if ( pcac->pCallbackGuard.get() && + pcac->createdByThread == epicsThreadGetIdSelf () ) { + epicsGuard < epicsMutex > guard ( pcac->mutex ); + isComplete = pcasg->ioComplete ( *pcac->pCallbackGuard.get(), guard ); + } + else { + // + // we will definately stall out here if all of the + // following are true + // + // o user creates non-preemtive mode client library context + // o user doesnt periodically call a ca function + // o user calls this function from an auxiillary thread + // + CallbackGuard cbGuard ( pcac->cbMutex ); + epicsGuard < epicsMutex > guard ( pcac->mutex ); + isComplete = pcasg->ioComplete ( cbGuard, guard ); + } + if ( isComplete ) { + caStatus = ECA_IODONE; + } + else{ + caStatus = ECA_IOINPROGRESS; + } + } + else { + caStatus = ECA_BADSYNCGRP; + } + } + return caStatus; +} + +/* + * ca_sg_array_put() + */ +extern "C" int epicsShareAPI ca_sg_array_put ( const CA_SYNC_GID gid, chtype type, + arrayElementCount count, chid pChan, const void *pValue ) +{ + ca_client_context *pcac; + + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + epicsGuard < epicsMutex > guard ( pcac->mutexRef() ); + CASG * const pcasg = pcac->lookupCASG ( guard, gid ); + if ( ! pcasg ) { + return ECA_BADSYNCGRP; + } + + try { + pcasg->put ( guard, pChan, type, + static_cast < unsigned > ( count ), pValue ); + return ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + return ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + return ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + return ECA_BADCOUNT; + } + catch ( cacChannel::noWriteAccess & ) + { + return ECA_NOWTACCESS; + } + catch ( cacChannel::notConnected & ) + { + return ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + return ECA_UNAVAILINSERV; + } + catch ( cacChannel::requestTimedOut & ) + { + return ECA_TIMEOUT; + } + catch ( std::bad_alloc & ) + { + return ECA_ALLOCMEM; + } + catch ( ... ) + { + return ECA_INTERNAL; + } +} + +/* + * ca_sg_array_get() + */ +extern "C" int epicsShareAPI ca_sg_array_get ( const CA_SYNC_GID gid, chtype type, + arrayElementCount count, chid pChan, void *pValue ) +{ + ca_client_context *pcac; + + int caStatus = fetchClientContext ( &pcac ); + if ( caStatus != ECA_NORMAL ) { + return caStatus; + } + + epicsGuard < epicsMutex > guard ( pcac->mutexRef() ); + CASG * const pcasg = pcac->lookupCASG ( guard, gid ); + if ( ! pcasg ) { + return ECA_BADSYNCGRP; + } + + try { + pcasg->get ( guard, pChan, type, + static_cast < unsigned > ( count ), pValue ); + return ECA_NORMAL; + } + catch ( cacChannel::badString & ) + { + return ECA_BADSTR; + } + catch ( cacChannel::badType & ) + { + return ECA_BADTYPE; + } + catch ( cacChannel::outOfBounds & ) + { + return ECA_BADCOUNT; + } + catch ( cacChannel::noReadAccess & ) + { + return ECA_NORDACCESS; + } + catch ( cacChannel::notConnected & ) + { + return ECA_DISCONN; + } + catch ( cacChannel::unsupportedByService & ) + { + return ECA_UNAVAILINSERV; + } + catch ( std::bad_alloc & ) + { + return ECA_ALLOCMEM; + } + catch ( ... ) + { + return ECA_INTERNAL; + } +} diff --git a/modules/ca/src/client/tcpRecvThread.cpp b/modules/ca/src/client/tcpRecvThread.cpp new file mode 100644 index 000000000..34bf8ca83 --- /dev/null +++ b/modules/ca/src/client/tcpRecvThread.cpp @@ -0,0 +1,11 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + + diff --git a/modules/ca/src/client/tcpRecvWatchdog.cpp b/modules/ca/src/client/tcpRecvWatchdog.cpp new file mode 100644 index 000000000..72c92968b --- /dev/null +++ b/modules/ca/src/client/tcpRecvWatchdog.cpp @@ -0,0 +1,251 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "cac.h" +#include "virtualCircuit.h" + +// +// the recv watchdog timer is active when this object is created +// +tcpRecvWatchdog::tcpRecvWatchdog + ( epicsMutex & cbMutexIn, cacContextNotify & ctxNotifyIn, + epicsMutex & mutexIn, tcpiiu & iiuIn, + double periodIn, epicsTimerQueue & queueIn ) : + period ( periodIn ), timer ( queueIn.createTimer () ), + cbMutex ( cbMutexIn ), ctxNotify ( ctxNotifyIn ), + mutex ( mutexIn ), iiu ( iiuIn ), + probeResponsePending ( false ), beaconAnomaly ( true ), + probeTimeoutDetected ( false ), shuttingDown ( false ) +{ +} + +tcpRecvWatchdog::~tcpRecvWatchdog () +{ + this->timer.destroy (); +} + +epicsTimerNotify::expireStatus +tcpRecvWatchdog::expire ( const epicsTime & /* currentTime */ ) +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->shuttingDown ) { + return noRestart; + } + if ( this->probeResponsePending ) { + if ( this->iiu.receiveThreadIsBusy ( guard ) ) { + return expireStatus ( restart, CA_ECHO_TIMEOUT ); + } + + { +# ifdef DEBUG + char hostName[128]; + this->iiu.getHostName ( guard, hostName, sizeof (hostName) ); + debugPrintf ( ( "CA server \"%s\" unresponsive after %g inactive sec" + "- disconnecting.\n", + hostName, this->period ) ); +# endif + // to get the callback lock safely we must reorder + // the lock hierarchy + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + // callback lock is required because channel disconnect + // state change is initiated from this thread, and + // this can cause their disconnect notify callback + // to be invoked. + callbackManager mgr ( this->ctxNotify, this->cbMutex ); + epicsGuard < epicsMutex > tmpGuard ( this->mutex ); + this->iiu.receiveTimeoutNotify ( mgr, tmpGuard ); + this->probeTimeoutDetected = true; + } + } + return noRestart; + } + else { + if ( this->iiu.receiveThreadIsBusy ( guard ) ) { + return expireStatus ( restart, this->period ); + } + this->probeTimeoutDetected = false; + this->probeResponsePending = this->iiu.setEchoRequestPending ( guard ); + debugPrintf ( ("circuit timed out - sending echo request\n") ); + return expireStatus ( restart, CA_ECHO_TIMEOUT ); + } +} + +void tcpRecvWatchdog::beaconArrivalNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( ! ( this->shuttingDown || this->beaconAnomaly || this->probeResponsePending ) ) { + this->timer.start ( *this, this->period ); + debugPrintf ( ("saw a normal beacon - reseting circuit receive watchdog\n") ); + } +} + +// +// be careful about using beacons to reset the connection +// time out watchdog until we have received a ping response +// from the IOC (this makes the software detect reconnects +// faster when the server is rebooted twice in rapid +// succession before a 1st or 2nd beacon has been received) +// +void tcpRecvWatchdog::beaconAnomalyNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->beaconAnomaly = true; + debugPrintf ( ("Saw an abnormal beacon\n") ); +} + +void tcpRecvWatchdog::messageArrivalNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! ( this->shuttingDown || this->probeResponsePending ) ) { + this->beaconAnomaly = false; + this->timer.start ( *this, this->period ); + debugPrintf ( ("received a message - reseting circuit recv watchdog\n") ); + } +} + +void tcpRecvWatchdog::probeResponseNotify ( + epicsGuard < epicsMutex > & cbGuard ) +{ + bool restartNeeded = false; + double restartDelay = DBL_MAX; + { + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->probeResponsePending && ! this->shuttingDown ) { + restartNeeded = true; + if ( this->probeTimeoutDetected ) { + this->probeTimeoutDetected = false; + this->probeResponsePending = this->iiu.setEchoRequestPending ( guard ); + restartDelay = CA_ECHO_TIMEOUT; + debugPrintf ( ("late probe response - sending another probe request\n") ); + } + else { + this->probeResponsePending = false; + restartDelay = this->period; + this->iiu.responsiveCircuitNotify ( cbGuard, guard ); + debugPrintf ( ("probe response on time - circuit was tagged reponsive if unresponsive\n") ); + } + } + } + if ( restartNeeded ) { + this->timer.start ( *this, restartDelay ); + debugPrintf ( ("recv wd restarted with delay %f\n", restartDelay) ); + } +} + +// +// The thread for outgoing requests in the client runs +// at a higher priority than the thread in the client +// that receives responses. Therefore, there could +// be considerable large array write send backlog that +// is delaying departure of an echo request and also +// interrupting delivery of an echo response. +// We must be careful not to timeout the echo response as +// long as we see indication of regular departures of +// message buffers from the client in a situation where +// we know that the TCP send queueing has been exceeded. +// The send watchdog will be responsible for detecting +// dead connections in this case. +// +void tcpRecvWatchdog::sendBacklogProgressNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + // We dont set "beaconAnomaly" to be false here because, after we see a + // beacon anomaly (which could be transiently detecting a reboot) we will + // not trust the beacon as an indicator of a healthy server until we + // receive at least one message from the server. + if ( this->probeResponsePending && ! this->shuttingDown ) { + this->timer.start ( *this, CA_ECHO_TIMEOUT ); + debugPrintf ( ("saw heavy send backlog - reseting circuit recv watchdog\n") ); + } +} + +void tcpRecvWatchdog::connectNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( this->shuttingDown ) { + return; + } + this->timer.start ( *this, this->period ); + debugPrintf ( ("connected to the server - initiating circuit recv watchdog\n") ); +} + +void tcpRecvWatchdog::sendTimeoutNotify ( + epicsGuard < epicsMutex > & /* cbGuard */, + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + bool restartNeeded = false; + if ( ! ( this->probeResponsePending || this->shuttingDown ) ) { + this->probeResponsePending = this->iiu.setEchoRequestPending ( guard ); + restartNeeded = true; + } + if ( restartNeeded ) { + this->timer.start ( *this, CA_ECHO_TIMEOUT ); + } + debugPrintf ( ("TCP send timed out - sending echo request\n") ); +} + +void tcpRecvWatchdog::cancel () +{ + this->timer.cancel (); + debugPrintf ( ("canceling TCP recv watchdog\n") ); +} + +void tcpRecvWatchdog::shutdown () +{ + { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->shuttingDown = true; + } + this->timer.cancel (); + debugPrintf ( ("canceling TCP recv watchdog\n") ); +} + +double tcpRecvWatchdog::delay () const +{ + return this->timer.getExpireDelay (); +} + +void tcpRecvWatchdog::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->mutex ); + + ::printf ( "Receive virtual circuit watchdog at %p, period %f\n", + static_cast ( this ), this->period ); + if ( level > 0u ) { + ::printf ( "\t%s %s %s\n", + this->probeResponsePending ? "probe-response-pending" : "", + this->beaconAnomaly ? "beacon-anomaly-detected" : "", + this->probeTimeoutDetected ? "probe-response-timeout" : "" ); + } +} + diff --git a/modules/ca/src/client/tcpRecvWatchdog.h b/modules/ca/src/client/tcpRecvWatchdog.h new file mode 100644 index 000000000..0b15e22d8 --- /dev/null +++ b/modules/ca/src/client/tcpRecvWatchdog.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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef tcpRecvWatchdogh +#define tcpRecvWatchdogh + +#ifdef epicsExportSharedSymbols +# define tcpRecvWatchdogh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsTimer.h" + +#ifdef tcpRecvWatchdogh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +class tcpiiu; + +class tcpRecvWatchdog : private epicsTimerNotify { +public: + tcpRecvWatchdog ( epicsMutex & cbMutex, + cacContextNotify & ctxNotify, + epicsMutex & mutex, tcpiiu &, + double periodIn, epicsTimerQueue & ); + virtual ~tcpRecvWatchdog (); + void sendBacklogProgressNotify ( + epicsGuard < epicsMutex > & ); + void messageArrivalNotify ( + epicsGuard < epicsMutex > & guard ); + void probeResponseNotify ( + epicsGuard < epicsMutex > & ); + void beaconArrivalNotify ( + epicsGuard < epicsMutex > & ); + void beaconAnomalyNotify ( epicsGuard < epicsMutex > & ); + void connectNotify ( + epicsGuard < epicsMutex > & ); + void sendTimeoutNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void cancel (); + void shutdown (); + void show ( unsigned level ) const; + double delay () const; +private: + const double period; + epicsTimer & timer; + epicsMutex & cbMutex; + cacContextNotify & ctxNotify; + epicsMutex & mutex; + tcpiiu & iiu; + bool probeResponsePending; + bool beaconAnomaly; + bool probeTimeoutDetected; + bool shuttingDown; + expireStatus expire ( const epicsTime & currentTime ); + tcpRecvWatchdog ( const tcpRecvWatchdog & ); + tcpRecvWatchdog & operator = ( const tcpRecvWatchdog & ); +}; + +#endif // #ifndef tcpRecvWatchdogh + diff --git a/modules/ca/src/client/tcpSendWatchdog.cpp b/modules/ca/src/client/tcpSendWatchdog.cpp new file mode 100644 index 000000000..6834b39e7 --- /dev/null +++ b/modules/ca/src/client/tcpSendWatchdog.cpp @@ -0,0 +1,76 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "iocinf.h" +#include "cac.h" +#include "virtualCircuit.h" + +tcpSendWatchdog::tcpSendWatchdog ( + epicsMutex & cbMutexIn, cacContextNotify & ctxNotifyIn, + epicsMutex & mutexIn, tcpiiu & iiuIn, + double periodIn, epicsTimerQueue & queueIn ) : + period ( periodIn ), timer ( queueIn.createTimer () ), + cbMutex ( cbMutexIn ), ctxNotify ( ctxNotifyIn ), + mutex ( mutexIn ), iiu ( iiuIn ) +{ +} + +tcpSendWatchdog::~tcpSendWatchdog () +{ + this->timer.destroy (); +} + +epicsTimerNotify::expireStatus tcpSendWatchdog::expire ( + const epicsTime & /* currentTime */ ) +{ + { + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->iiu.receiveThreadIsBusy ( guard ) ) { + return expireStatus ( restart, this->period ); + } + } + { + callbackManager mgr ( this->ctxNotify, this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->mutex ); +# ifdef DEBUG + char hostName[128]; + this->iiu.getHostName ( guard, hostName, sizeof ( hostName ) ); + debugPrintf ( ( "Request not accepted by CA server %s for %g sec. Disconnecting.\n", + hostName, this->period ) ); +# endif + this->iiu.sendTimeoutNotify ( mgr, guard ); + } + return noRestart; +} + +void tcpSendWatchdog::start ( const epicsTime & /* currentTime */ ) +{ + this->timer.start ( *this, this->period ); +} + +void tcpSendWatchdog::cancel () +{ + this->timer.cancel (); +} + + diff --git a/modules/ca/src/client/tcpSendWatchdog.h b/modules/ca/src/client/tcpSendWatchdog.h new file mode 100644 index 000000000..05a2dfe75 --- /dev/null +++ b/modules/ca/src/client/tcpSendWatchdog.h @@ -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 Versions 3.13.7 +* and higher are distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef tcpSendWatchdogh +#define tcpSendWatchdogh + + +#ifdef epicsExportSharedSymbols +# define tcpSendWatchdogh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "epicsTimer.h" + +#ifdef tcpSendWatchdogh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +class tcpSendWatchdog : private epicsTimerNotify { +public: + tcpSendWatchdog ( + epicsMutex & cbMutex, cacContextNotify & ctxNotify, + epicsMutex & mutex, tcpiiu &, + double periodIn, epicsTimerQueue & queueIn ); + virtual ~tcpSendWatchdog (); + void start ( const epicsTime & ); + void cancel (); +private: + const double period; + epicsTimer & timer; + epicsMutex & cbMutex; + cacContextNotify & ctxNotify; + epicsMutex & mutex; + tcpiiu & iiu; + expireStatus expire ( const epicsTime & currentTime ); + tcpSendWatchdog ( const tcpSendWatchdog & ); + tcpSendWatchdog & operator = ( const tcpSendWatchdog & ); +}; + +#endif // #ifndef tcpSendWatchdog diff --git a/modules/ca/src/client/tcpiiu.cpp b/modules/ca/src/client/tcpiiu.cpp new file mode 100644 index 000000000..045f8d57e --- /dev/null +++ b/modules/ca/src/client/tcpiiu.cpp @@ -0,0 +1,2213 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + * + */ + +#ifdef _MSC_VER +# pragma warning(disable:4355) +#endif + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include +#include + +#include + +#include "errlog.h" + +#define epicsExportSharedSymbols +#include "localHostName.h" +#include "iocinf.h" +#include "virtualCircuit.h" +#include "inetAddrID.h" +#include "cac.h" +#include "netiiu.h" +#include "hostNameCache.h" +#include "net_convert.h" +#include "bhe.h" +#include "epicsSignal.h" +#include "caerr.h" +#include "udpiiu.h" + +using namespace std; + +tcpSendThread::tcpSendThread ( + class tcpiiu & iiuIn, const char * pName, + unsigned stackSize, unsigned priority ) : + thread ( *this, pName, stackSize, priority ), iiu ( iiuIn ) +{ +} + +tcpSendThread::~tcpSendThread () +{ +} + +void tcpSendThread::start () +{ + this->thread.start (); +} + +void tcpSendThread::show ( unsigned /* level */ ) const +{ +} + +void tcpSendThread::exitWait () +{ + this->thread.exitWait (); +} + +void tcpSendThread::run () +{ + try { + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + + bool laborPending = false; + + while ( true ) { + + // dont wait if there is still labor to be done below + if ( ! laborPending ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->iiu.sendThreadFlushEvent.wait (); + } + + if ( this->iiu.state != tcpiiu::iiucs_connected ) { + break; + } + + laborPending = false; + bool flowControlLaborNeeded = + this->iiu.busyStateDetected != this->iiu.flowControlActive; + bool echoLaborNeeded = this->iiu.echoRequestPending; + this->iiu.echoRequestPending = false; + + if ( flowControlLaborNeeded ) { + if ( this->iiu.flowControlActive ) { + this->iiu.disableFlowControlRequest ( guard ); + this->iiu.flowControlActive = false; + debugPrintf ( ( "fc off\n" ) ); + } + else { + this->iiu.enableFlowControlRequest ( guard ); + this->iiu.flowControlActive = true; + debugPrintf ( ( "fc on\n" ) ); + } + } + + if ( echoLaborNeeded ) { + this->iiu.echoRequest ( guard ); + } + + while ( nciu * pChan = this->iiu.createReqPend.get () ) { + this->iiu.createChannelRequest ( *pChan, guard ); + + if ( CA_V42 ( this->iiu.minorProtocolVersion ) ) { + this->iiu.createRespPend.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_createRespPend; + } + else { + // This wakes up the resp thread so that it can call + // the connect callback. This isnt maximally efficent + // but it has the excellent side effect of not requiring + // that the UDP thread take the callback lock. There are + // almost no V42 servers left at this point. + this->iiu.v42ConnCallbackPend.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_v42ConnCallbackPend; + this->iiu.echoRequestPending = true; + laborPending = true; + } + + if ( this->iiu.sendQue.flushBlockThreshold () ) { + laborPending = true; + break; + } + } + + while ( nciu * pChan = this->iiu.subscripReqPend.get () ) { + // this installs any subscriptions as needed + pChan->resubscribe ( guard ); + this->iiu.connectedList.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_connected; + if ( this->iiu.sendQue.flushBlockThreshold () ) { + laborPending = true; + break; + } + } + + while ( nciu * pChan = this->iiu.subscripUpdateReqPend.get () ) { + // this updates any subscriptions as needed + pChan->sendSubscriptionUpdateRequests ( guard ); + this->iiu.connectedList.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_connected; + if ( this->iiu.sendQue.flushBlockThreshold () ) { + laborPending = true; + break; + } + } + + if ( ! this->iiu.sendThreadFlush ( guard ) ) { + break; + } + } + if ( this->iiu.state == tcpiiu::iiucs_clean_shutdown ) { + this->iiu.sendThreadFlush ( guard ); + // this should cause the server to disconnect from + // the client + int status = ::shutdown ( this->iiu.sock, SHUT_WR ); + if ( status ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC TCP clean socket shutdown error was %s\n", + sockErrBuf ); + } + } + } + catch ( ... ) { + errlogPrintf ( + "cac: tcp send thread received an unexpected exception " + "- disconnecting\n"); + // this should cause the server to disconnect from + // the client + int status = ::shutdown ( this->iiu.sock, SHUT_WR ); + if ( status ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC TCP clean socket shutdown error was %s\n", + sockErrBuf ); + } + } + + this->iiu.sendDog.cancel (); + this->iiu.recvDog.shutdown (); + + while ( ! this->iiu.recvThread.exitWait ( 30.0 ) ) { + // it is possible to get stuck here if the user calls + // ca_context_destroy() when a circuit isnt known to + // be unresponsive, but is. That situation is probably + // rare, and the IP kernel might have a timeout for + // such situations, nevertheless we will attempt to deal + // with it here after waiting a reasonable amount of time + // for a clean shutdown to finish. + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + this->iiu.initiateAbortShutdown ( guard ); + } + + // user threads blocking for send backlog to be reduced + // will abort their attempt to get space if + // the state of the tcpiiu changes from connected to a + // disconnecting state. Nevertheless, we need to wait + // for them to finish prior to destroying the IIU. + { + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + while ( this->iiu.blockingForFlush ) { + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadSleep ( 0.1 ); + } + } + this->iiu.cacRef.destroyIIU ( this->iiu ); +} + +unsigned tcpiiu::sendBytes ( const void *pBuf, + unsigned nBytesInBuf, const epicsTime & currentTime ) +{ + unsigned nBytes = 0u; + assert ( nBytesInBuf <= INT_MAX ); + + this->sendDog.start ( currentTime ); + + while ( true ) { + int status = ::send ( this->sock, + static_cast < const char * > (pBuf), (int) nBytesInBuf, 0 ); + if ( status > 0 ) { + nBytes = static_cast ( status ); + // printf("SEND: %u\n", nBytes ); + break; + } + else { + epicsGuard < epicsMutex > guard ( this->mutex ); + if ( this->state != iiucs_connected && + this->state != iiucs_clean_shutdown ) { + break; + } + // winsock indicates disconnect by returning zero here + if ( status == 0 ) { + this->disconnectNotify ( guard ); + break; + } + + int localError = SOCKERRNO; + + if ( localError == SOCK_EINTR ) { + continue; + } + + if ( localError == SOCK_ENOBUFS ) { + errlogPrintf ( + "CAC: system low on network buffers " + "- send retry in 15 seconds\n" ); + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadSleep ( 15.0 ); + } + continue; + } + + if ( + localError != SOCK_EPIPE && + localError != SOCK_ECONNRESET && + localError != SOCK_ETIMEDOUT && + localError != SOCK_ECONNABORTED && + localError != SOCK_SHUTDOWN ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: unexpected TCP send error: %s\n", + sockErrBuf ); + } + + this->disconnectNotify ( guard ); + break; + } + } + + this->sendDog.cancel (); + + return nBytes; +} + +void tcpiiu::recvBytes ( + void * pBuf, unsigned nBytesInBuf, statusWireIO & stat ) +{ + assert ( nBytesInBuf <= INT_MAX ); + + while ( true ) { + int status = ::recv ( this->sock, static_cast ( pBuf ), + static_cast ( nBytesInBuf ), 0 ); + + if ( status > 0 ) { + stat.bytesCopied = static_cast ( status ); + assert ( stat.bytesCopied <= nBytesInBuf ); + stat.circuitState = swioConnected; + return; + } + else { + epicsGuard < epicsMutex > guard ( this->mutex ); + + if ( status == 0 ) { + this->disconnectNotify ( guard ); + stat.bytesCopied = 0u; + stat.circuitState = swioPeerHangup; + return; + } + + // if the circuit was locally aborted then supress + // warning messages about bad file descriptor etc + if ( this->state != iiucs_connected && + this->state != iiucs_clean_shutdown ) { + stat.bytesCopied = 0u; + stat.circuitState = swioLocalAbort; + return; + } + + int localErrno = SOCKERRNO; + + if ( localErrno == SOCK_SHUTDOWN ) { + stat.bytesCopied = 0u; + stat.circuitState = swioPeerHangup; + return; + } + + if ( localErrno == SOCK_EINTR ) { + continue; + } + + if ( localErrno == SOCK_ENOBUFS ) { + errlogPrintf ( + "CAC: system low on network buffers " + "- receive retry in 15 seconds\n" ); + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadSleep ( 15.0 ); + } + continue; + } + + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + + // the replacable printf handler isnt called here + // because it reqires a callback lock which probably + // isnt appropriate here + char name[64]; + this->hostNameCacheInstance.getName ( + name, sizeof ( name ) ); + errlogPrintf ( + "Unexpected problem with CA circuit to" + " server \"%s\" was \"%s\" - disconnecting\n", + name, sockErrBuf ); + + stat.bytesCopied = 0u; + stat.circuitState = swioPeerAbort; + return; + } + } +} + +tcpRecvThread::tcpRecvThread ( + class tcpiiu & iiuIn, class epicsMutex & cbMutexIn, + cacContextNotify & ctxNotifyIn, const char * pName, + unsigned int stackSize, unsigned int priority ) : + thread ( *this, pName, stackSize, priority ), + iiu ( iiuIn ), cbMutex ( cbMutexIn ), + ctxNotify ( ctxNotifyIn ) {} + +tcpRecvThread::~tcpRecvThread () +{ +} + +void tcpRecvThread::start () +{ + this->thread.start (); +} + +void tcpRecvThread::show ( unsigned /* level */ ) const +{ +} + +bool tcpRecvThread::exitWait ( double delay ) +{ + return this->thread.exitWait ( delay ); +} + +void tcpRecvThread::exitWait () +{ + this->thread.exitWait (); +} + +bool tcpRecvThread::validFillStatus ( + epicsGuard < epicsMutex > & guard, const statusWireIO & stat ) +{ + if ( this->iiu.state != tcpiiu::iiucs_connected && + this->iiu.state != tcpiiu::iiucs_clean_shutdown ) { + return false; + } + if ( stat.circuitState == swioConnected ) { + return true; + } + if ( stat.circuitState == swioPeerHangup || + stat.circuitState == swioPeerAbort ) { + this->iiu.disconnectNotify ( guard ); + } + else if ( stat.circuitState == swioLinkFailure ) { + this->iiu.initiateAbortShutdown ( guard ); + } + else if ( stat.circuitState == swioLocalAbort ) { + // state change already occurred + } + else { + errlogMessage ( "cac: invalid fill status - disconnecting" ); + this->iiu.disconnectNotify ( guard ); + } + return false; +} + +void tcpRecvThread::run () +{ + try { + { + bool connectSuccess = false; + { + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + this->connect ( guard ); + connectSuccess = this->iiu.state == tcpiiu::iiucs_connected; + } + if ( ! connectSuccess ) { + this->iiu.recvDog.shutdown (); + this->iiu.cacRef.destroyIIU ( this->iiu ); + return; + } + if ( this->iiu.isNameService () ) { + this->iiu.pSearchDest->setCircuit ( &this->iiu ); + this->iiu.pSearchDest->enable (); + } + } + + this->iiu.sendThread.start (); + epicsThreadPrivateSet ( caClientCallbackThreadId, &this->iiu ); + this->iiu.cacRef.attachToClientCtx (); + + comBuf * pComBuf = 0; + while ( true ) { + + // + // We leave the bytes pending and fetch them after + // callbacks are enabled when running in the old preemptive + // call back disabled mode so that asynchronous wakeup via + // file manager call backs works correctly. This does not + // appear to impact performance. + // + if ( ! pComBuf ) { + pComBuf = new ( this->iiu.comBufMemMgr ) comBuf; + } + + statusWireIO stat; + pComBuf->fillFromWire ( this->iiu, stat ); + + epicsTime currentTime = epicsTime::getCurrent (); + + { + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + + if ( ! this->validFillStatus ( guard, stat ) ) { + break; + } + if ( stat.bytesCopied == 0u ) { + continue; + } + + this->iiu.recvQue.pushLastComBufReceived ( *pComBuf ); + pComBuf = 0; + + this->iiu._receiveThreadIsBusy = true; + } + + bool sendWakeupNeeded = false; + { + // only one recv thread at a time may call callbacks + // - pendEvent() blocks until threads waiting for + // this lock get a chance to run + callbackManager mgr ( this->ctxNotify, this->cbMutex ); + + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + + // route legacy V42 channel connect through the recv thread - + // the only thread that should be taking the callback lock + while ( nciu * pChan = this->iiu.v42ConnCallbackPend.first () ) { + this->iiu.connectNotify ( guard, *pChan ); + pChan->connect ( mgr.cbGuard, guard ); + } + + this->iiu.unacknowledgedSendBytes = 0u; + + bool protocolOK = false; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + // execute receive labor + protocolOK = this->iiu.processIncoming ( currentTime, mgr ); + } + + if ( ! protocolOK ) { + this->iiu.initiateAbortShutdown ( guard ); + break; + } + this->iiu._receiveThreadIsBusy = false; + // reschedule connection activity watchdog + this->iiu.recvDog.messageArrivalNotify ( guard ); + // + // if this thread has connected channels with subscriptions + // that need to be sent then wakeup the send thread + if ( this->iiu.subscripReqPend.count() ) { + sendWakeupNeeded = true; + } + } + + // + // we dont feel comfortable calling this with a lock applied + // (it might block for longer than we like) + // + // we would prefer to improve efficency by trying, first, a + // recv with the new MSG_DONTWAIT flag set, but there isnt + // universal support + // + bool bytesArePending = this->iiu.bytesArePendingInOS (); + { + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + if ( bytesArePending ) { + if ( ! this->iiu.busyStateDetected ) { + this->iiu.contigRecvMsgCount++; + if ( this->iiu.contigRecvMsgCount >= + this->iiu.cacRef.maxContiguousFrames ( guard ) ) { + this->iiu.busyStateDetected = true; + sendWakeupNeeded = true; + } + } + } + else { + // if no bytes are pending then we must immediately + // switch off flow control w/o waiting for more + // data to arrive + this->iiu.contigRecvMsgCount = 0u; + if ( this->iiu.busyStateDetected ) { + sendWakeupNeeded = true; + this->iiu.busyStateDetected = false; + } + } + } + + if ( sendWakeupNeeded ) { + this->iiu.sendThreadFlushEvent.signal (); + } + } + + if ( pComBuf ) { + pComBuf->~comBuf (); + this->iiu.comBufMemMgr.release ( pComBuf ); + } + } + catch ( std::bad_alloc & ) { + errlogPrintf ( + "CA client library tcp receive thread " + "terminating due to no space in pool " + "C++ exception\n" ); + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + this->iiu.initiateCleanShutdown ( guard ); + } + catch ( std::exception & except ) { + errlogPrintf ( + "CA client library tcp receive thread " + "terminating due to C++ exception \"%s\"\n", + except.what () ); + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + this->iiu.initiateCleanShutdown ( guard ); + } + catch ( ... ) { + errlogPrintf ( + "CA client library tcp receive thread " + "terminating due to a non-standard C++ exception\n" ); + epicsGuard < epicsMutex > guard ( this->iiu.mutex ); + this->iiu.initiateCleanShutdown ( guard ); + } +} + +/* + * tcpRecvThread::connect () + */ +void tcpRecvThread::connect ( + epicsGuard < epicsMutex > & guard ) +{ + // attempt to connect to a CA server + while ( true ) { + int status; + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + osiSockAddr tmp = this->iiu.address (); + status = ::connect ( this->iiu.sock, + & tmp.sa, sizeof ( tmp.sa ) ); + } + + if ( this->iiu.state != tcpiiu::iiucs_connecting ) { + break; + } + if ( status >= 0 ) { + // put the iiu into the connected state + this->iiu.state = tcpiiu::iiucs_connected; + this->iiu.recvDog.connectNotify ( guard ); + break; + } + else { + int errnoCpy = SOCKERRNO; + + if ( errnoCpy == SOCK_EINTR ) { + continue; + } + else if ( errnoCpy == SOCK_SHUTDOWN ) { + if ( ! this->iiu.isNameService () ) { + break; + } + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: Unable to connect because \"%s\"\n", + sockErrBuf ); + if ( ! this->iiu.isNameService () ) { + this->iiu.disconnectNotify ( guard ); + break; + } + } + { + double sleepTime = this->iiu.cacRef.connectionTimeout ( guard ); + epicsGuardRelease < epicsMutex > unguard ( guard ); + epicsThreadSleep ( sleepTime ); + } + continue; + } + } + return; +} + +// +// tcpiiu::tcpiiu () +// +tcpiiu::tcpiiu ( + cac & cac, epicsMutex & mutexIn, epicsMutex & cbMutexIn, + cacContextNotify & ctxNotifyIn, double connectionTimeout, + epicsTimerQueue & timerQueue, const osiSockAddr & addrIn, + comBufMemoryManager & comBufMemMgrIn, + unsigned minorVersion, ipAddrToAsciiEngine & engineIn, + const cacChannel::priLev & priorityIn, + SearchDestTCP * pSearchDestIn ) : + caServerID ( addrIn.ia, priorityIn ), + hostNameCacheInstance ( addrIn, engineIn ), + recvThread ( *this, cbMutexIn, ctxNotifyIn, "CAC-TCP-recv", + epicsThreadGetStackSize ( epicsThreadStackBig ), + cac::highestPriorityLevelBelow ( cac.getInitializingThreadsPriority() ) ), + sendThread ( *this, "CAC-TCP-send", + epicsThreadGetStackSize ( epicsThreadStackMedium ), + cac::lowestPriorityLevelAbove ( + cac.getInitializingThreadsPriority() ) ), + recvDog ( cbMutexIn, ctxNotifyIn, mutexIn, + *this, connectionTimeout, timerQueue ), + sendDog ( cbMutexIn, ctxNotifyIn, mutexIn, + *this, connectionTimeout, timerQueue ), + sendQue ( *this, comBufMemMgrIn ), + recvQue ( comBufMemMgrIn ), + curDataMax ( MAX_TCP ), + curDataBytes ( 0ul ), + comBufMemMgr ( comBufMemMgrIn ), + cacRef ( cac ), + pCurData ( (char*) freeListMalloc(this->cacRef.tcpSmallRecvBufFreeList) ), + pSearchDest ( pSearchDestIn ), + mutex ( mutexIn ), + cbMutex ( cbMutexIn ), + minorProtocolVersion ( minorVersion ), + state ( iiucs_connecting ), + sock ( INVALID_SOCKET ), + contigRecvMsgCount ( 0u ), + blockingForFlush ( 0u ), + socketLibrarySendBufferSize ( 0x1000 ), + unacknowledgedSendBytes ( 0u ), + channelCountTot ( 0u ), + _receiveThreadIsBusy ( false ), + busyStateDetected ( false ), + flowControlActive ( false ), + echoRequestPending ( false ), + oldMsgHeaderAvailable ( false ), + msgHeaderAvailable ( false ), + earlyFlush ( false ), + recvProcessPostponedFlush ( false ), + discardingPendingData ( false ), + socketHasBeenClosed ( false ), + unresponsiveCircuit ( false ) +{ + if(!pCurData) + throw std::bad_alloc(); + + this->sock = epicsSocketCreate ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if ( this->sock == INVALID_SOCKET ) { + freeListFree(this->cacRef.tcpSmallRecvBufFreeList, this->pCurData); + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + std :: string reason = + "CAC: TCP circuit creation failure because \""; + reason += sockErrBuf; + reason += "\""; + throw runtime_error ( reason ); + } + + int flag = true; + int status = setsockopt ( this->sock, IPPROTO_TCP, TCP_NODELAY, + (char *) &flag, sizeof ( flag ) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: problems setting socket option TCP_NODELAY = \"%s\"\n", + sockErrBuf ); + } + + flag = true; + status = setsockopt ( this->sock , SOL_SOCKET, SO_KEEPALIVE, + ( char * ) &flag, sizeof ( flag ) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: problems setting socket option SO_KEEPALIVE = \"%s\"\n", + sockErrBuf ); + } + + // load message queue with messages informing server + // of version, user, and host name of client + { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->versionMessage ( guard, this->priority() ); + this->userNameSetRequest ( guard ); + this->hostNameSetRequest ( guard ); + } + +# if 0 + { + int i; + + /* + * some concern that vxWorks will run out of mBuf's + * if this change is made joh 11-10-98 + */ + i = MAX_MSG_SIZE; + status = setsockopt ( this->sock, SOL_SOCKET, SO_SNDBUF, + ( char * ) &i, sizeof ( i ) ); + if (status < 0) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: problems setting socket option SO_SNDBUF = \"%s\"\n", + sockErrBuf ); + } + i = MAX_MSG_SIZE; + status = setsockopt ( this->sock, SOL_SOCKET, SO_RCVBUF, + ( char * ) &i, sizeof ( i ) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: problems setting socket option SO_RCVBUF = \"%s\"\n", + sockErrBuf ); + } + } +# endif + + { + int nBytes; + osiSocklen_t sizeOfParameter = static_cast < int > ( sizeof ( nBytes ) ); + status = getsockopt ( this->sock, SOL_SOCKET, SO_SNDBUF, + ( char * ) &nBytes, &sizeOfParameter ); + if ( status < 0 || nBytes < 0 || + sizeOfParameter != static_cast < int > ( sizeof ( nBytes ) ) ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC: problems getting socket option SO_SNDBUF = \"%s\"\n", + sockErrBuf ); + } + else { + this->socketLibrarySendBufferSize = static_cast < unsigned > ( nBytes ); + } + } + + if ( isNameService() ) { + pSearchDest->setCircuit ( this ); + } + + memset ( (void *) &this->curMsg, '\0', sizeof ( this->curMsg ) ); +} + +// this must always be called by the udp thread when it holds +// the callback lock. +void tcpiiu::start ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->recvThread.start (); +} + +void tcpiiu::initiateCleanShutdown ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( this->state == iiucs_connected ) { + if ( this->unresponsiveCircuit ) { + this->initiateAbortShutdown ( guard ); + } + else { + this->state = iiucs_clean_shutdown; + this->sendThreadFlushEvent.signal (); + this->flushBlockEvent.signal (); + } + } + else if ( this->state == iiucs_clean_shutdown ) { + if ( this->unresponsiveCircuit ) { + this->initiateAbortShutdown ( guard ); + } + } + else if ( this->state == iiucs_connecting ) { + this->initiateAbortShutdown ( guard ); + } +} + +void tcpiiu::disconnectNotify ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->state = iiucs_disconnected; + this->sendThreadFlushEvent.signal (); + this->flushBlockEvent.signal (); +} + +void tcpiiu::responsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + if ( this->unresponsiveCircuit ) { + this->unresponsiveCircuit = false; + while ( nciu * pChan = this->unrespCircuit.get() ) { + this->subscripUpdateReqPend.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_subscripUpdateReqPend; + pChan->connect ( cbGuard, guard ); + } + this->sendThreadFlushEvent.signal (); + } +} + +void tcpiiu::sendTimeoutNotify ( + callbackManager & mgr, + epicsGuard < epicsMutex > & guard ) +{ + mgr.cbGuard.assertIdenticalMutex ( this-> cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + this->unresponsiveCircuitNotify ( mgr.cbGuard, guard ); + // setup circuit probe sequence + this->recvDog.sendTimeoutNotify ( mgr.cbGuard, guard ); +} + +void tcpiiu::receiveTimeoutNotify ( + callbackManager & mgr, + epicsGuard < epicsMutex > & guard ) +{ + mgr.cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + this->unresponsiveCircuitNotify ( mgr.cbGuard, guard ); +} + +void tcpiiu::unresponsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! this->unresponsiveCircuit ) { + this->unresponsiveCircuit = true; + this->echoRequestPending = true; + this->sendThreadFlushEvent.signal (); + this->flushBlockEvent.signal (); + + // must not hold lock when canceling timer + { + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbUnguard ( cbGuard ); + this->recvDog.cancel (); + this->sendDog.cancel (); + } + } + + if ( this->connectedList.count() ) { + char hostNameTmp[128]; + this->getHostName ( guard, hostNameTmp, sizeof ( hostNameTmp ) ); + genLocalExcep ( cbGuard, guard, this->cacRef, + ECA_UNRESPTMO, hostNameTmp ); + while ( nciu * pChan = this->connectedList.get () ) { + // The cac lock is released herein so there is concern that + // the list could be changed while we are traversing it. + // However, this occurs only if a circuit disconnects, + // a user deletes a channel, or a server disconnects a + // channel. The callback lock must be taken in all of + // these situations so this code is protected. + this->unrespCircuit.add ( *pChan ); + pChan->channelNode::listMember = + channelNode::cs_unrespCircuit; + pChan->unresponsiveCircuitNotify ( cbGuard, guard ); + } + } + } +} + +void tcpiiu::initiateAbortShutdown ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! this->discardingPendingData ) { + // force abortive shutdown sequence + // (discard outstanding sends and receives) + struct linger tmpLinger; + tmpLinger.l_onoff = true; + tmpLinger.l_linger = 0u; + int status = setsockopt ( this->sock, SOL_SOCKET, SO_LINGER, + reinterpret_cast ( &tmpLinger ), sizeof (tmpLinger) ); + if ( status != 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC TCP socket linger set error was %s\n", + sockErrBuf ); + } + this->discardingPendingData = true; + } + + iiu_conn_state oldState = this->state; + if ( oldState != iiucs_abort_shutdown && oldState != iiucs_disconnected ) { + this->state = iiucs_abort_shutdown; + + epicsSocketSystemCallInterruptMechanismQueryInfo info = + epicsSocketSystemCallInterruptMechanismQuery (); + switch ( info ) { + case esscimqi_socketCloseRequired: + // + // on winsock and probably vxWorks shutdown() does not + // unblock a thread in recv() so we use close() and introduce + // some complexity because we must unregister the fd early + // + if ( ! this->socketHasBeenClosed ) { + epicsSocketDestroy ( this->sock ); + this->socketHasBeenClosed = true; + } + break; + case esscimqi_socketBothShutdownRequired: + { + int status = ::shutdown ( this->sock, SHUT_RDWR ); + if ( status ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC TCP socket shutdown error was %s\n", + sockErrBuf ); + } + } + break; + case esscimqi_socketSigAlarmRequired: + this->recvThread.interruptSocketRecv (); + this->sendThread.interruptSocketSend (); + break; + default: + break; + }; + + // + // wake up the send thread if it isnt blocking in send() + // + this->sendThreadFlushEvent.signal (); + this->flushBlockEvent.signal (); + } +} + +// +// tcpiiu::~tcpiiu () +// +tcpiiu :: ~tcpiiu () +{ + if ( this->pSearchDest ) { + this->pSearchDest->disable (); + } + + this->sendThread.exitWait (); + this->recvThread.exitWait (); + this->sendDog.cancel (); + this->recvDog.shutdown (); + + if ( ! this->socketHasBeenClosed ) { + epicsSocketDestroy ( this->sock ); + } + + // free message body cache + if ( this->pCurData ) { + if ( this->curDataMax <= MAX_TCP ) { + freeListFree(this->cacRef.tcpSmallRecvBufFreeList, this->pCurData); + } + else if ( this->cacRef.tcpLargeRecvBufFreeList ) { + freeListFree(this->cacRef.tcpLargeRecvBufFreeList, this->pCurData); + } + else { + free ( this->pCurData ); + } + } +} + +void tcpiiu::show ( unsigned level ) const +{ + epicsGuard < epicsMutex > locker ( this->mutex ); + char buf[256]; + this->hostNameCacheInstance.getName ( buf, sizeof ( buf ) ); + ::printf ( "Virtual circuit to \"%s\" at version V%u.%u state %u\n", + buf, CA_MAJOR_PROTOCOL_REVISION, + this->minorProtocolVersion, this->state ); + if ( level > 1u ) { + ::printf ( "\tcurrent data cache pointer = %p current data cache size = %lu\n", + static_cast < void * > ( this->pCurData ), this->curDataMax ); + ::printf ( "\tcontiguous receive message count=%u, busy detect bool=%u, flow control bool=%u\n", + this->contigRecvMsgCount, this->busyStateDetected, this->flowControlActive ); + ::printf ( "\receive thread is busy=%u\n", + this->_receiveThreadIsBusy ); + } + if ( level > 2u ) { + ::printf ( "\tvirtual circuit socket identifier %d\n", this->sock ); + ::printf ( "\tsend thread flush signal:\n" ); + this->sendThreadFlushEvent.show ( level-2u ); + ::printf ( "\tsend thread:\n" ); + this->sendThread.show ( level-2u ); + ::printf ( "\trecv thread:\n" ); + this->recvThread.show ( level-2u ); + ::printf ("\techo pending bool = %u\n", this->echoRequestPending ); + ::printf ( "IO identifier hash table:\n" ); + + if ( this->createReqPend.count () ) { + ::printf ( "Create request pending channels\n" ); + tsDLIterConst < nciu > pChan = this->createReqPend.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + if ( this->createRespPend.count () ) { + ::printf ( "Create response pending channels\n" ); + tsDLIterConst < nciu > pChan = this->createRespPend.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + if ( this->v42ConnCallbackPend.count () ) { + ::printf ( "V42 Conn Callback pending channels\n" ); + tsDLIterConst < nciu > pChan = this->v42ConnCallbackPend.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + if ( this->subscripReqPend.count () ) { + ::printf ( "Subscription request pending channels\n" ); + tsDLIterConst < nciu > pChan = this->subscripReqPend.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + if ( this->connectedList.count () ) { + ::printf ( "Connected channels\n" ); + tsDLIterConst < nciu > pChan = this->connectedList.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + if ( this->unrespCircuit.count () ) { + ::printf ( "Unresponsive circuit channels\n" ); + tsDLIterConst < nciu > pChan = this->unrespCircuit.firstIter (); + while ( pChan.valid () ) { + pChan->show ( level - 2u ); + pChan++; + } + } + } +} + +bool tcpiiu::setEchoRequestPending ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + this->echoRequestPending = true; + this->sendThreadFlushEvent.signal (); + if ( CA_V43 ( this->minorProtocolVersion ) ) { + // we send an echo + return true; + } + else { + // we send a NOOP + return false; + } +} + +void tcpiiu::flushIfRecvProcessRequested ( + epicsGuard < epicsMutex > & guard ) +{ + if ( this->recvProcessPostponedFlush ) { + this->flushRequest ( guard ); + this->recvProcessPostponedFlush = false; + } +} + +bool tcpiiu::processIncoming ( + const epicsTime & currentTime, + callbackManager & mgr ) +{ + mgr.cbGuard.assertIdenticalMutex ( this->cbMutex ); + + while ( true ) { + + // + // fetch a complete message header + // + if ( ! this->msgHeaderAvailable ) { + if ( ! this->oldMsgHeaderAvailable ) { + this->oldMsgHeaderAvailable = + this->recvQue.popOldMsgHeader ( this->curMsg ); + if ( ! this->oldMsgHeaderAvailable ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->flushIfRecvProcessRequested ( guard ); + return true; + } + } + if ( this->curMsg.m_postsize == 0xffff ) { + static const unsigned annexSize = + sizeof ( this->curMsg.m_postsize ) + + sizeof ( this->curMsg.m_count ); + if ( this->recvQue.occupiedBytes () < annexSize ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->flushIfRecvProcessRequested ( guard ); + return true; + } + this->curMsg.m_postsize = this->recvQue.popUInt32 (); + this->curMsg.m_count = this->recvQue.popUInt32 (); + } + this->msgHeaderAvailable = true; +# ifdef DEBUG + epicsGuard < epicsMutex > guard ( this->mutex ); + debugPrintf ( + ( "%s Cmd=%3u Type=%3u Count=%8u Size=%8u", + this->pHostName ( guard ), + this->curMsg.m_cmmd, + this->curMsg.m_dataType, + this->curMsg.m_count, + this->curMsg.m_postsize) ); + debugPrintf ( + ( " Avail=%8u Cid=%8u\n", + this->curMsg.m_available, + this->curMsg.m_cid) ); +# endif + } + + // check for 8 byte aligned protocol + if ( this->curMsg.m_postsize & 0x7 ) { + this->printFormated ( mgr.cbGuard, + "CAC: server sent missaligned payload 0x%x\n", + this->curMsg.m_postsize ); + return false; + } + + // + // make sure we have a large enough message body cache + // + if ( this->curMsg.m_postsize > this->curDataMax ) { + assert (this->curMsg.m_postsize > MAX_TCP); + + char * newbuf = NULL; + arrayElementCount newsize; + + if ( !this->cacRef.tcpLargeRecvBufFreeList ) { + // round size up to multiple of 4K + newsize = ((this->curMsg.m_postsize-1)|0xfff)+1; + + if ( this->curDataMax <= MAX_TCP ) { + // small -> large + newbuf = (char*)malloc(newsize); + + } else { + // expand large to larger + newbuf = (char*)realloc(this->pCurData, newsize); + } + + } else if ( this->curMsg.m_postsize <= this->cacRef.maxRecvBytesTCP ) { + newbuf = (char*) freeListMalloc(this->cacRef.tcpLargeRecvBufFreeList); + newsize = this->cacRef.maxRecvBytesTCP; + + } + + if ( newbuf) { + if (this->curDataMax <= MAX_TCP) { + freeListFree(this->cacRef.tcpSmallRecvBufFreeList, this->pCurData ); + + } else if (this->cacRef.tcpLargeRecvBufFreeList) { + freeListFree(this->cacRef.tcpLargeRecvBufFreeList, this->pCurData ); + + } else { + // called realloc() + } + this->pCurData = newbuf; + this->curDataMax = newsize; + + } else { + this->printFormated ( mgr.cbGuard, + "CAC: not enough memory for message body cache (ignoring response message)\n"); + } + } + + if ( this->curMsg.m_postsize <= this->curDataMax ) { + if ( this->curMsg.m_postsize > 0u ) { + this->curDataBytes += this->recvQue.copyOutBytes ( + &this->pCurData[this->curDataBytes], + this->curMsg.m_postsize - this->curDataBytes ); + if ( this->curDataBytes < this->curMsg.m_postsize ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->flushIfRecvProcessRequested ( guard ); + return true; + } + } + bool msgOK = this->cacRef.executeResponse ( mgr, *this, + currentTime, this->curMsg, this->pCurData ); + if ( ! msgOK ) { + return false; + } + } + else { + static bool once = false; + if ( ! once ) { + this->printFormated ( mgr.cbGuard, + "CAC: response with payload size=%u > EPICS_CA_MAX_ARRAY_BYTES ignored\n", + this->curMsg.m_postsize ); + once = true; + } + this->curDataBytes += this->recvQue.removeBytes ( + this->curMsg.m_postsize - this->curDataBytes ); + if ( this->curDataBytes < this->curMsg.m_postsize ) { + epicsGuard < epicsMutex > guard ( this->mutex ); + this->flushIfRecvProcessRequested ( guard ); + return true; + } + } + + this->oldMsgHeaderAvailable = false; + this->msgHeaderAvailable = false; + this->curDataBytes = 0u; + } +} + +void tcpiiu::hostNameSetRequest ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! CA_V41 ( this->minorProtocolVersion ) ) { + return; + } + + const char * pName = this->cacRef.pLocalHostName (); + unsigned size = strlen ( pName ) + 1u; + unsigned postSize = CA_MESSAGE_ALIGN ( size ); + assert ( postSize < 0xffff ); + + if ( this->sendQue.flushEarlyThreshold ( postSize + 16u ) ) { + this->flushRequest ( guard ); + } + + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_HOST_NAME, postSize, + 0u, 0u, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + this->sendQue.pushString ( pName, size ); + this->sendQue.pushString ( cacNillBytes, postSize - size ); + minder.commit (); +} + +/* + * tcpiiu::userNameSetRequest () + */ +void tcpiiu::userNameSetRequest ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! CA_V41 ( this->minorProtocolVersion ) ) { + return; + } + + const char *pName = this->cacRef.userNamePointer (); + unsigned size = strlen ( pName ) + 1u; + unsigned postSize = CA_MESSAGE_ALIGN ( size ); + assert ( postSize < 0xffff ); + + if ( this->sendQue.flushEarlyThreshold ( postSize + 16u ) ) { + this->flushRequest ( guard ); + } + + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_CLIENT_NAME, postSize, + 0u, 0u, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + this->sendQue.pushString ( pName, size ); + this->sendQue.pushString ( cacNillBytes, postSize - size ); + minder.commit (); +} + +void tcpiiu::disableFlowControlRequest ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( this->sendQue.flushEarlyThreshold ( 16u ) ) { + this->flushRequest ( guard ); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_EVENTS_ON, 0u, + 0u, 0u, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::enableFlowControlRequest ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( this->sendQue.flushEarlyThreshold ( 16u ) ) { + this->flushRequest ( guard ); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_EVENTS_OFF, 0u, + 0u, 0u, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::versionMessage ( epicsGuard < epicsMutex > & guard, + const cacChannel::priLev & priority ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + assert ( priority <= 0xffff ); + + if ( this->sendQue.flushEarlyThreshold ( 16u ) ) { + this->flushRequest ( guard ); + } + + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_VERSION, 0u, + static_cast < ca_uint16_t > ( priority ), + CA_MINOR_PROTOCOL_REVISION, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::echoRequest ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + epicsUInt16 command = CA_PROTO_ECHO; + if ( ! CA_V43 ( this->minorProtocolVersion ) ) { + // we fake an echo to early server using a read sync + command = CA_PROTO_READ_SYNC; + } + + if ( this->sendQue.flushEarlyThreshold ( 16u ) ) { + this->flushRequest ( guard ); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + command, 0u, + 0u, 0u, 0u, 0u, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::writeRequest ( epicsGuard < epicsMutex > & guard, + nciu &chan, unsigned type, arrayElementCount nElem, const void *pValue ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( INVALID_DB_REQ ( type ) ) { + throw cacChannel::badType (); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestWithPayLoad ( CA_PROTO_WRITE, + type, nElem, chan.getSID(guard), chan.getCID(guard), pValue, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + + +void tcpiiu::writeNotifyRequest ( epicsGuard < epicsMutex > & guard, + nciu &chan, netWriteNotifyIO &io, unsigned type, + arrayElementCount nElem, const void *pValue ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( ! this->ca_v41_ok ( guard ) ) { + throw cacChannel::unsupportedByService(); + } + if ( INVALID_DB_REQ ( type ) ) { + throw cacChannel::badType (); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestWithPayLoad ( CA_PROTO_WRITE_NOTIFY, + type, nElem, chan.getSID(guard), io.getId(), pValue, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::readNotifyRequest ( epicsGuard < epicsMutex > & guard, + nciu & chan, netReadNotifyIO & io, + unsigned dataType, arrayElementCount nElem ) +{ + guard.assertIdenticalMutex ( this->mutex ); + if ( INVALID_DB_REQ ( dataType ) ) { + throw cacChannel::badType (); + } + arrayElementCount maxBytes; + if ( CA_V49 ( this->minorProtocolVersion ) ) { + maxBytes = 0xfffffff0; + } + else { + maxBytes = MAX_TCP; + } + arrayElementCount maxElem = + ( maxBytes - dbr_size[dataType] ) / dbr_value_size[dataType]; + if ( nElem > maxElem ) { + throw cacChannel::msgBodyCacheTooSmall (); + } + if (nElem == 0 && !CA_V413(this->minorProtocolVersion)) + nElem = chan.getcount(); + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_READ_NOTIFY, 0u, + static_cast < ca_uint16_t > ( dataType ), + static_cast < ca_uint32_t > ( nElem ), + chan.getSID(guard), io.getId(), + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::createChannelRequest ( + nciu & chan, epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( this->state != iiucs_connected && + this->state != iiucs_connecting ) { + return; + } + + const char *pName; + unsigned nameLength; + ca_uint32_t identity; + if ( this->ca_v44_ok ( guard ) ) { + identity = chan.getCID ( guard ); + pName = chan.pName ( guard ); + nameLength = chan.nameLen ( guard ); + } + else { + identity = chan.getSID ( guard ); + pName = 0; + nameLength = 0u; + } + + unsigned postCnt = CA_MESSAGE_ALIGN ( nameLength ); + + if ( postCnt >= 0xffff ) { + throw cacChannel::unsupportedByService(); + } + + comQueSendMsgMinder minder ( this->sendQue, guard ); + // + // The available field is used (abused) + // here to communicate the minor version number + // starting with CA 4.1. + // + this->sendQue.insertRequestHeader ( + CA_PROTO_CREATE_CHAN, postCnt, + 0u, 0u, identity, CA_MINOR_PROTOCOL_REVISION, + CA_V49 ( this->minorProtocolVersion ) ); + if ( nameLength ) { + this->sendQue.pushString ( pName, nameLength ); + } + if ( postCnt > nameLength ) { + this->sendQue.pushString ( cacNillBytes, postCnt - nameLength ); + } + minder.commit (); +} + +void tcpiiu::clearChannelRequest ( epicsGuard < epicsMutex > & guard, + ca_uint32_t sid, ca_uint32_t cid ) +{ + guard.assertIdenticalMutex ( this->mutex ); + // there are situations where the circuit is disconnected, but + // the channel does not know this yet + if ( this->state != iiucs_connected ) { + return; + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_CLEAR_CHANNEL, 0u, + 0u, 0u, sid, cid, + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +// +// this routine return void because if this internally fails the best response +// is to try again the next time that we reconnect +// +void tcpiiu::subscriptionRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, netSubscription & subscr ) +{ + guard.assertIdenticalMutex ( this->mutex ); + // there are situations where the circuit is disconnected, but + // the channel does not know this yet + if ( this->state != iiucs_connected && + this->state != iiucs_connecting ) { + return; + } + unsigned mask = subscr.getMask(guard); + if ( mask > 0xffff ) { + throw cacChannel::badEventSelection (); + } + arrayElementCount nElem = subscr.getCount ( + guard, CA_V413(this->minorProtocolVersion) ); + arrayElementCount maxBytes; + if ( CA_V49 ( this->minorProtocolVersion ) ) { + maxBytes = 0xfffffff0; + } + else { + maxBytes = MAX_TCP; + } + unsigned dataType = subscr.getType ( guard ); + // data type bounds checked when sunscription created + arrayElementCount maxElem = ( maxBytes - dbr_size[dataType] ) / dbr_value_size[dataType]; + if ( nElem > maxElem ) { + throw cacChannel::msgBodyCacheTooSmall (); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + // nElement bounds checked above + this->sendQue.insertRequestHeader ( + CA_PROTO_EVENT_ADD, 16u, + static_cast < ca_uint16_t > ( dataType ), + static_cast < ca_uint32_t > ( nElem ), + chan.getSID(guard), subscr.getId(), + CA_V49 ( this->minorProtocolVersion ) ); + + // extension + this->sendQue.pushFloat32 ( 0.0f ); // m_lval + this->sendQue.pushFloat32 ( 0.0f ); // m_hval + this->sendQue.pushFloat32 ( 0.0f ); // m_toval + this->sendQue.pushUInt16 ( static_cast < ca_uint16_t > ( mask ) ); // m_mask + this->sendQue.pushUInt16 ( 0u ); // m_pad + minder.commit (); +} + +// +// this routine return void because if this internally fails the best response +// is to try again the next time that we reconnect +// +void tcpiiu::subscriptionUpdateRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, netSubscription & subscr ) +{ + guard.assertIdenticalMutex ( this->mutex ); + // there are situations where the circuit is disconnected, but + // the channel does not know this yet + if ( this->state != iiucs_connected ) { + return; + } + arrayElementCount nElem = subscr.getCount ( + guard, CA_V413(this->minorProtocolVersion) ); + arrayElementCount maxBytes; + if ( CA_V49 ( this->minorProtocolVersion ) ) { + maxBytes = 0xfffffff0; + } + else { + maxBytes = MAX_TCP; + } + unsigned dataType = subscr.getType ( guard ); + // data type bounds checked when subscription constructed + arrayElementCount maxElem = ( maxBytes - dbr_size[dataType] ) / dbr_value_size[dataType]; + if ( nElem > maxElem ) { + throw cacChannel::msgBodyCacheTooSmall (); + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + // nElem boounds checked above + this->sendQue.insertRequestHeader ( + CA_PROTO_READ_NOTIFY, 0u, + static_cast < ca_uint16_t > ( dataType ), + static_cast < ca_uint32_t > ( nElem ), + chan.getSID (guard), subscr.getId (), + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +void tcpiiu::subscriptionCancelRequest ( epicsGuard < epicsMutex > & guard, + nciu & chan, netSubscription & subscr ) +{ + guard.assertIdenticalMutex ( this->mutex ); + // there are situations where the circuit is disconnected, but + // the channel does not know this yet + if ( this->state != iiucs_connected ) { + return; + } + comQueSendMsgMinder minder ( this->sendQue, guard ); + this->sendQue.insertRequestHeader ( + CA_PROTO_EVENT_CANCEL, 0u, + static_cast < ca_uint16_t > ( subscr.getType ( guard ) ), + static_cast < ca_uint16_t > ( subscr.getCount ( + guard, CA_V413(this->minorProtocolVersion) ) ), + chan.getSID(guard), subscr.getId(), + CA_V49 ( this->minorProtocolVersion ) ); + minder.commit (); +} + +bool tcpiiu::sendThreadFlush ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + if ( this->sendQue.occupiedBytes() > 0 ) { + while ( comBuf * pBuf = this->sendQue.popNextComBufToSend () ) { + epicsTime current = epicsTime::getCurrent (); + + unsigned bytesToBeSent = pBuf->occupiedBytes (); + bool success = false; + { + // no lock while blocking to send + epicsGuardRelease < epicsMutex > unguard ( guard ); + success = pBuf->flushToWire ( *this, current ); + pBuf->~comBuf (); + this->comBufMemMgr.release ( pBuf ); + } + + if ( ! success ) { + while ( ( pBuf = this->sendQue.popNextComBufToSend () ) ) { + pBuf->~comBuf (); + this->comBufMemMgr.release ( pBuf ); + } + return false; + } + + // set it here with this odd order because we must have + // the lock and we must have already sent the bytes + this->unacknowledgedSendBytes += bytesToBeSent; + if ( this->unacknowledgedSendBytes > + this->socketLibrarySendBufferSize ) { + this->recvDog.sendBacklogProgressNotify ( guard ); + } + } + } + + this->earlyFlush = false; + if ( this->blockingForFlush ) { + this->flushBlockEvent.signal (); + } + + return true; +} + +void tcpiiu :: flush ( epicsGuard < epicsMutex > & guard ) +{ + this->flushRequest ( guard ); + // the process thread is not permitted to flush as this + // can result in a push / pull deadlock on the TCP pipe. + // Instead, the process thread scheduals the flush with the + // send thread which runs at a higher priority than the + // receive thread. The same applies to the UDP thread for + // locking hierarchy reasons. + if ( ! epicsThreadPrivateGet ( caClientCallbackThreadId ) ) { + // enable / disable of call back preemption must occur here + // because the tcpiiu might disconnect while waiting and its + // pointer to this cac might become invalid + assert ( this->blockingForFlush < UINT_MAX ); + this->blockingForFlush++; + while ( this->sendQue.flushBlockThreshold() ) { + + bool userRequestsCanBeAccepted = + this->state == iiucs_connected || + ( ! this->ca_v42_ok ( guard ) && + this->state == iiucs_connecting ); + // fail the users request if we have a disconnected + // or unresponsive circuit + if ( ! userRequestsCanBeAccepted || + this->unresponsiveCircuit ) { + this->decrementBlockingForFlushCount ( guard ); + throw cacChannel::notConnected (); + } + + epicsGuardRelease < epicsMutex > unguard ( guard ); + this->flushBlockEvent.wait ( 30.0 ); + } + this->decrementBlockingForFlushCount ( guard ); + } +} + +unsigned tcpiiu::requestMessageBytesPending ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); +#if 0 + if ( ! this->earlyFlush && this->sendQue.flushEarlyThreshold(0u) ) { + this->earlyFlush = true; + this->sendThreadFlushEvent.signal (); + } +#endif + return sendQue.occupiedBytes (); +} + +void tcpiiu::decrementBlockingForFlushCount ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + assert ( this->blockingForFlush > 0u ); + this->blockingForFlush--; + if ( this->blockingForFlush > 0 ) { + this->flushBlockEvent.signal (); + } +} + +osiSockAddr tcpiiu::getNetworkAddress ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->address(); +} + +// not inline because its virtual +bool tcpiiu::ca_v42_ok ( + epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->mutex ); + return CA_V42 ( this->minorProtocolVersion ); +} + +void tcpiiu::requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + this->recvProcessPostponedFlush = true; +} + +unsigned tcpiiu::getHostName ( + epicsGuard < epicsMutex > & guard, + char * pBuf, unsigned bufLength ) const throw () +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->hostNameCacheInstance.getName ( pBuf, bufLength ); +} + +const char * tcpiiu::pHostName ( + epicsGuard < epicsMutex > & guard ) const throw () +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->hostNameCacheInstance.pointer (); +} + +void tcpiiu::disconnectAllChannels ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, + class udpiiu & discIIU ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + + while ( nciu * pChan = this->createReqPend.get () ) { + discIIU.installDisconnectedChannel ( guard, *pChan ); + } + + while ( nciu * pChan = this->createRespPend.get () ) { + // we dont yet know the server's id so we cant + // send a channel delete request and will instead + // trust that the server can do the proper cleanup + // when the circuit disconnects + discIIU.installDisconnectedChannel ( guard, *pChan ); + } + + while ( nciu * pChan = this->v42ConnCallbackPend.get () ) { + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + discIIU.installDisconnectedChannel ( guard, *pChan ); + } + + while ( nciu * pChan = this->subscripReqPend.get () ) { + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + discIIU.installDisconnectedChannel ( guard, *pChan ); + pChan->unresponsiveCircuitNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->connectedList.get () ) { + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + discIIU.installDisconnectedChannel ( guard, *pChan ); + pChan->unresponsiveCircuitNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->unrespCircuit.get () ) { + // if we know that the circuit is unresponsive + // then we dont send a channel delete request and + // will instead trust that the server can do the + // proper cleanup when the circuit disconnects + pChan->disconnectAllIO ( cbGuard, guard ); + discIIU.installDisconnectedChannel ( guard, *pChan ); + } + + while ( nciu * pChan = this->subscripUpdateReqPend.get () ) { + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + discIIU.installDisconnectedChannel ( guard, *pChan ); + pChan->unresponsiveCircuitNotify ( cbGuard, guard ); + } + + this->channelCountTot = 0u; + this->initiateCleanShutdown ( guard ); +} + +void tcpiiu::unlinkAllChannels ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + guard.assertIdenticalMutex ( this->mutex ); + + while ( nciu * pChan = this->createReqPend.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->createRespPend.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + // we dont yet know the server's id so we cant + // send a channel delete request and will instead + // trust that the server can do the proper cleanup + // when the circuit disconnects + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->v42ConnCallbackPend.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->subscripReqPend.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->connectedList.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->unrespCircuit.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->disconnectAllIO ( cbGuard, guard ); + // if we know that the circuit is unresponsive + // then we dont send a channel delete request and + // will instead trust that the server can do the + // proper cleanup when the circuit disconnects + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + while ( nciu * pChan = this->subscripUpdateReqPend.get () ) { + pChan->channelNode::listMember = + channelNode::cs_none; + pChan->disconnectAllIO ( cbGuard, guard ); + this->clearChannelRequest ( guard, + pChan->getSID(guard), pChan->getCID(guard) ); + pChan->serviceShutdownNotify ( cbGuard, guard ); + } + + this->channelCountTot = 0u; + this->initiateCleanShutdown ( guard ); +} + +void tcpiiu::installChannel ( + epicsGuard < epicsMutex > & guard, + nciu & chan, unsigned sidIn, + ca_uint16_t typeIn, arrayElementCount countIn ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + this->createReqPend.add ( chan ); + this->channelCountTot++; + chan.channelNode::listMember = channelNode::cs_createReqPend; + chan.searchReplySetUp ( *this, sidIn, typeIn, countIn, guard ); + // The tcp send thread runs at apriority below the udp thread + // so that this will not send small packets + this->sendThreadFlushEvent.signal (); +} + +bool tcpiiu :: connectNotify ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + bool wasExpected = false; + // this improves robustness in the face of a server sending + // protocol that does not match its declared protocol revision + if ( chan.channelNode::listMember == channelNode::cs_createRespPend ) { + this->createRespPend.remove ( chan ); + this->subscripReqPend.add ( chan ); + chan.channelNode::listMember = channelNode::cs_subscripReqPend; + wasExpected = true; + } + else if ( chan.channelNode::listMember == channelNode::cs_v42ConnCallbackPend ) { + this->v42ConnCallbackPend.remove ( chan ); + this->subscripReqPend.add ( chan ); + chan.channelNode::listMember = channelNode::cs_subscripReqPend; + wasExpected = true; + } + // the TCP send thread is awakened by its receive thread whenever the receive thread + // is about to block if this->subscripReqPend has items in it + return wasExpected; +} + +void tcpiiu::uninstallChan ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + guard.assertIdenticalMutex ( this->mutex ); + + switch ( chan.channelNode::listMember ) { + case channelNode::cs_createReqPend: + this->createReqPend.remove ( chan ); + break; + case channelNode::cs_createRespPend: + this->createRespPend.remove ( chan ); + break; + case channelNode::cs_v42ConnCallbackPend: + this->v42ConnCallbackPend.remove ( chan ); + break; + case channelNode::cs_subscripReqPend: + this->subscripReqPend.remove ( chan ); + break; + case channelNode::cs_connected: + this->connectedList.remove ( chan ); + break; + case channelNode::cs_unrespCircuit: + this->unrespCircuit.remove ( chan ); + break; + case channelNode::cs_subscripUpdateReqPend: + this->subscripUpdateReqPend.remove ( chan ); + break; + default: + errlogPrintf ( + "cac: attempt to uninstall channel from tcp iiu, but it inst installed there?" ); + } + chan.channelNode::listMember = channelNode::cs_none; + this->channelCountTot--; + if ( this->channelCountTot == 0 && ! this->isNameService() ) { + this->initiateCleanShutdown ( guard ); + } +} + +int tcpiiu :: printFormated ( + epicsGuard < epicsMutex > & cbGuard, + const char *pformat, ... ) +{ + cbGuard.assertIdenticalMutex ( this->cbMutex ); + + va_list theArgs; + int status; + + va_start ( theArgs, pformat ); + + status = this->cacRef.varArgsPrintFormated ( cbGuard, pformat, theArgs ); + + va_end ( theArgs ); + + return status; +} + +void tcpiiu::flushRequest ( epicsGuard < epicsMutex > & ) +{ + if ( this->sendQue.occupiedBytes () > 0 ) { + this->sendThreadFlushEvent.signal (); + } +} + +bool tcpiiu::bytesArePendingInOS () const +{ +#if 0 + FD_SET readBits; + FD_ZERO ( & readBits ); + FD_SET ( this->sock, & readBits ); + struct timeval tmo; + tmo.tv_sec = 0; + tmo.tv_usec = 0; + int status = select ( this->sock + 1, & readBits, NULL, NULL, & tmo ); + if ( status > 0 ) { + if ( FD_ISSET ( this->sock, & readBits ) ) { + return true; + } + } + return false; +#else + osiSockIoctl_t bytesPending = 0; /* shut up purifys yapping */ + int status = socket_ioctl ( this->sock, + FIONREAD, & bytesPending ); + if ( status >= 0 ) { + if ( bytesPending > 0 ) { + return true; + } + } + return false; +#endif +} + +double tcpiiu::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const +{ + return this->recvDog.delay (); +} + +/* + * Certain OS, such as HPUX, do not unblock a socket system call + * when another thread asynchronously calls both shutdown() and + * close(). To solve this problem we need to employ OS specific + * mechanisms. + */ +void tcpRecvThread::interruptSocketRecv () +{ + epicsThreadId threadId = this->thread.getId (); + if ( threadId ) { + epicsSignalRaiseSigAlarm ( threadId ); + } +} +void tcpSendThread::interruptSocketSend () +{ + epicsThreadId threadId = this->thread.getId (); + if ( threadId ) { + epicsSignalRaiseSigAlarm ( threadId ); + } +} + +void tcpiiu::operator delete ( void * /* pCadaver */ ) +{ + // 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__ ); +} + +unsigned tcpiiu::channelCount ( epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->channelCountTot; +} + +void tcpiiu::uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > & guard, nciu & chan, + const class epicsTime & currentTime ) +{ + netiiu::uninstallChanDueToSuccessfulSearchResponse ( + guard, chan, currentTime ); +} + +bool tcpiiu::searchMsg ( + epicsGuard < epicsMutex > & guard, ca_uint32_t id, + const char * pName, unsigned nameLength ) +{ + return netiiu::searchMsg ( + guard, id, pName, nameLength ); +} + +SearchDestTCP :: SearchDestTCP ( + cac & cacIn, const osiSockAddr & addrIn ) : + _ptcpiiu ( NULL ), + _cac ( cacIn ), + _addr ( addrIn ), + _active ( false ) +{ +} + +void SearchDestTCP :: disable () +{ + _active = false; + _ptcpiiu = NULL; +} + +void SearchDestTCP :: enable () +{ + _active = true; +} + +void SearchDestTCP :: searchRequest ( + epicsGuard < epicsMutex > & guard, + const char * pBuf, size_t len ) +{ + // restart circuit if it was shut down + if ( ! _ptcpiiu ) { + tcpiiu * piiu = NULL; + bool newIIU = _cac.findOrCreateVirtCircuit ( + guard, _addr, cacChannel::priorityDefault, + piiu, CA_UKN_MINOR_VERSION, this ); + if ( newIIU ) { + piiu->start ( guard ); + } + _ptcpiiu = piiu; + } + + // does this server support TCP-based name resolution? + if ( CA_V412 ( _ptcpiiu->minorProtocolVersion ) ) { + guard.assertIdenticalMutex ( _ptcpiiu->mutex ); + assert ( CA_MESSAGE_ALIGN ( len ) == len ); + comQueSendMsgMinder minder ( _ptcpiiu->sendQue, guard ); + _ptcpiiu->sendQue.pushString ( pBuf, len ); + minder.commit (); + _ptcpiiu->flushRequest ( guard ); + } +} + +void SearchDestTCP :: show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + :: printf ( "tcpiiu :: SearchDestTCP\n" ); +} + +void tcpiiu :: versionRespNotify ( const caHdrLargeArray & msg ) +{ + this->minorProtocolVersion = msg.m_count; +} + +void tcpiiu :: searchRespNotify ( + const epicsTime & currentTime, const caHdrLargeArray & msg ) +{ + /* + * the type field is abused to carry the port number + * so that we can have multiple servers on one host + */ + osiSockAddr serverAddr; + if ( msg.m_cid != INADDR_BROADCAST ) { + serverAddr.ia.sin_family = AF_INET; + serverAddr.ia.sin_addr.s_addr = htonl ( msg.m_cid ); + serverAddr.ia.sin_port = htons ( msg.m_dataType ); + } + else { + serverAddr = this->address (); + } + cacRef.transferChanToVirtCircuit + ( msg.m_available, msg.m_cid, 0xffff, + 0, minorProtocolVersion, serverAddr, currentTime ); +} diff --git a/modules/ca/src/client/test/ca_test.c b/modules/ca/src/client/test/ca_test.c new file mode 100644 index 000000000..998536843 --- /dev/null +++ b/modules/ca/src/client/test/ca_test.c @@ -0,0 +1,323 @@ +/*************************************************************************\ +* 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: Jeff Hill + * Date: 07-01-91 + */ + +/* + * ANSI + */ +#include +#include +#include + +#include "cadef.h" +#include "epicsTime.h" + +int ca_test(char *pname, char *pvalue); +static int cagft(char *pname); +static void printit(struct event_handler_args args); +static int capft(char *pname, char *pvalue); +static void verify_value(chid chan_id, chtype type); + +static unsigned long outstanding; + + +/* + * ca_test + * + * find channel, write a value if supplied, and + * read back the current value + * + */ +int ca_test( +char *pname, +char *pvalue +) +{ + int status; + if(pvalue){ + status = capft(pname,pvalue); + } + else{ + status = cagft(pname); + } + ca_task_exit(); + return status; +} + + + +/* + * cagft() + * + * ca get field test + * + * test ca get over the range of CA data types + */ +static int cagft(char *pname) +{ + const unsigned maxTries = 1000ul; + unsigned ntries = 0u; + chid chan_id; + int status; + int i; + + /* + * convert name to chan id + */ + status = ca_search(pname, &chan_id); + SEVCHK(status,NULL); + status = ca_pend_io(5.0); + if(status != ECA_NORMAL){ + SEVCHK(ca_clear_channel(chan_id),NULL); + printf("Not Found %s\n", pname); + return -1; + } + + printf("name:\t%s\n", + ca_name(chan_id)); + printf("native type:\t%s\n", + dbr_type_to_text(ca_field_type(chan_id))); + printf("native count:\t%lu\n", + ca_element_count(chan_id)); + + + /* + * fetch as each type + */ + for(i=0; i<=LAST_BUFFER_TYPE; i++){ + if(ca_field_type(chan_id)==DBR_STRING) { + if( (i!=DBR_STRING) + && (i!=DBR_STS_STRING) + && (i!=DBR_TIME_STRING) + && (i!=DBR_GR_STRING) + && (i!=DBR_CTRL_STRING)) { + continue; + } + } + /* ignore write only types */ + if ( + i == DBR_PUT_ACKT || + i == DBR_PUT_ACKS ) { + continue; + } + + status = ca_array_get_callback( + i, + ca_element_count(chan_id), + chan_id, + printit, + NULL); + SEVCHK(status, NULL); + + outstanding++; + } + + /* + * wait for the operation to complete + * before returning + */ + while ( ntries < maxTries ) { + unsigned long oldOut; + + oldOut = outstanding; + ca_pend_event ( 0.05 ); + + if ( ! outstanding ) { + SEVCHK ( ca_clear_channel ( chan_id ), NULL ); + printf ( "\n\n" ); + return 0; + } + + if ( outstanding == oldOut ) { + ntries++; + } + } + + SEVCHK ( ca_clear_channel ( chan_id ), NULL ); + return -1; +} + + +/* + * PRINTIT() + */ +static void printit ( struct event_handler_args args ) +{ + if ( args.status == ECA_NORMAL ) { + ca_dump_dbr ( args.type, args.count, args.dbr ); + } + else { + printf ( "%s\t%s\n", dbr_text[args.type], ca_message(args.status) ); + } + + outstanding--; +} + +/* + * capft + * + * test ca_put() over a range of data types + * + */ +static int capft( +char *pname, +char *pvalue +) +{ + dbr_short_t shortvalue; + dbr_long_t longvalue; + dbr_float_t floatvalue; + dbr_char_t charvalue; + dbr_double_t doublevalue; + unsigned long ntries = 10ul; + int status; + chid chan_id; + + if (((*pname < ' ') || (*pname > 'z')) + || ((*pvalue < ' ') || (*pvalue > 'z'))){ + printf("\nusage \"pv name\",\"value\"\n"); + return -1; + } + + /* + * convert name to chan id + */ + status = ca_search(pname, &chan_id); + SEVCHK(status,NULL); + status = ca_pend_io(5.0); + if(status != ECA_NORMAL){ + SEVCHK(ca_clear_channel(chan_id),NULL); + printf("Not Found %s\n", pname); + return -1; + } + + printf("name:\t%s\n", ca_name(chan_id)); + printf("native type:\t%d\n", ca_field_type(chan_id)); + printf("native count:\t%lu\n", ca_element_count(chan_id)); + + /* + * string value ca_put + */ + status = ca_put( + DBR_STRING, + chan_id, + pvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_STRING); + + if(ca_field_type(chan_id)==0)goto skip_rest; + + if(sscanf(pvalue,"%hd",&shortvalue)==1) { + /* + * short integer ca_put + */ + status = ca_put( + DBR_SHORT, + chan_id, + &shortvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_SHORT); + status = ca_put( + DBR_ENUM, + chan_id, + &shortvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_ENUM); + charvalue=(dbr_char_t)shortvalue; + status = ca_put( + DBR_CHAR, + chan_id, + &charvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_CHAR); + } + if(sscanf(pvalue,"%d",&longvalue)==1) { + /* + * long integer ca_put + */ + status = ca_put( + DBR_LONG, + chan_id, + &longvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_LONG); + } + if(epicsScanFloat(pvalue, &floatvalue)==1) { + /* + * single precision float ca_put + */ + status = ca_put( + DBR_FLOAT, + chan_id, + &floatvalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_FLOAT); + } + if(epicsScanDouble(pvalue, &doublevalue)==1) { + /* + * double precision float ca_put + */ + status = ca_put( + DBR_DOUBLE, + chan_id, + &doublevalue); + SEVCHK(status, NULL); + verify_value(chan_id, DBR_DOUBLE); + } + +skip_rest: + + /* + * wait for the operation to complete + * (outstabnding decrements to zero) + */ + while(ntries){ + ca_pend_event(1.0); + + if(!outstanding){ + SEVCHK(ca_clear_channel(chan_id),NULL); + printf("\n\n"); + return 0; + } + + ntries--; + } + + SEVCHK(ca_clear_channel(chan_id),NULL); + return -1; +} + + +/* + * VERIFY_VALUE + * + * initiate print out the values in a database access interface structure + */ +static void verify_value(chid chan_id, chtype type) +{ + int status; + + /* + * issue a get which calls back `printit' + * upon completion + */ + status = ca_array_get_callback( + type, + ca_element_count(chan_id), + chan_id, + printit, + NULL); + SEVCHK(status, NULL); + + outstanding++; +} diff --git a/modules/ca/src/client/test/ca_test.h b/modules/ca/src/client/test/ca_test.h new file mode 100644 index 000000000..076f8936a --- /dev/null +++ b/modules/ca/src/client/test/ca_test.h @@ -0,0 +1,23 @@ +/*************************************************************************\ +* 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: Jeff Hill + * Date: 21JAN2000 + */ + +#ifdef __cplusplus +extern "C" { +#endif + +int ca_test(char *pname, char *pvalue); + +#ifdef __cplusplus +} +#endif diff --git a/modules/ca/src/client/test/ca_test_main.c b/modules/ca/src/client/test/ca_test_main.c new file mode 100644 index 000000000..85fd7ea19 --- /dev/null +++ b/modules/ca/src/client/test/ca_test_main.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* 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: Jeff Hill + * Date: 21JAN2000 + */ +#include +#include + +#include "ca_test.h" +#include "dbDefs.h" + +int main(int argc, char **argv) +{ + + /* + * print error and return if arguments are invalid + */ + if(argc < 2 || argc > 3){ + printf("usage: %s [optional value to be written]\n", argv[0]); + printf("the following arguments were received\n"); + while(argc>0) { + printf("%s\n",argv[0]); + argv++; argc--; + } + return -1; + } + + + /* + * check for supplied value + */ + if(argc == 2){ + return ca_test(argv[1], NULL); + } + else if(argc == 3){ + char *pt; + + /* strip leading and trailing quotes*/ + if(argv[2][1]=='"') argv[2]++; + if( (pt=strchr(argv[2],'"')) ) *pt = 0; + return ca_test(argv[1], argv[2]); + } + else{ + return -1; + } + +} diff --git a/modules/ca/src/client/test_event.cpp b/modules/ca/src/client/test_event.cpp new file mode 100644 index 000000000..8b5a8bb54 --- /dev/null +++ b/modules/ca/src/client/test_event.cpp @@ -0,0 +1,588 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * T E S T _ E V E N T . C + * Author: Jeffrey O. Hill + * simple stub for testing monitors + */ + +#include "epicsStdioRedirect.h" + +#define epicsExportSharedSymbols +#include "cadef.h" + +extern "C" void epicsShareAPI ca_test_event ( struct event_handler_args args ) +{ + chtype nativeType = ca_field_type ( args.chid ); + const char * pNativeTypeName = ""; + if ( VALID_DB_REQ ( nativeType ) ) { + pNativeTypeName = dbr_text[nativeType]; + } + else { + if ( nativeType == TYPENOTCONN ) { + pNativeTypeName = ""; + } + } + + printf ( "ca_test_event() for channel \"%s\" with native type %s\n", + ca_name(args.chid), pNativeTypeName ); + + if ( ! ( CA_M_SUCCESS & args.status ) ) { + printf ( "Invalid CA status \"%s\"\n", ca_message ( args.status ) ); + return; + } + + if ( args.dbr ) { + ca_dump_dbr ( args.type, args.count, args.dbr ); + } +} + +/* + * ca_dump_dbr() + * dump the specified dbr type to stdout + */ +extern "C" void epicsShareAPI ca_dump_dbr ( + chtype type, unsigned count, const void * pbuffer ) +{ + unsigned i; + char tsString[50]; + + if ( INVALID_DB_REQ ( type ) ) { + printf ( "bad DBR type %ld\n", type ); + } + + printf ( "%s\t", dbr_text[type] ); + + switch ( type ) { + case DBR_STRING: + { + dbr_string_t *pString = (dbr_string_t *) pbuffer; + + for(i=0; istatus,pvalue->severity); + printf("\tValue: %s",pvalue->value); + break; + } + case DBR_STS_ENUM: + { + struct dbr_sts_enum *pvalue + = (struct dbr_sts_enum *)pbuffer; + dbr_enum_t *pEnum = &pvalue->value; + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pEnum++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%u ",*pEnum); + } + break; + } + case DBR_STS_SHORT: + { + struct dbr_sts_short *pvalue + = (struct dbr_sts_short *)pbuffer; + dbr_short_t *pshort = &pvalue->value; + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pshort++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%u ",*pshort); + } + break; + } + case DBR_STS_FLOAT: + { + struct dbr_sts_float *pvalue + = (struct dbr_sts_float *)pbuffer; + dbr_float_t *pfloat = &pvalue->value; + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pfloat++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",*pfloat); + } + break; + } + case DBR_STS_CHAR: + { + struct dbr_sts_char *pvalue + = (struct dbr_sts_char *)pbuffer; + dbr_char_t *pchar = &pvalue->value; + + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pchar++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%u ", *pchar); + } + break; + } + case DBR_STS_LONG: + { + struct dbr_sts_long *pvalue + = (struct dbr_sts_long *)pbuffer; + dbr_long_t *plong = &pvalue->value; + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,plong++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*plong); + } + break; + } + case DBR_STS_DOUBLE: + { + struct dbr_sts_double *pvalue + = (struct dbr_sts_double *)pbuffer; + dbr_double_t *pdouble = &pvalue->value; + printf("%2d %2d",pvalue->status,pvalue->severity); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pdouble++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",(float)(*pdouble)); + } + break; + } + case DBR_TIME_STRING: + { + struct dbr_time_string *pvalue + = (struct dbr_time_string *) pbuffer; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + printf("\tValue: "); + printf("%s",pvalue->value); + break; + } + case DBR_TIME_ENUM: + { + struct dbr_time_enum *pvalue + = (struct dbr_time_enum *)pbuffer; + dbr_enum_t *pshort = &pvalue->value; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pshort++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*pshort); + } + break; + } + case DBR_TIME_SHORT: + { + struct dbr_time_short *pvalue + = (struct dbr_time_short *)pbuffer; + dbr_short_t *pshort = &pvalue->value; + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d", + pvalue->status, + pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pshort++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*pshort); + } + break; + } + case DBR_TIME_FLOAT: + { + struct dbr_time_float *pvalue + = (struct dbr_time_float *)pbuffer; + dbr_float_t *pfloat = &pvalue->value; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pfloat++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",*pfloat); + } + break; + } + case DBR_TIME_CHAR: + { + struct dbr_time_char *pvalue + = (struct dbr_time_char *)pbuffer; + dbr_char_t *pchar = &pvalue->value; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pchar++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",(short)(*pchar)); + } + break; + } + case DBR_TIME_LONG: + { + struct dbr_time_long *pvalue + = (struct dbr_time_long *)pbuffer; + dbr_long_t *plong = &pvalue->value; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,plong++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*plong); + } + break; + } + case DBR_TIME_DOUBLE: + { + struct dbr_time_double *pvalue + = (struct dbr_time_double *)pbuffer; + dbr_double_t *pdouble = &pvalue->value; + + epicsTimeToStrftime(tsString,sizeof(tsString), + "%Y/%m/%d %H:%M:%S.%06f",&pvalue->stamp); + printf("%2d %2d",pvalue->status,pvalue->severity); + printf("\tTimeStamp: %s",tsString); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pdouble++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",(float)(*pdouble)); + } + break; + } + case DBR_GR_SHORT: + { + struct dbr_gr_short *pvalue + = (struct dbr_gr_short *)pbuffer; + dbr_short_t *pshort = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pshort++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*pshort); + } + break; + } + case DBR_GR_FLOAT: + { + struct dbr_gr_float *pvalue + = (struct dbr_gr_float *)pbuffer; + dbr_float_t *pfloat = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf(" %3d\n\t%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f", + pvalue->precision, + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pfloat++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",*pfloat); + } + break; + } + case DBR_GR_ENUM: + { + struct dbr_gr_enum *pvalue + = (struct dbr_gr_enum *)pbuffer; + printf("%2d %2d",pvalue->status, + pvalue->severity); + printf("\tValue: %d",pvalue->value); + if(pvalue->no_str>0) { + printf("\n\t%3d",pvalue->no_str); + for (i = 0; i < (unsigned) pvalue->no_str; i++) + printf("\n\t%.26s",pvalue->strs[i]); + } + break; + } + case DBR_CTRL_ENUM: + { + struct dbr_ctrl_enum *pvalue + = (struct dbr_ctrl_enum *)pbuffer; + printf("%2d %2d",pvalue->status, + pvalue->severity); + printf("\tValue: %d",pvalue->value); + if(pvalue->no_str>0) { + printf("\n\t%3d",pvalue->no_str); + for (i = 0; i < (unsigned) pvalue->no_str; i++) + printf("\n\t%.26s",pvalue->strs[i]); + } + break; + } + case DBR_GR_CHAR: + { + struct dbr_gr_char *pvalue + = (struct dbr_gr_char *)pbuffer; + dbr_char_t *pchar = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pchar++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%u ",*pchar); + } + break; + } + case DBR_GR_LONG: + { + struct dbr_gr_long *pvalue + = (struct dbr_gr_long *)pbuffer; + dbr_long_t *plong = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,plong++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*plong); + } + break; + } + case DBR_GR_DOUBLE: + { + struct dbr_gr_double *pvalue + = (struct dbr_gr_double *)pbuffer; + dbr_double_t *pdouble = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf(" %3d\n\t%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f", + pvalue->precision, + (float)(pvalue->upper_disp_limit), + (float)(pvalue->lower_disp_limit), + (float)(pvalue->upper_alarm_limit), + (float)(pvalue->upper_warning_limit), + (float)(pvalue->lower_warning_limit), + (float)(pvalue->lower_alarm_limit)); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pdouble++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",(float)(*pdouble)); + } + break; + } + case DBR_CTRL_SHORT: + { + struct dbr_ctrl_short *pvalue + = (struct dbr_ctrl_short *)pbuffer; + dbr_short_t *pshort = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + printf(" %8d %8d", + pvalue->upper_ctrl_limit,pvalue->lower_ctrl_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pshort++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*pshort); + } + break; + } + case DBR_CTRL_FLOAT: + { + struct dbr_ctrl_float *pvalue + = (struct dbr_ctrl_float *)pbuffer; + dbr_float_t *pfloat = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf(" %3d\n\t%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f", + pvalue->precision, + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + printf(" %8.3f %8.3f", + pvalue->upper_ctrl_limit,pvalue->lower_ctrl_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pfloat++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.4f ",*pfloat); + } + break; + } + case DBR_CTRL_CHAR: + { + struct dbr_ctrl_char *pvalue + = (struct dbr_ctrl_char *)pbuffer; + dbr_char_t *pchar = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + printf(" %8d %8d", + pvalue->upper_ctrl_limit,pvalue->lower_ctrl_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pchar++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%4d ",(short)(*pchar)); + } + break; + } + case DBR_CTRL_LONG: + { + struct dbr_ctrl_long *pvalue + = (struct dbr_ctrl_long *)pbuffer; + dbr_long_t *plong = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf("\n\t%8d %8d %8d %8d %8d %8d", + pvalue->upper_disp_limit,pvalue->lower_disp_limit, + pvalue->upper_alarm_limit,pvalue->upper_warning_limit, + pvalue->lower_warning_limit,pvalue->lower_alarm_limit); + printf(" %8d %8d", + pvalue->upper_ctrl_limit,pvalue->lower_ctrl_limit); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,plong++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%d ",*plong); + } + break; + } + case DBR_CTRL_DOUBLE: + { + struct dbr_ctrl_double *pvalue + = (struct dbr_ctrl_double *)pbuffer; + dbr_double_t *pdouble = &pvalue->value; + printf("%2d %2d %.8s",pvalue->status,pvalue->severity, + pvalue->units); + printf(" %3d\n\t%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f", + pvalue->precision, + (float)(pvalue->upper_disp_limit), + (float)(pvalue->lower_disp_limit), + (float)(pvalue->upper_alarm_limit), + (float)(pvalue->upper_warning_limit), + (float)(pvalue->lower_warning_limit), + (float)(pvalue->lower_alarm_limit)); + printf(" %8.3f %8.3f", + (float)(pvalue->upper_ctrl_limit), + (float)(pvalue->lower_ctrl_limit)); + if(count==1) printf("\tValue: "); + for (i = 0; i < count; i++,pdouble++){ + if(count!=1 && (i%10 == 0)) printf("\n"); + printf("%6.6f ",(float)(*pdouble)); + } + break; + } + case DBR_STSACK_STRING: + { + struct dbr_stsack_string *pvalue + = (struct dbr_stsack_string *)pbuffer; + printf("%2d %2d",pvalue->status,pvalue->severity); + printf(" %2d %2d",pvalue->ackt,pvalue->acks); + printf(" %s",pvalue->value); + break; + } + case DBR_CLASS_NAME: + { + dbr_class_name_t * pvalue = + ( dbr_class_name_t * ) pbuffer; + printf ( "%s", *pvalue ); + break; + } + default: + printf ( + "unsupported by ca_dbrDump()" ); + break; + } + printf("\n"); +} + diff --git a/modules/ca/src/client/ucx.h b/modules/ca/src/client/ucx.h new file mode 100644 index 000000000..c164161a5 --- /dev/null +++ b/modules/ca/src/client/ucx.h @@ -0,0 +1,102 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ +/* + * + * U C X . H + * UNIX ioctl structures and defines used for VAX/UCX + * + */ +#ifndef _UCX_H_ +# define _UCX_H_ +#ifdef UCX + +#define IFF_UP 0x1 /* interface is up */ +#define IFF_BROADCAST 0x2 /* broadcast address valid */ +#define IFF_LOOPBACK 0x8 /* is a loopback net */ +#define IFF_POINTOPOINT 0x10 /* interface is point to point */ +/* + * Interface request structure used for socket + * ioctl's. All interface ioctl's must have parameter + * definitions which begin with ifr_name. The + * remainder may be interface specific. + */ +struct ifreq { +#define IFNAMSIZ 16 + char ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + union { + struct sockaddr ifru_addr; + struct sockaddr ifru_dstaddr; + struct sockaddr ifru_broadaddr; + short ifru_flags; + int ifru_metric; + caddr_t ifru_data; + } ifr_ifru; +#define ifr_addr ifr_ifru.ifru_addr /* address */ +#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */ +#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ +#define ifr_flags ifr_ifru.ifru_flags /* flags */ +#define ifr_metric ifr_ifru.ifru_metric /* metric */ +#define ifr_data ifr_ifru.ifru_data /* for use by interface */ +}; + +/* Structure used in SIOCGIFCONF request. + * Used to retrieve interface configuration + * for machine (useful for programs which + * must know all networks accessible). + */ +struct ifconf { + int ifc_len; /* size of associated buffer */ + union { + caddr_t ifcu_buf; + struct ifreq *ifcu_req; + } ifc_ifcu; +#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */ +#define ifc_req ifc_ifcu.ifcu_req /* array of structures returned */ +}; + +#ifndef NBBY +# define NBBY 8 +#endif + + +#ifndef FD_SETSIZE +# define FD_SETSIZE 256 +#endif + +typedef long fd_mask ; +#define NFDBITS (sizeof (fd_mask) * NBBY ) /* bits per mask */ +#ifndef howmany +# define howmany(x, y) (((x)+((y)-1))/(y)) +#endif + +/* + * Both DEC C and VAX C only allow 32 fd's at once + */ +typedef int fd_set ; + +#define FD_SET(n, p) (*(p) |= (1 << ((n) % NFDBITS))) +#define FD_CLR(n, p) (*(p) &= ~(1 << ((n) % NFDBITS))) +#define FD_ISSET(n, p) (*(p) & (1 << ((n) % NFDBITS))) +#define FD_ZERO(p) memset((char *)(p), 0, sizeof (*(p))) + +#include +#define IO$_RECEIVE (IO$_WRITEVBLK) + +struct timezone { + int tz_minuteswest ; /* minutes west of Greenwich */ + int tz_dsttime ; /* type of dst correction */ +}; + +#define TWOPOWER32 4294967296.0 +#define TWOPOWER31 2147483648.0 +#define UNIX_EPOCH_AS_MJD 40587.0 +#endif +#endif + diff --git a/modules/ca/src/client/udpiiu.cpp b/modules/ca/src/client/udpiiu.cpp new file mode 100644 index 000000000..73d4ee4cb --- /dev/null +++ b/modules/ca/src/client/udpiiu.cpp @@ -0,0 +1,1445 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * Author: Jeff Hill + */ + +#ifdef _MSC_VER +# pragma warning(disable:4355) +#endif + +#define epicsAssertAuthor "Jeff Hill johill@lanl.gov" + +#include "envDefs.h" +#include "dbDefs.h" +#include "osiProcess.h" +#include "osiWireFormat.h" +#include "epicsAlgorithm.h" +#include "errlog.h" +#include "locationException.h" + +#define epicsExportSharedSymbols +#include "addrList.h" +#include "caerr.h" // for ECA_NOSEARCHADDR +#include "udpiiu.h" +#include "iocinf.h" +#include "inetAddrID.h" +#include "cac.h" +#include "disconnectGovernorTimer.h" + +// UDP protocol dispatch table +const udpiiu::pProtoStubUDP udpiiu::udpJumpTableCAC [] = +{ + &udpiiu::versionAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::searchRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::exceptionRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::beaconAction, + &udpiiu::notHereRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::badUDPRespAction, + &udpiiu::repeaterAckAction, +}; + + +static +double getMaxPeriod() +{ + double maxPeriod = maxSearchPeriodDefault; + + if ( envGetConfigParamPtr ( & EPICS_CA_MAX_SEARCH_PERIOD ) ) { + long longStatus = envGetDoubleConfigParam ( + & EPICS_CA_MAX_SEARCH_PERIOD, & maxPeriod ); + if ( ! longStatus ) { + if ( maxPeriod < maxSearchPeriodLowerLimit ) { + epicsPrintf ( "\"%s\" out of range (low)\n", + EPICS_CA_MAX_SEARCH_PERIOD.name ); + maxPeriod = maxSearchPeriodLowerLimit; + epicsPrintf ( "Setting \"%s\" = %f seconds\n", + EPICS_CA_MAX_SEARCH_PERIOD.name, maxPeriod ); + } + } + else { + epicsPrintf ( "EPICS \"%s\" wasnt a real number\n", + EPICS_CA_MAX_SEARCH_PERIOD.name ); + epicsPrintf ( "Setting \"%s\" = %f seconds\n", + EPICS_CA_MAX_SEARCH_PERIOD.name, maxPeriod ); + } + } + + return maxPeriod; +} + +static +unsigned getNTimers(double maxPeriod) +{ + unsigned nTimers = static_cast < unsigned > ( 1.0 + log ( maxPeriod / minRoundTripEstimate ) / log ( 2.0 ) ); + + if ( nTimers > channelNode::getMaxSearchTimerCount () ) { + nTimers = channelNode::getMaxSearchTimerCount (); + epicsPrintf ( "\"%s\" out of range (high)\n", + EPICS_CA_MAX_SEARCH_PERIOD.name ); + epicsPrintf ( "Setting \"%s\" = %f seconds\n", + EPICS_CA_MAX_SEARCH_PERIOD.name, + (1<<(nTimers-1)) * minRoundTripEstimate ); + } + + return nTimers; +} + +// +// udpiiu::udpiiu () +// +udpiiu::udpiiu ( + epicsGuard < epicsMutex > & cacGuard, + epicsTimerQueueActive & timerQueue, + epicsMutex & cbMutexIn, + epicsMutex & cacMutexIn, + cacContextNotify & ctxNotifyIn, + cac & cac, + unsigned port, + tsDLList < SearchDest > & searchDestListIn ) : + recvThread ( *this, ctxNotifyIn, cbMutexIn, "CAC-UDP", + epicsThreadGetStackSize ( epicsThreadStackMedium ), + cac::lowestPriorityLevelAbove ( + cac::lowestPriorityLevelAbove ( + cac.getInitializingThreadsPriority () ) ) ), + m_repeaterTimerNotify ( *this ), + repeaterSubscribeTmr ( + m_repeaterTimerNotify, timerQueue, cbMutexIn, ctxNotifyIn ), + govTmr ( *this, timerQueue, cacMutexIn ), + maxPeriod ( getMaxPeriod() ), + rtteMean ( minRoundTripEstimate ), + rtteMeanDev ( 0 ), + cacRef ( cac ), + cbMutex ( cbMutexIn ), + cacMutex ( cacMutexIn ), + nTimers ( getNTimers(maxPeriod) ), + ppSearchTmr ( nTimers ), + nBytesInXmitBuf ( 0 ), + beaconAnomalyTimerIndex ( 0 ), + sequenceNumber ( 0 ), + lastReceivedSeqNo ( 0 ), + sock ( 0 ), + repeaterPort ( 0 ), + serverPort ( port ), + localPort ( 0 ), + shutdownCmd ( false ), + lastReceivedSeqNoIsValid ( false ) +{ + cacGuard.assertIdenticalMutex ( cacMutex ); + + double powerOfTwo = log ( beaconAnomalySearchPeriod / minRoundTripEstimate ) / log ( 2.0 ); + this->beaconAnomalyTimerIndex = static_cast < unsigned > ( powerOfTwo + 1.0 ); + if ( this->beaconAnomalyTimerIndex >= this->nTimers ) { + this->beaconAnomalyTimerIndex = this->nTimers - 1; + } + + for ( unsigned i = 0; i < this->nTimers; i++ ) { + this->ppSearchTmr[i].reset ( + new searchTimer ( *this, timerQueue, i, cacMutexIn, + i > this->beaconAnomalyTimerIndex ) ); + } + + this->repeaterPort = + envGetInetPortConfigParam ( &EPICS_CA_REPEATER_PORT, + static_cast (CA_REPEATER_PORT) ); + + this->sock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if ( this->sock == INVALID_SOCKET ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC: unable to create datagram socket because = \"%s\"\n", + sockErrBuf ); + throwWithLocation ( noSocket () ); + } + +#ifdef IP_ADD_MEMBERSHIP + { + osiSockOptMcastLoop_t flag = 1; + if ( setsockopt ( this->sock, IPPROTO_IP, IP_MULTICAST_LOOP, + (char *) &flag, sizeof ( flag ) ) == -1 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf("CAC: failed to set mcast loopback\n"); + } + } +#endif + +#ifdef IP_MULTICAST_TTL + { + int ttl; + long val; + if(envGetLongConfigParam(&EPICS_CA_MCAST_TTL, &val)) + val =1; + ttl = val; + if ( setsockopt(this->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ttl, sizeof(ttl))) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf("CAC: failed to set mcast ttl %d\n", ttl); + } + } +#endif + + int boolValue = true; + int status = setsockopt ( this->sock, SOL_SOCKET, SO_BROADCAST, + (char *) &boolValue, sizeof ( boolValue ) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ("CAC: IP broadcasting enable failed because = \"%s\"\n", + sockErrBuf ); + } + +#if 0 + { + /* + * some concern that vxWorks will run out of mBuf's + * if this change is made joh 11-10-98 + * + * bump up the UDP recv buffer + */ + int size = 1u<<15u; + status = setsockopt ( this->sock, SOL_SOCKET, SO_RCVBUF, + (char *)&size, sizeof (size) ); + if (status<0) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: unable to set socket option SO_RCVBUF because \"%s\"\n", + sockErrBuf ); + } + } +#endif + + // force a bind to an unconstrained address so we can obtain + // the local port number below + static const unsigned short PORT_ANY = 0u; + osiSockAddr addr; + memset ( (char *)&addr, 0 , sizeof (addr) ); + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + addr.ia.sin_port = htons ( PORT_ANY ); + status = bind (this->sock, &addr.sa, sizeof (addr) ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy (this->sock); + errlogPrintf ( "CAC: unable to bind to an unconstrained address because = \"%s\"\n", + sockErrBuf ); + throwWithLocation ( noSocket () ); + } + + { + osiSockAddr tmpAddr; + osiSocklen_t saddr_length = sizeof ( tmpAddr ); + status = getsockname ( this->sock, &tmpAddr.sa, &saddr_length ); + if ( status < 0 ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + epicsSocketDestroy ( this->sock ); + errlogPrintf ( "CAC: getsockname () error was \"%s\"\n", sockErrBuf ); + throwWithLocation ( noSocket () ); + } + if ( tmpAddr.sa.sa_family != AF_INET) { + epicsSocketDestroy ( this->sock ); + errlogPrintf ( "CAC: UDP socket was not inet addr family\n" ); + throwWithLocation ( noSocket () ); + } + this->localPort = ntohs ( tmpAddr.ia.sin_port ); + } + + /* + * load user and auto configured + * broadcast address list + */ + ELLLIST dest; + ellInit ( & dest ); + configureChannelAccessAddressList ( & dest, this->sock, this->serverPort ); + while ( osiSockAddrNode * + pNode = reinterpret_cast < osiSockAddrNode * > ( ellGet ( & dest ) ) ) { + SearchDestUDP & searchDest = * + new SearchDestUDP ( pNode->addr, *this ); + _searchDestList.add ( searchDest ); + free ( pNode ); + } + + /* add list of tcp name service addresses */ + _searchDestList.add ( searchDestListIn ); + + caStartRepeaterIfNotInstalled ( this->repeaterPort ); + + this->pushVersionMsg (); + + // start timers and receive thread + for ( unsigned j =0; j < this->nTimers; j++ ) { + this->ppSearchTmr[j]->start ( cacGuard ); + } + this->govTmr.start (); + this->repeaterSubscribeTmr.start (); + this->recvThread.start (); +} + +/* + * udpiiu::~udpiiu () + */ +udpiiu::~udpiiu () +{ + { + epicsGuard < epicsMutex > cbGuard ( this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->cacMutex ); + this->shutdown ( cbGuard, guard ); + } + + tsDLIter < SearchDest > iter ( _searchDestList.firstIter () ); + while ( iter.valid () ) + { + SearchDest & curr ( *iter ); + iter++; + delete & curr; + } + + epicsSocketDestroy ( this->sock ); +} + +void udpiiu::shutdown ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ) +{ + // stop all of the timers + this->repeaterSubscribeTmr.shutdown ( cbGuard, guard ); + this->govTmr.shutdown ( cbGuard, guard ); + for ( unsigned i =0; i < this->nTimers; i++ ) { + this->ppSearchTmr[i]->shutdown ( cbGuard, guard ); + } + + { + this->shutdownCmd = true; + epicsGuardRelease < epicsMutex > unguard ( guard ); + { + epicsGuardRelease < epicsMutex > cbUnguard ( cbGuard ); + + if ( ! this->recvThread.exitWait ( 0.0 ) ) { + unsigned tries = 0u; + + this->wakeupMsg (); + + // wait for recv threads to exit + double shutdownDelay = 1.0; + while ( ! this->recvThread.exitWait ( shutdownDelay ) ) { + this->wakeupMsg (); + if ( shutdownDelay < 16.0 ) { + shutdownDelay += shutdownDelay; + } + if ( ++tries > 3 ) { + fprintf ( stderr, "cac: timing out waiting for UDP thread shutdown\n" ); + } + } + } + } + } +} + +udpRecvThread::udpRecvThread ( + udpiiu & iiuIn, cacContextNotify & ctxNotifyIn, epicsMutex & cbMutexIn, + const char * pName, unsigned stackSize, unsigned priority ) : + iiu ( iiuIn ), cbMutex ( cbMutexIn ), ctxNotify ( ctxNotifyIn ), + thread ( *this, pName, stackSize, priority ) {} + +udpRecvThread::~udpRecvThread () +{ +} + +void udpRecvThread::start () +{ + this->thread.start (); +} + +bool udpRecvThread::exitWait ( double delay ) +{ + return this->thread.exitWait ( delay ); +} + +void udpRecvThread::show ( unsigned /* level */ ) const +{ +} + +void udpRecvThread::run () +{ + epicsThreadPrivateSet ( caClientCallbackThreadId, &this->iiu ); + + if ( this->iiu._searchDestList.count () == 0 ) { + callbackManager mgr ( this->ctxNotify, this->cbMutex ); + epicsGuard < epicsMutex > guard ( this->iiu.cacMutex ); + genLocalExcep ( mgr.cbGuard, guard, + this->iiu.cacRef, ECA_NOSEARCHADDR, NULL ); + } + + do { + osiSockAddr src; + osiSocklen_t src_size = sizeof ( src ); + int status = recvfrom ( this->iiu.sock, + this->iiu.recvBuf, sizeof ( this->iiu.recvBuf ), 0, + & src.sa, & src_size ); + + if ( status <= 0 ) { + + if ( status < 0 ) { + int errnoCpy = SOCKERRNO; + if ( + errnoCpy != SOCK_EINTR && + errnoCpy != SOCK_SHUTDOWN && + errnoCpy != SOCK_ENOTSOCK && + errnoCpy != SOCK_EBADF && + // Avoid spurious ECONNREFUSED bug in linux + errnoCpy != SOCK_ECONNREFUSED && + // Avoid ECONNRESET from disconnected socket bug + // in windows + errnoCpy != SOCK_ECONNRESET ) { + + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + errlogPrintf ( "CAC: UDP recv error was \"%s\"\n", + sockErrBuf ); + } + } + } + else if ( status > 0 ) { + this->iiu.postMsg ( src, this->iiu.recvBuf, + (arrayElementCount) status, epicsTime::getCurrent() ); + } + + } while ( ! this->iiu.shutdownCmd ); +} + +/* for sunpro compiler */ +udpiiu::M_repeaterTimerNotify::~M_repeaterTimerNotify () +{ +} + +/* + * udpiiu::M_repeaterTimerNotify::repeaterRegistrationMessage () + * + * register with the repeater + */ +void udpiiu :: M_repeaterTimerNotify :: repeaterRegistrationMessage ( unsigned attemptNumber ) +{ + epicsGuard < epicsMutex > cbGuard ( m_udpiiu.cacMutex ); + caRepeaterRegistrationMessage ( m_udpiiu.sock, m_udpiiu.repeaterPort, attemptNumber ); +} + +/* + * caRepeaterRegistrationMessage () + * + * register with the repeater + */ +void epicsShareAPI caRepeaterRegistrationMessage ( + SOCKET sock, unsigned repeaterPort, unsigned attemptNumber ) +{ + osiSockAddr saddr; + caHdr msg; + int status; + int len; + + assert ( repeaterPort <= USHRT_MAX ); + unsigned short port = static_cast ( repeaterPort ); + + /* + * In 3.13 beta 11 and before the CA repeater calls local_addr() + * to determine a local address and does not allow registration + * messages originating from other addresses. In these + * releases local_addr() returned the address of the first enabled + * interface found, and this address may or may not have been the loop + * back address. Starting with 3.13 beta 12 local_addr() was + * changed to always return the address of the first enabled + * non-loopback interface because a valid non-loopback local + * address is required in the beacon messages. Therefore, to + * guarantee compatibility with past versions of the repeater + * we alternate between the address returned by local_addr() + * and the loopback address here. + * + * CA repeaters in R3.13 beta 12 and higher allow + * either the loopback address or the address returned + * by local address (the first non-loopback address found) + */ + if ( attemptNumber & 1 ) { + saddr = osiLocalAddr ( sock ); + if ( saddr.sa.sa_family != AF_INET ) { + /* + * use the loop back address to communicate with the CA repeater + * if this os does not have interface query capabilities + * + * this will only work with 3.13 beta 12 CA repeaters or later + */ + saddr.ia.sin_family = AF_INET; + saddr.ia.sin_addr.s_addr = htonl ( INADDR_LOOPBACK ); + saddr.ia.sin_port = htons ( port ); + } + else { + saddr.ia.sin_port = htons ( port ); + } + } + else { + saddr.ia.sin_family = AF_INET; + saddr.ia.sin_addr.s_addr = htonl ( INADDR_LOOPBACK ); + saddr.ia.sin_port = htons ( port ); + } + + memset ( (char *) &msg, 0, sizeof (msg) ); + AlignedWireRef < epicsUInt16 > ( msg.m_cmmd ) = REPEATER_REGISTER; + msg.m_available = saddr.ia.sin_addr.s_addr; + + /* + * Intentionally sending a zero length message here + * until most CA repeater daemons have been restarted + * (and only then will they accept the above protocol) + * (repeaters began accepting this protocol + * starting with EPICS 3.12) + */ +# if defined ( DOES_NOT_ACCEPT_ZERO_LENGTH_UDP ) + len = sizeof (msg); +# else + len = 0; +# endif + + status = sendto ( sock, (char *) &msg, len, 0, + &saddr.sa, sizeof ( saddr ) ); + if ( status < 0 ) { + int errnoCpy = SOCKERRNO; + /* + * Different OS return different codes when the repeater isnt running. + * Its ok to supress these messages because I print another warning message + * if we time out registerring with the repeater. + * + * Linux returns SOCK_ECONNREFUSED + * Windows 2000 returns SOCK_ECONNRESET + */ + if ( errnoCpy != SOCK_EINTR && + errnoCpy != SOCK_ECONNREFUSED && + errnoCpy != SOCK_ECONNRESET ) { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + fprintf ( stderr, "error sending registration message to CA repeater daemon was \"%s\"\n", + sockErrBuf ); + } + } +} + +/* + * caStartRepeaterIfNotInstalled () + * + * Test for the repeater already installed + * + * NOTE: potential race condition here can result + * in two copies of the repeater being spawned + * however the repeater detects this, prints a message, + * and lets the other task start the repeater. + * + * QUESTION: is there a better way to test for a port in use? + * ANSWER: none that I can find. + * + * Problems with checking for the repeater installed + * by attempting to bind a socket to its address + * and port. + * + * 1) Closed socket may not release the bound port + * before the repeater wakes up and tries to grab it. + * Attempting to bind the open socket to another port + * also does not work. + * + * 072392 - problem solved by using SO_REUSEADDR + */ +void epicsShareAPI caStartRepeaterIfNotInstalled ( unsigned repeaterPort ) +{ + bool installed = false; + int status; + SOCKET tmpSock; + union { + struct sockaddr_in ia; + struct sockaddr sa; + } bd; + + if ( repeaterPort > 0xffff ) { + fprintf ( stderr, "caStartRepeaterIfNotInstalled () : strange repeater port specified\n" ); + return; + } + + tmpSock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if ( tmpSock != INVALID_SOCKET ) { + ca_uint16_t port = static_cast < ca_uint16_t > ( repeaterPort ); + memset ( (char *) &bd, 0, sizeof ( bd ) ); + bd.ia.sin_family = AF_INET; + bd.ia.sin_addr.s_addr = htonl ( INADDR_ANY ); + bd.ia.sin_port = htons ( port ); + status = bind ( tmpSock, &bd.sa, sizeof ( bd ) ); + if ( status < 0 ) { + if ( SOCKERRNO == SOCK_EADDRINUSE ) { + installed = true; + } + else { + fprintf ( stderr, "caStartRepeaterIfNotInstalled () : bind failed\n" ); + } + } + } + + /* + * turn on reuse only after the test so that + * this works on kernels that support multicast + */ + epicsSocketEnableAddressReuseDuringTimeWaitState ( tmpSock ); + + epicsSocketDestroy ( tmpSock ); + + if ( ! installed ) { + + /* + * This is not called if the repeater is known to be + * already running. (in the event of a race condition + * the 2nd repeater exits when unable to attach to the + * repeater's port) + */ + osiSpawnDetachedProcessReturn osptr = + osiSpawnDetachedProcess ( "CA Repeater", "caRepeater" ); + if ( osptr == osiSpawnDetachedProcessNoSupport ) { + epicsThreadId tid; + + tid = epicsThreadCreate ( "CAC-repeater", epicsThreadPriorityLow, + epicsThreadGetStackSize ( epicsThreadStackMedium ), caRepeaterThread, 0); + if ( tid == 0 ) { + fprintf ( stderr, "caStartRepeaterIfNotInstalled : unable to create CA repeater daemon thread\n" ); + } + } + else if ( osptr == osiSpawnDetachedProcessFail ) { + fprintf ( stderr, "caStartRepeaterIfNotInstalled (): unable to start CA repeater daemon detached process\n" ); + } + } +} + +bool udpiiu::badUDPRespAction ( + const caHdr &msg, const osiSockAddr &netAddr, const epicsTime ¤tTime ) +{ + char buf[64]; + sockAddrToDottedIP ( &netAddr.sa, buf, sizeof ( buf ) ); + char date[64]; + currentTime.strftime ( date, sizeof ( date ), "%a %b %d %Y %H:%M:%S"); + errlogPrintf ( "CAC: Undecipherable ( bad msg code %u ) UDP message from %s at %s\n", + msg.m_cmmd, buf, date ); + return false; +} + +bool udpiiu::versionAction ( + const caHdr & hdr, const osiSockAddr &, const epicsTime & /* currentTime */ ) +{ + epicsGuard < epicsMutex > guard ( this->cacMutex ); + + // update the round trip time estimate + if ( hdr.m_dataType & sequenceNoIsValid ) { + this->lastReceivedSeqNo = hdr.m_cid; + this->lastReceivedSeqNoIsValid = true; + } + + return true; +} + +bool udpiiu :: searchRespAction ( + const caHdr & msg, const osiSockAddr & addr, + const epicsTime & currentTime ) +{ + /* + * we dont currently know what to do with channel's + * found to be at non-IP type addresses + */ + if ( addr.sa.sa_family != AF_INET ) { + return true; + } + + /* + * Starting with CA V4.1 the minor version number + * is appended to the end of each UDP search reply. + * This value is ignored by earlier clients. + */ + ca_uint32_t minorVersion; + if ( msg.m_postsize >= sizeof ( minorVersion ) ){ + /* + * care is taken here not to break gcc 3.2 aggressive alias + * analysis rules + */ + const ca_uint8_t * pPayLoad = + reinterpret_cast < const ca_uint8_t *> ( & msg + 1 ); + unsigned byte0 = pPayLoad[0]; + unsigned byte1 = pPayLoad[1]; + minorVersion = ( byte0 << 8u ) | byte1; + } + else { + minorVersion = CA_UKN_MINOR_VERSION; + } + + /* + * the type field is abused to carry the port number + * so that we can have multiple servers on one host + */ + osiSockAddr serverAddr; + serverAddr.ia.sin_family = AF_INET; + if ( CA_V48 ( minorVersion ) ) { + if ( msg.m_cid != INADDR_BROADCAST ) { + serverAddr.ia.sin_addr.s_addr = htonl ( msg.m_cid ); + } + else { + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + serverAddr.ia.sin_port = htons ( msg.m_dataType ); + } + else if ( CA_V45 (minorVersion) ) { + serverAddr.ia.sin_port = htons ( msg.m_dataType ); + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + else { + serverAddr.ia.sin_port = htons ( this->serverPort ); + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + + if ( CA_V42 ( minorVersion ) ) { + cacRef.transferChanToVirtCircuit + ( msg.m_available, msg.m_cid, 0xffff, + 0, minorVersion, serverAddr, currentTime ); + } + else { + cacRef.transferChanToVirtCircuit + ( msg.m_available, msg.m_cid, msg.m_dataType, + msg.m_count, minorVersion, serverAddr, currentTime ); + } + + return true; +} + +bool udpiiu::beaconAction ( + const caHdr & msg, + const osiSockAddr & net_addr, const epicsTime & currentTime ) +{ + struct sockaddr_in ina; + + memset(&ina, 0, sizeof(struct sockaddr_in)); + + if ( net_addr.sa.sa_family != AF_INET ) { + return false; + } + + /* + * this allows a fan-out server to potentially + * insert the true address of the CA server + * + * old servers: + * 1) set this field to one of the ip addresses of the host _or_ + * 2) set this field to INADDR_ANY + * new servers: + * always set this field to INADDR_ANY + * + * clients always assume that if this + * field is set to something that isnt INADDR_ANY + * then it is the overriding IP address of the server. + */ + ina.sin_family = AF_INET; + ina.sin_addr.s_addr = htonl ( msg.m_available ); + if ( msg.m_count != 0 ) { + ina.sin_port = htons ( msg.m_count ); + } + else { + /* + * old servers dont supply this and the + * default port must be assumed + */ + ina.sin_port = htons ( this->serverPort ); + } + unsigned protocolRevision = msg.m_dataType; + ca_uint32_t beaconNumber = msg.m_cid; + + this->cacRef.beaconNotify ( ina, currentTime, + beaconNumber, protocolRevision ); + + return true; +} + +bool udpiiu::repeaterAckAction ( + const caHdr &, + const osiSockAddr &, const epicsTime &) +{ + this->repeaterSubscribeTmr.confirmNotify (); + return true; +} + +bool udpiiu::notHereRespAction ( + const caHdr &, + const osiSockAddr &, const epicsTime & ) +{ + return true; +} + +bool udpiiu::exceptionRespAction ( + const caHdr &msg, + const osiSockAddr & net_addr, const epicsTime & currentTime ) +{ + const caHdr &reqMsg = * ( &msg + 1 ); + char name[64]; + sockAddrToDottedIP ( &net_addr.sa, name, sizeof ( name ) ); + char date[64]; + currentTime.strftime ( date, sizeof ( date ), "%a %b %d %Y %H:%M:%S"); + + if ( msg.m_postsize > sizeof ( caHdr ) ){ + errlogPrintf ( + "error condition \"%s\" detected by %s with context \"%s\" at %s\n", + ca_message ( msg.m_available ), + name, reinterpret_cast ( &reqMsg + 1 ), date ); + } + else{ + errlogPrintf ( + "error condition \"%s\" detected by %s at %s\n", + ca_message ( msg.m_available ), name, date ); + } + + return true; +} + +void udpiiu::postMsg ( + const osiSockAddr & net_addr, + char * pInBuf, arrayElementCount blockSize, + const epicsTime & currentTime ) +{ + caHdr *pCurMsg; + + this->lastReceivedSeqNoIsValid = false; + this->lastReceivedSeqNo = 0u; + + while ( blockSize ) { + arrayElementCount size; + + if ( blockSize < sizeof ( *pCurMsg ) ) { + char buf[64]; + sockAddrToDottedIP ( &net_addr.sa, buf, sizeof ( buf ) ); + errlogPrintf ( + "%s: Undecipherable (too small) UDP msg from %s ignored\n", + __FILE__, buf ); + return; + } + + pCurMsg = reinterpret_cast < caHdr * > ( pInBuf ); + + /* + * fix endian of bytes + */ + pCurMsg->m_postsize = AlignedWireRef < epicsUInt16 > ( pCurMsg->m_postsize ); + pCurMsg->m_cmmd = AlignedWireRef < epicsUInt16 > ( pCurMsg->m_cmmd ); + pCurMsg->m_dataType = AlignedWireRef < epicsUInt16 > ( pCurMsg->m_dataType ); + pCurMsg->m_count = AlignedWireRef < epicsUInt16 > ( pCurMsg->m_count ); + pCurMsg->m_available = AlignedWireRef < epicsUInt32 > ( pCurMsg->m_available ); + pCurMsg->m_cid = AlignedWireRef < epicsUInt32 > ( pCurMsg->m_cid ); + +#if 0 + printf ( "UDP Cmd=%3d Type=%3d Count=%4d Size=%4d", + pCurMsg->m_cmmd, + pCurMsg->m_dataType, + pCurMsg->m_count, + pCurMsg->m_postsize ); + printf (" Avail=%8x Cid=%6d\n", + pCurMsg->m_available, + pCurMsg->m_cid ); +#endif + + size = pCurMsg->m_postsize + sizeof ( *pCurMsg ); + + /* + * dont allow msg body extending beyond frame boundary + */ + if ( size > blockSize ) { + char buf[64]; + sockAddrToDottedIP ( &net_addr.sa, buf, sizeof ( buf ) ); + errlogPrintf ( + "%s: Undecipherable (payload too small) UDP msg from %s ignored\n", + __FILE__, buf ); + return; + } + + /* + * execute the response message + */ + pProtoStubUDP pStub; + if ( pCurMsg->m_cmmd < NELEMENTS ( udpJumpTableCAC ) ) { + pStub = udpJumpTableCAC [pCurMsg->m_cmmd]; + } + else { + pStub = &udpiiu::badUDPRespAction; + } + bool success = ( this->*pStub ) ( *pCurMsg, net_addr, currentTime ); + if ( ! success ) { + char buf[256]; + sockAddrToDottedIP ( &net_addr.sa, buf, sizeof ( buf ) ); + errlogPrintf ( "CAC: Undecipherable UDP message from %s\n", buf ); + return; + } + + blockSize -= size; + pInBuf += size;; + } +} + +bool udpiiu::pushVersionMsg () +{ + epicsGuard < epicsMutex > guard ( this->cacMutex ); + + this->sequenceNumber++; + + caHdr msg; + AlignedWireRef < epicsUInt16 > ( msg.m_cmmd ) = CA_PROTO_VERSION; + AlignedWireRef < epicsUInt32 > ( msg.m_available ) = 0; + AlignedWireRef < epicsUInt16 > ( msg.m_dataType ) = sequenceNoIsValid; + AlignedWireRef < epicsUInt16 > ( msg.m_count ) = CA_MINOR_PROTOCOL_REVISION; + AlignedWireRef < epicsUInt32 > ( msg.m_cid ) = this->sequenceNumber; // sequence number + + return this->pushDatagramMsg ( guard, msg, 0, 0 ); +} + +bool udpiiu::pushDatagramMsg ( epicsGuard < epicsMutex > & guard, + const caHdr & msg, const void * pExt, ca_uint16_t extsize ) +{ + guard.assertIdenticalMutex ( this->cacMutex ); + + ca_uint16_t alignedExtSize = static_cast (CA_MESSAGE_ALIGN ( extsize )); + arrayElementCount msgsize = sizeof ( caHdr ) + alignedExtSize; + + /* fail out if max message size exceeded */ + if ( msgsize >= sizeof ( this->xmitBuf ) - 7 ) { + return false; + } + + if ( msgsize + this->nBytesInXmitBuf > sizeof ( this->xmitBuf ) ) { + return false; + } + + caHdr * pbufmsg = ( caHdr * ) &this->xmitBuf[this->nBytesInXmitBuf]; + *pbufmsg = msg; + if ( extsize ) { + memcpy ( pbufmsg + 1, pExt, extsize ); + if ( extsize != alignedExtSize ) { + char *pDest = (char *) ( pbufmsg + 1 ); + memset ( pDest + extsize, '\0', alignedExtSize - extsize ); + } + } + AlignedWireRef < epicsUInt16 > ( pbufmsg->m_postsize ) = alignedExtSize; + this->nBytesInXmitBuf += msgsize; + + return true; +} + +udpiiu :: SearchDestUDP :: SearchDestUDP ( + const osiSockAddr & destAddr, udpiiu & udpiiuIn ) : + _destAddr ( destAddr ), _udpiiu ( udpiiuIn ) +{ +} + +void udpiiu :: SearchDestUDP :: searchRequest ( + epicsGuard < epicsMutex > & guard, const char * pBuf, size_t bufSize ) +{ + guard.assertIdenticalMutex ( _udpiiu.cacMutex ); + assert ( bufSize <= INT_MAX ); + int bufSizeAsInt = static_cast < int > ( bufSize ); + while ( true ) { + // This const_cast is needed for vxWorks: + int status = sendto ( _udpiiu.sock, const_cast(pBuf), bufSizeAsInt, 0, + & _destAddr.sa, sizeof ( _destAddr.sa ) ); + if ( status == bufSizeAsInt ) { + break; + } + if ( status >= 0 ) { + errlogPrintf ( "CAC: UDP sendto () call returned strange xmit count?\n" ); + break; + } + else { + int localErrno = SOCKERRNO; + + if ( localErrno == SOCK_EINTR ) { + if ( _udpiiu.shutdownCmd ) { + break; + } + else { + continue; + } + } + else if ( localErrno == SOCK_SHUTDOWN ) { + break; + } + else if ( localErrno == SOCK_ENOTSOCK ) { + break; + } + else if ( localErrno == SOCK_EBADF ) { + break; + } + else { + char sockErrBuf[64]; + epicsSocketConvertErrnoToString ( + sockErrBuf, sizeof ( sockErrBuf ) ); + char buf[64]; + sockAddrToDottedIP ( &_destAddr.sa, buf, sizeof ( buf ) ); + errlogPrintf ( + "CAC: error = \"%s\" sending UDP msg to %s\n", + sockErrBuf, buf); + break; + } + } + } +} + +void udpiiu :: SearchDestUDP :: show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( _udpiiu.cacMutex ); + char buf[64]; + sockAddrToDottedIP ( &_destAddr.sa, buf, sizeof ( buf ) ); + :: printf ( "UDP Search destination \"%s\"\n", buf ); +} + +udpiiu :: SearchRespCallback :: SearchRespCallback ( udpiiu & udpiiuIn ) : + _udpiiu ( udpiiuIn ) +{ +} + +void udpiiu :: SearchRespCallback :: notify ( + const caHdr & msg, const void * pPayloadUntyped, + const osiSockAddr & addr, const epicsTime & currentTime ) +{ + /* + * we dont currently know what to do with channel's + * found to be at non-IP type addresses + */ + if ( addr.sa.sa_family != AF_INET ) { + return; + } + + /* + * 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. + */ + ca_uint32_t minorVersion; + if ( msg.m_postsize >= sizeof ( minorVersion ) ){ + /* + * care is taken here not to break gcc 3.2 aggressive alias + * analysis rules + */ + const ca_uint8_t * pPayLoad = reinterpret_cast < const ca_uint8_t *> ( pPayloadUntyped ); + unsigned byte0 = pPayLoad[0]; + unsigned byte1 = pPayLoad[1]; + minorVersion = ( byte0 << 8u ) | byte1; + } + else { + minorVersion = CA_UKN_MINOR_VERSION; + } + + /* + * the type field is abused to carry the port number + * so that we can have multiple servers on one host + */ + osiSockAddr serverAddr; + serverAddr.ia.sin_family = AF_INET; + if ( CA_V48 ( minorVersion ) ) { + if ( msg.m_cid != INADDR_BROADCAST ) { + serverAddr.ia.sin_addr.s_addr = htonl ( msg.m_cid ); + } + else { + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + serverAddr.ia.sin_port = htons ( msg.m_dataType ); + } + else if ( CA_V45 (minorVersion) ) { + serverAddr.ia.sin_port = htons ( msg.m_dataType ); + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + else { + serverAddr.ia.sin_port = htons ( _udpiiu.serverPort ); + serverAddr.ia.sin_addr = addr.ia.sin_addr; + } + + if ( CA_V42 ( minorVersion ) ) { + _udpiiu.cacRef.transferChanToVirtCircuit + ( msg.m_available, msg.m_cid, 0xffff, + 0, minorVersion, serverAddr, currentTime ); + } + else { + _udpiiu.cacRef.transferChanToVirtCircuit + ( msg.m_available, msg.m_cid, msg.m_dataType, + msg.m_count, minorVersion, serverAddr, currentTime ); + } +} + +void udpiiu :: SearchRespCallback :: show ( + epicsGuard < epicsMutex > & guard, unsigned level ) const +{ + guard.assertIdenticalMutex ( _udpiiu.cacMutex ); + ::printf ( "udpiiu :: SearchRespCallback\n" ); +} + +bool udpiiu :: datagramFlush ( + epicsGuard < epicsMutex > & guard, const epicsTime & currentTime ) +{ + guard.assertIdenticalMutex ( cacMutex ); + + // dont send the version header by itself + if ( this->nBytesInXmitBuf <= sizeof ( caHdr ) ) { + return false; + } + + tsDLIter < SearchDest > iter ( _searchDestList.firstIter () ); + while ( iter.valid () ) + { + iter->searchRequest ( guard, this->xmitBuf, this->nBytesInXmitBuf ); + iter++; + } + + this->nBytesInXmitBuf = 0u; + + this->pushVersionMsg (); + + return true; +} + +void udpiiu :: show ( unsigned level ) const +{ + epicsGuard < epicsMutex > guard ( this->cacMutex ); + + ::printf ( "Datagram IO circuit (and disconnected channel repository)\n"); + if ( level > 1u ) { + ::printf ("\trepeater port %u\n", this->repeaterPort ); + ::printf ("\tdefault server port %u\n", this->serverPort ); + ::printf ( "Search Destination List with %u items\n", + _searchDestList.count () ); + if ( level > 2u ) { + tsDLIterConst < SearchDest > iter ( + _searchDestList.firstIter () ); + while ( iter.valid () ) + { + iter->show ( guard, level - 2 ); + iter++; + } + } + } + if ( level > 2u ) { + ::printf ("\tsocket identifier %d\n", this->sock ); + ::printf ("\tbytes in xmit buffer %u\n", this->nBytesInXmitBuf ); + ::printf ("\tshut down command bool %u\n", this->shutdownCmd ); + ::printf ( "\trecv thread exit signal:\n" ); + this->recvThread.show ( level - 2u ); + this->repeaterSubscribeTmr.show ( level - 2u ); + this->govTmr.show ( level - 2u ); + } + if ( level > 3u ) { + for ( unsigned i =0; i < this->nTimers; i++ ) { + this->ppSearchTmr[i]->show ( level - 3u ); + } + } +} + +bool udpiiu::wakeupMsg () +{ + caHdr msg; + AlignedWireRef < epicsUInt16 > ( msg.m_cmmd ) = CA_PROTO_VERSION; + AlignedWireRef < epicsUInt32 > ( msg.m_available ) = 0u; + AlignedWireRef < epicsUInt16 > ( msg.m_dataType ) = 0u; + AlignedWireRef < epicsUInt16 > ( msg.m_count ) = 0u; + AlignedWireRef < epicsUInt32 > ( msg.m_cid ) = 0u; + AlignedWireRef < epicsUInt16 > ( msg.m_postsize ) = 0u; + + osiSockAddr addr; + addr.ia.sin_family = AF_INET; + addr.ia.sin_addr.s_addr = htonl ( INADDR_LOOPBACK ); + addr.ia.sin_port = htons ( this->localPort ); + + // send a wakeup msg so the UDP recv thread will exit + int status = sendto ( this->sock, reinterpret_cast < char * > ( &msg ), + sizeof (msg), 0, &addr.sa, sizeof ( addr.sa ) ); + if ( status == sizeof (msg) ) { + return true; + } + return false; +} + +void udpiiu::beaconAnomalyNotify ( + epicsGuard < epicsMutex > & cacGuard ) +{ + for ( unsigned i = this->beaconAnomalyTimerIndex+1u; + i < this->nTimers; i++ ) { + this->ppSearchTmr[i]->moveChannels ( cacGuard, + *this->ppSearchTmr[this->beaconAnomalyTimerIndex] ); + } +} + +void udpiiu::uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > & guard, nciu & chan, + const epicsTime & currentTime ) +{ + channelNode::channelState chanState = + chan.channelNode::listMember; + if ( chanState == channelNode::cs_disconnGov ) { + this->govTmr.uninstallChan ( guard, chan ); + } + else { + this->ppSearchTmr[ chan.getSearchTimerIndex ( guard ) ]-> + uninstallChanDueToSuccessfulSearchResponse ( + guard, chan, this->lastReceivedSeqNo, + this->lastReceivedSeqNoIsValid, currentTime ); + } +} + +void udpiiu::uninstallChan ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + channelNode::channelState chanState = + chan.channelNode::listMember; + if ( chanState == channelNode::cs_disconnGov ) { + this->govTmr.uninstallChan ( guard, chan ); + } + else { + this->ppSearchTmr[ chan.getSearchTimerIndex ( guard ) ]-> + uninstallChan ( guard, chan ); + } +} + +bool udpiiu::searchMsg ( + epicsGuard < epicsMutex > & guard, ca_uint32_t id, + const char * pName, unsigned nameLength ) +{ + caHdr msg; + AlignedWireRef < epicsUInt16 > ( msg.m_cmmd ) = CA_PROTO_SEARCH; + AlignedWireRef < epicsUInt32 > ( msg.m_available ) = id; + AlignedWireRef < epicsUInt16 > ( msg.m_dataType ) = DONTREPLY; + AlignedWireRef < epicsUInt16 > ( msg.m_count ) = CA_MINOR_PROTOCOL_REVISION; + AlignedWireRef < epicsUInt32 > ( msg.m_cid ) = id; + return this->pushDatagramMsg ( + guard, msg, pName, (ca_uint16_t) nameLength ); +} + +void udpiiu::installNewChannel ( + epicsGuard < epicsMutex > & guard, nciu & chan, netiiu * & piiu ) +{ + piiu = this; + this->ppSearchTmr[0]->installChannel ( guard, chan ); +} + +void udpiiu::installDisconnectedChannel ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + chan.setServerAddressUnknown ( *this, guard ); + this->govTmr.installChan ( guard, chan ); +} + +void udpiiu::noSearchRespNotify ( + epicsGuard < epicsMutex > & guard, nciu & chan, unsigned index ) +{ + const unsigned nTimersMinusOne = this->nTimers - 1; + if ( index < nTimersMinusOne ) { + index++; + } + else { + index = nTimersMinusOne; + } + this->ppSearchTmr[index]->installChannel ( guard, chan ); +} + +void udpiiu::boostChannel ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + this->ppSearchTmr[this->beaconAnomalyTimerIndex]-> + installChannel ( guard, chan ); +} + +void udpiiu::govExpireNotify ( + epicsGuard < epicsMutex > & guard, nciu & chan ) +{ + this->ppSearchTmr[0]->installChannel ( guard, chan ); +} + +int udpiiu :: M_repeaterTimerNotify :: printFormated ( + epicsGuard < epicsMutex > & cbGuard, + const char * pformat, ... ) +{ + va_list theArgs; + int status; + + va_start ( theArgs, pformat ); + + status = m_udpiiu.cacRef.varArgsPrintFormated ( cbGuard, pformat, theArgs ); + + va_end ( theArgs ); + + return status; +} + +void udpiiu::updateRTTE ( epicsGuard < epicsMutex > & guard, double measured ) +{ + guard.assertIdenticalMutex ( this->cacMutex ); + if ( measured > maxRoundTripEstimate ) { + measured = maxRoundTripEstimate; + } + if ( measured < minRoundTripEstimate ) { + measured = minRoundTripEstimate; + } + double error = measured - this->rtteMean; + this->rtteMean += 0.125 * error; + if ( error < 0.0 ) { + error = - error; + } + this->rtteMeanDev = this->rtteMeanDev + .25 * ( error - this->rtteMeanDev ); +} + +double udpiiu::getRTTE ( epicsGuard < epicsMutex > & guard ) const +{ + guard.assertIdenticalMutex ( this->cacMutex ); + return this->rtteMean + 4 * this->rtteMeanDev; +} + +unsigned udpiiu::getHostName ( + epicsGuard < epicsMutex > & cacGuard, + char *pBuf, unsigned bufLength ) const throw () +{ + return netiiu::getHostName ( cacGuard, pBuf, bufLength ); +} + +const char * udpiiu::pHostName ( + epicsGuard < epicsMutex > & cacGuard ) const throw () +{ + return netiiu::pHostName ( cacGuard ); +} + +bool udpiiu::ca_v42_ok ( + epicsGuard < epicsMutex > & cacGuard ) const +{ + return netiiu::ca_v42_ok ( cacGuard ); +} + +bool udpiiu::ca_v41_ok ( + epicsGuard < epicsMutex > & cacGuard ) const +{ + return netiiu::ca_v41_ok ( cacGuard ); +} + +void udpiiu::writeRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, unsigned type, + arrayElementCount nElem, const void * pValue ) +{ + netiiu::writeRequest ( guard, chan, type, nElem, pValue ); +} + +void udpiiu::writeNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netWriteNotifyIO & io, unsigned type, + arrayElementCount nElem, const void *pValue ) +{ + netiiu::writeNotifyRequest ( guard, chan, io, type, nElem, pValue ); +} + +void udpiiu::readNotifyRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netReadNotifyIO & io, unsigned type, arrayElementCount nElem ) +{ + netiiu::readNotifyRequest ( guard, chan, io, type, nElem ); +} + +void udpiiu::clearChannelRequest ( + epicsGuard < epicsMutex > & guard, + ca_uint32_t sid, ca_uint32_t cid ) +{ + netiiu::clearChannelRequest ( guard, sid, cid ); +} + +void udpiiu::subscriptionRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netSubscription & subscr ) +{ + netiiu::subscriptionRequest ( guard, chan, subscr ); +} + +void udpiiu::subscriptionUpdateRequest ( + epicsGuard < epicsMutex > & guard, nciu & chan, + netSubscription & subscr ) +{ + netiiu::subscriptionUpdateRequest ( + guard, chan, subscr ); +} + +void udpiiu::subscriptionCancelRequest ( + epicsGuard < epicsMutex > & guard, + nciu & chan, netSubscription & subscr ) +{ + netiiu::subscriptionCancelRequest ( guard, chan, subscr ); +} + +void udpiiu::flushRequest ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::flushRequest ( guard ); +} + +unsigned udpiiu::requestMessageBytesPending ( + epicsGuard < epicsMutex > & guard ) +{ + return netiiu::requestMessageBytesPending ( guard ); +} + +void udpiiu::flush ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::flush ( guard ); +} + +void udpiiu::requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & guard ) +{ + netiiu::requestRecvProcessPostponedFlush ( guard ); +} + +osiSockAddr udpiiu::getNetworkAddress ( + epicsGuard < epicsMutex > & guard ) const +{ + return netiiu::getNetworkAddress ( guard ); +} + +double udpiiu::receiveWatchdogDelay ( + epicsGuard < epicsMutex > & guard ) const +{ + return netiiu::receiveWatchdogDelay ( guard ); +} + +ca_uint32_t udpiiu::datagramSeqNumber ( + epicsGuard < epicsMutex > & ) const +{ + return this->sequenceNumber; +} + diff --git a/modules/ca/src/client/udpiiu.h b/modules/ca/src/client/udpiiu.h new file mode 100644 index 000000000..6b41e38ed --- /dev/null +++ b/modules/ca/src/client/udpiiu.h @@ -0,0 +1,317 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef udpiiuh +#define udpiiuh + +#include + +#ifdef epicsExportSharedSymbols +# define udpiiuh_accessh_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include "osiSock.h" +#include "epicsThread.h" +#include "epicsTime.h" +#include "tsDLList.h" + +#ifdef udpiiuh_accessh_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include "netiiu.h" +#include "searchTimer.h" +#include "disconnectGovernorTimer.h" +#include "repeaterSubscribeTimer.h" +#include "SearchDest.h" + +extern "C" void cacRecvThreadUDP ( void *pParam ); + +epicsShareFunc void epicsShareAPI caStartRepeaterIfNotInstalled ( + unsigned repeaterPort ); +epicsShareFunc void epicsShareAPI caRepeaterRegistrationMessage ( + SOCKET sock, unsigned repeaterPort, unsigned attemptNumber ); +extern "C" epicsShareFunc void caRepeaterThread ( + void * pDummy ); +epicsShareFunc void ca_repeater ( void ); + +class cac; +class cacContextNotify; + +class udpRecvThread : + private epicsThreadRunable { +public: + udpRecvThread ( + class udpiiu & iiuIn, cacContextNotify &, epicsMutex &, + const char * pName, unsigned stackSize, unsigned priority ); + virtual ~udpRecvThread (); + void start (); + bool exitWait ( double delay ); + void show ( unsigned level ) const; +private: + class udpiiu & iiu; + epicsMutex & cbMutex; + cacContextNotify & ctxNotify; + epicsThread thread; + void run(); +}; + +static const double minRoundTripEstimate = 32e-3; // seconds +static const double maxRoundTripEstimate = 30; // seconds +static const double maxSearchPeriodDefault = 5.0 * 60.0; // seconds +static const double maxSearchPeriodLowerLimit = 60.0; // seconds +static const double beaconAnomalySearchPeriod = 5.0; // seconds + +class udpiiu : + private netiiu, + private searchTimerNotify, + private disconnectGovernorNotify { +public: + udpiiu ( + epicsGuard < epicsMutex > & cacGuard, + class epicsTimerQueueActive &, + epicsMutex & callbackControl, + epicsMutex & mutualExclusion, + cacContextNotify &, + class cac &, + unsigned port, + tsDLList < SearchDest > & ); + virtual ~udpiiu (); + void installNewChannel ( + epicsGuard < epicsMutex > &, nciu &, netiiu * & ); + void installDisconnectedChannel ( + epicsGuard < epicsMutex > &, nciu & ); + void beaconAnomalyNotify ( + epicsGuard < epicsMutex > & guard ); + void shutdown ( epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void show ( unsigned level ) const; + + // exceptions + class noSocket {}; + +private: + class SearchDestUDP : + public SearchDest { + public: + SearchDestUDP ( const osiSockAddr &, udpiiu & ); + void searchRequest ( + epicsGuard < epicsMutex > &, const char * pBuf, size_t bufLen ); + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; + private: + osiSockAddr _destAddr; + udpiiu & _udpiiu; + }; + class SearchRespCallback : + public SearchDest :: Callback { + public: + SearchRespCallback ( udpiiu & ); + void notify ( + const caHdr &, const void * pPayload, + const osiSockAddr &, const epicsTime & ); + void show ( + epicsGuard < epicsMutex > &, unsigned level ) const; + private: + udpiiu & _udpiiu; + }; + class M_repeaterTimerNotify : + public repeaterTimerNotify { + public: + M_repeaterTimerNotify ( udpiiu & iiu ) : + m_udpiiu ( iiu ) {} + ~M_repeaterTimerNotify (); /* for sunpro compiler */ + // repeaterTimerNotify + void repeaterRegistrationMessage ( + unsigned attemptNumber ); + int printFormated ( + epicsGuard < epicsMutex > & callbackControl, + const char * pformat, ... ); + private: + udpiiu & m_udpiiu; + }; + char xmitBuf [MAX_UDP_SEND]; + char recvBuf [MAX_UDP_RECV]; + udpRecvThread recvThread; + M_repeaterTimerNotify m_repeaterTimerNotify; + repeaterSubscribeTimer repeaterSubscribeTmr; + disconnectGovernorTimer govTmr; + tsDLList < SearchDest > _searchDestList; + const double maxPeriod; + double rtteMean; + double rtteMeanDev; + cac & cacRef; + epicsMutex & cbMutex; + epicsMutex & cacMutex; + const unsigned nTimers; + struct SearchArray { + typedef std::auto_ptr value_type; + value_type *arr; + SearchArray(size_t n) : arr(new value_type[n]) {} + ~SearchArray() { delete[] arr; } + value_type& operator[](size_t i) const { return arr[i]; } + private: + SearchArray(const SearchArray&); + SearchArray& operator=(const SearchArray&); + } ppSearchTmr; + unsigned nBytesInXmitBuf; + unsigned beaconAnomalyTimerIndex; + ca_uint32_t sequenceNumber; + ca_uint32_t lastReceivedSeqNo; + SOCKET sock; + ca_uint16_t repeaterPort; + ca_uint16_t serverPort; + ca_uint16_t localPort; + bool shutdownCmd; + bool lastReceivedSeqNoIsValid; + + bool wakeupMsg (); + + void postMsg ( + const osiSockAddr & net_addr, + char *pInBuf, arrayElementCount blockSize, + const epicsTime ¤Time ); + + bool pushDatagramMsg ( epicsGuard < epicsMutex > &, + const caHdr & hdr, const void * pExt, + ca_uint16_t extsize); + + typedef bool ( udpiiu::*pProtoStubUDP ) ( + const caHdr &, + const osiSockAddr &, const epicsTime & ); + + // UDP protocol dispatch table + static const pProtoStubUDP udpJumpTableCAC[]; + + // UDP protocol stubs + bool versionAction ( + const caHdr &, + const osiSockAddr &, const epicsTime & ); + bool badUDPRespAction ( + const caHdr &msg, + const osiSockAddr &netAddr, const epicsTime & ); + bool searchRespAction ( + const caHdr &msg, + const osiSockAddr &net_addr, const epicsTime & ); + bool exceptionRespAction ( + const caHdr &msg, + const osiSockAddr &net_addr, const epicsTime & ); + bool beaconAction ( + const caHdr &msg, + const osiSockAddr &net_addr, const epicsTime & ); + bool notHereRespAction ( + const caHdr &msg, + const osiSockAddr &net_addr, const epicsTime & ); + bool repeaterAckAction ( + const caHdr &msg, + const osiSockAddr &net_addr, const epicsTime & ); + + // netiiu stubs + unsigned getHostName ( + epicsGuard < epicsMutex > &, char * pBuf, + unsigned bufLength ) const throw (); + const char * pHostName ( + epicsGuard < epicsMutex > & ) const throw (); + bool ca_v41_ok ( + epicsGuard < epicsMutex > & ) const; + bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const; + unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void writeRequest ( + epicsGuard < epicsMutex > &, nciu &, + unsigned type, arrayElementCount nElem, + const void *pValue ); + void writeNotifyRequest ( + epicsGuard < epicsMutex > &, + nciu &, netWriteNotifyIO &, + unsigned type, arrayElementCount nElem, + const void *pValue ); + void readNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, + netReadNotifyIO &, unsigned type, + arrayElementCount nElem ); + void clearChannelRequest ( + epicsGuard < epicsMutex > &, + ca_uint32_t sid, ca_uint32_t cid ); + void subscriptionRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ); + void subscriptionUpdateRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & ); + void subscriptionCancelRequest ( + epicsGuard < epicsMutex > &, + nciu & chan, netSubscription & subscr ); + void flushRequest ( + epicsGuard < epicsMutex > & ); + void requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & ); + osiSockAddr getNetworkAddress ( + epicsGuard < epicsMutex > & ) const; + void uninstallChan ( + epicsGuard < epicsMutex > &, nciu & ); + void uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, + const class epicsTime & currentTime ); + double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const; + bool searchMsg ( + epicsGuard < epicsMutex > &, ca_uint32_t id, + const char * pName, unsigned nameLength ); + + // searchTimerNotify stubs + double getRTTE ( epicsGuard < epicsMutex > & ) const; + void updateRTTE ( epicsGuard < epicsMutex > &, double rtte ); + bool pushVersionMsg (); + void boostChannel ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + void noSearchRespNotify ( + epicsGuard < epicsMutex > &, nciu & chan, unsigned index ); + bool datagramFlush ( + epicsGuard < epicsMutex > &, const epicsTime & currentTime ); + ca_uint32_t datagramSeqNumber ( + epicsGuard < epicsMutex > & ) const; + + // disconnectGovernorNotify + void govExpireNotify ( + epicsGuard < epicsMutex > &, nciu & ); + + udpiiu ( const udpiiu & ); + udpiiu & operator = ( const udpiiu & ); + + friend class udpRecvThread; + + // These are needed for the vxWorks 5.5 compiler: + friend class udpiiu::SearchDestUDP; + friend class udpiiu::SearchRespCallback; + friend class udpiiu::M_repeaterTimerNotify; +}; + +#endif // udpiiuh + diff --git a/modules/ca/src/client/virtualCircuit.h b/modules/ca/src/client/virtualCircuit.h new file mode 100644 index 000000000..d06d87c60 --- /dev/null +++ b/modules/ca/src/client/virtualCircuit.h @@ -0,0 +1,421 @@ +/*************************************************************************\ +* 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. +\*************************************************************************/ + +/* + * + * + * L O S A L A M O S + * Los Alamos National Laboratory + * Los Alamos, New Mexico 87545 + * + * Copyright, 1986, The Regents of the University of California. + * + * + * Author Jeffrey O. Hill + * johill@lanl.gov + * 505 665 1831 + */ + +#ifndef virtualCircuith +#define virtualCircuith + +#include "tsDLList.h" + +#include "comBuf.h" +#include "caServerID.h" +#include "netiiu.h" +#include "comQueSend.h" +#include "comQueRecv.h" +#include "tcpRecvWatchdog.h" +#include "tcpSendWatchdog.h" +#include "hostNameCache.h" +#include "SearchDest.h" +#include "compilerDependencies.h" + +class callbackManager; + +// a modified ca header with capacity for large arrays +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 +}; + +class ipAddrToAsciiEngine; + +class tcpRecvThread : private epicsThreadRunable { +public: + tcpRecvThread ( + class tcpiiu & iiuIn, epicsMutex & cbMutexIn, cacContextNotify &, + const char * pName, unsigned int stackSize, unsigned int priority ); + virtual ~tcpRecvThread (); + void start (); + void exitWait (); + bool exitWait ( double delay ); + void interruptSocketRecv (); + void show ( unsigned level ) const; +private: + epicsThread thread; + class tcpiiu & iiu; + epicsMutex & cbMutex; + cacContextNotify & ctxNotify; + void run (); + void connect ( + epicsGuard < epicsMutex > & guard ); + bool validFillStatus ( + epicsGuard < epicsMutex > & guard, + const statusWireIO & stat ); +}; + +class tcpSendThread : private epicsThreadRunable { +public: + tcpSendThread ( + class tcpiiu & iiuIn, const char * pName, + unsigned int stackSize, unsigned int priority ); + virtual ~tcpSendThread (); + void start (); + void exitWait (); + void interruptSocketSend (); + void show ( unsigned level ) const; +private: + epicsThread thread; + class tcpiiu & iiu; + void run (); +}; + +class SearchDestTCP : public SearchDest { +public: + SearchDestTCP ( cac &, const osiSockAddr & ); + void searchRequest ( epicsGuard < epicsMutex > & guard, + const char * pbuf, size_t len ); + void show ( epicsGuard < epicsMutex > & guard, unsigned level ) const; + void setCircuit ( tcpiiu * ); + void disable (); + void enable (); +private: + tcpiiu * _ptcpiiu; + cac & _cac; + const osiSockAddr _addr; + bool _active; +}; + +class tcpiiu : + public netiiu, public tsDLNode < tcpiiu >, + public tsSLNode < tcpiiu >, public caServerID, + private wireSendAdapter, private wireRecvAdapter { + friend void SearchDestTCP::searchRequest ( epicsGuard < epicsMutex > & guard, + const char * pbuf, size_t len ); +public: + tcpiiu ( cac & cac, epicsMutex & mutualExclusion, epicsMutex & callbackControl, + cacContextNotify &, double connectionTimeout, epicsTimerQueue & timerQueue, + const osiSockAddr & addrIn, comBufMemoryManager &, unsigned minorVersion, + ipAddrToAsciiEngine & engineIn, const cacChannel::priLev & priorityIn, + SearchDestTCP * pSearchDestIn = NULL); + ~tcpiiu (); + void start ( + epicsGuard < epicsMutex > & ); + void responsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void sendTimeoutNotify ( + callbackManager & cbMgr, + epicsGuard < epicsMutex > & guard ); + void receiveTimeoutNotify( + callbackManager &, + epicsGuard < epicsMutex > & ); + void beaconAnomalyNotify ( + epicsGuard < epicsMutex > & ); + void beaconArrivalNotify ( + epicsGuard < epicsMutex > & ); + void probeResponseNotify ( + epicsGuard < epicsMutex > & ); + + void flushRequest ( + epicsGuard < epicsMutex > & ); + unsigned requestMessageBytesPending ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + void flush ( + epicsGuard < epicsMutex > & mutualExclusionGuard ); + + void show ( unsigned level ) const; + bool setEchoRequestPending ( + epicsGuard < epicsMutex > & ); + void requestRecvProcessPostponedFlush ( + epicsGuard < epicsMutex > & ); + void clearChannelRequest ( + epicsGuard < epicsMutex > &, + ca_uint32_t sid, ca_uint32_t cid ); + + bool ca_v41_ok ( + epicsGuard < epicsMutex > & ) const; + bool ca_v42_ok ( + epicsGuard < epicsMutex > & ) const; + bool ca_v44_ok ( + epicsGuard < epicsMutex > & ) const; + bool ca_v49_ok ( + epicsGuard < epicsMutex > & ) const; + + unsigned getHostName ( + epicsGuard < epicsMutex > &, + char *pBuf, unsigned bufLength ) const throw (); + bool alive ( + epicsGuard < epicsMutex > & ) const; + bool connecting ( + epicsGuard < epicsMutex > & ) const; + bool receiveThreadIsBusy ( + epicsGuard < epicsMutex > & ); + osiSockAddr getNetworkAddress ( + epicsGuard < epicsMutex > & ) const; + int printFormated ( + epicsGuard < epicsMutex > & cbGuard, + const char *pformat, ... ); + unsigned channelCount ( + epicsGuard < epicsMutex > & ); + void disconnectAllChannels ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard, class udpiiu & ); + void unlinkAllChannels ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void installChannel ( + epicsGuard < epicsMutex > &, nciu & chan, + unsigned sidIn, ca_uint16_t typeIn, arrayElementCount countIn ); + void uninstallChan ( + epicsGuard < epicsMutex > & guard, nciu & chan ); + bool connectNotify ( + epicsGuard < epicsMutex > &, nciu & chan ); + + void searchRespNotify ( + const epicsTime &, const caHdrLargeArray & ); + void versionRespNotify ( const caHdrLargeArray & ); + + void * operator new ( size_t size, + tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & ); + epicsPlacementDeleteOperator (( void *, + tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & )) + +private: + hostNameCache hostNameCacheInstance; + tcpRecvThread recvThread; + tcpSendThread sendThread; + tcpRecvWatchdog recvDog; + tcpSendWatchdog sendDog; + comQueSend sendQue; + comQueRecv recvQue; + // nciu state field tells us which list + // protected by the callback mutex + tsDLList < nciu > createReqPend; + tsDLList < nciu > createRespPend; + tsDLList < nciu > v42ConnCallbackPend; + tsDLList < nciu > subscripReqPend; + tsDLList < nciu > connectedList; + tsDLList < nciu > unrespCircuit; + tsDLList < nciu > subscripUpdateReqPend; + caHdrLargeArray curMsg; + arrayElementCount curDataMax; + arrayElementCount curDataBytes; + comBufMemoryManager & comBufMemMgr; + cac & cacRef; + char * pCurData; + SearchDestTCP * pSearchDest; + epicsMutex & mutex; + epicsMutex & cbMutex; + unsigned minorProtocolVersion; + enum iiu_conn_state { + iiucs_connecting, // pending circuit connect + iiucs_connected, // live circuit + iiucs_clean_shutdown, // live circuit will shutdown when flush completes + iiucs_disconnected, // socket informed us of disconnect + iiucs_abort_shutdown // socket has been closed + } state; + epicsEvent sendThreadFlushEvent; + epicsEvent flushBlockEvent; + SOCKET sock; + unsigned contigRecvMsgCount; + unsigned blockingForFlush; + unsigned socketLibrarySendBufferSize; + unsigned unacknowledgedSendBytes; + unsigned channelCountTot; + bool _receiveThreadIsBusy; + bool busyStateDetected; // only modified by the recv thread + bool flowControlActive; // only modified by the send process thread + bool echoRequestPending; + bool oldMsgHeaderAvailable; + bool msgHeaderAvailable; + bool earlyFlush; + bool recvProcessPostponedFlush; + bool discardingPendingData; + bool socketHasBeenClosed; + bool unresponsiveCircuit; + + bool processIncoming ( + const epicsTime & currentTime, callbackManager & ); + unsigned sendBytes ( const void *pBuf, + unsigned nBytesInBuf, const epicsTime & currentTime ); + void recvBytes ( + void * pBuf, unsigned nBytesInBuf, statusWireIO & ); + const char * pHostName ( + epicsGuard < epicsMutex > & ) const throw (); + double receiveWatchdogDelay ( + epicsGuard < epicsMutex > & ) const; + void unresponsiveCircuitNotify ( + epicsGuard < epicsMutex > & cbGuard, + epicsGuard < epicsMutex > & guard ); + void initiateCleanShutdown ( + epicsGuard < epicsMutex > & ); + void initiateAbortShutdown ( + epicsGuard < epicsMutex > & ); + void disconnectNotify ( + epicsGuard < epicsMutex > & ); + bool bytesArePendingInOS () const; + void decrementBlockingForFlushCount ( + epicsGuard < epicsMutex > & guard ); + bool isNameService () const; + + // send protocol stubs + void echoRequest ( + epicsGuard < epicsMutex > & ); + void versionMessage ( + epicsGuard < epicsMutex > &, const cacChannel::priLev & priority ); + void disableFlowControlRequest ( + epicsGuard < epicsMutex > & ); + void enableFlowControlRequest ( + epicsGuard < epicsMutex > & ); + void hostNameSetRequest ( + epicsGuard < epicsMutex > & ); + void userNameSetRequest ( + epicsGuard < epicsMutex > & ); + void createChannelRequest ( + nciu &, epicsGuard < epicsMutex > & ); + void writeRequest ( + epicsGuard < epicsMutex > &, nciu &, + unsigned type, arrayElementCount nElem, const void *pValue ); + void writeNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, + netWriteNotifyIO &, unsigned type, + arrayElementCount nElem, const void *pValue ); + void readNotifyRequest ( + epicsGuard < epicsMutex > &, nciu &, + netReadNotifyIO &, unsigned type, + arrayElementCount nElem ); + void subscriptionRequest ( + epicsGuard < epicsMutex > &, + nciu &, netSubscription & subscr ); + void subscriptionUpdateRequest ( + epicsGuard < epicsMutex > &, + nciu & chan, netSubscription & subscr ); + void subscriptionCancelRequest ( + epicsGuard < epicsMutex > &, + nciu & chan, netSubscription & subscr ); + void flushIfRecvProcessRequested ( + epicsGuard < epicsMutex > & ); + bool sendThreadFlush ( + epicsGuard < epicsMutex > & ); + + // netiiu stubs + void uninstallChanDueToSuccessfulSearchResponse ( + epicsGuard < epicsMutex > &, nciu &, const class epicsTime & ); + bool searchMsg ( + epicsGuard < epicsMutex > &, ca_uint32_t id, + const char * pName, unsigned nameLength ); + + friend class tcpRecvThread; + friend class tcpSendThread; + + tcpiiu ( const tcpiiu & ); + tcpiiu & operator = ( const tcpiiu & ); + void operator delete ( void * ); +}; + +inline void * tcpiiu::operator new ( size_t size, + tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & mgr ) +{ + return mgr.allocate ( size ); +} + +#ifdef CXX_PLACEMENT_DELETE +inline void tcpiiu::operator delete ( void * pCadaver, + tsFreeList < class tcpiiu, 32, epicsMutexNOOP > & mgr ) +{ + mgr.release ( pCadaver ); +} +#endif + +inline bool tcpiiu::ca_v41_ok ( + epicsGuard < epicsMutex > & ) const +{ + return CA_V41 ( this->minorProtocolVersion ); +} + +inline bool tcpiiu::ca_v44_ok ( + epicsGuard < epicsMutex > & ) const +{ + return CA_V44 ( this->minorProtocolVersion ); +} + +inline bool tcpiiu::ca_v49_ok ( + epicsGuard < epicsMutex > & ) const +{ + return CA_V49 ( this->minorProtocolVersion ); +} + +inline bool tcpiiu::alive ( + epicsGuard < epicsMutex > & ) const +{ + return ( this->state == iiucs_connecting || + this->state == iiucs_connected ); +} + +inline bool tcpiiu::connecting ( + epicsGuard < epicsMutex > & ) const +{ + return ( this->state == iiucs_connecting ); +} + +inline bool tcpiiu::receiveThreadIsBusy ( + epicsGuard < epicsMutex > & guard ) +{ + guard.assertIdenticalMutex ( this->mutex ); + return this->_receiveThreadIsBusy; +} + +inline void tcpiiu::beaconAnomalyNotify ( + epicsGuard < epicsMutex > & guard ) +{ + //guard.assertIdenticalMutex ( this->cacRef.mutexRef () ); + this->recvDog.beaconAnomalyNotify ( guard ); +} + +inline void tcpiiu::beaconArrivalNotify ( + epicsGuard < epicsMutex > & guard ) +{ + //guard.assertIdenticalMutex ( this->cacRef.mutexRef () ); + this->recvDog.beaconArrivalNotify ( guard ); +} + +inline void tcpiiu::probeResponseNotify ( + epicsGuard < epicsMutex > & cbGuard ) +{ + this->recvDog.probeResponseNotify ( cbGuard ); +} + +inline bool tcpiiu::isNameService () const +{ + return ( this->pSearchDest != NULL ); +} + +inline void SearchDestTCP::setCircuit ( tcpiiu * piiu ) +{ + _ptcpiiu = piiu; +} + +#endif // ifdef virtualCircuith diff --git a/modules/ca/src/perl/CA.pm b/modules/ca/src/perl/CA.pm new file mode 100644 index 000000000..d79cdd33e --- /dev/null +++ b/modules/ca/src/perl/CA.pm @@ -0,0 +1,671 @@ +# Bootstrap wrapper for the Perl 5 Channel Access client module. +# This wrapper also contains the POD documentation for the module. + +use strict; +use warnings; + +my $version = '0.6'; + + +package CA; + +our $VERSION = $version; + + +package Cap5; +# This package is required because the loadable library containing the +# Perl interface code shouldn't be called CA but DynaLoader needs the +# package name to match the library name. The loadable library actually +# declares the packages for both Cap5 and CA which is why this works, +# although the only symbols in the Cap5 package are associated with the +# requirements of the DynaLoader module. + +our $VERSION = $version; +our @ISA = qw(DynaLoader); + +# Library is specific to the Perl version and archname +use Config; +my $perl_version = $Config::Config{version}; +my $perl_archname = $Config::Config{archname}; + +require DynaLoader; + +# Add our lib/ directory to the shared library search path +use File::Basename; +my $Lib = dirname(__FILE__); +push @DynaLoader::dl_library_path, "$Lib/$perl_version/$perl_archname"; + +bootstrap Cap5 $VERSION; + + +package CA; +# This END block runs the ca_context_destroy function, but we don't +# document it since the user never needs to call it explicitly. + +END { CA->context_destroy; } + + +package CA::Subscription; +# A subscription reference is a distinct object type. This package +# provides a convenience method allowing a subscription to clear itself. + +our $VERSION = $version; + +sub clear { + CA->clear_subscription(shift); +} + +1; +__END__ + +=head1 NAME + +CA - Perl 5 interface to EPICS Channel Access + +=head1 SYNOPSIS + + use lib '/path/to/cap5/lib/perl'; + use CA; + + my $chan = CA->new('pvname'); + CA->pend_io(1); + + my @access = ('no ', ''); + printf " PV name: %s\n", $chan->name; + printf " Data type: %s\n", $chan->field_type; + printf " Element count: %d\n", $chan->element_count; + printf " Host: %s\n", $chan->host_name; + printf " State: %s\n", $chan->state; + printf " Access: %sread, %swrite\n", + $access[$chan->read_access], $access[$chan->write_access]; + + die "PV not found!" unless $chan->is_connected; + + $chan->get; + CA->pend_io(1); + printf " Value: %s\n", $chan->value; + + $chan->create_subscription('v', \&callback, 'DBR_TIME_DOUBLE'); + CA->pend_event(10); + + sub callback { + my ($chan, $status, $data) = @_; + if ($status) { + printf "%-30s %s\n", $chan->name, $status; + } else { + printf " Value: %g\n", $data->{value}; + printf " Severity: %s\n", $data->{severity}; + printf " Timestamp: %.6f\n", + $data->{stamp} + $data->{stamp_fraction}; + } + } + + +=head1 DESCRIPTION + +C is an efficient interface to the EPICS Channel Access client library for +use by Perl 5 programs. It provides most of the functionality of the C library +(omitting Synchronous Groups) but only handles the three standard Perl data +types integer (long), floating point (double) and string (now including long +strings). Programmers who understand the C API will very quickly pick up how to +use this library since the calls and concepts are virtually identical. + + +=head1 FUNCTIONS + + +=head2 Constructor + +=over 4 + +=item new( I ) + +=item new( I, I ) + +Create a channel for the named PV. If given, I will be called whenever the +connection state of the channel changes. The arguments passed to I are the +channel object and a scalar value that is true if the channel is now up. + +The underlying CA channel will be cleaned up properly when the channel object is +garbage-collected by Perl. + +=back + + +=head2 Object Methods + +The following methods are provided for channel objects returned by +C<< CA->new() >>. + +=over 4 + + +=item name + +The PV name provided when this channel was created. + + +=item field_type + +Returns the native DBF type of the process variable as a string, or the string +C if unconnected. + + +=item element_count + +The maximum array element count from the server. Zero if the channel is not +connected. + + +=item host_name + +A string containing the server's hostname and port number. If the channel is +disconnected it will report C<< >>. + + +=item read_access + +=item write_access + +A true/false value that indicates whether the client has read or write access to +the specified channel. + + +=item state + +A string giving the current connection state of the channel, one of C, C, C or C. + + +=item is_connected + +Returns C if the channel is currently connected, else C. Use this +in preference to the equivalent code Sstate eq 'connected' >>>. + + +=item get + +=item value + +The C method makes a C request for a single element of the Perl +type closest to the channel's native data type; a C field will be +fetched as a DBF_STRING, and a C array with multiple elements will +converted into a Perl string. Once the server has returned the value (for which +see the C function below) it can be retrieved using the channel's +C method. Note that the C method deliberately only provides limited +capabilities; the C method must be used for more complex +requirements. + + +=item get_callback( I ) + +=item get_callback( I, I ) + +=item get_callback( I, I ) + +=item get_callback( I, I, I ) + +The C method takes a subroutine reference or name and calls that +routine when the server returns the data requested. With no other arguments the +data type requested will be the widened form of the channel's native type +(widening is discussed below), and if the channel is an array the request will +fetch all available elements. + +The element count can be overridden by providing an integer argument in the +range 0 .. C, where zero means use the current length from the +server. Note that the count argument must be an integer; add 0 to it if it is +necessary to convert it from a string. +The optional data type I should be a string naming +the desired C type; the actual type used will have the C part +widened to one of C, C, C or C. The valid type +names are listed in the L under the +section titled Channel Access Data Types; look in the CA Type Code column of the +two tables. + +The callback subroutine will be given three arguments: the channel object, a +status value from the server, and the returned data. If there were no errors +the status value will be C and the data will be valid; if an error +occurred the data will be C and the status a printable string giving more +information. The format of the data is described under L +below. + +Callback subroutines should only call Perl's C, C or similar +functions if they are expecting the program to exit at that time; attempts to +C with an exception object in the callback and catch that using C in +the main thread are not likely to succeed and will probably result in a crash. +Callbacks should not perform any operations that would block for more than a +fraction of a second as this will hold up network communications with the +relevant server and could cause the Perl program and/or the Channel Access +server to crash. Calling C<< CA->pend_event >> from within a callback is not +permitted by the underlying Channel Access library. + + +=item create_subscription( I, I ) + +=item create_subscription( I, I, I ) + +=item create_subscription( I, I, I ) + +=item create_subscription( I, I, I, I ) + +Register a state change subscription and specify a subroutine to be called +whenever the process variable undergoes a significant state change. I +must be a string containing one or more of the letters C, C, C and C

+which indicate that this subscription is for Value, Log (Archive), Alarm and +Property changes. The subroutine I is called as described for the +C method above, and the same optional I and I +arguments may be supplied to modify the data type and element count requested +from the server. + +The C method returns a C object which is +required to cancel that particular subscription. Either call the C +method on that object directly, or pass it to the C<< CA->clear_subscription >> +class method. + + +=item put( I ) + +=item put( I, I, ... ) + +The C method makes a C or C call depending on the +number of elements given in its argument list. The data type used will be the +native type of the channel, widened to one of C, array of C, +C or C. + + +=item put_callback( I, I ) + +=item put_callback( I, I, I, ... ) + +C is similar to the C method with the addition of the +subroutine reference or name I which is called when the server reports that +all actions resulting from the put have completed. For some applications this +callback can be delayed by minutes, hours or possibly even longer. The data +type is chosen the same way as for C. The arguments to the subroutine will +be the channel object and the status value from the server, which is either +C or a printable string if an error occurred. The same restrictions +apply to the callback subroutine as described in C above. + + +=item put_acks( I ) + +=item put_acks( I, I ) + +Applications that need to acknowledge alarms by doing a C with type +C can do so using the C method. The severity argument +may be provided as an integer from zero through three or as a string containing +one of the corresponding EPICS severity names C, C, C or +C. If a subroutine reference is provided it will be called after the +operation has completed on the server as described in C above. + + +=item put_ackt( I ) + +=item put_ackt( I, I ) + +This method is for applications that need to enable/disable transient alarms by +doing a C with type C. The C argument is a +true/false value, and an optional subroutine reference can be provided as +described above. + + +=item change_connection_event( I ) + +This method replaces, adds or cancels the connection handler subroutine for the +channel; see the C constructor for details. If I is C any +existing handler is removed, otherwise the new subroutine will be used for all +future connection events on this channel. + + +=item change_access_rights_event( I ) + +This method replaces, adds or cancels an access rights handler subroutine for +the channel, which will be called if the client's right to read from or write +to the channel changes. If I is C any existing handler is removed, +otherwise the new subroutine will be used for all future rights change events +on this channel. + +The arguments passed to I are the channel object and a pair of scalar +values for read and write permissions respectively, that are true when the +access is permitted, false when it is not. + +=back + + +=head2 Channel Data + +The data provided to a callback function registered with either C +or C can be a scalar value or a reference to an array or a +hash, depending on the data type that was used for the data transfer. If the +request was for a single item of one of the basic data types, the data argument +will be a Perl scalar that holds the value directly. If the request was for +multiple items of one of the basic types, the data argument will be a reference +to an array holding the data. There is one exception though; if the data type +requested was for an array of C values that array will be represented +as a single Perl string containing all the characters before the first zero +byte. + +If the request was for one of the compound data types, the data argument will be +a reference to a hash with keys as described below. Keys that are not classed +as metadata are named directly after the fields in the C C, +and are only included when the C structure contains that particular field. + + +=head3 Metadata + +These metadata will always be present in the hash: + + +=over 4 + +=item TYPE + +The C name of the data type from the server. This might have been +widened from the original type used to request or subscribe for the data. + + +=item COUNT + +The number of elements in the data returned by the server. If the data type is +C the value given for C is the number of bytes (including any +trailing zeros) returned by the server, although the value field is given as a +Perl string containing all the characters before the first zero byte. + +=back + + +=head3 Fixed Fields + +These fields are always present in the hash: + +=over 4 + + +=item value + +The actual process variable data, expressed as a Perl scalar or a reference to +an array of scalars, depending on the request. An array of C elements +will be represented as a string; to access the array elements as numeric values +the request must be for the C equivalent data type. + +If I is C or C, C can be accessed both +as the integer choice value and (if within range) as the string associated with +that particular choice. + + +=item status + +The alarm status of the PV as a printable string, or C if not in alarm. + + +=item severity + +The alarm severity of the PV, or C if not in alarm. A defined severity +can be used as a human readable string or as a number giving the numeric value +of the alarm severity (1 = C, 2 = C, 3 = C). + +=back + + +=head3 Ephemeral Fields + +These fields are only present for some values of I: + +=over 4 + + +=item strs + +A reference to an array containing all the possible choice strings for an ENUM. + +Present only when I is C or C. + + +=item no_str + +The number of choices defined for an ENUM. + +Present only when I is C or C. + + +=item stamp + +The process variable timestamp, converted to a local C. This value is +suitable for passing to the Perl C or C functions. + +Present only when I is C. + +=item stamp_fraction + +The fractional part of the process variable timestamp as a positive floating +point number less than 1.0. + +Present only when I is C. + + +=item ackt + +The value of the process variable's transient acknowledgment flag, an integer. + +Present only when I is C. + + +=item acks + +The alarm severity of the highest unacknowledged alarm for this process +variable. As with the C value, this scalar is both a string and +numeric severity. + +Present only when I is C. + + +=item precision + +The process variable's display precision, an integer giving the number of +decimal places to display. + +Present only when I is C or C. + + +=item units + +The engineering units string for the process variable. + +Present only when I is C or C where C is +not C. + + +=item upper_disp_limit + +=item lower_disp_limit + +The display range for the process variable; graphical tools often provide a way +to override these limits. + +Present only when I is C or C where C is +not C. + + +=item upper_alarm_limit + +=item upper_warning_limit + +=item lower_warning_limit + +=item lower_alarm_limit + +These items give the values at which the process variable should go into an +alarm state, although in practice the alarm severity associated with each level +is not provided. + +Present only when I is C or C where C is +not C. + + +=item upper_ctrl_limit + +=item lower_ctrl_limit + +The range over which a client can control the value of the process variable. + +Present only when I is C where C is not C. + +=back + + +=head2 Class Methods + + +The following functions are not channel methods, and should be called using the +class method syntax, e.g. C<< CA->pend_io(10) >>. + +=over 4 + +=item version + +Returns the EPICS_VERSION_STRING from the version of EPICS Base this software +was built using. + +=item flush_io + +Flush outstanding IO requests to the server. This routine is useful for users +who need to flush requests prior to performing client side labor in parallel +with labor performed in the server. Outstanding requests are also sent whenever +the buffer which holds them becomes full. Note that the routine can return +before all flush operations have completed. + + +=item test_io + +This function tests to see if all C requests are complete and channels +created without a connection callback subroutine are connected. It will return +a true value if all such operations are complete, otherwise false. + + +=item pend_io( I ) + +This function flushes the send buffer and then blocks until all outstanding +C requests complete and all channels created without a connection callback +subroutine have connected for the first time. Unlike C, this +routine does not process CA's background activities if no IO requests are +pending. + +If any I/O or connection operations remain incomplete after I seconds, +the function will die with the error C; see L +below. A I interval of zero is taken to mean wait forever if +necessary. The I value should take into account worst case network +delays such as Ethernet collision exponential back off until retransmission +delays which can be quite long on overloaded networks. + + +=item pend_event( I ) + +Flush the send buffer and process CA's background activities for I +seconds. This function always blocks for the full I period, and if a +value of zero is used it will never return. + +It is generally advisable to replace any uses of Perl's built-in function +C with calls to this routine, allowing Channel Access to make use of the +delay time to perform any necessary housekeeping operations. + + +=item poll + +Flush the send buffer and process any outstanding CA background activity. + + +=item clear_subscription( I ) + +Cancel a subscription. Note that for this to take effect immediately it is +necessary to call C<< CA->flush_io >> or one of the other class methods that +flushes the send buffer. + + +=item add_exception_event( I ) + +Trap exception events and execute I whenever they occur. The subroutine is +provided with four arguments: The channel object (if applicable), the status +value from the server, a printable context string giving more information about +the error, and a hash reference containing some additional data. If the +exception is not specific to a particular channel the channel object will be +C. The status value is a printable string. The hash may contain any of +the following members: + +=over 8 + +=item * OP + +The operation in progress when the exception occurred. This scalar when used as +a string is one of C, C, C, C, +C or C but can also be accessed as an integer (0-5). + +=item * TYPE + +The C name of the data type involved. + +=item * COUNT + +The number of elements in the request. + +=item * FILE + +=item * LINE + +These refer to the source file and line number inside the CA client library +where the exception was noticed. + +=back + +=item replace_printf_handler( I ) + +This function provides a method to trap error messages from the CA client +library and redirect them to somewhere other than the C stream. The +subroutine provided will be called with a single string argument every time the +client library wishes to output an error or warning message. Note that a single +error or warning message may result in several calls to this subroutine. + +To revert back to the original handler, call C<< CA->replace_printf_handler() >> +passing C as the subroutine reference. + +=back + + +=head1 ERROR HANDLING + +Errors in using the library will be indicated by the module throwing an +exception, i.e. calling C with an appropriate error message. These +exceptions can be caught using the standard Perl C statement and +testing the C<$@> variable afterwards; if not caught, they will cause the +running program to C with an appropriate error message pointing to the +program line that called the C library. + +Error messages reported by the underlying CA client library all start with the +string C and the remainder of the symbol for the associated CA error +number, and are followed after a space-hyphen-space by a human-readable message +describing the error. Errors that are detected by the Perl interface layer do +not follow this pattern, but are still printable strings. + + +=head1 SEE ALSO + +=over + +=item [1] R3.15 Channel Access Reference Manual by Jeffrey O. Hill + +L + +=back + + +=head1 AUTHOR + +Andrew Johnson, Eanj@aps.anl.govE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2008-2014 UChicago Argonne LLC, as Operator of Argonne National +Laboratory. + +This software is distributed under the terms of the EPICS Open License. + +=cut diff --git a/modules/ca/src/perl/Cap5.xs b/modules/ca/src/perl/Cap5.xs new file mode 100644 index 000000000..e519198ec --- /dev/null +++ b/modules/ca/src/perl/Cap5.xs @@ -0,0 +1,1505 @@ +/* Provides an EPICS Channel Access client interface for Perl5. */ + +/* This macro disables perl's reentr.inc file, which we don't need + * here and just generates unnecessary compiler warnings. */ +#define REENTRINC + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +#include "cadef.h" +#include "db_access.h" +#include "epicsVersion.h" +#include "alarm.h" + +typedef union { + dbr_long_t iv; + dbr_double_t nv; + dbr_string_t pv; +} CA_data; + +typedef struct CA_channel { + chid chan; + CA_data data; /* Value storage for CA::get */ + char *sdata; /* String storage for CA::get */ + size_t ssize; /* Length allocated for sdata, excluding nil */ + SV *chan_ref; + SV *conn_sub; + SV *rights_sub; +} CA_channel; + +static +void *p5_ctx; + +static +const char * get_error_msg(int status) { + static const char * const messages[] = { + "ECA_NORMAL - Normal successful completion", + "ECA_MAXIOC - Maximum simultaneous IOC connections exceeded", + "ECA_UKNHOST - Unknown internet host", + "ECA_UKNSERV - Unknown internet service", + "ECA_SOCK - Unable to allocate a new socket", + "ECA_CONN - Unable to connect to internet host or service", + "ECA_ALLOCMEM - Unable to allocate additional dynamic memory", + "ECA_UKNCHAN - Unknown IO channel", + "ECA_UKNFIELD - Record field specified inappropriate for channel specified", + "ECA_TOLARGE - The requested data transfer is greater than available memory or EPICS_CA_MAX_ARRAY_BYTES", + "ECA_TIMEOUT - User specified timeout on IO operation expired", + "ECA_NOSUPPORT - Sorry, that feature is planned but not supported at this time", + "ECA_STRTOBIG - The supplied string is unusually large", + "ECA_DISCONNCHID - The request was ignored because the specified channel is disconnected", + "ECA_BADTYPE - The data type specifed is invalid", + "ECA_CHIDNOTFND - Remote Channel not found", + "ECA_CHIDRETRY - Unable to locate all user specified channels", + "ECA_INTERNAL - Channel Access Internal Failure", + "ECA_DBLCLFAIL - The requested local DB operation failed", + "ECA_GETFAIL - Channel read request failed", + "ECA_PUTFAIL - Channel write request failed", + "ECA_ADDFAIL - Channel subscription request failed", + "ECA_BADCOUNT - Invalid element count requested", + "ECA_BADSTR - Invalid string", + "ECA_DISCONN - Virtual circuit disconnect", + "ECA_DBLCHNL - Identical process variable names on multiple servers", + "ECA_EVDISALLOW - Request inappropriate within subscription (monitor) update callback", + "ECA_BUILDGET - Database value get for that channel failed during channel search", + "ECA_NEEDSFP - Unable to initialize without the vxWorks VX_FP_TASK task option set", + "ECA_OVEVFAIL - Event queue overflow has prevented first pass event after event add", + "ECA_BADMONID - Bad event subscription (monitor) identifier", + "ECA_NEWADDR - Remote channel has new network address", + "ECA_NEWCONN - New or resumed network connection", + "ECA_NOCACTX - Specified task isnt a member of a CA context", + "ECA_DEFUNCT - Attempt to use defunct CA feature failed", + "ECA_EMPTYSTR - The supplied string is empty", + "ECA_NOREPEATER - Unable to spawn the CA repeater thread- auto reconnect will fail", + "ECA_NOCHANMSG - No channel id match for search reply- search reply ignored", + "ECA_DLCKREST - Reseting dead connection- will try to reconnect", + "ECA_SERVBEHIND - Server (IOC) has fallen behind or is not responding- still waiting", + "ECA_NOCAST - No internet interface with broadcast available", + "ECA_BADMASK - Invalid event selection mask", + "ECA_IODONE - IO operations have completed", + "ECA_IOINPROGRESS - IO operations are in progress", + "ECA_BADSYNCGRP - Invalid synchronous group identifier", + "ECA_PUTCBINPROG - Put callback timed out", + "ECA_NORDACCESS - Read access denied", + "ECA_NOWTACCESS - Write access denied", + "ECA_ANACHRONISM - Requested feature is no longer supported", + "ECA_NOSEARCHADDR - Empty PV search address list", + "ECA_NOCONVERT - No reasonable data conversion between client and server types", + "ECA_BADCHID - Invalid channel identifier", + "ECA_BADFUNCPTR - Invalid function pointer", + "ECA_ISATTACHED - Thread is already attached to a client context", + "ECA_UNAVAILINSERV - Not supported by attached service", + "ECA_CHANDESTROY - User destroyed channel", + "ECA_BADPRIORITY - Invalid channel priority", + "ECA_NOTTHREADED - Preemptive callback not enabled - additional threads may not join context", + "ECA_16KARRAYCLIENT - Client's protocol revision does not support transfers exceeding 16k bytes", + "ECA_CONNSEQTMO - Virtual circuit connection sequence aborted", + "ECA_UNRESPTMO - Virtual circuit unresponsive" + }; + + return messages[CA_EXTRACT_MSG_NO(status)]; +} + + +static +chtype best_type(CA_channel *pch) { + switch (ca_field_type(pch->chan)) { + case DBF_STRING: + case DBF_ENUM: + return DBF_STRING; + case DBF_CHAR: + if (ca_element_count(pch->chan) > 1) + return DBF_CHAR; + /* Fall through */ + case DBF_INT: + case DBF_LONG: + return DBF_LONG; + case DBF_FLOAT: + case DBF_DOUBLE: + return DBF_DOUBLE; + } + croak("Unexpected field type %s", + dbf_type_to_text(ca_field_type(pch->chan))); +} + + +static +SV * newSVdbf(chtype type, const void *dbr, int index) { + switch (type) { + char *pc; + size_t len; + + case DBR_STRING: + pc = (char *)dbr + index * MAX_STRING_SIZE; + len = strlen(pc); + return newSVpv(pc, len < MAX_STRING_SIZE ? len : MAX_STRING_SIZE); + case DBR_LONG: + return newSViv(((dbr_long_t *)dbr)[index]); + case DBR_DOUBLE: + return newSVnv(((dbr_double_t *)dbr)[index]); + default: + croak("Unexpected data type %s", dbf_type_to_text(type)); + } +} + + +static +SV * newSValarm(int sevr) { + SV *alarm = &PL_sv_undef; + if (sevr) { + alarm = newSViv(sevr); + sv_setpv(alarm, epicsAlarmSeverityStrings[sevr]); + SvIOK_on(alarm); + } + return alarm; +} + +static +void hashAdd(HV *hash, const char *key, I32 klen, SV *val) { + SV **result = hv_store(hash, key, klen, val, 0); + + if (result == NULL) + SvREFCNT_dec(val); +} + +static +SV * newSVdbr(struct event_handler_args *peha) { + const int is_primitive = dbr_type_is_plain(peha->type) || + (peha->type == DBR_CLASS_NAME); + HV *hash; + SV *val; + chtype value_type; + union db_access_val *u; + + if (dbr_type_is_STRING(peha->type) || + peha->type == DBR_STSACK_STRING || + peha->type == DBR_CLASS_NAME) + value_type = DBR_STRING; + else if (dbr_type_is_CHAR(peha->type)) + value_type = DBR_CHAR; + else if (dbr_type_is_LONG(peha->type)) + value_type = DBR_LONG; + else if (dbr_type_is_DOUBLE(peha->type)) + value_type = DBR_DOUBLE; + else if (dbr_type_is_ENUM(peha->type)) + /* Only seen as DBR_GR_ENUM and DBR_CTRL_ENUM */ + value_type = DBR_ENUM; + else { + croak("Unexpected data type %s", + dbf_type_to_text(peha->type)); + } + + if (is_primitive) { + if (value_type == DBR_CHAR) { + /* Long string => Perl scalar */ + ((char *)peha->dbr) [peha->count - 1] = 0; + return newSVpv(peha->dbr, 0); + } + + if (peha->count != 1) { + /* Array of values => Perl array reference */ + AV *array; + int i; + + array = newAV(); + for (i = 0; i < peha->count; i++) { + av_push(array, newSVdbf(value_type, peha->dbr, i)); + } + return newRV_noinc((SV *)array); + } + + /* Single value => Perl scalar */ + return newSVdbf(value_type, peha->dbr, 0); + } + + /* Compound => Perl hash reference */ + u = (union db_access_val *)peha->dbr; + hash = newHV(); + + /* Add basic meta-data */ + hashAdd(hash, "TYPE", 4, + newSVpv(dbr_type_to_text(peha->type), 0)); + hashAdd(hash, "COUNT", 5, newSViv(peha->count)); + + /* Alarm status and severity are always in the same place */ + if (u->slngval.status) + val = newSVpv(epicsAlarmConditionStrings[u->slngval.status], 0); + else + val = &PL_sv_undef; + hashAdd(hash, "status", 6, val); + hashAdd(hash, "severity", 8, + newSValarm(u->slngval.severity)); + + if (peha->type == DBR_GR_ENUM || + peha->type == DBR_CTRL_ENUM) { + AV *strings = newAV(); + int n = u->genmval.no_str; + int i; + + val = newSViv(u->genmval.value); + + for (i = 0; i < n; i++) { + size_t slen = strlen(u->genmval.strs[i]); + if (slen > MAX_ENUM_STRING_SIZE) + slen = MAX_ENUM_STRING_SIZE; + av_push(strings, newSVpv(u->genmval.strs[i], slen)); + if (i == u->genmval.value) { + sv_setpvn(val, u->genmval.strs[i], slen); + SvIOK_on(val); + } + } + hashAdd(hash, "strs", 4, + newRV_noinc((SV *)strings)); + hashAdd(hash, "no_str", 6, + newSViv(u->genmval.no_str)); + hashAdd(hash, "value", 5, val); + + return newRV_noinc((SV *)hash); + } + + /* Value */ + if (value_type == DBR_CHAR) { + char *str = dbr_value_ptr(peha->dbr, peha->type); + + /* Long string => Perl scalar */ + str[peha->count - 1] = 0; + val = newSVpv(str, 0); + } else if (peha->count == 1) { + /* Single value => Perl scalar */ + val = newSVdbf(value_type, + dbr_value_ptr(peha->dbr, peha->type), 0); + } else { + /* Array of values => Perl array reference */ + AV *array = newAV(); + int i; + + for (i = 0; i < peha->count; i++) { + av_push(array, newSVdbf(value_type, + dbr_value_ptr(peha->dbr, peha->type), i)); + } + val = newRV_noinc((SV *)array); + } + hashAdd(hash, "value", 5, val); + + /* Timestamp follows status and severity in DBR_TIME */ + if (dbr_type_is_TIME(peha->type)) { + struct timespec t; + + epicsTimeToTimespec(&t, &u->tlngval.stamp); + hashAdd(hash, "stamp", 5, + newSViv(t.tv_sec)); + hashAdd(hash, "stamp_fraction", 14, + newSVnv((double)t.tv_nsec / 1e9)); + } + else if (peha->type == DBR_STSACK_STRING) { + struct dbr_stsack_string *s = (struct dbr_stsack_string *)peha->dbr; + + hashAdd(hash, "ackt", 4, + newSViv(s->ackt)); + hashAdd(hash, "acks", 4, + newSValarm(s->acks)); + } + else if (value_type != DBR_STRING && + (dbr_type_is_GR(peha->type) || + dbr_type_is_CTRL(peha->type))) { + char *units; + size_t ulen; + void *limit; + int i = dbr_type_is_CTRL(peha->type) ? 7 : 5; + + if (value_type == DBR_DOUBLE) { + units = u->gdblval.units; + limit = &u->gdblval.upper_disp_limit; + hashAdd(hash, "precision", 9, + newSViv(u->gdblval.precision)); + } else { /* value_type == DBR_LONG */ + units = u->glngval.units; + limit = &u->glngval.upper_disp_limit; + } + + ulen = strlen(units); + hashAdd(hash, "units", 5, newSVpv(units, + ulen < MAX_UNITS_SIZE ? ulen : MAX_UNITS_SIZE)); + + while (i >= 0) { + static const char * const limit_name[] = { + "upper_disp_limit", "lower_disp_limit", + "upper_alarm_limit", "upper_warning_limit", + "lower_warning_limit", "lower_alarm_limit", + "upper_ctrl_limit", "lower_ctrl_limit", + }; + + hashAdd(hash, limit_name[i], strlen(limit_name[i]), + newSVdbf(value_type, limit, i)); + i--; + } + } + + return newRV_noinc((SV *)hash); +} + + +enum io_type { + IO_GET, + IO_PUT, + IO_MONITOR, +}; + +static +void io_handler(struct event_handler_args *peha, enum io_type io) { + PERL_SET_CONTEXT(p5_ctx); + { + CA_channel *pch = ca_puser(peha->chid); + SV *code = (SV *)peha->usr; + SV *status = &PL_sv_undef; + SV *data = &PL_sv_undef; + dSP; + + ENTER; + SAVETMPS; + + if (peha->status != ECA_NORMAL) { + status = sv_2mortal(newSVpv(get_error_msg(peha->status), 0)); + } else if (io != IO_PUT) { + data = sv_2mortal(newSVdbr(peha)); + } + + sv_setsv(ERRSV, &PL_sv_undef); + + PUSHMARK(SP); + XPUSHs(pch->chan_ref); + XPUSHs(status); + XPUSHs(data); + PUTBACK; + + call_sv(code, G_VOID | G_DISCARD | G_EVAL | G_KEEPERR); + + if (io != IO_MONITOR) + SvREFCNT_dec(code); + + if (SvTRUE(ERRSV)) + croak(NULL); + + FREETMPS; + LEAVE; + } +} + + +static +int replace_handler(SV * sub, SV ** ph_sub, long *phandler) { + if (SvOK(sub) && SvTRUE(sub)) { + if (*ph_sub != NULL) { + SvSetSV(*ph_sub, sub); + return FALSE; + } + *ph_sub = newSVsv(sub); + } else { + if (*ph_sub == NULL) + return FALSE; + + SvREFCNT_dec(*ph_sub); + *ph_sub = NULL; + *phandler = 0; + } + return TRUE; +} + + +/******************************************************************************/ + +/* CA::new($class, $name, [\&sub]) */ + +static +void connect_handler(struct connection_handler_args cha) { + CA_channel *pch = ca_puser(cha.chid); + + PERL_SET_CONTEXT(p5_ctx); + { + dSP; + + SvSetSV(ERRSV, &PL_sv_undef); + + PUSHMARK(SP); + XPUSHs(pch->chan_ref); + XPUSHs(cha.op == CA_OP_CONN_UP ? &PL_sv_yes : &PL_sv_no); + PUTBACK; + + call_sv(pch->conn_sub, G_EVAL | G_VOID | G_DISCARD | G_KEEPERR); + + if (SvTRUE(ERRSV)) + croak(NULL); + } +} + +SV * CA_new(const char *class, const char *name, ...) { + dXSARGS; + SV *ca_ref = newSViv(0); + SV *ca_obj = newSVrv(ca_ref, class); + CA_channel *pch; + caCh *handler; + int status; + + Newz(0, pch, 1, CA_channel); + sv_setiv(ca_obj, (IV)pch); + SvREADONLY_on(ca_obj); + + pch->chan_ref = ca_ref; + (void) SvREFCNT_inc(ca_ref); + + if (items > 2 + && SvOK(ST(2))) { + /* Connection handler provided */ + pch->conn_sub = newSVsv(ST(2)); + handler = &connect_handler; + } else + handler = NULL; + + status = ca_create_channel(name, handler, pch, 0, &pch->chan); + if (status != ECA_NORMAL) { + SvREFCNT_dec(ca_ref); + if (pch->conn_sub) + SvREFCNT_dec(pch->conn_sub); + croak("%s", get_error_msg(status)); + } + + return ca_ref; +} + +static int destroyed = 0; + +/* CA::DESTROY($ca_ref) */ + +void CA_DESTROY(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + int status; + + status = destroyed ? ECA_NORMAL : ca_clear_channel(pch->chan); + + if (pch->conn_sub) + SvREFCNT_dec(pch->conn_sub); + + if (pch->rights_sub) + SvREFCNT_dec(pch->rights_sub); + + if (pch->sdata) + Safefree(pch->sdata); + + SvREFCNT_dec(pch->chan_ref); + Safefree(pch); + + if (status != ECA_NORMAL) + croak("%s", get_error_msg(status)); +} + + +/* CA::context_destroy($class) */ + +void CA_context_destroy(const char *class) { + ca_context_destroy(); + destroyed = 1; +} + + +/* CA::change_connection_event($ca_ref, \$sub) */ + +void CA_change_connection_event(SV *ca_ref, SV *sub) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + caCh *handler = &connect_handler; + int status; + + if (! replace_handler(sub, &pch->conn_sub, (long *)&handler)) + return; + + status = ca_change_connection_event(pch->chan, handler); + + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } +} + +/* CA::replace_access_rights_event($ca_ref, \$sub) */ + +static +void rights_handler(struct access_rights_handler_args arha) { + CA_channel *pch = ca_puser(arha.chid); + + PERL_SET_CONTEXT(p5_ctx); + { + dSP; + + SvSetSV(ERRSV, &PL_sv_undef); + + PUSHMARK(SP); + XPUSHs(pch->chan_ref); + XPUSHs(arha.ar.read_access ? &PL_sv_yes : &PL_sv_no); + XPUSHs(arha.ar.write_access ? &PL_sv_yes : &PL_sv_no); + PUTBACK; + + call_sv(pch->rights_sub, G_EVAL | G_VOID | G_DISCARD | G_KEEPERR); + + if (SvTRUE(ERRSV)) + croak(NULL); + } +} + +void CA_replace_access_rights_event(SV *ca_ref, SV *sub) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + caArh *handler = &rights_handler; + int status; + + if (! replace_handler(sub, &pch->rights_sub, (long *)&handler)) + return; + + status = ca_replace_access_rights_event(pch->chan, handler); + + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } +} + + +/* CA::put($ca_ref, @values) */ + +void CA_put(SV *ca_ref, SV *val, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + int num_values = items - 1; + int status; + + if (num_values == 1) { + if (ca_field_type(pch->chan) == DBF_CHAR && + ca_element_count(pch->chan) > 1) { + size_t len; + char *long_string = SvPV(val, len); + + status = ca_array_put(DBF_CHAR, len+1, pch->chan, long_string); + } else { + union { + dbr_long_t dbr_long; + dbr_double_t dbr_double; + dbr_string_t dbr_string; + } data; + chtype type = best_type(pch); + + switch (type) { + case DBF_LONG: + data.dbr_long = SvIV(val); + break; + case DBF_DOUBLE: + data.dbr_double = SvNV(val); + break; + case DBF_STRING: + strncpy(data.dbr_string, SvPV_nolen(val), MAX_STRING_SIZE); + break; + } + status = ca_put(type, pch->chan, &data); + } + } else { + union { + dbr_char_t *dbr_char; + dbr_long_t *dbr_long; + dbr_double_t *dbr_double; + char *dbr_string; + void *dbr; + } p; + int i; + chtype type = best_type(pch); + + switch (type) { + case DBF_CHAR: + New(0, p.dbr_char, num_values, dbr_char_t); + for (i = 0; i < num_values; i++) { + p.dbr_char[i] = SvIV(ST(i + 1)); + } + break; + case DBF_LONG: + New(0, p.dbr_long, num_values, dbr_long_t); + for (i = 0; i < num_values; i++) { + p.dbr_long[i] = SvIV(ST(i + 1)); + } + break; + case DBF_DOUBLE: + New(0, p.dbr_double, num_values, dbr_double_t); + for (i = 0; i < num_values; i++) { + p.dbr_double[i] = SvNV(ST(i + 1)); + } + break; + case DBF_STRING: + New(0, p.dbr_string, num_values * MAX_STRING_SIZE, char); + for (i = 0; i < num_values; i++) { + char * src = SvPV_nolen(ST(i + 1)); + strncpy(p.dbr_string + i, src, MAX_STRING_SIZE); + } + break; + } + + status = ca_array_put(type, num_values, pch->chan, p.dbr); + Safefree(p.dbr); + } + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } + XSRETURN(0); +} + + +/* CA::put_callback($ca_ref, \&sub, @values) */ + +static +void put_handler(struct event_handler_args eha) { + io_handler(&eha, IO_PUT); +} + +void CA_put_callback(SV *ca_ref, SV *sub, SV *val, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + SV *put_sub = newSVsv(sub); + int num_values = items - 2; + int status; + + if (num_values == 1) { + if (ca_field_type(pch->chan) == DBF_CHAR && + ca_element_count(pch->chan) > 1) { + size_t len; + char *long_string = SvPV(val, len); + + status = ca_array_put_callback(DBF_CHAR, len+1, pch->chan, + long_string, put_handler, put_sub); + } else { + union { + dbr_long_t dbr_long; + dbr_double_t dbr_double; + dbr_string_t dbr_string; + } data; + chtype type = best_type(pch); + + switch (type) { + case DBF_LONG: + data.dbr_long = SvIV(val); + break; + case DBF_DOUBLE: + data.dbr_double = SvNV(val); + break; + case DBF_STRING: + strncpy(data.dbr_string, SvPV_nolen(val), MAX_STRING_SIZE); + break; + } + + status = ca_put_callback(type, pch->chan, &data, put_handler, put_sub); + } + } else { + union { + dbr_char_t *dbr_char; + dbr_long_t *dbr_long; + dbr_double_t *dbr_double; + char *dbr_string; + void *dbr; + } p; + int i; + chtype type = best_type(pch); + + switch (type) { + case DBF_CHAR: + New(0, p.dbr_char, num_values, dbr_char_t); + for (i = 0; i < num_values; i++) { + p.dbr_char[i] = SvIV(ST(i + 1)); + } + break; + case DBF_LONG: + New(0, p.dbr_long, num_values, dbr_long_t); + for (i = 0; i < num_values; i++) { + p.dbr_long[i] = SvIV(ST(i + 2)); + } + break; + case DBF_DOUBLE: + New(0, p.dbr_double, num_values, dbr_double_t); + for (i = 0; i < num_values; i++) { + p.dbr_double[i] = SvNV(ST(i + 2)); + } + break; + case DBF_STRING: + New(0, p.dbr_string, num_values * MAX_STRING_SIZE, char); + for (i = 0; i < num_values; i++) { + char * src = SvPV_nolen(ST(i + 2)); + strncpy(p.dbr_string + i, src, MAX_STRING_SIZE); + } + break; + } + + status = ca_array_put_callback(type, num_values, pch->chan, p.dbr, + put_handler, put_sub); + Safefree(p.dbr); + } + if (status != ECA_NORMAL) { + SvREFCNT_dec(put_sub); + croak("%s", get_error_msg(status)); + } + XSRETURN(0); +} + + +/* CA::put_acks($ca_ref, $sevr, [\&sub]) */ + +void CA_put_acks(SV *ca_ref, SV *sevr, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + dbr_put_acks_t acks; + int status; + + if (! SvOK(sevr)) { + acks = NO_ALARM; + } else if (SvIOK(sevr)) { + acks = SvIV(sevr); + if (acks > INVALID_ALARM) + croak("Bad acknowledgement severity %d", acks); + } else { + size_t slen; + char *sname = SvPV(sevr, slen); + for (acks = NO_ALARM; acks <= INVALID_ALARM; acks++) { + if (strcmp(sname, epicsAlarmSeverityStrings[acks]) == 0) + break; + } + if (acks > INVALID_ALARM) + croak("Bad acknowledgment severity '%s'", sname); + } + + if (items > 2) { + SV *put_sub = newSVsv(ST(2)); + status = ca_put_callback(DBR_PUT_ACKS, pch->chan, &acks, + put_handler, put_sub); + if (status != ECA_NORMAL) + SvREFCNT_dec(put_sub); + } else + status = ca_put(DBR_PUT_ACKS, pch->chan, &acks); + + if (status != ECA_NORMAL) + croak("%s", get_error_msg(status)); + + XSRETURN(0); +} + + +/* CA::put_ackt($ca_ref, $trans, [\&sub]) */ + +void CA_put_ackt(SV *ca_ref, int ack, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + dbr_put_ackt_t ackt = !! ack; /* 0 or 1 only */ + int status; + + if (items > 2) { + SV *put_sub = newSVsv(ST(2)); + status = ca_put_callback(DBR_PUT_ACKT, pch->chan, &ackt, + put_handler, put_sub); + if (status != ECA_NORMAL) + SvREFCNT_dec(put_sub); + } else + status = ca_put(DBR_PUT_ACKS, pch->chan, &ackt); + + if (status != ECA_NORMAL) + croak("%s", get_error_msg(status)); + + XSRETURN(0); +} + + +/* CA::get($ca_ref) */ + +void CA_get(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + size_t count = ca_element_count(pch->chan); + int status; + + if (ca_field_type(pch->chan) == DBF_CHAR && + count > 1) { + if (!pch->sdata) { + New(0, pch->sdata, count + 1, char); + pch->ssize = count; + } else if (pch->ssize < count) { /* Reconnected to larger array? */ + Safefree(pch->sdata); + New(0, pch->sdata, count + 1, char); + pch->ssize = count; + } + status = ca_array_get(DBF_CHAR, 0, pch->chan, pch->sdata); + } else { + status = ca_get(best_type(pch), pch->chan, &pch->data); + } + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } +} + + +/* CA::value($ca_ref) */ + +SV * CA_value(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + if (ca_field_type(pch->chan) == DBF_CHAR && + ca_element_count(pch->chan) > 1 && + pch->sdata) { + return newSVpv(pch->sdata, 0); + } + return newSVdbf(best_type(pch), &pch->data, 0); +} + + +/* CA::get_callback($ca_ref, \&sub, [$type | $count]) */ + +static +void get_handler(struct event_handler_args eha) { + io_handler(&eha, IO_GET); +} + +void CA_get_callback(SV *ca_ref, SV *sub, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + SV *get_sub = newSVsv(sub); + int status; + chtype type = best_type(pch); + int count = 0; + int i = 2; + const char *croak_msg; + + while (items > i + && SvOK(ST(i))) { + if (SvIOK(ST(i))) { + /* Interger => Count arg, zero means current size */ + count = SvIV(ST(i)); + if (count < 0 || count > ca_element_count(pch->chan)) { + croak_msg = "Requested array size is out of range"; + goto exit_croak; + } + } else if (SvPOKp(ST(i))) { + /* String => Type arg */ + char *treq = SvPV_nolen(ST(i)); + + dbr_text_to_type(treq, type); + if (type < 0 || + type == DBR_PUT_ACKT || + type == DBR_PUT_ACKS) { + croak_msg = "Requested DBR type is invalid"; + goto exit_croak; + } else if (type == DBR_GR_ENUM || + type == DBR_CTRL_ENUM || + type == DBR_CLASS_NAME || + type == DBR_STSACK_STRING) + /* The above types are supported */ ; + else if (dbr_type_is_SHORT(type)) + type += (DBR_LONG - DBR_SHORT); + else if (dbr_type_is_FLOAT(type)) + type += (DBR_DOUBLE - DBR_FLOAT); + else if (dbr_type_is_ENUM(type)) + type += (DBR_STRING - DBR_ENUM); + } + i++; + } + + status = ca_array_get_callback(type, count, + pch->chan, get_handler, get_sub); + if (status != ECA_NORMAL) { + croak_msg = get_error_msg(status); + goto exit_croak; + } + + XSRETURN(0); + return; + +exit_croak: + SvREFCNT_dec(get_sub); + croak("%s", croak_msg); +} + + +/* CA::create_subscription($ca_ref, $mask, \&sub, [$type | $count]) */ + +static +void subscription_handler(struct event_handler_args eha) { + io_handler(&eha, IO_MONITOR); +} + +SV * CA_create_subscription(SV *ca_ref, const char *mask_str, SV *sub, ...) { + dXSARGS; + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + SV *mon_sub = newSVsv(sub); + SV *mon_ref = newSViv(0); + SV *mon_obj = newSVrv(mon_ref, "CA::Subscription"); + chtype type = best_type(pch); + int count = ca_element_count(pch->chan); + int i = 3; + int mask = 0; + evid event; + int status; + const char *croak_msg; + + if (strchr(mask_str, 'v') || strchr(mask_str, 'V')) mask |= DBE_VALUE; + if (strchr(mask_str, 'l') || strchr(mask_str, 'L')) mask |= DBE_LOG; + if (strchr(mask_str, 'a') || strchr(mask_str, 'A')) mask |= DBE_ALARM; + if (strchr(mask_str, 'p') || strchr(mask_str, 'P')) mask |= DBE_PROPERTY; + + while (items > i + && SvOK(ST(i))) { + if (SvIOK(ST(i))) { + /* Interger => Count arg, zero means current size */ + count = SvIV(ST(i)); + if (count < 0 || count > ca_element_count(pch->chan)) { + croak_msg = "Requested array size is out of range"; + goto exit_croak; + } + } else if (SvPOKp(ST(i))) { + /* String => Type arg */ + size_t tlen; + char *treq = SvPV(ST(i), tlen); + + dbr_text_to_type(treq, type); + if (type < 0) { + croak_msg = "Unknown CA data type"; + goto exit_croak; + } + if (type == DBR_PUT_ACKT || + type == DBR_PUT_ACKS) { + croak_msg = "DBR_PUT_ACK types are write-only"; + goto exit_croak; + } else if (type == DBR_GR_ENUM || + type == DBR_CTRL_ENUM || + type == DBR_CLASS_NAME || + type == DBR_STSACK_STRING) + /* These above types are supported */ ; + else if (dbr_type_is_SHORT(type)) + type += (DBR_LONG - DBR_SHORT); + else if (dbr_type_is_FLOAT(type)) + type += (DBR_DOUBLE - DBR_FLOAT); + else if (dbr_type_is_ENUM(type)) + type += (DBR_STRING - DBR_ENUM); + } + i++; + } + + status = ca_create_subscription(type, count, pch->chan, mask, + subscription_handler, mon_sub, &event); + if (status != ECA_NORMAL) { + croak_msg = get_error_msg(status); + goto exit_croak; + } + + sv_setiv(mon_obj, (IV)event); + SvREADONLY_on(mon_obj); + (void) SvREFCNT_inc(mon_ref); + + return mon_ref; + +exit_croak: + SvREFCNT_dec(mon_ref); + SvREFCNT_dec(mon_sub); + croak("%s", croak_msg); +} + + +/* CA::clear_subscription($class, $subscription) */ + +void CA_clear_subscription(const char *class, SV *mon_ref) { + evid event = (evid)SvIV(SvRV(mon_ref)); + int status; + + if (! sv_isa(mon_ref, "CA::Subscription")) { + croak("Not a CA::Subscription"); + } + + status = ca_clear_subscription(event); + + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } +} + + +/* CA::pend_io($class, $timeout) */ + +void CA_pend_io(const char *class, double timeout) { + int status = ca_pend_io(timeout); + if (status != ECA_NORMAL) { + croak("%s", get_error_msg(status)); + } +} + +/* CA::test_io($class) */ + +int CA_test_io(const char *class) { + return (ca_test_io() == ECA_IODONE); +} + +/* CA::pend_event($class, $timeout) */ + +void CA_pend_event(const char *class, double timeout) { + int status = ca_pend_event(timeout); + if (status != ECA_TIMEOUT) { + croak("%s", get_error_msg(status)); + } +} + +/* CA::poll($class) */ + +void CA_poll(const char *class) { + ca_poll(); +} + + +/* CA::flush_io($class) */ + +void CA_flush_io(const char *class) { + ca_flush_io(); +} + + +/* CA::version($class) */ + +const char * CA_version(const char *class) { + return EPICS_VERSION_STRING; +} + +/* CA::add_exception_event($class, \&sub) */ + +static +SV * exception_sub = NULL; + +static +void exception_handler(struct exception_handler_args eha) { + if (! exception_sub) + return; + + PERL_SET_CONTEXT(p5_ctx); + { + SV *channel = &PL_sv_undef; + SV *status = &PL_sv_undef; + HV *hash = newHV(); + SV *op; + const char *opString[] = { + "GET", "PUT", "CREATE_CHANNEL", "ADD_EVENT", "CLEAR_EVENT", "OTHER" + }; + dSP; + + ENTER; + SAVETMPS; + + if (eha.chid) { + CA_channel *pch = ca_puser(eha.chid); + channel = pch->chan_ref; + } + if (eha.stat != ECA_NORMAL) { + status = sv_2mortal(newSVpv(get_error_msg(eha.stat), 0)); + } + + op = newSViv(eha.op); + sv_setpv(op, opString[eha.op]); + SvIOK_on(op); + hashAdd(hash, "OP", 2, op); + hashAdd(hash, "TYPE", 4, + newSVpv(dbr_type_to_text(eha.type), 0)); + hashAdd(hash, "COUNT", 5, newSViv(eha.count)); + if (eha.pFile) + hashAdd(hash, "FILE", 4, newSVpv(eha.pFile, 0)); + if (eha.lineNo) + hashAdd(hash, "LINE", 4, newSVuv(eha.lineNo)); + + PUSHMARK(SP); + XPUSHs(channel); + XPUSHs(status); + XPUSHs(sv_2mortal(newSVpv(eha.ctx, 0))); + XPUSHs(sv_2mortal(newRV_noinc((SV *)hash))); + PUTBACK; + + call_sv(exception_sub, G_EVAL | G_VOID | G_DISCARD); + + FREETMPS; + LEAVE; + } +} + +void CA_add_exception_event(const char *class, SV *sub) { + caExceptionHandler *handler = exception_handler; + int status; + + if (! replace_handler(sub, &exception_sub, (long *)&handler)) + return; + + status = ca_add_exception_event(handler, NULL); + + if (status != ECA_NORMAL) { + SvREFCNT_dec(exception_sub); + exception_sub = NULL; + croak("%s", get_error_msg(status)); + } +} + + +/* CA::replace_printf_handler($class, \&sub) */ + +static +SV * printf_sub = NULL; + +#ifndef va_copy +# ifdef __GNUC__ +# define va_copy(d, s) __va_copy(d, s) +# else +# define va_copy(d, s) ((d) = (s)) +/* The above macro is NOT PORTABLE but works on Windows when + * stdarg.h doesn't provide va_copy(). Some architectures need + * define va_copy(d, s) ((*d) = (*s)) + * while others may be even more complicated, but hopefully the + * system stdarg.h header defines va_copy() in those cases. + */ +# endif +#endif + +static +int printf_handler(const char *format, va_list args) { + if (! printf_sub) + return 0; + + PERL_SET_CONTEXT(p5_ctx); + { + SV *printf_str; + dSP; + va_list argcopy; + + ENTER; + SAVETMPS; + + va_copy(argcopy, args); + + printf_str = NEWSV(0, strlen(format) + 32); + sv_vsetpvf(printf_str, format, &argcopy); + va_end(argcopy); + + PUSHMARK(SP); + XPUSHs(sv_2mortal(printf_str)); + PUTBACK; + + call_sv(printf_sub, G_EVAL | G_VOID | G_DISCARD); + + FREETMPS; + LEAVE; + } + return 0; +} + +void CA_replace_printf_handler(const char *class, SV *sub) { + caPrintfFunc *handler = printf_handler; + int status; + + if (! replace_handler(sub, &printf_sub, (long *)&handler)) + return; + + status = ca_replace_printf_handler(handler); + + if (status != ECA_NORMAL) { + SvREFCNT_dec(printf_sub); + printf_sub = NULL; + croak("%s", get_error_msg(status)); + } +} + + +/* CA::field_type($ca_ref) */ + +const char * CA_field_type(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + chtype t = ca_field_type(pch->chan); + if (t == TYPENOTCONN) + return "TYPENOTCONN"; + return dbr_type_to_text(t); +} + + +/* CA::element_count($ca_ref) */ + +int CA_element_count(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_element_count(pch->chan); +} + + +/* CA::name($ca_ref) */ + +const char * CA_name(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_name(pch->chan); +} + + +/* CA::state($ca_ref) */ + +const char * CA_state(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + static const char * const state_name[] = { + "never connected", "previously connected", "connected", "closed" + }; + return state_name[ca_state(pch->chan)]; +} + + +/* CA::is_connected($ca_ref) */ + +int CA_is_connected(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_state(pch->chan) == cs_conn; +} + + +/* CA::host_name($ca_ref) */ + +const char * CA_host_name(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_host_name(pch->chan); +} + + +/* CA::read_access($ca_ref) */ + +int CA_read_access(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_read_access(pch->chan); +} + + +/* CA::write_access($ca_ref) */ + +int CA_write_access(SV *ca_ref) { + CA_channel *pch = (CA_channel *)SvIV(SvRV(ca_ref)); + return ca_write_access(pch->chan); +} + +/******************************************************************************/ + +/* Ensure that the generated boot_Cap5 function is visible + * outside of the libCap5.so shared library when compiling + * with GCC4+ and -fvisibility=hidden is used. + */ +#if __GNUC__ >= 4 +XS(boot_Cap5) __attribute__ ((visibility ("default"))); +#endif + + +MODULE = Cap5 PACKAGE = Cap5 + +MODULE = Cap5 PACKAGE = CA PREFIX = CA_ + +PROTOTYPES: DISABLE + +BOOT: + p5_ctx = Perl_get_context(); + + +SV * +CA_new (class, name, ...) + const char * class + const char * name + PREINIT: + I32* temp; + CODE: + temp = PL_markstack_ptr++; + RETVAL = CA_new(class, name); + PL_markstack_ptr = temp; + OUTPUT: + RETVAL + +void +CA_DESTROY (ca_ref) + SV * ca_ref + +void +CA_context_destroy (class) + const char * class + +void +CA_change_connection_event (ca_ref, sub) + SV * ca_ref + SV * sub + +void +CA_replace_access_rights_event (ca_ref, sub) + SV * ca_ref + SV * sub + +void +CA_put (ca_ref, val, ...) + SV * ca_ref + SV * val + PREINIT: + I32* temp; + PPCODE: + temp = PL_markstack_ptr++; + CA_put(ca_ref, val); + if (PL_markstack_ptr != temp) { + /* truly void, because dXSARGS not invoked */ + PL_markstack_ptr = temp; + XSRETURN_EMPTY; /* return empty stack */ + } + /* must have used dXSARGS; list context implied */ + return; /* assume stack size is correct */ + +void +CA_put_callback (ca_ref, sub, val, ...) + SV * ca_ref + SV * sub + SV * val + PREINIT: + I32* temp; + PPCODE: + temp = PL_markstack_ptr++; + CA_put_callback(ca_ref, sub, val); + if (PL_markstack_ptr != temp) { + /* truly void, because dXSARGS not invoked */ + PL_markstack_ptr = temp; + XSRETURN_EMPTY; /* return empty stack */ + } + /* must have used dXSARGS; list context implied */ + return; /* assume stack size is correct */ + +void +CA_put_acks (ca_ref, sevr, ...) + SV * ca_ref + SV * sevr + PREINIT: + I32* temp; + PPCODE: + temp = PL_markstack_ptr++; + CA_put_acks(ca_ref, sevr); + if (PL_markstack_ptr != temp) { + /* truly void, because dXSARGS not invoked */ + PL_markstack_ptr = temp; + XSRETURN_EMPTY; /* return empty stack */ + } + /* must have used dXSARGS; list context implied */ + return; /* assume stack size is correct */ + +void +CA_put_ackt (ca_ref, ack, ...) + SV * ca_ref + int ack + PREINIT: + I32* temp; + PPCODE: + temp = PL_markstack_ptr++; + CA_put_ackt(ca_ref, ack); + if (PL_markstack_ptr != temp) { + /* truly void, because dXSARGS not invoked */ + PL_markstack_ptr = temp; + XSRETURN_EMPTY; /* return empty stack */ + } + /* must have used dXSARGS; list context implied */ + return; /* assume stack size is correct */ + +void +CA_get (ca_ref) + SV * ca_ref + +SV * +CA_value (ca_ref) + SV * ca_ref + +void +CA_get_callback (ca_ref, sub, ...) + SV * ca_ref + SV * sub + PREINIT: + I32* temp; + PPCODE: + temp = PL_markstack_ptr++; + CA_get_callback(ca_ref, sub); + if (PL_markstack_ptr != temp) { + /* truly void, because dXSARGS not invoked */ + PL_markstack_ptr = temp; + XSRETURN_EMPTY; /* return empty stack */ + } + /* must have used dXSARGS; list context implied */ + return; /* assume stack size is correct */ + +SV * +CA_create_subscription (ca_ref, mask_str, sub, ...) + SV * ca_ref + const char * mask_str + SV * sub + PREINIT: + I32* temp; + CODE: + temp = PL_markstack_ptr++; + RETVAL = CA_create_subscription(ca_ref, mask_str, sub); + PL_markstack_ptr = temp; + OUTPUT: + RETVAL + +void +CA_clear_subscription (class, mon_ref) + const char * class + SV * mon_ref + +void +CA_pend_io (class, timeout) + const char * class + double timeout + +int +CA_test_io (class) + const char * class + +void +CA_pend_event (class, timeout) + const char * class + double timeout + +void +CA_poll (class) + const char * class + +void +CA_flush_io (class) + const char * class + +const char * +CA_version (class) + const char * class + +void +CA_add_exception_event (class, sub) + const char * class + SV * sub + +void +CA_replace_printf_handler (class, sub) + const char * class + SV * sub + +const char * +CA_field_type (ca_ref) + SV * ca_ref + +int +CA_element_count (ca_ref) + SV * ca_ref + +const char * +CA_name (ca_ref) + SV * ca_ref + +const char * +CA_state (ca_ref) + SV * ca_ref + +int +CA_is_connected (ca_ref) + SV * ca_ref + +const char * +CA_host_name (ca_ref) + SV * ca_ref + +int +CA_read_access (ca_ref) + SV * ca_ref + +int +CA_write_access (ca_ref) + SV * ca_ref + diff --git a/modules/ca/src/perl/Makefile b/modules/ca/src/perl/Makefile new file mode 100644 index 000000000..5b2bd5920 --- /dev/null +++ b/modules/ca/src/perl/Makefile @@ -0,0 +1,71 @@ +#************************************************************************* +# 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. +#************************************************************************* + +TOP = ../.. +include $(TOP)/configure/CONFIG + +# Special settings for Darwin: +ifeq ($(OS_CLASS),Darwin) + # Use hdepends command (not GNU compiler flags) + # to generate header file dependancies for Darwin. + # Darwin has multiple -arch compiler flags. + HDEPENDS_METHOD = MKMF + + # Perl loadable libraries on Darwin have funny names + LOADABLE_SHRLIB_PREFIX = + LOADABLE_SHRLIB_SUFFIX = .$(shell $(PERL) ../perlConfig.pl dlext) +endif + +ifdef T_A + PERL_VERSION = $(shell $(PERL) ../perlConfig.pl version) + PERL_ARCHNAME = $(shell $(PERL) ../perlConfig.pl archname) + PERL_ARCHPATH := $(PERL_VERSION)/$(PERL_ARCHNAME) + + EXTUTILS := $(shell $(PERL) ../perlConfig.pl privlib)/ExtUtils + PERLBIN := $(shell $(PERL) ../perlConfig.pl bin) + XSUBPP := $(firstword $(wildcard $(PERLBIN)/xsubpp $(EXTUTILS)/xsubpp)) + +ifeq ($(strip $(XSUBPP)),) + $(warning Perl's xsubpp program was not found.) + $(warning The Perl CA module will not be built.) +else +ifeq ($(T_A),$(EPICS_HOST_ARCH)) # No cross-builds (wrong Perl!) +ifeq ($(findstring $(OS_CLASS),WIN32 cygwin32),) # Doesn't build on WIN32 + LOADABLE_LIBRARY_HOST = Cap5 + + PERL_SCRIPTS += capr.pl + + PERL_MODULES += CA.pm + PERL_MODULES += $(PERL_ARCHPATH)/$(LOADABLE_SHRLIB_PREFIX)Cap5$(LOADABLE_SHRLIB_SUFFIX) + + PERL_SCRIPTS += caModuleDirs.pm + + HTMLS_DIR = . + HTMLS = CA.html +endif +endif +endif +endif + +Cap5_SRCS = Cap5.xs +Cap5_LIBS = ca Com +Cap5_INCLUDES = -I$(shell $(PERL) ../perlConfig.pl archlib)/CORE +Cap5_CFLAGS = $(shell $(PERL) ../perlConfig.pl ccflags) + +CLEANS += Cap5.c + +include $(TOP)/configure/RULES + +ifdef T_A + %.c: ../%.xs + $(RM) $@ $@_new + $(PERL) $(XSUBPP) -typemap $(EXTUTILS)/typemap $< > $@_new && $(MV) $@_new $@ + + $(INSTALL_PERL_MODULES)/$(PERL_ARCHPATH)/%: % + $(ECHO) "Installing loadable shared library $@" + @$(INSTALL_LIBRARY) -d -m $(LIB_PERMISSIONS) $< $(INSTALL_PERL_MODULES)/$(PERL_ARCHPATH) +endif diff --git a/modules/ca/src/perl/capr.pl b/modules/ca/src/perl/capr.pl new file mode 100644 index 000000000..e551afcef --- /dev/null +++ b/modules/ca/src/perl/capr.pl @@ -0,0 +1,424 @@ +#!/usr/bin/env perl + +####################################################################### +# +# capr: A program that attempts to do a "dbpr" command via channel +# access. +# +####################################################################### + +use strict; + +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use caModuleDirs; +no lib $Bin; + +use Getopt::Std; +use EPICS::Path; +use CA; + +######### Globals ########## + +our ($opt_h, $opt_f, $opt_r); +our $opt_d = $ENV{EPICS_CAPR_DBD_FILE} || "$Bin/../../dbd/softIoc.dbd"; +our $opt_w = 1; + +my %record = (); # Empty hash to put dbd data in +my $iIdx = 0; # Array indexes for interest, data type and base +my $tIdx = 1; +my $bIdx = 2; +my %device = (); # Empty hash to record which rec types have device support + +# EPICS field types +my %fieldType = ( + DBF_CHAR => 'DBF_CHAR', + DBF_UCHAR => 'DBF_CHAR', + DBF_DOUBLE => 'DBF_FLOAT', + DBF_FLOAT => 'DBF_FLOAT', + DBF_LONG => 'DBF_LONG', + DBF_SHORT => 'DBF_LONG', + DBF_ULONG => 'DBF_LONG', + DBF_USHORT => 'DBF_LONG', + DBF_DEVICE => 'DBF_STRING', + DBF_ENUM => 'DBF_STRING', + DBF_FWDLINK => 'DBF_STRING', + DBF_INLINK => 'DBF_STRING', + DBF_MENU => 'DBF_STRING', + DBF_OUTLINK => 'DBF_STRING', + DBF_STRING => 'DBF_STRING', + DBF_NOACCESS => 'DBF_NOACCESS', +); + +# globals for sub caget +my %callback_data; +my %timed_out; +my $callback_incomplete; + +######### Main program ############ + +HELP_MESSAGE() unless getopts('hd:f:rw:'); +HELP_MESSAGE() if $opt_h; + +die "File $opt_d not found. (\"capr.pl -h\" gives help)\n" + unless -f $opt_d; + +parseDbd($opt_d); +print "Using $opt_d\n\n"; + +# Print a list of record types +if ($opt_r) { + print ("Record types found:\n"); + printList(0); + exit; +} + +# Print the fields defined for given record +if ($opt_f) { + printRecordList($opt_f); + exit; +} + +HELP_MESSAGE() unless @ARGV; + +$_ = shift; +if (@ARGV) { + # Drop any ".FIELD" part + s/\. \w+ $//x; + printRecord($_, @ARGV); +} else { + if (m/^ \s* ([]+:;<>0-9A-Za-z[-]+) (?:\. \w+)? \s* , \s* (\d+) \s* $/x) { + # Recognizes ",n" as an interest leve, drops any ".FIELD" part + printRecord($1, $2); + } else { + # Drop any ".FIELD" part + s/\. \w+ $//x; + printRecord($_, 0); + } +} + +########## End of main ########### + + + +# parseDbd +# Takes given dbd file and parses it to produce a hash table of record types +# giving their fields, and for each field its interest level and data type. +# usage: parseDbd("fileName"); +# Output goes to the global %record, a hash of references to other hashes +# containing the fields of each record type. Those hash values (keyed by +# field name) are references to arrays containing the interest level, data +# type and number base of the field. +sub parseDbd { + my $dbdFile = shift; + + open(DBD, "< $dbdFile") or die "Can't open file $dbdFile: $!\n"; + my @dbd = ; + close(DBD) or die "Can't close $dbdFile: $!\n"; + + my $i = 1; + my $level = 0; + my $isArecord = 0; + my $isAfield = 0; + my $thisRecord; + my $thisField; + my $thisType; + my $field = {}; + my $interest = 0; + my $thisBase = 'DECIMAL'; + + while (@dbd) { + $_ = shift @dbd; + chomp; + if ( m/recordtype \s* \( \s* (\w+) \)/x ) { + die "File format error at line $i of file\n $opt_d\n" + unless $level == 0; + $isArecord = 1; + $thisRecord = $1; + } + elsif ( m/field \s* \( \s* (\w+) \s* , \s* (\w+) \s* \)/x ) { + die "File format error at line $i of file\n $opt_d\n" + unless $level == 1 && $isArecord; + $thisField = $1; + $thisType = $2; + $isAfield = 1; + } + elsif ( m/interest \s* \( \s* (\w+) \s* \)/x ) { + die "File format error at line $i of file\n $opt_d\n" + unless $level == 2 && $isAfield; + $interest = $1; + } + elsif ( m/base \s* \( \s* (\w+) \s* \)/x ) { + die "File format error at line $i of file\n $opt_d\n" + unless $level == 2 && $isAfield; + $thisBase = $1; + } + elsif ( m/device \s* \( (\w+) \s* ,/x ) { + die "File format error at line $i of file\n $opt_d\n" + unless $level == 0; + $device{$1}++; + } + if ( m/\{/ ) { + $level++; + } + if ( m/\}/ ) { + if ($level == 2 && $isAfield) { + my $params = []; + $params->[$iIdx] = $interest; + $params->[$tIdx] = $thisType; + $params->[$bIdx] = $thisBase; + $field->{$thisField} = $params; + $isAfield = 0; + $interest = 0; # reset default + $thisBase = 'DECIMAL'; # reset default + } + elsif ($level == 1 && $isArecord) { + $isArecord = 0; + $record{$thisRecord} = $field; + $field = {}; # start another hash + } + $level--; + } + $i++; + } +} + + +# Given a record name, attempts to find the record and its type. +# Usage: $recordType = getRecType("recordName"); +sub getRecType { + my $arg = shift; + my $name = "$arg.RTYP"; + + my $fields_read = caget($name); + + die "Could not determine record type of $arg\n" + unless $fields_read == 1; + + return $callback_data{$name}; +} + +# Given the record type and field, returns the interest level, data type +# and number base for the field +# Usage: ($dataType, $interest, $base) = getFieldParams($recType, $field); +sub getFieldParams { + my ($recType, $field) = @_; + + my $params = $record{$recType}{$field} or + die "Can't find params for $recType.$field"; + exists($fieldType{$params->[$tIdx]}) || + die "Field data type $field for $recType not found in dbd file --"; + exists($params->[$iIdx]) || + die "Interest level for $field in $recType not found in dbd file --"; + + my $fType = $fieldType{$params->[$tIdx]}; + my $fInterest = $params->[$iIdx]; + my $fBase = $params->[$bIdx]; + return ($fType, $fInterest, $fBase); +} + +# Prints field name and data for given field. Formats output so +# that fields align in to 4 columns. Tries to imitate dbpf format +# Usage: printField( $fieldName, $data, $dataType, $base, $firstColumnPosn) +sub printField { + my ($fieldName, $fieldData, $dataType, $base, $col) = @_; + + my $screenWidth = 80; + my ($outStr, $wide); + + my $field = "$fieldName:"; + + if ( $dataType eq 'DBF_STRING' ) { + $outStr = sprintf('%-5s %s', $field, $fieldData); + } elsif ( $base eq 'HEX' ) { + my $val = ( $dataType eq 'DBF_CHAR' ) ? ord($fieldData) : $fieldData; + $outStr = sprintf('%-5s 0x%x', $field, $val); + } elsif ( $dataType eq 'DBF_DOUBLE' || $dataType eq 'DBF_FLOAT' ) { + $outStr = sprintf('%-5s %.8f', $field, $fieldData); + } elsif ( $dataType eq 'DBF_CHAR' ) { + $outStr = sprintf('%-5s %d', $field, ord($fieldData)); + }else { + # DBF_LONG, DBF_SHORT, DBF_UCHAR, DBF_ULONG, DBF_USHORT + $outStr = sprintf('%-5s %d', $field, $fieldData); + } + + my $len = length($outStr); + if ($len <= 20) { $wide = 20; } + elsif ( $len <= 40 ) { $wide = 40; } + elsif ( $len <= 60 ) { $wide = 60; } + else { $wide = 80;} + + my $pad = $wide - $len; + + $col += $wide; + if ($col > $screenWidth ) { + print("\n"); + $col = $wide; + } + + print $outStr, ' ' x $pad; + + return $col; +} + +# Query for a list of fields simultaneously. +# The results are filled in the the %callback_data global hash +# and the result of the operation is the number of read pvs +# +# NOTE: Not re-entrant because results are written to global hash +# %callback_data +# +# Usage: $fields_read = caget( @pvlist ) +sub caget { + my @chans = map { CA->new($_); } @_; + + #clear results; + %callback_data = (); + %timed_out = (); + + eval { CA->pend_io($opt_w); }; + if ($@) { + if ($@ =~ m/^ECA_TIMEOUT/) { + my $err = (@chans > 1) ? 'some PV(s)' : '"' . $chans[0]->name . '"'; + print "Channel connect timed out: $err not found.\n"; + foreach my $chan (@chans) { + $timed_out{$chan->name} = $chan->is_connected; + } + @chans = grep { $_->is_connected } @chans; + } else { + die $@; + } + } + + map { + my $type; + $type = $_->field_type; + $_->get_callback(\&caget_callback, $type); + } @chans; + + my $fields_read = @chans; + $callback_incomplete = @chans; + CA->pend_event(0.1) while $callback_incomplete; + return $fields_read; +} + +sub caget_callback { + my ($chan, $status, $data) = @_; + die $status if $status; + $callback_data{$chan->name} = $data; + $callback_incomplete--; +} + +# Given record name and interest level prints data from record fields +# that are at or below the interest level specified. +# Usage: printRecord($recordName, $interestLevel) +sub printRecord { + my ($name, $interest) = @_; + + my $recType = getRecType($name); + print("Record $name type $recType\n"); + die "Record type $recType not found\n" + unless exists $record{$recType}; + + #capture list of fields + my @readlist = (); #fields to read via CA + my @fields_pr = (); #fields for print-out + my @ftypes = (); #types, from parser + my @bases = (); #bases, from parser + foreach my $field (sort keys %{$record{$recType}}) { + # Skip DTYP field if this rec type doesn't have device support defined + if ($field eq 'DTYP' && !(exists($device{$recType}))) { next; } + + my ($fType, $fInterest, $base) = getFieldParams($recType, $field); + unless( $fType eq 'DBF_NOACCESS' ) { + if ($interest >= $fInterest ) { + my $fToGet = "$name.$field"; + push @fields_pr, $field; + push @readlist, $fToGet; + push @ftypes, $fType; + push @bases, $base; + } + } + } + my $fields_read = caget( @readlist ); + + # print while iterating over lists gathered + my $col = 0; + for (my $i=0; $i < scalar @readlist; $i++) { + my $field = $fields_pr[$i]; + my $fToGet = $readlist[$i]; + my ($fType, $data, $base); + if ($timed_out{$fToGet}) { + $fType = $fieldType{DBF_STRING}; + $data = ''; + } + else { + $fType = $ftypes[$i]; + $base = $bases[$i]; + $data = $callback_data{$fToGet}; + } + $col = printField($field, $data, $fType, $base, $col); + } + print("\n"); # Final newline +} + +# Prints list of record types found in dbd file. If level > 0 +# then the fields of that record type, their interest levels and types are +# also printed. +# Diagnostic routine, usage: void printList(level); +sub printList { + my $level = shift; + + foreach my $rkey (sort keys(%record)) { + print(" $rkey\n"); + if ($level > 0) { + foreach my $fkey (keys %{$record{$rkey}}) { + print("\tField $fkey - interest $record{$rkey}{$fkey}[$iIdx] "); + print("- type $record{$rkey}{$fkey}[$tIdx] "); + print("- base $record{$rkey}{$fkey}[$bIdx]\n"); + } + } + } +} + +# Prints list of fields with interest levels for given record type +# Diagnostic routine, usage: void printRecordList("recordType"); +sub printRecordList { + my $type = shift; + + if (exists($record{$type}) ) { + print("Record type - $type\n"); + foreach my $fkey (sort keys %{$record{$type}}) { + printf('%-4s', $fkey); + printf(" interest = $record{$type}{$fkey}[$iIdx]"); + printf(" type = %-12s ",$record{$type}{$fkey}[$tIdx]); + print (" base = $record{$type}{$fkey}[$bIdx]\n"); + } + } + else { + print("Record type $type not defined in dbd file $opt_d\n"); + } +} + +sub HELP_MESSAGE { + print STDERR "\n", + "Usage: capr.pl -h\n", + " capr.pl [options] -r\n", + " capr.pl [options] -f \n", + " capr.pl [options] []\n", + "\n", + " -h Print this help message.\n", + "Channel Access options:\n", + " -w : Wait time, specifies CA timeout, default is $opt_w second\n", + "Database Definitions:\n", + " -d : The file containing record type definitions.\n", + " This can be set using the EPICS_CAPR_DBD_FILE environment variable.\n", + " Default: ", AbsPath($opt_d), "\n", + "Output Options:\n", + " -r Lists all record types in the selected dbd file.\n", + " -f : Lists all fields with their interest level, data type\n", + " and number base for the given record_type.\n", + "\n", + "Base version: ", CA->version, "\n"; + exit 1; +} diff --git a/modules/ca/src/perl/perlConfig.pl b/modules/ca/src/perl/perlConfig.pl new file mode 100644 index 000000000..5292041d1 --- /dev/null +++ b/modules/ca/src/perl/perlConfig.pl @@ -0,0 +1,15 @@ +#!/usr/bin/env perl + +# This script is used to extract information about the Perl build +# configuration, so the EPICS build system uses the same settings. + +use strict; +use Config; + +my $arg = shift; +my $val = $Config{$arg}; + +$val =~ s{\\}{/}go + if $^O eq 'MSWin32'; + +print $val; diff --git a/modules/ca/src/template/Makefile b/modules/ca/src/template/Makefile new file mode 100644 index 000000000..913d92bf4 --- /dev/null +++ b/modules/ca/src/template/Makefile @@ -0,0 +1,17 @@ +TOP=../.. + +include $(TOP)/configure/CONFIG + +TEMPLATES_DIR = makeBaseApp + +TEMPLATES += top/caClientApp/Makefile +TEMPLATES += top/caClientApp/caExample.c +TEMPLATES += top/caClientApp/caMonitor.c + +TEMPLATES += top/caPerlApp/Makefile +TEMPLATES += top/caPerlApp/cainfo.pl +TEMPLATES += top/caPerlApp/caget.pl +TEMPLATES += top/caPerlApp/caput.pl +TEMPLATES += top/caPerlApp/camonitor.pl + +include $(TOP)/configure/RULES diff --git a/modules/ca/src/template/top/caClientApp/Makefile b/modules/ca/src/template/top/caClientApp/Makefile new file mode 100644 index 000000000..47011375c --- /dev/null +++ b/modules/ca/src/template/top/caClientApp/Makefile @@ -0,0 +1,19 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +PROD_HOST += caExample +caExample_SRCS += caExample.c +caExample_LIBS += $(EPICS_BASE_HOST_LIBS) + +PROD_HOST += caMonitor +caMonitor_SRCS += caMonitor.c +caMonitor_LIBS += $(EPICS_BASE_HOST_LIBS) + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/ca/src/template/top/caClientApp/caExample.c b/modules/ca/src/template/top/caClientApp/caExample.c new file mode 100644 index 000000000..cc342e237 --- /dev/null +++ b/modules/ca/src/template/top/caClientApp/caExample.c @@ -0,0 +1,25 @@ +/*caExample.c*/ +#include +#include +#include +#include + +#include "cadef.h" + +int main(int argc,char **argv) +{ + double data; + chid mychid; + + if(argc != 2) { + fprintf(stderr,"usage: caExample pvname\n"); + exit(1); + } + SEVCHK(ca_context_create(ca_disable_preemptive_callback),"ca_context_create"); + SEVCHK(ca_create_channel(argv[1],NULL,NULL,10,&mychid),"ca_create_channel failure"); + SEVCHK(ca_pend_io(5.0),"ca_pend_io failure"); + SEVCHK(ca_get(DBR_DOUBLE,mychid,(void *)&data),"ca_get failure"); + SEVCHK(ca_pend_io(5.0),"ca_pend_io failure"); + printf("%s %f\n",argv[1],data); + return(0); +} diff --git a/modules/ca/src/template/top/caClientApp/caMonitor.c b/modules/ca/src/template/top/caClientApp/caMonitor.c new file mode 100644 index 000000000..9554cc744 --- /dev/null +++ b/modules/ca/src/template/top/caClientApp/caMonitor.c @@ -0,0 +1,130 @@ +/*caMonitor.c*/ + +/* This example accepts the name of a file containing a list of pvs to monitor. + * It prints a message for all ca events: connection, access rights and monitor. + */ + +#include +#include +#include +#include + +#include "cadef.h" +#include "dbDefs.h" +#include "epicsString.h" +#include "cantProceed.h" + +#define MAX_PV 1000 +#define MAX_PV_NAME_LEN 40 + +typedef struct{ + char value[20]; + chid mychid; + evid myevid; +} MYNODE; + + +static void printChidInfo(chid chid, char *message) +{ + printf("\n%s\n",message); + printf("pv: %s type(%d) nelements(%ld) host(%s)", + ca_name(chid),ca_field_type(chid),ca_element_count(chid), + ca_host_name(chid)); + printf(" read(%d) write(%d) state(%d)\n", + ca_read_access(chid),ca_write_access(chid),ca_state(chid)); +} + +static void exceptionCallback(struct exception_handler_args args) +{ + chid chid = args.chid; + long stat = args.stat; /* Channel access status code*/ + const char *channel; + static char *noname = "unknown"; + + channel = (chid ? ca_name(chid) : noname); + + + if(chid) printChidInfo(chid,"exceptionCallback"); + printf("exceptionCallback stat %s channel %s\n", + ca_message(stat),channel); +} + +static void connectionCallback(struct connection_handler_args args) +{ + chid chid = args.chid; + + printChidInfo(chid,"connectionCallback"); +} + +static void accessRightsCallback(struct access_rights_handler_args args) +{ + chid chid = args.chid; + + printChidInfo(chid,"accessRightsCallback"); +} +static void eventCallback(struct event_handler_args eha) +{ + chid chid = eha.chid; + + if(eha.status!=ECA_NORMAL) { + printChidInfo(chid,"eventCallback"); + } else { + char *pdata = (char *)eha.dbr; + printf("Event Callback: %s = %s\n",ca_name(eha.chid),pdata); + } +} + +int main(int argc,char **argv) +{ + char *filename; + int npv = 0; + MYNODE *pmynode[MAX_PV]; + char *pname[MAX_PV]; + int i; + char tempStr[MAX_PV_NAME_LEN]; + char *pstr; + FILE *fp; + + if (argc != 2) { + fprintf(stderr,"usage: caMonitor filename\n"); + exit(1); + } + filename = argv[1]; + fp = fopen(filename,"r"); + if (!fp) { + perror("fopen failed"); + return(1); + } + while (npv < MAX_PV) { + size_t len; + + pstr = fgets(tempStr, MAX_PV_NAME_LEN, fp); + if (!pstr) break; + + len = strlen(pstr); + if (len <= 1) continue; + + pstr[len - 1] = '\0'; /* Strip newline */ + pname[npv] = epicsStrDup(pstr); + pmynode[npv] = callocMustSucceed(1, sizeof(MYNODE), "caMonitor"); + npv++; + } + fclose(fp); + SEVCHK(ca_context_create(ca_disable_preemptive_callback),"ca_context_create"); + SEVCHK(ca_add_exception_event(exceptionCallback,NULL), + "ca_add_exception_event"); + for (i=0; imychid), + "ca_create_channel"); + SEVCHK(ca_replace_access_rights_event(pmynode[i]->mychid, + accessRightsCallback), + "ca_replace_access_rights_event"); + SEVCHK(ca_create_subscription(DBR_STRING,1,pmynode[i]->mychid, + DBE_VALUE,eventCallback,pmynode[i],&pmynode[i]->myevid), + "ca_create_subscription"); + } + /*Should never return from following call*/ + SEVCHK(ca_pend_event(0.0),"ca_pend_event"); + return 0; +} diff --git a/modules/ca/src/template/top/caPerlApp/Makefile b/modules/ca/src/template/top/caPerlApp/Makefile new file mode 100644 index 000000000..34439cbfd --- /dev/null +++ b/modules/ca/src/template/top/caPerlApp/Makefile @@ -0,0 +1,17 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +PERL_SCRIPTS += _APPNAME_ModuleDirs.pm + +PERL_SCRIPTS += cainfo.pl +PERL_SCRIPTS += caput.pl +PERL_SCRIPTS += caget.pl +PERL_SCRIPTS += camonitor.pl + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/modules/ca/src/template/top/caPerlApp/caget.pl b/modules/ca/src/template/top/caPerlApp/caget.pl new file mode 100644 index 000000000..5e7be9f79 --- /dev/null +++ b/modules/ca/src/template/top/caPerlApp/caget.pl @@ -0,0 +1,194 @@ +#!/usr/bin/env perl + +use strict; + +# This construct sets @INC to search lib/perl of all RELEASE entries +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use _APPNAME_ModuleDirs; +no lib $Bin; + +use Getopt::Std; +use Scalar::Util qw(looks_like_number); +use CA; + +our ($opt_0, $opt_a, $opt_d, $opt_e, $opt_f, $opt_g, $opt_h, $opt_n); +our ($opt_s, $opt_S, $opt_t); +our $opt_c = 0; +our $opt_F = ' '; +our $opt_w = 1; + +$Getopt::Std::OUTPUT_HELP_VERSION = 1; + +HELP_MESSAGE() unless getopts('0:ac:d:e:f:F:g:hnsStw:'); +HELP_MESSAGE() if $opt_h; + +die "caget.pl: -c option takes a positive number\n" + unless looks_like_number($opt_c) && $opt_c >= 0; + +die "No pv name specified. ('caget.pl -h' gives help.)\n" + unless @ARGV; + +my @chans = map { CA->new($_); } grep { $_ ne '' } @ARGV; + +die "caget.pl: Please provide at least one non-empty pv name\n" + unless @chans; + +eval { CA->pend_io($opt_w); }; +if ($@) { + if ($@ =~ m/^ECA_TIMEOUT/) { + my $err = (@chans > 1) ? 'some PV(s)' : "'" . $chans[0]->name . "'"; + print "Channel connect timed out: $err not found.\n"; + @chans = grep { $_->is_connected } @chans; + } else { + die $@; + } +} + +my %rtype; + +map { + my $type; + if ($opt_d) { + $type = $opt_d; + } else { + $type = $_->field_type; + $type = 'DBR_STRING' + if $opt_s && $type =~ m/ ^ DBR_ ( DOUBLE | FLOAT ) $ /x; + $type = 'DBR_LONG' + if ($opt_n && $type eq 'DBR_ENUM') + || (!$opt_S && $type eq 'DBR_CHAR'); + $type =~ s/^DBR_/DBR_TIME_/ + if $opt_a; + } + $rtype{$_} = $type; + $_->get_callback(\&get_callback, $type, 0+$opt_c); +} @chans; + +my $incomplete = @chans; +CA->pend_event(0.1) while $incomplete; + + +sub get_callback { + my ($chan, $status, $data) = @_; + die $status if $status; + display($chan, $rtype{$chan}, $data); + $incomplete--; +} + +sub format_number { + my ($data, $type) = @_; + if ($type =~ m/_DOUBLE$/) { + return sprintf "%.${opt_e}e", $data if $opt_e; + return sprintf "%.${opt_f}f", $data if $opt_f; + return sprintf "%.${opt_g}g", $data if $opt_g; + } + if ($type =~ m/_LONG$/) { + return sprintf "%lx", $data if $opt_0 eq 'x'; + return sprintf "%lo", $data if $opt_0 eq 'o'; + if ($opt_0 eq 'b') { + my $bin = unpack "B*", pack "l", $data; + $bin =~ s/^0*//; + return $bin; + } + } + return $data; +} + +sub display { + my ($chan, $type, $data) = @_; + if (ref $data eq 'ARRAY') { + display($chan, $type, join($opt_F, scalar @{$data}, @{$data})); + } elsif (ref $data eq 'HASH') { + printf "%s\n", $chan->name; + my $dtype = $data->{TYPE}; # Can differ from request + printf " Native data type: %s\n", $chan->field_type; + printf " Request type: %s\n", $dtype; + printf " Element count: %d\n", $data->{COUNT} + if exists $data->{COUNT}; + my $val = $data->{value}; + if (ref $val eq 'ARRAY') { + printf " Value: %s\n", join($opt_F, + map { format_number($_, $dtype); } @{$val}); + } else { + printf " Value: %s\n", format_number($val, $dtype); + } + if (exists $data->{stamp}) { + my @t = localtime $data->{stamp}; + splice @t, 6; + $t[5] += 1900; + $t[0] += $data->{stamp_fraction}; + printf " Timestamp: %4d-%02d-%02d %02d:%02d:%09.6f\n", + reverse @t; + } + printf " Status: %s\n", $data->{status}; + printf " Severity: %s\n", $data->{severity}; + if (exists $data->{units}) { + printf " Units: %s\n", $data->{units}; + } + if (exists $data->{precision}) { + printf " Precision: %d\n", $data->{precision}; + } + if (exists $data->{upper_disp_limit}) { + printf " Lo disp limit: %g\n", $data->{lower_disp_limit}; + printf " Hi disp limit: %g\n", $data->{upper_disp_limit}; + } + if (exists $data->{upper_alarm_limit}) { + printf " Lo alarm limit: %g\n", $data->{lower_alarm_limit}; + printf " Lo warn limit: %g\n", $data->{lower_warning_limit}; + printf " Hi warn limit: %g\n", $data->{upper_warning_limit}; + printf " Hi alarm limit: %g\n", $data->{upper_alarm_limit}; + } + if (exists $data->{upper_ctrl_limit}) { + printf " Lo ctrl limit: %g\n", $data->{lower_ctrl_limit}; + printf " Hi ctrl limit: %g\n", $data->{upper_ctrl_limit}; + } + } else { + my $value = format_number($data, $type); + if ($opt_t) { + print "$value\n"; + } else { + printf "%s%s%s\n", $chan->name, $opt_F, $value; + } + } +} + +sub HELP_MESSAGE { + print STDERR "\nUsage: caget.pl [options] ...\n", + "\n", + " -h: Help: Print this message\n", + "Channel Access options:\n", + " -w : Wait time, specifies CA timeout, default is $opt_w second\n", + "Format options:\n", + " -t: Terse mode - print only value, without name\n", + " -a: Wide mode \"name timestamp value stat sevr\" (read PVs as DBR_TIME_xxx)\n", + " -d : Request specific dbr type from one of the following:\n", + " DBR_STRING DBR_LONG DBR_DOUBLE\n", + " DBR_STS_STRING DBR_STS_LONG DBR_STS_DOUBLE\n", + " DBR_TIME_STRING DBR_TIME_LONG DBR_TIME_DOUBLE\n", + " DBR_GR_STRING DBR_GR_LONG DBR_GR_DOUBLE DBR_GR_ENUM\n", + " DBR_CTRL_STRING DBR_CTRL_LONG DBR_CTRL_DOUBLE DBR_CTRL_ENUM\n", + " DBR_CLASS_NAME DBR_STSACK_STRING\n", + "Arrays: Value format: print number of values, then list of values\n", + " Default: Print all values\n", + " -c : Print first elements of an array\n", + " -S: Print array of char as a string (long string)\n", + "Enum format:\n", + " -n: Print DBF_ENUM value as number (default is enum string)\n", + "Floating point type format:\n", + " Default: Use %g format\n", + " -e : Use %e format, with a precision of digits\n", + " -f : Use %f format, with a precision of digits\n", + " -g : Use %g format, with a precision of digits\n", + " -s: Get value as string (may honour server-side precision)\n", + "Integer number format:\n", + " Default: Print as decimal number\n", + " -0x: Print as hex number\n", + " -0o: Print as octal number\n", + " -0b: Print as binary number\n", + "Set output field separator:\n", + " -F : Use to separate fields on output\n", + "\n", + "Base version: ", CA->version, "\n"; + exit 1; +} diff --git a/modules/ca/src/template/top/caPerlApp/cainfo.pl b/modules/ca/src/template/top/caPerlApp/cainfo.pl new file mode 100644 index 000000000..3e38e8baf --- /dev/null +++ b/modules/ca/src/template/top/caPerlApp/cainfo.pl @@ -0,0 +1,71 @@ +#!/usr/bin/env perl + +use strict; + +# This construct sets @INC to search lib/perl of all RELEASE entries +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use _APPNAME_ModuleDirs; +no lib $Bin; + +use Getopt::Std; +use CA; + +our $opt_w = 1; +our $opt_h; + +$Getopt::Std::OUTPUT_HELP_VERSION = 1; + +HELP_MESSAGE() unless getopts('hw:'); +HELP_MESSAGE() if $opt_h; + +die "No pv name specified. ('cainfo.pl -h' gives help.)\n" + unless @ARGV; + +my @chans = map { CA->new($_); } grep { $_ ne '' } @ARGV; + +die "cainfo.pl: Please provide at least one non-empty pv name\n" + unless @chans; + +eval { + CA->pend_io($opt_w); +}; +if ($@) { + if ($@ =~ m/^ECA_TIMEOUT/) { + my $err = (@chans > 1) ? 'some PV(s)' : "'" . $chans[0]->name . "'"; + print "Channel connect timed out: $err not found.\n"; + } else { + die $@; + } +} + +map { display($_); } @chans; + + +sub display { + my $chan = shift; + + printf "%s\n", $chan->name; + printf " State: %s\n", $chan->state; + printf " Host: %s\n", $chan->host_name; + + my @access = ('no ', ''); + printf " Access: %sread, %swrite\n", + $access[$chan->read_access], $access[$chan->write_access]; + + printf " Data type: %s\n", $chan->field_type; + printf " Element count: %d\n", $chan->element_count; +} + +sub HELP_MESSAGE { + print STDERR "\nUsage: cainfo.pl [options] ...\n", + "\n", + " -h: Help: Print this message\n", + "Channel Access options:\n", + " -w : Wait time, specifies CA timeout, default is $opt_w second\n", + "\n", + "Example: cainfo my_channel another_channel\n", + "\n", + "Base version: ", CA->version, "\n"; + exit 1; +} diff --git a/modules/ca/src/template/top/caPerlApp/camonitor.pl b/modules/ca/src/template/top/caPerlApp/camonitor.pl new file mode 100644 index 000000000..564688d68 --- /dev/null +++ b/modules/ca/src/template/top/caPerlApp/camonitor.pl @@ -0,0 +1,156 @@ +#!/usr/bin/env perl + +use strict; + +# This construct sets @INC to search lib/perl of all RELEASE entries +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use _APPNAME_ModuleDirs; +no lib $Bin; + +use Getopt::Std; +use Scalar::Util qw(looks_like_number); +use CA; + +our ($opt_0, $opt_e, $opt_f, $opt_g, $opt_h, $opt_n, $opt_s, $opt_S); +our $opt_c = 0; +our $opt_F = ' '; +our $opt_w = 1; +our $opt_m = 'va'; + +$Getopt::Std::OUTPUT_HELP_VERSION = 1; + +HELP_MESSAGE() unless getopts('0:c:e:f:F:g:hm:nsSw:'); +HELP_MESSAGE() if $opt_h; + +die "camonitor.pl: -c option takes a positive number\n" + unless looks_like_number($opt_c) && $opt_c >= 0; + +die "No pv name specified. ('camonitor.pl -h' gives help.)\n" + unless @ARGV; + +my %monitors; +my @chans = map { CA->new($_, \&conn_callback); } grep { $_ ne '' } @ARGV; + +die "camonitor.pl: Please provide at least one non-empty pv name\n" + unless @chans; + +my $fmt = ($opt_F eq ' ') ? "%-30s %s\n" : "%s$opt_F%s\n"; + +CA->pend_event($opt_w); +map { + printf $fmt, $_->name, '*** Not connected (PV not found)' + unless $monitors{$_}; +} @chans; +CA->pend_event(0); + + +sub conn_callback { + my ($chan, $up) = @_; + if ($up && ! $monitors{$chan}) { + my $type = $chan->field_type; + $type = 'DBR_STRING' + if $opt_s && $type =~ m/ ^ DBR_ ( DOUBLE | FLOAT ) $ /x; + $type = 'DBR_LONG' + if ($opt_n && $type eq 'DBR_ENUM') + || (!$opt_S && $type eq 'DBR_CHAR'); + $type =~ s/^DBR_/DBR_TIME_/; + + $monitors{$chan} = + $chan->create_subscription($opt_m, \&mon_callback, $type, 0+$opt_c); + } +} + +sub mon_callback { + my ($chan, $status, $data) = @_; + if ($status) { + printf $fmt, $chan->name, $status; + } else { + display($chan, $data); + } +} + +sub format_number { + my ($data, $type) = @_; + if ($type =~ m/_DOUBLE$/) { + return sprintf "%.${opt_e}e", $data if $opt_e; + return sprintf "%.${opt_f}f", $data if $opt_f; + return sprintf "%.${opt_g}g", $data if $opt_g; + } + if ($type =~ m/_LONG$/) { + return sprintf "%lx", $data if $opt_0 eq 'x'; + return sprintf "%lo", $data if $opt_0 eq 'o'; + if ($opt_0 eq 'b') { + my $bin = unpack "B*", pack "l", $data; + $bin =~ s/^0*//; + return $bin; + } + } + return $data; +} + +sub display { + my ($chan, $data) = @_; + die "Internal error" + unless ref $data eq 'HASH'; + + my $type = $data->{TYPE}; + my $value = $data->{value}; + if (ref $value eq 'ARRAY') { + $value = join($opt_F, $data->{COUNT}, + map { format_number($_, $type); } @{$value}); + } else { + $value = format_number($value, $type); + } + my $stamp; + if (exists $data->{stamp}) { + my @t = localtime $data->{stamp}; + splice @t, 6; + $t[5] += 1900; + $t[0] += $data->{stamp_fraction}; + $stamp = sprintf "%4d-%02d-%02d %02d:%02d:%09.6f", reverse @t; + } + printf $fmt, $chan->name, join($opt_F, + $stamp, $value, $data->{status}, $data->{severity}); +} + +sub HELP_MESSAGE { + print STDERR "\nUsage: camonitor.pl [options] ...\n", + "\n", + " -h: Help: Print this message\n", + "Channel Access options:\n", + " -w : Wait time, specifies CA timeout, default is $opt_w second\n", + " -m : Specify CA event mask to use, with being any combination of\n", + " 'v' (value), 'a' (alarm), 'l' (log/archive), 'p' (property)", + " Default: '$opt_m'\n", +# "Timestamps:\n", +# " Default: Print absolute timestamps (as reported by CA)\n", +# " -r: Relative timestamps (time elapsed since start of program)\n", +# " -i: Incremental timestamps (time elapsed since last update)\n", +# " -I: Incremental timestamps (time elapsed since last update for this channel)\n", + "Enum format:\n", + " -n: Print DBF_ENUM values as number (default are enum string values)\n", + "Arrays: Value format: print number of values, then list of values\n", + " Default: Print all values\n", + " -c : Print first elements of an array\n", + " -S: Print array of char as a string (long string)\n", + "Floating point type format:\n", + " Default: Use %g format\n", + " -e : Use %e format, with a precision of digits\n", + " -f : Use %f format, with a precision of digits\n", + " -g : Use %g format, with a precision of digits\n", + " -s: Get value as string (may honour server-side precision)\n", + "Integer number format:\n", + " Default: Print as decimal number\n", + " -0x: Print as hex number\n", + " -0o: Print as octal number\n", + " -0b: Print as binary number\n", + "Alternate output field separator:\n", + " -F : Use to separate fields on output\n", + "\n", + "Example: camonitor -f8 my_channel another_channel\n", + " (doubles are printed as %f with 8 decimal digits)\n", + "\n", + "Base version: ", CA->version, "\n"; + exit 1; +} diff --git a/modules/ca/src/template/top/caPerlApp/caput.pl b/modules/ca/src/template/top/caPerlApp/caput.pl new file mode 100644 index 000000000..64ff9cbda --- /dev/null +++ b/modules/ca/src/template/top/caPerlApp/caput.pl @@ -0,0 +1,192 @@ +#!/usr/bin/env perl + +use strict; + +# This construct sets @INC to search lib/perl of all RELEASE entries +use FindBin qw($Bin); +use lib ($Bin, "$Bin/../../lib/perl"); +use _APPNAME_ModuleDirs; +no lib $Bin; + +use Getopt::Std; +use CA; + +our ($opt_0, $opt_c, $opt_e, $opt_f, $opt_g, $opt_h, $opt_l, + $opt_n, $opt_s, $opt_S, $opt_t); +our $opt_w = 1; + +$Getopt::Std::OUTPUT_HELP_VERSION = 1; + +HELP_MESSAGE() unless getopts('achlnsStw:'); +HELP_MESSAGE() if $opt_h; + +die "No pv name specified. ('caput.pl -h' gives help.)\n" + unless @ARGV; +my $pv = shift; +die "caput.pl: Empty pv name given.\n" + unless $pv ne ''; + +die "No value specified. ('caput.pl -h' gives help.)\n" + unless @ARGV; + +my $chan = CA->new($pv); +eval { + CA->pend_io($opt_w); +}; +if ($@) { + if ($@ =~ m/^ECA_TIMEOUT/) { + print "Channel connect timed out: '$pv' not found.\n"; + exit 2; + } else { + die $@; + } +} + +die "Write access denied for '$pv'.\n" unless $chan->write_access; + +my $n = $chan->element_count(); +die "Too many values given, '$pv' limit is $n\n" + unless $n >= @ARGV; + +my $type = $chan->field_type; +$type = 'DBR_STRING' + if $opt_s && $type =~ m/ ^ DBR_ (ENUM | FLOAT | DOUBLE) $ /x; +$type = 'DBR_LONG' + if ($opt_n && $type eq 'DBR_ENUM') + || (!$opt_S && $type eq 'DBR_CHAR'); +$type =~ s/^DBR_/DBR_TIME_/ + if $opt_l; + +my @values; +if ($type !~ m/ ^ DBR_ (STRING | ENUM | CHAR) $ /x) { + # Make @ARGV strings numeric + @values = map { +$_; } @ARGV; +} else { + # Use strings + @values = @ARGV; +} + +my $done = 0; +if ($opt_t) { + do_put(); +} else { + $chan->get_callback(\&old_callback, $type); +} +CA->pend_event(0.1) until $done; + + +sub old_callback { + my ($chan, $status, $data) = @_; + die $status if $status; + display($chan, $type, $data, 'Old'); + do_put(); +} + +sub do_put { + if ($opt_c) { + $chan->put_callback(\&put_callback, @values); + } else { + $chan->put(@values); + $chan->get_callback(\&new_callback, $type); + } +} + +sub put_callback { + my ($chan, $status) = @_; + die $status if $status; + $chan->get_callback(\&new_callback, $type); +} + +sub new_callback { + my ($chan, $status, $data) = @_; + die $status if $status; + display($chan, $type, $data, 'New'); + $done = 1; +} + +sub format_number { + my ($data, $type) = @_; + if ($type =~ m/_DOUBLE$/) { + return sprintf "%.${opt_e}e", $data if $opt_e; + return sprintf "%.${opt_f}f", $data if $opt_f; + return sprintf "%.${opt_g}g", $data if $opt_g; + } + if ($type =~ m/_LONG$/) { + return sprintf "%lx", $data if $opt_0 eq 'x'; + return sprintf "%lo", $data if $opt_0 eq 'o'; + if ($opt_0 eq 'b') { + my $bin = unpack "B*", pack "l", $data; + $bin =~ s/^0*//; + return $bin; + } + } + return $data; +} + +sub display { + my ($chan, $type, $data, $prefix) = @_; + if (ref $data eq 'ARRAY') { + display($chan, $type, join(' ', @{$data}), $prefix); + } elsif (ref $data eq 'HASH') { + $type = $data->{TYPE}; # Can differ from request + my $value = $data->{value}; + if (ref $value eq 'ARRAY') { + $value = join(' ', map { format_number($_, $type); } @{$value}); + } else { + $value = format_number($value, $type); + } + my $stamp; + if (exists $data->{stamp}) { + my @t = localtime $data->{stamp}; + splice @t, 6; + $t[5] += 1900; + $t[0] += $data->{stamp_fraction}; + $stamp = sprintf "%4d-%02d-%02d %02d:%02d:%09.6f", reverse @t; + } + printf "%-30s %s %s %s %s\n", $chan->name, + $stamp, $value, $data->{status}, $data->{severity}; + } else { + my $value = format_number($data, $type); + if ($opt_t) { + print "$value\n"; + } else { + printf "$prefix : %-30s %s\n", $chan->name, $value; + } + } +} + +sub HELP_MESSAGE { + print STDERR "\nUsage: caput.pl [options] ...\n", + "\n", + " -h: Help: Print this message\n", + "Channel Access options:\n", + " -w : Wait time, specifies CA timeout, default is $opt_w second\n", + " -c: Use put_callback to wait for completion\n", + "Format options:\n", + " -t: Terse mode - print only sucessfully written value, without name\n", + " -l: Long mode \"name timestamp value stat sevr\" (read PVs as DBR_TIME_xxx)\n", + " -S: Put string as an array of char (long string)\n", + "Enum format:\n", + " Default: Auto - try value as ENUM string, then as index number\n", + " -n: Force interpretation of values as numbers\n", + " -s: Force interpretation of values as strings\n", + "Floating point type format:\n", + " Default: Use %g format\n", + " -e : Use %e format, with a precision of digits\n", + " -f : Use %f format, with a precision of digits\n", + " -g : Use %g format, with a precision of digits\n", + " -s: Get value as string (may honour server-side precision)\n", + "Integer number format:\n", + " Default: Print as decimal number\n", + " -0x: Print as hex number\n", + " -0o: Print as octal number\n", + " -0b: Print as binary number\n", + "\n", + "Examples:\n", + " caput my_channel 1.2\n", + " caput my_waveform 1.2 2.4 3.6 4.8 6.0\n", + "\n", + "Base version: ", CA->version, "\n"; + exit 1; +} + diff --git a/modules/ca/src/tools/Makefile b/modules/ca/src/tools/Makefile new file mode 100644 index 000000000..7ebcc021b --- /dev/null +++ b/modules/ca/src/tools/Makefile @@ -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. +# Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +# Synchrotronstrahlung. +# 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 + +PROD_DEFAULT += caget camonitor cainfo caput +PROD_vxWorks = -nil- +PROD_RTEMS = -nil- +PROD_iOS = -nil- + +PROD_SRCS = tool_lib.c + +caget_SRCS = caget.c +caput_SRCS = caput.c +camonitor_SRCS = camonitor.c +cainfo_SRCS = cainfo.c + +PROD_LIBS = ca Com + +include $(TOP)/configure/RULES diff --git a/modules/ca/src/tools/caget.c b/modules/ca/src/tools/caget.c new file mode 100644 index 000000000..00056709e --- /dev/null +++ b/modules/ca/src/tools/caget.c @@ -0,0 +1,554 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* Copyright (c) 2006 Diamond Light Source Ltd. +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2006/01/17 Malcolm Walters (Tessella/Diamond Light Source) + * Fixed problem with "-c -w 0" hanging forever + * 2008/04/16 Ralph Lange (BESSY) + * Updated usage info + * 2009/03/31 Larry Hoff (BNL) + * Added field separators + * 2009/04/01 Ralph Lange (HZB/BESSY) + * Added support for long strings (array of char) and quoting of nonprintable characters + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "tool_lib.h" + +#define VALID_DOUBLE_DIGITS 18 /* Max usable precision for a double */ +#define PEND_EVENT_SLICES 5 /* No. of pend_event slices for callback requests */ + +/* Different output formats */ +typedef enum { plain, terse, all, specifiedDbr } OutputT; + +/* Different request types */ +typedef enum { get, callback } RequestT; + +static int nConn = 0; /* Number of connected PVs */ +static int nRead = 0; /* Number of channels that were read */ +static int floatAsString = 0; /* Flag: fetch floats as string */ + + +static void usage (void) +{ + fprintf (stderr, "\nUsage: caget [options] ...\n\n" + " -h: Help: Print this message\n" + "Channel Access options:\n" + " -w : Wait time, specifies CA timeout, default is %f second(s)\n" + " -c: Asynchronous get (use ca_get_callback and wait for completion)\n" + " -p : CA priority (0-%u, default 0=lowest)\n" + "Format options:\n" + " Default output format is \"name value\"\n" + " -t: Terse mode - print only value, without name\n" + " -a: Wide mode \"name timestamp value stat sevr\" (read PVs as DBR_TIME_xxx)\n" + " -d : Request specific dbr type; use string (DBR_ prefix may be omitted)\n" + " or number of one of the following types:\n" + " DBR_STRING 0 DBR_STS_FLOAT 9 DBR_TIME_LONG 19 DBR_CTRL_SHORT 29\n" + " DBR_INT 1 DBR_STS_ENUM 10 DBR_TIME_DOUBLE 20 DBR_CTRL_INT 29\n" + " DBR_SHORT 1 DBR_STS_CHAR 11 DBR_GR_STRING 21 DBR_CTRL_FLOAT 30\n" + " DBR_FLOAT 2 DBR_STS_LONG 12 DBR_GR_SHORT 22 DBR_CTRL_ENUM 31\n" + " DBR_ENUM 3 DBR_STS_DOUBLE 13 DBR_GR_INT 22 DBR_CTRL_CHAR 32\n" + " DBR_CHAR 4 DBR_TIME_STRING 14 DBR_GR_FLOAT 23 DBR_CTRL_LONG 33\n" + " DBR_LONG 5 DBR_TIME_INT 15 DBR_GR_ENUM 24 DBR_CTRL_DOUBLE 34\n" + " DBR_DOUBLE 6 DBR_TIME_SHORT 15 DBR_GR_CHAR 25 DBR_STSACK_STRING 37\n" + " DBR_STS_STRING 7 DBR_TIME_FLOAT 16 DBR_GR_LONG 26 DBR_CLASS_NAME 38\n" + " DBR_STS_SHORT 8 DBR_TIME_ENUM 17 DBR_GR_DOUBLE 27\n" + " DBR_STS_INT 8 DBR_TIME_CHAR 18 DBR_CTRL_STRING 28\n" + "Enum format:\n" + " -n: Print DBF_ENUM value as number (default is enum string)\n" + "Arrays: Value format: print number of requested values, then list of values\n" + " Default: Print all values\n" + " -# : Print first elements of an array\n" + " -S: Print array of char as a string (long string)\n" + "Floating point type format:\n" + " Default: Use %%g format\n" + " -e : Use %%e format, with a precision of digits\n" + " -f : Use %%f format, with a precision of digits\n" + " -g : Use %%g format, with a precision of digits\n" + " -s: Get value as string (honors server-side precision)\n" + " -lx: Round to long integer and print as hex number\n" + " -lo: Round to long integer and print as octal number\n" + " -lb: Round to long integer and print as binary number\n" + "Integer number format:\n" + " Default: Print as decimal number\n" + " -0x: Print as hex number\n" + " -0o: Print as octal number\n" + " -0b: Print as binary number\n" + "Alternate output field separator:\n" + " -F : Use as an alternate output field separator\n" + "\nExample: caget -a -f8 my_channel another_channel\n" + " (uses wide output format, doubles are printed as %%f with precision of 8)\n\n" + , DEFAULT_TIMEOUT, CA_PRIORITY_MAX); +} + + + +/*+************************************************************************** + * + * Function: event_handler + * + * Description: CA event_handler for request type callback + * Allocates the dbr structure and copies the data + * + * Arg(s) In: args - event handler args (see CA manual) + * + **************************************************************************-*/ + +static void event_handler (evargs args) +{ + pv* ppv = args.usr; + + ppv->status = args.status; + if (args.status == ECA_NORMAL) + { + ppv->dbrType = args.type; + ppv->value = calloc(1, dbr_size_n(args.type, args.count)); + memcpy(ppv->value, args.dbr, dbr_size_n(args.type, args.count)); + ppv->nElems = args.count; + nRead++; + } +} + + + +/*+************************************************************************** + * + * Function: caget + * + * Description: Issue read requests, wait for incoming data + * and print the data according to the selected format + * + * Arg(s) In: pvs - Pointer to an array of pv structures + * nPvs - Number of elements in the pvs array + * request - Request type + * format - Output format + * dbrType - Requested dbr type + * reqElems - Requested number of (array) elements + * + * Return(s): Error code: 0 = OK, 1 = Error + * + **************************************************************************-*/ + +static int caget (pv *pvs, int nPvs, RequestT request, OutputT format, + chtype dbrType, unsigned long reqElems) +{ + unsigned int i; + int n, result; + + for (n = 0; n < nPvs; n++) { + unsigned long nElems; + + /* Set up pvs structure */ + /* -------------------- */ + + /* Get natural type and array count */ + nElems = ca_element_count(pvs[n].chid); + pvs[n].dbfType = ca_field_type(pvs[n].chid); + pvs[n].dbrType = dbrType; + + /* Set up value structures */ + if (format != specifiedDbr) + { + pvs[n].dbrType = dbf_type_to_DBR_TIME(pvs[n].dbfType); /* Use native type */ + if (dbr_type_is_ENUM(pvs[n].dbrType)) /* Enums honour -n option */ + { + if (enumAsNr) pvs[n].dbrType = DBR_TIME_INT; + else pvs[n].dbrType = DBR_TIME_STRING; + } + else if (floatAsString && + (dbr_type_is_FLOAT(pvs[n].dbrType) || dbr_type_is_DOUBLE(pvs[n].dbrType))) + { + pvs[n].dbrType = DBR_TIME_STRING; + } + } + + /* Issue CA request */ + /* ---------------- */ + + if (ca_state(pvs[n].chid) == cs_conn) + { + nConn++; + pvs[n].onceConnected = 1; + if (request == callback) + { + /* Event handler will allocate value and set nElems */ + pvs[n].reqElems = reqElems > nElems ? nElems : reqElems; + result = ca_array_get_callback(pvs[n].dbrType, + pvs[n].reqElems, + pvs[n].chid, + event_handler, + (void*)&pvs[n]); + } else { + /* We allocate value structure and set nElems */ + pvs[n].nElems = reqElems && reqElems < nElems ? reqElems : nElems; + pvs[n].value = calloc(1, dbr_size_n(pvs[n].dbrType, pvs[n].nElems)); + if (!pvs[n].value) { + fprintf(stderr,"Memory allocation failed\n"); + return 1; + } + result = ca_array_get(pvs[n].dbrType, + pvs[n].nElems, + pvs[n].chid, + pvs[n].value); + } + pvs[n].status = result; + } else { + pvs[n].status = ECA_DISCONN; + } + } + if (!nConn) return 1; /* No connection? We're done. */ + + /* Wait for completion */ + /* ------------------- */ + + result = ca_pend_io(caTimeout); + if (result == ECA_TIMEOUT) + fprintf(stderr, "Read operation timed out: some PV data was not read.\n"); + + if (request == callback) /* Also wait for callbacks */ + { + if (caTimeout != 0) + { + double slice = caTimeout / PEND_EVENT_SLICES; + for (n = 0; n < PEND_EVENT_SLICES; n++) + { + ca_pend_event(slice); + if (nRead >= nConn) break; + } + if (nRead < nConn) + fprintf(stderr, "Read operation timed out: some PV data was not read.\n"); + } else { + /* For 0 timeout keep waiting until all are done */ + while (nRead < nConn) { + ca_pend_event(1.0); + } + } + } + + /* Print the data */ + /* -------------- */ + + for (n = 0; n < nPvs; n++) { + + switch (format) { + case plain: /* Emulate old caget behaviour */ + if (pvs[n].nElems <= 1 && fieldSeparator == ' ') printf("%-30s", pvs[n].name); + else printf("%s", pvs[n].name); + printf("%c", fieldSeparator); + case terse: + if (pvs[n].status == ECA_DISCONN) + printf("*** not connected\n"); + else if (pvs[n].status == ECA_NORDACCESS) + printf("*** no read access\n"); + else if (pvs[n].status != ECA_NORMAL) + printf("*** CA error %s\n", ca_message(pvs[n].status)); + else if (pvs[n].value == 0) + printf("*** no data available (timeout)\n"); + else + { + if (charArrAsStr && dbr_type_is_CHAR(pvs[n].dbrType) && (reqElems || pvs[n].nElems > 1)) { + dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pvs[n].value, pvs[n].dbrType); + int dlen = epicsStrnEscapedFromRawSize((char*)s, strlen((char*)s)); + char *d = calloc(dlen+1, sizeof(char)); + if(d) { + epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, strlen((char*)s)); + printf("%s", d); + free(d); + } else { + fprintf(stderr,"Failed to allocate space for escaped string\n"); + } + } else { + if (reqElems || pvs[n].nElems > 1) printf("%lu%c", pvs[n].nElems, fieldSeparator); + for (i=0; i 1)) { + dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pvs[n].value, pvs[n].dbrType); + int dlen = epicsStrnEscapedFromRawSize((char*)s, strlen((char*)s)); + char *d = calloc(dlen+1, sizeof(char)); + if(d) { + epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, strlen((char*)s)); + printf("%s", d); + free(d); + } else { + fprintf(stderr,"Failed to allocate space for escaped string\n"); + } + } else { + for (i=0; i DBR_DOUBLE) /* Extended type extra info */ + printf("%s\n", dbr2str(pvs[n].value, pvs[n].dbrType)); + } + } + break; + default : + break; + } + } + return 0; +} + + + +/*+************************************************************************** + * + * Function: main + * + * Description: caget main() + * Evaluate command line options, set up CA, connect the + * channels, collect and print the data as requested + * + * Arg(s) In: [options] ... + * + * Arg(s) Out: none + * + * Return(s): Standard return code (0=success, 1=error) + * + **************************************************************************-*/ + +static void complainIfNotPlainAndSet (OutputT *current, const OutputT requested) +{ + if (*current != plain) + fprintf(stderr, + "Options t,d,a are mutually exclusive. " + "('caget -h' for help.)\n"); + *current = requested; +} + +int main (int argc, char *argv[]) +{ + int n; + int result; /* CA result */ + OutputT format = plain; /* User specified format */ + RequestT request = get; /* User specified request type */ + IntFormatT outType; /* Output type */ + + int count = 0; /* 0 = not specified by -# option */ + int opt; /* getopt() current option */ + int type = -1; /* getopt() data type argument */ + int digits = 0; /* getopt() no. of float digits */ + + int nPvs; /* Number of PVs */ + pv* pvs; /* Array of PV structures */ + + LINE_BUFFER(stdout); /* Configure stdout buffering */ + + while ((opt = getopt(argc, argv, ":taicnhsSe:f:g:l:#:d:0:w:p:F:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(); + return 0; + case 't': /* Terse output mode */ + complainIfNotPlainAndSet(&format, terse); + break; + case 'a': /* Wide output mode */ + complainIfNotPlainAndSet(&format, all); + break; + case 'c': /* Callback mode */ + request = callback; + break; + case 'd': /* Data type specification */ + complainIfNotPlainAndSet(&format, specifiedDbr); + /* Argument (type) may be text or number */ + if (sscanf(optarg, "%d", &type) != 1) + { + dbr_text_to_type(optarg, type); + if (type == -1) /* Invalid? Try prefix DBR_ */ + { + char str[30] = "DBR_"; + strncat(str, optarg, 25); + dbr_text_to_type(str, type); + } + } + if (type < DBR_STRING || type > DBR_CLASS_NAME + || type == DBR_PUT_ACKT || type == DBR_PUT_ACKS) + { + fprintf(stderr, "Requested dbr type out of range " + "or invalid - ignored. ('caget -h' for help.)\n"); + format = plain; + } + break; + case 'n': /* Print ENUM as index numbers */ + enumAsNr = 1; + break; + case 'w': /* Set CA timeout value */ + if(epicsScanDouble(optarg, &caTimeout) != 1) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "- ignored. ('caget -h' for help.)\n", optarg); + caTimeout = DEFAULT_TIMEOUT; + } + break; + case '#': /* Array count */ + if (sscanf(optarg,"%d", &count) != 1) + { + fprintf(stderr, "'%s' is not a valid array element count " + "- ignored. ('caget -h' for help.)\n", optarg); + count = 0; + } + break; + case 'p': /* CA priority */ + if (sscanf(optarg,"%u", &caPriority) != 1) + { + fprintf(stderr, "'%s' is not a valid CA priority " + "- ignored. ('caget -h' for help.)\n", optarg); + caPriority = DEFAULT_CA_PRIORITY; + } + if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; + break; + case 's': /* Select string dbr for floating type data */ + floatAsString = 1; + break; + case 'S': /* Treat char array as (long) string */ + charArrAsStr = 1; + break; + case 'e': /* Select %e/%f/%g format, using digits */ + case 'f': + case 'g': + if (sscanf(optarg, "%d", &digits) != 1) + fprintf(stderr, + "Invalid precision argument '%s' " + "for option '-%c' - ignored.\n", optarg, opt); + else + { + if (digits>=0 && digits<=VALID_DOUBLE_DIGITS) + sprintf(dblFormatStr, "%%-.%d%c", digits, opt); + else + fprintf(stderr, "Precision %d for option '-%c' " + "out of range - ignored.\n", digits, opt); + } + break; + case 'l': /* Convert to long and use integer format */ + case '0': /* Select integer format */ + switch ((char) *optarg) { + case 'x': outType = hex; break; /* x print Hex */ + case 'b': outType = bin; break; /* b print Binary */ + case 'o': outType = oct; break; /* o print Octal */ + default : + outType = dec; + fprintf(stderr, "Invalid argument '%s' " + "for option '-%c' - ignored.\n", optarg, opt); + } + if (outType != dec) { + if (opt == '0') { + type = DBR_LONG; + outTypeI = outType; + } else { + outTypeF = outType; + } + } + break; + case 'F': /* Store this for output and tool_lib formatting */ + fieldSeparator = (char) *optarg; + break; + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'. ('caget -h' for help.)\n", + optopt); + return 1; + case ':': + fprintf(stderr, + "Option '-%c' requires an argument. ('caget -h' for help.)\n", + optopt); + return 1; + default : + usage(); + return 1; + } + } + + nPvs = argc - optind; /* Remaining arg list are PV names */ + + if (nPvs < 1) + { + fprintf(stderr, "No pv name specified. ('caget -h' for help.)\n"); + return 1; + } + /* Start up Channel Access */ + + result = ca_context_create(ca_disable_preemptive_callback); + if (result != ECA_NORMAL) { + fprintf(stderr, "CA error %s occurred while trying " + "to start channel access.\n", ca_message(result)); + return 1; + } + /* Allocate PV structure array */ + + pvs = calloc (nPvs, sizeof(pv)); + if (!pvs) + { + fprintf(stderr, "Memory allocation for channel structures failed.\n"); + return 1; + } + /* Connect channels */ + + for (n = 0; optind < argc; n++, optind++) + pvs[n].name = argv[optind] ; /* Copy PV names from command line */ + + result = connect_pvs(pvs, nPvs); + + /* Read and print data */ + if (!result) + result = caget(pvs, nPvs, request, format, type, count); + + /* Shut down Channel Access */ + ca_context_destroy(); + + return result; +} diff --git a/modules/ca/src/tools/cainfo.c b/modules/ca/src/tools/cainfo.c new file mode 100644 index 000000000..ad580f473 --- /dev/null +++ b/modules/ca/src/tools/cainfo.c @@ -0,0 +1,224 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2008/04/16 Ralph Lange (BESSY) + * Updated usage info + * 2009/04/01 Ralph Lange (HZB/BESSY) + * Clarified output for native data type + * + */ + +#include +#include + +#include +#include + +#include "tool_lib.h" + +static unsigned statLevel = 0; /* ca_client_status() interest level */ + + +void usage (void) +{ + fprintf (stderr, "\nUsage: cainfo [options] ...\n\n" + " -h: Help: Print this message\n" + "Channel Access options:\n" + " -w : Wait time, specifies CA timeout, default is %f second(s)\n" + " -s : Call ca_client_status with the specified interest level\n" + " -p : CA priority (0-%u, default 0=lowest)\n" + "\nExample: cainfo my_channel another_channel\n\n" + , DEFAULT_TIMEOUT, CA_PRIORITY_MAX); +} + + + +/*+************************************************************************** + * + * Function: cainfo + * + * Description: Print CA info data or call ca_client_status + * + * Arg(s) In: pvs - Pointer to an array of pv structures + * nPvs - Number of elements in the pvs array + * + * Return(s): Error code: 0 = OK, 1 = Error + * + **************************************************************************-*/ + +int cainfo (pv *pvs, int nPvs) +{ + int n; + long dbfType; + long dbrType; + unsigned long nElems; + enum channel_state state; + char *stateStrings[] = { + "never connected", "previously connected", "connected", "closed" }; + char *boolStrings[] = { "no ", "" }; + + if (statLevel) { + ca_client_status(statLevel); + + } else { + + for (n = 0; n < nPvs; n++) { + + /* Print the status data */ + /* --------------------- */ + + state = ca_state(pvs[n].chid); + nElems = ca_element_count(pvs[n].chid); + dbfType = ca_field_type(pvs[n].chid); + dbrType = dbf_type_to_DBR(dbfType); + + printf("%s\n" + " State: %s\n" + " Host: %s\n" + " Access: %sread, %swrite\n" + " Native data type: %s\n" + " Request type: %s\n" + " Element count: %lu\n" + , pvs[n].name, + stateStrings[state], + ca_host_name(pvs[n].chid), + boolStrings[ca_read_access(pvs[n].chid)], + boolStrings[ca_write_access(pvs[n].chid)], + dbf_type_to_text(dbfType), + dbr_type_to_text(dbrType), + nElems + ); + } + } + + return 0; +} + + + +/*+************************************************************************** + * + * Function: main + * + * Description: cainfo main() + * Evaluate command line options, set up CA, connect the + * channels, print the data as requested + * + * Arg(s) In: [options] ... + * + * Arg(s) Out: none + * + * Return(s): Standard return code (0=success, 1=error) + * + **************************************************************************-*/ + +int main (int argc, char *argv[]) +{ + int n; + int result; /* CA result */ + + int opt; /* getopt() current option */ + + int nPvs; /* Number of PVs */ + pv* pvs; /* Array of PV structures */ + + LINE_BUFFER(stdout); /* Configure stdout buffering */ + + while ((opt = getopt(argc, argv, ":nhw:s:p:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(); + return 0; + case 'w': /* Set CA timeout value */ + if(epicsScanDouble(optarg, &caTimeout) != 1) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "- ignored. ('cainfo -h' for help.)\n", optarg); + caTimeout = DEFAULT_TIMEOUT; + } + break; + case 's': /* ca_client_status interest level */ + if (sscanf(optarg,"%du", &statLevel) != 1) + { + fprintf(stderr, "'%s' is not a valid interest level " + "- ignored. ('cainfo -h' for help.)\n", optarg); + statLevel = 0; + } + break; + case 'p': /* CA priority */ + if (sscanf(optarg,"%u", &caPriority) != 1) + { + fprintf(stderr, "'%s' is not a valid CA priority " + "- ignored. ('cainfo -h' for help.)\n", optarg); + caPriority = DEFAULT_CA_PRIORITY; + } + if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; + break; + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'. ('cainfo -h' for help.)\n", + optopt); + return 1; + case ':': + fprintf(stderr, + "Option '-%c' requires an argument. ('cainfo -h' for help.)\n", + optopt); + return 1; + default : + usage(); + return 1; + } + } + + nPvs = argc - optind; /* Remaining arg list are PV names */ + + if (!statLevel && nPvs < 1) + { + fprintf(stderr, "No pv name specified. ('cainfo -h' for help.)\n"); + return 1; + } + /* Start up Channel Access */ + + result = ca_context_create(ca_disable_preemptive_callback); + if (result != ECA_NORMAL) { + fprintf(stderr, "CA error %s occurred while trying " + "to start channel access.\n", ca_message(result)); + return 1; + } + /* Allocate PV structure array */ + + pvs = calloc (nPvs, sizeof(pv)); + if (!pvs) + { + fprintf(stderr, "Memory allocation for channel structures failed.\n"); + return 1; + } + /* Connect channels */ + + for (n = 0; optind < argc; n++, optind++) + pvs[n].name = argv[optind] ; /* Copy PV names from command line */ + + result = connect_pvs(pvs, nPvs); + + /* Print data */ + if (!result) + result = cainfo(pvs, nPvs); + + /* Shut down Channel Access */ + ca_context_destroy(); + + return result; +} diff --git a/modules/ca/src/tools/camonitor.c b/modules/ca/src/tools/camonitor.c new file mode 100644 index 000000000..307dad8d6 --- /dev/null +++ b/modules/ca/src/tools/camonitor.c @@ -0,0 +1,391 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2008/04/16 Ralph Lange (BESSY) + * Updated usage info + * 2009/03/31 Larry Hoff (BNL) + * Added field separators + * 2009/04/01 Ralph Lange (HZB/BESSY) + * Added support for long strings (array of char) and quoting of nonprintable characters + * + */ + +#include +#include +#include + +#include +#include + +#include "tool_lib.h" + +#define VALID_DOUBLE_DIGITS 18 /* Max usable precision for a double */ + +static unsigned long reqElems = 0; +static unsigned long eventMask = DBE_VALUE | DBE_ALARM; /* Event mask used */ +static int floatAsString = 0; /* Flag: fetch floats as string */ +static int nConn = 0; /* Number of connected PVs */ + + +void usage (void) +{ + fprintf (stderr, "\nUsage: camonitor [options] ...\n" + "\n" + " -h: Help; Print this message\n" + "Channel Access options:\n" + " -w : Wait time, specifies CA timeout, default is %f second(s)\n" + " -m : Specify CA event mask to use. is any combination of\n" + " 'v' (value), 'a' (alarm), 'l' (log/archive), 'p' (property).\n" + " Default event mask is 'va'\n" + " -p : CA priority (0-%u, default 0=lowest)\n" + "Timestamps:\n" + " Default: Print absolute timestamps (as reported by CA server)\n" + " -t : Specify timestamp source(s) and type, with containing\n" + " 's' = CA server (remote) timestamps\n" + " 'c' = CA client (local) timestamps (shown in '()'s)\n" + " 'n' = no timestamps\n" + " 'r' = relative timestamps (time elapsed since start of program)\n" + " 'i' = incremental timestamps (time elapsed since last update)\n" + " 'I' = incremental timestamps (time since last update, by channel)\n" + " 'r', 'i' or 'I' require 's' or 'c' to select the time source\n" + "Enum format:\n" + " -n: Print DBF_ENUM values as number (default is enum string)\n" + "Array values: Print number of elements, then list of values\n" + " Default: Request and print all elements (dynamic arrays supported)\n" + " -# : Request and print up to elements\n" + " -S: Print arrays of char as a string (long string)\n" + "Floating point format:\n" + " Default: Use %%g format\n" + " -e : Use %%e format, with a precision of digits\n" + " -f : Use %%f format, with a precision of digits\n" + " -g : Use %%g format, with a precision of digits\n" + " -s: Get value as string (honors server-side precision)\n" + " -lx: Round to long integer and print as hex number\n" + " -lo: Round to long integer and print as octal number\n" + " -lb: Round to long integer and print as binary number\n" + "Integer number format:\n" + " Default: Print as decimal number\n" + " -0x: Print as hex number\n" + " -0o: Print as octal number\n" + " -0b: Print as binary number\n" + "Alternate output field separator:\n" + " -F : Use to separate fields in output\n" + "\n" + "Example: camonitor -f8 my_channel another_channel\n" + " (doubles are printed as %%f with precision of 8)\n\n" + , DEFAULT_TIMEOUT, CA_PRIORITY_MAX); +} + + + +/*+************************************************************************** + * + * Function: event_handler + * + * Description: CA event_handler for request type callback + * Prints the event data + * + * Arg(s) In: args - event handler args (see CA manual) + * + **************************************************************************-*/ + +static void event_handler (evargs args) +{ + pv* pv = args.usr; + + pv->status = args.status; + if (args.status == ECA_NORMAL) + { + pv->dbrType = args.type; + pv->nElems = args.count; + pv->value = (void *) args.dbr; /* casting away const */ + + print_time_val_sts(pv, reqElems); + fflush(stdout); + + pv->value = NULL; + } +} + + +/*+************************************************************************** + * + * Function: connection_handler + * + * Description: CA connection_handler + * + * Arg(s) In: args - connection_handler_args (see CA manual) + * + **************************************************************************-*/ + +static void connection_handler ( struct connection_handler_args args ) +{ + pv *ppv = ( pv * ) ca_puser ( args.chid ); + if ( args.op == CA_OP_CONN_UP ) { + nConn++; + if (!ppv->onceConnected) { + ppv->onceConnected = 1; + /* Set up pv structure */ + /* ------------------- */ + + /* Get natural type and array count */ + ppv->dbfType = ca_field_type(ppv->chid); + ppv->dbrType = dbf_type_to_DBR_TIME(ppv->dbfType); /* Use native type */ + if (dbr_type_is_ENUM(ppv->dbrType)) /* Enums honour -n option */ + { + if (enumAsNr) ppv->dbrType = DBR_TIME_INT; + else ppv->dbrType = DBR_TIME_STRING; + } + else if (floatAsString && + (dbr_type_is_FLOAT(ppv->dbrType) || dbr_type_is_DOUBLE(ppv->dbrType))) + { + ppv->dbrType = DBR_TIME_STRING; + } + /* Set request count */ + ppv->nElems = ca_element_count(ppv->chid); + ppv->reqElems = reqElems > ppv->nElems ? ppv->nElems : reqElems; + + /* Issue CA request */ + /* ---------------- */ + /* install monitor once with first connect */ + ppv->status = ca_create_subscription(ppv->dbrType, + ppv->reqElems, + ppv->chid, + eventMask, + event_handler, + (void*)ppv, + NULL); + } + } + else if ( args.op == CA_OP_CONN_DOWN ) { + nConn--; + ppv->status = ECA_DISCONN; + print_time_val_sts(ppv, reqElems); + } +} + + +/*+************************************************************************** + * + * Function: main + * + * Description: camonitor main() + * Evaluate command line options, set up CA, connect the + * channels, collect and print the data as requested + * + * Arg(s) In: [options] ... + * + * Arg(s) Out: none + * + * Return(s): Standard return code (0=success, 1=error) + * + **************************************************************************-*/ + +int main (int argc, char *argv[]) +{ + int returncode = 0; + int n; + int result; /* CA result */ + IntFormatT outType; /* Output type */ + + int opt; /* getopt() current option */ + int digits = 0; /* getopt() no. of float digits */ + + int nPvs; /* Number of PVs */ + pv* pvs; /* Array of PV structures */ + + LINE_BUFFER(stdout); /* Configure stdout buffering */ + + while ((opt = getopt(argc, argv, ":nhm:sSe:f:g:l:#:0:w:t:p:F:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(); + return 0; + case 'n': /* Print ENUM as index numbers */ + enumAsNr=1; + break; + case 't': /* Select timestamp source(s) and type */ + tsSrcServer = 0; + tsSrcClient = 0; + { + int i = 0; + char c; + while ((c = optarg[i++])) + switch (c) { + case 's': tsSrcServer = 1; break; + case 'c': tsSrcClient = 1; break; + case 'n': break; + case 'r': tsType = relative; break; + case 'i': tsType = incremental; break; + case 'I': tsType = incrementalByChan; break; + default : + fprintf(stderr, "Invalid argument '%c' " + "for option '-t' - ignored.\n", c); + } + } + break; + case 'w': /* Set CA timeout value */ + if(epicsScanDouble(optarg, &caTimeout) != 1) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "- ignored. ('camonitor -h' for help.)\n", optarg); + caTimeout = DEFAULT_TIMEOUT; + } + break; + case '#': /* Array count */ + if (sscanf(optarg,"%ld", &reqElems) != 1) + { + fprintf(stderr, "'%s' is not a valid array element count " + "- ignored. ('camonitor -h' for help.)\n", optarg); + reqElems = 0; + } + break; + case 'p': /* CA priority */ + if (sscanf(optarg,"%u", &caPriority) != 1) + { + fprintf(stderr, "'%s' is not a valid CA priority " + "- ignored. ('camonitor -h' for help.)\n", optarg); + caPriority = DEFAULT_CA_PRIORITY; + } + if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; + break; + case 'm': /* Select CA event mask */ + eventMask = 0; + { + int i = 0; + char c, err = 0; + while ((c = optarg[i++]) && !err) + switch (c) { + case 'v': eventMask |= DBE_VALUE; break; + case 'a': eventMask |= DBE_ALARM; break; + case 'l': eventMask |= DBE_LOG; break; + case 'p': eventMask |= DBE_PROPERTY; break; + default : + fprintf(stderr, "Invalid argument '%s' " + "for option '-m' - ignored.\n", optarg); + eventMask = DBE_VALUE | DBE_ALARM; + err = 1; + } + } + break; + case 's': /* Select string dbr for floating type data */ + floatAsString = 1; + break; + case 'S': /* Treat char array as (long) string */ + charArrAsStr = 1; + break; + case 'e': /* Select %e/%f/%g format, using digits */ + case 'f': + case 'g': + if (sscanf(optarg, "%d", &digits) != 1) + fprintf(stderr, + "Invalid precision argument '%s' " + "for option '-%c' - ignored.\n", optarg, opt); + else + { + if (digits>=0 && digits<=VALID_DOUBLE_DIGITS) + sprintf(dblFormatStr, "%%-.%d%c", digits, opt); + else + fprintf(stderr, "Precision %d for option '-%c' " + "out of range - ignored.\n", digits, opt); + } + break; + case 'l': /* Convert to long and use integer format */ + case '0': /* Select integer format */ + switch ((char) *optarg) { + case 'x': outType = hex; break; /* x print Hex */ + case 'b': outType = bin; break; /* b print Binary */ + case 'o': outType = oct; break; /* o print Octal */ + default : + outType = dec; + fprintf(stderr, "Invalid argument '%s' " + "for option '-%c' - ignored.\n", optarg, opt); + } + if (outType != dec) { + if (opt == '0') outTypeI = outType; + else outTypeF = outType; + } + break; + case 'F': /* Store this for output and tool_lib formatting */ + fieldSeparator = (char) *optarg; + break; + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'. ('camonitor -h' for help.)\n", + optopt); + return 1; + case ':': + fprintf(stderr, + "Option '-%c' requires an argument. ('camonitor -h' for help.)\n", + optopt); + return 1; + default : + usage(); + return 1; + } + } + + nPvs = argc - optind; /* Remaining arg list are PV names */ + + if (nPvs < 1) + { + fprintf(stderr, "No pv name specified. ('camonitor -h' for help.)\n"); + return 1; + } + /* Start up Channel Access */ + + result = ca_context_create(ca_disable_preemptive_callback); + if (result != ECA_NORMAL) { + fprintf(stderr, "CA error %s occurred while trying " + "to start channel access.\n", ca_message(result)); + return 1; + } + /* Allocate PV structure array */ + + pvs = calloc (nPvs, sizeof(pv)); + if (!pvs) + { + fprintf(stderr, "Memory allocation for channel structures failed.\n"); + return 1; + } + /* Connect channels */ + + /* Copy PV names from command line */ + for (n = 0; optind < argc; n++, optind++) + { + pvs[n].name = argv[optind]; + } + /* Create CA connections */ + returncode = create_pvs(pvs, nPvs, connection_handler); + if ( returncode ) { + return returncode; + } + /* Check for channels that didn't connect */ + ca_pend_event(caTimeout); + for (n = 0; n < nPvs; n++) + { + if (!pvs[n].onceConnected) + print_time_val_sts(&pvs[n], reqElems); + } + + /* Read and print data forever */ + ca_pend_event(0); + + /* Shut down Channel Access */ + ca_context_destroy(); + + return result; +} diff --git a/modules/ca/src/tools/caput.c b/modules/ca/src/tools/caput.c new file mode 100644 index 000000000..5e4d10e23 --- /dev/null +++ b/modules/ca/src/tools/caput.c @@ -0,0 +1,567 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* Copyright (c) 2006 Diamond Light Source Ltd. +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2006/01/17 Malcolm Walters (Tessella/Diamond Light Source) + * Added put_callback option - heavily based on caget + * 2008/03/06 Andy Foster (OSL/Diamond Light Source) + * Remove timeout dependency of ca_put_callback by using an EPICS event + * (semaphore), i.e. remove ca_pend_event time slicing. + * 2008/04/16 Ralph Lange (BESSY) + * Updated usage info + * 2009/03/31 Larry Hoff (BNL) + * Added field separators + * 2009/04/01 Ralph Lange (HZB/BESSY) + * Added support for long strings (array of char) and quoting of nonprintable characters + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "tool_lib.h" + +#define VALID_DOUBLE_DIGITS 18 /* Max usable precision for a double */ + +/* Different output formats */ +typedef enum { plain, terse, all } OutputT; + +/* Different request types */ +typedef enum { get, callback } RequestT; + +/* Valid EPICS string */ +typedef char EpicsStr[MAX_STRING_SIZE]; + +static int nConn = 0; /* Number of connected PVs */ +static epicsEventId epId; + +void usage (void) +{ + fprintf (stderr, "\nUsage: caput [options] ...\n" + " caput -a [options] ...\n\n" + " -h: Help: Print this message\n" + "Channel Access options:\n" + " -w : Wait time, specifies CA timeout, default is %f second(s)\n" + " -c: Asynchronous put (use ca_put_callback and wait for completion)\n" + " -p : CA priority (0-%u, default 0=lowest)\n" + "Format options:\n" + " -t: Terse mode - print only sucessfully written value, without name\n" + " -l: Long mode \"name timestamp value stat sevr\" (read PVs as DBR_TIME_xxx)\n" + "Enum format:\n" + " Default: Auto - try value as ENUM string, then as index number\n" + " -n: Force interpretation of values as numbers\n" + " -s: Force interpretation of values as strings\n" + "Arrays:\n" + " Default: Put scalar\n" + " Value format: all value arguments concatenated with spaces\n" + " -S: Put string as an array of chars (long string)\n" + " -a: Put array\n" + " Value format: number of values, then list of values\n" + "Alternate output field separator:\n" + " -F : Use as an alternate output field separator\n" + "\nExample: caput my_channel 1.2\n" + " (puts 1.2 to my_channel)\n\n" + , DEFAULT_TIMEOUT, CA_PRIORITY_MAX); +} + + +/*+************************************************************************** + * + * Function: put_event_handler + * + * Description: CA event_handler for request type callback + * Sets status flags and marks as done. + * + * Arg(s) In: args - event handler args (see CA manual) + * + **************************************************************************-*/ + +void put_event_handler ( struct event_handler_args args ) +{ + /* Retrieve pv from event handler structure */ + pv* pPv = args.usr; + + /* Store status, then give EPICS event */ + pPv->status = args.status; + epicsEventSignal( epId ); +} + + +/*+************************************************************************** + * + * Function: caget + * + * Description: Issue read request, wait for incoming data + * and print the data + * + * Arg(s) In: pvs - Pointer to an array of pv structures + * nPvs - Number of elements in the pvs array + * format - Output format + * dbrType - Requested dbr type + * reqElems - Requested number of (array) elements + * + * Return(s): Error code: 0 = OK, 1 = Error + * + **************************************************************************-*/ + +int caget (pv *pvs, int nPvs, OutputT format, + chtype dbrType, unsigned long reqElems) +{ + unsigned int i; + int n, result; + + for (n = 0; n < nPvs; n++) { + + /* Set up pvs structure */ + /* -------------------- */ + + /* Get natural type and array count */ + pvs[n].nElems = ca_element_count(pvs[n].chid); + pvs[n].dbfType = ca_field_type(pvs[n].chid); + pvs[n].dbrType = dbrType; + + /* Set up value structures */ + pvs[n].dbrType = dbf_type_to_DBR_TIME(pvs[n].dbfType); /* Use native type */ + if (dbr_type_is_ENUM(pvs[n].dbrType)) /* Enums honour -n option */ + { + if (enumAsNr) pvs[n].dbrType = DBR_TIME_INT; + else pvs[n].dbrType = DBR_TIME_STRING; + } + + if (reqElems == 0 || pvs[n].nElems < reqElems) /* Adjust array count */ + pvs[n].reqElems = pvs[n].nElems; + else + pvs[n].reqElems = reqElems; + + /* Issue CA request */ + /* ---------------- */ + + if (ca_state(pvs[n].chid) == cs_conn) + { + nConn++; + pvs[n].onceConnected = 1; + /* Allocate value structure */ + pvs[n].value = calloc(1, dbr_size_n(pvs[n].dbrType, pvs[n].reqElems)); + if(!pvs[n].value){ + fprintf(stderr,"Allocation failed\n"); + exit(1); + } + result = ca_array_get(pvs[n].dbrType, + pvs[n].reqElems, + pvs[n].chid, + pvs[n].value); + pvs[n].status = result; + } else { + pvs[n].status = ECA_DISCONN; + } + } + if (!nConn) return 1; /* No connection? We're done. */ + + /* Wait for completion */ + /* ------------------- */ + + result = ca_pend_io(caTimeout); + if (result == ECA_TIMEOUT) + fprintf(stderr, "Read operation timed out: PV data was not read.\n"); + + /* Print the data */ + /* -------------- */ + + for (n = 0; n < nPvs; n++) { + + switch (format) { + case plain: /* Emulate old caput behaviour */ + if (pvs[n].reqElems <= 1 && fieldSeparator == ' ') printf("%-30s", pvs[n].name); + else printf("%s", pvs[n].name); + printf("%c", fieldSeparator); + case terse: + if (pvs[n].status == ECA_DISCONN) + printf("*** not connected\n"); + else if (pvs[n].status == ECA_NORDACCESS) + printf("*** no read access\n"); + else if (pvs[n].status != ECA_NORMAL) + printf("*** CA error %s\n", ca_message(pvs[n].status)); + else if (pvs[n].value == 0) + printf("*** no data available (timeout)\n"); + else + { + if (charArrAsStr && dbr_type_is_CHAR(pvs[n].dbrType) && (reqElems || pvs[n].reqElems > 1)) { + dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pvs[n].value, pvs[n].dbrType); + int dlen = epicsStrnEscapedFromRawSize((char*)s, strlen((char*)s)); + char *d = calloc(dlen+1, sizeof(char)); + if(!d){ + fprintf(stderr,"Allocation failed\n"); + exit(1); + } + epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, strlen((char*)s)); + printf("%s", d); + free(d); + } else { + if (reqElems || pvs[n].nElems > 1) printf("%lu%c", pvs[n].reqElems, fieldSeparator); + for (i=0; i ... + * + * Arg(s) Out: none + * + * Return(s): Standard return code (0=success, 1=error) + * + **************************************************************************-*/ + +int main (int argc, char *argv[]) +{ + int i; + int result; /* CA result */ + OutputT format = plain; /* User specified format */ + RequestT request = get; /* User specified request type */ + int isArray = 0; /* Flag for array operation */ + int enumAsString = 0; /* Force ENUM values to be strings */ + + int count = 1; + int opt; /* getopt() current option */ + chtype dbrType = DBR_STRING; + char *pend; + EpicsStr *sbuf; + double *dbuf; + char *cbuf = 0; + char *ebuf = 0; + void *pbuf; + int len = 0; + int waitStatus; + struct dbr_gr_enum bufGrEnum; + + int nPvs; /* Number of PVs */ + pv* pvs; /* Array of PV structures */ + + LINE_BUFFER(stdout); /* Configure stdout buffering */ + putenv("POSIXLY_CORRECT="); /* Behave correct on GNU getopt systems */ + + while ((opt = getopt(argc, argv, ":cnlhatsS#:w:p:F:")) != -1) { + switch (opt) { + case 'h': /* Print usage */ + usage(); + return 0; + case 'n': /* Force interpret ENUM as index number */ + enumAsNr = 1; + enumAsString = 0; + break; + case 's': /* Force interpret ENUM as menu string */ + enumAsString = 1; + enumAsNr = 0; + break; + case 'S': /* Treat char array as (long) string */ + charArrAsStr = 1; + isArray = 0; + break; + case 't': /* Select terse output format */ + format = terse; + break; + case 'l': /* Select long output format */ + format = all; + break; + case 'a': /* Select array mode */ + isArray = 1; + charArrAsStr = 0; + break; + case 'c': /* Select put_callback mode */ + request = callback; + break; + case 'w': /* Set CA timeout value */ + if(epicsScanDouble(optarg, &caTimeout) != 1) + { + fprintf(stderr, "'%s' is not a valid timeout value " + "- ignored. ('caput -h' for help.)\n", optarg); + caTimeout = DEFAULT_TIMEOUT; + } + break; + case '#': /* Array count */ + if (sscanf(optarg,"%d", &count) != 1) + { + fprintf(stderr, "'%s' is not a valid array element count " + "- ignored. ('caput -h' for help.)\n", optarg); + count = 0; + } + break; + case 'p': /* CA priority */ + if (sscanf(optarg,"%u", &caPriority) != 1) + { + fprintf(stderr, "'%s' is not a valid CA priority " + "- ignored. ('caget -h' for help.)\n", optarg); + caPriority = DEFAULT_CA_PRIORITY; + } + if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; + break; + case 'F': /* Store this for output and tool_lib formatting */ + fieldSeparator = (char) *optarg; + break; + case '?': + fprintf(stderr, + "Unrecognized option: '-%c'. ('caput -h' for help.)\n", + optopt); + return 1; + case ':': + fprintf(stderr, + "Option '-%c' requires an argument. ('caput -h' for help.)\n", + optopt); + return 1; + default : + usage(); + return 1; + } + } + + nPvs = argc - optind; /* Remaining arg list are PV names and values */ + + if (nPvs < 1) { + fprintf(stderr, "No pv name specified. ('caput -h' for help.)\n"); + return 1; + } + if (nPvs == 1) { + fprintf(stderr, "No value specified. ('caput -h' for help.)\n"); + return 1; + } + + nPvs = 1; /* One PV - the rest is value(s) */ + + epId = epicsEventCreate(epicsEventEmpty); /* Create empty EPICS event (semaphore) */ + + /* Start up Channel Access */ + + result = ca_context_create(ca_enable_preemptive_callback); + if (result != ECA_NORMAL) { + fprintf(stderr, "CA error %s occurred while trying " + "to start channel access.\n", ca_message(result)); + return 1; + } + /* Allocate PV structure array */ + + pvs = calloc (nPvs, sizeof(pv)); + if (!pvs) { + fprintf(stderr, "Memory allocation for channel structure failed.\n"); + return 1; + } + /* Connect channels */ + + pvs[0].name = argv[optind] ; /* Copy PV name from command line */ + + result = connect_pvs(pvs, nPvs); /* If the connection fails, we're done */ + if (result) { + ca_context_destroy(); + return result; + } + + /* Get values from command line */ + optind++; + + if (isArray) { + optind++; /* In case of array skip first value (nr + * of elements) - actual number of values is used */ + count = argc - optind; + + } else { /* Concatenate the remaining line to one string + * (sucks but is compatible to the former version) */ + for (i = optind; i < argc; i++) { + len += strlen(argv[i]); + len++; + } + cbuf = calloc(len, sizeof(char)); + if (!cbuf) { + fprintf(stderr, "Memory allocation failed.\n"); + return 1; + } + strcpy(cbuf, argv[optind]); + + if (argc > optind+1) { + for (i = optind + 1; i < argc; i++) { + strcat(cbuf, " "); + strcat(cbuf, argv[i]); + } + } + + if ((argc - optind) >= 1) + count = 1; + argv[optind] = cbuf; + } + + sbuf = calloc (count, sizeof(EpicsStr)); + dbuf = calloc (count, sizeof(double)); + if(!sbuf || !dbuf) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } + + /* ENUM? Special treatment */ + + if (ca_field_type(pvs[0].chid) == DBR_ENUM) { + + /* Get the ENUM strings */ + + result = ca_array_get (DBR_GR_ENUM, 1, pvs[0].chid, &bufGrEnum); + result = ca_pend_io(caTimeout); + if (result == ECA_TIMEOUT) { + fprintf(stderr, "Read operation timed out: ENUM data was not read.\n"); + return 1; + } + + if (enumAsNr) { /* Interpret values as numbers */ + + for (i = 0; i < count; ++i) { + dbuf[i] = epicsStrtod(*(argv+optind+i), &pend); + if (*(argv+optind+i) == pend) { /* Conversion didn't work */ + fprintf(stderr, "Enum index value '%s' is not a number.\n", + *(argv+optind+i)); + return 1; + } + if (dbuf[i] >= bufGrEnum.no_str) { + fprintf(stderr, "Warning: enum index value '%s' may be too large.\n", + *(argv+optind+i)); + } + } + dbrType = DBR_DOUBLE; + + } else { /* Interpret values as strings */ + + for (i = 0; i < count; ++i) { + epicsStrnRawFromEscaped(sbuf[i], sizeof(EpicsStr), *(argv+optind+i), sizeof(EpicsStr)); + *( sbuf[i]+sizeof(EpicsStr)-1 ) = '\0'; + dbrType = DBR_STRING; + + /* Compare to ENUM strings */ + for (len = 0; len < bufGrEnum.no_str; len++) + if (!strcmp(sbuf[i], bufGrEnum.strs[len])) + break; + + if (len >= bufGrEnum.no_str) { + /* Not a string? Try as number */ + dbuf[i] = epicsStrtod(sbuf[i], &pend); + if (sbuf[i] == pend || enumAsString) { + fprintf(stderr, "Enum string value '%s' invalid.\n", sbuf[i]); + return 1; + } + if (dbuf[i] >= bufGrEnum.no_str) { + fprintf(stderr, "Warning: enum index value '%s' may be too large.\n", sbuf[i]); + } + dbrType = DBR_DOUBLE; + } + } + } + + } else { /* Not an ENUM */ + + if (charArrAsStr) { + dbrType = DBR_CHAR; + ebuf = calloc(len, sizeof(char)); + if(!ebuf) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } + count = epicsStrnRawFromEscaped(ebuf, len, cbuf, len-1) + 1; + } else { + for (i = 0; i < count; ++i) { + epicsStrnRawFromEscaped(sbuf[i], sizeof(EpicsStr), *(argv+optind+i), sizeof(EpicsStr)); + *( sbuf[i]+sizeof(EpicsStr)-1 ) = '\0'; + } + dbrType = DBR_STRING; + } + } + + /* Read and print old data */ + if (format != terse) { + printf("Old : "); + result = caget(pvs, nPvs, format, 0, 0); + } + + /* Write new data */ + if (dbrType == DBR_STRING) pbuf = sbuf; + else if (dbrType == DBR_CHAR) pbuf = ebuf; + else pbuf = dbuf; + + if (request == callback) { + /* Use callback version of put */ + pvs[0].status = ECA_NORMAL; /* All ok at the moment */ + result = ca_array_put_callback ( + dbrType, count, pvs[0].chid, pbuf, put_event_handler, (void *) pvs); + } else { + /* Use standard put with defined timeout */ + result = ca_array_put (dbrType, count, pvs[0].chid, pbuf); + } + if (result != ECA_NORMAL) { + fprintf(stderr, "Error from put operation: %s\n", ca_message(result)); + return 1; + } + + result = ca_pend_io(caTimeout); + if (result == ECA_TIMEOUT) { + fprintf(stderr, "Write operation timed out: Data was not written.\n"); + return 1; + } + if (request == callback) { /* Also wait for callbacks */ + waitStatus = epicsEventWaitWithTimeout( epId, caTimeout ); + if (waitStatus) + fprintf(stderr, "Write callback operation timed out\n"); + + /* retrieve status from callback */ + result = pvs[0].status; + } + + if (result != ECA_NORMAL) { + fprintf(stderr, "Error occured writing data: %s\n", ca_message(result)); + return 1; + } + + /* Read and print new data */ + if (format != terse) + printf("New : "); + + result = caget(pvs, nPvs, format, 0, 0); + + /* Shut down Channel Access */ + ca_context_destroy(); + + return result; +} diff --git a/modules/ca/src/tools/tool_lib.c b/modules/ca/src/tools/tool_lib.c new file mode 100644 index 000000000..29b252e37 --- /dev/null +++ b/modules/ca/src/tools/tool_lib.c @@ -0,0 +1,640 @@ +/*************************************************************************\ +* Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2009/03/31 Larry Hoff (BNL) + * Added field separators + * 2009/04/01 Ralph Lange (HZB/BESSY) + * Added support for long strings (array of char) and quoting of nonprintable characters + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "tool_lib.h" + +/* Time stamps for program start, first incoming monitor, + previous value (client and server stamp): + used for relative resp. incremental timestamps with monitors */ +static epicsTimeStamp tsStart, tsFirst, tsPreviousC, tsPreviousS; + +static int tsInitS = 0; /* Flag: Server timestamps init'd */ +static int tsInitC = 0; /* Flag: Client timestamps init'd */ + +TimeT tsType = absolute; /* Timestamp type flag (-t option) */ +int tsSrcServer = 1; /* Timestamp source flag (-t option) */ +int tsSrcClient = 0; /* Timestamp source flag (-t option) */ +IntFormatT outTypeI = dec; /* For -0.. output format option */ +IntFormatT outTypeF = dec; /* For -l.. output format option */ + +char dblFormatStr[30] = "%g"; /* Format string to print doubles (-efg options) */ +char timeFormatStr[30] = "%Y-%m-%d %H:%M:%S.%06f"; /* Time format string */ +char fieldSeparator = ' '; /* OFS default is whitespace */ + +int enumAsNr = 0; /* used for -n option - get DBF_ENUM as number */ +int charArrAsStr = 0; /* used for -S option - treat char array as (long) string */ +double caTimeout = 1.0; /* wait time default (see -w option) */ +capri caPriority = DEFAULT_CA_PRIORITY; /* CA Priority */ + +#define TIMETEXTLEN 28 /* Length of timestamp text buffer */ + + + +static void sprint_long (char *ret, dbr_long_t val, IntFormatT outType) +{ + if (outType == bin && val != 0) { + /* sprintf doesn't do binary; this code doesn't handle 0 */ + int i, skip = -1; + + for (i = 31; i >= 0; i--) { + int bit = (val >> i) & 1; + + if (skip < 0 && bit) { + skip = 31 - i; /* skip leading 0's */ + ret[i+1] = '\0'; + } + if (skip >= 0) { + ret[31-i-skip] = '0' + bit; + } + } + } + else { + const char *fmt[4] = { /* Order must match the enum IntFormatT */ + "%d" /* dec */, + "0" /* bin and val is 0 */, + "0o%o" /* oct */, + "0x%X" /* hex */ + }; + + /* dbr_long_t is actually an int on all supported platforms */ + sprintf(ret, fmt[outType], (int) val); + } +} + + + +/*+************************************************************************** + * + * Function: val2str + * + * Description: Print (convert) value to a string + * + * Arg(s) In: v - Pointer to dbr_... structure + * type - Numeric dbr type + * index - Index of element to print (for arrays) + * + * Return(s): Pointer to static output string + * + **************************************************************************-*/ + +char *val2str (const void *v, unsigned type, int index) +{ +#define STR 500 + static char str[STR]; + char ch; + void *val_ptr; + unsigned base_type; + dbr_long_t val_long; + + if (!dbr_type_is_valid(type)) { + strcpy (str, "*** invalid type"); + return str; + } + strcpy (str, "!!!"); + + base_type = type % (LAST_TYPE+1); + + if (type == DBR_STSACK_STRING || type == DBR_CLASS_NAME) + base_type = DBR_STRING; + + val_ptr = dbr_value_ptr(v, type); + + switch (base_type) { + case DBR_STRING: + epicsStrnEscapedFromRaw(str, STR, ((dbr_string_t*) val_ptr)[index], strlen(((dbr_string_t*) val_ptr)[index])); + break; + case DBR_FLOAT: + if (outTypeF == dec) { + sprintf(str, dblFormatStr, ((dbr_float_t*) val_ptr)[index]); + } else { + if (((dbr_float_t*) val_ptr)[index] > 0.0) + val_long = ((dbr_float_t*) val_ptr)[index] + 0.5; + else + val_long = ((dbr_float_t*) val_ptr)[index] - 0.5; + sprint_long(str, val_long, outTypeF); + } + break; + case DBR_DOUBLE: + if (outTypeF == dec) { + sprintf(str, dblFormatStr, ((dbr_double_t*) val_ptr)[index]); + } else { + if (((dbr_double_t*) val_ptr)[index] > 0.0) + val_long = ((dbr_double_t*) val_ptr)[index] + 0.5; + else + val_long = ((dbr_double_t*) val_ptr)[index] - 0.5; + sprint_long(str, val_long, outTypeF); + } + break; + case DBR_CHAR: + ch = ((dbr_char_t*) val_ptr)[index]; + sprintf(str, "%d", ch); + break; + case DBR_INT: + sprint_long(str, ((dbr_int_t*) val_ptr)[index], outTypeI); + break; + case DBR_LONG: + sprint_long(str, ((dbr_long_t*) val_ptr)[index], outTypeI); + break; + case DBR_ENUM: + { + dbr_enum_t *val = (dbr_enum_t *)val_ptr; + if (dbr_type_is_GR(type) && !enumAsNr) { + if (val[index] >= MAX_ENUM_STATES) + sprintf(str, "Illegal Value (%d)", val[index]); + else if (val[index] >= ((struct dbr_gr_enum *)v)->no_str) + sprintf(str, "Enum Index Overflow (%d)", val[index]); + else + sprintf(str, "%s", ((struct dbr_gr_enum *)v)->strs[val[index]]); + } else if (dbr_type_is_CTRL(type) && !enumAsNr) { + if (val[index] >= MAX_ENUM_STATES) + sprintf(str, "Illegal Value (%d)", val[index]); + else if (val[index] >= ((struct dbr_ctrl_enum *)v)->no_str) + sprintf(str, "Enum Index Overflow (%d)", val[index]); + else + sprintf(str, "%s", ((struct dbr_ctrl_enum *)v)->strs[val[index]]); + } else + sprintf(str, "%d", val[index]); + } + } + return str; +} + + + +/*+************************************************************************** + * + * Function: dbr2str + * + * Description: Print (convert) additional information contained in dbr_... + * + * Arg(s) In: value - Pointer to dbr_... structure + * type - Numeric dbr type + * + * Return(s): Pointer to static output string + * + **************************************************************************-*/ + +/* Definitions for sprintf format strings and matching argument lists */ + +#define FMT_TIME \ + " Timestamp: %s" + +#define ARGS_TIME(T) \ + timeText + +#define FMT_STS \ + " Status: %s\n" \ + " Severity: %s" + +#define ARGS_STS(T) \ + stat_to_str(((struct T *)value)->status), \ + sevr_to_str(((struct T *)value)->severity) + +#define ARGS_STS_UNSIGNED(T) \ + stat_to_str_unsigned(((struct T *)value)->status), \ + sevr_to_str_unsigned(((struct T *)value)->severity) + +#define FMT_ACK \ + " Ack transient?: %s\n" \ + " Ack severity: %s" + +#define ARGS_ACK(T) \ + ((struct T *)value)->ackt ? "YES" : "NO", \ + sevr_to_str_unsigned(((struct T *)value)->acks) + +#define FMT_UNITS \ + " Units: %s" + +#define ARGS_UNITS(T) \ + ((struct T *)value)->units + +#define FMT_PREC \ + " Precision: %d" + +#define ARGS_PREC(T) \ + ((struct T *)value)->precision + +#define FMT_GR(FMT) \ + " Lo disp limit: " #FMT "\n" \ + " Hi disp limit: " #FMT "\n" \ + " Lo alarm limit: " #FMT "\n" \ + " Lo warn limit: " #FMT "\n" \ + " Hi warn limit: " #FMT "\n" \ + " Hi alarm limit: " #FMT + +#define ARGS_GR(T,F) \ + (F)((struct T *)value)->lower_disp_limit, \ + (F)((struct T *)value)->upper_disp_limit, \ + (F)((struct T *)value)->lower_alarm_limit, \ + (F)((struct T *)value)->lower_warning_limit, \ + (F)((struct T *)value)->upper_warning_limit, \ + (F)((struct T *)value)->upper_alarm_limit + +#define FMT_CTRL(FMT) \ + " Lo ctrl limit: " #FMT "\n" \ + " Hi ctrl limit: " #FMT + +#define ARGS_CTRL(T,F) \ + (F)((struct T *)value)->lower_ctrl_limit, \ + (F)((struct T *)value)->upper_ctrl_limit + + +/* Definitions for the actual sprintf calls */ + +#define PRN_DBR_STS(T) \ + sprintf(str, \ + FMT_STS, \ + ARGS_STS(T)) + +#define PRN_DBR_TIME(T) \ + epicsTimeToStrftime(timeText, TIMETEXTLEN, timeFormatStr, \ + &(((struct T *)value)->stamp)); \ + sprintf(str, \ + FMT_TIME "\n" FMT_STS, \ + ARGS_TIME(T), ARGS_STS(T)) + +#define PRN_DBR_GR(T,F,FMT) \ + sprintf(str, \ + FMT_STS "\n" FMT_UNITS "\n" FMT_GR(FMT), \ + ARGS_STS(T), ARGS_UNITS(T), ARGS_GR(T,F)) + +#define PRN_DBR_GR_PREC(T,F,FMT) \ + sprintf(str, \ + FMT_STS "\n" FMT_UNITS "\n" FMT_PREC "\n" FMT_GR(FMT), \ + ARGS_STS(T), ARGS_UNITS(T), ARGS_PREC(T), ARGS_GR(T,F)) + +#define PRN_DBR_CTRL(T,F,FMT) \ + sprintf(str, \ + FMT_STS "\n" FMT_UNITS "\n" FMT_GR(FMT) "\n" FMT_CTRL(FMT), \ + ARGS_STS(T), ARGS_UNITS(T), ARGS_GR(T,F), ARGS_CTRL(T,F)) + +#define PRN_DBR_CTRL_PREC(T,F,FMT) \ + sprintf(str, \ + FMT_STS "\n" FMT_UNITS "\n" FMT_PREC "\n" FMT_GR(FMT) "\n" FMT_CTRL(FMT), \ + ARGS_STS(T), ARGS_UNITS(T), ARGS_PREC(T), ARGS_GR(T,F), ARGS_CTRL(T,F)) + +#define PRN_DBR_STSACK(T) \ + sprintf(str, \ + FMT_STS "\n" FMT_ACK, \ + ARGS_STS_UNSIGNED(T), ARGS_ACK(T)) + +#define PRN_DBR_X_ENUM(T) \ + n = ((struct T *)value)->no_str; \ + PRN_DBR_STS(T); \ + sprintf(str+strlen(str), \ + "\n Enums: (%2d)", n); \ + for (i=0; istrs[i]); + + +/* Make a good guess how long the dbr_... stuff might get as worst case */ +#define DBR_PRINT_BUFFER_SIZE \ + 50 /* timestamp */ \ + + 2 * 30 /* status / Severity */ \ + + 2 * 30 /* acks / ackt */ \ + + 20 + MAX_UNITS_SIZE /* units */ \ + + 30 /* precision */ \ + + 6 * 45 /* graphic limits */ \ + + 2 * 45 /* control limits */ \ + + 30 + (MAX_ENUM_STATES * (20 + MAX_ENUM_STRING_SIZE)) /* enums */ \ + + 50 /* just to be sure */ + +char *dbr2str (const void *value, unsigned type) +{ + static char str[DBR_PRINT_BUFFER_SIZE]; + char timeText[TIMETEXTLEN]; + int n, i; + + switch (type) { + case DBR_STRING: /* no additional information for basic data types */ + case DBR_INT: + case DBR_FLOAT: + case DBR_ENUM: + case DBR_CHAR: + case DBR_LONG: + case DBR_DOUBLE: break; + + case DBR_CTRL_STRING: /* see db_access.h: not implemented */ + case DBR_GR_STRING: /* see db_access.h: not implemented */ + case DBR_STS_STRING: PRN_DBR_STS(dbr_sts_string); break; + case DBR_STS_SHORT: PRN_DBR_STS(dbr_sts_short); break; + case DBR_STS_FLOAT: PRN_DBR_STS(dbr_sts_float); break; + case DBR_STS_ENUM: PRN_DBR_STS(dbr_sts_enum); break; + case DBR_STS_CHAR: PRN_DBR_STS(dbr_sts_char); break; + case DBR_STS_LONG: PRN_DBR_STS(dbr_sts_long); break; + case DBR_STS_DOUBLE: PRN_DBR_STS(dbr_sts_double); break; + + case DBR_TIME_STRING: PRN_DBR_TIME(dbr_time_string); break; + case DBR_TIME_SHORT: PRN_DBR_TIME(dbr_time_short); break; + case DBR_TIME_FLOAT: PRN_DBR_TIME(dbr_time_float); break; + case DBR_TIME_ENUM: PRN_DBR_TIME(dbr_time_enum); break; + case DBR_TIME_CHAR: PRN_DBR_TIME(dbr_time_char); break; + case DBR_TIME_LONG: PRN_DBR_TIME(dbr_time_long); break; + case DBR_TIME_DOUBLE: PRN_DBR_TIME(dbr_time_double); break; + + case DBR_GR_CHAR: + PRN_DBR_GR(dbr_gr_char, char, %8d); break; + case DBR_GR_INT: + PRN_DBR_GR(dbr_gr_int, int, %8d); break; + case DBR_GR_LONG: + PRN_DBR_GR(dbr_gr_long, long int, %8ld); break; + case DBR_GR_FLOAT: + PRN_DBR_GR_PREC(dbr_gr_float, float, %g); break; + case DBR_GR_DOUBLE: + PRN_DBR_GR_PREC(dbr_gr_double, double, %g); break; + case DBR_GR_ENUM: + PRN_DBR_X_ENUM(dbr_gr_enum); break; + case DBR_CTRL_CHAR: + PRN_DBR_CTRL(dbr_ctrl_char, char, %8d); break; + case DBR_CTRL_INT: + PRN_DBR_CTRL(dbr_ctrl_int, int, %8d); break; + case DBR_CTRL_LONG: + PRN_DBR_CTRL(dbr_ctrl_long, long int, %8ld); break; + case DBR_CTRL_FLOAT: + PRN_DBR_CTRL_PREC(dbr_ctrl_float, float, %g); break; + case DBR_CTRL_DOUBLE: + PRN_DBR_CTRL_PREC(dbr_ctrl_double, double, %g); break; + case DBR_CTRL_ENUM: + PRN_DBR_X_ENUM(dbr_ctrl_enum); break; + case DBR_STSACK_STRING: + PRN_DBR_STSACK(dbr_stsack_string); break; + default : strcpy (str, "can't print data type"); + } + return str; +} + + + +/*+************************************************************************** + * + * Function: print_time_val_sts + * + * Description: Print (to stdout) one wide output line + * (name, timestamp, value, status, severity) + * + * Arg(s) In: pv - Pointer to pv structure + * nElems - Number of elements (array) + * + **************************************************************************-*/ + +#define PRN_TIME_VAL_STS(TYPE,TYPE_ENUM) \ + printAbs = !pv->firstStampPrinted; \ + \ + ptsNewS = &((struct TYPE *)value)->stamp; \ + ptsNewC = &tsNow; \ + \ + if (!tsInitS) \ + { \ + tsFirst = *ptsNewS; \ + tsInitS = 1; \ + } \ + \ + switch (tsType) { \ + case relative: \ + ptsRefC = &tsStart; \ + ptsRefS = &tsFirst; \ + break; \ + case incremental: \ + ptsRefC = &tsPreviousC; \ + ptsRefS = &tsPreviousS; \ + break; \ + case incrementalByChan: \ + ptsRefC = &pv->tsPreviousC; \ + ptsRefS = &pv->tsPreviousS; \ + break; \ + default : \ + printAbs = 1; \ + } \ + \ + if (printAbs) { \ + if (tsSrcServer) { \ + epicsTimeToStrftime(timeText, TIMETEXTLEN, timeFormatStr, ptsNewS); \ + printf("%s", timeText); \ + } \ + if (tsSrcClient) { \ + epicsTimeToStrftime(timeText, TIMETEXTLEN, timeFormatStr, ptsNewC); \ + printf("(%s)", timeText); \ + } \ + pv->firstStampPrinted = 1; \ + } else { \ + if (tsSrcServer) { \ + printf(" %+12.6f", epicsTimeDiffInSeconds(ptsNewS, ptsRefS) ); \ + } \ + if (tsSrcClient) { \ + printf(" (%+12.6f)", epicsTimeDiffInSeconds(ptsNewC, ptsRefC) ); \ + } \ + } \ + \ + if (tsType == incrementalByChan) { \ + pv->tsPreviousC = *ptsNewC; \ + pv->tsPreviousS = *ptsNewS; \ + } \ + \ + tsPreviousC = *ptsNewC; \ + tsPreviousS = *ptsNewS; \ + \ + if (charArrAsStr && dbr_type_is_CHAR(TYPE_ENUM) && (reqElems || pv->nElems > 1)) { \ + dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pv->value, pv->dbrType); \ + size_t len = strlen((char*)s); \ + unsigned long elems = reqElems && (reqElems < pv->nElems) ? reqElems : pv->nElems; \ + int dlen; \ + char *d; \ + if (len < elems) elems = len; \ + dlen = epicsStrnEscapedFromRawSize((char*)s, elems); \ + d = calloc(dlen+1, sizeof(char)); \ + if(d) { \ + epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, elems); \ + printf("%c%s", fieldSeparator, d); \ + free(d); \ + } else { \ + printf("Failed to allocate for print_time_val_sts\n"); \ + } \ + } else { \ + if (reqElems || pv->nElems > 1) printf("%c%lu", fieldSeparator, pv->nElems); \ + for (i=0; inElems; ++i) { \ + printf("%c%s", fieldSeparator, val2str(value, TYPE_ENUM, i)); \ + } \ + } \ + /* Print Status, Severity - if not NO_ALARM */ \ + if ( ((struct TYPE *)value)->status || ((struct TYPE *)value)->severity ) \ + { \ + printf("%c%s%c%s\n", \ + fieldSeparator, \ + stat_to_str(((struct TYPE *)value)->status), \ + fieldSeparator, \ + sevr_to_str(((struct TYPE *)value)->severity)); \ + } else { \ + printf("%c%c\n", \ + fieldSeparator, fieldSeparator); \ + } + + +void print_time_val_sts (pv* pv, unsigned long reqElems) +{ + char timeText[2*TIMETEXTLEN+2]; + int i, printAbs; + void* value = pv->value; + epicsTimeStamp *ptsRefC, *ptsRefS; /* Reference timestamps (client, server) */ + epicsTimeStamp *ptsNewC, *ptsNewS; /* Update timestamps (client, server) */ + epicsTimeStamp tsNow; + + epicsTimeGetCurrent(&tsNow); + epicsTimeToStrftime(timeText, TIMETEXTLEN, timeFormatStr, &tsNow); + + if (pv->nElems <= 1 && fieldSeparator == ' ') printf("%-30s", pv->name); + else printf("%s", pv->name); + printf("%c", fieldSeparator); + if (!pv->onceConnected) + printf("*** Not connected (PV not found)\n"); + else if (pv->status == ECA_DISCONN) + printf("%s *** disconnected\n", timeText); + else if (pv->status == ECA_NORDACCESS) + printf("%s *** no read access\n", timeText); + else if (pv->status != ECA_NORMAL) + printf("%s *** CA error %s\n", timeText, ca_message(pv->status)); + else if (pv->value == 0) + printf("%s *** no data available (timeout)\n", timeText); + else + switch (pv->dbrType) { + case DBR_TIME_STRING: + PRN_TIME_VAL_STS(dbr_time_string, DBR_TIME_STRING); + break; + case DBR_TIME_SHORT: + PRN_TIME_VAL_STS(dbr_time_short, DBR_TIME_SHORT); + break; + case DBR_TIME_FLOAT: + PRN_TIME_VAL_STS(dbr_time_float, DBR_TIME_FLOAT); + break; + case DBR_TIME_ENUM: + PRN_TIME_VAL_STS(dbr_time_enum, DBR_TIME_ENUM); + break; + case DBR_TIME_CHAR: + PRN_TIME_VAL_STS(dbr_time_char, DBR_TIME_CHAR); + break; + case DBR_TIME_LONG: + PRN_TIME_VAL_STS(dbr_time_long, DBR_TIME_LONG); + break; + case DBR_TIME_DOUBLE: + PRN_TIME_VAL_STS(dbr_time_double, DBR_TIME_DOUBLE); + break; + default: printf("can't print data type\n"); + } +} + + +/*+************************************************************************** + * + * Function: create_pvs + * + * Description: Creates an arbitrary number of PVs + * + * Arg(s) In: pvs - Pointer to an array of pv structures + * nPvs - Number of elements in the pvs array + * pCB - Connection state change callback + * + * Arg(s) Out: none + * + * Return(s): Error code: + * 0 - All PVs created + * 1 - Some PV(s) not created + * + **************************************************************************-*/ + +int create_pvs (pv* pvs, int nPvs, caCh *pCB) +{ + int n; + int result; + int returncode = 0; + + if (!tsInitC) /* Initialize start timestamp */ + { + epicsTimeGetCurrent(&tsStart); + tsInitC = 1; + } + /* Issue channel connections */ + for (n = 0; n < nPvs; n++) { + result = ca_create_channel (pvs[n].name, + pCB, + &pvs[n], + caPriority, + &pvs[n].chid); + if (result != ECA_NORMAL) { + fprintf(stderr, "CA error %s occurred while trying " + "to create channel '%s'.\n", ca_message(result), pvs[n].name); + pvs[n].status = result; + returncode = 1; + } + } + + return returncode; +} + + +/*+************************************************************************** + * + * Function: connect_pvs + * + * Description: Connects an arbitrary number of PVs + * + * Arg(s) In: pvs - Pointer to an array of pv structures + * nPvs - Number of elements in the pvs array + * + * Arg(s) Out: none + * + * Return(s): Error code: + * 0 - All PVs connected + * 1 - Some PV(s) not connected + * + **************************************************************************-*/ + +int connect_pvs (pv* pvs, int nPvs) +{ + int returncode = create_pvs ( pvs, nPvs, 0); + if ( returncode == 0 ) { + /* Wait for channels to connect */ + int result = ca_pend_io (caTimeout); + if (result == ECA_TIMEOUT) + { + if (nPvs > 1) + { + fprintf(stderr, "Channel connect timed out: some PV(s) not found.\n"); + } else { + fprintf(stderr, "Channel connect timed out: '%s' not found.\n", + pvs[0].name); + } + returncode = 1; + } + } + return returncode; +} diff --git a/modules/ca/src/tools/tool_lib.h b/modules/ca/src/tools/tool_lib.h new file mode 100644 index 000000000..fb5c4af3c --- /dev/null +++ b/modules/ca/src/tools/tool_lib.h @@ -0,0 +1,105 @@ +/*************************************************************************\ +* 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. +* Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer +* Synchrotronstrahlung. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange (BESSY) + * + * Modification History + * 2009/03/31 Larry Hoff (BNL) + * Added field separators + * + */ + +#ifndef INCLtool_libh +#define INCLtool_libh + +#include + +/* Convert status and severity to strings */ +#define stat_to_str(stat) \ + ((stat) >= 0 && (stat) <= (signed)lastEpicsAlarmCond) ? \ + epicsAlarmConditionStrings[stat] : "??" + +#define sevr_to_str(stat) \ + ((stat) >= 0 && (stat) <= (signed)lastEpicsAlarmSev) ? \ + epicsAlarmSeverityStrings[stat] : "??" + +#define stat_to_str_unsigned(stat) \ + ((stat) <= lastEpicsAlarmCond) ? \ + epicsAlarmConditionStrings[stat] : "??" + +#define sevr_to_str_unsigned(stat) \ + ((stat) <= lastEpicsAlarmSev) ? \ + epicsAlarmSeverityStrings[stat] : "??" + +/* The different versions are necessary because stat and sevr are + * defined unsigned in CA's DBR_STSACK structure and signed in all the + * others. Some compilers generate warnings if you check an unsigned + * being >=0 */ + + +#define DEFAULT_CA_PRIORITY 0 /* Default CA priority */ +#define DEFAULT_TIMEOUT 1.0 /* Default CA timeout */ + +#ifndef _WIN32 +# define LINE_BUFFER(stream) setvbuf(stream, NULL, _IOLBF, BUFSIZ) +#else +/* Windows doesn't support line mode, turn buffering off completely */ +# define LINE_BUFFER(stream) setvbuf(stream, NULL, _IONBF, 0) +#endif + + +/* Type of timestamp */ +typedef enum { absolute, relative, incremental, incrementalByChan } TimeT; + +/* Output formats for integer data types */ +typedef enum { dec, bin, oct, hex } IntFormatT; + +/* Structure representing one PV (= channel) */ +typedef struct +{ + char* name; + chid chid; + long dbfType; + long dbrType; + unsigned long nElems; // True length of data in value + unsigned long reqElems; // Requested length of data + int status; + void* value; + epicsTimeStamp tsPreviousC; + epicsTimeStamp tsPreviousS; + char firstStampPrinted; + char onceConnected; +} pv; + + +extern TimeT tsType; /* Timestamp type flag (-t option) */ +extern int tsSrcServer; /* Timestamp source flag (-t option) */ +extern int tsSrcClient; /* Timestamp source flag (-t option) */ +extern IntFormatT outTypeI; /* Flag used for -0.. output format option */ +extern IntFormatT outTypeF; /* Flag used for -l.. output format option */ +extern int enumAsNr; /* Used for -n option (get DBF_ENUM as number) */ +extern int charArrAsStr; /* used for -S option - treat char array as (long) string */ +extern double caTimeout; /* Wait time default (see -w option) */ +extern char dblFormatStr[]; /* Format string to print doubles (see -e -f option) */ +extern char fieldSeparator; /* Output field separator */ +extern capri caPriority; /* CA priority */ + +extern char *val2str (const void *v, unsigned type, int index); +extern char *dbr2str (const void *value, unsigned type); +extern void print_time_val_sts (pv *pv, unsigned long reqElems); +extern int create_pvs (pv *pvs, int nPvs, caCh *pCB ); +extern int connect_pvs (pv *pvs, int nPvs ); + +/* + * no additions below this endif + */ +#endif /* ifndef INCLtool_libh */