/*
    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_anon.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3011 $
    Last Date of Change:   $Date: 2007-04-20 15:13:30 +0200 (Fre, 20 Apr 2007) $  by: $Author: sryffel $
 
*/

/*
	 vm_anon: the anonymous pager object.
 */
#include "Topsy.h"
#include "Error.h"
#include "Assert.h"
#include "Support.h"
#include "MemoryLayout.h"

#include "vm.h"
#include "vm_alloc.h"
#include "vm_object.h"
#include "vm_hash.h"
#include "tailq.h"
#include "vm_page.h"
#include "steal_alloc.h"

#include "vm_anon.h"

static char vm_anon_string[] = "anonymous memory manager: $Id: vm_anon.c 3011 2007-04-20 13:13:30Z sryffel $";

struct vm_anon_pref
{
  struct vm_pref pref;
  TAILQ_ENTRY(vm_anon_pref) prefq;
  TAILQ_ENTRY(vm_anon_pref) objq;
};

TAILQ_HEAD(pageqh, vm_anon_pref);
TAILQ_HEAD(anonqh, vm_anon_object) vm_anonq;
/* XXX lock vm_anonq_lock */

struct vm_anon_object
{
  TAILQ_ENTRY(vm_anon_object) anonq; /* queue to store all the objects */
  struct pageqh pageq;	/* queue of all pages */
  unsigned int npage;		/* number of pages in this object */
  /* XXX simple lock page_lock locks both pageq and npage */
  int refcnt;						/* needs lock on vm_anonq */
  int flags;
  struct vm_oref oref;	/* next object used for copy on write */
};


/* function declarations */
int vm_anon_init(void);
int vm_anon_connect(struct vm_oref* oref, struct vm_object_arg* args);
int vm_anon_disconnect(struct vm_oref* oref);
int vm_anon_share(struct vm_oref* dest, struct vm_oref* src, int flag);
int vm_anon_attach(struct vm_oref* oref);
int vm_anon_extend(struct vm_oref* oref, size_t size);
int vm_anon_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page);
int vm_anon_map(struct vm_oref* oref, offset_t offset, address_t virt, address_t phys);
int vm_anon_get_type(struct vm_oref* oref, unsigned short* type, char** s);
int vm_anon_get_arg(struct vm_oref* oref, struct vm_object_arg* arg);


struct vm_manager vm_anon_manager =
  {
    vm_anon_connect,
    vm_anon_disconnect,
    vm_anon_share,
    vm_anon_attach,
    vm_anon_extend,
    default_loan_alloc,
    default_loan_dealloc,
    default_loan_out,
    default_loan_in,
    vm_anon_lookup,
    vm_anon_map,
    vm_anon_get_type,
    vm_anon_get_arg,
    vm_anon_init
  };

static struct vm_anon_object* vm_anon_copy(struct vm_anon_object* orig);
static struct vm_anon_pref* vm_anon_add_page(struct vm_oref* oref,
          offset_t offset);
static struct vm_anon_pref* vm_anon_copy_page(struct vm_oref* oref,
          offset_t offset, struct vm_page* page);
static int vm_anon_unadd_page(struct vm_oref* oref, offset_t offset);

int vm_anon_init(void)
{

  TAILQ_INIT(&vm_anonq);

  return VM_OK;
}


/*
 * vm_anon_connect: create a new anonymous object. args has to be NULL
 * because specifing an anonymous object does not make sense.
 */
int vm_anon_connect(struct vm_oref* oref, struct vm_object_arg* args)
{
  struct vm_anon_object *new;
  //	char *arg;
  //	offset_t offset;
  //	int tsize;
  //	int flags;
  //	unsigned char type, size;

  ASSERT(oref != NULL);
  ASSERT(args != NULL);

  oref->manager = &vm_anon_manager;
  oref->obj.object = NULL;

  if ( args->argsize != 0 )
  {
    WARNING("anonymous region can not be specified with args");
    return VM_INVAL;
  }

  /* get new object entry (allocate memory somehow) */
  new = (struct vm_anon_object*)vm_alloc_desc( sizeof(struct vm_anon_object) );
  if ( new == NULL )
  {
    PANIC("Ups, no mem left");
    return VM_NOMEM;
  }
  /* XXX lock anonq */
  TAILQ_ADDTAIL(&vm_anonq, new, anonq);

  /* init the new object */
  TAILQ_INIT(&new->pageq);
  new->npage = 0;
  new->refcnt = 1;
  new->flags = 0;
  zeroOut(&new->oref, sizeof(struct vm_oref));
  /* XXX unlock anonq */

  oref->offset = 0;
  oref->obj.object = new;
  return VM_OK;
}

