/*
 * nasd_dirty.c
 *
 * Manage dirty blocks
 *
 * 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_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_cache.h>

#define NASD_DEBUG_DIRTY_SHUTDOWN 0

#define NASD_MAX_FREE_FLUSH  16
#define NASD_FLUSH_INC        4
#define NASD_FLUSH_INITIAL    4

static nasd_freelist_t *nasd_flush_freelist;

NASD_DECLARE_COND(nasd_odc_dirty_go)
NASD_DECLARE_COND(nasd_odc_dirty_revolve)
NASD_DECLARE_COND(nasd_odc_dirty_cleaned)
NASD_DECLARE_MUTEX(nasd_odc_dirtyq_lock)
int nasd_odc_dirtycnt;
nasd_odc_ent_t nasd_odc_dirtyq;

nasd_threadgroup_t nasd_odc_dirtythread_group;
int nasd_odc_dirtythread_force = 0;

nasd_odc_flush_t *nasd_odc_dirty_cur_global_flush = NULL;
nasd_uint64 nasd_odc_dirty_thread_revolve = 0;
nasd_uint64 nasd_odc_dirty_thread_revolve2 = 0;
nasd_uint64 nasd_odc_dirty_thread_lastchange = 0;

nasd_thread_t nasd_odc_dirty_thread;

void
nasd_odc_dirty_kill_dirtythreadgroup(
  void  *ignored)
{
  nasd_status_t rc;

  rc = nasd_destroy_threadgroup(&nasd_odc_dirtythread_group);
  if (rc) {
    nasd_printf(
      "DRIVE WARNING: got 0x%x (%s) destroying "
      "nasd_odc_dirtythread_group\n",
      rc, nasd_error_string(rc));
  }
}

static nasd_status_t
init_flush(
  nasd_odc_flush_t  *f)
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&f->lock);
  if (rc)
    return(rc);
  rc = nasd_cond_init(&f->cond);
  if (rc) {
    nasd_mutex_destroy(&f->lock);
    return(rc);
  }
  return(NASD_SUCCESS);
}

static void
clean_flush(
  nasd_odc_flush_t  *f)
{
  nasd_mutex_destroy(&f->lock);
  nasd_cond_destroy(&f->cond);
}

/*
 * Cache has become clean
 */
void
nasd_odc_dirty_cleaned_done()
{
  NASD_BROADCAST_COND(nasd_odc_dirty_cleaned);
  /* ??? mark clean somewhere for fast recover? */
}

/*
 * Automatic deallocator for flushes.
 */
void
nasd_odc_dirty_killflush(
  void  *arg)
{
  NASD_FREELIST_DESTROY_CLEAN(nasd_flush_freelist,next,
    (nasd_odc_flush_t *), clean_flush);
}

