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

static char rcsid [] = "$Id: VM_Manager.c,v 3.6 95/12/16 18:36:53 Martin_Apel Exp $";

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

PRIVATE BOOL LockPage (ULONG Page)

{
struct VMMsg *LockMsg;

if ((LockMsg = DoOrigAllocMem (sizeof (struct VMMsg), MEMF_PUBLIC)) == NULL)
  return (FALSE);

LockMsg->VMCommand = VMCMD_LockPage;
LockMsg->PageAddress  = Page;
LockMsg->ReplySignal = 0;
PutMsg (PageHandlerPort, (struct Message*)LockMsg);

Wait (1L << LockAckSignal);
return (ThisPageLocked);
}
  
/*********************************************************************/

PRIVATE BOOL LockPages (ULONG From, ULONG To)

{
ULONG Page;

for (Page = PAGEADDR (From); Page <= PAGEADDR (To-1); Page += PAGESIZE)
  {
  if (!LockPage (Page))
    return (FALSE);
  }

return (TRUE);
}

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

PRIVATE BOOL LoadRemainingPages (void)

{
/* Walk the memory list to find out how many page frames are still needed.
 */

struct MemChunk *mc;
ULONG start_of_alloc,
      end_of_alloc,
      end_of_last_alloc;

start_of_alloc = (ULONG)(VirtMem + 1);  /* points behind MemHeader */
end_of_alloc = (ULONG)VirtMem;

OBTAIN_VM_SEMA;
for (mc = VirtMem->mh_First; mc != NULL; mc = mc->mc_Next)
  {
  end_of_last_alloc = end_of_alloc;
  end_of_alloc = (ULONG)mc;
  if (start_of_alloc < end_of_alloc)
    {
    if (!LockPages (start_of_alloc, end_of_alloc))
      {
      RELEASE_VM_SEMA;
      return (FALSE);
      }
    }
    
  start_of_alloc = (ULONG)mc + mc->mc_Bytes;
  }

if (start_of_alloc < (ULONG)VirtMem->mh_Upper)
  {
  if (!LockPages (start_of_alloc, (ULONG)VirtMem->mh_Upper))
    {
    RELEASE_VM_SEMA;
    return (FALSE);
    }
  }

RELEASE_VM_SEMA;
return (TRUE);
}

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

PRIVATE BOOL ExitPossible (void)

{
/* This routine checks if it is currently possible to exit VMM.
 * 1. It tries to remove library if it is installed.
 * 2. It checks the remaining allocated virtual memory. If the still 
 *    allocated memory fits into the page frames, quit is allowed.
 *    CAUTION: This may cause the VM_Manager to block for a page-fault.
 */

struct Library *VMLib;
struct MemHeader *mh;
ULONG size = 0;

Forbid ();
VMLib = (struct Library*)FindName (&(SysBase->LibList), LIBNAME);
if (VMLib != NULL)
  {
  /* Try to remove library. */
  RemLibrary (VMLib);
  if (FindName (&(SysBase->LibList), LIBNAME) != NULL)
    {
    /* Library couldn't be expunged */
    Permit ();
    PRINT_DEB ("Library is still running", 0L);
    ReportError (GetVMMString (msgVMMLibStillOpen), ERR_CONTINUE);
    return (FALSE);
    }
  }
Permit ();

/* It's a bit troublesome to find out, if there are enough page frames
 * to hold the rest of the allocated memory. To avoid this, simple
 * tests are done first.
 */

if (VirtMem->mh_Free == AddedMemSize - sizeof (struct MemHeader))
  return (TRUE);

size = 0;
Forbid ();
for (mh = (struct MemHeader*)SysBase->MemList.lh_Head;
     mh->mh_Node.ln_Succ != NULL; mh = (struct MemHeader*)mh->mh_Node.ln_Succ)
  {
  if ((mh->mh_Attributes & CurrentConfig.MemFlags) == CurrentConfig.MemFlags)
    size += (ULONG)mh->mh_Upper - (ULONG)mh->mh_Lower;
  }
Permit ();

PRINT_DEB ("Maximum size of available memory: %ld", size);
PRINT_DEB ("%ld bytes of VM are allocated", AddedMemSize - VirtMem->mh_Free);
if (AddedMemSize - VirtMem->mh_Free > size)
  {
  ReportError (GetVMMString(msgNotEnoughFrames), ERR_CONTINUE);
  return (FALSE);
  }

if (!LoadRemainingPages ())
  {
  struct VMMsg UnlockMsg;

  PRINT_DEB ("Not enough frames to exit", 0L);
  UnlockMsg.VMCommand = VMCMD_UnlockAllPages;
  UnlockMsg.VMSender = (struct Task*)VM_ManagerProcess;
  UnlockMsg.ReplySignal = LockAckSignal;
  PutMsg (PageHandlerPort, (struct Message*) &UnlockMsg);
  Wait (1L << LockAckSignal);

  ReportError (GetVMMString(msgNotEnoughFrames), ERR_CONTINUE);
  return (FALSE);
  }

EarlyExit = TRUE;
ReportError (GetVMMString (msgStillVMAllocated), ERR_NOERROR);
return (TRUE);   
}

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

