/*
    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/kalloc_api.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3012 $
    Last Date of Change:   $Date: 2007-04-20 22:22:13 +0200 (Fre, 20 Apr 2007) $  by: $Author: sryffel $
 
*/
#include "Assert.h"
#include "Error.h"
#include "HeapAllocator.h"
#include "Support.h"
#include "TMProtectionDomain.h"

#include "vm.h"
#include "vm_calls.h"
#include "vm_bootup.h"

#include "kalloc_api.h"

/* kernel heap memory manager API. This are the high level functions to the
	 HeapAllocater. The main difference between km_alloc and malloc is the
	 additional type. This is used to have multiple allocation areas which are
	 independent (no locks and possible to share between Protection Domains).
	 
	 Following types are defined:
		KM_GENERIC: kernel generic space 
		KM_MEMORY:  memory related space (shared only between the Memory Manager and the Kernel)
		KM_THREAD:  thread related space (shared only between the Thread Manager and the Kernel)
		KM_SHARED:  Shared space (Memory, Thread and Kernel). Mainly for PD Descriptors.
		KM_IO:      IO related space (IO Manager and Kernel)
 */

void km_set_heap_region(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                        address_t phys, address_t virt, size_t size);


struct HeapAllocator_ctxt kernelheapctxt[KM_NUMOFSPACES];