static void
nasd_odc_kill_dirtythread(
  void  *arg)
{
  nasd_odc_ent_t eh, *ent, *next, *el;
  int dirty_found;

  NASD_THREADGROUP_INDICATE_SHUTDOWN(&nasd_odc_dirtythread_group);
  NASD_BROADCAST_COND(nasd_odc_dirty_go);
  NASD_THREADGROUP_WAIT_STOP(&nasd_odc_dirtythread_group);

  /*
   * Write out remaining dirty blocks here
   */
  NASD_ODC_LRU_LOCK();
  eh.inext = eh.iprev = &eh;
  if (nasd_odc_dirtycnt == 0) {
    NASD_ASSERT(nasd_odc_dirtyq.dnext == &nasd_odc_dirtyq);
    NASD_ASSERT(nasd_odc_dirtyq.dprev == &nasd_odc_dirtyq);
  }
  else {
    nasd_printf("%d blocks still dirty, flushing\n", nasd_odc_dirtycnt);
    /*
     * Write out dirty blocks.
     */
    NASD_DIRTY_LOCK();
    dirty_found = 0;
    for(ent=nasd_odc_dirtyq.dnext;ent!=&nasd_odc_dirtyq;ent=next) {
      next = ent->dnext;
      dirty_found++;
      ent->iocb_arg = NULL;
      ent->iocb = nasd_odc_dirty_cb;
      ent->dprev->dnext = ent->dnext;
      ent->dnext->dprev = ent->dprev;
      ent->dprev = ent->dnext = NULL;
      NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
      NASD_ODC_LOCK_BLOCK(ent);
      nasd_odc_wait_not_busy_invalid(ent);
      ent->data_flags |= NASD_CD_BUSY;
      NASD_ODC_UNLOCK_BLOCK(ent);
      ent->dirty_flags &= ~NASD_CR_DIRTY_Q;
      ent->dirty_flags |= NASD_CR_DIRTYW_Q;
      ent->iprev = &eh;
      ent->inext = eh.inext;
      ent->iprev->inext = ent;
      ent->inext->iprev = ent;
    }
    NASD_DIRTY_UNLOCK();
    nasd_printf("Prepped %d additional blocks for writing\n", dirty_found);
  }
  NASD_ODC_LRU_UNLOCK();
  if (eh.inext != &eh) {
    /*
     * eh is an unsorted list of the still-dirty blocks
     */
    eh.iprev->inext = NULL;
    el = eh.inext;
    el->iprev = NULL;
    nasd_printf("Issue I/Os\n");
    nasd_od_io_enq(el, NASD_U_WRITE, NASD_IO_PRI_MED);
    nasd_printf("I/Os issued\n");
  }
  NASD_DIRTY_LOCK();
  while(nasd_odc_dirtycnt) {
    NASD_WAIT_COND(nasd_odc_dirty_cleaned,nasd_odc_dirtyq_lock);
  }
  NASD_DIRTY_UNLOCK();
  if (nasd_odc_dirty_cur_global_flush) {
    nasd_odc_flushc_free(nasd_odc_dirty_cur_global_flush);
    nasd_odc_dirty_cur_global_flush = NULL;
  }
#if NASD_DRIVE_SHUTDOWN_DEBUG > 0
  nasd_printf("DRIVE: no more dirty blocks\n");
#endif /* NASD_DRIVE_SHUTDOWN_DEBUG > 0 */
}

nasd_status_t
nasd_odc_dirtysys_init()
{
  nasd_status_t rc;

  nasd_odc_dirty_cur_global_flush = NULL;
  nasd_odc_dirty_thread_revolve = 1;
  nasd_odc_dirty_thread_revolve2 = 1;

  rc = nasd_cond_init(&nasd_odc_dirty_go);
  if (rc)
    return(rc);
  rc = nasd_shutdown_cond(nasd_odc_shutdown, &nasd_odc_dirty_go);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&nasd_odc_dirty_revolve);
  if (rc)
    return(rc);
  rc = nasd_shutdown_cond(nasd_odc_shutdown, &nasd_odc_dirty_revolve);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&nasd_odc_dirty_cleaned);
  if (rc)
    return(rc);
  rc = nasd_shutdown_cond(nasd_odc_shutdown, &nasd_odc_dirty_cleaned);
  if (rc) {
    return(rc);
  }

  rc = nasd_mutex_init(&nasd_odc_dirtyq_lock);
  if (rc)
    return(rc);
  rc = nasd_shutdown_mutex(nasd_odc_shutdown, &nasd_odc_dirtyq_lock);
  if (rc) {
    return(rc);
  }

  nasd_odc_dirtyq.dnext = nasd_odc_dirtyq.dprev = &nasd_odc_dirtyq;

  nasd_odc_dirtycnt = 0;

  NASD_FREELIST_CREATE(nasd_flush_freelist, NASD_MAX_FREE_FLUSH,
    NASD_FLUSH_INC, sizeof(nasd_odc_flush_t));
  if (nasd_flush_freelist == NULL) {
    return(NASD_NO_MEM);
  }
  NASD_FREELIST_PRIME_INIT(nasd_flush_freelist, NASD_FLUSH_INITIAL,next,
    (nasd_odc_flush_t *),init_flush);
  rc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_odc_dirty_killflush, NULL);
  if (rc) {
    nasd_odc_dirty_killflush(NULL);
    return(rc);
  }

  rc = nasd_init_threadgroup(&nasd_odc_dirtythread_group);
  if (rc) {
    return(rc);
  }
  rc = nasd_shutdown_proc(nasd_odc_shutdown,
    nasd_odc_dirty_kill_dirtythreadgroup, NULL);
  if (rc) {
    nasd_odc_dirty_kill_dirtythreadgroup(NULL);
    return(rc);
  }

  rc = nasd_thread_create_w_name(&nasd_odc_dirty_thread, nasd_odc_dirty_loop,
    NULL, "nasd_odc_dirty_thread");
  if (rc)
    return(rc);
  NASD_THREADGROUP_STARTED(&nasd_odc_dirtythread_group);
  NASD_THREADGROUP_WAIT_START(&nasd_odc_dirtythread_group);

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

  return(NASD_SUCCESS);
}

