/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/****************************************************************************/
/*                                                                          */
/*  MODULE: PERFTEST                                                        */
/*                                                                          */
/*  This module defines the external procedure PERFTEST, which makes use of */
/*  the SAVETIME call to read the counter, and put an entry in the array.   */
/*  It also contains the WrapCounter procedure, which concurrently checks   */
/*  for wrapping of the timer, the PrintTest procedure which dumps the      */
/*  buffer to file and calculates the elapsed times, as well as some other  */
/*  initialisation procedures.                                              */
/*                                                                          */
/*                                                                          */
/****************************************************************************/
/*  The following identifiers include or exclude parts of the code:         */
/*                                                                          */
/*  FULL_OUTPUT   if defined, will output wrapcount and counter at the      */
/*                location, otherwise only gives the elapsed time.          */
/*  MICROSECS     if defined, output is in microseconds. Default is         */
/*                milliseconds.                                             */
/****************************************************************************/

/**********************************************************************/
/* Note: This module (and perfasm.asm) is 16bit code and is linked    */
/*       into the 32bit XGA driver.                                   */
/*                                                                    */
/* This means that we can't use any c runtime routines as I can't     */
/* link both 16bit and 32bit versions!!!                              */
/*                                                                    */
/* Routines used WERE: fopen, fprintf, fclose                         */
/*                     long multiply and divide routines!!            */
/*                                                                    */
/* The file routines have been replaced by DosOpen etc calls.         */
/* The long multiply, divide routines have been replaced by our       */
/* own versions in perfasm.asm!!!!                                    */
/**********************************************************************/

#include <stdio.h>
#define INCL_DOS
#include <os2.h>

/**********************************************************************/
/* Define constants.                                                  */
/**********************************************************************/
#define ARRAYLEN        257
//#define STACKSIZE       1024
#define STACKSIZE       4096    /* minimum for thread calling OS   */
#define SLEEPTIME       20L
#define CALIBRATE       0
#define PRINT           9999
#define DELETE          9998

#define FileName        "B:\TIMER.OUT"

/**********************************************************************/
/* Typedef for information.                                           */
/**********************************************************************/
typedef struct
{
    USHORT          last_counter;
    ULONG           last_wrapcount;
    USHORT          total_counter;
    ULONG           total_wrapcount;
    USHORT          number_of_calls;
} TIMESTRUC;

/**********************************************************************/
/* Globals.                                                           */
/**********************************************************************/
TIMESTRUC   TimeArray[ARRAYLEN];/* holds timer info                   */
USHORT      last_count;         /* last value for WrapCounter test    */
UINT        wrap_count;         /* Current number of clock wraps      */
ULONG       call_time;          /* Time for a call to execute         */
HSYSSEM     ThreadSem;          /* sem to stop WrapCounter            */
HSYSSEM     ProcSem;            /* Sem to stop PerfTest               */
USHORT      first_time = TRUE;  /* first time flag                    */
//FILE        *fp;                /* PrintTest file pointer             */
HFILE       FileHandle;          /* file handle for DosOpen            */
USHORT      FileError;          /* Error value from DosOpen, DOsWrite..*/
UCHAR       fTesting = FALSE;   /* Are we actually doing a test       */
UCHAR       fDumpdata = FALSE;  /* Dump the data to disk flag         */
UCHAR       fResetTest = FALSE; /* To reset totals to 0               */
USHORT      FirstCall = TRUE;   /* used in perfasm.asm to indicate first call */

/**********************************************************************/
/* Forward declarations.                                              */
/**********************************************************************/
extern VOID   pascal        PerfTest(USHORT loc,SHORT fstart);
       VOID   pascal        PTPrintTest(VOID);
       USHORT pascal        PTAllocMem(VOID);
extern USHORT pascal        SaveTime(VOID);
       VOID   pascal        PTCreateThread(VOID);
       VOID   far pascal    PTWrapCounter(VOID);
       VOID   pascal        PTWrapCheck(VOID);
extern VOID   pascal        PTSemInit(VOID);
       VOID   pascal        PTCalibrateCode(VOID);

       VOID   pascal        PTPrintLine(USHORT loc,
                                        ULONG  elapsed_int,
                                        ULONG  elapsed_dec,
                                        USHORT no_of_calls);
       VOID   pascal        PTAddShort(char * buffer,
                                       USHORT length,
                                       USHORT number);

extern  ULONG near pascal   PTUlongMult(ULONG a, ULONG b);
extern  ULONG near pascal   PTUlongDiv(ULONG a, ULONG b);

