/*****************************************************************
 ** MuFastZero                                                  **
 **                                                             **
 ** A MMU library compatible zeropage remapper                  **
 ** Version 1.04 27.03.1999 © THOR-Software, by Thomas Richter  **
 *****************************************************************/

/// Includes
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <mmu/mmubase.h>
#include <mmu/context.h>
#include <mmu/mmutags.h>
#include <workbench/startup.h>

#include <proto/exec.h>
#include <proto/mmu.h>
#include <proto/dos.h>
#include <proto/icon.h>

#include <string.h>
///
/// Defines
#define STRINGDATE "27.3.99"
#define STRINGVERSION "40.3"
#define ROMEND 0x01000000L
#define MAGIC_ROMEND 0x14L

#define TEMPLATE "ON=FASTZERO/S,OFF=NOFASTZERO/S,FASTEXEC/S"

#define OPT_ON  0
#define OPT_OFF 1
#define OPT_FASTEXEC 2
#define OPT_WINDOW 3
#define OPT_COUNT 4
///
/// Statics
struct MMUBase *MMUBase;
struct DosLibrary *DOSBase;
struct ExecBase *SysBase;
struct Library *IconBase;
///
/// Protos
int __asm __saveds main(void);
int BuildFastZero(LONG fastexec);
int RemoveFastZero(void);
ULONG ChipLowEnd(void);
struct RDArgs *ReadTTArgs(struct WBStartup *msg,LONG args[],struct RDArgs **tmp);
BOOL SetPages(struct MMUContext *ctx,ULONG props,ULONG lower,ULONG size,ULONG pagesize,void *dest);
///
/// Strukturen
struct FastZeroPort {
        struct MsgPort          frp_Port;
        UWORD                   frp_cludgefill;
        void                   *frp_Logical;            /* Logical position this is remapped to */
        void                   *frp_Physical;           /* Physical location of the RAM used for remapping */
        ULONG                   frp_Lower;              /* where remapping was started */
        ULONG                   frp_Size;               /* size of the allocated memory */
        struct Library         *frp_Base;               /* Keep the library open */
        struct MemHeader       *frp_LowMemHeader;
        char                    frp_Name[32];           /* keeps the name of the port */
};

/* This is the port build by MuMove4k */

struct MuMove4KPort {
        struct MsgPort          mrp_Port;
        UWORD                   mrp_cludgefill;
        void                   *mrp_private;
        struct MemHeader       *mrp_LowMemHeader;
        ULONG                   mrp_UpperEnd;
};
///

char version[]="$VER: MuFastZero " STRINGVERSION " (" STRINGDATE ") © THOR";

