/*
 * nasd_cbasic.c
 *
 * Basics of on-disk cache.
 *
 * 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_drive_options.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_cache.h>
#include <nasd/nasd_common.h>

int nasd_odc_force_format = 0; /* force formatting */
int nasd_odc_need_format = 0;  /* need to format disk */
int nasd_odc_need_recover = 0; /* need to recover cache state */

nasd_odc_state_t *nasd_odc_state;

int nasd_odc_dirty_autokick; /* how many dirty blocks auto-starts flush */
int nasd_odc_refblocks;      /* number of refcnt blocks */

extern int nasd_odc_buckets; /* number of buckets */

nasd_blkcnt_t nasd_od_blocks; /* number of data blocks on disk */

nasd_odc_oq_t nasd_odc_unusedq;
nasd_odc_oq_t nasd_odc_lru[NASD_ODC_NLRUS];
nasd_odc_oq_t nasd_odc_wireq;

#define NASD_MAX_FREE_ODC_ENT  (nasd_odc_size*2)
#define NASD_ODC_ENT_INC        5
#define NASD_ODC_ENT_INITIAL   (2*NASD_ODC_ENT_INC)

static nasd_freelist_t *nasd_odc_ent_freelist;

static void clean_ent(void *arg);
static nasd_status_t init_ent(void *arg);

void
nasd_odc_uninit_ent(
  nasd_odc_ent_t *ent)
{
  nasd_mutex_destroy(&ent->lock);
  nasd_cond_destroy(&ent->cond);
  nasd_cond_destroy(&ent->acond);
  nasd_rwlock_destroy(&ent->rwlock);
  nasd_odc_io_release_page(ent);
}

nasd_status_t
nasd_odc_init_ent(
  nasd_odc_ent_t  *ent,
  int              type,
  int              alloc_page)
{
  nasd_status_t rc, rc2;

  NASD_ASSERT(ent != NULL);

  bzero((char *)ent, sizeof(nasd_odc_ent_t));

  rc = nasd_mutex_init(&ent->lock);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&ent->cond);
  if (rc) {
    rc2 = nasd_mutex_destroy(&ent->lock);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down mutex 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->lock,
        __FILE__, __LINE__);
    }
    return(rc);
  }

  rc = nasd_cond_init(&ent->acond);
  if (rc) {
    rc2 = nasd_mutex_destroy(&ent->lock);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down mutex 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->lock,
        __FILE__, __LINE__);
    }
    rc2 = nasd_cond_destroy(&ent->cond);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down cond 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->cond,
        __FILE__, __LINE__);
    }
    return(rc);
  }

  rc = nasd_rwlock_init(&ent->rwlock);
  if (rc) {
    rc2 = nasd_mutex_destroy(&ent->lock);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down mutex 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->lock,
        __FILE__, __LINE__);
    }
    rc2 = nasd_cond_destroy(&ent->cond);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down cond 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->cond,
        __FILE__, __LINE__);
    }
    rc2 = nasd_cond_destroy(&ent->acond);
    if (rc2) {
      nasd_printf("DRIVE ERROR: got 0x%x (%s) shutting down cond 0x%lx at %s:%d\n",
        rc2, nasd_error_string(rc2), (unsigned long)&ent->acond,
        __FILE__, __LINE__);
    }
    return(rc);
  }

  ent->data_flags = 0;
  ent->lru_flags = 0;
  ent->io_flags = 0;
  ent->dirty_flags = 0;
  ent->type = type;
  ent->lru_num = NASD_ODC_LRU_NONE;

  if (alloc_page) {
    rc = nasd_odc_io_alloc_page(ent);
    if (rc) {
      rc2 = nasd_mutex_destroy(&ent->lock);
      if (rc2) {
        nasd_printf("DRIVE ERROR: "
          "got 0x%x (%s) shutting down mutex 0x%lx at %s:%d\n",
          rc2, nasd_error_string(rc2), (unsigned long)&ent->lock,
          __FILE__, __LINE__);
      }
      rc2 = nasd_cond_destroy(&ent->cond);
      if (rc2) {
        nasd_printf("DRIVE ERROR: "
          "got 0x%x (%s) shutting down cond 0x%lx at %s:%d\n",
          rc2, nasd_error_string(rc2), (unsigned long)&ent->cond,
          __FILE__, __LINE__);
      }
      rc2 = nasd_cond_destroy(&ent->acond);
      if (rc2) {
        nasd_printf("DRIVE ERROR: "
          "got 0x%x (%s) shutting down cond 0x%lx at %s:%d\n",
          rc2, nasd_error_string(rc2), (unsigned long)&ent->acond,
          __FILE__, __LINE__);
      }
      rc2 = nasd_rwlock_destroy(&ent->rwlock);
      if (rc2) {
        nasd_printf("DRIVE ERROR: "
          "got 0x%x (%s) shutting down rwlock 0x%lx at %s:%d\n",
          rc2, nasd_error_string(rc2), (unsigned long)&ent->rwlock,
          __FILE__, __LINE__);
      }
      return(rc);
    }
  }
  else {
    NASD_ODC_IO_ENT_CLEAR_PAGE(ent);
    ent->lru_flags |= NASD_CL_GHOST;
  }

  return(NASD_SUCCESS);
}

