/*
    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_loan.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/
/*
	 vm_loan: the loan-out pager object.
 */
#include "Topsy.h"
#include "Error.h"
#include "Assert.h"
#include "Lock.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 "simpleq.h"
#include "tailq.h"

#include "vm_loan.h"

static char vm_loan_string[] = "loan out manager: $Id: vm_loan.c 2922 2007-03-19 16:48:52Z baderma $";

#define STD_SIZE 16
#define IS_USED 0x8000

struct vm_loan_pref
{
  struct vm_pref pref;
  int flags;
  SIMPLEQ_ENTRY(vm_loan_pref) freeq;
};

TAILQ_HEAD(loanqh, vm_loan_object) vm_loanq;
LockDesc vm_loanq_lock;

struct vm_loan_object
{
  TAILQ_ENTRY(vm_loan_object) loanq; /* queue to store all the objects */
  struct vm_loan_pref* array;
  SIMPLEQ_HEAD(freeqh, vm_loan_pref) freeq;
  LockDesc freeq_lock;
  int size;
};


int vm_loan_init(void);
int vm_loan_connect(struct vm_oref* oref, struct vm_object_arg* args);
int vm_loan_disconnect(struct vm_oref* oref);
int vm_loan_get_type(struct vm_oref* oref, unsigned short* type, char** s);
int vm_loan_get_arg(struct vm_oref* oref, struct vm_object_arg* arg);
int vm_loan_alloc(struct vm_oref* oref, offset_t* offset,
                  struct vm_page** page, int flags);
int vm_loan_dealloc(struct vm_oref* oref, offset_t offset);
int vm_loan_out(struct vm_oref* oref, offset_t offset,
                struct vm_page** page, int* remove);
int vm_loan_in(struct vm_oref* oref, offset_t* offset, struct vm_page* page);
int vm_loan_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page);

struct vm_manager loan_manager =
  {
    vm_loan_connect,
    vm_loan_disconnect,
    default_share,
    default_attach,
    default_extend,
    vm_loan_alloc,
    vm_loan_dealloc,
    vm_loan_out,
    vm_loan_in,
    vm_loan_lookup,
    default_map,
    vm_loan_get_type,
    vm_loan_get_arg,
    vm_loan_init
  };



int vm_loan_init(void)
{
  TAILQ_INIT(&vm_loanq);
  lockInit(&vm_loanq_lock);

  return VM_OK;
}

/*
 * vm_loan_connect: create a new loan-out object. args has to be NULL
 * because specifing an loanout object does not make sense.
 */
int vm_loan_connect(struct vm_oref* oref, struct vm_object_arg* args)
{
  struct vm_loan_object *new;
  offset_t o;
  int i;

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

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

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

  /* get new object entry (allocate memory somehow) */
  new = (struct vm_loan_object*)vm_alloc_desc( sizeof(struct vm_loan_object) );
  if ( new == NULL )
  {
    PANIC("Ups, no mem left");
    return VM_NOMEM;
  }
  /* init the new object */
  SIMPLEQ_INIT(&new->freeq);
  lockInit(&new->freeq_lock);
  new->size = STD_SIZE;

  /* allocate the needed space */
  new->array = (struct vm_loan_pref*)
               vm_alloc_desc( STD_SIZE * sizeof(struct vm_loan_pref) );
  if ( new->array == NULL )
  {
    PANIC("Ups, no mem left");
    return VM_NOMEM;
  }
  /* init the vm_prefs */
  for ( i = 0, o = 0; i < STD_SIZE; i++ )
  {
    SIMPLEQ_ADDHEAD(&new->freeq, &new->array[i], freeq);
    VM_PREF(&new->array[i])->offset = o;
    VM_PREF(&new->array[i])->object = new;
    o += PAGESIZE;
  }

  lock(&vm_loanq_lock);
  TAILQ_ADDTAIL(&vm_loanq, new, loanq);
  unlock(&vm_loanq_lock);

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

/*
 * vm_loan_disconnect: remove reference from object, if empty free all pages.
 */
int vm_loan_disconnect(struct vm_oref* oref)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;
  int i;

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

  lock(&vm_loanq_lock);
  TAILQ_REMOVE(&vm_loanq, obj, loanq);
  unlock(&vm_loanq_lock);

  t = obj->array;
  lock(&obj->freeq_lock);
  for ( i = 0; i < obj->size; i++ )
  {
    if ( t[i].flags & IS_USED )
    {
      /* page in use, free her */
      if ( vm_page_unref(VM_PREF(&t[i].pref)->page) <= 0 )
      {
        vm_page_unget(VM_PREF(&t[i].pref)->page);
      }
    }
  }
  unlock(&obj->freeq_lock);

  vm_free_desc(obj->array);
  vm_free_desc(obj);

  return VM_OK;
}

