/*
    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_map.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3013 $
    Last Date of Change:   $Date: 2007-04-23 17:26:43 +0200 (Mon, 23 Apr 2007) $  by: $Author: sryffel $
 
*/

/* XXX XXX XXX XXX LOCKING XXX LOCKING XXX XXX XXX XXX */
#include "Assert.h"
#include "Error.h"
#include "Support.h"
#include "MemoryLayout.h"

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

/* vm_map_lookup_region needs a read lock whereas the vm_map_link/unlink
	 calls need a write lock. The 1Mio SFr. question is: Do we need a write
	 lock also for changes in the vm_region structure? Needs vm_region_chprot
	 to raise the lock for the changes?
	 Another good question is: Do we need to hold the read lock or can we release
	 the lock and regaining it again? 
 */

/* vm_map_lookup_region: lookup the region hint in the vm_map.
	 There are multiple return values:
	 VM_OK: region found (region points to this region)
	 VM_FREE: hint points to a free sektor (region points to previous region)
	 VM_BADDESC: bad hint descriptor (region points to the found region)
	 VM_OVERLAP: hint points to a free region but overlaps with the
	 adjacent region (end is to big) (region points to previous region)
	 VM_OUTOFMAP: hint points to space outside the vm_map (region points to 
	 the vm_map region header)
	 
	 This function can be used to get a valid region without knowing the correct
	 hint. Just set hint->start and hint->end to a pointer in the searched region.
	 If VM_FREE is returned the pointer points to a free area (region is the
	 previous region) if VM_BADDESC is returned then region points to the region
	 where region->start <= hint->start <= region->end.
 */
int vm_map_lookup_region(struct vm_map** vm_map, RegionPtr_t hint,
                         struct vm_region** region)
{
  struct vm_region* t;

  // printf("vm_map_lookup_region: hint->start = %p, hint->end = %p, header.start = %p, header.end = %p\n",hint->start, hint->end, (*vm_map)->header.start, (*vm_map)->header.end);
  if ((*vm_map)->header.start > hint->start ||
      (*vm_map)->header.end < hint->end)
  {
    *region = &(*vm_map)->header;
    return VM_OUTOFMAP;
  }

  if ((t = (*vm_map)->hint) == NULL )
  {
    t = DOUBLEQ_NEXT(&(*vm_map)->header, regionq);
    if ( t == NULL )
    {
      *region = &(*vm_map)->header;
      return VM_FREE;
    }
  }


  if ( t->start > hint->start )
  {
    /* the searched region is smaller then the current */
    /* there are three possible cases:
    		1: match found, hint points to an existing region.
    		2: free found, hint points to a free (non mapped) region.
    		3: submap, recurse into the submap and do the search there.
    	 following errors exist:
    		1: VM_BADDESC: there is a problem with the descriptor.
    		2: VM_OVERLAP: Not enough room for the new region
     */
    DOUBLEQ_HCAEROF(t, t, regionq)
    {
      if ( t->start > hint->start )
        continue;

      /* We can stop searching now, because this is a ordered queue. */
      *region = t;
      if ( t->start == hint->start && !DOUBLEQ_FIRST(t, regionq) )
      {
        if ( t->end == hint->end )
        {
          /* first case: match found */
          return VM_OK;
        }
        else
        {
          /* bad descriptor */
          return VM_BADDESC;
        }
      }

      if ( t->end < hint->start || DOUBLEQ_FIRST(t, regionq) )
      {
        /* possible case 2: check if it doesn't overlap with the next region */
        t = DOUBLEQ_NEXT(t, regionq);
        ASSERT(t!=NULL); /* should be impossible */

        if ( hint->end < t->start )
          return VM_FREE;
        else
          return VM_OVERLAP;
      }
      if ( t->flags & VM_SUBMAP )
      {
        /* third case: submap */
        *vm_map = t->oref.obj.vm_map;
        return vm_map_lookup_region(vm_map, hint, region);
      }
      /* VM_OVERLAP is only returned if the end of the hint overlaps with
      	 a region. */
      return VM_BADDESC;
    }
  }
  else
  {
    DOUBLEQ_FOREACH(t, t, regionq)
    {
      if ( t->start < hint->start && !DOUBLEQ_LAST(t, regionq) )
        continue;

      /* We can stop searching now, because this is a ordered queue. */
      if ( t->start == hint->start )
      {
        if ( t->end == hint->end )
        {
          /* first case: match found */
          *region = t;
          return VM_OK;
        }
        else
        {
          /* bad descriptor */
          *region = t;
          return VM_BADDESC;
        }
      }

      /* Last element in queue needs special treating.
      	 Just check if the end of this region does overlap with hint or not.
      	 But only if the hint is bigger than the region start.
       */
      if ( t->start < hint->start && DOUBLEQ_LAST(t, regionq) )
      {
        *region = t;
        if ( hint->start > t->end )
        {
          return VM_FREE;
        }
        else
        {
          return VM_BADDESC;
        }
      }
      /* we have to make the checks on the previos region */
      t = DOUBLEQ_PREV(t, regionq);
      ASSERT(t!=NULL); /* should be impossible */
      *region = t;

      if ( t->end < hint->start || DOUBLEQ_FIRST(t, regionq) )
      {
        /* possible case 2: check if it doesn't overlap with the prev region */
        t = DOUBLEQ_NEXT(t, regionq);
        ASSERT(t!=NULL); /* should be impossible */

        if ( hint->end < t->start )
          return VM_FREE;
        else
          return VM_OVERLAP;
      }
      if ( t->flags & VM_SUBMAP )
      {
        /* third case: submap */
        *vm_map = t->oref.obj.vm_map;
        return vm_map_lookup_region(vm_map, hint, region);
      }
      /* VM_OVERLAP is only returned if the end of the hint overlaps with
      	 a region. */
      return VM_BADDESC;
    }
  }
  PANIC("Ups, shound never reach this point!");
  return VM_FAILED;
}

