/*
Copyright Notice
================
BOCHS is Copyright 1994 by Kevin P. Lawton.

BOCHS is shareware for PERSONAL USE only.

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
              528 Lexington St.
              Waltham, MA 02154

    EMail:    bochs@tiac.net
*/



#define BX_MEMORY_C 1



#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "bx_bochs.h"
#include "iodev/iodev.h"



#define BX_PAGE_UNINITIALIZED 0x01
#define BX_PAGE_IO_MAPPED     0x02



bx_phy_mem_page_t bx_phy_mem_pages[BX_NUM_PAGES];



Bit32u bx_translate_linear(Bit32u addr, Bit8u pl, Bit8u rw);
void bx_access_physical(Bit32u addr, Bit8u len, Bit8u rw, void *data);


Boolean report_vm_access = 1;

  char *
bx_strseg(bx_segment_reg_t *seg)
{
  if (seg == &bx_cpu.es) return("ES");
  else if (seg == &bx_cpu.cs) return("CS");
  else if (seg == &bx_cpu.ss) return("SS");
  else if (seg == &bx_cpu.ds) return("DS");
  else if (seg == &bx_cpu.fs) return("FS");
  else if (seg == &bx_cpu.gs) return("GS");
  else {
    bx_printf(0, "undefined segment passed to bx_strseg()!\n");
    return("??"); /* keep compiler happy */
    }
}

  void
bx_init_memory(int memsize)
{
  int i;

  for (i=0; i<BX_NUM_PAGES; i++) {
    bx_phy_mem_pages[i].mem = NULL;
    bx_phy_mem_pages[i].flags = BX_PAGE_UNINITIALIZED;
    bx_phy_mem_pages[i].read_funct = NULL;
    bx_phy_mem_pages[i].write_funct = NULL;
    }
}

  void
bx_virtual_block_write(bx_segment_reg_t *seg, Bit32u offset, Bit32u length,
  void *data)
{
  Bit32u page_index, chunksize;
  Bit32u phy_addr;
  Bit8u  *data_ptr;

  data_ptr = (Bit8u *) data;
  phy_addr = (seg->selector.value << 4) + offset;

  do {
    page_index = phy_addr / BX_PAGE_SIZE;
    offset     = phy_addr % BX_PAGE_SIZE;

    if (bx_phy_mem_pages[page_index].flags & BX_PAGE_UNINITIALIZED) {
      bx_phy_mem_pages[page_index].mem =
          (Bit8u *) malloc((size_t) BX_PAGE_SIZE);
      if (!bx_phy_mem_pages[page_index].mem)
          bx_printf(1, "malloc failed allocating memory for physical memory emulation!\n");
      memset(&bx_phy_mem_pages[page_index].mem[0], 0, BX_PAGE_SIZE);
      bx_phy_mem_pages[page_index].flags -= BX_PAGE_UNINITIALIZED;
      }

    chunksize = BX_PAGE_SIZE - offset;
    if (length < chunksize) chunksize = length;
    memcpy(&bx_phy_mem_pages[page_index].mem[offset], data_ptr, chunksize);
    length -= chunksize;
    data_ptr += chunksize;
    phy_addr += chunksize;
    } while (length > 0);
}

  void
bx_virtual_block_read(bx_segment_reg_t *seg, Bit32u offset, Bit32u length,
  void *data)
{
  int i;
  Boolean save_report_vm_access;

  save_report_vm_access = report_vm_access;
  report_vm_access = 0;

  for (i=0; i < length; i++) {
    bx_access_virtual(seg, offset + i, 1, BX_READ, ((Bit8u*) data) + i);
    }
  report_vm_access = save_report_vm_access;
}

  void
bx_access_virtual(bx_segment_reg_t *seg, Bit32u offset, Bit8u length, 
    Bit8u rw, void *data)
{

#ifdef BX_DEBUG
  if (report_vm_access && (seg!=&bx_cpu.cs))
    bx_printf(0, "%-5s access to vm:%s[%u = %08x]*%u\n",
      (rw==BX_READ)?"read":"write", bx_strseg(seg),
      (unsigned) offset, (unsigned) offset, (unsigned) length);
#endif


  /* if real or virtual 8086 mode */
  if (!bx_cpu.cr0.pe) {
    if (offset + length  >  0x10000) {
bx_printf(0, "SEG EXCEPTION:  %x:%x + %x\n", (int) seg->selector.value,
(int) offset, (int) length);
      if (seg == &bx_cpu.ss) bx_segment_exception(BX_SS_EXCEPTION, 0);
      else bx_segment_exception(BX_GP_EXCEPTION, 0);
      }

    bx_access_linear((seg->selector.value << 4) + offset,
      length, bx_cpu.cs.selector.rpl, rw, (void *) data);
    }
  /* else protected mode */
  else {
    bx_printf(1, "protected mode unsupported!\n");
    }
}


  void
bx_segment_exception(int vector, Bit16u error_code)
{
  bx_printf(1, "segment exception!\n");
}


  void