/*
 * vm_anon_disconnect: remove reference from object, if empty free all pages.
 */
int vm_anon_disconnect(struct vm_oref* oref)
{
  struct vm_anon_object* obj;
  struct vm_anon_pref* t;

  ASSERT(oref != NULL);
  obj = oref->obj.object;

  /* XXX lock */
  if ( --obj->refcnt == 0 )
  {
    /* free the beast */
    TAILQ_REMOVE(&vm_anonq, obj, anonq);
    /* XXX unlock */

    while ( (t = TAILQ_FIRST(&obj->pageq)) )
    {
      vm_hash_removepage((address_t)obj, (struct vm_pref*)t);
      TAILQ_REMOVE(&obj->pageq, t, objq);
      if ( vm_page_unref(VM_PREF(t)->page) <= 0 )
      {
        vm_page_unget(VM_PREF(t)->page);
      }
      if ( steal_free(t, sizeof(struct vm_anon_pref), 0) != SA_OK )
        PANIC("vm_anon_map: steal_alloc failed");
    }
    vm_free_desc(obj);
    return VM_OK;
  }
  /* XXX unlock */

  return VM_OK;
}

/*
 * vm_anon_share: share the src anonymous object with the new dest. The
 * flags set the behaviour of the sharing.
 */
int vm_anon_share(struct vm_oref* dest, struct vm_oref* src, int flag)
{

  dest->manager = src->manager;
  dest->offset = src->offset;
  dest->flags = 0; /* XXX inherit flags */

  switch(flag)
  {
  case VM_OBJ_SHARE_RW:
    if ( ((struct vm_anon_object *)src->obj.object)->flags
         & VM_OBJ_SHARE_COW )
    {
      WARNING("tried to rw share an COW anonymous region");
      return VM_INVAL;
    }
    dest->obj.object = src->obj.object;
    /* XXX lock */
    ((struct vm_anon_object *)dest->obj.object)->refcnt++;
    ((struct vm_anon_object *)dest->obj.object)->flags |= VM_OBJ_SHARE_RW;
    /* XXX unlock */
    return VM_OK;
  case VM_OBJ_SHARE_SNAP:
    /* snapshot */
    dest->obj.object = vm_anon_copy(/* typecast */src->obj.object);
    if ( dest->obj.object == NULL )
    {
      PANIC("Ups, no mem left");
      return VM_NOMEM;
    }
    return VM_OK;
  case VM_OBJ_SHARE_COW:
  case VM_OBJ_SHARE_RO:
    dest->obj.object = src->obj.object;
    /* XXX lock */
    ((struct vm_anon_object *)dest->obj.object)->refcnt++;
    ((struct vm_anon_object *)dest->obj.object)->flags |= VM_OBJ_SHARE_COW;
    dest->flags |= VM_OBJ_NEEDS_COPY;
    /* XXX unlock */
    return VM_OK;
  default:
    WARNING("vm_anon_share: unknown sharing mode");
    return VM_INVAL;
  }
}

int vm_anon_attach(struct vm_oref* oref)
{
  struct vm_anon_object* new;

  ASSERT(oref != NULL);

  /* get new object entry (allocate memory somehow) */
  new = (struct vm_anon_object*)vm_alloc_desc(sizeof(struct vm_anon_object));
  if ( new == NULL )
  {
    PANIC("Ups, no mem left");
    return VM_NOMEM;
  }
  /* XXX lock anonq */
  TAILQ_ADDTAIL(&vm_anonq, new, anonq);

  /* init the new object */
  TAILQ_INIT(&new->pageq);
  new->npage = 0;
  new->refcnt = 1;
  byteCopy(&new->oref, oref, sizeof(struct vm_oref));
  //-CJ	memcpy(&new->oref, oref, sizeof(struct vm_oref));
  /* XXX unlock anonq */

  /* XXX lock the oref ... can be done with a nice flag */
  oref->obj.object = new;
  oref->manager = &vm_anon_manager;
  oref->offset = 0;
  /* XXX oref->flags XXX */

  return VM_OK;
}


/* this is a dummy function because it does not make sense to
	 resize an anonymous region. There is no partial free on the object
	 because it is unknown who is referencing to which part of this object.
	 Size is extended on demand.
 */
