/*
    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_dmap.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_dmapped: the direct mapped 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_dmap.h"

static char vm_dmap_string[] = "direct mapped manager: $Id: vm_dmap.c 2922 2007-03-19 16:48:52Z baderma $";

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

TAILQ_HEAD(pageqh, vm_dmap_pref);
TAILQ_HEAD(dmapqh, vm_dmap_object) vm_dmapq;
LockDesc vm_dmapq_lock;

struct vm_dmap_object
{
  TAILQ_ENTRY(vm_dmap_object) dmapq; /* queue to store all the objects */
  struct pageqh pageq;	/* queue of all pages */
  unsigned int npage;		/* number of pages in this object */
  address_t physaddr;
  size_t size;
};


int vm_dmap_init(void);
int vm_dmap_connect(struct vm_oref* oref, struct vm_object_arg* args);
int vm_dmap_disconnect(struct vm_oref* oref);
int vm_dmap_get_type(struct vm_oref* oref, unsigned short* type, char** s);
int vm_dmap_get_arg(struct vm_oref* oref, struct vm_object_arg* arg);
int vm_dmap_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page);
int vm_dmap_map(struct vm_oref* oref, offset_t offset,
                address_t virt, address_t phys);

struct vm_manager dmap_manager =
  {
    vm_dmap_connect,
    vm_dmap_disconnect,
    default_share,
    default_attach,
    default_extend,
    default_loan_alloc,
    default_loan_dealloc,
    default_loan_out,
    default_loan_in,
    vm_dmap_lookup,
    default_map,
    vm_dmap_get_type,
    vm_dmap_get_arg,
    vm_dmap_init
  };


int vm_dmap_init(void)
{
  TAILQ_INIT(&vm_dmapq);
  lockInit(&vm_dmapq_lock);

  return VM_OK;
}


static int allocate_pages(struct vm_dmap_object* o, address_t phys, size_t size)
{
  address_t i;
  address_t start;
  address_t end;
  struct vm_dmap_pref* t;

  start = (address_t)PAGEROUNDDOWN(phys);
  end = (address_t)PAGEROUNDUP(phys + size);

  for ( i = start; i < end; i+= PAGESIZE )
  {
    /* XXX Should we use steal alloc ? */
    t = (struct vm_dmap_pref*)vm_alloc_desc( sizeof(struct vm_dmap_pref) );
    if ( t == NULL )
    {
      PANIC("Ups, no mem left");
      return VM_NOMEM;
    }
    VM_PREF(t)->page = vm_page_wire(i);
    if ( VM_PREF(t)->page == NULL )
    {
      PANIC("Ups, no mem left");
      return VM_NOMEM;
    }
    VM_PREF(t)->offset = i - start;
    VM_PREF(t)->object = o;
    if ( vm_hash_add(o, (struct vm_pref*)t) != VM_OK )
    {
      PANIC("vm_hash_add failed");
      return VM_FAILED;
    }
  }
  return VM_OK;
}


/*
 * vm_dmap_connect: create a new direct mapped object. args may specify
 * an optional offset (virtual address + offset = physical address).
 */
int vm_dmap_connect(struct vm_oref* oref, struct vm_object_arg* args)
{
  struct vm_dmap_object *new;
  unsigned char *arg;
  address_t physbase;
  size_t mappedsize;
  int i;
  int err;
  unsigned char size;

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

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

  arg = args->arg;
  //-CJ	dumpMemory(arg,0,64);
  physbase = 0;
  mappedsize = 0;

  for ( i = args->argsize; i > 0; i -= (size + 2) )
  {
    ASSERT( i >= 2 );
    size = *arg++;
    switch ( *arg++ )
    {
    case VM_DMAPPED_PHYSADDR:
      if ( size == sizeof(offset_t) )
      {
        byteCopy(&physbase, arg, sizeof(offset_t) );
        //-CJ					printf("vm_file_connect: physbase %#lx\n", physbase);
      }
      else
      {
        WARNING("vm_file_manager: argument size does not match");
      }
      break;
    case VM_DMAPPED_SIZE:
      if ( size == sizeof(size_t) )
      {
        byteCopy(&mappedsize, arg, sizeof(size_t) );
        //-CJ					printf("vm_file_connect: size %#lx\n", mappedsize);
      }
      else
      {
        WARNING("vm_file_manager: argument size does not match");
      }
      break;
    default:
      WARNING("vm_file_manager: unknown argument");
      break;
    }
    arg += size;
  }

  /* get new object entry (allocate memory somehow) */
  new = (struct vm_dmap_object*)vm_alloc_desc( sizeof(struct vm_dmap_object) );
  if ( new == NULL )
  {
    PANIC("Ups, no mem left");
    return VM_NOMEM;
  }

  new->physaddr = physbase;
  new->size = mappedsize;
  /* init the new object */
  lock(&vm_dmapq_lock);
  TAILQ_ADDTAIL(&vm_dmapq, new, dmapq);
  unlock(&vm_dmapq_lock);

  if ( (err = allocate_pages(new, physbase, mappedsize)) != VM_OK )
    return err;

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

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

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

  lock(&vm_dmapq_lock);
  TAILQ_REMOVE(&vm_dmapq, obj, dmapq);
  unlock(&vm_dmapq_lock);

  TAILQ_FOREACH(&obj->pageq, t, objq)
  {
    vm_hash_removepage((address_t)obj, (struct vm_pref*)t);
    TAILQ_REMOVE(&obj->pageq, t, objq);
    /* page in use, free her */
    if ( vm_page_unref(VM_PREF(t)->page) <= 0 )
    {
      vm_page_unget(VM_PREF(t)->page);
    }
    vm_free_desc(t);
  }

  vm_free_desc(obj);

  return VM_OK;
}

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


int vm_dmap_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_dmap_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page)
{
  struct vm_loan_object* o;
  struct vm_pref* pref;

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

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

  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 > 0 then copy page (COW) */
    if ( vm_page_getref(pref->page) < 1 )
    {
      WARNING("wired vm_page has refcount < 1");
    }

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

  return VM_FAILED;
}
