CIsignalMonitor.c

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

	CIsignalMonitor.c			Source for BitFlow CI CIsignalMonitor program

	Apr 13,		2022	BitFlow, Inc./JTG

	© Copyright 2022, BitFlow, Inc. All rights reserved.

	Tabstops are 4

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

/*==========================================================================*/
/*
**	For access to command line display.
*/
#include	<stdlib.h>
#include	<stdio.h>
#include	<stdarg.h>
#include	<string.h>
/*
**	For case insensitive string comparison.
*/
#include	<ctype.h>
/*
**	For checking for keypress
*/
#include	<sys/time.h>
#include	<sys/types.h>
#include	<unistd.h>
/*
**	For event wait threads.
*/
#include	<pthread.h>
#include	<errno.h>
/*
**	For access to BitFlow camera interface library.
*/
#include	"BFciLib.h"
/*==========================================================================*/
/*
**	Lookup tables for signal names and operations.
*/
typedef struct
{
	tCIU32 ndx;
	tCIU32 linkMask;
	const char *name;
} tNamedEnumEntry;

typedef const tNamedEnumEntry *tNamedEEP;

static const tNamedEnumEntry sSigNamesTable[] = {
	{kCIsigHW, 0, "HW"},
	{kCIsigTRIG, 0, "TRIG"},
	{kCIsigSERIAL, 0, "SERIAL"},
	{kCIsigQUAD, 0, "QUAD"},
	{kCIsigCTAB, 0, "CTAB"},
	{kCIsigEOF, 0, "EOF"},
	{kCIsigOVSTEP, 0, "OVSTEP"},
	{kCIsigGn2xAcquired, 0, "Gn2xAcquired"},
	{kCIsigGn2zAcquired, 0, "Gn2zAcquired"},
	{kCIsigGn2bmError, 0, "Gn2bmError"},
	{kCIsigGn2aeLossOfSync, 0, "Gn2aeLossOfSync"},
	{kCIsigGn2pciePktDropped, 0, "Gn2pciePktDropped"},
	{kCIsigGn2encA, 0, "Gn2encA"},
	{kCIsigGn2encB, 0, "Gn2encB"},
	{kCIsigGn2xStart, 0, "Gn2xStart"},
	{kCIsigGn2yStart, 0, "Gn2yStart"},
	{kCIsigGn2zStart, 0, "Gn2zStart"},
	{kCIsigCXPunderCurrent, 255, "CXPunderCurrent"},
	{kCIsigCXPoverCurrent, 255, "CXPoverCurrent"},
	{kCIsigCXPtrigAckRcvd, 255, "CXPtrigAckRcvd"},
	{kCIsigCXPctlAckVerErr, 255, "CXPctlAckVerErr"},
	{kCIsigCXPctlAckRcvd, 255, "CXPctlAckRcvd"},
	{kCIsigCXPeventRcvd, 255, "CXPeventRcvd"},
	{kCIsigCXPeventFIFOoverflow, 255, "CXPeventFIFOoverflow"},
	{kCIsigCXPctlRspFIFOovf, 255, "CXPctlRspFIFOovf"},
	{kCIsigCXPctlReqFIFOovf, 255, "CXPctlReqFIFOovf"},
	{kCIsigCXPdownTrigRcvd, 255, "CXPdownTrigRcvd"},
	{kCIsigCXPtrigNoMatch, 255, "CXPtrigNoMatch"},
	{kCIsigCXPioackUnknownType, 255, "CXPioackUnknownType"},
	{kCIsigCXPioackNoMatch, 255, "CXPioackNoMatch"},
	{kCIsigCXPhbError, 255, "CXPhbError"},
	{kCIsigCXPhbRcvd, 255, "CXPhbRcvd"},
	{kCIsigCXPstrmPktDrop, 255, "CXPstrmPktDrop"},
	{kCIsigCXPstrmNotEnoughDat, 255, "CXPstrmNotEnoughDat"},
	{kCIsigCXPstrmTooMuchDat, 255, "CXPstrmTooMuchDat"},
	{kCIsigCXPstrmBadCRC, 255, "CXPstrmBadCRC"},
	{kCIsigCXPstrmOverflow, 255, "CXPstrmOverflow"},
	{kCIsigCXPstrmCorner, 255, "CXPstrmCorner"},
	{kCIsigCXPserdesLostAlign, 255, "CXPserdesLostAlign"},
	{0, 0, NULL}
};