/// main
int __asm __saveds main(void)
{
LONG args[OPT_COUNT];
struct RDArgs *rd,*myrd;
struct Process *proc;
int rc=20;
LONG err;
struct WBStartup *msg;
BPTR oldout;
struct MsgPort *oldconsole;



        SysBase=*((struct ExecBase **)(4L));

        memset(args,0,sizeof(LONG)*OPT_COUNT);
        /* Wait for the workbench startup, if any */
        proc=(struct Process *)FindTask(NULL);

        if (!(proc->pr_CLI)) {
                WaitPort(&(proc->pr_MsgPort));
                msg=(struct WBStartup *)GetMsg(&(proc->pr_MsgPort));
        } else  msg=NULL;

        if (DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",37L)) {
                if (MMUBase=(struct MMUBase *)OpenLibrary("mmu.library",0L)) {

                        err=ERROR_REQUIRED_ARG_MISSING;

                        myrd=NULL;      /* reset the temporary ReadArgs */
                        oldout=NULL;
                        oldconsole=NULL;
                        if (msg) {
                                oldout=SelectOutput(NULL);
                                oldconsole=SetConsoleTask(NULL);
                                rd=ReadTTArgs(msg,args,&myrd);
                        } else  rd=ReadArgs(TEMPLATE,args,NULL);

                        if (rd) {
                                if (!GetMMUType()) {
                                        Printf("MuFastZero requires a working MMU.\n");
                                        err=10;
                                } else {
                                        /* Argument parser worked, call main routine */
                                        if (args[OPT_ON])
                                                err=BuildFastZero(args[OPT_FASTEXEC]);

                                        if (args[OPT_OFF])
                                                err=RemoveFastZero();
                                }

                                FreeArgs(rd);
                                if (myrd) FreeDosObject(DOS_RDARGS,myrd);
                                if (msg)  Close(SelectOutput(NULL));
                        } else  err=IoErr();

                        if (msg) {
                                SelectOutput(oldout);
                                SetConsoleTask(oldconsole);
                        }

                        if (err<64) {
                                rc=err;
                                err=0;
                        } else {
                                if (!msg) PrintFault(err,"MuFastZero failed");
                                rc=10;
                        }
                        SetIoErr(err);

                        CloseLibrary((struct Library *)MMUBase);
                } else PrintFault(ERROR_OBJECT_NOT_FOUND,"MuFastZero requires the mmu.library");
                CloseLibrary((struct Library *)DOSBase);
        }

        return rc;
}
///
/// ReadTTArgs
struct RDArgs *ReadTTArgs(struct WBStartup *msg,LONG args[],struct RDArgs **tmp)
{
struct WBArg *wbarg;
struct DiskObject *dop;
char **tt;                      /* ToolTypes array */
char *wbstr;                    /* Our self-made workbench argument string */
char *here;
BPTR oldlock;
ULONG len;
struct RDArgs *rd=NULL,*myrd=NULL;
LONG err=0;
BPTR newout;

        if (IconBase=OpenLibrary("icon.library",37L)) {
                if (wbarg=msg->sm_ArgList) {
                        /* use a project icon if there is one... */
                        if (msg->sm_NumArgs > 1) wbarg++;

                        /* go into the directory */
                        oldlock=CurrentDir(wbarg->wa_Lock);

                        if (dop=GetDiskObject(wbarg->wa_Name)) {
                                if (tt=dop->do_ToolTypes) {
                                        /* Read a special tool type for the output window */

                                        /* Calc the size of the argument string */

                                        len = 3;        /* reserve space for SPC,LF,NUL */
                                        while (*tt) {
                                                len += strlen(*tt)+1;   /* string, plus space */
                                                tt++;
                                        }

                                        if (wbstr=AllocVec(len,MEMF_PUBLIC)) {
                                                /* Now copy the arguments into this string, one by one
                                                   and check whether the argument string is still valid. */

                                                tt=dop->do_ToolTypes;
                                                here=wbstr;
                                                do{
                                                        *here='\0';                     /* terminate string */
                                                        /* Check whether this tool type is
                                                           commented out. Just ignore it in this case */
                                                        if (*tt) {
                                                                if (**tt=='(' || **tt==';')
                                                                        continue;

                                                                strcpy(here,*tt);      /* Add TT string */
                                                        }
                                                        len=strlen(here);
                                                        here[len]='\n';
                                                        here[len+1]='\0';               /* terminate string */

                                                        /* Now try to ReadArg' this string */

                                                        /* release old arguments left over from last loop */
                                                        if (rd) FreeArgs(rd);
                                                        if (myrd) FreeDosObject(DOS_RDARGS,myrd);
                                                        rd=NULL;
                                                        memset(args,0,sizeof(LONG)*OPT_COUNT);

                                                        if (myrd=AllocDosObject(DOS_RDARGS,NULL)) {
                                                                /* Allocate and setup the ReadArgs source */
                                                                myrd->RDA_Source.CS_Buffer=wbstr;
                                                                myrd->RDA_Source.CS_Length=strlen(wbstr);

                                                                if (rd=ReadArgs(TEMPLATE ",WINDOW/K",args,myrd)) {
                                                                        /* Is this still valid? */
                                                                        here[len]=' ';
                                                                        here+=len+1;
                                                                        /* if so, accept this argument and go on */
                                                                } else {
                                                                        err=IoErr();
                                                                        if (err==ERROR_NO_FREE_STORE) break;
                                                                        else    err=0;  /* Ignore unknown or invalid arguments silently */
                                                                }
                                                        } else {
                                                                err=ERROR_NO_FREE_STORE;
                                                                break;
                                                        }
                                                }while(*tt++);

                                                FreeVec(wbstr);
                                        } else err=ERROR_NO_FREE_STORE;
                                } else err=ERROR_REQUIRED_ARG_MISSING; /* Huh, how should this happen ? */
                                FreeDiskObject(dop);
                        } else err=IoErr();
                        CurrentDir(oldlock);
                } else err=ERROR_REQUIRED_ARG_MISSING; /* This should not happen either */
                CloseLibrary(IconBase);
        } else err=ERROR_OBJECT_NOT_FOUND;    /* This should not happen */