/*
 * Initialize a queue
 */
nasd_status_t
nasd_queue_init(
  nasd_odc_oq_t  *q)
{
  int rc;

  rc = nasd_mutex_init(&q->lock);
  if (rc)
    return(NASD_FAIL);
  rc = nasd_shutdown_mutex(nasd_odc_shutdown, &q->lock);
  if (rc) {
    return(rc);
  }

  q->lock_file = NULL;
  q->lock_line = (-1);

  q->head.type = NASD_ODC_T_BOGUS;
  q->head.hnext = q->head.hprev = &q->head;
  q->head.lhnext = q->head.lhprev = &q->head;
  q->head.dnext = q->head.dprev = &q->head;
  q->head.lnext = q->head.lprev = &q->head;
  q->head.wnext = q->head.wprev = &q->head;
  q->head.snext = q->head.sprev = &q->head;
  q->head.data.buf = NULL;

  q->size = 0;

  return(NASD_SUCCESS);
}

/*
 * Initialize a simple queue -- skips initializing mutex
 * note that the caller is responsible for ensuring that only the NOLOCK
 * versions of the macros are used with this queue!
 */
nasd_status_t
nasd_queue_simple_init(
  nasd_odc_oq_t  *q)
{
  q->lock_file = NULL;
  q->lock_line = (-1);

  q->head.type = NASD_ODC_T_BOGUS;
  q->head.hnext = q->head.hprev = &q->head;
  q->head.lhnext = q->head.lhprev = &q->head;
  q->head.dnext = q->head.dprev = &q->head;
  q->head.lnext = q->head.lprev = &q->head;
  q->head.wnext = q->head.wprev = &q->head;
  q->head.snext = q->head.sprev = &q->head;
  q->head.data.buf = NULL;

  q->size = 0;

  return(NASD_SUCCESS);
}

void
nasd_odc_preinsert(
  nasd_odc_ent_t  *ent)
{
  ent->refcnt = 0;
  ent->data_flags = NASD_CD_INVALID;
  ent->iocb = NULL;
  ent->iocb_arg = NULL;
  ent->real_sectno = nasd_odc_real_sectno(ent->blkno, ent->type);
  nasd_odc_block_hash_ins(ent);
}

/*
 * Initialize cache subsystem logic
 * (no I/Os performed yet, just set up
 * datastructures)
 */
