/* drvAb.c -  Driver Support Routines for Allen Bradley */
/* base/src/drv $Id$ */
/*
 * routines that are used, below the ai, ao, bi and bo drivers to interface
 * to the Allen-Bradley Remote Serial IO
 *
 * 	Author:	Bob Dalesio
 * 	Date:	6-21-88
 *
 *	Experimental Physics and Industrial Control System (EPICS)
 *
 *	Copyright 1991, the Regents of the University of California,
 *	and the University of Chicago Board of Governors.
 *
 *	This software was produced under  U.S. Government contracts:
 *	(W-7405-ENG-36) at the Los Alamos National Laboratory,
 *	and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *	Initial development by:
 *		The Controls and Automation Group (AT-8)
 *		Ground Test Accelerator
 *		Accelerator Technology Division
 *		Los Alamos National Laboratory
 *
 *	Co-developed with
 *		The Controls and Computing Group
 *		Accelerator Systems Division
 *		Advanced Photon Source
 *		Argonne National Laboratory
 *
 * Notes:	
 *   1. This driver uses Asynchronous Communications to the AB scanner
 *	The manual description for this type of interface is
 *	misleading in the area of interrupt handling.
 *   2.	We are using double slot addressing as the ab1771il card only works
 *      in this addressing mode. This dictates that analog io cards alternate 
 *	between input and output. Therefore there are 6 input slots and 
 *	6 output slots per adapter. The binaries may be placed in either slot.
 *      Slots 1,3,5,7,9,11 are analog input slots
 *      Slots 2,4,6,8,10,12 are analog output slots 
 *      The database uses card numbers 0-11 to address slots 1-12
 *   3. The binary io is memory mapped and the analog io is handled through
 *	block transfers of whole cards of data.
 *   4. The timing rates for the analog inputs should not be made lower as
 *      the cards will not respond to the AB scanner any faster.
 *
 * Modification Log:
 * -----------------
 * .01 09-15-88		lrd	only spawn tasks if Allen-Bradley IO is present
 * .02 09-15-88		lrd	use the ##open(name,flags,mode)## circuit detect
 *				from the 6008 for read failure indication
 * .03 09-15-88		lrd	documentation for data and command flow
 * .04 12-18-88		lrd	fix the conversions
 * .05 02-01-89		lrd	changed vxTas to sysBusTas!!!!!!!!!!!!
 *				delete tasks before spawning again
 *				add 1771IFE card support
 * .06	02-10-89	lrd	read OFE at initialization
 * .07	02-24-89	lrd	modify for vxWorks 4.0
 *				change spawn to taskSpawn and rearranged args
 *				change sysSetBCL to sysIntEnable
 *				move task data to task_params.h
 * .08	03-10-89	lrd	keep the scan list at initialization
 * .09	03-14-89	lrd	move interrupt enable to ioc_init
 * .10	03-17-89	lrd	modify link_init for restart
 *				provide read routines for outputs
 * .11  03-23-89        lrd     fixed ab_bidriver address not even numbers only
 *                              and the mask and pvalue args were switched
 * .12	03-25-89	lrd	routines for the database to read the outputs
 *				at initialization
 * .13	04-22-89	lrd	implement binary IO to ignore addressing mode
 *				implement more than one serial link
 * .14	04-24-89	lrd	have one outstanding attempt for initialization
 * .15	04-26-89	lrd	removed card type checking for binary IO
 *				as the binaries are memory mapped into a
 *				seperate area
 * .16	04-27-89	lrd	recover from IO failures and pass the failure
 *				status back to the database
 * .17	04-28-89	lrd	fleshed out timeout counters and added check
 *				for conversion errors on IL and IFE modules
 * .18	05-15-89	lrd	took out scaling error check and made thre
 *				consecutive timeouts before a change of status
 * .19	08-01-89	lrd	changed mr_wait and bt_queue to always set 
 *				length to 0 for compatibility with PLC-5
 *				communication
 * .20	09-29-89	ba/lrd	changed the interrupt routine to remove response
 *				data from the dual port memory and queueing the
 *				entire response
 * .21	09-29-89	lrd	add scan_list command and support to build the
 *				scan list according to the database thus polling
 *				racks that may not have been powered at
 *				initialization
 * .22	09-29-89	lrd	add more error checking
 * .23  10-17-89	cbf	modified the way interrupts are dispatched in 
 *				ab_intr
 * .24  10-17-89	cbf	added logic to avoid queueing requests to cards
 *				which already had a request pending to avoid
 *				overflowing scanner's queue when I/O card is missing
 * .25  10-17-89	cbf	added code to grab the operating status word
 *				in the interrupt routine to determine when the
 *				status of any scanned adapter has changed
 * .26	10-17-89	cbf	added logic to see if the scanner was using the
 *				dual port when we had it locked.  it is!
 * ****************************************************************************
 * *    Many of the above were needed to work around problems in the          *
 * *    6008-SV firmware.  These problems have been reported to Allen-        *
 * *    Bradley and fixes are promised.  The PROMs we are presently using     *
 * *	are both labeled "91332-501".  U63 chksum = A7A1.  U64 chksum = 912B  *
 * *	(Series A, Revision C.)						      *
 * ****************************************************************************
 * .27	12-11-89	cbf	new PROMs implemented which allow output image table
 *				to be preserved during SYSTEM RESET. This driver
 *				is still compatible with the old firmware also.
 *				A piece of code was added to store the firmware
 *				revision level in array ab_firmware_info.
 * .28  12-11-89	cbf	With the new firmware, the scanner no longer uses
 *				the dual port when we have it locked so the tests
 *				for this condition have been removed.
 * .29  01-23-90	cbf	In the previous rev of this driver we made an attempt
 *				to build a scan list automatically by waiting until
 *				the periodic scan task had asked for data from each
 *				needed adapter.  This scheme caused problems with the
 *				the initialization of BOs and AOs, because they were
 *				not initially in the scan list and so could not be read
 *				at initialization.  In this rev, that code has been
 *				removed and the scan list is always built to include all
 *				eight possible adapters.  This has some performance
 *				implications because each non-existant adapter which
 *				appears in the scan list adds an extra 5 ms to each
 *				I/O scan time for the 6008SV.  The decreased performance
 *				does not appear to be very significant, however.  The
 *				6008SV front panel "SER" LED blinks if not all adapters
 *				in the scan list are responding.  With this rev of the
 *				driver, the light will therefore be blinking. 
 * .30	01-30-90	lrd	add plc/adapter distinction
 * .31	05-24-90	mk	added 4-20Ma IFE support
 * .32	05-25-90	jcr	added PLC readback support
 * .33	07-03-90	lrd	fixed PLC readback verification
 * .34	07-19-90	mk/lrd	fixed the overrange indications (<< -> <<=)
 * .35	11-02-90	lrd	initialize the adapter buffers whether or not
 *				the link is there for detection by the bi and
 *				bo driver interface to the database library
 * .36	11-27-90	lrd	add support for the 16 BI and BO cards
 * .37	12-19-90	lrd	added 0-5V IFE support
 *****************************************************************************************
 **	Allen-Bradley has given us a new firmware revision. This revision allows the reset	*
 **	of the scanner without using the sysReset on the VME backplane. It also maintains	*
 **	the output image table for the binary outputs. This reset is used to recover from	*
 **	a scanner fault, where the 6008 firmware deadends - ab_reset provides this function.	*
 ************************************************************************************************
 * .38	03-07-91	lrd	modify ab_driver_init to not reinitialize semaphores and interrupts
 *				if this is not the first invocation
 * .39	03-07-91	lrd	modify the ab_aodriver to only set the update bit if the new value
 *				is different than the old value
 * .40	03-08-91	lrd	fix ab_boread to verify card is present
 * .41	05-16-91	lrd	have the ab_scan_task driver analog outputs
 *				periodically as well as on change
 * .42	08-06-91	rac	remove include for alarm.h--it's not used
 * .43  09-11-91	joh 	updated for v5 vxWorks
 * .44  11-16-91	 bg 	moved io_report for Allen Bradley to this 
 *				module. Broke io_report up into subroutines.
 *				Added sysBusToLocalAdrs and SysIntEnable
 * .45   1-13-92         bg     added level to io_report and added the 
 *				ability to print raw 
 *                              values if the level is > 0 .    
 * .46	05-22-92	lrd	added the task that monitors the binary 
 *				inputs and simulates a change
 *				of state interrupt - wakes up the io event 
 *				scanner
 * .47	06-10-92	 bg	combined drvAb.c and ab_driver.c    
 * .48	06-26-92	 bg	added level to the ab_io_report in 
 *				the drvSup structure          
 * .49	06-29-92	joh	removed FILE pointer argument to io report
 * .50	06-29-92	joh	moved ab reset here
 * .51	07-10-92	lrd	mode interrupt on change of state to scan once on initialization
 * .52	08-11-92	joh	io report format cleanup
 * .53	08-25-92	mrk	made masks a macro
 * .54	08-25-92	mrk	added support for Type E,T,R,S Tcs
 * .55  08-25-92	mrk	support epics I/O event scan
 * .56  06-30-93	mrk	After 3 attempts to queue request Ask to initialize
 * .57  07-22-93	mrk	For BI call scanIoRequest when adapter status changes
 * .58  07-22-93	mrk	For AB1771IL sign_bit>>= becomes sign_bit<<=
 * .59  07-27-93	mrk	Included changes made by Jeff Hill to stop warning messages
 * .60  07-27-93	mrk	Made changes for vxWorks 5.x semLib
 * .61  08-02-93	mrk	Added call to taskwdInsert
 * .62  08-04-93	mgb	Removed V5/V4 and EPICS_V2 conditionals
 * .62  09-04-93	mrk	for bo and ao change value even if down
 * .63  09-15-93	mrk	make report shorter.
 * .64  09-16-93	mrk	ab_reset: all links; only reset scanner.
 * .65  05-05-94	kornke	IL Differential Fix
 */

/*
 * Binary Input Code Portions:
 *
 *           process_bi
 *                 |
 *                 |
 *                 |
 *                 V
 *           ab_bidriver
 *      |                 ^
 *      | mark card       | data 
 *      | as BI           |
 *      V                 |
 * ab_config     AB dual ported memory
 *                        ^
 *                        |
 *                        |
 *                        |
 *              AB 6008 scanner card
 */



/*
 * Binary Output Code Portions:
 *
 *           process_bo
 *                 |
 *                 |
 *                 |
 *                 V
 *           ab_bodriver
 *      |                 |
 *      | mark card       | data 
 *      | as BO           | 
 *      V                 V
 * ab_config     AB dual ported memory
 *                        |
 *                        |
 *                        |
 *                        V
 *              AB 6008 scanner card
 */

/*
 * Analog Input Code Portions:
 *
 *           process_ai
 *                 |
 *                 V
 *           ab_aidriver
 *      |                 ^
 *      | mark card       |  
 *      | as AI           | data
 *      V                 |
 *  ab_config         ab_btdata
 *      |                 ^
 *      | cards           | data
 *      | present         | returned
 *      V                 |
 * abScanTask         abDoneTask
 *      |                 ^
 *      | commands        | data
 *      V                 |
 * bt_queue             bt_done
 *      |                 ^
 *      | commands        | data
 *      V                 |
 *      AB dual ported memory
 *      |                 ^
 *      | commands        | data
 *      V                 |
 *      AB 6008 scanner card
 */

