/*
 * 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: dagdetect.c 13133 2010-08-25 23:33:40Z sfd $
 */

/* executable alternative to parsing /proc/dag to detect DAGs from a shell */

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


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagdetect.c 13133 2010-08-25 23:33:40Z sfd $";
static const char* const kRevisionString = "$Revision: 13133 $";


#define DAG_MAX_BOARDS (1 << DAGMINORBITS)


/* GPP register offsets */
enum
{
	GPP_config  = 0x0000 /* from uGppBase */
};


/* Commandline argument codes. */
enum
{
	CLA_COUNT_DAGS,
	CLA_COUNT_PORTS,
	CLA_DEVICE,
	CLA_HELP,
	CLA_NAME_DAGS,
	CLA_PATH_NAME,
	CLA_VERBOSE,
	CLA_VERSION
};


static char uDagNameBuffer[DAGNAME_BUFSIZE] = "dag0";
static char uDagName[DAGNAME_BUFSIZE];


/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr clarg);
static unsigned int dagiomread(volatile uint8_t * iom_address, unsigned int addr);
static unsigned int dag_device_ports(volatile uint8_t * iom_address, dag_reg_t * regs);
static void count_dag_cards(void);
static void name_dag_card(const char* dagname, int path_name);
static void count_card_ports(const char* dagname);
static void enumerate_dag_cards(void);


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


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagdetect - shell scripting convenience utility.\n");
	printf("Usage: dagdetect [options]\n");
	dagclarg_display_usage(clarg, stdout);
}


static unsigned int
dagiomread(volatile uint8_t * iom_address, unsigned int offset)
{
	return *(volatile unsigned int*) (iom_address + offset);
}


static unsigned int
dag_device_ports(volatile uint8_t * iom_address, dag_reg_t *regs)
{
	unsigned int reg_count = 0;
	int ports = 1;
	dag_reg_t result[DAG_REG_MAX_ENTRIES];
	
	/* Find GPP */
	if ((0 == dag_reg_table_find(regs, 0, DAG_REG_GPP, result, &reg_count)) && (reg_count > 0))
	{
		uint32_t gpp_base = DAG_REG_ADDR(*result);
		
		if (gpp_base)
		{
			ports = 1 + ((dagiomread(iom_address, gpp_base + GPP_config) >> 6) & 0x03);
		}
	}
	
	return ports;
}


static void
count_dag_cards(void)
{
	int count = 0;
	int i;
	
	/* If no commandline options are given, enumerate all DAG cards. */
	for (i = 0; i < DAG_MAX_BOARDS; i++)
	{
		int dagfd = -1;
		int dagstream = -1;
		char dagname_buf[DAGNAME_BUFSIZE];
		char dagname[DAGNAME_BUFSIZE];
		
		snprintf(dagname_buf, sizeof(dagname_buf), "dag%d", i);
		if (-1 == dag_parse_name(dagname_buf, dagname, sizeof(dagname), &dagstream))
		{
			dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
		}
		
		dagfd = dag_open(dagname);
		if (dagfd >= 0)
		{
			count++;
			dag_close(dagfd);
		}
	}
	
	printf("%u\n", count);
}


static void
count_card_ports(const char* dagname)
{
	volatile uint8_t * iom_address = NULL;
	dag_reg_t * regs = NULL;
	int dagfd;

	dagfd = dag_open((char*) dagname);
	if (dagfd < 0)
	{
		dagutil_panic("could not open DAG card '%s'.\n", dagname);
		exit(EXIT_FAILURE);
	}

	iom_address = dag_iom(dagfd);
	regs = dag_regs(dagfd);
	
	printf("%u\n", dag_device_ports(iom_address, regs));
	dag_close(dagfd);
}


static void
name_dag_card(const char* dagname, int path_name)
{
	int dagfd;
	daginf_t * inf;
	char namebuf[32];

	dagfd = dag_open((char*) dagname);
	if (dagfd < 0)
	{
		dagutil_panic("could not open DAG card '%s'.\n", dagname);
		exit(EXIT_FAILURE);
	}

	inf = dag_info(dagfd);
	
	snprintf(namebuf, 32, "%s", dag_device_name(inf->device_code, 1));
	
	if (path_name)
	{
		/* Replace whitespace with underscores. */
		int i;
		
		for (i = 0; i < strlen(namebuf); i++)
		{
			if (isspace(namebuf[i]))
			{
				namebuf[i] = '_';
			}
		}
	}
	
	printf("%s\n", namebuf);
	dag_close(dagfd);
}


