/********************************************************************
*   DrvSystem.c
*
*   Macintosh drivers system subsystem implementation.
*
*   Copyright (c) 1994-1997, Willows Software Inc.  All rights reserved.  
********************************************************************/

/* System includes */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unix.h>
#include <console.h>
#include <Threads.h>
#include <SegLoad.h>

/* Driver includes */
#include "DrvHook.h"
#include "DrvGlobals.h"
#include "DrvSystem.h"
#include "Log.h"
#include "DrvUtils.h"
#include "DrvKeyboard.h"
#include "DrvErrors.h"
#include "DrvEvents.h"

typedef void (*THREADENTRYFUNC)();

/* Imported prototypes */
extern int InitOS(void);

//#define ALLOCUSINGNEWPTR			1		//Use toolbox NewPtr() for memory allocation.  This is not efficient but allows QC and Jasik to do memory checks.

//#define USELIBRARYALLOCATION	1		//This will do memory allocation in the library, mainly for debugging (only works if ALLOCUSINGNEWPTR is NOT defined)
#ifdef USELIBRARYALLOCATION
	#define malloc LibMalloc
	#define free LibFree
	#define realloc LibRealloc
#endif

#ifdef MALLOCMALLOCINFO
#define MALLOCINFOMALLOC	malloc
#define MALLOCINFOFREE		free
#else
#define MALLOCINFOMALLOC	DrvMalloc
#define MALLOCINFOFREE		DrvFree
#endif

static void InitThreads(void);
static DWORD DrvCreateThread(THREADENTRYFUNC EntryProc);
static DWORD DrvFreeThread(ThreadID thread);
static DWORD DrvYieldToThread(ThreadID oldThread, ThreadID newThread);
static DrvLoadLibrary(char *);
pascal void *ThreadEntryProc(void *threadParam);

extern char gOpenDocument[256];
char *myArgV[3];


#define LASTHANDLESIZE 4096		/* Memory that is allocated at start, freed when needed for emergency exit */
Handle gLastHandle = NULL;			/* Last ditch memory placeholder */

/* Thread support */
#define kDefaultStackSize 		0 				/* System determines stack size */
#define kNumOfThreads				5				/* Number of threads in our pool */
static Boolean gCanDoThreads = FALSE;		/* Only true if thread manager is present */


/********************************************************************
*   PrivateSystemHook
*
********************************************************************/
DWORD PrivateSystemHook(DWORD dwCode, LPARAM dwParam1,	LPARAM dwParam2, LPVOID lpStruct)
{
Handle h;

    switch(dwCode) {
	/* dwParam1 - compatibility mask */
	/* dwParam2 - init/exit flag */
	case DSUBSYSTEM_INIT:
		if (dwParam2) {
			/* Allocate and lock the alert resources so they are there in an */
			/*   emergency low mem condition. */
		    h = GetResource('DITL', SIMPLEALERT_ID);
	   		HNoPurge(h);
	    	h = GetResource('ALRT', SIMPLEALERT_ID);
		    HNoPurge(h);
	    	gLastHandle = NewHandle(LASTHANDLESIZE);       /* guarantee we will have memory for "last words" */
 
 			InitThreads();				/* Initialize the thread manager and pools */
  		}
		break;
		
	case DSUBSYSTEM_GETCAPS:
	case DSUBSYSTEM_EVENTS:
	    return 1L;
		
 	/* dwParam1 - number of bytes to allocate */
   case PSSH_ALLOCMEM:
		return (DWORD) DrvMalloc(dwParam1);
			
	/* dwParam1 - number of bytes */
	/* lpStruct - pointer to realloc */
	case PSSH_REALLOCMEM:
		return (DWORD) DrvRealloc(lpStruct, dwParam1);

	/* lpStruct - address to free */
	case PSSH_FREEMEM:
		DrvFree(lpStruct);
		return 1L;

	case PSSH_CREATETHREAD:
	    return DrvCreateThread((THREADENTRYFUNC) dwParam1);
	    
	case PSSH_FREETHREAD:
	    return DrvFreeThread(dwParam1);

	case PSSH_YIELDTOTHREAD:
	    return DrvYieldToThread(dwParam1, dwParam2);

	case PSSH_CANDOTHREADS:
	    return gCanDoThreads;

	case PSSH_GETMAINTHREAD:
	    return (DWORD) kApplicationThreadID;

	case PSSH_MAKEEXEDATA:
		/* Call the PowerPC call to make data executable (by flushing the instruction cace?) */
		MakeDataExecutable(lpStruct, dwParam1);
		return (TRUE);
		
	case PSSH_GETFREEDISKSPACE:				/* stubbed - currently the library uses statfs */
	case PSSH_CREATEPSDKEY:					/* stubbed */
	case PSSH_DELETEPSDKEY:					/* stubbed */
	case PSSH_SETPSD:						/* stubbed */
	case PSSH_GETPSD:						/* stubbed */
		return (1L);

	case PSSH_LOADLIBRARY:
	    return DrvLoadLibrary((char *)lpStruct);

	case PSSH_SLEEP:						/* stubbed */
	    return (1L);

	default:
	    return (0L);						/* eventually, make this blow up */
	}
}


