/*
    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/steal_alloc.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3045 $
    Last Date of Change:   $Date: 2007-07-06 19:57:17 +0200 (Fre, 06 Jul 2007) $  by: $Author: sryffel $
 
*/
#include "Assert.h"
#include "Error.h"
#include "MemoryLayout.h"
#include "Lock.h"
#include "Support.h"

#include "types.h"
#include "simpleq.h"

#include "vm.h"
#include "vm_object.h"
#include "vm_page.h"

#include "steal_alloc.h"

#define MIN_SIZE 32
#define MIN_SIZE_SHIFT 5 /* ld(MIN_SIZE * 8) */
#define MAX_SIZE PAGESIZE /* may also be smaller than a page but not bigger */
#define NUM_OF_SIZES 8 /* 32 64 128 256 512 1024 2048 4096 */
#define MAX_BLOCKS 128 /* PAGESIZE/MIN_SIZE */
#define MASK_SIZE 4    /* MAX_BLOCKS/sizeof(uint32) */
#define MASK_SHIFT 5   /* ld(MASK_SIZE * 8) */
#define MASK_MASK 0x1f /* MASK_SIZE * 8 - 1 */

#define ADD	0x01
#define RM	0x80

struct salloc_header
{
  uint8 size; /* stored as ld(realsize) */
  uint8 usage;
  uint8 lock;
  address_t start;
  struct vm_page* page;
  SIMPLEQ_ENTRY(salloc_header) saq; /* element queue */
  uint32 mask[MASK_SIZE]; /* 128 bit for 128 * 32 = 4096 bytes */
};

#define SHEADER_SIZE 32 /* sizeof(struct salloc_header) rounded to 2^n bytes */
#define SHEADER_SHIFT 5

SIMPLEQ_HEAD(salloc_head, salloc_header) steal_queues[NUM_OF_SIZES];

static char steal_string[] = "steal heap manager: $Id: steal_alloc.c 3045 2007-07-06 17:57:17Z sryffel $";

struct vm_pmap* steal_pmap;
address_t steal_start; /* where steal region starts */
address_t steal_next; /* read and write lock needed */
int steal_freepages;
int steal_usedpages;
LockDesc steal_pagelock;
LockDesc steal_ctxtlock;

static int steal_lock(struct salloc_header* ph, uint8 type, int lockf)
{
  uint8 t;
  Boolean b;

  do
  {
    b = lockTry(&steal_pagelock);
  }
  while ( b == FALSE && !(lockf & SA_NONBLOCK) );
  if ( b == FALSE )
    return 0; /* would block */

  t = ph->lock;
  if ( t & type )
  {
    unlock(&steal_pagelock);
    return 0;
  }
  ph->lock |= type;
  unlock(&steal_pagelock);
  return 1;
}

static int steal_unlock(struct salloc_header* ph, uint8 type, int lockf)
/* to unlock the page header no lock on the steal_lock is needed
	 because the write back should not result in transition states
	 where the page header is unintended unlocked.
 */
{
  //-CJ	lock(&steal_pagelock);
  ph->lock &= ~type;
  //-CJ	unlock(&steal_pagelock);
  return 1;
}

void steal_init(struct vm_pmap *vm_pmap, address_t start, int numpages)
/* we need a virtual mapable region (non direct mapped).
	 Also needed the pmap of this vm_space...
	 (if we share this region we need two pmap entries, therefor do
	 NOT share this region)
 */
{
  struct salloc_header* ph;
  struct vm_page *page;
  address_t phys;
  int err;
  int i;


  ASSERT(MIN_SIZE_SHIFT <= SHEADER_SHIFT);
  ASSERT(MAX_SIZE <= PAGESIZE);

  printf("Kernel page stealer init...\n");
  printf("steal_init: sizeof(salloc_header) = %i\n",
         (int)sizeof(struct salloc_header));
  lockInit(&steal_pagelock);
  lockInit(&steal_ctxtlock);

  for ( i = 0; i < NUM_OF_SIZES; i++ )
    SIMPLEQ_INIT(&steal_queues[i]);

  steal_start = start;
  steal_next = start;
  steal_freepages = numpages;
  steal_pmap = vm_pmap;

  /* Now steal the first page to store the page headers in it */

  /* steal a page */
  page = vm_page_get(PQ_ZERO);
  if ( page == NULL )
  {
    PANIC("steal_init: No page left");
  }
  phys = vm_page_getaddr(page);
  if ( vm_page_ref(page) != 1 )
    WARNING("steal_init: Page has refcount non equal to one");

  /* update the pmap layer */
  err = vm_pmap_enter(steal_pmap, steal_next, phys,
                      VM_ACCESS_READ|VM_ACCESS_WRITE);
  if ( err != VM_OK )
    PANIC("steal_init: vm_pmap_enter failed");

  /* make sure that an illegal access is trapped */
  mem_set_uint32(steal_next, 0xfeedface, PAGESIZE);

  ph = steal_next;
  ph->size = SHEADER_SHIFT;
  ph->lock = 0;
  ph->page = page;
  ph->start = steal_next;
  for ( i = 0; i < MASK_SIZE; i++ )
    ph->mask[i] = 0;

  ph->mask[0] = 0x1; /* first block filled with this header */
  ph->usage = 1;

  /* add the page to the queue */
  SIMPLEQ_ADDTAIL(&steal_queues[SHEADER_SHIFT-MIN_SIZE_SHIFT], ph, saq);
  steal_freepages--;
  steal_usedpages++;
  steal_next += PAGESIZE;

}

