/*****************************************************************************
 * Copyright 1990, 1992 Free Software Foundation, Inc.
 *
 * This code was donated by Intel Corp.
 *
 * Intel hereby grants you permission to copy, modify, and 
 * distribute this software and its documentation.  Intel grants
 * this permission provided that the above copyright notice 
 * appears in all copies and that both the copyright notice and
 * this permission notice appear in supporting documentation.  In
 * addition, Intel grants this permission provided that you
 * prominently mark as not part of the original any modifications
 * made to this software or documentation, and that the name of 
 * Intel Corporation not be used in advertising or publicity 
 * pertaining to distribution of the software or the documentation 
 * without specific, written prior permission.  
 *
 * Intel Corporation does not warrant, guarantee or make any 
 * representations regarding the use of, or the results of the use
 * of, the software and documentation in terms of correctness, 
 * accuracy, reliability, currentness, or otherwise; and you rely
 * on the software, documentation and results solely at your own risk.
 *****************************************************************************/

static char rcsid[] =
	"Id: nindy.c,v 1.1.1.1 1991/03/28 16:20:57 rich Exp $";

/******************************************************************************
 *
 *	 		NINDY INTERFACE ROUTINES
 *
 * The routines in this file define and implement an interface between code
 * (such as a high-level debugger) running on a remote host and the NINDY
 * ROM monitor on an i960 board.  These routines are to be linked with 
 * and called by the host code.
 *
 * These routines handle both the formatting/transferring of commands to NINDY
 * and the receipt/formatting of data returned in response to them.  The
 * actual transfer protocol is hidden from the host programmer within them.
 * For a full description of the lowest level NINDY/host transfer protocol,
 * see the block header of the file gdb.c, in the NINDY source code.
 *
 * The caller of these routines should be aware that:
 *
 * (1) ninConnect() should be called to open communications with the
 *     remote NINDY board before any of the other routines are invoked.
 *
 * (2) almost all interactions are driven by the host: nindy sends information
 *     in response to host commands.
 *
 * (3) the lone exception to (2) is the single character DLE (^P, 0x10).
 *     Receipt of a DLE from NINDY indicates that the application program
 *     running under NINDY has stopped execution and that NINDY is now
 *     available to talk to the host (all other communication received after
 *     the application has been started should be presumed to come from the
 *     application and should be passed on by the host to stdout).
 *
 * (4) the reason the application program stopped can be determined with the
 *     ninStopWhy() function.  There are three classes of stop reasons:
 *
 *	(a) the application has terminated execution.
 *	    The host should take appropriate action.
 *
 *	(b) the application had a fault or trace event.
 *	    The host should take appropriate action.
 *
 *	(c) the application wishes to make a service request (srq) of the host;
 *	    e.g., to open/close a file, read/write a file, etc.  The ninSrq()
 *	    function should be called to determine the nature of the request
 *	    and process it.
 *
 * WARNING: Changes made here should be tested in both gdb960 and comm960.
 *
 ******************************************************************************/

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>	/* Needed by file.h on Sys V */
#include <sys/file.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>	/* Needed on Sys V */
#include "defs.h" /* for xmalloc */
#include "ttycntl.h"
#include "block_io.h"
#include "wait.h"
#include "env.h"


#ifdef USG
#	include <unistd.h>
#	include "sysv.h"
#else	/* BSD */
#	include "string.h"
#	include <sys/time.h>
#endif

#ifndef ERROR
#define ERROR	-1
#endif

#define DLE	0x10	/* ^P */
#define XON	0x11	/* ^Q */
#define XOFF	0x13	/* ^S */
#define ESC	0x1b


#define NINDY_REGISTER_BYTES	((36*4) + (4*8))

#define TIMEOUT		-1

extern void free();

static int quiet = 0;	/* 1 => stifle unnecessary messages */
static int nindy_fd;	/* File descriptor of tty connected to 960/NINDY board*/

static int old_nindy = 0; /* 1 => use old (hex) communication protocol */
static ninStrGet();

		/****************************
		 *                          *
		 *  MISCELLANEOUS UTILTIES  *
		 *                          *
		 ****************************/


#if 0
/******************************************************************************
 * byteswap:
 *	If the host byte order is different from 960 byte order (i.e., the
 *	host is big-endian), reverse the bytes in the passed value;  otherwise,
 *	return the passed value unchanged.
 *
 ******************************************************************************/
static
long
byteswap( n )
    long n;
{
	long rev;
	int i;
	static short test = 0x1234;

	if (*((char *) &test) == 0x12) {
		/*
		 * Big-endian host, swap the bytes.
		 */
		rev = 0;
		for ( i = 0; i < sizeof(n); i++ ){
			rev <<= 8;
			rev |= n & 0xff;
			n >>= 8;
		}
		n = rev;
	}
	return n;
}
#endif /* 0 */


/******************************************************************************
 * get_int:
 *	Copy the little-endian integer pointed at by 'p'  and return it in
 *	the host byte order.  'p' may be an unaligned address, so do the copy
 *	a byte at a time.
 ******************************************************************************/
int
get_int( p )
    unsigned char *p;
{
	int n;
	int i;

	n = 0;
	p += sizeof(int) - 1;
	for ( i = 0; i < sizeof(n); i++ ){
		n <<= 8;
		n |= *p--;
	}

	return n;
}


