#include <exec/types.h>
#include "defs.h"
#define MANAGER_PRIV
#include "Manager_Priv.h"

static char rcsid [] = "$Id: VM_Manager_Init.c,v 1.8 95/12/16 18:37:04 Martin_Apel Exp $";

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

PRIVATE int OpenLibs (void)

{
SysBase = *((struct ExecBase**)4L);
if (SysBase->LibNode.lib_Version < 37)
  return (ERR_LOW_VERSION);

if ((DOSBase = (struct DosLibrary*)OpenLibrary ("dos.library", 37L)) == NULL)
  return (ERR_NO_DOS);

if ((IntuitionBase = OpenLibrary ("intuition.library", 37L)) == NULL)
  return (ERR_NO_INTUITION);

if ((GfxBase = (struct GfxBase*)OpenLibrary ("graphics.library", 37L)) == NULL)
  return (ERR_NO_GFX);

if ((UtilityBase = OpenLibrary ("utility.library", 37L)) == NULL)
  return (ERR_NO_UTILITY);

if ((ExpansionBase = OpenLibrary ("expansion.library", 37L)) == NULL)
  return (ERR_NO_EXPANSION);

if ((CxBase = OpenLibrary ("commodities.library", 37L)) == NULL)
  return (ERR_NO_COMMODITIES);

if ((RexxSysBase = OpenLibrary ("rexxsyslib.library", 36L)) == NULL)
  return (ERR_NO_REXX);

LocaleBase = OpenLibrary ("locale.library", 38L);

return (SUCCESS);
}

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

PRIVATE BOOL ReceiveInitMsg (void)

{
struct VMMsg *InitMsg;
BOOL Success;

Wait (1L << InitPort->mp_SigBit);

if ((InitMsg = (struct VMMsg*)GetMsg (InitPort)) == NULL)
  {
  PRINT_DEB ("VM_Manager: Init failure", 0L);
  return (FALSE);
  }

if (InitMsg->VMCommand == VMCMD_InitReady)
  Success = TRUE;
else
  Success = FALSE;

if (InitMsg->ReplySignal == NULL)
  {
  PRINT_DEB ("Freeing InitMsg", 0L);
  FreeMem (InitMsg, sizeof (struct VMMsg));
  }
else
  {
  PRINT_DEB ("Returning init msg to sender", 0L);
  Signal (InitMsg->VMSender, 1L << InitMsg->ReplySignal);
  }
return (Success);
}

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

PRIVATE int LaunchPageHandler (void)

{
struct TagItem PH_Tags [] =
{ { NP_Entry, (ULONG)PageHandler },
  { NP_Name, (ULONG)PAGEHANDLER_NAME },
  { NP_Priority, 19L },
  { NP_StackSize, 6000L },
  { TAG_DONE, 0L } };

switch (CurrentConfig.PageDev)
  {
  case PD_FILE:
  case PD_PSEUDOPART:
    if ((PageHandlerProcess = CreateNewProc (PH_Tags)) == NULL)
      return (ERR_NOT_ENOUGH_MEM);
    PageHandlerTask = (struct Task*)PageHandlerProcess;
    break;

  case PD_PART:
    if ((PageHandlerTask = CreateTask (PAGEHANDLER_NAME, 19L, PageHandler, 4000L))
                   == NULL)
      return (ERR_NOT_ENOUGH_MEM);
    break;

#ifdef DEBUG
  default:
    PRINT_DEB ("LaunchPageHandler: Unknown paging device", 0L);
    ColdReboot ();
#endif
  }

if (!ReceiveInitMsg ())
  return (ERR_MSG_POSTED);
return (SUCCESS);
}

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

PRIVATE int LaunchPrePager (void)

{
struct TagItem PP_Tags [] =
{ { NP_Entry, (ULONG)PrePager },
  { NP_Name, (ULONG)PREPAGER_NAME },
  { NP_Priority, 20L },
  { NP_StackSize, 4000L },
  { TAG_DONE, 0L } };

switch (CurrentConfig.PageDev)
  {
  case PD_FILE:
    if ((PrePagerProcess = CreateNewProc (PP_Tags)) == NULL)
      return (ERR_NOT_ENOUGH_MEM);
    PrePagerTask = (struct Task*)PrePagerProcess;
    break;

  case PD_PART:
  case PD_PSEUDOPART:
    if ((PrePagerTask = CreateTask (PREPAGER_NAME, 20L, PrePager, 4000L)) == NULL)
      return (ERR_NOT_ENOUGH_MEM);
    break;

#ifdef DEBUG
  default:
    PRINT_DEB ("LaunchPrePager: Unknown paging device", 0L);
    ColdReboot ();
#endif
  }

if (!ReceiveInitMsg ())
  return (ERR_MSG_POSTED);
return (SUCCESS);
}

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

