/*
 * nasd_shutdown.c
 *
 * Shutdown management for NASD.
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_shutdown.h>

NASD_DECLARE_ONCE(nasd_shutdown_init_once)
NASD_DECLARE_MUTEX(nasd_shutdown_use_counter_lock)
int nasd_shutdown_use_counter;

nasd_freelist_t *nasd_free_shutdown = NULL;
#define NASD_MAX_FREE_SHUTDOWN  64
#define NASD_SHUTDOWN_INC        8
#define NASD_SHUTDOWN_INITIAL   16

nasd_freelist_t *nasd_free_shutdown_list;
#define NASD_MAX_FREE_SHUTDOWN_LIST 256
#define NASD_SHUTDOWN_LIST_INC       32
#define NASD_SHUTDOWN_LIST_INITIAL  128

static nasd_shutdown_t *
nasd_shutdown_get()
{
  nasd_shutdown_t *s;

  NASD_FREELIST_GET(nasd_free_shutdown,s,next,(nasd_shutdown_t *));
  return(s);
}

static nasd_status_t
init_shutdown_list(
  nasd_shutdown_list_t  *sl)
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&sl->lock);
  return(rc);
}

static void
clean_shutdown_list(
  nasd_shutdown_list_t  *sl)
{
  nasd_mutex_destroy(&sl->lock);
}

/*
 * nasd_shutdown_sys_real_init
 *
 * Called when the system comes into use after not being
 * used (start-of-day call to nasd_shutdown_sys_init(), or first
 * such call after the last call to nasd_shutdown_cleanup()
 * which actually deactivated the system).
 */
