// Copyright Notice
// ================
// BOCHS is Copyright 1994-1999 by Kevin P. Lawton.
//
// BOCHS is commercial software.
//
// For more information, read the file 'LICENSE' included in the bochs
// distribution.  If you don't have access to this file, or have questions
// regarding the licensing policy, the author may be contacted via:
//
//     US Mail:  Kevin Lawton
//               439 Marrett Rd.
//               Lexington, MA 02421-7714
//
//     EMail:    bochs@world.std.com





#include "bochs.h"



  void
BX_CPU_C::POP_Ed(BxInstruction_t *i)
{
  Bit32u val32;

  pop_32(&val32);

  if (i->mod == 0xc0) {
    BX_WRITE_32BIT_REG(i->rm, val32);
    }
  else {
    // Note: there is one little weirdism here.  When 32bit addressing
    // is used, it is possible to use ESP in the modrm addressing.
    // If used, the value of ESP after the pop is used to calculate
    // the address.
    if (i->as_32 && (i->mod!=0xc0) && (i->rm==4) && (i->base==4)) {
      i->ResolveModrm(i);
      }
    write_virtual_dword(i->seg, i->rm_addr, &val32);
    }
}

  void
BX_CPU_C::PUSH_ERX(BxInstruction_t *i)
{
  push_32(BX_CPU_THIS_PTR gen_reg[i->b1 & 0x07].erx);
}

  void
BX_CPU_C::POP_ERX(BxInstruction_t *i)
{
  Bit32u erx;

  pop_32(&erx);
  BX_CPU_THIS_PTR gen_reg[i->b1 & 0x07].erx = erx;
}


  void
BX_CPU_C::PUSH_CS(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value);
}
  void
BX_CPU_C::PUSH_DS(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value);
}
  void
BX_CPU_C::PUSH_ES(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value);
}
  void
BX_CPU_C::PUSH_FS(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value);
}
  void
BX_CPU_C::PUSH_GS(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value);
}
  void
BX_CPU_C::PUSH_SS(BxInstruction_t *i)
{
  if (i->os_32)
    push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value);
  else
    push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value);
}


  void
BX_CPU_C::POP_DS(BxInstruction_t *i)
{
  if (i->os_32) {
    Bit32u ds;
    pop_32(&ds);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS], (Bit16u) ds);
    }
  else {
    Bit16u ds;
    pop_16(&ds);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS], ds);
    }
}
  void
BX_CPU_C::POP_ES(BxInstruction_t *i)
{
  if (i->os_32) {
    Bit32u es;
    pop_32(&es);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES], (Bit16u) es);
    }
  else {
    Bit16u es;
    pop_16(&es);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES], es);
    }
}
  void
BX_CPU_C::POP_FS(BxInstruction_t *i)
{
  if (i->os_32) {
    Bit32u fs;
    pop_32(&fs);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS], (Bit16u) fs);
    }
  else {
    Bit16u fs;
    pop_16(&fs);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS], fs);
    }
}
  void
BX_CPU_C::POP_GS(BxInstruction_t *i)
{
  if (i->os_32) {
    Bit32u gs;
    pop_32(&gs);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS], (Bit16u) gs);
    }
  else {
    Bit16u gs;
    pop_16(&gs);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS], gs);
    }
}
  void
BX_CPU_C::POP_SS(BxInstruction_t *i)
{
  if (i->os_32) {
    Bit32u ss;
    pop_32(&ss);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS], (Bit16u) ss);
    }
  else {
    Bit16u ss;
    pop_16(&ss);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS], ss);
    }

  // POP SS inhibits interrupts, debug exceptions and single-step
  // trap exceptions until the execution boundary following the
  // next instruction is reached.
  // Same code as MOV_SwEw()
  BX_CPU_THIS_PTR inhibit_mask |=
    BX_INHIBIT_INTERRUPTS | BX_INHIBIT_DEBUG;
  BX_CPU_THIS_PTR async_event = 1;
}


  void
