/****************************************************************************/
/* Copyright 2004 MBARI														*/
/****************************************************************************/
/* Summary	: Diagnostic routines to debug OASIS3 software					*/
/* Filename : swdiag.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS3 Mooring												*/
/* $Revision: 1.2 $															*/
/* Created	: 08/11/2004 rah												*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 11aug2004 rah - created from OASIS usrcmds.c								*/
/* $Log: swdiag.c,v $
/* Revision 1.2	 2015/01/22 18:10:50  bobh
/* Changes due to 2015 BRS GOC cruise
/*
/* Revision 1.1	 2011/10/25 22:39:22  bobh
/* Initial oasis4 revision
/*
/* Revision 1.9	 2010/12/08 18:57:03  bobh
/* Added flags to ModBusStruct, global debug flag
/*
/* Revision 1.8	 2010/10/26 17:15:06  bobh
/* Moved ModBus errors to file
/*
/* Revision 1.7	 2010/10/25 16:42:50  bobh
/* Changes to support scripting
/*
/* Revision 1.6	 2010/05/06 22:25:46  bobh
/* Added user functions to manipulate remote I/O, changed function parser to parse and pass 4 parameters
/*
/* Revision 1.5	 2009/11/19 19:05:58  bobh
/* Changes to support con to remote ports and fixes to file I/O due to testing
/*
/* Revision 1.4	 2009/04/29 20:35:28  bobh
/* Added remSerChown, minor other mods
/*
/* Revision 1.3	 2009/04/27 22:49:43  bobh
/* First working version of remote serial ports
/*
/* Revision 1.2	 2009/04/17 18:05:04  bobh
/* Changes leading up to CDR
/*
/* Revision 1.1	 2009/03/25 20:34:41  bobh
/* Initial checkin
/*
/* Revision 1.1	 2004/08/18 21:07:25  bobh
/* Software diagnostic routines
/*
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* PWR_PERM definition				*/
#include <debug.h>
#include <io.h>							/* OASIS I/O definitions			*/
#include <spi.h>						/* OASIS SPI I/O definitions		*/
#include <list.h>						/* OASIS linked list definitions	*/
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <sem.h>						/* OASIS semaphore definitions		*/
#include <task.h>						/* OASIS task dispatcher			*/
#include <malloc.h>						/* OASIS malloc routines			*/
#include <log.h>						/* OASIS data logging definitions	*/
#include <drvr.h>						/* OASIS driver functions			*/
#include <modbus.h>						/* For Modbus testing				*/
#include <remote.h>						/* Remote I/O library			*/
#include <swdiag.h>						/* Software diagnostic func protos	*/
#include <utils.h>						/* OASIS Utility routines			*/
#include <clock.h>						/* OASIS Clock module				*/
#include <sensors.h>					/* OASIS Utility routines			*/
#include <usrcmds.h>

#include <cfxpico.h>					/* Persistor PicoDOS definitions	*/
#include <stdio.h>						/* Standard I/O						*/
#include <string.h>						/* String library functions			*/
#include <ctype.h>						/* Standard ctype.h defs			*/


/********************************/
/*		External Data			*/
/********************************/

/* serPort and serMode are special task variables:	they get saved		*/
/*	and restored during task swap, so they always refer to the serial	*/
/*	port and mode for the task that's currently in context				*/
Extern Semaphore		ser_sem[PHYS_SER_PORTS]; /* Mutex sems for ser ports*/
Extern Nat16	serPort;
Extern Word		serMode;
Extern Nat32	modbusDebug;
Extern Errno	modErrno;
Extern Parm_t	con_dsc;				/* Disconnect character for CONNECT */
Extern Parm_t	con_tmout;				/* Timeout during CONNECT			*/
Extern Parm_t	brkchr;					/* Character that sends break		*/

#define REM_BREAK_TIME	1000			/* Length of serial break in ms		*/


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal	testThreadNum = 0;

typedef struct
{
	Parm_t		serport;
	Parm_t		parm2;
	Parm_t		loops;
} TaskTestStruct;


#ifdef DEBUG_FREEZE_CMD

