/* -*- Mode: c++ -*-
 *
 *  Copyright 1997 Massachusetts Institute of Technology
 * 
 *  Permission to use, copy, modify, distribute, and sell this software and its
 *  documentation for any purpose is hereby granted without fee, provided that
 *  the above copyright notice appear in all copies and that both that
 *  copyright notice and this permission notice appear in supporting
 *  documentation, and that the name of M.I.T. not be used in advertising or
 *  publicity pertaining to distribution of the software without specific,
 *  written prior permission.  M.I.T. makes no representations about the
 *  suitability of this software for any purpose.  It is provided "as is"
 *  without express or implied warranty.
 * 
 */
/*
 * Copyright 2001 Free Software Foundation, Inc.
 * 
 * This file is part of GNU Radio
 * 
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 * 
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "mc4020.h"
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define PAGE_SIZE 4096
#define SAMPLE_RATE 20000000

const char *device_name = "/dev/mc4020_0";

inline void pbufferpb(unsigned char *b, int index) {
  if(index!=0) {
    short *sp = (short *) &b[index * PAGE_SIZE];
    fprintf(stderr, "Info: page boundary %4u/%4u: %5d %5d %5d %5d   %5d %5d %5d %5d\n",
	    index-1, index,
	    sp[-4] - 0x800,
	    sp[-3] - 0x800,
	    sp[-2] - 0x800,
	    sp[-1] - 0x800,
	    sp[0]  - 0x800,
	    sp[1]  - 0x800,
	    sp[2]  - 0x800,
	    sp[3]  - 0x800);
  }
}

int main(int argc, char **argv) {

  int device_fd;
  unsigned int bufferSize = (20L << 20) / PAGE_SIZE; // 20 MBytes ~ 1/2 second
  unsigned char *readbuf;
  unsigned char temp=0;
  struct mc4020_config	config;

  //double check page size setting
  if(PAGE_SIZE != getpagesize()) {
    fprintf(stderr, "Page size incorrect, recompile.\n");
    exit(-1);
  }

  /* Read tests */
  if ((device_fd = open(device_name, O_RDONLY)) < 0) {
    fprintf (stderr, "Fail: ");
    perror (device_name);
    exit(-1);
  }
  else
    fprintf(stderr,"Success: open\n");

  config.scan_rate = SAMPLE_RATE;
  config.bitmask = MCC_CH2_EN |  MCC_ALL_5V | MCC_ASRC_BNC;
  if (ioctl (device_fd, GIOCSETCONFIG, &config) < 0){
    perror ("Failed to set configuration");
  }
  else
    fprintf (stderr, "Success: setconfig\n");

  if((ioctl(device_fd, GIOCSETBUFSIZE, bufferSize*PAGE_SIZE)) < 0) {
    perror("Failed to set buffersize");
  } else
    fprintf(stderr,"Success: set buffer size\n");

  //Check things that _shouldn't_ work
  if(mmap(0,bufferSize*PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, 
	  device_fd, 0)!=MAP_FAILED) {
    perror("Failure: mmap permitted PROT_WRITE!");
    exit(-1);
  } else
    fprintf(stderr,"Success: mmap protection test\n");

  //do the real mmap
  if((readbuf=(unsigned char *) mmap(0,bufferSize*PAGE_SIZE, PROT_READ, MAP_SHARED, 
		   device_fd, 0))==MAP_FAILED) {
    perror("Failed to mmap data");
    exit(-1);
  } else fprintf(stderr,"Success: mmap\n");

  struct mc4020_status status;

  //check status before receive
  status.num=0;
  if((ioctl(device_fd, GIOCSETGETSTATUS, &status)) < 0) {
    fprintf (stderr, "Success: failed to get status before receive\n");

    status.index = 0;	// initialize values
    status.num = 0;
    status.lost = 0;
  }
  else
    fprintf(stderr,"Failed: got status before receive\n");

  //Fault in all the pages by reading from them
  for(uint i=0;i<bufferSize;i++) {
    if((temp=readbuf[i*PAGE_SIZE])!=0) {
      fprintf(stderr,"Fail: buffer does not contain zeros (%d).\n",(int) temp);
      exit(-1);
    }
  }
  if(temp==0)
    fprintf(stderr,"Success: buffer faulted into user space\n");

  //Start receive
  if((ioctl(device_fd, GIOCSTART)) < 0) {
    perror("Failed to start receive");
  } else fprintf(stderr,"Success: start receive\n");


  usleep(100000);	// 100 ms

  int num=0;
  status.num=0;
  uint i=0;

  for(uint x=0;x<10;x++) {
    i = i % bufferSize;
    //run through 20 buffers
    for(;i<bufferSize*20;) {

      /*free some blocks if appropriate*/
      unsigned int reserve=10+750*(i/bufferSize);
      if(reserve > bufferSize - 2048) //don't let the buf get too full
	reserve = bufferSize - 2048; //2048 = saftey in driver + transferring
      if(status.num > reserve) {
	status.num-=reserve;      
	//      fprintf(stderr,"Info: freeing %u blocks\n",status.num);
	num-=status.num;
	i+=status.num;
      } else
	status.num = 0;

      unsigned int currentindex = i % bufferSize;

      //free the blocks and find out how many blocks are ready
      if((ioctl(device_fd, GIOCSETGETSTATUS, &status)) < 0) {
	perror("Failed to get status");
	exit(-1);
      }
    
      if(status.index != currentindex) {
	fprintf(stderr,"Fail: current index should be %u, instead the driver thinks it's %u\n",currentindex, status.index);
	fprintf(stderr,"      num_valid=%u and num_lost=%u\n",status.num, status.lost);
	exit(-1);
      } else if(status.lost) {
	fprintf(stderr,"Warning: lost %u pages\n", status.lost);
      }

      if (status.num-num) {
	fprintf(stderr,"Info: got %u new pages (now holding %u pages at index %u)\n", status.num-num, status.num, status.index);
	pbufferpb(readbuf,status.index);
      }
      num=status.num;

      //slow down a bit
      unsigned long delay = bufferSize*2/3-reserve;
      if(reserve < bufferSize*2/3) {
	delay *=1024;
	delay = (unsigned long) (delay*1000000.0/SAMPLE_RATE);
	usleep(delay);
      }
    }
  }

  //Free wrong blocks
  status.index=1;
  status.num=1;

  if((ioctl(device_fd, GIOCSETGETSTATUS, status)) == 0) {
    perror("Fail: invalid free block request accepted by driver");
  } else
    fprintf(stderr,"Success: bad free test\n");

  //Try an overrun
  sleep(3);
  status.num=0;
  if((ioctl(device_fd, GIOCSETGETSTATUS, &status)) < 0) {
    perror("Failed to get status");
  }

  if(status.lost == 0) {
    fprintf(stderr, "Fail: lost block count incorrect.\n");
  } else {
    fprintf(stderr, "Success: lost block test: %u pages lost in 3 seconds.\n",
	    status.lost);
  }

  //Stop receive
  if((ioctl(device_fd, GIOCSTOP)) < 0) {
    perror("Failed to stop receive");
  } else fprintf(stderr,"Success: stop receive\n");

  //Munmap and Close
  if(munmap(readbuf,bufferSize*PAGE_SIZE)<0) {
    perror("Failed to munmap buffer");
  } else fprintf(stderr,"Success: munmap\n");
  if(close(device_fd)<0) {
    perror("Failed to close fd");
  } else fprintf(stderr,"Success: close\n");

  return 0;
}

