/*
 * Copyright (c) 2002-2005 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagvcam.c 9927 2008-09-11 00:05:20Z wilson.zhu $
 */

/* Endace headers. */
#include "dagapi.h"
#include "dag_platform.h"
#include "dagutil.h"
#include "dagclarg.h"

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagvcam.c 9927 2008-09-11 00:05:20Z wilson.zhu $";
static const char* const kRevisionString = "$Revision: 9927 $";

#define NVCS   4
#define NCAMS  NVCS*4


/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr);
static void dagbaseupdate(int dagfd);

static int parse_input(uint32_t *inputs);
static void print_input(uint32_t *inputs);
static void build_cams(uint32_t *inputs, uint32_t *cams);
static void print_cams(uint32_t *cams);
static int program_cams(u_char *addr, uint32_t *cams);
static void test(void);

static void program(void);
static void dstat(void);
static void stats(int interval);


enum {
	CMD_PROGRAM = 0x01,
	CMD_STAT    = 0x02,
	CMD_STATS   = 0x04,
	CMD_TEST    = 0x08,
	CMD_DEFAULT = CMD_STAT
};

static int hdim_base = 0;
static int mar_base  = 0;
static int VC_OFF    = 0x100;

static uint8_t *dagiom = NULL;
static int hdim=0;

static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static int dagstream;


enum
{
	CLA_HELP,
	CLA_VERSION,
	CLA_VERBOSE,
	CLA_DEVICE,
	CLA_PRGVPVC,
	CLA_HDIM,
	CLA_STATS,
	CLA_REPEAT,
	CLA_TEST
};

int
dagvcam_main(int argc, char *argv[])
{
	int dagfd = 0;
	int cmd = CMD_DEFAULT;
	int interval = 0;
	ClArgPtr clarg = NULL;
	int clarg_result;
	int argindex;
	int code;
	FILE *errorfile = NULL;

	dagutil_set_progname("dagvcam");

	/* Set up default DAG device. */
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char * const *)argv);

	dagclarg_add(clarg, "display help (this page)", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add(clarg, "display version information", "--version", 'V', CLA_VERSION);
	dagclarg_add(clarg, "increase verbosity", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "program VPVCs from stdin", "--program", 'p', CLA_PRGVPVC);
	dagclarg_add(clarg, "HDIM input mode", "--hdim", 'm', CLA_HDIM);
	dagclarg_add(clarg, "show statistics counters", "--stat", 's', CLA_STATS);
	dagclarg_add_int(clarg, "repeat statistics with interval int seconds", "--repeat", 'i', "interval", &interval, CLA_REPEAT);
	dagclarg_add(clarg, "test mode", "--test", 't', CLA_TEST);

	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == clarg_result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				break;

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				errorfile = stderr;
				break;

			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;
				break;

			case CLA_PRGVPVC:
				cmd = CMD_PROGRAM;
				break;

			case CLA_DEVICE:
				if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
				{
					dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
				}
				break;

			case CLA_HDIM:
				hdim = 1;
				break;

			case CLA_STATS:
				cmd = CMD_STATS;
				break;

			case CLA_REPEAT:
				break;

			case CLA_TEST:
				cmd = CMD_TEST;

 			default:
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[argindex]); 
					print_usage(clarg);
					return EXIT_FAILURE;
				
				
		}
		clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == clarg_result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}

	

	dagfd = dag_open(dagname);
	if (dagfd < 0)
		dagutil_panic("dag_open %s: %s\n", dagname, strerror(errno));
	dagiom = dag_iom(dagfd);

	dagbaseupdate(dagfd);

	switch(cmd) {
	case CMD_PROGRAM:
		program();
		/* fall through */
	case CMD_STAT:
		dstat();
		break;
	case CMD_STATS:
		stats(interval);
		break;
	case CMD_TEST:
		test();
		break;
	default:
		dagutil_panic("internal error at line %u file %s\n",__LINE__, __FILE__);
	}

	dag_close(dagfd);
		
	return EXIT_SUCCESS;
}


