/* $Id: hps_cload.c,v 2.6 91/09/06 10:17:59 annette Exp $ */
/****************************************************************/
/* hps_cload.c:  HPS Network Downloader							*/
/****************************************************************/
/*																*/
/*                 Confidential Information                     */
/*      Limited Distribution to Authorized Persons Only         */
/*     Created 1988 and Protected as an Unpublished Work        */
/*          Under the U.S. Copyright Act of 1976.               */
/*         Copyright (c) 1988, 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 (in Systech's ".dnl"		*/
/*	format) into HPS cluster controllers and other network		*/
/*	devices (using the network interface task and the cluster	*/
/*	download task) and starts the downloaded code.  			*/
/*																*/
/*	The program is invoked as follows:							*/
/*																*/
/*		hps_cload [fdc] [config] [devname]						*/
/*																*/
/*	where the options are:										*/
/*																*/
/*		f - the configuration file (default is hps_config)		*/
/*		d - the device name (default is /dev/rhp0)				*/
/*		c - run continuously (as a daemon)						*/
/*																*/
/*	The format of config file is:								*/
/*																*/
/*		- Comment lines start with a "#" character in column 1.	*/
/*		- Empty lines are ignored.								*/
/*		- The starting network address is the first field.		*/
/*		- The ending network address is the second field.		*/
/*		- The raw device name is the third field.				*/
/*		- The download file name is the fourth field. Hps_cload	*/
/*		  can handle multiple files in the fourth field, but	*/
/*		  the feature is there for future use only.				*/
/*																*/
/*	For example:												*/
/*																*/
/*		#	starting	ending			device		download	*/
/*		#	net addr	net addr		 name		file name	*/
/*		#	--------	--------		------		---------	*/
/*																*/
/*				1			1		HPS-7080-020	uno.dnl		*/
/*																*/
/*		# 	The above line downloads uno.dnl to HPS-7080-020	*/
/*		#	at station address 1.								*/
/*																*/
/*				5			3F			ALL			uno.dnl		*/
/*																*/
/*		# 	The above line downloads uno.dnl to all device 		*/
/*		#	types whose address is between 5 and 3F inclusive.	*/
/*																*/
/*				0			FF		HPS-7082-020	uno.dnl		*/
/*																*/
/*		# 	The above line downloads uno.dnl to HPS-7082-020	*/
/*		#	at any address.										*/
/*																*/
/****************************************************************/


/*
 *	INCLUDES
 */

#include <stdio.h>
#include <fcntl.h>
#include <sys/errno.h>
#include "hps.std.h"
#include "hps.iocb.h"
#include "hps.ioctl.h"
#include "hps.conf.h"
#include "hps.onb.h"
#include "hps.net.h"
#include "hps.ccdl.h"
#include "hps.dev_type.h"
#include "hps.swap.h"
#ifdef HPS_STREAMS
#include <sys/types.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include "hps_svcintf.h"
#endif


/*
 *	DEFINES
 */

#define		MAX_LLEN	256				/* maximum line length */
#define		MAX_DNL_BLK	490				/* max download block size */
typedef struct 							/* device info table */
{
	int 	dev_n;						/* device number */
	char 	*name;						/* device name */
} DEV_INFO;


/*
 *	EXPORTS
 */

char  hps_intel		= 0;				/* host byte ordering	*/

/*
 *	IMPORTS
 */

extern int errno;						/* global error number */


/*
 *	LOCALS
 */

static int	do_load();					/* execute load sequence */
static int	look_up_dev_type();			/* get device type */
static void	usage();					/* print usage string */

static char files[MAX_LLEN];			/* ptr to download filenames string */
static char dlfile[MAX_LLEN];			/* ptr to download filename string */
static char dev_type_name[MAX_LLEN];	/* device type name to download to */
static int 	dev_type;					/* the device type code	*/
static int 	hpsfd;						/* file descriptor for raw device */
static long mailbox = 0;				/* Holds iocb errors */

static bit8 lgl[MAXCNCT];				/* buffer for Load Group List */
static int 	lgl_len;					/* LGL length counter */

static AST	astbuf[64];					/* buffer for Active Station Table */
										/*
										 * 64 = 512/8
										 * 64 = sizeof(Sun raw data buffer)/
										 * 		sizeof(AST)
										 */
										

static DEV_INFO device_info[] = 
{
	/*
	 *	cluster controllers, Old DUART design, Downloadable
	 */
	HPS_7080_020,	"HPS-7080-020",		/* 8-channel downloadable */
	HPS_7082_020,	"HPS-7082-020",		/* 8-channel downloadable with Centr*/
	HPS_7088_020,	"HPS-7088-020",		/* 16-channel downloadable */

	/*
	 * cluster controllers, DUART/ASIC design, Downloadable
	 */
	HPS_7080_040,   "HPS-7080-040",		/* 8-channel */
	HPS_7082_040,   "HPS-7082-040",		/* 8-channel with centronics */
	HPS_7088_040,   "HPS-7088-040",		/* 16-channel */

	/*
	 * cluster controllers, OCTART/ASIC design, Downloadable
	 */
	HPS_7080_030,	"HPS-7080-030",		/* 8-channel */
	HPS_7082_030,	"HPS-7082-030",		/* 8-channel with centronics */
	HPS_7088_030,	"HPS-7088-030",		/* 16-channel */

	/*
	 * Host adapters 
	 */
	HPS_6840,	"HPS-6840",				/* MBI */
	HPS_6860,	"HPS-6860",				/* MBI */
	HPS_6945,	"HPS-6945",				/* VME */
	HPS_6940,	"HPS-6940",				/* MBI */
	HPS_6960,	"HPS-6960",				/* MBI */
	HPS_6850,	"HPS-6850",				/* MBII */
	HPS_6950,	"HPS-6950",				/* MBII */
	-1,			"all",
	-1,			"ALL",
	0,			"END OF TABLE"
};
	


/*
 *	FUNCTION main - Generic loader
 *
 *	DESCRIPTION
 *		This program loads executable code into HPS cluster
 *		controllers and host adaptors and starts the down-
 *		loaded code.
 *
 *	PARAMETERS
 *		Usage: hps_cload [fdc] [config] [devname]
 *
 *	RETURN VALUES
 *		0 - Success.
 *		1 - Failure.
 *
 */
main(argc, argv)
int			argc;						/* argument count */
char		**argv;						/* argument array */
{
	int		pid;						/* process identification */
	int		status;						/* child process return */
	char	*dldev;						/* default device */
	char	*config_file;				/* default config file*/
	long	ast_len;					/* AST length */
	int		ast_len_save;				/* copy of AST length */
	long	cd;							/* network state change id */
	int		i;							/* temp counter */
	int		aststat;					/* AST status */
	int		net_start;					/* start of network address range */
	int		net_end;					/* end of network address range */
	AST		*astp;						/* pointer to AST buffer */
	FILE	*stream;					/* config file pointer */
	char	s[MAX_LLEN];				/* config file line */
	short	daemon;						/* execution type */
	short	error;						/* argument error */
	char	*key;						/* argument key */
	char	c;							/* temp character */
#ifdef HPS_STREAMS
	struct strbuf	ctlbuf;
	long			ctlcmd;
	int				gflags;
#endif	
	short tmp;

	tmp = 0;
	hps_intel = *(char *)&tmp;

	/*
	 *	Set up defaults.
	 */
	dldev = RAW_DEVNAME;				/* default device */
	config_file = "hps_config";			/* default config file */
	daemon = NO;						/* don't run as a daemon */

	error = NO;

	/*
	 *	Scan the command line.
	 */
	if (argc >= 2)
	{
		key = argv[1];
		i = argc - 2;
		while (*key)
		{
			switch (c = *key++)			/* Possible values: f, d, c */
			{
				case 'f':				/* Set the config file name */
					if (i <= 0)
					{
						fprintf(stderr,
							"%s: Need configuration file name\n", argv[0]);
						error = YES;
					}
					else 
					{
						config_file = argv[argc - i];
						i--;
					}
					break;

				case 'd':				/* Set the device name */
					if (i <= 0)
					{
						fprintf(stderr, "%s: Need device name\n", argv[0]);
						error = YES;
					}
					else 
					{
						dldev = argv[argc - i];
						i--;
					}
					break;

				case 'c':				/* Set the execution mode */
					daemon = YES;
					break;

				default:
					fprintf(stderr,
						"%s: \"%c\" bad keyletter\n", argv[0], c);
					error = YES;
					break;
			}
		}
	}

	if (error)
	{
		usage(argv[0]);
		exit(1);
	}

	/* 
	 *	Open the HPS raw driver.
	 */
	if ((hpsfd = open(dldev, O_RDWR)) < 0)
	{
		fprintf(stderr, "%s: Cannot open device %s\n", argv[0], dldev);
		perror(argv[0]);
		exit(1);
	}

	if (daemon)
	{
		/*
		 *	Open network state change notification minor device of network 
		 *	interface module to turn on network notification.  Should return
		 *	zero.
		 */
		cd = nif_open(hpsfd, NET_STNTFY, &mailbox);
		if ( cd < 0 || mailbox >> 16 )
		{
			fprintf(stderr, "%s: Cannot enable network state change in interface module.\n", argv[0]);
			if (errno)
				perror(argv[0]);
			else
				errtab(mailbox >> 16, argv[0]);
			
			exit(1);
		}
	}

	/*
	 *	Open network configuration minor device of network interface 
	 *	module to get a connection descriptor for AST requests.  Should
	 *	return zero in upper word if successful.
	 */
	if ((cd = nif_open(hpsfd, NET_AST, &mailbox)) < 0)
	{
		fprintf(stderr, "%s: Cannot retrieve active station table from network interface module\n", argv[0]);
		if (errno)
			perror(argv[0]);
		else
			errtab(mailbox >> 16, argv[0]);

		if (daemon)
		{
			nif_close(hpsfd, 0, NET_STNTFY);
		}
		exit(1);
	}
	cd &= 0xFF;							/* connection descriptor */

	/*
	 *	This section of code takes an "Active Station Table" (AST), and
	 *	searches for stations (devices) in the "Primitive Idle" (PRIMIDLE)
	 *	or "Primitive Idle--Fail Ports" (PRIMIDLEFP) states.  This code
	 *	reads the configuration file and for each configuration file entry,
	 *	it searches for PRIMIDLE/FP devices that match the entry.  For
	 *	each configuration file entry that has matching devices, this routine
	 *	establishes a Load Group.  The configuration file specifies the
	 *	download image appropriate for each Load Group.  Then, for each Load
	 *	Group, this routine spawns a child process that manages the Load
	 *	Sequence for that Load Group.
	 */

	for(;;)
	{
		/*
		 *	As long as upper word of returned mail is set to E_OVERFLOW,
		 *	complete AST has not been read.  Start by faking it.
		 */
		aststat = E_OVERFLOW;
		lgl_len = 0;

		while (aststat)
		{
			ast_len = nif_read(hpsfd, cd, astbuf, sizeof(astbuf), &mailbox);
			ast_len_save = ast_len;		/* save it	*/

			/*
			 *	Return code is in upper word. Length of AST is in lower 
			 *	word.
			 */
			aststat = ast_len >> 16;

			/*
			 *	Status returned should either be zero (AST complete)
			 *	or E_OVERFLOW (more to read).  Anything else is an error.
			 */
			if (aststat && aststat != E_OVERFLOW)
			{
				fprintf(stderr, "%s: Cannot read active station table.\n", argv[0]);
				if (errno)
					perror(argv[0]);
				else
					errtab(mailbox >> 16, argv[0]);

				if (daemon)
				{
					nif_close(hpsfd, 0, NET_STNTFY);
				}
				nif_close(hpsfd, cd, NET_AST);
				exit(1);
			}

			/* 
			 *	If the AST is empty, do nothing.
			 */
			if (!(ast_len &= 0xFFFF))
				break;

			/* 
			 *	Open the config file.
			 */
			if ((stream = fopen(config_file,"r")) == NULL)
			{
				fprintf(stderr,
			     "%s: Cannot open configuration file %s\n",argv[0],config_file);
				if (errno)
					perror(argv[0]);
				if (daemon)
				{
					nif_close(hpsfd, 0, NET_STNTFY);
				}
				nif_close(hpsfd, cd, NET_AST);
				exit(1);
			}

			/* 
			 *	Search through the config file.
			 */
			while (fgets(s,MAX_LLEN,stream) != NULL)
			{
				if (s[0] == '#') 		/* ignore comments lines */
					continue;
				if (s[0] == '\n') 		/* ignore blank lines */
					continue;

				for (i=0; i<sizeof(files); ++i)
					files[i] = 0;
				if (sscanf(s,"%x %x %s %256c",	/* 256 == MAX_LLEN */
					&net_start, &net_end, dev_type_name,files) != 4)
				{
					fprintf(stderr,
							"%s: Illegal format in configuration file %s\n",
							argv[0],config_file);
					if (errno)
						perror(argv[0]);
					if (daemon)
					{
						nif_close(hpsfd, 0, NET_STNTFY);
					}
					nif_close(hpsfd, cd, NET_AST);
					exit(1);
				}

				/*
				 *	Go through the AST, looking for stations in primitive 
				 *	idle state, matching the download criteria of the
				 *	current line in the config file.
				 */
				astp = astbuf;

				dev_type = look_up_dev_type(dev_type_name);

				ast_len = ast_len_save;
				while (ast_len)
				{
					if ((astp->state==PRIMIDLE || astp->state==PRIMIDLEFP) &&
						((bit8)astp->net_addr >= net_start &&
						 (bit8)astp->net_addr <= net_end) &&
						(dev_type < 0 || astp->dev_type == dev_type))
					{
						/*
						 *	Found a station in primitive idle state with
						 *	the correct network address and device type.
						 *
						 *	Add its network address to the LGL.
						 */

						lgl [lgl_len++] = astp->net_addr;
						if (lgl_len == MAXCNCT)
						{
							/* 
							 *	LGL is full--do a load sequence.
							 */
							if ((pid = fork()) == 0) /* child process */
							{
								do_load(argv[0]);
								exit(0);
							}
							lgl_len = 0;
						}
					}
					++astp;
					ast_len -= sizeof(AST);

				}	/* end while processing this portion of the AST */

				if (lgl_len)
				{
					if ((pid = fork()) == 0) /* child process */
					{
						do_load(argv[0]);
						exit(0);
					}
					lgl_len = 0;
				}
			}
			fclose(stream);
		}		/* end while more of AST to read from HPS */
		do
		{
			pid = wait(&status);
		} while (pid != FAIL || errno != ECHILD);

		/*
		 *	Wait for another station to download.
		 */
		if (daemon)
		{
#ifdef HPS_STREAMS
			ctlbuf.maxlen = 0;
			ctlbuf.len    = sizeof(long);
			ctlbuf.buf    = (char *)&ctlcmd;
			ctlcmd        = HPS_NOTIFYACT;

			if (putmsg(hpsfd,&ctlbuf,0,0) < 0)
			{
				printf("hps_cload: putmsg failed\n");
				exit(5);
			}

			ctlbuf.len    = 0;
			ctlbuf.maxlen = sizeof(long);
			ctlbuf.buf    = (char *)&ctlcmd;
			gflags        = 0;
			ctlcmd        = 0;

			if (getmsg(hpsfd,&ctlbuf,0,&gflags) != 0)
			{
				printf("hps_cload: getmsg failed\n");
				exit(6);
			}
#else
			ioctl(hpsfd, NOTIFY_ACT);
#endif
		}
		else
		{
			break;
		}

	}	/* end forever */

	/*
	 *	If we exit, then close various files and minor devices that we
	 *	previously opened.
	 */
	if (daemon)
	{
		nif_close(hpsfd, 0, NET_STNTFY);
	}
	nif_close(hpsfd, cd, NET_AST);
	close(hpsfd);
}


/*
 *	FUNCTION do_load - execute a load sequence
 *
 *	DESCRIPTION
 *		This routine executes a load sequence.
 *
 *	PARAMETERS
 *		None.
 *
 *		There are no parameters, but this routine uses the global variables
 *		dlfile, hpsfd, lgl and lgl_len.
 *
 *	RETURN VALUES
 *		SUCCESS (0), or
 *		FAIL (-1)
 *
 *		An E_CLOSED (load sequence terminated) or E_CONNECT (no clusters
 *		connected) error on start load seuqence or write is not considered
 *		to be a failure.  If the clusters have dropped out of the load group
 *		because they no longer need to be downloaded, then they've been
 *		taken care of by someone else.  If they have dropped out because of
 *		an error, we will eventually find them back in primitive idle state,
 *		and try again.
 */
static int do_load(pname)
char *pname;
{
	int		i;
	int		j;
	int		load_id;
	int		result;
	int		dlfd;
	int		count;
	int		n_blocks;
	long	total_size;
	long	onb_addr;
	long	start_addr;
	short	sum;
	char	*dp;
	DNL_HDR	dnl_header, sdnl_header;
	char	iobuf[MAX_DNL_BLK];
	char	*fs = files;
	char	*df;
	
	while (*fs != '\0')					/* while not end of string */
	{
		for (i = 0; i < sizeof(dlfile); ++i)
			dlfile[i] = 0;

		df = dlfile;
		/* get one download file */
		while (*fs != ' ' && *fs != '\t' && *fs != '\n' && *fs != '\0')	
		{
			*df++ = *fs++;
		}
		while (*fs == ' ' || *fs == '\t' || *fs == '\n')
			fs++;			/* skip over white space to the next string */

		/* 
		 *	Open download file (executable binary image in .dnl format)
		 */
		if ((dlfd = open(dlfile, O_RDONLY)) < 0)
		{
			fprintf(stderr, "%s: Cannot open cluster controller download file %s\n", pname, dlfile);
			/*
			 *  The perror parameter must be a null string as opposed to
			 *  a NULL since SCO UNIX does not support the NULL parameter
			 *  like other versions of UNIX.
			 */
			if (errno)
				perror(pname);
			return(FAIL);
		}

		/* 
		 *	Get download file's header.
		 */
		if (read(dlfd, hps_intel ? &sdnl_header : &dnl_header, sizeof(DNL_HDR))
			!= sizeof(DNL_HDR))
		{
			fprintf(stderr, "%s: Error reading %s's header\n", pname, dlfile);
			if (errno)
				perror(pname);
			close(dlfd);
			return(FAIL);
		}

		if (hps_intel)
		{
			SWAP_4BYTE(&dnl_header.start,      &sdnl_header.start);
			SWAP_4BYTE(&dnl_header.end,        &sdnl_header.end);
			SWAP_4BYTE(&dnl_header.vt_start,   &sdnl_header.vt_start);
			SWAP_4BYTE(&dnl_header.vt_len,     &sdnl_header.vt_len);
			SWAP_4BYTE(&dnl_header.tt_start,   &sdnl_header.tt_start);
			SWAP_4BYTE(&dnl_header.tt_len,     &sdnl_header.tt_len);
			SWAP_4BYTE(&dnl_header.boot_start, &sdnl_header.boot_start);
			SWAP_4BYTE(&dnl_header.v_ws,       &sdnl_header.v_ws);
			SWAP_4BYTE(&dnl_header.v_ws_size,  &sdnl_header.v_ws_size);
			SWAP_2BYTE(&dnl_header.v_us_size,  &sdnl_header.v_us_size);
			SWAP_2BYTE(&dnl_header.t_count,    &sdnl_header.t_count);
			SWAP_2BYTE(&dnl_header.b_pri,      &sdnl_header.b_pri);
			SWAP_2BYTE(&dnl_header.b_id,       &sdnl_header.b_id);
		}

		/* 
		 *	Set up for load.
		 */
		total_size = dnl_header.end - dnl_header.start;
		onb_addr   = dnl_header.start;		/* starting load addr */
		start_addr = dnl_header.boot_start;	/* starting execution addr */

		/*
		 *	Send start load sequence with LGL.  If there are no load 
		 *	sequences available, sleep awhile and try again.
		 */
		do
		{
			load_id = cdl_stload (hpsfd, lgl, lgl_len, onb_addr, 
										total_size, &mailbox);
			switch (result = load_id >> 16)
			{
				case SUCCESS:
					break;

				case E_CONNECT:
				case E_CLOSED:
					close(dlfd);
					return(SUCCESS);

				case E_BUSY:
					sleep(2);
					break;

				default:
					fprintf(stderr,
						"%s: Error on ST_LOAD.\n", pname);
					if (errno)
						perror(pname);
					else
						errtab(mailbox >> 16, pname);
					close(dlfd);
					return(FAIL);
			}
		} while (result);

		load_id &= 0xFF;

		/*
		 *	This is the main loop of the routine.  Each time around, one block
		 *	of code is written.  The loop terminates after all the code and 
		 *	initialized data of the download file has been written.
		 */
		i = total_size;
		n_blocks = 0;
		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);
				/* 
				 *	Read failed.  Send abort load sequence.
				 */
				if (result = (cdl_abtld ( hpsfd, load_id, &mailbox ) >> 16))
				{
					fprintf(stderr, 
						"%s: Abort load error.\n", pname);
					if (errno)
						perror(pname);
					else
						errtab(mailbox >>16, pname);
				}
				close(dlfd);
				return(FAIL);
			}

			/*
			 *	Compute the checksum.
			 */
			j = count;
			dp = iobuf;
			sum = 0;
			while (j--)
				sum += *dp++ & 0xFF;

			/*
			 *	Write the block.
			 */
			result = cdl_write(hpsfd,iobuf,count,load_id,onb_addr,
						sum,&mailbox) >> 16;
			switch (result)
			{
				case SUCCESS:
					break;

				case E_CONNECT:
				case E_CLOSED:
					close(dlfd);
					return(SUCCESS);

				case E_CHKSUM:
				default:
					fprintf(stderr, "%s: Write error.\n", pname);
					if (errno)
						perror(pname);
					else
						errtab(mailbox >>16, pname);

					/* Send abort load sequence */
					if (result = (cdl_abtld(hpsfd, load_id, &mailbox) >> 16))
					{
						fprintf(stderr, "%s: Abort load error.\n", pname);
						if (errno)
							perror(pname);
						else
							errtab(mailbox >>16, pname);
					}
					close(dlfd);
					return(FAIL);
			}
			n_blocks++;
			onb_addr += count;
			i -= count;
		}

		/*
		 *	Send end load sequence to start execution.  Zero data length
		 *	will tell cluster download module that no LGL is to be returned.
		 */
		result = cdl_endld(hpsfd, 0, 0, load_id, start_addr, &mailbox) >> 16;
		if (result && !(result == E_CONNECT || result == E_CLOSED))
		{
			fprintf(stderr, 
				"%s: End load sequence error.\n", pname);
			if (errno)
				perror(pname);
			else
				errtab(mailbox >>16, pname);
			close(dlfd);
			return(FAIL);
		}

		close(dlfd);
	}

	return(SUCCESS);
}


/*
 *	FUNCTION look_up_dev_type - get device type
 *
 *	DESCRIPTION
 *		This routine searchs the structure device_info array and
 *		returns the device type code, if found.
 *
 *	PARAMETERS
 *		name - device name (read only)
 *
 *	RETURN VALUES
 *		n  - device code, if found
 *		0  - device not found
 *		-1 - ALL device type.
 */
static int look_up_dev_type(name)
char *name;
{
	register DEV_INFO *df = &device_info[0];

	while (df->dev_n != 0)
	{
		if (strcmp(name,df->name) == 0)
			return(df->dev_n);
		df++;
	}
	return(0);
}
	

/*
 *	FUNCTION usage - print usage string
 *
 *	DESCRIPTION
 *		Print the usage string.
 *
 *	PARAMETERS
 *		name - program name (read only)
 *
 *	RETURN VALUES
 *		None.
 */
static void usage(name)
char *name;
{
	fprintf (stderr, "%s: usage: %s [fdc] [config] [devname]\n", name, name);
}

/*
 * MODIFICATION HISTORY:
 *
 * $Log:	hps_cload.c,v $
 * Revision 1.1.2.1  92/06/24  13:53:29  marker
 * Branch RISCOS_5_01_2_FIXES off of trunk.
 * 
 * Revision 2.6  91/09/06  10:17:59  annette
 * Updated mailbox in some calls to &mailbox.  Changed check for error return
 * for nif_open.
 * INTERNAL
 * 
 * Revision 2.5  91/09/03  17:36:54  annette
 * Changed error message handling.
 * INTERNAL
 * 
 * Revision 2.4  91/08/06  10:19:33  annette
 * Changed reference for raw device from a hardcoded "/dev/rhp0" to
 * RAW_DEVNAME.
 * SPR=741
 * 
 * Revision 2.3  91/01/10  17:21:55  dave
 * Modify header; add footer.
 * COSMETIC
 * 
 */