BX_CPU_C::PUSHAD32(BxInstruction_t *i)
{
#if BX_CPU_LEVEL < 2
  bx_panic("PUSHAD: not supported on an 8086\n");
#else
  Bit32u temp_ESP;
  Bit32u esp;

  if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b)
    temp_ESP = ESP;
  else
    temp_ESP = SP;


    if (protected_mode()) {
      if ( !can_push(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache, temp_ESP, 32) ) {
        bx_panic("PUSHAD(): stack doesn't have enough room!\n");
        exception(BX_SS_EXCEPTION, 0, 0);
        return;
        }
      }
    else {
      if (temp_ESP < 32)
        bx_panic("pushad: eSP < 32\n");
      }

    esp = ESP;

    /* ??? optimize this by using virtual write, all checks passed */
    push_32(EAX);
    push_32(ECX);
    push_32(EDX);
    push_32(EBX);
    push_32(esp);
    push_32(EBP);
    push_32(ESI);
    push_32(EDI);
#endif
}

  void
BX_CPU_C::POPAD32(BxInstruction_t *i)
{
#if BX_CPU_LEVEL < 2
  bx_panic("POPAD not supported on an 8086\n");
#else /* 286+ */
    Bit32u edi, esi, ebp, etmp, ebx, edx, ecx, eax;

    if (protected_mode()) {
      if ( !can_pop(32) ) {
        bx_panic("pop_ad: not enough bytes on stack\n");
        exception(BX_SS_EXCEPTION, 0, 0);
        return;
        }
      }

    /* ??? optimize this */
    pop_32(&edi);
    pop_32(&esi);
    pop_32(&ebp);
    pop_32(&etmp); /* value for ESP discarded */
    pop_32(&ebx);
    pop_32(&edx);
    pop_32(&ecx);
    pop_32(&eax);

    EDI = edi;
    ESI = esi;
    EBP = ebp;
    EBX = ebx;
    EDX = edx;
    ECX = ecx;
    EAX = eax;
#endif
}

  void
BX_CPU_C::PUSH_Id(BxInstruction_t *i)
{
#if BX_CPU_LEVEL < 2
  bx_panic("PUSH_Iv: not supported on 8086!\n");
#else

    Bit32u imm32;

    imm32 = i->Id;

    push_32(imm32);
#endif
}

  void
BX_CPU_C::PUSH_Ed(BxInstruction_t *i)
{
    Bit32u op1_32;

    /* op1_32 is a register or memory reference */
    if (i->mod == 0xc0) {
      op1_32 = BX_READ_32BIT_REG(i->rm);
      }
    else {
      /* pointer, segment address pair */
      read_virtual_dword(i->seg, i->rm_addr, &op1_32);
      }

    push_32(op1_32);
}


  void
