/*
	File:		Switch.c

	Contains:	Process switching routines.

	Written by:	Erich Ringewald

	Copyright:	© 1986-1992 by Apple Computer, Inc., all rights reserved.

	Change History (most recent first):
	 
		<22>	10/28/92	DTY		Use new Get/Set macros to access ExpandMem.
		<21>	10/27/92	DTY		Change BlockMoves to BlockMoveData.
		<20>	 9/11/92	DRF		Take out Òwhile(FSBUSY);Ó in ÒTheFutureÓ to allow for faster
									switching.
		<19>	 8/26/92	DTY		Roll in latest changes for A/UX.
		<18>	 3/30/92	DTY		#1025416,<DDG>: Tell Skia to switch itÕs globals on a context
									switch.
		<17>	 3/23/92	JSM		OSEvents.h is obsolete, use Events.h.
		<16>	 2/18/92	DTY		#1021226: Switch emMessageManagerGlobals again.
		<15>	 10/4/91	JSM		Change PsychoticFarmerOrLater conditionals to TheFuture.
		<14>	 9/22/91	DTY		Change PsychoticFarmerAndLater to PsychoticFarmerOrLater.
		<13>	 9/13/91	DTY		Conditionalise previous change so it doesnÕt get built for
									CubeE. (ItÕll get built for PsychoticFarmerAndLater.)
		<12>	  6/4/91	DFH		Switch emMessageManagerGlobals.
		<11>	 1/28/91	DFH		JSM,#81425:Include MFPrivate.h, since that's where
									DisableSwitching and EnableSwitching are now.
		<10>	 1/18/91	DFH		(ewa) Fix kill_vbl check of VBL record address. Was checking the
									wrong element.
		 <9>	 1/15/91	DFH		(VL) Conditionalize out the AUX switch prototypes.
		 <8>	 1/14/91	DFH		(JDR) Conditionalize out AUX support.
		 <6>	12/19/90	gbm		(dba) Fix off-by-one bug in an A/UX change. Also kill VBLs
		 							whose queue entries are in an applicationÕs heap as well
									as those whose task pointers are.
		 <5>	 12/5/90	DFH		Integrated AUX support.
		 <4>	 11/6/90	DFH		Renamed emAppleEventsGlobal to emAppleEvents.
		 <3>	 11/1/90	DFH		Unconditionalized edition mgr lomem switch.
		 <0>	  9/2/86	ELR		New Today.

*/ 
 
#include <types.h>
#include <memory.h>
#include <menus.h>
#include <osutils.h>
#include <quickdraw.h>
#include <events.h>
#include <resources.h>
#include <retrace.h>
#include <osutils.h>
#include <fonts.h>
#include <devices.h>
#include <errors.h>
#include <ExpandMemPriv.h>
#include <MFPrivate.h>

#include "Glue.h"
#include "Lomem.h"
#include "Data.h"
#include "SysMisc.h"
#include "Patches.h"
#include "Aux.h"

#ifdef MORE_SEGMENTS
#pragma segment	kernel_segment
#endif MORE_SEGMENTS

void				SwitchSkiaGlobals(ProcessSerialNumber theNewProcess)
 = {0x303C, 0xFFFE, 0xA832};
 
/* Some function prototypes that should be in (yet another) header file */
void				save_or_restore_dispatch(PCB *, int);
void				kill_dispatch(PCB *);
void				SwitchAllRegs(unsigned long *, unsigned long);
void				SwitchCPURegs(unsigned long *, unsigned long);
#ifdef HAS_AUX_PROCESSMGR
void				aux_ctxsw (unsigned long *, unsigned long, long);
void				aux_fpxsw (unsigned long *, unsigned long, long);
#endif HAS_AUX_PROCESSMGR
void				save_lomem(PEntryPtr);
void				restore_lomem(PEntryPtr, Boolean);
void				save_lmemtab(Ptr);
void				restore_lmemtab(Ptr);

