/*
    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_file.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3037 $
    Last Date of Change:   $Date: 2007-06-03 23:23:51 +0200 (Son, 03 Jun 2007) $  by: $Author: sryffel $
 
*/

/*
	 vm_file: the read only file 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_file.h"

static char vm_file_string[] = "file memory manager: $Id: vm_file.c 3037 2007-06-03 21:23:51Z sryffel $";

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

TAILQ_HEAD(pageqh, vm_file_pref);
TAILQ_HEAD(fileqh, vm_file_object) vm_fileq;
/* XXX lock vm_fileq_lock */

struct vm_file_object
{
  TAILQ_ENTRY(vm_file_object) fileq; /* 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_fileq */
  char *name;
  int flags;
  address_t start;
  size_t size;
};

#define DIRECT_MAPPED 0x01

/* function declarations */
int vm_file_init(void);
int vm_file_connect(struct vm_oref* oref, struct vm_object_arg* args);
int vm_file_disconnect(struct vm_oref* oref);
int vm_file_share(struct vm_oref* dest, struct vm_oref* src, int flag);
int vm_file_extend(struct vm_oref* oref, size_t size);
int vm_file_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page);
int vm_file_map(struct vm_oref* oref, offset_t offset, address_t virt, address_t phys);
int vm_file_get_type(struct vm_oref* oref, unsigned short* type, char** s);
int vm_file_get_arg(struct vm_oref* oref, struct vm_object_arg* arg);


struct vm_manager vm_file_manager =
  {
    vm_file_connect,
    vm_file_disconnect,
    vm_file_share,
    NULL,
    vm_file_extend,
    default_loan_alloc,
    default_loan_dealloc,
    default_loan_out,
    default_loan_in,
    vm_file_lookup,
    vm_file_map,
    vm_file_get_type,
    vm_file_get_arg,
    vm_file_init
  };

static struct vm_file_object* vm_file_lookup_file(char *name);

static char* file_str_dup(char* s)
{
  char *t;

  t = vm_alloc_desc(str_len(s) + 1);
  if ( t == NULL )
  {
    PANIC("Ups, no mem left");
    return t;
  }
  str_cpy(t, s);

  return t;
}


int vm_file_init(void)
{
  TAILQ_INIT(&vm_fileq);

  return VM_OK;
}


/*
 * vm_file_connect: create a new file object.
 */
int vm_file_connect(struct vm_oref* oref, struct vm_object_arg* args)
{
  struct vm_file_object *new;
  char *arg;
  char *name;
  address_t filestart;
  size_t filesize;
  offset_t offset;
  int flags;
  int i;
  unsigned char size;

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

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

  if ( args->argsize == 0 )
  {
    WARNING("file region must specify the file");
    return VM_INVAL;
  }

  arg = args->arg;
  //-CJ	dumpMemory(arg,0,64);
  new = NULL;
  name = NULL;
  flags = 0;
  offset = 0;
  filestart = NULL;
  filesize = 0;

  for ( i = args->argsize; i > 0; i -= (size + 2) )
  {
    ASSERT( i >= 2 );
    size = *arg++;
    switch ( *arg++ )
    {
    case VM_FILE_NAME:
      if ( arg[size - 1] != '\0' )
      {
        WARNING("vm_file_manager: file name not terminated");
        arg[size - 1] = '\0';
      }
      printf("vm_file_connect: file %s\n", arg);
      new = vm_file_lookup_file(arg);
      if ( new == NULL )
      {
        name = file_str_dup(arg);
      }
      break;
    case VM_FILE_MAP_START:
      if ( size == sizeof(address_t) )
      {
        byteCopy(&filestart, arg, sizeof(address_t) );
       printf("vm_file_connect: filestart %p\n", filestart);
      }
      else
      {
        WARNING("vm_file_manager: argument size does not match");
      }
      break;
    case VM_FILE_MAP_SIZE:
      if ( size == sizeof(address_t) )
      {
        byteCopy(&filesize, arg, sizeof(size_t) );
        printf("vm_file_connect: filesize %#lx\n", filesize);
      }
      else
      {
        WARNING("vm_file_manager: argument size does not match");
      }
      break;
    case VM_FILE_OFFSET:
      if ( size == sizeof(offset_t) )
      {
        byteCopy(&offset, arg, sizeof(offset_t) );
        printf("vm_file_connect: offset %#lx\n", offset);
      }
      else
      {
        WARNING("vm_file_manager: argument size does not match");
      }
      break;
    case VM_FILE_DIRECT_MAPPED:
      if ( size != 0 )
        WARNING("vm_file_manager: bad argument size");
      flags |= DIRECT_MAPPED;
      break;
    default:
      WARNING("vm_file_manager: unknown argument");
      break;
    }
    arg += size;
  }

  if ( new == NULL && name == NULL )
  {
    WARNING("vm_file_manager: file not specified");
    return VM_FAILED;
  }

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

    /* XXX if file is not direct mapped load the code via tftp */

    /* XXX lock fileq */
    TAILQ_ADDTAIL(&vm_fileq, new, fileq);

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

    /* XXX unlock fileq */
    new->flags = flags;
    if ( flags & DIRECT_MAPPED )
    {
      new->start = filestart;
      new->size = filesize;
    }
    else
    {
      new->start = 0;
      new->size = 0;
    }
  }
  else
  {
    /* the file is already mapped, check if we talk about the same... */
    if ( flags != 0 )
    {
      if ( new->flags != flags &&
           new->start != filestart &&
           new->size != filesize )
      {
        WARNING("vm_file_manager: file badly specified");
        return VM_FAILED;
      }
    }
    else
    {
      if ( filestart != NULL || filesize != 0 )
      {
        WARNING("vm_file_manager: flags not set but needed");
        return VM_FAILED;
      }
    }
    /* XXX lock fileq */
    new->refcnt++;
    /* XXX unlock fileq */
  }

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