int LaunchStat (void)

{
DISABLE_VM;
if ((StatTask = CreateTask (STAT_NAME, 5L, Statistics, 4000L)) == NULL)
  {
  PRINT_DEB ("Could not create statistics task", 0L);
  ENABLE_VM;
  return (ERR_NOT_ENOUGH_MEM);
  }

if (!ReceiveInitMsg ())
  {
  PRINT_DEB ("ReceiveInitMsg failed", 0L);
  ENABLE_VM;
  return (ERR_MSG_POSTED);
  }

ENABLE_VM;
return (SUCCESS);
}

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

PRIVATE void adjust_priority (void)

{
char *PagingDevName;
struct Task *PagingDevTask;
LONG PagingDevPri;

/* If the paging device has its own task, the priority of the page handler
 * is one below that task, otherwise priority 19 is taken
 * It is assumed that the task handling the IO for the device is named
 * the same as the device itself
 */

PagingDevName = PagingDevParams.device->dd_Library.lib_Node.ln_Name;
if ((PagingDevTask = FindTask (PagingDevName)) != NULL)
  {
  PagingDevPri = PagingDevTask->tc_Node.ln_Pri;
  SetTaskPri (PageHandlerTask, PagingDevPri - 1);
  SetTaskPri (PrePagerTask, PagingDevPri - 2);
  }
else
  {
  SetTaskPri (PageHandlerTask, 19L);
  SetTaskPri (PrePagerTask, 18L);
  }
}  

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

PRIVATE int InitTrapstructs (void)

{
int i;

NewList (&Free);
NewList (&PageReq);
NewList (&InTransit);

for (i = 0; i < MAX_FAULTS; i++)
  {
  TrapInfo [i].TmpStackSwap.stk_Lower = &(TrapInfo [i].TmpStack [0]);
  TrapInfo [i].TmpStackSwap.stk_Upper = (ULONG)&(TrapInfo [i].TmpStack [TMP_STACKSIZE]);
  TrapInfo [i].TmpStackSwap.stk_Pointer = &(TrapInfo [i].TmpStack [TMP_STACKSIZE]);

  AddHead (&Free, (struct Node*)&(TrapInfo [i]));
  }

return (SUCCESS);
}


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

PRIVATE void patch (void)

{
Forbid ();
PRINT_DEB ("Patching LoadSeg ()", 0L);
OrigLoadSeg = (void (*) ()) SetFunction ((struct Library*)DOSBase, -0x96, 
                              (ULONG (*) ()) LoadSegPatch);

PRINT_DEB ("Patching NewLoadSeg ()", 0L);
OrigNewLoadSeg = (void (*) ()) SetFunction ((struct Library*)DOSBase, -0x300, 
                              (ULONG (*) ()) NewLoadSegPatch);

#ifdef DEBUG
PRINT_DEB ("Patching RemTask ()", 0L);
OrigRemTask = (void (*) ()) SetFunction ((struct Library*)SysBase,
                                   -0x120, (ULONG (*) ()) RemTaskPatch);

/*
PRINT_DEB ("Patching StackSwap ()", 0L);
OrigStackSwap = (void (*) ()) SetFunction ((struct Library*)SysBase,
                                   -0x2dc, (ULONG (*) ()) StackSwapPatch);
*/

PRINT_DEB ("Patching Open ()", 0L);
OrigOpen = (void (*) ()) SetFunction ((struct Library*)DOSBase,
                                   -0x1e, (ULONG (*) ()) OpenPatch);

#endif

if (CurrentConfig.PatchWB)
  {
  PRINT_DEB ("Patching SetWindowTitles ()", 0L);
  OrigSetWindowTitles = (void (*) ()) SetFunction ((struct Library*)IntuitionBase,
                                     -0x114, (ULONG (*) ()) SetWindowTitlesPatch);
  }
  
if (ProcessorType == PROC_68030)
  {
  PRINT_DEB ("Patching ColdReboot ()", 0L);
  OrigColdReboot = (void (*) ()) SetFunction ((struct Library*)SysBase,
                                     -0x2d6, (ULONG (*) ()) ColdRebootPatch);
  }

PRINT_DEB ("Patching CachePreDMA ()", 0L);
OrigCachePreDMA = (void (*) ()) SetFunction ((struct Library*)SysBase, 
                                   -0x2fa, (ULONG (*) ()) CachePreDMAPatch);

PRINT_DEB ("Patching CachePostDMA ()", 0L);
OrigCachePostDMA = (void (*) ()) SetFunction ((struct Library*)SysBase, 
                                   -0x300, (ULONG (*) ()) CachePostDMAPatch);

PRINT_DEB ("Patching Switch ()", 0L);
OrigSwitch = (void (*) ()) SetFunction ((struct Library*)SysBase, 
                                   -0x36, (ULONG (*) ()) SwitchPatch);

PRINT_DEB ("Patching AddTask ()", 0L);
OrigAddTask = (void (*) ()) SetFunction ((struct Library*)SysBase,
                                   -0x11a, (ULONG (*) ()) AddTaskPatch);

PRINT_DEB ("Patching Wait ()", 0L);
OrigWait = (void (*) ()) SetFunction ((struct Library*)SysBase,
                                   -0x13e, (ULONG (*) ()) WaitPatch);

PRINT_DEB ("Patching AvailMem ()", 0L);
OrigAvailMem = (void (*) ()) SetFunction ((struct Library*)SysBase, -0xd8, 
                              (ULONG (*) ()) AvailMemPatch);

PRINT_DEB ("Patching FreeMem ()", 0L);
OrigFreeMem = (void (*) ()) SetFunction ((struct Library*)SysBase, -0xd2, 
                              (ULONG (*) ()) FreeMemPatch);

PRINT_DEB ("Patching AllocMem ()", 0L);
OrigAllocMem = (void (*) ()) SetFunction ((struct Library*)SysBase, -0xc6, 
                              (ULONG (*) ()) AllocMemPatch);

PRINT_DEB ("Patching done", 0L);
Permit ();
}

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