/************************************************************************/
/* Function	   : usr_freeze												*/
/* Purpose	   : Get/Set task dispatcher freeze state					*/
/* Inputs	   : Parm Mask, New freeze state							*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
usr_freeze(Nat16 pmask, Parm_t newState)
{
  if (pmask != 0)
	taskSetFreezeState(newState ? TRUE : FALSE);

  xprintf("Dispatcher is %s FROZEN\n",
		  taskGetFreezeState() ? "" : "NOT");

  return(OK);

} /* freeze() */

#endif

#ifdef DEBUG_SYSTEM_CMD

/************************************************************************/
/* Function	   : getTaskDelay											*/
/* Purpose	   : Passed to showTaskList() to show delay ticks			*/
/* Inputs	   : Task Descriptor ptr									*/
/* Outputs	   : Task Delay count										*/
/************************************************************************/
		MLocal Int32
getTaskDelay(TaskDesc *td)
{
  Nat32			timeSecs;
  Int32			delayTicks;
  Nat16			timeTicks;
  
  RTCGetTime(&timeSecs, &timeTicks);			/* Get accurate current time*/

  delayTicks = TICKS_PER_SECOND*(td->td_delaysec - timeSecs) + 
			  ((Int32)td->td_delaytick - timeTicks)/RTC_TICKS_PER_O3_TICK;

  return(delayTicks);

} /* getTaskDelay() */


/************************************************************************/
/* Function	   : showTaskList											*/
/* Purpose	   : Show List of tasks										*/
/* Inputs	   : Buffer, LstHead ptr									*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal Void		
showTaskList(char *buf, LstHead *list)
{
  Reg Node		*np;
  Reg TaskDesc	*td;
  Nat16			n;
  Nat32			timeSecs;
  Nat16			timeTicks;
  
  xprintf("Task List:\n");

  if ((np = list_first(list)) == NULLNODE)
	xprintf("  Empty\n");
  else
  {
	for (n = 0; np != NULLNODE; np = list_next(np))
	{
	  td = ((TaskNode *)np)->tn_td;

	  switch(td->td_state)
	  {
		case RUNNING:
			sprintf(buf, "RUNNING");
			break;

		case READY:
			sprintf(buf, "READY");
			break;

		case PENDING:
			if (td->td_sem->sem_name[0] == '\0')
				sprintf(buf, "PENDING on semaphore %#lx", td->td_sem);
			else
				sprintf(buf, "PENDING on %s", td->td_sem->sem_name);
			break;

		case DELAY:
			RTCGetTime(&timeSecs, &timeTicks);			/* Get accurate current time*/
			sprintf(buf, "DELAY %ld ticks (%ld,%u,%u)", getTaskDelay(td),
					(td->td_delaysec - timeSecs), td->td_delaytick, timeTicks);
			break;

		default:
			sprintf(buf, "UNKNOWN state (%d)", td->td_state);
	  }

	  xprintf("	 %#lx  %-20s %s\n", td, td->td_name, buf);
	}
  }
  xprintf("\n");

} /* showTaskList() */


/************************************************************************/
/* Function	   : showSemList											*/
/* Purpose	   : Show List of all semaphores in system					*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal Void		
showSemList(char *buf)
{
  Reg Node		*np, *snp;
  LstHead		*semList;
  Reg SemID		sem;
  Reg Nat16		n;

  semList = semGetSemList();
  xprintf("%d Semaphores:\n", list_count(semList));

  for (np = list_first(semList); np != NULLNODE; np = list_next(np))
  {
	sem = (SemID)np;
	n = sprintf(buf, "	%#lx %16s ", sem, sem->sem_name);

	n += sprintf(buf+n, "Type %s Count %d",
				 sem->sem_type ? "COUNTING" : "MUTEX   ", sem->sem_cnt);

	if (sem->sem_cnt <= 0)
	{
	  if (sem->sem_type == MUTEX)
		n += sprintf(buf+n, "  Owner %s", 
					 ((TaskDesc *)(sem->sem_owner))->td_name);
	  if ((snp = list_first(&sem->sem_list)) != NULL)
	  {
		n += sprintf(buf+n, "  Pending: ");
		for ( ; snp != NULLNODE; snp = list_next(snp))
		  n += sprintf(buf+n, "%s ", ((TaskDesc *)snp)->td_name);
	  }
	}
	xprintf("%s\n", buf);
  }
  xprintf("\n");

} /* showSemList() */