/*
 * Called when a dirty block is being ejected from the
 * cache _without_ being written out (for instance, if
 * the object is deleted). Assumes caller holds dirty lock.
 */
void
nasd_odc_dirty_eject(
  nasd_odc_ent_t  *ent)
{
  NASD_ASSERT(ent->refcnt == 0);
  NASD_ASSERT(ent->dt_flushcp == NULL);
  NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
  /* caller should have waited for quiescence */
  NASD_ASSERT(!(ent->data_flags&NASD_CD_BUSY));
  /* can only be in dirty write queue if busy */
  NASD_ASSERT(!(ent->dirty_flags&NASD_CR_DIRTYW_Q));
  nasd_odc_state->nvstate->dirty_counts[ent->type]--;
  nasd_odc_dirtycnt--;
  if (nasd_odc_dirtycnt == 0) {
    nasd_odc_dirty_cleaned_done();
  }
  if (ent->dirty_flags&NASD_CR_DIRTY_Q) {
    NASD_ODC_Q_DEQ_NOLOCK(ent,dnext,dprev);
  }
  ent->dirty_flags &= ~(NASD_CR_DIRTY|NASD_CR_DIRTY_Q);;

  if (ent->flushcp) {
    NASD_LOCK_MUTEX(ent->flushcp->lock);
    ent->flushcp->counter--;
    if (ent->flushcp->counter == 0) {
      NASD_BROADCAST_COND(ent->flushcp->cond);
    }
    NASD_UNLOCK_MUTEX(ent->flushcp->lock);
    ent->flushcp = NULL;
  }
}

/*
 * Called when a dirty block is flushed.
 */
void
nasd_odc_dirty_donewrite(
  nasd_odc_ent_t  *ent)
{
  int nr;

  NASD_DIRTY_LOCK();
  if (ent->dt_flushcp) {
    NASD_LOCK_MUTEX(ent->dt_flushcp->lock);
    ent->dt_flushcp->counter--;
    ent->dt_flushcp->refcnt--;
    nr = ent->dt_flushcp->refcnt;
    if ((ent->dt_flushcp->counter == 0) && nr) {
      NASD_BROADCAST_COND(ent->dt_flushcp->cond);
    }
    NASD_UNLOCK_MUTEX(ent->dt_flushcp->lock);
    if (nr == 0) {
      if (ent->dt_flushcp != nasd_odc_dirty_cur_global_flush) {
        nasd_odc_flushc_free(ent->dt_flushcp);
      }
    }
    ent->dt_flushcp = NULL;
  }
  ent->dirty_flags &= ~NASD_CR_DIRTYW_Q;
  ent->dirty_flags &= ~NASD_CR_DIRTY;
  nasd_odc_state->nvstate->dirty_counts[ent->type]--;
  NASD_DIRTY_UNLOCK();

  NASD_ODC_LOCK_BLOCK(ent);
  ent->data_flags &= ~NASD_CD_BUSY;
  NASD_ODC_UNLOCK_BLOCK(ent);
  NASD_BROADCAST_COND(ent->cond);
  if (ent->flushcp) {
    /* do inside dirty lock */
    NASD_LOCK_MUTEX(ent->flushcp->lock);
    ent->flushcp->counter--;
    if (ent->flushcp->counter == 0) {
      NASD_BROADCAST_COND(ent->flushcp->cond);
    }
    NASD_UNLOCK_MUTEX(ent->flushcp->lock);
    ent->flushcp = NULL;
  }

  NASD_DIRTY_LOCK();
  /* defer this to here for correct shutdown */
  nasd_odc_dirtycnt--;
  if (nasd_odc_dirtycnt == 0) {
    nasd_odc_dirty_cleaned_done();
  }
  NASD_DIRTY_UNLOCK();
}