/**********************************************************************/
/* Our own inline assembler for multiply/divide of two longs          */
/*          A = B * C and A = B / C                                   */
/**********************************************************************/
#define ULONG_MULT(A, B, C)  (A) = PTUlongMult((B), (C))
#define ULONG_DIV(A, B, C)   (A) = PTUlongDiv((B), (C))

/**********************************************************************/
/* Code to calibrate the timer.                                       */
/**********************************************************************/
VOID pascal PTCalibrateCode(VOID)
{
    INT i;
    ULONG tmp_time = 0L;
    ULONG tmp = 0L;

    for (i=0;i<10 ;i++ )
    {
        PerfTest(CALIBRATE,1);
        PerfTest(CALIBRATE,0);
//      tmp_time =  (ULONG)(TimeArray[CALIBRATE].total_counter * 84L / 10L);
//      tmp_time += (550494L * TimeArray[CALIBRATE].total_wrapcount);
//      tmp_time /= 10L;
        ULONG_MULT(tmp_time, TimeArray[CALIBRATE].total_counter, 84L);
        ULONG_DIV( tmp_time, tmp_time, 10L);
        ULONG_MULT(tmp, 550494L, TimeArray[CALIBRATE].total_wrapcount);
        tmp_time += tmp;
        ULONG_DIV(tmp_time, tmp_time, 10L);
        TimeArray[CALIBRATE].total_counter = 0;
        TimeArray[CALIBRATE].total_wrapcount = 0L;
        if (tmp_time < call_time)
        {
            call_time = tmp_time;
        }
    }
}


/**********************************************************************/
/* Performance tester.                                                */
/**********************************************************************/
VOID pascal PerfTest(USHORT loc,SHORT fstart)
{
    USHORT tmp_counter,work_counter;
    ULONG  tmp_wrapcount,work_wrapcount;
    ULONG  ii;

    if (fDumpdata)
    {
        fDumpdata = FALSE;
        PTPrintTest();
    }
    if (fResetTest)
    {
        /**************************************************************/
        /* Reset all totals to 0                                      */
        /**************************************************************/
        for (ii = 0; ii < ARRAYLEN; ii++)
        {
            TimeArray[ii].last_counter = 0;
            TimeArray[ii].last_wrapcount = 0L;
            TimeArray[ii].total_counter = 0;
            TimeArray[ii].total_wrapcount = 0L;
            TimeArray[ii].number_of_calls = 0;
        }
        fResetTest = FALSE;
    }
    /******************************************************************/
    /* Do we want to do performance test yet?                         */
    /******************************************************************/
    if (!fTesting)
    {
        return;
    }

    /******************************************************************/
    /* Do initialisation if this is the first time.                   */
    /******************************************************************/
    if (first_time)
    {
        fTesting = FALSE;
        PTSemInit();

        if (!PTAllocMem()) return;
        first_time = FALSE;
        PTCalibrateCode();
    }

    /******************************************************************/
    /* Store information for process.                                 */
    /******************************************************************/
    DosSemRequest(ProcSem,-1L);
    if (loc < ARRAYLEN)
    {
        /**************************************************************/
        /* Stop other thread and store time, incrementing the wrap    */
        /* count if the check function has been bypassed.             */
        /**************************************************************/
        DosSemRequest(ThreadSem,-1L);
        tmp_counter = SaveTime();
        tmp_wrapcount = (wrap_count) + ((last_count) > tmp_counter);
        DosSemClear(ThreadSem);
        if (fstart)
        {
            TimeArray[loc].last_counter = tmp_counter;
            TimeArray[loc].last_wrapcount = tmp_wrapcount;
        }
        else
        {
            TimeArray[loc].number_of_calls++;
            if (tmp_counter > TimeArray[loc].last_counter)
            {
                work_counter = tmp_counter - TimeArray[loc].last_counter;
                work_wrapcount=tmp_wrapcount-TimeArray[loc].last_wrapcount;
            }
            else
            {
                work_counter = tmp_counter - TimeArray[loc].last_counter;
                work_wrapcount=tmp_wrapcount-TimeArray[loc].last_wrapcount-1;
            }
            TimeArray[loc].total_counter += work_counter;
            TimeArray[loc].total_wrapcount += work_wrapcount;
            if (TimeArray[loc].total_counter < work_counter)
            {
                TimeArray[loc].total_wrapcount++;
            }
        }
    }

    DosSemClear(ProcSem);
    return;
}