/* initalize the kernel heap memory manager */
void km_init(struct vm_segmap* segmap, struct vm_pmap* vm_pmap)
{
  struct vm_page* page;
  address_t start;
  address_t hint;
  address_t phys;
  size_t size;
  int err;
  int i;

  start = (address_t)0x10000000; /* start at 128 MB *//* XXX */

  printf("Kernel heap memory init... \n");
  /* the kernel boot/generic space needs special treatment */
  for ( i = 0; i < KM_NUMOFSPACES; i++ )
  {
    /* first we need to steal a page to work with */
    page = vm_page_get(PQ_ZERO);
    phys = vm_page_getaddr(page);
    if ( vm_page_ref(page) != 1 )
      WARNING("Page has refcount non equal to one");
    start = (address_t)PAGEROUNDDOWN(start);
    size = PAGESIZE;

    /* update segmap, so that them memory can be mapped into the kernel */
    hint = segmap->bootalloc[segmap->bootalloccnt - 1].phys;
    segmap->bootalloc[segmap->bootalloccnt - 1].virt = start;
    segmap->bootalloc[segmap->bootalloccnt - 1].phys = phys;
    segmap->bootalloc[segmap->bootalloccnt - 1].size = size;
    segmap->bootalloc[segmap->bootalloccnt - 1].type = VM_TYPE_HEAP;
    segmap->bootalloc[segmap->bootalloccnt].virt = NULL;
    segmap->bootalloc[segmap->bootalloccnt].phys = hint;
    segmap->bootalloc[segmap->bootalloccnt].size = 0;
    segmap->bootalloc[segmap->bootalloccnt].type = 0;
    segmap->bootalloccnt++;

    printf("km_init:\n");

    ASSERT((address_t)&i > (address_t)&segmap->bootalloc[segmap->bootalloccnt - 1].type); /* HEAP-STACK overflow */

    err = vm_pmap_region_enter(vm_pmap, phys, start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("kernel heap memory init: vm_pmap_region_enter failed");

    printf("calling HeapAllocator_init with &hctx=0x%8x start=0x%8x size=0x%x\n", &kernelheapctxt[i], start, PAGESIZE);
    if ( HeapAllocator_init(&kernelheapctxt[i], start, PAGESIZE) != HA_OK )
    {
      /* vm memory is so cool, the anonymous handler will allocate pages on demand */
      /* XXX this should probably be changed but for now this is cool.
      	 The main problem is that befor the VM Manager is running no page can be
      	 added on demand. So if somebody allocates to much memory we will fail
      	 horribly*/
      PANIC("kernel heap memory init: HeapAllocator_init failed");
    }
    start += 0x04000000; /* 64 MB virtual size should be enough *//* XXX */
  }
  printf("km_init: no longer in bootup, on step nearer the real thing(TM)\n");
}

void km_set_heap_region(struct vm_map* vm_map, struct vm_pmap* vm_pmap,
                        address_t phys, address_t virt, size_t size)
{
  Region_t r;
  size_t maxsize;
  int err;


  ENSURE( PAGEREMAINDER(virt) == 0 );
  maxsize = PAGEROUNDUP(0x04000000);

  r.obj_arg.argsize = 0;
  r.obj_arg.arg[0] = 0;
  r.start = virt;
  r.end = virt + maxsize - 1;
  /* VM uses the last valid address and not the first invalid */

  r.cur = VM_ACCESS_READ|VM_ACCESS_WRITE;
  r.max = VM_ACCESS_READ|VM_ACCESS_WRITE;
  r.type = (unsigned short)VM_MANAGER_ANON;

  printf("%s\t-> address %p, size %#lx offset 0\n",
         "anonymous", r.start, maxsize);

  err = vm_region_add(vm_map, vm_pmap, &r);
  switch ( err )
  {
  case VM_OK:
    break;
  default:
    printf("\tmapping FAILED error %i\n", err);
    PANIC("km_set_heap_region: vm_region_add failed");
  }

  err = vm_region_map(vm_map, vm_pmap, &r, virt, virt + size - 1, phys);
  switch ( err )
  {
  case VM_OK:
    break;
  default:
    printf("\tmapping FAILED error %i\n", err);
    PANIC("km_set_heap_region: vm_region_map failed");
  }
}

/* allocate memory in the specified space.
	 XXX options should be used to specify NONBLOCK, RETRY, etc
 */
address_t km_alloc(size_t size, int type /*, int options */)
{
  address_t address;
  int err;

  if ( type < 0 || type >= KM_NUMOFSPACES )
  {
    WARNING("kernel heap memory invalid space");
    return NULL;
  }

  err = HeapAllocator_alloc(&kernelheapctxt[type], &address, size);
  switch ( err )
  {
  case HA_OK:
    return address;
  default:
    PANIC("kernel heap memory: HeapAllocator_alloc failed");
    return NULL;
  }
}

/* XXX options ... */
void km_free(address_t addr)
{
  int i;
  int err;

  for ( i = 0; i < KM_NUMOFSPACES; i++)
  {
    if ((address_t)kernelheapctxt[i].TopOfMemSeg < addr &&
        (address_t)kernelheapctxt[i].LastHeader  > addr)
    {
      err = HeapAllocator_free(&kernelheapctxt[i], addr);
      switch ( err )
      {
      case HA_OK:
        return;
      default:
        PANIC("kernel heap memory: HeapAllocator_free failed");
        return;
      }
    }
  }
  WARNING("kernel heap memory invalid space");
}

/* XXX options ... */
void km_free_type(address_t addr, int type)
{
  int err;

  if ( type < 0 || type >= KM_NUMOFSPACES )
  {
    WARNING("kernel heap memory invalid space");
    return;
  }

  err = HeapAllocator_free(&kernelheapctxt[type], addr);
  switch ( err )
  {
  case HA_OK:
    return;
  default:
    PANIC("kernel heap memory: HeapAllocator_free failed");
    return;
  }
}

int heap_resize(struct HeapAllocator_ctxt* ctxt, size_t size)
{
  size_t s;
  int err;
  extern pdom_t mainKernelPD;

  ENSURE( PAGEREMAINDER(size) == 0 );

  for ( s = 0; s < size; s += PAGESIZE )
  {
    //-CJ		printf("heap_resize: looking up %p\n", (address_t)ctxt->LastHeader->end + s);
    err = vm_region_lookup(&mainKernelPD->vm_space.vm_map,
                           &mainKernelPD->vm_space.vm_pmap,
                           (address_t)ctxt->LastHeader->end + s, VM_ACCESS_WRITE);
    if ( err != VM_OK )
    {
      WARNING("vm_region_lookup failed");
      return HA_FAILED;
    }
  }
  return HA_OK;
}

