/* Hey Emacs, this is -*-C-*- 
 ******************************************************************************
 * asynch.c -- Measure the a-synchronous interface.
 * 
 * Author          : Peter Bosch
 * Created On      : Thu Mar  4 13:41:31 1999
 * Last Modified By: Peter Bosch
 * Last Modified On: Mon Apr 12 14:31:16 1999
 * Status          : Unknown, Use with caution!
 * 
 * Unless other notices are present in any part of this file
 * explicitly claiming copyrights for other people and/or 
 * organizations, the contents of this file is fully copyright 
 * (C) 1999 Peter Bosch, all rights reserved.
 * 
 ******************************************************************************
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <fcntl.h>

#include <cwif.h>

#include <sched.h>

#define _IO(a, b)	(((a) << 8)|(b))
#define BLKGETSIZE	_IO(0x12,96)	/* return device size */
#define K 1024

static char *progname;

static int bsize = 128 * K;
static int nruns = 100;
static int bandwidth;
static int preload = 1;

static Time_t
now()
{
  struct timeval tv;

  if (gettimeofday(&tv, NULL) < 0) {
    fprintf(stderr, "%s: Cannot get time of day %s\n", progname, 
	    strerror(errno));
    exit(1);
  }
  return S(tv.tv_sec) + US(tv.tv_usec);
}

static void
_wait(Time_t until)
{
  Time_t t;
  struct timeval tv;

  t = now();
  if (until < t) return;
  tv.tv_sec  = (int)((until - t) / S(1));
  tv.tv_usec = (int)((until - t) % S(1)) / US(1);

  if (select(0, NULL, NULL, NULL, &tv) < 0) {
    fprintf(stderr, "%s: Cannot wait: %s\n", progname, strerror(errno));
    exit(1);
  }
}

static void
usage()
{
  fprintf(stderr, "Usage: %s [-p preload] [-B bw] [-b bsize] [-n nruns] filename\n",
	  progname);
  exit(1);
}

static void
_block(int fd, cwrelease_t *p, Time_t expect, Time_t ibd)
{
  struct timeval tv;
  fd_set fds;
  int rv;

repeat:
  memset(&tv, 0, sizeof(struct timeval));
  if (bandwidth) {
    Time_t t   = expect - now();
    tv.tv_sec  = (int)(t / S(1));
    tv.tv_usec = (int)((t % S(1)) / US(1));
  }
  FD_ZERO(&fds);
  FD_SET(fd, &fds);

  if ((rv = select(fd + 1, &fds, NULL, NULL, &tv)) < 0) {
    fprintf(stderr, "%s: Cannot select: %s\n",
	    progname, strerror(errno));
    exit(1);
  }

  if (rv == 0) {
    printf("%s: Missed deadline, block not yet finished\n", progname);
    expect = now() + ibd;
    goto repeat;
  }
      
  if ((rv = read(fd, p->rel_buf, p->rel_size)) < 0) {
    fprintf(stderr, "%s: Cannot read: %s\n",
	    progname, strerror(errno));
    exit(1);
  }

  if (rv != p->rel_size) {
    fprintf(stderr, "%s: Requested %d, got %d\n",
	    progname, p->rel_size, rv);
    exit(1);
  }	

  _wait(expect);
}