/******************************************************************************
 * put_int:
 *	Copy the integer 'n' (which is in host byte order) to the location
 *	pointed at by 'p', leaving it in little-endian byte order.
 *	'p' may be an unaligned address, so do the move a byte at a time.
 ******************************************************************************/
int
put_int( p, n )
    unsigned char *p;
    int n;
{
	int i;

	for ( i = 0; i < sizeof(n); i++ ){
		*p++ = n;
		n >>= 8;
	}
}


/******************************************************************************
 * say:
 *	This is a printf that takes at most two arguments (in addition to the
 *	format string) and that outputs nothing if verbose output has been
 *	suppressed.
 ******************************************************************************/
/* FIXME: use varargs for this.  */
static
say( fmt, arg1, arg2 )
    char *fmt;
    int arg1, arg2;
{
	if ( !quiet ){
		printf( fmt, arg1, arg2 );
		fflush( stdout );
	}
}

/******************************************************************************
 * exists:
 *	Creates a full pathname by concatenating up to three name components
 *	onto a specified base name; optionally looks up the base name as a
 *	runtime environment variable;  and checks to see if the file or
 *	directory specified by the pathname actually exists.
 *
 *	Returns:  the full pathname if it exists, NULL otherwise.
 *		(returned pathname is in malloc'd memory and must be freed
 *		by caller).
 *****************************************************************************/
static
char *
exists( base, c1, c2, c3, env )
    char *base;		/* Base directory of path */
    char *c1, *c2, *c3;	/* Components (subdirectories and/or file name) to be
			 *	appended onto the base directory name.  One or
			 *	more may be omitted by passing NULL pointers.
			 */
    int env;		/* If 1, '*base' is the name of an environment variable
			 *	to be examined for the base directory name;
			 *	otherwise, '*base' is the actual name of the
			 *	base directory.
			 */
{
	struct stat buf;/* For call to 'stat' -- never examined */
	char *path;	/* Pointer to full pathname (malloc'd memory) */
	int len;	/* Length of full pathname (incl. terminator) */
	extern char *getenv();


	if ( env ){
		base = getenv( base );
		if ( base == NULL ){
			return NULL;
		}
	}

	len = strlen(base) + 4;
			/* +4 for terminator and "/" before each component */
	if ( c1 != NULL ){
		len += strlen(c1);
	}
	if ( c2 != NULL ){
		len += strlen(c2);
	}
	if ( c3 != NULL ){
		len += strlen(c3);
	}

	path = xmalloc( len );

	strcpy( path, base );
	if ( c1 != NULL ){
		strcat( path, "/" );
		strcat( path, c1 );
		if ( c2 != NULL ){
			strcat( path, "/" );
			strcat( path, c2 );
			if ( c3 != NULL ){
				strcat( path, "/" );
				strcat( path, c3 );
			}
		}
	}

	if ( stat(path,&buf) != 0 ){
		free( path );
		path = NULL;
	}
	return path;
}

		/*****************************
		 *                           *
		 *  LOW-LEVEL COMMUNICATION  *
		 *                           *
		 *****************************/

/******************************************************************************
 * timed_read:
 *	Read up to 'n' characters (less if fewer are available) from the NINDY
 *	tty. Wait up to 'timeout' seconds for something to arrive.  Return
 *	the number of characters read, 0 on timeout.
 ******************************************************************************/
#ifdef USG

static int saw_alarm;

static void
alarm_handler()
{
	saw_alarm = 1;
}

static
int
timed_read(buf,n,timeout)
    unsigned char * buf;	/* Where to put the read characters	*/
    int n;			/* Max number of characters to read	*/
    int timeout;		/* Timeout, in seconds			*/
{
        void (*old_alarm)();    /* Save alarm signal handler here on entry */
	int cnt;


        old_alarm = signal( SIGALRM,alarm_handler );
        saw_alarm = 0;
        alarm(timeout);
        do {
		cnt = n;
                TTY_NBREAD(nindy_fd,cnt,buf);
        } while ( (cnt <= 0) && !saw_alarm );

        alarm(0);
        signal( SIGALRM,old_alarm );

	return saw_alarm ? 0 : cnt;
}

#else		/* BSD */

static
int
timed_read(buf,n,timeout)
    unsigned char * buf;	/* Where to put the read characters	*/
    int n;			/* Max number of characters to read	*/
    int timeout;		/* Timeout, in seconds			*/
{
	struct timeval t;
	fd_set f;

	t.tv_sec = (long) timeout;
	t.tv_usec= 0;

	FD_ZERO( &f );
	FD_SET( nindy_fd, &f );
	if ( select(nindy_fd+1,&f,0,0,&t) ){
		return read( nindy_fd, buf, n );
	} else {
		return 0;
	}
}
#endif /* USG */

/******************************************************************************
 * rdnin:
 *	Read *exactly* 'n' characters from the NINDY tty.  Translate escape
 *	sequences into single characters, counting each such sequence as 
 *	1 character.
 *
 *	An escape sequence consists of ESC and a following character.  The
 *	ESC is discarded and the other character gets bit 0x40 cleared --
 *	thus ESC P == ^P, ESC S == ^S, ESC [ == ESC, etc.
 *
 *	Return 1 if successful, 0 if more than 'timeout' seconds pass without
 *	any input.
 ******************************************************************************/
