/*
 * nasd_trace_dr.c
 *
 * Generic tracing package for NASD drive, accessed via
 * control objects
 *
 * 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_security_dr.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_control.h>
#include <nasd/nasd_trace.h>
#include <nasd/nasd_trace_dr.h>
#include <nasd/nasd_control_marshall.h>
#include <nasd/nasd_types_marshall.h>


#if NASD_TRACE_DR > 0

extern int nasd_dt_max_trace_size;

NASD_DECLARE_MUTEX(nasd_dt_lock)
#define LOCK_TRACE()   NASD_LOCK_MUTEX(nasd_dt_lock)
#define UNLOCK_TRACE() NASD_UNLOCK_MUTEX(nasd_dt_lock)

nasd_uint64 nasd_dt_enable = 0;

int nasd_dt_overflow;
int nasd_dt_size;
int nasd_dt_cur_offset;
int nasd_dt_roffset;
nasd_dt_trace_rec_t *nasd_dt_first, *nasd_dt_cur;
nasd_trace_seq_t nasd_trace_seq;

nasd_freelist_t *nasd_dt_rec_freelist;
#define NASD_MAX_FREE_DT_REC  8
#define NASD_DT_REC_INC       2
#define NASD_DT_REC_INITIAL   2

extern nasd_freelist_t *nasd_free_pagebuf;

nasd_dt_trace_rec_t *
nasd_dt_getrec()
{
  nasd_dt_trace_rec_t *rec;

  NASD_FREELIST_GET(nasd_dt_rec_freelist,rec,next,(nasd_dt_trace_rec_t *));
  if (rec) {
    rec->next = NULL;
  }
  return(rec);
}

void
nasd_dt_freerec(
  nasd_dt_trace_rec_t  *rec)
{
  NASD_FREELIST_FREE(nasd_dt_rec_freelist,rec,next);
}

/*
 * Call with trace lock held or other
 * sequentiality guarantee (ie, shutdown)
 */
void
nasd_dt_stop()
{
  nasd_dt_trace_rec_t *rec, *n;

  if (nasd_dt_cur) {
    NASD_ASSERT(nasd_dt_first != NULL);
    for(rec=nasd_dt_first;rec;rec=n) {
      n = rec->next;
      nasd_dt_freerec(rec);
    }
  }
  else {
    NASD_ASSERT(nasd_dt_first == NULL);
  }

  nasd_dt_first = NULL;
  nasd_dt_cur = NULL;
  nasd_dt_overflow = 0;
  nasd_dt_size = 0;
  nasd_dt_cur_offset = 0;
  nasd_dt_roffset = 0;
}

void
nasd_dt_freerecs(
  void  *arg)
{
  nasd_dt_stop();
  NASD_FREELIST_DESTROY(nasd_dt_rec_freelist,next,(nasd_dt_trace_rec_t *));
}

