/*
 * nasd_ioqueue_fifo.c
 *
 * FIFO queueing for NASD drive
 *
 * Authors: 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>
#include <nasd/nasd_common.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_drive_io.h>
#include <nasd/nasd_ioqueue.h>

#if NASD_DRIVE_IOQUEUE_INCLUDE_FIFO > 0

typedef struct nasd_ioqueue_fifo_queue_s {
  nasd_odc_ent_t  *head;
  nasd_odc_ent_t  *tail;
} nasd_ioqueue_fifo_queue_t;

nasd_ioqueue_fifo_queue_t nasd_ioqueue_fifo_queues[NASD_IO_PRI_NUM];

/*
 * Initialize fifo queueing subsystem.
 * Called at start-of-day.
 */
nasd_status_t
nasd_ioqueue_fifo_init(
  nasd_od_config_t  *config)
{
  nasd_ioqueue_fifo_queue_t *queue;
  int i;

  for(i=0;i<NASD_IO_PRI_NUM;i++) {
    queue = &nasd_ioqueue_fifo_queues[i];
    bzero((char *)queue, sizeof(nasd_ioqueue_fifo_queue_t));
  }

  return(NASD_SUCCESS);
}

/*
 * Enqueue a sorted list of I/Os. Called with
 * I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_enq_sorted(
  nasd_odc_ent_t  *entlist,
  int              dir,
  int              pri,
  char            *file,
  int              line)
{
  nasd_status_t rc;

  /*
   * Since we don't care about ordering, we'll just
   * enqueue it all as given to us.
   */
  rc = nasd_ioqueue_fifo_enq(entlist, dir, pri, file, line);
  return(rc);
}

/*
 * Enqueue an unsorted list of I/Os. Called with
 * I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_enq(
  nasd_odc_ent_t  *entlist,
  int              dir,
  int              pri,
  char            *file,
  int              line)
{
  nasd_ioqueue_fifo_queue_t *queue;
  nasd_odc_ent_t *ent, *prev;

  NASD_ASSERT(entlist != NULL);

  queue = &nasd_ioqueue_fifo_queues[pri];
  prev = queue->tail;
  if (queue->tail) {
    queue->tail->inext = entlist;
  }
  else {
    queue->head = queue->tail = entlist;
  }
  for(ent=entlist;ent;prev=ent,ent=ent->inext) {
    NASD_ASSERT(!(ent->io_flags&(NASD_CI_DISPATCH|NASD_CI_IOQ)));
    ent->iprev = prev;
    ent->io_flags |= NASD_CI_IOQ;
    ent->iodir = dir;
    ent->iopri = pri;
  }
  queue->tail = prev;

  /*
   * Issue I/Os if necessary.
   */
  nasd_od_io_launch_as_necessary();

  return(NASD_SUCCESS);
}

/*
 * Dequeue next valid I/O (or I/O chain). Called with
 * I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_deq_next(
  nasd_odc_ent_t  **entlist,
  char             *file,
  int               line)
{
  int i, coalesced, max_coalesce, dir;
  nasd_odc_ent_t *el, *ell, *e, *next;
  nasd_ioqueue_fifo_queue_t *queue;
  nasd_blkno_t next_sectno;
#if NASD_IOQUEUE_TIMERS > 0
  nasd_timespec_t ts;
  nasd_timer_t tm;
#endif /* NASD_IOQUEUE_TIMERS > 0 */

  *entlist = NULL;

#if NASD_IOQUEUE_TIMERS > 0
  NASD_TM_START(&tm);
