/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   Generic VGA driver for DOSEMU with CHIPSET driver support.
 * 
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/port.h"
#include "base/memory.h"
#include "base/misc.h"
#include "base/bios.h"
#include "base/detach.h"
#include "env/video.h"
#include "env/libvga.h"
#include "console.h"
#include "vgaregs.h"

#include <sys/mman.h>

/*
 * These must correspond EXACTLY to the values defined in EMU_VIDEO.H
 */

static DriverSpecs *drivers[] = {
  NULL,    /* chipset undefined */
  &ega_driverspecs,
  &vga_driverspecs,
#ifdef CONFIG_ENV_VGA_ARK
  &ark_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_ATI
  &ati_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_CIRRUS
  &cirrus_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_ET3000
  &et3000_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_ET4000
  &et4000_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_GVGA6400
  &gvga6400_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_MACH32
  NULL,             /* I lied... */
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_OAK
  &oak_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_PARADISE
  &paradise_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_S3
  &s3_driverspecs,
#else
  NULL,
#endif
#ifdef CONFIG_ENV_VGA_TRIDENT
  &trident_driverspecs,
#else
  NULL,
#endif
};

static DriverSpecs *chipset = NULL;

Bit16u vregs[60] =
{
/* BIOS mode 0x12 */
  0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0B, 0x3E, 0x00, 0x40, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xEA, 0x8C, 0xDF, 0x28, 0x00, 0xE7, 0x04, 0xE3,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B,
  0x3C, 0x3D, 0x3E, 0x3F, 0x01, 0x00, 0x0F, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0xFF,
  0x03, 0x21, 0x0F, 0x00, 0x06,
  0xE3
};

vga_info_struct  dosemu_regs;  /* the regs for our vc */
vga_info_struct  linux_regs;   /* the regs for other vc's */
Boolean          isr_read;
Bit8u            att_d_index;
Bit16u           CRT_I, CRT_D, IS1_R, FCR_W;

static caddr_t    vmem_mapped;  /* we're gonna map the video memory
				   into our address space twice. */
static Boolean    vga_inited = FALSE;

#define GRAPH_PROT     (PROT_READ | PROT_WRITE)

#define chipset_inb                    chipset->ext_inb
#define chipset_outb                   chipset->ext_outb
#define chipset_saveregs               chipset->saveregs
#define chipset_restoreregs            chipset->restoreregs
#define chipset_setpage                chipset->setpage
#define chipset_get_perm               chipset->get_perm
#define chipset_release_perm           chipset->release_perm

static void vga_dump_regs(void)
{
  int i;

  /* save VGA registers */
  v_printf("CRT=");
  for (i = 0; i < CRT_C; i++) {
    port_out(i, CRT_I);
    v_printf("%02x ", (u_char) port_in(CRT_D));
  }
  v_printf("\n");
  v_printf("ATT=");
  for (i = 0; i < ATT_C; i++) {
    port_in(IS1_R);
    port_out(i, ATT_IW);
    v_printf("%02x ", (u_char) port_in(ATT_R));
  }
  v_printf("\n");
  v_printf("GRA=");
  for (i = 0; i < GRA_C; i++) {
    port_out(i, GRA_I);
    v_printf("%02x ", (u_char) port_in(GRA_D));
  }
  v_printf("\n");
  v_printf("SEQ=");
  for (i = 0; i < SEQ_C; i++) {
    port_out(i, SEQ_I);
    v_printf("%02x ", (u_char) port_in(SEQ_D));
  }
  v_printf("\n");
  v_printf("MIS=0x%02x\n", (u_char) port_in(MIS_R));
}

static void vga_dump_state(const vga_info_struct *regs)
{
  u_short i;

  v_printf ("/* BIOS mode 0x%02X */\n", regs->video_mode);
  v_printf ("static char regs[60] = {\n  ");
  for (i = 0; i < 12; i++)
    v_printf ("0x%02X,", regs->regs[CRT + i]);
  v_printf ("\n  ");
  for (i = 12; i < CRT_C; i++)
    v_printf ("0x%02X,", regs->regs[CRT + i]);
  v_printf ("\n  ");
  for (i = 0; i < 12; i++)
    v_printf ("0x%02X,", regs->regs[ATT + i]);
  v_printf ("\n  ");
  for (i = 12; i < ATT_C; i++)
    v_printf ("0x%02X,", regs->regs[ATT + i]);
  v_printf ("\n  ");
  for (i = 0; i < GRA_C; i++)
    v_printf ("0x%02X,", regs->regs[GRA + i]);
  v_printf ("\n  ");
  for (i = 0; i < SEQ_C; i++)
    v_printf ("0x%02X,", regs->regs[SEQ + i]);
  v_printf ("\n  ");
  v_printf ("0x%02X", regs->regs[MIS]);
  v_printf ("\n};\n");
  if (config.chipset != CHIPSET_VGA) {
    v_printf ("Extended Regs/if applicable:\n");
    for (i = 0; i < MAX_X_REGS; i++)
      v_printf ("0x%02X,", regs->xregs[i]);
    v_printf ("0x%02X\n", regs->xregs[MAX_X_REGS]);
    v_printf ("Extended 16 bit Regs/if applicable:\n");
    for (i = 0; i < MAX_X16_REGS; i++)
      v_printf ("0x%04X,", regs->xregs16[i]);
    v_printf ("0x%04X\n", regs->xregs16[MAX_X16_REGS]);
  }
}