int
main(int argc, char **argv)
{
  struct sched_param schedpars;
  int fd, opt, n, npreload, qindex, dindex;
  char *fname;
  uint8_t *buf;
  uint32_t fsize;
  uint64_t offset;
  Time_t ibd, lastblock;
  cwrelease_t *p;

  progname = argv[0];
  while ((opt = getopt(argc, argv, "B:b:p:n:?")) != EOF) {
    switch (opt) {
    case 'b':
      bsize = strtol(optarg, (char **)NULL, 0);
      break;
    case 'n':
      nruns = strtol(optarg, (char **)NULL, 0);
      break;
    case 'B':
      bandwidth = strtol(optarg, (char **)NULL, 0);
      break;
    case 'p':
      preload = strtol(optarg, (char **)NULL, 0);
      break;
    case '?':
      usage();
    }
  }
  if (optind != argc - 1) usage();
  fname = argv[optind++];

  fd = open(fname, O_RDONLY|O_NDELAY);
  if (fd < 0) {
    fprintf(stderr, "%s: Cannot open %s: %s\n",
	    progname, fname, strerror(errno));
    exit(1);
  }

  if (bandwidth) {
    cwqos_t qos;

    qos.qos_bw = bandwidth;
    qos.qos_bs = bsize;

    if (ioctl(fd, CW_QOS, &qos) < 0) {
      fprintf(stderr, "%s: Cannot allocate bandwidth for %s: %s\n",
	      progname, fname, strerror(errno));
      exit(1);
    }

    ibd = S(bsize) / bandwidth;
  }

  if (ioctl(fd, BLKGETSIZE, &fsize) < 0) {
    fprintf(stderr, "%s: Cannot get device size from %s: %s\n",
	    progname, fname, strerror(errno));
    exit(1);
  }
  fsize <<= 9;

  p = (cwrelease_t *)malloc(preload * sizeof(cwrelease_t));
  if (p == 0) {
    fprintf(stderr, "%s: Cannot allocate %d bytes\n", progname, 
	    preload * sizeof(cwrelease_t));
    exit(1);
  }
  
  for (n = 0; n != preload; n++) {
    p[n].rel_buf = (uint8_t *)malloc(bsize);
    if (p[n].rel_buf == NULL) {
      fprintf(stderr, "%s: Cannot allocate %d bytes\n", progname, bsize);
      exit(1);
    }

    if (mlock(p[n].rel_buf, bsize) < 0) {
      fprintf(stderr, "%s: Cannot lock memory: %s\n",
	      progname, strerror(errno));
      exit(1);
    }

    p[n].rel_size = bsize;
    p[n].rel_offs = 0;
    p[n].rel_rw   = CWRead;
  }
  qindex = dindex = npreload = 0;

  if ((schedpars.sched_priority = sched_get_priority_max(SCHED_FIFO)) < 0) {
    fprintf(stderr, "%s: Cannot get max schedule prio: %s\n",
	    progname, strerror(errno));
    exit(1);
  }

  schedpars.sched_priority--;
  if (sched_setscheduler(0, SCHED_FIFO, &schedpars) < 0) {
    fprintf(stderr, "%s: Cannot set schedule: %s\n",
	    progname, strerror(errno));
    exit(1);
  }

  lastblock = now();
  for (n = 0; n != nruns; n++) {

    if (lseek(fd, 0, SEEK_SET) < 0) {
      fprintf(stderr, "%s: Cannot reposition %s: %s\n",
	      progname, fname, strerror(errno));
      exit(1);
    }
    offset = 0;

    while (offset + bsize <= fsize) {
      int rv;

      while (npreload < preload) {
	/* Prepare the next element */
	p[qindex].rel_offs = offset;
	
	if (ioctl(fd, CW_RELEASE, &p[qindex]) < 0) {
	  fprintf(stderr, "%s: Cannot release request on %s: %s\n",
		  progname, fname, strerror(errno));
	  exit(1);
	}
	qindex = (qindex + 1) % preload;
	npreload++;
	offset += bsize;
      }
      
      printf("preloaded %d\n", npreload);
      _block(fd, &p[dindex], lastblock + ibd, ibd);
      lastblock = now();
      dindex = (dindex + 1) % preload;
      npreload--;
    }

    /* Wait for the remainder of the blocks */
    while (npreload > 0) {
      _block(fd, &p[dindex], lastblock + ibd, ibd);
      dindex = (dindex + 1) % preload;
      npreload--;
    }
  }
  exit(0);
}

  
      