void
_nasd_dt_trace_enter(
  int                trace_class,
  int                trace_type,
  int                len,
  nasd_byte_t       *data,
  nasd_trace_seq_t  *trace_seq,
  int                seq_provided)
{
  int max_fill, l, done, cp, remain;
  nasd_trace_header_otw_t th_otw;
  nasd_trace_header_t th;
  char *ib, *b;

  max_fill = nasd_dt_max_trace_size - sizeof(nasd_trace_header_t);
  l = len + sizeof(nasd_trace_header_t);

  th.tr_class = trace_class&0xff;
  th.tr_type = trace_type&0xff;
  th.tr_len = len;

  LOCK_TRACE();
  if (seq_provided) {
    th.tr_seq = *trace_seq;
  }
  else {
    *trace_seq = nasd_trace_seq;
    nasd_trace_seq++;
  }

  if (nasd_dt_overflow)
    goto abort_trace;

  if ((nasd_dt_size + l) > max_fill) {
    nasd_dt_overflow = 1;
    th.tr_type = NASD_TR_TRACE;
    th.tr_type = NASD_TR_OVERFLOW;
    th.tr_len = 0;
  }
  nasd_gettime(&th.tr_time);
  if (nasd_dt_cur == NULL) {
    NASD_ASSERT(nasd_dt_first == NULL);
    nasd_dt_first = nasd_dt_getrec();
    if (nasd_dt_first == NULL) {
      goto abort_trace;
    }
    nasd_dt_cur = nasd_dt_first;
    nasd_dt_cur_offset = 0;
  }

  done = 0;
  nasd_trace_header_t_marshall(&th, th_otw);
  ib = (char *)th_otw;
  while(done<sizeof(nasd_trace_header_t)) {
    remain = NASD_DT_BUF_SIZE-nasd_dt_cur_offset;
    if (remain == 0) {
      nasd_dt_cur->next = nasd_dt_getrec();
      if (nasd_dt_cur->next == NULL)
        NASD_PANIC();
      nasd_dt_cur = nasd_dt_cur->next;
      nasd_dt_cur_offset = 0;
    }
    cp = NASD_MIN(sizeof(nasd_trace_header_t)-done,remain);
    b = (char *)nasd_dt_cur->data;
    bcopy(ib+done,(char *)&b[nasd_dt_cur_offset],cp);
    nasd_dt_cur_offset += cp;
    done += cp;
  }

  if (th.tr_len) {
    done = 0;
    ib = (char *)data;
    while(done<th.tr_len) {
      remain = NASD_DT_BUF_SIZE-nasd_dt_cur_offset;
      if (remain == 0) {
        nasd_dt_cur->next = nasd_dt_getrec();
        if (nasd_dt_cur->next == NULL)
          NASD_PANIC();
        nasd_dt_cur = nasd_dt_cur->next;
        nasd_dt_cur_offset = 0;
      }
      cp = NASD_MIN(th.tr_len-done,remain);
      b = (char *)nasd_dt_cur->data;
      bcopy(ib+done,(char *)&b[nasd_dt_cur_offset],cp);
      nasd_dt_cur_offset += cp;
      done += cp;
    }
  }

abort_trace:
  UNLOCK_TRACE();
}

nasd_status_t
nasd_dt_trace_init()
{
  nasd_status_t rc;

  NASD_ASSERT(sizeof(nasd_ctrl_trace_basic_t) == NASD_INFO_PAGESIZE);

  nasd_dt_first = NULL;
  nasd_dt_cur = NULL;
  nasd_dt_overflow = 0;
  nasd_dt_size = 0;
  nasd_dt_cur_offset = 0;
  nasd_dt_roffset = 0;
  nasd_trace_seq = 1;

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

  NASD_FREELIST_CREATE(nasd_dt_rec_freelist, NASD_MAX_FREE_DT_REC,
    NASD_DT_REC_INC, sizeof(nasd_dt_trace_rec_t));
  if (nasd_dt_rec_freelist == NULL)
    return(NASD_NO_MEM);
  NASD_FREELIST_PRIME(nasd_dt_rec_freelist, NASD_DT_REC_INITIAL,next,
    (nasd_dt_trace_rec_t *));
  rc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_dt_freerecs, NULL);
  if (rc) {
    nasd_dt_freerecs(NULL);
    return(rc);
  }

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_dt_write_trace_info(
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len, 
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  nasd_len_t bytes_recvd_this_time, bytes_recvd;
  nasd_ctrl_trace_basic_t *info;
  nasd_pagebuf_t *pb, *pb_net;
  nasd_offset_t cur_offset;
  nasd_timespec_t ts;
  nasd_status_t rc;
  nasd_timer_t tm;
  nasd_uint64 ba;

  NASD_TIMESPEC_ZERO(ts);
  ba = 0;

  pb = NULL;
  pb_net = NULL;

  /*
   * Write pagesized chunks on pagealigned boundaries.
   * In other words, whole pages only, please.
   */
  if (in_len % NASD_INFO_PAGESIZE)
    return(NASD_BAD_LEN);
  if (offset % NASD_INFO_PAGESIZE)
    return(NASD_BAD_OFFSET);

  rc = NASD_SUCCESS;

  cur_offset = offset;
  NASD_FREELIST_GET(nasd_free_pagebuf,pb,next,(nasd_pagebuf_t *));
  if (pb == NULL) {
    rc = NASD_NO_MEM;
    goto done;
  }
  NASD_FREELIST_GET(nasd_free_pagebuf,pb_net,next,(nasd_pagebuf_t *));
  if (pb_net == NULL) {
    rc = NASD_NO_MEM;
    goto done;
  }

  while((*out_len) < in_len) {
    switch(offset) {
      case 0:
        bytes_recvd = 0;
        NASD_TM_START(&tm);
        do {
          rc = byte_pipe->pull(byte_pipe->state,
            (nasd_byte_t *)&pb_net->buf[bytes_recvd],
            NASD_INFO_PAGESIZE-bytes_recvd,
            &bytes_recvd_this_time, NULL, NULL, NULL, NULL);
          if (rc)
            goto done;
          bytes_recvd += bytes_recvd_this_time;
        } while(bytes_recvd < NASD_INFO_PAGESIZE);
        NASD_TM_STOP_ACCUM_TS(&tm,&ts);
        ba += bytes_recvd;
        info = (nasd_ctrl_trace_basic_t *)pb->buf;
        nasd_ctrl_trace_basic_t_unmarshall(pb_net->buf, info);
        LOCK_TRACE();
        if (info->enabled) {
          nasd_dt_enable = info->enabled;
        }
        else {
          nasd_dt_enable = 0;
          nasd_dt_stop();
        }
        UNLOCK_TRACE();
        break;
      default:
        rc = NASD_BAD_OFFSET;
        goto done;
    }
    *out_len+=NASD_INFO_PAGESIZE;
    offset+=NASD_INFO_PAGESIZE;
  }

done:
  if (pb) {
    NASD_FREELIST_FREE(nasd_free_pagebuf,pb,next);
  }
  if (pb_net) {
    NASD_FREELIST_FREE(nasd_free_pagebuf,pb_net,next);
  }

  NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_cache_stats.write_pipe_stall_time, &ts);
  NASD_ATOMIC_ADD64(&nasd_drive_cache_stats.write_bytes, ba);

  return(rc);
}