/* Function prototypes internal to this file */
void				save_state(PEntryPtr);
void				save_dce(PEntryPtr);
void				save_vbl(PEntryPtr);
void				AtomicVBLSave(Handle, unsigned long, unsigned long);
void				save_hooks(PEntryPtr);
unsigned short		restore_state(PEntryPtr, Boolean);
void				restore_dce(PEntryPtr);
void				restore_vbl(PEntryPtr);
void				restore_hooks(PEntryPtr);
void				kill_dce(THz);
void				GiveGoodByeKiss(DCtlPtr);
void				kill_vbl(PEntryPtr);
void				kill_hooks(void);

/* Scheduling constant to cause applications to yield the CPU even if there is 
 * is no other reason to switch.  For foreground processes, this is checked
 * only when there are no pending events.  For background processes, it is
 * checked before events are.  Of course, the process can continue with the
 * processor (get a new quantum right away), if there are no other processes
 * ready to be scheduled.
 */ 
#define QUANTUM		(5)

/* switch_task.  Switch to the specified process.  The outgoing process,
 * pCurrentProcess, can be nil.  It happens when switching out of the dead
 * app in c_exittoshell().
 */
void 
switch_task(register PEntryPtr pNewProc)
	{
	register PEntryPtr		pOldProc;
	unsigned short			ps;
#ifdef HAS_AUX_PROCESSMGR
	long					auxPID;
#endif HAS_AUX_PROCESSMGR
	unsigned long			olda5;
	Ptr						ourQDGlobals;
	unsigned long			*oldStackSave;
	
	assert((pNewProc != nil) && (IsProcessEntry(pNewProc))
			&& (pNewProc->p_state != PRSLEEPING));
	
	/* Save Process Mgr a5 and proc globals */
	olda5 = ProcessMgrA5Setup();
	ourQDGlobals = *((Ptr *) olda5);
	
	/* Do nothing if this would all be a NOP */
	if ((pOldProc = pCurrentProcess) == pNewProc)
		{
		A5Restore(olda5);
		return;
		}
	
	/* Let the error handling routines know we can't handle switching now,
	 * but only do it if we'll see the code on the other side of SwitchxxxRegs().
	 */
	if (pNewProc->p_slices > 0)
		DisableSwitching();

#if	TheFuture

//	We don't do anything special for old apps anymore

#else
	/* Wait until file system and AppleShare async calls are done.
	 * NOTE: This is done for the sole reason that MacWrite (4.5??) has an
	 * HFS completion routine that doesn't set up A5.  If we can workaround
	 * (or forget) them, we should.  This would let switches go faster, and
	 * allow apps to sleep pending I/O completion (i.e. WNE with big sleep,
	 * and an I/O completion routine that calls WakeupProcess).
	 */
#ifdef HAS_AUX_PROCESSMGR
	/* No need to wait for synchronous FM since A/UX does it asynchronously */
	if (! AUXIsPresent)
#endif HAS_AUX_PROCESSMGR
	while (FSBUSY)
		;

#endif	TheFuture

	// <18> Call Skia to let it know that a context switch is occuring
	
	if (skiaExists)
		SwitchSkiaGlobals(pNewProc->p_serialNumber);
	
	/* Update count of switched-in time for outgoing process.
	 * NOTE: On TICKS rollover we don't bother to updateÉ who'll miss 'em?
	 * Assume we never have to worry about p_activeTime rollover.
	 */
	if ((lastswitch < TICKS) && (pOldProc != nil))
		pOldProc->p_activeTime += (TICKS - lastswitch);
	lastswitch = TICKS;
	
	/* Update count of slices for incoming process */
	pNewProc->p_slices++;
		
	/* Set time of next quantum cut (even works if TICKS is about to roll over) */
	nextswitch = TICKS + QUANTUM;

	/* Invalidate current process information during switch */
	pCurrentProcess = nil;
	
	/* Save outgoing state, set up incoming state */
	save_state(pOldProc);
	ps = restore_state(pNewProc, false);

	/* Everything is in place.  It's time for the final act of switching the CPU
	 * context from the old to the new.  The SwitchAllRegs and SwitchCPURegs routines
	 * are entered by the old process, but are returned from by the new one.  The most
	 * interesting part is that this switches the stack pointer: all local variables
	 * used after this use the values that the incomer saved when it was the outgoer.
	 */
	pCurrentProcess = pNewProc;
	oldStackSave = (pOldProc != nil) ? &pOldProc->p_sp : nil;
#ifdef HAS_AUX_PROCESSMGR
	if (AUXIsPresent)
		{		
		auxPID = ((pOldProc == nil) || (pNewProc->aux_realpid != pOldProc->aux_realpid)) ? pNewProc->aux_realpid : 0;
		if (MachineHasFPU)
			aux_fpxsw(oldStackSave, pNewProc->p_sp, auxPID);
		else
			aux_ctxsw(oldStackSave, pNewProc->p_sp, auxPID);
		}
	else
		{
#endif HAS_AUX_PROCESSMGR
		if (MachineHasFPU)
			SwitchAllRegs(oldStackSave, pNewProc->p_sp);
		else
			SwitchCPURegs(oldStackSave, pNewProc->p_sp);
#ifdef HAS_AUX_PROCESSMGR
		}
#endif HAS_AUX_PROCESSMGR

	/* Restore interrupt level to what old guy saved before passing the torch */
	spl(ps);
		
	/* let the error handling routines know we can handle switching again */
	EnableSwitching();

	/* Restore his A5 world and his quickdraw globals */
	A5Restore(olda5);
	*((Ptr *) olda5) = ourQDGlobals;
	
	/* Now kill him! */
	if (pCurrentProcess->p_condemned)
		{
		DSERRCODE = noErr;
		ExitToShell();
		}
}