PRIVATE BOOL PatchRamlib (void)

{
struct MsgPort *ramlib_port;
UWORD *ramlib_private;
struct Task *RamLib;

ramlib_private = (UWORD*) SysBase->ex_RamLibPrivate;

if ((RamLib = FindTask ("ramlib")) == NULL)
  {
  PRINT_DEB ("PatchRamlib: Didn't find ramlib task", 0L);
  return (FALSE);
  }

/* Search the next 200 bytes for something that looks like a port. */
while (ramlib_private < (UWORD*)SysBase->ex_RamLibPrivate + 100)
  {
  ramlib_port = (struct MsgPort*) ramlib_private;

  if (ramlib_port->mp_Flags == PA_SIGNAL &&
      ramlib_port->mp_SigTask == RamLib)
    {
    /* Found the port. Find a free signal bit, mark it as used and enter it
     * as the port bit into that port.
     */
    UBYTE signal_bit;

    if (ramlib_port->mp_SigBit != SIGB_SINGLE)
      {
      PRINT_DEB ("PatchRamlib: Patch has already been applied", 0L);
      return (TRUE);
      }

    signal_bit = 31;
    Forbid ();
    while (signal_bit > 15 && 
          ((1L << signal_bit) & RamLib->tc_SigAlloc))
      signal_bit--;

    if (signal_bit == 15)
      {
      Permit ();
      PRINT_DEB ("PatchRamlib: All signal bits of ramlib task in use", 0L);
      return (FALSE);
      }

    RamLib->tc_SigAlloc |= (1L << signal_bit);
    RamLib->tc_SigWait |= (1L << signal_bit);
    ramlib_port->mp_SigBit = signal_bit;
    Signal (RamLib, SIGF_SINGLE | (1L << signal_bit));
    Permit ();
    PRINT_DEB ("PatchRamlib: Successfully patched ramlib", 0L);
    return (TRUE);
    }
  else      
    ramlib_private++;
  }

PRINT_DEB ("PatchRamlib: Didn't find ramlib's port", 0L);
return (FALSE);
}

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

int Init_VM_Manager (void)

