/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   Scan the system for BIOS extensions (e.g. VGA bios)
 * 
 * Maintainers: 
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/bios.h"
#include "base/memory.h"
#include "base/int.h"
#include "base/bios_entry.h"

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>

#define b_printf g_printf

#if 0
static Bit8u sys_model_type, sys_submodel_type;
static Bit8u bios_revision;
static Bit8u bios_date[] = "01/01/95";
#endif

/*
 * Write-protect our precious BIOS
 */

static inline void bios_protect(void)
{
  mprotect((void *)0xfc000, 0x1000, PROT_READ|PROT_EXEC);
  mprotect((void *)0xfe000, 0x2000, PROT_READ|PROT_EXEC);
}

/*
 * Allow those horrible DOS programmers to modify our BIOS
 */
static inline void bios_unprotect(void)
{
  mprotect((void *)0xfc000, 0x1000, PROT_WRITE|PROT_READ|PROT_EXEC);
  mprotect((void *)0xfe000, 0x2000, PROT_WRITE|PROT_READ|PROT_EXEC);
}

/*
 * Scan the specified segment to see if it contains a valid BIOS and
 * return the size (0 if the segment is invalid)
 */

static Bit32u bios_check_seg(Bit16u seg)
{
  Bit32u   seg_size = 0;
  Bit8u    buf64k[0x10000];
  Bit32u   i;
  Bit8u    crc;

  if (mem_fd < 0) {
    b_printf("BIOS: can not scan segment because mem_fd == -1\n");
    return 0;
  }

  b_printf("BIOS: scanning segment 0x%04x for valid BIOS\n", seg);

  DOS_SYSCALL(lseek(mem_fd, seg << 4, SEEK_SET));
  RPT_SYSCALL(read(mem_fd, buf64k, 3));

  if (buf64k[0] == 0x55 && buf64k[1] == 0xaa) {
    seg_size = (Bit32u)buf64k[2] << 9;
    RPT_SYSCALL(read(mem_fd, &buf64k[3], seg_size - 3));

    for (crc = 0, i = 0; i < seg_size; i++)
      crc += buf64k[i];

    b_printf("BIOS: scan, crc = 0x%02x for 0x%lx bytes\n", (int)crc, seg_size);
    if (crc != 0)
      seg_size = 0;
  }
  else
    b_printf("BIOS: scan, segment is not a valid BIOS segment\n");

  return seg_size;
}

/*
 * load <msize> bytes of file <name> starting at offset <foffset>
 * into memory at <mstart>
 */
static int load_file(const char *name, int foffset, char *mstart, int msize)
{
  int fd;

  if (strcmp(name, "/dev/kmem") == 0) {
    b_printf("BIOS: load_file, loading information from /dev/kmem\n");
    fd = mem_fd;
  }
  else
    fd = open(name, O_RDONLY);

  DOS_SYSCALL(lseek(fd, foffset, SEEK_SET));
  RPT_SYSCALL(read(fd, mstart, msize));

  if (strcmp(name, "/dev/kmem") != 0)
    close(fd);
  return 0;
}

/*
 * Map our custom BIOS into main memory
 */

static inline void bios_map_custom(void)
{
  static Boolean first_time = TRUE;
  Bit8u *bios_seg;

  bios_unprotect();

  /* Fetch our custom BIOS */
  bios_seg = (Bit8u *)(BIOSSEG << 4);
  MEMCPY_2DOS(bios_seg, bios_f000, (Bit32u)bios_f000_end - (Bit32u)bios_f000);

  /* Fetch your PC's graphics character table */
  if (i_am_root)
    load_file("/dev/kmem", GFX_CHARS, (char *) GFX_CHARS, GFXCHAR_SIZE);

#if 0
  {
    Bit8u bios_config_table[52];
    Bit8u sys_model_id, sys_submodel_id;

    sys_model_id    = 0xfc;
    sys_submodel_id = 0x01;

    memset(bios_config_table, sizeof(bios_config_table), 0);

    bios_config_table[0] = 0x08; /* 8 bytes following this word */
    bios_config_table[1] = 0x00;
    bios_config_table[2] = sys_model_id;
    bios_config_table[3] = sys_submodel_id;
    bios_config_table[4] = 0x00; /* BIOS revision number */
    bios_config_table[5] = (0 << 7) | /* 1=DMA channel 3 used by hard disk */
                           (1 << 6) | /* 1=2 interrupt controllers present */
			   (1 << 5) | /* 1=RTC present */
			   (1 << 4) | /* 1=BIOS calls int 15h, 4Fh every key */
			   (0 << 3) | /* 1=wait for extern event supported */
			   (1 << 2) | /* 1=extended BIOS data area used */
			   (0 << 1);  /* 1=AT bus */
    bios_config_table[6] = 0x00; /* 1=int16h funct 9 supported */
    bios_config_table[7] = 0x00;
    bios_config_table[8] = 0x00;
    bios_config_table[9] = 0x00;
    strncpy(&bios_config_table[10], BIOS_VERSION_STR,
	    sizeof(bios_config_table) - 10);

    MEMCPY_2DOS(0xffffe, bios_config_table, sizeof(bios_config_table));
    MEMCPY_2DOS(0xfffff, &bios_date, strlen(bios_date));

    WRITE_BYTE(0xfffff, sys_model_id);
    WRITE_BYTE(0xfffff, sys_submodel_id);
  }
#endif

  if (first_time) {
    memcheck_addtype('r', "DOSEMU custom BIOS");
    memcheck_reserve('r', 0xF0000, 0x10000);              /* dosemu bios */
    first_time = FALSE;
  }

  bios_protect();
}