#pragma segment	kernel_segment

/* save_state.  Save all process specific stuff in the process state records.
 * We make an effort to remove all executable things (drivers, vbls, etc)
 * before switching the patches or lomem.  This guarantees that those
 * executables never run without the context the need.
 * NOTE: Should we switch timer tasks?
 * NOTE: VBLs, drivers, and DAs in the system heap are not switched out by
 * this code, so they better not rely on low memory and patches being
 * fair game.  Not only that, but they shouldn't rely on TWGetPID to tell
 * them, because they'll see an incorrect value during the switch.
 */
void
save_state(PEntryPtr pPEntry)
	{
	PCB			**pcbHdl;
	
	/* Weed out the degenerates */
	if ( (pPEntry == nil) || (pcbHdl = pPEntry->p_pcb) == nil)
		return;
	
	/* Disarm executables */
	save_dce(pPEntry);
	save_vbl(pPEntry);
	save_hooks(pPEntry);
	
	/* Remove the guy's patches and save off low memory */
	save_or_restore_dispatch(*pcbHdl, SAVE_DISPATCH);
	save_lomem(pPEntry);
	}

/* restore_state.  Restores all process specific stuff from the state records.
 * NOTE:  Interrupts must be turned off while restoring volatile portions of
 * low memory (e.g. STKLOWPT), because they may be affected by the interrupts
 * and VBLs that execute even as we speak.
 */
unsigned short 
restore_state(register PEntryPtr pPEntry, Boolean shouldRestoreVolatile)
	{
	u_short		ps;

	if (pPEntry->p_pcb == nil)
		return(0);
	
	/* Restore data context (lomem) */
	restore_lomem(pPEntry, shouldRestoreVolatile);

	/* Now restore executable context */
	save_or_restore_dispatch(*pPEntry->p_pcb, RESTORE_DISPATCH);
	restore_dce(pPEntry);
	restore_vbl(pPEntry);
	restore_hooks(pPEntry);
	
	/* Restore volatile state with interrupts off.  These were deferred by
	 * restore_lomem() until we got to this section with interrupts off.
	 * Please excuse the lack of layering.
	 * NOTE:  To leave interrupts off even less we could lift this into
	 * switch_task() itself.
	 */
	if (shouldRestoreVolatile == false)
		{
		ps = disable();
		STKLOWPT = (*pPEntry->p_pcb)->stklowpt;
		}
		
	return(ps);
	}