nasd_status_t
nasd_shutdown_sys_real_init()
{
  if (nasd_free_shutdown)
    return(NASD_SUCCESS);

  NASD_FREELIST_CREATE(nasd_free_shutdown, NASD_MAX_FREE_SHUTDOWN,
    NASD_SHUTDOWN_INC, sizeof(nasd_shutdown_t));
  if (nasd_free_shutdown == NULL)
    return(NASD_NO_MEM);
  NASD_FREELIST_PRIME(nasd_free_shutdown, NASD_SHUTDOWN_INITIAL,next,
    (nasd_shutdown_t *));

  NASD_FREELIST_CREATE(nasd_free_shutdown_list, NASD_MAX_FREE_SHUTDOWN_LIST,
    NASD_SHUTDOWN_LIST_INC, sizeof(nasd_shutdown_list_t));
  if (nasd_free_shutdown_list == NULL) {
    NASD_FREELIST_DESTROY(nasd_free_shutdown,next,(nasd_shutdown_list_t *));
    return(NASD_NO_MEM);
  }
  NASD_FREELIST_PRIME_INIT(nasd_free_shutdown_list,
    NASD_SHUTDOWN_LIST_INITIAL,next,(nasd_shutdown_list_t *),
    init_shutdown_list);

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_shutdown_list_init(
  nasd_shutdown_list_t  **slp)
{
  nasd_shutdown_list_t *sl;

  NASD_FREELIST_GET_INIT(nasd_free_shutdown_list,sl,next,
    (nasd_shutdown_list_t *),init_shutdown_list);
  if (sl == NULL) {
    return(NASD_NO_MEM);
  }
  sl->list = NULL;

  *slp = sl;

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_shutdown_list_shutdown(
  nasd_shutdown_list_t  *sl,
  int                    verbose)
{
  nasd_status_t rc, ret_rc;
  nasd_shutdown_t *s, *n;

  ret_rc = NASD_SUCCESS;

  NASD_LOCK_MUTEX(sl->lock);
  for(s=sl->list;s;s=n) {
    n = s->next;
#if defined(LINUX) && defined(KERNEL)
    if (verbose&NASD_SHUTDOWN_ANNOUNCE_SCHED) {
      nasd_printf("Before shutdown: Try to yield\n");
      nasd_sys_thread_yield();
      nasd_printf("Before shutdown: Did yield\n");
    }
#endif /* LINUX && KERNEL */
    switch(s->type) {
      case NASD_SHUTDOWN_TYPE_PROC:
        if (verbose & NASD_SHUTDOWN_ANNOUNCE_PROC) {
          nasd_printf("Shutdown proc 0x%lx  %s(0x%lx) %s:%d\n",
            (unsigned long)s->proc, s->name,
            (unsigned long)s->arg, s->file, s->line);
        }
        s->proc(s->arg);
        break;
      case NASD_SHUTDOWN_TYPE_MEM:
        if (verbose & NASD_SHUTDOWN_ANNOUNCE_MEM) {
          nasd_printf("Shutdown mem 0x%lx (%ld) %s:%d\n", (unsigned long)s->arg,
            (unsigned long)s->size, s->file, s->line);
        }
        NASD_Free(s->arg, s->size);
        break;
      case NASD_SHUTDOWN_TYPE_MUTEX:
        if (verbose & NASD_SHUTDOWN_ANNOUNCE_MUTEX) {
          nasd_printf("Shutdown mutex 0x%lx %s:%d\n", (unsigned long)s->arg,
            s->file, s->line);
        }
        rc = nasd_mutex_destroy(s->arg);
        if (rc) {
          nasd_printf("WARNING: got 0x%x (%s) destroying NASD mutex 0x%lx"
            " created %s:%d\n",
            rc, nasd_error_string(rc), (unsigned long)s->arg,
            s->file, s->line);
        }
        break;
      case NASD_SHUTDOWN_TYPE_COND:
        if (verbose & NASD_SHUTDOWN_ANNOUNCE_COND) {
          nasd_printf("Shutdown cond 0x%lx %s:%d\n", (unsigned long)s->arg,
            s->file, s->line);
        }
        rc = nasd_cond_destroy(s->arg);
        if (rc) {
          nasd_printf("WARNING: got 0x%x (%s) destroying NASD cond 0x%lx"
            " created %s:%d\n",
            rc, nasd_error_string(rc), (unsigned long)s->arg,
            s->file, s->line);
        }
        break;
      case NASD_SHUTDOWN_TYPE_RWLOCK:
        if (verbose & NASD_SHUTDOWN_ANNOUNCE_RWLOCK) {
          nasd_printf("Shutdown rwlock 0x%lx %s:%d\n", (unsigned long)s->arg,
            s->file, s->line);
        }
        rc = nasd_rwlock_destroy(s->arg);
        if (rc) {
          nasd_printf("WARNING: got 0x%x (%s) destroying NASD rwlock 0x%lx"
            " created %s:%d\n",
            rc, nasd_error_string(rc), (unsigned long)s->arg,
            s->file, s->line);
        }
        break;
#if defined(DEC_OSF) && defined(KERNEL)
      case NASD_SHUTDOWN_TYPE_VMPAGE: {
        vm_page_t pp;
        pp = (vm_page_t)s->arg;
        vm_pg_free(pp);
        break;
      }
#endif /* DEC_OSF && KERNEL */
      default:
        nasd_printf("WARNING: got unknown shutdown type %d registered %s:%d\n",
          s->type, s->file, s->line);
        ret_rc = NASD_FAIL;
        break;
    }

#if defined(LINUX) && defined(KERNEL)
    if (verbose&NASD_SHUTDOWN_ANNOUNCE_SCHED) {
      nasd_printf("After shutdown: Try to yield\n");
      nasd_sys_thread_yield();
      nasd_printf("After shutdown: Did yield\n");
    }
#endif /* LINUX && KERNEL */

    NASD_FREELIST_FREE(nasd_free_shutdown,s,next);

  }

  NASD_UNLOCK_MUTEX(sl->lock);

  NASD_FREELIST_FREE_CLEAN(nasd_free_shutdown_list,sl,next,
    clean_shutdown_list);

  return(ret_rc);
}

nasd_status_t
_nasd_shutdown_proc(
  nasd_shutdown_list_t   *sl,
  void                  (*proc)(void *),
  void                   *arg,
  char                   *name,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;

  s = nasd_shutdown_get();
  if (s == NULL)
    return(NASD_NO_MEM);

  s->proc = proc;
  s->arg = arg;
  s->size = 0;
  s->type = NASD_SHUTDOWN_TYPE_PROC;
  s->name = name;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}

nasd_status_t
_nasd_shutdown_mem(
  nasd_shutdown_list_t   *sl,
  void                   *addr,
  int                     size,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;

  s = nasd_shutdown_get();
  if (s == NULL)
    return(NASD_NO_MEM);

  s->proc = NULL;
  s->arg = addr;
  s->size = size;
  s->type = NASD_SHUTDOWN_TYPE_MEM;
  s->name = NULL;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}

nasd_status_t
_nasd_shutdown_mutex(
  nasd_shutdown_list_t   *sl,
  void                   *addr,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;
  nasd_status_t rc;

  s = nasd_shutdown_get();
  if (s == NULL) {
    rc = nasd_mutex_destroy(addr);
    if (rc) {
      nasd_printf("ERROR: failed cleanup destroy of mutex 0x%lx %s:%d\n",
        (unsigned long)addr, file, line);
    }
    return(NASD_NO_MEM);
  }

  s->proc = NULL;
  s->arg = addr;
  s->size = 0;
  s->type = NASD_SHUTDOWN_TYPE_MUTEX;
  s->name = NULL;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}

nasd_status_t
_nasd_shutdown_cond(
  nasd_shutdown_list_t   *sl,
  void                   *addr,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;
  nasd_status_t rc;

  s = nasd_shutdown_get();
  if (s == NULL) {
    rc = nasd_cond_destroy(addr);
    if (rc) {
      nasd_printf("ERROR: failed cleanup destroy of cond 0x%lx %s:%d\n",
        (unsigned long)addr, file, line);
    }
    return(NASD_NO_MEM);
  }

  s->proc = NULL;
  s->arg = addr;
  s->size = 0;
  s->type = NASD_SHUTDOWN_TYPE_COND;
  s->name = NULL;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}

nasd_status_t
_nasd_shutdown_rwlock(
  nasd_shutdown_list_t   *sl,
  void                   *addr,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;
  nasd_status_t rc;

  s = nasd_shutdown_get();
  if (s == NULL) {
    rc = nasd_rwlock_destroy(addr);
    if (rc) {
      nasd_printf("ERROR: failed cleanup destroy of rwlock 0x%lx %s:%d\n",
        (unsigned long)addr, file, line);
    }
    return(NASD_NO_MEM);
  }

  s->proc = NULL;
  s->arg = addr;
  s->size = 0;
  s->type = NASD_SHUTDOWN_TYPE_RWLOCK;
  s->name = NULL;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}

#if defined(DEC_OSF) && defined(KERNEL)
nasd_status_t
_nasd_shutdown_vmpage(
  nasd_shutdown_list_t   *sl,
  void                   *addr,
  char                   *file,
  int                     line)
{
  nasd_shutdown_t *s;

  s = nasd_shutdown_get();
  if (s == NULL)
    return(NASD_NO_MEM);

  s->proc = NULL;
  s->arg = addr;
  s->size = 0;
  s->type = NASD_SHUTDOWN_TYPE_VMPAGE;
  s->name = NULL;
  s->file = file;
  s->line = line;

  NASD_LOCK_MUTEX(sl->lock);
  s->next = sl->list;
  sl->list = s;
  NASD_UNLOCK_MUTEX(sl->lock);

  return(NASD_SUCCESS);
}
#endif /* DEC_OSF && KERNEL */

/*
 * nasd_shutdown_real_cleanup
 *
 * Called when last user of the shutdown system calls
 * nasd_shutdown_real_cleanup(). Clean up and deallocate resources.
 */
void
nasd_shutdown_real_cleanup()
{
  NASD_FREELIST_DESTROY(nasd_free_shutdown,next,(nasd_shutdown_t *));
  NASD_FREELIST_DESTROY_CLEAN(nasd_free_shutdown_list,next,
    (nasd_shutdown_list_t *),clean_shutdown_list);
  nasd_free_shutdown = NULL;
  nasd_free_shutdown_list = NULL;
}

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

  rc = nasd_mutex_init(&nasd_shutdown_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }
  nasd_shutdown_use_counter = 0;
}

/*
 * nasd_shutdown_sys_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_shutdown_sys_init()
{
  nasd_status_t rc;

  nasd_once(&nasd_shutdown_init_once, nasd_shutdownsys_init);
  NASD_LOCK_MUTEX(nasd_shutdown_use_counter_lock);
  nasd_shutdown_use_counter++;
  if (nasd_shutdown_use_counter == 1) {
    rc = nasd_shutdown_sys_real_init();
    if (rc) {
      nasd_shutdown_use_counter = 0;
      nasd_free_shutdown = NULL;
      nasd_free_shutdown_list = NULL;
    }
  }
  else {
    rc = NASD_SUCCESS;
  }
  NASD_UNLOCK_MUTEX(nasd_shutdown_use_counter_lock);

  return(rc);
}

/*
 * nasd_shutdown_cleanup
 *
 * Previous caller of nasd_shutdown_sys_init() not using the shutdown
 * subsystem any more. Deallocate and cleanup iff necessary.
 */
void
nasd_shutdown_cleanup()
{
  NASD_ASSERT(nasd_shutdown_use_counter != 0);

  NASD_LOCK_MUTEX(nasd_shutdown_use_counter_lock);
  nasd_shutdown_use_counter--;
  if (nasd_shutdown_use_counter == 0) {
    nasd_shutdown_real_cleanup();
  }
  NASD_UNLOCK_MUTEX(nasd_shutdown_use_counter_lock);
}

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