        /* Open an output stream */

        if (err==0) {
                if (newout=Open((args[OPT_WINDOW])?((char *)args[OPT_WINDOW]):("NIL:"),MODE_NEWFILE)) {
                        SelectOutput(newout);
                        /* Hack in the output console. Well, well... */
                        SetConsoleTask(((struct FileHandle *)(BADDR(newout)))->fh_Type);
                } else err=IoErr();
        }

        if (err) {
                if (rd)   FreeArgs(rd);
                if (myrd) FreeDosObject(DOS_RDARGS,myrd);
                SetIoErr(err);
                rd=NULL;
                myrd=NULL;
        }

        *tmp=myrd;
        return rd;
}
///
/// SetPages
BOOL SetPages(struct MMUContext *ctx,ULONG props,ULONG lower,ULONG size,ULONG pagesize,void *dest)
{
ULONG pages,i;
ULONG base;

        pages=size/pagesize;
        base=0;
        for(i=0;i<pages;i++) {
                if (!SetPageProperties(ctx,props,~0L,lower+base,MAPTAG_DESTINATION,((ULONG)(dest))+base,TAG_DONE))
                        return FALSE;
                base+=pagesize;
        }

        return TRUE;
}
///
/// BuildFastZero
int BuildFastZero(LONG fastexec)
{
void *mem=NULL,*pmem=NULL;
ULONG pagesize,size,psize;
ULONG lower;
struct FastZeroPort *port;
struct MuMove4KPort *mport;
struct MMUContext *ctx,*sctx;   /* default context, supervisorcontext */
struct MinList *ctxl,*sctxl;    /* Copies of the mappings currently active */
int err=ERROR_NO_FREE_STORE;
ULONG props;
BOOL retry;


        Forbid();                               /* Shut up PatchWork */

        /* If this is already installed, warn and return. */

        if (FindPort("« MuFastZero »")) {
                Permit();
                Printf("MuFastZero already running.\n");
                return 5;
        }

        Permit();

        ctx=DefaultContext();   /* get the default context */
        sctx=SuperContext(ctx); /* get the supervisor context for this one */

        /* Read the size */
        pagesize=GetPageSize(ctx);

        if (GetPageSize(sctx)!=pagesize) {
                Printf("Supervisor and user page size are different,\n"
                       "MuFastZero can't be installed.\n");
                return 10;
        }

        size=ChipLowEnd()&(-(LONG)pagesize);

        if (fastexec) {
                Forbid();
                mport=(struct MuMove4KPort *)FindPort("« MuMove4K »");
                if (mport) {
                        size=mport->mrp_UpperEnd;
                }
                Permit();
                if (mport==NULL) {
                        Printf("MuMove4K is not installed, can't remap ExecBase.\n");
                        return 10;
                }
        } else mport=NULL;

        if (size==0) {
                Printf("The MMU page size is too large for MuFastZero.\n");
                return 10;
        }

        port=AllocMem(sizeof(struct FastZeroPort),MEMF_CLEAR|MEMF_PUBLIC);
        if (port) {

         /* Lock the context, make sure nobody is messing with the memory */
         LockContextList();      /* We've to lock the list first, we want to lock more than one */
         LockMMUContext(ctx);
         LockMMUContext(sctx);
         err=0;

         /* Read the properties of the zeropage. If this is either invalid
            or already remapped, don't touch. */

         lower=0;

         do {

                retry=FALSE;
                props=GetPageProperties(ctx,lower,TAG_DONE);
                props|=GetPageProperties(sctx,lower,TAG_DONE);

                if ((props & MAPP_INVALID) || (props & MAPP_SWAPPED) ||
                    (props & MAPP_SUPERVISORONLY)) {

                        if (fastexec && (size>pagesize)) {
                                size-=pagesize;
                                lower+=pagesize;
                                retry=TRUE;
                        } else {
                                Printf("The zero page is invalidated and can't be remapped.\n");
                                err=10;
                        }
                }
         } while(retry);

         if ((props & MAPP_REMAPPED) && (err==0)) {
                Printf("The zero page is already remapped.\n");
                err=10;
         }

         if (err==0L) {

          /* We request FAST here, explicitly. Taking chip memory for the
             image doesn't make sense, this isn't fast either... */

          mem=AllocAligned(size,MEMF_PUBLIC|MEMF_FAST,pagesize);
          if (mem) {
           psize=size;
           pmem=mem;
           PhysicalLocation(ctx,&pmem,&psize);
           if (psize!=size) {
                Printf("The allocated memory is fragmentated, unsupported by MuFastZero.\n");
                err=10;
           }
          } else err=ERROR_NO_FREE_STORE;
         }

         if (err==0L) {
            /* Make a copy of the software levels to be able to restore
               them later in case of an error. */

            if (ctxl=GetMapping(ctx)) {
             if (sctxl=GetMapping(sctx)) {

              /* Modify both contexts such that the zeropage is SINGLE.
                 This operation is very costy, but it doesn't matter
                 except for speed penalties if this is not un-done */

              err=ERROR_NO_FREE_STORE;
              props=MAPP_SINGLEPAGE;

              if (SetProperties(ctx,props,props,lower,size,TAG_DONE)) {
               if (RebuildTree(ctx)) {
                if (SetProperties(sctx,props,props,lower,size,TAG_DONE)) {
                 if (RebuildTree(sctx)) {

                  /* Copy the zeropage over */
                  Disable();
                  memcpy(mem,(char *)lower,(size_t)size);

                  /* The new property flags */
                  props=MAPP_REMAPPED|MAPP_SINGLEPAGE|MAPP_COPYBACK;
                  CacheClearU();

                  /* Map in the properties immediately. This should always work,
                     except the parameters are invalid - which they aren't.
                     This extra step is required because all other calls might
                     break the Disable() above. */
                  if (SetPages(ctx,props,lower,size,pagesize,pmem)) {
                   if (SetPages(sctx,props,lower,size,pagesize,pmem)) {

                    /* Now make the modifications in the abstraction level. */
                    if (SetProperties(ctx,props,~0L,lower,size,MAPTAG_DESTINATION,pmem,TAG_DONE)) {
                     if (SetProperties(sctx,props,~0L,lower,size,MAPTAG_DESTINATION,pmem,TAG_DONE)) {

                        /* The modifications in the hardware level are actually already done.
                           Calling RebuildTree isn't stricly required here, but we do it
                           anyways to clear the dirty flags. This might or might not fail,
                           but the resulting MMU trees are always what we want. In
                           case it fails, the dirty flags are set. So what... */

                        /* The RebuildTree's are below... */

                        /* Everything worked fine so far.... */
                        port->frp_Port.mp_Node.ln_Type=NT_MSGPORT;
                        port->frp_Port.mp_Node.ln_Name=port->frp_Name;
                        strcpy(port->frp_Name,"« MuFastZero »");
                        port->frp_Port.mp_Flags=PA_IGNORE;
                        NewList(&(port->frp_Port.mp_MsgList));

                        port->frp_Physical=pmem;
                        port->frp_Logical=mem;
                        port->frp_Size=size;
                        port->frp_Lower=lower;
                        if (mport) {
                                port->frp_LowMemHeader=mport->mrp_LowMemHeader;
                                port->frp_LowMemHeader->mh_Attributes=0x0000;   /* remove the chip attribute */
                        }

                        port->frp_Base=OpenLibrary("mmu.library",0L);
                        AddPort(&(port->frp_Port));


                        /* We're done here. Fine. */
                        err=0;
                        mem=NULL;
                        port=NULL;

                     }
                    }

                   /* We can't set the software level. Urgh. At least, we can
                      restore it how it looked like before. */
                   }
                  }

                  /* We try to restore the hardware tables now. */
                  if (err) {
                        props=MAPP_CACHEINHIBIT|MAPP_SINGLEPAGE;

                        if ((!SetPageProperties(ctx,props,~0L,0L,TAG_DONE)) ||
                            (!SetPageProperties(sctx,props,~0L,0L,TAG_DONE))) {
                                /* We can't restore the hardware tables either! */
                                Alert(0xbe000101);
                                /* Sorry, go guru. We can't do much more here... */
                        }
                  }
                  Enable();
                 }
                }
               }
              }

              /* We can't build the software level. In the one way or the other,
                 just restore the previous software abstraction level. */

              if (err) {
                SetPropertyList(ctx,ctxl);
                SetPropertyList(sctx,sctxl);
              }

              /* The software level is now restored. The
                 hardware level has been restored above.
                 Now call RebuildTree() to clear the dirty
                 flags.
                 If this fails, tough luck. Both levels are
                 actually fine, except for the busy flags. */

              RebuildTree(ctx);
              RebuildTree(sctx);

              ReleaseMapping(sctx,sctxl);
             }
             ReleaseMapping(ctx,ctxl);
            }
         }

           /* Release the locks */
         UnlockMMUContext(sctx);
         UnlockMMUContext(ctx);
         UnlockContextList();

           if (mem) FreeMem(mem,size);
           if (port) FreeMem(port,sizeof(struct FastZeroPort));
        }

        return err;
}
///
/// RemoveFastZero
int RemoveFastZero(void)
{
struct MMUContext *ctx,*sctx;
struct MinList *ctxl,*sctxl;
struct FastZeroPort *port;
ULONG size,pagesize,lower;
void *mem,*pmem;
ULONG props;
int err;

        Forbid();                               /* Shut up PatchWork */

        /* If this is not installed, warn and return. */

        port=(struct FastZeroPort *)FindPort("« MuFastZero »");

        if (port==NULL) {
                Permit();
                Printf("MuFastZero not installed.\n");
                return 5;
        }
        /* Remove it, to ensure nobody else tries to remove it */
        RemPort(&(port->frp_Port));
        Permit();

        ctx=DefaultContext();   /* get the default context */
        sctx=SuperContext(ctx); /* get the supervisor context for this one */

        pagesize=GetPageSize(ctx);
        if (GetPageSize(sctx)!=pagesize) {
                Printf("Supervisor and user page size are different,\n"
                       "MuFastZero can't be removed.\n");
                return 10;
        }

        LockContextList();      /* We've to lock the list first, we want to lock more than one */
        LockMMUContext(ctx);
        LockMMUContext(sctx);

        err=ERROR_NO_FREE_STORE;

        /* Make a copy of the mapping like it is now */
        if (ctxl=GetMapping(ctx)) {
         if (sctxl=GetMapping(sctx)) {

          /* Read the properties of the zeropage. If this is either invalid
             or already remapped, don't touch. */

          lower=port->frp_Lower;
          props=GetPageProperties(ctx,lower,TAG_DONE);
          props|=GetPageProperties(sctx,lower,TAG_DONE);
          err=0;

          if ((props & MAPP_INVALID) || (props & MAPP_SWAPPED) ||
              (props & MAPP_SUPERVISORONLY)) {
                Printf("The zero page is now invalidated and can't be restored for that reason.\n");
                err=10;
          }

          if ((err==0) && ((props & MAPP_REMAPPED)==0)) {
                Printf("The zero page is no longer remapped to FastMem.\n");
                err=10;
          }

          /* If everything is fine so far, start removing things */
          if (err==0) {
           size=port->frp_Size;
           pmem=port->frp_Physical;
           mem=port->frp_Logical;
           lower=port->frp_Lower;
           Disable();

           err=ERROR_NO_FREE_STORE;
           props=MAPP_CACHEINHIBIT|MAPP_SINGLEPAGE;       /* This is the only flag for chip memory */

           /* Now copy the data back to the zeropage */
           memcpy((char *)lower,mem,(size_t)size);


           /* First, try to modify the hardware tables. */

           if (SetPages(ctx,props,lower,size,pagesize,0L)) {
            if (SetPages(sctx,props,lower,size,pagesize,0L)) {



             /* Restore the software level,singlepage is no longer required */

             props=MAPP_CACHEINHIBIT;
             if (SetProperties(ctx,props,~0L,lower,size,TAG_DONE)) {
              if (SetProperties(sctx,props,~0L,lower,size,TAG_DONE)) {

                        if (port->frp_LowMemHeader) {
                                /* No, neither public nor anything else */
                                port->frp_LowMemHeader->mh_Attributes=MEMF_CHIP;
                        }

                        FreeMem(mem,size);
                        if (port->frp_Base)
                                CloseLibrary(port->frp_Base);

                        FreeMem(port,sizeof(struct FastZeroPort));
                        err=0;
              }
             }

              /* We can't setup the software level. Urgh. At least,
                 we should try to set things back how they used to be. */
            }
           }


           /* We try to restore the hardware tables now. */
           if (err) {
                props=MAPP_REMAPPED|MAPP_SINGLEPAGE|MAPP_COPYBACK;

                if ((!SetPages(ctx,props,lower,size,pagesize,pmem)) ||
                    (!SetPages(sctx,props,lower,size,pagesize,pmem))) {
                        /* We can't restore the hardware tables either! */
                        Alert(0xbe000101);
                        /* Sorry, go guru. We can't do much more here... */
                }
           }
           Enable();

           if (err) {
                SetPropertyList(ctx,ctxl);
                SetPropertyList(sctx,sctxl);
                /* More luck next time */
                AddPort(&(port->frp_Port));
           }

             /* The software level is now restored. The
                hardware level has been restored above.
                Now call RebuildTree() to clear the dirty
                flags.
                If this fails, tough luck. Both levels are
                actually fine, except for the busy flags. */

           RebuildTree(ctx);
           RebuildTree(sctx);
          }
          ReleaseMapping(sctx,sctxl);
         }
         ReleaseMapping(ctx,ctxl);
        }


        UnlockMMUContext(sctx);
        UnlockMMUContext(ctx);
        UnlockContextList();      /* We've to lock the list first, we want to lock more than one */

        if (err) {
                /* Uhoh, things didn't work here... */
                Printf("Can't remove the FastZero node, sorry.\n");
        }

        return err;
}
///
/// ChipLowEnd
ULONG ChipLowEnd(void)
{
ULONG low;
struct MemHeader *head;

        low=0x00200000;         /* This is definitely the upper end of chip mem */

        Forbid();
        for(head=(struct MemHeader *)SysBase->MemList.lh_Head;
                   head->mh_Node.ln_Succ;
                   head=(struct MemHeader *)(head->mh_Node.ln_Succ)) {
                if (head->mh_Attributes & MEMF_CHIP) {
                        if ((ULONG)(head->mh_Lower)<low) {
                                low=(ULONG)(head->mh_Lower);
                        }
                }
        }
        Permit();

        return low;
}
///