/* kill_state.  Chuck the switched context of the specified process.  The idea here is
 * a) neutralize the context, and b) make sure that the info is lost so that another
 * save_state/restore_state would not re-install any of the custom context.  This is
 * important when calling StandardLaunch in HandleShellDeath.  We want the restore_state
 * at the end of NewProcess to not re-install patches, dces, etc.  The dces, vbls, and
 * hooks are naturally handled, since the save_state will see genericness.   The dispatch
 * table, tho, must be explicitly removed now.
 */
void
kill_state(PEntryPtr pPEntry)
	{
	PCB				**pcbHdl;

	/* Do nothing if app had no state info */
	if ((pcbHdl = pPEntry->p_pcb) == nil)
		return;
	
	/* Neutralize! */
	kill_dispatch(*pcbHdl);
	kill_dce((*pcbHdl)->applzone);
	kill_vbl(pPEntry);
	kill_hooks();
	}
	
/* Save_lomem copies the current low memory state for the process described
 * by `pp'.
 *
 * Low memory locations can be divided into 4 classes:
 *	(1) Undefined Memory:  Doesn't matter what we do with these
 *	(2) Application Specific Memory:  Must be switched.
 *	(3) Static System Memory:  Must be switched.
 *		Only examples so far: BOOTVOL (SWITCHDATA?).
 *	(4) Dynamic System Memory: Must not be switched.
 *
 * Low memory should be switched out only after all the executable objects that
 * use the low memory have been switched out.  These executables include VBLs,
 * drivers, and "hooks".
 *
 * NOTE: Assumes A5 = PROCESSMGRGLOBALS
 */
void
save_lomem(PEntryPtr pp)
	{
	PCB			*pc;

	assert(pp->p_pcb != nil && pp->p_lmemtool != nil);

	pc = *pp->p_pcb;
		
	/* Save ExpandMem switchables */
	pc->emScriptAppGlobals = GetExpandMemScriptAppGlobals();
	pc->emAppleEvents = GetExpandMemAppleEvents();
	pc->emEditionMgrPerApp = GetExpandMemEditionMgrPerApp();
	pc->emMessageManagerGlobals = GetExpandMemMessageManagerGlobals(0);

	/* Save OS lomem */
	pc->bootvol = BOOTVOL;
	pc->switchdata = SWITCHDATA;
	pc->defvcbptr = DEFVCBPTR;
	pc->defaultwdcb = *((WDCBPtr) (WDCBSPTR + 2));
	pc->defvrefnum = DEFVREFNUM;
	pc->dserrcode = DSERRCODE;
	
	/* Save toolbox lomem */
	save_lmemtab(*pp->p_lmemtool);
	
	/* Save randomly accessed toolbox lomem */
	pc->stklowpt = STKLOWPT;
	pc->appllimit = APPLLIMIT;
	pc->applzone = APPLZONE;
	pc->currenta5 = (unsigned long)CURRENTA5;
	BlockMoveData(CURAPNAME, &pc->curapname, *((unsigned char *)CURAPNAME) + 1);
	pc->curlayer = GetCurLayer();
	pc->topmaphandle = TOPMAPHANDLE;
	pc->curmap = CURMAP;
	if (Colorized)
		pc->menucinfo = MENUCINFO;
	pc->sfsavedisk = SFSAVEDISK;
	pc->curdirstore = CURDIRSTORE;
	}

/* restore_lomem.  Switches in the low memory settings of the given process.
 * Low memory should be switched in before any executable objects that
 * use the low memory have been switched in.  These executables include VBLs,
 * drivers, and "hooks".
 */