static struct salloc_header* steal_page(struct salloc_head* sq,
                                              uint8 size, int lockf)
      /* this is why we called it steal_alloc. */
{
  struct salloc_header* ph;
  struct vm_page *page;
  address_t  phys;
  int err;
  int i;
  Boolean b;

  if( steal_freepages == 0 )
  {
    PANIC("steal_alloc: out of virtual memory, use bigger region");
  }

  /* get a new header for this page */
  if ( steal_alloc((address_t*)&ph, SHEADER_SIZE,
                   lockf | SA_STEALING) != SA_OK )
  {
    /* XXX unterscheidung zw. AGAIN und ... ? */
    WARNING("NO SPACE LEFT");
    return NULL;
  }

  ph->size = size;
  ph->usage = 0;
  ph->lock = 0;
  for ( i = 0; i < MASK_SIZE; i++ )
    ph->mask[i] = 0;

  /* steal a page */
  page = vm_page_get(PQ_ZERO);
  if ( page == NULL )
  {
    WARNING("steal_page: No page left");
    return NULL;
  }
  phys = vm_page_getaddr(page);
  if ( vm_page_ref(page) != 1 )
    WARNING("steal_page: Page has refcount non equal to one");

  do
  {
    b = lockTry(&steal_ctxtlock);
  }
  while ( b == FALSE && !(lockf & SA_NONBLOCK) );
  if ( b == FALSE )
    return NULL; /* would block */

  /* update the pmap layer */
  err = vm_pmap_enter(steal_pmap, steal_next, phys,
                      VM_ACCESS_READ|VM_ACCESS_WRITE);
  if ( err != VM_OK )
    PANIC("steal_page: vm_pmap_enter failed");

  ph->page = page;
  ph->start = steal_next;

  /* make sure that an illegal access is trapped */
  mem_set_uint32(steal_next, 0xfeedface, PAGESIZE);

  /* add the page to the queue */
  SIMPLEQ_ADDTAIL(sq, ph, saq);
  steal_freepages--;
  steal_usedpages++;
  steal_next += PAGESIZE;
  unlock(&steal_ctxtlock);

  //-CJ	dumpMemory(ph->start, 0, PAGESIZE);
  return ph;
}

int steal_alloc(address_t *addr, size_t size, int lockf)
{
  struct salloc_header* ph;
  struct salloc_header* ph2;
  size_t roundedsize;
  uint32 mask;
  uint32 bit;
  int ld;
  int i;
  int lockstate;
  uint8 full;

  ENSURE( size <= MAX_SIZE );

  for ( ld = 0, roundedsize = MIN_SIZE; roundedsize < size; ld++)
    roundedsize <<=1;

  ASSERT(ld < NUM_OF_SIZES);

  full = (uint8)((int)MAX_BLOCKS >> ld);

  SIMPLEQ_FOREACH(&steal_queues[ld], ph, saq)
  if ( ph->usage < full )
    break;

  if ( ph == NULL )
  {
    printf("steal_alloc: no page available, stealing one\n");
    ph = steal_page(&steal_queues[ld], ld, lockf);
    if ( ph == NULL )
      return lockf&SA_NONBLOCK?SA_AGAIN:SA_NOMEM;
  }
  // printf("steal_alloc: requested size %lu rounded %lu, ld %i, full %i usage %i lock %i\n",
  //       size, roundedsize, ld, full, ph->usage, lockf);

  /* keep allways some space left on the list used for the page headers */
  if ( roundedsize == SHEADER_SIZE && !(lockf & SA_STEALING) &&
       SIMPLEQ_LAST(&steal_queues[ld], salloc_head) == ph )
  {
    if ( ph->usage > full - 8 )
    {
      printf("steal_alloc: preallocating page, stealing one\n");
      /* only link the new page into the heap do not use it */
      ph2 = steal_page(&steal_queues[ld], ld, lockf);
    }
  }

  /* found page with space, locate space and return it */
  do
  {
    lockstate = steal_lock(ph, ADD, lockf);
  }
  while ( lockstate != 1 && !(lockf & SA_NONBLOCK) );
  if ( lockstate == 0 )
    return SA_AGAIN;

  i = 0;
  do
  {
    mask = ph->mask[i>>MASK_SHIFT];
    // printf("try mask %i %p\n",i>>MASK_SHIFT, mask);
    do
    {
      if ((mask & 1) == 0 )
        goto found;
      mask >>=1;
      i++;
    }
    while	(i < full && i & MASK_MASK);
  }
  while ( i < full );
  steal_unlock(ph, ADD, lockf);

  PANIC("No free slot found.");

found:
  bit = 1 << (i & MASK_MASK);

  ph->usage++;
  ph->mask[i>>MASK_SHIFT] |= bit;

  steal_unlock(ph, ADD, lockf);

  *addr = ph->start + ( i<<(ld + MIN_SIZE_SHIFT) );
  //-CJ	printf("steal_alloc: addr %p, mask %.8x%.8x%.8x%.8x\n",
  //-CJ			*addr, ph->mask[3], ph->mask[2], ph->mask[1], ph->mask[0]);

  return SA_OK;
}

