/*
 *  Library for Allen-Bradley #6008-SV VMEbus I/O Scanner
 *	based on examples given in the Allen-Bradley users manual
 *	with kludges added as needed.
 *
 */

/*
 *      Author: Bill Brown
 *      Date:   as noted in change history
 *
 *      Copyright 1992, 1993, the Regents of the University of California.
 *
 *      This software was produced under  U.S. Government contract:
 *      (DE-AC03-76SF00098) at the Lawrence Berkeley Laboratory.
 *
 *
 */

#ifndef lint
static char rcsid[] =
    "@(#) $Header: vmeIOScannerLib.c,v 3.7 93/05/06 13:06:02 wbrown Exp $(LBL)";
#endif

/*
    @(#) $Log:	vmeIOScannerLib.c,v $
 * Revision 3.7  93/05/06  13:06:02  wbrown
 * added copyright notice
 * 
 * Revision 3.6  92/07/09  14:06:48  wbrown
 * Moved to new environment
 * added call to sysIntEnable()
 * renamed probe
 * tested on mv167
 * 
 * Revision 3.5  92/04/07  14:21:48  wbrown
 * added adc support, digital I/O
 *  getAbPlcbit() setAbPlcBit() plcIrqDaemon() plcQueueBTMsg()
 *  createAb1771_IFE_dev() initAb1771_IFE_dev() readAb1771Ife()
 *  adcReader() addAdcToReadList()
 * 
 * Revision 3.4  92/03/04  13:56:46  wbrown
 * routine checkin during developement
 *   everything up except "block transfers" (startup, echoBits, etc)
 *   seems to be working.
 * 
 * Revision 3.3  92/02/24  10:29:54  wbrown
 * routine checkin during development.
 * 
 * 
 * Revision 3.2  92/02/03  13:30:46  wbrown
 * preliminary testing of #1771-IFE adc module,
 *   misc. clean-up.
 * 
 * Revision 3.1  92/01/15  09:52:48  wbrown
 * twidged various things to compile in new ($aip_3)
 *   environment
 * 
 * Revision 3.0  91/12/19  16:44:06  bordua
 * Baseline for new aip project.
 * 
 * Revision 1.4  91/12/12  10:42:34  wbrown
 * added features to deal with SYSFAIL asserted
 * at "power-on" time.
 * 
 * 
 * Revision 1.3  91/10/23  13:50:30  wbrown
 * replaced calls to logMsg() with utility library calls
 *   except at interrupt level.
 * 
 * Revision 1.2  91/10/15  09:02:43  wbrown
 * first cut at "beta" version
 * 
 * Revision 1.1  91/10/11  11:05:18  wbrown
 * Initial revision
 * 
 * 
 */

#include "vxWorks.h"
#include "intLib.h"
#include "68k/iv.h"
#include "msgQLib.h"
#include "sysLib.h"
#include "taskLib.h"
#include "types.h"
#include "vme.h"
#include "wdLib.h"

#include "utility_lib.h"
#define DEBUG 1
#undef DEBUG

#define PLC_SCANNER_DRVR 1

#include "vmeIOScannerLib.h"
#include "ab_1771_ifeLib.h"

PLC_SCANNER_LINK plcScnrLinkTbl[ MAX_NR_PLC_SCANNERS ];

MSG_Q_ID plcBTMsgQ = NULL;

VOID
plcIrqService( plcIrqId )
/*
 * "give" specified semaphore in response to interrupt
 *
 */
int plcIrqId;
    {
    LINK_ID_FORM linkIdForm;
    STATUS status;
#ifdef DEBUG
    logMsg("+++ Interrupt received, SEM_ID = 0x%x\n", plcIrqId);
#endif
    linkIdForm.linkIdInt = plcIrqId;
    {
    if ( plcBTMsgQ != NULL )
	{
	if ( (status = ( msgQSend( plcBTMsgQ, linkIdForm.linkIdChar,
		4, NO_WAIT ) )) != OK ) 
	    {
	    logMsg("### msgQSend() returned ERROR\n");
	    }
	}
    else
	logMsg("### Interrupt received but \"plcBTMsgQ\" = NULL.\n");
    }
    return;
    }

PLC_SCANNER_LINK
( *vmePlcScannerLinkCreate( scannerAddress, scannerAM ) )
/*
 *
 *  build and initialize vme plc scanner connection
 *	returns pointer to link description structure
 *	or NULL.  If pointer is returned, check status in
 *	descriptor structure for status.
 */