void
restore_lomem(PEntryPtr pp, Boolean shouldRestoreVolatile)
	{
	PCB					*pc;
	unsigned short		ps;

	assert(pp->p_pcb != nil && pp->p_lmemtool != nil);

	pc = *pp->p_pcb;

	/* Restore location zero to it's startup time value */
	LOCATIONZERO = initLocationZero;
	
	/* Restore ExpandMem switchables */
	SetExpandMemScriptAppGlobals(pc->emScriptAppGlobals);
	SetExpandMemAppleEvents(pc->emAppleEvents);
	SetExpandMemEditionMgrPerApp(pc->emEditionMgrPerApp);
	SetExpandMemMessageManagerGlobals(0, pc->emMessageManagerGlobals);

	/* Restore OS lomem */
	BOOTVOL = pc->bootvol;
	SWITCHDATA = pc->switchdata;
	DEFVCBPTR = pc->defvcbptr;
	*((WDCBPtr) (WDCBSPTR + 2)) = pc->defaultwdcb;
	DEFVREFNUM = pc->defvrefnum;
	DSERRCODE = pc->dserrcode;
	
	/* Restore toolbox lomem */
	restore_lmemtab(*pp->p_lmemtool);

	/* Restore interrupt sensitive lomem. */
	if (shouldRestoreVolatile)
		{
		ps = disable();
		STKLOWPT = pc->stklowpt;
		APPLZONE = pc->applzone;
		spl(ps);
		}
	else
		{
		STKLOWPT = 0;
		APPLZONE = pc->applzone;
		}
	
	/* Restore randomly accessed toolbox lomem.
	 * NOTE: These must be done after the other toolbox entries since we may
	 * explicitly store values here (e.g. newproc()).
	 */
	APPLLIMIT = pc->appllimit;
	CURRENTA5 = pc->currenta5;
	BlockMoveData(&pc->curapname, CURAPNAME, Length(&(pc->curapname)) + 1);
	SetCurLayer(pc->curlayer);
	TOPMAPHANDLE = pc->topmaphandle;
	CURMAP = pc->curmap;
	if (Colorized)
		MENUCINFO = pc->menucinfo;
	SFSAVEDISK = pc->sfsavedisk;
	CURDIRSTORE = pc->curdirstore;
	}
	
/* save_vbl.  Go through all of the current VBL tasks and replace the receiver
 * addresses with dummies, and set all counts to a large number, making the VBL
 * a NOP.
 * This must be called before save_or_restore_dispatch and save_lomem, to ensure
 * that no VBLs run without their required context.
 */
void
save_vbl(PEntryPtr pp)
	{
	unsigned long		base, bound;
	
	base = (u_long) pp->p_zone;
	bound = base + pp->p_size;
	AtomicVBLSave((*pp->p_pcb)->vblvars, base, bound);
	}

/* restore_vbl.  Reinstate the VBLs that were swapped out by previous save_vbl. */
void
restore_vbl(PEntryPtr pp)
	{
	VBLTask			*pVBL;
	VBLDescPtr		pVBLSave;
	VBLDescHdl		hVBLSave;
	short			vblDescCount;
	unsigned short	ps;

	assert(pp->p_pcb != nil);
		
	hVBLSave = (*pp->p_pcb)->vblvars;
	vblDescCount = (u_long) GetHandleSize((Handle) hVBLSave) / sizeof(VBLDesc);
	
	pVBLSave = *hVBLSave;
	while (--vblDescCount >= 0) // <6>
		{
		pVBL = pVBLSave->v_eladdr;
		assert(pVBL->qType == vType);
		
		ps = disable();
		pVBL->vblCount = pVBLSave->v_count;
		pVBL->vblAddr = pVBLSave->v_addr;
		spl(ps);
		
		pVBLSave++;
		}
	}
	
/* kill_vbl.  Remove all VBL tasks local to the given process.
 * Note that under A/UX tasks above PhysMemTop should be killed as well.
 */
