/******************************************************************************/
/*                              SCRAMNet GT                                   */
/******************************************************************************/
/*                                                                            */
/* Copyright (c) 2020 Curtiss-Wright Controls Electronic System, Inc.         */
/*     dtn_support@curtisswright.com 800-252-5601 (U.S. only) 937-252-5601    */
/*                                                                            */
/* This program 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             */
/* of the License, or (at your option) any later version.                     */
/*                                                                            */
/* See the GNU General Public License for more details.                       */
/*                                                                            */
/******************************************************************************/

#define APP_VER_STRING "GT Speed Test (gtspeed) rev. 1.02 (2005/03/14)"
#define APP_VxW_NAME gtspeed

/**************************************************************************/
/****** TAKING CARE OF ALL POSSIBLE OS TYPES - COPY-PASTE *****************/
/**************************************************************************/
/* control by PLATFORM_WIN, PLATFORM_UNIX, PLATFORM_VXWORKS */          /**/
#include <math.h>                                                       /**/
#include <stdio.h>                                                      /**/
#include <stdlib.h>                                                     /**/
#include <time.h>                                                       /**/
#include <string.h>                                                     /**/
#include <ctype.h>                                                      /**/
                                                                        /**/
#define PARSER                                                          /**/
#define MAIN_IN      int main(int argc, char **argv) {                  /**/
                                                                        /**/
#ifdef PLATFORM_WIN                                                     /**/
    #include <windows.h>                                                /**/
                                                                        /**/
#elif PLATFORM_VXWORKS                                                  /**/
    #undef PARSER                                                       /**/
    #undef MAIN_IN                                                      /**/
    #define VXL     40                                                  /**/
    #define MAIN_IN int APP_VxW_NAME(char *cmdLine) { \
            int argc; char argv0[]="gtspeed"; char *argv[VXL]={argv0};  /**/
    #define PARSER     argc = parseCls(cmdLine, argv);                  /**/
                                                                        /**/
static int parseCls(char *cLine, char *argv[VXL])                       /**/
{                                                                       /**/
    char *token, *ptr = NULL;                                           /**/
    int numTokens = 1;                                                  /**/
    char seps[] = " ,\t\n";                                             /**/
    if(cLine == NULL) return numTokens; /* no command line given */     /**/
    printf(" \b"); token=(char*)strtok_r(cLine,seps,&ptr);              /**/
    while(token != NULL)                                                /**/
    { argv[numTokens] = token;                                          /**/
      numTokens++;                                                      /**/
      token = (char*)strtok_r(NULL, seps, &ptr);                        /**/
    }                                                                   /**/
    return numTokens;                                                   /**/
}                                                                       /**/
#endif                                                                  /**/
/************ END OF TAKING CARE OF ALL POSSIBLE OS TYPES *****************/

/**************************************************************************/
/************** APPLICATION-SPECIFIC CODE STARTS HERE *********************/
/**************************************************************************/

#include "scgtapi.h"
#include "usys.h"

#define INPUT_OPTIONS \
    uint32  unit, interval, linkSpeed, pciSpeed, displayMode, helpLevel;

typedef struct
{
    INPUT_OPTIONS
    scgtHandle hDriver;
} iOptionsType;

static uint32 gtspeedDiffCounter(uint32 currCnt, uint32 lastCnt, uint32 maxCnt);
static void buildOptionsString( char *optionsString, iOptionsType *iO );
static void parseCommand(int argc, char **argv, iOptionsType *iO,
                         char *optionsString);
static void printHelpLevel1(iOptionsType *iO, char *optionsString);
static void printHelpLevel2();

#define D_NON_VRB  0x1

/**************************************************************************/
/*********************** G T S P E E D   C O D E **************************/
/**************************************************************************/