{
ULONG NeededForTables;
struct Process *myself;
struct VMMsg *StartupMsg;
int rc;

if ((rc = OpenLibs ()) != SUCCESS)
  return (rc);

#ifdef DEBUG
if (!OpenDebugWindow ())
  ReportError ("Debug buffer could not be installed\n"
               "Resuming without debug information", ERR_CONTINUE);
#endif

/* Wait for startup message */
myself = (struct Process*)FindTask (NULL);
PRINT_DEB ("Waiting for startup message", 0L);
WaitPort (&(myself->pr_MsgPort));
StartupMsg = (struct VMMsg*) GetMsg (&(myself->pr_MsgPort));
CxParams = StartupMsg->StartupParams;

/* CxParams == NULL can happen if started from vmm.library */
if (CxParams != NULL)
  {
  PRINT_DEB ("Received CxParams", 0L);
  if (!(VMEnabled = CxParams->VMEnable))
    DISABLE_VM;
  ExtCxPort = CxParams->ExtCxPort;
  CxSignal = AllocSignal (-1L);
  PRINT_DEB ("Path for preferences file:", 0L);
  PRINT_DEB (StartupMsg->StartupParams->PrefsPath, 0L);
  Forbid ();
  ExtCxPort->CxPort.mp_SigBit = CxSignal;
  ExtCxPort->CxPort.mp_SigTask = FindTask (NULL);
  Permit ();
  }

PRINT_DEB ("Received startup message", 0L);

FreeMem (StartupMsg, sizeof (struct VMMsg));

if (!PatchRamlib ())
  {
  PRINT_DEB ("*** Could not install ramlib patch", 0L);
#ifdef DEBUG
  ReportError ("Ramlib patch could not be installed.\nPlease contact author "
               "with output of\n'print_debug' and Kickstart version number", ERR_CONTINUE);
#endif
  }

NewList (&LoadingTasksList);

INIT_SEMA (&VirtMemSema);

CfgChangeSignal = AllocSignal (-1L);
LockAckSignal = AllocSignal (-1L);

VM_ManagerProcess = (struct Process*)FindTask (NULL);

OpenVMMCatalog ();

if (SysBase->AttnFlags & AFF_68040)
  {
  PRINT_DEB ("Checking for 68060", 0L);
  if (Is68060 ())
    {
    PRINT_DEB ("68060 detected", 0L);
    ProcessorType = PROC_68060;
    FlushVirt = (void (*) (ULONG)) EmptyFunc;
    CPushP = CPushP60;
    CPushL = CPushL60;
    PFlushP = PFlushP60;
    PFlushA = PFlushA60;
    GenDescr = GenDescr60;
    }
  else
    {
    PRINT_DEB ("68040 detected", 0L);
    ProcessorType = PROC_68040;
    FlushVirt = (void (*) (ULONG)) EmptyFunc;
    CPushP = CPushP40;
    CPushL = CPushL40;
    PFlushP = PFlushP40;
    PFlushA = PFlushA40;
    GenDescr = GenDescr40;
    }
  }
else if (SysBase->AttnFlags & AFF_68030)
  {
  PRINT_DEB ("68030 detected", 0L);
  ProcessorType = PROC_68030;
  FlushVirt = (void (*) (ULONG)) CacheClearU;
  CPushP = (void (*) (ULONG)) EmptyFunc;
  CPushL = (void (*) (ULONG)) EmptyFunc;
  PFlushP = PFlushP30;
  PFlushA = PFlushA30;
  GenDescr = GenDescr30;
  }
else if ((SysBase->AttnFlags & AFF_68020) && MMU68851 ())
  {
  PRINT_DEB ("68020 + 68851 detected", 0L);
  ProcessorType = PROC_68851;
  FlushVirt = (void (*) (ULONG)) CacheClearU;
  CPushP = (void (*) (ULONG)) EmptyFunc;
  CPushL = (void (*) (ULONG)) EmptyFunc;
  PFlushP = PFlushP30;
  PFlushA = PFlushA30;
  GenDescr = GenDescr30;
  }
else
  {
  PRINT_DEB ("VM_Manager: Need 68060, 68040, 68030 or 68020+68851", 0L);
  return (ERR_WRONG_CPU);
  }

/* Figure out if we're running on an A3000. If yes we can force a hard
 * rekick upon reboot if necessary.
 */
IsA3000 = ((GfxBase->ChipRevBits0 & SETCHIPREV_AA) == SETCHIPREV_ECS) &&
           (ProcessorType == PROC_68030 ) && 
           (ALIGN_DOWN ((*GenDescr) (0xF80000), PAGEALIGN) == 0x7f80000);

PRINT_DEB ("Initializing trapstructs", 0L);

if ((rc = InitTrapstructs ()) != SUCCESS)
  return (rc);

PRINT_DEB ("Before MMU table", 0L);

if ((rc = SetupMMUTable ()) != SUCCESS)
  {
  PRINT_DEB ("Couldn't set up MMU table", 0L);
  return (rc);
  }

PRINT_DEB ("SetupMMUTable ready. Pagesize is %ld", PAGESIZE);

if ((InitPort = CreateMsgPort ()) == NULL)
  {
  PRINT_DEB ("VM_Manager: Couldn't allocate InitPort", 0L);
  return (ERR_NOT_ENOUGH_MEM);
  }
  
if ((VMPort = CreateMsgPort ()) == NULL)
  {
  PRINT_DEB ("VM_Manager: Couldn't allocate VMPort", 0L);
  return (ERR_NOT_ENOUGH_MEM);
  }

VMPort->mp_Node.ln_Pri = 0L;
VMPort->mp_Node.ln_Name = VMPORTNAME;
AddPort (VMPort);

PRINT_DEB ("VM_Manager: Public port added successfully", 0L);

if ((RexxPort = CreateMsgPort ()) == NULL)
  {
  PRINT_DEB ("VM_Manager: Couldn't allocate RexxPort", 0L);
  return (ERR_NOT_ENOUGH_MEM);
  }

RexxPort->mp_Node.ln_Pri = 0L;
RexxPort->mp_Node.ln_Name = PROGNAME;   /* Rexx port should have the same
                                         * name as the program. */

AddPort (RexxPort);

PRINT_DEB ("VM_Manager: Rexx port added successfully", 0L);

if ((rc = AllocAddressRange ()) != SUCCESS)
  return (rc);

PRINT_DEB ("VM_Manager: Start of address space is %lx", VirtAddrStart);
PRINT_DEB ("VM_Manager: End   of address space is %lx", VirtAddrEnd);

if ((rc = InitTaskTable ()) != SUCCESS)
  return (rc);

if ((rc = InitTrackInfo ()) != SUCCESS)
  return (rc);

if ((rc = ReadConfigFile ((CxParams != NULL) ? CxParams->ConfigPath : CFG_FILENAME)) != SUCCESS)
  {
  PRINT_DEB ("ReadConfigFile returned error %ld", (ULONG)rc);
  return (rc);
  }

PRINT_DEB ("VM_Manager: Config file successfully read", 0L);

AllowZorroIICaching ((BOOL)CurrentConfig.CacheZ2RAM);
if ((rc = SwitchFastROM (CurrentConfig.FastROM)) != SUCCESS)
  {
  PRINT_DEB ("Init_VM_Manager: Error during turning on FastROM", 0L);
  return (rc);
  }

if ((rc = EnterTask (ERR_NAME, USE_NEVER, USE_NEVER, CNOSWAP, INSERT_FRONT)) != SUCCESS)
  {
  PRINT_DEB ("Init_VM_Manager: Couldn't enter task", 0L);
  return (rc);
  }

if (CurrentConfig.PageDev == PD_FILE)
  {
  if ((rc = EnterTask (PartitionName, USE_NEVER, USE_NEVER, CNOSWAP, INSERT_FRONT)) != SUCCESS)
    {
    PRINT_DEB ("Init_VM_Manager: Couldn't enter task", 0L);
    return (rc);
    }
  }

if ((rc = EnterTask (PREPAGER_NAME, USE_NEVER, USE_NEVER, CNOSWAP, INSERT_FRONT)) != SUCCESS)
  {
  PRINT_DEB ("Init_VM_Manager: Couldn't enter prepager", 0L);
  return (rc);
  }


if (CxParams != NULL)
  {
  if ((rc = EnterTask (FilePart (CxParams->PrefsPath), USE_NEVER, USE_NEVER, CNOSWAP, INSERT_FRONT)) != SUCCESS)
    {
    PRINT_DEB ("Init_VM_Manager: Couldn't enter preferences task", 0L);
    return (rc);
    }
  }

VMFreeCounter = COLLECT_INTERVAL;

if (!FindDevParams (PartitionName, &PagingDevParams))
  {
  PRINT_DEB ("VM_Manager: Couldn't get partition information", 0L);
  return (ERR_VOLUME_NOT_FOUND);
  }

PRINT_DEB ("VM_Manager: Found Device params", 0L);

if ((CurrentConfig.PageDev == PD_PART) && (PagingDevParams.SysTask != NULL) && 
    (!Inhibit (PartWithColon, DOSTRUE)))
  {
  PRINT_DEB ("VM_Manager: Couldn't inhibit paging partition", 0L);
  return (ERR_INHIBIT_FAILED);
  }

if ((rc = LaunchPrePager ()) != SUCCESS)
  {
  PRINT_DEB ("VM_Manager: Couldn't create prepager", 0L);
  /* Error msg has already been posted */
  return (rc);
  }

if ((rc = LaunchPageHandler ()) != SUCCESS)
  {
  PRINT_DEB ("VM_Manager: Couldn't create page handler", 0L);
  return (rc);
  }

if (CurrentConfig.StatEnabled && (rc = LaunchStat ()) != SUCCESS)
  {
  PRINT_DEB ("VM_Manager: Couldn't init stat", 0L);
  /* Error msg has already been posted */
  return (rc);
  }

PRINT_DEB ("VM_Manager: All tasks launched", 0L);
  
SetTaskPri (FindTask (NULL), 5L);

if (CurrentConfig.PageDev != PD_FILE)
  adjust_priority ();

CfgChangeNotify.nr_Name = CFG_FILENAME;
CfgChangeNotify.nr_Flags = NRF_SEND_SIGNAL;
CfgChangeNotify.nr_stuff.nr_Signal.nr_Task = FindTask (NULL);
CfgChangeNotify.nr_stuff.nr_Signal.nr_SignalNum = CfgChangeSignal;
StartNotify (&CfgChangeNotify);

patch ();

AddedMemSize = (NUM_PTR_TABLES * POINTERS_PER_TABLE * PAGES_PER_TABLE - 1)
                * PAGESIZE;

NeededForTables = ALIGN_UP (PartSize / (PAGESIZE / sizeof (ULONG)), PAGESIZE);

PRINT_DEB ("Needed for tables: %lx", NeededForTables);

if (PartSize - NeededForTables < AddedMemSize)
  AddedMemSize = PartSize - NeededForTables;

VirtMem = (struct MemHeader*)VirtAddrStart;
VirtAddrEnd = VirtAddrStart + AddedMemSize;
PRINT_DEB ("Setting VirtAddrEnd to %08lx", VirtAddrEnd);

/* If the VM_Manager is started from StartVMM40, the memory list is 
 * installed in the system memory pool. Otherwise the manager was started
 * by the init routine of vmm.library. Then the memory header is simply
 * initialized but not added to the system
 */

if (FindPort (STARTER_PORT_STD))
  {
  AddMemList (AddedMemSize, MEMF_FAST, CurrentConfig.VMPriority, 
              (APTR)VirtAddrStart, MEMLISTNAME);
  }
else
  {
  VirtMem->mh_Node.ln_Type = NT_MEMORY;
  VirtMem->mh_Node.ln_Name = NULL;      /* Marker for not added to system pool */
  VirtMem->mh_Node.ln_Pri  = CurrentConfig.VMPriority;
  VirtMem->mh_Attributes   = MEMF_FAST;
  VirtMem->mh_First = (struct MemChunk*)(VirtAddrStart + sizeof (struct MemHeader));
  VirtMem->mh_Lower = (void*)(VirtAddrStart + sizeof (struct MemHeader));
  VirtMem->mh_Upper = (void*)(VirtAddrStart + AddedMemSize);
  VirtMem->mh_Free  = AddedMemSize - sizeof (struct MemHeader);
  VirtMem->mh_First->mc_Next = NULL;
  VirtMem->mh_First->mc_Bytes = AddedMemSize - sizeof (struct MemHeader);
  }

PRINT_DEB ("VM_Manager: AddedMemSize = %ld", AddedMemSize);

return (SUCCESS);
}

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