char *scannerAddress;	/* address of scanner 2-port menory. 		*/
char *scannerAM; 	/* address modifier for scanner address 	*/
    {
    u_char tVec;
    long i, index = -1;
    long waitCounter;
    PLC_SCANNER_DUAL_PORT *scanBusAdrs;
    STATUS status;
    static SEM_ID plcLinkCreatBusy;
    VOID plcScannerReset();
    static int daemonTID;

    extern VOID plcIrqDaemon();

#ifdef DEBUG_DUMP
    extern VOID d();
#endif#
    
    DP_MAILBOX setUpImage =
	{
	0,		/* 102-03, confirmation status	 */
	SET_UP,		/* 104-05, command		 */
	0,		/* 106-07, mod. adrs. (unused)	 */
	0,0,0,0,	/* 108-09, bt_tag (unused)	 */
	0,		/* 10A-0B,  RESERVED 		 */
	0,		/* 10C-0D,	 "		 */
	0,		/* 10E-0F,	 "		 */
	0,		/* 110-11,	 "		 */
	0,		/* 112-13,	 "		 */
	0,		/* 114-15,	 "		 */
	0,		/* 116-17,	 "		 */
	0,		/* 118-19,	 "		 */
	0,		/* 11A-1B,	 "		 */
	(1 << 7),	/* 11C-1D, semaphore		 */
	0,		/* 11E-1F, data length (unused)	 */
#ifdef DO_IT_RIGHT
	(HI_BAUD_RATE << 8),		/* baud rate, watchdog		 */
	(PLC_DBG_ON << 8) + DEF_IRQ_LVL,	/* debug mode, irq level */
#else
	(u_short)0x200, /* 120-21, baud rate; watchdog variable	 */
	(u_short)0x101, /* 122-23, debug mode, irq level		 */
#endif
	0,		/* 124-25, interrupt vector goes here		 */
	IGNORE_SYSFAIL, /* 126-27, don't wanna die on SYSFAIL		 */
	};

    /* set up interlock first time thru		 */
    status = taskLock();
    if ( plcLinkCreatBusy == NULL )
	{
	if ( (plcLinkCreatBusy = semBCreate( SEM_Q_PRIORITY, SEM_FULL )) == NULL )
	    {
	    ul_fatal("Attempt to create \"plcLinkCreatBusy\" failed.");
	    status = taskUnlock();
	    return( (PLC_SCANNER_LINK *)ERROR );
	    }
	else
	    {
	    ul_debug("\"plcLinkCreatBusy\" created., SEM_ID = 0x%x",
		plcLinkCreatBusy);
	    }
	}
    status = taskUnlock();

    if ( status = (semTake( plcLinkCreatBusy,
	MAX_PLC_SCANNER_BUSY_WAIT * sysClkRateGet() )) == ERROR )
	{
	ul_warn("\"(semTake( plcLinkCreatBusy )\" time out.");
	return( NULL );
	}

    /* spawn interrupt daemon if not already running		*/
    if ( daemonTID != 0 )
	{
	if ( taskIsSuspended( daemonTID ) )
	    {
	    status = taskDelete( daemonTID );
	    }
	}

    if  ( (daemonTID = taskNameToId( "plcIrqDaemon" )) == ERROR )
	{
	if ( (status = taskSpawn( "plcIrqDaemon", 60,
	    VX_SUPERVISOR_MODE + VX_DEALLOC_STACK + VX_STDIO,
		0x10000, plcIrqDaemon )) == ERROR )
	    {
	    ul_fatal("attempt to spawn \"plcIrqDaemon\" failed, status = 0x%x.",
		status);
	    }
	}

    /* calculate bus address and check if scanner is initialized 	*/
    if ( (status = sysBusToLocalAdrs( scannerAM, scannerAddress,
        &scanBusAdrs )) == ERROR )
	{
	return( (PLC_SCANNER_LINK *)ERROR );
	}

    for ( i = 0; i < MAX_NR_PLC_SCANNERS; i++ )
	{
	if ( ( plcScnrLinkTbl[i].plcScanner2Port == scanBusAdrs )
	     || ( plcScnrLinkTbl[i].plcScanner2Port == NULL ) )
	    {
	    index = i;
	    break;
	    }
	}

    if ( index == MAX_NR_PLC_SCANNERS )
	{
	ul_critical("\"scanner logical descriptor\" table full.");
	semGive(plcLinkCreatBusy);
	return( NULL );
	}

    if ( plcScnrLinkTbl[index].plcScanner2Port == scanBusAdrs )
	{	/* entry found - re-initialize the hardware	*/
	ul_critical("Scanner at 0x%x is already init.  Reset & Re-initializing",
	    scannerAddress);
	ul_debug("\"index\" = %d", index);
	}
    else
	{	/* entry not found - configure table		*/
	plcScnrLinkTbl[index].plcScanner2Port = scanBusAdrs;
		/* table entry has been allocated	 	*/
	ul_debug("  index = %d, plcScanner2Port = 0x%x", index,
	    plcScnrLinkTbl[i].plcScanner2Port);

	/* initialize allocation table 	*/
	plcScnrLinkTbl[index].plcResponse
	    = semCCreate( SEM_Q_PRIORITY, SEM_EMPTY );
	ul_debug("\"semBCreate\" for  \"plcScannerStatus\" returned 0x%x");
	if ( plcScnrLinkTbl[index].plcResponse == NULL )
	    {
	    plcScnrLinkTbl[index].plcScannerStatus = PLC_SEMCREATE_FAIL;
	    semGive(plcLinkCreatBusy);
	    ul_critical("\"semBCreate()\" for \"plcScannerStatus\" failed.");
	    return( &plcScnrLinkTbl[index] );
	    }
	else
	    {
	    ul_debug("plcResponse semaphore created = 0x%x",
		plcScnrLinkTbl[index].plcResponse);
	    }
	/*  allocate interrupt vector			*/

	if ( ( setUpImage.data_area[2] = iVecAlloc( plcIrqService,
	    (int)&plcScnrLinkTbl[index]) ) == NULL )
	    {

	    ul_critical("Allocation of interrupt vector failed.");
	    plcScnrLinkTbl[index].plcScannerStatus = PLC_IVECALLOC_FAIL;
	    semGive(plcLinkCreatBusy);
	    return( &plcScnrLinkTbl[index] );
	    }
	else
	    {
	    /* put vector in EVEN byte */
	    setUpImage.data_area[2] = setUpImage.data_area[2] << 8;
	    }
	ul_debug("plcScnrLinkTbl entry completed.");
	}

    while ( sysBusTas( scanBusAdrs->mailBox.fl_lock ) != OK )
	{
	if ( waitCounter++ >= (MAX_PLC_SCANNER_BUSY_WAIT * sysClkRateGet()) )
	    {
	    ul_critical("\"vmePlcScannerLinkCreate()\". PLC interface busy.");
	    return( (PLC_SCANNER_LINK *)ERROR );
	    }
	else
	    {
	    taskDelay( 2 );
	    }
	}

    if ( scanBusAdrs->mailBox.conf_stat != SCAN_STAT_SCANNER_PWR_UP )
	/* issue a reset to the scanner */
	{
	plcScannerReset( &(plcScnrLinkTbl[index]) );
	taskDelay( sysClkRateGet() );
	}

    /* Initialize the Scanner 	*/
    semGive(plcLinkCreatBusy);

    i = 0;
    while ( scanBusAdrs->mailBox.conf_stat != SCAN_STAT_SCANNER_PWR_UP )
	{
	if ( i++ >= MAX_PLC_SCANNER_BUSY_WAIT )
	    {
	    ul_critical("Scanner not responding to RESET.");
	    plcScnrLinkTbl[index].plcScannerStatus = PLC_RESET_FAIL;
	    return( NULL );
	    }
	taskDelay( sysClkRateGet() );
	}
    ul_debug("Interrupting Scanner for \"wakeup\".");
    scanBusAdrs->mailBox.interruptScanner = 0;
#ifdef DEBUG_DUMP
    d( &setUpImage, 32 );
#endif
    if ( (status = sysIntEnable( setUpImage.data_area[ 1 ] & 0x7 ))
	!= OK )
	    ul_fatal("Attempt to enable interrupt level failed.");
    status = sndCmndAndWait4Done( &plcScnrLinkTbl[index], &setUpImage );
#ifdef DEBUG_DUMP
    d( &setUpImage, 32 );
#endif
    if ( status == OK )
	{
	return( &plcScnrLinkTbl[index] );
	}
    else
	{
	return( NULL );
	}
    }