MAIN_IN

    iOptionsType iO;
    char optionsString[120];
    usysMsTimeType curTime;
    uint32 oscTicks[2], pciTicks[2];
    uint32 startMS1, startMS2, stopMS1, stopMS2, elapsedMS;
    uint32 freq, fract;

    PARSER;        /* prepare argc and agrv if not provided by OS (VxWorks) */

    parseCommand(argc, argv, &iO, optionsString);        /* parsing options */

    if(iO.helpLevel!=0) /*1, 2, 3, ...*/
    {
        printHelpLevel1(&iO, optionsString);
        return 0;
    }

    if (scgtOpen(iO.unit, &iO.hDriver) != SCGT_SUCCESS)
    {
        printf("gtspeed: could not open unit %i\n", iO.unit);
        return -1;
    }

    usysMsTimeStart(&curTime);

    usysMsTimeDelay( 1 );

    /* clear counters */
    startMS1 = usysMsTimeGetElapsed(&curTime);
    oscTicks[0] = scgtReadCR(&iO.hDriver, 0x40);
    pciTicks[0] = scgtReadCR(&iO.hDriver, 0x44);
    startMS2 = usysMsTimeGetElapsed(&curTime);

    usysMsTimeDelay( iO.interval );

    /* read counters */
    stopMS1 = usysMsTimeGetElapsed(&curTime);
    oscTicks[1] = scgtReadCR(&iO.hDriver, 0x40);
    pciTicks[1] = scgtReadCR(&iO.hDriver, 0x44);
    stopMS2 = usysMsTimeGetElapsed(&curTime);

    /* try for accurate elapsed time */
    elapsedMS = (stopMS2 + stopMS1)/2 - (startMS2 + startMS1)/2;
    usysMsTimeStop(&curTime);

    oscTicks[0] = gtspeedDiffCounter(oscTicks[1], oscTicks[0], 0xFFFFFFFF);
    pciTicks[0] = gtspeedDiffCounter(pciTicks[1], pciTicks[0], 0xFFFFFFFF);

    if( iO.linkSpeed )
    {
        oscTicks[0] *= 20;
        freq = (oscTicks[0]/elapsedMS)/1000000;
        fract = ((oscTicks[0]/elapsedMS)/10000)%100;

        if( !(iO.displayMode & D_NON_VRB) )
            printf("LINK(Gpbs): ");

        printf("%u.%.2u\n", freq, fract);
    }

    if( iO.pciSpeed )
    {
        freq = (pciTicks[0]/elapsedMS)/1000;
        fract = ((pciTicks[0]/elapsedMS)/10)%100;

        if( !(iO.displayMode & D_NON_VRB) )
            printf("PCI  (MHz): ");

        printf("%u.%.2u\n", freq, fract);
    }

    scgtClose(&iO.hDriver);
    return 0;
}

/***************************************************************************/
/* Function:    gtspeedDiffCounter()                                       */
/* Description: Calculates difference between current counter value and    */
/*    previous counter value, accounting for (at most 1) roll-over.        */
/***************************************************************************/
static uint32 gtspeedDiffCounter(uint32 currCnt, uint32 lastCnt, uint32 maxCnt)
{
    if( currCnt >= lastCnt)
        return ( currCnt - lastCnt );
    else /* overflow */
        return ( currCnt + ( maxCnt - lastCnt ) + 1 );
}

/**************************************************************************/
/*  function:     buildOptionsString                                      */
/*  description:  Updates the options string                              */
/**************************************************************************/
static void buildOptionsString( char *optionsString, iOptionsType *iO )
{
    char *str = optionsString;
    int off = 0;

    off += sprintf(&str[off],
              "gtspeed%s%s%s -u %u -t %u -d %#x",
              (iO->linkSpeed || iO->pciSpeed) ? " -":"",
              iO->linkSpeed ? "L":"", iO->pciSpeed ? "P" : "",
              iO->unit, iO->interval, iO->displayMode);
}