/*
 * If own_console is TRUE, we are at our home console and own the vga chip
 * and memory -- therefore we can just write the registers.  HOWEVER, due
 * to the way Linux works, if we ever reach this procedure it's because
 * the port I/O was disallowed (otherwise the I/O just goes through).  In
 * that case we're going to cause a protection violation here due to the
 * lack of permissions.
 *
 * If own_console is FALSE, we are not at our home console (we no longer
 * own the vga chip and its registers.)  We will store the values away
 * and restore them when the user switches back.  Note that this is very
 * unsatisfactory because the video memory will probably not be properly
 * handled when the user is not at the DOSEMU home console and is running
 * a graphics program.  Moral:  Don't play high-res games in a DOSEMU
 * console, switch away, and expect things to keep working well.
 */

static Bit8u vga_port_inb(Bit32u port)
{
  if (own_console) {
    return port_safe_inb(port);
  }
  else {
    switch (port & 0x3ff) {
    case CRT_IC:
    case CRT_IM:
      v_printf("VIDEO: Read Index CRTC 0x%02x\n", dosemu_regs.regs[CRTI]);
      return dosemu_regs.regs[CRTI];

    case CRT_DC:
    case CRT_DM:
      if (dosemu_regs.regs[CRTI] <= 0x18) {
	v_printf("VIDEO: Read Data at CRTC Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[CRTI],
		 dosemu_regs.regs[CRT+dosemu_regs.regs[CRTI]]);
	  return dosemu_regs.regs[CRT + dosemu_regs.regs[CRTI]];
      }
      break;

    case GRA_I:
      v_printf("VIDEO: Read Index GRAI 0x%02x\n", GRAI);
      return dosemu_regs.regs[GRAI];

    case GRA_D:
      if (dosemu_regs.regs[GRAI] < 0x0a) {
	v_printf("VIDEO: Read Data at GRA Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[GRAI],
		 dosemu_regs.regs[GRA + dosemu_regs.regs[GRAI]]);
	  return dosemu_regs.regs[GRA + dosemu_regs.regs[GRAI]];
      }
      break;

    case SEQ_I:
      v_printf("VIDEO: Read Index SEQI 0x%02x\n", SEQI);
      return dosemu_regs.regs[SEQI];

    case SEQ_D:
      if (dosemu_regs.regs[SEQI] < 0x07) {
	v_printf("VIDEO: Read Data at SEQ Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[SEQI],
		 dosemu_regs.regs[SEQ + dosemu_regs.regs[SEQI]]);
	  return dosemu_regs.regs[SEQ + dosemu_regs.regs[SEQI]];
      }
      break;

    case ATT_IW:
      v_printf("VIDEO: Read Index ATTIW 0x%02x\n", att_d_index);
      return att_d_index;

    case ATT_R:
      if (att_d_index <= 20) {
	v_printf("VIDEO: Read Index ATTR 0x%02x\n",
		  dosemu_regs.regs[ATT + att_d_index]);
	return dosemu_regs.regs[ATT + att_d_index];
      }
      break;

    case IS1_RC:
    case IS1_RM:
      v_printf("VIDEO: Read ISR1_R 0x%02x\n", dosemu_regs.regs[ISR1]);
      isr_read = 1;		/* Next ATT write will be to the index */
      return dosemu_regs.regs[ISR1];

    case MIS_R:
      v_printf("VIDEO: Read MIS_R 0x%02x\n", dosemu_regs.regs[MIS]);
      return dosemu_regs.regs[MIS];

    case IS0_R:
      v_printf("VIDEO: Read ISR0_R 0x%02x\n", dosemu_regs.regs[ISR0]);
      return dosemu_regs.regs[ISR0];

    case PEL_IW:
      v_printf("VIDEO: Read PELIW 0x%02x\n", dosemu_regs.regs[PELIW] / 3);
      return dosemu_regs.regs[PELIW] / 3;

    case PEL_IR:
      v_printf("VIDEO: Read PELIR 0x%02x\n", dosemu_regs.regs[PELIR] / 3);
      return dosemu_regs.regs[PELIR] / 3;

    case PEL_D:
      v_printf("VIDEO: Read PELIR Data 0x%02x\n",
	       dosemu_regs.palette[dosemu_regs.regs[PELIR]]);
      return dosemu_regs.palette[dosemu_regs.regs[PELIR]++];

    case PEL_M:
      v_printf("VIDEO: Read PELM Data 0x%02x\n",
	       (dosemu_regs.regs[PELM] == 0) ? 0xff : dosemu_regs.regs[PELM]);
      return (dosemu_regs.regs[PELM] == 0) ? 0xff : dosemu_regs.regs[PELM];
    }
    return chipset_inb(port);
  }
}

