/*
    Copyright 2001 (c) by Boris Lutz,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
	
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/VMemory/Attic/vm_loader.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3044 $
    Last Date of Change:   $Date: 2007-07-04 22:16:17 +0200 (Mit, 04 Jul 2007) $  by: $Author: sryffel $
 
*/
#include "types.h"
#include "Topsy.h"
#include "Messages.h"
#include "Syscall.h"
#include "Support.h"
#include "TMThread.h"
#include "Assert.h"

#include "TMProtectionDomain.h"
#include "MemoryLayout.h"
#include "vm_loader.h"
#include "kalloc_api.h"
#include "headerLayout.h"
#include "vm_hal.h"


/*** internal function prototypes ***/
slList* lookupList(slList* rootNode, char* name);
slList* insertList(slList** rootNode, topsyHead_t	fileHead);
RegionPtr_t getFileRegion(char* name, offset_t offset, offset_t start,
                          offset_t end, ThreadPermission regPermission, ThreadId pdId,
                          address_t* addNearHandle);
RegionPtr_t getHeapRegion(offset_t start,	offset_t end, ThreadPermission regPermission, ThreadId pdId);
RegionPtr_t getLoanOutRegion(ThreadPermission regPermission, ThreadId pdId);
int freeRegion(RegionPtr_t myReg, ThreadId pdId);

/*** Makro to make free a little bit eaysier & shorter :-) ***/
#define FREE_REGION(x)																							\
	do {																															\
		if (freeRegion((x), newPD->pdId) != LD_OK) {										\
			printf("vm_loader: failed to free File-Region (%p)\n", (x));	\
		}																																\
	} while (0)