static
int
rdnin(buf,n,timeout)
    unsigned char * buf;	/* Where to place characters read	*/
    int n;			/* Number of characters to read		*/
    int timeout;		/* Timeout, in seconds			*/
{
	static unsigned char *mybuf = NULL;
				/* Dynamically allocated local buffer */
	static int mybuflen = 0;
				/* Current size of local buffer	*/
	int escape_seen;	/* 1 => last character of a read was an ESC */
	int nread;		/* Number of chars returned by timed_read() */
	unsigned char c;
	int i;

	/* Make sure local buffer is big enough
	 */
	if ( n > mybuflen ){
		if ( mybuf ){
			free( mybuf );
		}
		mybuf = (unsigned char *) xmalloc( mybuflen=n );
	}


	/* More than one loop will be necessary if there are any
	 * escape sequences in the input
	 */
	escape_seen = 0;
	while ( n ){
		nread = timed_read(mybuf,n,timeout);
		if ( nread <= 0 ){
			return 0;	/* TIMED OUT */
		}

		/* Copy characters from local buffer to caller's buffer,
		 * converting escape sequences as they're encountered.
		 */
		for ( i = 0; i < nread; i++ ){
			c = mybuf[i];
			if ( escape_seen ){
				escape_seen = 0;
				c &= ~0x40;
			} else if ( c == ESC ){
				if ( ++i >= nread ){
					/* Need to refill local buffer */
					escape_seen = 1;
					break;
				}
				c = mybuf[i] & ~0x40;
			}
			*buf++ = c;
			n--;
		}
	}
	return 1;
}


/******************************************************************************
 * getpkt:
 *	Read a packet from a remote NINDY, with error checking, into the
 *	indicated buffer.
 *
 *	Return packet status byte on success, TIMEOUT on failure.
 ******************************************************************************/
static
int
getpkt(buf)
     unsigned char *buf;
{
	int i;
	unsigned char hdr[3];	/* Packet header:
				 *	hdr[0] = low byte of message length
				 *	hdr[1] = high byte of message length
				 *	hdr[2] = message status
				 */
	int cnt;		/* Message length (status byte + data)	*/
	unsigned char cs_calc;	/* Checksum calculated			*/
	unsigned char cs_recv;	/* Checksum received			*/
	static char errfmt[] =
			"Bad checksum (recv=0x%02x; calc=0x%02x); retrying\r\n";

	while (1){
		if ( !rdnin(hdr,3,5) ){
			return TIMEOUT;
		}
		cnt = (hdr[1]<<8) + hdr[0] - 1;
					/* -1 for status byte (already read) */

		/* Caller's buffer may only be big enough for message body,
		 * without status byte and checksum, so make sure to read
		 * checksum into a separate buffer.
		 */
		if ( !rdnin(buf,cnt,5) || !rdnin(&cs_recv,1,5) ){
			return TIMEOUT;
		}

		/* Calculate checksum
		 */
		cs_calc = hdr[0] + hdr[1] + hdr[2];
		for ( i = 0; i < cnt; i++ ){
			cs_calc += buf[i];
		}
		if ( cs_calc == cs_recv ){
			write (nindy_fd, "+", 1);
			return hdr[2];
		}
	
		/* Bad checksum: report, send NAK, and re-receive
		 */
		fprintf(stderr, errfmt, cs_recv, cs_calc );
		write (nindy_fd, "-", 1);
	}
}


/******************************************************************************
 * putpkt:
 *	Send a packet to NINDY, checksumming it and converting special
 *	characters to escape sequences.
 ******************************************************************************/

/* This macro puts the character 'c' into the buffer pointed at by 'p',
 * and increments the pointer.  If 'c' is one of the 4 special characters
 * in the transmission protocol, it is converted into a 2-character
 * escape sequence.
 */
#define PUTBUF(c,p)						\
	if ( c == DLE || c == ESC || c == XON || c == XOFF ){	\
		*p++ = ESC;					\
		*p++ = c | 0x40;				\
	} else {						\
		*p++ = c;					\
	}

static
putpkt( msg, len )
    unsigned char *msg;	/* Command to be sent, without lead ^P (\020) or checksum */
    int len;	/* Number of bytes in message			*/
{
	static char *buf = NULL;/* Local buffer -- build packet here	*/
	static int maxbuf = 0;	/* Current length of buffer		*/
	unsigned char ack;	/* Response received from NINDY		*/
	unsigned char checksum;	/* Packet checksum			*/
	char *p;		/* Pointer into buffer			*/
	int lenhi, lenlo; 	/* High and low bytes of message length	*/
	int i;


	/* Make sure local buffer is big enough.  Must include space for
	 * packet length, message body, and checksum.  And in the worst
	 * case, each character would expand into a 2-character escape
	 * sequence.
	 */
	if ( maxbuf < ((2*len)+10) ){
		if ( buf ){
			free( buf );
		}
		buf = xmalloc( maxbuf=((2*len)+10) );
	}

	/* Attention, NINDY!
	 */
	write( nindy_fd, "\020", 1 );


	lenlo = len & 0xff;
	lenhi = (len>>8) & 0xff;
	checksum = lenlo + lenhi;
	p = buf;

	PUTBUF( lenlo, p );
	PUTBUF( lenhi, p );

	for ( i=0; i<len; i++ ){
		PUTBUF( msg[i], p );
		checksum += msg[i];
	}

	PUTBUF( checksum, p );

	/* Send checksummed message over and over until we get a positive ack
	 */
	write( nindy_fd, buf, p-buf );
	while (1){
		if ( !rdnin(&ack,1,5) ){
			/* timed out */
			fprintf(stderr,"ACK timed out; resending\r\n");
			write(nindy_fd,"\020",1);/* Attention, NINDY! */
			write( nindy_fd, buf, p-buf );
		} else if ( ack == '+' ){
			return;
		} else if ( ack == '-' ){
			fprintf( stderr, "Remote NAK; resending\r\n" );
			write( nindy_fd, buf, p-buf );
		} else {
			fprintf( stderr, "Bad ACK, ignored: <%c>\r\n", ack );
		}
	}
}