/*
 * Analog Output Code Portions:
 *
 *           process_ao
 *                 |
 *                 V
 *           ab_aidriver
 *      |                 |
 *      | mark card       |  
 *      | as AI           | data
 *      V                 V
 *  ab_config         ab_btdata
 *      |                 |
 *      | cards           | data
 *      | present         | returned
 *      V                 V
 *           abScanTask
 *      |
 *      | command/data
 *      V
 * bt_queue             bt_done
 *      |                 ^
 *      | command/data    | acknowledge
 *      V                 |
 *      AB dual ported memory
 *      |                 ^
 *      | command/data    | acknowledge
 *      V                 |
 *      AB 6008 scanner card
 */

#include	<vxWorks.h>
#include	<sysLib.h>		/* library for task  support */
#include	<semLib.h>		/* library for semaphore support */
#include	<vme.h> 
#include	<rebootLib.h> 
#include 	<wdLib.h>		/* library for watchdog timer support */
#include 	<rngLib.h>             /* library for ring buffer support */
#include	<task_params.h>
#include	<stdioLib.h>

#include	<dbDefs.h>
#include	<drvSup.h>
#include	<taskwd.h>
#include	<module_types.h>
#include	<drvAb.h>


		/* AllenBradley serial link and ai,ao,bi and bo cards */
#define LOCK -2
		/* AllenBradley serial link and ai,ao,bi and bo cards */

/* If any of the following does not exist replace it with #define <> NULL */
static long report();
static long init();

struct {
	long	number;
	DRVSUPFUN	report;
	DRVSUPFUN	init;
} drvAb={
	2,
	report,
	init};


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

/* forward reference for ab_reboot_hook */
int ab_reboot_hook();

static long init()
{
    return(ab_driver_init());
}


short	   ab_disable=0;

/*jcr */
/* response queue */
struct ab_response{
unsigned short	status;
unsigned short	link;
unsigned short	adapter;
unsigned short	card;
unsigned short	command;
unsigned short	data[64];
};

/* internal timeout variables */
short ab_tout[AB_MAX_LINKS];		/* time out flag */
WDOG_ID wd_id[AB_MAX_LINKS];	/* watchdog task ID */
int	ab_timeout();		/* internal timeout handler */

/* message complete variables */
SEM_ID	ab_data_sem;		/* transfer complete semaphore */
SEM_ID	ab_cmd_sem;		/* transfer complete semaphore */
		/* following flag indicates to interrupt handler that we're
		   requesting a block xfer (and expect immediate confirmation) */
short		ab_requesting_bt[AB_MAX_LINKS];
LOCAL RING_ID	ab_cmd_q;		/* links ready to be read */
int	ab_intr ();			/* interrupt service routine */
#define	AB_Q_SZ	32*sizeof(struct ab_response)	/* AB command Q size */

/*
 * The configuration table contains a word to describe each card 
 * in each adapter. The word contains the following data:
 * 0x0000 - not initialized
 * 0xffff - no card present
 * 0x8000 - initialized
 * 0x4000 - updated data flag (used for sending analog outputs)
 * 0x2000 - real time scan initialized
 * 0x1000 - adapter/plc flag
 *	0 - adapter
 *	1 - PLC
 * 0x0f00 - conversion 
 *	For IXE
 *	0 - no conversion (use millivolt range)
 *	1 - linear (use millivolt range)
 *	2 - K_DGF
 *	3 - K_DGC
 *	4 - J_DGF
 *	5 - J_DGC
 *	6 - E_DGF
 *	7 - E_DGC
 *	8 - T_DGF
 *	9 - T_DGC
 *	10- R_DGF
 *	11- R_DGC
 *	12- S_DGF
 *	13- S_DGC
 *	For Ir
 *	0-  degF
 *	1-  degC
 * 0x00e0 - interface type
 * 	0 - Not Assigned
 *	1 - Binary Input
 *	2 - Binary Output
 *	3 - Analog Input
 *	4 - Analog Output
 * 0x001f - the Allen-Bradley card type (from ~gta/h/module_types.h)
 *	unique only per interface type
 */
unsigned short	ab_config[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];

/*
 * Allen-Bradley Raw Data read through the abScanTask. This table
 * will contain the analog inputs read using block transfers. It
 * is also where analog outputs are placed to affect a change in an
 * analog output signal Binary cards read and write directly into the 
 * Allen-Bradley dual ported memory
 */
short	ab_btdata[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS][AB_CHAN_CARD];

/* array of pointers to the Allen-Bradley 6008 serial interface cards present */
struct ab_region	*p6008s[AB_MAX_LINKS];

/* Allen-Bradley Block Transfer Task */
int	abDoneId;		/* id of the Allen-Bradley io complete task */
int	abCOSId;		/* id of the Allen-Bradley binary input change of state scan wakeup */
int	abScanId;		/* id of the Allen-Bradley scan task */

