/*	$Id$
 *   DESCRIPTION: Channel access interface for sequencer.
 *
 *	Author:  Andy Kozubal
 *	Date:    July, 1991
 *
 *	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
 *
 * Modification Log:
 * -----------------
 * 03jul91,ajk	.
 * 11dec91,ajk	Cosmetic changes (comments & names)
 * 13feb92,ajk	All seqLog() calls compile only if DEBUG is defined.
 * 28apr92,ajk	Implemented new event flag mode.
 * 21may92,ajk	Will periodically announce number of connected channels
 *		if waiting form some to connect.
 * 17feb93,ajk	Implemented code to allow sharing of a single CA task by
 *		all state programs.  Added seq_disconnect() and ca_import_cancel().
		DB name resolution was moved to seq_main.c.
 * 19feb93,ajk	Added patched version of VxWorks 5.02b taskVarDelete().
 */

#define		ANSI
#include	"seq.h"

LOCAL VOID *getPtrToValue(union db_access_val *, chtype);

/*#define		DEBUG*/

/*
 * seq_connect() - Connect to all database channels through channel access.
 */
seq_connect(pSP)
SPROG		*pSP;
{
	CHAN		*pDB;
	int		status, i;
	extern		VOID seq_conn_handler();
	extern int	seqInitialTaskId;

	pSP->conn_count = 0;

	/*
	 * For each channel: substitute macros, connect to db,
	 * & isssue monitor (if monitor request flaf is TRUE).
	 */
	pDB = pSP->channels;
	for (i = 0; i < pSP->nchan; i++)
	{
#ifdef	DEBUG
		printf("connect to \"%s\"\n", pDB->db_name);
#endif	DEBUG
		/* Connect to it */
		status = ca_build_and_connect(
			pDB->db_name,		/* DB channel name */
			TYPENOTCONN,		/* Don't get initial value */
			0,			/* get count (n/a) */
			&(pDB->chid),		/* ptr to chid */
			0,			/* ptr to value (n/a) */
			seq_conn_handler,	/* connection handler routine */
			pDB);			/* user ptr is CHAN structure */
		if (status != ECA_NORMAL)
		{
			SEVCHK(status, "ca_search");
			ca_task_exit();
			return -1;
		}
		/* Clear monitor indicator */
		pDB->monitored = FALSE;

		/*
		 * Issue monitor request
		 */
		if (pDB->mon_flag)
		{
			seq_pvMonitor(pSP, 0, pDB);
		}
		pDB++;
	}
	ca_flush_io();
	return 0;
}
/*
 * seq_event_handler() - Channel access events (monitors) come here.
 * args points to CA event handler argument structure.  args.usr contains
 * a pointer to the channel structure (CHAN *).
 */
VOID seq_event_handler(args)
struct	event_handler_args args;
{
	SPROG			*pSP;
	CHAN			*pDB;
	struct dbr_sts_char	*dbr_sts_ptr;
	void			*pVal;
	int			i, nbytes;

	/* User arg is ptr to db channel structure */
	pDB = (CHAN *)args.usr;

	/* Copy value returned into user variable */
	pVal = getPtrToValue((union db_access_val *)args.dbr, pDB->get_type);
	nbytes = pDB->size * pDB->count;
	bcopy(pVal, pDB->var, nbytes);

	/* Copy status & severity */
	dbr_sts_ptr = (struct dbr_sts_char *)args.dbr;
	pDB->status = dbr_sts_ptr->status;
	pDB->severity = dbr_sts_ptr->severity;

	/* Process event handling in each state set */
	pSP = pDB->sprog;	/* State program that owns this db entry */

	/* Wake up each state set that is waiting for event processing */
	seq_efSet(pSP, 0, pDB->index + 1);