/******************************************************************************
 * send:
 *	Send a message to a remote NINDY.  Check message status byte
 *	for error responses.  If no error, return NINDY reponse (if any).
 ******************************************************************************/
static
send( out, len, in )
    unsigned char *out;	/* Message to be sent to NINDY			*/
    int len;		/* Number of meaningful bytes in out buffer	*/
    unsigned char *in;	/* Where to put response received from NINDY	*/
{
	char *fmt;
	int status;
	static char *errmsg[] = {
		"",						/* 0 */
		"Buffer overflow",				/* 1 */
		"Unknown command",				/* 2 */
		"Wrong amount of data to load register(s)",	/* 3 */
		"Missing command argument(s)",			/* 4 */
		"Odd number of digits sent to load memory",	/* 5 */
		"Unknown register name",			/* 6 */
		"No such memory segment",			/* 7 */
		"No breakpoint available",			/* 8 */
		"Can't set requested baud rate",		/* 9 */
	};
#	define NUMERRS	( sizeof(errmsg) / sizeof(errmsg[0]) )

	static char err1[] = "Unknown error response from NINDY: #%d\r\n";
	static char err2[] = "Error response #%d from NINDY: %s\r\n";

	while (1){
		putpkt(out,len);
		status = getpkt(in);
		if ( status == TIMEOUT ){
			fprintf( stderr, "Response timed out; resending\r\n" );
		} else {
			break;
		}
	}

	if ( status ){
		fmt =  status > NUMERRS ? err1 : err2;
		fprintf( stderr, fmt, status, errmsg[status] );
		abort();
	}
}

		/************************
		 *                      *
		 *  BAUD RATE ROUTINES  *
		 *                      *
		 ************************/

/* Table of baudrates known to be acceptable to NINDY.  Each baud rate
 * appears both as character string and as a Unix baud rate constant.
 */
struct baudrate {
	char *string;
	int rate;
};

static struct baudrate baudtab[] = {
	 "1200", B1200,
	 "2400", B2400,
	 "4800", B4800,
	 "9600", B9600,
	"19200", B19200,
	"38400", B38400,
	NULL,    0		/* End of table */
};
 

/******************************************************************************
 * parse_baudrate:
 *	Look up the passed baud rate in the baudrate table.  If found, change
 *	our internal record of the current baud rate, but don't do anything
 *	about the tty just now.
 *
 *	Return pointer to baudrate structure on success, NULL on failure.
 ******************************************************************************/
static
struct baudrate *
parse_baudrate(s)
    char *s;	/* Desired baud rate, as an ASCII (decimal) string */
{
	int i;

	for ( i=0; baudtab[i].string != NULL; i++ ){
		if ( !strcmp(baudtab[i].string,s) ){
			return &baudtab[i];
		}
	}
	return NULL;
}

/******************************************************************************
 * try_baudrate:
 *	Try speaking to NINDY via the specified file descriptor at the
 *	specified baudrate.  Assume success if we can send an empty command
 *	with a bogus checksum and receive a NAK (response of '-') back within
 *	one second.
 *
 *	Return 1 on success, 0 on failure.
 ******************************************************************************/

static int
try_baudrate( fd, brp )
    int fd;
    struct baudrate *brp;
{
	TTY_STRUCT tty;
	unsigned char c;
	

	/* Set specified baud rate and flush all pending input */
	ioctl( fd, TIOCGETP, &tty );
	TTY_REMOTE( tty, brp->rate );
	ioctl( fd, TIOCSETP, &tty );
	tty_flush( fd );

	/* Send empty command with bad checksum, hope for NAK ('-') response */
	write( fd, "\020\0\0\001", 4 );
	if ( !rdnin(&c,1,1) ){
		/* timed out */
		return 0;
	} else {
		return (c == '-');
	}
}

/******************************************************************************
 * autobaud:
 *	Get NINDY talking over the specified file descriptor at the specified
 *	baud rate.  First see if NINDY's already talking at 'baudrate'.  If
 *	not, run through all the legal baudrates in 'baudtab' until one works,
 *	and then tell NINDY to talk at 'baudrate' instead.
 ******************************************************************************/
static
autobaud( fd, brp )
    int fd;
    struct baudrate *brp;
{
	int i;
	TTY_STRUCT tty;
	int failures;


	say("NINDY at wrong baud rate? Trying to autobaud...\n");
	failures = i = 0;
	while ( 1 ){
		say( "\r%s...   ", baudtab[i].string );
		if ( try_baudrate(fd, &baudtab[i]) ){
			break;
		}
		if ( baudtab[++i].string == NULL ){
			/* End of table -- wraparound */
			i = 0;
			if ( failures++ ){
				say("\nAutobaud failed again.  Giving up.\n");
				exit(1);
			} else {
				say("\nAutobaud failed. Trying again...\n");
			}
		}
	}