/*--------------------------------------------------------------------------*/
/*
**	Signal thread data type.
*/
typedef struct
{
	tNamedEEP	entry;		/* named signal entry */
	tCIU32		link;		/* cxp link */
	tCISHP		hSignal;	/* signal handle */
	tCISHP		hCancelSignal;	/* signal handle for CiSignalExec cancelation*/
	pthread_t	hThread;	/* thread handle */
	tCIU32		eventCount;	/* signal events processed */
	tCIU32		dropCount;	/* signal events dropped */
} tSignalThreadData, *tSTDP;
/*--------------------------------------------------------------------------*/

/*
**	Atomic getch fetch and add.
**
**	Perform an atomic fetch-and-add on the given variable, for the given
**	increment. Evaluates to the value before the increment.
*/
#define ATOMIC_ADD(ATM, INC) (__sync_fetch_and_add(&(ATM), (INC)))

/*--------------------------------------------------------------------------*/
static int sExitAns = 0;		/* program exit code */
static tCIp sCIp = NULL;		/* device open token */
static int sNdx = 0;			/* device index */
static tCIU32 sCXPlink = 0xFF;	/* cxp link to monitor (default master) */
static tCIU32 sMaxSigs = 0;		/* total signal events to display */
static volatile tCIU32 sRcvdSigs = 0;	/* total count of signals received (atomic) */
static tCIU32 sSkipSigs = 0;	/* signal events to skip between display */
static tCIU32 sSigCount = 0;	/* the cound of signals tp monitor */
static tCIU32 sTimeoutMs = 0;	/* timeout after N ms of no signals */
static tNamedEEP sSigEntryAry[kCIsigCOUNT];	/* array of signals to monitor */

/*--------------------------------------------------------------------------*/
int StrICmp (const char *lStr, const char *rStr)
/*
**	Perform a case insensitive string comparison. Returns 0 if the strings
**	are equivalent, non-zero, otherwise.
*/
{
	if (NULL == lStr)
		return -1;
	else if (NULL == rStr)
		return 1;

	while ('\0' != (*lStr) && tolower(*lStr) == tolower(*rStr))
	{
		lStr++;
		rStr++;
	}

	return(tolower(*lStr) - tolower(*rStr));
}

/*--------------------------------------------------------------------------*/
tNamedEEP FindNamedEntry (tNamedEEP entryTable, const char *name)
/*
**	Find the named entry in the table. Comparison is case insensitive.
*/
{
	while (NULL != entryTable->name)
	{
		if (StrICmp(entryTable->name, name) == 0)
			return entryTable;
		entryTable++;
	}
	return(NULL);
}

/*--------------------------------------------------------------------------*/
#define	SHOW(x)	{ (void)printf x ; (void)fflush(stdout); }
#define	ERR(x)	{ SHOW(("ERR: ")); SHOW(x); }

static char *sArgv0 = NULL;		/* name of executable */
static void ShowHelp(void)
{
	SHOW(("%s of " __DATE__ " at " __TIME__ "\n", sArgv0));
	SHOW(("   -h           display this message and exit\n"));
	SHOW(("   -a           print a list of known signals and exit\n"));
	SHOW(("\n"));
	SHOW(("   -x ndx       choose available device ndx (default 0)\n"));
	SHOW(("   +sigName     add one signal to monitor (case insensitive)\n"));
	SHOW(("   -l lnk       cxp link to use for cxp signals (default master)\n"));
	SHOW(("   -m maxSigs   max signal events to receive (default infinite)\n"));
	SHOW(("   -s skipSigs  signals to skip between display (default 0)\n"));
	SHOW(("   -t mSecs     timeout after mSecs of no signals received (default infinite)\n"));
	SHOW(("\n"));
	SHOW(("  : initialize an interface and display hi-level signals as they occur\n"));
	SHOW(("      display ends with newline\n"));
	SHOW(("\n"));
}
static void ShowSigs(void)
{
	tNamedEEP sig = sSigNamesTable;
	while (NULL != sig->name)
	{
		SHOW(("%s\n", sig->name));
		sig++;
	}
}

/*==========================================================================*/
#include	<time.h>
#include	<sys/timeb.h>
static tCIDOUBLE GetTime(void)
/*
**	Return fractional seconds
*/
{
	tCIDOUBLE ans = 0.0;

#ifdef _POSIX_TIMERS

	struct timespec tp;

	(void)clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
	ans = (tCIDOUBLE) tp.tv_sec;
	ans += ((tCIDOUBLE) tp.tv_nsec) / 1000000000.0;

#else

	struct timeb tb;

	(void)ftime(&tb);
	ans = tb.millitm;
	ans /= 1000.0;
	ans += tb.time;

#endif

	return (ans);
}