void vm_loader(ThreadArg arg)
{
  Message							msg,
  reply;
  ThreadId							thisId,
  startedId,
  dummy;
  pdom_t							newPD;
  Thread							*callerThread,
  *loaderThread,
  *startedThread;
  ThreadPermission					pdPerm,
  thPerm;
  ThreadPriority					pdPrio,
  thPrio;
  RegionPtr_t						loadRegion[5];
  slList							*sizeList,
  *tmpNode;
  topsyHead_t						fileSegMap;
  char								*fileName;
  ThreadPriority					*priority;
  ThreadPermission					*permission;
  size_t							len,
  datbss_start,
  heap_start;
  address_t							segMapPtr;


  // init
  sizeList = km_alloc(sizeof(slList), KM_GENERIC);
  if (sizeList == NULL)
  {
    PANIC("VM_LOADER: km_alloc failed\n");
  }
  sizeList->prev = NULL;
  sizeList->next = NULL;
  sizeList->txtStart = 0L;
  sizeList->txtSize = 0L;
  sizeList->datStart = 0L;
  sizeList->datSize = 0L;
  sizeList->bssStart = 0L;
  sizeList->bssSize = 0L;
  sizeList->name[0] = '\0';
  if (tmGetInfo(SELF, &thisId, &dummy) < 0)
  {
    PANIC("VM_LOADER: tmGetInfo failed\n");
  }
  printf("vm_loader: after tmGetInfo  (tid %i  ptid %i)\n", thisId, dummy);
  loaderThread = getTHbyId(thisId);
  loadRegion[0] = loadRegion[1] = loadRegion[2] = loadRegion[3] = loadRegion[4] = NULL;

  // main loop
  while(TRUE)
  {
    thisId = ANY;
    CLEAR_MSGENV(reply);
    // printf("vm_loader: before tmMsgRecv\n");
    if (tmMsgRecv(&thisId, ANYMSGTYPE, &msg, INFINITY) == TM_MSGRECVFAILED)
      PANIC("message receive error!\n");
    
    printf("vm_loader: recv from %li  &msg   %p  msg.id   %i\n", msg.from, &msg, msg.id);
    
    switch (msg.id)
    {
    case LD_LOAD:
      /* Message: file name, permission, priority  *
       * Reply:   ErrorCode                        */

      // init
      reply.id = LD_REPLY;

      len = popEnvMsg(&msg, (address_t*)&fileName);
      if (len >= (sizeof(char) * MAXNAMESIZE) )
      {
        reply.msg.ld_reply.err = LD_FAIL;
        detachEnvMsg(&msg);
        tmMsgSend(msg.from, &reply);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&permission);
      if (len != sizeof(ThreadPermission) )
      {
        reply.msg.ld_reply.err = LD_FAIL;
        detachEnvMsg(&msg);
        tmMsgSend(msg.from, &reply);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&priority);
      if (len != sizeof(ThreadPriority) )
      {
        reply.msg.ld_reply.err = LD_FAIL;
        detachEnvMsg(&msg);
        tmMsgSend(msg.from, &reply);
        break;
      }

      printf("vm_loader: received message from %i to launch %s with permission %x and priority %i\n", (int)msg.from, fileName, *permission, *priority);
      // get structure of calling thread
      callerThread = getTHbyId(msg.from);

      // check permission (could add a separate bit just for
      // loader permission later if necessary)
      if (callerThread->permission & KERNEL_PERM_MSK)
      {
        // calculate permission & priority (enforce if necessary)
        thPerm = callerThread->permission;
        thPrio = callerThread->schedInfo.priority;
        pdPerm = *permission;
        pdPrio = *priority;
        // kernel can set any priority (respect system boundaries!)
        if ( (pdPrio < thPrio) && !(thPerm & KERNEL_PERM_MSK) )
        {
          // enforce (lower numers meen higher priority)
          printf("Warning: Loader is enforcing priority\n");
          pdPrio = thPrio;
        }
        // kernel can set any permission
        if (pdPerm & ~thPerm && !(thPerm & KERNEL_PERM_MSK) )
        {
          // enforce
          printf("Warning: threadBuild is enforcing permiss2a992ion\n");
          pdPerm = thPerm;
        }

        // check if SegMap already loaded
        tmpNode = lookupList(sizeList, fileName);
        if (tmpNode == NULL)
        {
          // if not, load first page to extract sizes
          segMapPtr = NULL;
          loadRegion[0] = getFileRegion(fileName, 0, 0, PAGESIZE - 1,
                                        VM_ACCESS_READ,	loaderThread->pdPtr->pdId, &segMapPtr);
          if (loadRegion[0] == NULL)
          {
            reply.msg.ld_reply.err = LD_FAIL;
            detachEnvMsg(&msg);
            tmMsgSend(msg.from, &reply);
            break;
          }
          //-BL printf("dumping region & part of segmap...\n");
          //-BL dumpMemory((address_t)loadRegion[0], 0, 64);
          //-BL dumpMemory(segMapPtr, 0, 64);
          // header starts at offset 0x10 see headerLayout.h
          fileSegMap = (address_t) segMapPtr + ORG_MARKER;
          // and store in list
          printf("vm_laoder: insert list %p  segmap %p\n", &sizeList, fileSegMap);
          tmpNode = insertList(&sizeList, fileSegMap);
          if (tmpNode == NULL)
            PANIC("VM_LOADER: failed to alloc memory for list\n");
          // free first page again (don't use Makro here since this is a other PD!!!)
          if (freeRegion(loadRegion[0], loaderThread->pdPtr->pdId) != LD_OK)
          {
            printf("vm_loader: failed to free File-Region (%p)\n", loadRegion[0]);
          }
        }
        printf("vm_loader: loaded segmap\n  txtStart\t= %x\n  txtSize\t\t= %x\n  datStart\t= %x\n  datSize\t\t= %x\n  bssStart\t= %x\n  bssSize\t\t= %x\n", 
              (uint32)tmpNode->txtStart, (uint32)tmpNode->txtSize, (uint32)tmpNode->datStart, (uint32)tmpNode->datSize, (uint32)tmpNode->bssStart, (uint32)tmpNode->bssSize);

        // set up new PD
        newPD = pdCreate(msg.from, fileName, getSegStart( (pdPerm &
                         KERNEL_PERM_MSK)?KERNEL:USER ), getSegEnd( (pdPerm &
                             KERNEL_PERM_MSK)?KERNEL:USER ), pdPerm, pdPrio);

        // get text segment
        loadRegion[0] = getFileRegion(fileName, 0, tmpNode->txtStart,
                                      tmpNode->txtStart + tmpNode->txtSize - 1, VM_ACCESS_READ |
                                      VM_ACCESS_EXEC, newPD->pdId, NULL);
        if (loadRegion[0] == NULL)
        {
          pdDestroy(newPD->pdId);		// XXX check out if this works
          reply.msg.ld_reply.err = LD_FAIL;
          detachEnvMsg(&msg);
          tmMsgSend(msg.from, &reply);
          break;
        }

        // get data & bss segment
        // check if bss and data are one segment or separated
        if ( tmpNode->datStart + tmpNode->datSize == tmpNode->bssStart  ||
             tmpNode->bssStart + tmpNode->bssSize == tmpNode->datStart)
        {
          // dat and bss together
          datbss_start = ( (tmpNode->datStart < tmpNode->bssStart) ?
                           tmpNode->datStart : tmpNode->bssStart );
          loadRegion[1] = getFileRegion(fileName,
                                        datbss_start - tmpNode->txtStart, datbss_start,
                                        datbss_start + tmpNode->datSize + tmpNode->bssSize - 1,
                                        VM_ACCESS_READ | VM_ACCESS_WRITE, newPD->pdId, NULL);
          if (loadRegion[1] == NULL)
          {
            FREE_REGION(loadRegion[0]);
            pdDestroy(newPD->pdId);		// XXX check out if this works
            reply.msg.ld_reply.err = LD_FAIL;
            detachEnvMsg(&msg);
            tmMsgSend(msg.from, &reply);
            break;
          }
        }
        else
        {
          // dat and bss separated (in this case bss is not loaded from
          // the file, it is allocated as a anonymous region instead)
          loadRegion[1] = getFileRegion(fileName,
                                        tmpNode->datStart - tmpNode->txtStart, tmpNode->datStart,
                                        tmpNode->datStart + tmpNode->datSize - 1,
                                        VM_ACCESS_READ | VM_ACCESS_WRITE, newPD->pdId, NULL);
          if (loadRegion[1] == NULL)
          {
            FREE_REGION(loadRegion[0]);
            pdDestroy(newPD->pdId);		// XXX check out if this works
            reply.msg.ld_reply.err = LD_FAIL;
            detachEnvMsg(&msg);
            tmMsgSend(msg.from, &reply);
            break;
          }
          // load bss as anonymous Region
          loadRegion[2] = getHeapRegion(tmpNode->bssStart,
                                        tmpNode->bssStart + tmpNode->bssSize - 1,
                                        VM_ACCESS_READ | VM_ACCESS_WRITE, newPD->pdId);
          if (loadRegion[2] == NULL)
          {
            FREE_REGION(loadRegion[0]);
            FREE_REGION(loadRegion[1]);
            pdDestroy(newPD->pdId);		// XXX check out if this works
            reply.msg.ld_reply.err = LD_FAIL;
            detachEnvMsg(&msg);
            tmMsgSend(msg.from, &reply);
            break;
          }
        }
        // add heap region just after bss & dat
        heap_start = ( (tmpNode->datStart < tmpNode->bssStart) ?
                       (tmpNode->bssStart + tmpNode->bssSize) : (tmpNode->datStart +
                           tmpNode->datSize) );
        heap_start = PAGEROUNDUP(heap_start);     // heap has to start at a page boundary
        loadRegion[3] = getHeapRegion(heap_start, heap_start + 0x00100000 * 128 - 1, VM_ACCESS_READ | VM_ACCESS_WRITE, newPD->pdId);
        if (loadRegion[3] == NULL)
        {
          FREE_REGION(loadRegion[0]);
          FREE_REGION(loadRegion[1]);
          if (loadRegion[2] != NULL)
          {
            FREE_REGION(loadRegion[2]);
          }
          pdDestroy(newPD->pdId);		// XXX check out if this works
          reply.msg.ld_reply.err = LD_FAIL;
          detachEnvMsg(&msg);
          tmMsgSend(msg.from, &reply);
          break;
        }
        // add loan out region (somewhere in the middle of the PD)
        loadRegion[4] = getLoanOutRegion(VM_ACCESS_READ | VM_ACCESS_WRITE,
                                         newPD->pdId);
        if (loadRegion[4] == NULL)
        {
          FREE_REGION(loadRegion[0]);
          FREE_REGION(loadRegion[1]);
          if (loadRegion[2] != NULL)
          {
            FREE_REGION(loadRegion[2]);
          }
          FREE_REGION(loadRegion[3]);
          pdDestroy(newPD->pdId);		// XXX check out if this works
          reply.msg.ld_reply.err = LD_FAIL;
          detachEnvMsg(&msg);
          tmMsgSend(msg.from, &reply);
          break;
        }
        printf("Loader: loan-out region @ %p - %p\n",
               loadRegion[4]->start, loadRegion[4]->end);

        printf("vm_loader trying to launch thread\n");
        // set up first thread in PD
        startedId = threadStart( (ThreadMainFunction) tmpNode->txtStart,
                                 (address_t)heap_start, (pdPerm &	KERNEL_PERM_MSK) ?
                                 KERNEL : USER, fileName,	callerThread->id, newPD->pdId,
                                 pdPrio, 0);
        if (startedId == TM_THREADSTARTFAILED)
        {
          printf("vm_loader: failed to start Thread\n");
          FREE_REGION(loadRegion[0]);
          FREE_REGION(loadRegion[1]);
          if (loadRegion[2] != NULL)
          {
            FREE_REGION(loadRegion[2]);
          }
          FREE_REGION(loadRegion[3]);
          FREE_REGION(loadRegion[4]);
          pdDestroy(newPD->pdId);		// XXX check out if this works
          reply.msg.ld_reply.err = LD_FAIL;
        }
        else
        {
          printf("vm_loader: started Thread (id:%li) in PD %li\n",
                 startedId, newPD->pdId);
          startedThread = getTHbyId(startedId);
          startedThread->permission = pdPerm;
          reply.msg.ld_reply.err = LD_OK;
        }
      }
      detachEnvMsg(&msg);
      tmMsgSend(msg.from, &reply);
      break;
    case UNKNOWN_SYSCALL:
      // WARNING("vm_loader: message type UNKOWN_SYSCALL received");
      printf("vm_loader: got UNKNOWN message %i  &msg=%p\n", msg.id, &msg);
      WARNING("unknown message");
      break; /* do not reply on unknown syscalls to prevent loops */
    default:
      reply.id = UNKNOWN_SYSCALL;
      tmMsgSend(msg.from, &reply);
      break;
    }
  }
}