/********************************************************************
*   LibMalloc
*
*	Call up to the library to do memory allocation.
*	This is for instances when the library will be freeing the memory.
********************************************************************/
void *LibMalloc(size_t theSize)
{
void *newPtr;

	LOGSTR((LF_PSH,"LibMalloc %d\n", theSize));
	
	newPtr = (void*) LibCallback(TWINLIBCALLBACK_MEMORY, TLC_MEMALLOC, theSize, NULL);		/* Post close to active window */

	if (newPtr == NULL)
		SystemError(0, SYSERR_MEMORY, 3, theSize);

	LOGSTR((LF_PSH,"LibMalloc address %x %x\n", newPtr, theSize));
	
	return(newPtr);
}


/********************************************************************
*   LibFree
*
*	Call up to the library to memory that was allocated using the LibMalloc() call.
*	This is for instances when the library will be freeing the memory.
********************************************************************/
void LibFree(void *ptr)
{
	LOGSTR((LF_PSH,"LibFree %x\n", ptr));

	if (ptr == NULL)
		return;
		
	LibCallback(TWINLIBCALLBACK_MEMORY, TLC_MEMFREE, 0, ptr);		/* Post close to active window */
}


/********************************************************************
*   LibRealloc
*
*	Call up to the library to reallocate memory that was allocated using the LibMalloc() call.
*	This is for instances when the library will be freeing the memory.
********************************************************************/
void *LibRealloc(void *ptr, size_t theSize)
{
void *newPtr;

	LOGSTR((LF_PSH,"LibRealloc %x for %x bytes\n", ptr, theSize));

	/* Call up to the library */
	newPtr =  (void*) LibCallback(TWINLIBCALLBACK_MEMORY, TLC_MEMREALLOC, theSize, ptr);
	
	/* Post alert if action failed */
	if (newPtr == NULL)
		SystemError(0,SYSERR_MEMORY,4, theSize);
	
	return(newPtr);
}



/********************************************************************
*	DrvFatalExit
*
*	Route all driver exits through here.
********************************************************************/
void DrvFatalExit(short exitCode)
{
#pragma unused (exitCode)

		ExitToShell();				// LATER: Exit up through the library.

}


/********************************************************************
*	DropIntoDebugger
*
*	Called to drop into debugger, msg is optional.
********************************************************************/
void DropIntoDebugger(char *msg)
{
#ifdef DEBUGGERENABLED
	if (msg) {
#ifdef mac_ppc	
		DebugStr(C2PStr(msg));
#else
		SysBreakStr(C2PStr(msg));
#endif
	}
	else {
#ifdef mac_ppc	
		Debugger();
#else
		SysBreak();
#endif
	}
#endif			/* DEBUGGER */	
}


