/* vim: set filetype=c:
 *
 * net.c86  -  MGL runtime network related routines
 *
 * Copyright (C) 2003-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: net.c86,v 1.19 2007/01/06 18:31:38 gkminix Exp $
 */

#include "rtcommon.h86"
#include "bootsect.h86"
#include "headers/osload.h86"
#include <system/bootrom.h86>
#include <system/bios.h86>
#include <system/partition.h86>
#include <i386/memory.h86>
#include <i386/misc.h86>
#include <pxe/general.h86>
#include <pxe/entry.h86>
#include <pxe/base.h86>
#include <pxe/undi.h86>
#include <pxe/tftp.h86>
#include <netboot.h86>



/*
 *****************************************************************************
 *
 * Global and local Variables
 */
static short loadflags;			/* Loader flags */
static bool_t haveservice;		/* TRUE if bootrom has NB service API */
static __u16 undiseg, undisize;		/* Start segment and size of UNDI */



/*
 *****************************************************************************
 *
 * Convert a string into an IP address
 */

/* Routine which is callable from within the runtime library */
bool_t rtint_str2ip(const char *s1, __u8 *ret)
{
  short ipbuf[4];
  size_t slen, dot, i, j;

  /*
   * Presently we can only convert dotted-decimal host addresses. Any
   * other name gives an error because we don't have a name resolver
   * in the runtime library yet.
   */
  slen = (size_t)(s1[0]);
  if (slen > 0 && s1[1] >= '0' && s1[1] <= '9') {
	ipbuf[0] = 0;
	for (i = 1, dot = 1, j = 0; i <= slen; i++) {
		if (s1[i] == '.') {
			if ((i - dot) <= 1)
				break;
			dot = i;
			j++;
			if (j > 3)
				break;
			ipbuf[j] = 0;
		} else if (s1[i] >= '0' && s1[i] <= '9') {
			ipbuf[j] = ipbuf[j] * 10 + (short)(s1[i] - '0');
			if (ipbuf[j] > 255)
				break;
		} else
			break;
	}
	if (j == 3 && i > slen && (i - dot) > 1) {
		for (i = 0; i < 4; i++)
			ret[i] = (__u8)(ipbuf[i] & 0xff);
		return(TRUE);
	}
  }
  return(FALSE);
}


/* Interface routine to the compiled user program */
__INTERNAL(str2ip, __nptr,
		__nptr retptr,
		__nptr s1ptr)
{
  __u8 *ret = (__u8 *)((int)retptr);
  char *s1 = (char *)((int)s1ptr);
  short i;

  if (!rtint_str2ip(s1, ret))
	for (i = 0; i < 4; i++)
		ret[i] = (__u8)0;
  return(retptr);
}


/* Function wrapper */
__PUBLIC(str2ip, 2, __A(__nptr, 1), __A(__nptr, 0));


/* Export info */
__EXPORT_PROC(str2ip, IPADDR, 1, __ARGS(__D(STRING, CONST)));



/*
 *****************************************************************************
 *
 * Convert an IP address into a string
 */

/* Routine which is callable from within the runtime library */
size_t rtint_ip2str(const __u8 *s1, char *ret, size_t retsize)
{
  char *s;
  size_t slen, i;

  slen = 0;
  for (i = 0; i < 4; i++) {
	s = __numcvt((unsigned long)(s1[i]), 10);
	while (*s) {
		if (slen >= retsize)
			break;
		ret[slen++] = *s++;
	}
	if (i < 3 && slen < retsize)
		ret[slen++] = '.';
  }
  return(slen);
}


/* Interface routine to the compiled user program */
__INTERNAL(ip2str, __nptr,
		__nptr retptr, size_t retsize,
		__nptr s1ptr)
{
  __u8 *s1 = (__u8 *)((int)s1ptr);
  char *ret = (char *)((int)retptr);
  size_t slen;

  slen = rtint_ip2str(s1, (ret + 1), (retsize - 1));
  ret[0] = (char)(slen & 0xff);
  return(retptr);
}