slList* lookupList(slList* rootNode, char* name)
{
  while (rootNode != NULL)
  {
    if ( str_ncmp(rootNode->name, name, 256) != 0 )
      rootNode = rootNode->next;
    else
      break;
  }
  return rootNode;
}


slList* insertList(slList** rootNode, topsyHead_t fileHead)
{
  slList*		tmpNode;

  tmpNode = km_alloc(sizeof(slList), KM_MEMORY);
  if (tmpNode == NULL)
    return NULL;

  tmpNode->next = *rootNode;
  tmpNode->prev = NULL;
  tmpNode->txtStart = fileHead->textstart;
  tmpNode->txtSize  = fileHead->textsize;
  tmpNode->datStart = fileHead->datastart;
  tmpNode->datSize  = fileHead->datasize;
  tmpNode->bssStart = fileHead->bssstart;
  tmpNode->bssSize  = fileHead->bsssize;
  *rootNode = tmpNode;
  return tmpNode;
}

RegionPtr_t getFileRegion(char* name, offset_t offset, offset_t start,
                          offset_t end, ThreadPermission regPermission, ThreadId pdId,
                          address_t* addNearHandle)
{
  size_t					len,
  namelen;
  RegionPtr_t			myReg;
  Message					msg,
  reply;
  ThreadId				fromId;
  unsigned char 	*regArg;



  // set up region for loading the segment
  // calculate length of arguments
  namelen = str_len(name) + 1;
  if ( namelen > 255 )
  {
    PANIC("VM_LOADER: filename to long\n");
  }
  len = namelen + 2;
  len += sizeof(offset_t) + 2;
  // allocate region
  myReg = km_alloc(sizeof(Region_t) + len, KM_MEMORY);
  if ( myReg == NULL )
  {
    PANIC("VM_LOADER: no more memory left\n");
  }
  // set up general region information
  myReg->start = (address_t*)start;
  myReg->end   = (address_t*)end;
  myReg->cur = myReg->max = regPermission;
  myReg->type  = VM_MANAGER_FILE;
  // set up argument (file name)
  regArg = myReg->obj_arg.arg;
  *regArg++ = namelen;						// length of 1st argument (file name)
  *regArg++ = VM_FILE_NAME;				// type of 1st argument
  str_cpy(regArg, name);					// copy argunent
  regArg += namelen;							// point to 2nd argument
  *regArg++ = sizeof(offset_t);		// length of 2nd argument (offset)
  *regArg++ = VM_FILE_OFFSET;			// type of 2nd argument
  byteCopy(regArg, &offset, sizeof(offset_t));	// copy argument
  regArg += sizeof(offset_t);			// next (to make sure & for debugging)
  myReg->obj_arg.argsize = len;		// total length of all arguments
  ASSERT( len == (int)((address_t)regArg - (address_t)myReg->obj_arg.arg) );

  // attach a page to msg (don't keep, init PP)
  if (attachEnvMsg(&msg, VM_PP_INIT) != 0)
  {
    printf("getFileRegion: failed to attach EnvMsg\n");
    return NULL;
  }
  // could check length returned after each push...
  pushEnvMsg(&msg, &pdId, sizeof(ThreadId));
  pushEnvMsg(&msg, myReg, sizeof(Region_t) + len);
  if (addNearHandle == NULL)
  {
    msg.id = VM_REGION_ADD;
  }
  else
  {
    msg.id = VM_REGION_ADD_NEAR;
    pushEnvMsg(&msg, addNearHandle, sizeof(address_t));
  }

  tmMsgSend(MMTHREADID, &msg);
  fromId = MMTHREADID;
  tmMsgRecv(&fromId, VM_REPLY, &reply, INFINITY);
  if (reply.msg.vm_reply.err != VM_OK)
  {
    printf("getFileRegion: received Error from MM (%li)\n", reply.msg.vm_reply.err);
    return NULL;
  }
  else
  {
    if (addNearHandle)
    {
      myReg->start = *addNearHandle = reply.msg.vm_reply.ptr1;
      myReg->end = reply.msg.vm_reply.ptr2;
    }
    printf("==> myReg: addr %p  reg %p-%p\n", myReg, myReg->start, myReg->end);
    return myReg;
  }
}