	/* Found NINDY's current baud rate;  now change it.
	 */
	say("Changing NINDY baudrate to %s\n", brp->string);
	ninBaud( brp->string );

	/* Change our baud rate back to rate to which we just set NINDY.
	 */
	ioctl( fd, TIOCGETP, &tty );
	TTY_REMOTE( tty, brp->rate );
	ioctl( fd, TIOCSETP, &tty );
}

/*****************************************************************************
 * coffstrip:
 *	Passed the name of an executable object file in either b.out or
 *	COFF format.
 *
 *	If the file is in b.out format, it is converted to little-endian
 *	COFF format (i.e., the format suitable for downloading to NINDY).
 *	In either case, the COFF file is then stripped of all relocation
 *	and symbol information, to speed up its download.
 *
 * RETURNS:
 *	pointer to the name of the stripped COFF file (a tmp file that has
 *	been created and closed); NULL on error.
 *****************************************************************************/

#if 0
#define STRIP	"bfd_strip"	/* Name of bfd strip utility	*/
#endif
#define NINDY_OBJ	"coff-Intel-little"

char *
coffstrip( fn )
    char *fn;	/* Name of object file */
{
	extern char *mktemp();
	static char template[] = "/tmp/commXXXXXX";
	static char newfile[sizeof template];
	char *strip;	/* Pointer to full pathname of strip utility	*/
	int success;	/* Return value					*/
	int pid;	/* Process ID of xmodem transfer utility	*/
	WAITTYPE w;	/* xmodem transfer completion status		*/
	int wret;	/* Value returned by wait			*/
	char *tempfile;	/* Stripped copy of object file			*/
	char buf[400];


	strcpy (newfile, template);
	tempfile = mktemp( newfile );

#if 0
	/* Make sure the strip utility is findable.
	 */
	if ( ((strip = exists("G960BIN",STRIP,NULL,NULL,1)) == NULL)
	&&   ((strip = exists("G960BASE","bin",STRIP, NULL,1)) == NULL)
#ifdef HOST
	&&   ((strip = exists(DEFAULT_BASE,HOST,"bin",STRIP,0)) == NULL)
#endif
	){
		fprintf(stderr,"Can't find '%s' utility\n",STRIP);
		fprintf(stderr,"Check env variables G960BIN and G960BASE\n");
		return NULL;
	}
#endif /* 0 */

	success = 0;
	sprintf( buf, "cp %s %s", fn, tempfile );
	printf ("%s\n", buf);
	if ( system(buf) == 0 ){
		sprintf(buf, "%s -d %s %s", STRIP, NINDY_OBJ, tempfile);
		printf ("%s\n", buf);
		if ( system(buf) == 0 ){
			return tempfile;
		}
	}

	return NULL;
}

		/**********************************
		 *				  *
		 *   NINDY INTERFACE ROUTINES	  *
		 *                            	  *
		 * ninConnect *MUST* be the first *
		 * one of these routines called.  *
		 **********************************/


/******************************************************************************
 * ninBaud:
 *	Ask NINDY to change the baud rate on its serial port.
 *	Assumes we know the baud rate at which NINDY's currently talking.
 ******************************************************************************/
ninBaud( baudrate )
    char *baudrate;	/* Desired baud rate, as a string of ASCII decimal
			 * digits.
			 */
{
	unsigned char msg[100];

	if ( old_nindy ){
		OninBaud( baudrate );
		return;
	}

	tty_flush( nindy_fd );

	/* Can't use "send" because NINDY reply will be unreadable after
	 * baud rate change.
	 */
	sprintf( msg, "z%s", baudrate );
	putpkt( msg, strlen(msg)+1 );	/* "+1" to send terminator too */
}


/******************************************************************************
 * ninBptDel:
 *	Ask NINDY to delete the specified type of *hardware* breakpoint at
 *	the specified address.  If the 'addr' is -1, all breakpoints of
 *	the specified type are deleted.
 ******************************************************************************/
ninBptDel( addr, type )
    long addr;	/* Address in 960 memory	*/
    char type;	/* 'd' => data bkpt, 'i' => instruction breakpoint */
{
	unsigned char buf[10];

	if ( old_nindy ){
		OninBptDel( addr, type == 'd' ? 1 : 0 );
		return;
	}

	buf[0] = 'b';
	buf[1] = type;

	if ( addr == -1 ){
		send( buf, 2, NULL );
	} else {
		put_int( &buf[2], addr );
		send( buf, 6, NULL );
	}
}


/******************************************************************************
 * ninBptSet:
 *	Ask NINDY to set the specified type of *hardware* breakpoint at
 *	the specified address.
 ******************************************************************************/
ninBptSet( addr, type )
    long addr;	/* Address in 960 memory	*/
    char type;	/* 'd' => data bkpt, 'i' => instruction breakpoint */
{
	unsigned char buf[10];

	if ( old_nindy ){
		OninBptSet( addr, type == 'd' ? 1 : 0 );
		return;
	}


	buf[0] = 'B';
	buf[1] = type;
	put_int( &buf[2], addr );
	send( buf, 6, NULL );
}


/******************************************************************************
 * ninConnect:
 *	Open the specified tty.  Get communications working at the specified
 *	baud rate.  Flush any pending I/O on the tty.
 *
 *	Return the file descriptor, or -1 on failure.
 ******************************************************************************/
