/*
** VMEM - a virtual memory manager for the Amiga operating system
**
** Version 0.1 ©1990 by Edward Hutchins
** Based in part on the SetCPU program by Dave Haynie
** Authors:
**
**      Edward Hutchins: eah1@cec1.wustl.edu
**      Loren Rittle: l-rittle@uiuc.edu
**
** Revisions:
** 10/29/91 cleaned up code somewhat - Ed.
** 12/19/91 made the spy faster with SpyPenCache - Ed.
** 12/19/91 code released as freeware under the GNU general public license - Ed.
**
**    This program is free software; you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation; either version 1, or (at your option)
**    any later version.
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program; if not, write to the Free Software
**    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
*/

#include "vmem.h"

/*
** typedefs and defines
*/

#define VMEM_VERSION        "0.4"               /*  VMem version                    */
#define VMEM_NAME           "virtual memory"    /*  Memory pool name                */
#define VMEM_BASE           0x01500000          /*  Virtual memory base address     */
#define VMEM_PRI            -5                  /*  Allocation priority             */
#define DEF_SWAP_FILE_NAME  "DATI:SomePages"    /*  Should be SYS:                  */
#define DEF_NUMFRAMES       0x0020              /*  16k of frames                    */
#define DEF_NUMPAGES        0x0040              /*  lots of virtual memory            */
#define INITIAL_SHIFT       5                   /*  5 + 14 + 13 == 32               */
#define PAGE_SIZE           0x2000              /*  8k pages                        */
#define PAGE_SIZE_MASK      0x1FFF              /*  Mask off 8k                     */
#define PAGE_SIZE_INDEX     13                  /*  8k page offset bits             */
#define PAGE_TABLE_INDEX    14                  /*  TIA index bits                  */
#define PAGE_TABLE_MIN      0x0800              /*  16M of 8k pages                 */
#define PAGEDAEMON_PRI      80                  /*  VERY high priority              */
#define AGEING_PERIOD       10000              /*  0.1 second                      */
#define FASTPUB 	MEMF_PUBLIC				/*  Fast public memory              */

/* spy stuff */

#define WIDTH  320
#define HEIGHT  32
#define DEPTH    3
#define MAX_PEN  8

/*
** globals
*/

struct ExecBase      *ExecBase;
struct GfxBase       *GfxBase;
struct IntuitionBase *IntuitionBase;
struct Window        *window;
struct RastPort      *RP;

UWORD            NumFrames = DEF_NUMFRAMES; /*  Number of frames            */
UWORD            NumPages = DEF_NUMPAGES;   /*  Number of pages             */
CPTR             ROMBase;                   /*  ROMs mapped to this RAM     */
CPTR             FrameBase;                 /*  Frame table                 */
PFRAME_DESC      FrameTable;                /*  Frame descriptors           */
PPD_SHORT        PageTable;                 /*  Page table                  */
CPTR             SysBusErrHandler;          /*  Default bus fault vector    */
ULONG            SwapFile;                  /*  Handle to swap file         */
ULONG            PageDaemonSig;             /*  Daemon's signal             */
struct Task      *PageDaemonTask;           /*  The pager (us for now)      */
struct MemHeader *FastMemHeader;            /*  Public memory area          */
struct MinList   PageFaultList;             /*  Queue faults here           */
char             SwapFileName[80];          /*  Swap file name              */
WORD             PendingPageFaults;         /*  Page faults to be served    */

/* this will break once NumPages gets parsed from the command line... */

BYTE             SpyPenCache[PAGE_TABLE_MIN + DEF_NUMPAGES];

#define VMEM_LENGTH (NumPages * PAGE_SIZE)  /* Virtual memory size          */
#define VMEM_END (VMEM_BASE + VMEM_LENGTH)  /* End of virt mem              */

/* get a frame index from a page address */

#define PAGE_TO_FRAME(pd) ((((pd) & PD_ADDR_MASK) - (ULONG) FrameBase) / PAGE_SIZE)

/*
** locals
*/

UWORD ctable[] =
{
    0x0000,
    0x0222,
    0x0630,
    0x0f00,
    0x000f,
    0x0070,
    0x0304,
    0x0740
};

/* spy pen types (map to colors above) */

