/*
 * nasd_ioqueue.c
 *
 * Policy-independent disk-queueing code for drive
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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>
#include <nasd/nasd_common.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_drive_io.h>
#include <nasd/nasd_ioqueue.h>

nasd_ioqueue_switch_t
nasd_ioqueue_switch[] = {
#if NASD_DRIVE_IOQUEUE_INCLUDE_CSCAN > 0
  { 'c',
    "CSCAN",
    nasd_ioqueue_cscan_init,
    nasd_ioqueue_cscan_enq_sorted,
    nasd_ioqueue_cscan_enq,
    nasd_ioqueue_cscan_deq_next,
    nasd_ioqueue_cscan_deq_ent,
    nasd_ioqueue_cscan_try_raise_pri,
    nasd_ioqueue_cscan_sync_launched},
#endif /* NASD_DRIVE_IOQUEUE_INCLUDE_CSCAN > 0 */

#if NASD_DRIVE_IOQUEUE_INCLUDE_FIFO > 0
  { 'f',
    "FIFO",
    nasd_ioqueue_fifo_init,
    nasd_ioqueue_fifo_enq_sorted,
    nasd_ioqueue_fifo_enq,
    nasd_ioqueue_fifo_deq_next,
    nasd_ioqueue_fifo_deq_ent,
    nasd_ioqueue_fifo_try_raise_pri,
    nasd_ioqueue_fifo_sync_launched},
#endif /* NASD_DRIVE_IOQUEUE_INCLUDE_FIFO > 0 */
};

int nasd_od_nioq_types = 
  (sizeof(nasd_ioqueue_switch)/sizeof(nasd_ioqueue_switch_t));

nasd_ioqueue_switch_t *nasd_ioq_cur = NULL;

nasd_ctrl_ioqueue_stat_t nasd_drive_ioqueue_stats;

/* initialized from config */
int nasd_od_ioq_max_outstanding;

/*
 * Dealt with by I/O module.
 */
nasd_sectno_t nasd_od_io_last_completed_sect;
int nasd_od_io_ios_outstanding;

NASD_DECLARE_MUTEX(nasd_od_io_lock)
#if NASD_IO_LOCK_DEBUG > 0
nasd_thread_id_t nasd_od_io_lock_holder;
#endif /* NASD_IO_LOCK_DEBUG > 0 */

#if NASD_IO_LOCK_DEBUG > 0

NASD_INLINE int
nasd_od_io_lock_try()
{
  int ret;

  ret = NASD_TRY_LOCK_MUTEX(nasd_od_io_lock);
  if (ret) {
    NASD_ASSERT(nasd_od_io_lock_holder == NASD_THREAD_ID_NULL);
    nasd_od_io_lock_holder = nasd_thread_self();
  }
  return(ret);
}

#endif /* NASD_IO_LOCK_DEBUG > 0 */

nasd_status_t
nasd_od_ioqueue_init(
  nasd_od_config_t  *config)
{
  nasd_status_t rc;
  int i;

  bzero((char *)&nasd_drive_ioqueue_stats, sizeof(nasd_drive_ioqueue_stats));
  nasd_drive_ioqueue_stats.ctrl_id = NASD_CTRL_DRIVE_INFO;
  nasd_drive_ioqueue_stats.max_ios_outstanding = nasd_od_ioq_max_outstanding;

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

  nasd_ioq_cur = NULL;
  for(i=0;i<nasd_od_nioq_types;i++) {
    if (nasd_ioqueue_switch[i].ioq_type == config->ioqueue_type) {
      nasd_ioq_cur = &nasd_ioqueue_switch[i];
      break;
    }
  }

  if (nasd_ioq_cur == NULL)
    return(NASD_BAD_IOQUEUE_TYPE);

  rc = nasd_ioq_cur->ioq_init(config);
  if (rc)
    return(rc);

  nasd_printf("DRIVE: using ioqueue type %s, %d outstanding\n",
    nasd_ioq_cur->ioq_name, nasd_od_ioq_max_outstanding);
  /* bzero already null-terminated this in target */
  bcopy((char *)nasd_ioq_cur->ioq_name, nasd_drive_ioqueue_stats.ioqueue_name,
    NASD_MIN(strlen(nasd_ioq_cur->ioq_name), NASD_CTRL_IOQUEUE_NAME_LEN));

  return(NASD_SUCCESS);
}

