EPICS Home

Experimental Physics and Industrial Control System


 
1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: Record processing twice after upgrading to Base 3.15.5
From: "Dunning, Michael" <[email protected]>
To: Andrew Johnson <[email protected]>
Cc: "Condamoor, Shantha" <[email protected]>, EPICS Tech-Talk <[email protected]>
Date: Fri, 29 Sep 2017 15:15:13 -0700
Andrew, thanks for your (appropriately blunt) response.  Unfortunately
the developer of this code is retired, and I'm fairly new to non-asyn
device support.

In the driver there's a thread that periodically does I/O to the
device, and this thread makes calls to process_record_li() after it
parses the response from the device.  As far as I can tell, the
process_record_li() routine is the only thing causing the record to
process.

The double-processing starts about 10 seconds after iocInit (I/O is
done approximately once per second). I don't yet understand what is
going on in the driver for the first 10 seconds, but during that time
the record appears to process normally, i.e., no double-processing.

This is the longin support from device support:


struct {
    long        number;
    DEVSUPFUN   report;
    DEVSUPFUN   init;
    DEVSUPFUN   init_record;
    DEVSUPFUN   get_ioint_info;
    DEVSUPFUN   read_longin;
}devLiEtherPSC={
    5,
    NULL,
    NULL,
    init_li_record,
    NULL,
    read_li,
};
epicsExportAddress(dset, devLiEtherPSC);


static long init_li_record( struct longinRecord *pli )
{
    struct bitbusio     *pb = (struct bitbusio*)&(pli->inp.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pli ) );

}


static long read_li( struct longinRecord *pli )
{
    PSCRECORD        *PSCRecord;

    if ( ! (PSCRecord = pli->dpvt) ) return (2);

    if ( PSCRecord->nsta ) recGblSetSevr( pli, PSCRecord->nsta, INVALID_ALARM );

    pli->val  = PSCRecord->val.li;
    pli->udf  = FALSE;
    pli->pact = FALSE;

    return(0);
}


Now I see that compared to other non-asyn device support examples,
this does look like kind of a mess.  In addition to making it
thread-safe and checking pli->pact, does anyone have any suggestions
on if/how I should restructure this?
I've attached the driver and device support source files if anyone
would be willing to take a look.

Thank you!



Michael Dunning
SLAC National Accelerator Laboratory
2575 Sand Hill Road
Menlo Park, CA 94025
(650) 926-5200


On Wed, Sep 27, 2017 at 2:43 PM, Andrew Johnson <[email protected]> wrote:
> Hi Michael,
>
> Assuming the process_record_li() routine is for triggering record
> processing after some I/O has been received, why is it unconditionally
> setting pli->pact? This looks to me like a cross between an asynchronous
> I/O completion routine (i.e. part of an asynchronous read_longin()
> device support method) and something for triggering record processing.
>
> It isn't thread-safe; it accesses two pli fields in the second if()
> condition without holding the record's scan-lock, which has been illegal
> in all Base versions for quite a long time. Is there an associated
> longin device support that goes along with this?
>
> Is the I/O operation the only thing that causes this record to process?
> The fact that you're getting SCAN/INVALID alarms suggests that something
> else is going on as well, but I can't tell what from just the code you
> showed us.
>
> This might have worked in 3.14.12, but since it isn't following the
> rules so there is no guarantee what it will do in other versions.
>
> Sorry to be so blunt,
>
> - Andrew
>
>
> On 09/27/2017 03:30 PM, Dunning, Michael wrote:
>> After upgrading to Base 3.15.5, we are seeing what looks like
>> double-processing of a certain custom record type.  It is from our
>> device support for Bira Ethernet Power Supply Controllers. The problem
>> is with this record:
>>
>> record(longin, "ESB:BEND:2110:GainADCRaw")
>> {
>>     field(DESC, "ADC Gain Raw Data")
>>     field(ASG,  "Internal")
>>     field(DTYP, "EtherPSC")
>>     field(INP,   "#L0 N0 P0 S50 @172.27.248.43")
>>     field(FLNK, "ESB:BEND:2110:GainADC.PROC")
>> }
>>
>>
>>
>> When the IOC boots, the record has STAT: SCAN and SEVR: INVALID.  If
>> we camonitor this record, it seems to be processed twice:
>>
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:15.648796 529
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:15.648796 529 SCAN INVALID
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:16.549097 534
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:16.549097 534 SCAN INVALID
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:17.449359 543
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:17.449359 543 SCAN INVALID
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:18.349763 549
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:18.349763 549 SCAN INVALID
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:19.250071 551
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:19.250071 551 SCAN INVALID
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:20.150256 549
>> ESB:BEND:2110:GainADCRaw       2017-09-10 20:12:20.150256 549 SCAN INVALID
>>
>>
>>
>> The double-processing explains the SCAN and INVALID states.
>>
>> Here is the record processing routine from device support:
>>
>> static void process_record_li( ETHERPSCNODE *node, int signal, long i )
>> {
>>     PSCRECORD           *PSCRecord;
>>     struct longinRecord *pli;
>>     struct rset         *prset;
>>
>>     PSCRecord = &node->record[signal];
>>     if ( ! (pli = (struct longinRecord*) PSCRecord->precord) ) return;
>>
>>     if ( pli->val != i  ||  pli->udf  ||  PSCRecord->nsta )
>>     {
>>         PSCRecord->nsta = NO_ALARM;
>>         PSCRecord->val.li = i;
>>         prset = (struct rset *) pli->rset;
>>
>>         dbScanLock( (struct dbCommon*) pli );
>>         pli->pact = TRUE;
>>         (*prset->process)( pli );
>>         dbScanUnlock( (struct dbCommon*) pli );
>>     }
>> }
>>
>>
>>
>> I confirmed that this problem doesn't occur with Base 3.14.12.4.  Does
>> anyone have any idea why this record would be processed twice with
>> Base-3.15?
>>
>>
>>
>>
>>
>> Michael Dunning
>> SLAC National Accelerator Laboratory
>> 2575 Sand Hill Road
>> Menlo Park, CA 94025
>> (650) 926-5200
>>
>
> --
> Arguing for surveillance because you have nothing to hide is no
> different than making the claim, "I don't care about freedom of
> speech because I have nothing to say." -- Edward Snowdon
/* devEtherPSC.c */
/* SPEAR Ethernet Power Supply Controller device support module */
  
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "alarm.h"
#include "cvtTable.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "errlog.h"
#include "recGbl.h"
#include "recSup.h"
#include "devSup.h"
#include "aiRecord.h"
#include "aoRecord.h"
#include "biRecord.h"
#include "boRecord.h"
#include "longinRecord.h"
#include "stringinRecord.h"
#include "link.h"
#include "epicsExport.h"