BX_CPU_C::ENTER_IwIb(BxInstruction_t *i)
{
#if BX_CPU_LEVEL < 2
  bx_panic("ENTER_IwIb: not supported by 8086!\n");
#else
  Bit32u frame_ptr32;
  Bit16u frame_ptr16;
  Bit8u level;

  level = i->Ib2;

  invalidate_prefetch_q();

  level %= 32;
/* ??? */
if (level) bx_panic("enter(): level > 0\n");
//if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b && i->os_32==0) {
//  bx_printf("enter(): stacksize!=opsize: I'm unsure of the code for this\n");
//  bx_panic("         The Intel manuals are a mess on this one!\n");
//  }

  if ( protected_mode() ) {
    Bit32u bytes_to_push, temp_ESP;

    if (level == 0) {
      if (i->os_32)
        bytes_to_push = 4 + i->Iw;
      else
        bytes_to_push = 2 + i->Iw;
      }
    else { /* level > 0 */
      if (i->os_32)
        bytes_to_push = 4 + (level-1)*4 + 4 + i->Iw;
      else
        bytes_to_push = 2 + (level-1)*2 + 2 + i->Iw;
      }
    if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b)
      temp_ESP = ESP;
    else
      temp_ESP = SP;
    if ( !can_push(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache, temp_ESP, bytes_to_push) ) {
      bx_panic("ENTER: not enough room on stack!\n");
      exception(BX_SS_EXCEPTION, 0, 0);
      }
    }

  if (i->os_32)
    push_32(EBP);
  else
    push_16(BP);

  // can just do frame_ptr32 = ESP for either case ???
  if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b)
    frame_ptr32 = ESP;
  else
    frame_ptr32 = SP;

  if (level > 0) {
    /* do level-1 times */
    while (--level) {
      if (i->os_32) {
        Bit32u temp32;

        if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) { /* 32bit stacksize */
          EBP -= 4;
          read_virtual_dword(BX_SEG_REG_SS, EBP, &temp32);
          ESP -= 4;
          write_virtual_dword(BX_SEG_REG_SS, ESP, &temp32);
          }
        else { /* 16bit stacksize */
          BP -= 4;
          read_virtual_dword(BX_SEG_REG_SS, BP, &temp32);
          SP -= 4;
          write_virtual_dword(BX_SEG_REG_SS, SP, &temp32);
          }
        }
      else { /* 16bit opsize */
        Bit16u temp16;

        if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) { /* 32bit stacksize */
          EBP -= 2;
          read_virtual_word(BX_SEG_REG_SS, EBP, &temp16);
          ESP -= 2;
          write_virtual_word(BX_SEG_REG_SS, ESP, &temp16);
          }
        else { /* 16bit stacksize */
          BP -= 2;
          read_virtual_word(BX_SEG_REG_SS, BP, &temp16);
          SP -= 2;
          write_virtual_word(BX_SEG_REG_SS, SP, &temp16);
          }
        }
      } /* while (--level) */

    /* push(frame pointer) */
    if (i->os_32) {
      if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) { /* 32bit stacksize */
        ESP -= 4;
        write_virtual_dword(BX_SEG_REG_SS, ESP, &frame_ptr32);
        }
      else {
        SP -= 4;
        write_virtual_dword(BX_SEG_REG_SS, SP, &frame_ptr32);
        }
      }
    else { /* 16bit opsize */
      if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) { /* 32bit stacksize */
        frame_ptr16 = frame_ptr32;
        ESP -= 2;
        write_virtual_word(BX_SEG_REG_SS, ESP, &frame_ptr16);
        }
      else {
        frame_ptr16 = frame_ptr32;
        SP -= 2;
        write_virtual_word(BX_SEG_REG_SS, SP, &frame_ptr16);
        }
      }
    } /* if (level > 0) ... */

  if (i->os_32)
    EBP = frame_ptr32;
  else
    BP = frame_ptr32;

  if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) { /* 32bit stacksize */
    ESP = ESP - i->Iw;
    }
  else { /* 16bit stack */
    SP = SP - i->Iw;
    }
#endif
}

  void
BX_CPU_C::LEAVE(BxInstruction_t *i)
{
#if BX_CPU_LEVEL < 2
  bx_panic("LEAVE: not supported by 8086!\n");
#else
  Bit32u temp_EBP;


  invalidate_prefetch_q();

#if BX_CPU_LEVEL >= 3
  if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b)
    temp_EBP = EBP;
  else
#endif
    temp_EBP = BP;

  if ( protected_mode() ) {
    if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.c_ed) { /* expand up */
      if (temp_EBP <= BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.limit_scaled) {
        bx_panic("LEAVE: BP > BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].limit\n");
        exception(BX_SS_EXCEPTION, 0, 0);
        return;
        }
      }
    else { /* normal */
      if (temp_EBP > BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.limit_scaled) {
        bx_panic("LEAVE: BP > BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].limit\n");
        exception(BX_SS_EXCEPTION, 0, 0);
        return;
        }
      }
    }


  // delete frame
#if BX_CPU_LEVEL >= 3
  if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b)
    ESP = EBP;
  else
#endif
    SP = BP;

  // restore frame pointer
#if BX_CPU_LEVEL >= 3
  if (i->os_32) {
    Bit32u temp32;

    pop_32(&temp32);
    EBP = temp32;
    }
  else
#endif
    {
    Bit16u temp16;

    pop_16(&temp16);
    BP = temp16;
    }
#endif
}
