/*
    Copyright 2001 (c) by Claudio Jeker,
    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_main.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3048 $
    Last Date of Change:   $Date: 2007-07-20 18:11:54 +0200 (Fre, 20 Jul 2007) $  by: $Author: sryffel $
 
*/
#include "Topsy.h"
#include "Messages.h"
#include "Syscall.h"
#include "Threads.h"
#include "TMThread.h"
#include "TMScheduler.h"
#include "MemoryLayout.h"

#include "Support.h"
#include "Assert.h"

#include "vm.h"
#include "vm_extern.h"
#include "vm_calls.h"
#include "steal_alloc.h"
#include "vm_hash.h"

#include "TMProtectionDomain.h"

#include "vm_main.h"

#include "TMHal.h"

/*** MM internal function prototypes ***/
int detachMMEnvMsg (Message *msg);


/* main event loop */
void vm_main(ThreadArg arg)
{
  Region_t						region, hint_region,
  *regPtr;
  Message						msg;
  Message						reply;
  ThreadId						any,
  *pdId, *pdId2;
  ThreadPtr						caller;
  pdom_t						pd, pd2;
  int							err, direction, *dirPtr, flags, *flagsPtr;
  size_t						len,
  *lrSize, size, *sizePtr;
  address_t       hint, *hintPtr, addr;
  ThreadPermission	*lrPerm;
  struct vm_region *prev,*t;
  struct vm_map* vm_map;
  offset_t offset;
  struct vm_page* page;

  printf("starting virtual Memory Manager...\n");

  /* the kernel is up and running now it is save to allocate big buffers */
  //-CJ	vm_hash_rehash();

  while(TRUE)
  {
    any = ANY;
    CLEAR_MSGENV(reply);

    if (tmMsgRecv(&any, ANYMSGTYPE, &msg, INFINITY) == TM_MSGRECVFAILED)
      PANIC("message receive error!");

    // printf("mmThread: recv from %2li  &msg   %p  msg.id   %i\n", msg.from, &msg, msg.id);

    // printf("vm_main: got message %li from %li   (VM_LOOKUP = %li)\n", msg.id, msg.from, VM_LOOKUP);
    switch (msg.id)
    {
    case VM_REGION_ADD:
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&regPtr);
      if (len < sizeof(Region_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      pd = getPDbyId(*pdId);
      // printf("==> regPtr = %p start %p  end %p\n", regPtr,regPtr->start,regPtr->end);
      reply.msg.vm_reply.err = vm_region_add(&pd->vm_space.vm_map,
                                             &pd->vm_space.vm_pmap, regPtr);
      detachMMEnvMsg(&msg);
      break;

    case VM_REGION_ADD_NEAR:
      /* Message: pointer to enveloppe page
       *          env: pdId, region, near
       * Reply: ErrorCode
       */
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&regPtr);
      if (len < sizeof(Region_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&hintPtr);
      if (len != sizeof(address_t))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      pd = getPDbyId(*pdId);
      hint = *hintPtr;
      reply.msg.vm_reply.err = vm_region_add_near(&pd->vm_space.vm_map,
                               &pd->vm_space.vm_pmap, regPtr, hint, 1);
      reply.msg.vm_reply.ptr1 = regPtr->start;
      reply.msg.vm_reply.ptr2 = regPtr->end;
      detachMMEnvMsg(&msg);
      break;

    case VM_REGION_ADD_LOAN:
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&lrSize);
      if (len < sizeof(size_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&lrPerm);
      if (len < sizeof(ThreadPermission) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      region.start = NULL;
      region.end = (address_t)*lrSize;
      region.cur = region.max = *lrPerm;
      region.type = VM_MANAGER_LOAN;
      region.obj_arg.arg[0] = 0;
      region.obj_arg.argsize = 0;

      pd = getPDbyId(*pdId);
      err = vm_region_add_near(&pd->vm_space.vm_map, &pd->vm_space.vm_pmap,
                               &region, 0, 1);
      if (err != VM_OK || vm_region_set_loan(&pd->vm_space.vm_map,
                                             &pd->vm_space.vm_pmap, &region, NULL) != VM_OK)
      {
        reply.msg.vm_reply.err = VM_FAILED;
      }
      else
      {
        reply.msg.vm_reply.err = VM_OK;
      }
      reply.msg.vm_reply.ptr1 = region.start;
      reply.msg.vm_reply.ptr2 = region.end;
      detachMMEnvMsg(&msg);
      break;

    case VM_REGION_FREE:
      /* Message: PD Id, RegionPtr
      	 Reply: ErrorCode
       */
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&regPtr);
      if (len < sizeof(Region_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      pd = getPDbyId(*pdId);
      reply.msg.vm_reply.err = vm_region_free(&pd->vm_space.vm_map,
                                              &pd->vm_space.vm_pmap, regPtr);
      detachMMEnvMsg(&msg);
      break;

    case VM_REGION_FREE_ALL:
      /* Message: PD-Id
      	 Reply:	ErrorCode
       */
      reply.id = VM_REPLY;
      pd = getPDbyId(msg.msg.vm_free_all.pdId);
      err = vm_region_free_all(&pd->vm_space.vm_map, &pd->vm_space.vm_pmap);
      reply.msg.vm_reply.err = err;
      break;

    case VM_REGION_FREE_HINT:
      /* Message: PD Id, hint
      	 Reply: ErrorCode
       */
      reply.id = VM_REPLY;
      if ( msg.msg.vm_free_hint.pdId == SELFPD )
      {
        caller = getTHbyId(msg.from);
        if ( caller == NULL )
        {
          reply.msg.vm_reply.err = VM_FAILED;
          break;
        }
        pd = caller->pdPtr;
      }
      else
      {
        pd = getPDbyId(msg.msg.vm_free_hint.pdId);
        if ( pd == NULL )
        {
          reply.msg.vm_reply.err = VM_FAILED;
          break;
        }
      }
      region.obj_arg.argsize = 0;
      region.obj_arg.arg[0] = 0;

      reply.msg.vm_reply.err = vm_region_get(&pd->vm_space.vm_map,
                                             &pd->vm_space.vm_pmap, msg.msg.vm_free_hint.hintaddr, &region);
      if ( reply.msg.vm_reply.err != VM_OK )
        break;

      reply.msg.vm_reply.err = vm_region_free(&pd->vm_space.vm_map,
                                              &pd->vm_space.vm_pmap, &region);
      break;

    case VM_REGION_SHARE:
      /* Message: pointer to enveloppe page
       *          env: sourcePdId, destPdId, region, hint, flags, direction
       * Reply: ErrorCode
       */
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&pdId2);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&regPtr);
      if (len < sizeof(Region_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&hintPtr);
      if (len != sizeof(address_t))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&flagsPtr);
      if (len != sizeof(int))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&dirPtr);
      if (len != sizeof(int))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      pd  = getPDbyId(*pdId);
      pd2 = getPDbyId(*pdId2);
      hint = *hintPtr;
      flags = *flagsPtr;
      direction = *dirPtr;

      // printf("mmThread VM_REGION_SHARE\n");
      // printf("  source: pd %i  start %p  end %p\n", *pdId, regPtr->start, regPtr->end);
      // printf("  dest  : pd %i  hint  %p\n", *pdId2, hint);
      
      reply.msg.vm_reply.err = vm_region_share_near( &pd->vm_space.vm_map, &pd->vm_space.vm_pmap, regPtr,  // source: pd
                               &pd2->vm_space.vm_map,   &pd2->vm_space.vm_pmap,                            // destination: pd2
                               &hint, flags, direction);
      reply.msg.vm_reply.ptr1 = hint;
      reply.msg.vm_reply.ptr2 = 0;
      detachMMEnvMsg(&msg);
      break;

    case VM_REGION_RESIZE:
      /* Message: PD Id, RegionPtr
      	 Reply: ErrorCode
       */
      // XXX similar to REGION_ADD
      // vm_region_resize(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
      // RegionPtr_t old, RegionPtr_t new);
      reply.msg.vm_reply.err = VM_NIMP;
      break;

    case VM_REGION_CHPROT:
      /* Message: PD Id, RegionPtr, mode, max mode
      	 Reply: ErrorCode
       */
      // XXX can't pass regPtr like that.... XXX
      // vm_region_chprot(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
      // RegionPtr_t region, unsigned int cur, unsigned int max);
      reply.msg.vm_reply.err = VM_NIMP;
      break;

    case VM_REGION_GET:
      /* Message: PD Id, hint, ptr to allocated region structure
      	 Reply: ErrorCode (region structure is filled)
       */
      // XXX can't pass regPtr like that.... XXX
      // reply.msg.vm_reply.err = vm_region_get(&pd->vm_space.vm_map,
      //		&pd->vm_space.vm_pmap, msg.msg.vm_get.hint,
      //		msg.msg.vm_get.regPtr);
      reply.msg.vm_reply.err = VM_NIMP;
      break;

    case VM_LOOKUP:
      /* Message: PD Id, virtualaddr, type
      	 Reply: ErrorCode
       */
      pd = getPDbyId(msg.msg.vm_lookup.pdId);
      caller = getTHbyId(msg.from);
      ASSERT( pd != NULL );
      ASSERT( caller != NULL );
      //-CJ				printf("vm_main: lookup: %p in pd %li type %s\n",
      //-CJ						msg.msg.vm_lookup.badVAddr, msg.msg.vm_lookup.pdId,
      //-CJ						msg.msg.vm_lookup.type==VM_ACCESS_WRITE?"write":"read");

      err = vm_region_lookup(&pd->vm_space.vm_map,
                             &pd->vm_space.vm_pmap, msg.msg.vm_lookup.badVAddr,
                             msg.msg.vm_lookup.type);

      if ( err != VM_OK )
      {
        printf("vm_main: lookup failed, err %i: invalid address killing thread %li\n",
               err, msg.from);
        reply.id = TM_KILL;
        reply.msg.tmKill.id = msg.from;
        tmMsgSend(TMTHREADID, &reply);
        continue;
      }
      // printf("vm_main: lookup succeded: waking thread %li up\n", msg.from);

      LOCK_THREAD(caller, flags);
      {
        tmHalThreadSetReady(caller, PAGE_FAULT);
      }
      UNLOCK_THREAD(caller, flags);

      /* VM_LOOKUP does not send a message back. Only wake up the caller. */
      continue;

    case VM_ALLOC_STACK:
      /* Message: PD Id, hintaddr, size
      	 Reply: ErrorCode
       */
      reply.id = VM_REPLY;
      region.obj_arg.argsize = 0;
      region.obj_arg.arg[0] = 0;
      region.start = 0;
      region.end = (address_t)msg.msg.vm_alloc_stack.size - 1;
      /* VM uses the last valid address and not the first invalid */

      region.cur = VM_ACCESS_READ|VM_ACCESS_WRITE;
      region.max = VM_ACCESS_READ|VM_ACCESS_WRITE;
      region.type = (unsigned short)VM_MANAGER_ANON;
    
      pd = getPDbyId(msg.msg.vm_alloc_stack.pdId);
      ASSERT( pd != NULL );

      reply.msg.vm_reply.err = vm_region_add_near(&pd->vm_space.vm_map,
                               &pd->vm_space.vm_pmap, &region,
                               msg.msg.vm_alloc_stack.hintaddr, -1);
      if ( reply.msg.vm_reply.err != VM_OK )
        break;

      caller = getTHbyId(msg.from);
      ASSERT( caller != NULL );

      reply.msg.vm_reply.ptr2 = 0;
      reply.msg.vm_reply.err = vm_region_share_near(
                                 &pd->vm_space.vm_map, &pd->vm_space.vm_pmap, &region,
                                 &caller->pdPtr->vm_space.vm_map, &caller->pdPtr->vm_space.vm_pmap,
                                 &reply.msg.vm_reply.ptr2, VM_OBJ_SHARE_RW, 1);
      reply.msg.vm_reply.ptr1 = region.start;

      // printf("VM_ALLOC_STACK: \n");
      // printf("     pdId1=%i  err1=%i  ptr1=%p\n", msg.msg.vm_alloc_stack.pdId, reply.msg.vm_reply.err, region.start);
      // printf("     pdId2=%i  err2=%i  ptr2=%p\n", msg.from, reply.msg.vm_reply.err, reply.msg.vm_reply.ptr2);
      break;

    case VM_ATTACH_MSG_PAGE:
      /* Message: flags
       * Reply:   page address, error
       */

      caller = getTHbyId(msg.from);
      pd = caller->pdPtr;

      reply.msg.vm_msg_page.error = vm_region_loan_alloc(
                                      &pd->vm_space.vm_map,
                                      &pd->vm_space.vm_pmap,
                                      &reply.msg.vm_msg_page.pageAddr,
                                      msg.msg.vm_msg_page.flags);

      //-CJ				printf("VM_ATTACH_MSG_PAGE: from % li  env @ %p\n",
      //-CJ						msg.from, reply.msg.vm_msg_page.pageAddr);

      reply.id = VM_ATTACH_MSG_PAGE_REPLY;
      break;

    case VM_DETACH_MSG_PAGE:
      /* Message: pointer to enveloppe page
       * Reply:   error
       */

      caller = getTHbyId(msg.from);
      pd = caller->pdPtr;

      //-CJ				printf("VM_DETACH_MSG_PAGE: from % li  env @ %p\n",
      //-CJ						msg.from, reply.msg.vm_msg_page.pageAddr);

      reply.msg.vm_msg_page.error = vm_region_loan_dealloc(
                                      &pd->vm_space.vm_map,
                                      &pd->vm_space.vm_pmap,
                                      msg.msg.vm_msg_page.pageAddr);

      reply.id = VM_DETACH_MSG_PAGE_REPLY;
      break;

    case VM_MAP_FIND_NEAR:
      /* Message: pointer to enveloppe page
       *            containing: pdId, hint, size, region, direction
       * Reply:   error
       */
      reply.id = VM_REPLY;
      len = popEnvMsg(&msg, (address_t*)&pdId);
      if (len != sizeof(ThreadId) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&hintPtr);
      if (len != sizeof(address_t))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&sizePtr);
      if (len != sizeof(size_t))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&regPtr);
      if (len < sizeof(Region_t) )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      len = popEnvMsg(&msg, (address_t*)&dirPtr);
      if (len != sizeof(int))
      {
        reply.msg.vm_reply.err = VM_FAILED;
        detachMMEnvMsg(&msg);
        break;
      }
      pd = getPDbyId(*pdId);
      vm_map = &pd->vm_space.vm_map;
      hint = *hintPtr;
      size = *sizePtr;
      direction = *dirPtr;

      // printf("mmThread VM_MAP_FIND_NEAR: pd %i  hint  %p\n  size %p  direction %i \n  start %p  end %p\n",
      //   *pdId, hint, size, direction, regPtr->start, regPtr->end);
      reply.msg.vm_reply.err = vm_map_find_near(&vm_map,  hint, size,
                               regPtr, &prev, direction);
      reply.msg.vm_reply.ptr1 = regPtr->start;
      reply.msg.vm_reply.ptr2 = regPtr->end;
      detachMMEnvMsg(&msg);
      break;


    case VM_REGION_ENTER:
      /*
       *
       */
      // printf("VM_REGION_ENTER: pdId %i  hint %p\n", msg.msg.vm_get.pdId,  msg.msg.vm_get.hint);
      reply.id = VM_REPLY;
      caller = getTHbyId(msg.from);

      pd = getPDbyId(msg.msg.vm_get.pdId);
      vm_map = &pd->vm_space.vm_map;

      // search for region in map
      hint_region.start = msg.msg.vm_get.hint;
      hint_region.end = msg.msg.vm_get.hint;
      err = vm_map_lookup_region(&vm_map, &hint_region, &t); 

      if (err == VM_FREE || err == VM_OUTOFMAP)
      {
        // hint does not point to a valid region
        reply.msg.vm_reply.err = VM_FAILED;
        break;
      }
      /* address belongs to a region, lookup via manager */
      if ( t->oref.manager == NULL || t->oref.manager->lookup == NULL )
      {
        reply.msg.vm_reply.err = VM_FAILED;
        break;
      }

      // printf("   found region from %p to %p\n", t->start, t->end);

      // enter region into pmap
      for (addr=t->start; addr<t->end; addr += vm->stat.pagesize)
      {
        offset = (offset_t)((address_t)
                        ((unsigned long)addr & vm->stat.pagemask) - t->start);
        // printf("   entering addr %p\n", addr); 
        if (t->oref.manager->lookup(&t->oref, offset, VM_ACCESS_READ, &page) != TM_OK 
            || vm_pmap_enter(&pd->vm_space.vm_pmap, 
                          (address_t)((unsigned long)addr & vm->stat.pagemask),
                          vm_page_getaddr(page), t->cur) != VM_OK)
        {
          reply.msg.vm_reply.err = VM_FAILED;
          break;
        } 
      }
      reply.msg.vm_reply.err = VM_OK;

      break;

    case TM_KILLREPLY:
      /* just digest messages of killed thread because of lookup faults */
      continue;
    default:
      printf("vm_main: got UNKNOWN message %i  &msg=%p\n", msg.id, &msg);
      WARNING("unknown message");
      reply.id = UNKNOWN_SYSCALL;
      break;
    }
    // printf("mmThread: send to   %2li  &reply %p  reply.id %i\n", msg.from, &reply, reply.id);
    tmMsgSend(msg.from, &reply);
  }
}


int detachMMEnvMsg (Message *msg)
{
  Thread	*caller;
  pdom_t	pd;


  caller = getTHbyId(msg->from);
  pd = caller->pdPtr;
  return vm_region_loan_dealloc(&pd->vm_space.vm_map, &pd->vm_space.vm_pmap,
                                msg->enveloppe);

}
