/*-
 * Copyright (c) 1997 Causality Limited.
 * All rights reserved.
 *
 * This code was written by Mark Brinicombe
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Causality Limited.
 * 4. The name of Causality Limited may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CAUSALITY LIMITED ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUSALITY LIMITED BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Angel communication code
 *
 * Copyright 1997 Perihelion Software Ltd.
 * All Rights Reserved.
 */

/* Modified by Deborah A. Wallach (kerr@pa.dec.com) 12/22/97 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <getopt.h>
#include <arpa/inet.h>

#include "options.h"

#define QUIET 1

#define swap(x) x;

int verbose = 0;

struct bootoptions boot_options = {
	DEFAULT_SERIAL_DEVICE, DEFAULT_SERIAL_OPTIONS,
	NULL, 0, 0,
	DEFAULT_BASE_ADDRESS, DEFAULT_ENTRY_ADDRESS,
	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	NULL,
	DEFAULT_BAUD_RATE,
	NULL, 0, 0
};

void usage(void);
int boot(int serial_fd, int image_fd, int o_image_fd, struct bootoptions *boot_options);
int parse_options_file(char *file, struct bootoptions *boot_options, int flags);
void dump_options(struct bootoptions *boot_options);

extern int Angel_SendMessage(int serial_fd, unsigned char *buf, unsigned int count);
extern int Angel_DumpPacket(int serial_fd);
extern int Angel_SetBaudrate(int serial_fd, int rate, int fifoon);

int main(int argc, char *argv[])
{
	char *image;
	int serial_fd;
	int image_fd, o_image_fd = 0;
	int ch;

	parse_options_file(".angelrc", &boot_options, QUIET);

	while ((ch = getopt(argc, argv, "d:c:b:s:o:e:f:0:1:2:3:x:vhS:O:B:")) != -1) {
		switch(ch) {
		case 'v':
			verbose = 1;
			break;
		case 'd':
			boot_options.serial_device = optarg;
			break;
		case 'c':
			boot_options.serial_options = optarg;
			break;
		case 'f':
			parse_options_file(optarg, &boot_options, 0);
			break;
		case 'b':
			boot_options.base_address = strtoul(optarg, NULL, 16);
			break;
		case 'e':
			boot_options.entry_address = strtoul(optarg, NULL, 16);
			break;
		case 's':
			boot_options.image_size = strtoul(optarg, NULL, 16);
			break;
		case 'o':
			boot_options.image_offset = strtoul(optarg, NULL, 16);
			break;
		case '0':
			boot_options.regs[0] = strtoul(optarg, NULL, 16);
			break;
		case '1':
			boot_options.regs[1] = strtoul(optarg, NULL, 16);
			break;
		case '2':
			boot_options.regs[2] = strtoul(optarg, NULL, 16);
			break;
		case '3':
			boot_options.regs[3] = strtoul(optarg, NULL, 16);
			break;
		case 'x':
			boot_options.exec = optarg;
			break;
		case 'h':
			usage();
			break;
		case 'S':
			boot_options.baud_rate = strtoul(optarg, NULL, 10);
			break;
		case 'O':
			boot_options.o_image_file = optarg;
			break;
		case 'B':
			boot_options.o_base_address = strtoul(optarg, NULL, 16);
			break;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0 && boot_options.image_file == NULL)
		usage();

	if (argc > 0) {
		boot_options.image_file = argv[0];
		++argv;
		--argc;
	}

	if (verbose)
		dump_options(&boot_options);

	serial_fd = serial_open(boot_options.serial_device, boot_options.serial_options);
	if (serial_fd == -1)
		exit(1);

	image_fd = open(boot_options.image_file, O_RDONLY);
	if (image_fd == -1) {
		fprintf(stderr, "Cannot read image file\n");
		return(1);
	}

	if (boot_options.image_size == 0) {
		off_t size;
		size = lseek(image_fd, 0, SEEK_END);

		if (size > 0xffffffffL) {
			fprintf(stderr, "Image file is too big\n");
			return(1);
		}
		boot_options.image_size = (unsigned int)size;
	}

	if (lseek(image_fd, (off_t)boot_options.image_offset, SEEK_SET) != (off_t)boot_options.image_offset) {
		fprintf(stderr, "Cannot seek to requested image offset\n");
		return(1);
	}



	if (boot_options.o_image_file) {
	  o_image_fd = open(boot_options.o_image_file, O_RDONLY);
	  if (o_image_fd == -1) {
	    fprintf(stderr, "Cannot read other image file\n");
	    return(1);
	  }
	  if (boot_options.o_image_size == 0) {
		off_t size;
		size = lseek(o_image_fd, 0, SEEK_END);

		if (size > 0xffffffffL) {
			fprintf(stderr, "Image file is too big\n");
			return(1);
		}
		boot_options.o_image_size = (unsigned int)size;
	      }
	  if (lseek(o_image_fd, (off_t)0, SEEK_SET) != (off_t)0) {
		fprintf(stderr, "Cannot seek to requested image offset\n");
		return(1);
	      }
	}

	boot(serial_fd, image_fd, o_image_fd, &boot_options);

	close(image_fd);
	if (boot_options.o_image_file) close(o_image_fd);

	serial_close(serial_fd);

	if (boot_options.exec)
		execlp(boot_options.exec, boot_options.exec, NULL);

}

void usage(void)
{
	fprintf(stderr, "Usage:\tangelboot [-v] [-h] [-f options file] [-d device] [-c options] [-b base] [-s size] [-o offset] [-e entry] [-S baudrate] [-O otherimage] [-B other base] image\n");
	exit(0);
}


int boot(int serial_fd, int image_fd, int o_image_fd, struct bootoptions *boot_options)
{
	unsigned char	Home, Oppo;
	unsigned char	AngelMsg[4096];
	unsigned char	*p, *r;
	int		l, i, reply, Size;
	unsigned int regs[6];

	if (verbose) printf("Negotiating...");

	p = AngelMsg;
			/******* LEN *******/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x24;

	/* Chan */   /* Home */   /* Opp */    /* Flags */
	*p++ = 0x03; *p++ = 0x01; *p++ = 0x00; *p++ = 0x01;

	/*---- REASON ---------*/ /* Channel */ /* Direct */
	*p++ = 0x05; *p++ = 0x00; *p++ = 0x03; *p++ = 0x00; /* Param negotiate	*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/
	*p++ = 0x01; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* # parameter blks	*/
	*p++ = 0x00; *p++ = 0xc0; *p++ = 0x00; *p++ = 0x00; /* Baudrate		*/
	*p++ = 0x01; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* # options	*/
	{
	  int baud = htonl(boot_options->baud_rate);
	  *p++ = (unsigned char)((0xff000000&baud)>>24);
	  *p++ = (unsigned char)((0x00ff0000&baud)>>16);
	  *p++ = (unsigned char)((0x0000ff00&baud)>> 8);
	  *p++ = (unsigned char)((0x000000ff&baud)>> 0);
	}

	Angel_SendMessage(serial_fd, AngelMsg, 39);

	/* Right now I don't care, just consume the packet. Of course if you ask for
	 * a baud rate you can't have then you'll die soon.
	 */

	Angel_DumpPacket(serial_fd);

	Angel_SetBaudrate(serial_fd, boot_options->baud_rate, 0);

	/* After issue of Param Neg, need to wait while target switches */

	sleep(1);

	/* Now check all is well */

	if (verbose) printf("Issuing 'ping'...");

	p = AngelMsg;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x14;
	*p++ = 0x03; *p++ = 0x02; *p++ = 0x00; *p++ = 0x01;
	*p++ = 0x06; *p++ = 0x00; *p++ = 0x03; *p++ = 0x00; /* Ping		*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/

	Angel_SendMessage(serial_fd, AngelMsg, 23);

	Angel_DumpPacket(serial_fd);

	if (verbose) printf("Issue reset...");

	p = AngelMsg;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x18;
	*p++ = 0x03; *p++ = 0x01; *p++ = 0x00; *p++ = 0x01;
	*p++ = 0x03; *p++ = 0x00; *p++ = 0x03; *p++ = 0x00; /* Reset		*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/

	Angel_SendMessage(serial_fd, AngelMsg, 27);

	Angel_DumpPacket(serial_fd);

	/* ADP_Booted reply */

	Angel_DumpPacket(serial_fd);

	printf("Initialising execution environment.\n");

	/* Setup register contents required for kernel startup */
	regs[0] = boot_options->regs[0];	/* r0 */
	regs[1] = boot_options->regs[1];	/* r1 */
	regs[2] = boot_options->regs[2];	/* r2 */
	regs[3] = boot_options->regs[3];	/* r3 */

	regs[4] = swap(boot_options->entry_address);	/* pc = kernel start address */
	regs[5] = swap(0xD3L);				/* cpsr = SVC32 mode FIQ/IRQ disabled */

	p = AngelMsg;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x18;
	*p++ = 0x04; *p++ = 0x02; *p++ = 0x00; *p++ = 0x01;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x04; *p++ = 0x00;
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff;
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;

	Angel_SendMessage(serial_fd, AngelMsg, (7 + 16 + 4));


	/* Okay we're off the boot channel and onto ADP proper now */
	Home = 0x01;
	Oppo = 0x00;

	p = AngelMsg;
	r = (unsigned char *) regs;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x31;
	*p++ = 0x01; *p++ = Home; *p++ = Oppo; *p++ = 0x01;

				  /* CHANNEL */
	*p++ = 0x06; *p++ = 0x00; *p++ = 0x01; *p++ = 0x00; /* CPUWrite, HADP	*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/

	*p++ = 0xff;	/* current mode */

	*p++ = 0x0F; *p++ = 0x00; *p++ = 0x03; *p++ = 0x00; /* Write a0 - a3, pc, cpsr */
			
	/* Transfer register data */
	for(i = 0; i < 6 * sizeof(int); i++)
		*p++ = *r++;

	Angel_SendMessage(serial_fd, AngelMsg, (7 + 16 + 1 + 4 + 24));

	Angel_DumpPacket(serial_fd);
			
	Home += 1; Oppo += 1;

	/* Send that them there nucleus down */

	printf("Downloading image size 0x%lx to 0x%x\n",
	    boot_options->image_size, boot_options->base_address);

	l = boot_options->base_address;
	i = 0;
	while (boot_options->image_size > 0) {
		p = AngelMsg;

		*p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
		*p++ = 0x01; *p++ = Home; *p++ = Oppo; *p++ = 0x01;

		*p++ = 0x04; *p++ = 0x00; *p++ = 0x01; *p++ = 0x00; /* ADP_Write, HADP	*/
		*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
		*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
		*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/

		/* Address */
		*p++ = (char) (l & 0xff);
		*p++ = (char) ((l >> 8) & 0xff);
		*p++ = (char) ((l >> 16) & 0xff);
		*p++ = (char) ((l >> 24) & 0xff);

		if ((Size = read(image_fd, (p + 4), 2016)) == 0) {
	      		fprintf(stderr, "Failed to read image data\n");
			return(1);
            	}

		/* Num bytes */
		*p++ = (char) (Size & 0xff);
		*p++ = (char) ((Size >> 8) & 0xff);
		*p++ = (char) ((Size >> 16) & 0xff);
		*p++ = (char) ((Size >> 24) & 0xff);

		/* Fill in size */
		p = &AngelMsg[1];
		*p++ = (char)(((Size + 4 + 16 + 8) >> 8) & 0xff);
		*p++ = (char)((Size + 4 + 16 + 8) & 0xff);

		Angel_SendMessage(serial_fd, AngelMsg, (7 + 16 + 8 + Size));

		reply = Angel_DumpPacket(serial_fd);

		putchar('.');fflush(stdout);
		/* What does this mean ? failure ? */
		if (reply == 11)
			return(1);

		Home += 1; Oppo += 1;
		l += Size;
		boot_options->image_size -= Size;
		i++;
	}
	printf("\n\r");

	/* ------------------------------------------------------------------------- */

	if(boot_options->o_image_file) {
	/* Send the other file down */

	  printf("Downloading other image size 0x%lx to 0x%x\n",
		 boot_options->o_image_size, boot_options->o_base_address);
	  
	  l = boot_options->o_base_address;
	  i = 0;
	  while (boot_options->o_image_size > 0) {
	    p = AngelMsg;
	    
	    *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
	    *p++ = 0x01; *p++ = Home; *p++ = Oppo; *p++ = 0x01;
	    
	    *p++ = 0x04; *p++ = 0x00; *p++ = 0x01; *p++ = 0x00; /* ADP_Write, HADP	*/
	    *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	    *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	    *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/
	    
	    /* Address */
	    *p++ = (char) (l & 0xff);
	    *p++ = (char) ((l >> 8) & 0xff);
	    *p++ = (char) ((l >> 16) & 0xff);
	    *p++ = (char) ((l >> 24) & 0xff);
	    
	    if ((Size = read(o_image_fd, (p + 4), 2016)) == 0) {
	      fprintf(stderr, "Failed to read image data\n");
	      return(1);
	    }
	    
	    /* Num bytes */
	    *p++ = (char) (Size & 0xff);
	    *p++ = (char) ((Size >> 8) & 0xff);
	    *p++ = (char) ((Size >> 16) & 0xff);
	    *p++ = (char) ((Size >> 24) & 0xff);
	    
	    /* Fill in size */
	    p = &AngelMsg[1];
	    *p++ = (char)(((Size + 4 + 16 + 8) >> 8) & 0xff);
	    *p++ = (char)((Size + 4 + 16 + 8) & 0xff);
	    
	    Angel_SendMessage(serial_fd, AngelMsg, (7 + 16 + 8 + Size));
	    
	    reply = Angel_DumpPacket(serial_fd);
	    
	    putchar('.');fflush(stdout);
	    /* What does this mean ? failure ? */
	    if (reply == 11)
		return(1);
	    
	    Home += 1; Oppo += 1;
	    l += Size;
	    boot_options->o_image_size -= Size;
	    i++;
	  }
	  printf("\n\r");
	}

	/* ------------------------------------------------------------------------- */


	if (verbose) printf("Starting ...");
	p = AngelMsg;

	*p++ = 0x00; *p++ = 0x00; *p++ = 0x14;
	*p++ = 0x01; *p++ = Home; *p++ = Oppo; *p++ = 0x01;

				  /* CHANNEL */
	*p++ = 0x0d; *p++ = 0x00; *p++ = 0x01; *p++ = 0x00; /* EXECUTE, HADP	*/
	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; /* ID		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo1		*/
	*p++ = 0xff; *p++ = 0xff; *p++ = 0xff; *p++ = 0xff; /* OSInfo2		*/

	Angel_SendMessage(serial_fd, AngelMsg, (7 + 16));

	Angel_DumpPacket(serial_fd);

	Home += 1; Oppo += 1;
	return(0);
}

