/*
 * passes.c  -  Compress kernel image and concatenate it with a boot loader
 *
 * Copyright (C) 1997-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: passes.c,v 1.22 2007/02/01 12:09:22 gkminix Exp $
 */

#define NEED_TIME 1
#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include <system/bootrom.h>
#include <system/pnp.h>
#include <system/pci.h>
#include <pxe/general.h>
#include <pxe/romid.h>
#include "makerom.h"



/*
 * Definitions local to this module
 */
#define ROMSIG		0xAA55		/* ROM signature */
#define ROMVECT18	0x18		/* ROM interrupt vector 18h */
#define ROMVECT19	0x19		/* ROM interrupt vector 19h */

#define MINROMSIZE	8192				/* Minimum ROM size */
#define MAXROMSIZE	((FLOPEND - FLOPMEM) * 16L)	/* Maximum ROM size */
#define MAXOUTSIZE	((DECOMPEND - DECOMPMEM) * 16L)	/* Maximum kern size */



/*
 * Table of bus type strings
 */
static char *bustypestr[] = {
	"ISAR", "EISA", "MCAR", "PCIR", "VESA", "PCCR"
};



/*
 * Table of bus type ID values
 */
static __u8 bustypeid[] = {
	PXENV_BUS_ISA, PXENV_BUS_EISA, PXENV_BUS_MCA, PXENV_BUS_PCI
};




/*
 * Convert a hex character into a binary number
 */
static unsigned int cvthex __F((c), hchar_t c)
{
  pcchar_t pc = chartotarget(c);

  if (pc >= 48 && pc <= 57)
	return(pc - 48);
  else if (pc >= 65 && pc <= 70)
	return(pc - 65 + 10);
  prnerr("invalid PnP ID string");
  nbexit(EXIT_MAKEROM_INVPNPID);
}



/*
 * Convert a single character into a binary number
 */
static unsigned int cvtchar __F((c), hchar_t c)
{
  pcchar_t pc = chartotarget(c);

  if (pc >= 65 && pc <= 90)
	return(pc - 65);
  prnerr("invalid PnP ID string");
  nbexit(EXIT_MAKEROM_INVPNPID);
}



/*
 * Set a PnP device ID
 */
static void setpnpid __F((buf, cp), __u8 *buf AND const char *cp)
{
  hchar_t *pnpid, *pp;
  size_t i;
  __u8 val;

  /* Convert string to upper case */
  pnpid = strtohost(cp);
  for (i = 0; pnpid[i] != 0; i++)
	pnpid[i] = chartoupper(pnpid[i]);
  if (i != PNPIDLEN) {
	prnerr("invalid PnP ID string %s", cp);
	nbexit(EXIT_MAKEROM_INVPNPID);
  }

  /* Convert string into PnP ID number */
  pp = pnpid;
  val  = (unsigned)(cvtchar(*pp++) & 0x1f) << 2;
  val |= (unsigned)(cvtchar(*pp)   & 0x1f) >> 3;
  *buf++ = val & 0x7f;
  val  = (unsigned)(cvtchar(*pp++) & 0x1f) << 5;
  val |= (unsigned)(cvtchar(*pp++) & 0x1f);
  *buf++ = val & 0xff;
  val  = (unsigned)(cvthex(*pp++) & 0x0f) << 4;
  val |= (unsigned)(cvthex(*pp++) & 0x0f);
  *buf++ = val & 0xff;
  val  = (unsigned)(cvthex(*pp++) & 0x0f) << 4;
  val |= (unsigned)(cvthex(*pp++) & 0x0f);
  *buf++ = val & 0xff;
  free(pnpid);
}



/*
 * Pass 1 - concatenate the kernel image with the network driver
 */