/***************************************************************************/
/* Function:    parseCommand()                                             */
/* Description: Parses command line options                                */
/***************************************************************************/
static void parseCommand(int argc,char **argv,iOptionsType *iO,char *optionsString)
{
    char *endptr, nullchar = 0;
    int  i, j, len, tookParam=0;                         /* setting defaults */
    iO->helpLevel = 0;      iO->unit = 0;
    iO->linkSpeed = 1;      iO->pciSpeed = 1;
    iO->interval = 200;     iO->displayMode = 0;

    buildOptionsString(optionsString, iO);

    if(argc == 1)                                       /* start processing */
        { iO->helpLevel=1; return; }

    for(i = 1; i < argc; i++, tookParam=0)
    {
        len = strlen(argv[i]);
        if((argv[i][0] != '-') || (len < 2))
        {
            printf("\nERROR: Unexpected option: \"%s\"\n\n", argv[i]);
            iO->helpLevel=1;
            return;
        }

        if ( (argv[i][1] == '-') )
        {
            if( !strcmp( &argv[i][2], "version" ) )
            {
                printf("%s\n", APP_VER_STRING);
                printf(" - built with API revision %s\n", scgtapiRevisionStr);
                exit (0);
            }
            else if( !strcmp( &argv[i][2], "help" ) )
            {
                iO->helpLevel=2;
                return;
            }
        }
                                                       /* parse options */
        for(j=1; j<len; j++)          /* do options with arguments first*/
        {
            if( (j == (len-1)) && ((i+1) < argc))
            {
                tookParam = 1;  endptr = &nullchar;
                /* test for options which take parameters */
                /* these options only valid as last char of -string */
                if(     argv[i][j]=='u')
                    iO->unit=strtoul(argv[++i],&endptr,0);
                else if(argv[i][j]=='d')
                    iO->displayMode=strtoul(argv[++i],&endptr,0);
                else if(argv[i][j]=='t')
                    iO->interval=strtoul(argv[++i],&endptr,0);
                else tookParam = 0;

                if( *endptr )
                {
                    printf("\nInvalid parameter \"%s\" for option \"-%c\"\n\n",
                           argv[i], argv[i-1][j]);
                    return;
                }

                if( tookParam )
                    break;
            }
                                       /* options without arguments now */
            if(     toupper(argv[i][j])=='L') iO->linkSpeed++;
            else if(         argv[i][j]=='P') iO->pciSpeed++;
            else if(tolower(argv[i][j])=='h')
            {
                iO->helpLevel=2;
                if(argc > 2) iO->helpLevel=3;
                return;
            }
            else
            {
                printf("\nERROR: Unexpected option: \"-%s\"\n\n",&(argv[i][j]));
                iO->helpLevel=1;
                return;
            }
        }
    }

    if ( iO->linkSpeed > iO->pciSpeed )
        iO->pciSpeed = 0;
    else if ( iO->pciSpeed > iO->linkSpeed )
        iO->linkSpeed = 0;

    buildOptionsString(optionsString, iO);
}

/***************************************************************************/
/* Function:    printHelpLevel1()                                          */
/* Description: Display help and usage info                                */
/***************************************************************************/
static void printHelpLevel1(iOptionsType *iO, char *optionsString)
{
    printf("%s\n\n", APP_VER_STRING);
    printf("Usage: gtspeed [-u unit] [-t ms_time] [-d displayMode] "
           "[-LPh]\n\n");

    printf("Defaults: %s\n", optionsString);
#ifdef PLATFORM_VXWORKS
    printf("VxWorks users: Enclose options list in a set of quotes \" \".\n");
#endif
    printf("\n");
    if(iO->helpLevel>1)
        printHelpLevel2();
}

static void printHelpLevel2()
{
    printf(
    "Options:\n"
    "  -u #    - board/unit number\n"
    "  -L      - calculate link speed, data rate in Gbps\n"
    "  -P      - calculate PCI bus speed, frequency in MHz\n"
    "  -t #    - time interval for calculations (milliseconds)\n"
    "  -d #    - display mode flags (default is 0x0):\n"
    "            0x1 - non-verbose\n"
    "  -h      - display this help menu\n"
    );
}