enum
{
    PEN_UNUSED,
    PEN_VIRT,
    PEN_INVALID,
    PEN_MOD,
    PEN_USED,
    PEN_WP,
    PEN_CI,
    PEN_FRAME
};

/*
** We don't need any termination routines
*/

VOID MemCleanup (VOID) {}

/*
** AllocBlocks - allocate memory aligned by lBound + 1
*/

static void *AllocBlocks (ULONG lSize, ULONG lBound)
{
    void *mem, *aligned;

    if (!(mem = AllocMem (lSize + lBound, FASTPUB)))
        return (NULL);
    Forbid ();
    aligned = (void *) (((ULONG) mem + lBound) & ~(lBound));
    FreeMem (mem, lSize + lBound);
    mem = AllocAbs (lSize, aligned);
    Permit ();
    return (mem);
}

/*
** AllocVMem - find the fast memory list, allocate needed physical space
*/

BOOL AllocVMem (VOID)
{
    ULONG FrameSize = NumFrames * PAGE_SIZE;
    ULONG FrameTableSize = NumFrames * sizeof (FRAME_DESC);
    ULONG PageTableSize = (PAGE_TABLE_MIN + NumPages) * sizeof (PD_SHORT);
    BOOL bSuccess = FALSE;

/* prevent mucking about with the MemLists */

    Disable ();

/* find a fast+public memory MemHeader for the fault nodes */
/* note: this works because lh_Head and ln_Succ are the same */

    FastMemHeader = (struct MemHeader *) & (ExecBase->MemList);
    if (FastMemHeader == NULL)
    {
        Enable ();
        goto BailOut;
    }

    for ( ;; )
    {
        FastMemHeader = (struct MemHeader *) FastMemHeader->mh_Node.ln_Succ;
        if (FastMemHeader == NULL)
            goto BailOut;
        if ((FastMemHeader->mh_Attributes & FASTPUB) == FASTPUB)
            break;
    }

    Enable ();

/* allocate in this order to minimize fragging */

    if (!(FrameBase = (CPTR) AllocBlocks (FrameSize, PAGE_SIZE_MASK)))
        goto BailOut;

    if (!(ROMBase = (CPTR) AllocBlocks (ROMSIZE, PAGE_SIZE_MASK)))
        goto BailOut;

/* the !@#%!&* CRP needs to be aligned to 16-byte boundaries */

    if (!(PageTable = (PPD_SHORT) AllocBlocks (PageTableSize, 15)))
        goto BailOut;

    if (!(FrameTable = (PFRAME_DESC) AllocBlocks (FrameTableSize, 15)))
        goto BailOut;

    bSuccess = TRUE;

/* come here to bomb */

BailOut:
    return (bSuccess);
}

/*
** SetPageTableFlags - set flags in the page table over a range of addresses
*/

VOID SetPageTableFlags (ULONG begad, ULONG endad, ULONG Flags)
{
    while (begad < endad)
    {
        PageTable[begad / PAGE_SIZE] |= Flags;
        begad += PAGE_SIZE;
    }
}

/*
** ClearPageTableFlags - clear flags in the page table over a range of addresses
*/

VOID ClearPageTableFlags (ULONG begad, ULONG endad, ULONG Flags)
{
    while (begad < endad)
    {
        PageTable[begad / PAGE_SIZE] &= ~Flags;
        begad += PAGE_SIZE;
    }
}

/*
** This routine creates the Fast ROM and sets up memory.
**
** Address:
** 31           23              15              7               0
** +------+--------------+----------+
** | IS 5 |    TIA 14    |  PS  13  | 13 bits = 8K pages
** +------+--------------+----------+
**
** First 5 bits of logical address -> shifted into the bit bucket
** next 14 bits of logical address -> index into PageTable (level A)
** last 13 bits of logical address -> offset into page
*/