VOID
plcScannerReset( linkId )
/*
 *
 *  issue a RESET to the scanner
 */
PLC_SCANNER_LINK *linkId;
    {
    PLC_SCANNER_DUAL_PORT *hwPtr = linkId->plcScanner2Port;

    hwPtr->mailBox.data_area[RESET_PREP_ADRS] = RESET_PREP;
    hwPtr->mailBox.data_area[RESET_XEQ_ADRS] = RESET_XEQ;
    return;
    }

STATUS
plcAutoConfig( linkId, configData )
/*
 *
 * autoconfiguer the plc scanner
 */
PLC_SCANNER_LINK *linkId;
DATA_AREA *configData;
    {
    long j;
    PLC_SCANNER_DUAL_PORT *hw2Port;
    STATUS status;
    DP_MAILBOX autoConfigImage =
	{
	0,		/* 102-03, confirmation status */
	AUTOCONFIGURE,	/* 104-05, command		 */
	0,		/* 106-07,			 */
	0,0,0,0,	/* 108-09, bt_tag (unused)	 */
	0,		/* 10A-0B,  RESERVED 		 */
	0,		/* 10C-0D,	 "		 */
	0,		/* 10E-0F,	 "		 */
	0,		/* 110-11,	 "		 */
	0,		/* 112-13,	 "		 */
	0,		/* 114-15,	 "		 */
	0,		/* 116-17,	 "		 */
	0,		/* 118-19,	 "		 */
	0,		/* 11A-1B,	 "		 */
	0x80		/* semaphore			 */
	};

    hw2Port = linkId->plcScanner2Port;
    if ( (status = sndCmndAndWait4Done( linkId, &autoConfigImage )) == OK)
	{
	if ( configData != NULL )
	    {
	    /* copy configuration to application's buffer */
	    /* for (j = 0; j < sizeof( *configData )/2; j++ ) */
	    for (j = 0; j < 64; j++ )
		{
		configData->all[ j ] = hw2Port->mailBox.data_area[ j ];
		}
	    }
	return( OK );
	}
    else
	{
	return( status );
	}
    }

STATUS
plcEnableOutput( linkId )
/*
 *
 * enable plc scanner outputs
 */
PLC_SCANNER_LINK *linkId;
    {
    STATUS status;
    DP_MAILBOX plcOutputsEnableImage =
	{
	0,		/* 102-03, confirmation status	 */
	SET_MODE,	/* 104-05, command		 */
	0,		/* 106-07,			 */
	0,0,0,0,	/* 108-09, bt_tag (unused)	 */
	0,		/* 10A-0B,  RESERVED 		 */
	0,		/* 10C-0D,	 "		 */
	0,		/* 10E-0F,	 "		 */
	0,		/* 110-11,	 "		 */
	0,		/* 112-13,	 "		 */
	0,		/* 114-15,	 "		 */
	0,		/* 116-17,	 "		 */
	0,		/* 118-19,	 "		 */
	0,		/* 11A-1B,	 "		 */
	0x80,		/* 11C-1D,	semaphore	 */
	0,		/* 11E-1F,			 */
	MODE_RUN << 8	/* 120-21,  mode		 */
	};

    if ( (status = ( sndCmndAndWait4Done( linkId, &plcOutputsEnableImage ) ))
        == OK)
	    linkId->plcScannerState = PLC_STATE_RUN_MODE;
    return( status );
    }

/*************************************************************************/

/*  Single-bit DIGITAL I/O functions					 */

/*
    Allen-Bradley uses the following bit-addressing convention:
	16 BITS per GROUP (numbered 0-15 decimal, 0-17 octal, 1-f hex)
	 8 GROUPs per RACK (numbered 0-7)
	 8 RACKS per SCANNER (numbered 0-7)
    
    We will identify bits by SCANNERID, which will point to the
	particular scanner, and by BIT_NUMBER which is calculated
	as BIT_# * GROUP_# * RACK_#.
 */

int
setAbPlcBit( linkId, bitNr, bitState)
/*
 *
 * set specified output bit to specified state
 *  returns:
 *	1  = bit set TRUE
 *	0  = bit set FALSE
 *	-1 = ERROR
 */
    PLC_SCANNER_LINK *linkId;	/* points to allocation table entry      */
    long bitNr, bitState;
    {
    int reply = -1;
    int shiftCount, offset;
    PLC_SCANNER_DUAL_PORT *hwPtr;

    if ( (hwPtr = linkId->plcScanner2Port) != NULL )
	{
	offset = bitNr >> 4;
	if ( offset < 64 )
	    {
	    shiftCount = bitNr & 0xf;
	    switch ( bitState )
		{
		case 0:
		    {
		    hwPtr->g_oit[ offset] &= ~( 1 << shiftCount );
		    reply = 0;
		    break;
		    }
		case 1:
		    {
		    hwPtr->g_oit[ offset] |= ( 1 << shiftCount );
		    reply = 1;
		    break;
		    }
		}
	    }
	}
    return( reply );
    }

int
getAbPlcbit( linkId, bitNr )
/*
 *
 *  get state of specified input bit
 *  returns:
 *	1  = bit was TRUE
 *	0  = bit was FALSE
 *	-1 = ERROR
 */
    PLC_SCANNER_LINK *linkId;	/* points to allocation table entry      */
    long bitNr;
    {
    int reply = -1;
    int shiftCount, offset;
    PLC_SCANNER_DUAL_PORT *hwPtr;

    if ( (hwPtr = linkId->plcScanner2Port) != NULL )
	{
	offset = bitNr >> 4;
	if ( offset < 64 )
	    {
	    shiftCount = bitNr & 0xf;
	    reply = ( ( hwPtr->g_iit[ offset] ) >> shiftCount ) & 0x01;
	    }
	}
    return( reply );
    }