/* Function wrapper */
__PUBLIC(ip2str, 3, __A(__nptr, 1), __A(short, 2), __A(__nptr, 0));


/* Export info */
__EXPORT_PROC(ip2str, STRING, 1, __ARGS(__D(IPADDR, CONST)));



/*
 *****************************************************************************
 *
 * Determine the address of the BOOTP record within a PXE bootrom
 */
static short getbootpbuf(__fptr *ptr, __u16 *len)
{
  t_base_get_binl_info params;

  /* Request DHCP buffer from bootrom */
  params.status = PXENV_STATUS_SUCCESS;
  params.packet_type = BASE_PACKET_DHCP_ACK;
  params.buffer_size = 0;
  params.buffer = 0;
  params.buffer_limit = 0;
  if (pxe(BASE_GET_BINL_INFO, &params) != PXENV_EXIT_SUCCESS ||
      params.buffer_size < BOOTP_RFC_MIN_SIZE)
	return(FALSE);

  /*
   * Check returned parameters. Most commercial bootroms don't return
   * buffer_limit correctly.
   */
  *ptr = params.buffer;
  *len = MAX(params.buffer_limit, params.buffer_size);

  /* Return with success */
  return(TRUE);
}



/*
 *****************************************************************************
 *
 * Load a boot image file via PXE into extended memory
 */
static unsigned long loadnet(t_ipaddr mcastaddr,
				unsigned short cport,
				unsigned short sport)
{
  t_tftp_read_file params;
  unsigned long memsize;
  size_t i;

  /* Determine the amount of free extended memory */
  memsize = extmem();
  if (memsize <= 2048)
	rtint_raise(ELOADMEMORY);

  /* Copy boot image file name into parameter record */
  i = (size_t)(bootfile[0]);
  if (i > sizeof(params.filename))
	i = sizeof(params.filename);
  else if (i < sizeof(params.filename))
	params.filename[i] = 0;
  while (i > 0) {
	params.filename[i - 1] = bootfile[i];
	i--;
  }

  /* Now load the boot image */
  printf("Loading %.*s... ", sizeof(params.filename), params.filename);
  params.status = PXENV_STATUS_SUCCESS;
  params.bufsize = (memsize * 1024) + (1024 * 1024) - IMAGE_ADDR;
  params.buffer = (__lptr)IMAGE_ADDR;
  params.server = tftpaddr;
  params.gateway = gatewayaddr;
  params.mcast = mcastaddr;
  params.clntport = cport;
  params.srvport = sport;
  params.opentimeout = DEFAULT_OPENTIMEOUT;
  params.reopendelay = DEFAULT_REOPENDELAY;
  if (pxe(TFTP_READ_FILE, &params) != PXENV_EXIT_SUCCESS) {
	printf("PXE error %hu\n", params.status);
	if (params.status == PXENV_STATUS_TFTP_TOO_MANY ||
	    params.status == PXENV_STATUS_MTFTP_TOO_MANY)
		rtint_raise(ELOADMEMORY);
	else
		rtint_raise(ELOADPXE);
  }
  printf("ok\n");
  return((unsigned long)params.bufsize);
}



/*
 *****************************************************************************
 *
 * Get the boot image file header and setup the copy records
 */
static struct image_rec *decodeheader(unsigned long len, short drivenum)
{
#define HEADER_DISK	0
#define HEADER_STANDARD	1
#define HEADER_NETBOOT	2
#define HEADER_PXE	3

  struct image_rec *image;
  struct load_header *header;
  struct load_record *rec;
  unsigned long memend = ((extmem() * 64) + EXTMEM) << 4;
  unsigned long copylen, loadaddr;
  unsigned long destaddr, lastend, laststart;
  short htype, i;

  /* Make space on heap for header structures */
  image = (struct image_rec *)malloc(sizeof(struct image_rec));
  header = (struct load_header *)malloc(sizeof(struct load_header));
  if (image == NULL || header == NULL) {
	if (image != NULL)
		free(image);
	if (header != NULL)
		free(header);
	rtint_raise(ELOADHEAP);
  }

