/*
 * makeflash.c  -  Generate boot image file for programming a FlashCard
 *
 * 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: makeflash.c,v 1.19 2007/01/06 18:31:14 gkminix Exp $
 */

#define NEED_BINARY 1
#include <common.h>
#include <nblib.h>
#include <system/nbloader.h>
#include "makerom.h"
#include "makeflash.h"
#include "doconfig.h"



/*
 * Variables private to this module
 */
static char *outname;			/* Name of output file */
static int outfile;			/* File handle for output file */
static int cur_rec_num = -1;		/* Number of current load record */
static struct load_header header;	/* Load header */
static struct load_record *cur_rec;	/* Pointer to current load record */



/*
 * Check if the device for a certain network driver is supported
 * by the Flash EPROM programmer, except FlashCard. For this we
 * scan through the list of interface definitions contained in the
 * Flash EPROM programmer binary.
 */
int checkflash __F((bp), const struct bootdef *bp)
{
  __u8 *cp;
  int devtype;
  int vendid, devid;
  unsigned int textsize;
  unsigned int tableofs;
  unsigned int ifofs;
  unsigned int idofs;
  struct istruct *ip;
  struct nbld_header *hp = (struct nbld_header *)&(flash_data[NBLD_OFFSET]);

  textsize = roundup(get_word(hp->textsize), (unsigned short)16);
  tableofs = get_word(hp->userptr);
  while ((ifofs = get_word(*((__u16 *)&(flash_data[tableofs + textsize])))) != 0) {
	ip = (struct istruct *)&(flash_data[ifofs + textsize]);
	devtype = get_word(ip->devtype);
	idofs = get_word(ip->devlist);
	if (devtype == IDEVTYPE_PCI &&
	    bp->bus_type == BUSTYPE_PCI &&
	    idofs != 0) {
		vendid = get_word(*((__u16 *)&(flash_data[idofs + textsize])));
		idofs += 2;
		while ((devid = get_word(*((__u16 *)&(flash_data[idofs + textsize])))) != 0) {
			if (vendid == bp->pci_vendid && devid == bp->pci_devid)
				return(TRUE);
			idofs += 2;
		}
	} else if (devtype == IDEVTYPE_ISA &&
		   bp->bus_type != BUSTYPE_PCI &&
	           bp->pnp_devid != NULL &&
		   idofs != 0) {
		cp = &(flash_data[idofs + textsize]);
		while (*cp != 0) {
			if (bytecmp(bp->pnp_devid, cp, PNPIDLEN))
				return(TRUE);
			cp += PNPIDLEN;
		}
	}
	tableofs += 2;
  }
  return(FALSE);
}



/*
 * Read one buffer from the temporary rom image file
 */
static int readbuf __F((buf, romimage), __u8 *buf AND int romimage)
{
  size_t i;

  if ((i = nbread(buf, DEFBUFSIZE, romimage)) == 0)
	return(FALSE);
  else if (i != DEFBUFSIZE) {
	/* The ROM image file has to have a multiple of DEFBUFSIZE bytes */
	prnerr("unexpected end of temporary rom image file");
	nbexit(EXIT_MAKEROM_ROMEOF);
  }
  return(TRUE);
}



/*
 * Write a buffer into the output file and update the load record
 */
static void putrec __F((recnum, src, size),
				int recnum AND
				__u8 *src AND
				size_t size)
{
  unsigned long l;

  assert(cur_rec_num == recnum);
  (void)nbwrite(src, size, outfile);
  l = get_long(cur_rec->ilength) + size;
  assign(cur_rec->ilength.low, htot(low_word(l)));
  assign(cur_rec->ilength.high, htot(high_word(l)));
  l = get_long(cur_rec->mlength) + size;
  assign(cur_rec->mlength.low, htot(low_word(l)));
  assign(cur_rec->mlength.high, htot(high_word(l)));
}



/*
 * Initialize a load record
 */