/*
 * Callback when a write on a dirty block has
 * completed. Clear the dirty and busy flags,
 * and release the block (we took a ref in
 * the dirty loop).
 */
void
nasd_odc_dirty_cb(
  nasd_odc_ent_t  *ent,
  void            *arg)
{
  nasd_odc_dirty_donewrite(ent);
  nasd_odc_block_release(ent);
}

/*
 * Dirty loop- wait to be told to write out dirty
 * blocks, wake up, write them out, sleep again
 */
void
nasd_odc_dirty_loop(
  nasd_threadarg_t  ignored)
{
  nasd_odc_ent_t eh_ref, eh, *ent, *next, *e, *el;
  int opd;

  bzero((char *)&eh, sizeof(eh));
  bzero((char *)&eh_ref, sizeof(eh_ref));

  NASD_THREADGROUP_RUNNING(&nasd_odc_dirtythread_group);

#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
  {
    nasd_thread_id_t self = nasd_thread_self();
    nasd_printf("nasd_odc_dirty_loop running: threadid %" NASD_THREAD_ID_FMT "\n",
      self);
  }
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */

  while(1) {
    /* wait to be kicked */
    NASD_ODC_LRU_LOCK();
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
    nasd_printf("dirty_loop got ODC_LRU lock\n");
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */
    while ((nasd_odc_dirtythread_force == 0)
      && ((nasd_odc_dirtycnt == 0)
        || (nasd_odc_dirty_thread_revolve > nasd_odc_dirty_thread_lastchange))
      && (!NASD_THREADGROUP_SHUTDOWNP(&nasd_odc_dirtythread_group)))
    {
      if (nasd_odc_dirtycnt == 0) {
        NASD_BROADCAST_COND(nasd_odc_dirty_cleaned);
      }
      nasd_odc_dirty_thread_revolve2++;
      NASD_BROADCAST_COND(nasd_odc_dirty_revolve);
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
      nasd_printf("dirty_loop waiting\n");
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */
      NASD_WAIT_COND(nasd_odc_dirty_go,nasd_odc_lru_mutex);
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
      nasd_printf("dirty_loop kicked\n");
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */
    }
    if (NASD_THREADGROUP_SHUTDOWNP(&nasd_odc_dirtythread_group)) {
      NASD_ODC_LRU_UNLOCK();
      goto done;
    }
    NASD_DIRTY_LOCK();
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
    nasd_printf("dirty_loop got dirty lock dirtycnt %d\n", nasd_odc_dirtycnt);
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */
    if (nasd_odc_dirty_cur_global_flush == NULL) {
      nasd_odc_dirty_cur_global_flush = nasd_odc_flushc_get();
      if (nasd_odc_dirty_cur_global_flush == NULL) {
        NASD_PANIC();
      }
      nasd_odc_dirty_cur_global_flush->refcnt = 0;
      nasd_odc_dirty_cur_global_flush->counter = 0;
    }
    NASD_LOCK_MUTEX(nasd_odc_dirty_cur_global_flush->lock);
    /*
     * Pull blocks off dirty queue, generate sorted list by block number
     * Mark blocks busy as we do so, and take a ref on them.
     */
    opd = 0;
    eh.inext = eh.iprev = &eh;
    eh_ref.inext = eh_ref.iprev = &eh_ref;
    for(ent=nasd_odc_dirtyq.dnext;ent!=&nasd_odc_dirtyq;ent=next) {
      opd++;
      nasd_odc_dirty_cur_global_flush->refcnt++;
      nasd_odc_dirty_cur_global_flush->counter++;
      next = ent->dnext;
      ent->dt_flushcp = nasd_odc_dirty_cur_global_flush;
      ent->iocb_arg = NULL;
      ent->iocb = nasd_odc_dirty_cb;
      if (ent->type == NASD_ODC_T_REFCNT) {
        if (ent->real_sectno > eh_ref.iprev->real_sectno) {
          /* optimized insert-at-end case */
          e = &eh_ref;
        }
        else {
          for(e=eh_ref.inext;e!=&eh_ref;e=e->inext) {
            if (e->real_sectno > ent->real_sectno)
              break;
          }
        }
      }
      else {
        if (ent->real_sectno > eh.iprev->real_sectno) {
          /* optimized insert-at-end case */
          e = &eh;
        }
        else {
          for(e=eh.inext;e!=&eh;e=e->inext) {
            if (e->real_sectno > ent->real_sectno)
              break;
          }
        }
      }
      ent->dnext = ent->dprev = NULL;
      NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
      NASD_ODC_LOCK_BLOCK(ent);
      nasd_odc_wait_not_busy_invalid(ent);
      ent->data_flags |= NASD_CD_BUSY;
      NASD_ODC_UNLOCK_BLOCK(ent);
      /* insert ent before e */
      ent->iprev = e->iprev;
      ent->inext = e;
      ent->iprev->inext = ent;
      ent->inext->iprev = ent;
      /* update dirty flags */
      ent->dirty_flags &= ~NASD_CR_DIRTY_Q;
      ent->dirty_flags |= NASD_CR_DIRTYW_Q;
    }
    NASD_UNLOCK_MUTEX(nasd_odc_dirty_cur_global_flush->lock);
    nasd_odc_dirtyq.dnext = nasd_odc_dirtyq.dprev = &nasd_odc_dirtyq;
    nasd_odc_dirty_thread_revolve++;
    nasd_odc_dirty_thread_revolve2++;
    NASD_BROADCAST_COND(nasd_odc_dirty_revolve);
    NASD_DIRTY_UNLOCK();
    NASD_ODC_LRU_UNLOCK();
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
    nasd_printf("dirty_loop released dirty & ODC_LRU locks opd %d\n", opd);
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */

    /*
     * eh_ref is now a list of the currently-dirty refcnt blocks,
     * sorted by block number- write 'em out
     */
    if (eh_ref.inext != &eh_ref) {
      eh_ref.iprev->inext = NULL;
      el = eh_ref.inext;
      el->iprev = NULL;
      nasd_od_io_enq_sorted(el, NASD_U_WRITE, NASD_IO_PRI_LO);
    }
    else {
      NASD_ASSERT(eh_ref.iprev == &eh_ref);
    }

    /*
     * eh is now a list of the currently-dirty blocks,
     * sorted by block number- write 'em out
     */
    if (eh.inext != &eh) {
      eh.iprev->inext = NULL;
      el = eh.inext;
      el->iprev = NULL;
      nasd_od_io_enq_sorted(el, NASD_U_WRITE, NASD_IO_PRI_MED);
    }
    else {
      NASD_ASSERT(eh.iprev == &eh);
    }
  }
done:
#if NASD_DEBUG_DIRTY_SHUTDOWN > 0
  nasd_printf("dirty_loop shutting down\n");
#endif /* NASD_DEBUG_DIRTY_SHUTDOWN > 0 */
  NASD_THREADGROUP_DONE(&nasd_odc_dirtythread_group);
  NASD_THREAD_KILL_SELF();
}