  /* Clear image copy structure */
  memset(image, 0, sizeof(struct image_rec));

  /* Copy the header from extended memory */
  copylen = len > sizeof(struct load_header) ? sizeof(struct load_header) : len;
  if (!movemem(IMAGE_ADDR, far2linear(MK_FP(getds(), (int)(header))),
								copylen)) {
	free(image);
	free(header);
	rtint_raise(ELOADCOPY);
  }

  /* Check type of boot image header */
  htype = HEADER_DISK;
  if (drivenum < 0) {
	htype = (havepxe ? HEADER_PXE : HEADER_STANDARD);
	if (len >= sizeof(struct load_header)) {
		if (header->magic == NBHEADER_MAGIC)
			htype = HEADER_NETBOOT;
		else if (header->bootsig == NBHEADER_SIG)
			htype = HEADER_STANDARD;
	}
  }

  /*
   * For PXE and disk boot images, the parameters in the image copy structure
   * can be preset with known values.
   */
  if (htype == HEADER_PXE) {
	/* PXE network boot image */
	copylen = (convmem() * 1024 - BOOT_OFFSET);
	if (len < copylen)
		copylen = len;
	image->startaddr = MK_FP(0, BOOT_OFFSET);
	image->hflags1 = FLAG1_KEEP_ALL;
	if (havepxe) {
		image->reg_es = FP_SEG(pxenvstruct);
		image->reg_bx = FP_OFF(pxenvstruct);
		image->stack[0] = (__u32)pxestruct;
	}
	image->addrnum = (copylen < len ? 2 : 1);
	image->addrs[0].srcaddr = IMAGE_ADDR;
	image->addrs[0].destaddr = BOOT_OFFSET;
	image->addrs[0].copysize = copylen;
	if (copylen < len) {
		image->addrs[1].srcaddr = IMAGE_ADDR + copylen;
		image->addrs[1].destaddr = (EXTMEM << 4);
		image->addrs[1].copysize = len - copylen;
	}
	free(header);
	return(image);
  } else if (htype == HEADER_STANDARD) {
	/* Network boot image with disk boot sector */
	copylen = (convmem() * 1024 - (DOSEXTMEM << 4));
	image->startaddr = MK_FP(0, BOOT_OFFSET);
	image->hflags1 = 0;
	image->reg_dx = 0x00FF;
	image->addrnum = (len <= SECTSIZE ? 1 :
				(copylen < (len - SECTSIZE) ? 3 : 2));
	image->addrs[0].srcaddr = IMAGE_ADDR;
	image->addrs[0].destaddr = BOOT_OFFSET;
	image->addrs[0].copysize = (len <= SECTSIZE) ? len : SECTSIZE;
	if (len > SECTSIZE) {
		if ((len - SECTSIZE) < copylen)
			copylen = len - SECTSIZE;
		image->addrs[1].srcaddr = IMAGE_ADDR + SECTSIZE;
		image->addrs[1].destaddr = (DOSEXTMEM << 4);
		image->addrs[1].copysize = copylen;
		if (copylen < (len - SECTSIZE)) {
			image->addrs[2].srcaddr = IMAGE_ADDR + copylen + SECTSIZE;
			image->addrs[2].destaddr = (EXTMEM << 4);
			image->addrs[2].copysize = len - copylen - SECTSIZE;
		}
	}
	free(header);
	return(image);
  } else if (htype == HEADER_DISK) {
	/* Disk boot sector plus MBR */
	image->startaddr = MK_FP(0, BOOT_OFFSET);
	image->hflags1 = 0;
	image->reg_dx = (__u16)(drivenum & 0x00FF);
	image->reg_ds = 0;
	i = ((drivenum >> 8) & 0x000F);
	if (i < 1 || i > 4)
		image->reg_si = 0;
	else
		image->reg_si = (__u16)(PART_OFFSET + PART_TABLE_OFS +
					(i - 1) * sizeof(struct partition));
	image->addrnum = 2;
	image->addrs[0].srcaddr = IMAGE_ADDR;		/* partition table */
	image->addrs[0].destaddr = PART_OFFSET;
	image->addrs[0].copysize = SECTSIZE;
	image->addrs[1].srcaddr = IMAGE_ADDR + SECTSIZE; /* boot sector */
	image->addrs[1].destaddr = BOOT_OFFSET;
	image->addrs[1].copysize = SECTSIZE;
	free(header);
	return(image);
  }