/**********************************************************************/
/* Allocate the required memory.                                      */
/**********************************************************************/
USHORT pascal PTAllocMem(VOID)
{
    /******************************************************************/
    /* No longer need to share memory - there is only one process.    */
    /* It is now global and we just zero it here.                     */
    /******************************************************************/
    INT     ii;

    PTCreateThread();
    call_time = 0xffffffff;
    for (ii = 0; ii < ARRAYLEN; ii++)
    {
        TimeArray[ii].last_counter = 0;
        TimeArray[ii].last_wrapcount = 0L;
        TimeArray[ii].total_counter = 0;
        TimeArray[ii].total_wrapcount = 0L;
        TimeArray[ii].number_of_calls = 0;
    }

    return (TRUE);
}

/**********************************************************************/
/* Initialise the semaphores.                                         */
/**********************************************************************/
VOID pascal PTSemInit(VOID)
{
    /******************************************************************/
    /* Create semaphore: if it is already made then just open it      */
    /*                 : otherwise clear it so that we can carry on.  */
    /******************************************************************/
    if (!DosCreateSem(CSEM_PUBLIC,&ProcSem,"\\SEM\\perftest.sem"))
    {
        DosSemClear(ProcSem);
    }
    else
    {
        DosOpenSem(&ProcSem,"\\SEM\\perftest.sem");
    }

    if (!DosCreateSem(CSEM_PUBLIC,&ThreadSem,"\\SEM\\testsem.sem"))
    {
        DosSemClear(ThreadSem);
    }
    else
    {
        DosOpenSem(&ThreadSem,"\\SEM\\testsem.sem");
    }
    return;
}


/**********************************************************************/
/* Create extra thread for wrap checking.                             */
/**********************************************************************/
VOID pascal PTCreateThread(VOID)
{
    USHORT  selector;
    PVOID   StackPtr;
    TID     tid_wrap;

    /******************************************************************/
    /* Set up stack, start new thread and wait till it has started.   */
    /******************************************************************/
    DosAllocSeg(STACKSIZE,&selector,0);
    StackPtr = (PVOID)((CHAR *)(MAKEP(selector,0)) + (STACKSIZE - 2));
    DosSemSet(ThreadSem);
    DosCreateThread(PTWrapCounter,&(tid_wrap),StackPtr);
    DosSemWait(ThreadSem,-1L);
    return;
}


/**********************************************************************/
/* Update the wrap counter.                                           */
/**********************************************************************/
VOID far pascal PTWrapCounter()
{
    PIDINFO info;
    USHORT  change = 31;

    (wrap_count) = 0;

    DosGetPID(&info);
    while ((DosSetPrty(PRTYS_THREAD,                /* scope */
             PRTYC_TIMECRITICAL,                    /* class */
             change--,                              /* change - maximum */
             info.tid))                             /* thread id */
             && (change > 0));                      /* keep trying */
    (last_count) = SaveTime();
    DosSemClear(ThreadSem);
    while (TRUE)
    {
        DosSleep(SLEEPTIME);
        PTWrapCheck();
    }
}


/**********************************************************************/
/* Test for clock wrapping.                                           */
/**********************************************************************/
VOID PTWrapCheck(VOID)
{
    USHORT temp_count;

    DosSemRequest(ThreadSem,-1L);
    temp_count = SaveTime();
    if (temp_count < (last_count))
    {
        (wrap_count)++;
    }
    (last_count) = temp_count;
    DosSemClear(ThreadSem);
    return;
}


