/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1987, 1991 by John E. Lloyd.			    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/

/*---------------------------------------------------------------------------
 *		
 * MODULE NAME:	auxBoot
 *
 * DESCRIPTION:	This file contains procedures which load automatically
 *		boot the 'minikernel' on the auxiliary RCI task CPUs.
 * 
 * AUTHOR/DATE:	John Lloyd, Fall 1987
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

#include <stdio.h>
#include <errno.h>

#include "../h/cdefs.h"
#include "../h/rciConfig.h"
#include "../h/errorCodes.h"

#if defined(uVAX_CFG)
#include <a.out.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>

#include "../sys.uVAX/rciext/kaioctl.h"		/* UNIX auxiliary CPU ioctls */
#include "../sys.uVAX/rciext/kamsgs.h"		/* UNIX auxiliary CPU control messages */
#endif

char *sbrk();
char *getenvRCCL();
extern int errno;

#define BOOT_FILE	"/lib/auxbootstrap"
#define KERNEL_FILE	"/lib/auxkernel"
#define BOOTMSG_SIZE	0x1800

#define FNS	80	/* file name size */

/* ----- Error Messages. Defined externally to save space ----- */

static char *badIoctlMsg	= "%s -- %s ioctl failed, errno %d\n";
static char *noRCCLEnvMsg	= "%s -- environment variable RCCL not set\n";
static char *cantOpenFileMsg	= "%s -- can't open file %s\n";
static char *cantStatFileMsg	= "%s -- can't stat file %s\n";
static char *outOfMemoryMsg	= "%s -- out of memory\n";
static char *fileErrorMsg	= "%s -- error in file %s\n";
static char *readErrorMsg	= "%s -- error reading file\n";

static sendKernelText();
static sendKernelData();
static sendKernelExec();

auxKernelVerify (kafd, str)
{
	char *fxnName = "auxKernelVerify()";

#if defined(uVAX_CFG)
	/* Routine used to verify that a particular aux CPU is running
	   the approparite mini-kernel. This is done by sending a
	   'verify' message to the CPU in question and checking the
	   string that it returns againts 'str'. The message is sent
	   using an ioctl provided by the ka driver. If no reply
	   to the message is received, it can be assumed that the
	   CPU is not running any mini-kernel (or at least any mini-kernel
	   that supports the 'verify' message).
	*/

	/* current minikernel id string is ``RCI minikernel 1.00'' */

	/* possible ioctl errors :

		ETIMEDOUT
		EMSGSIZE	(can't happen)
		EWOULDBLOCK	(can't happen)
		ENXIO		nothing on slave
		EFAULT		(can't happen)
	*/

#define MAX_SKV	64
	char skvBuf[MAX_SKV];
	struct blkdescto skvDesc;

	skvDesc.size = MAX_SKV;
	skvDesc.addr = skvBuf;
	skvDesc.timeout = 20;
	if (ioctl(kafd, KAIOC_GETSKV, &skvDesc) < 0)
	 { if (errno == ETIMEDOUT ||
	       errno == ENXIO)
	    { return (0);
	    }
	   else
	    { errorMsg (EInternal, badIoctlMsg, fxnName, "GETSKV", errno);
	      return (-1);
	    }
	 }
	if (strncmp (str, skvBuf, MAX_SKV) == 0)
  	 { return (1);
	 }
 	else
	 { return (0);
	 }
#else
	errorMsg (ENotSupported, "%s aux CPU code not implemented\n");
	return(-1);
#endif
}