static void initrec __F((recnum, segment, flags, vendor_size),
				int recnum AND
				unsigned long segment AND
				int flags AND
				int vendor_size)
{
  cur_rec_num++;
  assert(cur_rec_num == recnum);
  if (cur_rec_num > 0)
	cur_rec = (struct load_record *)((__u8 *)cur_rec +
					((cur_rec->rlength << 2) & 0x3c) +
					((cur_rec->rlength >> 2) & 0x3c));
  cur_rec->rlength      = (((sizeof(struct load_record) -
#ifdef USE_VENDOR
		             sizeof(union vendor_data)
#else
		             0
#endif
		                                      ) >> 2) |
                           (((vendor_size + 3) & 0x3c) << 2)) & 0xff;
  cur_rec->rtag1   = (recnum + LOADER_TAG) & 0xff;
  cur_rec->rflags  = flags & 0xff;
  assign(cur_rec->address.low, htot(low_word(segment << 4)));
  assign(cur_rec->address.high, htot(high_word(segment << 4)));
}



/*
 * Process Flash EPROM programmer image
 */
static void do_programmer __F_NOARGS
{
  unsigned long tsize, dsize, ssize, msize;
  size_t lsize;
  __u8 *dataptr;
  struct nbld_header *hp;

  /*
   * Determine the flash loader image size and round it up to DEFBUFSIZE
   */
  lsize = roundup(flash_data_size, DEFBUFSIZE);
  dataptr = (__u8 *)nbmalloc(lsize);
  memcpy(dataptr, flash_data, flash_data_size);

  /*
   * Determine the image memory size.
   */
  hp = (struct nbld_header *)&(dataptr[NBLD_OFFSET]);
  tsize = get_word(hp->textsize);
  dsize = get_word(hp->datasize);
  ssize = get_word(hp->heapsize) + get_word(hp->stacksize);
  msize = roundup(tsize, 16) + roundup((dsize + ssize), 16) + 1;

  /*
   * Check that the size values are within range. We can use assert()
   * here because an error should never happen. The images are stored
   * within this program and can never change.
   */
  assert(lsize <= PROGRAMLSIZE);
  assert(msize <= PROGRAMMSIZE && msize >= lsize);

  /*
   * Copy the image into the output file and set the load record
   * according to the sizes determined above.
   */
  initrec(PROGRAMNUM, PROGRAMSEG, 0, 0);
  putrec(PROGRAMNUM, dataptr, lsize);
  assign(cur_rec->mlength.low, htot(low_word(msize)));
  assign(cur_rec->mlength.high, htot(high_word(msize)));

  /*
   * Set the image entry address correctly.
   */
  assign(header.execute.segment, htot(PROGRAMSEG));
  assign(header.execute.offset, getval(hp->nbsetup));
  free(dataptr);
}



/*
 * Dump the load record information to stderr
 */
static void dump_header __F((lh), const struct load_header *lh)
{
  static const char *s_tags[] = { /* PROGRAMNUM */  "flash EPROM programmer",
				  /* ROMIMAGENUM */ "boot rom image"};
  static const char *s_flags[]= { "absolute address",
				  "after previous segment",
				  "at end of memory",
				  "before previous segment"};
  struct load_record *lr;
  char *vendstr = NULL;
  int i, num = 0;

  if (logfd(LOGLEVEL_INFO) == NULL)
	return;

  i = (lh->hlength >> 2) & 0x3c;
  vendstr = (char *)nbmalloc(i + 2);
  while (i > 0) {
	i--;
	vendstr[i] = lh->dummy[i];
  }

  prnlog(LOGLEVEL_INFO,
	  "\nLoad record information:\n"
	  "  Magic number:     0x%08lX\n"
	  "  Length of header: %d bytes\n"
	  "  Flags:            0x%08lX\n"
	  "  Location address: %04X:%04X\n"
	  "  Execute address:  %04X:%04X\n"
	  "  Vendor data:      %s\n",
	  get_long(lh->magic),
	  (lh->hlength << 2) & 0x3c,
	  (unsigned long)lh->hflags1 +
		((unsigned long)lh->hflags2 << 8) +
		((unsigned long)lh->hflags3 << 16),
	  get_word(lh->locn.segment), get_word(lh->locn.offset),
	  get_word(lh->execute.segment), get_word(lh->execute.offset),
	  vendstr);

  i  = ((lh->hlength >> 2) & 0x3c) + ((lh->hlength << 2) & 0x3c);
  lr = (struct load_record *)&(((__u8 *)lh)[i]);

  while (TRUE) {
	prnlog(LOGLEVEL_INFO,
	    "\nRecord #%d:\n"
	    "  Length of header: %d bytes (standard) + %d bytes (vendor)\n"
	    "  Vendor tag:       0x%02X (%s)\n"
	    "  Reserved flags:   0x%02X\n"
	    "  Flags:            0x%02X (%s%s)\n"
	    "  Load address:     0x%08lX%s\n"
	    "  Image length:     0x%08lX bytes\n"
	    "  Memory length:    0x%08lX bytes\n",
	    ++num,
	    (lr->rlength << 2) & 0x3c,
	    (lr->rlength >> 2) & 0x3c,
	    (int)lr->rtag1,
	    (lr->rtag1 < (__u8)LOADER_TAG) ||
		(lr->rtag1 - (__u8)LOADER_TAG >= (__u8)NUM_RECORDS) ?
		"unknown" : s_tags[(unsigned)(lr->rtag1 - (__u8)LOADER_TAG)],
	    (int)lr->rtag2,
	    (int)lr->rflags, s_flags[(unsigned)(lr->rflags & 0x03)],
	    lr->rflags & BOOT_FLAG_EOF ? ", last record" : "",
	    get_long(lr->address),
	    get_long(lr->address) >= 0x100000L &&
	    (lr->rflags & 0x03) == 0? " (high memory)" : "",
	    get_long(lr->ilength),
	    get_long(lr->mlength));

	if (lr->rflags & BOOT_FLAG_EOF)
		break;

	i  = ((lr->rlength >> 2) & 0x3c) + ((lr->rlength << 2) & 0x3c);
	lr = (struct load_record *)&(((__u8 *)lr)[i]);
  }
  prnlog(LOGLEVEL_INFO, "\n");
  free(vendstr);
}