/* Timeout counters for outstanding data requests */
/* The driver has to be smart about when the data requests are made because  */
/* the Allen-Bradley IO will hold up all requests if an IO card isn't ready  */
/* to talk. The thermocouple input card has a max scan rate of .5 second and */
/* if it isn't ready when a request is made, all proceeding block transfers  */
/* will be held up. This table is used so that requests are only made at     */
/* a rate that the card can accept.					 */
/* Some of the unpleasant side affects of making this number too small are: */
/*	1. the interrupt for command done comes when the dual port memory is */
/*	unlocked - so you have to wait for the lock flag in a loop          */
/*	2. timeouts from the scanner occur rendering the data useless       */
/*	Note that this problem increases exponentially                      */
/* scan task rate is .1 second						    */
short	ab_timers[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
#define	AB_IXE_RATE	5	/* .5 seconds */
#define AB_IL_RATE	4	/* .4 seconds */
#define	AB_IR_RATE	5	/* .5 seconds */
#define AB_IFE_RATE	1	/* .1 seconds */
#define AB_OFE_RATE	10	/* 1 seconds - written immediately */
#define AB_INT_LEVEL    5

/* status on the Allen-Bradley driver interface */
		/* got back read/write info instead of cmd response */
short	ab_bad_response[AB_MAX_LINKS];
		/* got back cmd info instead of read/write response */
short	ab_rw_resp_err[AB_MAX_LINKS];
		/* watchdog timeouts through our internal timer */
short	ab_comm_to[AB_MAX_LINKS];
		/* data transfer timed out */
short	ab_data_to[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
		/* data xfer timeout detected through the AB scanner */
short	ab_cmd_to[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
		/* link status and scan list command timeouts */
short	ab_link_to[AB_MAX_LINKS];
		/* under range scaling error returned through block xfer */
short	ab_scaling_error[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
		/* over range scaling error returned through block xfer */
short	ab_or_scaling_error[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
		/* awakened but dual ported memory was unlocked */
short	ab_post_no_lock[AB_MAX_LINKS];
		/* no. time dpm locked when we wanted to talk */
short	ab_not_ready[AB_MAX_LINKS];
		/* card status 0 - good  -1 - bad */
char	ab_btsts[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS][AB_CHAN_CARD];
		/* scanner operational status word */
short	ab_op_stat[AB_MAX_LINKS];
		/* Keeps track of how many times we've asked for data
		   before we actually receive it for a given card.  Keeps
		   us from asking for data faster than card can supply it */
unsigned short	ab_btq_cnt[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];
#include <dbScan.h>
static IOSCANPVT ioscanpvt[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];

/*
 * flags a communication error on a link status
 * which in turn flags all channels on the adapter as hardware errors
 */
short	ab_adapter_status[AB_MAX_LINKS][AB_MAX_ADAPTERS];
short	ab_adapter_status_change[AB_MAX_LINKS][AB_MAX_ADAPTERS];

/* disables the scanner during a restart - required for   */
/* successful restart of the IOC			  */

/* scan list management variables */
/* these are used to build and reset the scan list according to the */
/* database							    */
		/* scan list built from the adapters_present array */
char	ab_scan_list[AB_MAX_LINKS][AB_MAX_ADAPTERS];
		/* scan attempts while waiting for initialization */
char	ab_init_cnt[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];

/* debug variable (must be >1 to see all messages) */
int	ab_debug = 0;

/* location to store scanner firmware revision info which is avail only temporarily */
char	ab_firmware_info[AB_MAX_LINKS][96];

static char *ab_stdaddr;

/*
 *
 * support for ab reset
 *
 *
 */
unsigned short 		ab_reset_wait;
unsigned short		reset_code[100];
unsigned short		reset_cnt = 0;

/* forward references */
static void 	wtrans();
int 	abScanTask();
void	ab_reset_task();
int	ab_reset();

typedef enum{abbtSuccess,abbtCardUsed,abbtBusy,
	abbtTimeout,abbtLinkDown,abbtError} abbtStatus;
/*definitions for BT_READ and BT_WRITE */
typedef struct {
	void 		(*callback)();
	unsigned short	*pbuffer;
	abbtStatus 	status;
	unsigned short	nwords;
	void		*userPvt;
} ABBTREQUEST;

ABBTREQUEST	*pabbtrequest[AB_MAX_LINKS][AB_MAX_ADAPTERS][AB_MAX_CARDS];

static void abBtCallback(link,adapter,card)
unsigned short 	link;
unsigned short 	adapter;
unsigned short 	card;
{
    ABBTREQUEST	*preq=pabbtrequest[link][adapter][card];
    unsigned short *pcard = &ab_config[link][adapter][card];

    if(!preq ) {
	printf("Allen Bradley: abBtCallback Logic Error");
	return;
    }
    (preq->callback)(preq);
    pcard = &ab_config[link][adapter][card];
    *pcard &= ~(AB_INTERFACE_TYPE|AB_INIT_BIT|AB_SENT_INIT);
    pabbtrequest[link][adapter][card] = NULL;
}

int ab_bt_read(link,adapter,card,preq)
unsigned short 	link;
unsigned short 	adapter;
unsigned short 	card;
ABBTREQUEST	*preq;
{
	/* pointer to the Allen-Bradley configuration table */
        unsigned short *pcard = &ab_config[link][adapter][card];
	abbtStatus	status;

        /* If card is initialized then error */
        if (*pcard & AB_INTERFACE_TYPE) { 
	    if(((*pcard & AB_INTERFACE_TYPE)==AB_BT_READ) 
	    || ((*pcard & AB_INTERFACE_TYPE)==AB_BT_WRITE)) {
		status = abbtBusy;
	    } else {
		status = abbtCardUsed;
	    }
	    return(status);
	}
	if(ab_adapter_status[link][adapter]) return(abbtLinkDown);
	pabbtrequest[link][adapter][card] = preq;
	ab_btq_cnt[link][adapter][card] = 0;
	*pcard |= AB_BT_READ | AB_INIT_BIT;
	return(abbtSuccess);
}

int ab_bt_write(link,adapter,card,preq)
unsigned short 	link;
unsigned short 	adapter;
unsigned short 	card;
ABBTREQUEST	*preq;
{
	/* pointer to the Allen-Bradley configuration table */
        unsigned short *pcard = &ab_config[link][adapter][card];
	abbtStatus	status;

        /* If card is initialized then error */
        if (*pcard & AB_INTERFACE_TYPE) { 
	    if(((*pcard & AB_INTERFACE_TYPE)==AB_BT_READ) 
	    || ((*pcard & AB_INTERFACE_TYPE)==AB_BT_WRITE)) {
		status = abbtBusy;
	    } else {
		status = abbtCardUsed;
	    }
	    return(status);
	}
	if(ab_adapter_status[link][adapter]) return(abbtLinkDown);
	ab_btq_cnt[link][adapter][card] = 0;
	pabbtrequest[link][adapter][card] = preq;
	*pcard |= AB_BT_WRITE | AB_INIT_BIT;
	return(abbtSuccess);
}

/*
 * READ_AB_ADAPTER
 *
 * read an adapter of AB IO 
 */
void read_ab_adapter(link,adapter,pass)
register unsigned short	link;
register unsigned short	adapter;
register short	pass;
{
    register unsigned short     card;
    register unsigned short     *pcard;
    short    			btq_err;
    short			msg[64];

    /* each card */
    for (card = 0, pcard = &ab_config[link][adapter][0]; 
      card < AB_MAX_CARDS;
      card++, pcard++){

	if (*pcard & AB_INTERFACE_TYPE){
	    if((*pcard & AB_INTERFACE_TYPE)==AB_BT_READ) {
		ABBTREQUEST	*preq = pabbtrequest[link][adapter][card];
		abbtStatus	status = abbtSuccess;
		unsigned short	count;
		

		count = ab_btq_cnt[link][adapter][card]++ ;
		if(count>1 && count<50) continue;
		if(count>=50) status = abbtTimeout;
		else if(bt_queue(AB_READ,link,adapter,card,
			preq->nwords,preq->pbuffer)!=OK) status = abbtError;
		preq->status = status;
		if(status) abBtCallback(link,adapter,card);
		continue;
	    }
	    if((*pcard & AB_INTERFACE_TYPE)==AB_BT_WRITE) {
		ABBTREQUEST	*preq = pabbtrequest[link][adapter][card];
		abbtStatus	status = abbtSuccess;
		unsigned short	count;

		ab_btq_cnt[link][adapter][card]++;
		count = ab_btq_cnt[link][adapter][card]++ ;
		if(count>1 && count<50) continue;
		if(count>=50) status = abbtTimeout;
		else if(bt_queue(AB_WRITE,link,adapter,card,
			preq->nwords,preq->pbuffer)!=OK) status = abbtError;
		preq->status = status;
		if(status) abBtCallback(link,adapter,card);
		continue;
	    }

	    /* need intialization */ /* jcr */
	    if ((*pcard  & AB_INIT_BIT) == 0){
		if ((*pcard & AB_SENT_INIT) == 0){
		    if (*pcard & AB_PLC) {
		    	    *pcard |= AB_INIT_BIT;
			    *pcard |= AB_SENT_INIT;
		    }
		    else if (ab_card_init(pcard,adapter,card,link) == 0){
			    *pcard |= AB_SENT_INIT;
			    ab_init_cnt[link][adapter][card] = 0;
		    }
		/* did the init message get lost - try each second */
		}else if (ab_init_cnt[link][adapter][card]++ > 10){
		    if (ab_card_init(pcard,adapter,card,link) == 0){
			    ab_init_cnt[link][adapter][card] = 0;
		    }
		}
		continue;
	    }
	    /* need block transfer */
	    btq_err = OK;	/* assume success */

	    /* don't make another block transfer request if one's outstanding... */
	    if((ab_btq_cnt[link][adapter][card] % 10) != 0) {
		/* ...but try again periodically in case a request got lost. */
		ab_btq_cnt[link][adapter][card]++;
		continue;
	    }
	    /*If 3 queue attempts fail then reinitialize*/
	    if(ab_btq_cnt[link][adapter][card] >= 30) {
		*pcard &= ~(AB_INIT_BIT | AB_SENT_INIT);
		ab_btq_cnt[link][adapter][card] = 0;
		continue;
	    }

	    if ((*pcard & AB_INTERFACE_TYPE) == AB_AI_INTERFACE){
		    switch (*pcard&AB_CARD_TYPE){
		    case (AB1771IrPlatinum) :
		    case (AB1771IrCopper) :
			if ((pass % AB_IR_RATE) == 0) {
				btq_err = bt_queue(AB_READ,link,adapter,card,8,&msg[0]);
			}
			break;
		    case (AB1771IL):
			if ((pass % AB_IL_RATE) == 0) {
		  		btq_err = bt_queue(AB_READ,link,adapter,card,12,&msg[0]);
			}	
			break;
		    case (AB1771IXE ):
			if ((pass % AB_IXE_RATE) == 0) {
		  		btq_err = bt_queue(AB_READ,link,adapter,card,12,&msg[0]);
			}
			break;
		    case (AB1771IFE):
		    case (AB1771IFE_4to20MA):
		    case (AB1771IFE_0to5V):
		  	btq_err = bt_queue(AB_READ,link,adapter,card,12,&msg[0]);
			break;
		    case (AB1771IFE_SE):
		  	btq_err = bt_queue(AB_READ,link,adapter,card,20,&msg[0]);
			break;
		    }
		    if(btq_err != OK){
			if(ab_debug)
			    logMsg("Error on AI BT request for L%x A%x C%x\n",
				link,adapter,card);
		    }

	    }else if ((*pcard&AB_INTERFACE_TYPE)==AB_AO_INTERFACE){
              	if (((pass % AB_OFE_RATE) == 0) || (*pcard & AB_UPDATE)){
		    ab_btwrite(pcard,adapter,card,link);

		}
	    }
	}
    }
}

/*
 * ab_card_init
 *
 * Allen-Bradley card initialization routines
 */
ab_card_init(pcard,adapter,card,link)
register unsigned short	*pcard;		/* AB configuration word */
register unsigned short	adapter;
register unsigned short	card;
short			link;
{
    short		msg[64];
    register short	*pmsg = &msg[0];
    register short	length;
    register short 	i;
    register short	*pab_table;

    bfill(pmsg,64*2,0);
    *pmsg = 0;
    switch (*pcard & AB_INTERFACE_TYPE){
    case (AB_AI_INTERFACE):
        switch (*pcard & AB_CARD_TYPE){
	case (AB1771IrCopper) :
	    *pmsg = IR_COPPER;
	case (AB1771IrPlatinum) :
	    i = (*pcard & AB_CONVERSION) >> 8;
	    if(i==IR_degF) *pmsg |= IR_UNITS_DEGF;
	    if(i==IR_Ohms) *pmsg |= IR_UNITS_OHMS;
	    *pmsg |= IR_SIGNED;
	    length = 1;
            break;
        case (AB1771IXE):       /* millivolt module */
	    /* need to base this on conversion */
	    switch ((*pcard & AB_CONVERSION) >> 8){
	    case (K_DGF):
		*pmsg = IXE_K | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
		break;
	    case (K_DGC):
		*pmsg = IXE_K | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
		break;
	    case (J_DGF):
		*pmsg = IXE_J | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
		break;
	    case (J_DGC):
		*pmsg = IXE_J | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
		break;
            case (E_DGF):
                *pmsg = IXE_E | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (E_DGC):
                *pmsg = IXE_E | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (T_DGF):
                *pmsg = IXE_T | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (T_DGC):
                *pmsg = IXE_T | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (R_DGF):
                *pmsg = IXE_R | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (R_DGC):
                *pmsg = IXE_R | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (S_DGF):
                *pmsg = IXE_S | IXE_DEGF | IXE_SIGNED | IXE_HALFSEC;
                break;
            case (S_DGC):
                *pmsg = IXE_S | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
                break;
	    default:
		*pmsg = IXE_MILLI | IXE_DEGC | IXE_SIGNED | IXE_HALFSEC;
		break;
	    }
	    length = 27;
            break;
        case (AB1771IL):
	    *pmsg = IL_RANGE;		/* -10 to +10 volts */
	    *(pmsg+1) = IL_DATA_FORMAT;	/* signed magnitude */
	    *(pmsg+2) = 0x0ff;
            for (i = 4; i <= 18; i+=2) *(pmsg+i) = 0x4095;
	    length = 19;
            break;
        case (AB1771IFE):
	    *pmsg = 0xffff;/*IFE_RANGE;			 -10 to +10 volts */
	    *(pmsg+1) = 0xffff;/*IFE_RANGE;		 -10 to +10 volts */
	    *(pmsg+2) = 0x0700;/*IFE_DATA_FORMAT; signed magnitude, differential */
	    *(pmsg+3) = 0x0ffff;
	    for (i = 6; i <= 36; i+=2) *(pmsg+i) = 0x4095;
	    length = 37;
            break;
        case (AB1771IFE_SE):
	    *pmsg = 0xffff;/*IFE_RANGE;			 -10 to +10 volts */
	    *(pmsg+1) = 0xffff;/*IFE_RANGE;		 -10 to +10 volts */
	    *(pmsg+2) = 0x0600;/*IFE_SE_DATA_FORMAT;  signed magnitude, single-ended */
	    *(pmsg+3) = 0x0ffff;
	    for (i = 6; i <= 36; i+=2) *(pmsg+i) = 0x4095;
	    length = 37;
            break;
        case (AB1771IFE_4to20MA):
	    *pmsg = 0x0000;/*IFE_RANGE;			 4to20 MilliAmps  */
	    *(pmsg+1) = 0x0000;/*IFE_RANGE;		 4to20 MilliApms  */
	    *(pmsg+2) = 0x0700;/*IFE_DATA_FORMAT; signed magnitude, double */
	    for (i = 6; i <= 36; i+=2) *(pmsg+i) = 0x4095;
	    length = 37;
            break;
        case (AB1771IFE_0to5V):
	    *pmsg = 0x5555;/*IFE_RANGE;			 0 to 5 Volts  */
	    *(pmsg+1) = 0x5555;/*IFE_RANGE;		 0 to 5 Volts  */
	    *(pmsg+2) = 0x0700;/*IFE_DATA_FORMAT; signed magnitude, double */
	    for (i = 6; i <= 36; i+=2) *(pmsg+i) = 0x4095;
	    length = 37;
            break;
        default:
            return(-1);
        }
        break;

    case (AB_AO_INTERFACE):
	/* initializing an analog output card includes writing the values */
	/* see also ab_aoread below */
	/* analog output reads the current outputs at initialization */
        if ((*pcard & AB_CARD_TYPE) == AB1771OFE){
	    /* configuration data */
	    *(pmsg+4) = OFE_BINARY | OFE_SCALING;
	    for (i = 6; i <= 12; i+=2) *(pmsg+i) = 0x4095;
	    length = 5;
	    return(bt_queue(AB_READ,link,adapter,card,length,pmsg));
	}else
            return(-1);
        break;
    default:
	return(-1);
    }

    /* initate block tranfer */
    return(bt_queue(AB_WRITE,link,adapter,card,length,pmsg));
}

/*
 * ab_btwrite
 *
 * block transfer - to write data
 */
ab_btwrite(pcard,adapter,card,link)
unsigned short		*pcard;
register unsigned short	adapter;
register unsigned short	card;
register unsigned short	link;
{
	short	msg[64];
	register short	*pmsg = &msg[0];
	register i;
	register short	*pab_table = &ab_btdata[link][adapter][card][0];
	short    btq_err;

	/* clear out the dual ported memory */
	bfill(&msg[0],64*2,0);

        if ((*pcard & AB_CARD_TYPE) == AB1771OFE){
		/* take the data from the Allen-Bradley data table */
		for (i = 0; i < 4; i++, pab_table++)
			*(pmsg+i) = *pab_table;
		/* configuration data */
		*(pmsg+4) = OFE_BINARY | OFE_SCALING;
	    	for (i = 6; i <= 12; i+=2) *(pmsg+i) = 0x4095;

		/* initate block tranfer */
	    	if(btq_err = bt_queue(AB_WRITE,link,adapter,card,5,pmsg)){ 
	    	   if(ab_debug)
		     logMsg("link %x, ab_btwrite error %x\n",link,btq_err);
	    	   return (btq_err);
		}
	}else{
		return(-1);
	}
	return(0);
}

/*
 * bt_queue
 *
 * queue a block transfer request
 */
bt_queue(command,link,adapter,card,length,pmsg)
register unsigned short	command;
register unsigned short link;
register unsigned short	adapter;
unsigned short		card;
unsigned short		length;
unsigned short		*pmsg;
{
	unsigned short *pcard = &ab_config[link][adapter][card];
	register struct ab_region	*p6008 = p6008s[link];
	register struct dp_mbox 	*pmb = (struct dp_mbox *)(&p6008->mail);
	register unsigned short		*pmb_msg = (unsigned short *)&pmb->da;
	register unsigned short		lock_stat,i;
	int				status;
	register unsigned short	command_back;

	/* try to get access to (lock) the dual port memory */
	/* This prevents the Allen-Bradley scanner from accessing the dual */
	/* ported memory while a request is being made.			   */
	for(i=0; i<100; i++) {
	    if ((lock_stat = sysBusTas (&pmb->fl_lock)) == TRUE) break;
	    taskDelay(1);
	}
	if(lock_stat == FALSE) {
		if(ab_debug != 0)
		    logMsg("link %x BTQ-dpm locked on %x cmd\n",
			link,command);
		ab_not_ready[link] += 1;
		return (LOCK);
	}

	/* As the scanner will not be sending us data during this command */
	/* request, we direct the interrupts here and wait for command    */
	/* accepted.							  */
	ab_requesting_bt[link] = TRUE;

	/* clear the timeout flag */
	ab_tout[link] = OK;

	/* copy the message into the dual-ported memory */

	/* clear the semaphore to make sure we stay in sync */
	if((semTake(ab_cmd_sem,NO_WAIT) == OK) && ab_debug)
		logMsg("link %x, semaphore set before bt_queue cmd %x,%x,%x\n",
		    link,command,pmb->command,pmb->conf_stat);

	pmb->command = command;
	/* jcr */
	if ((*pcard & AB_PLC)==0) {
		pmb->address = (adapter << 4) + card;
		pmb->data_len = length;
	}else{
		pmb->address = (adapter << 4);	/* always writes to card 0 */
		pmb->data_len = 0;		/* PLCs govern the length  */
		/* used by the PLC to determine where the data comes from  */
		/* the PLC programmers have to use this knowledge fopr the */
		/* interface						   */
		pmsg[62] = card;
	}
	wtrans(pmsg,pmb_msg);

	/* initiate the timeout clock */
	wdStart(wd_id[link],vxTicksPerSecond,ab_timeout,link);
	/* alert the scanner to the new request */
	p6008->sc_intr = 1;

	/* wait for the command to be accepted */
	semTake(ab_cmd_sem, WAIT_FOREVER);
	/* determine if the command was accepted */
	if (ab_tout[link] == ERROR){
		if(ab_debug)
		    logMsg("link %x, BTQ cmd %x...timeout\n",link,command);
		ab_cmd_to[link][adapter][card]++;
		ab_requesting_bt[link] = FALSE;
		/* unlock the dual ported memory */
		pmb->fl_lock = 0;
		return(ERROR);
	}

	/* get the status & command from the dual-ported memory */
	status = pmb->conf_stat;
	command_back = pmb->command;
	if(command_back != command){
		if (ab_debug)
			logMsg("link %x, BTQ cmd %x, resp=%x\n"
			    ,link,command,command_back);
		ab_bad_response[link]++;
		status = ERROR;
	}
	if (status != BT_ACCEPTED){
		if (status == 0x14){
			taskDelay(vxTicksPerSecond/10);
			if(ab_debug >1) 
			    logMsg("link %x BTQ full, delaying...\n",link);
		}else{
			if(ab_debug != 0)
			    logMsg("link %x BTQ failure %x\n",link,status);
		}
	}else
		status = OK;
	/* return the interrupts to the data received state */
	ab_requesting_bt[link] = FALSE;

	/* unlock the dual ported memory */
	pmb->fl_lock = 0;

	/* indicate that a response is queued for this card */
	if(command == AB_READ) ab_btq_cnt[link][adapter][card]++;

	return(0);
}

/*
 * abDoneTask
 *
 * This task handles block transfer data returned from the Allen-Bradley 
 * scan task through the dual ported memory.
 * It needs to acknowledge that a block tranfer card (analog io) has been
 * successfully configured or place the read data into the block transfer
 * memory.
 */
abDoneTask(){
    register short		i,sign_bit,card,plc_card;
    unsigned short		adapter,link;
    register unsigned short	*pcard;		/* ptr to configuration word */
    register short		*pab_table;
    char			*pab_sts;
    struct ab_response		response;
    struct ab_response		*presponse = &response;
    while(TRUE){
	/* wait for block transfer completion */
	semTake(ab_data_sem, WAIT_FOREVER);
	while (rngBufGet(ab_cmd_q,&response,sizeof(struct ab_response)) 
	  == sizeof(struct ab_response)){
	    link = presponse->link;

	    /* make sure we got a response to a read or write command */
	    if ((presponse->command != AB_READ)
	       &&  (presponse->command != AB_WRITE)){
		ab_rw_resp_err[link]++;
		continue;
	    }

	    /* find whose data this is */
	    /* unpacks the card pack out of the message */
	    /* this codes determines if it is a PLC or  */
	    /* adapter response                         */
	    adapter = presponse->adapter;
	    card = presponse->card;
	    plc_card = presponse->data[62];

    	    /* let's determine if this is a PLC - verify with config table */
	    if ((plc_card <= 16) && (plc_card >= 0)
	      && (ab_config[link][adapter][plc_card] & AB_PLC))
	    	card = plc_card;

	    pcard = &ab_config[link][adapter][card];

	    /* zero counter to indicate that a requested BT was received */
	    ab_btq_cnt[link][adapter][card] = 0;
	    if(((*pcard & AB_INTERFACE_TYPE)==AB_BT_READ) 
	    || ((*pcard & AB_INTERFACE_TYPE)==AB_BT_WRITE)) {
		ABBTREQUEST	*preq = pabbtrequest[link][adapter][card];

	        /* block transfer timeout */
		if (presponse->status == 0x23)  {
			preq->status = abbtTimeout;
		} else {
			preq->status = abbtSuccess;
		}
		if((*pcard & AB_INTERFACE_TYPE)==AB_BT_READ) {
			for(i=0; i<preq->nwords; i++)
				preq->pbuffer[i] = presponse->data[i];
		}
		abBtCallback(link,adapter,card);
		continue;
	    }

	    /* block transfer timeout */
	    if (presponse->status == 0x23){
		if (ab_debug >1)
		    logMsg("link %x adapter %x card %x timeout %x\n"
			,link,adapter,card,presponse->status);
		ab_data_to[link][adapter][card]++;
		/* mark the channels on this card as harware error */
		pab_sts = &ab_btsts[link][adapter][card][0];
		for (i=0; i<AB_CHAN_CARD; i++,pab_sts++)
		    *pab_sts -= 1;

		/* mark for reinitialization */
		if (ab_btsts[link][adapter][card][0] <= 0)
			*pcard &= ~(AB_INIT_BIT | AB_SENT_INIT);
		continue;
	    }else if (presponse->status != 0){ 
		if (ab_debug != 0)
			logMsg("L%x A%x C%x, Bad Done Status=%x\n"
			,link,adapter,card,presponse->status);
		continue;
	    }

	    /* was it a response to configuration */
	    if ((*pcard & AB_INIT_BIT) == 0){
		*pcard |= AB_INIT_BIT;
		ab_init_cnt[link][adapter][card] = 0;

		/* analog outputs return values on init */
		if ((*pcard & AB_INTERFACE_TYPE)==AB_AO_INTERFACE)
		{
		    register struct ab1771ofe_write *pmsg
		      = (struct ab1771ofe_write *)presponse->data;
		    pab_table = &ab_btdata[link][adapter][card][0];
		    for (i = 0; i < 4; i++,pab_table++)
			*pab_table = pmsg->data[i];
		}

	    /* it was a response to a command */
	    /* analog input response */
	    }else if ((*pcard & AB_INTERFACE_TYPE) == AB_AI_INTERFACE){
		if((*pcard & AB_CARD_TYPE) == AB1771IrPlatinum
		|| (*pcard & AB_CARD_TYPE) == AB1771IrCopper)
		{
		    struct ab1771ir_read *pmsg = (struct ab1771ir_read *) presponse->data;
		    short under,over,overflow,polarity;

		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    for(i=0, under = 0x1, over=0x100, overflow=0x1, polarity=0x100;
		    i<ai_num_channels[AB1771IrPlatinum];
		    i++, under<<=1, over<<=1, overflow<<=1, polarity<<=1) {
			    *pab_table = pmsg->data[i];
			    if(pmsg->pol_over&polarity) *pab_table = -*pab_table;
			    if((pmsg->status&under) || (pmsg->status&over)
			    || (pmsg->pol_over&overflow)) {
			    	*pab_sts = -3;
			    	ab_scaling_error[link][adapter][card]++;
			    }else{
				*pab_sts = 0;
			    }
			    pab_sts++;
			    pab_table++;
			}
		}
		else if ((*pcard & AB_CARD_TYPE) == AB1771IL)
		{
		register struct ab1771il_read *pmsg
		  = (struct ab1771il_read *)presponse->data;
		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    for (i=0, sign_bit=1; i < ai_num_channels[AB1771IL]; i++, sign_bit<<= 1){
			/* status */
			if((pmsg->urange & sign_bit) || (pmsg->orange & sign_bit)){
			    *pab_sts = -3;
			    ab_scaling_error[link][adapter][card]++;
			}else{
			    *pab_sts = 0;
			    /* data */
			    *pab_table = pmsg->data[i];
			}
			pab_sts++;
			pab_table++;
		    }
		}
		else if ((*pcard & AB_CARD_TYPE) == AB1771IXE)
		{
		register struct ab1771ixe_read *pmsg
		  = (struct ab1771ixe_read *)presponse->data;
    		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    for (i=0, sign_bit=0x1; i<ai_num_channels[AB1771IXE]; i++, sign_bit<<=1){

			/* status */
			if ((pmsg->out_of_range & sign_bit) 
			  || (pmsg->out_of_range & (sign_bit << 8))){
			    *pab_sts = -3;
			    ab_scaling_error[link][adapter][card]++;
			}else{
			    *pab_sts = 0;
			    /* data */
			    *pab_table = pmsg->data[i];
			    if(pmsg->pol_stat & (sign_bit << 8)) *pab_table = -*pab_table;
			}
			pab_sts++;
			pab_table++;
		    }
		}
		else if ((*pcard & AB_CARD_TYPE) == AB1771IFE)
		{
		register struct ab1771ife_read *pmsg
		  = (struct ab1771ife_read *)presponse->data;
		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    /* check each channel as overrange */
		    for (i=0, sign_bit=0x1; i<ai_num_channels[AB1771IFE]; i++, sign_bit<<=1){

			/* status */
			if (pmsg->urange & sign_bit){
				*pab_sts = -3;
			    ab_scaling_error[link][adapter][card]++;
			}else if(pmsg->orange & sign_bit){
			    *pab_sts = -3;
			    ab_or_scaling_error[link][adapter][card]++;
			}else if ((pmsg->data[i] & 0xf000) > 0){
			    ab_or_scaling_error[link][adapter][card]++;
			}else{
			    *pab_sts = 0;
			    *pab_table = pmsg->data[i];	/* put data in table */
			}
			pab_sts++;
			pab_table++;
		    }
		}
		else if ((*pcard & AB_CARD_TYPE) == AB1771IFE_SE)
		{
		register struct ab1771ife_read *pmsg
		  = (struct ab1771ife_read *)presponse->data;
		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    /* check each channel as overrange */
		    for (i=0, sign_bit=0x1; i<ai_num_channels[AB1771IFE_SE]; i++, sign_bit<<=1){

			/* status */
			if (pmsg->urange & sign_bit){
				*pab_sts = -3;
			    ab_scaling_error[link][adapter][card]++;
			}else if(pmsg->orange & sign_bit){
			    *pab_sts = -3;
			    ab_or_scaling_error[link][adapter][card]++;
			}else if ((pmsg->data[i] & 0xf000) > 0){
			    ab_or_scaling_error[link][adapter][card]++;
			}else{
			    *pab_sts = 0;
			    *pab_table = pmsg->data[i];	/* put data in table */
			}
			pab_sts++;
			pab_table++;
		    }
		}
		else if ( ((*pcard & AB_CARD_TYPE) == AB1771IFE_4to20MA)
		      ||  ((*pcard & AB_CARD_TYPE) == AB1771IFE_0to5V  )   )
		{
		register struct ab1771ife_read *pmsg
		  = (struct ab1771ife_read *)presponse->data;
		    pab_table = &ab_btdata[link][adapter][card][0];
		    pab_sts = &ab_btsts[link][adapter][card][0];
		    /* check each channel as overrange */
		    for (i=0, sign_bit=0x1;
		    i<ai_num_channels[AB1771IFE_4to20MA]; i++, sign_bit<<=1){

			/* status */
			if (pmsg->urange & sign_bit){
				*pab_sts = -3;
			    ab_scaling_error[link][adapter][card]++;
			}else if(pmsg->orange & sign_bit){
			    *pab_sts = -3;
			    ab_or_scaling_error[link][adapter][card]++;
			}else if ((pmsg->data[i] & 0xf000) > 0){
			    ab_or_scaling_error[link][adapter][card]++;
			}else{
			    *pab_sts = 0;
			    *pab_table = pmsg->data[i];	/* put data in table */
			}
			pab_sts++;
			pab_table++;
		    }
		}
           /* analog output response */
            }else if ((*pcard & AB_CARD_TYPE) == AB1771OFE){ 
	           *pcard = *pcard & (~AB_UPDATE); /* mark as updated */
	    }
	}
    }
}


/*
 * AB_BI_COS_SIMULATOR
 *
 * simulate a change of state interrupt from the Allen-Bradley
 */
unsigned char		ab_old_binary_ins[AB_MAX_LINKS*AB_MAX_ADAPTERS*AB_MAX_CARDS];
ab_bi_cos_simulator()
{
        register struct ab_region       *p6008;
        register unsigned short 	link;
	register unsigned short		*pcard;
	unsigned short			*ps_input,*ps_oldval;
	short				adapter,card,inpinx;
	short				first_scan,first_scan_complete;
	short				adapter_status_change;

	/* dont do anything until interruptAccept */
	for(;;){
		if(interruptAccept) break;
		taskDelay(vxTicksPerSecond/15);
	}
	first_scan_complete = FALSE;
	first_scan = TRUE;
	for(;;){
		/* check each link */
		link = 0;
		for (link = 0; link < AB_MAX_LINKS; link++){
		    if ((p6008 = p6008s[link]) == 0) continue;
		    for (adapter = 0; adapter < AB_MAX_ADAPTERS; adapter++){
			adapter_status_change = ab_adapter_status_change[link][adapter];
			ab_adapter_status_change[link][adapter] = 0;

			/* check each card that is configured to be a binary input card */
			for (card = 0; card < AB_MAX_CARDS; card++){
				pcard = &ab_config[link][adapter][card];
				inpinx =  (adapter * AB_CARD_ADAPTER) + card;
				if ((*pcard & AB_INTERFACE_TYPE) != AB_BI_INTERFACE) continue;
					
				if ((*pcard & AB_CARD_TYPE) == ABBI_16_BIT){
					/* sixteen bit byte ordering in dual ported memory 	*/
					/* byte 0011 2233 4455 6677 8899 AABB			*/
					/* card 0000 2222 4444 6666 8888 AAAA			*/
					if (inpinx & 0x1)	continue;
					ps_input = (unsigned short *)&(p6008->iit[inpinx]);
					ps_oldval = (unsigned short *)&(ab_old_binary_ins[inpinx]);
					if ((*ps_input != *ps_oldval) || first_scan || adapter_status_change){
						scanIoRequest(ioscanpvt[link][adapter][card]);
						*ps_oldval = *ps_input;
					}
				}else{
					/* eight bit byte ordering in dual ported memory */
					/* 1100 3322 5544 7766 9988 BBAA 		 */
					if (inpinx & 0x1)	inpinx--;	/* shuffle those bytes */
					else inpinx++;
					if ((p6008->iit[inpinx] != ab_old_binary_ins[inpinx]) || first_scan || adapter_status_change){
						scanIoRequest(ioscanpvt[link][adapter][card]);
						ab_old_binary_ins[inpinx] = p6008->iit[inpinx];
					}
				}
			}
		}
	    }

	    /* turn off first scan */
	    if (first_scan){
		first_scan_complete = TRUE;
		first_scan = FALSE;
	    }

	    /* check for changes at about 15 Hertz */
	    taskDelay(vxTicksPerSecond/15);
	}
}

int ab_bi_getioscanpvt(link,adapter,card,scanpvt)
unsigned short link;
unsigned short adapter;
unsigned short card;
IOSCANPVT *scanpvt;
{
	*scanpvt = ioscanpvt[link][adapter][card];	
	return(0);
}

/*
 * ALLEN-BRADLEY DRIVER INITIALIZATION CODE
 */
ab_driver_init()
{
	unsigned short		cok,got_one;
	register unsigned short	err_cnt;
	register unsigned short	not_init;
	register unsigned short	link;
	struct ab_region	*p6008;
        int status;

	/* initialize the Allen-Bradley 'done' semaphore */
	if (abScanId == 0){
		if(!(ab_cmd_sem=semBCreate(SEM_Q_FIFO,SEM_EMPTY)))
		     errMessage(0,"semBcreate failed in initEvent");
		if(!(ab_data_sem=semBCreate(SEM_Q_FIFO,SEM_EMPTY)))
		     errMessage(0,"semBcreate failed in initEvent");
		if ((ab_cmd_q = rngCreate(AB_Q_SZ)) == (RING_ID)NULL)
		    panic ("ab_init: ab_cmd_q\n");
	}
	got_one = FALSE;
        if ((status = sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,AB_BASE_ADDR,&ab_stdaddr)) != OK){
           logMsg("Addressing error in ab driver\n");
           return ERROR;
        }
        p6008 =(struct ab_region *)ab_stdaddr; 
	for (link=0;link < AB_MAX_LINKS; link++,p6008++){
		/* initialize the AB config buffer for this link */
		bfill(&ab_config[link][0][0],
		  AB_MAX_ADAPTERS*AB_MAX_CARDS*sizeof(short),
		  0);
		bfill(&ab_btdata[link][0][0][0],
		  AB_MAX_ADAPTERS*AB_MAX_CARDS*AB_CHAN_CARD*sizeof(short),
		  0);
		bfill(&pabbtrequest[link][0][0],
		  AB_MAX_ADAPTERS*AB_MAX_CARDS*sizeof(ABBTREQUEST *),
		  0);

		/* initialize each 6008 that exists */
		if (vxMemProbe(p6008,READ,1,&cok) != ERROR){
			p6008s[link] = p6008;
			err_cnt = 0;
			not_init = TRUE; 
			/* create the internal watchdog timer */
			if (wd_id[link] == 0)
				wd_id[link] = wdCreate(); 

			/*connect intr service routine to intr vector */
			intConnect((AB_VEC_BASE+link)*4,ab_intr,link);
                        sysIntEnable(AB_INT_LEVEL); 

			while ((err_cnt < 5) && (not_init)){
				if (link_init(link) >=0) not_init = FALSE;
				else {
					err_cnt++;
					logMsg("link %x, link_init error\n",link);
				}
			}
			if (err_cnt >= 5){
				p6008s[link] = 0;
			}else{
				got_one = TRUE;
			} 
		}else{
			p6008s[link] = 0;
		}
		if(p6008s[link]) {
			int adapter,card;
			for(adapter=0; adapter<AB_MAX_ADAPTERS; adapter++)
		   	for(card=0; card<AB_MAX_CARDS; card++)
				scanIoInit(&ioscanpvt[link][adapter][card]);
		}
	}

	if (got_one){ 

		/* spawn the scan task to handle block transfer requests */
		if (abScanId) td(abScanId);
        	abScanId = taskSpawn(ABSCAN_NAME,ABSCAN_PRI,ABSCAN_OPT,
					ABSCAN_STACK,abScanTask);
		taskwdInsert(abScanId,NULL,NULL);

		 /* spawn the done task to handle block transfer responses */
		if (abDoneId) td(abDoneId);
		abDoneId = taskSpawn(ABDONE_NAME,ABDONE_PRI,ABDONE_OPT,
					ABDONE_STACK,abDoneTask);
		taskwdInsert(abDoneId,NULL,NULL);

		/* spawn the ab change of state interrupt simulator */
		if (abCOSId) td(abCOSId);
		abCOSId = taskSpawn(ABCOS_NAME,ABCOS_PRI,ABCOS_OPT,ABCOS_STACK,ab_bi_cos_simulator);
		taskwdInsert(abCOSId,NULL,NULL);
		rebootHookAdd(ab_reboot_hook);
	}
        return(0);
}

/*
 * link_init
 *
 * establish the communication link with the AB scanner
 */
link_init(link)
register short	link;
{
	register struct ab_region	*p6008 = p6008s[link];
	char	buffer[80];
	char	*pbuffer = &buffer[0];
	register short	length;
	short mr_w_err,i;
	register struct dp_mbox *pmb = (struct dp_mbox *)(&p6008->mail);
	char		*pfirmware_info = &ab_firmware_info[link][0];

	ab_bad_response[link]=0;
	ab_rw_resp_err[link]=0;

	/* the scanner comes up with the dual ported memory locked */
	pmb->fl_lock = 0;		/* so unlock it */

	/* initialize the scanner on power up */
	if (pmb->conf_stat == SCANNER_POWERUP){
		logMsg("link %x, powerup...\n",link);
		/* on initialization the scanner puts its firmware revision info	*/
		/* into the general data are of the dual-port.  We save it here. 	*/
		/* (The most current revision is Series A, Revision D.)			*/
		strcpy(pfirmware_info,(char *)&pmb->da);
		if(ab_debug != 0)
			logMsg("link %x f/w = :\n\t%s\n",link,ab_firmware_info[link]);
		/*scanner comes up in program_mode */
		ab_op_stat[link] = PROGRAM_MODE;
		/*interrupt the scanner to wake it up */
		p6008->sc_intr = 1;
	}

	logMsg("Link %x, initial operating status word = %x\n",link,ab_op_stat[link]);	

	if((ab_op_stat[link] & PROGRAM_MODE) != PROGRAM_MODE){
		/* This link must already be initialized.  We're done */
		logMsg("Link %x already initialized\n",link);
		return(0);
	}

	/* setup scanner */
	bfill(pbuffer,80,0);
	*(pbuffer) = DEF_RATE;	/* baud rate */
	*(pbuffer+2) = DEBUG;	/* always in debug mode - don't know why */
	*(pbuffer+3) = AB_IL;	/* interrupt level */
	*(pbuffer+4) = AB_VEC_BASE+link;	/* interrupt vector */
	*(pbuffer+5) = AB_INT_ENABLE;		/* enable interrupt */
	*(pbuffer+6) = AB_SYSFAIL_DISABLE; 	/* disable VMEbus SYSFAIL sig */
	length = 7;
	if((mr_w_err = mr_wait(SET_UP,length,pbuffer,link)) != OK){
		if (ab_debug != 0)
			logMsg("link %x link_init set_up cmd - mr_wait error %x\n"
			,link,mr_w_err);
		return(-1);
	}

	/* Once scanner has been placed in RUN_MODE, putting it back into	*
	 * PROGRAM_MODE will disable binary outputs until it is placed back in	*
	 * RUN_MODE.  Some scanner commands, such as SCAN_LIST, can only be	*
	 * performed in PROGRAM_MODE.  These commands should only be issued	*
	 * immediately after initialization.  Re-booting an IOC (without powering
	 * it down) is the presently the only way of getting it into PROGRAM_MODE
	 * without disabling binary outputs */

	/* initialize scan list for each link present */
	if(scan_list(link) != OK){
		logMsg("  scan_list error on link %x\n",link);
		return(-1);
	}

	return(0);
}

/*
 *  MR_WAIT
 *
 * scanner management command send
 */
mr_wait(command,length,pmsg,link)
unsigned short	command;
short		length;
short		*pmsg;
unsigned short	link;
{
	register struct ab_region	*p6008 = p6008s[link];
	register struct dp_mbox 	*pmb = (struct dp_mbox *)&p6008->mail;
	register short 			*pmb_msg, i, lock_stat;
	short				rtn_val;
	
	/* pointer to the message portion of the mailbox */
	pmb_msg = (short *)&pmb->da;

	/* try to get access to (lock) the dual port memory */
	for(i=0; i<100; i++) {
	    if ((lock_stat = sysBusTas (&pmb->fl_lock)) == TRUE) break;
	    taskDelay(1);
	}
	if(lock_stat == FALSE) {
		if(ab_debug != 0)
		    logMsg("link %x mr_wait-dpm locked on %x cmd\n",
			link,command);
		return (LOCK);
	}
	/* reset timeout */
	ab_tout[link] = OK;


	/* clear the semaphore to make sure we stay in sync */
	if((semTake(ab_cmd_sem,NO_WAIT) == OK) && ab_debug)
		logMsg("link %x, semaphore set before mr_wait cmd %x,%x,%x\n",
		    link,command,pmb->command,pmb->conf_stat);

	/* process commands */
	pmb->command = command;
	pmb->data_len = length;
	rtn_val = OK; 			/* assume success */
	switch (command) {

	/* commands with response */
	case AUTO_CONF:
	case LINK_STATUS:
		wdStart(wd_id[link],vxTicksPerSecond*10,ab_timeout,link);
		p6008->sc_intr = 1;	/* wakeup scanner tsk */
		semTake(ab_cmd_sem, WAIT_FOREVER);
		/* was this a timeout? */
		if(ab_tout[link] == ERROR){
		    if(ab_debug != 0)
			logMsg("link %x mr_wait - timeout on %x cmd\n"
			    ,link,command);
		    rtn_val = ERROR;
		    break;
		}
		if ((pmb->conf_stat != 0) && (ab_debug != 0)){
			logMsg("link %x mr_wait cmd %x failed, status=%x\n"
			  ,link,pmb->command,pmb->conf_stat);
			rtn_val = ERROR;
			break;
		}
		if (pmb->command == command){
		    wtrans(pmb_msg,pmsg);	/* xfer data to local mailbox */
		}else{
		    ab_bad_response[link]++;
		    if(ab_debug != 0)
			logMsg("link %x mr_wait - bad resp on %x cmd\n"
			    ,link,command);
		    rtn_val = ERROR;
		}
		break;

	/* commands which send data */
	case SCAN_LIST:
	case SET_MODE:
	case SET_UP:
		wtrans(pmsg,pmb_msg);		/* xfer data to local mailbox */
		wdStart(wd_id[link],vxTicksPerSecond*5,ab_timeout,link);
		p6008->sc_intr = 1;	/* wakeup scanner */
		semTake(ab_cmd_sem, WAIT_FOREVER);
		/* was this a timeout? */
		if(ab_tout[link] == ERROR){
		    if(ab_debug != 0)
			logMsg("link %x mr_wait - timeout on %x cmd\n"
			    ,link,command);
		    rtn_val = ERROR;
		    break;
		}
		if ((pmb->conf_stat != 0) && (ab_debug != 0)){
			logMsg("link %x mr_wait cmd %x failed status %x\n"
			  ,link,pmb->command,pmb->conf_stat);
			rtn_val = ERROR;
		}
		break;
	}
	/* unlock the dual port memory */
	pmb->fl_lock = 0;
	return (rtn_val);
}

int	ab_intr_cnt[AB_MAX_LINKS];
/*
 * AB_INTR
 *
 * interrupt service routine
 *
 * The Allen-Bradley protocol requires that an interrupt be received when
 * a block transfer request is given to the scanner board through the dual
 * ported memory and then another when the command is complete. As the command
 * accept interrupt occurs while the Allen-Bradley scanner is locked out,
 * there is no danger of getting a data interrupt back. The documentation is
 * very misleading in this area: It gives no indication of how the interrupts
 * are used to accomplish asynchronous communication and implies that the 
 * dual-ported memory lock is controlled in a much different fashion than it is.
 */
ab_intr(link)
short		link;
{
	struct	ab_response		response;
	register struct ab_response	*presponse;
	register unsigned short		*presp_data;
	register unsigned short		*pdata;
	register struct dp_mbox		*pmb;
	register unsigned short		i;

	register short 		tmp_op_stat;
	register struct ab_region	*p6008 = p6008s[link];

	    pmb = &p6008s[link]->mail;

    /* Test the operating status word of link to see if adapter		  *
     * status has changed since the last time the scanner interrupted us. */
	tmp_op_stat = (p6008->osw & OSW_MASK);
	if (tmp_op_stat != ab_op_stat[link]){
/*		logMsg("old stat %x, new stat %x\n",ab_op_stat[link], tmp_op_stat);  */
		ab_op_stat[link] = tmp_op_stat;
	}

	ab_intr_cnt[link]++;
	if (((pmb->command != AB_WRITE) && (pmb->command != AB_READ)) || 
	    (ab_requesting_bt[link])){
		semGive(ab_cmd_sem);
		wdCancel(wd_id[link]);
	}else{
	    presponse = &response;

	    /* wait for the genius, who posted us, to lock the data area */
	    ab_post_no_lock[link] = 0;
	    while(TRUE) {
		unsigned char fl_lock;

		fl_lock = pmb->fl_lock;
		if(fl_lock&0x80) break;
		ab_post_no_lock[link] += 1;
		if(ab_post_no_lock[link]>=1000) {
			logMsg("link %x exceeded stop count\n",link);
			return;
		}
	    }
	    /* put the response on the queue for abDoneTask */
	    presponse->link = link;
	    presponse->card = pmb->address & 0x0f;
	    presponse->adapter = (pmb->address & 0x70) >> 4;
	    presponse->command = pmb->command;
	    presponse->status = pmb->conf_stat;
	    presp_data = &presponse->data[0];
	    pdata = &pmb->da.wd[0];
	    for (i = 0; i < 64; i++,pdata++,presp_data++)
	    	*presp_data = *pdata;
	    if (rngBufPut(ab_cmd_q,&response,sizeof(struct ab_response))
	      != sizeof(struct ab_response))
		logMsg("ab_cmd_q - Full\n");

	    /* wake up abDoneTask */
	    semGive(ab_data_sem);
	    pmb->fl_lock = 0;
	}
}

/*
 * AB_TIMEOUT
 *
 * internal watchdog timer
 */
ab_timeout(link)
int	link;
{
	/* mark a timeout error */
	ab_tout[link] = ERROR;

	/* only the command receive gives a damn */
	semGive(ab_cmd_sem);

	/* keep track in case someone wants to snoop */
	ab_comm_to[link]++;
}
static void wtrans (from, to)
register short *from, *to;
{
	register i;
	for (i=0;i<64;i++)
		*to++ = *from++;
}

/*
 * AB_AIDRIVER
 *
 * data request from the Analog Input driver
 */
ab_aidriver(card_type,link,adapter,card,signal,adapter_plc,pvalue,conversion)
unsigned short 		card_type;
register unsigned short	link;
register unsigned short adapter;
register unsigned short card;
unsigned short          signal;
unsigned short 		*pvalue;
unsigned short 		conversion;
unsigned short		adapter_plc;
{
	/* pointer to the Allen-Bradley configuration table */
        register unsigned short *pcard = &ab_config[link][adapter][card];

        /* verify that the card is initialized */
        if ((*pcard & AB_INIT_BIT) == 0){ 
	    if ((*pcard & AB_SENT_INIT) == 0){
		/* configuration data - the scan task takes over from here */
                *pcard = AB_AI_INTERFACE | card_type
                  | ((conversion << 8) & AB_CONVERSION);
		if (adapter_plc) *pcard |= AB_PLC;
	    }
	    /* sorry, but the initalization is not immediate */
            return(-1);
        }

       /* verify that link is ok and card type is correct */
	if (ab_adapter_status[link][adapter] == ERROR) return(-1);
        if ((*pcard & AB_INTERFACE_TYPE) != AB_AI_INTERFACE) return(-1);
        if ((*pcard & AB_CARD_TYPE) != card_type) return(-1);

        /* get the value from the table */
        *pvalue = ab_btdata[link][adapter][card][signal];
        if (ab_btsts[link][adapter][card][signal] < -2) return(-1);
        return(0);
}

/*
 * AB_AODRIVER
 *
 * data send from the Analog Output driver
 */
ab_aodriver(card_type,link,adapter,card,signal,adapter_plc,value)
unsigned short		card_type;
register unsigned short	link;
register unsigned short adapter;
register unsigned short card;
unsigned short          signal;
unsigned short		adapter_plc;
unsigned short          value;
{
        register unsigned short *pcard = &ab_config[link][adapter][card];

        /* verify that the card is initialized */
        if ((*pcard & AB_INIT_BIT) == 0){
	    if ((*pcard & AB_SENT_INIT) == 0){
 		/* configuration data - the scan task takes over from here */
		/* we assume the write will work and return 0 */
                *pcard = AB_AO_INTERFACE | card_type | AB_UPDATE;
		if (adapter_plc) *pcard |= AB_PLC;

		/* put the value into the table */
		ab_btdata[link][adapter][card][signal] = value;
	    }
	    return(-1);
        }

        /* put the value into the table */
	if ((unsigned short)ab_btdata[link][adapter][card][signal] == value) return(0);
        ab_btdata[link][adapter][card][signal] = value;
	*pcard |= AB_UPDATE;

        /* verify that link is ok and card type is correct */
	if (ab_adapter_status[link][adapter] == ERROR) return(-1);
	if ((*pcard & AB_INTERFACE_TYPE) != AB_AO_INTERFACE) return(-1);
        if ((*pcard & AB_CARD_TYPE) != card_type) return(-1);

        return(0);
}

/*
 * AB_AOREAD
 *
 * read the analog output value
 *	called from read_ao in ../../dblib/src/iocao.c 
 *	used to initialize AOs
 */
ab_aoread(card_type,link,adapter,card,signal,adapter_plc,pvalue)
register unsigned short card_type;
unsigned short          link;
register unsigned short adapter;
register unsigned short card;
unsigned short          signal;
unsigned short		adapter_plc;
unsigned short          *pvalue;
{
        register unsigned short *pcard = &ab_config[link][adapter][card];

        if ((*pcard & AB_INIT_BIT) == 0){
	    /* add adapter to the scan list if neccessary */
	    if ((*pcard & AB_SENT_INIT) == 0){
		if (*pcard == 0){
 			/* configuration data-scan task takes over from here */
			*pcard = AB_AO_INTERFACE | card_type;
			if (adapter_plc) *pcard |= AB_PLC;
		}
	    }
	    return(-1);
        }
	*pvalue = ab_btdata[link][adapter][card][signal];
	return(0);
}

/*
 * AB_BIDRIVER
 *
 * data read from the Binary Input driver
 */
ab_bidriver(card_type,link,adapter,card,adapter_plc,mask,pvalue)
unsigned short          card_type;
register unsigned short	link;
register unsigned short adapter;
register unsigned short card;
unsigned short		adapter_plc;
unsigned long		*pvalue;
unsigned long           mask;
{
        register struct ab_region       *p6008 = p6008s[link];
        register unsigned short *pcard = &ab_config[link][adapter][card];
        register unsigned short inpinx;
	register unsigned short	*pshort;
	/* verify there is a scanner */
        if (p6008 == 0) return(-1);

        /* claim the card as a binary input */
        if (((*pcard & AB_INIT_BIT) == 0) && ((*pcard & AB_SENT_INIT) == 0)){
            *pcard = AB_BI_INTERFACE | card_type | AB_INIT_BIT;
	    if (adapter_plc) *pcard |= AB_PLC;
	}

	/* verify link communication is OK */
	/* This doesn't work in a box with no analog IO */
	if (ab_adapter_status[link][adapter] == ERROR) return(-1);

	/* eight bit byte ordering in dual ported memory */
	/* 1100 3322 5544 7766 9988 BBAA 		 */
	if (card_type == ABBI_08_BIT){
		/* eight bit byte ordering in dual ported memory 	*/
		/* byte 1100 3322 5544 7766 9988 BBAA			*/
		inpinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	inpinx--;	/* shuffle those bytes */
		else inpinx++;
		*pvalue = p6008->iit[inpinx] & mask;
	}else{
		/* sixteen bit byte ordering in dual ported memory 	*/
		/* byte 0011 2233 4455 6677 8899 AABB			*/
		/* card 0000 2222 4444 6666 8888 AAAA			*/
		inpinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	return(-1);
		pshort = (unsigned short *)&(p6008->iit[inpinx]);
		*pvalue = *pshort & mask;
	}
	return(0);
}

/*
 * AB_BODRIVER
 *
 * data send from the Binary Output driver
 */
ab_bodriver(card_type,link,adapter,card,adapter_plc,value,mask)
unsigned short          card_type;
register unsigned short	link;
unsigned short 		adapter;
register unsigned short card;
unsigned short		adapter_plc;
unsigned long		value;
unsigned long           mask;
{
	register struct ab_region       *p6008 = p6008s[link];
	register unsigned short *pcard = &ab_config[link][adapter][card];
	register unsigned short outinx;
	register unsigned short	*pshort;

	/* verify there is a scanner */
        if (p6008 == 0) return(-1);

	/* claim the card as a binary output */
        if (((*pcard & AB_INIT_BIT) == 0) && ((*pcard & AB_SENT_INIT) == 0)){
	    *pcard = AB_BO_INTERFACE | card_type | AB_INIT_BIT;
	    if (adapter_plc) *pcard |= AB_PLC;
	}

	/* eight bit byte ordering in dual ported memory */
	/* 1100 3322 5544 7766 9988 BBAA 		 */
	if (card_type == ABBO_08_BIT){
		/* eight bit byte ordering in dual ported memory 	*/
		/* byte 1100 3322 5544 7766 9988 BBAA			*/
		outinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	outinx--;	/* shuffle those bytes */
		else outinx++;
		p6008->oit[outinx] = (p6008->oit[outinx] & ~mask) | value;
	}else{
		/* sixteen bit byte ordering in dual ported memory 	*/
		/* byte 0011 2233 4455 6677 8899 AABB			*/
		/* card 1111 3333 5555 7777 9999 BBBB			*/
		outinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	outinx--;	/* shuffle those bytes */
		else 		return(-1);
		pshort = (unsigned short *)&(p6008->oit[outinx]);
		*pshort = (*pshort & ~mask) | value;
	}
	/* verify link communication is OK */
	/* This doesn't work in a box with no analog IO */
	if (ab_adapter_status[link][adapter] == ERROR) return(-1);
	return(0);
}
/*
 * AB_BOREAD
 *
 * read the binary output channel
 */
ab_boread(card_type,link,adapter,card,pvalue,mask)
unsigned short          card_type;
unsigned short          link;
register unsigned short adapter;
register unsigned short card;
register unsigned long  *pvalue;
unsigned long           mask;
{
	register struct ab_region       *p6008 = p6008s[link];
	register unsigned short outinx;
	register unsigned short	*pshort;

	/* verify there is a scanner */
        if (p6008 == 0) return(-1);

	/* verify link communication is OK */
	/* This doesn't work in a box with no analog IO */
	if (ab_adapter_status[link][adapter] == ERROR) return(-1);

	/* eight bit byte ordering in dual ported memory */
	/* 1100 3322 5544 7766 9988 BBAA 		 */
	if (card_type == ABBO_08_BIT){
		/* eight bit byte ordering in dual ported memory 	*/
		/* byte 1100 3322 5544 7766 9988 BBAA			*/
		outinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	outinx--;	/* shuffle those bytes */
		else outinx++;
		*pvalue = p6008->oit[outinx] & mask;
	}else{
		/* sixteen bit byte ordering in dual ported memory 	*/
		/* byte 0011 2233 4455 6677 8899 AABB			*/
		/* card 1111 3333 5555 7777 9999 BBBB			*/
		outinx =  (adapter * AB_CARD_ADAPTER) + card;
		if (card & 0x1)	outinx--;	/* shuffle those bytes */
		else 		return(-1);
		pshort = (unsigned short *)(&p6008->oit[outinx]);
		*pvalue = *pshort & mask;
	}
	return(0);
}

/*
 * LINK_STATUS
 *
 * Fetches the status of the adapters on the specified link
 * The ab_adapter_status table is used to determine hardware communication
 * errors and convey them to the database
 */
link_status(link)
unsigned short	link;
{
        short		buffer[200];
        register short	*pbuffer = &buffer[0];
	register short	i, mr_w_err;

	/* for no card present - mark all adapters as offline */
	if (p6008s[link] == 0){
		for (i = 0; i < AB_MAX_ADAPTERS; i++){
			ab_adapter_status[link][i] = -1;
		}
		return(0);
	}

	/* get the link status */
	if((mr_w_err = mr_wait(LINK_STATUS,0,pbuffer,link)) != OK){
		if (ab_debug != 0)
		    logMsg("  link_status cmd error\n");
                ab_link_to[link]++;
		return(-1);
        }else{
		/* check each adapter on this link */
		for (i = 0; i< AB_MAX_ADAPTERS; i++){
			/* good status */
			if (*(pbuffer+(i*4)) & 0x70){
				if (ab_adapter_status[link][i] == -1) {
			            printf("link %d adapter %d change bad to good\n"
				      ,link,i);
				ab_adapter_status_change[link][i]=1;
				}
				ab_adapter_status[link][i] = 0;
			/* bad status */
			}else {
				if (ab_adapter_status[link][i] == 0){
				    printf("link %d adapter %d change good to bad\n"
					  ,link,i);
				ab_adapter_status_change[link][i]=1;
				}
				ab_adapter_status[link][i] = -1;
			}
		}
        }
	return(0);
}

/*
 * SCAN_LIST
 *
 * sets the scan list for the Allen-Bradley scanner
 */
scan_list(link)
unsigned short	link;
{
	register short	i,sl_inx,fail,mr_w_err;
	char	buffer[80];
	register char	*pbuffer = &buffer[0];

	/* for no card present - mark all adapters as offline */
	if (p6008s[link] == 0)	return(0);


	/* A promised firmware change will allow us to RESET
	   the scanner over the vmeBus.  For now, the only way
	   to get the scanner into prog-mode without having
	   the BO's glitch is with the vmeBus SYSRESET signal,
	   which occurs when the RESET switch on the VME chassis 
	   is used.  Until the f/w change is made, changing the
	   scanner from run mode to program mode (to modify the
	   scan list, for instance) will cause the BO's to turn 
	   off until the scanner is returned to run mode.  It's 
	   not nice, but for now we'll have to assume that all
	   adapters are needed and put them all in the scan list. */

	/* build the scan list  */
	bfill(&ab_scan_list[link][0],AB_MAX_ADAPTERS,0);
	for (sl_inx = 0, i = 0; i < AB_MAX_ADAPTERS; i++){
			ab_scan_list[link][sl_inx] = (i << 2);
			sl_inx++;
	}

	/* set new scan list - assume we're in program mode */
	if((mr_w_err = mr_wait(SCAN_LIST,sl_inx,&ab_scan_list[link][0],link)) != OK){
		if (ab_debug != 0)
			logMsg("  scan_list SCAN_LIST cmd error\n");
		return(-1);
       	}


	/* place the scanner into run mode */
	bfill(pbuffer,80,0);
	*pbuffer = RUN_MODE;
	if((mr_w_err = mr_wait(SET_MODE,0,pbuffer,link)) != OK){
		if (ab_debug != 0)
			logMsg("  scan_list SET_MODE cmd error\n");
		return(-1);
	}
	return(0);
}

#define  MAX_8BIT 8
ab_io_report(level)
    short int level;
  {
	short	i,j,k,l,m,card,adapter,plc_card,x,type;
        unsigned short jrval,krval,lrval,mrval;
        unsigned long val,jval,kval,lval,mval;
        
	/* report all of the Allen-Bradley Serial Links present */
	for (i = 0; i < AB_MAX_LINKS; i++){
	    if(!p6008s[i]) continue;
	    printf("AB-6008SV:\tcard %d ",i);
	    for(adapter=0; adapter<AB_MAX_ADAPTERS; adapter++) {
		if(ab_adapter_status[i][adapter]==0)
		    printf("U");else printf("D");
	    }
	    printf(" cto: %d lto: %d badres: %d\n",
		ab_comm_to[i],ab_link_to[i],ab_bad_response[i]);
	    if(level>0) printf("%s\n",ab_firmware_info[i]);
	    /* report all cards to which the database has interfaced */
	    /* as there is no way to poll the Allen-Bradley IO to      */
	    /* determine which card is there we assume that any interface */
	    /* which is successful implies the card type is correct       */
	    /* since all binaries succeed and some analog inputs will     */
	    /* succeed for either type this is a shakey basis             */
	    for (adapter = 0; adapter < AB_MAX_ADAPTERS; adapter++){
		for (card = 0; card < AB_MAX_CARDS; card++){
                    
                    /* Determine whether the card is in a plc or not. */

                    if(ab_config[i][adapter][card] & AB_PLC){
                       plc_card = 1;
                    /*   printf("This card is a plc card.\n"); */ 
                    } else {
                       plc_card = 0;
                    /*   printf("This card is not a plc card.\n"); */ 
                    } 

		    switch (ab_config[i][adapter][card] & AB_INTERFACE_TYPE){

		    case (AB_BI_INTERFACE):
			printf("\tBI: AB\tadapter %d card %d",adapter,card);
			ab_bi_report(i,adapter,card,plc_card);
			break;
		    case (AB_BO_INTERFACE):
			printf("\tBO: AB\tadapter %d card %d",adapter,card);
                        ab_bo_report(i,adapter,card,plc_card);
			break;
		    case (AB_AI_INTERFACE):
                        type = ab_config[i][adapter][card]&AB_CARD_TYPE;    
			if ((ab_config[i][adapter][card]&AB_CARD_TYPE)==AB1771IXE){
		          printf("\tAI: AB1771IXE\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IL){
			    printf("\tAI: AB1771IL\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IrPlatinum){
			    printf("\tAI: AB1771IrPlatinum\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IrCopper){
			    printf("\tAI: AB1771IrPlatinum\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IFE_SE){
			    printf("\tAI: AB1771IFE_SE\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IFE_4to20MA){
			    printf("\tAI: AB1771IFE_4to20MA\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			      adapter,card,ab_cmd_to[i][adapter][card],
			      ab_data_to[i][adapter][card],
			      ab_scaling_error[i][adapter][card],
			      ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IFE){
			      printf("\tAI: AB1771IFE\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			         adapter,card,ab_cmd_to[i][adapter][card],
			         ab_data_to[i][adapter][card],
			         ab_scaling_error[i][adapter][card],
			         ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                        } else if ((ab_config[i][adapter][card] & AB_CARD_TYPE) == AB1771IFE_0to5V){
			      printf("\tAI: AB1771IFE_0to5V\tadapter %d card %d:\tcto: %d dto: %d sclerr: %d %d",
			         adapter,card,ab_cmd_to[i][adapter][card],
			         ab_data_to[i][adapter][card],
			         ab_scaling_error[i][adapter][card],
			         ab_or_scaling_error[i][adapter][card]);
                              if (level > 0){
                              	ab_ai_report(type,i,adapter,card,plc_card);
                              }
                         } 
			break;
		    case (AB_AO_INTERFACE):
			printf("\tAO: AB1771OFE\tadapter %d card %d:\tcto: %d dto: %d",
			  adapter,card,ab_cmd_to[i][adapter][card],
			  ab_data_to[i][adapter][card]);
                              if (level > 0 ){
                             	ab_ao_report(AB1771OFE,i,adapter,card,plc_card,&jrval,0);
                              }
			break;
		    default:
			continue;
		    }
		    if ((ab_config[i][adapter][card] & AB_INIT_BIT) == 0)
			printf(" NOT INITIALIZED\n");
		    else {
                        printf("\n");
                    }  
		}
	    }
	}

 }

ab_bi_report(link,adapter,card,plc_card)
   short link,adapter,card,plc_card;
  {
        unsigned short type;
        unsigned long value;

        type = ab_config[link][adapter][card] & AB_CARD_TYPE;
	ab_bidriver(type,link,adapter,card,plc_card,0xffffffff,&value);
	printf(" value=%08.8x",value);
  }

ab_bo_report(link,adapter,card)
   short link,adapter,card;
  {
        unsigned short type;
        unsigned long value;


        type = ab_config[link][adapter][card] & AB_CARD_TYPE;
	ab_boread(type,link,adapter,card,&value,0xffffffff);
  }

ab_ai_report(type,link,adapter,card,plc_card)
      
   unsigned short type;
   short link,adapter,card,plc_card;
  {
	short	i,num_chans;
        unsigned short value;

	num_chans = ai_num_channels[type];
	for(i=0; i<num_chans; i++) {
	    if(i%8 == 0) printf("\n\t");
	    ab_aidriver(type,link,adapter,card,i,plc_card,&value,0);
	    printf(" %4x",value);
 	}
  }

ab_ao_report(type,link,adapter,card,plc_card)
   unsigned short type;
   short link,adapter,card,plc_card;
  {
	short	i,num_chans;
        unsigned short value;

        printf("\n\t");
	num_chans = ao_num_channels[type];
	for(i=0; i<num_chans; i++) {
	    ab_aoread(type,link,adapter,card,i,plc_card,&value);
	     printf("%4x",value);
	}
}

/*
 * abScanTask
 *
 * Scans the AB IO according to the AB configuration table.
 * Entries are made in the AB configuration table when an IO
 * interface is attempted from the database scan tasks.
 * The sleep time assures that there is at least 1/10 second between the passes.
 * The time through the scan loop seems to be minimal so there are no provisions
 * for excluding the scan time from the sleep time.
 */

abScanTask(){
register unsigned short	adapter,link;
register short pass=0;
register int tmp_op_stat;
struct ab_region	*p6008;
int status;

    while(TRUE){
	pass++;
        p6008 =(struct ab_region *)ab_stdaddr;
	/* check each link */

	for (link=0; link < AB_MAX_LINKS; link++,p6008++){
		if(ab_disable) break;
		if (p6008s[link] == 0) continue;	/* no link */

		/* See if we've detected any unsolicited block transfers.	*/
		/* These can result if scanner writes discrete info to a slot	*/
		/* requiring a block transfer.					*/
		if(ab_op_stat[link] & UNSOLICITED_BT){
		    if (ab_debug != 0) 
			logMsg("link %x, unsolicited_block xfer\n",link);
			p6008->osw = 0;	/* scanner depends on us to clear some bits */
		}

		
		/* check each adapter */
		for (adapter = 0; adapter < AB_MAX_ADAPTERS; adapter++){
			if (ab_adapter_status[link][adapter] >= 0)
				read_ab_adapter(link,adapter,pass);
		}

		/* Every second perform a link check to see if any adapters */
		/* have  changed state.  (Don't want to queue up requests if*/
		/* they're off) */
		if((pass % 10) == 0){
			if (link_status(link) != 0){
				if(ab_debug)
					logMsg("%x link_stat error %x\n",link);
			}
		}

	}

	/* run every 1/10 second */
	taskDelay(vxTicksPerSecond/10);
    }
}

/* ab_reboot_hook - routine to call when IOC is rebooted with a control-x */

int ab_reboot_hook(boot_type)
int	boot_type;
{
	short i;
	static char	wait_msg[] = {"I Hate to WAIT"};
	register char	*pmsg = &wait_msg[0];

	/* Stop communication to the Allen-Bradley Scanner Cards */
	if (abScanId != 0){
		/* flag the analog output and binary IO routines to stop */ 
		ab_disable = 1;

		/* delete the scan task stops analog input communication */
		taskDelete(abScanId);

		/* this seems to be necessary for the AB card to stop talking */
		printf("\nReboot: delay ");
		for(i = 0; i <= 14; i++){
			printf("%c",*(pmsg+i));
			taskDelay(20);
		}
	}
}

/*
 *
 * ab_reset_task()
 *
 *
 *
 */
void
ab_reset_task()
{
	short			link,adapter,card;
	struct ab_region	*pab_region=0;

	/* keep track of the status and frequency */
	if (reset_cnt < 100){
		reset_code[reset_cnt] = pab_region->mail.conf_stat;
		reset_cnt++;
	}

	/* disable scanning during reset */
	ab_disable = 1;
	printf("Disabled AB Scanner Task\n");
	taskDelay(vxTicksPerSecond*2);
	/* Signal the Scanner to Reset */
	for(link=0; link<AB_MAX_LINKS; link++){
	    pab_region = p6008s[link];
	    if(!pab_region) continue;
	    pab_region->sys_fail_set2 = 0xa0a0;
	    pab_region->sys_fail_set1 = 0x0080;
	    printf("Card %d Reset\n",0);
	}

	ab_reset_wait = 0;
	for(link=0; link<AB_MAX_LINKS; link++){
	    pab_region = p6008s[link];
	    if(!pab_region) continue;
	    /*mark all block transfer cards for initialization*/
	    for(adapter = 0; adapter < AB_MAX_ADAPTERS; adapter++){
		for (card = 0; card < AB_MAX_CARDS; card++){
		    switch (ab_config[link][adapter][card] & AB_INTERFACE_TYPE){
		    case (AB_AO_INTERFACE):
			 ab_config[link][adapter][card] |= AB_UPDATE;
			 break;
		    case (AB_AI_INTERFACE):
			ab_config[link][adapter][card]
			    &= ~(AB_INIT_BIT | AB_SENT_INIT);
	    		ab_btq_cnt[link][adapter][card] = 0;
			break;
		    default:
		        break;
		    }
		}
	    }
	    while(pab_region->mail.conf_stat != SCANNER_POWERUP){
		taskDelay(1);
		ab_reset_wait++;
	    }
	}
	printf("Link Power Up After %d Ticks\n",ab_reset_wait);
	/* Reinitialize the Link */
	for(link=0; link<AB_MAX_LINKS; link++){
	    pab_region = p6008s[link];
	    if(!pab_region) continue;
	    link_init(link);
	}
	/* enable the scanner */
	ab_disable = 0;
}

/*
 *
 * ab_reset()
 *
 */
int
ab_reset()
{
	int status;

	status = taskSpawn("ab_reset",38,0,8000,ab_reset_task);
	return status;
}