int vm_map_find_near(struct vm_map** vm_map, address_t hint, size_t size,
                     RegionPtr_t region, struct vm_region** prevreg, int direction)
{
  /* NOTE: if run in negative direction the returned region could be bigger
  	 than hint. */
  Region_t hintreg;
  struct vm_region* prev;
  int err;

  /* 0 is special (search from middle of segment) */
  if (hint == 0)
  {
    hint = (address_t)((uint32)(*vm_map)->header.start/2
                       + (uint32)(*vm_map)->header.end/2);
    if (hint > (address_t)0x60000000)
    {
      // limit since above 0x80000000 there's the direct mapped
      // segment on mips (is arch dependent, but this limit won't
      // hurt other architectures) XXX
      hint = (address_t)0x60000000;
    }
  }
  if ( direction > 0 )
  {
    hint = (address_t)PAGEROUNDUP(hint);
  }
  else
  {
    hint = (address_t)PAGEROUNDDOWN(hint);
  }
  hintreg.start = hint;
  hintreg.end = hint;

  if ( PAGEREMAINDER(size) != 0 )
  {
    WARNING("Region size is not n*PAGESIZE, forcing it");
    size = PAGEROUNDUP(size);
  }

  /* XXX read lock */
  err = vm_map_lookup_region(vm_map, &hintreg, &prev);
  switch (err)
  {
  case VM_FREE:
  case VM_BADDESC:
    break;
  default:
    printf("vm_map_lookup_region failed with error %i\n", err);
    return VM_FAILED;
  }
  do
  {
    /* region prev points to previous region */
    hintreg.start = prev->end + 1;
    if ( !DOUBLEQ_LAST(prev, regionq) )
      hintreg.end = DOUBLEQ_NEXT(prev, regionq)->start - 1;
    else
      hintreg.end = (*vm_map)->header.end;

    /* have a closer look. Does our region fit into this free space */
    //-CJ		printf("   hintreg %p - %p  %p <= %p\n",
    //-CJ				hintreg.start, hintreg.end, hintreg.start + size + 2*PAGESIZE,
    //-CJ				hintreg.end);
    if ( hintreg.start + size + 2*PAGESIZE <= hintreg.end )
    {
      /* region does fit. Update start and end address. */
      if ( hint + size + PAGESIZE <= hintreg.end &&
           hintreg.start + PAGESIZE <= hint )
      {
        /* if possible try to allocate at hint */
        region->start = hint;
        region->end = region->start + size - 1;
        //-CJ				printf("vm_map_find_near: hint match region %p - %p\n",
        //-CJ						region->start, region->end);
      }
      else if ( direction > 0 )
      {
        /* else allocate in the prefered direction */
        region->start = hintreg.start + PAGESIZE;
        region->end = region->start + size - 1;
        //-CJ				printf("vm_map_find_near: direction + region %p - %p\n",
        //-CJ						region->start, region->end);
      }
      else
      {
        region->end = hintreg.end - PAGESIZE;
        region->start = region->end - size + 1;
        //-CJ				printf("vm_map_find_near: direction - region %p - %p\n",
        //-CJ						region->start, region->end);
      }
      break;
    }
    /* either space is to small or region is not free, try next */
    if ( direction > 0 )
    {
      if ( DOUBLEQ_LAST(prev, regionq) )
        return VM_OUTOFMAP;
      prev = DOUBLEQ_NEXT(prev, regionq);
    }
    else
    {
      prev = DOUBLEQ_PREV(prev, regionq);
      if ( DOUBLEQ_FIRST(prev, regionq) )
        return VM_OUTOFMAP;
    }
  }
  while ( 1 );

  ASSERT(prev != NULL);

  *prevreg = prev;

  return VM_OK;
}


