/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  load.c
 * Purpose: Get boot image from server
 * Entries: load
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2003 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.c,v 1.6 2003/01/25 23:29:41 gkminix Exp $
 */


#include <general.h>
#include <memory.h>
#include <kernel/net.h>
#include <kernel/arpa.h>
#include <kernel/romlib.h>
#include <pxe/common.h>
#include "bootpriv.h"
#include "load.h"



/*
 **************************************************************************
 * 
 * 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:
 */
       struct loadparams ldparams;	/* TFTP read file parameters	*/
static struct imghdr headbuf;		/* Temporary header buffer	*/
static struct loadrec *recp;		/* Pointer to load record	*/
static unsigned long highmem;		/* Top of conventional memory	*/
static unsigned long topmem;		/* Top of memory		*/
static unsigned long laststart;		/* Start of last memory block	*/
static unsigned long lastend;		/* End of last memory block	*/
static unsigned long nextput;		/* Address to put next block	*/
static unsigned long header;		/* Linear address of header	*/
static unsigned long remaining;		/* Remaining bytes in record	*/
static short hdrmode;			/* Header mode			*/
static short ldmode;			/* Loading mode			*/
static short quiet;			/* Quiet flag			*/
static int recnum;			/* Number of current record	*/



/*
 **************************************************************************
 * 
 * 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 int put_mem(buf, len, conv_only)
unsigned char *buf;
unsigned int   len;
int            conv_only;
{
  unsigned int chunk_size;
  register int ret = FALSE;

  /* Check if destination pointer is valid */
  if (nextput < (LOWMEM << 4) ||
      (nextput >= header && nextput < (header + sizeof(struct imghdr))))
	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 = (int)(highmem - nextput);
