/*
    Copyright 2000 (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/Net/NetMemoryManager.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $  by: $Author: baderma $
 
*/
#include "NetMemoryManager.h"
#include "NetBuf.h"
#include "NetDebug.h"
#include "List.h"
#include "Lock.h"
#include "Syscall.h"

/* a lock is needed in order to keep the heap pointers consistent. only
 * one network thread may be in netMMAlloc and netMMFree at a time
 */
static LockDesc netMMLockDesc;
static Lock netMMLock;

static NetBuf netMMHeap;

static void netMMAllocAndSplit(NetBuf nb, NetMMEntry entry, short len);
static void netInitMMDesc(NetMMEntry mmEntry, short length,
                          short next, short prev, short flags)
{
#ifdef NMM_DEBUG
  checkMEM((Address) mmEntry + sizeof(NetMMEntryDesc) , length - sizeof(NetMMEntryDesc), FALSE);
  mmEntry->magic_pre[0] = 0xDEADBEEF;
  mmEntry->magic_pre[1] = 0x0C0FFEE0;
  mmEntry->magic_pre[2] = 0xDEADBEEF;
  mmEntry->magic_post[0] = 0xFEEBDAED;
  mmEntry->magic_post[1] = 0x0EEFF0C0;
  mmEntry->magic_post[2] = 0xFEEBDAED;
#endif

  netdbgPrintf(NETDEBUG_MEMORY,
               "netMMInitDesc: prev %i, next %i, len %i, flags %#x\n",
               prev, next, length, flags);

  mmEntry->prev = prev;
  mmEntry->next = next;
  mmEntry->flags = flags;
  mmEntry->len = length;
}

void netMMInit(void)
{
  if ( ! netbufAlloc(&netMMHeap, NET_MMBUF_SIZE) )
  {
    netdbgPrintf(NETDEBUG_MEMORY, "netMMInit: can not alloc memory\n");
    tmExit();
  }

  netMMLock = &netMMLockDesc;
  lockInit(netMMLock);

  NETBUF_END(netMMHeap) += NETBUF_SIZE(netMMHeap);
  netInitMMDesc( (NetMMEntry) NETBUF_DATA(netMMHeap),
                 NETBUF_LEN(netMMHeap) - sizeof(NetMMEntryDesc),
                 0, 0,
                 NMM_FREE );
}

int netMMAlloc(Address *buf, int size)
{
  NetBuf nb = netMMHeap;
  NetMMEntry entry = 0;
  short s;

  s = (((short) size + 3) >> 2) << 2;

  if ( size <= 0 || size > NET_MMBUF_MAX_SIZE )
  {
    netdbgPrintf(NETDEBUG_MEMORY, "netMMAlloc: bad buffer size %i\n", size);
    *buf = NULL;
    return NMM_FAILED;
  }
  /* GET LOCK */
  lock(netMMLock);
  netdbgPrintf(NETDEBUG_MEMORY, "netMMAlloc: allocating %i (%i) bytes\n", size, s);
  while ( nb )
  {
    entry = (NetMMEntry) NETBUF_DATA(nb);
    do
    {
      if ( IS_NMM_FREE(entry->flags) && entry->len >= s)
      {
        netdbgPrintf(NETDEBUG_MEMORY,
                     "netMMAlloc: found space at %i (%p)\n",
                     NMM_THIS(nb, entry), entry);
        /* found free place split and alloc */
        netMMAllocAndSplit(nb, entry, s);
        dumpMEM();
        unlock(netMMLock);
        *buf = (Address) entry + sizeof(NetMMEntryDesc);
        netdbgPrintf(NETDEBUG_MEMORY,
                     "netMMAlloc: space %i @ %p\n", s, *buf);
        return NMM_OK;
      }
      if ( entry->next == 0 )
        break;
      entry = (NetMMEntry)((char *)NETBUF_DATA(nb) +
                           entry->next - NETBUF_START(nb) );
    }
    while ( 1 );
    nb = nb->next;
  }
  netdbgPrintf(NETDEBUG_MEMORY,
               "netMMAlloc: not enough space found allocating new NetBuf\n");

  /* not enough space found alloc new buffer */
  if ( ! netbufAlloc(&nb, NET_MMBUF_SIZE) )
  {
    netdbgPrintf(NETDEBUG_MEMORY, "netMMAlloc: can not alloc memory\n");
    *buf = NULL;
    return NMM_FAILED;
  }
  NETBUF_END(nb) += NETBUF_SIZE(nb);
  entry = (NetMMEntry) NETBUF_DATA(nb);
  netInitMMDesc( entry, NETBUF_LEN(nb) - sizeof(NetMMEntryDesc),
                 0, 0, NMM_FREE );
  /* alloced free place split and alloc */
  netMMAllocAndSplit(nb, entry, s);
  nb->next = netMMHeap;
  netMMHeap = nb;
  dumpMEM();
  unlock(netMMLock);
  *buf = (Address) entry + sizeof(NetMMEntryDesc);
  netdbgPrintf(NETDEBUG_MEMORY,
               "netMMAlloc: space %i @ %p\n", size, *buf);
  return NMM_OK;
}