/*************************************************************************/

STATUS
sndCmnd( linkId, cmndImagePtr )
/*
 *
 *  send specified command to plc scanner
 */
PLC_SCANNER_LINK *linkId;	/* points to allocation table entry	 */
DP_MAILBOX *cmndImagePtr;	/* points to command image	 */
    {
    PLC_SCANNER_DUAL_PORT *hwPtr;
    STATUS status;
    long j;
    u_short waitCounter = 0;

    ul_debug("\"sndCmnd( 0x%x, 0x%x )\" call received.",
	linkId, cmndImagePtr);
    if ( linkId == NULL )
	{
	/* NULL implies link not established 	*/
	return( ERROR );
	}
    else
	{
	hwPtr = linkId->plcScanner2Port;
	}

    /* in any case, we need to wait until scanner is available 	*/
    while ( sysBusTas( hwPtr->mailBox.fl_lock ) != OK )
	{
	if ( waitCounter++ >= (MAX_PLC_SCANNER_BUSY_WAIT * sysClkRateGet()) )
	    {
	    linkId->plcScannerStatus = PLC_INTERFACE_BUSY;
	    ul_critical("PLC interface busy.");
	    return( ERROR );
	    }
	else
	    {
	    taskDelay( 2 );
	    }
	}
    switch( (int)(cmndImagePtr->command) )
    /* save time by handling special cases explicitly	 */
	{
	case SET_UP:
	    ul_debug(" \"SET_UP\" command recognized.");
	    hwPtr->mailBox.command = cmndImagePtr->command;
	    for ( j = 0; j < 4; j++ )
		{
	        hwPtr->mailBox.data_area[j] = cmndImagePtr->data_area[j];
		}
	    break;
	case AUTOCONFIGURE:
	    ul_debug(" \"AUTOCONFIGURE\" command recognized.");
	    hwPtr->mailBox.command = cmndImagePtr->command;
	    break;
	case SET_MODE:
	    ul_debug(" \"SET_MODE\" command recognized.");
	    hwPtr->mailBox.command = cmndImagePtr->command;
	    hwPtr->mailBox.data_area[0] = cmndImagePtr->data_area[0];
	    break;
	default :
	    ul_debug(" \"default\" command recognized.");
	    hwPtr->mailBox.command = cmndImagePtr->command;
	    hwPtr->mailBox.address = cmndImagePtr->address;
	    hwPtr->mailBox.bt_tag = cmndImagePtr->bt_tag;
	    hwPtr->mailBox.data_len = cmndImagePtr->data_len;
	    if ( cmndImagePtr->data_len != 0 )
		{
		for ( j = 0; j < cmndImagePtr->data_len; j++ )
		    {
		    hwPtr->mailBox.data_area[j] = cmndImagePtr->data_area[j];
		    }
		}
	}
    ul_debug("Interrupting scanner for \"COMMAND\" = 0x%x.",
	cmndImagePtr->command);
    hwPtr->mailBox.interruptScanner = 0;	/* interrupt controller 	*/
    return( OK );
    }

STATUS
sndCmndAndWait4Done( linkId, cmndImagePtr )
/*
 *
 *  send specified command to plc scanner and await reply.
 */
PLC_SCANNER_LINK *linkId;	/* points to allocation table entry	 */
DP_MAILBOX *cmndImagePtr;	/* points to command image	 */
    {
    STATUS status;
    PLC_SCANNER_DUAL_PORT *hwPtr;

    ul_debug("\"sndCmndAndWait4Done( 0x%x, 0x%x )\" call received.",
	linkId, cmndImagePtr);
    if ( linkId == NULL )
	{
	/* NULL implies link not established 	*/
	return( ERROR );
	}
    else
	{
	hwPtr = linkId->plcScanner2Port;
	}

    if( (status = sndCmnd( linkId, cmndImagePtr )) != OK)
	ul_fatal("\"sndCmnd( linkId, cmndImagePtr )\" did not return OK");

    if ( (status = semTake( linkId->plcResponse,
	(MAX_PLC_SCANNER_BUSY_WAIT * sysClkRateGet()) )) != OK )
	{
	ul_critical("PLC Timeout waiting for End of Op, \"linkId\" = 0x%x.",
	    linkId);
	linkId->plcScannerStatus = PLC_NO_REPLY;
	return( ERROR );
	}
    switch( linkId->plcConfStatus )
        {
	case CONF_STATUS_OK:
	    linkId->plcScannerStatus = PLC_OK;
	    ul_debug("Daemon returned \"PLC_DONE\" semaphore.");
	    return( OK );
	    break;
	default:
	    linkId->plcScannerStatus = PLC_ERROR;
	    ul_debug("PLC returned \"ERROR = %x\" status.",
		hwPtr->mailBox.conf_stat);
	    return( ERROR );
	}
    }

PLC_CONFIG_BUFFER configInfo;

PLC_SCANNER_LINK
( *startPLC( scannerAddress, scannerAM ) )
/*
 *  start up and enable the plc controller with a generic configuration
 *
 */
char *scannerAddress;	/* address of scanner 2-port menory. 		*/
char *scannerAM; 	/* address modifier for scanner address 	*/
    {
    STATUS status;
    PLC_SCANNER_LINK *linkId;

    linkId = vmePlcScannerLinkCreate( scannerAddress, scannerAM );
    if ( linkId != NULL )
	{
	linkId->cFigBufPtr = &configInfo;
	if ( (status = plcAutoConfig( linkId, &configInfo )) != OK )
	    {
	    ul_fatal("### \"plcAutoConfig()\" failed ###");
	    }
	if ( (status = plcEnableOutput( linkId )) != OK )
	    {
	    ul_fatal("### \"plcEnableOutput()\" failed ###");
	    }
	ul_info("*** PLC Scanner startup complete. ***");
	}
    else
	{
	ul_fatal("### \"vmePlcScannerLinkCreate()\" failed ###");
	}
    return( linkId );
    }