BOOL CreatePageTable (VOID)
{
    ULONG i, myCRP[2], myTC;

#if defined (DEBUG)
    printf ("AllocVMem ()\n");
#endif

/* allocate all needed memory */

    if (!AllocVMem ())
        return (FALSE);

#if defined (DEBUG)
    printf ("CopyMemQuick ()\n");
#endif

/* Here I set up the ROM, as quickly as possible! */

    CopyMemQuick ((ULONG *) ROMBASE, (ULONG *) ROMBase, ROMSIZE);

#if defined (DEBUG)
    printf ("Translation setup...\n");
#endif

/* transparent translation of existing address space... */

    for (i = 0x00000000 ; i < ROMBASE ; i += PAGE_SIZE)
        PageTable [i / PAGE_SIZE] = PD_ADDR (i) | PD_DT_PAGE;

/* ...except re-map the ROM image and write protect it... */

    for (i = ROMBASE ; i < 0x00FFFFFF ; i += PAGE_SIZE)
        PageTable [i / PAGE_SIZE] = PD_ADDR (ROMBase + (i - ROMBASE)) | PD_WP | PD_DT_PAGE;

/* ...as well as the physical memory containing the ROM image */

    SetPageTableFlags ((ULONG) ROMBase, (ULONG) ROMBase + ROMSIZE, PD_WP);

/*
** cache-inhibit chip memory and custom registers
**
** $00000000
**      |       2M chip memory, cache inhibit
** $001FFFFF
** $00200000
**      |       8M fast ram, public
** $00BFFFFF
** $00A00000
**      |       2M reserved area, cache inhibit anyway
** $00BFFFFF
** $00C00000
**      |       1M fast (slow really) ram on some machines, public
** $00CFFFFF
** $00D00000
**      |       2M custom chip area, cache inhibit
** $00F7FFFF
** $00F80000
**      |       256K system rom area (part mapped to FASTROM)
** $00FFFFFF
** $01000000
**      |       (up to) 16M virtual memory area
** $01FFFFFF
*/

/* set up cache-inhibit */

    SetPageTableFlags (0x00000000, 0x001FFFFF, PD_CI);
    SetPageTableFlags (0x00A00000, 0x00BFFFFF, PD_CI);
    SetPageTableFlags (0x00D00000, 0x00F7FFFF, PD_CI);

/* map the first of the virtual pages to the available frames */

    for (i = 0 ; i < NumFrames ; ++i)
    {
        FrameTable [i].Age = 0;
        FrameTable [i].PageIndex = PAGE_TABLE_MIN + i;
        PageTable [PAGE_TABLE_MIN + i] = PD_ADDR (FrameBase + (i * PAGE_SIZE)) | PD_DT_PAGE;
    }

/* clear out the rest of virtual memory to invalid status */

    for (i = NumFrames ; i < NumPages ; ++i)
    {
        PageTable [PAGE_TABLE_MIN + i] = PD_DT_INVALID;
    }

/*
** Now I have to set up the MMU.  The CPU Root Pointer tells the MMU about
** the table I've set up, and the Translation Control register will turn
** the thing on.  Note that the first half of the CRP is control data, the
** second the address of my table.
*/

#if defined (DEBUG)
    printf ("CRP and TC Setup...\n");
#endif

    Disable ();
    SetTC (0);
    myCRP [0] = CRP_LIMIT (PAGE_TABLE_MIN + NumPages - 1) | CRP_SG | CRP_DT_V4BYTE;
    myCRP [1] = (ULONG) PageTable;
    SetCRP (myCRP);
    myTC = TC_ENB | TC_IS (INITIAL_SHIFT) | TC_TIA (PAGE_TABLE_INDEX) | TC_TIB (0) | TC_TIC (0) | TC_TID (0) | TC_PS (PAGE_SIZE_INDEX);
    SetTC (myTC);
    Enable ();

    return (TRUE);
}

/*
** AddVirtMem - add the virtual memory to the system free list
*/

VOID AddVirtMem (VOID)
{
    static char VirtMemName [] = VMEM_NAME;

/* the page table better have a valid descriptor for the first page... */

    AddMemList (VMEM_LENGTH, MEMF_FAST, VMEM_PRI, (APTR) VMEM_BASE, VirtMemName);
}

/*
** ShowFrameTableAges - show the page age table
*/

VOID ShowFrameTableAges (VOID)
{
    PFRAME_DESC pFrame = &FrameTable [0];
    PFRAME_DESC pFrameEnd = &FrameTable [NumFrames];
    UWORD FrameIndex = 0;

    while (pFrame < pFrameEnd)
    {
        printf ("%d (%4.4x, %4.4x) ", FrameIndex, pFrame->Age, pFrame->PageIndex);
        if (FrameIndex % 4 == 0)
            printf ("\n");
        ++FrameIndex, ++pFrame;
    }
    printf ("\n\n");
}