/********************************************************************
*   DrvGetCommandLine
*
*	Sets up the command line arguments in one of three ways,
*	A) If AppleEvent ODOC is recieved, ONE item will be put into the argument list.
*	B) If no AppleEvent, holding down option key opens the command line arg window.
*	C) Otherwise, the argument list is empty.
********************************************************************/
int DrvGetCommandLine(int argc, char ***argv)
{
	/* Safety, just in case someone has modified our TwinStartup.c or TwinRT.c and may have actual stuff here. */
	if (argc !=0)
		return(argc);

	/* Make sure the ToolBox has been initialized. */
	/* Caution, many subsystems may have not been initialized yet. */
	InitOS();
	
	/* Flush the que for the first apple event */
	GetOneHighLevelEvent(240);		/* Wait at least 240 ticks (arbitrary) for event to enter que */
		
	/* For a great hack, we will see if the apple event ODOC placed something */
	/*  in the gOpenDocument variable for us to open. */
	if (strlen(gOpenDocument) > 0) {
		myArgV[0] = malloc(10);
		strcpy(myArgV[0], "MacWin");				// Application name
		myArgV[1] = malloc((strlen(gOpenDocument) + 1) * sizeof(char));
		strcpy(myArgV[1], gOpenDocument);		// Document name
		myArgV[2] = NULL;
		*argv = (char **)&myArgV;
		gOpenDocument[0] = '\0';					/* No longer available */
		return(2);
	}
	else {
		/* No document to open, see if we should put up the command line dialog */
		if (IsKeyPressed(AK_Option))			
			return ccommand(argv);				/* Option key will put up the command line*/
		else
			return(0);
	}
}


/********************************************************************
*   logstr
*
********************************************************************/
/*VARARGS */
void
logstr(unsigned long flg, ...)
{
	va_list args;
	char *fmt;

	va_start(args, flg);

	if(LibCallback)
		LibCallback(TWINLIBCALLBACK_VSLOGSTR, flg, 0, args);

	va_end(args);
}


/********************************************************************
*   Initialize the support for the threads.
********************************************************************/
static void InitThreads(void)
{
long response;
OSErr err;
Size stackSize;

	err = Gestalt(gestaltThreadMgrAttr, &response);

	/* gestaltThreadMgrPresent = 0, gestaltSpecificMatchSupport = 1, gestaltThreadsLibraryPresent = 2
	*		gestaltThreadMgrPresent This bit is set if the Thread Manager is present.
	*		gestaltSpecificMatchSupport This bit is set if the Thread Manager supports the allocation of threads based on an exact match with the requested stack size. If this bit is not set, the Thread Manager allocates threads based on the closest match to the requested stack size.
	*		gestaltThreadsLibraryPresent This bit is set if the native version of the threads library has been loaded. */
	if (err == noErr) {
		gCanDoThreads = BitTest(response, gestaltThreadMgrPresent);

		GetDefaultThreadStackSize(kCooperativeThread, &stackSize);
		stackSize = 131072;
		err = CreateThreadPool(kCooperativeThread, kNumOfThreads, stackSize);		//kDefaultStackSize
	}

}


/********************************************************************
*   ThreadEntryProc
*
*	The threads entry procedure, called when thread is yielded to.
********************************************************************/
pascal void *ThreadEntryProc(void *threadParam)
{
THREADENTRYFUNC EntryProc = (THREADENTRYFUNC) threadParam;
	
	EntryProc();		/* Call the libraries entry procedure that we were initialized with */
	
	return(NULL);	
}