VOID
announceError( linkId)
PLC_SCANNER_LINK *linkId;
/*
 *
 *  put all the long-winded error stuff in on place
 */
 {
    PLC_SCANNER_DUAL_PORT *scannerLoc;

    scannerLoc = linkId->plcScanner2Port;
    ul_critical("ERROR - PLC_STATUS = 0x%x, PLC_STATE = 0x%x, CONF_STAT. = 0x%x.",
	linkId->plcScannerStatus, linkId->plcScannerState,
	    scannerLoc->mailBox.conf_stat);
    return;
    }

BT_REQUEST *btRequestQueue[ 256 ];
int lastBTID = 0;

VOID
plcIrqDaemon()
/*
 *  Spawned as a task, this function takes care of interrupt responses
 *	to requests.
 *
 */
    {
    extern MSG_Q_ID plcBTMsgQ;

    long j;
    u_short idTag;
    LINK_ID_FORM isrMsg;
    PLC_SCANNER_LINK *linkId;
    PLC_SCANNER_DUAL_PORT *hwPtr;
    STATUS status;
    PLC_STATE scannerState;
    u_int bytesFromISR;
    PLC_COMMAND cmndInt;
    BT_REQUEST *btPtr;

    extern u_short blocxXferID[];
    extern MSG_Q_ID plcBTMsgQ;

    ul_error_name( "plcIrqDaemon()" );
    ul_error_log( UL_FDEBUG, UL_FNONE );

    /*	on startup, create the message queue, and		*/
    if ( (plcBTMsgQ = msgQCreate( MAX_PLC_MSG_CNT, PLC_MSG_SIZE, MSG_Q_FIFO ))
	    == NULL )
	ul_fatal("\"msgQcreate()\", called from \"plcIrqDaemon()\", failed");

    /*	stand-by to receive messages.				*/
    for ( ; ; )
	{
	bytesFromISR = msgQReceive( plcBTMsgQ, &isrMsg.linkIdChar, PLC_MSG_SIZE,
	    WAIT_FOREVER );
	if ( bytesFromISR != PLC_MSG_SIZE )
	    ul_critical("Incorrect byte count in \"isrMsg\" = %d", bytesFromISR);
	else
	    {
	    linkId = isrMsg.linkIdPtr;
	    hwPtr = (isrMsg.linkIdPtr)->plcScanner2Port;
	    cmndInt = ( PLC_COMMAND )(hwPtr->mailBox.command);
	    linkId->plcConfStatus = hwPtr->mailBox.conf_stat;
	    switch( cmndInt )
		{
		case SETUP_PLC :
		    {
		    switch( hwPtr->mailBox.conf_stat )
			{
			case CONF_STATUS_OK:
			    linkId->plcScannerStatus = PLC_OK;
			    linkId->plcScannerState = PLC_STATE_LINK_CREATED;
			    hwPtr->mailBox.fl_lock = 0;
			    ul_debug("PLC returned \"DONE\", SETUP_PLC.");
			    semGive( linkId->plcResponse );
			    break;
			default:
			    hwPtr->mailBox.fl_lock = 0;
			    announceError( linkId );
			    break;
			}
		    break;
		    }

		case AUTOCONFIG_PLC :
		    {
		    switch( hwPtr->mailBox.conf_stat )
			{
			case CONF_STATUS_OK:
			    linkId->plcScannerStatus = PLC_OK;
			    linkId->plcScannerState = PLC_STATE_CONFIG;
			    hwPtr->mailBox.fl_lock = 0;
			    ul_debug("PLC returned \"DONE\", AUTOCONFIG.");
			    semGive( linkId->plcResponse );
			    break;
			case CONF_STATUS_BLOCK_XFER_QUEUED:
			    linkId->plcScannerStatus = PLC_COMMAND_QUEUED;
			    ul_debug
				("PLC returned \"QUEUED\" status, AUTOCONFIG.");
			    break;
			default:
			    hwPtr->mailBox.fl_lock = 0;
			    announceError( linkId );
			    break;
			}
		    break;
		    }

		case SCAN_LIST_PLC :
		    {
		    switch( hwPtr->mailBox.conf_stat )
			{
			case CONF_STATUS_OK:
			    {
			    PLC_CONFIG_BUFFER *cFigPtr;

			    linkId->plcScannerStatus = PLC_OK;
			    linkId->plcScannerState = PLC_STATE_PROG_MODE;

			    if ( linkId->cFigBufPtr != NULL )
				{
				cFigPtr = linkId->cFigBufPtr;

				/* copy configuration to application's buffer */
				for (j = 0; j < 72; j++ )
				    {
				    cFigPtr->cFigBuffer[ j ]
				        = hwPtr->mailBox.data_area[ j ];
				    }
				}
			    hwPtr->mailBox.fl_lock = 0;
			    semGive( linkId->plcResponse );
			    ul_debug("PLC returned \"DONE\"for CONFIG.");
			    break;
			    }
			default:
			    hwPtr->mailBox.fl_lock = 0;
			    announceError( linkId );
			    break;
			}
		    break;
		    }

		case SET_MODE_PLC :
		    {
		    switch( hwPtr->mailBox.conf_stat )
			{
			case CONF_STATUS_OK:
			    linkId->plcScannerStatus = PLC_OK;
			    linkId->plcScannerState = PLC_STATE_RUN_MODE;
			    hwPtr->mailBox.fl_lock = 0;
			    ul_debug("PLC returned \"DONE\", SET_MODE.");
			    semGive( linkId->plcResponse );
			    break;
			default:
			    hwPtr->mailBox.fl_lock = 0;
			    linkId->plcScannerStatus = PLC_SET_MODE_FAIL,
			     /*linkId->plcScannerState = ; */
			    announceError( linkId );
			    break;
			}
		    break;
		    }

		case LINK_STATUS_PLC :
		    {
		    switch( hwPtr->mailBox.conf_stat )
			{
			case CONF_STATUS_OK:
			    linkId->plcScannerStatus = PLC_OK;
			    hwPtr->mailBox.fl_lock = 0;
			    ul_debug
				("PLC returned \"DONE\" status, LINK_STATUS.");
			    semGive( linkId->plcResponse );
			    break;
			default:
			    hwPtr->mailBox.fl_lock = 0;
			    linkId->plcScannerStatus = PLC_LINK_STATUS_ERROR,
			    linkId->plcScannerState = PLC_LINK_STATUS_REQ;
			    announceError( linkId );
			    break;
			}
		    break;
		    }

		case BT_WRITE_PLC:
		    {
		    idTag = hwPtr->mailBox.bt_tag;
		    if ( (btPtr = btRequestQueue[ idTag ]) == NULL )
			{
			ul_critical(
			    "NULL idTag received by \"plcIrqDaemon()\".");
			break;
			}
		    switch ( (int)(hwPtr->mailBox.conf_stat) )
			{
			case CONF_STATUS_OK:
			case CONF_STATUS_QUEUE_FULL:
			case CONF_STATUS_XFER_TIMEOUT:
			    {
			    btPtr->confStatWd = hwPtr->mailBox.conf_stat;
			    btRequestQueue[ idTag ] = NULL; /* release queue */
			    if ( (btPtr->btCompleteSem) != NULL)
				semGive( btPtr->btCompleteSem );
			    break;
			    }

			case CONF_STATUS_BLOCK_XFER_QUEUED:
			    {
			    btPtr->confStatWd = hwPtr->mailBox.conf_stat;
			    break;
			    }

			default:
			    {
			    btPtr->confStatWd = hwPtr->mailBox.conf_stat;
			    ul_critical(
				"plc scanner errorcode = 0x%2x, ID = 0x%8x.", 
				    hwPtr->mailBox.conf_stat,
				    isrMsg.linkIdPtr);
			    btRequestQueue[ idTag ] = NULL; /* release queue */
			    break;
			    }
			break;
			}
		    break;
		    }

		case BT_READ_PLC:
		    {
		    idTag = hwPtr->mailBox.bt_tag;
		    if ( (btPtr = btRequestQueue[ idTag ]) == NULL )
			{
			ul_critical(
			    "NULL idTag received by \"plcIrqDaemon()\".");
			break;
			}
		    switch ( (int)(hwPtr->mailBox.conf_stat) )
			{
			case CONF_STATUS_OK:
			    {
				long dataSize;
				btPtr->confStatWd = hwPtr->mailBox.conf_stat;
				btPtr->wordCount = hwPtr->mailBox.data_len;
				if ( (dataSize = hwPtr->mailBox.data_len)  > 64 )
				    dataSize = 64;
				for ( j = 0; j < dataSize; j++)
				    btPtr->dataBuffer[ j ]
					= hwPtr->mailBox.data_area[ j ];
				if ( (btPtr->btCompleteSem) != NULL)
				    semGive( btPtr->btCompleteSem );
				/* release queue */
				btRequestQueue[ idTag ] = NULL;
				break;
				}

			case CONF_STATUS_QUEUE_FULL:
			case CONF_STATUS_XFER_TIMEOUT:
			    {
			    btPtr->confStatWd = hwPtr->mailBox.conf_stat;
			    if ( (btPtr->btCompleteSem) != NULL)
				semGive( btPtr->btCompleteSem );
			    btRequestQueue[ idTag ] = NULL; /* release queue */
			    break;
			    }

			case CONF_STATUS_BLOCK_XFER_QUEUED:
			    {
			    btPtr->confStatWd = hwPtr->mailBox.conf_stat;
			    break;
			    }

			default:
			    {
			    ul_critical(
				"plc scanner error = 0x%2x, ID = 0x%8x.", 
				    hwPtr->mailBox.conf_stat,
				    isrMsg.linkIdPtr);
			    btRequestQueue[ idTag ] = NULL; /* release queue */
			    break;
			    }
			break;
			}
		    break;
		    }
		default:
		    ul_critical("Non-BLOCK_TRANSFER oper det. in daemon = 0x%x",
			cmndInt);
		}
	    hwPtr->mailBox.fl_lock = 0;     /* clear interlock semaphore    */
	    }
	}
    }

