// 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



//
// This is the glue logic needed to connect the wm-FPU-emu
// FPU emulator written by Bill Metzenthen to bochs.
//


#include "bochs.h"
extern "C" {
#include "fpu_emu.h"
#include "fpu2cpu.h"
#include "linux/signal.h"
}

// Use this to hold a pointer to the instruction since
// we can't pass this to the FPU emulation routines, which
// will ultimately call routines here.
static BxInstruction_t *fpu_iptr = NULL;

i387_t i387;

extern "C" void
math_emulate2(fpu_addr_modes addr_modes,
              u_char  FPU_modrm,
              u_char byte1,
              void *data_address,
              struct address data_sel_off,
              struct address entry_sel_off);

extern "C" void printfp(char *s, FPU_REG *r);


  // This is called by bochs upon reset
  void
BX_CPU_C::fpu_init(void)
{
  finit();
}

  void
BX_CPU_C::fpu_execute(BxInstruction_t *i)
{
  fpu_addr_modes addr_modes;
  void *data_address;
  struct address data_sel_off;
  struct address entry_sel_off;
  Boolean is_32;

  fpu_iptr = i;

#if 0
  addr_modes.default_mode = VM86;
  addr_modes.default_mode = 0; // FPU_CS == __USER_CS && FPU_DS == __USER_DS
  addr_modes.default_mode = SEG32;
  addr_modes.default_mode = PM16;
#endif
  if (protected_mode()) {
    addr_modes.default_mode = SEG32;
    }
  else if (v8086_mode()) {
    addr_modes.default_mode = VM86;
    }
  else {
    // real mode, use vm86 for now
    addr_modes.default_mode = VM86;
    }


  // Mark if instruction used opsize or addrsize prefixes
  // Actually, addr_modes.override.address_size is not used,
  // could delete that code.
  is_32 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.u.segment.d_b;
  if (i->as_32 == is_32)
    addr_modes.override.address_size = 0;
  else
    addr_modes.override.address_size = ADDR_SIZE_PREFIX;
  if (i->os_32 == is_32)
    addr_modes.override.operand_size = 0;
  else
    addr_modes.override.operand_size = OP_SIZE_PREFIX;

  // For now set access_limit to max.  It seems to be
  // a number from 0..255 denoting how many bytes the
  // current instruction can access according to its
  // memory operand.  255 means >= 255.
access_limit = 0xff;

  // fill in orig eip here in offset
  // fill in CS in selector
  entry_sel_off.offset = BX_CPU_THIS_PTR prev_eip;
  entry_sel_off.selector =
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value;

// should set these fields to 0 if mem operand not used
  data_address = (void *) i->rm_addr;
  data_sel_off.offset = i->rm_addr;
  data_sel_off.selector = BX_CPU_THIS_PTR sregs[i->seg].selector.value;

  math_emulate2(addr_modes, i->modrm, i->b1, data_address,
                data_sel_off, entry_sel_off);
}


  unsigned
fpu_get_ds(void)
{
  return(BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value);
}

  void
fpu_set_ax(unsigned short val16)
{
  AX = val16;
//fprintf(stderr, "fpu_set_ax(0x%04x)\n", (unsigned) val16);
}

  void
fpu_verify_area(unsigned what, void *ptr, unsigned n)
{
  bx_segment_reg_t *seg;

  seg = &BX_CPU_THIS_PTR sregs[fpu_iptr->seg];

  if (what == VERIFY_READ) {
    BX_CPU.read_virtual_checks(seg, (Bit32u) ptr, n);
    }
  else {  // VERIFY_WRITE
    BX_CPU.write_virtual_checks(seg, (Bit32u) ptr, n);
    }
//fprintf(stderr, "verify_area: 0x%x\n", (Bit32u) ptr);
}


  void
FPU_printall(void)
{
  bx_panic("FPU_printall\n");
}

  unsigned
fpu_get_user(void *ptr, unsigned len)
{
  Bit32u temp32 = 0;
  if (len > 4)
    bx_panic("fpu_get_user: len=%u\n", len);
  __copy_from_user(&temp32, ptr, len);
  return(temp32);
}

  void
fpu_put_user(unsigned val, void *ptr, unsigned len)
{
  if (len > 4)
    bx_panic("fpu_put_user: len=%u\n", len);
  __copy_to_user(ptr, &val, len);
}

  void
math_abort(void *info, unsigned int signal)
{
  UNUSED(info); // info is always passed NULL
#if BX_CPU_LEVEL >= 4

// values of signal:
//   SIGILL  : opcodes which are illegal
//   SIGFPE  : unmasked FP exception before WAIT or non-control instruction
//   SIGSEGV : access data beyond segment violation
  switch (signal) {
    case SIGFPE:
      if (BX_CPU.cr0.ne == 0) {
        // MSDOS compatibility external interrupt (IRQ13)
        bx_panic("math_abort: MSDOS compatibility not supported yet\n");
        }
      BX_CPU.exception(BX_MF_EXCEPTION, 0, 0);
      // execution does not reach here

    case SIGILL:
      bx_panic("math_abort: SIGILL not implemented yet.\n");
      break;
    case SIGSEGV:
      bx_panic("math_abort: SIGSEGV not implemented yet.\n");
      break;
    }

#else
  UNUSED(signal);
  bx_panic("math_abort: CPU<4 not supported yet\n");
#endif
}


//
// Memory access functions with checks
//
  void
copy_to_user(void *to, void *from, int n)
{
  __copy_to_user(to, from, n);
}

  void
copy_from_user(void *to, void *from, int n)
{
  __copy_from_user(to, from, n);
}

//
// Memory access functions without checks
//
  void
__copy_to_user(void *to, void *_from, unsigned n)
{
  Bit32u laddr;
  bx_segment_reg_t *seg;
  unsigned char *from = (unsigned char *) _from;

  seg = &BX_CPU.sregs[fpu_iptr->seg];
  BX_CPU.write_virtual_checks(seg, (Bit32u) to, n);

  laddr = seg->cache.u.segment.base + (Bit32u) to;

  for (unsigned c=0; c<n; c++) {
    BX_INSTR_MEM_DATA(laddr, 1, BX_WRITE);
#if BX_CPU_LEVEL >= 3
    if (BX_CPU_THIS_PTR cr0.pg)
      BX_CPU.access_linear(laddr, 1, CPL==3, BX_WRITE, (void *) from);
    else
#endif
      {
      BX_INSTR_LIN_WRITE(laddr, laddr, 1);
      BX_MEM.write_physical(laddr, 1, from);
      }
    from++;
    laddr++;
    }
}


  void
__copy_from_user(void *_to, void *from, unsigned n)
{
  Bit32u laddr;
  bx_segment_reg_t *seg;
  unsigned char *to = (unsigned char *) _to;

  seg = &BX_CPU.sregs[fpu_iptr->seg];
  BX_CPU.read_virtual_checks(seg, (Bit32u) from, n);

  laddr = seg->cache.u.segment.base + (Bit32u) from;

  for (unsigned c=0; c<n; c++) {
    BX_INSTR_MEM_DATA(laddr, 1, BX_READ);
#if BX_CPU_LEVEL >= 3
    if (BX_CPU. cr0.pg)
      BX_CPU.access_linear(laddr, 1, CPL==3, BX_READ, (void *) to);
    else
#endif
      {
      BX_INSTR_LIN_READ(laddr, laddr, 1);
      BX_MEM.read_physical(laddr, 1, to);
      }
    to++;
    laddr++;
    }
}

  int
printk(const char * fmt, ...)
{
  bx_printf("printk not complete: %s\n", fmt);
  return(0); // for now
}