static void vga_port_outb(Bit32u port, Bit8u value)
{
  if (own_console) {
    port_safe_outb(port, value);
  }
  else {
    switch (port & 0x3ff) {
    case CRT_IC:
    case CRT_IM:
      v_printf("VIDEO: Set Index CRTC 0x%02x\n", value);
      dosemu_regs.regs[CRTI] = value;
      return;

    case CRT_DC:
    case CRT_DM:
      if (dosemu_regs.regs[CRTI] <= 0x18) {
	v_printf("VIDEO: Write Data at CRTC Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[CRTI], value);
	dosemu_regs.regs[CRT + dosemu_regs.regs[CRTI]] = value;
	return;
      }
      break;

    case GRA_I:
      v_printf("VIDEO: Set Index GRAI 0x%02x\n", value);
      dosemu_regs.regs[GRAI] = value;
      return;

    case GRA_D:
      if (dosemu_regs.regs[GRAI] < 0x0a) {
	v_printf("VIDEO: Write Data at GRAI Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[GRAI], value);
	dosemu_regs.regs[GRA + dosemu_regs.regs[GRAI]] = value;
	return;
      }
      break;

    case SEQ_I:
      v_printf("VIDEO: Set Index SEQI 0x%02x\n", value);
      dosemu_regs.regs[SEQI] = value;
      return;

    case SEQ_D:
      if (dosemu_regs.regs[SEQI] <= 0x05) {
	v_printf("VIDEO: Write Data at SEQ Index 0x%02x = 0x%02x\n",
		 dosemu_regs.regs[SEQI], value);
	dosemu_regs.regs[SEQ + dosemu_regs.regs[SEQI]] = value;
	return;
      }
      break;

    case ATT_IW:
      if (isr_read) {
	v_printf("VIDEO: Set Index ATTI 0x%02x\n", value & 0x1f);
	att_d_index = value & 0x1f;
	isr_read = 0;
	return;
      }
      else {
	isr_read = 1;
	if (att_d_index <= 20) {
	  v_printf("VIDEO: Write Data at ATT Index 0x%02x = 0x%02x\n",
		   att_d_index, value);
	  dosemu_regs.regs[ATT + att_d_index] = value;
	  return;
	}
      }
      break;

    case MIS_W:
      v_printf("VIDEO: Set MISW 0x%02x\n", value);
      dosemu_regs.regs[MIS] = value;
      return;

    case PEL_IW:
      v_printf("VIDEO: Set PELIW  to 0x%02x\n", value);
      dosemu_regs.regs[PELIW] = value * 3;
      return;

    case PEL_IR:
      v_printf("VIDEO: Set PELIR to 0x%02x\n", value);
      dosemu_regs.regs[PELIR] = value * 3;
      return;

    case PEL_D:
      v_printf("VIDEO: Put PEL_D[0x%02x] = 0x%02x\n", dosemu_regs.regs[PELIW],
	       value);
      dosemu_regs.palette[dosemu_regs.regs[PELIW]] = value;
      dosemu_regs.regs[PELIW] += 1;
      return;

    case PEL_M:
      v_printf("VIDEO: Set PEL_M to 0x%02x\n", value);
      dosemu_regs.regs[PELM] = value;
      return;

    case GR1_P:
      v_printf("VIDEO: Set GR1_P to 0x%02x\n", value);
      dosemu_regs.regs[GR1P] = value;
      return;

    case GR2_P:
      v_printf("VIDEO: Set GR2_P to 0x%02x\n", value);
      dosemu_regs.regs[GR2P] = value;
      return;
    }
    chipset_outb(port, value);
  }
}

static void vga_get_perm(void)
{
  v_printf("VGA: get permission to do direct port I/O\n");
  /* setup port access */
  if (set_ioperm(0x3b0, 0x3bb - 0x3b0 + 1, 1)) {
    error("VGA: can't get I/O permissions \n");
    leavedos(1);
  }
  if (set_ioperm(0x3c0, 0x3df - 0x3c0 + 1, 1)) {
    error("VGA: can't get I/O permissions \n");
    leavedos(1);
  }
  if (chipset && chipset_get_perm)
    chipset_get_perm();
}

static void vga_giveup_perm(void)
{
  v_printf("VGA: giving up permission to do direct port I/O\n");

  /* setup port access */
  if (set_ioperm(0x3b0, 0x3bb - 0x3b0 + 1, 0)) {
    error("VGA: can't giveup I/O permissions \n");
    leavedos(1);
  }
  if (set_ioperm(0x3c0, 0x3df - 0x3c0 + 1, 0)) {
    error("VGA: can't giveup I/O permissions \n");
    leavedos(1);
  }
  if (chipset && chipset_release_perm)
    chipset_release_perm();
}

void __vga_delay(void)
{
  /* don't mess with this function lest the optimizer vaporize it for us */
  int i;
  for (i = 0; i < 10; i++);
}

static void vga_screenoff(void)
{
  v_printf("VGA: vga_screenoff\n");

  if (config.chipset != CHIPSET_EGA) {
    /* synchronous reset on */
    port_out(0x00, SEQ_I);
    port_out(0x01, SEQ_D);

    /* turn off screen for faster VGA memory acces */
    port_out(0x01, SEQ_I);
    port_out(port_in(SEQ_D) | 0x20, SEQ_D);

    /* synchronous reset off */
    port_out(0x00, SEQ_I);
    port_out(0x03, SEQ_D);
    
    /* disable the video card */
    port_in(IS1_R);
    port_out(0x00, ATT_IW);
  }
}

static void vga_screenon(void)
{
  v_printf("VGA: vga_screenon\n");

  /* re-enable the video card */
  if (config.chipset != CHIPSET_EGA) {
    /* enable the card */
    port_in(IS1_R);
    port_out(0x20, ATT_IW);

    /* synchronous reset on */
    port_out(0x00, SEQ_I);
    port_out(0x01, SEQ_D);

    /* turn screen back on */
    port_out(0x01, SEQ_I);
    port_out(port_in(SEQ_D) & 0xDF, SEQ_D);

    /* synchronous reset off */
    port_out(0x00, SEQ_I);
    port_out(0x03, SEQ_D);
  }
}

static void vga_savepalette(Bit8u *palette)
{
  int i;

  if (config.chipset == CHIPSET_EGA)
    return;

  v_printf("VGA: vga_savepalette\n");

#if 0 /* XXX */
  if ((config.chipset == CHIPSET_MACH32) && SVGAMODE(CM)) {
    /* Actually the same but we are in 8514 mode and the dac
       does not respond to the VGA circuitry anymore... */

    port_out(0, PEL8514_IR); 
    for (i = 0; i < 256; i++) {
      __vga_delay();
      *(palette++) = port_in(PEL8514_D);
      __vga_delay();
      *(palette++) = port_in(PEL8514_D);
      __vga_delay();
      *(palette++) = port_in(PEL8514_D);
    }
    return;
  }
#endif

  /* save graphics mode palette - first select palette index 0 */
  port_out(0, PEL_IR); 

  /* read RGB components - index is autoincremented */
  for (i = 0; i < 256; i++) {
    __vga_delay();
    *(palette++) = port_in(PEL_D);
    __vga_delay();
    *(palette++) = port_in(PEL_D);
    __vga_delay();
    *(palette++) = port_in(PEL_D);
  }
}

static void vga_restorepalette(const Bit8u *palette)
{
  int i;

  if (config.chipset == CHIPSET_EGA)
    return;

  v_printf("VGA: vga_restorepalette\n");

#if 0 /* XXX */
  if ((config.chipset == CHIPSET_MACH32) && SVGAMODE(CM)) {
    /* Actually the same but we are in 8514 mode and the dac
       does not respond to the VGA circuitry anymore... */
    
    port_out(0, PEL8514_IW); 
    for (i = 0; i < 256; i++) {
      __vga_delay();
      port_out(*(palette++), PEL8514_D);
      __vga_delay();
      port_out(*(palette++), PEL8514_D);
      __vga_delay();
      port_out(*(palette++), PEL8514_D);
    }
    return;
  }
#endif

  /* restore saved palette */
  port_out(0, PEL_IW); 

  /* read RGB components - index is autoincremented */
  for (i = 0; i < 256; i++) {
    __vga_delay();
    port_out(*(palette++), PEL_D);
    __vga_delay();
    port_out(*(palette++), PEL_D);
    __vga_delay();
    port_out(*(palette++), PEL_D);
  }
}

static void vga_savestdregs(Bit16u *regs)
{
  int i;

  v_printf("VGA: vga_savestdregs\n");

  /* Start with INDEXS */
  regs[CRTI] = port_in(CRT_I);
  __vga_delay();
  regs[GRAI] = port_in(GRA_I);
  __vga_delay();
  regs[SEQI] = port_in(SEQ_I);
  __vga_delay();
  regs[FCR]  = port_in(FCR_R);
  __vga_delay();
  regs[ISR1] = port_in(IS1_R) | 0x09;
  __vga_delay();
  regs[ISR0] = port_in(IS0_R);
  __vga_delay();
  regs[PELIW] = port_in(PEL_IW);
  __vga_delay();
  regs[PELIR] = port_in(PEL_IR);
  __vga_delay();

  /* save VGA registers */
  for (i = 0; i < CRT_C; i++) {
    port_out(i, CRT_I);
    __vga_delay();
    regs[CRT + i] = port_in(CRT_D);
    __vga_delay();
  }

  for (i = 0; i < ATT_C; i++) {
    port_in(IS1_R);
    __vga_delay();
    port_out(i, ATT_IW);
    __vga_delay();
    regs[ATT + i] = port_in(ATT_R);
    __vga_delay();
  }
  for (i = 0; i < GRA_C; i++) {
    port_out(i, GRA_I);
    __vga_delay();
    regs[GRA + i] = port_in(GRA_D);
    __vga_delay();
  }
  for (i = 1; i < SEQ_C; i++) {
    port_out(i, SEQ_I);
    __vga_delay();
    regs[SEQ + i] = port_in(SEQ_D);
    __vga_delay();
  }
  regs[SEQ + 1] = regs[SEQ + 1] | 0x20;

  regs[MIS] = port_in(MIS_R);

  port_out(regs[CRTI], CRT_I);
  port_out(regs[GRAI], GRA_I);
  port_out(regs[SEQI], SEQ_I);
}

static void vga_restorestdregs(const Bit16u *regs)
{
  int i;

  v_printf("VGA: vga_restorestdregs\n");

  /* update misc output register */
  port_out (regs[MIS], MIS_W);

  /* synchronous reset on */
  port_out (0x00, SEQ_I);
  port_out (0x01, SEQ_D);

  /* write sequencer registers */
  port_out (0x01, SEQ_I);
  port_out (regs[SEQ + 1] | 0x20, SEQ_D);
  for (i = 2; i < SEQ_C; i++) {
    port_out (i, SEQ_I);
    __vga_delay();
    port_out (regs[SEQ + i], SEQ_D);
    __vga_delay();
  }

  /* synchronous reset off */
  port_out (0x00, SEQ_I);
  port_out (0x03, SEQ_D);

  /* deprotect CRT registers 0-7 */
  if (config.chipset != CHIPSET_EGA) {
    port_out(0x11, CRT_I);
    port_out(port_in (CRT_D) & 0x7F, CRT_D);
  }

  /* write CRT registers */
  for (i = 0; i < CRT_C; i++) {
    port_out (i, CRT_I);
    __vga_delay();
    port_out (regs[CRT + i], CRT_D);
    __vga_delay();
  }

  /* write graphics controller registers */
  for (i = 0; i < GRA_C; i++) {
    port_out (i, GRA_I);
    __vga_delay();
    port_out (regs[GRA + i], GRA_D);
    __vga_delay();
  }

  /* write attribute controller registers */
  for (i = 0; i < ATT_C; i++) {
    port_in (IS1_R);		/* reset flip-flop */
    __vga_delay();
    port_out (i, ATT_IW);
    __vga_delay();
    port_out (regs[ATT + i], ATT_IW);
    __vga_delay();
  }

  /* write palette control registers */
  port_out(regs[PELIW], PEL_IW);
  __vga_delay();
  port_out(regs[PELIR], PEL_IR);
  __vga_delay();
}

static void vga_saveregs(vga_info_struct *regs)
{
  if (config.chipset == CHIPSET_EGA) {  
    /* Special case: Don't save standard VGA registers. */
    chipset_saveregs(regs->xregs);
    return;
  }

  vga_savestdregs(regs->regs);
  chipset_saveregs(regs->xregs);
}

static void vga_restoreregs(const vga_info_struct *regs)
{
  if (config.chipset == CHIPSET_EGA) {  
    /* Enable graphics register modification */
    port_out(0x00, GRA_E0);
    port_out(0x01, GRA_E1);
  }
  vga_restorestdregs(regs->regs);
  chipset_restoreregs(regs->xregs);
}

static void vga_savemem(vga_info_struct *regs)
{
  Bit8u *mem;
  int i;

  port_out(0x06, GRA_I);
  if (!(port_in(GRA_D) & 0x01)) {
    regs->page_size  = 8;
    regs->page_count = 1;
  }
  else if ((regs->video_mode <= 19) || (regs->video_mode == 0x55)) {
    regs->page_size  = 64;
    regs->page_count = 1;
  }
  else {
    regs->page_size  = 64;
    regs->page_count = (config.gfxmemsize + 255) / 256;
  }

  if (regs->mem_size < regs->page_size * regs->page_count * 1024) {
    free(regs->mem);
    regs->mem = NULL;
  }

  if (!regs->mem) {
    regs->mem_size = regs->page_size * regs->page_count * 1024 * 4;
    if ((regs->mem = malloc(regs->mem_size)) == NULL) {
      error("Unable to allocate memory for video buffer\n");
      leavedos(1);
    }
  }

  v_printf("VGA: vga_savemem (mem=%p, mem_size=%ld, pages=%d, page_size=%d)\n",
	   regs->mem, regs->mem_size, regs->page_count, regs->page_size);

  mem = regs->mem;

  if (regs->page_count == 1) {
    vga_restorestdregs(vregs);
    for (i = 0; i < 4; i++) {
      /* save plane i */
      port_out(0x04, GRA_I);
      port_out(i, GRA_D);
      memcpy(mem, vmem_mapped, regs->page_size*1024);
      v_printf("VGA: savemem, mem(%p) = %02hx %02hx %02hx %02hx\n", mem, *mem,
	       *(mem+1), *(mem+2), *(mem+3));
      mem += regs->page_size * 1024;
    }
  }
  else {
    int bank;
    for (bank = 0; bank < regs->page_count; ++bank) {
      chipset_setpage(bank);
      for (i = 0; i < 4; i++) {
	/* save plane i */
	port_out(0x04, GRA_I); 
	port_out(i, GRA_D);
	memcpy(mem, vmem_mapped, regs->page_size*1024);
	mem += regs->page_size * 1024;
      }
    }
  }
}

static void vga_restoremem(const vga_info_struct *regs)
{
  Bit8u *mem;
  Bit8u setresetreg, planereg;
  int i, page;

  v_printf("VGA: vga_restoremem (mem=%p, mem_size=%ld, pages=%d, page_size=%d)\n",
	   regs->mem, regs->mem_size, regs->page_count, regs->page_size);

  mem = regs->mem;

  /* disable Set/Reset Register */
  port_out(0x01, GRA_I); 
  setresetreg = (config.chipset == CHIPSET_EGA) ? 0 : port_in(GRA_D);
  port_out(0x00, GRA_D);   
  port_out(0x02, SEQ_I);
  planereg = (config.chipset == CHIPSET_EGA) ? 0 : port_in(SEQ_D);

  if (regs->page_count == 1) {
    vga_restorestdregs(vregs);
    for (i = 0; i < 4; i++) {
      /* restore plane i */
      port_out(0x02, SEQ_I); 
      port_out(1<<i, SEQ_D);
      memcpy(vmem_mapped, mem, regs->page_size*1024);
      v_printf("VGA: restoremem, mem(%p) = %02hx %02hx %02hx %02hx\n", mem,
	       *mem, *(mem+1), *(mem+2), *(mem+3));
      mem += regs->page_size * 1024;
    }
  }
  else {
    for (page = 0; page < regs->page_count; ++page) {
      chipset_setpage(page);
      for (i = 0; i < 4; i++) {
	/* save plane i */
	port_out(0x02, SEQ_I); 
	port_out(1<<i, SEQ_D);
	memcpy(vmem_mapped, mem, regs->page_size*1024);
	mem += regs->page_size * 1024;
      }
    }
  }

  /* enable Set/Reset Register */
  port_out(0x01, GRA_I);
  port_out(setresetreg, GRA_D);
  port_out(0x02, SEQ_I);
  port_out(planereg, SEQ_D);
}

static void vga_restorestate(const vga_info_struct *regs)
{
  vga_restoreregs(regs);
  vga_restorepalette(regs->palette);
  vga_restoremem(regs);
  vga_restoreregs(regs);
  vga_dump_state(regs);
}

static void vga_savestate(vga_info_struct *regs)
{
  vga_saveregs(regs);
  vga_savepalette(regs->palette);
  vga_savemem(regs);
  vga_restoreregs(regs);
  vga_dump_state(regs);
}

static void vga_attach(void)
{
  vga_get_perm();
  vga_screenoff();
  console_mmap();
  vga_restorestate(&dosemu_regs);
  vga_screenon();
}

static void vga_detach(void)
{
  dosemu_regs.video_mode = READ_BYTE(BIOS_VIDEO_MODE);
  vga_screenoff();
  vga_savestate(&dosemu_regs);
  vga_restorestate(&linux_regs);
  console_munmap();
  vga_screenon();
  vga_giveup_perm();
}

static int vga_init(void)
{
  emu_iodev_t  io_device;
  vc_handler_t vc_handler;
  int          i;

  v_printf("VGA: vga_init()\n");
  console_init();

  /* register our port handler */
  io_device.read_portb   = vga_port_inb;
  io_device.write_portb  = vga_port_outb;
  io_device.read_portw   = NULL;
  io_device.write_portw  = NULL;
  io_device.handler_name = "VGA";
  io_device.irq          = EMU_NO_IRQ;

  /*
   * The original video code allowed port access to 0x3ff & [video port]
   * to go through.  This is expressed by the loop below.  Personally, I
   * feel that the specific module should do it itself.
   */
#if 0
  for (i = 0; i < 0xffff; i += 0x400) {
#else
  i = 0;
#endif
  io_device.start_addr   = 0x3b0 + i;
  io_device.end_addr     = 0x3bb + i;
  port_register_handler(io_device);

  io_device.start_addr   = 0x3c0 + i;
  io_device.end_addr     = 0x3df + i;
  port_register_handler(io_device);
#if 0
  }
#endif
  /*
   * Map the memory into our address space.  This saves us from having
   * to try to copy things in and out of the dos memory -- we have a
   * pointer directly to the video memory.
   */
  v_printf("VGA: mapping video card RAM into our address space\n");
  vmem_mapped = mmap(0, graph_size, GRAPH_PROT, MAP_SHARED, mem_fd,
		     (off_t)graph_base);
  if ((off_t)vmem_mapped <= 0) {
    error("Error virtually mapping VGA RAM into our address space %s\n",
	  strerror(errno));
    leavedos(1);
  }

  /* get the video state */
  linux_regs.mem         = NULL;
  linux_regs.mem_size    = 0;
  linux_regs.page_count  = 4;
  linux_regs.page_size   = 8;
  linux_regs.video_mode  = 3;
  dosemu_regs.mem        = NULL;
  dosemu_regs.mem_size   = 0;
  dosemu_regs.page_count = (config.gfxmemsize + 255) / 256;
  dosemu_regs.page_size  = 8;
  dosemu_regs.video_mode = 3;
  dosemu_regs.regs[CRTI] = 0;
  dosemu_regs.regs[SEQI] = 0;
  dosemu_regs.regs[GRAI] = 0;

  v_printf("VGA: Starting card detection, going to IOPL(3)\n");
#ifndef CONFIG_RUN_AS_ROOT
  priv_on();
#endif
  iopl(3);

  /* chose registers for color/monochrome emulation */
  if (port_in(MIS_R) & 0x01) {
    CRT_I = CRT_IC;
    CRT_D = CRT_DC;
    IS1_R = IS1_RC;
    FCR_W = FCR_WC;
  }
  else {
    CRT_I = CRT_IM;
    CRT_D = CRT_DM;
    IS1_R = IS1_RM;
    FCR_W = FCR_WM;
  }

  /* do our card detection */
  if (config.chipset == CHIPSET_DETECT) {
    for (i = sizeof(drivers)/sizeof(drivers[0])-1; i > 0; i--) {
      if (drivers[i] && drivers[i]->test) {
	v_printf("VGA: Testing for \"%s\" driver\n", drivers[i]->name);
	if (drivers[i]->test()) {
	  config.chipset = i;
	  break;
	}
      }
    }
    if (config.chipset == CHIPSET_DETECT) {
      v_printf("VGA: No chipset matched -- trying plain VGA\n");
      config.chipset = CHIPSET_VGA;
    }
  }
  else {
    i = config.chipset;
    if (drivers[i]) {
      v_printf("VGA: Testing \"%s\" video driver\n", drivers[i]->name);
      if (drivers[i]->test) {
	if (!drivers[i]->test()) {
	  v_printf("VGA: Driver \"%s\" test FAILED!!!, using plain VGA\n",
		   drivers[i]->name);
	  config.chipset = CHIPSET_VGA;
	}
      }
    }
    else {
      v_printf("VGA: You video chipset support is not compiled in DOSEMU!\n");
      config.chipset = CHIPSET_VGA;
    }
  }

  chipset = drivers[config.chipset];

  /* Get the I/O permissions */
  vga_get_perm();

  if (!chipset->init()) {
    v_printf("VGA: Driver \"%s\" init FAILED!!!, using plain VGA\n",
	     drivers[i]->name);
    /* drivers[CHIPSET_VGA]->init() does nothing, so we won't call it here. */
    config.chipset = CHIPSET_VGA;
    chipset        = drivers[CHIPSET_VGA];
  }
  v_printf("VGA: Using driver \"%s\", going to iopl(0)\n", chipset->name);
  iopl(0);
#ifndef CONFIG_RUN_AS_ROOT
  priv_off();
#endif

  vga_screenoff();
  vga_savestate(&linux_regs);
  vga_screenon();

  vga_inited = TRUE;

  vc_handler.detach = vga_detach;
  vc_handler.attach = vga_attach;
  vc_register_handler(vc_handler);

  return 1;
}

static void vga_reset(void)
{
  if (dosemu_regs.mem)
    free(dosemu_regs.mem);
  dosemu_regs.mem = NULL;

  vga_screenoff();
  vga_restorestate(&linux_regs);
  vga_savestate(&dosemu_regs);
  vga_screenon();
}

static void vga_term(void)
{
  v_printf("VGA: vga_term(), going to iopl(3) to restore state\n");
  console_term();

  iopl(3);
  if (vga_inited) {
    vga_screenoff();
    vga_restorestate(&linux_regs);
    vga_screenon();
  }
  else
    vga_screenon();
  iopl(0);
  v_printf("VGA: vga_term() leaving and returning to iopl(0)\n");
}

struct video_system Video_graphics = {
   1,                /* is_mapped */
   vga_init,         /* initialize */
   vga_term,         /* close */
   NULL,             /* set mode */
   NULL,             /* update_screen */
   NULL
};

static void    nothing(void)                      { }
static void    nothingi(int i)                    { }
static Boolean vga_drvr_test(void)                { return TRUE; }
static Boolean vga_drvr_init(void)                { return TRUE; }
static Bit8u   null_inb(Bit32u port)              { return 0xff; }
static void    null_outb(Bit32u port, Bit8u val)  { }
static void    null_saveregs(Bit16u* regs)        { }
static void    null_restoreregs(const Bit16u* regs) { }

static void vga_setpage(int page)
{
  port_out(0x04, GRA_I);
  port_out(page, GRA_D);
}

DriverSpecs vga_driverspecs = {
	"Vanilla VGA",
	null_saveregs,
	null_restoreregs,
	vga_drvr_test,
	vga_drvr_init,
	vga_setpage,
	nothing,		/* set_perm */
	nothing,		/* release_perm */
	null_inb,
	null_outb
};

/* XXX -- These EGA defs are all wrong.  :) */

static Boolean ega_test(void)                     { return FALSE; }
static Boolean ega_init(void)                     { return FALSE; }
static void    ega_saveregs(Bit16u* regs)         { }
static void    ega_restoreregs(const Bit16u* regs) { }

DriverSpecs ega_driverspecs = {
	"Elementary EGA",
	ega_saveregs,
	ega_restoreregs,
	ega_test,
	ega_init,
	nothingi,		/* setpage */
	nothing,		/* set_perm */
	nothing,		/* release_perm */
	null_inb,
	null_outb
};

/*
 * These are simple functions to write a value to a VGA Graphics
 * Controller, CRTC Controller or Sequencer register.
 * The idea is that drivers call these as a function call, making the
 * code smaller and inserting small delays between I/O accesses when
 * doing mode switches.
 */

void outGR( int indx, Bit8u val ) {
	int v;
	v = ((int)val << 8) + indx;
	outw(GRA_I, v);
}

void outbGR( int indx, Bit8u val ) {
	outb(GRA_I, indx);
	outb(GRA_D, val);
}

void outCR( int indx, Bit8u val ) {
	int v;
	v = ((int)val << 8) + indx;
	outw(CRT_IC, v);
}

void outbCR( int indx, Bit8u val ) {
	outb(CRT_IC, indx);
	outb(CRT_D, val);
}

void outSR( int indx, Bit8u val ) {
	int v;
	v = ((int)val << 8) + indx;
	outw(SEQ_I, v);
}

void outbSR( int indx, Bit8u val ) {
	outb(SEQ_I, indx);
	outb(SEQ_D, val);
}

Bit8u inGR( int indx ) {
	outb(GRA_I, indx);
	return inb(GRA_D);
}

Bit8u inCR( int indx ) {
	outb(CRT_IC, indx);
	return inb(CRT_DC);
}

Bit8u inSR( int indx ) {
	outb(SEQ_I, indx);
	return inb(SEQ_D);
}