/**********************************************************************/
/* Save the information to disk.                                      */
/**********************************************************************/
VOID pascal PTPrintTest(VOID)
{
    USHORT      ii;
    ULONG       elapsed;
    ULONG       tmp, tmp2;
    USHORT      ActionTaken;

    /******************************************************************/
    /* Wait for PTWrapCounter to finish.                                */
    /******************************************************************/
    DosSemWait(ThreadSem,-1L);

    DosEnterCritSec();
//    fp = fopen("TIMER.OUT","a");
//    if (!fp)
//    {
//        fp = stdout;
//    }
    FileError = DosOpen(FileName,
                        &FileHandle,
                        &ActionTaken,
                        0,             /* File Size */
                        0,             /* File Attributes */
                        OPEN_ACTION_REPLACE_IF_EXISTS |
                        OPEN_ACTION_CREATE_IF_NEW,
                        OPEN_FLAGS_FAIL_ON_ERROR |
                        OPEN_FLAGS_NO_CACHE |
                        OPEN_FLAGS_SEQUENTIAL |
                        OPEN_SHARE_DENYNONE |
                        OPEN_ACCESS_WRITEONLY,
                        0L);
    if (FileError)
    {
        /**************************************************************/
        /* Error creating file                                        */
        /**************************************************************/
        return;
    }

    for (ii = 1; ii < ARRAYLEN; ii++)
    {
        PTWrapCheck();
        /**************************************************************/
        /* Have stopped PTWrapCounter in this thread, so check counter. */
        /* N.B. <55 ms must have elapsed since critsec entered, or    */
        /* wrap will have been missed                                 */
        /**************************************************************/

        if (TimeArray[ii].number_of_calls != 0)
        {
//          elapsed =  (ULONG)(TimeArray[ii].total_counter * 84L / 10L);
//          elapsed += (550494L * TimeArray[ii].total_wrapcount);
//          elapsed /= 10L;
//          elapsed /= (ULONG)TimeArray[ii].number_of_calls;
//          elapsed -= call_time;
            ULONG_MULT(elapsed, TimeArray[ii].total_counter, 84L);
            ULONG_DIV(elapsed, elapsed, 10L);
            ULONG_MULT(tmp, 550494L, TimeArray[ii].total_wrapcount);
            elapsed += tmp;
            ULONG_DIV(elapsed, elapsed, 10L);
            ULONG_DIV(elapsed, elapsed, TimeArray[ii].number_of_calls);
            elapsed -= call_time;

#ifdef MICROSECS

            fprintf(fp,"Loc=%5hu,Elapsed=%8ld microsecs,#calls=%5hu\n",
                       ii,
                       elapsed,
                       TimeArray[ii].number_of_calls);

#else

//          fprintf(fp,"Loc=%5hu,Elapsed=%5ld.%02ld ms,#calls=%5hu\n",
//                     ii,
//                     (elapsed / 1000),
//                     ((elapsed % 1000) / 10),
//                     TimeArray[ii].number_of_calls);

            ULONG_DIV(tmp, elapsed, 1000L);
            ULONG_MULT(tmp2, tmp, 1000L);
            tmp2 = elapsed - tmp;
            ULONG_DIV(tmp2, tmp2, 10L);
            PTPrintLine(ii, tmp, tmp2, TimeArray[ii].number_of_calls);

#endif /* MICROSECS */

        }

/*****************************************************************************/
/*  This conversion routine is harder to understand than it is could be, but */
/*  this is to allow for a wider range of timings. It works like this:       */
/*  suppose e= elapsed, d=counter difference, w=wrap difference              */
/*  then e = (d + w*65535)*0.84 - calltime                                   */
/*                      [65535 = no of ticks per wrap]                       */
/*         = ((d*84/10) + (w*65535*84/10)) /10  - calltime                   */
/*         = ((d*84/10) + (w*550494))/10  - calltime                         */
/*                      [550494 = 65535 * 84 / 10                            */
/*  this allows for timings up to 214.7 s instead of 21.47 as would be given */
/*  by the first method.                                                     */
/*****************************************************************************/

    }
//    fclose(fp);
    DosClose(FileHandle);
    DosExitCritSec();
}


/**********************************************************************/
/* Routine to print one line of timings information. Replaces fprintf */
/* as can't use C routines....!                                       */
/**********************************************************************/
VOID   pascal        PTPrintLine(USHORT loc,
                                 ULONG  elapsed_int,
                                 ULONG  elapsed_dec,
                                 USHORT no_of_calls)
{
    char    buffer[] = "Loc=xxxxx,Elapsed=xxxxx.xx ms,#calls=xxxxx\n";
    USHORT  buffer_len = 43;
    USHORT  BytesWritten;

    /******************************************************************/
    /* Add the information to the buffer                              */
    /******************************************************************/
    PTAddShort(buffer+4, 5, loc);
    PTAddShort(buffer+18, 5, (USHORT)elapsed_int);
    PTAddShort(buffer+24, 2, (USHORT)elapsed_dec);
    PTAddShort(buffer+37, 5, no_of_calls);

    FileError = DosWrite(FileHandle, &buffer, buffer_len, &BytesWritten);
    if (FileError)
    {
        /**************************************************************/
        /* Error writing this line.                                   */
        /**************************************************************/
        return;
    }

}
/**********************************************************************/
/* Routine to convert a short into ASCII format.                      */
/* Used by PTPrintLine                                                */
/**********************************************************************/
VOID  pascal PTAddShort(char * buffer, USHORT length, USHORT number)
{
    USHORT  i;
    USHORT  j;

    for (i = length; i-- ;)
    {
        j = number % 10;
        number = number / 10;
        buffer[i] = '0' + j;
    }
}