#include "etherPSCInclude.h"

#define	DEBUG 0


/*Create the dsets */

static long init_airecord();
static long read_ai();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	read_ai;
	DEVSUPFUN	special_linconv;
}devAiEtherPSC={
	6,
	NULL,
	NULL,
	init_airecord,
	NULL,
	read_ai,
	NULL,
};
epicsExportAddress(dset, devAiEtherPSC);

static long init_aorecord();
static long write_ao();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	write_ao;
	DEVSUPFUN	special_linconv;
}devAoEtherPSC={
	6,
	NULL,
	NULL,
	init_aorecord,
	NULL,
	write_ao,
	NULL,
};
epicsExportAddress(dset, devAoEtherPSC);

static long init_bi_record();
static long read_bi();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	read_bi;
}devBiEtherPSC={
	5,
	NULL,
	NULL,
	init_bi_record,
	NULL,
	read_bi,
};
epicsExportAddress(dset, devBiEtherPSC);

static long init_bo_record();
static long write_bo();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	write_bo;
}devBoEtherPSC={
	5,
	NULL,
	NULL,
	init_bo_record,
	NULL,
	write_bo,
};
epicsExportAddress(dset, devBoEtherPSC);

static long init_li_record();
static long read_li();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	read_longin;
}devLiEtherPSC={
	5,
	NULL,
	NULL,
	init_li_record,
	NULL,
	read_li,
};
epicsExportAddress(dset, devLiEtherPSC);

static long init_si_record();
static long read_si();

struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	read_stringin;
}devSiEtherPSC={
	5,
	NULL,
	NULL,
	init_si_record,
	NULL,
	read_si,
};
epicsExportAddress(dset, devSiEtherPSC);

static long init_airecord( struct aiRecord *pai )
{

    struct bitbusio 	*pb = (struct bitbusio*)&(pai->inp.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pai ) );
}

static long read_ai( struct aiRecord *pai)
{
    PSCRECORD		*PSCRecord;

#if  DEBUG
    struct bitbusio     *pb = (struct bitbusio*)&(pai->inp.value);

    epicsPrintf("read_ai@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
		pb->link, pb->node, pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = pai->dpvt) ) return (2);

    if ( PSCRecord->nsta ) recGblSetSevr( pai, PSCRecord->nsta, INVALID_ALARM );

    pai->val  = PSCRecord->val.ai;
    pai->udf  = FALSE;
    pai->pact = FALSE;

    return(2);
}


static long init_aorecord( struct aoRecord *pao )
{

    struct bitbusio	*pb = (struct bitbusio*)&(pao->out.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pao ) );

}

static long write_ao( struct aoRecord *pao )
{
    PSCRECORD		*PSCRecord;

#if  DEBUG
    struct bitbusio     *pb = (struct bitbusio*)&(pao->out.value);

    epicsPrintf("write_ao@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
		pb->link, pb->node, pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = pao->dpvt) ) return (2);

    PSCRecord->val.ao.next = pao->val;
    PSCRecord->set = 1;

    return(0);
}


static long init_bi_record( struct biRecord *pbi )
{

    struct bitbusio 	*pb = (struct bitbusio*)&(pbi->inp.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pbi ) );

}

static long read_bi( struct biRecord *pbi )
{
    PSCRECORD		*PSCRecord;

#if  DEBUG
    struct bitbusio     *pb = (struct bitbusio*)&(pbi->inp.value);

    epicsPrintf("read_bi@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
		pb->link, pb->node, pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = pbi->dpvt) ) return (2);

    if ( PSCRecord->nsta ) recGblSetSevr( pbi, PSCRecord->nsta, INVALID_ALARM );

    pbi->rval = PSCRecord->val.bi;
    pbi->udf  = FALSE;
    pbi->pact = FALSE;

    return(0);
}


static long init_bo_record( struct boRecord *pbo )
{

    struct bitbusio     *pb = (struct bitbusio*)&(pbo->out.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pbo ) ); 

}

static long write_bo( struct boRecord *pbo )
{
    PSCRECORD		*PSCRecord;
    struct bitbusio     *pb = (struct bitbusio*)&(pbo->out.value);


#if  DEBUG
    epicsPrintf("write_bo@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
		pb->link, pb->node, pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = pbo->dpvt) ) return (2);

    switch ( pb->signal )
    {
	case	SIGNAL_PS_ON_OFF :
		PSCRecord->val.bo = pbo->val;
		PSCRecord->set = 1;	
	break;

	case	SIGNAL_INT_RESET :
		if ( pbo->val ) PSCRecord->set = 1;	
	break;
    }

    return(0);
}
 

static long init_li_record( struct longinRecord *pli )
{

    struct bitbusio 	*pb = (struct bitbusio*)&(pli->inp.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) pli ) );

}

static long read_li( struct longinRecord *pli )
{
    PSCRECORD		*PSCRecord;

#if  DEBUG
    struct bitbusio     *pb = (struct bitbusio*)&(pli->inp.value);

    epicsPrintf ( "read_li@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
		pb->link, pb->node,pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = pli->dpvt) ) return (2);

    if ( PSCRecord->nsta ) recGblSetSevr( pli, PSCRecord->nsta, INVALID_ALARM );

    pli->val  = PSCRecord->val.li;
    pli->udf  = FALSE;
    pli->pact = FALSE;

    return(0);
}