void
_nasd_od_io_enq_sorted(
  nasd_odc_ent_t  *entlist,
  int              dir,
  int              pri,
  char            *file,
  int              line)
{
  nasd_odc_ent_t *l;
  nasd_status_t rc;

  NASD_IO_ASSERT_DIR(dir);
  NASD_IO_ASSERT_PRI(pri);
  NASD_ASSERT(entlist != NULL);

  NASD_IO_LOCK();

  for(l=entlist;l;l=l->inext) {
    NASD_ASSERT(!(l->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH)));
    NASD_IO_TM_ENQ(l);
  }

  /*
   * Queue policy will launch I/O.
   */
  rc = nasd_ioq_cur->ioq_enq_sorted(entlist, dir, pri, file, line);
  if (rc) {
    NASD_PANIC();
  }

  NASD_IO_UNLOCK();
}

void
_nasd_od_io_enq(
  nasd_odc_ent_t  *entlist,
  int              dir,
  int              pri,
  char            *file,
  int              line)
{
  nasd_odc_ent_t *l;
  nasd_status_t rc;

  NASD_IO_ASSERT_DIR(dir);
  NASD_IO_ASSERT_PRI(pri);
  NASD_ASSERT(entlist != NULL);

  NASD_IO_LOCK();

  for(l=entlist;l;l=l->inext) {
    NASD_ASSERT(!(l->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH)));
    NASD_IO_TM_ENQ(l);
  }

  /*
   * Queue policy will launch I/O.
   */
  rc = nasd_ioq_cur->ioq_enq(entlist, dir, pri, file, line);
  if (rc) {
    NASD_PANIC();
  }

  NASD_IO_UNLOCK();
}

void
_nasd_od_io_deq_next(
  nasd_odc_ent_t  **entlistp,
  int               io_lock_held,
  char             *file,
  int               line)
{
  nasd_status_t rc;

  if (io_lock_held == 0) {
    NASD_IO_LOCK();
  }

  rc = nasd_ioq_cur->ioq_deq_next(entlistp, file, line);
  if (rc) {
    NASD_PANIC();
  }

  if (io_lock_held == 0) {
    NASD_IO_UNLOCK();
  }
}

/*
 * Raise the priority of ent's pending I/O to newpri,
 * iff it is currently lower. Called when ent is locked
 * already. Returns NASD_SUCCESS if able to raise
 * priority, or if it's already there. Returns NASD_FAIL
 * if we can't get the I/O lock (we can't block for it,
 * since that could lead to deadlock).
 */
nasd_status_t
nasd_od_io_try_raise_pri(
  nasd_odc_ent_t  *ent,
  int              newpri)
{
  nasd_status_t rc;

  rc = NASD_FAIL;

  if (NASD_IO_LOCK_TRY()) {
    if (ent->io_flags&NASD_CI_DISPATCH) {
      rc = NASD_SUCCESS;
      NASD_IO_INC_STAT(raise_pri_busy);
      goto done;
    }
    if (!(ent->io_flags&NASD_CI_IOQ)) {
      /* not in queue, nothing to do */
      rc = NASD_SUCCESS;
      goto done;
    }
    if (newpri >= ent->iopri) {
      NASD_IO_INC_STAT(raise_lower_pri);
      rc = NASD_SUCCESS;
      goto done;
    }
    NASD_ASSERT(ent->data_flags&NASD_CD_BUSY);

    rc = nasd_ioq_cur->ioq_try_raise_pri(ent, newpri);
    if (rc == NASD_SUCCESS) {
      NASD_IO_INC_STAT(raise_pri);
    }
done:
    NASD_IO_UNLOCK();
  }

  return(rc);
}

