/* 
 * DANG_BEGIN_MODULE
 * 
 * Contains the run_vm86 function
 *
 * DANG_END_MODULE
 *
 * DANG_BEGIN_CHANGELOG
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 *
 * DANG_END_CHANGELOG
 */

#include "dosemu.h"
#include "base/memory.h"
#include "base/port.h"
#include "base/int.h"
#include "base/pic.h"
#include "base/port.h"
#include "base/hlt.h"
#include "base/timers.h"
#include "base/ioctlq.h"
#include "arch/vm86plus.h"

/*
 * DANG_BEGIN_FUNCTION vm86_GP_fault();
 *
 * description:
 * All from the kernel unhandled general protection faults from V86 mode
 * are handled here. This are mainly port IO and the HLT instruction.
 *
 * DANG_END_FUNCTION
 */

void vm86_GP_fault(void)
{
  Bit8u  *csp, *lina;
  long    pref_seg;
  Bit16u  org_eip;
  int     done, is_rep, is_32bit;

#if 0
  csp = SEG_ADR((unsigned char *), cs, ip);
  k_printf("SIGSEGV cs=0x%04x ip=0x%04lx *csp=0x%02x->0x%02x 0x%02x 0x%02x\n",
	   _CS, _IP, csp[-1], csp[0], csp[1], csp[2]);
/*
  show_regs(__FILE__, __LINE__);
*/
#endif

  if (ignore_segv) {
    error("ERROR: sigsegv ignored!\n");
    return;
  }

  if (in_sigsegv)
    error("ERROR: in_sigsegv=%d!\n", in_sigsegv);

  /* in_vm86 = 0; */
  in_sigsegv++;

  /* DANG_BEGIN_REMARK 
   * In a properly functioning emulator :-), sigsegv's will never come
   * while in a non-reentrant system call (ioctl, select, etc).  Therefore,
   * there's really no reason to worry about them, so I say that I'm NOT
   * in a signal handler (I might make this a little clearer later, to
   * show that the purpose of in_sighandler is to stop non-reentrant system
   * calls from being reentered.
   * I reiterate: sigsegv's should only happen when I'm running the vm86
   * system call, so I really shouldn't be in a non-reentrant system call
   * (except maybe vm86)
   * - Robert Sanders
   * DANG_END_REMARK
   */
  in_sighandler = 0;

  if (LWORD(eflags) & TF) {
    g_printf("SIGSEGV received while TF is set\n");
    show_regs(__FILE__, __LINE__);
  }

  csp = lina = SEG_ADR((Bit8u *), cs, ip);

  /* DANG_BEGIN_REMARK
   * Here we handle all prefixes prior switching to the appropriate routines
   * The exception CS:EIP will point to the first prefix that effects the
   * the faulting instruction, hence, 0x65 0x66 is same as 0x66 0x65.
   * So we collect all prefixes and remember them.
   * - Hans Lermen
   * DANG_END_REMARK
   */

  #define __SEG_ADR(type, seg, reg)  type((seg << 4) + LWORD(e##reg))
  done     = 0;
  is_rep   = 0;
  is_32bit = 0;
  pref_seg = -1;

  do {
    switch (*(csp++)) {
       case 0x66:      /* operant prefix */  is_32bit=1; break;
       case 0x2e:      /* CS */              pref_seg=_CS; break;
       case 0x3e:      /* DS */              pref_seg=_DS; break;
       case 0x26:      /* ES */              pref_seg=_ES; break;
       case 0x36:      /* SS */              pref_seg=_SS; break;
       case 0x65:      /* GS */              pref_seg=_GS; break;
       case 0x64:      /* FS */              pref_seg=_FS; break;
       case 0xf3:      /* rep */             is_rep=1; break;
#if 0
       case 0xf2:      /* repnz */
#endif
       default: done=1;
    }
  } while (!done);
  csp--;
  org_eip = _IP;
  _IP += (csp-lina);

  switch (*csp) {

  case 0x6c:                    /* insb */
    /* NOTE: ES can't be overwritten */
    if (is_rep) {
      int delta = 1;
      if (LWORD(eflags) & DF) delta = -1;
      i_printf("Doing REP F3 6C (rep insb) %04x bytes, DELTA %d\n",
              _CX, delta);
      while (_CX) {
        *(SEG_ADR((Bit8u *),es,di)) = port_inb(_DX);
        _DI += delta;
        _CX--;
      }
    }
    else {
      *(SEG_ADR((Bit8u *),es,di)) = port_inb(_DX);
      i_printf("insb(0x%04x) value %02x\n", _DX, *(SEG_ADR((Bit8u *),es,di)));
      if (LWORD(eflags) & DF)
	_DI--;
      else
	_DI++;
    }
    _IP++;
    break;

  case 0x6d:			/* (rep) insw / insd */
    /* NOTE: ES can't be overwritten */
    if (is_rep) {
      #define ___LOCALAUX(typ,iotyp,incr,x) \
        int delta = incr; \
        if (LWORD(eflags) & DF) delta = -incr; \
        i_printf("rep ins%c %04x words, DELTA %d\n", x, \
                _CX, delta); \
        while (_CX) { \
          *(SEG_ADR(typ,es,di)) = iotyp(_DX); \
          _DI += delta; \
          _CX--; \
        }

      if (is_32bit) {          /* rep insd */
        ___LOCALAUX((Bit32u *), port_ind, 4, 'd')
      }
      else {                   /* rep insw */
        ___LOCALAUX((Bit16u *), port_inw, 2, 'w')
      }
      #undef  ___LOCALAUX
    }
    else {
      if (is_32bit) {           /* insd */
        *(SEG_ADR((Bit32u *), es, di)) = port_ind(_DX);
        i_printf("insd(0x%04x) value %08lx\n", _DX,
		 *(SEG_ADR((Bit32u *), es, di)));
        if (LWORD(eflags) & DF) _DI -= 4;
        else _DI += 4;
      }
      else {                    /* insw */
        *(SEG_ADR((Bit16u *), es, di)) = port_inw(_DX);
        i_printf("insw(0x%04x) value %04hx\n", _DX,
		 *(SEG_ADR((Bit16u *), es, di)));
        if (LWORD(eflags) & DF) _DI -= 2;
        else _DI += 2;
      }
    }
    _IP++;
    break;

  case 0x6e:			/* (rep) outsb */
    if (pref_seg < 0) pref_seg = _DS;
    if (is_rep) {
      int delta = 1;
      i_printf("untested: rep outsb\n");
      if (LWORD(eflags) & DF) delta = -1;
      while (_CX) {
        port_outb(_DX, *(__SEG_ADR((Bit8u *),pref_seg,si)));
        _SI += delta;
        _CX--;
      }
    }
    else {
      i_printf("untested: outsb port 0x%04x value %02x\n",
	       _DX, *(__SEG_ADR((Bit8u *),pref_seg,si)));
      port_outb(_DX, *(__SEG_ADR((Bit8u *),pref_seg,si)));
      if (LWORD(eflags) & DF) _SI--;
      else _SI++;
    }
    _IP++;
    break;


  case 0x6f:			/* (rep) outsw / outsd */
    if (pref_seg < 0) pref_seg = _DS;
    if (is_rep) {
      #define ___LOCALAUX(typ,iotyp,incr,x) \
        int delta = incr; \
        i_printf("untested: rep outs%c\n", x); \
        if (LWORD(eflags) & DF) delta = -incr; \
        while (_CX) { \
          iotyp(_DX, *(__SEG_ADR(typ,pref_seg,si))); \
          _SI += delta; \
          _CX--; \
        }
      
      if (is_32bit) {           /* rep outsd */
        ___LOCALAUX((Bit32u *),port_outd,4,'d')
      }
      else {                    /* rep outsw */
        ___LOCALAUX((Bit16u *),port_outw,2,'w')
      }
      #undef  ___LOCALAUX
    }
    else {
      if (is_32bit) {           /* outsd */
        i_printf("untested: outsd port 0x%04x value %04lx\n",
                 _DX, *(__SEG_ADR((Bit32u *), pref_seg, si)));
        port_outd(_DX, *(__SEG_ADR((Bit32u *), pref_seg, si)));
        if (LWORD(eflags) & DF) _SI -= 4;
        else _SI += 4;
      }
      else {                    /* outsw */
        i_printf("untested: outsw port 0x%04x value %04x\n",
                 _DX, *(__SEG_ADR((Bit16u *), pref_seg, si)));
        port_outw(_DX, *(__SEG_ADR((Bit16u *), pref_seg, si)));
        if (LWORD(eflags) & DF) _SI -= 2;
        else _SI += 2;
      }
      #undef  ___LOCALAUX
    } 
    _IP++;
    break;

  case 0xe5:			/* inw xx, ind xx */
    if (is_32bit) _EAX = port_ind(csp[1]);
    else _AX = port_inw(csp[1]);
    _IP += 2;
    break;
  case 0xe4:			/* inb xx */
    _AL = port_inb(csp[1]);
    _IP += 2;
    break;

  case 0xed:			/* inw dx, ind dx */
    if (is_32bit) _EAX = port_ind(_DX);
    else _AX = port_inw(_DX);
    _IP += 1;
    break;
  case 0xec:			/* inb dx */
    _AL = port_inb(_DX);
    _IP += 1;
    break;

  case 0xe7:			/* outw xx */
    if (is_32bit) port_outd(csp[1], _EAX);
    else port_outw(csp[1], _AX);
    _IP += 2;
    break;
  case 0xe6:			/* outb xx */
    port_outb(csp[1], _AL);
    _IP += 2;
    break;

  case 0xef:			/* outw dx */
    if (is_32bit) port_outd(_EDX, _EAX);
    else port_outw(_DX, _AX);
    _IP += 1;
    break;
  case 0xee:			/* outb dx */
    port_outb(_DX, _AL);
    _IP += 1;
    break;

  case 0xf4:
    hlt_handle();
    break;

  case 0x0f: /* CLTS -- ignore */
    if (csp[1] == 0x06) {
      LWORD(eip)+=2;
      break;
    }

  case 0xf0:			/* lock */
  default:
    if (is_rep)
      fprintf(stderr, "Nope REP F3,CSP = 0x%04x\n", csp[0]);

    /* er, why don't we advance eip here, and why does it work??  */
    error("general protection at %p: %x\n", lina,*lina);
    _IP = org_eip;
    show_regs(__FILE__, __LINE__);
    show_ints(0, 0x33);
    error("ERROR: SIGSEGV, protected insn...exiting!\n");
    fatalerr = 4;
    leavedos(fatalerr);		/* shouldn't return */
  }				/* end of switch() */

  if (LWORD(eflags) & TF) {
    g_printf("TF: trap done");
    show_regs(__FILE__, __LINE__);
  }

  in_sigsegv--;
  in_sighandler = 0;
}