void vm_map_init(struct vm_map* vm_map, address_t start, address_t end)
{
  vm_map->header.start = start;
  vm_map->header.end   = end;
  zeroOut(&vm_map->header.oref, sizeof(struct vm_oref));
  vm_map->header.flags = 0;
  vm_map->header.cur   = 0;
  vm_map->header.max   = 0;
  DOUBLEQ_INIT(&vm_map->header, regionq);

  vm_map->numentries = 0;
  vm_map->loan = NULL;
  vm_map->hint = NULL;
  vm_map->refcount = 1;
  vm_map->flags = 0;

  lockInit(&vm_map->hint_lock);
  lockInit(&vm_map->ref_lock);
  lockInit(&vm_map->flag_lock);
  bigLockInit(&vm_map->lock);
}

/* vm_map_link_region: link new region after prev region.
	 This function needs to raise the lock on the vm_map.
 */
int vm_map_link_region(struct vm_map* vm_map, struct vm_region* prev,
                       struct vm_region* new)
{
  /* XXX raise lock */

  DOUBLEQ_ADDAFTER(prev, new, regionq);
  vm_map->numentries++;

  /* XXX lower lock */
  return VM_OK;
}


/* vm_map_link_region: remove reg region from the vm_map.
	 This function needs to raise the lock on the vm_map.
	 The reg region needs to be freed afterwards.
 */
int vm_map_unlink_region(struct vm_map* vm_map, struct vm_region* reg)
{
  /* XXX raise lock */

  DOUBLEQ_REMOVE(reg, regionq);
  vm_map->numentries--;

  /* XXX lower lock */
  return VM_OK;
}

/* Create a new region and connect this region to the specified
	 object. This is a lot of fun because this function needs to allocate
	 memory without producing a chicken-egg problem or deadlocks
 */
int vm_map_create_region(RegionPtr_t region, struct vm_region** new)
{
  int err;

  *new = (struct vm_region*)vm_alloc_desc( sizeof(struct vm_region) );
  if ( *new == NULL )
  {
    PANIC("Ups, no memory left");
    return VM_NOMEM;
  }

  (*new)->start = region->start;
  (*new)->end = region->end;

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

  if ( (region->max & region->cur) ^ region->cur )
  {
    WARNING("protection violation");
    /* protection violation cur > max */
    vm_free_desc(*new);
    return VM_ACCESS;
  }

  (*new)->cur = region->cur;
  (*new)->max = region->max;

  switch( err =
            vm_object_connect(region->type, &region->obj_arg, &(*new)->oref) )
  {
  case VM_OK:
    return VM_OK;
  default:
    vm_free_desc(*new);
    WARNING("vm_object_connect failed");
    return err;
  }
}

/* Create a new region and share the object with the additional source. */
int vm_map_share_region(RegionPtr_t region,
                        struct vm_region* source, struct vm_region** new, int flags)
{
  int err;

  *new = (struct vm_region*)vm_alloc_desc( sizeof(struct vm_region) );
  if ( *new == NULL )
  {
    PANIC("Ups, no memory left");
    return VM_NOMEM;
  }
  (*new)->start = region->start;
  (*new)->end = region->end;
  if ( (region->max & region->cur) ^ region->cur )
  {
    /* protection violation cur > max */
    vm_free_desc(*new);
    return VM_ACCESS;
  }
  if ( (source->max & region->cur) ^ region->cur )
  {
    /* protection violation cur > max */
    vm_free_desc(*new);
    return VM_ACCESS;
  }
  (*new)->cur = region->cur;
  (*new)->max = region->max;

  ASSERT(source->oref.manager != NULL );
  if ( source->oref.manager->share == NULL )
    return VM_NIMP;
  switch( err =
            source->oref.manager->share(&(*new)->oref, &source->oref, flags) )
  {
  case VM_OK:
    return VM_OK;
  default:
    vm_free_desc(*new);
    return err;
  }
}


/* Free a region. Befor releasing memory we need to disconnect form the
	 underlying VM object.
 */
int vm_map_free_region(struct vm_region* r)
{
  int err;

  ASSERT(r->oref.manager != NULL );
  if ( r->oref.manager->disconnect == NULL )
  {
    vm_free_desc(r);
    return VM_NIMP;
  }
  err = r->oref.manager->disconnect(&r->oref);
  vm_free_desc(r);
  return err;
}