/********************************************************************
*   DrvCreateThread
*
*	Create a new thread.
*	EntryProc is the function pointer which is to be called when the thread is yielded to.
********************************************************************/
static DWORD DrvCreateThread(THREADENTRYFUNC EntryProc)
{
OSErr err;
ThreadID threadMade;

	if (!gCanDoThreads)
		return(NULL);

	/* Create the thread and pass the EntryProc function pointer to the thread */
	err = NewThread(kCooperativeThread, ThreadEntryProc, EntryProc, kDefaultStackSize, kUsePremadeThread, NULL, &threadMade);

	if (err)
		return(NULL);
	else
		return(threadMade);
}


/********************************************************************
*  DrvFreeThread
*
*	Dispose of a thread and return it to the pool.
********************************************************************/
static DWORD DrvFreeThread(ThreadID thread)
{
#define kRecycleThread TRUE
	
	if (thread)
		DisposeThread(thread, NULL, kRecycleThread);

	return(TRUE);
}


/********************************************************************
*	DrvYieldToThread
*
*  Dispose of a thread and return it to the pool.
********************************************************************/
static DWORD DrvYieldToThread(ThreadID oldThread, ThreadID newThread)
{

    if (oldThread == newThread || !oldThread || !newThread || !gCanDoThreads)
		return(TRUE);

	YieldToThread(newThread);

	return(TRUE);
}


int DrvLoadLibrary(char *lpszLibFileName)
{
	void		*hSO;
	char		LibraryPath[256];
	char		LibraryFile[256];
	char		LibraryFullName[256];
	char		*s;
	OSErr		err;
	Ptr		mainAddr;
	Str255		errName;
	ConnectionID	connectionID;

#ifdef LATER
	/*
	 *  Eventually want to call into MFS layer to look for an
	 *  equivalent name for this library.  The function is not properly
	 *  added at this time, so we skip this step for now.
	 */
	xdoscall(XDOS_GETALTPATH,0,(LPVOID)LibraryName,(LPSTR)lpszLibFileName);
#endif

	/*
	 *  Split the file into path and filename components.  The
	 *  path might be null at this point.
	 */
	s = strrchr (lpszLibFileName, '/');
	if (s == NULL)
	{
		/*
		 *  No path info, entire string is the filename.
		 */
		strcpy(LibraryPath, "");
		strcpy(LibraryFile, lpszLibFileName);
	}
	else
	{
		/*
		 *  Copy the info in as the path, and set the null terminator
		 *  immediately following the found '/'.  Set the filename
		 *  portion to be the part after the '/'.
		 */
		strcpy(LibraryPath, lpszLibFileName);
		LibraryPath[(s - (char *)lpszLibFileName) + 1] = 0;
		strcpy(LibraryFile, s+1);
	}

	/*
	 *  Convert the filename to lowercase.  Probably not the
	 *  optimal thing to do, but we need to handle getting uppercase
	 *  module names at this point, so enforcing a "lowercase only"
	 *  policy is a moderately ok way to do this.  Do not touch
	 *  the case of the path, if it exists.  Only the filename itself
	 *  is modified at this point.
	 */
	strlwr(LibraryFile);

	/*
	 *  Look for an extension.  We only munge the filename if the
	 *  extension is .dll, or there is no extension.  Other names
	 *  are left untouched.
	 */
	s = strrchr (LibraryFile, '.');
	if (s)
	{
		if (strcmp(s, ".dll") == 0)
		{
		/*
		 *  A .dll extension has been found, so remove it.  Set
		 *  the pointer to NULL to indicate that there is no longer
		 *  an extension.
		 */
		*s = 0;
		s = NULL;
		}
	}

	/*
	 *  If there is still an extension on the file, then we do not
	 *  munge the name --- revert to the original for the full name.
	 *  Otherwise, we prepend "lib" to the filename, append "32" for
	 *  the 32-bit version of the library, and append the system-specific
	 *  shared-library extension.
	 */
	if (s)
	{
		strcpy(LibraryFullName, lpszLibFileName);
	}
	else
	{
		strcpy(LibraryFullName, LibraryFile);
#ifdef TWIN32
		strcat(LibraryFullName, "32");
#endif
	}

	ERRSTR((LF_LOG,"Loading %s(%s)\n",LibraryFullName,lpszLibFileName));
	err = GetSharedLibrary(c2pstr(LibraryFullName), kPowerPCArch, 1, &connectionID, &mainAddr, (StringPtr)&errName);
	return(err == noErr);
}