/*
 * DANG_BEGIN_FUNCTION run_vm86
 * 
 * description: Here is where DOSEMU runs VM86 mode with the vm86() call
 * which also has the registers that it will be called with.  It will stop
 * vm86 mode for many reasons, like trying to execute an interrupt, doing
 * port I/O to ports not opened for I/O, etc ...
 * 
 * always invoke vm86() with this call.  all the messy stuff will be
 * in here.
 * 
 * DANG_END_FUNCTION
 */
void run_vm86(void)
{
  static int timer_flip;
  struct timeval cur_time;
  int retval;

  /*
   * XXX -- Enabling this slows down the CONSOLE code by 25%.  jlarry?
   */
#if 0
#if 1 /* <ESC> BUG FIXER (if 1) */
  #define OVERLOAD_THRESHOULD2  600000 /* maximum acceptable value */
  #define OVERLOAD_THRESHOULD1  238608 /* good average value */
  #define OVERLOAD_THRESHOULD0  100000 /* minum acceptable value */
  if ((pic_icount || (pic_dos_time < pic_sys_time)) &&
      ((pic_sys_time - pic_dos_time) < OVERLOAD_THRESHOULD1))
    REG(eflags) |= (VIP);
  else
    REG(eflags) &= ~(VIP);
#else
  if (pic_icount || (pic_dos_time < pic_sys_time))
    REG(eflags) |= (VIP);
#endif
#endif

  in_vm86 = 1;
  retval  = DO_VM86(&vm86s);
  in_vm86 = 0;

  switch (VM86_TYPE(retval)) {
  case VM86_UNKNOWN:
    vm86_GP_fault();
    timer_flip = !timer_flip;
    break;
  case VM86_STI:
    I_printf("VM86: STI\n");
    pic_iret();
    timer_flip = (timer_flip + 1) & 3;
    break;
  case VM86_INTx:
    int_run((Bit8u)VM86_ARG(retval));
    timer_flip = !timer_flip;
    break;
  case VM86_SIGNAL:
    I_printf("VM86: SIGNAL\n");
    timer_flip = 0;
    break;
#ifdef USE_VM86PLUS
  case VM86_PICRETURN:
    I_printf("VM86: Return for FORCE_PIC\n");
    timer_flip = 0;
    break;
#endif
  default:
    error("unknown return value from vm86()=%x,%d-%x\n", VM86_TYPE(retval),
	  VM86_TYPE(retval), VM86_ARG(retval));
    fatalerr = 4;
    timer_flip = 0;
  }

  signal_run();

  if (timer_flip == 0) {
    gettimeofday(&cur_time, NULL);
    timer_schedule(&cur_time);
  }

  /* update the pic to reflect IEF */
  if (REG(eflags) & VIF_MASK) {
    if (pic_iflag)
      pic_sti();
  } else {
    if (!pic_iflag)
      pic_cli();		/* pic_iflag=0 => enabled */
  }

  /*
   * This is here because ioctl() is non-reentrant, and signal handlers
   * may have to use ioctl().  This results in a possible (probable)
   * time lag of indeterminate length (and a bad return value). Ah, life
   * isn't perfect.
   * 
   * I really need to clean up the queue functions to use real queues.
   */
  if (iq.queued)
    ioctl_queue_run();
}