void 
kill_vbl(PEntryPtr pProc)
	{
	register VBLTask			*pThisVBL, *pNextVBL;
	register unsigned long		vblAddr, base, bound;
#ifdef HAS_AUX_PROCESSMGR
	register unsigned long		secondBound;
#endif HAS_AUX_PROCESSMGR

	base = (unsigned long) pProc->p_zone;
	bound = base + pProc->p_size;
#ifdef HAS_AUX_PROCESSMGR
	secondBound = (AUXIsPresent) ? PHYSMEMTOP : (0xFFFFFFFF);
#endif HAS_AUX_PROCESSMGR
	
	pNextVBL = VBLQHDR->qHead;
	while (pNextVBL != nil)
		{
		pThisVBL = pNextVBL;
		pNextVBL = pNextVBL->qLink;
		vblAddr = StripAddress((Ptr) pThisVBL->vblAddr);
		if (((((unsigned long) pThisVBL) >= base) && (((unsigned long) pThisVBL) <= bound)) ||
			((vblAddr >= base) && (vblAddr <= bound  
#ifdef HAS_AUX_PROCESSMGR
			|| ( AUXIsPresent && vblAddr > secondBound)
#endif
			)))
				(void) VRemove(pThisVBL);
		}
	}

/* save_dce.  Switch out all drivers/DAs associated the specified process.
 * This should be called before save_or_restore_dispatch and save_lomem.
 */
void
save_dce(PEntryPtr pp)
	{
	DCtlHandle		*h;
	u_short			i, n, daNumber;
	u_long			crit, base, bound;
	DCEDescPtr		del;

	assert(pp->p_pcb != nil);
	daNumber = (*(pp->p_pcb))->p_daResource;
	base = (unsigned long) pp->p_zone;
	bound = base + pp->p_size;
		
	/* tally up the DAs/drivers we'll be switching */
	h = UNITTABLE;
	i=0; n=0;
	while (n < UNITNTRYCNT)
		{
		if (*h != nil)
			{
			/* Is it a DA handler one? */
			if (n == daNumber)
				i++;
			
			/* swap local drivers */
			else
				{
				if ( (((crit = (unsigned long) (**h)->dCtlWindow) == 0)
						&& ((crit = (unsigned long) (**h)->dCtlStorage) == 0)) )
					crit = (unsigned long) (**h)->dCtlDriver;
				crit = (u_long) StripAddress((Ptr) crit);
				
				if ( (crit >= base) && (crit < bound) )
					i++;
				}
			}
		h++;
		n++;
	}

	/* Make handle correct size (back to 0 if drivers all went away) */
	SetHandleSize((*pp->p_pcb)->dces, i*sizeof(DCEDesc));

	/* No use going on */
	if (i == 0)
		return;
	
	/*  Do the save now that we have storage for it. */
	del = *(*pp->p_pcb)->dces;
	n = 0;
	h = UNITTABLE;
	while (n < UNITNTRYCNT)
		{
		Boolean		removeIt;
		
		if (*h != nil)
			{
			/* Check whether current DCE is local driver or DA from DA Handler */
			if ((removeIt = (n == daNumber)) == false)
				{
				if ( ((crit = (unsigned long) (**h)->dCtlWindow) == 0)
						&& ((crit = (unsigned long) (**h)->dCtlStorage) == 0) )
					crit = (unsigned long) (**h)->dCtlDriver;
				crit = (u_long) StripAddress((Ptr) crit);
				removeIt = ((crit >= base) && (crit < bound));
				}
			
			/* Act on our findings */
			if (removeIt)
				{
				assert(i-- != 0);
				del->d_unit = n;
				del++->d_handle = *h;
				*h = nil;
				}
			}
		h++;
		n++;
		}
	}