int
ninConnect( name, baudrate, brk, silent, old_protocol )
    char *name;		/* "/dev/ttyXX" to be opened			*/
    char *baudrate;/* baud rate: a string of ascii decimal digits (eg,"9600")*/
    int brk;		/* 1 => send break to tty first thing after opening it*/
    int silent;		/* 1 => stifle unnecessary messages when talking to 
			 *	this tty.
			 */
    int old_protocol;
{
	int i;
	char *p;
	struct baudrate *brp;

	/* We will try each of the following paths when trying to open the tty
	 */
	static char *prefix[] = { "", "/dev/", "/dev/tty", NULL };

	if ( old_protocol ){
		old_nindy = 1;
		return OninConnect( name, baudrate, brk, silent );
	}

	quiet = silent;		/* Make global to this file */

	for ( i=0; prefix[i] != NULL; i++ ){
		p = xmalloc(strlen(prefix[i]) + strlen(name) + 1 );
		strcpy( p, prefix[i] );
		strcat( p, name );
		nindy_fd = open(p,O_RDWR);
		if ( nindy_fd >= 0 ){
#ifdef TIOCEXCL
			/* Exclusive use mode (hp9000 does not support it) */
			ioctl(nindy_fd,TIOCEXCL,NULL);
#endif
			if ( brk ){
				send_break( nindy_fd );
			}

			brp = parse_baudrate( baudrate );
			if ( brp == NULL ){
				say("Illegal baudrate %s ignored; using 9600\n",
								baudrate);
				brp = parse_baudrate( "9600" );
			}

			if ( !try_baudrate(nindy_fd,brp) ){
				autobaud(nindy_fd,brp);
			}
			tty_flush( nindy_fd );
			say( "Connected to %s\n", p );
			free(p);
			break;
		}
		free(p);
	}
	return nindy_fd;
}



/******************************************************************************
 * ninDownload:
 *	Ask NINDY to start up it's COFF downloader. Invoke 'sx' to perform
 *	the XMODEM download from the host end.
 *
 *	Return 1 on success, 0 on failure.
 ******************************************************************************/

#define XMODEM	"sx"	/* Name of xmodem transfer utility	*/

int
ninDownload( fn, quiet )
    char *fn;		/* Stripped copy of object file			*/
    int quiet;
{
	char *p;	/* Pointer to full pathname of sx utility	*/
	int success;	/* Return value					*/
	int pid;	/* Process ID of xmodem transfer utility	*/
	WAITTYPE w;	/* xmodem transfer completion status		*/
	int wret;	/* Value returned by wait			*/
	char buf[200];


	if ( old_nindy ){
		return OninDownload( fn, quiet );
	}

	/* Make sure the xmodem utility is findable.  This must be done before
	 * we start up the NINDY end of the download (NINDY will hang if we
	 * don't complete the download).
	 */
	if ( ((p = exists("G960BIN",XMODEM,NULL,NULL,1)) == NULL)
	&&   ((p = exists("G960BASE","bin",XMODEM, NULL,1)) == NULL)
#ifdef HOST
	&&   ((p = exists(DEFAULT_BASE,HOST,"bin",XMODEM,0)) == NULL)
#endif
								      ){

		fprintf(stderr,"Can't find '%s' download utility\n",XMODEM);
		fprintf(stderr,"Check env variables G960BIN and G960BASE\n");
		return 0;
	}

	if ( !quiet ){
		printf( "Downloading %s\n", fn );
	}

	/* Reset NINDY,  wait until "reset-complete" ack,
	 * and start up the NINDY end of the download.
	 */
	ninReset();
	putpkt((unsigned char *) "D", 1 );

	/* Invoke x-modem transfer, a separate process.  DON'T
	 * use system() to do this -- under system V Unix, the
	 * redirection of stdin/stdout causes the nindy tty to
	 * lose all the transmission parameters we've set up.
	 */
	success = 0;

#if defined(USG) && !defined(HAVE_VFORK)
	pid = fork ();
#else
	pid = vfork ();
#endif
	if ( pid == -1 ){
		perror( "Can't fork process:" );

	} else if ( pid == 0 ){		/* CHILD */
		dup2( nindy_fd, 0 );	/* Redirect stdin */
		dup2( nindy_fd, 1 );	/* Redirect stout */
		if ( quiet ){
			execl( p, p, "-q", fn, (char*)0 );
		} else {
			execl( p, p, fn, (char*)0 );
		}
		/* Don't get here unless execl fails */
		sprintf( buf, "Can't exec %s", p );
		perror( buf );

	} else {			/* PARENT */
		do {
			wret = wait(&w);
		} while ( wret != pid && wret != -1 );

		if ( wret == -1 ){
			perror( "Wait failed" );
		} else if (WIFEXITED(w) && (WEXITSTATUS(w) == 0)){
			success = 1;
		}
	}
	return success;
}


/******************************************************************************
 * ninGdbExit:
 *	Ask NINDY to leave GDB mode and print a NINDY prompt.
 ******************************************************************************/
ninGdbExit()
{
	if ( old_nindy ){
		OninGdbExit();
		return;
	}
        putpkt((unsigned char *) "E", 1 );
}


/******************************************************************************
 * ninGo:
 *	Ask NINDY to start or continue execution of an application program
 *	in it's memory at the current ip.
 ******************************************************************************/