nasd_status_t
nasd_cache_init()
{
  int i, rc, need_blks, thread_need_blks, nnpt;
  nasd_odc_ent_t *ent;
  nasd_status_t nrc;

  /*
   * Check it we're going to have enough cache blocks to function
   */
  thread_need_blks = 0;
  need_blks = 0;
  for(i=1;i<=NASD_OD_ILVLS;i++)
    thread_need_blks += i;
  thread_need_blks += NASD_DRIVE_MAX_BLOCKMAPCHUNK;
  thread_need_blks++;
  need_blks = 2 + thread_need_blks;
#if NASD_DRIVE_WIRE_REFCNT > 0
  need_blks += nasd_odc_refblocks;
#endif /* NASD_DRIVE_WIRE_REFCNT > 0 */
#if NASD_DRIVE_WIRE_NPT == 1
  nrc = nasd_drive_nnpt_expected(nasd_od_blocks, &nnpt);
  if (nrc)
    return(nrc);
  need_blks += nnpt;
#endif /* NASD_DRIVE_WIRE_NPT == 1 */
#if NASD_DRIVE_WIRE_NPT == 2
  nrc = nasd_drive_nnpt_expected(nasd_od_blocks, &nnpt);
  if (nrc)
    return(nrc);
  need_blks += 2 * nnpt;
#endif /* NASD_DRIVE_WIRE_NPT == 2 */
#if NASD_DRIVE_WIRE_NPT > 2
  nasd_printf("DRIVE: unknown NPT wire mode!\n");
  NASD_PANIC();
#endif /* NASD_DRIVE_WIRE_NPT > 2 */
  if (need_blks > nasd_odc_size) {
    nasd_printf(
      "DRIVE ERROR: configuration requires at least %d cache blocks\n",
      need_blks);
    nasd_printf(
      "DRIVE NOTE: %d blocks are necessary, but may not be sufficient\n",
      need_blks);
    return(NASD_NO_MEM);
  }

  /* init free queue */
  nrc = nasd_queue_init(&nasd_odc_unusedq);
  if (nrc)
    return(nrc);

  /* init wired queue */
  nrc = nasd_queue_init(&nasd_odc_wireq);
  if (nrc)
    return(nrc);

  /* init LRUs */
  for(i=0;i<NASD_ODC_NLRUS;i++) {
    nrc = nasd_queue_init(&nasd_odc_lru[i]);
    if (nrc)
      return(nrc);
  }

  NASD_FREELIST_CREATE(nasd_odc_ent_freelist, NASD_MAX_FREE_ODC_ENT,
    NASD_ODC_ENT_INC, sizeof(nasd_odc_ent_t));
  if (nasd_odc_ent_freelist == NULL)
    return(NASD_NO_MEM);

  rc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_odc_ent_destroy, NULL);
  if (rc) {
    nasd_odc_ent_destroy(NULL);
    return(rc);
  }

  NASD_FREELIST_PRIME_INIT(nasd_odc_ent_freelist, NASD_ODC_ENT_INITIAL,lnext,
    (nasd_odc_ent_t *),init_ent);

  /*
   * From here on out, any failures result in a call to
   * nasd_odc_shutdown_cache(). This can be used to cleanup
   * partially-initialized state, provided that any early
   * initialization is done above here so that it knows
   * what it needs to clean up.
   */

  /* init block lookup subsys */
  nrc = nasd_odc_blocksys_init();
  if (nrc) {
    nasd_printf("ERROR: cannot init block sys, rc=0x%x (%s)\n",
      nrc, nasd_error_string(nrc));
    nasd_odc_shutdown_cache(NULL);

    return(nrc);
  }

  nasd_odc_dirty_autokick = (2*nasd_odc_size)/5;

  nasd_printf("DRIVE: %d GP cache blocks\n", nasd_odc_size);
  nasd_printf("DRIVE: dirty autokick at %d blocks\n",
    nasd_odc_dirty_autokick);

  /*
   * See nasd_drive_options.h for an explanation of the settings
   */
#if NASD_DRIVE_WIRE_REFCNT > 0
  nasd_printf("DRIVE: refcnt blocks will be wired\n");
#endif /* NASD_DRIVE_WIRE_REFCNT > 0 */
#if NASD_DRIVE_WIRE_NPT == 1
  nasd_printf("DRIVE: NPT-2 blocks will be wired\n");
#endif /* NASD_DRIVE_WIRE_NPT == 1 */
#if NASD_DRIVE_WIRE_NPT == 2
  nasd_printf("DRIVE: all NPT blocks will be wired\n");
#endif /* NASD_DRIVE_WIRE_NPT == 2 */
#if NASD_DRIVE_WIRE_NPT > 2
  nasd_printf("DRIVE: unknown NPT wire mode!\n");
  NASD_PANIC();
#endif /* NASD_DRIVE_WIRE_NPT > 2 */

  /* init data handles */
  for(i=0;i<nasd_odc_size;i++) {
    rc = nasd_odc_alloc_ent(&ent);
    if (rc) {
      nasd_odc_shutdown_cache(NULL);
      return(NASD_NO_MEM);
    }

    rc = nasd_odc_init_ent(ent, NASD_ODC_T_FREE, 1);
    if (rc) {
      nasd_odc_free_ent(ent);
      nasd_odc_shutdown_cache(NULL);
      return(rc);
    }

    NASD_ODC_Q_INS_NOLOCK(&nasd_odc_unusedq,ent,lnext,lprev);
  }

  nasd_printf("DRIVE: allocated cache\n");

  nrc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_odc_shutdown_cache, NULL);
  if (nrc) {
    nasd_odc_shutdown_cache(NULL);
    return(nrc);
  }

  rc = nasd_odc_dirtysys_init();
  if (rc) {
    nasd_printf("ERROR (%s:%d): cannot init dirty sys, rc=0x%x (%s)\n",
      __FILE__, __LINE__, rc, nasd_error_string(rc));
    return(rc);
  }

#if MJU_DEBUG
  nasd_printf("nasd_cache_init() done\n");
#endif /* MJU_DEBUG */

  return(NASD_SUCCESS);
}

/*
 * Call with ent lock held.
 *
 * "real" version is entirely macroized in nasd_cache.h
 */
void
_nasd_odc_wait_not_busy(
  nasd_odc_ent_t  *ent)
{
  nasd_status_t raise_rc;

  raise_rc = NASD_FAIL;
  while(ent->data_flags&NASD_CD_BUSY) {
    if (raise_rc != NASD_SUCCESS)
      raise_rc = nasd_od_io_try_raise_pri(ent, NASD_IO_PRI_MED);
    NASD_WAIT_COND(ent->cond,ent->lock);
  }
}