PRIVATE void CloseLibs (void)

{
if (DOSBase != NULL)
  CloseLibrary ((struct Library*)DOSBase);

if (IntuitionBase != NULL)
  CloseLibrary (IntuitionBase);

if (GfxBase != NULL)
  CloseLibrary ((struct Library*)GfxBase);

if (UtilityBase != NULL)
  CloseLibrary (UtilityBase);

if (ExpansionBase != NULL)
  CloseLibrary (ExpansionBase);

if (CxBase != NULL)
  CloseLibrary (CxBase);

if (RexxSysBase != NULL)
  CloseLibrary (RexxSysBase);

if (LocaleBase != NULL)
  {
  CloseLibrary (LocaleBase);
  LocaleBase = NULL;
  }
}

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

PRIVATE void InstallFreeMemAfterExit (void)

{
/* The following is very ugly, but I haven't found a better way to install
 * a routine that stays alive after exit.
 */

extern ULONG FreeMemAfterExit,
             FreeMemAfterExitEnd,
             PatchLoc1,
             PatchLoc2,
             PatchLoc3,
             ExecFreeMem,
             RemainingBytes;
ULONG NewFreeMem,
      NewPatchLoc1,
      NewPatchLoc2,
      NewPatchLoc3,
      NewExecFreeMem,
      NewRemainingBytes;

NewFreeMem = (ULONG)DoOrigAllocMem ((ULONG)&FreeMemAfterExitEnd - (ULONG)&FreeMemAfterExit,
                       MEMF_PUBLIC);
if (NewFreeMem == NULL)
  {
  PRINT_DEB ("PANIC: Not enough memory for FreeMemAfterExit", 0L);
  return;
  }

CopyMem ((ULONG*)&FreeMemAfterExit, (ULONG*)NewFreeMem,
         (ULONG)&FreeMemAfterExitEnd - (ULONG)&FreeMemAfterExit);

NewPatchLoc1 = (ULONG)&PatchLoc1 - (ULONG)&FreeMemAfterExit + NewFreeMem + 2;
NewPatchLoc2 = (ULONG)&PatchLoc2 - (ULONG)&FreeMemAfterExit + NewFreeMem + 2;
NewPatchLoc3 = (ULONG)&PatchLoc3 - (ULONG)&FreeMemAfterExit + NewFreeMem + 2;
NewExecFreeMem = (ULONG)&ExecFreeMem - (ULONG)&FreeMemAfterExit + NewFreeMem;
NewRemainingBytes = (ULONG)&RemainingBytes - (ULONG)&FreeMemAfterExit + NewFreeMem;

*(ULONG*)NewExecFreeMem = (ULONG)OrigFreeMem;
*(ULONG*)NewPatchLoc1   = VirtAddrEnd;
*(ULONG*)NewPatchLoc2   = VirtAddrStart;
*(ULONG*)NewPatchLoc3   = (ULONG)(RootTable + ROOTINDEX (VirtAddrStart));
*(ULONG*)NewRemainingBytes = AddedMemSize - sizeof (struct MemHeader) - 
                             VirtMem->mh_Free;

OrigFreeMem = (void (*) ())NewFreeMem;

CacheClearU ();
}

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