void netMMFree(Address buf)
{
  NetBuf nb = netMMHeap;
  NetMMEntry entry, t;

  if ( buf == NULL )
  {
    netdbgPrintf(NETDEBUG_MEMORY, "netMMFree: tried to free NULL\n");
    return;
  }

  lock(netMMLock);
  netdbgPrintf(NETDEBUG_MEMORY,
               "netMMFree: space @ %p\n", buf-8);
  while ( nb )
  {
    if ( (Address) buf > (Address) nb &&
         (Address) buf < (Address)(nb + NET_MMBUF_SIZE) )
    {
      /* buf is in this NetBuf */
      entry = (NetMMEntry)((char *)buf - sizeof(NetMMEntryDesc));
#ifdef NMM_DEBUG

      checkMEM((Address) entry, sizeof(NetMMEntryDesc), TRUE );
#endif

      if ( IS_NMM_FREE(entry->flags) )
      {
        netdbgPrintf(NETDEBUG_MEMORY,
                     "netMMFree: tried to free already freed region\n");
        return;
      }
      entry->flags = NMM_FREE;
      /* compact the heap if possible */
      t = (NetMMEntry)((char *) NETBUF_DATA(nb) +
                       entry->next - NETBUF_START(nb) );
#ifdef NMM_DEBUG

      checkMEM((Address) t, sizeof(NetMMEntryDesc), TRUE );
#endif

      if ( IS_NMM_FREE(t->flags) )
      {
        entry->next = t->next;
        entry->len += t->len + sizeof(NetMMEntryDesc);
        zeroOut(t, sizeof(NetMMEntryDesc));
      }
      t = (NetMMEntry)((char *) NETBUF_DATA(nb) +
                       entry->prev - NETBUF_START(nb) );
#ifdef NMM_DEBUG

      checkMEM((Address) t, sizeof(NetMMEntryDesc), TRUE );
#endif

      if ( IS_NMM_FREE(t->flags) )
      {
        t->next = entry->next;
        t->len += entry->len + sizeof(NetMMEntryDesc);
        zeroOut(entry, sizeof(NetMMEntryDesc));
      }
      dumpMEM();
      unlock(netMMLock);
      return;
    }
    nb = nb->next;
  }
  unlock(netMMLock);
  netdbgPrintf(NETDEBUG_MEMORY,
               "netMMFree: tried to free non netMM region\n");
  return;
}

static void netMMAllocAndSplit(NetBuf nb, NetMMEntry entry, short len)
{
  NetMMEntry new;
  short r;
  /* XXX ALIGN TO WORD */
  /* we know for sure that the new mem will fit in the region */
  if ( len + sizeof(NetMMEntryDesc) <= entry->len )
  {
    /* split */
    new = (NetMMEntry)((char*)entry + len + sizeof(NetMMEntryDesc));
    r = entry->len - (len + sizeof(NetMMEntryDesc) );
    netInitMMDesc( new, r, entry->next, NMM_THIS(nb, entry), NMM_FREE);
    netInitMMDesc( entry, len, NMM_THIS(nb, new), entry->prev, NMM_USED );
  }
  else
  {
    netInitMMDesc( entry, entry->len, entry->next, entry->prev, NMM_USED );
  }
}

#ifdef NMM_DEBUG
void checkMEM(Address from, int size, Boolean ok)
{
  int i = 0, match = 0;
  long *p = (long *)from;

  /*	for ( i = 0; i<size; i+=4 ) {
  		printf("%p: %#08lx %#08lx %#08lx %#08lx\n", p, p[0], p[1], p[2], p[3]);
  		p++;
  	} */
  for ( i = size; i>0; i-=4 )
  {
    if ( *p == 0xDEADBEEF || *p == 0x0C0FFEE0 || *p == 0xFEEBDAED || *p == 0x0EEFF0C0 )
    {
      if (! ok )
        printf ("checkMEM: possible header found @ %p\n", p);
      match = 1;
    }
    p++;
  }
  if ( ok && match == 0 )
    printf ("checkMEM: no header found\n");
}

#endif
void dumpMEM()
{
  NetBuf nb = netMMHeap;
  NetMMEntry entry = 0;

  while ( nb )
  {
    netdbgPrintf(NETDEBUG_MEMORY, "netMM: dumping NetBuf @ %p\n", nb);
    entry = (NetMMEntry) NETBUF_DATA(nb);
    do
    {
      netdbgPrintf(NETDEBUG_MEMORY,
                   "\t%p: prev: %4i next: %4i len: %4i flags: %s\n",
                   entry, entry->prev, entry->next, entry->len,
                   entry->flags==NMM_FREE?"free":entry->flags==NMM_USED?
                   "used":"UNKNOWN" );
      if ( entry->next == 0 )
        break;
      entry = (NetMMEntry)((char *)NETBUF_DATA(nb) +
                           entry->next - NETBUF_START(nb) );
    }
    while ( 1 );
    nb = nb->next;
  }
  netdbgPrintf(NETDEBUG_MEMORY, "netMM: done dumping\n", nb);

}
