/*****************************************************************************
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);
}