/*******************************************************************************
 * 
 * (C) Copyright Microsoft Corp. 1986
 * 
 *    TITLE:	VDMM
 *
 *    MODULE:	EMM40.C - EMM 4.0 functions code.
 *
 *    VERSION:	0.00
 *
 *    DATE:	Feb 25, 1987
 *
 *******************************************************************************
 *	CHANGE LOG
 *  Date     Version	   Description
 * --------  --------	-------------------------------------------------------
 * 02/25/87	0.00	Orignal
 *
 *******************************************************************************
 *     FUNCTIONAL DESCRIPTION
 *
 * Paged EMM Driver for the iAPX 386.
 * Extra functions defined in the 4.0 spec required by Windows.
 * 
 ******************************************************************************/ 

/******************************************************************************
	INCLUDE FILES
 ******************************************************************************/ 
#include "emm.h"
/*#include "mem_mgr.h"*/


/******************************************************************************
	EXTERNAL DATA STRUCTURES
 ******************************************************************************/ 
/*
 * handle_table
 *	This is an array of handle pointers.
 *	page_index of zero means free
 */
extern struct handle_ptr handle_table[];
extern Handle_Name Handle_Name_Table[]; 	/* Handle names */
extern unsigned short	handle_table_size;	/* number of entries */
extern unsigned short	handle_count;		/* active handle count */

/*
 * EMM Page table
 *	this array contains lists of indexes into the 386
 *	Page Table.  Each list is pointed to by a handle
 *	table entry and is sequential/contiguous.  This is
 *	so that maphandlepage doesn't have to scan a list
 *	for the specified entry.
 */
extern unsigned	short *emm_page;	/* _emm_page array */
extern int	free_count;		/* current free count */
extern int	total_pages;		/* number being managed */
extern unsigned	emmpt_start;		/* next free entry in table */

/*
 * EMM free table
 *	this array is a stack of available page table entries. 
 *	each entry is an index into the pseudo page table
 */
/*extern	unsigned free_stack_count;	/* number of entries */

/*
 * Current status of `HW'. The way this is handled is that
 * when returning status to caller, normal status is reported 
 * via EMMstatus being moved into AX. Persistant errors
 * (such as internal datastructure inconsistancies, etc) are
 * placed in `EMMstatus' as HW failures. All other errors are 
 * transient in nature (out of memory, handles, ...) and are 
 * thus reported by directly setting AX. The EMMstatus variable
 * is provided for expansion and is not currently being
 * set to any other value.
 */
extern unsigned short EMMstatus;

/*
 * 4.0 EXTRAS
 */

extern unsigned short emm40_info[5];		/* hardware information */
extern struct mappable_page mappable_pages[];	/* mappable segments
					           and corresponding pages */
extern short	mappable_page_count;		/* number of entries in above */
extern short	page_frame_pages;		/* pages in the page frame */
extern short	physical_page_count;		/* number of physical pages */
/*extern char	VM1_cntxt_pages;		/* pages in a VM1 context */
/*extern char	VMn_cntxt_pages;		/* pages in a VM context */
/*extern char	VM1_cntxt_bytes;		/* bytes in a VM1 context */
/*extern char	VMn_cntxt_bytes;		/* bytes in a VM context */
extern char cntxt_pages;		/* pages in context */
extern char cntxt_bytes;		/* bytes in context */
extern unsigned short PF_Base;
extern unsigned short VM1_EMM_Pages;
/*extern unsigned short VM1_EMM_Offset;*/
extern long	page_frame_base[];
extern char	EMM_MPindex[];
extern long	OSEnabled;			/* OS/E function flag */
extern long	OSKey;				/* Key for OS/E function */

/******************************************************************************
	EXTERNAL FUNCTIONS
 ******************************************************************************/ 
extern	struct handle_ptr	*valid_handle();	/* validate handle */
extern	unsigned far	*source_addr(); 		/* get DS:SI far ptr */
extern	unsigned far	*dest_addr();			/* get ES:DI far ptr */
extern	unsigned	wcopyb();
extern	unsigned	copyout();
extern	unsigned short	Avail_Pages();


/******************************************************************************
	ROUTINES
 ******************************************************************************/ 

/*
 * Reallocate Pages
 *	parameters:
 *		bx    -- new number of pages
 *		dx    -- handle
 *	returns:
 *		bx    -- new number of pages
 *
 * Change the number of pages allocated to a handle.
 *
 * ISP 5/23/88 Updated for MEMM
 */