int vm_loan_get_type(struct vm_oref* oref, unsigned short* type, char** s)
{
  *type = VM_MANAGER_LOAN;
  if ( s != NULL )
    *s = vm_loan_string;
  return VM_OK;
}


int vm_loan_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;
}


int vm_loan_alloc(struct vm_oref* oref, offset_t* offset,
                  struct vm_page** page, int flags)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;

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

  lock(&obj->freeq_lock);
  SIMPLEQ_POP(&obj->freeq, t, freeq);
  unlock(&obj->freeq_lock);
  if ( t == NULL )
  {
    PANIC("vm_loan: no more pages left to loan, increase STD_SIZE");
    *page = NULL;
    return VM_NOMEM;
  }

  *page = vm_page_get(PQ_ZERO);
  if ( *page == NULL )
  {
    PANIC("UPS no more pages left");
    return VM_NOMEM;
  }
  if ( vm_page_ref(*page) != 1 )
    WARNING("vm_loan_alloc: Page has refcount non equal to one");

  //-CJ	printf("vm_loan_alloc: page %p %#lx offset %#lx\n",
  //-CJ			*page, (*page)->pagenr, VM_PREF(t)->offset);

  ASSERT( (flags&IS_USED) == 0);
  t->flags = (flags | IS_USED);
  VM_PREF(t)->page = *page;
  *offset = VM_PREF(t)->offset;

  return VM_OK;
}


int vm_loan_dealloc(struct vm_oref* oref, offset_t offset)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;
  int i;

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

  i = PAGENUMBER(offset);

  if ( i >= obj->size )
  {
    WARNING("invalid offset for loan out");
    return VM_INVAL;
  }

  t = &obj->array[i];

  if (! (t->flags & IS_USED) )
  {
    WARNING("page was not loan out");
    return VM_INVAL;
  }
  /* remove page form object */
  if ( vm_page_unref(VM_PREF(t)->page) <= 0 )
    vm_page_unget(VM_PREF(t)->page);
  VM_PREF(t)->page = NULL;
  t->flags = 0;
  lock(&obj->freeq_lock);
  SIMPLEQ_ADDTAIL(&obj->freeq, t, freeq);
  unlock(&obj->freeq_lock);

  return VM_OK;
}


int vm_loan_out(struct vm_oref* oref, offset_t offset,
                struct vm_page** page, int* remove)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;
  int i;

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

  i = PAGENUMBER(offset);

  if ( i >= obj->size )
  {
    WARNING("invalid offset for loan out");
    return VM_INVAL;
  }

  t = &obj->array[i];

  if (! (t->flags & IS_USED) )
  {
    WARNING("page was not loan out");
    return VM_INVAL;
  }

  if ( t->flags & VM_LOANOUT_KEEP )
  {
    *page = VM_PREF(t)->page;
    /* increase refcount because there is a new reference on the page */
    vm_page_ref(*page);
    *remove = 0;
  }
  else
  {
    /* remove page form object */
    *page = VM_PREF(t)->page;
    t->flags = 0;
    VM_PREF(t)->page = NULL;
    lock(&obj->freeq_lock);
    SIMPLEQ_ADDTAIL(&obj->freeq, t, freeq);
    unlock(&obj->freeq_lock);
    *remove = 1;
  }
  return VM_OK;
}


int vm_loan_in(struct vm_oref* oref, offset_t* offset, struct vm_page* page)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;

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

  lock(&obj->freeq_lock);
  SIMPLEQ_POP(&obj->freeq, t, freeq);
  unlock(&obj->freeq_lock);
  if ( t == NULL )
  {
    PANIC("vm_loan: no more pages left to loan, increase STD_SIZE");
    return VM_NOMEM;
  }

  t->flags = IS_USED;
  VM_PREF(t)->page = page;
  *offset = VM_PREF(t)->offset;

  return VM_OK;

}

int vm_loan_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page)
{
  struct vm_loan_object* obj;
  struct vm_loan_pref* t;
  int i;

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

  i = PAGENUMBER(offset);

  if ( i >= obj->size )
  {
    WARNING("invalid offset for loan out");
    return VM_INVAL;
  }

  t = &obj->array[i];

  if (! (t->flags & IS_USED) )
  {
    WARNING("page was not allocated");
    return VM_FAILED;
  }

  *page = VM_PREF(t)->page;
  /* page found */
  /* check page ref count if > 1 then copy page (COW) */
  if ( type == VM_ACCESS_WRITE && vm_page_getref(*page) > 1 )
  {
    *page = VM_PREF(t)->page = vm_page_copy(*page);
    if ( VM_PREF(t)->page == NULL )
    {
      PANIC("no more pages left");
      return VM_NOMEM;
    }
  }
  return VM_OK;
}