static void
enumerate_dag_cards(void)
{
	int count = 0;
	int i;
	
	/* If no commandline options are given, enumerate all DAG cards. */
	for (i = 0; i < DAG_MAX_BOARDS; i++)
	{
		int dagfd = -1;
		int dagstream = -1;
		char dagname_buf[DAGNAME_BUFSIZE];
		char dagname[DAGNAME_BUFSIZE];
		
		snprintf(dagname_buf, sizeof(dagname_buf), "dag%d", i);
		if (-1 == dag_parse_name(dagname_buf, dagname, sizeof(dagname), &dagstream))
		{
			dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
		}
		
		dagfd = dag_open(dagname);
		if (dagfd >= 0)
		{
			volatile uint8_t * iom_address = NULL;
			dag_reg_t * regs = NULL;
			daginf_t * inf;
			char rev[20] = {0};
			
			regs = dag_regs(dagfd);
			iom_address = dag_iom(dagfd);
			inf = dag_info(dagfd);
			
			if (inf->brd_rev)
			{
				snprintf(rev, sizeof(rev), " Rev %d", inf->brd_rev);
			}
			
			printf("%d\t%u\t%04X\t%s%s\n", i, dag_device_ports(iom_address, regs), inf->device_code, dag_device_name(inf->device_code, 1), rev);
			count++;
			dag_close(dagfd);
		}
	}

	exit(count);
}


int
dagdetect_main(int argc, const char ** argv)
{
	ClArgPtr clarg;
	FILE * errorfile = NULL;
	int count_dags = 0;
	int count_ports = 0;
	int name_dag = 0;
	int path_name = 0;
	int index;
	int argindex;
	int code;
	int result;
	int dagstream;

	dagutil_set_progname("dagdetect");

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

	/* Set up the command line arguments. */
	clarg = dagclarg_init(argc, argv);
	dagclarg_add(clarg, "Write the number of DAG cards in the system to standard output.", "--count", 'c', CLA_COUNT_DAGS);
	dagclarg_add(clarg, "Write the number of physical interfaces on a specified DAG card to standard output.", "--ifaces", 'i', CLA_COUNT_PORTS);
	dagclarg_add(clarg, "Write the name of a specified DAG card to standard output.", "--name", 'n', CLA_NAME_DAGS);
	dagclarg_add(clarg, "Write the name of a specified DAG card in a form suitable for use as a filesystem path (i.e. no whitespace).  Implies '--name'.", "--path", 'p', CLA_PATH_NAME);

	dagclarg_add(clarg, "This page.", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');

	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "Display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", uDagNameBuffer, DAGNAME_BUFSIZE, CLA_DEVICE);

	/* Parse the command line arguments. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
			case CLA_COUNT_DAGS:
				count_dags = 1;
				break;
	
			case CLA_COUNT_PORTS:
				count_ports = 1;
				break;
	
			case CLA_DEVICE:
				if (-1 == dag_parse_name(uDagNameBuffer, uDagName, DAGNAME_BUFSIZE, &dagstream))
				{
					dagutil_panic("dag_parse_name(%s): %s\n", uDagNameBuffer, strerror(errno));
				}
				dagutil_verbose_level(2, "device=%s\n", uDagName);
				break;

			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				break;
	
			case CLA_NAME_DAGS:
				name_dag = 1;
				break;
	
			case CLA_PATH_NAME:
				name_dag = 1;
				path_name = 1;
				break;
	
			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				errorfile = stderr;
				break;
	
			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;
				break;
	
			default:
				if (argv[argindex][0] == '-')
				{
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[argindex]);
					print_usage(clarg);
					return EXIT_FAILURE;
				}
				break;
		}

		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == result)
	{
		/* Error occurred. */
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]); 
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	
	/* Display unprocessed arguments if verbose flag was given. */
	argv = (const char**) dagclarg_get_unprocessed_args(clarg, &argc);
	if ((NULL != argv) && (0 < argc) && (1 < dagutil_get_verbosity()))
	{
		for (index = 0; index < argc; index++)
		{
			dagutil_verbose_level(2, "unprocessed argument: '%s'\n", argv[index]);
		}
		dagutil_verbose_level(2, "unprocessed arguments may be consumed by card-specific modules\n");
	}
	
	/* ClargPtr should no longer be necessary. */
	dagclarg_dispose(clarg);

	if (count_dags)
	{
		/* Print the number of DAG cards found to standard output. */
		count_dag_cards();
	}
	else if (count_ports)
	{
		/* Print the number of ports on the specified DAG card to standard output. */
		count_card_ports((const char*) uDagName);
	}
	else if (name_dag)
	{
		/* Print the name of the specified DAG card to standard output. */
		name_dag_card((const char*) uDagName, path_name);
	}
	else
	{
		/* Old behaviour. */
		enumerate_dag_cards();
	}
	
	return EXIT_SUCCESS;
}


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