PRIVATE void InstallColdRebootAfterExit (void)

{
/* The following is very ugly, but I haven't found a better way to install
 * a routine that stays alive after exit.
 */

extern ULONG ColdRebootPatchStart,
             ColdRebootPatchEnd,
             PatchLoc4;
ULONG NewColdReboot,
      NewPatchLoc4;

PRINT_DEB ("Installing cold reboot patch after exit", 0L);
NewColdReboot = (ULONG)DoOrigAllocMem ((ULONG)&ColdRebootPatchEnd - 
                                       (ULONG)&ColdRebootPatchStart,
                                       MEMF_PUBLIC);
if (NewColdReboot == NULL)
  {
  PRINT_DEB ("PANIC: Not enough memory for ColdRebootAfterExit", 0L);
  return;
  }

CopyMem ((ULONG*)&ColdRebootPatchStart, (ULONG*)NewColdReboot,
         (ULONG)&ColdRebootPatchEnd - (ULONG)&ColdRebootPatchStart);

NewPatchLoc4 = (ULONG)&PatchLoc4 - (ULONG)&ColdRebootPatchStart + NewColdReboot + 2;

*(ULONG*)NewPatchLoc4   = (ULONG)OrigColdReboot;
*(UWORD*)(NewPatchLoc4 - 2) = 0x2f3c;        /* move.l      #dummy,-(sp) */

OrigColdReboot = (void (*) ())NewColdReboot;

CacheClearU ();
}

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

