/*
 * nasd_mem.c
 *
 * Generic memory-management
 *
 * Authors: Jim Zelenka, Sean Levy
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_freelist.h>

#if NASD_MEM_COUNT_ALLOC > 0
nasd_uint64 nasd_mem_allocated = 0;
#endif /* NASD_MEM_COUNT_ALLOC > 0 */

NASD_DECLARE_ONCE(nasd_mem_init_once)
NASD_DECLARE_MUTEX(nasd_mem_use_counter_lock)
int nasd_mem_use_counter;

#define NASD_MEM_TRACK_NBUCKETS 512
nasd_mem_track_t *nasd_mem_track_buckets[NASD_MEM_TRACK_NBUCKETS];

#define NASD_MEM_TRACK_ADDR_HASH(_addr_) ((int)((((unsigned long)(_addr_))>>3)&0x1FUL))

#if NASD_MEM_FILL != 0
nasd_uint32 nasd_mem_filler = NASD_MEM_FILL;
#endif /* NASD_MEM_FILL != 0 */

/*
 * nasd_mem_real_init
 *
 * Called when the system comes into use after not being
 * used (start-of-day call to nasd_mem_init(), or first
 * such call after the last call to nasd_mem_shutdown()
 * which actually deactivated the system).
 */
nasd_status_t
nasd_mem_real_init()
{
  NASD_ASSERT(nasd_mem_use_counter == 1);

  bzero((char *)nasd_mem_track_buckets, sizeof(nasd_mem_track_buckets));

  /* already did threads_init() */

#ifdef NASD_MEM_SYS_INIT_CODE
  NASD_MEM_SYS_INIT_CODE;
#endif /* NASD_MEM_SYS_INIT_CODE */ 

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_mem_dump_unfreed()
{
#if NASD_TRACK_ALLOCATIONS > 0
  nasd_mem_track_t *r, *n;
  long total;
  int i;
#endif /* NASD_TRACK_ALLOCATIONS > 0 */

  if (nasd_mem_use_counter == 0)
    return(NASD_SYS_NOT_INITIALIZED);

#if NASD_TRACK_ALLOCATIONS > 0
  nasd_printf("Dump of known unfreed memory locations (%" NASD_MEMALLOC_FMT
         " outstanding):\n",
         nasd_mem_allocated);
  total = 0;
  for(i=0;i<NASD_MEM_TRACK_NBUCKETS;i++) {
    for(r=nasd_mem_track_buckets[i];r;r=n) {
      n = r->next;
      nasd_printf("Active memory track %d bytes at 0x%lx from %s:%d\n",
        r->size, r->addr, r->file, r->line);
      total += r->size;
      nasd_mem_freetrack(r);
    }
    nasd_mem_track_buckets[i] = NULL;
  }
  nasd_printf("End dump of known unfreed memory locations.\n");
  nasd_printf("%d bytes at known unfreed memory locations, %" NASD_MEMALLOC_FMT
    " bytes outstanding\n",
    total, nasd_mem_allocated);
  return(NASD_SUCCESS);
#else /* NASD_TRACK_ALLOCATIONS > 0 */
  return(NASD_OP_NOT_SUPPORTED);
#endif /* NASD_TRACK_ALLOCATIONS > 0 */
}

/*
 * nasd_mem_real_shutdown
 *
 * Called when last user of the mem system calls nasd_mem_shutdown().
 * Clean up and deallocate resources.
 */
void
nasd_mem_real_shutdown()
{
#if NASD_TRACK_ALLOCATIONS > 0
  nasd_mem_track_t *r, *n;
  long total;
  int i;
#endif /* NASD_TRACK_ALLOCATIONS > 0 */

  NASD_ASSERT(nasd_mem_use_counter == 0);

#if NASD_TRACK_ALLOCATIONS > 0
  nasd_printf("Dump of known unfreed memory locations (%" NASD_MEMALLOC_FMT
         " outstanding):\n",
         nasd_mem_allocated);
  total = 0;
  for(i=0;i<NASD_MEM_TRACK_NBUCKETS;i++) {
    for(r=nasd_mem_track_buckets[i];r;r=n) {
      n = r->next;
      nasd_printf("Active memory track %d bytes at 0x%lx from %s:%d\n",
        r->size, r->addr, r->file, r->line);
      total += r->size;
      nasd_mem_freetrack(r);
    }
    nasd_mem_track_buckets[i] = NULL;
  }
  nasd_printf("End dump of known unfreed memory locations.\n");
  nasd_printf("%d bytes at known unfreed memory locations, %" NASD_MEMALLOC_FMT
    " bytes outstanding\n",
    total, nasd_mem_allocated);
#endif /* NASD_TRACK_ALLOCATIONS > 0 */

  nasd_threads_shutdown();
}

/*
 * nasd_memsys_init
 *
 * Executed exactly once, the first time nasd_mem_init() is
 * called. Initialize counter tracking number of times system
 * is initted.
 */
void
nasd_memsys_init()
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&nasd_mem_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }

  nasd_mem_use_counter = 0;
}