/* restore_dce.  Replace all the DCEs we switched out in save_dce. */
void
restore_dce(PEntryPtr pProc)
	{
	register DCEDescHdl		hDCEDesc;
	register DCEDescPtr		pDCEDesc;
	short					dceDescCount;

	assert(pProc->p_pcb != nil);

	hDCEDesc = (*pProc->p_pcb)->dces;
	pDCEDesc = *hDCEDesc;
	dceDescCount = GetHandleSize(hDCEDesc) / sizeof(DCEDesc);
	while (--dceDescCount >= 0)
		{
		*(UNITTABLE + pDCEDesc->d_unit) = pDCEDesc->d_handle;
		pDCEDesc++;
		}
	}

/* kill_dce.  Fix up the unit table when the given appl zone is nuked.
 * This contains the basic functionality of the ROM routine InstallRDrivers().
 * Zeroes out the DCE except for dCtlRefNum (restored because _DrvrInstall won't be
 * called again on an allocated DCE).  Also, gives appropriate GBKisses to drivers.
 * NOTE: A known bug is that a system DA opened locally with no window or storage but
 * a menu id will not be cleaned up or get a gb kiss.
 * NOTE: There should be no need to StripAddress handles, only the MPs.
 * NOTE: It is somewhat dangerous to not _ReleaseResource on the drivers if there are
 * misbehaved drivers (such as the "Windows" DA) that store data in their 'DRVR' handles
 * and depend on the data being reinitialized from disk since they assume they are app
 * heap resource handles and thus will be removed from memory by _RsrcZoneInit.
 */
void
kill_dce(THz applZone)
	{
	u_long				tmp;
	Ptr					baseCurAppHeap, boundsCurAppHeap;
	DCtlHandle			*unitEntry;
	short				unitNTryCnt;
	DCtlPtr				dCtlPtr;
	short				dRefNum;
	
	baseCurAppHeap = (Ptr)applZone;
	boundsCurAppHeap = ((THz) baseCurAppHeap)->bkLim;
	unitEntry = UNITTABLE;
	dRefNum = FIRSTDREFNUM+1;
	unitNTryCnt = UNITNTRYCNT;
	
	while (--unitNTryCnt >= 0)
		{
		/* Keep dRefNum and unitNTryCnt in synch */
		dRefNum -= 1;
		
		/* Get table entry (handle to driver) */
		if ((tmp = (u_long) *unitEntry++) == nil)
			continue;
		
		/* Dereference it to get pointer to driver, can do nothing if it was purged */
		if ((tmp = (u_long) *((DCtlHandle) tmp)) == nil)
			continue;

		/* Convert type just once */
		dCtlPtr = (DCtlPtr) tmp;

		/* Can do nothing if driver isn't available. */
		if ((tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlDriver)) == nil)
			continue;
		
		/* Look for driver based in application heap.  Wipe it out! */
		if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap)
			{
			GiveGoodByeKiss(dCtlPtr);						
			MemClear(dCtlPtr,sizeof(DCtlEntry));
			dCtlPtr->dCtlRefNum = dRefNum;
			continue;
			}
		
		/* Look for driver based in system heap who has storage in the application heap. */
		if (tmp < (u_long)SYSZONE->bkLim)
			{
			/* Is the storage handle in current application heap? */
			tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlStorage);
			if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap)
				{
				OSErr				resErr;
				Str255				driverNameString;
				
				/* Get driver name now, since goodbye kiss may prompt the driver
				 * to nuke dCtlPtr->dCtlDriver.
				 */
				GetResInfo(dCtlPtr->dCtlDriver, nil, nil, &driverNameString);
				resErr = RESERR;
				
				/* Smooch! */
				GiveGoodByeKiss(dCtlPtr);
				
				/* Look for DA vs regular driver.  If it's a DA, wipe it out altogether.
				 * If a driver, just nil the handle into the application heap (driver
				 * can later simple restore it before re-using).  Do any drivers take
				 * advantage of this?
				 * NOTE:  This assumes that all non-DA drivers start with a '.'.
				 */
				if ((resErr != noErr) ||
					((Length(&driverNameString) != 0) && (StringByte(&driverNameString, 0) == '\0')))
					{
					HPurge(dCtlPtr->dCtlDriver);
					MemClear(dCtlPtr, sizeof(DCtlEntry));
					dCtlPtr->dCtlRefNum = dRefNum;
					}
				else
					dCtlPtr->dCtlStorage = nil;
				continue;
				}
			}
			
		/* At this point we have a valid driver w/ last tries for GBKiss if it has
		 * a window in the current heap.  This is done primarily for the benefit of
		 * Calculator, which allocates its storage in the sys heap, but never removes it.
		 */
		tmp = (u_long) StripAddress((Ptr) dCtlPtr->dCtlWindow);
		if (tmp > baseCurAppHeap && tmp < boundsCurAppHeap)
			{
			GiveGoodByeKiss(dCtlPtr);
			HPurge(dCtlPtr->dCtlDriver);
			MemClear(dCtlPtr,sizeof(DCtlEntry));
			dCtlPtr->dCtlRefNum = dRefNum;
			}
		}
	}

