/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  load.c86
 * Purpose: Get boot image from server
 * Entries: load
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: load.c86,v 1.12 2007/01/06 18:30:46 gkminix Exp $
 */


#include <common.h86>
#include <romconfig.h86>
#include <netboot.h86>
#include <i386/string.h86>
#include <i386/memory.h86>
#include <i386/misc.h86>
#include <i386/conio.h86>
#include <system/bootrom.h86>
#include <pxe/general.h86>
#include "headers/net.h86"
#include "headers/arpa.h86"
#include "headers/pxe.h86"
#include "load.h86"



/*
 **************************************************************************
 * 
 * Header modes:
 */
#define HDRMODE_DOS	0		/* DOS header mode */
#define HDRMODE_NETBOOT	1		/* Netboot header mode */
#define HDRMODE_PXE	2		/* PXE header mode */



/*
 **************************************************************************
 * 
 * Global variables:
 */
static struct load_header headbuf;	/* Temporary header buffer	*/
static struct load_record *recp;	/* Pointer to load record	*/
static __lptr highmem;			/* Top of conventional memory	*/
static __lptr topmem;			/* Top of memory		*/
static __lptr laststart;		/* Start of last memory block	*/
static __lptr lastend;			/* End of last memory block	*/
static __lptr nextput;			/* Address to put next block	*/
static __lptr header;			/* Linear address of header	*/
static unsigned long remaining;		/* Remaining bytes in record	*/
static unsigned short recnum;		/* Number of current record	*/
static short hdrmode;			/* Header mode			*/
static short ldmode;			/* Loading mode			*/
static bool_t quiet;			/* Quiet flag			*/



/*
 **************************************************************************
 * 
 * Put TFTP buffer into memory, either into conventional or extended
 * memory.
 * This routine requires the following global variables to be setup
 * properly:  nextput, highmem, topmem, header
 */
static bool_t put_mem(__u8 *buf, size_t len, bool_t conv_only)
{
  size_t chunk_size;
  register bool_t ret = FALSE;

  /* Check if destination pointer is valid */
  if (nextput < (LOWMEM << 4) ||
      (nextput >= header && nextput < (header + sizeof(struct load_header))))
	goto putmem_ret;
  if (len == 0)
	goto putmem_true;

  /* Put block into lower memory */
  chunk_size = len;
  if (nextput < highmem) {
	if ((nextput + len) >= highmem)
		len = (size_t)(highmem - nextput);
#ifdef LDDEBUG
	printf(" low=%lx, len=%u *\b", nextput, len);
#endif
	if (!lmove(nextput, buf, len))
		goto putmem_ret;
	nextput += len;
	buf += len;
	chunk_size -= len;
	if (chunk_size > 0)
		nextput = (EXTMEM << 4);
  } else if (nextput < (EXTMEM << 4))
	goto putmem_ret;

  /* Put block into high memory */
  if (chunk_size > 0) {
	if (conv_only || nextput >= topmem || (nextput + chunk_size) > topmem)
		goto putmem_ret;
#ifdef LDDEBUG
	printf(" high=%lx, len=%u *\b", nextput, len);
#endif
	if (!lmove(nextput, buf, chunk_size))
		goto putmem_ret;
	nextput += chunk_size;
  }

putmem_true:
  ret = TRUE;

putmem_ret:
  return(ret);
}



/*
 **************************************************************************
 * 
 * Decode record header and advance pointer to next record.
 */
static bool_t next_record(void)
{
  register struct load_record *rp asm("esi");
  short offset = 0;

  if ((rp = recp) == NULL)
	return(FALSE);

  nextput = rp->address;
  if (LR_IS_B0(rp)) {
	if (LR_IS_B1(rp)) {
		/*
		 * B0 = 1  &&  B1 = 1
		 * Load address is subtracted from the start of
		 * the last image loaded.
		 */
		if (nextput > laststart)
			nextput = 0;		/* will give error	*/
		else
			nextput = laststart - nextput;
	} else {
		/*
		 * B0 = 1  &&  B1 = 0
		 * Load address is added to the last byte of the memory area
		 * used by the previous image.
		 */
		nextput += lastend;
	}
  } else {
	if (LR_IS_B1(rp)) {
		/*
		 * B0 = 0  &&  B1 = 1
		 * Load address is subtracted from the last writable location
		 * in memory.
		 */
		if (nextput > topmem)
			nextput = 0;		/* will give error	*/
		else
			nextput = topmem - nextput;
	} else {
		/*
		 * B0 = 0  &&  B1 = 0
		 * Load address is used as is.
		 */
	}
  }
  laststart = nextput;
  lastend = laststart + rp->mlength;
  remaining = rp->ilength;

  /* Compute pointer to next load record */
  if (nextput == 0 ||
      (offset = LR_HDRLEN(rp)) < (short)sizeof(struct load_record))
	return(FALSE);
  offset += LR_VENDLEN(rp);
  if (!LR_IS_EOF(rp)) {
	rp = (struct load_record *)((__u8 *)rp + offset);
	if (((int)rp - (int)(&headbuf)) >= (int)sizeof(struct load_header))
		return(FALSE);
  } else
	rp = NULL;

  recp = rp;
  return(TRUE);
}



