/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: tcs_load.c,v 1.3.2.1 92/06/24 13:54:03 marker Exp $"
/****************************************************************/
/*		 HPS Loader					*/
/****************************************************************/
/* PART NUMBER: 90-070070-3    					*/
/****************************************************************/
/*                 Confidential Information                     */
/*      Limited Distribution to Authorized Persons Only         */
/*     Created 1985 and Protected as an Unpublished Work        */
/*          Under the U.S. Copyright Act of 1976.               */
/*         Copyright (c) 1985,'88, Systech Corporation          */
/*                  All Rights Reserved                         */
/*								*/
/*THIS SOFTWARE IS LICENSED BY AND IS THE CONFIDENTIAL AND	*/
/*PROPRIETARY PROPERTY OF SYSTECH COMPUTER CORPORATION AND 	*/
/*MAY NOT, WITHOUT THE PRIOR WRITTEN CONSENT OF SYSTECH 	*/
/*COMPUTER CORPORATION, BE REPRODUCED, USED OR DISCLOSED 	*/
/*EXCEPT IN ACCORDANCE WITH A SYSTECH COMPUTER CORPORATION 	*/
/*SOFTWARE LICENSE AGREEMENT.					*/
/****************************************************************/
/*								*/
/*								*/
/*	This program loads executable code onto an HPS host-	*/
/*	adapter, and starts the downloaded code.  An options is	*/
/*	available to reconfigure the operating system after	*/
/*	downloading.						*/
/*								*/
/*	The "c" keyletter will reconfigure the on-board operating  */
/*	system after downloading.				*/
/*								*/
/*	This loader expects the downloaded file to be in	*/
/*	Systech's ".dnl" format.				*/
/*								*/
/****************************************************************/

/*
 * Generalized loader for HPS board.
 *
 *	1) Load the application code onto the board
 *	2) If the c keyletter is present, load the configuration information
 *	   into the on-board configuration table, and issue an INIT command.
 *	3) Issue a CREATE ioctl command for the downloaded code, to start the
 *	   boot task of the downloaded code.
 */

#include <stdio.h>
#include <errno.h>
#include "tcs_load.h"
#ifdef STREAM
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#endif

#define V_SIZE		sizeof(v_tab)
#define T_SIZE		sizeof(t_tab)

char *pname;
bit16 sum;
int hpsfd;

extern int errno;

DNL_HDR dnl_header;