/*
 * nasd_mem_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple subsystems use us without knowing about
 * one another.
 */
nasd_status_t
nasd_mem_init()
{
  nasd_status_t rc;

  rc = nasd_threads_init();
  if (rc)
    return(rc);
  nasd_once(&nasd_mem_init_once, nasd_memsys_init);
  NASD_LOCK_MUTEX(nasd_mem_use_counter_lock);
  nasd_mem_use_counter++;
  if (nasd_mem_use_counter == 1) {
    rc = nasd_mem_real_init();
    if (rc) {
      nasd_mem_use_counter = 0;
    }
  }
  else {
    /* get rid of extra threads init above */
    nasd_threads_shutdown();
    rc = NASD_SUCCESS;
  }
  NASD_UNLOCK_MUTEX(nasd_mem_use_counter_lock);

  return(rc);
}

/*
 * nasd_mem_shutdown
 *
 * Previous caller of nasd_mem_init() not using the mem
 * subsystem any more. Deallocate and cleanup iff necessary.
 */
void
nasd_mem_shutdown()
{
  NASD_ASSERT(nasd_mem_use_counter != 0);

  NASD_LOCK_MUTEX(nasd_mem_use_counter_lock);
  nasd_mem_use_counter--;
  if (nasd_mem_use_counter == 0) {
    nasd_mem_real_shutdown();
  }
  NASD_UNLOCK_MUTEX(nasd_mem_use_counter_lock);
}

void
nasd_mem_track_alloc(
  void  *addr,
  int    size,
  char  *file,
  int    line)
{
  nasd_mem_track_t *t, **bucket;

  nasd_once(&nasd_mem_init_once, nasd_memsys_init);
  NASD_LOCK_MUTEX(nasd_mem_use_counter_lock);
  if (nasd_mem_use_counter == 0) {
    nasd_printf("WARNING: allocation track at %s:%d, memory system not initted\n",
      file, line);
    nasd_printf("Allocation of %d bytes at 0x%lx not tracked (%s:%d)\n",
      size, (unsigned long)addr, file, line);
  }
  t = nasd_mem_gettrack();
  if (t == NULL) {
    nasd_printf("WARNING: unable to track allocation of %d bytes at "
      "0x%lx from %s:%d\n",
      size, (unsigned long)addr, file, line);
  }
  else {
    bucket = &nasd_mem_track_buckets[NASD_MEM_TRACK_ADDR_HASH(addr)];
    t->addr = addr;
    t->size = size;
    t->file = file;
    t->line = line;
    t->next = *bucket;
    *bucket = t;
  }
  NASD_UNLOCK_MUTEX(nasd_mem_use_counter_lock);
}