/************************************************************************/
/* Function	   : showSystem												*/
/* Purpose	   : Show state of system: dispatcher, etc					*/
/* Inputs	   : None													*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
showSystem(Nat16 pmask, Parm_t skipSem)
{
  char	*buf;

  if ((buf = malloc(2048)) == NULL)
  {
	xprintf("Can't malloc buffer\n");
	return(ERROR);
  }

  xprintf("Tick = %lu  Running task = %#lx\n\n", ioGetTick());
  showTaskList(buf, getTaskList());
  if (!((pmask & 1) && skipSem))
	  showSemList(buf);
  free(buf);
  return(OK);
 
} /* showSystem() */

#endif /* DEBUG_SYSTEM_CMD */


/************************************************************************/
/* Function	   : testThread												*/
/* Purpose	   : Test thread to put msgs out to serial port				*/
/* Inputs	   : TaskTestStruct											*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void testThread(TaskTestStruct *parm)
{
	int			loop;
	char		*name;
	Nat32		secs;
	Nat16		ticks;
	struct tm	*tmptr;

	serPort = parm->serport;
	serMode = (NO_PTY | BIT8 | STOP1 | FLOW | AUTOCR);
	io_serRelay(serPort, TRUE);
	io_serEnable(serPort, RS232);
	serPortInit(serPort, 38400, serMode);
	name = task_get()->td_name;

	xprintf("%s(%ld, %ld, %ld) create\n", name,
			parm->serport, parm->parm2, parm->loops);

	for (loop = 0; loop < parm->loops; loop++)
	{
		RTCGetTime(&secs, &ticks);
		ticks /= (RTC_TICKS_PER_SECOND/100);
		tmptr = gmtime(&secs);

		xprintf("%s loop %d at %02d:%02d:%02d.%02d\n",
				name, loop, tmptr-> tm_hour,
				tmptr->tm_min, tmptr->tm_sec, ticks);

		xdrain_ser(10);
		delay_secs(parm->parm2);
	}

	xprintf("%s(%ld, %ld, %ld) exit\n", name,
			parm->serport, parm->parm2, parm->loops);

	free(parm);

} /* testThread() */


/************************************************************************/
/* Function	   : startTestThread										*/
/* Purpose	   : UserIF routine to start a test thread to put msgs to ser*/
/* Inputs	   : Parm Mask, serial port, delay, number of loops			*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		Int16
startTestThread(Nat16 pmask, Parm_t serPort, Parm_t delay, Parm_t loops)
{
	TaskTestStruct		*ts;
	char				*name;

	if ((pmask & 1) != 1)
	{
		xputs("Usage: TestTask <serPort> [delay_secs] [loops]\n");
		return(ERROR);
	}

	name = malloc(sizeof(TaskTestStruct) + 32);
	ts = (TaskTestStruct *)name;
	name += sizeof(TaskTestStruct);

	sprintf(name, "testThread%d", testThreadNum++);
	ts->serport = serPort;
	ts->parm2 = (pmask & 2) ? delay : 10;
	ts->loops = (pmask & 4) ? loops : 20;

	task_create(testThread, ts, name);
	return(OK);

} /* startTestThread() */


/************************************************************************/
/* Function	   : testDelay												*/
/* Purpose	   : Test task_delay()										*/
/* Inputs	   : O3 ticks												*/
/* Outputs	   : None													*/
/************************************************************************/
Int16 testDelay(Nat16 pmask, Parm_t ticks)
{
	RTCTimer	timer;
	Nat32		elapsed;

	if ((pmask & 1) != 1)
	{
		xputs("Usage: delay <ticks>\n");
		return(ERROR);
	}

	RTCElapsedTimerSetup(&timer);
	task_delay(ticks);
	elapsed = RTCElapsedTime(&timer);

	xprintf("Elapsed time = %lu.%03lu secs\n",
			elapsed/1000000, (elapsed%1000000)/1000);

	return(OK);

} /* testDelay() */