auxPrimaryBoot (kafd)
int kafd;
{
	/* The first function called when it is necssary to boot an
	   auxiliary CPU. This does the primiary bootstrap, as required
	   by the microVAX CPU aux CPU booting procedure. A bootstrap
	   is set up in the memory of the ARBITER, which is then made visible
	   over the Qbus. The HALT bit in the auxiliaries CSR is then
	   cleared, and it boots from this bootstrap enough of a kernel
	   to load the main mini-kernel (which is done by the routine
	   'auxKernelBoot()'.

	   Most of the operation of this function is acheived through the
	   use of special ioctls serviced by the ka driver for the CPU
	   in question.
	*/

	char *fxnName = "auxPrimaryBoot()";

#if defined(uVAX_CFG)

	char bootfileName[FNS+1];	/* file containing bootstrap */
	char *rcclDir;			/* RCCL home directory */
	int bootfd;			/* boot file descriptor */
	struct stat bootstat;		/* boot file stat */
	int bootsize;			/* size of boot file */
	char *bootarea;			/* memory boot file is read into */
	struct blkdesc bootdesc;	/* descriptor for bootarea */

	if ((rcclDir = getenvRCCL()) == NULL)
  	 { errorMsg (EEnviron, noRCCLEnvMsg, fxnName);
	   return (-1);
	 }
	STRNCPY (bootfileName, rcclDir, FNS);
 	strncat (bootfileName, BOOT_FILE, FNS);
	if ((bootfd = open (bootfileName, O_RDONLY, 0)) < 0)
  	 { errorMsg (EBadAccess, cantOpenFileMsg, fxnName, bootfileName);
	   return (-1);
	 }
	if (fstat (bootfd, &bootstat) < 0)
  	 { errorMsg (EBadAccess, cantStatFileMsg, fxnName, bootfileName);
	   close (bootfd);
	   return (-1);
	 }
	bootsize = bootstat.st_size;
	if ((bootarea = sbrk(bootsize)) == (char *)-1)
  	 { errorMsg (EMemory, outOfMemoryMsg, fxnName);
	   close (bootfd);
	   return (-1);
	 }
 	if (read_restart (bootfd, bootarea, bootsize) != bootsize)
  	 { errorMsg (EBadObj, fileErrorMsg, fxnName, bootfileName);
	   close (bootfd);
	   return (-1);
	 }
	bootdesc.size = bootsize;
	bootdesc.addr = bootarea;
 	if (ioctl (kafd, KAIOC_BOOT, &bootdesc) < 0)
  	 { errorMsg (EInternal, badIoctlMsg, fxnName, "BOOT", errno);
	   close (bootfd);
	   return (-1);
	 }
	close (bootfd);
	return (1);	
#else
	errorMsg (ENotSupported, "%s aux CPU code not implemented\n");
	return(-1);
#endif
}

auxKernelBoot (kafd)
int kafd;
{
	/* Routine to load the main mini-kernel. This routine must
	   be called after auxPrimaryBoot(). It obtains the mini-kernel
	   from a system file, and then proceeds to load it into the
	   auxiliary CPU using ioctls maintained by ka driver for the
	   auxiliary CPU in question. These ioctls work by sending download
	   messages to the CPU; the messages are honored by the bootstrap
	   which was loaded by 'auxPrimaryBoot()'.
	 */

	char *fxnName = "auxKernelBoot()";	

#if defined(uVAX_CFG)
	char kernfileName[FNS+1];	/* file containing kernel */
	char *rcclDir;			/* RCCL home directory */
	int kernfd;			/* kern file descriptor */
	struct exec kernhdr;		/* kern file header */

	if ((rcclDir = getenvRCCL()) == NULL)
  	 { errorMsg (EEnviron, noRCCLEnvMsg, fxnName);
	   return (-1);
	 }
	STRNCPY (kernfileName, rcclDir, FNS);
 	strncat (kernfileName, KERNEL_FILE, FNS);
	if ((kernfd = open (kernfileName, O_RDONLY, 0)) < 0)
  	 { errorMsg (EBadAccess, cantOpenFileMsg, fxnName, kernfileName);
	   return (-1);
	 }
	if ((read_restart (kernfd, &kernhdr, sizeof(kernhdr))) != sizeof(kernhdr))
  	 { errorMsg (EBadObj, fileErrorMsg, fxnName, kernfileName);
	   close (kernfd);
	   return (-1);
	 }
	if (N_BADMAG (kernhdr))
  	 { errorMsg (EBadObj, fileErrorMsg, fxnName, kernfileName);
	   close (kernfd);
	   return (-1);
	 }
	if (sendKernelText(kafd, kernfd, &kernhdr) < 0)
	 { close (kernfd);
	   return (-1);
	 }
	if (sendKernelData(kafd, kernfd, &kernhdr) < 0)
  	 { close (kernfd);
	   return (-1);
	 }
	if (sendKernelExec(kafd, &kernhdr) < 0)
  	 { close (kernfd);
	   return (-1);
	 }
	close (kernfd);
	return (1);
#else
	errorMsg (ENotSupported, "%s aux CPU code not implemented\n");
	return(-1);
#endif
}

#if defined(uVAX_CFG)

static sendKernelText(kafd, kernfd, hdr)
int kafd, kernfd;
struct exec *hdr;
{
	/* routine to load the text portion of the mini-kernel */

	struct blkdesc msgdesc;		/* descriptor for boot messages */
	char bootmsg[BOOTMSG_SIZE];	/* area for boot messages */

	int left;
	int caddr;
	int n;

	char *fxnName = "sendKernelText()";

