/* Copyright (C) 2015 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 2007 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
#include <dpmi.h>
#include <go32.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/exceptn.h>

/* We enter with only CS known, and SS on a locked 4K stack which
   is *NOT* our SS.  We must set up everthing, including a stack swap,
   then restore it the way we found it.   C. Sandmann 4-93 */

/*
 * NOTE: we now store some information in the stack interrupt stack
 *	long 0 = flags	(currently bit 0 = this stack was malloc()ed)
 *	long 1 = block count (if non-zero, users's function won't be called)
 *	long 2 = count of how many times this wrapper has been hit
 *			(note: count includes times when we're recursively
 *                             called and don't call the user's function)
 */

#define	STACK_WAS_MALLOCED	(1 << 0)

#define	FILL	0x00

static unsigned char wrapper_intcommon[] = {
/* 00 */ 0x1e,				/*     push ds              	*/
/* 01 */ 0x06,				/*     push es			*/
/* 02 */ 0x0f, 0xa0,			/*     push fs              	*/
/* 04 */ 0x0f, 0xa8,			/*     push gs              	*/
/* 06 */ 0x60,				/*     pusha                	*/
/* 07 */ 0x66, 0xb8,			/*     mov ax,			*/
/* 09 */ FILL, FILL,			/*         _our_selector	*/
/* 0B */ 0x8e, 0xd8,			/*     mov ds, ax           	*/
/* 0D */ 0xff, 0x05, 			/*     incl			*/
/* 0F */ FILL, FILL, FILL, FILL,	/*	    _call_count		*/
/* 13 */ 0x83, 0x3d,			/*     cmpl			*/
/* 15 */ FILL, FILL, FILL, FILL,	/*         _in_this_handler	*/
/* 19 */ 0x00,				/*         $0			*/
/* 1A */ 0x75,				/*     jne			*/
/* 1B */ 0x2F,				/*         bypass		*/
/* 1C */ 0xc6, 0x05,			/*     movb			*/
/* 1E */ FILL, FILL, FILL, FILL,	/*         _in_this_handler 	*/
/* 22 */ 0x01,				/*         $1			*/
/* 23 */ 0x8e, 0xc0,			/*     mov es, ax           	*/
/* 25 */ 0x8e, 0xe0,			/*     mov fs, ax           	*/
/* 27 */ 0x8e, 0xe8,			/*     mov gs, ax           	*/
/* 29 */ 0xbb,				/*     mov ebx,			*/
/* 2A */ FILL, FILL, FILL, FILL,	/*         _local_stack		*/
/* 2E */ 0xfc,				/*     cld                  	*/
/* 2F */ 0x89, 0xe1,			/*     mov ecx, esp         	*/
/* 31 */ 0x8c, 0xd2,			/*     mov dx, ss           	*/
/* 33 */ 0x8e, 0xd0,			/*     mov ss, ax           	*/
/* 35 */ 0x89, 0xdc,			/*     mov esp, ebx         	*/
/* 37 */ 0x52,				/*     push edx             	*/
/* 38 */ 0x51,				/*     push ecx             	*/
/* 39 */ 0xe8,				/*     call			*/
/* 3A */ FILL, FILL, FILL, FILL,	/*         _rmih		*/
/* 3E */ 0x58,				/*     pop eax                 	*/
/* 3F */ 0x5b,				/*     pop ebx                 	*/
/* 40 */ 0x8e, 0xd3,			/*     mov ss, bx               */
/* 42 */ 0x89, 0xc4,			/*     mov esp, eax             */
/* 44 */ 0xc6, 0x05,			/*     movb			*/
/* 46 */ FILL, FILL, FILL, FILL,	/*         _in_this_handler	*/
/* 4A */ 0x00,				/*         $0			*/
/* 4B */ 0x61,				/* bypass:  popa		*/
/* 4C */ 0x90,				/*     nop			*/
/* 4D */ 0x0f, 0xa9,			/*     pop gs                   */
/* 4F */ 0x0f, 0xa1,			/*     pop fs                   */
/* 51 */ 0x07,				/*     pop es                   */
/* 52 */ 0x1f				/*     pop ds                   */
};

static unsigned char wrapper_intiret[] = {
/* 53 */ 0xcf,				/*     iret                     */
};

static unsigned char wrapper_intchain[] = {
/* 53 */ 0x2e, 0xff, 0x2d,		/*     jmp     cs: 		*/
/* 56 */ FILL, FILL, FILL, FILL,	/*     [_old_int+39] 		*/
/* 5A */ 0xcf,				/*     iret                     */
/* 5B */ FILL, FILL, FILL, FILL,	/*     old_address 		*/
/* 5F */ FILL, FILL,			/*     old_segment 		*/
};

ULONG32 _go32_interrupt_stack_size = 32256;

int _go32_dpmi_lock_data( ULONG32 lockaddr, ULONG32 locksize )
    {
    ULONG32 baseaddr;
    __dpmi_meminfo memregion;

    if( __dpmi_get_segment_base_address( _go32_my_ds(), &baseaddr) == -1 ) return( -1 );

    memset( &memregion, 0, sizeof(memregion) );

    memregion.address = baseaddr + (unsigned long) lockaddr;
    memregion.size    = locksize;

    if( __dpmi_lock_linear_region( &memregion ) == -1 ) return( -1 );

    return( 0 );
    }