int vm_anon_extend(struct vm_oref* oref, size_t size)
{
  return VM_OK;
}


int vm_anon_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page)
{
  struct vm_pref* pref;
  struct vm_page* ptmp;
  struct vm_anon_object* o;
  int err;

  ASSERT(page != NULL);
  ASSERT(oref != NULL);

  o = oref->obj.object;
  offset += oref->offset;
  /* XXX lock */

  if ( type == VM_ACCESS_WRITE && oref->flags & VM_OBJ_NEEDS_COPY )
  {
    o = vm_anon_copy(o);
    oref->obj.object = o;
  }

  pref = vm_hash_lookup(o, offset);
  /* If the page is not allocated allocate a new page.
  	 Anonymous memory does not have limits, the limits have already been checked
  	 with the region descriptor.
   */
  if ( pref != NULL )
  {
    /* page found */
    /* check page ref count if > 1 then copy page (COW) */
    if ( type == VM_ACCESS_WRITE && vm_page_getref(pref->page) > 1 )
    {
      ptmp = pref->page;
      pref->page = vm_page_copy(pref->page);
      if ( pref->page == NULL )
      {
        PANIC("no more pages left");
        return VM_NOMEM;
      }
      if ( vm_page_getref(pref->page) != 1 )
        WARNING("vm_anon_copy_page: Page has refcount non equal to one");
      vm_page_unref(ptmp);
    }

    *page = pref->page;
    return VM_OK;
  }

  /* page not found, if this object is used for copy on write,
  	 do the lookup on the next object. If the type of operation is write then
  	 copy the page into this object.
   */
  if ( o->oref.manager != NULL )
  {
    err = o->oref.manager->lookup(&o->oref, offset - oref->offset,
                                  VM_ACCESS_READ, page);
    if ( err != VM_OK )
      return err;
    switch ( type )
    {
    case VM_ACCESS_WRITE:
      /* copy page to us */
      pref = (struct vm_pref*)vm_anon_copy_page(oref, offset, *page);
      ASSERT(pref != NULL);

      *page = pref->page;
      return VM_OK;
    case VM_ACCESS_READ:
      return VM_OK;
    default:
      PANIC("vm_anon_lookup: unknown access type");
      /* panic does not return */
    }
    return VM_FAILED;
  }
  else
  {
    /* add a zero filled page and return her */
    pref = (struct vm_pref*)vm_anon_add_page(oref, offset);
    ASSERT(pref != NULL);

    *page = pref->page;
    return VM_OK;
  }
}


int vm_anon_map(struct vm_oref* oref, offset_t offset,
                address_t virt, address_t phys)
{
  struct vm_anon_pref* pref;
  struct vm_anon_object* o;
  unsigned long pagenr;

  ENSURE(oref != NULL);
  //-CJ	ENSURE(phys != NULL);

  ASSERT( PAGEREMAINDER(offset) == 0 );
  ASSERT( PAGEREMAINDER(virt) == 0 );

  o = oref->obj.object;
  offset += oref->offset;

  /* XXX lock */
  pref = (struct vm_anon_pref*)vm_hash_lookup(o, offset);
  if ( pref != NULL )
  {
    /* page already mapped, this is an error. */
    return VM_FAILED;
  }

  if ( steal_alloc((address_t*)&pref, sizeof(struct vm_anon_pref), 0) != SA_OK )
    PANIC("vm_anon_map: steal_alloc failed");

  ASSERT(pref != NULL);

  VM_PREF(pref)->offset = offset;
  VM_PREF(pref)->object = o;

  if ( phys == (address_t)-1 )
    pagenr = DMAPPEDTOPAGE(virt);
  else
    pagenr = PAGENUMBER(phys);

  VM_PREF(pref)->page = vm_page_map(pagenr);
  ASSERT( VM_PREF(pref)->page != NULL );

  /* XXX lock object pageq */
  TAILQ_ADDTAIL(&o->pageq, pref, objq);
  /* XXX unlock object pageq */
  if ( vm_hash_add(o, (struct vm_pref*)pref) != VM_OK )
  {
    PANIC("vm_hash_add failed");
  }

  return VM_OK;
}


int vm_anon_get_type(struct vm_oref* oref, unsigned short* type, char** s)
{
  *type = VM_MANAGER_ANON;
  if ( s != NULL )
    *s = vm_anon_string;
  return VM_OK;
}