nasd_status_t
nasd_dt_read_trace_info(
  nasd_pagebuf_t           *pb,
  nasd_pagebuf_t           *pb_net,
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len,
  int                       is_read2,
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  nasd_ctrl_trace_basic_t *info;
  nasd_timespec_t ts;
  nasd_uint64 ac, ba;
  nasd_status_t rc;
  nasd_byte_t *buf;
  nasd_timer_t tm;

  /*
   * Read pagesized chunks on pagealigned boundaries.
   * In other words, whole pages only, please.
   */

  ac = 0;
  ba = 0;

  if (in_len % NASD_INFO_PAGESIZE) 
    return(NASD_BAD_LEN);

  if (offset % NASD_INFO_PAGESIZE) 
    return(NASD_BAD_OFFSET);

  rc = NASD_SUCCESS;
  buf = pb->buf;

  while((*out_len) < in_len) {
    switch(offset) {
      case 0:
        info = (nasd_ctrl_trace_basic_t *)buf;
        info->ctrl_id = NASD_CTRL_TRACE_INFO;
        info->enabled = (nasd_dt_enable ? 1 : 0);
        nasd_ctrl_trace_basic_t_marshall(info, pb_net->buf);
        break;
      default:
        NASD_ASSERT(!(offset%NASD_INFO_PAGESIZE));
        /*
         * XXX Is this the right thing to do?
         * Here, we'll send back a page full of
         * zeroes. Why? Well, if the read is for
         * multiple info pages, then we don't want
         * to fail the good parts by returning
         * NASD_BAD_OFFSET or something. However,
         * a "good" page will have an identifier at
         * the top that we'll have zeroed, so the
         * client can tell we didn't know what it
         * was talking about, in case it's asking
         * for something that we don't support but
         * other drives do. As if there were any
         * other drives.
         */
        bzero((char *)pb_net->buf, NASD_INFO_PAGESIZE);
    }
    NASD_TM_START(&tm);
    rc = byte_pipe->push(byte_pipe->state, pb_net->buf,
      NASD_INFO_PAGESIZE, NULL,
      NULL, NULL);
    NASD_TM_STOP_ACCUM_TS(&tm,&ts);
    ba += NASD_INFO_PAGESIZE;
    if (rc)
      break;
    *out_len += NASD_INFO_PAGESIZE;
    offset += NASD_INFO_PAGESIZE;
  }

  if (is_read2) {
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_cache_stats.read2_pipe_stall_time, &ts);
    NASD_ATOMIC_ADD64(&nasd_drive_cache_stats.read2_bytes, ba);
  }
  else {
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_cache_stats.read_pipe_stall_time, &ts);
    NASD_ATOMIC_ADD64(&nasd_drive_cache_stats.read_bytes, ba);
  }

  return(rc);
}