main(argc, argv)
int argc;
char **argv;
{
	char c, *key, *dlfile, *dldev, iobuf[512];
	short c_flag, error;
	int i, ii, result, temp, count, dlfd;
	long total_size;
	long sys_addrs[8];

	bit8 b_pri, b_id;
	bit16 v_us_size, t_count;
	bit32 v_ws, v_ws_size, t_ws;
	bit32 onb_addr;
	bit32 onb_vtab;
	bit32 onb_ttab;
	HPS_LONG base_adr;
	struct v_table v_tab;
	struct t_table t_tab;


	pname = argv[0];
	c_flag = 0;
	error = 0;

	/* Scan the command line, if a keyword is present */
	i = argc;
	if (argc > 3)
	{
		key = argv[1];
		while ((c = *key++) != '\0')
		{
			switch (c) /* Possible values: cr */
			{
				case 'c':	/* Set the "configure" flag */
					c_flag = 1;
					i -= 1;
#					ifdef DEBUG
						fprintf(stderr, "%s: CONFIGURE\n", pname);
#					endif
					break;
					
				default:
					fprintf(stderr,
						"%s: \"%c\" bad keyletter\n", pname, c);
					usage(argv[0]);
					error = 1;
					break;
			}
		}
	}
	if (error)
		exit(1);

	if (i != 3)
	{
		fprintf(stderr,
			"%s: Need download device and file name to download\n", pname);
		usage(argv[0]);
		exit(1);
	}

	dldev = argv[argc-2];
	dlfile = argv[argc-1];

#ifdef DEBUG
	fprintf(stderr,"dldev name = %s\n",dldev);
	fprintf(stderr,"dlfile name = %s\n",dlfile);   
#endif

	/* Get the HPS host-adapter's on-board System address table */
	if (gaddrs(sys_addrs, dldev) != 0)
	{
		if (errno)
			exit(errno);
		else
			exit(1);
	}
#	ifdef DEBUG
		fprintf(stderr, "%s: Onboard addresses:\n", pname);
		for (ii=0; ii<8; ii++)
			fprintf(stderr, "0x%08X\n", sys_addrs[ii]);
#	endif

	/* Open download file (executable binary image in .dnl format) */
	if ((dlfd = open(dlfile, 0)) < 0)
	{
		fprintf(stderr, "%s: Cannot open file %s.\n", pname, dlfile);
		exit(1);
	}
#	ifdef DEBUG
		fprintf(stderr, "%s: Download file %s\n", pname, dlfile);
#	endif

	/* Open HPS raw driver */
	if ((hpsfd = open(dldev, 2)) < 0)
	{
		fprintf(stderr, "%s: Cannot open file %s\n", pname, dldev);
		perror(pname);
		exit(1);
	}

	/* Get download file's header */
	if (read(dlfd, &dnl_header, sizeof(dnl_header)) != sizeof(dnl_header))
	{
		fprintf(stderr, "%s: Error reading %s's header\n", pname, dlfile);
		perror(pname);
		exit(1);
	}

	/* Setup for load */
	total_size = dnl_header.end - dnl_header.start;
	onb_addr = dnl_header.start;

#	ifdef DEBUG
		fprintf(stderr, "%s: Total size is %d\n", pname, total_size);
#	endif


	/* Download code */
#	ifdef DEBUG
		fprintf(stderr, "%s: Downloading to 0x%06x\n", pname, onb_addr);
#	endif

	/* Read information needed to configure board */
	if (c_flag)
	{
		/* Get initial on-board configuration tables */
		if (hps_read(&v_tab, sys_addrs[1], V_SIZE) != V_SIZE)
		{
			fprintf(stderr, "%s: VRTX table read() failed\n", pname);
			exit(1);
		}
		if (v_tab.tracer != NULL)
		{
			if (hps_read(&t_tab, v_tab.tracer, T_SIZE) != T_SIZE)
			{
				fprintf(stderr, "%s: TRACER table read() failed\n", pname);
				exit(1);
			}
		}

		/* Collect download configuration info from object file */
		onb_vtab = dnl_header.vt_start;
		onb_ttab = v_tab.tracer ? dnl_header.tt_start : NULL;
		v_ws = dnl_header.v_ws;
		v_ws_size = dnl_header.v_ws_size;
		t_ws = v_ws + v_ws_size;
		v_us_size = dnl_header.v_us_size;
		t_count = dnl_header.t_count;

		/* Plug new values into our copy of configuration tables */
		v_tab.tracer = onb_ttab;
		v_tab.workspace = v_ws;
		v_tab.ws_size = v_ws_size;
		v_tab.us_size = v_us_size;
		v_tab.task_c = t_count;
/*		*(long *) ((char *)&t_tab+6) = t_ws;  /* store table workspace size */
		t_tab.workspacel = (short)((int)t_ws & 0xffff);
		t_tab.workspaceh = (short)(((int)t_ws >> 16) & 0xffff);

		/* make sure we're not going to overwrite the existing OS table ... */
		if (onb_vtab == sys_addrs[1])
		{
			/* fprintf(stderr, 
			"%s: ERROR--trying to overwrite configuration table\n", argv[0]); */
			close(dlfd);
			close(hpsfd);
			exit(3);
		}
	}
	

	/*
	 * This is the main loop of the loader.  Each time around, one block
	 * of code is written to the HPS host adpater.  The loop terminates
	 * after all the code and initialized data of the download file has
	 * been written to the host adapter.
	 */
	i=total_size;
	while (i)
	{
		/* Read a block from the download file */
		count = (i > sizeof(iobuf)) ? sizeof(iobuf) : i;
		if ((result = read(dlfd, iobuf, count)) != count)
		{
			fprintf(stderr, "%s: read(%s) == %d\n", pname, dlfile, result);
			fprintf(stderr, "%s: i == %d, count == %d, onb_addr == 0x%06x\n",
				pname, i, count, onb_addr);
			perror(pname);
			exit(1);
		}

		/* Write the block to the host adapter */
		if (hps_write(iobuf, onb_addr, count) != count)
		{
			fprintf(stderr, "%s: Write error\n", pname);
			exit(1);
		}
		onb_addr += count;
		i -= count;
	}

	/* This is where the configuration actually occurs */
	if (c_flag)
	{
		/* Send new configuration tables back to board */
#		ifdef DEBUG
			fprintf(stderr, "%s: New config    \n", pname);
			fprintf(stderr, "%s: V table   == 0x%x\n", pname, onb_vtab);
			fprintf(stderr, "%s: T table   == 0x%x\n", pname, onb_ttab);
			fprintf(stderr, "%s: v_ws      == 0x%x\n", pname, v_ws);
			fprintf(stderr, "%s: v_ws_size == 0x%x\n", pname, v_ws_size);
			fprintf(stderr, "%s: v_us_size == 0x%x\n", pname, v_us_size);
			fprintf(stderr, "%s: t_count   == 0x%x\n", pname, t_count);
			fprintf(stderr, "%s: t_ws      == 0x%x\n", pname, t_ws);
#		endif
#		ifdef DEBUG
			printf("About to write VRTX config, type return");
			getchar();
#		endif

		if (hps_write(&v_tab, onb_vtab, V_SIZE) != V_SIZE)
		{
			fprintf(stderr, "%s: VRTX table write() failed\n", pname);
			exit(1);
		}

		if (v_tab.tracer != NULL)
		{
#			ifdef DEBUG
				printf("About to write TRACER config, type return");
				getchar();
#			endif

			if (hps_write(&t_tab, onb_ttab, T_SIZE) != T_SIZE)
			{
				fprintf(stderr, "%s: TRACER table write() failed\n", pname);
				exit(1);
			}
		}

		/* Re-initialize VRTX */
#		ifdef DEBUG1
			printf("onb_vtab == 0x%06x\n", onb_vtab);
			printf("About to VINIT, type return");
			getchar();
#		endif
		/* TEMP */
		/*sleep(2);		/* to wait for write iocb to go out */
		if(hps_ioctl(INIT, onb_vtab + 4, 0, 0)) {
			close(dlfd);
			close(hpsfd);
			exit(1);
		}
#		ifdef DEBUG
			printf("VINIT complete\n");
#		endif
	}
	

	/* Issue a CREATE */
	if (dnl_header.boot_start)
	{
#		ifdef DEBUG1
		fprintf(stderr, "%s: addr == 0x%x\n", pname, dnl_header.boot_start);
		fprintf(stderr, "%s: b-pri   == 0x%x\n", pname, dnl_header.b_pri);
		fprintf(stderr, "%s: b_id    == 0x%x\n", pname, dnl_header.b_id);
		printf("About to CREATE, type return");
		getchar();
#		endif
		if (hps_ioctl(CREATE, dnl_header.boot_start, dnl_header.b_pri, 
					dnl_header.b_id)) {
			close(dlfd);
			close(hpsfd);
			exit(1);
		}
	}

	close(dlfd);
	close(hpsfd);

	/* fprintf(stderr,"Download of %s complete\n",dldev); */
	exit(0);
}