ninGo( step_flag )
    int step_flag;	/* 1 => run in single-step mode */
{
	if ( old_nindy ){
		OninGo( step_flag );
		return;
	}
	putpkt((unsigned char *) (step_flag ? "s" : "c"), 1 );
}


/******************************************************************************
 * ninMemGet:
 *	Read a string of bytes from NINDY's address space (960 memory).
 ******************************************************************************/
ninMemGet(ninaddr, hostaddr, len)
     long ninaddr;	/* Source address, in the 960 memory space	*/
     unsigned char *hostaddr;	/* Destination address, in our memory space */
     int len;		/* Number of bytes to read			*/
{
	unsigned char buf[BUFSIZE+20];
	int cnt;		/* Number of bytes in next transfer	*/

	if ( old_nindy ){
		OninMemGet(ninaddr, hostaddr, len);
		return;
	}

	for ( ; len > 0; len -= BUFSIZE ){
		cnt = len > BUFSIZE ? BUFSIZE : len;

		buf[0] = 'm';
		put_int( &buf[1], ninaddr );
		buf[5] = cnt & 0xff;
		buf[6] = (cnt>>8) & 0xff;

		send( buf, 7, hostaddr );

		ninaddr += cnt;
		hostaddr += cnt;
	}
}


/******************************************************************************
 * ninMemPut:
 *	Write a string of bytes into NINDY's address space (960 memory).
 ******************************************************************************/
ninMemPut( ninaddr, hostaddr, len )
     long ninaddr;	/* Destination address, in NINDY memory space	*/
     unsigned char *hostaddr;	/* Source address, in our memory space	*/
     int len;		/* Number of bytes to write			*/
{
	unsigned char buf[BUFSIZE+20];
	int cnt;		/* Number of bytes in next transfer	*/

	if ( old_nindy ){
		OninMemPut( ninaddr, hostaddr, len );
		return;
	}
	for ( ; len > 0; len -= BUFSIZE ){
		cnt = len > BUFSIZE ? BUFSIZE : len;

		buf[0] = 'M';
		put_int( &buf[1], ninaddr );
		bcopy( hostaddr, buf+5, cnt );
		send( buf, cnt+5, NULL );

		ninaddr += cnt;
		hostaddr += cnt;
	}
}

/******************************************************************************
 * ninRegGet:
 *	Retrieve the contents of a 960 register, and return them as a long
 *	in host byte order.
 *
 *	THIS ROUTINE CAN ONLY BE USED TO READ THE LOCAL, GLOBAL, AND
 *	ip/ac/pc/tc REGISTERS.
 *
 ******************************************************************************/
long
ninRegGet( regname )
    char *regname;	/* Register name recognized by NINDY, subject to the
			 * above limitations.
			 */
{
	unsigned char outbuf[10];
	unsigned char inbuf[20];

	if ( old_nindy ){
		return OninRegGet( regname );
	}

	sprintf( outbuf, "u%s:", regname );
	send( outbuf, strlen(outbuf), inbuf );
	return get_int(inbuf);
}

/******************************************************************************
 * ninRegPut:
 *	Set the contents of a 960 register.
 *
 *	THIS ROUTINE CAN ONLY BE USED TO SET THE LOCAL, GLOBAL, AND
 *	ip/ac/pc/tc REGISTERS.
 *
 ******************************************************************************/
ninRegPut( regname, val )
    char *regname;	/* Register name recognized by NINDY, subject to the
			 * above limitations.
			 */
    long val;		/* New contents of register, in host byte-order	*/
{
	unsigned char buf[20];
	int len;

	if ( old_nindy ){
		OninRegPut( regname, val );
		return;
	}

	sprintf( buf, "U%s:", regname );
	len = strlen(buf);
	put_int( &buf[len], val );
	send( buf, len+4, NULL );
}

/******************************************************************************
 * ninRegsGet:
 *	Get a dump of the contents of the entire 960 register set.  The
 *	individual registers appear in the dump in the following order:
 *
 *		pfp  sp   rip  r3   r4   r5   r6   r7 
 *		r8   r9   r10  r11  r12  r13  r14  r15 
 *		g0   g1   g2   g3   g4   g5   g6   g7 
 *		g8   g9   g10  g11  g12  g13  g14  fp 
 *		pc   ac   ip   tc   fp0  fp1  fp2  fp3
 *
 *	Each individual register comprises exactly 4 bytes, except for
 *	fp0-fp3, which are 8 bytes.  All register values are in 960
 *	(little-endian) byte order.
 *
 ******************************************************************************/
ninRegsGet( regp )
    unsigned char *regp;		/* Where to place the register dump */
{
	if ( old_nindy ){
		OninRegsGet( regp );
		return;
	}
	send( (unsigned char *) "r", 1, regp );
}


/******************************************************************************
 * ninRegsPut:
 *	Initialize the entire 960 register set to a specified set of values.
 *	The format of the register value data should be the same as that
 *	returned by ninRegsGet.
 *
 * WARNING:
 *	All register values must be in 960 (little-endian) byte order.
 *
 ******************************************************************************/
ninRegsPut( regp )
    char *regp;		/* Pointer to desired values of registers */
{
	unsigned char buf[NINDY_REGISTER_BYTES+10];

	if ( old_nindy ){
		OninRegsPut( regp );
		return;
	}

	buf[0] = 'R';
	bcopy( regp, buf+1, NINDY_REGISTER_BYTES );
	send( buf, NINDY_REGISTER_BYTES+1, NULL );
}