RegionPtr_t getHeapRegion(offset_t start,	offset_t end, ThreadPermission regPermission, ThreadId pdId)
{
  RegionPtr_t			myReg;
  Message					msg,
  reply;
  ThreadId				fromId;



  printf("getHeapRegion: start: %p, end: %p, PD: %i\n", start, end, pdId);
  // set up region for loading the segment
  // allocate region
  myReg = km_alloc(sizeof(Region_t), KM_MEMORY);
  if ( myReg == NULL )
  {
    PANIC("VM_LOADER: no more memory left\n");
  }
  // set up generla region information
  myReg->start = (address_t*)start;
  myReg->end   = (address_t*)end;
  myReg->cur = myReg->max = regPermission;
  myReg->type  = VM_MANAGER_ANON;
  // we don't need no arguments :-)
  myReg->obj_arg.arg[0] = 0;
  myReg->obj_arg.argsize = 0;

  // attach a page to msg (don't keep, init PP)
  if (attachEnvMsg(&msg, VM_PP_INIT) != 0)
  {
    printf("getFileRegion: failed to attach EnvMsg\n");
    return NULL;
  }
  msg.id = VM_REGION_ADD;
  // could check length returned after each push...
  pushEnvMsg(&msg, &pdId, sizeof(ThreadId));
  pushEnvMsg(&msg, myReg, sizeof(Region_t));
  tmMsgSend(MMTHREADID, &msg);
  fromId = MMTHREADID;
  tmMsgRecv(&fromId, VM_REPLY, &reply, INFINITY);
  if (reply.msg.vm_reply.err != VM_OK)
  {
    printf("getHeapRegion: received Error from MM (%li)\n", reply.msg.vm_reply.err);
    return NULL;
  }
  else
  {
    return myReg;
  }
}