/*--------------------------------------------------------------------------*/
static int DecodeArgs(int argc, char **argv)
/*
**	Parse the input arguments.
*/
{
	char *str;
	tNamedEEP namedEntry;
	tCIU32 i = 0;

	argv += 1;
	argc -= 1;					/* skip program name */

	while (argc-- > 0)
	{
		str = *argv++;
		if ('-' == str[0])
		{
			switch (str[1])
			{
			case 'h':
				ShowHelp();
				return (1);
			case 'a':
				ShowSigs();
				return (1);
			case 'x':
				(void)sscanf(*argv, "%d", &sNdx);
				argv += 1;
				argc -= 1;
				break;
			case 'l':
				(void)sscanf(*argv, "%d", &sCXPlink);
				argv += 1;
				argc -= 1;
				break;
			case 'm':
				(void)sscanf(*argv, "%d", &sMaxSigs);
				argv += 1;
				argc -= 1;
				break;
			case 's':
				(void)sscanf(*argv, "%d", &sSkipSigs);
				argv += 1;
				argc -= 1;
				break;
			case 't':
				(void)sscanf(*argv, "%d", &sTimeoutMs);
				argv += 1;
				argc -= 1;
				break;
			default:
				ERR(("Do not know arg '%s'\n", str));
				ShowHelp();
				sExitAns = 1;
				return (sExitAns);
			}
		}
		else if ('+' == str[0])
		{
			str++;
			namedEntry = FindNamedEntry(sSigNamesTable, str);
			if (NULL == namedEntry)
			{
				ERR(("Do not know signal '%s'. Try -a for a list of available signals.\n", str));
				sExitAns = 1;
				return (sExitAns);
			}

			i = sSigCount++;
			if (sSigCount >= kCIsigCOUNT)
			{
				ERR(("Max signals to monitor is %d.\n", kCIsigCOUNT));
				sExitAns = 1;
				return (sExitAns);
			}

			sSigEntryAry[i] = namedEntry;

			while (i-- > 0)
			{
				if (sSigEntryAry[i]->ndx == namedEntry->ndx)
				{
					ERR(("Duplicate signal '%s' detected.\n", str));
					sExitAns = 1;
					return (sExitAns);
				}
			}
		}
		else
		{
			ERR(("Do not know '%s' arg\n", str));
			ShowHelp();
			sExitAns = 1;
			return (sExitAns);
		}
	}

	return (kCIEnoErr);
}

/*--------------------------------------------------------------------------*/
static int CheckForKeyboardInput(void)
/*
**	Return 0 if no input available from stdin, 1 else
**
**	Note: the console needs a newline in order to post input.
*/
{
	fd_set exceptfds, readfds, writefds;
	struct timeval tv;
	int ans;
	char buff[1024];

	FD_ZERO(&exceptfds);
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_SET(fileno(stdin), &readfds);
	(void)memset(&tv, '\0', sizeof(struct timeval));

	ans = select(1, &readfds, &writefds, &exceptfds, &tv);

	if ((ans == 1) && FD_ISSET(fileno(stdin), &readfds))
	{
		/*
		 **   Consume the line.
		 */
		(void)fgets(buff, 1024, stdin);
		return (1);
	}

	return (0);
}