STATUS
plcQueueBTMsg( btRequest )
/*
 *
 *  queue a block-transfer request
 */
    BT_REQUEST *btRequest;
    {
    PLC_SCANNER_LINK *linkId;
    PLC_SCANNER_DUAL_PORT *hwPtr;
    STATUS status = OK;
    long j, k;
    long maxBTID = (sizeof( btRequestQueue ))/(sizeof( btRequestQueue[ 0 ] ));
    u_short waitCounter = 0;

    ul_debug("\"plcQueueBTMag( 0x%x, )\" call received.",
	btRequest);

    if ( (linkId = btRequest->scannerID) != NULL )
	{
	hwPtr = linkId->plcScanner2Port;
	if ( (btRequest->commandWd == BLOCK_RD)
	    || (btRequest->commandWd == BLOCK_WT) )
	    {
	    /* in any case, we need to wait until scanner is available 	*/
	    while ( sysBusTas( hwPtr->mailBox.fl_lock ) != OK )
		{
		if ( waitCounter++
			>= (MAX_PLC_SCANNER_BUSY_WAIT * sysClkRateGet()) )
		    {
		    linkId->plcScannerStatus = PLC_INTERFACE_BUSY;
		    ul_critical("PLC interface busy.");
		    return( ERROR );
		    }
		else
		    {
		    taskDelay( 2 );
		    }
		}

	    /* pick an ID for the BLOCK TRANSFER	*/
	    taskLock();
	    j = (lastBTID + 1) % maxBTID;
	    while( j != lastBTID )
		{
		if ( btRequestQueue[ j ] == NULL )
		    break;
		else
		    j = ( j + 1 ) % maxBTID;
		}
	    if ( j != lastBTID )
		{
		lastBTID = j;
		btRequestQueue[ j ] = btRequest;
		taskUnlock();

		/* queue the request to the scanner		*/
		hwPtr = linkId->plcScanner2Port;
		hwPtr->mailBox.command = btRequest->commandWd;
		hwPtr->mailBox.address = btRequest->devAdrs;
		hwPtr->mailBox.bt_tag = (u_short)j;
		if ( btRequest->commandWd == BLOCK_WT )
		    {
		    hwPtr->mailBox.data_len = btRequest->wordCount;
		    if ( hwPtr->mailBox.data_len != 0 )
			j = hwPtr->mailBox.data_len - 1;
		    else
			j = 63;
			
		    for ( ; j >= 0 ; j-- )
			hwPtr->mailBox.data_area[ j ]
			    = btRequest->dataBuffer[ j ];
		    }
		else
		    hwPtr->mailBox.data_len = 0;

		 /* interrupt controller         */
		hwPtr->mailBox.interruptScanner = 0;
		status = OK;
		}
	    else
		{
		taskUnlock();

		/* clear interlock semaphore	*/
		hwPtr->mailBox.fl_lock = 0;
		ul_critical("BLOCK_TRANSFER_ID table currently full.");
		status = ERROR;
		}
	    }
	else
	    {
	    ul_critical
		("Non-BLOCK_TRANSFER opcode sent to \"plcQueueBTMsg()\", 0x%x.",
		    btRequest->commandWd);
#ifdef DEBUG
	    d( btRequest, 64 );
#endif /* DEBUG */
	    status = ERROR;
	    }
	}
    else
	{
	/* NULL implies link not established 	*/
	ul_critical("Request with NULL scannerId passed to \"plcQueueBTMsg\"");
	status = ERROR;
	}
    return( status );	/* Daemon takes care of replies.		*/
    }