/*
** AgeFrameTable - age the frame table
*/

VOID AgeFrameTable (VOID)
{
    PFRAME_DESC pFrame = &FrameTable [0];
    PFRAME_DESC pFrameEnd = &FrameTable [NumFrames];

    Disable ();
    while (pFrame < pFrameEnd)
    {
        PPD_SHORT pTbl = &PageTable [pFrame->PageIndex];
        if (*pTbl & PD_USED)
        {
            *pTbl &= ~PD_USED;
            pFrame->Age /= 2;
        }
        else
            if (pFrame->Age < AGE_MAX)
                ++(pFrame->Age);
        ++pFrame;
    }
    Enable ();
}

/*
** CreateSwapFile - open and allocate the swap file
**
** Call AFTER AllocVMem is done
*/

BOOL CreateSwapFile (VOID)
{
    ULONG Offset, SizeNeeded = NumPages * PAGE_SIZE;
    ULONG BuffSize = NumFrames * PAGE_SIZE;
    BOOL bSuccess = FALSE;

#if defined (DEBUG)
    printf ("opening file, ");
#endif

/* get an exclusive lock on this file */

    SwapFile = Open (SwapFileName, MODE_OLDFILE);
    if (SwapFile == 0)
    {
        SwapFile = Open (SwapFileName, MODE_NEWFILE);
        if (SwapFile == 0)
            return (FALSE);
    }

/* make sure the file is at least big enough */

    if (Seek (SwapFile, 0, OFFSET_END) == -1)
        goto Abort;
    if ((Offset = Seek (SwapFile, 0, OFFSET_CURRENT)) == -1)
        goto Abort;

/* is it already big enough? */

    if (Offset >= SizeNeeded)
    {
        bSuccess = TRUE;
        goto Abort;
    }

#if defined (DEBUG)
    printf ("clearing buffer, ");
#endif

/* clear out the buffer */

    {
        char *p = (char *) FrameBase;
        char *e = (char *) FrameBase + BuffSize;

        while (p < e)
            *p++ = '\0';
    }

    SizeNeeded -= Offset;

#if defined (DEBUG)
    printf ("writing (%ld) ", SizeNeeded);
#endif

    while (SizeNeeded > BuffSize)
    {
        if (Write (SwapFile, (char *) FrameBase, BuffSize) != BuffSize)
            goto Abort;
        SizeNeeded -= BuffSize;

#if defined(DEBUG)
        printf (". ");
#endif
    }

    if (Write (SwapFile, (char *) FrameBase, SizeNeeded) == SizeNeeded)
        bSuccess = TRUE;

Abort:

#if defined (DEBUG)
    printf ("closing\n");
#endif

    Close (SwapFile);
    return (bSuccess);
}

/*
** StealPage - choose the oldest page and discard it, return it's index
*/

UWORD StealPage (VOID)
{
    PFRAME_DESC pFrame = &FrameTable [0];
    PFRAME_DESC pFrameEnd = &FrameTable [NumFrames];
    PFRAME_DESC pOldest = pFrame;
    UWORD Wage = WAGE (pOldest);
    UWORD FrameIndex;
    PD_SHORT pd;

    Disable ();

/* find the oldest page */

    while (++pFrame < pFrameEnd)
        if (WAGE(pFrame) > Wage)
        {
            pOldest = pFrame;
            Wage = WAGE (pOldest);
        }

    FrameIndex = (((ULONG) pOldest - (ULONG) FrameTable) / sizeof (FRAME_DESC));

/* mark the stolen page as invalid */

    pd = PageTable [pOldest->PageIndex];
    PageTable [pOldest->PageIndex] = PD_DT_INVALID;

    Enable ();

/* if the stolen page has been modified, we need to save it */

    if (pd & PD_MOD)
    {
        ULONG FrameOffset = FrameIndex * PAGE_SIZE;
        ULONG PageOffset = (pOldest->PageIndex * PAGE_SIZE) - VMEM_BASE;

        if ((SwapFile = Open (SwapFileName, MODE_OLDFILE)) == 0)
        {
#if defined (DEBUG)
            printf ("StealPage open file failed! (%s)\n", SwapFileName);
#endif
            Alert (1);
        }
        Seek (SwapFile, PageOffset, OFFSET_BEGINNING);
        Write (SwapFile, ((char *) FrameBase) + FrameOffset, PAGE_SIZE);
        Close (SwapFile);

#if defined (DEBUG_PAGING)
        printf ("Page %d (%lx) swapped out, ", pOldest->PageIndex, PageOffset);
#endif
    }

    return (FrameIndex);
}

