/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define KERNEL
#include "ixemul.h"
#include <stddef.h>

#undef DEBUG

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

#define mem_list (u.u_md.md_list)
#define mem_used (u.u_md.md_malloc_sbrk_used)

#define MAGIC1 0x55555555
#define MAGIC2 0xaaaaaaaa

struct mem_block {
  struct mem_block *next, *prev;
  u_int		   size;
  u_int		   magic[2];
  u_int		   realblock[2];
  /* realblock should really be [0], and at the end we'd have a magic2[2] */
};

struct memalign_ptr {
  u_char	magic;
  u_int		alignment:24;
};

#define MEMALIGN_MAGIC 0xdd

/* perhaps important later ;-) */
#define PAGESIZE 2048

void *
malloc (size_t size)
{
  struct mem_block *res;

  /* guarantee long sizes (so we can use CopyMemQuick in realloc) */
  size = (size + 3) & ~3;
  
  /* include management information */
  res = AllocMem (size + sizeof (struct mem_block), 0); /* not MEMF_PUBLIC ! */
  
  if (res)
    {
      u_int *lp = &res->size;
      *lp++ = size;
      *lp++ = MAGIC1; *lp++ = MAGIC1;
      lp = (u_int *)((u_char *)lp + size);
      *lp++ = MAGIC2; *lp   = MAGIC2;
      
      Forbid ();
      AddTail ((struct List *) &mem_list, (struct Node *) res);
      Permit ();

DP(("malloc (%ld) = $%lx.\n",size,&res->realblock));

      mem_used += size;
      return &res->realblock;
    }

  return 0;
}

void *
memalign (size_t alignment, size_t size)
{
  u_char *p = (u_char *)syscall (SYS_malloc, 
				 size + alignment + sizeof (struct memalign_ptr));
  struct memalign_ptr *al_start;

  if (! p)
    return p;

  /* if the block is already aligned right, just return it */
  if (! ((u_int)p & (alignment - 1)))
    return p;

  /* else find the start of the aligned block in our larger block */
  al_start = (struct memalign_ptr *)(((u_int)p + alignment - 1) & -alignment);

  /* this could be problematic on 68000, when an odd alignment is requested,
   * should I just don't allow odd alignments?? */
  al_start[-1].magic = MEMALIGN_MAGIC;
  al_start[-1].alignment = (u_char *)al_start - p;

DP(("memalign (%ld, %ld) = $%lx. real = $%lx, magic = $%lx.\n", alignment, size,
   al_start, p, al_start[-1]));

  return al_start;
}

void *
valloc (size_t size)
{
  return (void *) syscall (SYS_memalign, PAGESIZE, size);
}

void
free (void *mem)
{
  struct mem_block *block;
  u_int *end_magic;

  /* this seems to be standard... don't like it though ! */
  if (! mem)
    return;

  block = (struct mem_block *)((u_char *)mem - offsetof (struct mem_block, realblock));

DP(("free ($%lx). block = $%lx, ", mem, block));
  
  if (((struct memalign_ptr *)mem - 1)->magic == MEMALIGN_MAGIC)
    {
      block = (struct mem_block *)
	      ((u_char *)block - ((struct memalign_ptr *)mem - 1)->alignment);
DP(("-> block = $%lx, ", block));
    }

DP(("size = %ld\n", block->size));
   

  if (((u_int)block & 1) ||
      (block->magic[0] != MAGIC1 || block->magic[1] != MAGIC1))
    {
DP(("  m1 = $%lx, m2 = $%lx.\n", block->magic[0], block->magic[1]));
      ix_panic ("free: start of block corrupt!");
      syscall (SYS_exit, 20);
    }

  end_magic = (u_int *)((u_char *)&block->realblock + block->size);
  if (end_magic[0] != MAGIC2 || end_magic[1] != MAGIC2)
    {
DP(("  endmagic1 = $%lx, endmagic2 = $%lx.\n", end_magic[0], end_magic[1]));
      ix_panic ("free: end of block corrupt!");
      syscall (SYS_exit, 20);
    }

  Forbid ();
  Remove ((struct Node *) block);
  Permit ();

  mem_used -= block->size;
  FreeMem (block, block->size + sizeof (struct mem_block));
}


void
all_free ()
{
  struct mem_block *b, *nb;
  
DP(("all_free'ing.. "));
  for (b  = (struct mem_block *) mem_list.mlh_Head;
       nb = b->next;
       b  = nb)
    FreeMem (b, b->size + sizeof (struct mem_block));

  /* this makes it possible to call all_free() more than once */
  NewList ((struct List *) &mem_list);
DP(("done\n"));
}

void *
realloc (void *mem, size_t size)
{
  struct mem_block *block;
  u_int *end_magic;
  void *res;

  if (! mem)
    return (void *) syscall (SYS_malloc, size);
    
  block = (struct mem_block *)((u_char *)mem - offsetof (struct mem_block, realblock));

DP(("realloc ($%lx, %ld). block = $%lx, ", mem, size, block));
  
  /* duplicate the code in free() here so we don't have to check those magic
   * numbers twice */
  
  if (((struct memalign_ptr *)mem - 1)->magic == MEMALIGN_MAGIC)
    {
      block = (struct mem_block *)
	      ((u_char *)block - ((struct memalign_ptr *)mem - 1)->alignment);
DP(("-> block = $%lx, ", block));
    }

DP(("size = %ld\n", block->size));
   
  if (((u_int)block & 1) ||
      (block->magic[0] != MAGIC1 || block->magic[1] != MAGIC1))
    {
DP(("  magic1 = $%lx, magic2 = $%lx.\n", block->magic[0], block->magic[1]));
      ix_panic ("realloc: start of block corrupt!");
      syscall (SYS_exit, 20);
    }

  end_magic = (u_int *)((u_char *)&block->realblock + block->size);
  if (end_magic[0] != MAGIC2 || end_magic[1] != MAGIC2)
    {
DP(("  endmagic1 = $%lx, endmagic2 = $%lx. ", end_magic[0], end_magic[1]));
      ix_panic ("realloc: end of block corrupt!");
      syscall (SYS_exit, 20);
    }

  /* now that the block is validated, check whether we have to really
   * realloc, or if we just can return the old block */
  if (block->size >= size)
    res = mem;
  else
    {
      Forbid ();
      Remove ((struct Node *) block);
      Permit ();

      res = (void *) syscall (SYS_malloc, size);
      if (res)
	{
	  CopyMemQuick (mem, res, block->size);

	  /* according to the manpage, the old buffer should only be
	   * freed, if the allocation of the new buffer was successful */
	  mem_used -= block->size;
	  FreeMem (block, block->size + sizeof (struct mem_block));
	}
    }

DP((" result = $%lx.\n", res));
  return res;
}