ReallocatePages() 
{
#define	handle	((unsigned short)regp->hregs.x.rdx)

	register struct handle_ptr	*hp;
	struct handle_ptr		*hp_save;
	unsigned			new_size;
	register unsigned 		n_pages;
	register unsigned 		next;

	if ( (hp = valid_handle(handle)) == NULL_HANDLE )
		return;		/* (error code already set) */

	setAH((unsigned char)EMMstatus);	/* Assume success */
	new_size = regp->hregs.x.rbx;
	if ( new_size == hp->page_count )
		return;				/* do nothing... */

	if ( new_size > hp->page_count ) {
		if ( new_size > total_pages ) {
			setAH(NOT_ENOUGH_EXT_MEM);
			return;
		}
		n_pages = new_size - hp->page_count;
		if ( n_pages > Avail_Pages() ) {
			setAH(NOT_ENOUGH_FREE_MEM);
			return;
		}
		if ( hp->page_count == 0 )
			next = hp->page_index = emmpt_start;
		else
			next = hp->page_index + hp->page_count;
		hp->page_count = new_size;
		if ( next != emmpt_start ) {
				/*
				 * Must shuffle emm_page array to make room
				 * for the extra pages.  wcopyb correctly
				 * handles this case where the destination
				 * overlaps the source.
				 */
			wcopyb(emm_page+next, emm_page+next+n_pages,
			       emmpt_start - next);
			/* Now tell other handles where their pages went */
			hp_save = hp;
			for ( hp = handle_table;
			      hp < &handle_table[handle_table_size]; hp++ )
				if ( hp->page_index != NULL_PAGE &&
				     hp->page_index >= next )
					hp->page_index += n_pages;
			hp = hp_save;
		}
		emmpt_start += n_pages;
		if ( get_pages(n_pages, next) == NULL_PAGE) { /* strange failure */
			setAH(NOT_ENOUGH_FREE_MEM);
			new_size = hp->page_count - n_pages;  /* as it was! */
			setBX(new_size);
			goto shrink;			/* and undo damage */
		}
	} else {
		/* Shrinking - make handle point to unwanted pages */
	shrink:
		hp->page_count -= new_size;
		hp->page_index += new_size;
		free_pages(hp);    /* free space in emm_page array */
		/* Undo damage to handle, the index was not changed */
		hp->page_count = new_size;
		hp->page_index -= new_size;
	}

#undef	handle
}

/*
 * UndefinedFunction
 *
 * An undefined or unsupported function.
 *
 * 05/10/88  ISP  No update needed
 */
UndefinedFunction() 
{
	setAH(INVALID_FUNCTION);
}

/*
 * Get Mappable Physical Address Array
 *	parameters:
 *		al == 0
 *		es:di -- destination
 *	returns:
 *		cx    -- number of mappable pages
 *
 *	parameters:
 *		al == 1
 *	returns:
 *		cx    -- number of mappable pages
 * 
 * Get the number of mappable pages and the segment address for each
 * physical page.
 *
 * ISP	5/23/88 Updated for MEMM.  u_ptr made into a far pointer.
 */
GetMappablePAddrArray() 
{
	unsigned far *u_ptr;
	int	n_pages;
	int	i;
	struct mappable_page *mp = mappable_pages;

		n_pages = mappable_page_count;

	if ( regp->hregs.h.ral == 0 ) {
		if ( n_pages > 0 ) {
			u_ptr = dest_addr();		/* ES:DI */
			for (i=0 ; i < 48 ; i++)
				if (EMM_MPindex[i] != -1)
					copyout(((struct mappable_page far *)u_ptr)++,
						mp + EMM_MPindex[i],
						sizeof(struct mappable_page) );
		}
	} else if ( regp->hregs.h.ral != 1 ) {
		setAH(INVALID_SUBFUNCTION);
		return;
	}
	setCX(n_pages);
	setAH((unsigned char)EMMstatus);
}

/*
 * Get Expanded Memory Hardware Information
 *	parameters:
 *		al == 0
 *		es:di -- user array
 *	returns:
 *		es:di[0] = raw page size in paragraphs
 *		es:di[2] = number of EXTRA fast register sets
 *		es:di[4] = number of bytes needed to save a context 
 *		es:di[6] = number of settable DMA channels
 *
 *	parameters:
 *		al == 1
 *	returns:
 *		bx = number of free raw pages
 *		dx = total number of raw pages
 *		
 * ISP	5/23/88 Updated for MEMM. Made u_ptr into far ptr.
 */