/*
 * Routine to generate a boot image file which can program a FlashCard with
 * a new boot rom image.
 */
void makeflash __F((fname, romimage, ldsize),
				char *fname AND
				int romimage AND
				unsigned long ldsize)
{
  __u8 copyrec_buf[DEFBUFSIZE];

  /* Open the input and output files */
  outname = fname;
  if ((outfile = open(outname, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY,
								0644)) < 0) {
	prnerr("unable to create %s", outname);
	nbexit(EXIT_CREATE);
  }

  /* Initialize the boot header */
  memzero(&header, sizeof(header));
  assign(header.magic.low,    htot(low_word(NBHEADER_MAGIC)));
  assign(header.magic.high,   htot(high_word(NBHEADER_MAGIC)));
  assign(header.locn.segment, htot(HEADERSEG));
  assign(header.locn.offset,  htot(0));
  assign(header.bootsig,      htot(NBHEADER_SIG));
  header.hlength = ((__u8)(((int)(header.dummy - (__u8 *)&header) /
				sizeof(__u32))) & 0x0f) |
				((__u8)((VENDOR_SIZE << 4) & 0xf0));
  bytecpy(VENDOR_MAGIC, header.dummy, VENDOR_SIZE * sizeof(__u32));
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);

  /* Initialize pointer to first load record */
  cur_rec = (struct load_record *)&(header.dummy[VENDOR_SIZE * sizeof(__u32)]);

  /* Process the Flash EPROM programmer record */
  do_programmer();

  /* Process the boot rom image */
  initrec(ROMIMAGENUM, ROMIMAGESEG, BOOT_FLAG_EOF, sizeof(cur_rec->vendor_data));
  assign(cur_rec->vendor_data.ldsize.low, htot(low_word(ldsize)));
  assign(cur_rec->vendor_data.ldsize.high, htot(high_word(ldsize)));
  assign(cur_rec->mlength.low, htot(low_word(ROMIMAGEMADD)));
  assign(cur_rec->mlength.high, htot(high_word(ROMIMAGEMADD)));
  while (readbuf(copyrec_buf, romimage))
	putrec(ROMIMAGENUM, copyrec_buf, DEFBUFSIZE);
  if (get_long(cur_rec->mlength) > ROMIMAGEMAX) {
	prnerr("flash rom image size too large");
	nbexit(EXIT_MAKEROM_IMAGESIZE);
  }

  /* After writing out all this stuff, finally update the boot header */
  if (lseek(outfile, 0, SEEK_SET) != 0) {
	prnerr("unable to seek to beginning of %s", outname);
	nbexit(EXIT_SEEK);
  }
  (void)nbwrite((__u8 *)&header, sizeof(header), outfile);
  close(outfile);

  /* If user asked for detailed output, parse the header and output all of */
  /* the load record information */
  dump_header(&header);
}