static unsigned long pass1 __F((bp, tempfile),
				const struct bootdef *bp AND
				int tempfile)
{
  __u8 kernbuf[BLKSIZE];
  __u8 netbuf[BLKSIZE];
  __u8 firstnetbuf[BLKSIZE];
  unsigned long netdrvofs;
  unsigned long writecnt;
  unsigned long krnlength;
  unsigned long netlength;
  unsigned long romidofs;
  unsigned long nettxtsize;
  unsigned long kernstack, netstack;
  unsigned long kerntotsize, nettotsize, totsize;
  size_t kernbuflen, netbuflen, firstnetbuflen;
  int kernfile, netfile, i;
  __u16 *u16p;

  /* Open the kernel input file */
  if ((kernfile = open(bp->kernelname, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open kernel file %s", bp->kernelname);
	nbexit(EXIT_MAKEROM_OPENKERN);
  }

  /* Open the network driver interface input file */
  if ((netfile = open(bp->netdrv.name, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open network driver interface file %s",
							bp->netdrv.name);
	nbexit(EXIT_MAKEROM_OPENNET);
  }

  /*
   * Read the first kernel block and determine the actual size of the rom kernel
   * without the BSS segment. Also check the version number.
   */
  kernbuflen = BLKSIZE;
  if (nbread(kernbuf, kernbuflen, kernfile) != kernbuflen) {
	prnerr("unexpected end of file %s", bp->kernelname);
	nbexit(EXIT_MAKEROM_READKERN);
  }
  if (kernbuf[KRNMAJOROFS] != (__u8)VER_MAJOR ||
      kernbuf[KRNMINOROFS] != (__u8)VER_MINOR) {
	prnerr("invalid kernel version number");
	nbexit(EXIT_MAKEROM_INVKERN);
  }
  krnlength  = (get_word(*((__u16 *)(&kernbuf[KRNTEXTOFS]))) + 15L) & 0xfff0L;
  krnlength += get_word(*((__u16 *)(&kernbuf[KRNDATAOFS])));

  /*
   * Check that the kernel ROM ID structure is correct, determine it's
   * checksum and get the total memory size required for the kernel.
   */
  kerntotsize = 0;
  kernstack = 0;
  romidofs = get_word(*((__u16 *)(&kernbuf[KRNROMIDOFS])));
  if (romidofs > 0 && romidofs < (sizeof(kernbuf) - sizeof(t_kernel_romid))) {
	t_kernel_romid *idptr = (t_kernel_romid *)(&kernbuf[romidofs]);
	unsigned short romidlen;
	int romidchksum;

	romidlen = (unsigned short)(idptr->length & 0x00ff);
	if (romidlen < sizeof(t_kernel_romid) ||
	    (romidofs + romidlen) > kernbuflen) {
		prnerr("invalid ROM ID structure in kernel file");
		nbexit(EXIT_MAKEROM_INVKERN);
	}
	if (!bytecmp(KERNEL_ROMID_SIG, idptr->signature,
					sizeof(idptr->signature))) {
		prnerr("file %s is not a netboot kernel", bp->kernelname);
		nbexit(EXIT_MAKEROM_INVKERN);
	}
	romidchksum = 0;
	for (i = 0; i < romidlen; i++)
		romidchksum += (int)(kernbuf[romidofs + i] & 0x00ff);
	idptr->checksum = (__u8)((0 - romidchksum) & 0xff);
	kernstack    = (get_word(idptr->stacksize) + 15L) / 16L;
	kerntotsize  = (get_word(idptr->datasize) + 15L) / 16L;
	kerntotsize += (get_word(idptr->codesize) + 15L) / 16L;
  } else {
	/* The kernel ROMID structure has to exist */
	prnerr("missing ROM ID structure in kernel file");
	nbexit(EXIT_MAKEROM_INVKERN);
  }

  /* Set the kernel flags, but only if available */
  if (get_word(*((__u16 *)(&kernbuf[KRNFLAGOFS]))) == 0) {
	u16p = (__u16 *)(&kernbuf[KRNFLAGOFS]);
	assign(*u16p, htot(bp->kernel_flags & 0xffff));
  } else {
	/*
	 * If the storage place for the kernel flags is not zero, we have
	 * an old bootrom which is missing the flags value, and has the
	 * ROM ID structure in it's place.
	 */
	prnlog(LOGLEVEL_NORMAL, "Warning: Unable to set kernel flags, using default\n");
  }

  /*
   * Read the first network driver interface block and determine the
   * actual size of the interface without the BSS segment. Also check
   * the version number.
   */
  netbuflen = BLKSIZE;
  if (nbread(netbuf, netbuflen, netfile) != netbuflen) {
	prnerr("unexpected end of file %s", bp->netdrv.name);
	nbexit(EXIT_MAKEROM_READNET);
  }
  if (netbuf[NETMAJOROFS] != (__u8)VER_MAJOR ||
      netbuf[NETMINOROFS] != (__u8)VER_MINOR) {
	prnerr("invalid network driver interface version number");
	nbexit(EXIT_MAKEROM_INVDRV);
  }
  netlength  = (get_word(*((__u16 *)(&netbuf[NETTEXTOFS]))) + 15L) & 0xfff0L;
  netlength += get_word(*((__u16 *)(&netbuf[NETDATAOFS])));

  /*
   * Check that the network driver ROM ID structure is correct and determine
   * it's checksum. The total number of paragraphs required for the network
   * driver can only be determined after all drivers have been written. We
   * also setup the bus type string in the ROM ID structure.
   * The checksum of the ROM ID structure gets calculated lateron, when we
   * know the total size of the network driver.
   */
  nettxtsize = 0;
  nettotsize = 0;
  netstack = 0;
  romidofs = get_word(*((__u16 *)(&netbuf[NETROMIDOFS])));
  if (romidofs > 0 && romidofs < (sizeof(netbuf) - sizeof(t_undi_romid))) {
	t_undi_romid *idptr = (t_undi_romid *)(&netbuf[romidofs]);
	unsigned short romidlen;

	romidlen = (unsigned short)(idptr->length & 0x00ff);
	if (romidlen < sizeof(t_undi_romid) ||
	    (romidofs + romidlen) > netbuflen) {
		prnerr("invalid ROM ID structure in network driver interface file");
		nbexit(EXIT_MAKEROM_INVDRV);
	}
	if (!bytecmp(UNDI_ROMID_SIG, idptr->signature,
					sizeof(idptr->signature))) {
		prnerr("file %s is not a network driver interface", bp->netdrv.name);
		nbexit(EXIT_MAKEROM_INVDRV);
	}
	bytecpy(bustypestr[bp->bus_type - 1],
				idptr->bustype, sizeof(idptr->bustype));
	netstack    = (get_word(idptr->stacksize) + 15L) / 16L;
	nettxtsize  = (get_word(idptr->codesize) + 15L) / 16L;
	nettotsize  = (get_word(idptr->datasize) + 15L) / 16L;
	nettotsize += nettxtsize;
  } else {
	/* The kernel ROMID structure has to exist */
	prnerr("missing ROM ID structure in network driver interface file");
	nbexit(EXIT_MAKEROM_INVDRV);
  }

  /* Set the bus type into network driver image */
  netbuf[NETBUSTYPEOFS] = bustypeid[bp->bus_type - 1];

  /* Set the device ID within the network driver */
  u16p = (__u16 *)(&netbuf[NETDEVIDOFS]);
  if (bp->bus_type != BUSTYPE_PCI && bp->pnp_devid != NULL)
	setpnpid((__u8 *)u16p, bp->pnp_devid);
  else if (bp->bus_type == BUSTYPE_PCI) {
	assign(*u16p, htot(bp->pci_vendid & 0xffff));
	u16p = (__u16 *)(&netbuf[NETDEVIDOFS + 2]);
	assign(*u16p, htot(bp->pci_devid & 0xffff));
  }

  /* Set the network driver flags */
  u16p = (__u16 *)(&netbuf[NETFLAGOFS]);
  assign(*u16p, htot(bp->netdrv.driverflags & 0xffff));

  /*
   * The kernel and the network driver use one common stack, which
   * is part of the kernel. In order to determine the stack size,
   * we take the larger value of the two stack size values, and
   * use it to compute the total number of paragraphs required for
   * the kernel to run. Then set this number into the kernel image.
   */
  kerntotsize += MAX(kernstack, netstack);
  u16p = (__u16 *)(&kernbuf[KRNSIZEOFS]);
  assign(*u16p, htot(kerntotsize & 0xffff));

  /*
   * Now copy the kernel image file into the output file. Only copy the
   * relevant bytes excluding the BSS segment, which has only zero bytes
   * anyway.
   */
  writecnt = 0L;
  krnlength -= kernbuflen;
  while (TRUE) {
	writecnt += nbwrite(kernbuf, kernbuflen, tempfile);
	if (krnlength <= 0) break;
	kernbuflen = nbread(kernbuf, BLKSIZE, kernfile);
	if (kernbuflen == 0) {
		prnerr("unexpected end of kernel image file");
		nbexit(EXIT_MAKEROM_KERNEOF);
	}
	if (kernbuflen > krnlength)
		kernbuflen = krnlength;
	krnlength -= kernbuflen;
  }
  close(kernfile);
  prnlog(LOGLEVEL_DEBUG, "End position of kernel:         %lu\n", writecnt);

  /*
   * Copy the first network driver interface buffer into a temporary
   * space so that we can restore it after we know the total network
   * driver size.
   */
  memcpy(firstnetbuf, netbuf, sizeof(netbuf));
  firstnetbuflen = netbuflen;
  netdrvofs = writecnt;

  /*
   * Next copy the network driver interface file into the output file in the
   * same way as the kernel image has been copied.
   */
  netlength -= netbuflen;
  while (TRUE) {
	writecnt += nbwrite(netbuf, netbuflen, tempfile);
	if (netlength <= 0) break;
	netbuflen = nbread(netbuf, BLKSIZE, netfile);
	if (netbuflen == 0) {
		prnerr("unexpected end of network interface file");
		nbexit(EXIT_MAKEROM_DRVEOF);
	}
	if (netbuflen > netlength)
		netbuflen = netlength;
	netlength -= netbuflen;
  }
  close(netfile);
  prnlog(LOGLEVEL_DEBUG, "End position of interface file: %lu\n", writecnt);

  /*
   * Postprocess the network driver. This usually means to copy the
   * driver (like the packet driver) into the output file.
   */
  writecnt += donetdrv(&(bp->netdrv), &nettotsize, &nettxtsize, tempfile);
  prnlog(LOGLEVEL_DEBUG, "End position of bootrom image:  %lu\n", writecnt);
  prnlog(LOGLEVEL_DEBUG, "Paragraphs for kernel:          %lu (0x%lX)\n",
						kerntotsize, kerntotsize);
  prnlog(LOGLEVEL_DEBUG, "Paragraphs for network driver:  %lu (0x%lX)\n",
						nettotsize, nettotsize);

  /* Check that the output file size is not too large */
  if (writecnt > MAXOUTSIZE) {
	prnerr("size of output file exceeds %lu kB", MAXOUTSIZE / 1024L);
	nbexit(EXIT_MAKEROM_SIZE);
  }

  /*
   * Compute the total size required for the bootrom and check that it
   * doesn't exceed a limit.
   */
  totsize = kerntotsize + nettotsize;
  if (totsize > (MEMEND - OSENDMEM)) {
	prnerr("size of bootrom run image exceeds %lu kB",
					(MEMEND - OSENDMEM) / (1024L / 16L));
	nbexit(EXIT_MAKEROM_SIZE);
  }

  /*
   * Update the network driver ROM ID structure with the total size of
   * the network driver, and compute the structure checksum. We know
   * that romidofs is correct and points into firstnetbuf.
   */
  if (romidofs > 0) {
	t_undi_romid *idptr = (t_undi_romid *)(&firstnetbuf[romidofs]);
	unsigned long netdatsize = nettotsize - nettxtsize;
	int romidlen;
	int romidchksum;

	/* Compute the size of the data and code segments */
	if (netdatsize >= (65536L / 16L)) {
		prnerr("network driver data segment larger than 64kB");
		nbexit(EXIT_MAKEROM_NETSIZE);
	}
	if (nettxtsize >= (65536L / 16L)) {
		prnerr("network driver code segment larger than 64kB");
		nbexit(EXIT_MAKEROM_NETSIZE);
	}
	assign(idptr->datasize, htot((netdatsize * 16L) & 0xffff));
	assign(idptr->codesize, htot((nettxtsize * 16L) & 0xffff));

	/* Compute the structure checksum */
	romidlen = (int)(idptr->length & 0x00ff);
	romidchksum = 0;
	for (i = 0; i < romidlen; i++)
		romidchksum += (int)(firstnetbuf[romidofs + i] & 0x00ff);
	idptr->checksum = (__u8)((0 - romidchksum) & 0xff);
  }

  /*
   * Set the total network driver size into the first network driver
   * interface buffer and rewrite it into the output file.
   */
  u16p = (__u16 *)(&firstnetbuf[NETSIZEOFS]);
  assign(*u16p, htot(nettotsize & 0xffff));
  if (lseek(tempfile, netdrvofs, 0) < 0) {
	prnerr("unable to seek output file");
	nbexit(EXIT_SEEK);
  }
  (void)nbwrite(firstnetbuf, firstnetbuflen, tempfile);

  /* Rewind to beginning of output file for the following pass 2 */
  if (lseek(tempfile, 0L, 0) < 0) {
	prnerr("unable to seek to beginning of output file");
	nbexit(EXIT_SEEK);
  }

  return(totsize);
}



/*
 * Pass 2 - compress kernel image and concatenate it with the loader
 */
static void pass2 __F((bp, totsize, kernfile, loader),
				const struct bootdef *bp AND
				unsigned long totsize AND
				int kernfile AND
				int loader)
{
  const struct loaderdef *lp = &(bp->loaders[loader]);
  __u8 inbuf[BLKSIZE];			/* Input file buffer */
  __u8 firstbuf[BLKSIZE];		/* Buffer for first sector of ROM */
  unsigned long firstofs;		/* Offset to first ROM sector */
  unsigned long writecnt;		/* number of bytes in output file */
  unsigned long ldsize;			/* Size of rom image loader */
  unsigned long pci_ofs;		/* Offset to PCI header */
  unsigned long pnp_ofs;		/* Offset to PnP header */
  unsigned long outsize;		/* Physical size of ROM */
  unsigned long romvector;		/* Rom interrupt vector */
  unsigned long romsize;		/* Logical size of ROM */
  unsigned long romlength;		/* Size of bootrom kernel image */
  unsigned char romsizchar;
  unsigned int bootflags;
  size_t len, firstlen, i;
  int dorom, infile, outfile;
  __u16 *u16p;

  /*
   * Check for the ROM signature to determine whether we are going to build
   * a ROM or a floppy image.
   */
  if ((infile = open(lp->name, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open loader file %s", lp->name);
	nbexit(EXIT_MAKEROM_OPENLDR);
  }
  len = nbread(inbuf, BLKSIZE, infile);
  dorom = (get_word(*((__u16 *)(&inbuf[0]))) == ROMSIG);
  if (!dorom && lp->outtype == OUT_FLASH) {
	/* Don't produce a flash header for floppy images */
	prnerr("unable to produce floppy image with flash header");
	nbexit(EXIT_MAKEROM_INVLDR);
  }

  /*
   * Open the output file. If we have to produce anything different than raw
   * binary we have to initially use a temporary file.
   */
  if (lp->outtype != OUT_BINARY && lp->outtype != OUT_BIOS) {
	outfile = opentemp(verbose < 3);
	if (outfile == -1)
		nbexit(-1);
  } else if ((outfile = open(lp->outname,
			O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0644)) < 0) {
		prnerr("unable to open output file %s", lp->outname);
		nbexit(EXIT_CREATE);
  }

  /*
   * Now copy the specified bootrom loader into the output file. If
   * it's a floppy loader, save the first sector of the loader, and
   * read the next sector which is then the first ROM sector. Also
   * set the offset to that first ROM sector within the output file
   * to later allow rewriting it.
   */
  firstofs = 0L;
  if (!dorom) {
	if (len != BLKSIZE) {
		prnerr("unexpected end of loader image %s", lp->name);
		nbexit(EXIT_MAKEROM_INVLDR);
	}
	(void)nbwrite(inbuf, len, outfile);
	firstofs = BLKSIZE;
	len = nbread(inbuf, BLKSIZE, infile);
  }

  /* Check for correct version */
  if (inbuf[ROMMAJOROFS] != (__u8)VER_MAJOR ||
      inbuf[ROMMINOROFS] != (__u8)VER_MINOR) {
	prnerr("invalid loader version number");
	nbexit(EXIT_MAKEROM_INVLDR);
  }

  /* Check that we really have a netboot loader */
  if (!bytecmp(NBROMID, &inbuf[ROMIDOFS], NBROMIDLEN)) {
	prnerr("file %s is not a netboot loader image", lp->name);
	nbexit(EXIT_MAKEROM_INVLDR);
  }

  /* Write the serial number into the output file */
  u16p = (__u16 *)(&inbuf[ROMSEROFS + 0]);
  assign(*u16p, htot(serno & 0xffff));
  u16p = (__u16 *)(&inbuf[ROMSEROFS + 2]);
  assign(*u16p, htot((serno >> 16) & 0xffff));

  /* Compute the ROM interrupt vector - always int 18h for floppy loader */
  romvector = ((dorom ?
		(lp->useint18 ? ROMVECT18 : ROMVECT19) :
		ROMVECT18) * 4) & 0xffff;
  u16p = (__u16 *)(&inbuf[ROMVECTOFS]);
  assign(*u16p, htot(romvector & 0xffff));

  /* Set the bootrom flags */
  bootflags = 0;
  if (lp->bootask) {
	bootflags |= ROMFLG_ASKNET;
	if (lp->asktime > 0) {
		if (lp->asktime < 2)
			bootflags |= (0x02 << ROMFLG_SHIFT_ASKTIME) &
							ROMFLG_ASKTIME;
		else
			/* The time is set in 2 second intervalls */
			bootflags |= ((lp->asktime / 2) <<
					(1 + ROMFLG_SHIFT_ASKTIME)) &
							ROMFLG_ASKTIME;
	}
  }
  if (lp->devnum > 0)
	bootflags |= (lp->devnum << ROMFLG_SHIFT_DEVNUM) & ROMFLG_DEVNUM;
  if (lp->nobbs)
	bootflags |= ROMFLG_NOBBS;
  u16p = (__u16 *)(&inbuf[ROMBOOTFLGOFS]);
  assign(*u16p, htot(bootflags & 0xffff));

  /* Write total number of paragraphs required for the bootrom */
  u16p = (__u16 *)(&inbuf[ROMTOTSIZEOFS]);
  assign(*u16p, htot(totsize & 0xffff));

  /* Determine offset to PCI data structure and set vendor/device ID */
  pci_ofs = get_word(*((__u16 *)(&inbuf[ROMPCIPTROFS])));
  if (pci_ofs > 0 && pci_ofs < (sizeof(inbuf) - sizeof(struct pcihdr))) {
	struct pcihdr *pci_ptr = (struct pcihdr *)(&inbuf[pci_ofs]);
	unsigned long pci_len;

	pci_len = get_word(pci_ptr->size);
	if (pci_ofs + pci_len > len ||
	    !bytecmp(PCI_SIGNATURE, pci_ptr->sig, sizeof(pci_ptr->sig))) {
		prnerr("invalid PCI header");
		nbexit(EXIT_MAKEROM_INVLDR);
	}
	if (bp->bus_type == BUSTYPE_PCI) {
		assign(pci_ptr->vendorid, htot(bp->pci_vendid & 0xffff));
		assign(pci_ptr->deviceid, htot(bp->pci_devid & 0xffff));
	}
  } else {
	/* The PCI header has to exist, otherwise the loader is invalid */
	prnerr("missing PCI header");
	nbexit(EXIT_MAKEROM_INVLDR);
  }

  /* If we don't have a PCI card, set offset to PCI structure to zero */
  if (bp->bus_type != BUSTYPE_PCI) {
	inbuf[ROMPCIPTROFS + 0] = (__u8)0;
	inbuf[ROMPCIPTROFS + 1] = (__u8)0;
  }

  /* Compute the PnP expansion header checksum */
  pnp_ofs = get_word(*((__u16 *)(&inbuf[ROMPNPPTROFS])));
  if (pnp_ofs > 0 && pnp_ofs < (sizeof(inbuf) - sizeof(struct pnphdr))) {
	struct pnphdr *pnp_ptr = (struct pnphdr *)(&inbuf[pnp_ofs]);
	int pnp_chksum = 0;
	unsigned long pnp_len;

	/* Check if PnP header is correct */
	pnp_len = (unsigned long)(pnp_ptr->size * 16);
	if (pnp_ofs + pnp_len > len ||
	    !bytecmp(PNP_SIGNATURE, pnp_ptr->sig, sizeof(pnp_ptr->sig))) {
		prnerr("invalid PnP header");
		nbexit(EXIT_MAKEROM_INVLDR);
	}
	/* Generate PnP vendor and device ID */
	if (bp->bus_type != BUSTYPE_PCI && bp->pnp_devid != NULL)
		setpnpid(pnp_ptr->devid, bp->pnp_devid);
	/* Compute new checksum of PnP header */
	for (i = 0; i < pnp_len; i++) {
		pnp_chksum += inbuf[pnp_ofs + i];
		pnp_chksum &= 0xff;
	}
	pnp_ptr->chksum = (__u8)((0 - pnp_chksum) & 0xff);
  } else {
	/* The PnP header has to exist in any case */
	prnerr("missing PnP header");
	nbexit(EXIT_MAKEROM_INVLDR);
  }

  /* Save the first ROM loader sector for later reference. */
  memcpy(firstbuf, inbuf, sizeof(inbuf));
  firstlen = len;

  /* Always have to set the checksum to zero before writing the ROM code */
  write_chksum = 0;

  /* Write the loader image into the output file */
  writecnt = 0;
  while (TRUE) {
	writecnt += nbwrite(inbuf, len, outfile);
	if ((len = nbread(inbuf, BLKSIZE, infile)) == 0)
		break;
  }
  close(infile);

  /* The kernel image has to start at a paragraph boundary */
  if ((writecnt & 0x0f) != 0) {
	memzero(inbuf, 16);
	writecnt += nbwrite(inbuf, 16 - (writecnt & 0x0f), outfile);
  }
  ldsize = writecnt;

  /* Next compress the kernel image and copy it into the output file */
  romlength = freeze(kernfile, outfile);
  writecnt += romlength;
  if (writecnt > MAXROMSIZE) {
	prnerr("bootrom code cannot exceed %lu kB", MAXROMSIZE / 1024L);
	nbexit(EXIT_MAKEROM_SIZE);
  }

  /*
   * If we got a real ROM here, determine the next available ROM size and
   * fill the output file up to that amount with FF bytes. The last byte
   * has to remain for the checksum. Then update the size of the physical
   * ROM at the beginning of the output file, and write the size of the
   * bootrom kernel image.
   */

  /* Determine logical ROM size */
  romsize = roundup((writecnt + 3), 8192L);

  /* Determine required physical ROM size. */
  if (lp->outsize > 0) {
	unsigned long ul = (unsigned long)(lp->outsize) * 1024L;

	if (ul < romsize || ul > MAXROMSIZE) {
		prnerr("invalid ROM output size %d kb given", lp->outsize);
		nbexit(EXIT_MAKEROM_SIZE);
	}
	outsize = ul; 
  } else if (lp->outtype == OUT_BIOS) {
	/*
	 * For a BIOS flash file, we don't have to fill up the output file
	 * to the size of a full physical ROM device.
	 */
	outsize = romsize;
  } else for (outsize = MINROMSIZE; outsize < romsize; )
	outsize <<= 1;

  /* Compute the missing size values and the final checksum */
  romsizchar = (romsize >> 9) & 0xff;		/* Divide by 512 */
  write_chksum += romsizchar;
  if (pci_ofs > 0)
	write_chksum += romsizchar;
  for (i = 0; i < 4; i++)
	write_chksum += (romlength >> (8 * i)) & 0xff;

  /*
   * Copy 0xFF byte buffer and checksum to output file (only for real ROM).
   * For some network cards (notably 3Com), the last two bytes of the ROM
   * code have to be 0x80.
   */
  if (dorom) {
	memset(inbuf, 0xff, sizeof(inbuf));
	while (TRUE) {
		/* Check if this is the last block */
		if (outsize - writecnt <= BLKSIZE + 3) {
			len = (int)(outsize - writecnt);
			assert(len >= 3);
			writecnt += nbwrite(inbuf, len - 3, outfile);
			write_chksum += 0x80 * 2;
			inbuf[0] = (__u8)((0 - (write_chksum & 0xff)) & 0xff);
			inbuf[1] = 0x80;
			inbuf[2] = 0x80;
			writecnt += nbwrite(inbuf, 3, outfile);
			break;
		}
		writecnt += nbwrite((unsigned char *)inbuf, BLKSIZE, outfile);
	}
  }

  /* Put the physical ROM length into the output file */
  firstbuf[ROMLENOFS] = (__u8)romsizchar;

  /* Put the image length into the PCI data structure */
  if (pci_ofs > 0) {
	struct pcihdr *pci_ptr = (struct pcihdr *)(&firstbuf[pci_ofs]);

	assign(pci_ptr->romlength, (__u16)(romsizchar & 0x00ff));
  }

  /* Put the length of the compressed kernel image into the output file */
  u16p = (__u16 *)(&firstbuf[ROMCOMPSIZEOFS + 0]);
  assign(*u16p, htot(romlength & 0xffff));
  u16p = (__u16 *)(&firstbuf[ROMCOMPSIZEOFS + 2]);
  assign(*u16p, htot((romlength >> 16) & 0xffff));

  /* Finally rewrite the first sector of the loader */
  if (lseek(outfile, firstofs, 0) < 0) {
	if (lp->outtype != OUT_BINARY && lp->outtype != OUT_BIOS)
		prnerr("unable to seek in temporary file");
	else
		prnerr("unable to seek in %s", lp->outname);
	nbexit(EXIT_SEEK);
  }
  (void)nbwrite(firstbuf, firstlen, outfile);

  /* Rewind to beginning of kernel file for another pass 2 */
  if (lseek(kernfile, 0L, 0) < 0) {
	prnerr("unable to seek to beginning of kernel file");
	nbexit(EXIT_SEEK);
  }

  /* Generate the final output file format */
  if (lp->outtype != OUT_BINARY && lp->outtype != OUT_BIOS) {
	if (lseek(outfile, 0L, 0) < 0) {
		prnerr("unable to seek to beginning of temporary file");
		nbexit(EXIT_SEEK);
	}
	if (lp->outtype == OUT_FLASH)
		makeflash(lp->outname, outfile, ldsize);
	else
		makehex(lp->outname, outfile, lp->outtype);
	closetemp(outfile);
  } else
	close(outfile);
}



/*
 * Generate bootrom output files by successively calling both passes
 */
void dopasses __F((bp), const struct bootdef *bp)
{
  unsigned long totsize;
  int i, tmpfile;

  /* Compute the bootrom serial number */
  if (!debug)
	serno = (unsigned long)(((time(NULL) << 4) & 0x7ffffc00L) |
				((VER_MAJOR * 100 + VER_MINOR) & 0x000003ffL));

  /* Generate the output files */
  if ((tmpfile = opentemp(verbose < 3)) == -1)
	nbexit(-1);
  totsize = pass1(bp, tmpfile);
  for (i = 0; i < bp->loadernum; i++)
	pass2(bp, totsize, tmpfile, i);
  closetemp(tmpfile);
}