/*
 * Raw driver IOCB and data 
 */
struct whole_thing
{
	LOADER_IOCB 	raw_iocb;
	char 		raw_buffer[512];
} iocb_buf;


/*
 * This routine sends a block of code to the host adapter.  Routine
 * "check" is called to compare our computed checksum with that returned
 * by the HPS loader.
 */
hps_write(source, dest, count)
char *source;
bit32 dest;
int count;
{
	int i;
#ifdef STREAM
	struct strioctl strioctl;
	if (count + sizeof (LOADER_IOCB) > MAXIOCBSZ)
	{
		fprintf(stderr, "user data exeeded maximum ioctl msg size\n");
		return(-1);
	}
#endif

#	ifdef DEBUG
		fprintf(stderr, "%s: 0x%06x --> 0x%06x, %d bytes\n",
			pname, source, dest, count);
#	endif

	for (i = 0; i < count; i++)
		iocb_buf.raw_buffer[i] = source[i];

  	iocb_buf.raw_iocb.header.len = sizeof(LOADER_IOCB);
  	iocb_buf.raw_iocb.header.maj_dev = 1;
  	iocb_buf.raw_iocb.header.min_dev = 0;
  	iocb_buf.raw_iocb.header.int_enab = 1;
	iocb_buf.raw_iocb.header.datal.hps_addrl = (short)((bit32)iocb_buf.raw_buffer & 0xffff);
	iocb_buf.raw_iocb.header.datah.hps_addrh = (short)(((bit32)iocb_buf.raw_buffer >> 16) & 0xffff);
  	iocb_buf.raw_iocb.header.data_len = count;
  	iocb_buf.raw_iocb.header.data_dir = TO_HPS;

  	iocb_buf.raw_iocb.command = WRITE;
  	iocb_buf.raw_iocb.cmd_modifier = 0;
  	iocb_buf.raw_iocb.hpsadrl.hps_addrl = (short)(dest & 0xffff);
  	iocb_buf.raw_iocb.hpsadrh.hps_addrh = (short)((dest >> 16) & 0xffff);
  	iocb_buf.raw_iocb.priority = 0;
  	iocb_buf.raw_iocb.tid = 0;
  	iocb_buf.raw_iocb.mode = 1;

	/*
	 * At this point, the IOCB is passed to the HPS driver, which coordinates
	 * the actual interface to the board through the DPRAM parameter block.
	 */
#ifdef STREAM
/* for stream driver need to issue a special ioctl */
	strioctl.ic_cmd = DO_IOCB;
	strioctl.ic_timout = 0;		/* default 15 sec */
	strioctl.ic_dp = (char *)&iocb_buf;	/* data to be send with ioctl cmd */
	strioctl.ic_len = sizeof (LOADER_IOCB) + count;		/* data */
	if (ioctl(hpsfd, I_STR, &strioctl) < 0)
	{
		perror("ioctl I_STR: DO_IOCB failed\n");
	}
#else STREAM
#ifdef BSD
  	if (write(hpsfd, &iocb_buf, sizeof(iocb_buf)) < 0)
          	printf("Write failed: %d\n", errno);
#else BSD
	if (ioctl(hpsfd, DO_IOCB, &iocb_buf) < 0)
		printf("Write failed: %d\n", errno);
#endif BSD
#endif STREAM

/*
 * if using stream driver, dont check checksum because the mail fields are
 * not return with the iocb during write
 */
	if (check(source, count, iocb_buf.raw_iocb.header.maill.hps_addrl))
	{
		fprintf(stderr, 
			"%s: Checksum error: HPS == 0x%x; appl == 0x%x\n",
			pname, iocb_buf.raw_iocb.header.maill.hps_addrl, sum);
		return(-1);
	}
	return(count);
}