bx_access_linear(Bit32u laddress, Bit8u length, Bit8u pl, 
    Bit8u rw, void *data)
{
  Bit32u paddress1, paddress2;
  Bit8u  len1, len2;


  if ( ((laddress % 4096) + length) > 4096) {
    /* data spans multiple pages */
#ifdef LITTLE_ENDIAN
    len1 = 4096 - (laddress % 4096);
    paddress1 = bx_translate_linear(laddress, pl, rw);

    len2 = length - len1;
    paddress2 = bx_translate_linear(laddress + len1, pl, rw);

    bx_access_physical(paddress1, len1, rw, data);
    bx_access_physical(paddress2, len2, rw, data + len1);

#else /* BIG_ENDIAN */
    len2 = 4096 - (laddress % 4096);
    paddress2 = bx_translate_linear(laddress, pl, rw);

    len1 = length - len2;
    paddress1 = bx_translate_linear(laddress + len2, pl, rw);

    bx_access_physical(paddress1, len1, rw, ((Bit8u *)data) + len2);
    bx_access_physical(paddress2, len2, rw, data);
#endif

    }
  else {
    paddress1 = bx_translate_linear(laddress, pl, rw);
    bx_access_physical(paddress1, length, rw, data);
    }
}



  Bit32u
bx_translate_linear(Bit32u addr, Bit8u pl, Bit8u rw)
{
  if ( !bx_cpu.cr0.pg )
    return(addr);
  else {
    bx_printf(1, "paging not supported!\n");
    return(0);  /* keep compiler from complaining */
    }
}



  void
bx_access_physical(Bit32u addr, Bit8u len, Bit8u rw, void *data)
{
  Bit32u page_index, offset;
  Bit32u i;
  Bit8u  *data_ptr;




#ifdef LITTLE_ENDIAN
  data_ptr = (Bit8u *) data;

  for (i=1; i<=len; i++, addr++, data_ptr++) {
#else /* BIG_ENDIAN */
  data_ptr = (Bit8u *) data + sizeof(Bit8u) * (len - 1);

  for (i=1; i<=len; i++, addr++, data_ptr--) {
#endif

    page_index = addr / BX_PAGE_SIZE;
    offset     = addr % BX_PAGE_SIZE;

    if ( page_index >= BX_NUM_PAGES )
      bx_printf(1, "access beyond physical memory boundary!\n");

    if (bx_phy_mem_pages[page_index].flags) {
      if (bx_phy_mem_pages[page_index].flags & BX_PAGE_UNINITIALIZED) {
	bx_phy_mem_pages[page_index].mem =
          (Bit8u *) malloc((size_t) BX_PAGE_SIZE);
        if (!bx_phy_mem_pages[page_index].mem)
          bx_printf(1, "malloc failed allocating memory for physical memory emulation!\n");
        memset(&bx_phy_mem_pages[page_index].mem[0], 0, BX_PAGE_SIZE);
        bx_phy_mem_pages[page_index].flags -= BX_PAGE_UNINITIALIZED;
        }

      if (bx_phy_mem_pages[page_index].flags & BX_PAGE_IO_MAPPED) {
        if (rw == BX_READ)
          *(Bit8u *) data_ptr = bx_phy_mem_pages[page_index].read_funct(addr);
        else
          bx_phy_mem_pages[page_index].write_funct(addr, *(Bit8u *) data_ptr);
        continue;
        }
      }

#if 0
if (addr == (0x10E0 + 0x0584)) {
  bx_printf(0, "!!! %s ACCESS TO BIOS DATA AREA location %03x\n",
    (rw==BX_READ)?"read":"write", (int) addr);
  }
#endif

    if (rw == BX_READ)
      *(Bit8u *) data_ptr = bx_phy_mem_pages[page_index].mem[offset];
    else
      bx_phy_mem_pages[page_index].mem[offset] = *(Bit8u *) data_ptr;

    } /* for (i=... */
}

  int
bx_register_mem_handler(bx_memdev_t device)
{
  Bit32u i, pageno;

  if ( (device.start_addr % BX_PAGE_SIZE) != 0) {
    bx_printf(0, "bochs: error registering memory access handler '%s':\n",
      device.handler_name);
    bx_printf(1, "  start_addr must begin on a %d byte page boundary.\n",
      BX_PAGE_SIZE);
    }

  if ( (device.end_addr % BX_PAGE_SIZE) != (BX_PAGE_SIZE - 1)) {
    bx_printf(0, "bochs: error registering memory access handler '%s':\n",
      device.handler_name);
    bx_printf(1, "  end_addr must end on a %d byte page boundary.\n",
      BX_PAGE_SIZE);
    }

  if ( device.end_addr > (BX_NUM_PAGES * BX_PAGE_SIZE) ) {
    bx_printf(0, "bochs: error registering memory access handler '%s':\n",
      device.handler_name);
    bx_printf(1, "  end_addr greater than last memory address of %d\n",
      BX_NUM_PAGES * BX_PAGE_SIZE);
    }

  for (i = device.start_addr; i < device.end_addr; i += BX_PAGE_SIZE) {
    pageno = i / BX_PAGE_SIZE;
    bx_phy_mem_pages[pageno].flags |= BX_PAGE_IO_MAPPED;
    bx_phy_mem_pages[pageno].read_funct = device.read_funct;
    bx_phy_mem_pages[pageno].write_funct = device.write_funct;
    }
  return(1);
}

  void
bx_set_interrupt_vector(int interrupt, Bit32u address)
{
  Bit16u ip, cs;

  ip = (Bit16u) (address & 0xFFFF);
  cs = (Bit16u) ((address & 0xF0000) >> 4);
  bx_access_physical(interrupt*4, 2, BX_WRITE, &ip);
  bx_access_physical(interrupt*4 + 2, 2, BX_WRITE, &cs);
}