  /*
   * Decoding a netboot header is a bit more complex. First we build
   * an image copy structure for the header itself.
   */
  image->startaddr = header->execute;
  image->hflags1 = header->hflags1;
  image->hflags2 = header->hflags2;
  image->stack[0] = (__u32)(header->locn);
  image->stack[1] = (__u32)btpptr;
  image->addrnum = 1;
  image->addrs[0].srcaddr = IMAGE_ADDR;
  image->addrs[0].destaddr = far2linear(header->locn);
  image->addrs[0].copysize = sizeof(struct load_header);

  /*
   * Scan through all load records and build all relevant image copy
   * structures.
   */
  rec = (struct load_record *)((__u8 *)header +
				LH_HDRLEN(header) + LH_VENDLEN(header));
  i = 1;
  loadaddr = IMAGE_ADDR + sizeof(struct load_header);
  laststart = image->addrs[0].destaddr;
  lastend = laststart + sizeof(struct load_header);
  while(TRUE) {
	if (!LR_IS_B0(rec)) {
		if (!LR_IS_B1(rec))
			destaddr = rec->address;
		else
			destaddr = memend - rec->address;
	} else {
		if (!LR_IS_B1(rec))
			destaddr = lastend + rec->address;
		else
			destaddr = laststart - rec->address;
	}
	if (destaddr < (LOWMEM << 4) ||
	    destaddr > (memend - rec->mlength) ||
	    (destaddr >= (convmem() * 1024 - rec->mlength) &&
	     destaddr < (EXTMEM << 4))) {
		free(image);
		free(header);
		rtint_raise(ELOADHEADER);
	}
	image->addrs[i].srcaddr = loadaddr;
	image->addrs[i].destaddr = destaddr;
	image->addrs[i].copysize = rec->ilength;
	loadaddr += rec->ilength;
	laststart = destaddr;
	lastend = destaddr + rec->mlength;
	i++;
	if (LR_IS_EOF(rec))
		break;
	if (i >= COPY_REC_NUM) {
		free(image);
		free(header);
		rtint_raise(ELOADHEADER);
	}
	rec = (struct load_record *)((__u8 *)rec +
				LR_HDRLEN(rec) + LR_VENDLEN(rec));
  }
  image->addrnum = (__u16)i;

  free(header);
  return(image);
}



/*
 *****************************************************************************
 *
 * Reorganize the image copy records so that the image loader doesn't
 * overwrite parts of the loaded image while copying it into place.
 */
static void reorgcopy(struct image_rec *image)
{
  struct copy_rec tmp;
  short recnum;
  short i, j;

  /* First we sort all entries in ascending destination address order */
  recnum = (short)(image->addrnum);
  for (i = 0; i < recnum; i++)
	for (j = i + 1; j < recnum; j++)
		if (image->addrs[j].destaddr < image->addrs[i].destaddr) {
			memcpy(&tmp, &(image->addrs[j]),
						sizeof(struct copy_rec));
			memcpy(&(image->addrs[j]), &(image->addrs[i]),
						sizeof(struct copy_rec));
			memcpy(&(image->addrs[i]), &tmp,
						sizeof(struct copy_rec));
		}

  /* All entries for extended memory copy get sorted in descending order */
  for (i = 0; i < recnum; i++) {
	if (image->addrs[i].destaddr < (EXTMEM << 4))
		continue;
	for (j = i + 1; j < recnum; j++)
		if (image->addrs[j].destaddr > image->addrs[i].destaddr) {
			memcpy(&tmp, &(image->addrs[j]),
						sizeof(struct copy_rec));
			memcpy(&(image->addrs[j]), &(image->addrs[i]),
						sizeof(struct copy_rec));
			memcpy(&(image->addrs[i]), &tmp,
						sizeof(struct copy_rec));
		}
  }
}



