/*
 * netdrv.c  -  Handle different network drivers
 *
 * Copyright (C) 1998-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: netdrv.c,v 1.7 2003/01/25 23:29:42 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include "makerom.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.
 */
#define COMSIG		0x4B47		/* Signature for DOS program header */
#define EXESIG		0x5A4D		/* Signature of DOS EXE files */

#define MAXARGSIZE	126		/* Max length of DOS argument line */
#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 MAXCOMPARA	0x0FFFL		/* Max paragraphs for COM program */
#define COMSTACKPARA	(1024L / 16L)	/* Paragraph no. for COM stack */
#define MINPROGPARA	80		/* Min program size is paragraphs */

#define PSPSIZE		256L		/* Size of DOS PSP */
#define PSPPARA		(PSPSIZE / 16L)	/* Size of DOS PSP in paragraphs */
#define DOSBASEPARA	6L		/* DOS base paragraphs required */

#define EXESECTSIZE	512		/* Size of one sector in EXE file */
#define EXESIGOFS	0x0000		/* Offset to EXE file signature */
#define EXELASTSECT	0x0002		/* Number of bytes in last EXE sector */
#define EXEFSIZE	0x0004		/* Number of sectors in EXE file */
#define EXEHEADSIZE	0x0008		/* Offset to size of EXE file header */
#define EXEMINPARA	0x000A		/* Required number of paragraphs */
#define EXEHDRSIZE	0x001A		/* Minimum size of EXE header */

