/*
 * Copyright (c) 2002-2005 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology and no part  of it
 * may be redistributed, published or disclosed except as outlined in the
 * written contract supplied with this product.
 *
 * $Id: pbmbug.c 5260 2006-09-04 03:27:44Z sfd $
 */

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


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: pbmbug.c 5260 2006-09-04 03:27:44Z sfd $";
static const char* const kRevisionString = "$Revision: 5260 $";


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

static int show_rx = 1;
static int show_tx = 0;

static long pbm_module_addr = 0;
static int version = 0;

typedef enum stream
{
	RX = 0,
	TX = 1
} stream_t;

typedef union dagpbm_mkI_blk {
	dagpbm_mkI_t	pbm;
	/* for the MKI pbm the register blocks are at 0x60 apart so we force this union
	to be of size 0x60.*/
	unsigned char	space[0x60];
} dagpbm_mkI_blk_t;



enum
{
	CLA_HELP,
	CLA_VERSION,
	CLA_VERBOSE,
	CLA_DEVICE,
	CLA_RXREG,
	CLA_TXREG
};

/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr);
static int process_cmdline(int argc, char* argv[]);
static unsigned long memused(dagpbm_mkI_t* pbm, stream_t stream);
static unsigned long memused_mkII(dagpbm_mkII_stream_block_t* stream_block, stream_t stream);
static void display_control_status(dagpbm_mkI_t* mkI, stream_t stream);
static void display_control_status_mkII(dagpbm_mkII_stream_block_t* mkII, stream_t stream);
static void parse(void);
static void parse_mkII(void);


int
pbmbug_main(int argc, char *argv[])
{
	int dagfd;
	int count;
	int commandline_result = -1;
	uint8_t* iom = 0;
	dag_reg_t regs[DAG_REG_MAX_ENTRIES];

	dagutil_set_progname("pbmbug");

	/* 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));
	}

	commandline_result = process_cmdline(argc, argv);
	if (-1 != commandline_result)
	{
		return commandline_result;
	}

	dagfd = dag_open((char*) dagname);
	if (dagfd < 0)
		dagutil_panic("dag_open failed: %s\n", strerror(errno));
	
	iom = dag_iom(dagfd);
	
	/* try the enumeration table */
	dagutil_verbose("Searching for PBM\n");
	dagutil_verbose("Using the enumeration table\n");
	if ((count = dag_reg_find((char *)iom, DAG_REG_PBM, regs)) < 1)
	{
		dagutil_panic("Failed to find PBM module\n");
		return EXIT_FAILURE;
	}
	dagutil_verbose("Found PBM module at address 0x%.8x\n", regs[0].addr);
	pbm_module_addr = (long)(iom + regs[0].addr);
	version = regs[0].version;

	switch(regs[0].version) {
	case 0:
		parse();
		break;
	case 1:
		parse_mkII();
		break;
	default:
		dagutil_panic("Unknown PBM version %d\n", regs[0].version);
	}
	
	return EXIT_SUCCESS;
}


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

static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("pbmbug - Endace DAG card PCI Burst Manager debugging shell.\n");
	printf("Usage: pbmbug [options]\n");
	dagclarg_display_usage(clarg, stdout);
}