/*
 ****************************************************************
 *								*
 *  utilities for Allen Bradley # 1771-IFE A.D.C. module	*
 *								*
 ****************************************************************
 */

PLC_BT_DEV_DESC
( *createAb1771_IFE_dev( scannerId, devDesc ) )
/*
 *
 *  initialize device descriptor and return device identifier
 */
    PLC_SCANNER_LINK *scannerId;
    AB_1771_IFE_TBL *devDesc;
    {
    STATUS status;
    PLC_BT_DEV_DESC *newDescriptor;

    if ( scannerId->plcScannerState != PLC_STATE_RUN_MODE )
	{
	ul_critical
	    ("BLOCK_XFER req. via un-init. scanner, \"createAb1771-IFE_dev\"");
	return( (PLC_BT_DEV_DESC *) ERROR );
	}
    if ( (newDescriptor
	    = (PLC_BT_DEV_DESC *)(malloc( sizeof( *newDescriptor )) )) == NULL )
	{
	ul_critical
	    ("\"malloc()\" called by \"*createAb1771-IFE_dev()\" returned NULL.");
	return( (PLC_BT_DEV_DESC *) ERROR );
	}
    newDescriptor->scannerID = scannerId;
    newDescriptor->operStatus = PLC_BT_UNINIT;
    newDescriptor->btDevType = AB_1771_IFE;
    newDescriptor->devSpecStruc = (char *)devDesc;
    if ( (newDescriptor->adcBtDone =
	    semBCreate( SEM_Q_FIFO, SEM_EMPTY )) == NULL )
	ul_critical("cannot create semaphore for BT device." );

    return( newDescriptor );
    }

STATUS
initAb1771_IFE_dev( devTblPtr )
/*
 *
 *  initialize the A.D.C. hardware
 *	NOTE:  At this time, the commands necessary to take advantage
 *		of the adcs' built in scaling feature have not been
 *		implemented.
 */
    PLC_BT_DEV_DESC *devTblPtr;
    {
    long j, jLimit;
    STATUS status;
    AB_1771_IFE_TBL *cfigTblPtr;
    PLC_SCANNER_LINK *scnIdent;

    BT_REQUEST initMsg;		/* build initialization message here	*/

    cfigTblPtr = (AB_1771_IFE_TBL *)(devTblPtr->devSpecStruc);
    scnIdent = devTblPtr->scannerID;

    /* build the initialization message */
    /*	first, the boiler-plate		*/
    initMsg.scannerID = devTblPtr->scannerID;
    initMsg.btCompleteSem = devTblPtr->adcBtDone;

    initMsg.commandWd = BLOCK_WT;
    initMsg.devAdrs = cfigTblPtr->adcPlcSdrs;
    initMsg.wordCount = 3; 	/*  set to 3  - no scaling	*/

    /*  now build the adc configuration	*/
    /*	Range Selection			*/
    initMsg.dataBuffer[ 0 ] = 0;
    initMsg.dataBuffer[ 1 ] = 0;
    for ( j = 0; j < 8; j++ )
	initMsg.dataBuffer[ 0 ] = initMsg.dataBuffer[ 0 ]
	    | (cfigTblPtr->adcRange[ j ] << ( 2 * j) );
    if ( cfigTblPtr->inputConfig == SINGLE_ENDED )
	{
	for ( j = 8; j < 16; j++ )
	    initMsg.dataBuffer[ 1 ] = initMsg.dataBuffer[ 1 ]
		| (cfigTblPtr->adcRange[ j ] << 2);
	}
    /*	sampling, data format, input type, digital filter	*/
    initMsg.dataBuffer[ 2 ] = 0
			| (u_short)( cfigTblPtr->adcSmplRate << 11
    				| cfigTblPtr->adcDataFmt << 9
    				| cfigTblPtr->inputConfig << 8
				| cfigTblPtr->abAdcDigFiltTC);
    
#ifdef AUTO_SCALING_USED
    /*	Sign bits for MinimumMaximum scaling values			*/

    /*	Scale values							*/

    /* initialize the hardware.		*/
#endif  /* AUTO_SCALING_USED */

    if ( (status = plcQueueBTMsg( &initMsg ))  == OK )
	{
	if ( (status = semTake( initMsg.btCompleteSem,
		sysClkRateGet() * 6 )) != OK )
/*	if ( (status = semTake( initMsg, WAIT_FOREVER ) != OK ) ) */
	    {
	    ul_critical("attempt to initialize ADC timed out.");
	    }
	}
    else
	{
	ul_critical("attempt to initialize ADC failed");
	}

    return( status );
    }

#define ADC_STUFF_DUN 1
#ifdef ADC_STUFF_DUN