/*
 * vm_file_disconnect: remove reference from object.
 */
int vm_file_disconnect(struct vm_oref* oref)
{
  struct vm_file_object* obj;

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

  /* XXX lock */
  if ( --obj->refcnt == 0 )
  {
    /* XXX unlock */
    /* the file manager does not free memory (for the moment)
    	 we need a good replacement strategy here if we are out of memory.
     */
    return VM_OK;
  }
  /* XXX unlock */

  return VM_OK;
}

/*
 * vm_file_share: share the src object with the new dest. The
 * flags set the behaviour of the sharing.
 * Only read only and copy on write is allowed (handled the same way).
 */
int vm_file_share(struct vm_oref* dest, struct vm_oref* src, int flag)
{
  return VM_NIMP;

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

  switch(flag)
  {
  case VM_OBJ_SHARE_RW:
    WARNING("tried to RW share a read only file");
    return VM_FAILED;
  case VM_OBJ_SHARE_SNAP:
    /* snapshot */
    WARNING("tried to make a snapshot of a read only file");
    return VM_FAILED;
  case VM_OBJ_SHARE_COW:
  case VM_OBJ_SHARE_RO:
    dest->obj.object = src->obj.object;
    /* XXX lock */
    ((struct vm_file_object *)dest->obj.object)->refcnt++;
    /* XXX unlock */
    return VM_OK;
  default:
    WARNING("vm_file_share: unknown sharing mode");
    return VM_INVAL;
  }
}

/* this is a dummy function because it does not make sense to
	 resize an file region.
 */
int vm_file_extend(struct vm_oref* oref, size_t size)
{
  return VM_FAILED;
}


int vm_file_lookup(struct vm_oref* oref, offset_t offset, int type,
                   struct vm_page** page)
{
  struct vm_pref* pref;
  struct vm_file_object* o;
  int err;

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

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

  if ( type == VM_ACCESS_WRITE )
  {
    WARNING("write access to read only file, attaching CoW region");
    err = vm_object_attach(oref);
    if ( err != VM_OK )
      return err;
    ASSERT(oref->manager->lookup != NULL);
    return oref->manager->lookup(oref, offset, VM_ACCESS_WRITE, page);
  }

  offset += oref->offset;

  pref = vm_hash_lookup(o, offset);
  /* If the page is not allocated return an error.
  	 For the moment we do not allow resizing of files because on a
  	 tftp based "filesystem" this does not make sens. 
   */
  if ( pref != NULL )
  {
    /* page found */
    *page = pref->page;
    return VM_OK;
  }
  printf("vm_file_lookup: no valid page found. offset %#lx (%#lx)\n",
         offset, oref->offset);
  /* invalid offset, bomb out */
  *page = NULL;
  return VM_INVAL;
}


int vm_file_map(struct vm_oref* oref, offset_t offset,
                address_t virt, address_t phys)
{
  struct vm_file_pref* pref;
  struct vm_file_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;

  ASSERT( o->start + offset == virt );
  if ( offset > o->size )
  {
    /* invalid offset, bomb out */
    return VM_INVAL;
  }

  /* XXX lock */
  pref = (struct vm_file_pref*)vm_hash_lookup(o, offset);
  if ( pref != NULL )
  {
    /* page already mapped, this is an error. */
    printf("vm_file_map: found page with offset %#lx\n", offset);
    return VM_FAILED;
  }

  if ( steal_alloc((address_t*)&pref, sizeof(struct vm_file_pref), 0) != SA_OK )
    PANIC("vm_file_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 */
  //-CJ	printf("vm_file_map: mapping page with offset %#lx\n", offset);
  if ( vm_hash_add(o, (struct vm_pref*)pref) != VM_OK)
  {
    PANIC("vm_hash_add failed");
  }

  return VM_OK;
}


int vm_file_get_type(struct vm_oref* oref, unsigned short* type, char** s)
{
  *type = VM_MANAGER_FILE;
  if ( s != NULL )
    *s = vm_file_string;
  return VM_OK;
}


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

  return VM_NIMP;
}


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


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


static struct vm_file_object* vm_file_lookup_file(char *name)
{
  struct vm_file_object* f;

  TAILQ_FOREACH(&vm_fileq, f, fileq)
  {
    if ( str_cmp(f->name, name) == 0 )
    {
      //-CJ			printf("vm_file_lookup_file: object %p maps %s\n", f, name);
      return f;
    }
  }
  //-CJ	printf("vm_file_lookup_file: no object maps %s\n", name);
  return NULL;
}