static int
process_cmdline(int argc, char* argv[])
{
	ClArgPtr clarg = NULL;
	int argindex;
	int clarg_result;
	int code;
	FILE *errorfile = NULL;

	/* 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, "show rx registers.", "--rxreg", 'r', CLA_RXREG);
	dagclarg_add(clarg, "show tx registers.", "--txreg", 't', CLA_TXREG);

	/* 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_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_RXREG:
				show_rx++;
				break;

			case CLA_TXREG:
				show_tx++;
				show_rx--;
				break;

			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;
	}
	
	return -1;
}

static unsigned long
memused_mkII(dagpbm_mkII_stream_block_t* stream_block, stream_t stream)
{
	unsigned long head, tail, used = 0;

	tail = stream ? stream_block->record_pointer : stream_block->limit_pointer;
	head = stream ? stream_block->limit_pointer  : stream_block->record_pointer;

	if (tail < head || ( stream && tail == head) )
		used = head - tail;
	else
		used = stream_block->mem_size - (tail - head);

	return used - (stream?0:8);
}

// NB: dir = 0 for rx, dir = 1 for tx
static unsigned long
memused(dagpbm_mkI_t* pbm, stream_t stream)
{
	unsigned long head, tail, used = 0;

	tail = stream ? pbm->record_pointer : pbm->limit_pointer;
	head = stream ? pbm->limit_pointer  : pbm->record_pointer;

	if (tail < head || ( stream && tail == head) )
		used = head - tail;
	else
		used = pbm->mem_size - (tail - head);

	return used - (stream?0:8);
}

static void
display_control_status_mkII(dagpbm_mkII_stream_block_t* mkII, stream_t stream)
{
	unsigned mask;

	for( mask = 1 ; mask <= DAGPBM_DEAD ; mask <<= 1 )
	{
		switch(mkII->stream_status & mask) 
		{
			case 0:
				continue;
			case DAGPBM_PAUSED:
				printf("paused ");
				break;
			case DAGPBM_AUTOWRAP:
# ifdef LEGACY_PBM
				printf("wrap ");
# endif
				break;
			case DAGPBM_FLUSH:
# ifdef LEGACY_PBM
				printf("flush ");
# endif
				break;
			case DAGPBM_BYTESWAP:
# ifdef LEGACY_PBM
				printf("bs ");
# endif
				break;
			case DAGPBM_SAFETY:
				printf("safety ");
				break;
			case DAGPBM_WIDEBUS:
# if 0
				// NB: Some of these exist only in the first CS reg
				if( i==0 ) printf("wide ");
# endif
				break;
			case DAGPBM_SYNCL2R:
				printf("dp_reset ");
				break;
			case DAGPBM_REQPEND:
				if( stream==0 ) printf("req ");
				break;
			case DAGPBM_DEAD:
				if( stream==0 ) printf("dead ");
				break;
			default:
				dagutil_panic("internal error %s line %u\n", __FILE__, __LINE__);
		}
# ifdef LEGACY_PBM
		if(copy.underover)
			printf("uo=0x%.8x ", copy.underover);
# endif
	}
	printf("\n");
}

static void
display_control_status(dagpbm_mkI_t* mkI, stream_t stream)
{
	unsigned mask;

	for( mask = 1 ; mask <= DAGPBM_DEAD ; mask <<= 1 )
	{
		switch(mkI->control_status & mask) 
		{
			case 0:
				continue;
			case DAGPBM_PAUSED:
				printf("paused ");
				break;
			case DAGPBM_AUTOWRAP:
# ifdef LEGACY_PBM
				printf("wrap ");
# endif
				break;
			case DAGPBM_FLUSH:
# ifdef LEGACY_PBM
				printf("flush ");
# endif
				break;
			case DAGPBM_BYTESWAP:
# ifdef LEGACY_PBM
				printf("bs ");
# endif
				break;
			case DAGPBM_SAFETY:
				printf("safety ");
				break;
			case DAGPBM_WIDEBUS:
# if 0
				// NB: Some of these exist only in the first CS reg
				if( i==0 ) printf("wide ");
# endif
				break;
			case DAGPBM_SYNCL2R:
				printf("dp_reset ");
				break;
			case DAGPBM_REQPEND:
				if( stream==0 ) printf("req ");
				break;
			case DAGPBM_DEAD:
				if( stream==0 ) printf("dead ");
				break;
			default:
				dagutil_panic("internal error %s line %u\n", __FILE__, __LINE__);
		}
# ifdef LEGACY_PBM
		if(copy.underover)
			printf("uo=0x%.8x ", copy.underover);
# endif
	}
	printf("\n");
}

static void
parse_mkII(void)
{
	dagpbm_mkII_t* global_reg_block = NULL;
	dagpbm_mkII_stream_block_t copy_stream;
	dagpbm_mkII_stream_block_t* pbm_stream = NULL;
	dagpbm_mkII_t copy_global_reg_block;
	
	int i = 0;
	int receive_streams = 0;
	int transmit_streams = 0;
	int count = 0;
	long mem_used = 0;
	
	global_reg_block = (dagpbm_mkII_t*)pbm_module_addr;
	
	receive_streams = ((global_reg_block->control_status & 0xf00000) >> 20);
	printf("receive streams %d\n", receive_streams);
	
	transmit_streams = ((global_reg_block->control_status & 0xf000000) >> 24);
	printf("transmit streams %d\n", transmit_streams);
	
	while (1)
	{
		for (i = 0; i < receive_streams; i++)
		{
			pbm_stream = (dagpbm_mkII_stream_block_t*)(pbm_module_addr + (0x40 + 0x40*i*2));
			if ((count++ % 10) == 0)
			{
				printf("\nstream\t\tused\tutil\trecord_pointer\tlimit_pointer\tlimit_event\tcontrol_status\n");
			}
			if (show_rx)
			{
				/* copy it because registers might change while reading */
				copy_stream = *pbm_stream;
				copy_global_reg_block = *global_reg_block;
				mem_used = memused_mkII(&copy_stream, RX);
				printf("stream:%d %s\t%4ld %3.1f%%\t0x%.12x\t0x%.11x\t%11d\t0x%.12x\t", 
					i*2, "rx:", mem_used,
					(mem_used*100.0)/copy_stream.mem_size,
					copy_stream.record_pointer, copy_stream.limit_pointer,
					copy_stream.limit_count,
					copy_stream.stream_status);
				display_control_status_mkII(&copy_stream, RX);
			}
		}
		for (i = 0; i < transmit_streams; i++)
		{
			pbm_stream = (dagpbm_mkII_stream_block_t*)(pbm_module_addr + (long)(0x80 + 0x40*i*2));
			if ((count++ % 10) == 0)
			{
				printf("\nstream\t\tused\tutil\trecord_pointer\tlimit_pointer\tlimit_event\tcontrol_status\n");
			}
			if (show_tx)
			{
				copy_stream = *pbm_stream;
				copy_global_reg_block = *global_reg_block;
				mem_used = memused_mkII(&copy_stream, TX);
				printf("stream:%d %s\t%8ld\t%5.1f%%\t0x%.8x\t0x%.8x\t0x%.8x\t", 
					i*2+1, "tx:", mem_used,
					(mem_used*100.0)/copy_stream.mem_size,
					copy_stream.record_pointer, copy_stream.limit_pointer,
					copy_global_reg_block.control_status);
				display_control_status_mkII(&copy_stream, TX);
			}
		}
		sleep(1);
	}
}