void
nasd_od_io_iodone(
  nasd_odc_ent_t  *ent)
{
  int need_release, iodir_out;
  nasd_io_cbfunc_t iocb_out;
  void *iocb_arg_out;

  NASD_IO_TM_DONE(ent);
  need_release = 0;
  /*
   * We can examine this state without the I/O lock,
   * because no one's allowed to change it without
   * first making the block BUSY from not-BUSY
   * (dataflags), which can only happen after it
   * goes not-BUSY with the block lock below.
   */
  iocb_out = ent->iocb;
  iodir_out = ent->iodir;
  iocb_arg_out = ent->iocb_arg;
  ent->iodir = NASD_U_NOP;
  ent->iocb = NULL;
  if (iocb_out) {
    /*
     * Those who specify callback procs are responsible
     * for signalling no-longer-busy.
     */
    iocb_out(ent, iocb_arg_out);
  }
  else {
    NASD_ODC_LOCK_BLOCK(ent);
    if (iodir_out == NASD_U_READ) {
      /* I/O was read */
      if (ent->data_flags&NASD_CD_ANONF)
        need_release = 1;
      ent->data_flags &= ~(NASD_CD_BUSY|NASD_CD_MBUSY|NASD_CD_INVALID|NASD_CD_NZ|NASD_CD_ANONF);
    }
    else {
      /* I/O was write */
      NASD_ASSERT(iodir_out == NASD_U_WRITE);
      NASD_ASSERT(!(ent->data_flags&(NASD_CD_INVALID|NASD_CD_NZ|NASD_CD_ANONF)));
      ent->data_flags &= ~(NASD_CD_BUSY|NASD_CD_MBUSY);
    }
    NASD_ODC_UNLOCK_BLOCK(ent);
    NASD_BROADCAST_COND(ent->cond);
  }
  if (need_release) {
    nasd_odc_block_release(ent);
  }
}

/*
 * Caller must hold I/O lock.
 */
void
nasd_od_io_launch_as_necessary()
{
  nasd_odc_ent_t *ent;
  nasd_status_t rc;

  while (nasd_od_io_ios_outstanding < nasd_od_ioq_max_outstanding) {
    rc = nasd_ioq_cur->ioq_deq_next(&ent, __FILE__, __LINE__);
    if (rc) {
      NASD_PANIC();
    }
    if (ent == NULL) {
      /* no more I/O in queue */
      break;
    }
    rc = nasd_od_io_launch(ent);
    if (rc) {
      NASD_PANIC();
    }
  }
}

void
nasd_od_io_flush_block(
  nasd_odc_ent_t  *ent)
{
  nasd_io_cbfunc_t iocb_out;
  void *iocb_arg_out;
  nasd_status_t rc;
  int iodir_out;

  NASD_IO_LOCK();
  NASD_ODC_LOCK_BLOCK(ent);
  NASD_ASSERT(ent->data_flags&NASD_CD_BUSY);
  NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
  if (ent->io_flags&NASD_CI_DISPATCH) {
    NASD_ASSERT(ent->iodir == NASD_U_WRITE);
    NASD_IO_UNLOCK();
    while(ent->data_flags&NASD_CD_BUSY) {
      NASD_WAIT_COND(ent->cond,ent->lock);
    }
    NASD_ODC_UNLOCK_BLOCK(ent);
    return;
  }
  if (ent->io_flags&NASD_CI_IOQ) {
    NASD_ASSERT((ent->io_flags&(NASD_CI_DISPATCH|NASD_CI_IOQ)) == NASD_CI_IOQ);
    rc = nasd_ioq_cur->ioq_deq_ent(ent, __FILE__, __LINE__);
    if (rc)
      NASD_PANIC();
    NASD_ASSERT(!(ent->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH)));
    ent->io_flags |= NASD_CI_DISPATCH;
  }
  NASD_ODC_UNLOCK_BLOCK(ent);
  NASD_IO_UNLOCK();

  nasd_od_io_sys_flush_block(ent);

  NASD_IO_LOCK();
  ent->io_flags &= ~NASD_CI_DISPATCH;
  NASD_IO_UNLOCK();

  /*
   * Callback (or donewrite) responsible for
   * clearing busy flag.
   */
  NASD_IO_TM_DONE(ent);
  /*
   * We can examine this state without the I/O lock,
   * because no one's allowed to change it without
   * first making the block BUSY from not-BUSY
   * (dataflags), which can only happen after it
   * goes not-BUSY with the block lock below.
   */
  iocb_out = ent->iocb;
  iodir_out = ent->iodir;
  iocb_arg_out = ent->iocb_arg;
  ent->iodir = NASD_U_NOP;
  ent->iocb = NULL;
  if (iocb_out) {
    iocb_out(ent, iocb_arg_out);
  }
  else {
    NASD_ASSERT(iodir_out == NASD_U_WRITE);
    NASD_ASSERT(!(ent->data_flags&(NASD_CD_INVALID|NASD_CD_NZ|NASD_CD_ANONF)));
    nasd_odc_dirty_donewrite(ent);
  }
}