/*
 * Allocate a flush controller.
 */
nasd_odc_flush_t *
nasd_odc_flushc_get()
{
  nasd_odc_flush_t *fl;

  NASD_FREELIST_GET_INIT(nasd_flush_freelist,fl,next,
    (nasd_odc_flush_t *),init_flush);
  return(fl);
}

/*
 * Deallocate a flush controller.
 */
void
nasd_odc_flushc_free(
  nasd_odc_flush_t  *fl)
{
  NASD_FREELIST_FREE_CLEAN(nasd_flush_freelist,fl,next,clean_flush);
}

/*
 * Flush a particular object to disk.
 */
nasd_status_t
nasd_odc_flush_obj(
  nasd_odc_ent_t  *node_ent)
{
  nasd_odc_ent_t eh, *ent, *next, *e, *el;
  nasd_odc_flush_t *fl, *pre_fl;

  NASD_ODC_LRU_LOCK();
  NASD_DIRTY_LOCK();
  fl = node_ent->nd_flushcp;
  pre_fl = fl;
  if (fl) {
    NASD_LOCK_MUTEX(fl->lock);
    fl->refcnt++;
    NASD_UNLOCK_MUTEX(fl->lock);
    NASD_DIRTY_UNLOCK();
    NASD_ODC_LRU_UNLOCK();
    goto flush_wait;
  }
  fl = nasd_odc_flushc_get();
  if (fl == NULL) {
    NASD_DIRTY_UNLOCK();
    NASD_ODC_LRU_UNLOCK();
    return(NASD_NO_MEM);
  }
  fl->counter = 0;
  fl->refcnt = 1;
  node_ent->nd_flushcp = fl;
  NASD_LOCK_MUTEX(fl->lock);

  if (nasd_odc_dirty_cur_global_flush == NULL) {
    nasd_odc_dirty_cur_global_flush = nasd_odc_flushc_get();
    if (nasd_odc_dirty_cur_global_flush == NULL) {
      NASD_PANIC();
    }
    nasd_odc_dirty_cur_global_flush->refcnt = 0;
    nasd_odc_dirty_cur_global_flush->counter = 0;
  }
  NASD_LOCK_MUTEX(nasd_odc_dirty_cur_global_flush->lock);

  bzero((char *)&eh, sizeof(eh));
  eh.inext = eh.iprev = &eh;
  for(ent=nasd_odc_dirtyq.dnext;ent!=&nasd_odc_dirtyq;ent=next) {
    next = ent->dnext;
    if ((ent != node_ent) && (ent->node_ent != node_ent))
      continue;
    NASD_ODC_Q_DEQ_NOLOCK(ent,dnext,dprev);
    nasd_odc_dirty_cur_global_flush->refcnt++;
    nasd_odc_dirty_cur_global_flush->counter++;
    ent->dt_flushcp = nasd_odc_dirty_cur_global_flush;
    ent->iocb_arg = NULL;
    ent->iocb = nasd_odc_dirty_cb;
    if (ent->real_sectno > eh.iprev->real_sectno) {
      /* optimized insert-at-end case */
      e = &eh;
    }
    else {
      for(e=eh.inext;e!=&eh;e=e->inext) {
        if (e->real_sectno > ent->real_sectno)
          break;
      }
    }
    ent->dnext = ent->dprev = NULL;
    NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
    ent->flushcp = fl;
    fl->counter++;
    NASD_ODC_LOCK_BLOCK(ent);
    nasd_odc_wait_not_busy_invalid(ent);
    ent->data_flags |= NASD_CD_BUSY;
    NASD_ODC_UNLOCK_BLOCK(ent);
    /* insert ent before e */
    ent->iprev = e->iprev;
    ent->inext = e;
    ent->iprev->inext = ent;
    ent->inext->iprev = ent;
    /* update dirty flags */
    ent->dirty_flags &= ~NASD_CR_DIRTY_Q;
    ent->dirty_flags |= NASD_CR_DIRTYW_Q;
  }
  /*
   * Now check the node ent block itself
   */
  NASD_ODC_LOCK_BLOCK(node_ent);
  nasd_odc_wait_not_busy(node_ent);
  if (node_ent->dirty_flags&NASD_CR_DIRTY) {
    node_ent->data_flags |= NASD_CD_BUSY;
    nasd_odc_dirty_cur_global_flush->refcnt++;
    nasd_odc_dirty_cur_global_flush->counter++;
    node_ent->dt_flushcp = nasd_odc_dirty_cur_global_flush;
    node_ent->iocb_arg = NULL;
    node_ent->iocb = nasd_odc_dirty_cb;
    if (node_ent->real_sectno > eh.iprev->real_sectno) {
      /* optimized insert-at-end case */
      e = &eh;
    }
    else {
      for(e=eh.inext;e!=&eh;e=e->inext) {
        if (e->real_sectno > node_ent->real_sectno)
          break;
      }
    }
    /* insert node_ent before e */
    node_ent->iprev = e->iprev;
    node_ent->inext = e;
    node_ent->iprev->inext = node_ent;
    node_ent->inext->iprev = node_ent;
    node_ent->dnext = node_ent->dprev = NULL;
    NASD_ASSERT(node_ent->dirty_flags&NASD_CR_DIRTY);
    node_ent->flushcp = fl;
    fl->counter++;
    node_ent->dirty_flags &= ~NASD_CR_DIRTY_Q;
    node_ent->dirty_flags |= NASD_CR_DIRTYW_Q;
    node_ent->refcnt++; /* the extra ref for "being in the dirty queue" */
  }
  NASD_ODC_UNLOCK_BLOCK(node_ent);
  NASD_UNLOCK_MUTEX(nasd_odc_dirty_cur_global_flush->lock);
  NASD_UNLOCK_MUTEX(fl->lock);
  NASD_DIRTY_UNLOCK();
  NASD_ODC_LRU_UNLOCK();
  /*
   * eh is now a list of the currently-dirty blocks,
   * sorted by block number- write 'em out
   */
  if (eh.inext != &eh) {
    eh.iprev->inext = NULL;
    el = eh.inext;
    el->iprev = NULL;
    nasd_od_io_enq_sorted(el, NASD_U_WRITE, NASD_IO_PRI_HI);
  }
  else {
    NASD_ASSERT(eh.iprev == &eh);
  }

flush_wait:
  NASD_LOCK_MUTEX(fl->lock);
  while(fl->counter) {
    NASD_WAIT_COND(fl->cond,fl->lock);
  }
  NASD_UNLOCK_MUTEX(fl->lock);

  NASD_DIRTY_LOCK();
  fl->refcnt--;
  if (fl->refcnt == 0) {
    node_ent->nd_flushcp = NULL;
    nasd_odc_flushc_free(fl);
  }
  NASD_DIRTY_UNLOCK();

  return(NASD_SUCCESS);
}