/*
** LockPage - check if a page is swapped in, and lock it if so
*/

BOOL LockPage (CPTR Address)
{
    PD_SHORT pd;

    if (Address < VMEM_BASE)
        return (TRUE);

    pd = PageTable[Address / PAGE_SIZE];

/* the page is swapped out, so swap it in later */

    if ((pd & PD_DT_MASK) == PD_DT_INVALID)
        return (FALSE);

/* hopefully we won't get paged out right away */

    FrameTable [PAGE_TO_FRAME (pd)].Age = 0;
    return (TRUE);
}

/*
** LoadPage - load a swapped out page, stealing unlocked pages only
*/

VOID LoadPage (CPTR Address)
{
    UWORD PageIndex = Address / PAGE_SIZE;
    ULONG PageOffset = (PageIndex * PAGE_SIZE) - VMEM_BASE;
    UWORD FrameIndex;
    ULONG FrameOffset;

/* has this page already been fixed? */

    if ((PageTable [PageIndex] & PD_DT_MASK) == PD_DT_PAGE)
        return;

    FrameOffset = (FrameIndex = StealPage ()) * PAGE_SIZE;

    if ((SwapFile = Open (SwapFileName, MODE_OLDFILE)) == 0)
    {
#if defined (DEBUG)
        printf ("LoadPage open file failed! (%s)\n", SwapFileName);
#endif
        Alert (2);
    }
    Seek (SwapFile, PageOffset, OFFSET_BEGINNING);
    Read (SwapFile, ((char *) FrameBase) + FrameOffset, PAGE_SIZE);
    Close (SwapFile);

#if defined (DEBUG_PAGING)
    printf ("Page %d (%lx) swapped in\n", PageIndex, PageOffset);
#endif

    FrameTable [FrameIndex].Age = 0;
    FrameTable [FrameIndex].PageIndex = PageIndex;
    PageTable [PageIndex] = PD_ADDR (FrameBase + FrameOffset) | PD_DT_PAGE;
}

/*
** CorrectPageFault - lock and load all needed pages, signal task to restart
*/

BOOL CorrectPageFault (VOID)
{
    PFaultNode xFaultNode;
    CPTR Faults [4];         /* Data Fault (LSB and MSB), stage B, stage C */
    UWORD FaultCount = 0;
    UWORD Format;
    UWORD PPF;
    static UWORD OldFormat = 0;
    static UWORD SSW = 0;
    UWORD bDiff = FALSE;

    Disable ();
    xFaultNode = (PFaultNode) RemHead ((struct List *) & PageFaultList);
    Enable ();
    if (!xFaultNode)
        return (FALSE);

    Format = xFaultNode->Fault.S.VectorOffset & SF_FORMAT_MASK;

#if defined (DEBUG)
    PPF = --PendingPageFaults;
    if (PPF)
        printf ("PPF=%d ", PPF);
    if (Format != OldFormat)
        OldFormat = Format, bDiff = TRUE;
    if (xFaultNode->Fault.S.SpecialStatusReg != SSW)
        SSW = xFaultNode->Fault.S.SpecialStatusReg, bDiff = TRUE;
    if (bDiff)
        printf ("Format [%4.4x] SSW = %4.4x\n", OldFormat, SSW);
#endif

/* Data Fault is the same for both long and short frames */

    if (!LockPage (xFaultNode->Fault.S.DataCycleFaultAddr))
        Faults [FaultCount++] = xFaultNode->Fault.S.DataCycleFaultAddr;

/* check LSB of Data Fault */

    if (!LockPage (xFaultNode->Fault.S.DataCycleFaultAddr + 3))
        Faults [FaultCount++] = xFaultNode->Fault.S.DataCycleFaultAddr + 3;

    if (Format == SSF_FORMAT)
    {
/* stage B address is PC+4, stage C is PC+2 */
        if (!LockPage (xFaultNode->Fault.S.PC + 4))
            Faults [FaultCount++] = xFaultNode->Fault.S.PC + 4;
        if (!LockPage (xFaultNode->Fault.S.PC + 2))
            Faults [FaultCount++] = xFaultNode->Fault.S.PC + 2;
    }
    else /* must be LSF_FORMAT */
    {
/* stage B address is in stack frame, stage C is at B-2 */
        if (!LockPage (xFaultNode->Fault.L.StageBAddress))
            Faults [FaultCount++] = xFaultNode->Fault.L.StageBAddress;
        if (!LockPage (xFaultNode->Fault.L.StageBAddress - 2))
            Faults [FaultCount++] = xFaultNode->Fault.L.StageBAddress - 2;
    }

/* load all faulted pages */

    while (FaultCount--)
        LoadPage (Faults [FaultCount]);

/* wake up the sleeping task (it does the rest) */

    Signal (xFaultNode->FaultedTask, 1 << xFaultNode->RestartSigNum);
    return (TRUE);
}

