/*
** ppc.library emulation
** (c)1998-99 Frank Wille <frank@phoenix.owl.de>
**
** ELF LoadSeg Patch. Direct start of ELF objects from Shell or Workbench.
**
** V0.6c (23.04.1999) phx
**       Enforcer hit fixed: pr_CLI may be NULL.
** V0.5b (18.02.1999) phx
**       Increased default stack size from 16k to 64k.
** V0.5  (24.01.1999) phx
**       Created.
**       Parts are taken from ElfLoadSeg.c by Ralph Schmidt, Phase 5 1998.
*/

#include "ppclibemu.h"
#include "supp.h"
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <workbench/icon.h>
#include <powerup/ppclib/object.h>
#include <powerup/ppclib/tasks.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/powerpc.h>


#define	PATCHNAME       "ELFLoadSeg-Patch"
#define	LVO_LoadSeg     ((void *)-150)
#define	LVO_UnLoadSeg   ((void *)-156)
#define	LVO_NewLoadSeg  ((void *)-768)
#define DEFSTKSIZE      0x10000


struct HunkSegment
{
  ULONG Size;
  struct HunkSegment *Next;
};


static struct ExecBase *sysbase;
static const char *patchname = PATCHNAME;
static APTR OldNewLoadSeg;
static APTR OldLoadSeg;
static APTR OldUnLoadSeg;


/* Library functions */
extern void *PPCLoadObject(__reg("a0")char *,__reg("a6")struct PPCLibBase *);
extern void PPCUnLoadObject(__reg("a0")struct ElfObject *,
                            __reg("a6")struct PPCLibBase *);
extern void PPCCacheClearE(__reg("a0")APTR,__reg("d0")ULONG,
                           __reg("d1")ULONG,__reg("a6")struct PPCLibBase *);
extern void *PPCCreateTask(__reg("a0")void *,__reg("a1")struct TagItem *,
                           __reg("a6")struct PPCLibBase *);

/* elfsegment.asm */
extern void SegmentCode(void);
extern void SegmentCode_Object(void);
extern void SegmentCode_ID(void);
extern void SegmentCode_End(void);

/* exec.library - vbcc0.6e and earlier have some problems with a0=long */
APTR _SetFunction(__reg("a1")struct Library *,__reg("a0")void *,
                  __reg("d0")unsigned long (*)(),__reg("a6") void *) =
                  "\tjsr\t-420(a6)";



BPTR ELFNewLoadSeg(__reg("d1")char *name,__reg("d2")struct TagItem *lstags,
                   __reg("a6")struct Library *dosbase)
{
  static const char *FN = "NewLoadSeg(): ";
  struct PPCLibBase *ppcbase = NULL;
  BPTR (*oldfunc)(__reg("d1")char *,__reg("d2")struct TagItem *,
                  __reg("a6")struct Library *) = OldNewLoadSeg;
  BPTR SegList = oldfunc(name,lstags,dosbase);

  if (SegList==NULL && (ppcbase = (struct PPCLibBase *)
                        __OpenLibrary("ppc.library",46,sysbase))) {
    /* NewLoadSeg() failed, check for ELF format */
    void *elfobj;
    struct HunkSegment *seg;
    ULONG codesize,allocsize;
    ULONG *objptr;

    if (elfobj = PPCLoadObject(name,ppcbase)) {
      /* Ok, ELF object loaded! */

      codesize = (ULONG)(SegmentCode_End - SegmentCode);
      allocsize = codesize + sizeof(struct HunkSegment);

      if (seg = alloc32(allocsize,ppcbase)) {
        seg->Size = allocsize;
        seg->Next = NULL;
        __CopyMemQuick((APTR)SegmentCode,(APTR)&seg[1],codesize,sysbase);

        /* patch ELF-Object pointer into start-code */
        objptr = (ULONG *)((ULONG)&seg[1] +
                           ((ULONG)(SegmentCode_Object-SegmentCode)+2));
        *objptr = (ULONG)elfobj;
        SegList = MKBADDR(&seg->Next);
        PPCCacheClearE(seg,allocsize,CACRF_ClearI|CACRF_ClearD,ppcbase);
        Dprintf(ppcbase,"%sLoaded %s as ELF object\n",FN,name);
        __SetIoErr(RETURN_OK,dosbase);
      }

      else {
        /* loading ELF object failed - not enough memory */
        Dprintf(ppcbase,"%sLoading %s as ELF object failed\n",FN,name);
        PPCUnLoadObject(elfobj,ppcbase);
        __SetIoErr(ERROR_NO_FREE_STORE,dosbase);
      }
    }

    else {
      /* neither Hunk- nor ELF-format */
      __SetIoErr(ERROR_OBJECT_WRONG_TYPE,dosbase);
    }
  }

  if (ppcbase)
    __CloseLibrary((struct Library *)ppcbase,sysbase);
  return(SegList);
}