int _go32_dpmi_lock_code( ULONG32 lockaddr, ULONG32 locksize )
    {
    ULONG32 baseaddr;
    __dpmi_meminfo memregion;

    if( __dpmi_get_segment_base_address( _go32_my_cs(), &baseaddr) == -1 ) return( -1 );

    memset( &memregion, 0, sizeof(memregion) );

    memregion.address = baseaddr + (unsigned long) lockaddr;
    memregion.size    = locksize;

    if( __dpmi_lock_linear_region( &memregion ) == -1 ) return( -1 );

    return( 0 );
    }

static int _go32_dpmi_chain_protected_mode_interrupt_vector_with_stack(int vector,
    _go32_dpmi_seginfo *info, ULONG32 stack, ULONG32 stack_length)
{
  unsigned char *wrapper;
  __dpmi_meminfo wrp;
  __dpmi_paddr pm_int;

#define	CHECK_STACK()							\
  if ((stack_length && stack_length < 512) ||				\
		(!stack_length && _go32_interrupt_stack_size < 512))	\
    return 0x8015

  CHECK_STACK();

  wrp.size = sizeof(wrapper_intcommon) + sizeof(wrapper_intchain);
  __dpmi_allocate_memory(&wrp);
  wrapper = (unsigned char *)djaddr2ptr(wrp.address);
  if (wrapper == 0)
    return 0x8015;

  if( _go32_dpmi_lock_data( wrp.address - __djgpp_base_address,
    sizeof(wrapper_intcommon) + sizeof(wrapper_intchain)) ) return 0x8015;

#define	MALLOC_STACK()					\
  do {							\
      __dpmi_meminfo stk;				\
      if (!stack_length) {				\
	  stack_length = _go32_interrupt_stack_size;	\
	  stk.size = stack_length;			\
	  __dpmi_allocate_memory(&stk);			\
	  stack = stk.address - __djgpp_base_address;	\
	  if (stack == 0) {				\
	    __dpmi_free_memory(wrp.handle);		\
	    return 0x8015;				\
	  }						\
          if( _go32_dpmi_lock_data( stack,              \
            stack_length) ) return 0x8015;              \
	  ((long *)DATA_PTR(stack))[0] = STACK_WAS_MALLOCED;\
      } else						\
	  ((long *)DATA_PTR(stack))[0] = 0;		\
      ((long *)DATA_PTR(stack))[1] = 0;			\
      ((long *)DATA_PTR(stack))[2] = 0;			\
  } while (0)

  MALLOC_STACK();

  __dpmi_get_protected_mode_interrupt_vector(vector, &pm_int);

  memcpy(wrapper, wrapper_intcommon, sizeof(wrapper_intcommon));
  memcpy(wrapper+sizeof(wrapper_intcommon), wrapper_intchain,
						     sizeof(wrapper_intchain));

#define	FILL_INT_WRAPPER()						\
  *(short *)(wrapper+0x09) = __djgpp_ds_alias;				\
  *(long  *)(wrapper+0x0F) = (long) stack + 8;				\
  *(long  *)(wrapper+0x15) = (long) stack + 4;				\
  *(long  *)(wrapper+0x1E) = (long) stack + 4;				\
  *(long  *)(wrapper+0x2A) = (long) stack + stack_length;		\
  *(long  *)(wrapper+0x3A) = info->pm_offset - ((long)wrapper + 0x3E);	\
  *(long  *)(wrapper+0x46) = (long) stack + 4

  FILL_INT_WRAPPER();

  *(long  *)(wrapper+0x56) = (long) wrapper + 0x5B;
  *(long  *)(wrapper+0x5B) = pm_int.offset32;
  *(short *)(wrapper+0x5F) = pm_int.selector;

  pm_int.offset32 = (uintptr_t)wrapper;
  pm_int.selector = _my_cs();
  __dpmi_set_protected_mode_interrupt_vector(vector, &pm_int);
  return 0;
}

int _go32_dpmi_chain_protected_mode_interrupt_vector(int vector,
						      _go32_dpmi_seginfo *info)
{
    return _go32_dpmi_chain_protected_mode_interrupt_vector_with_stack(vector,
						 info, 0, 0);
}

static int _go32_dpmi_allocate_iret_wrapper_with_stack(_go32_dpmi_seginfo *info,
			      ULONG32 stack, ULONG32 stack_length)
{
  unsigned char *wrapper;
  __dpmi_meminfo wrp;

  CHECK_STACK();

  wrp.size = sizeof(wrapper_intcommon) + sizeof(wrapper_intiret);
  __dpmi_allocate_memory(&wrp);
  wrapper = (unsigned char *)djaddr2ptr(wrp.address);

  if (wrapper == 0)
    return 0x8015;

  if( _go32_dpmi_lock_data( wrp.address - __djgpp_base_address,
    sizeof(wrapper_intcommon) + sizeof(wrapper_intiret)) ) return 0x8015;

  MALLOC_STACK();

  memcpy(wrapper, wrapper_intcommon, sizeof(wrapper_intcommon));
  memcpy(wrapper+sizeof(wrapper_intcommon), wrapper_intiret,
	 sizeof(wrapper_intiret));

  FILL_INT_WRAPPER();

  info->pm_offset = (uintptr_t)wrapper;
  info->pm_selector = _my_cs();
  return 0;
}

int _go32_dpmi_allocate_iret_wrapper(_go32_dpmi_seginfo *info)
{
    return _go32_dpmi_allocate_iret_wrapper_with_stack(info,
							0, 0);
}

int _go32_dpmi_free_iret_wrapper(_go32_dpmi_seginfo *info)
{
  char *stack;
  char *wrapper = DATA_PTR(info->pm_offset);

  stack = (char *)(*(long *)(wrapper+0x0F) - 8);
  if (*(long *) stack & STACK_WAS_MALLOCED)
      free(stack);
  free(wrapper);
  return 0;
}