static void
parse(void)
{
	dagpbm_mkI_blk_t* pbm_blk;
	dagpbm_mkI_t copy_mkI;

	int count = 0;
	long mem_used = 0;

	pbm_blk = (dagpbm_mkI_blk_t*)pbm_module_addr;
	/* copy it because registers might change while reading */
	while (1)
	{
		if ((count++ % 10) == 0)
		{
			printf("\n\tused\tutil\trecord_pointer\tlimit_pointer\tlimit_event\tcontrol_status\n");
		}
		if (show_rx)
		{
			copy_mkI = pbm_blk[0].pbm;
			mem_used = memused(&copy_mkI, RX);
			printf("%s\t%4ld\t%3.1f%%\t0x%.12x\t0x%.11x\t%11d\t0x%.12x\t", 
				"rx:", mem_used,
				(mem_used*100.0)/copy_mkI.mem_size,
				copy_mkI.record_pointer, copy_mkI.limit_pointer,
				copy_mkI.limit_count,
				copy_mkI.control_status);
			display_control_status(&copy_mkI, RX);
		}
		if (show_tx)
		{
			copy_mkI = pbm_blk[1].pbm;
			mem_used = memused(&copy_mkI, TX);
			printf("%s\t%8ld\t%5.1f%%\t0x%.8x\t0x%.8x\t0x%.8x\t", 
				"tx:", mem_used,
				(mem_used*100.0)/copy_mkI.mem_size,
				copy_mkI.record_pointer, copy_mkI.limit_pointer,
				copy_mkI.control_status);
			display_control_status(&copy_mkI, TX);
		}
		sleep(1);
	}
}


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