/* Implementation of internal routines. */
static void
print_version(void)
{
	printf("dagvcam (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}

static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagvcam - Endace DAG card VCAM utility.\n");
	printf("Usage: dagvcam [-hvpms] [-d /dev/dagN] [-i interval]\n");
	dagclarg_display_usage(clarg, stdout);
}

static void
stats(int interval)
{
	int	count=0;
	int	csr = *(volatile int*)(dagiom+mar_base);

	if (csr == 0xffffffff)
		dagutil_panic("MAR not detected!\n");

again:
	if((count++ % 20) == 0) {
		printf(" rxcell rxframe othervc   oobuf ");
		if(csr&1)
			printf("  vpvc0   vpvc1   vpvc2   vpvc3 ");
		if(csr&2)
			printf("  vpvc4   vpvc5   vpvc6   vpvc7");
		printf("\n");
	}
	
	printf("%7d %7d %7d %7d ",
	       *(int*)(dagiom+mar_base+0x10),
	       *(int*)(dagiom+mar_base+0x1c),
	       *(int*)(dagiom+mar_base+0x14),
	       *(int*)(dagiom+mar_base+0x18)
		);
	if(csr&1)
		printf("%7d %7d %7d %7d ",
		       *(int*)(dagiom+mar_base+0x20),
		       *(int*)(dagiom+mar_base+0x24),
		       *(int*)(dagiom+mar_base+0x28),
		       *(int*)(dagiom+mar_base+0x2c)
			);
	if(csr&2)
		printf("%7d %7d %7d %7d",
		       *(int*)(dagiom+mar_base+0x30),
		       *(int*)(dagiom+mar_base+0x34),
		       *(int*)(dagiom+mar_base+0x38),
		       *(int*)(dagiom+mar_base+0x3c)
			);
	printf("\n");
	*(int*)(dagiom+mar_base) = 0x80000000;

	if(interval) {
		sleep(interval);
		goto again;
	}
}

static void
dstat(void)
{
	int	hdim, csr;

	if (hdim_base) {
		hdim = *(volatile int*)(dagiom+hdim_base);
		if (hdim != 0xffffffff)
			printf("HDIM: %sabled Input=%s WC=%d CC=%d DC=%d Thresh=%d %s%s%s %sreset\n",
			       (hdim>>9)&1?"en":"dis",
			       (hdim>>11)&1?"HDIM":"Phy",
			       hdim>>28,
			       (hdim>>16)&0x1ff,
			       (hdim>>12)&0x0f,
			       hdim&0x1ff,
			       (hdim>>27)&1?"Full_Error ":"",
			       (hdim>>26)&1?"Empty_Error ":"",
			       (hdim>>25)&1?"Running":"Stopped",
			       (hdim>>10)&1?"":"no"
				);
	}
	csr = *(int*)(dagiom+mar_base);
	if (csr != 0xffffffff)
		printf("MAR:  %spause %sBank0 %sBank1 %s%s%s%s\n",
		       (csr>>15)&1?"":"no",
		       csr&1?"":"no",
		       csr&2?"":"no",
		       (csr>>19)&1?"AQ_Full ":"",
		       (csr>>18)&1?"AQ_Empty ":"",
		       (csr>>17)&1?"DQ_Full ":"",
		       (csr>>16)&1?"DQ_Empty ":""
			);
	else
		dagutil_panic("MAR not detected!\n");
}

static void
program(void)
{
	uint8_t		*addr = NULL;
	uint32_t	inputs[NVCS];
	uint32_t	cams[NCAMS];
	int banks;
	int csr;

	if (hdim)
		*(volatile int*)(dagiom+hdim_base) = 0xa04;
	else
		*(volatile int*)(dagiom+hdim_base) = 0x0;

	addr = dagiom + mar_base + VC_OFF;
	for(banks=0;banks<2;banks++) {
		if(parse_input(inputs)!=0)
			return;

		if (dagutil_get_verbosity())
			print_input(inputs);

		build_cams(inputs, cams);

		if (dagutil_get_verbosity())
			print_cams(cams);

		program_cams(addr, cams);
		csr = *(volatile int*)(dagiom + mar_base);
		if (!(csr&(banks+1)) )
			dagutil_panic("VPVC bank %d failed to program!\n", banks);
		addr += 0x40;
	}
}

static int
parse_input(uint32_t *inputs)
{
	int i;
	char lbuf[80];

	for(i=0;i<NVCS;i++) {
		if (!fgets(lbuf, 80, stdin))
			return 1;
		if (!sscanf(lbuf, "%x\n", inputs+i))
			return 2;
	}
	
	return 0;
}

static void
print_input(uint32_t *inputs)
{
	int i;

	for(i=0;i<NVCS;i++) {
		fprintf(stderr, "%d: 0x%08x  gfc %2d vpi %3d vci %5d pti %1d IF %1d\n",
				i, inputs[i],
				(unsigned)inputs[i]>>28,
				(unsigned)(inputs[i]>>20)&0xff,
				(unsigned)(inputs[i]>>4)&0xffff,
				(unsigned)(inputs[i]>>1)&0x7,
				(unsigned)(inputs[i]&0x1)
			);
	}
	printf("\n");
}

static void
build_cams(uint32_t *inputs, uint32_t *cams)
{
	int i, j;
	uint32_t inibble;

	for(i=0; i<NCAMS; i++)
		cams[i]=0;

	for(i=0; i<NVCS; i++) {
		inibble = inputs[i] & 0x0f;
		cams[inibble] |= 1 << (i*8+7);
		for(j=1; j<8; j++) {
			inibble = inputs[i] & (0x0f << (4*j));
			//printf("%d %d %08x ", i, j, inibble);
			inibble = inibble >> (4*j);
			//printf("%08x\n", inibble);
			cams[inibble] |= 1 << ( i*8 + j-1 );
		}
	}
}

static void
print_cams(uint32_t *cams)
{
	int i, j;

	for(i=0;i<NCAMS;i++) {
		printf("%x: 0x%08x\t", i, cams[i]);
		for(j=31; j>=0; j--) {
			printf("%d", (cams[i] & (1 << j))!=0);
			if (!(j%8))
				printf(" ");
		}
		printf("\n");
	}
	printf("\n");	
}

static int
program_cams(uint8_t *addr, uint32_t *cams)
{
	int i;

	for(i=0; i<NCAMS; i+=1) {
		*(int*)(addr+i*4) = cams[i];
		usleep(1000); /* XXX Should not be needed */
	}

	return 0;
}

static void
test(void)
{
	int	count = 100*1000*1000;

	while(count--)
		*(volatile int*)(dagiom+hdim_base+0x4) = 0x202;
}

static void
dagbaseupdate(int dagfd)
{
	dag_reg_t	*regs;
	dag_reg_t	result[DAG_REG_MAX_ENTRIES];
	unsigned	regn;

	regs = dag_regs(dagfd);

	/* Find MAR */
	regn=0;
	if ((dag_reg_table_find(regs, 0, DAG_REG_MAR, result, &regn)) || (!regn))
		mar_base = 0;
	else
		mar_base = DAG_REG_ADDR(*result);

	if(!mar_base)
		dagutil_panic("No MAR found!\n");

	/* Find HDIM */
	regn = 0;
	if ((dag_reg_table_find(regs, 0, DAG_REG_HDIM, result, &regn)) || (!regn))
		hdim_base = 0;
	else
		hdim_base = DAG_REG_ADDR(*result);
}


#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagvcam_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */
