/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: installboot.c 1.5 90/07/27 $"

/*
 * installboot: install the appropriate bootstrap in the boot partition
 * of a disk.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/devsw.h>
#include <sys/vtoc.h>
#include <sys/fcntl.h>
#include <sys/ufsfilsys.h>

#include <sys/cfg.h>
#include <sys/slicreg.h>

#include <stdio.h>

#ifdef lint
struct streamtab {
	int dummy;
};
#endif	/* lint */

#define	BOOTDIR	"/stand"	/* directory for boot blocks */
#define SSMDIR "/ssm/fw"	/* directory for SSM level 2 bootstraps */
#define SSMLVL2 "lvl2"		/* name of level 2 firmware */
#define DIAGDIR "/dev/diag/rdsk" /* contains diagnostic interface for vtoc */
#define RAWDIR  "/dev/rdsk"	/* directory for raw disk interfaces */
#define CHUNKSIZE  8 * 1024	/* size of IO buffer for SSM lvl2 fw */

#define CD_SIZE         (CD_STAND_ADDR - (int)CD_LOC)

extern	char	*strrchr();
extern	char	*valloc();
extern	char	*memset();
extern	void	perror();

char	*get_part();

char diagdev[MAXPATHLEN];
char *source_file;		/* source file of firmware if SSM2 */
int   source_flag=0;		/* BOOL if source file was given */

/*
 * pad text to at least CD_STAND_ADDR so
 * later mmap() does not trash real text space.
 */
pad()
{

#if CD_STAND_ADDR != 0x4000
 ERROR: "CD_STAND_ADDR" has changed, check mmap() assumptions
#endif

#ifdef  i386
        /*
         * ASMQ, ASMEND workaround is necessary for now because
         * 386 optimizer moves pseudo operands out of a routine
         * and into data space.  Remove this comment and the "#ifdef i386"
         * code when optimizer is fixed.
         */
        asm("/ASMQ");
        asm(" .set .,.+[16\*1024]");
        asm("/ASMEND");
#else
        asm(" .set .,.+16\*1024]");
#endif
}


main(argc, argv)
	int argc;
	char *argv[];
{
	char *cp, *bp, *special;
	char type[3];
	struct stat sb;

#ifdef  lint
        pad();
#endif

        /*
         * Sanity check text padding at runtime
         */
        if ((unsigned int)main < CD_STAND_ADDR) {
                fprintf(stderr,
                "installboot: linked incorrectly, correct operation impossible.\n");
                exit(1);
        }

	argc--, argv++;
	if (argc < 1 || argc > 2) {
		(void) fprintf(stderr, "usage: installboot special-device [ source-file ]\n");
		(void) exit(1);
	}
	special = argv[0];

	if (argc == 2 ) {		/* if source file was given remember it */
		source_file = argv[1];
		source_flag = 1;
	}

	cp = strrchr(special, '/');
	if (cp == NULL)
		cp = special;
	else
		cp++;
	bp = cp;
	if (stat(special, &sb) >= 0 && (sb.st_mode & S_IFMT) != S_IFCHR)
		fatal("%s: Must specify a character-special device", special);
	type[0] = *cp++;
	type[1] = *cp;
	type[2] = '\0';

	/*
	 * derive the diagnostic device by nulling out the slice number
	 * (if present) and appending the diagnostic directory path
	 */
	cp = strrchr(bp, 's');
	if (cp != NULL && cp != bp)
		*cp = NULL;
	
	(void) sprintf(diagdev, "%s/%s", DIAGDIR, bp);

	switch (type[0]) {

	/*
	 * 'zd'
	 */
	case 'z':
		if (strcmp(type, "zd") == 0) {
			install_SCED_boot(get_part(diagdev, V_BOOT), type);
		} else {
			fatal("%s: Unknown controller type %s", special, type);
		}
		break;

	/*
	 * 'sd'
	 */
	case 's':
		if (strcmp(type, "sd") == 0) {
			install_SCED_boot(get_part(diagdev, V_BOOT), type);
		} else {
			fatal("%s: Unknown controller type %s", special, type);
		}
		break;

	/*
	 * 'wd'
	 */
	case 'w':
		if (strcmp(type, "wd") == 0) {
			install_SSM_boot(get_part(diagdev, V_FW), type);
		} else {
			fatal("%s: Unknown controller type %s", special, type);
		}
		break;

	default:
		fatal("%s: Unknown controller type %s", special, type);
	}

	(void) exit(0);
	/*NOTREACHED*/
}

char *bootimage;

/*
 * install_SCED_boot
 *
 *	Install the 8K bootstrap in the first 16 sectors of a disk.
 *
 * It is assumed that the device passed to this routine will be
 * mapped to the first 8K of usable disk space.  The 8K bootstrap
 * (which is needed on SCED-based systems) will be installed from
 * /stand/boot<type> into this partition.
 */
install_SCED_boot(dev, type)
	char *dev, *type;
{
	int fd;
	char standalonecode[MAXPATHLEN];

	if (bootimage == (char *)NULL) {
		bootimage = valloc(BBSIZE);
		if (bootimage == (char *)NULL) {
			(void) fprintf(stderr,
				"installboot: Cannot valloc bootimage");
			(void) exit(1);
		}
	}
	(void) memset(bootimage, '\0', BBSIZE);
	(void) sprintf(standalonecode, "%s/boot%s", BOOTDIR, type);
	(void) printf("installboot: installing %s into %s\n",
		standalonecode, dev);
	fd = open(standalonecode, 0);
	if (fd < 0) {
		(void) fprintf(stderr, "installboot: "); perror(standalonecode);
		(void) exit(1);
	}
	if (read(fd, bootimage, BBSIZE) < 0) {
		(void) fprintf(stderr, "installboot: "); perror(standalonecode);
		(void) exit(2);
	}
	(void) close(fd);
	fd = open(dev, 1);
	if (fd < 0) {
		(void) fprintf(stderr, "installboot: "); perror(dev);
		(void) exit(1);
	}
	if (write(fd, bootimage, BBSIZE) != BBSIZE) {
		(void) fprintf(stderr, "installboot: "); perror(dev);
		(void) exit(2);
	}
	(void) close(fd);
}

/*
 * install_SSM_boot
 */
/* ARGSUSED */
install_SSM_boot(dev, type)
	char *dev, *type;
{
	int fd, fd1, nbytes;
	char firmware[MAXPATHLEN];

	if (bootimage == (char *)NULL) {
		bootimage = valloc(CHUNKSIZE);
		if (bootimage == (char *)NULL) {
			(void) fprintf(stderr,
				"installboot: Cannot valloc bootimage");
			(void) exit(1);
		}
	}

	/* 
	 * check the console SSM board type, if its a SSM Series 1 then
	 *  use the older level 2 firmware to put onto the FW slice
	 * else if its a SSM2 Series II then
	 *  use the level B firmware as the source
	 */
	switch( determine_ssm_board_type() ) {
		case SLB_SSMBOARD:
			if (source_flag == 1)
				/*
				 * can utilize the 2nd argument (src filename)
				 */
				(void) sprintf(firmware, "%s", source_file);
			else
				/*
				 * old default name generation for SSMs
				 */
				(void) sprintf(firmware, "%s/%s", SSMDIR, SSMLVL2);
			break;

		case SLB_SSM2BOARD:
			/*
			 * if the console is on the SSM2 then the source_file
			 * argument MUST be present
			 */
			if (source_flag == 0) {
				(void) fprintf(stderr, "installboot: source-file argument must be specified for SSM2 systems\n"); 
				(void) exit(1);
			}

			(void) sprintf(firmware, "%s", source_file);
			break;

		default:
			fprintf(stderr, "installboot: bad ssm board type\n");
			exit(1);
	}
		
	(void) printf("installboot: installing %s into %s\n", firmware, dev);
	fd = open(firmware, 0);
	if (fd < 0) {
		(void) fprintf(stderr, "installboot: "); perror(firmware);
		(void) exit(1);
	}
	fd1 = open(dev, 1);
	if (fd1 < 0) {
		(void) fprintf(stderr, "installboot: "); perror(dev);
		(void) exit(1);
	}

	do {
		(void) memset(bootimage, '\0', CHUNKSIZE);
		if ((nbytes = read(fd, bootimage, CHUNKSIZE)) < 0) {
			(void) fprintf(stderr, "installboot: ");
			perror(firmware);
			(void) exit(2);
		}
		if (write(fd1, bootimage, CHUNKSIZE) != CHUNKSIZE) {
			(void) fprintf(stderr, "installboot: "); perror(dev);
			(void) exit(2);
		}
	} while (nbytes != 0);
	(void) close(fd);
	(void) close(fd1);
}

char partbuf[MAXPATHLEN];

/*
 * get_part
 *
 *	return the name of the boot partition
 *
 * The name passed into get_part() is a device which can be opened
 * and the VTOC read from.  The VTOC is searched for the first partition
 * of type "type", and the name which matches this partition is returned.
 */
char *
get_part(dev, type)
	char *dev;
	int   type;
{
	int fd, i;
	char *cp;
	struct vtoc *v;

	v = (struct vtoc *)valloc(V_SIZE);
	if (v == (struct vtoc *)NULL)
		fatal("Not enough memory");
	fd = open(dev, O_RDONLY);
	if (fd < 0)
		fatal("%s: unable to open device to read VTOC", dev);
	if (ioctl(fd, V_READ, v) < 0)
		fatal("%s: unable to read VTOC from device", dev);
	(void) close(fd);
	for (i = 0; i < v->v_nparts; i++) {
		if (v->v_part[i].p_type == type)
			break;
	}
	if (i == v->v_nparts) {
		fatal("%s: unable to find %s partition", dev,
		type == V_BOOT ? "bootstrap" : "firmware");
	}
	cp = strrchr(dev, '/');
	if (cp == NULL)
		cp = dev;
	else
		cp++;
	(void) sprintf(partbuf, "%s/%ss%d", RAWDIR, cp, i);
	return(partbuf);
}
	

/*VARARGS*/
fatal(fmt, arg1, arg2)
	char *fmt;
{

	(void) fprintf(stderr, "installboot: ");
	(void) fprintf(stderr, fmt, arg1, arg2);
	(void) putc('\n', stderr);
	(void) exit(10);
}


/*
 *  determine_ssm_board_type() looks into the controller descriptor and returns
 *   the board type of the SSM hosting the console
 *
 *	uses mmap to map the address of the descriptor area into this 
 *	program's address space
 */
determine_ssm_board_type()
{
	int fd, i;
	char *memfile="/dev/kmem";
        struct ctlr_toc *toc;
        register struct ctlr_desc *cd;

        /*
         * Open file containing config data.
         */
        fd = open(memfile, O_RDONLY, 0);
        if (fd < 0) {
                perror(memfile);
                exit(1);
        }

        /*
         * mmap() config data to CD_LOC in my virtual address
         * so config data pointers can be followed directly.
         *
         * This requires the pad function above to pad text space
         * so when we mmap() over it, we don't trash real code space.
         */
        if ( mmap((caddr_t)CD_LOC, CD_SIZE,
             PROT_READ, MAP_SHARED, fd, (off_t)CD_LOC) < 0) {
                perror("mmap");
                exit(1);
        }

	/*
 	 * look into the controler descriptor area for a SSMx board type
	 *  find the one with the console
	 */
	toc = &CD_LOC->c_toc[SLB_SSMBOARD];
        cd = &CD_LOC->c_ctlrs[toc->ct_start];

        /*
         * Only 1 SSM1 board will ever be found in a system, but scan
         * down the array looking for the console
         */
        for (i = 0; i < toc->ct_count; i++, cd++) {
           	if (FAILED(cd))
			continue;
                if (cd->cd_ssm_cons) {
			close(fd);
			return( SLB_SSMBOARD );
		}
        }

        /* for this device, looking into the table of contents for the SSM2
         * board, then address the controler descriptor for the SSM2. Scan
	 * for console, if found then this is the board
         */
        toc = &CD_LOC->c_toc[SLB_SSM2BOARD];
        cd = &CD_LOC->c_ctlrs[toc->ct_start];
        for (i = 0; i < toc->ct_count; i++, cd++) {
                if (FAILED(cd))
			continue;
                if (cd->cd_ssm_cons) {
			close(fd);
			return( SLB_SSM2BOARD );
		}
	}

	close(fd);

	return( 0 );	/* no SSM or SSM2 board found with console */
}