PRIVATE void unpatch (void)

{
if (OrigSwitch != NULL)
  {
  Forbid ();
  PRINT_DEB ("Undoing patches", 0L);
  SetFunction ((struct Library*)SysBase, -0x2fa, (ULONG (*) ()) OrigCachePreDMA);
  SetFunction ((struct Library*)SysBase, -0x300, (ULONG (*) ()) OrigCachePostDMA);
  SetFunction ((struct Library*)SysBase,  -0x36, (ULONG (*) ()) OrigSwitch);
  SetFunction ((struct Library*)SysBase, -0x11a, (ULONG (*) ()) OrigAddTask);
  SetFunction ((struct Library*)SysBase, -0x13e, (ULONG (*) ()) OrigWait);
  SetFunction ((struct Library*)SysBase,  -0xd8, (ULONG (*) ()) OrigAvailMem);
  SetFunction ((struct Library*)SysBase,  -0xd2, (ULONG (*) ()) OrigFreeMem);
  SetFunction ((struct Library*)SysBase,  -0xc6, (ULONG (*) ()) OrigAllocMem);
  SetFunction ((struct Library*)DOSBase,  -0x96, (ULONG (*) ()) OrigLoadSeg);
  SetFunction ((struct Library*)DOSBase, -0x300, (ULONG (*) ()) OrigNewLoadSeg);
#ifdef DEBUG
  SetFunction ((struct Library*)SysBase, -0x120, (ULONG (*) ()) OrigRemTask);
/*  SetFunction ((struct Library*)SysBase, -0x2dc, (ULONG (*) ()) OrigStackSwap); */
  SetFunction ((struct Library*)DOSBase, -0x1e,  (ULONG (*) ()) OrigOpen);
#endif
  if (ProcessorType == PROC_68030)
    SetFunction ((struct Library*)SysBase, -0x2d6, (ULONG (*) ()) OrigColdReboot);
  if (CurrentConfig.PatchWB)
    SetFunction ((struct Library*)IntuitionBase, -0x114, 
                 (ULONG (*) ()) OrigSetWindowTitles);
  Permit ();
  }
}

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