#endif /* NASD_IOQUEUE_TIMERS > 0 */

  if (nasd_drive_io_max_coalesce)
    max_coalesce = NASD_MIN(nasd_drive_io_max_coalesce, NASD_IO_MAX_COALESCE);
  else
    max_coalesce = NASD_IO_MAX_COALESCE;

  for(i=0;i<NASD_IO_PRI_NUM;i++) {
    queue = &nasd_ioqueue_fifo_queues[i];
    if (queue->head == NULL) {
      continue;
    }
    el = ell = queue->head;
    dir = el->iodir;
    coalesced = 1;
    next_sectno = el->real_sectno + NASD_OD_SECTORS_PER_BLK;
    el->io_flags &= ~NASD_CI_IOQ;
    el->io_flags |= NASD_CI_DISPATCH;
    for(e=el->inext;e;e=next) {
      next = e->inext;
      /* Is it adjacent? */
      if (e->real_sectno != next_sectno) {
        if (dir == NASD_U_READ) {
          NASD_IO_INC_STAT(coalesce_stop_contig_read);
        }
        else {
          NASD_IO_INC_STAT(coalesce_stop_contig_write);
        }
        break;
      }
      /* Is it the right direction? */
      if (e->iodir != dir) {
        NASD_IO_INC_STAT(coalesce_stop_dir);
        break;
      }
      /* Can we add another one? */
      if (coalesced >= max_coalesce) {
        if (dir == NASD_U_READ) {
          NASD_IO_INC_STAT(coalesce_stop_max_read);
        }
        else {
          NASD_IO_INC_STAT(coalesce_stop_max_write);
        }
        break;
      }
      /* Is it aligned? */
      if (nasd_drive_io_cluster_align &&
        ((e->real_sectno % nasd_drive_io_cluster_align) == 0))
      {
        /* next block starts in new alignment domain */
        if (dir == NASD_U_READ) {
          NASD_IO_INC_STAT(coalesce_stop_align_read);
        }
        else {
          NASD_IO_INC_STAT(coalesce_stop_align_write);
        }
        break;
      }
      coalesced++;
      ell = e;
      e->io_flags &= ~NASD_CI_IOQ;
      e->io_flags |= NASD_CI_DISPATCH;
      next_sectno = e->real_sectno + NASD_OD_SECTORS_PER_BLK;
    }
    if (dir == NASD_U_READ) {
      NASD_IO_INC_SIZE_STAT(coalesced,read);
      NASD_IO_INC_STAT(num_io_reads);
    }
    else {
      NASD_IO_INC_SIZE_STAT(coalesced,write);
      NASD_IO_INC_STAT(num_io_writes);
    }
    el->iprev = NULL;
    if (ell->inext) {
      queue->head = ell->inext;
      queue->head->iprev = NULL;
      ell->inext = NULL;
    }
    else {
      queue->head = queue->tail = NULL;
    }
    *entlist = el;
#if NASD_IOQUEUE_TIMERS > 0
    NASD_TM_STOP(&tm);
    NASD_TM_ELAPSED_TS(&tm, &ts);
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_ioqueue_stats.io_coalesce_time, &ts);
    NASD_IO_TM_LAUNCH(el);
#endif /* NASD_IOQUEUE_TIMERS > 0 */
    return(NASD_SUCCESS);
  }

  *entlist = NULL;

  return(NASD_SUCCESS);
}

/*
 * Dequeue a specific entry. Called with I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_deq_ent(
  nasd_odc_ent_t  *ent,
  char            *file,
  int              line)
{
  NASD_ASSERT((ent->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH))
    == NASD_CI_DISPATCH);
  ent->iprev->inext = ent->inext;
  ent->inext->iprev = ent->iprev;
  ent->io_flags &= ~NASD_CI_IOQ;

  return(NASD_SUCCESS);
}

/*
 * If enqueued, make sure that ent is enqueued at a priority
 * level of at least newpri. Called with I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_try_raise_pri(
  nasd_odc_ent_t  *ent,
  int              newpri)
{
  nasd_status_t rc;

  if (ent->iopri <= newpri)
    return(NASD_SUCCESS);
  NASD_ASSERT((ent->io_flags&(NASD_CI_IOQ|NASD_CI_DISPATCH)) == NASD_CI_IOQ);

  ent->iprev->inext = ent->inext;
  ent->inext->iprev = ent->iprev;

  ent->io_flags &= ~NASD_CI_IOQ;
  ent->inext = ent->iprev = NULL;

  rc = nasd_ioqueue_fifo_enq(ent, ent->iodir, newpri, __FILE__, __LINE__);
  if (rc) {
    NASD_PANIC();
  }

  return(NASD_SUCCESS);
}

/*
 * The I/O module is informing us that a synchronous I/O
 * is launching. Called with I/O lock held.
 */
nasd_status_t
nasd_ioqueue_fifo_sync_launched(
  nasd_sectno_t  sector)
{
  /*
   * Do nothing. We don't care where the arm is.
   */
  return(NASD_SUCCESS);
}

#endif /* NASD_DRIVE_IOQUEUE_INCLUDE_FIFO > 0 */

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