/* GiveGoodByeKiss.  Give this driver a goodbye kiss if it wants one. */
void
GiveGoodByeKiss(DCtlPtr dCtlPtr)
	{
	CntrlParam			goodByeParamBlock;
	
	/* Give him a goodbye kiss, if necessary */
	if ((dCtlPtr->dCtlFlags & (DNeedGoodbye | DOpened)) == (DNeedGoodbye | DOpened))
		{
		goodByeParamBlock.csCode = csCodeGoodBye;
		goodByeParamBlock.ioCRefNum = dCtlPtr->dCtlRefNum;
		PBControl(&goodByeParamBlock, SyncHFS);					
		}
	}

/* save_hooks.  Switch out any "hook" that may depend on the application's patches
 * or low memory.  This should be called before save_or_restore_dispatch and
 * save_lomem.
 */
void
save_hooks(PEntryPtr  pProc)
	{
	PCB		*pc;
	
	pc = *pProc->p_pcb;
	pc->fsqueuehook = FSQUEUEHOOK;
	pc->pmsphook = *((Ptr *)(PMSPPTR+PMSPHOOKINDEX));
	kill_hooks();
	}

/* restore_hooks.  Switch in the hooks that may depend on the application's patches
 * or low memory.  This should be called before save_or_restore_dispatch and
 * restore_lomem.
 */
void
restore_hooks(PEntryPtr  pProc)
	{
	PCB		*pc;
	
	pc = *pProc->p_pcb;
	FSQUEUEHOOK = pc->fsqueuehook;
	*((Ptr *)(PMSPPTR+PMSPHOOKINDEX)) = pc->pmsphook;
	}

/* kill_hooks.  Restore hooks to their neutral values. */
void
kill_hooks(void)
	{
	FSQUEUEHOOK = (void (**)())initFSQueueHook;
	*((Ptr *)(PMSPPTR+PMSPHOOKINDEX)) = nil;
	}

#pragma segment	INIT

/* GetSwitchTab.  Get table of toolbox switch locations and set up related globals */
void
GetSwitchTab(void)
	{
	short			*pLen, len;
	Handle			switchTabHdl;
	short			lmemID;
	
	/* Which table depends on whether the machine has color */
	lmemID = (Colorized) ? COLOR_LOMEMTAB_ID : BW_LOMEMTAB_ID;
	if ((switchTabHdl = GetResource(LOMEM_TAB_TYPE, lmemID)) == nil)
		{
		assert(RESERR == memFullErr);
		SysError(dsMemFullErr);
		}
	
	/* Remember table in globals (resource must be marked resLocked) */
	switchTabPtr = *switchTabHdl;
	
	/* NOTE:  This assumes only data addresses in the table */
	for (pLen = switchTabPtr, lmemToolDataSize = 0; (len = *pLen) != 0; (Ptr)pLen += 6)
		lmemToolDataSize += len;
	}
#pragma segment	kernel_segment