/*
 * Tell the system that it should start writing out dirty blocks
 */
nasd_status_t
nasd_odc_dirty_kick()
{
  nasd_odc_dirty_thread_lastchange = nasd_odc_dirty_thread_revolve;
  NASD_SIGNAL_COND(nasd_odc_dirty_go);
  return(NASD_SUCCESS);
}

/*
 * Actually write out all dirty blocks, block until any blocks
 * currently in the dirty queue are clean. There's sort of a
 * weakness here- dirty blocks not currently in the queue aren't
 * currently handled. They should be. Things to ponder.
 */
nasd_status_t
nasd_odc_flush_dirty(
  int  force_sync)
{
  nasd_odc_flush_t *in_progress, *now;
  nasd_uint64 ser, ser2;
  nasd_status_t rc;
  int d, nr;

  d = 0;

  NASD_DIRTY_LOCK();

  ser = nasd_odc_dirty_thread_revolve;
  ser2 = nasd_odc_dirty_thread_revolve2;

  if (nasd_odc_dirtycnt) {
    d = 1;
  }
  else {
    in_progress = NULL;
    now = NULL;
    goto flushes_set;
  }

  in_progress = nasd_odc_dirty_cur_global_flush;
  nasd_odc_dirty_cur_global_flush = NULL;
  if (in_progress) {
    NASD_LOCK_MUTEX(in_progress->lock);
    in_progress->refcnt++;
    NASD_UNLOCK_MUTEX(in_progress->lock);
  }

  now = nasd_odc_flushc_get();
  if (now == NULL) {
    NASD_PANIC();
  }
  now->refcnt = 1;
  now->counter = 0;
  nasd_odc_dirty_cur_global_flush = now;
  NASD_DIRTY_UNLOCK();

  nasd_odc_dirty_kick();

  NASD_DIRTY_LOCK();
  while((ser == nasd_odc_dirty_thread_revolve)
    && (ser2 == nasd_odc_dirty_thread_revolve2))
  {
    NASD_WAIT_COND(nasd_odc_dirty_revolve,nasd_odc_dirtyq_lock);
  }
  nasd_odc_dirty_cur_global_flush = NULL;

flushes_set:
  NASD_DIRTY_UNLOCK();

  if (in_progress) {
    NASD_LOCK_MUTEX(in_progress->lock);
    while(in_progress->counter) {
      NASD_WAIT_COND(in_progress->cond,in_progress->lock);
    }
    in_progress->refcnt--;
    nr = in_progress->refcnt;
    NASD_UNLOCK_MUTEX(in_progress->lock);
    if (nr == 0) {
      nasd_odc_flushc_free(in_progress);
    }
  }

  if (now) {
    NASD_LOCK_MUTEX(now->lock);
    while(now->counter) {
      NASD_WAIT_COND(now->cond,now->lock);
    }
    now->refcnt--;
    nr = now->refcnt;
    NASD_UNLOCK_MUTEX(now->lock);
    if (nr == 0) {
      nasd_odc_flushc_free(now);
    }
  }

  if (d || force_sync) {
    nasd_od_update_nvclock();
    rc = nasd_od_write_diskstate(force_sync);
  }
  else {
    rc = NASD_SUCCESS;
  }

  return(rc);
}

