/*
    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_calls.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 $
 
*/

/* vm_calls.c: definition of all vm hooks.
 
	 Following hooks need to be defined.
 
	 - add region: add a new region to a protection domains vm_space.
	 - resize region: change the boundaries of an existing region.
	 - share region: share a region with a child protection domain.
	 - change protection: modify the protection of an existing region.
	 - free region: remove a region form the protection domains vm_space.
	 - get region: get a descriptor with information about a region.
	 - page loan in : map a share page into the loan object.
	 - page loan out: share page from the loan object if neccesary remove page.
	 - page loan alloc: allocate a page from the loan object used for loan-out.
   - set loan region: set the region used for page loan out.
	 
	 missing: split and partial free (mainly pmap handling)
						wire (to wire pages to a driver (memory mapped I/O)
						memory import/export: import/export a memory region with another PD.
						snapshot: create a snapshot of a protection domains vm_space.
	 
	 Finaly a hook for the page fault handler is needed.
	 - lookup region: lookup a page and add it to the pmap and TLB.
 
 */
#include "Assert.h"
#include "Support.h"
#include "MemoryLayout.h"

#include "vm.h"
#include "vm_region.h"
#include "vm_object.h"

#include "vm_calls.h"

/* XXX XXX XXX XXX LOCKING XXX LOCKING XXX XXX XXX XXX */
/* XXX XXX XXX XXX XXX UPDATE HINT XXX XXX XXX XXX XXX */
int vm_region_add(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                  RegionPtr_t region)
{
  struct vm_region* prev;
  struct vm_region* r;
  int err;
  // printf("vm_region_add: %p - %p, type %04hx\n",
  // 			region->start, region->end, region->type);
  
  /* XXX read lock */
  err = vm_map_lookup_region(&vm_map, region, &prev);
  switch (err)
  {
  case VM_FREE:
    break;
  case VM_OK:
    return VM_OVERLAP; /* we can not return VM_OK as error */
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT(prev != NULL);

  err = vm_map_create_region(region, &r);
  if ( err != VM_OK )
  {
    return err;
  }

  err = vm_map_link_region(vm_map, prev, r);
  if ( err != VM_OK )
  {
    return err;
  }

  //-CJ	vm_region_print_region(vm_map, vm_pmap, r, "region to add:", 0);
  /* XXX unlock */
  /* XXX update hint */
  //	add_entry(vm_map); -> vm_map_link_region
  //	update_hint(vm_map, r);
  /* XXX update hint */

  return VM_OK;
}

int vm_region_add_near(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                       RegionPtr_t region, address_t hint, int direction)
{
  /* NOTE: if run in negative direction the returned region could be bigger
  	 than hint. */
  struct vm_region* prev;
  struct vm_region* r;
  int err;

  err = vm_map_find_near(&vm_map, hint, region->end - region->start + 1,
                         region, &prev, direction);
  if ( err != VM_OK )
  {
    return err;
  }

  // printf("vm_region_add_near: %p - %p, type %04hx\n",
  //			region->start, region->end, region->type);

  err = vm_map_create_region(region, &r);
  if ( err != VM_OK )
  {
    return err;
  }

  err = vm_map_link_region(vm_map, prev, r);
  if ( err != VM_OK )
  {
    return err;
  }

  //-CJ	vm_region_print_region(vm_map, vm_pmap, r, "region to add near:", 0);
  /* XXX unlock */
  /* XXX update hint */
  //	add_entry(vm_map); -> vm_map_link_region
  //	update_hint(vm_map, r);
  /* XXX update hint */

  return VM_OK;

}

/* this function is only used during bootup to map the kernel memory */
int vm_region_map(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                  RegionPtr_t region, address_t start, address_t end, address_t phys)
{
  struct vm_region* r;
  int err;
  address_t i;

  /* XXX read lock */
  err = vm_map_lookup_region(&vm_map, region, &r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( r != NULL );

  if ( r->oref.manager == NULL )
    return VM_FAILED;
  if ( r->oref.manager->map == NULL )
    return VM_NIMP;

  for ( i = start; i < end; i += PAGESIZE )
  {
    err = r->oref.manager->map(&r->oref, i - start, i, phys);
    switch ( err )
    {
    case VM_OK:
      break;
    default:
      printf("vm_region_map: object map failed %i\n", err);
      return err;
    }
  }
  return VM_OK;
}

int vm_region_resize(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                     RegionPtr_t old, RegionPtr_t new)
{
  struct vm_region* r;
  int err;

  /* XXX read lock */
  err = vm_map_lookup_region(&vm_map, old, &r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( r != NULL );

  /* XXX get prev, next region and check if changed region will fit */

  /* XXX unlock */
  /* XXX update hint */
  return VM_FAILED;
}

int vm_region_share(
  struct vm_map* source_map, struct vm_pmap* source_pmap, RegionPtr_t source,
  struct vm_map* dest_map, struct vm_pmap* dest_pmap, RegionPtr_t dest,
  int flags)
{
  /* XXX access permissions have to be checked earlier */
  struct vm_region* dest_r;
  struct vm_region* prev;
  struct vm_region* source_r;
  int err;

  /* XXX read lock both maps */
  err = vm_map_lookup_region(&source_map, source, &source_r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( source_r != NULL );

  err =  vm_map_lookup_region(&dest_map, dest, &prev);
  switch (err)
  {
  case VM_FREE:
    break;
  case VM_OK:
    return VM_OVERLAP;
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT(prev != NULL);

  err = vm_map_share_region(dest, source_r, &dest_r, flags);
  if ( err != VM_OK )
  {
    return err;
  }
  /* XXX flags handling */

  err = vm_map_link_region(dest_map, prev, dest_r);
  if ( err != VM_OK )
    return err;

  /* XXX exc Lock */
  source_map->numentries++;
  /* XXX unlock */

  /* flags handling */
  switch (flags)
  {
  case VM_OBJ_SHARE_RW:
    /* read write sharing doesn't need special pmap handling */
    /* nothing to be done */
    break;
  case VM_OBJ_SHARE_SNAP:
    /* here we need to mark all pmap entries as read-only,
    	 on dest and source */
    vm_pmap_change_region_flags(source_pmap, source_r->start,
                                source_r->end, VM_ACCESS_READ);
    break;
  case VM_OBJ_SHARE_COW:
    /* only the pmap entries of dest need to be ro */
    /* nothing to be done */
    break;
  default:
    WARNING("unknown sharing, reducing to RO sharing");
    /* fall through */
  case VM_OBJ_SHARE_RO:
    /* for the source there is no change */
    /* ensure that dest is only read-only */
    dest_r->cur = VM_ACCESS_READ;
    dest_r->max = VM_ACCESS_READ;
    break;
  }

  /* update hint */

  return VM_OK;
}

int vm_region_share_near(
  struct vm_map* source_map, struct vm_pmap* source_pmap, RegionPtr_t source,
  struct vm_map* dest_map, struct vm_pmap* dest_pmap, address_t* hint,
  int flags, int direction)
{
  /* NOTE: if run in negative direction the returned region could be bigger
  	 than hint. */
  Region_t reg;
  struct vm_region* dest_r;
  struct vm_region* prev;
  struct vm_region* source_r;
  size_t size;
  int err;

  /* XXX read lock both maps */
  err = vm_map_lookup_region(&source_map, source, &source_r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( source_r != NULL );

  reg.cur = source->cur;
  reg.max = source->max;

  size = source->end - source->start + 1;

  err = vm_map_find_near(&dest_map, *hint, size, &reg, &prev, direction);
  if ( err != VM_OK )
  {
    return err;
  }

  err = vm_map_share_region(&reg, source_r, &dest_r, flags);
  if ( err != VM_OK )
  {
    return err;
  }

  err = vm_map_link_region(dest_map, prev, dest_r);
  if ( err != VM_OK )
    return err;

  /* XXX exc Lock */
  source_map->numentries++;
  /* XXX unlock */

  /* flags handling */
  switch (flags)
  {
  case VM_OBJ_SHARE_RW:
    /* read write sharing doesn't need special pmap handling */
    /* nothing to be done */
    break;
  case VM_OBJ_SHARE_SNAP:
    /* here we need to mark all pmap entries as read-only,
    	 on dest and source */
    vm_pmap_change_region_flags(source_pmap, source_r->start,
                                source_r->end, VM_ACCESS_READ);
    break;
  case VM_OBJ_SHARE_COW:
    /* only the pmap entries of dest need to be ro */
    /* nothing to be done */
    break;
  default:
    WARNING("unknown sharing, reducing to RO sharing");
    /* fall through */
  case VM_OBJ_SHARE_RO:
    /* for the source there is no change */
    /* ensure that dest is only read-only */
    dest_r->cur = VM_ACCESS_READ;
    dest_r->max = VM_ACCESS_READ;
    break;
  }

  /* XXX lock */
  /* update hint */
  *hint = dest_r->start;

  return VM_OK;
}


int vm_region_chprot(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                     RegionPtr_t region, unsigned int cur, unsigned int max)
{
  struct vm_region* r;
  int err;
  unsigned char omax;

  err = vm_map_lookup_region(&vm_map, region, &r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( r != NULL );


  omax = r->max;
  cur &= VM_ACCESS_MASK; /* trust nobody */
  max &= VM_ACCESS_MASK;

  if ( (max & cur) ^ cur )
  {
    /* protection violation cur > max */
    /* It is not allowed to set higher rights than max (new or old) */
    return VM_ACCESS;
  }

  if ( (omax & cur) ^ cur )
  {
    /* protection violation cur > max */
    return VM_ACCESS;
  }

  if ( max != 0 )
  {
    if ( (max & cur ) ^ cur )
    {
      /* invalid arguments new max < cur */
      return VM_INVAL;
    }

    if ( (omax & max) ^ max )
    {
      /* protection violation new max > max
      	The maximum protection can only be lowered!
      */
      return VM_ACCESS;
    }
    /* finally set the new protection */
    r->max = max;
  }
  r->cur = cur;

  /* XXX PMAP */

  return VM_FAILED;
}


int vm_region_free(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                   RegionPtr_t region)
{
  struct vm_region* r;
  int err;
  
  err = vm_map_lookup_region(&vm_map, region, &r);
  switch (err)
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( r != NULL );
  // printf("vm_region_free: free region from %p - %p\n", r->start, r->end);
  
  err = vm_map_unlink_region(vm_map, r);
  if ( err != VM_OK )
    return err;

  /* XXX lock */

  /* PMAP */
  err = vm_pmap_region_remove(vm_pmap, r->start, r->end);
  if ( err != VM_OK )
    return err;

  err = vm_map_free_region(r);
  if ( err != VM_OK )
    return err;

  return VM_OK;
}


int vm_region_free_all(struct vm_map* vm_map, struct vm_pmap* vm_pmap)
{
  struct vm_region* r;
  int err;
  int saved_err = VM_OK;

  DOUBLEQ_FOREACH(&vm_map->header, r, regionq)
  {
    ASSERT( r != NULL );
    if ( DOUBLEQ_FIRST(r, regionq) )
      continue; /* first is the header */
    // printf("vm_region_free: free region form %p - %p\n", r->start, r->end);
    
    err = vm_map_unlink_region(vm_map, r);
    if ( err != VM_OK )
      saved_err = err;

    /* XXX lock */

    /* PMAP */
    err = vm_pmap_region_remove(vm_pmap, r->start, r->end);
    if ( err != VM_OK )
      saved_err = err;

    err = vm_map_free_region(r);
    if ( err != VM_OK )
      saved_err = err;
  }

  return saved_err;
}


int vm_region_get(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                  address_t hint, RegionPtr_t region)
{
  struct vm_region* t;

  ASSERT(region != NULL);
  region->start = hint;
  region->end = hint;

  switch( vm_map_lookup_region(&vm_map, region, &t) )
  {
  case VM_FREE:
    /* region points to previous region */
    region->start = t->end + 1;
    if ( !DOUBLEQ_LAST(t, regionq) )
      region->end = DOUBLEQ_NEXT(t, regionq)->start - 1;
    else
      region->end = vm_map->header.end;
    region->type = VM_FREE;
    region->cur = 0;
    region->max = 0;
    return VM_FREE;
  case VM_BADDESC:
    region->start = t->start;
    region->end   = t->end;
    if ( t->oref.manager != NULL && t->oref.manager->get_type != NULL )
      t->oref.manager->get_type(&t->oref, &region->type, NULL);
    else
      region->type = 0;
    region->cur = t->cur;
    region->max = t->max;
    if ( t->oref.manager != NULL && t->oref.manager->get_arg != NULL )
      if ( region->obj_arg.argsize > 0 )
        return t->oref.manager->get_arg(&t->oref, &region->obj_arg);
    return VM_OK;
  default:
    return VM_FAILED;
  }
}


int vm_region_lookup(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                     address_t addr, int type)
{
  Region_t region;
  struct vm_region* t;
  offset_t offset;
  struct vm_page* page;
  int err;

  region.start = addr;
  region.end = addr;

  switch( vm_map_lookup_region(&vm_map, &region, &t) )
  {
  case VM_FREE:
    printf("segmentation fault: %p type: %#o\n", addr, type);
    WARNING("segmentation fault");
    /* SIGSEGV: kill thread */
    return VM_INVAL;
  case VM_BADDESC:
    if ( type == VM_ACCESS_WRITE && !(t->cur & VM_ACCESS_WRITE) )
    {
      printf("access violation: %p type: %#o\n", addr, type);
      WARNING("access violation");
      return VM_ACCESS;
    }
    offset = (offset_t)((address_t)
                        ((unsigned long)addr & vm->stat.pagemask) - t->start);
    ASSERT( offset >= 0 );
    /* address belongs to a region, lookup via manager */
    if ( t->oref.manager == NULL || t->oref.manager->lookup == NULL )
    {
      WARNING("Invalid object handler");
      return VM_FAILED;
    }
    //-CJ			printf("vm_region_lookup: object lookup: %p - %p -> offset %#lx\n",
    //-CJ			addr, t->start, offset);
    err = t->oref.manager->lookup(&t->oref, offset, type, &page);
    switch (err)
    {
    case VM_OK:
      //-CJ					printf("vm_region_lookup: vm_pmap_enter: %p -> %p, %#o\n",
      //-CJ							(address_t)((unsigned long)addr & vm->stat.pagemask),
      //-CJ							vm_page_getaddr(page), t->cur);
      err = vm_pmap_enter(vm_pmap,
                          (address_t)((unsigned long)addr & vm->stat.pagemask),
                          vm_page_getaddr(page), t->cur);
      return err;
      break;
    default:
      return err;
    }
  default:
    return VM_FAILED;
  }
}


int vm_region_set_loan(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                       RegionPtr_t region, RegionPtr_t old)
{
  struct vm_region* r;
  struct vm_region* t;
  int err;

  ENSURE(region != NULL);

  switch( err = vm_map_lookup_region(&vm_map, region, &r) )
  {
  case VM_OK:
    break;
  case VM_FREE:
  case VM_BADDESC:
  case VM_OVERLAP:
  case VM_OUTOFMAP:
  default:
    return err;
  }
  ASSERT( r != NULL );

  t = vm_map->loan;

  if ( old != NULL )
  {
    if ( t == NULL )
    {
      old->start = NULL;
      old->end = NULL;
      old->type = 0;
    }
    else
    {
      old->start = t->start;
      old->end = t->end;
      old->cur = t->cur;
      old->max = t->max;
      ASSERT(t->oref.manager != NULL);
      if ( t->oref.manager->get_type != NULL )
        t->oref.manager->get_type(&t->oref, &old->type, NULL);
      else
        old->type = 0;
      if ( t->oref.manager->get_arg != NULL )
        if ( old->obj_arg.argsize > 0 )
          return t->oref.manager->get_arg(&t->oref, &old->obj_arg);
    }
  }

  vm_map->loan = r;

  return VM_OK;
}


int vm_region_loan_alloc(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                         address_t* pagestart, int flags)
{
  struct vm_page* page;
  offset_t offset;
  int err;

  if ( vm_map->loan == NULL )
    return VM_FAILED;

  ENSURE(pagestart != NULL );
  ASSERT(vm_map->loan->oref.manager != NULL);

  if ( vm_map->loan->oref.manager->loan_alloc == NULL )
    return VM_NIMP;
  err = vm_map->loan->oref.manager->loan_alloc(&vm_map->loan->oref,
        &offset, &page, flags);

  if ( err != VM_OK )
  {
    *pagestart = NULL;
    return err;
  }
  *pagestart = vm_map->loan->start + offset;
  //-CJ	printf("vm_region_loan_alloc: page @ %p\n", *pagestart);

  /* map the page, it will be used soon. Protection should be RW. */
  err = vm_pmap_enter(vm_pmap,
                      (address_t)((unsigned long)*pagestart & vm->stat.pagemask),
                      vm_page_getaddr(page), vm_map->loan->cur);
  //	if ( err != VM_OK ) return err;

  return err;
}


int vm_region_loan_dealloc(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                           address_t pagestart)
{
  offset_t offset;
  int err;

  if ( vm_map->loan == NULL )
    return VM_FAILED;

  ASSERT(vm_map->loan->oref.manager != NULL);

  if ( vm_map->loan->oref.manager->loan_dealloc == NULL )
    return VM_NIMP;

  offset = pagestart - vm_map->loan->start;
  //-BL printf("vm_region_loan_dealloc: %#lx %p %p %i\n", offset,
  //-BL 	pagestart, vm_map->loan->start, pagestart - vm_map->loan->start);
  ASSERT(pagestart - vm_map->loan->start >= 0);

  err = vm_map->loan->oref.manager->loan_dealloc(&vm_map->loan->oref,
        offset);

  if ( err != VM_OK )
  {
    WARNING("vm_region_loan_dealloc -> loan_dealloc failed");
    return err;
  }

  err = vm_pmap_page_remove(vm_pmap, pagestart);

  return err;
}


int vm_region_loan_out(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                       address_t pagestart, struct vm_page** vm_page_ptr)
{
  offset_t offset;
  int remove;
  int err;

  if ( vm_map->loan == NULL )
    return VM_FAILED;

  ENSURE( vm_page_ptr != NULL );
  ASSERT(vm_map->loan->oref.manager != NULL);

  if ( vm_map->loan->oref.manager->loan_out == NULL )
    return VM_NIMP;

  offset = pagestart - vm_map->loan->start;
  ASSERT(pagestart - vm_map->loan->start >= 0);

  err = vm_map->loan->oref.manager->loan_out(&vm_map->loan->oref,
        offset, vm_page_ptr, &remove);

  if ( err != VM_OK )
    return err;

  if ( remove == 1 )
  {
    /* remove flag was set */
    //-CJ		printf("vm_loanout: removing page %p\n", pagestart);
    err = vm_pmap_page_remove(vm_pmap, pagestart);
  }
  else
  {
    /* make page Copy-on-Write. NOTE: the refcount is updated in ->loan_out */
    err = vm_pmap_change_page_flags(vm_pmap,
                                    (address_t)((unsigned long)pagestart & vm->stat.pagemask),
                                    VM_ACCESS_READ);
  }
  return err;
}


int vm_region_loan_in(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                      address_t* pagestart, struct vm_page* vm_page)
{
  offset_t offset;
  int err;
  int prot;

  if ( vm_map->loan == NULL )
    return VM_FAILED;

  ENSURE( pagestart != NULL );
  ASSERT(vm_map->loan->oref.manager != NULL);

  if ( vm_map->loan->oref.manager->loan_in == NULL )
    return VM_NIMP;

  err = vm_map->loan->oref.manager->loan_in(&vm_map->loan->oref,
        &offset, vm_page);

  if ( err != VM_OK )
  {
    *pagestart = NULL;
    return err;
  }
  *pagestart = vm_map->loan->start + offset;

  prot = VM_ACCESS_READ;

  /* if the is only one reference to this page map her RW */
  if ( vm_page_getref(vm_page) == 1 )
    prot |= VM_ACCESS_WRITE;
  ASSERT( vm_page_getref(vm_page) != 0 );

  err = vm_pmap_enter(vm_pmap,
                      (address_t)((unsigned long)*pagestart & vm->stat.pagemask),
                      vm_page_getaddr(vm_page), prot);
  return err;
}


void vm_region_print_map(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                         int flags)
{
}


void vm_region_print_region(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                            struct vm_region* r, char* s, int flags)
{
  unsigned short type;
  char *str;

  if ( s != NULL )
    printf("%s\n", s);
  if ( r == NULL )
  {
    printf("  (null pointer)\n");
    return;
  }

  printf("   mapps %p - %p, protection: cur %#o max %#o, flags: %#x\n",
         r->start, r->end, r->cur, r->max, r->flags);

  if (r->oref.manager == NULL)
  {
    printf("   no manager attached\n");
    return;
  }

  if ( r->oref.manager->get_type != NULL )
    r->oref.manager->get_type(&r->oref, &type, &str);

  printf("   object: type %04hx -> %s\n", type, str);
}