/*
 *****************************************************************************
 *
 * Load a new operating system
 */
__INTERNAL(load, short,
		__nptr s1ptr, __nptr servptr, __nptr gwptr)
{
  char *cp;
  char *s1 = (char *)((int)s1ptr);
  size_t len, i;
  struct image_rec *image;
  unsigned short cport, sport;
  unsigned long loadlen = 0;
  short drivenum = -1;
  t_ipaddr mcastaddr;

  /* Update the global IP address variables */
  tftpaddr = *((t_ipaddr *)((int)servptr));
  gatewayaddr = *((t_ipaddr *)((int)gwptr));

  /*
   * Analyze the boot file name. Following the boot image file name, it
   * is possible to specify the multicast TFTP address and the client and
   * server port numbers, seperated by colons. This is necessary for PXE
   * bootroms which use the braindead Intel Multicast TFTP. If the bootrom
   * supports RFC-like multicast TFTP it is not necessary to specify these
   * parameters.
   */
  len = 0;
  i = (size_t)(s1[0]);
  cp = &(s1[1]);
  while(*cp && *cp != ':' && i > 0) {
	if (len < BOOTP_FILE_SIZE)
		bootfile[++len] = *cp;
	cp++;
	i--;
  }
  bootfile[0] = (__u8)(len & 0xFF);

  mcastaddr = ntohl(IP_ANY);
  if (*cp == ':' && i > 0) {
	s1 = cp++;
	i--;
	len = 0;
	while (*cp && *cp != ':' && i > 0) {
		len++;
		cp++;
		i--;
	}
	s1[0] = (__u8)(len & 0xFF);
	rtint_str2ip(s1, (__u8 *)(&mcastaddr));
	s1[0] = ':';
  }

  cport = 0;
  if (*cp == ':' && i > 0) {
	cp++;
	i--;
	while (*cp && *cp >= '0' && *cp <= '9' && i > 0) {
		if (cport > 3276 || (cport == 3276 && *cp > '7'))
			break;
		cport = cport * 10 + (*cp - '0');
		cp++;
		i--;
	}
	while (*cp && *cp != ':' && i > 0) {
		cp++;
		i--;
	}
  }

  sport = 0;
  if (*cp == ':' && i > 0) {
	cp++;
	i--;
	while (*cp && *cp >= '0' && *cp <= '9' && i > 0) {
		if (sport > 3276 || (sport == 3276 && *cp > '7'))
			break;
		sport = sport * 10 + (*cp - '0');
		cp++;
		i--;
	}
  }

  /*
   * Check if we have to load the boot image from a local disk or
   * from the network. If there is no PXE support, we have an old
   * netboot bootrom or an etherboot bootrom. In that case we return
   * with RTERROR_LOAD, which tells the MGL loader to return to the
   * bootrom with a modified BOOTP record. The bootrom will then perform
   * the load.
   */
  len = (size_t)(bootfile[0]);
  if (len > 5 && !strncmp(&(bootfile[1]), "/dev/", 5)) {
	if (!canrun)
		rtint_raise(ELOADUNSUPPORTED);
	loadlen = SECTSIZE * 2;
	drivenum = loaddisk(len - 5, &(bootfile[6]));
	if (drivenum == -1)
		rtint_raise(ELOADDISK);
	drivenum &= 0x07FF;
  } else if (!havepxe) {
	/*
	 * If we are not going to load the boot image from a disk, then
	 * we have to use the network. However, if there is no PXE inter-
	 * face available, we are unable to do this by ourselves. Instead
	 * return to the bootrom, which will then load the new boot image
	 * using the information found in the modified BOOTP record. Of
	 * course, this doesn't work with PXE bootroms.
	 */
	if (!canload)
		rtint_raise(ELOADUNSUPPORTED);
	rtint_exit(RTERROR_LOAD);
  } else {
	if (!canrun) {
		if (canload)
			rtint_exit(RTERROR_LOAD);
		rtint_raise(ELOADUNSUPPORTED);
	}
	loadlen = loadnet(mcastaddr, cport, sport);
  }

  /* Decode the load header */
  image = decodeheader(loadlen, drivenum);

  /* Reorganize the image copy records */
  reorgcopy(image);

  /* Copy the image copy records into lower memory */
  fmemcpy(MK_FP(0, IMAGE_REC_ADDR), MK_FP(getds(), image),
						sizeof(struct image_rec));
  loadflags = image->hflags1;
  free(image);
  rtint_exit(RTERROR_RUN);
}