BOOL TryToQuit (void)

{
PRINT_DEB ("Trying to quit", 0L);
Delay (50L);            /* Leave a little time for the program requesting
                         * to quit to free its memory
                         */
DISABLE_VM;
if (ExitPossible ())
    {
    PRINT_DEB ("Quit possible", 0L);
    NoMoreVM ();

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

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

    return (TRUE);
    }

ENABLE_VM;
return (FALSE);
}

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

void FillStat (struct VMMsg *StatMsg)

{
struct TrapStruct *tmp;
int i;

StatMsg->st_VMSize = AddedMemSize - sizeof (struct MemHeader);
StatMsg->st_VMFree = VirtMem->mh_Free;
StatMsg->st_Faults = NumPageFaults;
StatMsg->st_PagesWritten = PagesWritten;
StatMsg->st_PagesRead = PagesRead;
StatMsg->st_Frames = NumPageFrames;
StatMsg->st_PagesUsed = SlotsUsed ();
StatMsg->st_PageSize = PAGESIZE;

i = 0;
Forbid ();
for (tmp = (struct TrapStruct*)Free.lh_Head;
     tmp->TS_Node.mln_Succ != NULL;
     tmp = (struct TrapStruct*)tmp->TS_Node.mln_Succ)
  i++;
Permit ();
StatMsg->st_TrapStructsFree = i;
}

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

PRIVATE void FillMemHeader (struct VMMsg *ReqMsg)

{
ReqMsg->VMCommand = VMCMD_InitReady;
ReqMsg->VMHeader  = VirtMem;
ReqMsg->VMSema    = &VirtMemSema;
ReqMsg->MLName    = MEMLISTNAME;
}

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

int ShowGUI (void)

{
BPTR InOutHandle;
char command_buffer [200];

if (ExtCxPort->PrefsTask != NULL)
  {
  PRINT_DEB ("Prefs already running", 0L);
  Signal (ExtCxPort->PrefsTask, 1L << ExtCxPort->ShowSignal);
  return (SUCCESS);
  }

DISABLE_VM;

if ((InOutHandle = Open ("CON:0/0/640/200/VMM Window/CLOSE/AUTO/WAIT", 
                         MODE_NEWFILE)) == NULL)
  {
  PRINT_DEB ("Couldn't open shell window", 0L);
  ENABLE_VM;
  return (ERR_NOT_ENOUGH_MEM);
  }

PRINT_DEB ("Trying to create process", 0L);

sprintf (command_buffer, "\"%s\"", CxParams->PrefsPath);
if (SystemTags (command_buffer,
                SYS_Input, (ULONG)InOutHandle,
                SYS_Output, NULL,
                SYS_Asynch, (ULONG) TRUE,
                NP_Priority, 0L,
                TAG_DONE, 0L) != NULL)
  {
  PRINT_DEB ("Couldn't create preferences process", 0L);
  ENABLE_VM;
  return (ERR_NO_PREFS);
  }

PRINT_DEB ("Successfully created preferences process", 0L);
ENABLE_VM;
return (SUCCESS);
}

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