STATUS
readAb1771Ife( devTblPtr )
/*
 *
 *  read adc, put values in specified array of structures.
 */
    PLC_BT_DEV_DESC *devTblPtr;
    {
    long j;

    STATUS status = OK;
    AB_1771_IFE_TBL *specificDevTblPtr;
    PLC_SCANNER_LINK *linkId;

    BT_REQUEST reqAdcReadMsg;         /* build read request message here    */
    AB_ADC_DATA *usrDataBufPtr;

    if ( devTblPtr->btDevType == AB_1771_IFE)
	{
	linkId = devTblPtr->scannerID;
	if ( linkId->plcScannerState == PLC_STATE_RUN_MODE)
	    {
	    specificDevTblPtr = (AB_1771_IFE_TBL * )(devTblPtr->devSpecStruc);

	    reqAdcReadMsg.scannerID = devTblPtr->scannerID;
	    reqAdcReadMsg.btCompleteSem = devTblPtr->adcBtDone;
	    reqAdcReadMsg.commandWd = BLOCK_RD;
	    reqAdcReadMsg.devAdrs = specificDevTblPtr->adcPlcSdrs;

	    if ( (status = plcQueueBTMsg( &reqAdcReadMsg )) == OK )
		{
		if ( (status = semTake( reqAdcReadMsg.btCompleteSem,
			sysClkRateGet() * 6 )) == OK )
		    {
		    /*  untangle values and flags from returned message	 */
		    specificDevTblPtr->diagnostics.all 
			= reqAdcReadMsg.dataBuffer[ 0 ];
		    usrDataBufPtr = specificDevTblPtr->abAdcDataBuffP;
		    for ( j = 0; j < reqAdcReadMsg.wordCount; j++ )
			{
			usrDataBufPtr[ j ].underRange
			    = ( reqAdcReadMsg.dataBuffer[ 1 ] >> j) & 0x1;
			usrDataBufPtr[ j ].overRange
			    = ( reqAdcReadMsg.dataBuffer[ 2 ] >> j) & 0x1;
			usrDataBufPtr[ j ].sign
			    = ( reqAdcReadMsg.dataBuffer[ 3 ] >> j) & 0x1;
			usrDataBufPtr[ j ].adcValue
			    = reqAdcReadMsg.dataBuffer[ j + 4 ];
			if ( specificDevTblPtr->adcDataFmt == DATA_FMT_2s_COMP )
			    {
			    usrDataBufPtr[ j ].adcValue 
				=  ( usrDataBufPtr[ j ].adcValue << 3 );
			    }

			}
		    }
		else
		    {
		    ul_critical("attempt to read ADC timed out.");
		    status = ERROR;
		    }
		}
	    else
		{
		ul_critical("attempt to read ADC failed");
		status = ERROR;
		}
	    }
	else
	    {
	    ul_critical(
		"\"readAb1771Ife()\", scanner not in \"PLC_STATE_RUN_MODE\"");
	    status = ERROR;
	    }
	}
    else
	{
	ul_critical("call to \"readAb1771Ife()\" specifies bad device type");
	status = ERROR;
	}
    return( status );
    }

PLC_BT_DEV_DESC *adcSvcList[ MAX_NR_ADC ];

VOID
adcReader( readPeriod )
/*
 *
 *  spawn as a task to periodically read A-B 1771-IFE adc from list
 */
    int readPeriod;	/*  period in seconds			*/
    {
    int lastReadTime, nextReadDelay;
    long j;
    STATUS status;

    ul_error_name( "adcReader()" );
    ul_error_log( UL_FDEBUG, UL_FNONE );
    for ( ; ; )
	{
	lastReadTime = (int)tickGet();
	for ( j = 0; j < MAX_NR_ADC; j++ )
	    {
	    if ( adcSvcList[ j ] != NULL )
		{
		if ( (status = readAb1771Ife( adcSvcList[ j ] )) == ERROR)
		    {
		    ul_critical("\"readAb1771Ife( 0x%x )\" = ERROR.",
			adcSvcList[ j ]);
		    }
		}
	    }
	nextReadDelay = ( sysClkRateGet() * readPeriod )
		- ( (int)tickGet() - lastReadTime);
	status = taskDelay( nextReadDelay );
	}
    }

STATUS
addAdcToReadList( adcDescriptPtr )
/*
 *
 *  Add an ADC to the list of adc's to be scanned.
 *
 *  if the reader is not already running, spawn it.
 *
 *  if pointer to adc descriptor is NULL, simply check reader, spawn
 *	if necessary, and exit.
 */
    PLC_BT_DEV_DESC *adcDescriptPtr;
    {
    BOOL readerRunning;
    char *adcRdTaskName = "adcRdr\0";
    int adcReaderTID;
    long j;
    STATUS status;

    /* if pointer to adc descriptor != null, add it to list */
    if ( adcDescriptPtr != NULL )
	{
	status = ERROR;
	if ( adcDescriptPtr->btDevType == AB_1771_IFE )
	    {
	    for ( j = 0; j < MAX_NR_ADC; j++ )
		{
		if ( adcSvcList[ j ] == adcDescriptPtr )
		    {
		    ul_critical(
		        "attempt to re-install adcID = 0x%x in reader list",
			    adcDescriptPtr);
		    break;
		    }
		
		if ( adcSvcList[ j ] == NULL )
		    {
		    adcSvcList[ j ] = adcDescriptPtr;
		    status = OK;
		    ul_info("adcID = 0x%x installed in reader list.");
		    break;
		    }
		}
	    if ( j == MAX_NR_ADC )
		ul_critical("adc reader list overflow");
	    }
	else
	    ul_critical(
		"ptr to wrong device type passed to \"addAdcToReadList()\"");
	}

    /* if reader is not running, start it up	*/
    if( (adcReaderTID = taskNameToId( "adcRdr" )) != ERROR )
	{
	if ( (readerRunning = taskIsReady( adcReaderTID )) == FALSE )
	    {
	    adcReaderTID = ERROR;
	    if ( (status = taskDelete( adcReaderTID )) != OK )
		ul_fatal("attempt to delete stalled adc reader failed");
	    else
		ul_critical("stalled adc reader task deleted");
	    }
	}
    if ( adcReaderTID == ERROR )
	{
	if ( (adcReaderTID = taskSpawn( "adcRdr", ADC_RDR_PRIORITY,
		VX_FP_TASK + VX_DEALLOC_STACK + VX_STDIO,
		    0x18000, adcReader, ADC_READ_PERIOD,
		    0, 0, 0, 0, 0, 0, 0, 0, 0 )) == ERROR )
	    ul_fatal("attempt to spawn adc reader failed");
	else
	    {
	    ul_info( "adc reader spawned");
	    status = OK;
	    }
	}

    return( status );
    }

#endif