/*
 * This routine reads a block from the HPS host adapter.  The loader
 * uses this routine to read VRTX/TRACER configuration tables from the
 * board, before reconfiguring the HPS operating system.
 */
hps_read(dest, source, count)
char *dest;
bit32 source;
int count;
{
	int i;
#ifdef STREAM
	struct strioctl strioctl;
	if (count + sizeof (LOADER_IOCB) > MAXIOCBSZ)
	{
		fprintf(stderr, "user data exeeded maximum ioctl msg size\n");
		return(-1);
	}
#endif

#	ifdef DEBUG
		fprintf(stderr, "%s: 0x%06x <-- 0x%06x, %d bytes\n",
			pname, dest, source, count);
#	endif

  	iocb_buf.raw_iocb.header.len = sizeof(LOADER_IOCB);
  	iocb_buf.raw_iocb.header.maj_dev = 1;
  	iocb_buf.raw_iocb.header.min_dev = 0;
  	iocb_buf.raw_iocb.header.int_enab = 1;
	iocb_buf.raw_iocb.header.datal.hps_addrl = (short)((bit32)iocb_buf.raw_buffer & 0xffff);
	iocb_buf.raw_iocb.header.datah.hps_addrh = (short)(((bit32)iocb_buf.raw_buffer >> 16) & 0xffff);
  	iocb_buf.raw_iocb.header.data_len = count;
  	iocb_buf.raw_iocb.header.data_dir = FROM_HPS;

  	iocb_buf.raw_iocb.command = READ;
  	iocb_buf.raw_iocb.cmd_modifier = 0;
  	iocb_buf.raw_iocb.hpsadrl.hps_addrl = (short)(source & 0xffff);
  	iocb_buf.raw_iocb.hpsadrh.hps_addrh = (short)((source >> 16) & 0xffff);
  	iocb_buf.raw_iocb.priority = 0;
  	iocb_buf.raw_iocb.tid = 0;
  	iocb_buf.raw_iocb.mode = 1;

	/*
	 * At this point, the IOCB is passed to the HPS driver, which coordinates
	 * the actual interface to the board through the DPRAM parameter block.
	 */
#ifdef STREAM
/* for stream driver need to issue a special ioctl */
	strioctl.ic_cmd = DO_IOCB;
	strioctl.ic_timout = 0;		/* default 15 sec */
	strioctl.ic_dp = (char *)&iocb_buf;	/* data to be send with ioctl cmd */
	strioctl.ic_len = sizeof (LOADER_IOCB) + count;		/* data */
	if (ioctl(hpsfd, I_STR, &strioctl) < 0)
	{
		perror("ioctl I_STR: DO_IOCB failed\n");
	}
#else STREAM
#ifdef BSD
  	if (read(hpsfd, &iocb_buf, sizeof(iocb_buf)) < 0)
	 	printf("Read failed: %d\n",errno);
#else BSD
	if (ioctl(hpsfd, DO_IOCB, &iocb_buf) < 0)
		printf("Read failed: %d\n", errno);
#endif BSD
#endif STREAM

	for (i = 0; i < count; i++)
	  dest[i] = iocb_buf.raw_buffer[i];
	return(count);
}