/* Function wrapper */
__PUBLIC(load, 3,
		__A(__nptr, 2),
		__A(__nptr, 1),
		__A(__nptr, 0));


/* Export info */
__EXPORT_PROC(load, NONE, 3, __ARGS(
		__D(STRING, CONST),
		__D(IPADDR, CONST),
		__D(IPADDR, CONST)));



/*
 *****************************************************************************
 *
 * Terminate network handling
 */
static short exit_net(__u16 retval)
{
  __u16 newfbm;

  /*
   * This routine has to be last in the chain of exit routines,
   * because in case some other exit routine returns with an error
   * and we have already terminated the PXE bootrom services, there
   * is no chance of returning back to the bootrom. Therefore, we
   * should not continue if we are not going to a run a new boot
   * image, for example by using the bootrom for loading or by
   * running under DOS.
   */
  if (retval != RTERROR_RUN)
	return(retval);
  if (!canrun)
	return(RTERROR_ABORT);

  /*
   * If we have an old bootrom without PXE support, we can simply
   * call it's services interrupt, which will terminate the bootrom
   * completely.
   */
  if (pxenvstruct == FNULL) {
	if (loadflags != FLAG1_KEEP_ALL && haveservice)
		call_nb_service(NB_SERVICE_TERM);
	return(RTERROR_RUN);
  }

  /* Terminate the PXE base code */
  newfbm = 0;
  if (loadflags != FLAG1_KEEP_ALL) {
	union {
		t_base_stop_base    stopparms;
		t_base_unload_stack unloadparms;
	} parms;

	if (pxe(BASE_STOP_BASE, &parms.stopparms) == PXENV_EXIT_SUCCESS &&
	    pxe(BASE_UNLOAD_STACK, &parms.unloadparms) == PXENV_EXIT_SUCCESS)
		newfbm = undiseg;
  }

  /* Terminate the UNDI */
  if (loadflags != FLAG1_KEEP_ALL && loadflags != FLAG1_KEEP_UNDI) {
	union {
		t_undi_stop    stopparms;
		t_undi_cleanup cleanparms;
	} parms;

	if (pxe(UNDI_STOP_UNDI, &parms.stopparms) == PXENV_EXIT_SUCCESS &&
	    pxe(UNDI_CLEANUP, &parms.cleanparms) == PXENV_EXIT_SUCCESS) {
		if (newfbm != 0)
			newfbm = undiseg + undisize;
	}
  }

  /* Save new base of free memory */
  if (newfbm != 0) {
	__u16 d0, d1, d2;

	__asm__ __volatile__(
		"push %%es\n\t"
		"movw %0,%%es\n\t"
		"movw %2,%%es:(%1)\n\t"
		"pop %%es"
		: "=&r" (d0), "=&b" (d1), "=&r" (d2)
		: "0" ((__u16)BIOS_SEG), "1" ((__u16)BIOS_BASEMEM),
					"2" ((__u16)(newfbm >> 6))
		: "memory");
  }

  /* Now we can safely load the new operating system */
  return(RTERROR_RUN);
}



/*
 *****************************************************************************
 *
 * Initialize network handling
 */