/*
** ShowUsage - explain VMEM
*/

VOID ShowUsage (VOID)
{
    printf ("\2330;33mVMem " VMEM_VERSION " ©1990 by Edward Hutchins\2330m\n");
    printf ("Usage: VMem [-f <swapfile>] [-p <phys pages>] [-v <virt pages>]\n");
    printf ("Note: The default swap file is " DEF_SWAP_FILE_NAME "\n");
}

/*
** Spy - spy on memory usage
*/

void Spy (void)
{
    UWORD totalpages = PAGE_TABLE_MIN + NumPages;

    while (1)
    {
        UWORD i = totalpages;

        while (i--)
        {
            PD_SHORT pd = PageTable [i];
            BYTE pen;

            if (i < PAGE_TABLE_MIN)
            {
                PageTable [i] &= ~(PD_MOD | PD_USED);
                pen = PEN_UNUSED;
            }
            else
                pen = PEN_VIRT;

            if ((pd & PD_DT_MASK) == PD_DT_INVALID)
                pen = PEN_INVALID;
            else
                if (pd & PD_MOD)
                    pen = PEN_MOD;
                else
                    if (pd & PD_USED)
                        pen = PEN_USED;
                    else
                        if (pd & PD_WP)
                            pen = PEN_WP;
                        else
                            if (pd & PD_CI)
                                pen = PEN_CI;

            if (pen != SpyPenCache [i])
            {
                SetAPen (RP, SpyPenCache [i] = pen);
                WritePixel (RP, 80 + (i / 16), 8 + (i & 15));
            }
        }
    }
}

/*
** SetupSpy - create the spy task
*/

void SetupSpy (void)
{
    if (!(window = OpenScrn (WIDTH, HEIGHT, DEPTH, 0, 0)))
        return;

/* sig = 1 << (window->UserPort->mp_SigBit); */

    LoadRGB4 (ViewPortAddress (window), ctable, MAX_PEN);
    RP = window->RPort;
    SetRast (RP, PEN_FRAME);
    CreateTask ("VMEM Spy", -1L, Spy, 4096);
}

/*
** OpenLibs - open the needed libraries
*/

void OpenLibs (void)
{

/* we need to open this for some reason */

    ExecBase = (struct ExecBase *) OpenLibrary ("exec.library", 0);
    if (!ExecBase)
        exit (20);

    GfxBase = (struct GfxBase *) OpenLibrary ("graphics.library",0);
    if (!GfxBase)
        exit (20);

    IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library",0);
    if (!IntuitionBase)
        exit (20);
}

/*
** Main - VMEM Main entry point
*/