/*
 * This routine issues IOCTL commands to the HPS host adapter.  These
 * include the INIT and CREATE commands to reconfigure the HPS operating
 * system, and create the downloaded task.
 */
hps_ioctl(cmd_mod, hps_addr, pri, tid)
int cmd_mod, pri, tid;
char *hps_addr; 
{

#ifdef STREAM
	struct strioctl strioctl;
#endif

	iocb_buf.raw_iocb.header.len = sizeof(LOADER_IOCB);
	iocb_buf.raw_iocb.header.maj_dev = 1;
	iocb_buf.raw_iocb.header.min_dev = 0;
	if (cmd_mod == INIT)
		iocb_buf.raw_iocb.header.int_enab = 0;
	else
		iocb_buf.raw_iocb.header.int_enab = 1;
	iocb_buf.raw_iocb.header.data_len = 0;
	iocb_buf.raw_iocb.header.data_dir = TO_HPS;

	iocb_buf.raw_iocb.command = IOCTL;
	iocb_buf.raw_iocb.cmd_modifier = cmd_mod;
	iocb_buf.raw_iocb.hpsadrl.hps_addrl = (short)((bit32)hps_addr & 0xffff);
	iocb_buf.raw_iocb.hpsadrh.hps_addrh = (short)(((bit32)hps_addr >> 16) & 0xffff);
	iocb_buf.raw_iocb.priority = pri;
	iocb_buf.raw_iocb.tid = tid;
	iocb_buf.raw_iocb.mode = 1;

	/*
	 * At this point, the IOCB is passed to the HPS driver, which coordinates
	 * the actual interface to the board through the DPRAM parameter block.
	 */

/* try  to use write instead to see if it works with the custom driver */
#ifdef STREAM
		strioctl.ic_cmd = DO_IOCB;
		strioctl.ic_timout = 0;		/* default 15 sec */
		strioctl.ic_dp = (char *)&iocb_buf;	/* data to be send with ioctl cmd */
		strioctl.ic_len = sizeof (LOADER_IOCB);		/* data */
		if (ioctl(hpsfd, I_STR, &strioctl) < 0)
		{
			perror("ioctl I_STR: DO_IOCB failed\n");
		}

#else STREAM
#ifdef BSD
		ioctl(hpsfd, 
			DO_IOCB | (0xC0000000 | (sizeof(LOADER_IOCB)<<16)), &iocb_buf);
#else BSD
		ioctl(hpsfd, DO_IOCB, &iocb_buf);
#endif BSD
#endif STREAM

}