/*==========================================================================*/
void* SignalThread(void *d)
/*
**	Wait for signals continuously, or until count exceeds max.
*/
{
	const tSTDP sigData = (tSTDP)d;

	tCIRC circ = kCIEnoErr;
	tCIsignalEventData evtData;
	tCIU8 dispSig;
	tCIU32 cnt;

	sigData->eventCount = 0;

	/*
	 ** Flush the signal queue.
	 */
	CiSignalQueueOp(sCIp, sigData->hSignal, kCISQflush, &cnt);
	SHOW(("[%s] flushed %d.\n", sigData->entry->name, cnt));

	/*
	 ** Loop continuously until we reach an exit condition.
	 */
	while (1)
	{
		if (CheckForKeyboardInput())
		{
			SHOW(("[%s] Detected keyboard input\n", sigData->entry->name));
			break;
		}

		/*
		 ** Wait indefinitely for the signal to be emitted.
		 */
		circ = CiSignalWait(sCIp, sigData->hSignal, sizeof(evtData), &evtData, -1);
		if (kCIEcanceledErr == circ)
		{
			SHOW(("[%s] Signal canceled.\n", sigData->entry->name));
			break;
		}
		else if (kCIEnoErr != circ)
		{
			ERR(("[%s] CiSignalWait gave '%s'\n", sigData->entry->name, CiErrStr(circ)));
			sExitAns = 1;
			break;
		}

		/*
		 ** Record one.
		 */
		sigData->eventCount++;
		cnt = ATOMIC_ADD(sRcvdSigs, 1);

		/*
		 ** Optionally display this one.
		 */
		dispSig = (0 == sSkipSigs) || (0 == (sigData->eventCount % (1 + sSkipSigs)));
		if (dispSig)
		{
			SHOW(("[%s] OK %08d  Timestamp: 0x%08X'%08X  Count: %d\n", sigData->entry->name, sigData->eventCount,
				(tCIU32)(evtData.timestamp >> 32), (tCIU32)(evtData.timestamp & 0xFFFFFFFF), evtData.count));
		}

		if (0 != sMaxSigs && (cnt + 1) >= sMaxSigs)
		{
			SHOW(("[%s] Max count exceeded.\n", sigData->entry->name));
			break;
		}
	}

	/*
	 ** On exit, cancel CiSignalExec if
	 **  1) We are the cancel signal (i.e., ensure forward to Exec), or
	 **  2) We were not ourselves canceled (i.e., main thread needs cancel).
	 */
	if (sigData->hSignal == sigData->hCancelSignal || kCIEcanceledErr != circ)
		CiSignalWaitCancel(sCIp, sigData->hCancelSignal);

	return(NULL);
}