#ifdef LDDEBUG
	printf(" low=%lx, len=%u ", 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 ", 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 int next_record()
{
  register struct loadrec *rp;
  int offset;

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

  nextput = rp->lr_addr;
  if (LR_IS_B0(rp->lr_flags)) {
	if (LR_IS_B1(rp->lr_flags)) {
		/*
		 * 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->lr_flags)) {
		/*
		 * 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->lr_mlen;
  remaining = rp->lr_ilen;

  /* Compute pointer to next load record */
  if (nextput == 0 ||
      (offset = LR_HDRLEN(rp->lr_flags)) < sizeof(struct loadrec))
	return(FALSE);
  offset += LR_VENDLEN(rp->lr_flags);
  if (!LR_IS_EOF(rp->lr_flags)) {
	rp = (struct loadrec *)((unsigned char *)rp + offset);
	if (((int)rp - (int)(&headbuf)) >= sizeof(struct imghdr))
		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 int decode_header(buf, len)
char         *buf;
unsigned int  len;
{
  register struct imghdr *hp;
  int offset;
  int ret = PXENV_STATUS_SUCCESS;

  /*
   * 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 = 0L;
  header = 0L;

  /* First determine the mode of the header */
  hdrmode = HDRMODE_PXE;
  ldmode = LDMODE_KEEP_PXE;
  if (len == sizeof(struct imghdr) && ldparams.tftp.buffer == 0) {
	if (hp->ih_magic1 == IH_MAGIC1) {
		hdrmode = HDRMODE_NETBOOT;
		if (IH_KEEP_ALL(hp->ih_flags))
			ldmode = LDMODE_KEEP_ALL;
		else if (IH_KEEP_UNDI(hp->ih_flags))
			ldmode = LDMODE_KEEP_UNDI;
		else
			ldmode = LDMODE_KEEP_NONE;
	} else if (hp->ih_magic2 == IH_MAGIC2) {
		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((unsigned char *)hp, len, FALSE))
		ret = PXENV_STATUS_MCOPY_PROBLEM;
	else if (hdrmode == HDRMODE_DOS)
		nextput = DOSEXTMEM << 4;
	return(ret);
  }

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

  offset = IH_HDRLEN(hp->ih_flags) + IH_VENDLEN(hp->ih_flags);
  if (offset < IH_HEADERSIZE ||
      offset >= sizeof(struct imghdr) - sizeof(struct loadrec) - 2)
	ret = PXENV_STATUS_IMAGE_INVALID;

  if (ret == PXENV_STATUS_SUCCESS) {
	recp = (struct loadrec *)((unsigned char *)hp + offset);
#ifdef LDDEBUG
	printf("record=%x\n", recp);
#endif
	if (!next_record())
		ret = PXENV_STATUS_IMAGE_INVALID;
  }
  return(ret);
}



/*
 **************************************************************************
 * 
 * Load boot image from server
 */
int load(quietflag)
int quietflag;
{
  unsigned int len, blocknum, xlen;
  register unsigned char *cp;
  int ret = PXENV_STATUS_SUCCESS;

  /* Setup global values */
  recp = NULL;
  recnum = 0;
  remaining = 0L;
  quiet = quietflag;
  if (!quiet)
	printf("Loading %ls\n", ldparams.tftp.filename,
					sizeof(ldparams.tftp.filename));

#ifndef NODISK
  /* Load a bootblock from disk if requested */
  if (!memcmp(ldparams.tftp.filename, "/dev/", 5)) {
	ldparams.mode = LDMODE_KEEP_NONE;
	if (ldparams.tftp.buffer == 0)
		ldparams.exec = BOOTBLOCK;
	else if (ldparams.tftp.bufsize >= BOOTBSIZE)
		ldparams.exec = ldparams.tftp.buffer;
	else
		ret = PXENV_STATUS_TFTP_TOO_MANY;
	if (ret == PXENV_STATUS_SUCCESS)
		ret = loaddisk(&(ldparams.tftp.filename[5]), ldparams.exec,
					&(ldparams.header), &(ldparams.drive));
	goto load_ret;
  }
#endif

  /* Determine top of memory pointers */
  highmem = convmem();
  if ((topmem = extmem() + (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))) != PXENV_STATUS_SUCCESS)
	goto load_ret;
  if (!quiet) {
	printf("Options: Blocksize %u", tftpbufsize);
	if (filesize != -1L)
		printf(", Filesize %lu", filesize);
	if (srvrtout != -1L)
		printf(", Timeout %lu", srvrtout);
	printf("\n");
  }

  /* Read the image file header */
  if ((ret = tftp_get()) == PXENV_STATUS_SUCCESS &&
      (ret = decode_header(tftpbuf, tftpbuflen)) == PXENV_STATUS_SUCCESS) {
	len = tftpbuflen - sizeof(struct imghdr);
	cp = tftpbuf + sizeof(struct imghdr);
	blocknum = 1;
	if (!quiet)
		printf("Block 1 ");
  }

  /* Read all blocks of image file */
  while (ret == PXENV_STATUS_SUCCESS) {
	if (len == 0) {
		if (tftpbuflen < tftpbufsize ||
		    (ret = tftp_get()) != PXENV_STATUS_SUCCESS)
			break;
		len = tftpbuflen;
		cp = tftpbuf;
		blocknum++;
		if (!quiet)
			printf("\rBlock %u ", blocknum);
	}
	if (hdrmode != HDRMODE_NETBOOT) {
		if (!put_mem(cp, len, FALSE))
			ret = PXENV_STATUS_MCOPY_PROBLEM;
		len = 0;
		continue;
	}
	/* Handle netboot header mode loading. */
	if (len > remaining) {
		if (!put_mem(cp, (unsigned int)remaining, FALSE)) {
			ret = PXENV_STATUS_MCOPY_PROBLEM;
			break;
		}
		cp += (unsigned int)remaining;
		len -= (unsigned int)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 */
  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:
  if (!quiet) {
	if (ret == PXENV_STATUS_SUCCESS)
		printf("ok\n");
  }
  return(ret);
}



syntax highlighted by Code2HTML, v. 0.9.1