	return;
}
/*	Disconnect all database channels */
seq_disconnect(pSP)
SPROG		*pSP;
{
	CHAN		*pDB;
	STATUS		status;
	int		i;
	extern int	seqAuxTaskId;

	/* Did we already disconnect? */
	if (pSP->conn_count < 0)
		return 0;

	/* Import Channel Access context from the auxillary seq. task */
	ca_import(seqAuxTaskId);

	pDB = pSP->channels;
	for (i = 0; i < pSP->nchan; i++)
	{
#ifdef	DEBUG
		printf("disconnect \"%s\"\n", pDB->db_name);
#endif	DEBUG
		/* Disconnect this channel */
		status = ca_clear_channel(pDB->chid);

		if (status != ECA_NORMAL)
		{
			SEVCHK(status, "ca_clear_chan");
			ca_task_exit();
			return -1;
		}

		/* Clear monitor & connect indicators */
		pDB->monitored = FALSE;
		pDB->connected = FALSE;

		pDB++;
	}

	pSP->conn_count = -1; /* flag to indicate all disconnected */

	ca_flush_io();

	/* Cancel CA import */
	ca_import_cancel(taskIdSelf());

	return 0;
}

/*
 * seq_conn_handler() - Sequencer connection handler.
 * Called each time a connection is established or broken.
 */
VOID seq_conn_handler(args)
struct connection_handler_args	args;
{
	CHAN		*pDB;
	SPROG		*pSP;

	/* User argument is db ptr (specified at ca_search() ) */
	pDB = (CHAN *)ca_puser(args.chid);

	/* State program that owns this db entry */
	pSP = pDB->sprog;

	/* Check for connected */
	if (ca_field_type(args.chid) == TYPENOTCONN)
	{
		pDB->connected = FALSE;
		pSP->conn_count--;
#ifdef	DEBUG
		seq_log(pSP, "Channel \"%s\" disconnected\n", pDB->db_name);
#endif	DEBUG
	}
	else
	{
		pDB->connected = TRUE;
		pSP->conn_count++;
#ifdef	DEBUG
		seq_log(pSP, "Channel \"%s\" connected\n", pDB->db_name);
#endif	DEBUG
		if (pDB->count > ca_element_count(args.chid))
		{
			pDB->count = ca_element_count(args.chid);
#ifdef	DEBUG
			seq_log(pSP, "\"%s\": reset count to %d\n",
			 pDB->db_name, pDB->count);
#endif	DEBUG
		}
	}

	/* Wake up each state set that is waiting for event processing */
	seq_efSet(pSP, 0, 0);
	
	return;
}

/*
 * seq_efSet() - Set an event flag, then wake up each state
 * set that might be waiting on that event flag.
 */
VOID seq_efSet(pSP, dummy, ev_flag)
SPROG		*pSP;
int		dummy;
int		ev_flag;	/* event flag */
{
	SSCB		*pSS;
	int		nss;

	/* Set this bit (apply resource lock) */
	semTake(pSP->caSemId, WAIT_FOREVER);
	bitSet(pSP->events, ev_flag);

	/* Check flag against mask for all state sets: */
	for (nss = 0, 	pSS = pSP->sscb; nss < pSP->nss; nss++, pSS++)
	{
		/* Test for possible event trig based on bit mask for this state */
		if ( (ev_flag == 0) || bitTest(pSS->pMask, ev_flag) )
		{
			semGive(pSS->syncSemId); /* wake up the ss task */
		}

	}

	/* Unlock resource */
	semGive(pSP->caSemId);
}

/*
 * seq_efTest() - Test event flag against outstanding events.
 */
int seq_efTest(pSP, pSS, ev_flag)
SPROG		*pSP;
SSCB		*pSS;
int		ev_flag;	/* event flag */
{
	return bitTest(pSP->events, ev_flag);
}

/*
 * seq_efClear() - Test event flag against outstanding events, then clear it.
 */
int seq_efClear(pSP, pSS, ev_flag)
SPROG		*pSP;
SSCB		*pSS;
int		ev_flag;	/* event flag */
{
	int		isSet;

	isSet = bitTest(pSP->events, ev_flag);
	bitClear(pSP->events, ev_flag);
	return isSet;
}
/*
 * seq_pvGet() - Get DB value (uses channel access).
 */