/************************************************************************/
/* Function	   : testDelay1												*/
/* Purpose	   : Test task_delay_rtcTicks()								*/
/* Inputs	   : Seconds, RTC ticks										*/
/* Outputs	   : None													*/
/************************************************************************/
Int16 testDelay1(Nat16 pmask, Parm_t seconds, Parm_t rtcTicks)
{
	RTCTimer	timer;
	Nat32		elapsed;

	if ((pmask & 1) != 1)
	{
		xputs("Usage: delay <seconds>, [rtcTicks]\n");
		return(ERROR);
	}

	if ((pmask & 2) != 2)
		rtcTicks = 0;

	RTCElapsedTimerSetup(&timer);
	task_delay_rtcTicks(seconds, (Nat16)rtcTicks);
	elapsed = RTCElapsedTime(&timer);

	xprintf("Elapsed time = %lu.%03lu secs\n",
			elapsed/1000000, (elapsed%1000000)/1000);

	return(OK);

} /* testDelay1() */


/************************************************************************/
/* Function	   : turnOffCoils											*/
/* Purpose	   : Turn off coils on all slaves in test					*/
/* Inputs	   : ModbusHandle, first slave, number of slaves			*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		MLocal Void
turnOffCoils(ModBusHandle mh, Nat16 firstSlave, Nat16 nslaves)
{
	Nat16		slave;
	Byte		coils = 0;

	for (slave = firstSlave; slave < (firstSlave + nslaves); slave++)
		writeCoils(mh, slave, 1, 8, &coils);

} /* turnOffCoils() */


/************************************************************************/
/* Function	   : modbusTest												*/
/* Purpose	   : Modbus Test											*/
/* Inputs	   : Parm Mask, serial port, slave ID, number of slaves, mode*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		Int16
modbusTest(Nat16 pmask, Parm_t testPort, Parm_t testSlave, Parm_t numSlaves)
{
	Nat32		loop;
	Nat16		firstSlave, slave, nslaves;
	Errno		ret;
	ModBusHandle mh;
	Byte		coil1, coilRead, coilWrite;
	Nat32		errs = 0;

	if ((pmask & 1) != 1)
	{
		xputs("Usage: ModTest <serPort>, [slave], [nslaves]\n");
		return(ERROR);
	}

	firstSlave = (pmask & 2) ? testSlave : 1;
	nslaves = (pmask & 4) ? numSlaves : 1;

	if ((mh = modBusOpen(testPort)) == NULL)
	{
		printf("Error in modBusOpen()\n");
		return(ERROR);
	}

	xprintf("ModTest(%ld, %u, %u)\n", testPort, firstSlave, nslaves);
	task_delay(TICKS_PER_SECOND/2);
	ser_iflush(serPort);

	turnOffCoils(mh, firstSlave, nslaves);

	for (loop = 0; serRxCount(serPort) == 0; loop++)
	{
		for (slave = firstSlave; slave < (firstSlave+nslaves); slave++)
		{
			if ((ret = readCoils(mh, slave, 1, 8, &coilRead)) != OK)
			{
				clk_printtime(clk_read());
				xprintf(" Error in readCoils(): slave %hd loop %lu rtn = %#x (%s)\n",
						slave, loop, ret, modBusErrString(ret));
				errs++;
			}
			else if (modbusDebug >= 3)
				xprintf("readCoils() loop %lu return value = %#x, coils = %#x\n", 
						loop, ret, coilRead);

			if (slave == firstSlave)
				coil1 = coilRead;
			coilWrite = coilRead + 1;
			
			if ((ret = writeCoils(mh, slave, 1, 8, &coilWrite)) != OK)
			{
				clk_printtime(clk_read());
				xprintf(" Error in writeCoils(): slave %hd loop %lu rtn = %#x (%s)\n",
						slave, loop, ret, modBusErrString(ret));
				errs++;
			}
			else if (modbusDebug >= 3)
				xprintf("writeCoils() loop %lu return value = %#x\n", loop, ret);

			if (coilRead != coil1)
			{
				xprintf("Coils out of sync: ID %hd = %02X, ID %hd = %02X\n",
						firstSlave, coil1, slave, coilRead);
				xprintf("Clearing all coil\n");
				turnOffCoils(mh, firstSlave, nslaves);
				break;
			}
		}

		if ((modbusDebug >= 1) && (loop%20 == 0))
		{
			clk_printtime(clk_read());
			xprintf(" Loop %lu, %lu errors\n", loop, errs);
		}

		task_delay(TICKS_PER_SECOND/20);
	}

	xprintf("ModTest(%ld, %u, %u) done, %lu loops, %lu errors\n",
			testPort, firstSlave, nslaves, loop, errs);
	modBusClose(mh);
	return(OK);

} /* modbusTest() */