/*
 * Map the Video BIOS into memory.
 * This will attempt to auto-detect the video BIOS segment...
 */

static inline void bios_map_video(void)
{
  static Boolean first_time = TRUE;
  Bit16u vbios_seg;
  Bit32u vbios_ptr, vbios_size;

  if (!config.mapped_bios || !i_am_root)
    return;

  if (config.vbios_file) {
    b_printf("BIOS: loading VBIOS %s into mem at 0x%X (0x%lX bytes)\n",
	     config.vbios_file, VBIOS_START, VBIOS_SIZE);
    load_file(config.vbios_file, 0, (char *) VBIOS_START, VBIOS_SIZE);

    memcheck_addtype('V', "Video BIOS");
    memcheck_reserve('V', VBIOS_START, VBIOS_SIZE);
    return;
  }

  if (config.vbios_seg) {
    vbios_seg  = config.vbios_seg;
    vbios_size = config.vbios_size;

    b_printf("BIOS: will load video bios from segment 0x%04x\n", vbios_seg);
    if (bios_check_seg(vbios_seg) == 0) {
      b_printf("BIOS: WARNING!!! this does not appear to be valid BIOS\n");
    }
    if (!vbios_size) {
      vbios_size = bios_check_seg(vbios_seg);
      if (!vbios_size) {
	b_printf("BIOS: WARNING!!! this does not appear to be valid BIOS\n");
	switch (vbios_seg) {
	  case 0xe000 : vbios_size = 0x10000; break;
	  case 0xc000 : vbios_size = 0x8000; break;
	  default: vbios_size = 0x10000; break;
	}
      }
    }
  }
  else {
    b_printf("BIOS: autoscanning for video bios segment...\n");
    vbios_seg  = 0xc000;
    vbios_size = bios_check_seg(vbios_seg);
    if (!vbios_size) {
      vbios_seg  = 0xe000;
      vbios_size = bios_check_seg(vbios_seg);

      if (!vbios_size) {
	b_printf("BIOS: sorry -- unable to locate your video bios segment\n");
	return;
      }
    }
    b_printf("BIOS: located %ldK of video bios at 0x%04x:0x0000\n",
	     vbios_size/1024, vbios_seg);
  }

  vbios_ptr = (Bit32u)vbios_seg << 4;

  load_file("/dev/kmem", vbios_ptr, (void *)vbios_ptr, vbios_size);

  if (first_time) {
    memcheck_addtype('V', "Video BIOS");
    memcheck_reserve('V', vbios_ptr, vbios_size);
    first_time = FALSE;
  }
}

/*
 * Map the BIOS into dosemu memory
 */

static void bios_map(void)
{
  bios_map_custom();
  bios_map_video();
}

void bios_write(Bit8u *addr)
{
  Bit32u saved;
  b_printf("BIOS: Some nut is trying to overwrite bios address %p!\n", addr);
  saved = *((Bit32u *)addr);
  bios_unprotect();
  cpu_one_instr();
  *((Bit32u *)addr) = saved;
  bios_protect();
}

void bios_init(void)
{
  bios_map();
  bios_int_init();
  bios_int_reset();
  bios_mem_init();
  bios_mem_reset();
}

void bios_reset(void)
{
  bios_int_reset();
  bios_mem_reset();
}

void bios_term(void)
{
}