BPTR ELFLoadSeg(__reg("d1")char *name,__reg("a6")struct Library *dosbase)
{
  static const char *FN = "LoadSeg(): ";
  struct PPCLibBase *ppcbase = NULL;
  BPTR (*oldfunc)(__reg("d1")char *,
                  __reg("a6")struct Library *) = OldLoadSeg;
  BPTR SegList = oldfunc(name,dosbase);

  if (SegList==NULL && (ppcbase = (struct PPCLibBase *)
                        __OpenLibrary("ppc.library",46,sysbase))) {
    /* LoadSeg() failed, check for ELF format */
    void *elfobj;
    struct HunkSegment *seg;
    ULONG codesize,allocsize;
    ULONG *objptr;

    if (elfobj = PPCLoadObject(name,ppcbase)) {
      /* Ok, ELF object loaded! */

      codesize = (ULONG)(SegmentCode_End - SegmentCode);
      allocsize = codesize + sizeof(struct HunkSegment);

      if (seg = alloc32(allocsize,ppcbase)) {
        seg->Size = allocsize;
        seg->Next = NULL;
        __CopyMemQuick((APTR)SegmentCode,(APTR)&seg[1],codesize,sysbase);

        /* patch ELF-Object pointer into start-code */
        objptr = (ULONG *)((ULONG)&seg[1] +
                           ((ULONG)(SegmentCode_Object-SegmentCode)+2));
        *objptr = (ULONG)elfobj;
        SegList = MKBADDR(&seg->Next);
        PPCCacheClearE(seg,allocsize,CACRF_ClearI|CACRF_ClearD,ppcbase);
        Dprintf(ppcbase,"%sLoaded %s as ELF object\n",FN,name);
        __SetIoErr(RETURN_OK,dosbase);
      }

      else {
        /* loading ELF object failed - not enough memory */
        Dprintf(ppcbase,"%sLoading %s as ELF object failed\n",FN,name);
        PPCUnLoadObject(elfobj,ppcbase);
        __SetIoErr(ERROR_NO_FREE_STORE,dosbase);
      }
    }

    else {
      /* neither Hunk- nor ELF-format */
      __SetIoErr(ERROR_OBJECT_WRONG_TYPE,dosbase);
    }
  }

  if (ppcbase)
    __CloseLibrary((struct Library *)ppcbase,sysbase);
  return(SegList);
}


BOOL ELFUnLoadSeg(__reg("d1")BPTR seglist,__reg("a6")struct Library *dosbase)
{
  struct PPCLibBase *ppcbase = NULL;
  UBYTE *segment_addr;
  BOOL (*oldfunc)(__reg("d1")BOOL,
                  __reg("a6")struct Library *) = OldUnLoadSeg;

  if (seglist) {
    char *idptr;

    /* check if it's an ELF object */
    segment_addr = (UBYTE *)BADDR(seglist)+sizeof(ULONG);
    idptr = (char *)&segment_addr[(ULONG)(SegmentCode_ID-SegmentCode)];

    if (!_strcmp(idptr,(char *)SegmentCode_ID)
        && (ppcbase = (struct PPCLibBase *)
                      __OpenLibrary("ppc.library",46,sysbase))) {
      /* unload ELF */
      void *elfobj = (void *) *((ULONG *)
            (&segment_addr[(ULONG)(SegmentCode_Object-SegmentCode)+2]));

      Dprintf(ppcbase,"UnLoadSeg(): Unload ELF\n");
      if (elfobj)
        PPCUnLoadObject(elfobj,ppcbase);
    }
  }

  if (ppcbase)
    __CloseLibrary((struct Library *)ppcbase,sysbase);
  return (oldfunc(seglist,dosbase));
}