void init_net(void)
{
  __u16 endseg;

  /* Only initialize exit routine if not running under DOS */
  if (atexit != NNULL)
	rtint_error("Invalid call to network initialization");
  if (canrun)
	atexit = (__nptr)&exit_net;

  /* Check if the bootrom supports the old netboot service interrupt */
  haveservice = FALSE;
  if (getint(NB_SERVICE_INT) != FNULL &&
      call_nb_service(NB_SERVICE_CHECK) == NB_SERVICE_MAGIC)
	haveservice = TRUE;

  /* Set the loader flags to a known state */
  loadflags = FLAG1_KEEP_ALL;

  /*
   * Determine the beginning segment and size of UNDI so
   * that we can remove any PXE component from memory lateron.
   */
  undiseg = 0;
  endseg = 0;
  if (pxenvstruct != FNULL) {
	t_pxenv_entry *pxenvptr;

	/* Copy PXENV+ structure into some local memory */
	pxenvptr = (t_pxenv_entry *)malloc(sizeof(t_pxenv_entry));
	if (pxenvptr == NULL)
		rtint_raise(ENOMEMORY);
	movemem(far2linear(pxenvstruct),
			far2linear(MK_FP(getds(), (__u16)((int)pxenvptr))),
			sizeof(t_pxenv_entry));

	/* Determine start and end segment of UNDI */
	if (pxenvptr->net_cs_sel < pxenvptr->net_ds_sel) {
		undiseg = pxenvptr->net_cs_sel;
		endseg = pxenvptr->net_ds_sel +
				((pxenvptr->net_ds_size + 0x000F) >> 4);
	} else {
		undiseg = pxenvptr->net_ds_sel;
		endseg = pxenvptr->net_cs_sel +
				((pxenvptr->net_cs_size + 0x000F) >> 4);
	}
	free(pxenvptr);
  } else if (pxestruct != FNULL) {
	t_pxe_struct *pxeptr;

	/* Copy !PXE structure into some local memory */
	pxeptr = (t_pxe_struct *)malloc(sizeof(t_pxe_struct));
	if (pxeptr == NULL)
		rtint_raise(ENOMEMORY);
	movemem(far2linear(pxestruct),
			far2linear(MK_FP(getds(), (__u16)((int)pxeptr))),
			sizeof(t_pxe_struct));

	/* Determine start and end segment of UNDI */
	if (pxeptr->desc[PXE_DESC_UNDI_CODE].segment <
	    pxeptr->desc[PXE_DESC_UNDI_DATA].segment) {
		undiseg = pxeptr->desc[PXE_DESC_UNDI_CODE].segment;
		endseg = pxeptr->desc[PXE_DESC_UNDI_DATA].segment +
			((pxeptr->desc[PXE_DESC_UNDI_DATA].size + 0x000F) >> 4);
	} else {
		undiseg = pxeptr->desc[PXE_DESC_UNDI_DATA].segment;
		endseg = pxeptr->desc[PXE_DESC_UNDI_CODE].segment +
			((pxeptr->desc[PXE_DESC_UNDI_CODE].size + 0x000F) >> 4);
	}
	free(pxeptr);
  }
  if (undiseg > MEMEND)
	undiseg = MEMEND;
  if (endseg > MEMEND)
	endseg = MEMEND;
  if (endseg < undiseg)
	undisize = 0;
  else
	undisize = endseg - undiseg;

  /*
   * If we have a PXE capable bootrom, we set the address of the BOOTP
   * record using a PXE call. If we got called by a netboot bootrom,
   * we just use the returned information to update the length of the
   * BOOTP buffer.
   */
  if (havepxe) {
	__fptr ptr = FNULL;
	__u16 len = 0;

	if (!getbootpbuf(&ptr, &len))
		rtint_error("Unable to determine BOOTP/DHCP record address");
	if (btpptr == FNULL) {
		/* PXE bootrom */
		btpptr = ptr;
		btplen = len;
	} else if (btpptr == ptr) {
		/* netboot bootrom */
		btplen = len;
	}
  }
}