/*
 * Call with ent lock held.
 *
 * "real" version is entirely macroized in nasd_cache.h
 */
void
_nasd_odc_wait_not_busy_invalid(
  nasd_odc_ent_t  *ent)
{
  nasd_status_t raise_rc;

  raise_rc = NASD_FAIL;
  while(ent->data_flags&(NASD_CD_BUSY|NASD_CD_INVALID)) {
    if (raise_rc != NASD_SUCCESS)
      raise_rc = nasd_od_io_try_raise_pri(ent, NASD_IO_PRI_MED);
    NASD_WAIT_COND(ent->cond,ent->lock);
  }
}

void
nasd_odc_shutdown_cache(
  void  *ignored_arg)
{
  nasd_odc_ent_t *ent;
  int i;

  /*
   * The dirty system has already flushed all the
   * dirty pages, and made them clean. Likewise,
   * we've already registered functions that will
   * deallocate all the page ents. Our job is to
   * hunt down all the outstanding page ent structures
   * and deallocate them.
   */

  /*
   * The dirty system has already flushed all the
   * dirty pages, and made them clean. Here, we hunt
   * down all outstanding cache blocks and deallocate
   * them appropriately.
   */

  for(i=0;i<NASD_ODC_NLRUS;i++) {
    while(NASD_ODC_Q_SIZE(&nasd_odc_lru[i])) {
      NASD_ODC_Q_DEQ_TAIL_NOLOCK(&nasd_odc_lru[i],ent,lnext,lprev);
      nasd_odc_free_ent(ent);
    }
    NASD_ASSERT(nasd_odc_lru[i].head.lprev == &nasd_odc_lru[i].head);
    NASD_ASSERT(nasd_odc_lru[i].head.lnext == &nasd_odc_lru[i].head);
  }

  while(NASD_ODC_Q_SIZE(&nasd_odc_unusedq)) {
    NASD_ODC_Q_DEQ_TAIL_NOLOCK(&nasd_odc_unusedq,ent,lnext,lprev);
    nasd_odc_free_ent(ent);
  }
  NASD_ASSERT(nasd_odc_unusedq.head.lprev == &nasd_odc_unusedq.head);
  NASD_ASSERT(nasd_odc_unusedq.head.lnext == &nasd_odc_unusedq.head);

  while(NASD_ODC_Q_SIZE(&nasd_odc_wireq)) {
    NASD_ODC_Q_DEQ_TAIL_NOLOCK(&nasd_odc_wireq,ent,wnext,wprev);
    nasd_odc_free_ent(ent);
  }
  NASD_ASSERT(nasd_odc_wireq.head.lprev == &nasd_odc_wireq.head);
  NASD_ASSERT(nasd_odc_wireq.head.lnext == &nasd_odc_wireq.head);
}

static void
clean_ent(
  void  *arg)
{
  nasd_odc_ent_t *ent;

  ent = (nasd_odc_ent_t *)arg;
  nasd_odc_uninit_ent(ent);
}

static nasd_status_t
init_ent(
  void  *arg)
{
  nasd_odc_ent_t *ent;
  nasd_status_t rc;

  ent = (nasd_odc_ent_t *)arg;
  rc = nasd_odc_init_ent(ent, NASD_ODC_T_FREE, 0);

  return(rc);
}

void
nasd_odc_ent_destroy(
  void  *arg)
{
  NASD_FREELIST_DESTROY_CLEAN(nasd_odc_ent_freelist,lnext,
    (nasd_odc_ent_t *), clean_ent);
}

nasd_status_t
nasd_odc_alloc_ent(
  nasd_odc_ent_t  **entp)
{
  nasd_odc_ent_t *ent;

  NASD_FREELIST_GET_INIT(nasd_odc_ent_freelist,ent,lnext,
    (nasd_odc_ent_t *),init_ent);
  if (ent == NULL)
    return(NASD_NO_MEM);
  *entp = ent;
  return(NASD_SUCCESS);
}

void
nasd_odc_free_ent(
  nasd_odc_ent_t  *ent)
{
  nasd_odc_uninit_ent(ent);
  NASD_FREELIST_FREE_CLEAN(nasd_odc_ent_freelist,ent,lnext,clean_ent);
}

/*
 * Temporarily release empty entry to general-use pool.
 * ("Empty" in that it has no associated page.)
 */
void
nasd_odc_put_ent(
  nasd_odc_ent_t  *ent)
{
  NASD_FREELIST_FREE_CLEAN(nasd_odc_ent_freelist,ent,lnext,clean_ent);
}

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