int main (int argc, char *argv [])
{
    ULONG cacr, cpu, fpu, mmu = 0;
    struct MsgPort *AgePort = NULL;
    struct timerequest *TimerReq = NULL;
    ULONG AgeSig;
    ULONG Signals;
    UBYTE DaemonSigNum;

    OpenLibs ();

#if defined (DEBUG)
    printf ("This is the debugging version of VMEM\n\n");
#endif

/* If they're just asking for help */

    if (argc >= 2 && argv [1] [0] == '?')
    {
        ShowUsage ();
        exit (0);
    }

/* get the system setup */

    cpu = GetCPUType ();
    fpu = GetFPUType ();
    mmu = GetMMUType ();

/* trap silly users */

    if (cpu < 68020 || !mmu)
    {
        printf ("Sorry, your system doesn't have the balls for VMEM!\n");
        exit (0);
    }

/* allocate the paging daemon's signal */

    if ((DaemonSigNum = AllocSignal (-1)) == -1)
        goto Abort;

/* allocate the ageing timer port */

    AgePort = CreatePort (NULL, 0);
    if (AgePort == NULL)
        goto Abort;

    TimerReq = (PTIMER_REQ) CreateExtIO (AgePort, sizeof (TIMER_REQ));
    if (TimerReq == NULL)
        goto Abort;

    if (OpenDevice (TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerReq, 0))
        goto Abort;

/* parse arguments someday... */

    strcpy (SwapFileName, DEF_SWAP_FILE_NAME);

#if 0
    if (argc > 1)
    {
        for (i = 1 ; i < argc ; ++i)
        {
        }
    }
#endif

/* set up both the instruction and data caches */

/*
 *   cacr  = (CACR_INST | CACR_DATA) << CACR_WALLOC;
 *   cacr |= (CACR_INST | CACR_DATA) << CACR_BURST;
 *   cacr |= (CACR_INST | CACR_DATA) << CACR_CLEAR;
 *   cacr |= (CACR_INST | CACR_DATA) << CACR_ENABLE;
 *   SetCACR (cacr);
 */

#if defined (DEBUG)
   printf("Skipped cache controls...\n");
#endif

#if defined (DEBUG)
    printf ("MMU Check\n");
#endif

/* see if someone's using the MMU already */

    if (GetTC () & TC_ENB)
        printf ("MMU already in use, resetting...\n");

#if defined (DEBUG)
    printf ("CreatePageTable ()\n");
#endif

/* create the low-level paging tables and set up the MMU */

    if (!CreatePageTable ())
    {
        printf ("Error: not enough physical memory\n");
        goto Abort;
    }

#if defined (DEBUG)
    printf ("CreateSwapFile ()\n");
#endif

/* open the swap file and make sure it's big enough */

    if (!CreateSwapFile ())
    {
        printf ("Error: failed to create swap file\n");
        goto Abort;
    }

/* initialize misc variables */

    NewList ((struct List *) & PageFaultList);
    PageDaemonTask = FindTask (NULL);
    PageDaemonSig = 1 << DaemonSigNum;
    AgeSig = 1 << AgePort->mp_SigBit;

/* queue a timer request to get us started */

    TimerReq->tr_node.io_Command = TR_ADDREQUEST;
    TimerReq->tr_time.tv_secs = 0;
    TimerReq->tr_time.tv_micro = AGEING_PERIOD;
    SendIO ((struct IORequest *) TimerReq);

/*
** The point of no return...
*/

    SetupSpy ();

    SetTaskPri (PageDaemonTask, PAGEDAEMON_PRI);

#if defined (DEBUG)
    printf ("InsertFaultHandler ()\n");
#endif

    InsertFaultHandler ();

#if defined (DEBUG)
    printf ("AddVirtMem ()\n");
#endif

    AddVirtMem ();

/*
** This task never exits (system failure would result)
*/

#if defined (DEBUG)
    printf ("Waiting on %lx\n", PageDaemonSig | AgeSig);
#endif

    for ( ;; )
    {
        Signals = Wait (PageDaemonSig | AgeSig);

        if (Signals & AgeSig)
        {
#if defined (DEBUG_AGEING)
            static int nCnt = 0;

            if (nCnt++ % 50 == 0)
                ShowFrameTableAges ();
#endif

            GetMsg (AgePort);
            AgeFrameTable ();
            TimerReq->tr_node.io_Command = TR_ADDREQUEST;
            TimerReq->tr_time.tv_secs = 0;
            TimerReq->tr_time.tv_micro = AGEING_PERIOD;
            SendIO ((struct IORequest *) TimerReq);
        }

        if (Signals & PageDaemonSig)
            while (CorrectPageFault ())
                ;
    }

Abort:                  /* we only come here if there was a problem */

    if (DaemonSigNum != -1)
        FreeSignal (DaemonSigNum);
    if (AgePort)
        DeletePort (AgePort);
    if (TimerReq)
        DeleteExtIO ((struct IORequest *) TimerReq);
    exit (30);
}