int vm_anon_get_arg(struct vm_oref* oref, struct vm_object_arg* arg)
{
  ASSERT(arg != NULL);

  if ( arg->argsize < 1 )
  {
    /* size is to small, return error */
    return VM_NOBUF;
  }

  arg->argsize = 0;
  arg->arg[0] = '\0';

  return VM_OK;
}


/* internal functions */
static struct vm_anon_object* vm_anon_copy(struct vm_anon_object* orig)
{
  struct vm_anon_object* new;

  ASSERT(orig != NULL);

  /* get new object entry (allocate memory somehow) */
  new = (struct vm_anon_object*)vm_alloc_desc(sizeof(struct vm_anon_object));
  if ( new == NULL )
  {
    PANIC("Ups, no mem left");
    return NULL;
  }
  /* XXX lock anonq */
  TAILQ_ADDTAIL(&vm_anonq, new, anonq);

  /* init the new object */
  TAILQ_INIT(&new->pageq);
  new->npage = orig->npage;
  new->refcnt = 1;
  orig->refcnt--;

  /* XXX copy pageq */

  /* XXX unlock anonq */

  return new;
}


static struct vm_anon_pref* vm_anon_add_page(struct vm_oref* oref,
          offset_t offset)
{
  struct vm_anon_pref* pref;

  if ( steal_alloc((address_t*)&pref, sizeof(struct vm_anon_pref), 0) != SA_OK )
    PANIC("vm_anon_map: steal_alloc failed");

  ASSERT(pref != NULL);

  VM_PREF(pref)->offset = offset;
  VM_PREF(pref)->object = oref->obj.object;
  VM_PREF(pref)->page = vm_page_get(PQ_ZERO); /* get zeroed page */
  if ( VM_PREF(pref)->page == NULL )
    PANIC("vm_anon_add_page: No page left");
  if ( vm_page_ref(VM_PREF(pref)->page) != 1 )
    WARNING("vm_anon_add_page: Page has refcount non equal to one");

  /* XXX lock object pageq */
  TAILQ_ADDTAIL(&((struct vm_anon_object*)oref->obj.object)->pageq, pref, objq);
  /* XXX unlock object pageq */
  if ( vm_hash_add(oref->obj.object, (struct vm_pref*)pref) != VM_OK )
  {
    PANIC("vm_hash_add failed");
  }
  return pref;
}


static struct vm_anon_pref* vm_anon_copy_page(struct vm_oref* oref,
          offset_t offset, struct vm_page* page)
{
  struct vm_anon_pref* pref;

  if ( steal_alloc((address_t*)&pref, sizeof(struct vm_anon_pref), 0) != SA_OK )
    PANIC("vm_anon_copy_page: steal_alloc failed");

  ASSERT(pref != NULL);

  VM_PREF(pref)->offset = offset;
  VM_PREF(pref)->object = oref->obj.object;
  VM_PREF(pref)->page = vm_page_copy(page); /* copy the page */
  if ( VM_PREF(pref)->page == NULL )
    PANIC("vm_anon_copy_page: No page left");
  if ( vm_page_getref(VM_PREF(pref)->page) != 1 )
    WARNING("vm_anon_copy_page: Page has refcount non equal to one");


  /* XXX lock object pageq */
  TAILQ_ADDTAIL(&((struct vm_anon_object*)oref->obj.object)->pageq, pref, objq);
  /* XXX unlock object pageq */
  if ( vm_hash_add(oref->obj.object, (struct vm_pref*)pref) != VM_OK )
  {
    PANIC("vm_hash_add failed");
  }
  return pref;
}


static int vm_anon_unadd_page(struct vm_oref* oref, offset_t offset)
{
  struct vm_anon_pref* pref;

  pref = (struct vm_anon_pref*) vm_hash_remove(oref->obj.object, offset);

  if ( pref == NULL )
    return VM_INVAL;

  /* XXX lock object pageq */
  TAILQ_REMOVE(&((struct vm_anon_object*)oref->obj.object)->pageq, pref, objq);
  /* XXX unlock object pageq */

  if ( steal_free((address_t)pref, sizeof(struct vm_anon_pref), 0) != SA_OK )
    PANIC("vm_anon_map: steal_alloc failed");

  return VM_OK;
}


int vm_anon_lock(struct vm_oref* oref /* XXX, int type */)
{
  return VM_NIMP;
}


int vm_anon_unlock(struct vm_oref* oref)
{
  return VM_NIMP;
}

