/*
 * Copyright (c) 2002-2004 Endace Measurement Systems Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Measurement Systems and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagreset.c 13457 2010-12-03 01:12:25Z karthik.sharma $
 */

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

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagreset.c 13457 2010-12-03 01:12:25Z karthik.sharma $";
static const char* const kRevisionString = "$Revision: 13457 $";


#define DAGNAME_BUFSIZE 128
static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static int dagstream;
static int dagfd;
static int uForce = 0;
int reset_method = 3;
/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr);

/* Commandline argument codes. */
enum
{
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERSION,
	CLA_VERBOSE,
	CLA_REBOOT,
	CLA_CLKRST,
	CLA_RESET_METHOD,
	CLA_FORCE
};
	
	

static void
print_version(void)
{
	printf("dagreset (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}

static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagreset - Endace DAG card reset utility.\n");
	printf("Usage: dagreset [-hv] [-d device]\n");
	dagclarg_display_usage(clarg, stdout);
}


#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

/**
 * Non-windows only function for fully reseting the DAG card. This is different
 * from just sending an IOCTL command, because on the newer PCI-e cards there
 * is a new method involving the general purpose register (0x88).
 *
 * Currently there are 2 ways of loading the stable half:
 *
 * 1. The original way (still used on most cards), is to send an IOCTL to the
 *    driver, which issues a reset and then reloads the PCI config space.
 *
 * 2. The other way that is used on some PCI-e cards is to setup the general
 *    purpose register to tell the firmware when the link status goes down
 *    we should re-program either the current or stable half. This requires
 *    support from the driver to change the link status. On linux this
 *    done via the the standard DAGIOCRESET IOCTL with the DAGRESET_FULL arg.
 *    On windows the whole process is done by the ChangeFirmwareHalf function.
 *    The new format of the general reset register (0x88) is as follows:
 *        bit       use
 *        31        L2_RST to design
 *        30        L1_RST to design / CPLD
 *        27        on next link down, reprogram
 *        26        use new reprogramming mechanism
 *        25        reprogram to Current / User half
 *        24        reprogram to Stable / Factory half
 *        23        read '1' if running out of the Current Half
 *        20        always read as '1' to indicate new programming available. If 	
 *                  read as 0 then only old programming available.
 *
 *
 * To determine which method to use, we look at bit 20 of the general
 * register (register 0x88), if it is set we use method 2 above.
 *
 *
 * @param[in]  dagfd     File descriptor for the DAG driver, this need not be
 *                       opened with the dag_open API function.
 * 
 * @returns              An errorcode indicating success or failure.
 * @retval     0         Function succeeded.
 * @retval     EIO       Failed to find the correct /dev/dag* entry.
 * @retval     ENODEV    Failed to parse the /dev/dag* entry.
 * @retval     errno     One of the internal system functions failed.
 *
 */
static int
dagreset_full_reset(int dagfd, const char *dagname_p)
{
	regex_t             reg;
	regmatch_t          match;
	char		        buf[16];
	dag_reg_t           result[DAG_REG_MAX_ENTRIES];
	volatile uint8_t  * iom_p;
	volatile uint32_t * general_reg_p;
	int                 dagiomfd;
	daginf_t            daginf;
	int                 magic = DAGRESET_FULL;
	int                 r;
	volatile uint32_t * interrupt_mask_p;
	volatile uint32_t * interrupts_p;
	volatile uint32_t * reprogram_control_p;
	volatile uint32_t * reset_control_p;
	int i;
	//int method = 4;

	/* This is a bit messy .. but it goes a little like this ... the dag card handle
	 * supplied here need not be opened with dag_open, this is because of the way
	 * dagreset works. The downside of this is that there is no sheep / herd structure
	 * configured for this device, this is a problem when getting the IO memory pointer.
	 * To get around this, we use the dag_clone function to open the dagiom handle
	 * and use the IOM pointer from there. This is only needed on non-Windows machines.
	 */

	if(regcomp(&reg, "/dev/dag(iom|mem|arm|ram)*[0-9]", REG_EXTENDED) != 0)
	{
		dagutil_error("regcomp %s\n", strerror(errno));
		return EIO;
	}

	if((r = regexec(&reg, dagname_p, 1, &match, 0)) !=0)
	{
		dagutil_error("regexec %s\n", strerror(errno));
		return ENODEV;
	}
	
	regfree(&reg);

	snprintf(buf, 16, "/dev/dagiom%c%c", dagname_p[match.rm_eo-1],dagname_p[match.rm_eo]);
	buf[15] = '\0';

	if((dagiomfd = open(buf, O_RDWR)) < 0)
	{
		dagutil_error("dag_clone dagfd for dagiom: %s\n", strerror(errno));
		return errno;
	}

	if(ioctl(dagfd, DAGIOCINFO, &daginf) < 0)
	{
		dagutil_error("ioctl DAGIOCINFO failed: %s\n", strerror(errno));
		return errno;
	}

	if((iom_p = mmap(NULL, daginf.iom_size, PROT_READ | PROT_WRITE, MAP_SHARED, dagiomfd, 0)) == MAP_FAILED)
	{
		dagutil_error("mmap failed: %s\n", strerror(errno));
		return errno;
	}
	/* Sanity check the pointer is correct */
	if (iom_p == NULL)
	{
		dagutil_error("IO memory pointer invalid\n");
		return EINVAL;
	}

	/* Check for the firmware revision to see if we can use the safe PCI-E load current */
	if (dag_reg_find((char*)iom_p, DAG_REG_GENERAL, result) < 1)
	{
		dagutil_error("dag_reg_find failed\n");
		return EINVAL;
	}
	
	general_reg_p = (volatile uint32_t *) ((uintptr_t)iom_p + result[0].addr + 8);

	/* make sure there are no outstanding interrupts and disable interrupts*/
	//Disable Interrupts
	interrupt_mask_p = (volatile uint32_t *) ((uintptr_t)iom_p + result[0].addr + 0xc);
	*interrupt_mask_p = 0x0;
	usleep(1000);
	
	//check for outstanding interrupts 
	interrupts_p = (volatile uint32_t *) ((uintptr_t)iom_p + result[0].addr + 0);
	for(i=0; i<10; i++) {	
		if( ((*interrupts_p & 0xFFFF) == 0xffff) || ( (*interrupts_p & 0xFFFF) == 0x0) ) 
			break;
		dagutil_warning("Warning: this message is acceptable but should not happened very often 0x%x\n",*interrupts_p & 0xffff);
		usleep(50000);
	};
	if(dag_reg_find((char*)iom_p,DAG_REG_ROM,result)< 1)
	{
		dagutil_error("dag reg find failed \n");
		return EINVAL;
	}

	if(result[0].version == 0x2)
	{
		reprogram_control_p = (volatile uint32_t*) ((uintptr_t)iom_p + result[0].addr + DAGROM_REPROGRAM_CONTROL);
		reset_control_p = (volatile uint32_t*)((uintptr_t)iom_p + result[0].addr + DAGROM_RESET_CONTROL);
		/*Load the stable Half */
		*reprogram_control_p = 0;
		
		if(reset_method == 1)
        {
			if(dagutil_get_verbosity() > 0)
				printf("Reboot reset method \n");
			
			*reset_control_p = 2; /*using method ringo*/
			magic = DAGRESET_REBOOT;
			if (ioctl(dagfd, DAGIOCRESET, &magic) < 0)
				dagutil_error("DAGIOCRESET DAGRESET_FULL: %s\n", strerror(errno));
		}
		#if 0
	        else if(reset_method == 2)
        	{
               		dagutil_verbose_level(3,"reset method %d\n",reset_method);
					*reset_control_p = 4; /*using method George/Dave*/
				/* Use the extended PCI-e method Note the actual implementation is in the Driver for Linux
				* fir WIndows dagreset at the current stage do not support the PCI express new mecahnisms 
				* FIXME: 
				*/
				if ( /*(result[0].version != 0x1) ||*/ (*general_reg_p & BIT20) )
				{
					*general_reg_p |= (BIT27 | BIT26 | BIT24);
					*general_reg_p &= ~BIT25;
				}

				magic = DAGRESET_FULL;
				if (ioctl(dagfd, DAGIOCRESET, &magic) < 0)
					dagutil_error("DAGIOCRESET DAGRESET_FULL: %s\n", strerror(errno));	
			}
		#endif
		else if(reset_method == 2)
		{
			if(dagutil_get_verbosity() > 0)
				printf("reset method George (Upstream bridge undergoes Link Disable + Link Retrain)\n");
			/*Reprogram the FPGA by method George.
			Arm the FPGA to be reprogrammed by method George.
			Then instruct the upstream bridge to undergo Link Disable + Link Retrain.
			*/
			*reset_control_p = 4; /*using method George/Dave*/
			/*Arm the FPGA to be reprogrammed by Method George when it occours next.*/
			magic = DAGRESET_GEORGE;
			if(ioctl(dagfd,DAGIOCRESET,&magic) < 0)
				dagutil_error("DAGIOCRESET DAGRESET_GEORGE: %s\n",strerror(errno));

		}
		else if(reset_method == 3)
		{
			if(dagutil_get_verbosity() > 0)
				printf("reset method Dave (Upstream bridge undergoes secondary bus hot reset)\n");
			/*Arm the FPGA to be reprogrammed by method Dave.*/
			/*The instruct the upstream bridge to undergo Secondry Bus Hot Reset.*/
			*reset_control_p = 4; /*using method George/Dave*/
			/*Arm the FPGA to be reprogrammed by method Dave when it occours next*/
			magic = DAGRESET_DAVE;
			if(ioctl(dagfd,DAGIOCRESET,&magic) < 0)
				dagutil_error("DAGIOCRESET DAGRESET_DAVE: %s\n",strerror(errno));

		}

	}
	/* Use the extended PCI-e method Note the actual implementation is in the Driver for Linux
	* fir WIndows dagreset at the current stage do not support the PCI express new mecahnisms FIXME: 
	*/
	else
	{
		if ( /*(result[0].version != 0x1) ||*/ (*general_reg_p & BIT20) )
		{
			*general_reg_p |= (BIT27 | BIT26 | BIT24);
			*general_reg_p &= ~BIT25;
		}
		/* Tell the driver to reset the card */
		magic = DAGRESET_FULL;
		if (ioctl(dagfd, DAGIOCRESET, &magic) < 0)
			dagutil_error("DAGIOCRESET DAGRESET_FULL: %s\n", strerror(errno));

	}

	/* On non-Windows machines we had to open the dagiom object and memory map in the
	 * IO memory space manually, so now we need to unmap the memory and close the
	 * dagiom file descriptor.
	 */
	(void)close(dagiomfd);
	
	if (iom_p != NULL)
		munmap((void*)iom_p, daginf.iom_size);
		
	return 0;
}

#endif

int
dagreset_main(int argc, char *argv[])
{
	int magic = DAGRESET_FULL;
	FILE* errorfile = NULL;
	daginf_t daginf;
	ClArgPtr clarg;
	int argindex;
	int result;
	int reprogram_method = 0;
	int code;
#if defined(_WIN32)
	ULONG BytesTransferred;
#endif /* _WIN32*/

	dagutil_set_progname("dagreset");
	
	/* 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_string(clarg, "DAG device to use (default: dag0)", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg,	"reboot", "--reboot", 'r', CLA_REBOOT);
	dagclarg_add(clarg, "reset clock", "--clkreset", 'u', CLA_CLKRST);
	dagclarg_add_int(clarg, "Specify the Reset method to be used. [1.Ringo 2.Geroge(Link Disable + Link Retrain) 3.Dave(Secondary Bus Hot Reset)]","--reset-method" ,'m',"reprogram method",&reprogram_method,CLA_RESET_METHOD);
	dagclarg_add(clarg, "Force the reset.  Dangerous.", "--force", 0, CLA_FORCE);
	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch(code)
		{
			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				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_REBOOT:
				magic = DAGRESET_REBOOT;
				break;
			
			case CLA_FORCE:
				uForce = 1;
				dagutil_verbose("forcing reset (potentially dangerous)\n");
				break;
			
			case CLA_VERBOSE:
			       dagutil_inc_verbosity();
   			       errorfile = stderr;
			       break;
			case CLA_RESET_METHOD:
				reset_method = reprogram_method; 
				break;
			case CLA_CLKRST:
				magic = DAGRESET_DUCK;
				break;
			default:
				dagutil_panic("unknown option, see -h for help on usage\n");
				/* never returns */
				break;
		}
		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

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

#if defined(__FreeBSD__) || defined(__linux__) || defined (__NetBSD__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	if((dagfd = open(dagname, O_RDWR)) < 0)
		dagutil_panic("open %s: %s\n", dagname, strerror(errno));

	if(ioctl(dagfd, DAGIOCINFO, &daginf) < 0)
		dagutil_panic("ioctl %s: %s\n", dagname, strerror(errno));

	if (daginf.device_code == PCI_DEVICE_ID_DAG8_20F)
	{
		dagutil_warning("Reset of 2nd core of the 8.2x requested however this is not needed - reset the 1st core instead\n");
	    close(dagfd);
		return EXIT_SUCCESS;
	}

	/* if we are performing a full reset (DAGRESET_FULL), then we should call the new
	 * dagreset_full_reset() function. This is because it has supports for the new PCI-e
	 * loading technique as well as the old style. If the function fails then the IOCTL
	 * is still called as a backup, this is to try and maintain some sort of consistant
	 * behaviour.
	 */

	if(1 == (uint8_t)programming_supported(&daginf) ||  (uForce == 1))
	{
		if(uForce == 1)
			fprintf(stderr,"forcing dagreset.This my produce some unexpected results on some motherboards.\n");
		
		if((magic != DAGRESET_FULL) || (dagreset_full_reset(dagfd, dagname) != 0))
		{
			if(ioctl(dagfd, DAGIOCRESET, &magic) < 0)
				dagutil_panic("ioctl DAGIOCRESET %s: %s\n", dagname, strerror(errno));
		}
	}
	else
	{
		dagutil_warning("dagreset not supported on this card.Please use the power on image programming table.\n");
	}

	if (daginf.device_code == PCI_DEVICE_ID_DAG8_20E)
	{
		int dagfd_2nd_core = dag82x_open_2nd_core(dagname);
		if(ioctl(dagfd_2nd_core, DAGIOCRESET, &magic) < 0)
			dagutil_panic("Failed to reset 2nd core of the 8.2x: ioctl DAGIOCRESET %s: %s\n", dagname, strerror(errno));
		close(dagfd_2nd_core);
	}
#elif defined(_WIN32) /* _WIN32 */

	if((dagfd = dag_open(dagname)) < 0)
	  panic("open %s: %s\n", dagname, strerror(errno));
    
    /* Get device info */
	if(DeviceIoControl(dag_gethandle(dagfd),
		IOCTL_GET_DAGINFO,
		NULL,
		0,
		&daginf,
		sizeof(daginf_t),
		&BytesTransferred,
		NULL) == FALSE)
		panic("dag_open DeviceIoControl IOCTL_GET_DAGINFO\n");
    
    if (daginf.device_code == PCI_DEVICE_ID_DAG8_20F)
    {
        dagutil_warning("Reset of 2nd core of the 8.2x requested however this is not needed - reset the 1st core instead\n");
        dag_close(dagfd);
		return EXIT_SUCCESS;
    }
    
	switch(magic) {
	//stable image
	case DAGRESET_FULL:
	dagrom_loadstable(dagfd);
        if (daginf.device_code == PCI_DEVICE_ID_DAG8_20E)
        {
            HANDLE dag_core2_fd = dag82x_open_2nd_core(dagname);
            if (!dag_core2_fd)
            {
                dagutil_panic("Could not open 2nd core of the 8.2x\n");
                return EXIT_FAILURE;
            }   
            if (DeviceIoControl(dag_core2_fd, IOCTL_RESET, NULL, 0, NULL, 0, &BytesTransferred, NULL) == FALSE)
		        dagutil_panic("loadcurrent DAGIOCRESET DAGRESET_REBOOT\n");
        }
		break;
	//current image 
	case DAGRESET_REBOOT:
		if(DeviceIoControl(dag_gethandle(dagfd),
				   IOCTL_REBOOT,
				   NULL,
				   0,
				   NULL,
				   0,
				   &BytesTransferred,
				   NULL) == FALSE)
			panic("DeviceIoControl dag IOCTL_REBOOT: %s\n", strerror(errno));
		if (daginf.device_code == PCI_DEVICE_ID_DAG8_20E)
        {
            HANDLE dag_core2_fd = dag82x_open_2nd_core(dagname);
            if (!dag_core2_fd)
            {
                dagutil_panic("Could not open 2nd core of the 8.2x\n");
                return EXIT_FAILURE;
            }   
            if (DeviceIoControl(dag_core2_fd, IOCTL_REBOOT, NULL, 0, NULL, 0, &BytesTransferred, NULL) == FALSE)
		        dagutil_panic("loadcurrent DAGIOCRESET DAGRESET_REBOOT\n");
        }
        break;

	case DAGRESET_DUCK:
		if(DeviceIoControl(dag_gethandle(dagfd),
				   IOCTL_DUCK_RESET,
				   NULL,
				   0,
				   NULL,
				   0,
				   &BytesTransferred,
				   NULL) == FALSE)
			panic("DeviceIoControl dag IOCTL_DUCK_RESET: %s\n", strerror(errno));
		if (daginf.device_code == PCI_DEVICE_ID_DAG8_20E)
        {
            HANDLE dag_core2_fd = dag82x_open_2nd_core(dagname);
            if (!dag_core2_fd)
            {
                dagutil_panic("Could not open 2nd core of the 8.2x\n");
                return EXIT_FAILURE;
            }   
            if (DeviceIoControl(dag_core2_fd, IOCTL_DUCK_RESET, NULL, 0, NULL, 0, &BytesTransferred, NULL) == FALSE)
		        dagutil_panic("loadcurrent DAGIOCRESET DAGRESET_REBOOT\n");
        }
        break;
	}
#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

	return EXIT_SUCCESS;
}


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