/*
 * This routine computes an HPS checksum on a block, and compares
 * it with a given checksum.
 */
check(buffer, count, checksum)
bit8 *buffer;
bit16 checksum;
{
	sum = 0;
	while (count--)
		sum += *buffer++ & 0xff;
	return sum != checksum;
}

usage(name)

char *name;

{
	fprintf (stderr, "%s: usage: %s [c] <raw_device> <dnl_filename>\n", 
			name, name);
}





gaddrs(table, rawdev)
long *table;
char *rawdev;
{
	char *devname;
	int result, i, hpsfd;
	long *ptr;
#ifdef STREAM
	struct strioctl strioctl;
#endif

	/* Raw driver IOCB and table area */
	struct
	{
		LOADER_IOCB 	raw_iocb;
		long 		sys_addrs[8];
	} iocb_buf;

#	ifdef DEBUG
		fprintf(stderr, "gaddrs(): Enter.  devname == %s\n", rawdev);
#	endif

	if (rawdev != NULL)
		devname = rawdev;
	else
		devname = RAW_DEVNAME;

	/* Open HPS interface */
	if ((hpsfd = open(devname, 2)) < 0)
	{
#	ifdef DEBUG
		printf("gaddrs(): Open failed on %s\n", devname);
#	endif
		return (-1);
	}

	/* Get the system address table */
	iocb_buf.raw_iocb.header.len = sizeof(LOADER_IOCB);
	iocb_buf.raw_iocb.header.maj_dev = 1;
	iocb_buf.raw_iocb.header.min_dev = 0;
	iocb_buf.raw_iocb.header.int_enab = 1;
	iocb_buf.raw_iocb.header.datal.hps_addrl = (short)((bit32)iocb_buf.sys_addrs & 0xffff);
	iocb_buf.raw_iocb.header.datah.hps_addrh = (short)(((bit32)iocb_buf.sys_addrs >> 16) & 0xffff);
	iocb_buf.raw_iocb.header.data_len = sizeof(iocb_buf.sys_addrs);
	iocb_buf.raw_iocb.header.data_dir = FROM_HPS;

	iocb_buf.raw_iocb.command = IOCTL;
	iocb_buf.raw_iocb.cmd_modifier = GET_ADDRS;
	iocb_buf.raw_iocb.hpsadrl.hps_addrl = 0;
	iocb_buf.raw_iocb.hpsadrh.hps_addrh = 0;
	iocb_buf.raw_iocb.priority = 0;
	iocb_buf.raw_iocb.tid = 0;
	iocb_buf.raw_iocb.mode = 1;


#	ifdef STREAM
/* for stream driver need to issue a special ioctl */
	strioctl.ic_cmd = DO_IOCB;
	strioctl.ic_timout = 0;		/* default 15 sec */
	strioctl.ic_dp = (char *)&iocb_buf;	/* data to be send with ioctl cmd */
	strioctl.ic_len = sizeof (iocb_buf);		/* data */
	if ((result = ioctl(hpsfd, I_STR, &strioctl)) < 0)
	{
		perror("ioctl I_STR: DO_IOCB failed\n");
	}
#	else STREAM
#ifdef BSD
		result = read( hpsfd, &iocb_buf, sizeof( iocb_buf) );
#	else BSD
		result = ioctl(hpsfd, DO_IOCB, &iocb_buf);
#	endif BSD
#endif STREAM

	close(hpsfd);

#	ifdef DEBUG
		fprintf(stderr, "gaddrs(): %d\n", result);
#	endif

	if (result < 0)
		return(result);
	else
	{
		ptr = iocb_buf.sys_addrs;
		for (i = sizeof(iocb_buf.sys_addrs)/sizeof(long); i--;)
		{
#			ifdef DEBUG
				printf("%02d: %08x <-- %04x\n", i, table, *ptr);
#			endif
			*table++ = *ptr++;
		}
		return(0);
	}
}