/************************************************************************/
/* Function	   : remSerConnect											*/
/* Purpose	   : Open a connection to remote serial port				*/
/* Inputs	   : Remote Serial Handle									*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn remSerConnect(RemSerHandle rsh)
{
	Nat32		i, n, rtn;
	Nat32		startTick;
#define REMBUFSIZE 32
	char		buffer[REMBUFSIZE];

	startTick = ioGetTick();

	while ((ioGetTick() - startTick) < con_tmout*TICKS_PER_SECOND)
	{
		if ((n = xgetn_tmout(buffer, REMBUFSIZE, 0)) > 0)
		{
			startTick = ioGetTick();			/* Reset timeout		*/

			for (i = 0; i < n; i++)
			{
				if (buffer[i] == con_dsc)
					return(OK);
				else if (buffer[i] == brkchr)
				{
					remSerBreak(rsh, REM_BREAK_TIME);
					buffer[i] = '\0';
				}
			}

			if ((rtn = remSerWrite(rsh, buffer, n)) != n)
			{
				xprintf("remSerWrite() error: request %ld bytes, wrote %ld, modErrno = %d (%s)\n",
						n, rtn, modErrno, modBusErrString(modErrno));
				remSerClose(rsh);
				return(ERROR);
			}
		}

		if ((n = remSerRead(rsh, buffer, REMBUFSIZE)) > 0)
		{
			startTick = ioGetTick();			/* Reset timeout		*/
			xputn(buffer, n);
		}

		task_delay( 2 );						/* Run loop at 50 Hz   */

	} /* for */

	xputs("TIMEOUT\n");
	return(OK);

} /* remSerConnect() */


/************************************************************************/
/* Function	   : remConnect												*/
/* Purpose	   : Send test string to remote serial port					*/
/* Inputs	   : Parm Mask, serial port, slave ID, number of slaves, mode*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
		Int16
remConnect(Nat16 pmask, Parm_t bus, Parm_t slave, Parm_t port)
{
	RemSerHandle rsh;
	Errno		rtn;

	if ((pmask & 7) != 7)
	{
		xprintf("remCon <bus> <slave> <port>\n");
		return(ERROR);
	}

	xprintf("Connect to remote serial port on bus %ld slave %ld port %ld\n",
			bus, slave, port);

	if ((rsh = findRemSerHandle(bus, slave, port)) == NULL)
	{
		xprintf("Error finding remote serial port: modErrno = %d (%s)\n",
				modErrno, modBusErrString(modErrno));
		return(modErrno);
	}

	if (usrSemWait(&rsh->sem) != OK)
		return(OK);

	if ((rtn = openRemSerHandle(rsh, TRUE)) != OK)
	{
		xprintf("Error opening remote serial port: errno = %d (%s)\n",
				rtn, modBusErrString(rtn));
		return(rtn);
	}

	xputs("Connected\n");
	rtn = remSerConnect(rsh);
	remSerClose(rsh);
	return(rtn);

} /* remConnect() */


