/*
 * netdrv.c  -  Handle different network drivers
 *
 * Copyright (C) 1998-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: netdrv.c,v 1.22 2007/01/06 18:31:15 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include <dos/exec.h>
#include "makerom.h"
#include "protini.h"



/*
 * Definitions local for this module
 *
 * Note for MAXPROGSIZE/MAXUNDISIZE: The bootrom copies the program at one
 * piece, and can therefore only handle max. 64kB. However, since the starting
 * address within the decompressed image might not start at a segment boundary
 * and we can't allow segment overruns, the maximum allowable size for any
 * program file is 65535 - 16, e.g. 65519, minus one byte for safety.
 * However, this is just a theoretical maximum. The network driver interface
 * stores the whole DOS memory area within its own data segment, and that data
 * segment cant be larger than 64kB.
 */
#define MAXPROGSIZE	65518L		/* Max size of DOS program */
#define MAXUNDISIZE	65518L		/* Max size of UNDI driver */
#define MAXPISIZE	16424L		/* Max size of PROTOCOL.INI image */

#define DOSBASEPARA	6L		/* DOS base paragraphs required */

#define UNDISIGNATURE	"UNDI"		/* UNDI file signature */
#define UNDISIGOFS	0x0000		/* Offset to UNDI file signature */
#define UNDITEXT	0x0004		/* Size of text segment in bytes */
#define UNDIDATA	0x0006		/* Size of data segment in bytes */
#define UNDIBSS		0x0008		/* Size of BSS segment in bytes */
#define UNDITEXTOFS	0x000C		/* Offset to start of text segment */
#define UNDIDATAOFS	0x000E		/* Offset to start of data segment */
#define UNDISTACK	0x0010		/* Size of UNDI stack in bytes */
#define UNDIHEAP	0x0012		/* Size of UNDI heap in bytes */
#define UNDIHDRSIZE	0x0014		/* Size of UNDI file header */



/*
 * Variables local to this module
 */
static unsigned long dosminpara;	/* Minimum no. of DOS paragraphs */
static unsigned long dosmaxpara;	/* Maximum no. of DOS paragraphs */



/*
 * Determine required paragraphs for a program compressed with PKLITE.
 */
static unsigned long chk_pklite __F((fname, progpara, buf),
				const char *fname AND
				unsigned long progpara AND
				const __u8 *buf)
{
  unsigned long sizepara;

  sizepara = (get_word(*((__u16 *)(&buf[0x0001]))) + 15L) / 16L;
  if (sizepara < progpara) {
	/* PKLITE sometimes gives wrong sizes */
	prnlog(LOGLEVEL_NORMAL, "Warning: %s", fname);
	prnlog(LOGLEVEL_NORMAL, "Invalid size in file compressed with PKLITE.");
	prnlog(LOGLEVEL_NORMAL, "Continuing anyway with some hopefully safe defaults. You should");
	prnlog(LOGLEVEL_NORMAL, "better decompress the file before using it for a bootrom.");
	sizepara = MAX_PROG_PARA;
  }
  return(sizepara);
}



/*
 * Some DOS executables are packed or compressed. Most decompressors require
 * a little amount of memory behind the loaded program. This is only neces-
 * sary for COM programs, since EXE programs know in advance how large they
 * have to be for decompression.
 * The only compressor I know of so far is PKLITE. All other compressors like
 * tinyprog or lzexe can only compress EXE type files, or generate EXE even
 * when compressing COM programs.
 */
static struct compstruct {
	char           *name;
	char           *idstring;
	unsigned int    idoffset;
	unsigned long (*getsize)
			__P((const char *, unsigned long, const __u8 *));
} complist[] = {
	{ "PKLITE",	"PKLITE",	0x0030,	&chk_pklite },
	{ NULL,		NULL,		0,	NULL }
};



/*
 * Copy DOS program into output file
 */