BOOL ELFLoadSegPatch(__reg("a0")struct PPCLibBase *ppcbase)
/* LoadSeg() patch to allow ELF objects to be started directly. */
{
  struct SignalSemaphore *ss;
  char *ssname;

  sysbase = ppcbase->SysBase;
  if (!__FindSemaphore((char *)patchname,sysbase)) {
    if ((ss = alloc32c(sizeof(struct SignalSemaphore),ppcbase))
        && (ssname = alloc32(_strlen((char *)patchname)+1,ppcbase))) {
      struct CommandLineInterface *cli;

      _strcpy(ssname,(char *)patchname);
      ss->ss_Link.ln_Name = ssname;
      ss->ss_Link.ln_Pri = -127;
      __InitSemaphore(ss,sysbase);
      __AddSemaphore(ss,sysbase);

      /* patch LVOs of LoadSeg(), UnLoadSeg() and  NewLoadSeg() */
      OldNewLoadSeg = _SetFunction(ppcbase->DOSBase,
                                    LVO_NewLoadSeg,
                                    (unsigned long (*)())ELFNewLoadSeg,
                                    sysbase);
      OldLoadSeg = _SetFunction(ppcbase->DOSBase,
                                 LVO_LoadSeg,
                                 (unsigned long (*)())ELFLoadSeg,
                                 sysbase);
      OldUnLoadSeg = _SetFunction(ppcbase->DOSBase,
                                   LVO_UnLoadSeg,
                                   (unsigned long (*)())ELFUnLoadSeg,
                                   sysbase);
      if (cli = (struct CommandLineInterface *)BADDR((((struct Process *)
                __FindTask(NULL,sysbase))->pr_CLI)))
        cli->cli_Module = NULL;
    }
    else
      return (FALSE);  /* out of memory */
  }
  /* patch already installed */
  return (TRUE);
}


BOOL RemLoadSegPatch(__reg("a0")struct PPCLibBase *ppcbase)
/* Remove LoadSeg()-patch, when expunging the library */
{
  struct Library *dosbase = ppcbase->DOSBase;
  struct SignalSemaphore *ss;
  APTR currLoadSeg,currUnLoadSeg,currNewLoadSeg;
  BOOL ret = FALSE;

  if (ss = __FindSemaphore((char *)patchname,sysbase)) {
    currNewLoadSeg = _SetFunction(dosbase,LVO_NewLoadSeg,
                                   OldNewLoadSeg,sysbase);
    currLoadSeg = _SetFunction(dosbase,LVO_LoadSeg,
                                OldLoadSeg,sysbase);
    currUnLoadSeg = _SetFunction(dosbase,LVO_UnLoadSeg,
                                  OldUnLoadSeg,sysbase);
    if (currNewLoadSeg==(APTR)ELFNewLoadSeg &&
        currLoadSeg==(APTR)ELFLoadSeg &&
        currUnLoadSeg==(APTR)ELFUnLoadSeg) {
      /* successful! */
      __RemSemaphore(ss,sysbase);
      ret = TRUE;
    }
    else {
      /* patch removal failed - there's another patch active */
      _SetFunction(dosbase,LVO_NewLoadSeg,currNewLoadSeg,sysbase);
      _SetFunction(dosbase,LVO_LoadSeg,currLoadSeg,sysbase);
      _SetFunction(dosbase,LVO_UnLoadSeg,currUnLoadSeg,sysbase);
    }
  }
  return (ret);
}