nasd_status_t
nasd_dt_write_trace_data(
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len, 
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  return(NASD_NOT_ON_CONTROL);
}

nasd_status_t
nasd_dt_read_trace_data(
  nasd_pagebuf_t           *pb,
  nasd_pagebuf_t           *pb_net,
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len,
  int                       is_read2,
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  int avail, in_buf, lk, roff, ship;
  nasd_dt_trace_rec_t *rec;
  nasd_uint64 ac, ba;
  nasd_timespec_t ts;
  nasd_len_t remain;
  nasd_byte_t *buf;
  nasd_status_t rc;
  nasd_timer_t tm;

  rc = NASD_SUCCESS;

  ac = 0;
  ba = 0;

  if (offset)
    return(NASD_BAD_OFFSET);

  LOCK_TRACE();
  lk = 1;

  remain = in_len;
  while(nasd_dt_first && remain && (rc == NASD_SUCCESS)) {
    rec = nasd_dt_first;
    if (rec != nasd_dt_cur) {
      in_buf = NASD_DT_BUF_SIZE - nasd_dt_roffset;
    }
    else {
      in_buf = nasd_dt_cur_offset - nasd_dt_roffset;
    }
    roff = nasd_dt_roffset;
    ship = NASD_MIN(in_buf,remain);
    if (in_buf <= remain) {
      /* consume entirely */
      if (rec == nasd_dt_cur) {
        nasd_dt_first = NULL;
        nasd_dt_cur = NULL;
        nasd_dt_cur_offset = 0;
      }
      else {
        nasd_dt_first = nasd_dt_first->next;
      }
      nasd_dt_roffset = 0;
      UNLOCK_TRACE();
      lk = 0;
    }
    else {
      nasd_dt_roffset += ship;
    }
    buf = rec->data + roff;
    NASD_TM_START(&tm);
    rc = byte_pipe->push(byte_pipe->state, buf, ship, NULL, NULL, NULL);
    NASD_TM_STOP_ACCUM_TS(&tm,&ts);
    ba += ship;
    /* rc bad is fallthrough */
    if (rc == NASD_SUCCESS) {
      *out_len += ship;
      remain -= ship;
    }
    if (lk == 0) {
      LOCK_TRACE();
      lk = 1;
      nasd_dt_freerec(rec);
    }
  }

  UNLOCK_TRACE();
  lk = 0;

  if (is_read2) {
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_cache_stats.read2_pipe_stall_time, &ts);
    NASD_ATOMIC_ADD64(&nasd_drive_cache_stats.read2_bytes, ba);
  }
  else {
    NASD_ATOMIC_TIMESPEC_ADD(&nasd_drive_cache_stats.read_pipe_stall_time, &ts);
    NASD_ATOMIC_ADD64(&nasd_drive_cache_stats.read_bytes, ba);
  }

  return(rc);
}

#else /* NASD_TRACE_DR > 0 */

nasd_status_t
nasd_dt_trace_init()
{
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_dt_write_trace_info(
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len, 
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  return(NASD_OP_NOT_SUPPORTED);
}

nasd_status_t
nasd_dt_read_trace_info(
  nasd_pagebuf_t           *pb,
  nasd_pagebuf_t           *pb_net,
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len,
  int                       is_read2,
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  return(NASD_OP_NOT_SUPPORTED);
}

nasd_status_t
nasd_dt_write_trace_data(
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len, 
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  return(NASD_OP_NOT_SUPPORTED);
}

nasd_status_t
nasd_dt_read_trace_data(
  nasd_pagebuf_t           *pb,
  nasd_pagebuf_t           *pb_net,
  int                       partnum,
  nasd_offset_t             offset,
  nasd_len_t                in_len,
  int                       is_read2,
  nasd_procpipe_t          *byte_pipe,
  nasd_len_t               *out_len)
{
  return(NASD_OP_NOT_SUPPORTED);
}

#endif /* NASD_TRACE_DR > 0 */

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