/*****************************************************************************/

#ifdef DRVMALLOC_CHECK

/* from here on, DrvMalloc is DrvMalloc, etc... */
#undef DRVMALLOC_CHECK

#undef DrvMalloc
#undef DrvFree
#undef DrvRealloc

#endif

/********************************************************************
*   DrvMalloc
*
*	The only place where memory is allocated.
*	It can be called directly from the driver if needed.
*	If an error is detected, the program will log the error and exit.
********************************************************************/
void *DrvMalloc(size_t theSize)
{
Ptr newPtr;
OSErr err;

#ifdef ALLOCUSINGNEWPTR
	newPtr = NewPtr(theSize);
	err = MemError();
#else
	newPtr = malloc(theSize);
	err = noErr;
#endif	
	
	if ((newPtr == NULL) || (err != 0))
		SystemError(0, SYSERR_MEMORY, 3, theSize);
		
	return(newPtr);
}

/********************************************************************
*   DrvFree
*
*	The only place where memory is freed.
*	It can be called directly from the driver if needed.
********************************************************************/
void DrvFree(void *ptr)
{
OSErr err;

	if (ptr == NULL)
		return;
		
#ifdef ALLOCUSINGNEWPTR
	DisposePtr(ptr);
	err = MemError();
#else
	free(ptr);
	err = noErr;
#endif
	
	if (err != noErr)
		LOGSTR((LF_PSH,"Drvfree FAILED \n"));
}

/********************************************************************
*   DrvRealloc
*
*	The only place where memory is re-allocated.
*	It can be called directly from the driver if needed.
********************************************************************/
void *DrvRealloc(void *ptr, size_t theSize)
{
void *newPtr;

#ifdef ALLOCUSINGNEWPTR
	newPtr = NewPtr(theSize);							/* Just allocate a new space, SetPtrSize() sucks! */
	if (newPtr) {
		BlockMove(ptr, newPtr, theSize);		/* Move old to new space */
		DisposePtr(ptr);								/* Free the old space */
	}
#else
	newPtr = realloc(ptr, theSize);
#endif

	/* Post alert if action failed */
	if (newPtr == 0)
		SystemError(0,SYSERR_MEMORY,4, theSize);

	return(newPtr);
}

/*****************************************************************************/

typedef struct _malloc_tag {
	struct _malloc_tag *next;
	char	*data;
	int     size;
	int	call;
	char 	*lparam;
	int	 wparam;
	int	 flag;
	int 	 handle;
} MALLOCINFO;

MALLOCINFO	*lpMallocInfo;

#ifdef SEVERE
#define LF_MALLOC 	-1
#else
#define	LF_MALLOC	LF_LOG
#endif

static int	totalalloc;
static int	totalfree;
static int	totalacalls;
static int	totalfcalls;
static int	lf_malloc	= LF_MALLOC;

LPVOID
DrvMallocCheck(unsigned int size, char *lparam,int wparam,int flag, int handle)
{
        LPVOID   lpMemory;
	MALLOCINFO *p;

        lpMemory =  DrvMalloc(size);

        logstr(lf_malloc,"%s:%d: DrvMallocCheck data=%x size=%x %x %x\n",
		lparam,wparam,
                lpMemory,size,
		flag,handle);
	p = (MALLOCINFO *) MALLOCINFOMALLOC(sizeof(MALLOCINFO));
	memset(p,0,sizeof(MALLOCINFO));

	p->next = lpMallocInfo;
	p->data = lpMemory;
	p->size = size;

	p->lparam = lparam;
	p->wparam = wparam;
	p->flag   = flag;
	p->handle = handle; 
	p->call   = totalacalls;
	
	lpMallocInfo = p;
	totalacalls++;

	totalalloc += size;

        return lpMemory;
}

