/*  XMS emulation code for DOSRUN                  *
 *  (C)1999 LGB (Gabor Lenart), lgb@vlug.vein.hu   *
 *  Can be used as written in GPL license          */


#include "config.h"

#ifdef CONFIG_XMS   /* BIG ifdef section */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "xms.h"
#include "dosrun.h"


struct xms_struct {
  byte *p;
  int size;
};

static int xms_max_size,xms_size,hma_min=HMA_MIN,hma_used=0,xms_blocks,xms_max_blocks;
static struct xms_struct **xms;


/* XMS driver install two functions on int2f (see int2f.c emulation code too) :
   one for checking installed XMS driver, and one for get a pointer to the
   driver. This pointer is used to call (far) driver functions. Since dosrun
   must return from vm86 mode on XMS function request I use an interrupt to
   do it. Somewhere between 640K and 1M I setup an int <num> and a retf
   opcode, and driver return address of these instructions on int 2f driver
   entry point request. On calling XMS functions dosrun will return int <num>,
   do the work and return to vm86, then return from far procedure on retf.
   <num> is an interrupt number which is not used by DOS programs. - LGB */


int xms_init ( int maxsize , int maxblocks )
{
  byte *p=(byte*)((XMS_PROC_SEG<<4)+XMS_PROC_OFS);
  *p=0xCD;     /* int */
  *(p+1)=XMS_INT;
  *(p+2)=0xCB;     /* retf */
  xms_max_size=xms_size=maxsize;
  xms_max_blocks=xms_blocks=maxblocks;
  if ((xms=malloc(sizeof(struct xms_struct*)*maxblocks))==NULL) return 1;
  bzero(xms,maxblocks*sizeof(struct xms_struct*));
  return 0;
}


static int xms_alloc ( int size )
{
  int a=0;
  if (size+xms_size>xms_max_size) return -0xA0; /* all available memory is allocated */
  while (xms[a]->p&&a<xms_max_blocks) a++;
  if (a==xms_max_blocks) return -0xA1; /* all available handles are allocated */
  if ((xms[a]->p=malloc(size))==NULL) return -0xA0; /* no free RAM for us */
  xms_blocks--;
  xms[a]->size=size;
  return a;
}


static int xms_free ( int handle )
{
  if (handle>=xms_max_blocks||!xms[handle]->p) return 0xA2;  /* invalid handle */
  free(xms[handle]);
  xms_blocks++;
  return 0;
}


static inline int xms_move ( int seg , int ofs )
{
  unsigned int addr=DOS_PA(seg,ofs);
  unsigned int shandle=*(word*)(addr+4);
  unsigned int dhandle=*(word*)(addr+0xA);
  unsigned int count=*(dword*)addr;
  unsigned int dofs,sofs;
  byte *s,*d;
  if (shandle) {
    if (shandle>=xms_max_blocks||xms[shandle]->p==NULL) return 0xA3; /* invalid source handle */
    sofs=*(dword*)(addr+0x6);
    if (sofs+count>xms[shandle]->size) return 0xA4; /* invalid source offset */
    s=xms[shandle]->p+sofs;
  } else s=(byte*)(*(word*)(addr+0x6)+((*(word*)(addr+0x8))<<4));
  if (dhandle) {
    if (dhandle>=xms_max_blocks||xms[dhandle]->p==NULL) return 0xA5; /* invalid dest handle */
    dofs=*(dword*)(addr+0xC);
    if (dofs+count>xms[dhandle]->size) return 0xA6; /* invalid dest offset */
    d=xms[dhandle]->p+dofs;
  } else d=(byte*)(*(word*)(addr+0xC)+((*(word*)(addr+0xE))<<4));
  memcpy(d,s,count);
  return 0;
}



static int xms_realloc ( int handle , int newsize )
{
  char *p;
  if (handle>=xms_max_blocks||!xms[handle]->p) return 0xA2;  /* invalid handle */
  if (xms_size-xms[handle]->size+newsize>xms_max_size) return 0xA0;
  if ((p=realloc(xms[handle]->p,newsize))==NULL) return 0xA0;
  xms[handle]->p=p;
  xms[handle]->size=newsize;
  return 0;
}