/******************************************************************************
 * ninReset:
 *      Ask NINDY to perform a soft reset; wait for the reset to complete.
 *
 ******************************************************************************/
ninReset()
{
	unsigned char ack;

	if ( old_nindy ){
		OninReset();
		return;
	}

	while (1){
		putpkt((unsigned char *) "X", 1 );
		while (1){
			if ( !rdnin(&ack,1,5) ){
				/* Timed out */
				break;		/* Resend */
			}
			if ( ack == '+' ){
				return;
			}
		}
	}
}


/******************************************************************************
 * ninSrq:
 *	Assume NINDY has stopped execution of the 960 application program in
 *	order to process a host service request (srq).  Ask NINDY for the
 *	srq arguments, perform the requested service, and send an "srq
 *	complete" message so NINDY will return control to the application.
 *
 ******************************************************************************/
ninSrq()
{
	unsigned char buf[BUFSIZE];
	int retcode;
	unsigned char srqnum;
	int i;
	int offset;
	int arg[MAX_SRQ_ARGS];

	if ( old_nindy ){
		OninSrq();
		return;
	}


	/* Get srq number and arguments
	 */
	send((unsigned char *) "!", 1, buf );

	srqnum = buf[0];
	for  ( i=0, offset=1; i < MAX_SRQ_ARGS; i++, offset+=4 ){
		arg[i] = get_int(&buf[offset]);
	}

	/* Process Srq
	 */
	switch( srqnum ){
	case BS_CLOSE:
		/* args: file descriptor */
		if ( arg[0] > 2 ){
			retcode = close( arg[0] );
		} else {
			retcode = 0;
		}
		break;
	case BS_CREAT:
		/* args: filename, mode */
		ninStrGet( arg[0], buf );
		retcode = creat(buf,arg[1]);
		break;
	case BS_OPEN:
		/* args: filename, flags, mode */
		ninStrGet( arg[0], buf );
		retcode = open(buf,arg[1],arg[2]);
		break;
	case BS_READ:
		/* args: file descriptor, buffer, count */
		retcode = read(arg[0],buf,arg[2]);
		if ( retcode > 0 ){
			ninMemPut( arg[1], buf, retcode );
		}
		break;
	case BS_SEEK:
		/* args: file descriptor, offset, whence */
		retcode = lseek(arg[0],arg[1],arg[2]);
		break;
	case BS_WRITE:
		/* args: file descriptor, buffer, count */
		ninMemGet( arg[1], buf, arg[2] );
		retcode = write(arg[0],buf,arg[2]);
		break;
	default:
		retcode = ERROR;
		break;
	}

	/* Send request termination status to NINDY
	 */
	buf[0] = 'e';
	put_int( &buf[1], retcode );
	send( buf, 5, NULL );
}


/******************************************************************************
 * ninStopWhy:
 *	Assume the application program has stopped (i.e., a DLE was received
 *	from NINDY).  Ask NINDY for status information describing the
 *	reason for the halt.
 *
 *	Returns a non-zero value if the user program has exited, 0 otherwise.
 *	Also returns the following information, through passed pointers:
 *           - why: an exit code if program the exited; otherwise the reason
 *			why the program halted (see stop.h for values).
 *	    - contents of register ip (little-endian byte order)
 *	    - contents of register sp (little-endian byte order)
 *	    - contents of register fp (little-endian byte order)
 ******************************************************************************/
char
ninStopWhy( whyp, ipp, fpp, spp )
    unsigned char *whyp; /* Return the 'why' code through this pointer	*/
    long *ipp;	/* Return contents of register ip through this pointer	*/
    long *fpp;	/* Return contents of register fp through this pointer	*/
    long *spp;	/* Return contents of register sp through this pointer	*/
{
	unsigned char buf[30];
	extern char OninStopWhy ();

	if ( old_nindy ){
		return OninStopWhy( whyp, ipp, fpp, spp );
	}
	send((unsigned char *) "?", 1, buf );

	*whyp = buf[1];
	bcopy (&buf[2],  (char *)ipp, sizeof (*ipp));
	bcopy (&buf[6],  (char *)fpp, sizeof (*ipp));
	bcopy (&buf[10], (char *)spp, sizeof (*ipp));
	return buf[0];
}

/******************************************************************************
 * ninStrGet:
 *	Read a '\0'-terminated string of data out of the 960 memory space.
 *
 ******************************************************************************/
static
ninStrGet( ninaddr, hostaddr )
     unsigned long ninaddr;	/* Address of string in NINDY memory space */
     unsigned char *hostaddr;	/* Address of the buffer to which string should
				 *	be copied.
				 */
{
	unsigned char cmd[5];

	cmd[0] = '"';
	put_int( &cmd[1], ninaddr );
	send( cmd, 5, hostaddr );
}

/******************************************************************************
 * ninVersion:
 *	Ask NINDY for version information about itself.
 *	The information is sent as an ascii string in the form "x.xx,<arch>",
 *	where,
 *		x.xx	is the version number
 *		<arch>	is the processor architecture: "KA", "KB", "MC", "CA" *
 *
 ******************************************************************************/
int
ninVersion( p )
     unsigned char *p;		/* Where to place version string */
{

	if ( old_nindy ){
		return OninVersion( p );
	}
	send((unsigned char *) "v", 1, p );
	return strlen(p);
}