int parse_options_file(char *file, struct bootoptions *boot_options, int flags)
{
	if (firstfile(file) == -1) {
		if (! (flags & QUIET))
			fprintf(stderr, "Cannot open options file %s\n", file);
		return(1);
	}

	yyparse();	
}

void dump_options(struct bootoptions *boot_options)
{
	printf("serial device\t= %s\n", boot_options->serial_device);
	printf("serial options\t= %s\n", boot_options->serial_options);
	printf("baud rate\t= %d\n", boot_options->baud_rate);
	printf("image file\t= %s\n", boot_options->image_file);
	printf("image size\t= 0x%08x\n", boot_options->image_size);
	printf("image offset\t= 0x%08x\n", boot_options->image_offset);
	printf("base address\t= 0x%08x\n", boot_options->base_address);
	printf("entry address\t= 0x%08x\n", boot_options->entry_address);
	printf("r0\t\t= %08x\n", boot_options->regs[0]);
	printf("r1\t\t= %08x\n", boot_options->regs[1]);
	printf("r2\t\t= %08x\n", boot_options->regs[2]);
	printf("r3\t\t= %08x\n", boot_options->regs[3]);
	printf("other image file\t= %s\n", boot_options->o_image_file);
	printf("other image size\t= 0x%08x\n", boot_options->o_image_size);
	printf("other base address\t= 0x%08x\n", boot_options->o_base_address);
}