/************************************************************************/
/* Function	   : taskKillTest											*/
/* Purpose	   : Test task creation and killing task that owns semaphore*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Void taskKillTestTask(SemID sem)
{
	xprintf("Task %#lx (%s) alive, about to take SemID %#lx\n",
			task_get(), task_get()->td_name, sem);

	task_delay(TICKS_PER_SECOND);

	sem_take(sem);
	sem_take(sem);

	task_delay(300 * TICKS_PER_SECOND);

} /* taskKillTestTask() */


#ifdef DEBUG_KILL

/************************************************************************/
/* Function	   : taskKillTest											*/
/* Purpose	   : Test task creation and killing task that owns semaphore*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Int16 taskKillTest(Void)
{
	static Semaphore testSem;
	static char *killTaskName = "killTestTask";
	TaskDesc	*testTd, *tdLookup;

	sem_init(&testSem, MUTEX, 1);
	testTd = task_create(taskKillTestTask, &testSem, killTaskName);

	task_delay(TICKS_PER_SECOND);

	if ((tdLookup = task_find(killTaskName)) != testTd)
	{
		xprintf("task_find error: %s created at %#lx, task_find returns %#lx\n",
				killTaskName, testTd, tdLookup);
	}

	xprintf("Created semaphore %#lx and task %#lx.	Hit any key to continue\n",
			&testSem, testTd);
	xgetc();

	xprintf("Killing %#lx.	Sem pending list %#lx %#lx, sem count %d\n",
			testTd, testSem.sem_list.lst_head, testSem.sem_list.lst_tail,
			testSem.sem_cnt);

	task_kill(testTd);

	xprintf("Sem pending list %#lx %#lx, sem count %d, sem owner %#lx\n",
			testSem.sem_list.lst_head, testSem.sem_list.lst_tail,
			testSem.sem_cnt, testSem.sem_owner);

	return(OK);

} /* taskKilltest() */

#endif /* DEBUG_KILL */

/************************************************************************/
/* Function	   : testNum												*/
/* Purpose	   : Check output of CmdIsNumber
/* Inputs	   : Parm Mask, string										*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
testNum(Nat16 pmask, char *str)
{
	int			rtn, cnvlen;
	Nat32		lval;
	float		fval;

	if ((pmask & 1) == 0)
		return(ERROR);
	
	rtn = sscanf(str, " %ld%n", &lval, &cnvlen);
	xprintf("sscanf(%%ld) returns %d (len %d), parsed value is %ld\n", rtn, cnvlen, lval);

	rtn = sscanf(str, " %lx%n", &lval, &cnvlen);
	xprintf("sscanf(%%lx) returns %d (len %d), parsed value is %ld\n", rtn, cnvlen, lval);

	rtn = sscanf(str, " %f%n", &fval, &cnvlen);
	xprintf("sscanf(%%f) returns %d (len %d), parsed value is %.3f\n", rtn, cnvlen, fval);

	return(OK);

} /* testNum() */


/************************************************************************/
/* Function	   : usrEcho												*/
/* Purpose	   : Test command-line args									*/
/* Inputs	   : Parm Mask, string, string, string, string				*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
usrEcho(Nat16 pmask, char *s1, char *s2, char *s3, char *s4)
{
	if (pmask & 1)
		xprintf("Arg 1: %s\n", s1);

	if (pmask & 2)
		xprintf("Arg 2: %s\n", s2);

	if (pmask & 4)
		xprintf("Arg 3: %s\n", s3);

	if (pmask & 8)
		xprintf("Arg 4: %s\n", s4);

	return(OK);

} /* usrEcho() */


/************************************************************************/
/* Function	   : echoAll												*/
/* Purpose	   : Test command-line args									*/
/* Inputs	   : Parm Mask, string, string, string, string				*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
echoAll(Nat16 pmask, char *s1, char *s2, char *s3, char *s4)
{
	if (pmask & 1)
		xprintf("Arg 1: %s\n", s1);

	if (pmask & 2)
		xprintf("Arg 2: %s\n", s2);

	if (pmask & 4)
		xprintf("Arg 3: %s\n", s3);

	if (pmask & 8)
		xprintf("Arg 4: %s\n", s4);

	return(OK);

} /* echoAll() */