PRIVATE void UninstallAsCommodity (void)

{
struct Message *Msg;

PRINT_DEB ("Uninstalling commodity", 0L);

if (CxParams == NULL)
  return;

if (CxParams->Broker != NULL)
  DeleteCxObjAll (CxParams->Broker);

if (ExtCxPort != NULL)
  {
  RemPort (&(ExtCxPort->CxPort));
  while ((Msg = GetMsg (&(ExtCxPort->CxPort))) != NULL)
    ReplyMsg (Msg);

  FreeSignal ((LONG)ExtCxPort->CxPort.mp_SigBit);

  FreeVec (ExtCxPort->CxPort.mp_Node.ln_Name);
  FreeMem (ExtCxPort, sizeof (struct ExtPort));
  }

FreeVec (CxParams);
}

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

void Cleanup_VM_Manager ()

{
int rc;

if (VirtMem != NULL)
  EndNotify (&CfgChangeNotify);
FreeSignal ((LONG)CfgChangeSignal);
FreeSignal ((LONG)LockAckSignal);

if (VirtMem != NULL && VirtMem->mh_Node.ln_Name != NULL)
  {
  Forbid ();
  Remove ((struct Node*)VirtMem);
  Permit ();
  }

if (EarlyExit)
  {
  InstallFreeMemAfterExit ();
  if (ProcessorType == PROC_68030)
    InstallColdRebootAfterExit ();
  }

unpatch ();

UninstallAsCommodity ();

if ((rc = SwitchFastROM (FALSE)) != SUCCESS)
  RunTimeError (rc);

while (FindTask (WBUF_ALLOC_NAME) || FindTask (GARBAGE_COLL_NAME))
  Delay (1L);

if (CurrentConfig.StatEnabled && FindTask (STAT_NAME))
  {
  Signal (StatTask, 1L << StatQuitSignal);
  while (FindTask (STAT_NAME))
    Delay (1L);
  }

if (FindTask (PREPAGER_NAME))
  {
  Signal (PrePagerTask, 1L << PrePagerQuitSignal);
  while (FindTask (PREPAGER_NAME))
    Delay (1L);
  }

if (FindTask (PAGEHANDLER_NAME))
  {
  Signal (PageHandlerTask, 1L << PageHandlerQuitSignal);
  while (FindTask (PAGEHANDLER_NAME))
    Delay (1L);
  }

while (FindTask (ERR_NAME))
  Delay (1L);

PRINT_DEB ("VM_Manager: All subtasks terminated", 0L);

/* Wait until all users of the patched routines have finished */
while ((AllocMemUsers != 0) || (VirtMem != NULL && VirtMemSema.ss_Owner != NULL))
  Delay (1L);

PRINT_DEB ("All users of patched routines exited", 0L);

if (VirtAddrStart != NULL)
  {
/**********************************************************************
 *                     This doesn't work in OS3.0
 * PRINT_DEB ("VM_Manager: Freeing slot %lx", VirtAddrStart >> E_SLOTSHIFT);
 * FreeExpansionMem (VirtAddrStart >> E_SLOTSHIFT, NUM_PTR_TABLES * 0x200L);
 **********************************************************************/
  }

if ((CurrentConfig.PageDev == PD_PART) && (PartitionName [0] != 0) && 
    (PagingDevParams.SysTask != NULL))
  {
  if (Inhibit (PartWithColon, FALSE))
    PRINT_DEB ("Successfully removed inhibit", 0L);
  else
    PRINT_DEB ("Couldn't remove inhibit", 0L);
  }

KillTrackInfo ();

PRINT_DEB ("Killing task table", 0L);
KillTaskTable ();

if (RexxPort)
  {
  if (RexxPort->mp_Node.ln_Name != NULL)
    RemPort (RexxPort);
  DeleteMsgPort (RexxPort);
  }

if (VMPort)
  {
  if (VMPort->mp_Node.ln_Name != NULL)
    RemPort (VMPort);
  DeleteMsgPort (VMPort);
  }

if (InitPort)
  DeleteMsgPort (InitPort);

if (!EarlyExit)
  {
  PRINT_DEB ("Killing MMU table", 0L);
  KillMMUTable ();
  }

CloseVMMCatalog ();

PRINT_DEB ("VM_Manager: Exiting", 0L);

#ifdef DEBUG
CloseDebugWindow ();
#endif

CloseLibs ();
}