void
nasd_od_io_flush_block_async(
  nasd_odc_ent_t  *ent)
{
  nasd_status_t rc;

  NASD_IO_LOCK();
  NASD_ODC_LOCK_BLOCK(ent);
  NASD_ASSERT(ent->data_flags&NASD_CD_BUSY);
  NASD_ASSERT(ent->dirty_flags&NASD_CR_DIRTY);
  if (ent->io_flags&NASD_CI_DISPATCH) {
    NASD_ASSERT(ent->iodir == NASD_U_WRITE);
    NASD_IO_UNLOCK();
    while(ent->data_flags&NASD_CD_BUSY) {
      NASD_WAIT_COND(ent->cond,ent->lock);
    }
    NASD_ODC_UNLOCK_BLOCK(ent);
    return;
  }
  if (ent->io_flags&NASD_CI_IOQ) {
    NASD_ASSERT((ent->io_flags&(NASD_CI_DISPATCH|NASD_CI_IOQ)) == NASD_CI_IOQ);
    rc = nasd_ioq_cur->ioq_deq_ent(ent, __FILE__, __LINE__);
    if (rc)
      NASD_PANIC();
    NASD_ASSERT(!(ent->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH)));
    ent->io_flags |= NASD_CI_DISPATCH;
  }
  NASD_ODC_UNLOCK_BLOCK(ent);
  NASD_IO_UNLOCK();

  nasd_od_io_sys_flush_block_async(ent);
}

void
nasd_od_io_flush_block_async_finish(
  nasd_odc_ent_t  *ent)
{
  nasd_io_cbfunc_t iocb_out;
  void *iocb_arg_out;
  int iodir_out;

  NASD_IO_LOCK();
  NASD_ASSERT(ent->io_flags&NASD_CI_DISPATCH);
  ent->io_flags &= ~NASD_CI_DISPATCH;
  NASD_IO_UNLOCK();

  /*
   * Callback (or donewrite) responsible for
   * clearing busy flag.
   */
  NASD_IO_TM_DONE(ent);
  /*
   * We can examine this state without the I/O lock,
   * because no one's allowed to change it without
   * first making the block BUSY from not-BUSY
   * (dataflags), which can only happen after it
   * goes not-BUSY with the block lock below.
   */
  iocb_out = ent->iocb;
  iodir_out = ent->iodir;
  iocb_arg_out = ent->iocb_arg;
  ent->iodir = NASD_U_NOP;
  ent->iocb = NULL;
  if (iocb_out) {
    iocb_out(ent, iocb_arg_out);
  }
  else {
    NASD_ASSERT(iodir_out == NASD_U_WRITE);
    NASD_ASSERT(!(ent->data_flags&(NASD_CD_INVALID|NASD_CD_NZ|NASD_CD_ANONF)));
    nasd_odc_dirty_donewrite(ent);
  }
}

/*
 * Caller must not hold I/O lock.
 */
void
nasd_od_io_sync_launch(
  nasd_sectno_t  last_sector)
{
#if NASD_IO_LOCK_DEBUG > 0
  NASD_ASSERT(nasd_od_io_lock_holder != nasd_thread_self());
#endif /* NASD_IO_LOCK_DEBUG > 0 */
  NASD_IO_LOCK();
  nasd_ioq_cur->ioq_sync_launched(last_sector);
  NASD_IO_UNLOCK();
}

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