GetInformation() 
{
	unsigned far *u_ptr;
	unsigned pages;

	if ( OSEnabled >= OS_DISABLED ) {
		setAH(ACCESS_DENIED);		/* Denied by operating system */
		return;
	}

	if ( regp->hregs.h.ral == 0 ) {
		u_ptr = dest_addr();		/* ES:DI */
		emm40_info[2] = (short)cntxt_bytes;	/* update size */
		copyout(u_ptr, emm40_info, sizeof(emm40_info));
		setAH((unsigned char)EMMstatus);
	} else if ( regp->hregs.h.ral == 1 ) {
		GetUnallocatedPageCount();	/* Use existing code */
	} else
		setAH(INVALID_SUBFUNCTION);
}

/*
 * GetSetHandleAttribute
 *
 *	parameters:
 *		al == 0
 *	returns:
 *		al == 0 -- volatile handles
 *
 *	parameters:
 *		al == 1
 *	returns:
 *		ah = 91h -- Feature not supported
 *
 *	parameters:
 *		al == 2
 *	returns:
 *		al == 0 -- Supports ONLY volatile handles
 *
 * 05/09/88 ISP No update needed
 */
GetSetHandleAttribute()
{
#define	handle	((unsigned short)regp->hregs.x.rdx)

	if ( regp->hregs.h.ral == 0 ) {
		if (valid_handle(handle) == NULL_HANDLE)
			return;						/* (error code already set) */
		setAX(EMMstatus << 8);		/* AL = 0 [volatile attribute] */
	} else if ( regp->hregs.h.ral == 1 ) {
		setAH(FEATURE_NOT_SUPPORTED);
	} else if ( regp->hregs.h.ral == 2 ) {
		setAX(EMMstatus << 8);		/* AL = 0 [volatile attribute] */
	} else
		setAH(INVALID_SUBFUNCTION);

#undef	handle
}




/*
 * GetSetHandleName
 *
 *  Subfunction 0 Gets the name of a given handle
 *  Subfunction 1 Sets a new name for handle
 *
 *	parameters:
 *		al == 0
 *		es:di == Data area to copy handle name to
 *		dx    -- handle
 *	returns:
 *		[es:di] == Name of DX handle
 *
 *	parameters:
 *		al == 1
 *		ds:si == new handle name
 *		dx    -- handle
 *	returns:
 *		ah = Status
 *
 * ISP 5/23/88 Updated for MEMM. Name made into far *. Copyin routine used
 *	       to copy name in into handle name table.
 */
GetSetHandleName()
{
	register unsigned short handle = ((unsigned short)regp->hregs.x.rdx);
	register char far *Name;

    /* Validate subfunction */
	if ( (regp->hregs.h.ral != 0) && (regp->hregs.h.ral != 1) ) {
		setAH(INVALID_SUBFUNCTION);
		return;
	}

    /* Validate handle */

	if ( valid_handle(handle) == NULL_HANDLE )
		return; 	/* (error code already set) */

    /* Implement subfunctions 0 and 1 */
	if ( regp->hregs.h.ral == 0 ) {
		Name = (char far *)dest_addr(); 	   /* ES:DI */
		copyout(Name, Handle_Name_Table[handle & 0xFF], Handle_Name_Len);
		setAH((unsigned char)EMMstatus);
	} else if ( regp->hregs.h.ral == 1 ) {
		GetHandleDirectory();		/* See if already there */
		switch ( regp->hregs.h.rah ) {
		case NAMED_HANDLE_NOT_FOUND:
			break;
		case DUPLICATE_HANDLE_NAMES:
			return;
		default:
			if ( handle == regp->hregs.x.rdx )
				break;		/* same handle, OK */
			regp->hregs.x.rdx = handle;
			setAH(DUPLICATE_HANDLE_NAMES);
			return;
		}
		Name = (char far *)source_addr();
		copyin(Handle_Name_Table[handle & 0xFF], Name, Handle_Name_Len);
		setAH((unsigned char)EMMstatus);
	} else
		setAH(INVALID_SUBFUNCTION);

}




/*
 * GetHandleDirectory
 *
 *  Subfunction 0 Returns a directory of handles and handle names
 *  Subfunction 1 Returns the handle specified by the name at [ds:si]
 *
 *	parameters:
 *		al == 0
 *		es:di == Data area to copy handle name to
 *	returns:
 *		al == Number of entries in the handle_dir array
 *		[es:di] == Handle_Dir array
 *
 *	parameters:
 *		al == 1
 *		[ds:si] == Handle name to locate
 *	returns:
 *		ah == Status
 *
 *	parameters:
 *		al == 2
 *	returns:
 *		bx == Total handles in system
 *
 * ISP 5/23/88 Updated for MEMM.  nameaddress and dir_entry made into far *
 *	       copyin routine used to copy name into local area for search.
 */