/*==========================================================================*/
int main (int argc, char *argv[])
/*
**	Main method. Start the threads, then wait for an exit condition.
*/
{
	tCIRC circ;

	tCIU32 i, cnt;
	tCIU32 sigsLeft, wakeCnt;

	tSTDP sigDataAry = NULL;
	tCISHP *sigAry = NULL;

	tCIU32 sz;

	tCIDOUBLE entryTime, exitTime;

	/*
	 ** Copy application path for help.
	 */
	sArgv0 = *argv;

	/*
	 ** Decode the command arguments
	 */
	if (DecodeArgs(argc, argv) != kCIEnoErr)
		return(sExitAns);

	if (sSigCount == 0)
	{
		ERR(("At least one signal is required.\n"));
		ShowHelp();
		return(1);
	}

	/*
	 ** Allocate the signal thread data array.
	 */
	sz = sSigCount * sizeof(tSignalThreadData);
	sigDataAry = (tSTDP)malloc(sz);
	if (NULL == sigDataAry)
	{
		ERR(("Failed to allocate %d bytes for signal data handles.\n", sz));
		return(1);
	}
	memset(sigDataAry, '\0', sz);

	/*
	 ** Allocate the signal handle array.
	 */
	sz = sSigCount * sizeof(tCISHP);
	sigAry = (tCISHP*)malloc(sz);
	if (NULL == sigAry)
	{
		ERR(("Failed to allocate %d bytes for signal handles.\n", sz));
		free(sigDataAry);
		return(1);
	}
	memset(sigAry, '\0', sz);

	/*
	 ** Open the board.
	 */
	circ = CiVFGopen(sNdx, kCIBO_writeAccess, &sCIp);
	if (kCIEnoErr != circ)
	{
		ERR(("CiVFGopen(%d) gave '%s'\n", sNdx, CiErrStr(circ)));
		free(sigAry);
		free(sigDataAry);
		return(1);
	}

	/*
	 ** Setup all signals and threads.
	 */
	for (i = 0; sSigCount > i; i++)
	{
		/*
		 ** CXP link should be 0-3 or 255 for CXP signals, 0 for all others.
		 */
		sigDataAry[i].entry = sSigEntryAry[i];
		sigDataAry[i].link = (sCXPlink & sSigEntryAry[i]->linkMask);

		SHOW(("Setup %s on %d/%d.\n", sSigEntryAry[i]->name, sNdx, sigDataAry[i].link));

		/*
		 ** Setup the signal
		 */
		circ = CiSignalSetup(sCIp, sSigEntryAry[i]->ndx, sigDataAry[i].link, &sigAry[i]);
		if (kCIEnoErr != circ)
		{
			ERR(("CiSignalSetup(%d/%s/%d) gave '%s'\n", sNdx, sSigEntryAry[i]->name, sigDataAry[i].link, CiErrStr(circ)));

			while (i-- > 0)
			{
				CiSignalWaitCancel(sCIp, sigAry[i]);
				pthread_join(sigDataAry[i].hThread, NULL);
				CiSignalCleanup(sCIp, sigAry[i], kCISQflush, NULL);
			}

			CiVFGclose(sCIp);

			free(sigAry);
			free(sigDataAry);
			return(1);
		}
		sigDataAry[i].hSignal = sigAry[i];
		sigDataAry[i].hCancelSignal = sigDataAry[0].hSignal;

		/*
		 ** Launch the thread.
		 */
		circ = pthread_create(&sigDataAry[i].hThread, NULL, &SignalThread, &sigDataAry[i]);
		if (0 != circ)
		{
			circ = errno;
			ERR(("pthread_create(EventThread) gave (%s)\n", CiErrStr(circ)));

			CiSignalCleanup(sCIp, sigAry[i], kCISQflush, NULL);

			while (i-- > 0)
			{
				CiSignalWaitCancel(sCIp, sigAry[i]);
				pthread_join(sigDataAry[i].hThread, NULL);
				CiSignalCleanup(sCIp, sigAry[i], kCISQflush, NULL);
			}

			CiVFGclose(sCIp);

			free(sigAry);
			free(sigDataAry);
			return(1);
		}
	}

	SHOW(("Servicing %d signals, upto %d events. Press Enter/Return to exit.\n", sSigCount, sMaxSigs));

	/*
	 ** Record the wait entry time.
	 */
	entryTime = GetTime();

	/*
	 ** Wait for an error condition to occur.
	 */
	sigsLeft = sMaxSigs;
	wakeCnt = 0;
	while (sExitAns == 0)
	{
		if (CheckForKeyboardInput())
		{
			SHOW(("Detected keyboard input\n"));
			break;
		}

		/*
		 ** See if we have satisfied the max signal count.
		 */
		if (0 != sMaxSigs)
		{
			const tCIU32 sigsRcvd = ATOMIC_ADD(sRcvdSigs, 0);
			if (sigsRcvd >= sMaxSigs)
			{
				SHOW(("Max count exceeded.\n"));
				break;
			}

			sigsLeft = sMaxSigs - sigsRcvd;
		}

		/*
		 ** Service all enabled signals.
		 */
		circ = CiSignalExec(sCIp, sSigCount, sigAry, sigsLeft, &cnt, sTimeoutMs);
		wakeCnt += cnt;

		if (kCIEcanceledErr == circ)
		{
			SHOW(("CiSignalExec canceled.\n"));
			break;
		}
		else if (kCIEnoErr != circ)
		{
			sExitAns = 1;
			ERR(("CiSignalExec gave (%s)\n", CiErrStr(circ)));
			break;
		}
	}

	/*
	 ** Cancel any pending signal waits. Do this before cleaning up the
	 ** threads, to reduce overall wait time.
	 */
	for (i = 0; sSigCount > i; i++)
		CiSignalWaitCancel(sCIp, sigAry[i]);

	/*
	 ** Cleanup the signals, threads, and VFG handle.
	 */
	for (i = 0; sSigCount > i; i++)
	{
		pthread_join(sigDataAry[i].hThread, NULL);

		CiSignalQueueOp(sCIp, sigAry[i], kCISQenumDropped, &sigDataAry[i].dropCount);
		CiSignalCleanup(sCIp, sigAry[i], kCISQflush, NULL);
	}

	CiVFGclose(sCIp);

	/*
	 ** Get the exit time, the count dropped, and print a summation message.
	 **
	 ** NOTE: the number of signals serviced by CiSignalExec may exceed
	 **       the maximum count, as signals can occur in batches.
	 */
	exitTime = GetTime();

	SHOW(("Woke %d signals in %.3f sec\n", wakeCnt, (exitTime - entryTime)));
	for (i = 0; sSigCount > i; i++)
	{

		SHOW(("  %s: got %d (%.3f SPS), dropped %d\n", sigDataAry[i].entry->name, sigDataAry[i].eventCount,
			(tCIDOUBLE)sigDataAry[i].eventCount / (exitTime - entryTime), sigDataAry[i].dropCount));
	}

	/*
	 ** Free memory
	 */
	free(sigAry);
	free(sigDataAry);

	return(sExitAns);
}