static int xms_info ( int handle )
{
  if (handle>=xms_max_blocks||!xms[handle]->p) return -0xA2; /* invalid handle */
  return xms[handle]->size;
}


int xms_emu ( void )
{
  switch (AH) {
    /* get version number, MUST BE FOUND SOME WORKING CONSTANTS ... */
    case 0x00 : AX=1; /* version number in BCD */
                BX=1; /* internal version number */
		DX=1; /* HMA exists */
		return 1;
    /* request for HMA */
    case 0x01 : if (hma_used) {
                  BL=0x91;
		  AX=0;
                  return 1;
                }
                if (DX<hma_min) {
                  BL=0x92;
                  AX=0;
		} else {
		  AX=1;
		  hma_used=1;
		}
		return 1;
    /* release HMA */
    case 0x02 : if (!hma_used) {
                  AX=0;
		  BL=0x93;
		  return 1;
		} else {
		  AX=1;
		  hma_used=0;
      		  return 1;
		}
    /* local and global enable A20 : dosrun can't turn off A20 so we simply
       report success on enabling A20 */
    case 0x03 :
    case 0x05 : AX=1;
                return 1;
    /* local and global disable A20 : dosrun can't turn off A20 of course so
       we report some problems about disabling A20 */
    case 0x04 :
    case 0x06 : AX=0;
                BL=0x94; /* A20 is still enabled */
		return 1;
    /* query A20 state */
    case 0x07 : AX=1; /* ALWAYS enabled in DOSRUN ! */
                BL=0; /* no error */
		return 1;		
    /* query free XMS */
    case 0x08 : AX=xms_size>>10; /* largest memory block */
                DX=xms_size>>10; /* total extended memory */
		BL=0; /* what error code, if OK ? */
		return 1;
    /* allocate XMS */
    case 0x09 : {
                  int r=xms_alloc(DX<<10);
		  if (r<0) {
		    AX=0;
		    BL=-r;
		  } else {
		    AX=1;
		    DX=r;
		  }
		  return 1;
		}
    /* free XMS */
    case 0x0A : {
                  int r=xms_free(DX);
		  if (r) {
		    AX=0;
		    BX=r;
		  } else AX=1;
		  return 1;
		}
    /* move block */
    case 0x0B : {
                  int r=xms_move(DS,SI);
		  if (r) {
		    AX=0;
		    BX=r;
		  } else AX=1;
		  return 1;
		}
    /* lock block */
    case 0x0C : AX=1; /* locking is not supported */
                BL=0xAD; /* lock failed */
		return 1;
    /* unlock block. LOCKING : locking is used to lock XMS blocks and get
       its linear address to access them directly. Since dosrun does not
       support DPMI or such, it's not use to get 32 bit addresses: locking
       is not supported because of these. */
    case 0x0D : AX=1; /* locking is not supported */
                BL=0xAA; /* block is not locked */
		return 1;
    /* get handle information */
    case 0x0E : {
                  int r=xms_info(DX);
		  if (r<0) {
		    AX=0;
		    BL=-r;
		  } else {
                    BH=0; /* lock count=0 */
                    BL=xms_blocks; /* free handles left */
  		    DX=xms_info(DX);
		    AX=1; /* ok */
		  }
		  return 1;
		} 
    /* resize */
    case 0x0F : {
                  int r=xms_realloc(DX,BX<<10);
		  if (r) {
		    AX=0;
		    BL=r;
		  } else AX=1;
		}
    /* UMB functions : does not work now */
    case 0x10 :
    case 0x11 : BL=0xB1; /* no UMB available */
                AX=0;
		return 1;
  }
#ifdef CONFIG_DEBUG_UNIMPLEMENTED_FUNC
  fprintf(stderr,"XMS: unimplemented function AX=%04Xh BX=%04Xh\n",AX,BX);
#endif
  BL=0x80; /* function is not implemented */
  AX=0;  /* error */
  return 1;
}

#endif