void
DrvFreeCheck(LPVOID ptr,char *lparam, int wparam)
{
	MALLOCINFO *p,*l;

	for(p=lpMallocInfo; p; p = p->next) {
		if(p->data == ptr) {
			if(lpMallocInfo == p) {
				lpMallocInfo = p->next;
			} else {
				for(l=lpMallocInfo; l; l = l->next) {
					if(l->next == p) {
						l->next = p->next;
					}
				}
			}
        		logstr(lf_malloc,"%s:%d: DrvFreeCheck: data=%x from=%s:%d %x %x\n",
			    lparam,wparam,
                	    ptr,
			    p->lparam,p->wparam,
			    p->flag,p->handle);
			MALLOCINFOFREE(p);

			totalfree += p->size;
			totalfcalls++;
			break;
		}
	}
	if(p == 0) {
		logstr(lf_malloc,"*** ERROR *** DrvFreeCheck: freeing %x %s:%d\n",
			ptr, lparam,wparam);
	}
	DrvFree(ptr);
}

LPVOID
DrvReallocCheck(LPVOID ptr, unsigned int size, char *lparam, int wparam,int flag,int handle)
{
	MALLOCINFO *p;
	LPVOID	lp;

	for(p=lpMallocInfo; p; p = p->next) {
		if(p->data == ptr) {
			totalalloc -= p->size;
			break;
		}
	}

	lp = DrvRealloc(ptr,size);

        logstr(lf_malloc,"%s:%d: DrvReallocCheck: old data=%x new data=%x size=%x %x %x\n",
		lparam,wparam,
                ptr,lp,size,
		flag,handle);

	if(ptr == 0)
		return 0;

	if(lp) {
		p->size  = size;
	} else {
		p->size  = 0;
	}

	p->data  = lp;
	p->lparam = lparam;
	p->wparam = wparam;

	totalalloc += size;	

	return lp;
}

long
DrvMallocInfo(int opcode, char *ptr,int flag, int handle)
{
	MALLOCINFO *p;
	int	total,n;

	if(lpMallocInfo == 0)
		return 0;

	switch(opcode) {
	case WMI_DUMP:
		logstr(lf_malloc,"DrvMallocInfo Malloc Chain\n");

		total = 0;
		n     = 0;
		for(p=lpMallocInfo; p; p = p->next) {
			logstr(lf_malloc,"%d: %s:%d: #%d size=%d type=%x handle=%x\n",
				n++,
				p->lparam?p->lparam:"(void)",p->wparam,
				p->call,p->size,p->flag,p->handle);
			total += p->size;
			
		}

		logstr(lf_malloc,"DrvMallocInfo Statistics\n");
		logstr(lf_malloc,"   calls alloc = %d size=%d\n",
			totalacalls,totalalloc);
		logstr(lf_malloc,"   calls free = %d size=%d\n",
			totalfcalls,totalfree);
		logstr(lf_malloc,"   remaining = %d delta=%d\n",
			total, totalalloc-totalfree);
		break;

        case WMI_STAT:
		logstr(handle,"%s memory = %d\n", ptr, totalalloc-totalfree);
		return totalalloc-totalfree;
	
	case WMI_TAG:
		for(p=lpMallocInfo; p; p = p->next) {
			if(p->data == ptr) {
				p->flag   = flag;	
				p->handle = handle;
				break;
			}
		}
		return 0;

	case WMI_CHECK:
		for(p=lpMallocInfo; p; p = p->next) {
			if(p->handle == handle) {
				logstr(lf_malloc,"DrvMallocInfo: %s:%d %d %d\n",
					p->lparam, p->wparam,
					p->flag, p->handle);
				return 1;
			}
		}
		return 0;
		
	default:
		break;
	}
	return 0;
}