static long init_si_record( struct stringinRecord *psi )
{

    struct bitbusio 	*pb = (struct bitbusio*)&(psi->inp.value);

    return ( etherPSCdrvInitRecord( pb, (struct dbCommon*) psi ) );
}

static long read_si( struct stringinRecord *psi )
{

    PSCRECORD		*PSCRecord;

#if  DEBUG
    struct bitbusio     *pb = (struct bitbusio*)&(psi->inp.value);

    epicsPrintf ( "read_si@devEtherPSC, L=%d, N=%d, P=%d, S=%d\n",
                pb->link, pb->node,pb->port, pb->signal );
#endif

    if ( ! (PSCRecord = psi->dpvt) ) return (2);

    if ( PSCRecord->nsta ) recGblSetSevr( psi, PSCRecord->nsta, INVALID_ALARM );

    strncpy( psi->val, PSCRecord->val.si, sizeof(psi->val) );
    psi->oval[0]=0;
    psi->udf  = FALSE;
    psi->pact = FALSE;

    return ( 0 );
}

Attachment: devEtherPSC.dbd
Description: Binary data

/* drvEtherPSC.c - Driver Support Routines SPEAR Ethernet Power Supply
 *		 CONTROLLER
 *
 *	Author:		Clemens Wermelskirchen
 *	Date:		08-OCT-2005
 */

#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>

#include	"alarm.h"
#include	"dbCommon.h"
#include	"dbDefs.h"
#include	"dbScan.h"
#include	"dbLock.h"
#include	"drvSup.h"
#include	"devSup.h"
#include        "errMdef.h"        /* errMessage()         */
#include	"epicsThread.h"
#include	"errlog.h"
#include	<devLib.h>
#include	"aiRecord.h"
#include	"aoRecord.h"
#include	"biRecord.h"
#include	"longinRecord.h"
#include	"stringinRecord.h"
#include	"recSup.h"
#include	"link.h"
#include        "iocsh.h"
#include	"epicsExport.h"
#include	"initHooks.h"

#include	"etherPSCInclude.h"

#define	DEBUG 0

#ifndef OK
#define	OK	0
#endif
#ifndef ERROR
#define	ERROR	-1
#endif

/* Global variables */

double EtherPSCDelay = 0.300;  /* Seconds */

/* Local variables */

static ETHERPSC		etherpsc = { 0 };

typedef struct
	{
	    unsigned short	datalength;
	    unsigned short	port;
	    unsigned char	cmd;
	    unsigned char	rsp;
	    unsigned char	data[256];
	} ETHERPSCMSG;

static const ETHERPSCMSG  PS_ON_MSG = {
                4, 2000, BITBUSCMD_PS_ON, 0x00,
                { BITBUSCMD_PS_ON, 0 } };

static const ETHERPSCMSG  PS_OFF_MSG = {
                4, 2000, BITBUSCMD_PS_OFF, 0x00,
                { BITBUSCMD_PS_OFF, 0 } };

static const ETHERPSCMSG  RESET_PS_MSG = {
                4, 2000, BITBUSCMD_CNTL_RESET, 0x00,
                { 0, 1 } };

static const ETHERPSCMSG  PS_INT_RESET_MSG = {
                4, 2000, BITBUSCMD_INT_RESET, 0x00,
                { BITBUSCMD_INT_RESET, 0 } };

static const ETHERPSCMSG  PS_CURRENT_AC_MSG = {
                11, 2000, BITBUSCMD_SET_I, 0x00,
                { BITBUSCMD_SET_I, 1, 0, 0, 0, 0, 0, 1, 0 } };

/*                      this command is not being used
static const ETHERPSCMSG  SHORT_STATUS_MSG = {
                4, 2000, BITBUSCMD_SHORT_STATUS, 0x00,
                { BITBUSCMD_SHORT_STATUS, 0 } };
*/

static const ETHERPSCMSG  FAST_STATUS_MSG = {
                4, 2000, BITBUSCMD_FAST_STATUS, 0x00,
                { BITBUSCMD_FAST_STATUS, 0 } };

static const ETHERPSCMSG  INFO_MSG_MSG = {
                4, 2000, BITBUSCMD_INFO_MSG, 0x00,
                { BITBUSCMD_INFO_MSG, 0 } };

static const ETHERPSCMSG  DIAG_STATUS_MSG = {
                4, 2000, BITBUSCMD_DIAG_RDBK, 0x00,
                { BITBUSCMD_DIAG_RDBK, 0 } };

static const ETHERPSCMSG  DES_I_RDBK_MSG = {
                5, 2000, BITBUSCMD_DES_I_RDBK, 0x00,
                { BITBUSCMD_DES_I_RDBK, 1, 0 } };

static const ETHERPSCMSG  CNTL_INFO_MSG = {
                4, 2000, BITBUSCMD_CNTL_INFO, 0x00,
                { BITBUSCMD_CNTL_INFO, 0 } };

static const ETHERPSCMSG  CAL_DATA_MSG = {
                4, 2000, BITBUSCMD_CAL_DATA, 0x00,
                { BITBUSCMD_CAL_DATA, 0 } };

static const ETHERPSCMSG  ANALOG_RDBK_MSG = {
                4, 2000, BITBUSCMD_ANALOG_RDBK, 0x00,
                { BITBUSCMD_ANALOG_RDBK, 0 } };


/* local function prototypes */
static long init( );
static long report( int level );
static EPICSTHREADFUNC etherPSC_input_thread( ETHERPSC *net );
static EPICSTHREADFUNC etherPSC_output_thread( ETHERPSC *net );
static void process_etherpsc_rsp ( ETHERPSCNODE *node, unsigned char *rsp, long n );
static void process_record_si( ETHERPSCNODE*, int, unsigned char*, int );
static void process_record_li( ETHERPSCNODE*, int, long );
static void process_record_ao( ETHERPSCNODE *node, int signal, float f );


/* Global variables        */
/* Driver entry table */

struct {
	long		number;
	DRVSUPFUN	report;
	DRVSUPFUN	init;
} drvEtherPSC={
	2,
	report,
	init};
epicsExportAddress(drvet, drvEtherPSC);

/* Driver initialization routines */