/*
 **************************************************************************
 * 
 * Decode the header information. We have three different types of headers:
 *   -  the header contains 0xaa/0x55 at the end of the block: this is the
 *      magic ID of a DOS boot sector, so load this sector into 0x07c00,
 *      and the rest starting at 0x10000, and then jump to 0x07c00
 *   -  the header contains a magic word at the beginning of the block: this
 *      indicates a special header block with loading and starting information
 *      for the rest of the image (modes 2 and 3). If the RETURN flag is set
 *      in the header, this indicates that the loaded image will possibly
 *      use interrupts and services provided by the bootrom and also might
 *      return to the bootrom.
 *   -  the header doesnt contain any special identification. Handle this
 *      as a PXE boot image file. It gets loaded at 0x07c00.
 */
static short decode_header(__u8 *buf, size_t len, struct loadparams *ldparams)
{
  register struct load_header *hp asm("esi");
  short offset;

  /*
   * Copy the header into a temporary buffer, so that we don't have to
   * use far pointers.
   */
  if (len > sizeof(headbuf))
	len = sizeof(headbuf);
  hp = &headbuf;
  memcpy(hp, buf, len);
  ldparams->header = 0;
  header = 0;

  /* First determine the mode of the header */
  hdrmode = HDRMODE_PXE;
  ldmode = LDMODE_KEEP_PXE;
  if (len == sizeof(struct load_header) && ldparams->tftp.buffer == 0) {
	if (hp->magic == NBHEADER_MAGIC) {
		hdrmode = HDRMODE_NETBOOT;
		if (LH_KEEP_ALL(hp))
			ldmode = LDMODE_KEEP_ALL;
		else if (LH_KEEP_UNDI(hp))
			ldmode = LDMODE_KEEP_UNDI;
		else
			ldmode = LDMODE_KEEP_NONE;
	} else if (hp->bootsig == NBHEADER_SIG) {
		hdrmode = HDRMODE_DOS;
		ldmode = LDMODE_KEEP_NONE;
	}
  }
  ldparams->mode = ldmode;
#ifdef LDDEBUG
  printf("header mode %u, load mode %u, ", hdrmode, ldmode);
#endif

  /*
   * For DOS or PXE modes setup all the parameters needed by put_mem. Use
   * saved header block in order to restore it lateron at it's correct
   * position.
   */
  if (hdrmode != HDRMODE_NETBOOT) {
	if (hdrmode == HDRMODE_DOS || ldparams->tftp.buffer == 0) {
		nextput = far2long(BOOTBLOCK);
		ldparams->exec = BOOTBLOCK;
	} else {
		nextput = ldparams->tftp.buffer;
		highmem = topmem = nextput + ldparams->tftp.bufsize;
		ldparams->exec = long2far(nextput);
	}
	if (!put_mem((__u8 *)hp, len, FALSE))
		return(PXENV_STATUS_MCOPY_PROBLEM);
	if (hdrmode == HDRMODE_DOS)
		nextput = DOSEXTMEM << 4;
	return(PXENV_STATUS_SUCCESS);
  }

  /*
   * With netboot mode first save the header into it's final place. Then
   * setup the first record pointer.
   */
  nextput = laststart = far2long(hp->locn);
#ifdef LDDEBUG
  printf("header=%lx, ", nextput);
#endif
  if (!put_mem((__u8 *)hp, len, TRUE))
	return(PXENV_STATUS_MCOPY_PROBLEM);
  header = laststart;
  lastend = laststart + len;
  ldparams->header = hp->locn;
  ldparams->exec = hp->execute;
#ifdef LDDEBUG
  printf("headerend=%lx, ", lastend);
#endif

  offset = LH_HDRLEN(hp) + LH_VENDLEN(hp);
  recp = (struct load_record *)((__u8 *)hp + offset);
  if ((__u8 *)recp < (__u8 *)&(hp->dummy[0]) ||
      (__u8 *)recp >= (__u8 *)&(hp->bootsig))
	return(PXENV_STATUS_IMAGE_INVALID);

#ifdef LDDEBUG
  printf("record=%x\n", recp);
#endif
  if (!next_record())
	return(PXENV_STATUS_IMAGE_INVALID);
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 * 
 * Load boot image from server
 */
short load(struct loadparams *ldparams)
{
  size_t len, blocknum;
  register __u8 *cp;
  short ret = PXENV_STATUS_SUCCESS;

  /* Setup global values */
  recp = NULL;
  recnum = 0;
  remaining = 0;
  quiet = (statuscallout != FNULL && statuscallout != (__fptr)(-1));
  if (!quiet)
	printf("Loading %ls\n", ldparams->tftp.filename,
					sizeof(ldparams->tftp.filename));

  /* Determine top of memory pointers */
  highmem = convmem() * 1024;
  if ((topmem = (extmem() * 1024) + (EXTMEM << 4)) == (EXTMEM << 4))
	topmem = highmem;
#ifdef LDDEBUG
  printf("highmem=%lx  topmem=%lx\n", highmem, topmem);
#endif

  /* Open the TFTP connection */
  if ((ret = tftp_open(ldparams->tftp.server,
		ldparams->tftp.gateway,
		ldparams->tftp.filename, sizeof(ldparams->tftp.filename),
		ldparams->port)) != PXENV_STATUS_SUCCESS)
	goto load_ret;
  if (!quiet) {
	printf("Options: Blocksize %u", tftpbufsize);
	if (filesize != (unsigned long)(-1L))
		printf(", Filesize %lu, Number of blocks %lu",
					filesize, (filesize / tftpbufsize));
	printf("\n");
  }

  /* Read the image file header */
  if ((ret = tftp_get()) != PXENV_STATUS_SUCCESS ||
      (ret = decode_header(tftpbuf, tftpbuflen, ldparams)) !=
							PXENV_STATUS_SUCCESS)
	goto load_end;
  len = tftpbuflen - sizeof(struct load_header);
  cp = tftpbuf + sizeof(struct load_header);
  blocknum = 1;
  if (!quiet)
	printf("Block 1 ");

  /* Read all blocks of the image file */
  while (TRUE) {
	if (len == 0) {
		if (tftpbuflen < tftpbufsize ||
		    (ret = tftp_get()) != PXENV_STATUS_SUCCESS)
			break;
		len = tftpbuflen;
		cp = tftpbuf;
		blocknum++;
#ifdef LDDEBUG
		printf(" \rBlock %u *\b", blocknum);
#else
		if (!quiet)
			printf("\rBlock %u ", blocknum);
#endif
	}
	if (hdrmode != HDRMODE_NETBOOT) {
		/* Handle non-netboot header mode loading */
		if (!put_mem(cp, len, FALSE)) {
			ret = PXENV_STATUS_MCOPY_PROBLEM;
			break;
		}
		len = 0;
	} else {
		/* Handle netboot header mode loading. */
		if (len > remaining) {
			if (!put_mem(cp, (size_t)remaining, FALSE)) {
				ret = PXENV_STATUS_MCOPY_PROBLEM;
				break;
			}
			cp += (size_t)remaining;
			len -= (size_t)remaining;
			if (!next_record())
				break;
		} else {
			if (!put_mem(cp, len, FALSE)) {
				ret = PXENV_STATUS_MCOPY_PROBLEM;
				break;
			}
			remaining -= len;
			if (remaining == 0L && !next_record())
				break;
			len = 0;
		}
	}
  }

  /* Check for errors and close the TFTP connection */
load_end:
  if (ret == PXENV_STATUS_SUCCESS && (recp != NULL || remaining != 0L))
	ret = PXENV_STATUS_IMAGE_INVALID;
  if (ret == PXENV_STATUS_SUCCESS)
	ret = tftp_close();
  else
	tftp_close();

load_ret:
#ifdef LDDEBUG
  printf("\nStatus=%hd\n", ret);
#else
  if (!quiet) {
	if (ret == PXENV_STATUS_SUCCESS)
		printf("ok\n");
  }
#endif
  return(ret);
}