	left = hdr->a_text;
	lseek (kernfd,(off_t)N_TXTOFF(*hdr),L_SET);
	caddr = 0;
 	while (left > 0)
	 { n = (left < sizeof(bootmsg)-13) ? left : sizeof(bootmsg)-13;
	   bootmsg[0] = MSG_BOOTDATA;
    	   *(int *)&bootmsg[1] = n + 13;
    	   *(int *)&bootmsg[5] = caddr;
           *(int *)&bootmsg[9] = n;
           if (read_restart (kernfd, &bootmsg[13], n) != n)
            { errorMsg (EBadObj, readErrorMsg, fxnName);
	      return (-1);
	    }
	   msgdesc.size = n + 13;
	   msgdesc.addr = &bootmsg[0];
	   if (ioctl (kafd, KAIOC_WMSGBUF, &msgdesc) < 0)
	    { errorMsg (errno == ENXIO ? EAuxHalted : EInternal,
			badIoctlMsg, fxnName, "WMSGBUF", errno);
	      return (-1);
	    }
	   left -= n;
	   caddr += n;
	 }
	return (1);
}

static sendKernelData (kafd, kernfd, hdr)
int kafd, kernfd;
struct exec *hdr;
{
	/* Routien to load the data section of the mini-kernel */

	struct blkdesc msgdesc;		/* descriptor for boot messages */
	char bootmsg[BOOTMSG_SIZE];	/* area for boot messages */

	int left;
	int caddr;
	int n;

	char *fxnName = "sendKernelData()";
	 
	left = hdr->a_data;
	if (hdr->a_magic == OMAGIC)
	 { caddr = hdr->a_text;
	 }
	else
	 { caddr = (hdr->a_text + 1023) & ~1023;
	 }
	while (left > 0)
	 { n = (left < sizeof(bootmsg)-13) ? left : sizeof(bootmsg)-13;
	   bootmsg[0] = MSG_BOOTDATA;
	   *(int *)&bootmsg[1] = n + 13;
	   *(int *)&bootmsg[5] = caddr;
	   *(int *)&bootmsg[9] = n;
	   if (read_restart(kernfd,&bootmsg[13],n) != n)
            { errorMsg (EBadObj, readErrorMsg, fxnName);
	      return (-1);
	    }
	   msgdesc.size = n + 13;
	   msgdesc.addr = &bootmsg[0];
	   if (ioctl (kafd, KAIOC_WMSGBUF, &msgdesc) < 0)
	    { errorMsg (errno == ENXIO ? EAuxHalted : EInternal,
			badIoctlMsg, fxnName, "WMSGBUF", errno);
	      return (-1);
	    }
	   left -= n;
	   caddr += n;
	 }

	left = hdr->a_bss;
	while (left > 0)
	 { n = (left < sizeof(bootmsg)-13) ? left : sizeof(bootmsg)-13;
	   bootmsg[0] = MSG_BOOTDATA;
	   *(int *)&bootmsg[1] = n + 13;
	   *(int *)&bootmsg[5] = caddr;
	   *(int *)&bootmsg[9] = n;
	   bzero(&bootmsg[13],n);
	   msgdesc.size = n + 13;
	   msgdesc.addr = &bootmsg[0];
	   if (ioctl(kafd,KAIOC_WMSGBUF,&msgdesc) < 0)
	    { errorMsg (errno == ENXIO ? EAuxHalted : EInternal,
			badIoctlMsg, fxnName, "WMSGBUF", errno);
	      return (-1);
	    }
	   left -= n;
	   caddr += n;
	 }
	return (1);
}

static sendKernelExec (kafd, hdr)
int kafd;
struct exec *hdr;
{
	/* A routine which starts the mini-kernel running */

	struct blkdesc msgdesc;		/* descriptor for boot messages */
	char bootmsg[BOOTMSG_SIZE];	/* area for boot messages */

	char *fxnName = "sendKernelExec()";

	bootmsg[0] = MSG_BOOTEXEC;
	*(int *)&bootmsg[1] = 9;
	*(int *)&bootmsg[5] = hdr->a_entry + 2; /* skip entry mask */
	msgdesc.size = 9;
	msgdesc.addr = &bootmsg[0];
	if (ioctl(kafd,KAIOC_WMSGBUF,&msgdesc) < 0)
	 { errorMsg (EInternal, badIoctlMsg, fxnName, "WMSGBUF", errno);
	   return (-1);
	 }
	return (1);
}

#ifdef TEST

main()
{
	int kaf0, kaf1;

	if ((kaf0 = open ("/dev/ka0", O_RDWR)) < 0)
  	 { fatalMsg (-1, "can't open /dev/ka1\n");
	 }
	if ((kaf1 = open ("/dev/ka1", O_RDWR)) < 0)
  	 { fatalMsg (-1, "can't open /dev/ka1\n");
	 }
	if (auxPrimaryBoot (kaf1) < 0)
  	 { printErrors();
	   exit (-1);
	 }
 	if (auxKernelBoot (kaf1) < 0)
	 { printErrors();
	   exit (-1);
	 }
}
#endif

#endif