static unsigned long copy_prog __F((fname, args, minsize, maxsize, outfile),
				const char *fname AND
				const char *args AND
				long minsize AND
				long maxsize AND
				int outfile)
{
  __u8 inbuf[BLKSIZE];
  __u8 firstbuf[BLKSIZE];
  unsigned long progsize;
  unsigned long allocmin;
  unsigned long allocmax;
  unsigned long dospara;
  unsigned long writecnt = 0L;
  size_t inbuflen, firstbuflen;
  int i, len, infile;
  long l;
  __u16 *u16p;

  /*
   * Each DOS program requires a header which looks like:
   *
   *	Offset
   *	 0000  -  Two byte magic cookie
   *	 0002  -  Length of command line in bytes
   *	 0003  -  Command line (not terminated by zero)
   *	 var   -  Length of program image in bytes
   *	 var+2 -  Amount of memory to allocate in paragraphs
   *	 var+4 -  Program image
   *
   * Setup everything up to the command line.
   */
  memzero(firstbuf, sizeof(firstbuf));
  firstbuf[0] = (__u8)(COM_MAGIC & 0xff);
  firstbuf[1] = (__u8)((COM_MAGIC >> 8) & 0xff);

  if (args != NULL) {
	if ((len = strlen(args)) > PSP_MAXCMDL) {
		prnerr("DOS program argument too large");
		nbexit(EXIT_MAKEROM_ARGSIZE);
	}
	firstbuf[2] = (__u8)((len + 1) & 0xff);
	firstbuf[3] = (__u8)0x20;	/* cmd line has to start with a blank */
	for (firstbuflen = 4, i = 0;
	     firstbuflen < sizeof(firstbuf) && i < len && args[i];
	     firstbuflen++, i++)
		firstbuf[firstbuflen] = (__u8)(args[i] & 0xff);
  } else {
	firstbuf[2] = 0;
	firstbuflen = 3;
  }

  /* Now open the DOS program image file and determine it's size. */
  if ((l = filesize(fname)) == -1)
	nbexit(-1);
  progsize = (unsigned long)l;
  if ((infile = open(fname, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open DOS program %s", fname);
	nbexit(EXIT_MAKEROM_OPENDRV);
  }

  /*
   * Read first sector of DOS program image and check it's type. In case of
   * a COM program, we set the number of required memory paragraphs to the
   * total size of the program. Otherwise, for an EXE program we can take
   * this value out of the EXE file header.
   */
  inbuflen = nbread((unsigned char *)inbuf, sizeof(inbuf), infile);
  if (get_word(*((__u16 *)inbuf)) == EXE_MAGIC) {
	struct exehdr *exep = (struct exehdr *)inbuf;
	unsigned long exehlen = 0L;
	unsigned long exesize, l;

	/* Determine length of EXE file header */
	if (inbuflen >= sizeof(struct exehdr))
		exehlen = get_word(exep->headsize) * 16;
	if (exehlen < sizeof(struct exehdr)) {
		prnerr("invalid EXE header in file %s", fname);
		nbexit(EXIT_MAKEROM_INVEXE);
	}

	/* Determine total load size required for EXE program */
	l = get_word(exep->lastsect);
	exesize  = (get_word(exep->fsize) - 1) * EXE_SECTSIZE;
	exesize += (l == 0L ? EXE_SECTSIZE : l);
	if ((exesize + exehlen) > MAXPROGSIZE) {
		prnerr("DOS program %s too large", fname);
		nbexit(EXIT_MAKEROM_DOSSIZE);
	}

	/* Skip any possible debugging information in EXE file */
	if (progsize > (exesize + exehlen))
		progsize = exesize + exehlen;

	/* Determine minimum and maximum amount of memory to allocate */
	allocmin = ((exesize + 15L) / 16L) + get_word(exep->minpara) +
						(PSP_SIZE / 16);
	allocmax = ((exesize + 15L) / 16L) + get_word(exep->maxpara) +
						(PSP_SIZE / 16);
  } else {
	unsigned long reqmem;
	unsigned long progpara;
	int compr;

	/* Check amount of memory required to load the file */
	if (progsize > (MAXPROGSIZE - PSP_SIZE)) {
		prnerr("DOS program %s too large", fname);
		nbexit(EXIT_MAKEROM_DOSSIZE);
	}
	progpara = ((progsize + 15L) / 16L) + (PSP_SIZE / 16) + COM_STACK_PARA;

	/* Check if the program has been compressed */
	for (compr = 0; complist[compr].name != NULL; compr++) {
		if (bytecmp(complist[compr].idstring,
				&inbuf[complist[compr].idoffset],
				strlen(complist[compr].idstring))) {
			if (complist[compr].getsize == NULL) {
				prnerr("program %s is compressed with %s",
						fname, complist[compr].name);
				prnerr("it has to be decompressed before it can be used");
				nbexit(EXIT_MAKEROM_PROGCOMP);
			}
			progpara = (*complist[compr].getsize)(fname, progpara,
									inbuf);
			break;
		}
	}

	/*
	 * With COM programs it's fairly difficult to determine the amount
	 * of memory required for loading and running it (in contrast to
	 * EXE programs which have this information in their header). The
	 * code above just guesses some minimum value. In case the user
	 * has specified a different value with the maxsize parameter, use
	 * that instead. Also note that some programs might require more
	 * memory when running than for just loading them. In order to care
	 * for this, we use the larger of minsize and maxsize for the actual
	 * amount of memory to allocate.
	 * If the user has not specified a maxsize parameter, we take the
	 * size guessed above and round it up to the maximum value.
	 */
	if (maxsize < 0L &&
	    (minsize < 0 || (unsigned long)minsize < (progpara * 16L)))
		reqmem = progpara;
	else if (minsize < 0L || minsize < maxsize)
		reqmem = (maxsize + 15L) / 16L;
	else
		reqmem = (minsize + 15L) / 16L;
	if (reqmem > progpara)
		progpara = reqmem;

	/* Determine minimum and maximum amount of memory to allocate */
	allocmin = progpara;
	allocmax = 0xFFFF;
  }

  /* Check on minimum and maximum DOS program sizes */
  if (allocmin < MIN_PROG_PARA)
	allocmin = MIN_PROG_PARA;
  else if (allocmin > MAX_PROG_PARA) {
	prnerr("DOS program %s requires too much memory", fname);
	nbexit(EXIT_MAKEROM_DOSSIZE);
  }
  if (allocmax < allocmin)
	allocmax = allocmin;
  else if (allocmax > 0xFFFF)
	allocmax = 0xFFFF;

  /* Now set the program header in the output file */
  u16p = (__u16 *)(&firstbuf[firstbuflen]);
  assign(*u16p, htot(low_word(progsize)));
  u16p = (__u16 *)(&firstbuf[firstbuflen + 2]);
  assign(*u16p, htot(low_word(allocmin)));
  u16p = (__u16 *)(&firstbuf[firstbuflen + 4]);
  assign(*u16p, htot(low_word(allocmax)));
  firstbuflen += 6;

  /* Determine the minimum and maximum amount of memory required by program */
  if (maxsize < 0L)
	maxsize = allocmin * 16L;
  else if (((unsigned long)maxsize + 15L) / 16L < allocmin) {
	prnlog(LOGLEVEL_NORMAL, "Warning: %s", fname);
	prnlog(LOGLEVEL_NORMAL, "Given maxsize value probably too small, but using it anyway.");
	if (verbose > 1)
		prnlog(LOGLEVEL_NORMAL, "maxsize = %ld, progsize = %ld",
						maxsize, allocmin * 16L);
  }
  if (minsize < 0L)
	minsize = maxsize;
  if (minsize > maxsize)
	maxsize = minsize;

  /*
   * Determine the amount of memory required for the DOS simulator. When the
   * first program loads, it has to have at least maxsize bytes of memory
   * available. When it terminates, it will occupy minsize bytes of memory.
   * Therefore, for the next program the total amount of DOS memory has to
   * be at least the number of paragraphs required so far by the preceding
   * programs, plus it's own maximum value. At the end, dosmaxpara is the
   * number of paragraphs required by the DOS simulator.
   */
  dospara = dosminpara + ((maxsize + 15L) / 16L) + 1;
  if (dospara > dosmaxpara)
	dosmaxpara = dospara;
  dosminpara += ((minsize + 15L) / 16L) + 1;

  /* Now first write the program header and then the program image */
  writecnt += nbwrite(firstbuf, firstbuflen, outfile);
  while (TRUE) {
	if (inbuflen >= progsize) {
		writecnt += nbwrite(inbuf, progsize, outfile);
		break;
	}
	progsize -= inbuflen;
	writecnt += nbwrite(inbuf, inbuflen, outfile);
	if ((inbuflen = nbread(inbuf, BLKSIZE, infile)) == 0) {
		prnerr("unexepected end of DOS program %s", fname);
		nbexit(EXIT_MAKEROM_EXEEOF);
	}
  }
  close(infile);
  return(writecnt);
}



/*
 * Write DOS programs into output file
 */
static unsigned long dodosprog __F((pdp, outfile),
				const struct progdef *pdp AND
				int outfile)
{
  unsigned long writecnt = 0L;
  int i;

  for (i = 0; i < pdp->prognum; i++) {
	writecnt += copy_prog(pdp->prognames[i], pdp->progargs[i],
				pdp->minsizes[i], pdp->maxsizes[i], outfile);
	prnlog(LOGLEVEL_DEBUG, "End offset of prog %d:           %lu\n", i,
								writecnt);
  }
  return(writecnt);
}



/*
 * Write packet driver programs into output file
 */
static unsigned long dopktdrv __F((np, parareq, paratext, outfile),
				const struct netdrvdef *np AND
				unsigned long *parareq AND
				unsigned long *paratext AND
				int outfile)
{
  const struct pktdrvdef *pdp = &(np->driverdefs.pd);
  struct i_long il;
  unsigned long writecnt = 0L;

  /* Setup minimum amount of paragraphs required for DOS simulator */
  dosminpara = dosmaxpara = DOSBASEPARA;

  /* Write the number of DOS programs into output file */
  assign(il.low, htot(low_word(pdp->progs.prognum)));
  writecnt += nbwrite((__u8 *)&(il.low), sizeof(il.low), outfile);

  /* Write all DOS programs into output file */
  writecnt += dodosprog(&(pdp->progs), outfile);
  *parareq += dosmaxpara;
  return(writecnt);
}



/*
 * Write NDIS driver programs into output file
 */
static unsigned long dondisdrv __F((np, parareq, paratext, outfile),
				const struct netdrvdef *np AND
				unsigned long *parareq AND
				unsigned long *paratext AND
				int outfile)
{
  const struct ndisdef *ndp = &(np->driverdefs.ndis);
  struct i_long il;
  unsigned long writecnt = 0L;
  __u8 *pibuf = NULL;
  size_t pisize = 0L;

  /* Write the size and image of protocol.ini into the output file */
  parseprotini(ndp->protocolini, &pibuf, &pisize);
  if (pisize > MAXPISIZE) {
	prnerr("PROTOCOL.INI image too large");
	nbexit(EXIT_MAKEROM_PISIZE);
  }
  assign(il.low, htot(low_word(pisize)));
  writecnt += nbwrite((__u8 *)&(il.low), sizeof(il.low), outfile);
  writecnt += nbwrite((__u8 *)(pibuf), pisize, outfile);
  free(pibuf);

  /* Setup minimum amount of paragraphs required for DOS simulator */
  dosminpara = dosmaxpara = DOSBASEPARA + ((pisize + 15L) / 16L) + 1;

  /* Next write the number of DOS programs into output file */
  assign(il.low, htot(low_word(ndp->progs.prognum)));
  writecnt += nbwrite((__u8 *)&(il.low), sizeof(il.low), outfile);

  /* Write all DOS programs into output file */
  writecnt += dodosprog(&(ndp->progs), outfile);
  *parareq += dosmaxpara;
  return(writecnt);
}



/*
 * Write UNDI driver into output file
 */
static unsigned long doundidrv __F((np, parareq, paratext, outfile),
				const struct netdrvdef *np AND
				unsigned long *parareq AND
				unsigned long *paratext AND
				int outfile)
{
  const struct undidef *up = &(np->driverdefs.undi);
  unsigned long undisize, undiaddr;
  unsigned long textofs, dataofs;
  unsigned long textsize;
  unsigned long datasize;
  unsigned long heapsize;
  unsigned long writecnt = 0L;
  __u8 inbuf[BLKSIZE];
  size_t inbuflen;

  /* Determine text and data sizes of the interface file */
  textsize = *paratext * 16L;
  datasize = (*parareq - *paratext) * 16L;

  /* Open the UNDI driver image file */
  if (!elfopen(up->name, textsize, datasize)) {
	prnerr("unable to open UNDI driver %s: %s", up->name, elfgeterr());
	nbexit(EXIT_MAKEROM_OPENDRV);
  }

  /*
   * Read the first sector of the UNDI driver text segment and check
   * that it has a correct header.
   */
  if (!elfset(ELF_MODE_TEXT, &undiaddr, &undisize)) {
	prnerr("unable to load UNDI text segment: %s", elfgeterr());
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  if (undiaddr != textsize || undisize == 0) {
	prnerr("invalid UNDI text segment in file %s", up->name);
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  inbuflen = UNDIHDRSIZE;
  if (!elfread(inbuf, &inbuflen)) {
	prnerr("unable to read UNDI file header: %s", elfgeterr());
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  if (inbuflen < UNDIHDRSIZE ||
      !bytecmp(UNDISIGNATURE, inbuf, strlen(UNDISIGNATURE))) {
	prnerr("invalid UNDI driver header in file %s", up->name);
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  textofs = get_word(*((__u16 *)(&inbuf[UNDITEXTOFS])));
  dataofs = get_word(*((__u16 *)(&inbuf[UNDIDATAOFS])));
  if (textofs != textsize || dataofs != datasize) {
	prnerr("text/data offset invalid in UNDI driver file %s", up->name);
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  textsize = roundup(get_word(*((__u16 *)(&inbuf[UNDITEXT]))), 16);
  datasize = roundup(get_word(*((__u16 *)(&inbuf[UNDIBSS]))), 16);
  heapsize = roundup(get_word(*((__u16 *)(&inbuf[UNDISTACK]))), 16) +
             roundup(get_word(*((__u16 *)(&inbuf[UNDIHEAP]))), 16);
  if (textsize > MAXUNDISIZE) {
	prnerr("UNDI driver text segment in file %s too large", up->name);
	nbexit(EXIT_MAKEROM_UNDISIZE);
  }
  if ((datasize + heapsize) > MAXUNDISIZE) {
	prnerr("UNDI driver data segment in file %s too large", up->name);
	nbexit(EXIT_MAKEROM_UNDISIZE);
  }
  *paratext = ((textsize + 15L) / 16L);
  *parareq = (((datasize + heapsize) + 15L) / 16L) + 1 + *paratext;

  /* Write the UNDI text segment into the output file */
  writecnt += nbwrite(inbuf, inbuflen, outfile);
  do {
	inbuflen = BLKSIZE;
	if (!elfread(inbuf, &inbuflen)) {
		prnerr("unable to read UNDI text segment: %s", elfgeterr());
		nbexit(EXIT_MAKEROM_INVDRV);
	}
	writecnt += nbwrite(inbuf, inbuflen, outfile);
  } while (inbuflen == BLKSIZE);

  /* Round UNDI text segment to next paragraph boundary */
  if ((writecnt & 0x0f) != 0) {
	memzero(inbuf, 16);
	writecnt += nbwrite(inbuf, 16 - (writecnt & 0x0f), outfile);
  }

  /* Write the UNDI data segment into the output file */
  if (!elfset(ELF_MODE_DATA, &undiaddr, &undisize)) {
	prnerr("unable to load UNDI data segment: %s", elfgeterr());
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  if (undiaddr != dataofs || undisize == 0) {
	prnerr("invalid UNDI data segment in file %s", up->name);
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  do {
	inbuflen = BLKSIZE;
	if (!elfread(inbuf, &inbuflen)) {
		prnerr("unable to read UNDI data segment: %s", elfgeterr());
		nbexit(EXIT_MAKEROM_INVDRV);
	}
	writecnt += nbwrite(inbuf, inbuflen, outfile);
  } while (inbuflen == BLKSIZE);
  elfclose();
  return(writecnt);
}



/*
 * Copy the network driver into the output file
 */
unsigned long donetdrv __F((np, parareq, paratext, outfile),
				const struct netdrvdef *np AND
				unsigned long *parareq AND
				unsigned long *paratext AND
				int outfile)
{
  switch (np->drivertype) {
	case DRVTYPE_PD:
		return(dopktdrv(np, parareq, paratext, outfile));
	case DRVTYPE_NDIS:
		return(dondisdrv(np, parareq, paratext, outfile));
	case DRVTYPE_UNDI:
		return(doundidrv(np, parareq, paratext, outfile));
  }
  return(0);
}