int steal_free(address_t addr, size_t size, int lockf)
{
  struct salloc_header* ph;
  size_t roundedsize;
  size_t offset;
  uint32 bit;
  int lockstate;
  int ld;
  int i;

  ENSURE( size <= MAX_SIZE );

  for ( ld = 0, roundedsize = MIN_SIZE; roundedsize < size; ld++)
    roundedsize <<=1;

  ASSERT(ld < NUM_OF_SIZES);

  SIMPLEQ_FOREACH(&steal_queues[ld], ph, saq)
  if ( ph->start <= addr && addr < ph->start + PAGESIZE )
    break;

  if ( ph == NULL )
  {
    WARNING("steal_free: got non-managed address");
    return SA_INVAL;
  }

  /* make sure that an illegal access is trapped */
  mem_set_uint32(addr, 0xdeadbeef, roundedsize);

  offset = addr - ph->start;
  // printf("offset = 0x%x  addr = 0x%x  ph->start = 0x%x\n", offset, addr, ph->start);

  ASSERT( offset < PAGESIZE );

  i = offset >> (ld + MIN_SIZE_SHIFT);
  bit = 1 << (i & MASK_MASK);

  do
  {
    lockstate = steal_lock(ph, RM, lockf);
  }
  while ( lockstate != 1 && !(lockf & SA_NONBLOCK) );
  if ( lockstate == 0 )
    return SA_AGAIN;

  // printf("steal_free: requested size %lu rounded %lu, ld %i, usage %i lock %i\n     i %i imask %i bit %p",
  //       size, roundedsize, ld, ph->usage, lockf, i, i>>MASK_SHIFT, bit);

  ph->usage--;
  ph->mask[i>>MASK_SHIFT] &= ~bit;

  steal_unlock(ph, RM, lockf);

  return SA_OK;
}

/* create a minimal object handler (only connect and lookup is needed) */
int steal_connect(struct vm_oref* oref, struct vm_object_arg* args);
int steal_lookup(struct vm_oref* oref, offset_t offset, int type,
                 struct vm_page** page);
int steal_get_type(struct vm_oref* oref, unsigned short* type, char** s);
int steal_get_arg(struct vm_oref* oref, struct vm_object_arg* arg);

struct vm_manager steal_manager =
  {
    steal_connect,
    default_disconnect,
    default_share,
    default_attach,
    default_extend,
    default_loan_alloc,
    default_loan_dealloc,
    default_loan_out,
    default_loan_in,
    steal_lookup,
    default_map,
    steal_get_type,
    steal_get_arg,
    NULL
  };

int steal_connect(struct vm_oref* oref, struct vm_object_arg* args)
{
  oref->manager = &steal_manager;
  oref->obj.object = NULL;

  return VM_OK;
}

int steal_lookup(struct vm_oref* oref, offset_t offset, int type,
                 struct vm_page** page)
{
  struct salloc_header* ph;
  address_t virt;
  int i;

  if ( type == VM_ACCESS_EXEC )
    return VM_ACCESS;

  virt = (address_t)PAGEROUNDDOWN(steal_start + offset);
  if ( virt > steal_next )
  {
    *page = NULL;
    return VM_INVAL;
  }

  for ( i = 0; i < NUM_OF_SIZES; i++ )
    SIMPLEQ_FOREACH(&steal_queues[i], ph, saq)
    if ( ph->start == virt )  /* XXX ??? */
    {
      *page = ph->page;
      return VM_OK;
    }

  WARNING("steal_lookup: pointer was in range but nothing found");
  *page = NULL;
  return VM_INVAL;
}

int steal_get_type(struct vm_oref* oref, unsigned short* type, char** s)
{
  *type = VM_MANAGER_STEAL;
  if ( s != NULL )
    *s = steal_string;
  return VM_OK;
}


int steal_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;
}