void HideGUI (void)

{
Forbid ();
if (ExtCxPort->PrefsTask != NULL)
  Signal (ExtCxPort->PrefsTask, SIGBREAKF_CTRL_C);
Permit ();
}

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

PRIVATE BOOL HandleVMMsg (void)

{
struct VMMsg *VMMsg;
BOOL Quit = FALSE;
BOOL NoReply;

UWORD Command;

while ((VMMsg = (struct VMMsg*)GetMsg (VMPort)))
  {
  PRINT_DEB ("Received VMMsg", 0L);
  Command = VMMsg->VMCommand;
  NoReply = FALSE;
  switch (Command)
    {
    /**********************/
    case VMCMD_AskAllocMem:
      ExtCheckVirtMem (VMMsg->VMSender);
      break;

    /**********************/
    case VMCMD_QuitAll:
      if (TryToQuit ())
        Quit = TRUE;
      break;

    /**********************/
    case VMCMD_AskStat:
      PRINT_DEB ("Received AskStat msg", 0L);
      FillStat (VMMsg);
      break;

    /**********************/
    case VMCMD_ReqMemHeader:
      PRINT_DEB ("Received ReqMemHeader msg", 0L);
      FillMemHeader (VMMsg);
      break;

    /**********************/
    case VMCMD_DisableVM:
      PRINT_DEB ("Received disable msg from Prefs program", 0L);
      DISABLE_VM;
      break;

    /**********************/
    case VMCMD_EnableVM:
      PRINT_DEB ("Received enable msg from Prefs program", 0L);
      ENABLE_VM;
      break;

    /**********************/
    case VMCMD_AskVMUsage:
      PRINT_DEB ("Received AskVMUsage message", 0L);
      /* A separate task will reply this message when finished */
      VMUsageInfo (VMMsg);
      NoReply = TRUE;
      break;

    /**********************/
    case VMCMD_AskConfig:
      PRINT_DEB ("Received AskConfig message", 0L);
      *(VMMsg->Config) = CurrentConfig;
      break;

    /**********************/
    default:
      PRINT_DEB ("Internal error: Unknown msg type. Type is %ld", 
                 (ULONG)Command);
      PRINT_DEB ("Sender is", 0L);
      PRINT_DEB (VMMsg->VMSender->tc_Node.ln_Name, 0L);
#ifdef DEBUG
      ColdReboot ();
#endif
    }

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

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

PRIVATE BOOL HandleCxMsg (void)

{
CxMsg *MyMsg;
BOOL Quit = FALSE;
int rc;

while ((MyMsg = (CxMsg*)GetMsg (&(ExtCxPort->CxPort))) != NULL)
  {
  switch (CxMsgType (MyMsg))
    {
    case CXM_IEVENT: 
      switch (CxMsgID (MyMsg))
        {
        case APPEAR_ID: if ((rc = ShowGUI ()) != SUCCESS)
                          RunTimeError (rc);
                        break;

        case ENABLE_ID: if (!VMEnabled)
                          {
                          VMEnabled = TRUE;
                          ENABLE_VM;
                          DisplayBeep (NULL);
                          }
                        break;

        case DISABLE_ID:if (VMEnabled)
                          {
                          VMEnabled = FALSE;
                          DISABLE_VM;
                          DisplayBeep (NULL);
                          }
                        break;
        }
      break;

    case CXM_COMMAND:
      switch (CxMsgID (MyMsg))
        {
        case CXCMD_DISABLE: 
               PRINT_DEB ("Received CXCMD_DISABLE", 0L);
               if (VMEnabled)
                 {
                 VMEnabled = FALSE;
                 DISABLE_VM;
                 }
               PRINT_DEB ("Deactivating broker", 0L);
               ActivateCxObj (CxParams->Broker, FALSE);
               PRINT_DEB ("Returned from deactivate", 0L);
               break;

        case CXCMD_ENABLE: 
               PRINT_DEB ("Received CXCMD_ENABLE", 0L);
               ActivateCxObj (CxParams->Broker, TRUE);
               if (!VMEnabled)
                 {
                 VMEnabled = TRUE;
                 ENABLE_VM;
                 }
               break;

        case CXCMD_APPEAR: 
               PRINT_DEB ("Received CXCMD_APPEAR", 0L);
               if ((rc = ShowGUI ()) != SUCCESS)
                 RunTimeError (rc);
               break;

        case CXCMD_DISAPPEAR: 
               PRINT_DEB ("Received CXCMD_DISAPPEAR", 0L);
               HideGUI ();
               break;

        case CXCMD_KILL: 
               PRINT_DEB ("Received CXCMD_KILL", 0L);
               if (TryToQuit ())
                 {
                 HideGUI ();
                 Quit = TRUE;
                 }
               break;
        }
      break;
    }

  ReplyMsg ((struct Message*) MyMsg);
  }

return (Quit);
}

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

PRIVATE void NewConfigReceived (void)

{
struct VMMsg *CfgMsg;
int rc;
ULONG OldWBufSize = CurrentConfig.WriteBuffer;

NoMoreVM ();
if ((rc = ReadConfigFile (CFG_FILENAME)) != SUCCESS)
  {
  RunTimeError (rc);
  return;
  }

/* try to accommodate to the new values. The values which have to 
 * be taken into account are:
 *   MinMem
 *   MaxMem
 *   PrintStat
 *   VMPriority
 *   WriteBuffer
 *   Enable and DisableVM hotkeys
 */

/* New VMPriority value */
Forbid ();
if (VirtMem->mh_Node.ln_Name != NULL)      
  {
  Remove ((struct Node*)VirtMem);
  VirtMem->mh_Node.ln_Pri = CurrentConfig.VMPriority;
  Enqueue (&(SysBase->MemList), (struct Node*)VirtMem);
  }
Permit ();

/* New PrintStat value */
if (CurrentConfig.StatEnabled && (FindTask (STAT_NAME) == NULL))
  {
  if ((rc = LaunchStat ()) != SUCCESS)
    RunTimeError (rc);
  }
else if (!CurrentConfig.StatEnabled && (FindTask (STAT_NAME) != NULL))
  Signal (StatTask, 1L << StatQuitSignal);

/* New MinMem, MaxMem values */
if ((CfgMsg = DoOrigAllocMem (sizeof (struct VMMsg), MEMF_PUBLIC)) == NULL)
  {
  PRINT_DEB ("No mem for Config message to pagehandler", 0L);
  return;
  }

CfgMsg->VMSender = FindTask (NULL);
CfgMsg->ReplySignal = NULL;
CfgMsg->VMCommand = VMCMD_NewConfig;
PutMsg (PageHandlerPort, (struct Message*)CfgMsg);

/* New DontCacheZ2RAM value */
AllowZorroIICaching (CurrentConfig.CacheZ2RAM);
if ((rc = SwitchFastROM (CurrentConfig.FastROM)) != SUCCESS)
  {
  RunTimeError (rc);
  CurrentConfig.FastROM = FALSE;
  }

/* New PatchWB value */
if (CurrentConfig.PatchWB && (OrigSetWindowTitles == NULL))
  {
  PRINT_DEB ("Patching SetWindowTitles ()", 0L);
  OrigSetWindowTitles = (void (*) ()) SetFunction ((struct Library*)IntuitionBase,
                                     -0x114, (ULONG (*) ()) SetWindowTitlesPatch);
  }
else if (!CurrentConfig.PatchWB && (OrigSetWindowTitles != NULL))
  {
  SetFunction ((struct Library*)IntuitionBase, -0x114, 
               (ULONG (*) ()) OrigSetWindowTitles);
  OrigSetWindowTitles = NULL;
  }

/* New value for write-buffer */
if (OldWBufSize != CurrentConfig.WriteBuffer)
  {
  PRINT_DEB ("WriteBuffer size has changed", 0L);
  DesiredWriteBufferSize = CurrentConfig.WriteBuffer;
  CreateTask (WBUF_ALLOC_NAME, 5L, AllocNewCache, 1000L);
  }

SetFilter (CxParams->EnableFilter, CurrentConfig.EnableHotkey);
SetFilter (CxParams->DisableFilter, CurrentConfig.DisableHotkey);
}

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

void VM_Manager (void)

{
ULONG ReceivedSignals;
ULONG WaitMask;
BOOL DoQuit = FALSE;
struct MsgPort *StarterPort;
struct VMMsg *InitMsg;
int rc;

rc = Init_VM_Manager ();

if (((StarterPort = FindPort (STARTER_PORT_STD)) == NULL) &&
    ((StarterPort = FindPort (STARTER_PORT_LIB)) == NULL))
  {
  PRINT_DEB ("VERY, VERY STRANGE: COULDN'T FIND STARTER PORT", 0L);
  return;
  }
else if ((InitMsg = DoOrigAllocMem (sizeof (struct VMMsg), MEMF_PUBLIC)) == NULL)
  {
  PRINT_DEB ("Not enough memory for init msg", 0L);
  Signal (StarterPort->mp_SigTask, 1L << StarterPort->mp_SigBit);
  InitError (ERR_NOT_ENOUGH_MEM);
  Cleanup_VM_Manager ();
  return;
  }

InitMsg->VMSender  = (struct Task*)VM_ManagerProcess;
InitMsg->ReplySignal = NULL;

if (rc != SUCCESS)
  {
  InitError (rc);
  InitMsg->VMCommand = VMCMD_InitFailed;
  PutMsg (StarterPort, (struct Message*)InitMsg);
  Cleanup_VM_Manager ();
  return;
  }

InitMsg->VMCommand = VMCMD_InitReady;
InitMsg->VMHeader  = VirtMem;
InitMsg->VMSema    = &VirtMemSema;
PutMsg (StarterPort, (struct Message*)InitMsg);

WaitMask = (1L << VMPort->mp_SigBit) | (1L << CfgChangeSignal) |
           (1L << RexxPort->mp_SigBit);

if (ExtCxPort != NULL)
  {
  PRINT_DEB ("Including CXSigBit in WaitMask", 0L);
  WaitMask |= (1L << ExtCxPort->CxPort.mp_SigBit);
  }

while (!DoQuit)
  {
  PRINT_DEB ("Waiting for signals", 0L);
  ReceivedSignals = Wait (WaitMask);
  PRINT_DEB ("Received signals", 0L);

  if (ReceivedSignals & (1L << CfgChangeSignal))
    {
    PRINT_DEB ("Configuration changed", 0L);
    NewConfigReceived ();
    }

  if ((ExtCxPort != NULL) && 
      (ReceivedSignals & (1L << ExtCxPort->CxPort.mp_SigBit)))
    {
    PRINT_DEB ("Received commodities message", 0L);
    DoQuit = HandleCxMsg () || DoQuit;
    }

  if (ReceivedSignals & (1L << RexxPort->mp_SigBit))
    DoQuit = HandleRexxMsg () || DoQuit;

  if (ReceivedSignals & (1L << VMPort->mp_SigBit))
    DoQuit = HandleVMMsg () || DoQuit;
  }

Cleanup_VM_Manager ();
}