seq_pvGet(pSP, pSS, pDB)
SPROG	*pSP;	/* ptr to state program */
SSCB	*pSS;	/* ptr to current state set */
CHAN	*pDB;	/* ptr to channel struct */
{
	int		status, sem_status;
	extern		VOID seq_callback_handler();

	/* Check for channel connected */
	if (!pDB->connected)
		return ECA_DISCONN;

	/* Flag this pvGet() as not completed */
	pDB->get_complete = FALSE;

	/* If synchronous pvGet then clear the pvGet pend semaphore */
	if ( !(pSP->options & OPT_ASYNC) )
	{
		pDB->getSemId = pSS->getSemId;
		semTake(pSS->getSemId, NO_WAIT);
	}

	/* Perform the CA get operation with a callback routine specified */
	status = ca_array_get_callback(
			pDB->get_type,		/* db request type */
			pDB->count,		/* element count */
			pDB->chid,		/* chid */
			seq_callback_handler,	/* callback handler */
			pDB);			/* user arg */

	if ( (pSP->options & OPT_ASYNC) || (status != ECA_NORMAL) )
		return status;

	/* Synchronous pvGet() */

	ca_flush_io();

	/* Wait for completion (10s timeout) */	
	sem_status = semTake(pSS->getSemId, 600);
	if (sem_status == ERROR)
		status = ECA_TIMEOUT;
        
	return status;
}

/*
 * seq_callback_handler() - Sequencer callback handler.
 * Called when a "get" completes.
 * args.usr points to the db structure (CHAN *) for tis channel.
 */
VOID seq_callback_handler(args)
struct event_handler_args	args;
{
	SPROG			*pSP;
	CHAN			*pDB;
	struct dbr_sts_char	*dbr_sts_ptr;
	int			i, nbytes;
	void			*pVal;

	/* User arg is ptr to db channel structure */
	pDB = (CHAN *)args.usr;

	/* Copy value returned into user variable */
	pVal = getPtrToValue((union db_access_val *)args.dbr, pDB->get_type);
	nbytes = pDB->size * pDB->count;
	bcopy(pVal, pDB->var, nbytes);

	/* Copy status & severity */
	dbr_sts_ptr = (struct dbr_sts_char *)args.dbr;
	pDB->status = dbr_sts_ptr->status;
	pDB->severity = dbr_sts_ptr->severity;

	/* Set get complete flag */
	pDB->get_complete = TRUE;

	/* Wake up each state set that is waiting for event processing) */
	pSP = pDB->sprog;	/* State program that owns this db entry */
	seq_efSet(pSP, 0, pDB->index + 1);

	/* If syncronous pvGet then notify pending state set */
	if ( !(pSP->options & OPT_ASYNC) )
		semGive(pDB->getSemId);

	return;
}

/* Flush outstanding CA requests */
VOID seq_pvFlush()
{
	ca_flush_io();
}	

/*
 * seq_pvPut() - Put DB value (uses channel access).
 */
seq_pvPut(pSP, pSS, pDB)
SPROG	*pSP;	/* ptr to state program */
SSCB	*pSS;	/* ptr to current state set */
CHAN	*pDB;	/* ptr to channel struct */
{
	int	status;

	if (!pDB->connected)
		return ECA_DISCONN;

	status = ca_array_put(pDB->put_type, pDB->count,
	 pDB->chid, pDB->var);
	if (status != ECA_NORMAL)
	{
#ifdef	DEBUG
		seq_log(pSP, "pvPut on \"%s\" failed (%d)\n",
		 pDB->db_name, status);
		seq_log(pSP, "  put_type=%d\n", pDB->put_type);
		seq_log(pSP, "  size=%d, count=%d\n", pDB->size, pDB->count);
#endif	DEBUG
	}

	return status;
}
/*
 * seq_pvMonitor() - Initiate a monitor on a channel.
 */
seq_pvMonitor(pSP, pSS, pDB)
SPROG	*pSP;	/* ptr to state program */
SSCB	*pSS;	/* ptr to current state set */
CHAN	*pDB;	/* ptr to channel struct */
{
	int		status;
	extern		VOID seq_event_handler();

#ifdef	DEBUG
	printf("monitor \"%s\"\n", pDB->db_name);
#endif	DEBUG

	if (pDB->monitored)
		return;

	status = ca_add_array_event(
		 pDB->get_type,		/* requested type */
		 pDB->count,		/* element count */
		 pDB->chid,		/* chid */
		 seq_event_handler,	/* function to call */
		 pDB,			/* user arg (db struct) */
		 pDB->delta,		/* pos. delta value */
		 pDB->delta,		/* neg. delta value */
		 pDB->timeout,		/* timeout */
		 &pDB->evid);		/* where to put event id */

	if (status != ECA_NORMAL)
	{
		SEVCHK(status, "ca_add_array_event");
		ca_task_exit();	/* this is serious */
		return;
	}
	ca_flush_io();

	pDB->monitored = TRUE;
	return;
}