void
nasd_mem_track_dealloc(
  void  *addr,
  int    size,
  char  *file,
  int    line)
{
  nasd_mem_track_t *t, *r, *s, **bucket;

  nasd_once(&nasd_mem_init_once, nasd_memsys_init);
  NASD_LOCK_MUTEX(nasd_mem_use_counter_lock);
  if (nasd_mem_use_counter == 0) {
    nasd_printf("WARNING: deallocation track at %s:%d, memory system not initted\n",
      file, line);
    nasd_printf("Deallocation of %d bytes at 0x%lx not tracked (%s:%d)\n",
      size, (unsigned long)addr, file, line);
  }
  bucket = &nasd_mem_track_buckets[NASD_MEM_TRACK_ADDR_HASH(addr)];
  t = NULL;
  for(s=NULL,r=*bucket;r;s=r,r=r->next) {
    if (r->addr == addr) {
      t = r;
      if (s)
        s->next = r->next;
      else
        *bucket = r->next;
      break;
    }
  }
  if (t == NULL) {
    nasd_printf("WARNING: deallocation of %d bytes at 0x%lx from %s:%d -- "
      "memory was never tracked\n",
      size, (unsigned long)addr, file, line);
  }
  else {
    NASD_ASSERT(t->addr == addr);
    if (t->size != size) {
      nasd_printf("WARNING: deallocation of %d bytes at 0x%lx from %s:%d -- "
        "tracked size %d\n",
        size, (unsigned long)addr, file, line, t->size);
    }
    nasd_mem_freetrack(t);
  }
  NASD_UNLOCK_MUTEX(nasd_mem_use_counter_lock);
}

#if defined(DEC_OSF) && defined(KERNEL)

nasd_mem_track_t *
nasd_mem_gettrack()
{
  nasd_mem_track_t *t;
  int sz;

  sz = sizeof(nasd_mem_track_t);
  t = (nasd_mem_track_t *)malloc(sz, BUCKETINDEX(sz), M_DEVBUF, M_WAITOK);
  return(t);
}

void
nasd_mem_freetrack(
  nasd_mem_track_t  *t)
{
  free(t, M_DEVBUF);
}

#endif /* DEC_OSF && KERNEL */

#if defined(LINUX) && defined(KERNEL)

nasd_mem_track_t *
nasd_mem_gettrack()
{
  nasd_mem_track_t *t;
  int sz;

  sz = sizeof(nasd_mem_track_t);
  t = (nasd_mem_track_t *)kmalloc(sz, GFP_KERNEL);
  return t;
}

void
nasd_mem_freetrack(nasd_mem_track_t *t)
{
  kfree((void *)t);
}

#endif /* LINUX && KERNEL */

#ifndef KERNEL

nasd_mem_track_t *
nasd_mem_gettrack()
{
  nasd_mem_track_t *t;

  t = (nasd_mem_track_t *)malloc(sizeof(nasd_mem_track_t));
  return(t);
}

void
nasd_mem_freetrack(
  nasd_mem_track_t  *t)
{
  free(t);
}

#endif /* !KERNEL */

#if NASD_MEM_FILL != 0
void nasd_mem_do_fill(
  void  *addr,
  int    nbytes)
{
  nasd_uint32 *_fp, _n, _nb;
  unsigned long _al;
  char *_c, *_c1;
  void *_p, *_e;

  _nb = nbytes;
  _p = (void *)addr;
  _c = (char *)addr;
  _e = &_c[nbytes];
  _al = (unsigned long)_p;
  _n = _al & 3;
  _c1 = (char *)&nasd_mem_filler;
  if (_n) {
    _al &= (~3);
    _c = (char *)_al;
    for(;_n<4;_n++) {
      NASD_ASSERT(((unsigned long)&_c[_n]) >= ((unsigned long)_p));
      NASD_ASSERT(((unsigned long)&_c[_n]) < ((unsigned long)_e));
      _c[_n] = _c1[_n];
      _nb--;
    }
    _al += 4;
    _p = (void *)_al;
  }
  _fp = (nasd_uint32 *)_p;
  for(_n=0;_nb>=4;_nb-=4,_n++) {
    NASD_ASSERT(((unsigned long)&_fp[_n]) >= ((unsigned long)_p));
    NASD_ASSERT(((unsigned long)&_fp[_n]) < ((unsigned long)_e));
    _fp[_n] = nasd_mem_filler;
  }
  if (_nb) {
    _p = (void *)&_fp[_n];
    _c = (char *)_p;
    for(_n=0;_nb;_nb--,_n++) {
      NASD_ASSERT(((unsigned long)&_c[_n]) >= ((unsigned long)_p));
      NASD_ASSERT(((unsigned long)&_c[_n]) < ((unsigned long)_e));
      _c[_n] = _c1[_n];
    }
  }
}
#endif /* NASD_MEM_FILL == 0 */

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