GetHandleDirectory()
{
	char far			*NameAddress;
	register struct handle_ptr	*hp;
	struct Handle_Dir_Entry far	*Dir_Entry;
	unsigned short			Handle_Num, Found;
/*
 * since all local variables are allocated on stack (SS seg)
 * and DS and SS has grown apart (ie DS != SS),
 * we need variables in DS seg (ie static variables) to pass
 * to copyout(),copyin() and Names_Match() which expects those
 * parameters that are near pointers to be in DS
 *
 * PC 08/03/88
 */
	static Handle_Name			Name;
	static unsigned short		Real_Handle;

	if ( regp->hregs.h.ral == 0 ) {
		Dir_Entry = (struct Handle_Dir_Entry far *)dest_addr();
		hp = handle_table;
		for (Handle_Num = 0; Handle_Num < handle_table_size; Handle_Num++) {
		    if ( hp->page_index != NULL_PAGE) {
			Real_Handle =  Handle_Num;
			copyout(Dir_Entry, &Real_Handle, sizeof(short));
			copyout(Dir_Entry->Dir_Handle_Name, Handle_Name_Table[Handle_Num], Handle_Name_Len);
			Dir_Entry++;
		    } hp++;
		} setAX((EMMstatus << 8) + handle_count);
	} else if ( regp->hregs.h.ral == 1 ) {
		NameAddress = (char far *)source_addr();
		copyin(Name, NameAddress, Handle_Name_Len);
		hp = handle_table;
		Found = 0;
		Handle_Num = 0;
		while ((Handle_Num < handle_table_size) && (Found < 2)) {
		    if ( hp->page_index != NULL_PAGE ) {
			if (Names_Match(Name, Handle_Name_Table[Handle_Num])) {
			    Found++;
			    Real_Handle = Handle_Num;
			}
		    } hp++;
		    Handle_Num++;
		}
		switch (Found) {
		    case 0:
			setAH((unsigned char)NAMED_HANDLE_NOT_FOUND);
			break;
		    case 1:
			setDX(Real_Handle);
			setAH((unsigned char)EMMstatus);
			break;
		    default:
			setAH((unsigned char)DUPLICATE_HANDLE_NAMES);
		}

	} else if ( regp->hregs.h.ral == 2 ) {
		setBX(handle_table_size);
		setAH((unsigned char)EMMstatus);
	} else
		setAH(INVALID_SUBFUNCTION);

#undef	handle
}

/*
 * Prepare For Warm Boot
 *
 *	Always ready to reboot the system so just return status = OK
 *
 *	parameters:
 *		None
 *	returns:
 *		AH = EMMstatus
 *
 * 05/09/88 ISP No update needed.
 *
 */
PrepareForWarmBoot()
{
	setAH((unsigned char)EMMstatus);
}

/*
 * Enable/Disable OS/E Function Set Functions
 *
 *	Enable/Disable access to functions 26, 28 and 30
 *
 *	parameters:
 *		AL = 0		Enable Functions
 *		AL = 1		Disable Functions
 *		AL = 2		Return Access Key
 *		BX, CX		Access Key
 *	returns:
 *		AH = EMMstatus
 *		BX, CX		Access Key if successful
 *
 * 05/09/88 ISP Updated for MEMM. Removed check for pCurVMID
 *
 */
OSDisable()
{
	unsigned char function = regp->hregs.h.ral;

	if ( function > 2 ) {
		setAH(INVALID_SUBFUNCTION);
		return;
	}

	if ( OSEnabled == OS_IDLE ) {		/* First invocation */
		if ( function == 2 ) {
			setAH(ACCESS_DENIED);
			return;
		}
		OSKey = Get_Key_Val();		/* Suitably random number */
		regp->hregs.x.rbx = (short)OSKey;
		regp->hregs.x.rcx = (short)(OSKey >> 16);
	} else {				/* Check Key */
		if ( (short)OSKey != regp->hregs.x.rbx
		     || (short)(OSKey >> 16) != regp->hregs.x.rcx ) {
			setAH(ACCESS_DENIED);
			return;
		}
	}
	if ( function == 0 )			/* enable */
		OSEnabled = 1;
	else if ( function == 1 )		/* disable */
		OSEnabled = 2;
	else if ( function == 2 )		/* return key */
		OSEnabled = 0;

	setAH((unsigned char)EMMstatus);
}