#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 UNDIHDRSIZE	0x000E		/* 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(fname, progpara, buf)
char *fname;
unsigned long progpara;
__u8 *buf;
{
  unsigned long sizepara;

  sizepara = (ttoh(getval(*((__u16 *)(&buf[0x0001])))) + 15L) / 16L;
  if (sizepara < progpara) {
	/* PKLITE sometimes gives wrong sizes */
	prnerr1("Warning: %s", fname);
	prnerr0("Invalid size in file compressed with PKLITE.");
	prnerr0("Continuing anyway with some hopefully safe defaults. You should");
	prnerr0("better decompress the file before using it for a bootrom.");
	sizepara = MAXCOMPARA;
  }
  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 {
	unsigned char  *name;
	unsigned char  *idstring;
	unsigned int    idoffset;
	unsigned long (*getsize)__P((char *, unsigned long, __u8 *));
} complist[] = {
	{ "PKLITE",	"PKLITE",	0x0030,	&chk_pklite },
	{ NULL,		NULL,		0,	NULL }
};



/*
 * Copy DOS program into output file
 */
static unsigned long copy_prog(fname, args, minsize, maxsize, outfile)
char *fname;
char *args;
long minsize;
long maxsize;
int outfile;
{
  __u8 inbuf[BLKSIZE];
  __u8 firstbuf[BLKSIZE];
  unsigned long progsize;
  unsigned long progpara;
  unsigned long dospara;
  unsigned long writecnt = 0L;
  unsigned int inbuflen, firstbuflen;
  int i, len, infile;
  __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.
   */
  memset(firstbuf, 0, sizeof(firstbuf));
  firstbuf[0] = (__u8)(COMSIG & 0xff);
  firstbuf[1] = (__u8)((COMSIG >> 8) & 0xff);

  if (args != NULL) {
	if ((len = strlen(args)) > MAXARGSIZE - 1) {
		prnerr0("DOS program argument too large");
		exit(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. */
  progsize = filesize(fname);
  if ((infile = open(fname, O_RDONLY | O_BINARY)) < 0) {
	prnerr1("unable to open DOS program %s", fname);
	exit(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 (ttoh(getval(*((__u16 *)(&inbuf[EXESIGOFS])))) == EXESIG) {
	unsigned long exehlen = 0L;
	unsigned long exesize, l;

	/* Determine length of EXE file header */
	if (inbuflen >= EXEHDRSIZE)
		exehlen = ttoh(getval(*((__u16 *)(&inbuf[EXEHEADSIZE])))) * 16;
	if (exehlen < EXEHDRSIZE) {
		prnerr1("invalid EXE header in file %s", fname);
		exit(EXIT_MAKEROM_INVEXE);
	}

	/* Determine total amount of memory required for EXE program */
	l = ttoh(getval(*((__u16 *)(&inbuf[EXELASTSECT]))));
	exesize  = (ttoh(getval(*((__u16 *)(&inbuf[EXEFSIZE])))) - 1) * EXESECTSIZE;
	exesize += ttoh(getval(*((__u16 *)(&inbuf[EXEMINPARA])))) * 16;
	exesize += (l == 0L ? EXESECTSIZE : l);
	if ((exesize + exehlen) > MAXPROGSIZE) {
		prnerr1("DOS program %s requires too much memory", fname);
		exit(EXIT_MAKEROM_INVEXE);
	}

	/* Skip any possible debugging information in EXE file */
	if (progsize > (exesize + exehlen))
		progsize = exesize + exehlen;
	progpara = ((exesize + 15L) / 16L) + PSPPARA;
  } else {
	unsigned long reqmem;
	int compr;

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

	/* 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) {
				prnerr2("program %s is compressed with %s",
						fname, complist[compr].name);
				prnerr0("it has to be decompressed before it can be used");
				exit(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 || 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;

	/* Check amount of paragraphs to allocate */
	if (progpara > MAXCOMPARA) {
		prnerr1("DOS program %s requires too much memory", fname);
		exit(EXIT_MAKEROM_DOSSIZE);
	}
  }

  /* Minimum program size - smaller values get rejected by the bootrom */
  if (progpara < MINPROGPARA)
	progpara = MINPROGPARA;

  /* 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(progpara)));
  firstbuflen += 4;

  /* Determine the minimum and maximum amount of memory required by program */
  if (maxsize < 0L)
	maxsize = progpara * 16L;
  else if ((maxsize + 15L) / 16L < progpara) {
	prnerr1("Warning: %s", fname);
	prnerr0("Given maxsize value probably too small, but using it anyway.");
	if (verbose > 1)
		prnerr2("maxsize = %ld, progsize = %ld",
						maxsize, progpara * 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) {
		prnerr1("unexepected end of DOS program %s", fname);
		exit(EXIT_MAKEROM_EXEEOF);
	}
  }
  close(infile);
  return(writecnt);
}



/*
 * Write DOS programs into output file
 */
static unsigned long dodosprog(pdp, outfile)
struct progdef *pdp;
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);
	if (verbose > 2)
		printf("End offset of prog %d:           %lu\n", i, writecnt);
  }
  return(writecnt);
}



/*
 * Write packet driver programs into output file
 */
static unsigned long dopktdrv(np, parareq, outfile)
struct netdrvdef *np;
unsigned long *parareq;
int outfile;
{
  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(np, parareq, outfile)
struct netdrvdef *np;
unsigned long *parareq;
int outfile;
{
  struct ndisdef *ndp = &(np->driverdefs.ndis);
  struct i_long il;
  unsigned long writecnt = 0L;

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

  /* Setup minimum amount of paragraphs required for DOS simulator */
  dosminpara = dosmaxpara = DOSBASEPARA + ((ndp->protinisize + 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(np, parareq, outfile)
struct netdrvdef *np;
unsigned long *parareq;
int outfile;
{
  struct undidef *up = &(np->driverdefs.undi);
  struct stat sbuf;
  unsigned long undisize;
  unsigned long writecnt = 0L;
  __u8 inbuf[BLKSIZE];
  int infile, inbuflen;


  /* Open the UNDI driver image file */
  if ((infile = open(up->name, O_RDONLY | O_BINARY)) < 0) {
	prnerr1("unable to open UNDI driver%s", up->name);
	exit(EXIT_MAKEROM_OPENDRV);
  }
  if (fstat(infile, &sbuf) < 0) {
	prnerr1("unable to stat UNDI driver %s", up->name);
	exit(EXIT_STAT);
  }
  if (sbuf.st_size > MAXUNDISIZE) {
	prnerr1("UNDI driver %s too large", up->name);
	exit(EXIT_MAKEROM_UNDISIZE);
  }

  /*
   * Read the first sector of the UNDI driver and check that it has a
   * correct header.
   */
  inbuflen = nbread((__u8 *)inbuf, BLKSIZE, infile);
  if (inbuflen < UNDIHDRSIZE ||
      !bytecmp(UNDISIGNATURE, inbuf, strlen(UNDISIGNATURE))) {
	prnerr1("invalid UNDI driver header in file %s", up->name);
	exit(EXIT_MAKEROM_INVDRV);
  }
  undisize  = ttoh(getval(*((__u16 *)(&inbuf[UNDITEXT]))));
  undisize += ttoh(getval(*((__u16 *)(&inbuf[UNDIBSS]))));
  if (undisize > MAXUNDISIZE) {
	prnerr1("UNDI driver %s too large", up->name);
	exit(EXIT_MAKEROM_UNDISIZE);
  }
  *parareq += ((undisize + 15L) / 16L) + 1;

  /* Write the driver image into the output file */
  while (inbuflen > 0) {
	writecnt += nbwrite((__u8 *)inbuf, inbuflen, outfile);
	inbuflen = nbread((__u8 *)inbuf, BLKSIZE, infile);
  }
  close(infile);
  return(writecnt);
}



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



syntax highlighted by Code2HTML, v. 0.9.1