RegionPtr_t getLoanOutRegion(ThreadPermission regPermission, ThreadId pdId)
{
  RegionPtr_t				myReg;
  Message						msg,
  reply;
  ThreadId					fromId;
  size_t						lrSize;



  // set up region for loading the segment
  // allocate region
  myReg = km_alloc(sizeof(Region_t), KM_MEMORY);
  if ( myReg == NULL )
  {
    PANIC("VM_LOADER: no more memory left\n");
  }
  // set up general region information
  lrSize = 0x000FFFFF;

  // attach a page to msg (don't keep, init PP)
  if (attachEnvMsg(&msg, VM_PP_INIT) != 0)
  {
    printf("getFileRegion: failed to attach EnvMsg\n");
    return NULL;
  }
  msg.id = VM_REGION_ADD_LOAN;
  // could check length returned after each push...
  pushEnvMsg(&msg, &pdId, sizeof(ThreadId));
  pushEnvMsg(&msg, &lrSize, sizeof(size_t));
  pushEnvMsg(&msg, &regPermission, sizeof(ThreadPermission));
  tmMsgSend(MMTHREADID, &msg);
  fromId = MMTHREADID;
  tmMsgRecv(&fromId, VM_REPLY, &reply, INFINITY);
  if (reply.msg.vm_reply.err != VM_OK)
  {
    printf("getLoanOutRegion: received Error from MM (%li)\n", reply.msg.vm_reply.err);
    return NULL;
  }
  else
  {
    myReg->start = reply.msg.vm_reply.ptr1;
    myReg->end = reply.msg.vm_reply.ptr2;
    return myReg;
  }
}


int freeRegion(RegionPtr_t myReg, ThreadId pdId)
{
  Message		msg,
  reply;
  ThreadId	fromId;


  // attach a page to msg (don't keep, init PP)
  if (attachEnvMsg(&msg, VM_PP_INIT) != 0)
  {
    printf("freeRegion: failed to attach message\n");
    return LD_FAIL;
  }
  msg.id = VM_REGION_FREE;
  // could check length returned after each push...
  pushEnvMsg(&msg, &pdId, sizeof(ThreadId));
  pushEnvMsg(&msg, myReg, sizeof(Region_t));
  tmMsgSend(MMTHREADID, &msg);
  fromId = MMTHREADID;
  tmMsgRecv(&fromId, VM_REPLY, &reply, INFINITY);
  if (reply.msg.vm_reply.err != VM_OK)
    return LD_FAIL;
  else
    return LD_OK;
}