/*
 * seq_pvStopMonitor() - Cancel a monitor
 */
seq_pvStopMonitor(pSP, pSS, pDB)
SPROG	*pSP;	/* ptr to state program */
SSCB	*pSS;	/* ptr to current state set */
CHAN	*pDB;	/* ptr to channel struct */
{
	int		status;

	if (!pDB->monitored)
		return -1;

	status = ca_clear_event(pDB->evid);
	if (status != ECA_NORMAL)
	{
		SEVCHK(status, "ca_clear_event");
		return status;
	}

	pDB->monitored = FALSE;

	return status;
}

/* 
 * getPtr() - Given ptr to value structure & type, return ptr to value.
 */
LOCAL VOID *getPtrToValue(pBuf, dbrType)
union db_access_val *pBuf;
chtype	dbrType;
{
    switch (dbrType) {
	case DBR_STRING:	return (void *)pBuf->strval;
	case DBR_STS_STRING:	return (void *)pBuf->sstrval.value;

	case DBR_SHORT:		return (void *)&pBuf->shrtval;
	case DBR_STS_SHORT:	return (void *)&pBuf->sshrtval.value;

	case DBR_FLOAT:		return (void *)&pBuf->fltval;
	case DBR_STS_FLOAT:	return (void *)&pBuf->sfltval.value;

	case DBR_ENUM:		return (void *)&pBuf->enmval;
	case DBR_STS_ENUM:	return (void *)&pBuf->senmval.value;

	case DBR_CHAR:		return (void *)&pBuf->charval;
	case DBR_STS_CHAR:	return (void *)&pBuf->schrval.value;

	case DBR_LONG:		return (void *)&pBuf->longval;
	case DBR_STS_LONG:	return (void *)&pBuf->slngval.value;

	case DBR_DOUBLE:	return (void *)&pBuf->doubleval;
	case DBR_STS_DOUBLE:	return (void *)&pBuf->sdblval.value;
	default:		return NULL;
    }
}
#include "memLib.h"
#include "taskVarLib.h"
#include "taskLib.h"
/*******************************************************************************
* P A  T C H E D  5.02b -- allows call from taskDeleteHook routine -- ajk
* taskVarDelete - remove a task variable from a task
*
* This routine removes the specified task variable from the calling
* task's context.  The private value of that variable is lost.
*
* RETURNS
* OK, or
* ERROR if the calling task does not own the specified task variable.
*
* SEE ALSO: taskVarAdd(2), taskVarGet(2), taskVarSet(2)
*/

LOCAL STATUS taskVarDelete (tid, pVar)
    int tid;	/* task id whose task variable is to be retrieved */
    int *pVar;	/* pointer to task variable to be removed from task */

    {
    FAST TASK_VAR **ppTaskVar;		/* ptr to ptr to next node */
    FAST TASK_VAR *pTaskVar;
    WIND_TCB *pTcb = (WIND_TCB *)tid;	/* P A T C H -- ajk 19feb93*/

    if (pTcb == NULL)			/* check that task is valid */
	return (ERROR);

    /* find descriptor for specified task variable */

    for (ppTaskVar = &pTcb->pTaskVar;
	 *ppTaskVar != NULL;
	 ppTaskVar = &((*ppTaskVar)->next))
	{
	pTaskVar = *ppTaskVar;

	if (pTaskVar->address == pVar)
	    {
	    /* if active task, replace background value */

	    if (taskIdCurrent == pTcb)
		*pVar = pTaskVar->value;

	    *ppTaskVar = pTaskVar->next;/* delete variable from list */

	    free ((char *)pTaskVar);	/* free storage of deleted cell */

	    return (OK);
	    }
	}

    /* specified address is not a task variable for specified task */

    errnoSet (S_taskLib_TASK_VAR_NOT_FOUND);
    return (ERROR);
    }
/* Temporary routine to cancel ca_import() -- THIS SHOULD GO INTO CHANNEL ACCESS! */
ca_import_cancel(tid)
int		tid;
{
	extern int	ca_static;
	int		status;

	status = taskVarDelete(tid, &ca_static);
	if (status != OK)
	{
		logMsg("Seq: taskVarDelete failed for tid = 0x%x\n", tid);
	}

	return status;
}
