#include "rsys/common.h"
#include "dosmem.h"
#include "dpmilock.h"
#include "rsys/blockinterrupts.h"
#include <go32.h>
#include <dpmi.h>
#include <sys/exceptn.h>


/* Use this memory for temporary stack during calls to DOS memory.
 * DOS_STACK_TOP is the real mode offset you should use.
 */
uint16 dos_buf_segment;
uint16 dos_buf_selector;

/* This selector refers to DOS conventional memory. */
uint16 dos_rm_selector;

/* This is our %ds selector.  We copy %ds to %ss right away in main. */
uint16 dos_pm_ds;

/* This is an alias for %ds that will always be valid, even when
 * ctrl-break is hit.  We use this at interrupt time.
 */
uint16 dos_pm_interrupt_ds;

/* This is our %cs selector. */
uint16 dos_pm_cs;


/* Attempts to allocate a block of DOS memory of size SIZE which does
 * not span a 64K bank.  We need to make this guarantee so that calls
 * to floppy disk BIOS routines will work (DMA hardware requires
 * things not to cross 64K boundaries).  Returns TRUE iff successful,
 * else FALSE.
 */
static boolean_t
alloc_single_bank_dos_block (int size, uint16 *segp, uint16 *selp)
{
  int dos_sel[16], i, j, sel, seg;
  int paragraphs;
  boolean_t success_p;

warning_trace_info (NULL);

  if (size > 64 * 1024)
    abort ();

warning_trace_info (NULL);
  paragraphs = (size + 15) / 16;

warning_trace_info (NULL);
  /* Default values. */
  seg = sel = -1;
  success_p = FALSE;

warning_trace_info (NULL);

  /* Allocate a bunch of selectors until we happen to get one that
   * doesn't cross a 64K mark.
   */
  for (i = 0; i < NELEM (dos_sel); i++)
    {
warning_trace_info (NULL);

      seg = __dpmi_allocate_dos_memory (paragraphs, &sel);

      /* Did we run out of memory? */
      if (seg == -1)
	break;

warning_trace_info (NULL);

      dos_sel[i] = sel;

warning_trace_info (NULL);


      /* Make sure the start and end addresses are in the same 64K
       * bank.  Since these are segment values, they represent the
       * linear address divided by 16.
       */
      if ((seg & 0xFFF) == ((seg + paragraphs - 1) & 0xFFF))
	{
	  success_p = TRUE;
warning_trace_info (NULL);
	  break;
	}

warning_trace_info (NULL);
    }

warning_trace_info (NULL);

  /* Free up all the failed selectors. */
  for (j = 0; j < i; j++)
    {
warning_trace_info (NULL);
      gui_assert (dos_sel[j] != -1 && dos_sel[j] != sel);
      __dpmi_free_dos_memory (dos_sel[j]);
    }

warning_trace_info (NULL);
  /* Record the successful selector, if there is one. */
  if (!success_p)
    *segp = *selp = 0;
  else
    {
warning_trace_info (NULL);
      gui_assert (seg != -1 && sel != -1);
warning_trace_info (NULL);
      *segp = seg;
      *selp = sel;
    }

warning_trace_info (NULL);

  return success_p;
}


boolean_t
init_dos_memory (void)
{
  asm ("movw %%cs,%0" : "=g" (dos_pm_cs));
  asm ("movw %%ds,%0" : "=g" (dos_pm_ds));
  dos_rm_selector = _go32_info_block.selector_for_linear_memory;
  dos_pm_interrupt_ds = __djgpp_ds_alias;

warning_trace_info ("enter");

  /* Allocate a DOS buffer. */
  if (!alloc_single_bank_dos_block (DOS_BUF_SIZE, &dos_buf_segment,
				    &dos_buf_selector))
    {
      warning_unexpected ("Unable to allocate DOS memory.");
      return FALSE;
    }

  warning_trace_info ("DOS scratch buffer at 0x%04X:0000",
		      (unsigned) dos_buf_segment);

  /* Wire these down so we can use them at interrupt time. */
  dpmi_lock_memory (&dos_rm_selector, sizeof dos_rm_selector);
  dpmi_lock_memory (&dos_pm_ds, sizeof dos_pm_ds);
  dpmi_lock_memory (&dos_pm_interrupt_ds, sizeof dos_pm_interrupt_ds);
  dpmi_lock_memory (&dos_pm_cs, sizeof dos_pm_cs);

  /* NOTE: we can't free our DOS memory at atexit time since we use it
   * in other shutdown routines.
   */

  /* Let __dpmi_int (heavily used by libc) use our extra DOS stack
   * space, just to be safe.
   */
  __dpmi_int_ss = dos_buf_segment;
  __dpmi_int_sp = DOS_STACK_TOP;

  return TRUE;
}