static void initThreads(initHookState state)
{
  if (state == initHookAfterInitDatabase) {
    if ( ! epicsThreadCreate( "EtherPSC_rcv", epicsThreadPriorityMedium,
			epicsThreadGetStackSize(epicsThreadStackMedium),
			(EPICSTHREADFUNC) etherPSC_input_thread,
			(void*) &etherpsc ) ) {
	printf( "drvEtherPSC: receiver thread creation failed\n" );
	exit( 1 );
    }

    if ( ! epicsThreadCreate( "EtherPSC_snd", epicsThreadPriorityMedium,
                        epicsThreadGetStackSize(epicsThreadStackMedium),
                        (EPICSTHREADFUNC) etherPSC_output_thread,
                        (void*) &etherpsc ) ) {
        printf( "drvEtherPSC: transmission thread creation failed\n" );
        exit( 1 );
    }
  }
}
static long init()
{
    /* create socket for communication, for now, there is only one */

    if ( ( etherpsc.sock = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 ) {
	printf( "drvEtherPSC: failed to create socket: %s", strerror(errno) );
	exit( ERROR );
    }
    initHookRegister(initThreads);
    return(OK);
}


/*
 * Ether_io_report - report ethernet controllers present
 */
long Ether_io_report ( int level )
{
    ETHERPSCNODE	*pnode;

    printf( "\nEthernet & PSC Configuration\n" );

    for ( pnode = etherpsc.pnode; pnode; pnode = (ETHERPSCNODE*) pnode->pnode )
    {
	if ( pnode->present )
	{
	    printf( "  EtherPSC %15s alive, device=%s\n",
			inet_ntoa( pnode->sockAddr.sin_addr ),
			pnode->record[SIGNAL_MAGNET_ID].val.si );
	}
	else
	{
	    if ( pnode->record[SIGNAL_MAGNET_ID].val.si[0] )
	    {
		printf( "  EtherPSC %15s disconnected, device=%s\n",
			inet_ntoa( pnode->sockAddr.sin_addr ),
			pnode->record[SIGNAL_MAGNET_ID].val.si );
	    }
	    else
	    {
		printf( "  EtherPSC %15s disconnected\n",
			inet_ntoa( pnode->sockAddr.sin_addr ) );
	    }
	}
    }

    return(OK);
}

/* Driver report routines */

static long report( int level )
{
    return( Ether_io_report(level) );
}


long etherPSCdrvInitRecord( struct bitbusio *pb, struct dbCommon *pr )
{
    unsigned long	addr;
    ETHERPSCNODE	*node, **pnode;

    if ( pb->signal < 1  ||  pb->signal > SIGNAL_MAXNUM )
    {
        printf( "etherPSCdrvInitRecord: invalid signal #%d encountered!\n",
                        pb->signal );
        return (ERROR);
    }

    if ( pb->link != 0 )
    {
        printf( "etherPSCdrvInitRecord: invalid link #%d encountered!\n",
                        pb->link );
        return (ERROR);
    }

    if ( ( addr = inet_addr(  pb->parm ) ) == ERROR )
    {
        printf( "etherPSCdrvInitRecord: invalid ip address %s encountered!\n",
                        pb->parm );
        return (ERROR);
    }

    for ( pnode = (ETHERPSCNODE**) &etherpsc.pnode;
		(node = *pnode) && node->sockAddr.sin_addr.s_addr != addr;
		pnode = (ETHERPSCNODE**) &node->pnode ) {};
    if ( ! node )
    {
	node = malloc( sizeof(ETHERPSCNODE) );
	*pnode = node;

	if ( ! node )
	{
	    printf( "etherPSCdrvInitRecord: malloc failed\n" );
	    return( ERROR );
	}
	memset( node, 0, sizeof(ETHERPSCNODE) );
	node->sockAddrSize = sizeof(node->sockAddr);
	node->sockAddr.sin_addr.s_addr = addr;
	node->sockAddr.sin_family = AF_INET;
	node->present = 0;
    }

    node->record[pb->signal].precord = pr;
    pr->dpvt = &node->record[pb->signal];

    return (2);
}



static void node_gone ( ETHERPSCNODE *node )
{
    int                 i;
    struct rset         *prset;
    struct dbCommon     *record;

    if ( node->present ) {

	printf( "drvEtherPSC: lost node %s\n",
			inet_ntoa( node->sockAddr.sin_addr ) );

				/* PSC gone, cancel all queued commands */
	node->record[SIGNAL_PS_ON_OFF].set = 0;
	node->record[SIGNAL_INT_RESET].set = 0;
	node->record[SIGNAL_CURRENT_AC].set = 0;

        for ( i=9; i<SIGNAL_MAXNUM+1; i++ )
        {
                                /* PSC gone, UDF all output records */
            if ( (record = node->record[i].precord)  &&
                      ! node->record[i].nsta ) {
                node->record[i].nsta = COMM_ALARM;
                prset = (struct rset *) record->rset;
                dbScanLock(record);
                record->pact = TRUE;
                record->udf  = TRUE;
                (*prset->process)(record);
                dbScanUnlock(record);
            }
        }
        node->present = 0;
    }
}


/*
 * send_msg
 * send message to ethernet power supply controler
 */
static long send_msg( ETHERPSCNODE *node, const ETHERPSCMSG *msg )
{

    node->sockAddr.sin_port = htons( msg->port );

    if ( node->unanswered > 10 ) {
	node_gone( node );
    }
    else node->unanswered++;

    if ( sendto( etherpsc.sock, &msg->cmd, msg->datalength, 0,
	(struct sockaddr*) &node->sockAddr, node->sockAddrSize ) == ERROR ) {
	if ( node->present ) {
	    perror( "drvEtherPSC: sendto" );
	    node_gone( node );
	}
	return( 1 );
    }

					/* count message */
    node->record[SIGNAL_BITBUS_CMD_CNT].val.li++;

    return( OK );
}

/*
 * d2b
 * Convert double into Bitbus PSC byte stream
 */
static void d2b ( unsigned char *b, double *d )
{
    float               f;
    unsigned char       *p;

    p = (unsigned char*) &f;
    f = *d;
#ifdef __PPC__    
    b[0] = p[3];
    b[1] = p[2];
    b[2] = p[1];
    b[3] = p[0];
#else
    b[0] = p[0];
    b[1] = p[1];
    b[2] = p[2];
    b[3] = p[3];
#endif

    return;
}

/*
 * s2b
 * Convert short into Bitbus PSC byte stream
 */
static void s2b ( unsigned char *b, unsigned short s )
{
    b[0] = s & 0xff;
    b[1] = s >> 8;

    return;
}

/*
 * b2f
 * Convert Bitbus PSC byte stream into float
 */
static float b2f ( unsigned char *b )
{
    float               f;
    unsigned char       *p;

    p = (unsigned char*) &f;
#ifdef __PPC__    
    p[0] = b[3];
    p[1] = b[2];
    p[2] = b[1];
    p[3] = b[0];
#else
    p[0] = b[0];
    p[1] = b[1];
    p[2] = b[2];
    p[3] = b[3];
#endif

    return ( f );
}

/*
 * b2l
 * Convert Bitbus PSC byte stream (2 bytes) into long
 */
static long b2l ( unsigned char *b )
{
    long                l;
    unsigned short      us;
    short               *s;

    us = ( b[1] << 8 ) + b[0];
    s = (short*) &us;
    l = *s;

    return ( l );
}



static EPICSTHREADFUNC etherPSC_output_thread( ETHERPSC *etherpsc )
{
    int			phase;
    ETHERPSCNODE	*node;
    ETHERPSCMSG		msg;
    double		new_ao, dI, dIdt;
    int			dt100;
    char		*p;

    printf( "drvEtherPSC: transmitter thread launched\n" );

    for ( node = etherpsc->pnode; node;
                node = (ETHERPSCNODE*) node->pnode )
    {
	p = inet_ntoa( node->sockAddr.sin_addr );
        process_record_si( node, SIGNAL_CNTL_ADDRESS, (unsigned char *)p, strlen(p) );
        process_record_li( node, SIGNAL_BITBUS_LINE, 0 );
        process_record_li( node, SIGNAL_BITBUS_ADDRESS, 0 );


                                        /* request old current setpoint */
        send_msg( node, &DES_I_RDBK_MSG );
    }


                                        /* it takes three phases to get */
                                        /* all information from PSC */
    for ( phase = 0; ; phase = (phase + 1) % 3 )
    {
        epicsThreadSleep( EtherPSCDelay );      /* delay between cycles */

        for ( node = etherpsc->pnode; node;
                node = (ETHERPSCNODE*) node->pnode )
        {
	    if ( node->rampwait > 0 ) node->rampwait--;  /* Wait for ramp to start */
            if ( node->busy > 0 )
            {                           /* PSC needs more time, skip it */
                node->busy--;
                continue;
            }

            if ( node->record[SIGNAL_PS_ON_OFF].set )
            {                           /* turn PSC on or off */
		node->record[SIGNAL_PS_ON_OFF].set = 2;
                if ( node->record[SIGNAL_PS_ON_OFF].val.bo )
                {
                    send_msg( node, &PS_ON_MSG );
                    node->busy = 20;     /* this will take a while */
                    if (!node->record[SIGNAL_ON_STATUS].val.bi)
                      process_record_ao( node, SIGNAL_CURRENT_AC, (float) 0.0 );
                }
                else
                {
                    send_msg( node, &PS_OFF_MSG );
                    node->busy = 10;     /* give it some time */
                }
                continue;
            }

            if ( node->record[SIGNAL_INT_RESET].set )
            {                           /* execute RESET command */
                send_msg( node, &PS_INT_RESET_MSG );
                node->busy = 10;         /* give it some time */
                continue;
            }

            if ( node->record[SIGNAL_INFO_MSG].set )
            {
                                        /* there is a message to grab */
                node->record[SIGNAL_INFO_MSG].set = 0;
                send_msg( node, &INFO_MSG_MSG );
                continue;
            }

            if ( node->record[SIGNAL_CURRENT_AC].set  &&
		 node->rampwait <= 0                  &&
                 node->record[SIGNAL_RAMPING_STATUS].val.bi == 0 )
            {                           /* we have a setpoint to send */
                        /* don't send setpoint, if PS is off */
                if ( node->record[SIGNAL_ON_STATUS].val.bi == 0 ) {
		    node->record[SIGNAL_CURRENT_AC].set = 0;	/* done */
		    continue;
		}
		node->record[SIGNAL_CURRENT_AC].set = 2;
		node->rampwait = 2;

                new_ao = node->record[SIGNAL_CURRENT_AC].val.ao.next;

                                        /* calculate ramping time */
                dI = new_ao - node->record[SIGNAL_CURRENT_AC].val.ao.last;
                if ( dI < 0.0 ) dI = - dI;
                dIdt = node->record[SIGNAL_CURRENT_AC_DIDT].val.ao.next;
                if ( dIdt  > 0.0 )
                {
                    dt100 = dI / dIdt * 100.0 + 1.0;
                    dt100 = dt100 * 3.154/2.0;  /* correct for cosine curve */
                    if ( dt100 > 0xffff ) dt100 = 0xffff;
                }
                else
                {
                    dt100 = 1;
                }
                node->record[SIGNAL_CURRENT_AC].val.ao.last = new_ao;
                msg = PS_CURRENT_AC_MSG;
                d2b( &msg.data[3], &new_ao );
                s2b( &msg.data[7], (unsigned short) dt100 );
                send_msg( node, &msg );
                continue;
            }

            if ( node->record[SIGNAL_CHASSIS_TYPE].set )
            {                           /* controller available, */
                                        /* get static configuration info */
                send_msg( node, &CNTL_INFO_MSG );
                continue;
            }

            if ( node->record[SIGNAL_XDUCT1_V2I].set )
            {
                send_msg( node, &CAL_DATA_MSG );
                continue;
            }

            switch ( phase )
            {
                case 0 :
                    send_msg( node, &FAST_STATUS_MSG );
                break;

                case 1 :
                    send_msg( node, &DIAG_STATUS_MSG );
                break;

                case 2 :
                    send_msg( node, &ANALOG_RDBK_MSG );
                break;
            }
        }
    }

    return(OK);
}


static EPICSTHREADFUNC etherPSC_input_thread( ETHERPSC *etherpsc )
{
    int			n;
    unsigned char	rsp[512];
    struct sockaddr_in	sockAddr;
    socklen_t	        sockAddrSize;
    ETHERPSCNODE	*node;

    printf( "drvEtherPSC: receiver thread launched\n" );

    sockAddrSize = sizeof(sockAddr);

    for ( ; ; )
    {
	if ( ( n = recvfrom( etherpsc->sock, rsp, sizeof(rsp), 0,
			(struct sockaddr*) &sockAddr, &sockAddrSize ) ) < 0 ) 
	{
	    perror( "drvEtherPSC: recvfrom" );
	    return ( 0 );		/* needs further checking */
	}

#if DEBUG > 0
	printf("%d bytes from %s:%d\n", n, inet_ntoa(sockAddr.sin_addr),
			ntohs(sockAddr.sin_port) );
#endif

	if ( n > 2 )
	{
					/* find node description */
	    for ( node = etherpsc->pnode;
		node  && 
		node->sockAddr.sin_addr.s_addr != sockAddr.sin_addr.s_addr;
		node = (ETHERPSCNODE*) node->pnode ) { };
	    if ( node ) 
	    {
					/* count response */
		node->record[SIGNAL_BITBUS_RSP_CNT].val.li++;

		node->unanswered = 0;
	
		if ( ! node->present )
		{			/* PSC became available */
					/* trigger reading static info */
		    node->record[SIGNAL_CHASSIS_TYPE].set = 1;
		    node->record[SIGNAL_XDUCT1_V2I].set = 1;
		    node->present = 1;
		}

					/* process PSC response */
					/* skip command byte as we now have */
					/* the returned command byte */
		process_etherpsc_rsp( node, &rsp[2], n-2 );
	
	    }
	}
    }
    return(OK);
}


static void process_record_ai( ETHERPSCNODE *node, int signal, float f )
{
    PSCRECORD           *PSCRecord;
    aiRecord            *pai;
    struct rset         *prset;

    PSCRecord = &node->record[signal];
    if ( ! (pai = (aiRecord*) PSCRecord->precord) ) return;

    if ( pai->val != f  ||  pai->udf  ||  PSCRecord->nsta )
    {
        PSCRecord->nsta = NO_ALARM;
        PSCRecord->val.ai = f;
        prset = (struct rset *) pai->rset;

        dbScanLock( (struct dbCommon*) pai );
        pai->pact = TRUE;
        (*prset->process)( pai );
        dbScanUnlock( (struct dbCommon*) pai );
    }
}

static void process_record_ao( ETHERPSCNODE *node, int signal, float f )
{
    PSCRECORD           *PSCRecord;
    aoRecord            *pao;
    struct rset         *prset;

    PSCRecord = &node->record[signal];
    if ( ! (pao = (aoRecord*) PSCRecord->precord) ) return;

    prset = (struct rset *) pao->rset;

    dbScanLock( (struct dbCommon*) pao );
    pao->val = f;
    (*prset->process)( pao );
    dbScanUnlock( (struct dbCommon*) pao );
}

static void process_record_bi( ETHERPSCNODE *node, int signal, int i )
{
    PSCRECORD           *PSCRecord;
    biRecord            *pbi;
    struct rset         *prset;

    PSCRecord = &node->record[signal];
    if ( ! (pbi = (biRecord*) PSCRecord->precord) ) return;

    if ( pbi->rval != i  ||  pbi->udf  ||  PSCRecord->nsta )
    {
        PSCRecord->nsta = NO_ALARM;
        PSCRecord->val.bi = i;
        prset = (struct rset *) pbi->rset;

        dbScanLock( (struct dbCommon*) pbi );
        pbi->pact = TRUE;
        (*prset->process)( pbi );
        dbScanUnlock( (struct dbCommon*) pbi );
    }
}

static void process_record_li( ETHERPSCNODE *node, int signal, long i )
{
    PSCRECORD           *PSCRecord;
    struct longinRecord *pli;
    struct rset         *prset;

    PSCRecord = &node->record[signal];
    if ( ! (pli = (struct longinRecord*) PSCRecord->precord) ) return;

    if ( pli->val != i  ||  pli->udf  ||  PSCRecord->nsta )
    {
        PSCRecord->nsta = NO_ALARM;
        PSCRecord->val.li = i;
        prset = (struct rset *) pli->rset;

        dbScanLock( (struct dbCommon*) pli );
        //printf("signal=%d, i=%d, pact=%d...\n", signal, i, pli->pact);
        pli->pact = TRUE;
        (*prset->process)( pli );
        //printf("Processed li for signal %d...\n", signal);
        dbScanUnlock( (struct dbCommon*) pli );
    }
}

static void process_record_si( ETHERPSCNODE *node, int signal, unsigned char *s,
                                                int l )
{
    PSCRECORD           *PSCRecord;
    struct stringinRecord       *psi;
    struct rset         *prset;

    PSCRecord = &node->record[signal];
    if ( ! (psi = (struct stringinRecord*) PSCRecord->precord) ) return;

    PSCRecord->nsta = NO_ALARM;
    if ( l > sizeof(PSCRecord->val.si) ) l = sizeof(PSCRecord->val.si);
    strncpy( PSCRecord->val.si, (const char*) s, l );
    if ( l < sizeof(PSCRecord->val.si) ) PSCRecord->val.si[l] = '\0';
    prset = (struct rset *) psi->rset;

    dbScanLock( (struct dbCommon*) psi );
    psi->pact = TRUE;
    (*prset->process)( psi );
    dbScanUnlock( (struct dbCommon*) psi );
}

static void process_status1 ( ETHERPSCNODE *node, unsigned char *rsp )
{
    int         i;

    i = ( rsp[2] & 0x04 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_ON_STATUS, i );
    if ( i == 0 ) node->record[SIGNAL_CURRENT_AC].val.ao.last = 0.0;

    i = ( rsp[2] & 0x08 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_RAMPING_STATUS, i );
    if (i) node->rampwait = 0; 

    i = ( rsp[2] & 0x80 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_LOCAL_MODE, i );

    i = ( rsp[3] & 0x02 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_HW_STATUS, i );

    i = ( rsp[3] & 0x04 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_CAL_STATUS, i );

    i = ( rsp[3] & 0x08 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_XDUCT2_READY, i );

    if ( rsp[0] == BITBUSCMD_FAST_STATUS  &&
         rsp[3] & 0x01 )                        /* error message available */
    {
        node->record[SIGNAL_INFO_MSG].set = 1;
    }

}

static void process_status3 ( ETHERPSCNODE *node, unsigned char *rsp )
{
    int         i;

    i = ( rsp[4] & 0x01 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_INTERLOCK0, i );

    i = ( rsp[4] & 0x02 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_INTERLOCK1, i );

    i = ( rsp[4] & 0x04 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_INTERLOCK2, i );

    i = ( rsp[4] & 0x08 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_INTERLOCK3, i );

    i = ( rsp[4] & 0x10 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_READY_STATUS, i );

    i = ( rsp[4] & 0x20 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_XDUCT1_STATUS, i );

    i = ( rsp[4] & 0x40 ) ? 0 : 1;
    process_record_bi( node, SIGNAL_GND_CURRENT_FAULT, i);

    i = ( rsp[5] & 0x01 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_FAULT_LATCH_STATUS, i );

    i = ( rsp[5] & 0x02 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_PS_STATUS0, i );

    i = ( rsp[5] & 0x04 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_PS_STATUS1, i );

    i = ( rsp[5] & 0x08 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_PS_STATUS2, i );

    i = ( rsp[5] & 0x10 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_PS_STATUS3, i );

    i = ( rsp[5] & 0x20 ) ? 1 : 0;
    process_record_bi( node, SIGNAL_PS_ON_STATUS, i );
}

static void process_etherpsc_rsp ( ETHERPSCNODE *node, unsigned char *rsp, long n )
{
    int         i;

    node->busy = 0;

    switch ( rsp[0] )
    {
        case BITBUSRSP_PS_STATUS :
        case BITBUSCMD_SHORT_STATUS :
        case BITBUSCMD_FAST_STATUS :

            if ( n < 4  ||  n > 8 ) return;
            if ( ! ( rsp[2] & 0x01 ) ) return;
#if  DEBUG
            printf( "STATUS_RDBK: 0x%02x, 0x%02x, %.3f\n",
                        rsp[2], rsp[3], b2f(&rsp[4]) );
#endif

            process_status1 ( node, rsp );

            if ( n < 8 ) return;

            process_record_ai( node, SIGNAL_CURRENT, b2f(&rsp[4]) );

        break;

        case BITBUSCMD_ANALOG_RDBK :

            if ( n < 34 ) return;
#if  DEBUG
            printf(
        "ANALOG_RDBK: %.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f\n",
                        b2f(&rsp[2]), b2f(&rsp[6]), b2f(&rsp[10]),
                        b2f(&rsp[14]), b2f(&rsp[18]), b2f(&rsp[22]),
                        b2f(&rsp[26]), b2f(&rsp[30]) );

#endif

            process_record_ai( node, SIGNAL_XDUCT1_CURRENT, b2f(&rsp[2]) );
            process_record_ai( node, SIGNAL_XDUCT2_CURRENT, b2f(&rsp[6]) );
            process_record_ai( node, SIGNAL_DAC_CURRENT, b2f(&rsp[10]) );
            process_record_ai( node, SIGNAL_RIPPLE_CURRENT, b2f(&rsp[14]) );
            process_record_ai( node, SIGNAL_GND_CURRENT, b2f(&rsp[18]) );
            process_record_ai( node, SIGNAL_CNTL_TEMPERATURE,  b2f(&rsp[22]) );
            process_record_ai( node, SIGNAL_OUTPUT_VOLTAGE, b2f(&rsp[26]) );
            process_record_ai( node, SIGNAL_SPARE_VOLTAGE, b2f(&rsp[30]) );

        break;

        case BITBUSCMD_DIAG_RDBK :

            if ( n < 31 ) return;
#if  DEBUG
            printf(
        "DIAG_RDBK: 0x%02x, 0x%02x, 0x%02x, 0x%02x, %d\n",
                        rsp[2], rsp[3], rsp[4], rsp[5], rsp[6] );
                printf(
        "           %.3f, %.3f, %.3f\n",
                        b2f(&rsp[7]), b2f(&rsp[11]), b2f(&rsp[15]) );
                printf(
        "           %05ld, %05ld, %05ld, %05ld, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
                        b2l(&rsp[19]), b2l(&rsp[21]), b2l(&rsp[23]),
                        b2l(&rsp[25]),
                        rsp[27], rsp[28], rsp[29], rsp[30] );
#endif

            process_status1 ( node, rsp );
            process_status3 ( node, rsp );
            process_record_ai( node, SIGNAL_CURRENT_READBACK, b2f(&rsp[7]) );
            process_record_li( node, SIGNAL_ADC_OFFSET, b2l(&rsp[19]) );
            process_record_li( node, SIGNAL_ADC_GAIN, b2l(&rsp[21]) );
            process_record_li( node, SIGNAL_DAC_OFFSET, b2l(&rsp[23]) );
            process_record_li( node, SIGNAL_DAC_GAIN, b2l(&rsp[25]) );
            process_record_li( node, SIGNAL_LAST_RESET_CODE, rsp[27] );
            process_record_li( node, SIGNAL_LAST_TURNOFF_CODE, rsp[28] );
            process_record_li( node, SIGNAL_CAL_ERROR_CODE, rsp[29] );
            process_record_li( node, SIGNAL_SELF_TEST_CODE, rsp[30] );

        break;

        case BITBUSCMD_CNTL_INFO :

            if ( n < 26 ) return;
#if  DEBUG
            printf( "CNTL_INFO: %d, %.8s, %.8s, %.8s\n",
                        rsp[2], &rsp[3], &rsp[11], &rsp[19] );
#endif
            node->record[SIGNAL_CHASSIS_TYPE].set = 0;	/* ack, done */

            process_record_li( node, SIGNAL_CHASSIS_TYPE, (int) rsp[2] );
            process_record_si( node, SIGNAL_SERIAL_NUMBER, &rsp[3], 8 );
            process_record_si( node, SIGNAL_FIRMWARE_VERSION, &rsp[11], 8 );
            process_record_si( node, SIGNAL_MAGNET_ID, &rsp[19], 8 );

        break;

        case BITBUSCMD_CAL_DATA :

            if ( n < 29 ) return;

#if  DEBUG
            printf( "CAL_DATA: %.3f, %.3f, %.3f, %.3f, %.3f, %.8s\n",
                        b2f(&rsp[2]), b2f(&rsp[6]), b2f(&rsp[10]),
                        b2f(&rsp[14]), b2f(&rsp[18]), &rsp[22] );
#endif
            node->record[SIGNAL_XDUCT1_V2I].set = 0;	/* ack, done */

            process_record_ai( node, SIGNAL_XDUCT1_V2I, b2f(&rsp[2]) );
            process_record_ai( node, SIGNAL_XDUCT2_V2I, b2f(&rsp[6]) );
            process_record_ai( node, SIGNAL_GND_CURRENT_V2I, b2f(&rsp[10]) );
            process_record_ai( node, SIGNAL_PS_VOLTAGE_V2V, b2f(&rsp[14]) );
            process_record_ai( node, SIGNAL_VREF, b2f(&rsp[18]) );
            process_record_si( node, SIGNAL_CAL_DATE, &rsp[22], 8 );

        break;

        case BITBUSCMD_DES_I_RDBK :

            if ( n < 9 ) return;

#if  DEBUG
            printf( "DES_I_RDBK: 0x%02x, 0x%02x, %.3f, %ld\n",
                        rsp[2], rsp[3], b2f(&rsp[4]), b2l(&rsp[8]) );
#endif
            process_status1( node, rsp );
            node->record[SIGNAL_CURRENT_AC].val.ao.last = b2f(&rsp[4]);
            process_record_ao( node, SIGNAL_CURRENT_AC, b2f(&rsp[4]) );

        break;

        case BITBUSCMD_INFO_MSG :

            if ( n < 3 ) return;
            if ( n > 34 ) return;

#if  DEBUG
            printf( "INFO_MSG: %.32s\n", &rsp[2] );
#endif

            process_record_si( node, SIGNAL_INFO_MSG, &rsp[2], n-2 );

        break;

	case BITBUSCMD_SET_I :

			/* if desired value has not been changed in between */
	    if ( node->record[SIGNAL_CURRENT_AC].set == 2 )
        	node->record[SIGNAL_CURRENT_AC].set = 0;    /* ack, done */
				/* otherwise output thread will try again */

            process_status1( node, rsp );

	break;

	case BITBUSCMD_PS_ON :
	case BITBUSCMD_PS_OFF :

	    if ( n < 4 ) return;

                        /* if desired state has not been changed in between */
            if ( node->record[SIGNAL_PS_ON_OFF].set == 2 )
                node->record[SIGNAL_PS_ON_OFF].set = 0;    /* ack, done */
                                /* otherwise output thread will try again */

	    process_status1( node, rsp );
 
	break;
 
	case BITBUSCMD_INT_RESET :

            node->record[SIGNAL_INT_RESET].set = 0;    /* ack, done */
 
            process_status1( node, rsp );

	break;

        default :
                printf("rsp:");
                for ( i=0; i<n; i++ ) {
                    printf(" %02x", rsp[i] );
                }
                printf("\n");
        break;
    }
}


/*******************************************************************************
* EPICS iocsh Command registry
*/

/* Ether_io_report ( int level ) */
static const iocshArg Ether_io_reportArg0 = {"level", iocshArgInt};
static const iocshArg * const Ether_io_reportArgs[1] = {&Ether_io_reportArg0};
static const iocshFuncDef Ether_io_reportFuncDef =
    {"Ether_io_report",1,Ether_io_reportArgs};
static void Ether_io_reportCallFunc(const iocshArgBuf *args)
{
    Ether_io_report(args[0].ival);
}

/* drvEtherPSCConfig( unsigned long *base_addr, short irq, short link ) */
static const iocshArg drvEtherPSCConfigArg0 = {"ip_addr", iocshArgString};
static const iocshArg drvEtherPSCConfigArg1 = {"link", iocshArgInt};
static const iocshArg * const drvEtherPSCConfigArgs[2] = {
    &drvEtherPSCConfigArg0, &drvEtherPSCConfigArg1};
static const iocshFuncDef drvEtherPSCConfigFuncDef =
    {"drvEtherPSCConfig",2,drvEtherPSCConfigArgs};
/* static void drvEtherPSCConfigCallFunc(const iocshArgBuf *arg)
{
    drvEtherPSCConfig(arg[0].sval, arg[1].ival);
} */

LOCAL void drvEtherPSCRegistrar(void) {
    iocshRegister(&Ether_io_reportFuncDef,Ether_io_reportCallFunc);
/*    iocshRegister(&drvEtherPSCConfigFuncDef,drvEtherPSCConfigCallFunc); */
}
epicsExportRegistrar(drvEtherPSCRegistrar);

Replies:
Re: Record processing twice after upgrading to Base 3.15.5 Andrew Johnson
References:
Record processing twice after upgrading to Base 3.15.5 Dunning, Michael
Re: Record processing twice after upgrading to Base 3.15.5 Andrew Johnson

Navigate by Date:
Prev: Re: CA gatway runs away when zero length PV name in UDP search request Anton Derbenev
Next: Re: Record processing twice after upgrading to Base 3.15.5 Andrew Johnson
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: Record processing twice after upgrading to Base 3.15.5 Andrew Johnson
Next: Re: Record processing twice after upgrading to Base 3.15.5 Andrew Johnson
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  <20172018  2019  2020  2021  2022  2023  2024