ULONG RunELF(__reg("a0")char *cmdline,__reg("a1")void *elfobj)
/* Start the ELF object on the PowerPC */
{
  static const char *FN = "RunELF(): ";
  ULONG result = ERROR_NO_FREE_STORE;
  struct PPCLibBase *ppcbase = NULL;
  struct Library *dosbase = NULL;
  struct Process *process = (struct Process *)__FindTask(NULL,sysbase);
  struct CommandLineInterface *cli;
  const char *cmdname;
  void *pool;
  ULONG stksize;
  struct TagItem ti[16];

  if ((ppcbase = (struct PPCLibBase *)
                 __OpenLibrary("ppc.library",46,sysbase))
      && (dosbase = __OpenLibrary("dos.library",37,sysbase))) {
    Dprintf(ppcbase,"%sELF-Object=0x%08lx CmdLine=%s\n",FN,elfobj,cmdline);
    if (pool = newpool32(1024,ppcbase)) {

      if (cli = BADDR(process->pr_CLI)) {
        /* Started from CLI */
        ULONG cmdlinelen = _strlen(cmdline);
        ULONG cmdlen,oldbuf,oldpos,oldend;
        int cpyindex;
        char *p,*args,*rdargsline;
        BPTR inputBPTR;
        struct FileHandle *inputFH;
        struct TagItem infotags[1];
        struct PPCObjectInfo *info;
        BOOL ctrlC;

        cmdname = (const char *)BADDR(cli->cli_CommandName);
        cmdlen = (ULONG)cmdname[0];
        if ((stksize = cli->cli_DefaultStack*sizeof(ULONG)*2) < DEFSTKSIZE)
          stksize = DEFSTKSIZE;
        Dprintf(ppcbase,"%sCLI start (stack=%ld bytes)\n",FN,stksize);

        /* replace \n by \0 */
        p = cmdline + (cmdlinelen-1);
        if (*p == '\n')
          *p = '\0';

        if (args = allocp32(pool,cmdlinelen+(cmdname?cmdlen+6:0),ppcbase)) {
          if (cmdname) {
            args[0] = '"';
            _memcpy(&args[1],&cmdname[1],cmdlen);
            args[1+cmdlen] = '"';
            args[1+cmdlen+1] = ' ';
            cpyindex = cmdlen+3;
          }
          else
            cpyindex = 0;
          _memcpy(&args[cpyindex],cmdline,cmdlinelen);

          if (rdargsline = allocp32(pool,cmdlinelen+2,ppcbase)) {
            _memcpy(rdargsline,cmdline,cmdlinelen-1);
            rdargsline[cmdlinelen-1] = '\n';
            rdargsline[cmdlinelen] = '\0';
            Dprintf(ppcbase,"%sArgs = %s",FN,rdargsline);

            /* save I/O state */
            inputBPTR = __Input(dosbase);
            inputFH = (struct FileHandle *)BADDR(inputBPTR);
            oldbuf = inputFH->fh_Buf;
            oldpos = inputFH->fh_Pos;
            oldend = inputFH->fh_End;

#if 0 /* @@@ Hmmm... */
            /* ctrl-c */
            infotags[0].ti_Tag = TAG_END;
            if (PPCGetObjectAttrs(elfobj,&info,infotags))
              ctrlC = FALSE;
            else
#endif
              ctrlC = TRUE;

            /* create synchronous task from ELF object */
            ti[0].ti_Tag  = PPCTASKTAG_STOPTASK;
            ti[0].ti_Data = FALSE;
            ti[1].ti_Tag  = PPCTASKTAG_WAITFINISH;
            ti[1].ti_Data = TRUE;
            ti[2].ti_Tag  = PPCTASKTAG_INPUTHANDLE;
            ti[2].ti_Data = (ULONG)inputBPTR;
            ti[3].ti_Tag  = PPCTASKTAG_OUTPUTHANDLE;
            ti[3].ti_Data = (ULONG)__Output(dosbase);
            ti[4].ti_Tag  = PPCTASKTAG_ARG1;
            ti[4].ti_Data = (ULONG)args;
            ti[5].ti_Tag  = PPCTASKTAG_ARG2;
            ti[5].ti_Data = NULL;
            ti[6].ti_Tag  = PPCTASKTAG_STACKSIZE;
            ti[6].ti_Data = stksize;
            ti[7].ti_Tag  = NP_CloseInput;
            ti[7].ti_Data = FALSE;
            ti[8].ti_Tag  = NP_CloseOutput;
            ti[8].ti_Data = FALSE;
            ti[9].ti_Tag  = NP_Cli;
            ti[9].ti_Data = TRUE;
            ti[10].ti_Tag = PPCTASKTAG_BREAKSIGNAL;
            ti[10].ti_Data= (ULONG)ctrlC;
            ti[11].ti_Tag = NP_Arguments;
            ti[11].ti_Data= (ULONG)rdargsline;
            ti[12].ti_Tag = NP_Name;
            ti[12].ti_Data= (ULONG)cmdname;
            ti[13].ti_Tag = NP_CommandName;
            ti[13].ti_Data= (ULONG)cmdname;
            ti[14].ti_Tag = NP_StackSize;
            ti[14].ti_Data= stksize;
            ti[15].ti_Tag = TAG_END;

            Dprintf(ppcbase,"%sPPCCreateTask\n",FN);
            result = (ULONG)PPCCreateTask(elfobj,ti,ppcbase);

            Dprintf(ppcbase,"%sTask returned. Clean up.\n",FN);
            __UnGetC(inputBPTR,-1,dosbase);
            inputFH->fh_Buf = oldbuf;
            inputFH->fh_Pos = oldpos;
            if (inputFH->fh_End)
              inputFH->fh_End = oldend;
          }
        }
      }

      else {
        /* Started from Workbench */
        struct WBStartup *wbmsg;
        struct WBArg *wbarg;
        BPTR newcd=NULL,oldcd;

        cmdname = NULL;
        if ((stksize = process->pr_StackSize * 2) < DEFSTKSIZE)
          stksize = DEFSTKSIZE;
        Dprintf(ppcbase,"%sWB start (stack=%ld bytes)\n",FN,stksize);

        /* get WB message */
        __WaitPort(&process->pr_MsgPort,sysbase);
        if (wbmsg = (struct WBStartup *)__GetMsg(&process->pr_MsgPort,
                                                 sysbase)) {
          if (wbarg = wbmsg->sm_ArgList) {
            if (newcd = __DupLock(wbarg->wa_Lock,dosbase))
              oldcd = __CurrentDir(newcd,dosbase);
            cmdname = wbarg->wa_Name;
          }

          /* create synchronous task from ELF object */
          ti[0].ti_Tag  = PPCTASKTAG_STOPTASK;
          ti[0].ti_Data = FALSE;
          ti[1].ti_Tag  = PPCTASKTAG_WAITFINISH;
          ti[1].ti_Data = TRUE;
          ti[4].ti_Tag  = PPCTASKTAG_ARG1;
          ti[4].ti_Data = NULL;
          ti[5].ti_Tag  = PPCTASKTAG_ARG2;
          ti[5].ti_Data = (ULONG)wbmsg;
          ti[6].ti_Tag  = PPCTASKTAG_STACKSIZE;
          ti[6].ti_Data = stksize;
          ti[7].ti_Tag  = PPCTASKTAG_BREAKSIGNAL;
          ti[7].ti_Data = TRUE;
          ti[8].ti_Tag  = NP_Name;
          ti[8].ti_Data = (ULONG)cmdname;
          ti[9].ti_Tag  = NP_StackSize;
          ti[9].ti_Data = stksize;
          ti[10].ti_Tag = TAG_END;

          Dprintf(ppcbase,"%sPPCCreateTask\n",FN);
          result = (ULONG)PPCCreateTask(elfobj,ti,ppcbase);

          Dprintf(ppcbase,"%sTask returned. Clean up/reply msg.\n",FN);
          if (newcd) {
            __CurrentDir(oldcd,dosbase);
            __UnLock(newcd,dosbase);
          }
          __Forbid(sysbase);
          __ReplyMsg(&wbmsg->sm_Message,sysbase);
        }
      }

      freepool32(pool,ppcbase);
    }
    Dprintf(ppcbase,"%sFinished (rc=%ld). Closing libs.\n",FN,result);
    __CloseLibrary(dosbase,sysbase);
    __CloseLibrary((struct Library *)ppcbase,sysbase);
  }
  return(result);
}