/*
 * Mark the given ent as dirty.
 * Assumes caller holds a ref and a lock on the entry.
 * Don't put in list here- caller still holding ref!
 */
nasd_status_t
nasd_odc_dirty_ent(
  nasd_odc_ent_t  *ent)
{
  NASD_ASSERT(ent->refcnt > 0);

  NASD_DIRTY_LOCK();
  if (ent->dirty_flags&NASD_CR_DIRTY)
    goto done;
  ent->dirty_flags |= NASD_CR_DIRTY;
  nasd_odc_state->nvstate->dirty_counts[ent->type]++;
  nasd_odc_dirtycnt++;
  if (nasd_odc_dirtycnt > nasd_odc_dirty_autokick) {
    NASD_ODC_CSINC(dirty_threshold_kicks);
    nasd_odc_dirty_kick();
  }
  nasd_odc_dirty_thread_lastchange = nasd_odc_dirty_thread_revolve;
done:
  NASD_DIRTY_UNLOCK();

  return(NASD_SUCCESS);
}

/*
 * Actually put an ent on the dirty list
 * Assumes caller holds block, dirty locks
 */
void
nasd_odc_dirty_ins_nolock(
  nasd_odc_ent_t  *ent)
{
  NASD_ASSERT(ent->refcnt == 1);
  NASD_ASSERT(!(ent->dirty_flags&NASD_CR_DIRTY_Q));
  ent->dirty_flags |= NASD_CR_DIRTY_Q;
  ent->dnext = &nasd_odc_dirtyq;
  ent->dprev = nasd_odc_dirtyq.dprev;
  ent->dnext->dprev = ent;
  ent->dprev->dnext = ent;
}

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