//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*
 * tuxCaches.cpp
 */

/*
 * This file implements the tux specific part of the cache tests.
 * This includes the TESTPROCs (entry points) for the tests in the tux
 * function table.  There are some initialization functions for
 * setting stuff up.  The functions then call the code that runs the
 * test, which is in the cacheTest.* files.
 *
 * Each TESTPROC uses getArraySizeFromFT to figure out how large the
 * array should be.  It then checks that this size is within the
 * contraints for the given test that will be run.  It calls
 * runCacheTest with the cacheTestType of the cache test.
 *
 * runCacheTest allocates memory for the test and then figures out how
 * many test iterations need to be run to run the test for the time
 * interval given by the user.  runCacheTest then calls the test for
 * this given time period.  It frees the memory when the test is done
 * and returns to the TESTPROC, which propagates the return value to
 * tux.
 */

/***** Headers  **************************************************************/

/* gives TESTPROCs declarations for these functions */
#include "tuxCaches.h"

/* common utils */
#include "..\..\..\common\commonUtils.h"

/* actual implementation of the cache testing functions */
#include "cacheTest.h"

/* sets up the cache for the tests */
#include "cacheControl.h"

/***** Constants and Defines Local to This File ******************************/

/*
 * use this to change the method used to allocate the array.
 *
 * USE_VIRTUAL_ALLOC uses the vm system to allocate the memory.  The
 * memory region is not guaranteed to be contiguous.
 * USE_ALLOC_PHYS_MEM tells the kernel to allocate a physically
 * contiguous block of memory.  This memory has caching turned off by
 * default.  The test must turn it before proceeding.
 * 
 * Defining both of these produces undefined results.
 */
//#define USE_VIRTUAL_ALLOC
#define USE_ALLOC_PHYS_MEM

/*
 * Allows the code to be more modular.  Define one of these for each
 * individual test.  The TESTPROCs then just call into a standard
 * function that sets everything up.  We could do this with function
 * pointers, except that each function takes a different set and
 * different number are arguments.  Switch statements are easier.
 */
enum cacheTestType_t {CACHE_TEST_WRITE_BOUNDARIES_READ_EVERYTHING, 
		      CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING,
		      CACHE_TEST_VARY_POWERS_OF_TWO,
		      CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING_MIXEDUP};

/*
 * The tests step through memory on this interval.  It can't be less
 * than four, or some of the tests will overlap values and fail.  8 is
 * a good choice because most boundaries will be above it.  It is
 * small enough, though, that we exercise the cache a good deal.  
 *
 * If you change this be sure to change the limits on dwArraySizeBytes
 * in the functions below.  The minimum array size that the functions
 * can handle are checked in the TESTPROCs.
 */
#define CACHE_TEST_STEP_SIZE 8

/*
 * We calibrate the test by running it until this threshold is
 * reached.  We then extrapolate how long a real run (on the order
 * of hours) would take.  This is the calibration threshold in ms.
 *
 * If the clocks aren't working all bets are off.  This doesn't
 * influence cache correctness; it just provides useful output
 * concerning how long the test will take.
 */
#define TEST_CALIBRATION_THRESHOLD_MS 1000

/*
 * The length of the test, in seconds.  Currently, all of the tests
 * run for the same time period.
 *
 * This value can't be larger than 2^32 / 1000 seconds = 2.4 million
 * seconds.
 *
 * currently using 10 minutes.
 */
#define DEFAULT_CACHE_TEST_LENGTH_S (60 * 10)
//#define DEFAULT_CACHE_TEST_LENGTH_S 10

/*
 * The defaut array size.  This is used when the user doesn't specify
 * one from the command line (for the tests that require this option)
 * and for when the CeGetCacheInfo function returns zero for the cache
 * size.
 *
 * Currently using 4 MB
 */
#define DEFAULT_TEST_ARRAY_SIZE (4 * 1024 * 1024)

/*
 * Put the initialization code for a cache test in one place.
 * 
 * See the definition for more info.
 */
INT
runCacheTest (cacheTestType_t cacheTest, DWORD dwArraySize,
	      DWORD dwRunTimeS, DWORD dwParam);

/*****  Command line arg parsing  **************************************/

/*
 * Figure out how large the array needs to be from the argument passed
 * in the tux function table.
 *
 * See the definition for more info.
 */
BOOL
getArraySizeFromFT (DWORD dwParam, DWORD * dwSize);

/* return TRUE if the array length command line arg is set */
BOOL
cmdArgALisSet();

/* return TRUE if the run time command line arg is set */
BOOL
cmdArgRTisSet();

/* return the test run time in seconds */
BOOL
getTestRunTimeS (DWORD * pdoRt);

/* set the cache mode */
BOOL
setCacheMode (DWORD * vals, DWORD dwSizeBytes, DWORD dwParam);

/***** Guessing the cache size **********************************************/

/*
 * Guess the cache size, because we can't trust the value from
 * ceGetCacheInfo and IOCTL_HAL_GET_CACHE_INFO.  Currently we do use
 * this value (contradictory, yes), but in the future it will be
 * verified through tests.
 */

/* The possible cache types */
enum cacheType_t {L1DATA, L1INSTR, L2DATA, L2INSTR, TLB};

/* 
 * Guess the cache size.
 *
 * See the definition for more info.
 */
BOOL
guessCacheSize(cacheType_t cacheType, DWORD * dwSize);

/****** Kenel mode **********************************************************/
__inline BOOL inKernelMode( void )
{
  return (int)inKernelMode < 0;
}

/*****************************************************************************
 *
 *                                Print Usage / Help
 *
 *****************************************************************************/

/*
 * print a usage message for this set of tests
 *
 * See header for information.
 */
TESTPROCAPI 
cacheTestPrintUsage(
		       UINT uMsg, 
		       TPPARAM tpParam, 
		       LPFUNCTION_TABLE_ENTRY lpFTE) 
{
  /* only supporting executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      return (TPR_NOT_HANDLED);
    }

  Info (_T("These tests aim to test the caches on the system."));
  Info (_T("They allocate a contiguous block of memory and read and write"));
  Info (_T("values in differing patterns, trying to find bugs in the cache"));
  Info (_T("routines and memory hierarchy."));
  Info (_T(""));
  Info (_T("The test requires no arguments, but acceptable arguments are:"));
  Info (_T("  -runTime <minutes> Number of minutes that each test runs."));
  Info (_T("                     This setting applies to all test cases "));
  Info (_T("                     except Print Cache Info.  The default value"));
  Info (_T("                     is 10 minutes."));
  Info (_T("  -arrayLen <array>  Array length, in bytes.  This setting affects"));
  Info (_T("                     test cases for which you can specify the size"));
  Info (_T("                     of the array.  The default value is %u bytes"),
	DEFAULT_TEST_ARRAY_SIZE);

  return (TPR_PASS);
}



/*****************************************************************************
 *
 *                                Print Cache Info
 *
 *****************************************************************************/

/*
 * See header for information.
 */
TESTPROCAPI printCacheInfo(
					     UINT uMsg, 
					     TPPARAM tpParam, 
					     LPFUNCTION_TABLE_ENTRY lpFTE) 
{
  INT returnVal = TPR_PASS;
  
  /* only supporting executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      returnVal =  TPR_NOT_HANDLED;
      goto cleanAndExit;
    }

  if (cmdArgALisSet () == TRUE)
    {
      Info (_T("Ignoring command line argument for array length."));
    }

  if (cmdArgRTisSet () == TRUE)
    {
      Info (_T("Ignoring command line argument for test run time."));
    }

  /* struct to store cache information in */
  CacheInfo cacheInfo;
  
  BOOL rVal = CeGetCacheInfo (sizeof (cacheInfo), &cacheInfo);

  if (rVal == FALSE)
    {
      Error (_T("Got 0 as a return value from CeGetCacheInfo."));
      Error (_T("CeGetCacheInfo could not return information on the cache."));
      Error (_T("Most likely, IOCTL_HAL_GET_CACHE_INFO isn't implemented"));
      Error (_T("or is implemented incorrecty.  CeGetCacheInfo calls this"));
      Error (_T("ioctl."));
      returnVal = TPR_FAIL;
      goto cleanAndExit;
    }
  else if (rVal != TRUE)
    {
      Error (_T("CeGetCacheInfo is not returning the correct error value.  ")
	     _T("It should return TRUE (%u) or FALSE (%u) but instead ")
	     _T("returned %u."),
	     TRUE, FALSE, rVal);
      returnVal = TPR_FAIL;
      goto cleanAndExit;
    }

  /* at this point cacheInfo has the cache info information */

  Info (_T("This information is reported from the OAL.  It is not verified "));
  Info (_T("by this test.  A pass for this test means only that the test "));
  Info (_T("was able to get the information, not that the information is "));
  Info (_T("correct."));
  Info (_T("These names are taken from the Windows CE documentation.  Please "));
  Info (_T("consult the CeGetCacheInfo documentation for more information. "));
  Info (_T(""));

  Info (_T("L1 Flags - dwL1Flags = 0x%x   %s %s %s"), cacheInfo.dwL1Flags,
	(cacheInfo.dwL1Flags & CF_UNIFIED) ? _T("CF_UNIFIED") : _T(""),
	(cacheInfo.dwL1Flags & CF_WRITETHROUGH) ? _T("CF_WRITETHROUGH") : _T(""),
	(cacheInfo.dwL1Flags & CF_COHERENT) ? _T("CF_COHERENT") : _T(""));
  Info (_T(""));

  Info (_T("L1 Instruction Cache Size - dwL1ICacheSize = 0x%x = %u"), 
	cacheInfo.dwL1ICacheSize, cacheInfo.dwL1ICacheSize);
  Info (_T("L1 Instruction Cache Line Size - dwL1ICacheLineSize = 0x%x = %u"), 
	cacheInfo.dwL1ICacheLineSize, cacheInfo.dwL1ICacheLineSize);
  Info (_T("L1 Instruction Cache Associativity - dwL1ICacheNumWays = %u"), 
	cacheInfo.dwL1ICacheNumWays);
  Info (_T(""));

  Info (_T("L1 Data Cache Size - dwL1ICacheSize = 0x%x = %u"), 
	cacheInfo.dwL1DCacheSize, cacheInfo.dwL1DCacheSize);
  Info (_T("L1 Data Cache Line Size - dwL1DCacheLineSize = 0x%x = %u"), 
	cacheInfo.dwL1DCacheLineSize, cacheInfo.dwL1DCacheLineSize);
  Info (_T("L1 Data Cache Associativity - dwL1DCacheNumWays = %u"), 
	cacheInfo.dwL1DCacheNumWays);

  Info (_T(""));

  Info (_T("L2 Flags - dwL2Flags = 0x%x   %s %s %s"), cacheInfo.dwL2Flags,
	(cacheInfo.dwL2Flags & CF_UNIFIED) ? _T("CF_UNIFIED") : _T(""),
	(cacheInfo.dwL2Flags & CF_WRITETHROUGH) ? _T("CF_WRITETHROUGH") : _T(""),
	(cacheInfo.dwL2Flags & CF_COHERENT) ? _T("CF_COHERENT") : _T(""));
  Info (_T(""));

  Info (_T("L2 Instruction Cache Size - dwL2ICacheSize = 0x%x = %u"), 
	cacheInfo.dwL2ICacheSize, cacheInfo.dwL2ICacheSize);
  Info (_T("L2 Instruction Cache Line Size - dwL2ICacheLineSize = 0x%x = %u"), 
	cacheInfo.dwL2ICacheLineSize, cacheInfo.dwL2ICacheLineSize);
  Info (_T("L2 Instruction Cache Associativity - dwL2ICacheNumWays = %u"), 
	cacheInfo.dwL2ICacheNumWays);
  Info (_T(""));

  Info (_T("L2 Data Cache Size - dwL2ICacheSize = 0x%x = %u"), 
	cacheInfo.dwL2DCacheSize, cacheInfo.dwL2DCacheSize);
  Info (_T("L2 Data Cache Line Size - dwL2DCacheLineSize = 0x%x = %u"), 
	cacheInfo.dwL2DCacheLineSize, cacheInfo.dwL2DCacheLineSize);
  Info (_T("L2 Data Cache Associativity - dwL2DCacheNumWays = %u"), 
	cacheInfo.dwL2DCacheNumWays);

  Info (_T(""));

#ifdef _SHX_
  Info (_T("Currrently don't support querying cache settings on default "));
  Info (_T("TLB entry."));
#else
  /* query setting on default tlb entry */
  DWORD entry;

  if (!inKernelMode())
    {
      Info (_T(""));
      Info (_T("Warning!!!  AllocPhysMem will fail if not in kernel mode."));
      Info (_T("Are you sure you are running the test correctly?"));
      Info (_T("Consider using ktux)"));
      Info (_T(""));
    }

  if (queryDefaultCacheMode (&entry) != TRUE)
    {
      Error (_T("Couldn't query cache for default TLB entry."));
      returnVal = TPR_FAIL;
      goto cleanAndExit;
    }

  switch (entry)
    {
#ifdef _MIPS_
    case CT_WRITE_THROUGH:
    case CT_WRITE_BACK:
      Info (_T("MIPS doesn't support changing cache symantics in the TLB."));
      Info (_T("Cache is enabled for pages with default allocations."));
      break;
#else
    case CT_WRITE_THROUGH:
      Info (_T("Default cache behavior for TLB entries is write-through."));
      break;
    case CT_WRITE_BACK:
      Info (_T("Default cache behavior for TLB entries is write-back."));
      break;
#endif /* _MIPS_ */
    case CT_DISABLED:
      Info (_T("Default cache behavior for TLB entries is disabled."));
      break;
    default:
      IntErr (_T("queryDefaultCacheMode didn't return a valid value: %u"),
	      entry);
      returnVal = TPR_FAIL;
      goto cleanAndExit;
      break;
    }
#endif /* _SHX_ */

 cleanAndExit:
  
  return (returnVal);
}



/*****************************************************************************
 *
 *                                Cache Test TESTPROCs
 *
 *****************************************************************************/


/*
 * See header for information.
 */
TESTPROCAPI 
entryCacheTestWriteBoundariesReadEverything(
					    UINT uMsg, 
					    TPPARAM tpParam, 
					    LPFUNCTION_TABLE_ENTRY lpFTE) 
{
  INT returnVal = TPR_PASS;
  
  /* only support executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      returnVal =  TPR_NOT_HANDLED;
      goto cleanAndReturn;
    }

  Info (_T("This is the write-boundaries-read-everything cache test."));
  Info (_T("Please see the documentation for more information."));

  /* size of the array */
  DWORD dwArraySizeBytes;

  if (getArraySizeFromFT (lpFTE->dwUserData, &dwArraySizeBytes) == FALSE)
    {
      Error (_T("Could not determine how large the test array needs to be."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  /* This test requires at least 8 DWORDs in the array */
  if (dwArraySizeBytes < (8 * sizeof (DWORD)))
    {
      Error (_T("Array size of %u bytes is too small for this test. ")
	     _T("It must be >= %u."),
	     dwArraySizeBytes, 8 * sizeof (DWORD));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  if ((dwArraySizeBytes % sizeof (DWORD)) != 0)
    {
      /* truncate down */
      dwArraySizeBytes -= (dwArraySizeBytes % sizeof(DWORD));

      Info (_T("The array size in bytes, %u, is not a multiple of %u.  ")
	    _T("Truncating "), dwArraySizeBytes, sizeof (DWORD));
      Info (_T("it to %u for this test.  You should verify that this "),
	    dwArraySizeBytes);
      Info (_T("isn't a bug."));
    }
  else
    {
      Info (_T("Using an array size of %u bytes, which is %u DWORDs."),
	    dwArraySizeBytes, dwArraySizeBytes / sizeof (DWORD));
    } 

  DWORD dwRunTimeS;

  /* get test run time */
  if (getTestRunTimeS (&dwRunTimeS) == FALSE)
    {
      Error (_T("Could not get the test run time."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  returnVal = runCacheTest (CACHE_TEST_WRITE_BOUNDARIES_READ_EVERYTHING,
			    dwArraySizeBytes / sizeof (DWORD),
			    dwRunTimeS, lpFTE->dwUserData);

 cleanAndReturn:
  
  return (returnVal);

}

/*
 * See header for information.
 */
TESTPROCAPI
entryCacheTestWriteEverythingReadEverything(
					    UINT uMsg, 
					    TPPARAM tpParam, 
					    LPFUNCTION_TABLE_ENTRY lpFTE) 
{
  INT returnVal = TPR_PASS;
  
  /* only support executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      returnVal =  TPR_NOT_HANDLED;
      goto cleanAndReturn;
    }

  Info (_T("This cache test sequentially writes values and then verifies them."));
  Info (_T("Please see the documentation for more information."));

  /* size of the array */
  DWORD dwArraySizeBytes;

  if (getArraySizeFromFT (lpFTE->dwUserData, &dwArraySizeBytes) == FALSE)
    {
      Error (_T("Could not determine how large the test array needs to be."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  /* The test array must be at least a dword */
  if (dwArraySizeBytes < sizeof (DWORD))
    {
      Error (_T("Array size must be at least one DWORD (%u bytes)"),
	     sizeof (DWORD));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  if ((dwArraySizeBytes % sizeof (DWORD)) != 0)
    {
      /* truncate down */
      dwArraySizeBytes -= (dwArraySizeBytes % sizeof(DWORD));

      Info (_T("The array size in bytes, %u, is not a multiple of %u.  ")
	    _T("Truncating "), dwArraySizeBytes, sizeof (DWORD));
      Info (_T("it to %u for this test.  You should verify that this "),
	    dwArraySizeBytes);
      Info (_T("isn't a bug."));
    }
  else
    {
      Info (_T("Using an array size of %u bytes, which is %u DWORDs."),
	    dwArraySizeBytes, dwArraySizeBytes / sizeof (DWORD));
    }

  DWORD dwRunTimeS;

  /* get test run time */
  if (getTestRunTimeS (&dwRunTimeS) == FALSE)
    {
      Error (_T("Could not get the test run time."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  returnVal = runCacheTest (CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING, 
			    dwArraySizeBytes / sizeof (DWORD),
			    dwRunTimeS, lpFTE->dwUserData);

 cleanAndReturn:
  
  return (returnVal);

}

/*
 * See header for information.
 */
TESTPROCAPI
entryCacheTestVaryPowersOfTwo(
			      UINT uMsg, 
			      TPPARAM tpParam, 
			      LPFUNCTION_TABLE_ENTRY lpFTE) 
{
  INT returnVal = TPR_PASS;
  
  /* only support executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      returnVal =  TPR_NOT_HANDLED;
      goto cleanAndReturn;
    }

  Info (_T("This cache test works boundaries near powers of two."));
  Info (_T("Please see the documentation for more information."));

  /* size of the array */
  DWORD dwArraySizeBytes;

  if (getArraySizeFromFT (lpFTE->dwUserData, &dwArraySizeBytes) == FALSE)
    {
      Error (_T("Could not determine how large the test array needs to be."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  /* This test requires at least 8 DWORDs in the array */
  if (dwArraySizeBytes < (8 * sizeof (DWORD)))
    {
      Error (_T("Array size of %u bytes is too small for this test. ")
	     _T("It must be >= %u."),
	     dwArraySizeBytes, 8 * sizeof (DWORD));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  if (isPowerOfTwo (dwArraySizeBytes / sizeof (DWORD)) == FALSE)
    {
      Error (_T("The array size in bytes, %u, is not a power of 2.  "),
	     dwArraySizeBytes);
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  Info (_T("Using an array size of %u bytes, which is %u DWORDs."),
	dwArraySizeBytes, dwArraySizeBytes / sizeof (DWORD));

  DWORD dwRunTimeS;

  /* get test run time */
  if (getTestRunTimeS (&dwRunTimeS) == FALSE)
    {
      Error (_T("Could not get the test run time."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  returnVal = runCacheTest (CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING, 
			    dwArraySizeBytes / sizeof (DWORD),
			    dwRunTimeS, lpFTE->dwUserData);

 cleanAndReturn:
  
  return (returnVal);

}


/*
 * See header for information.
 */
TESTPROCAPI
entryCacheTestWriteEverythingReadEverythingMixedUp(
						   UINT uMsg, 
						   TPPARAM tpParam, 
						   LPFUNCTION_TABLE_ENTRY lpFTE)
{
  INT returnVal = TPR_PASS;
  
  /* only support executing the test */ 
  if (uMsg != TPM_EXECUTE) 
    { 
      returnVal =  TPR_NOT_HANDLED;
      goto cleanAndReturn;
    }

  Info (_T("This cache test sequentially writes values and then verifies them."));
  Info (_T("Please see the documentation for more information."));

  /* size of the array */
  DWORD dwArraySizeBytes;
  DWORD dwArraySizeDwords;

  if (getArraySizeFromFT (lpFTE->dwUserData, &dwArraySizeBytes) == FALSE)
    {
      Error (_T("Could not determine how large the test array needs to be."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  /* This test requires at least 8 DWORDs in the array */
  if (dwArraySizeBytes < sizeof (DWORD))
    {
      Error (_T("Array size must be at least one DWORD (%u bytes)"),
	     sizeof (DWORD));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  dwArraySizeDwords = dwArraySizeBytes / sizeof (DWORD);

  if ((dwArraySizeDwords == 0) ||
      (dwArraySizeBytes % sizeof (DWORD) != 0) ||
      (isPowerOfTwo (dwArraySizeDwords) == FALSE))
    {
      /* wrong array size */
      Error (_T("The array size in bytes, %u, is incorrect.  It must be a power ")
	     _T("of two >= 8."), dwArraySizeBytes);
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    } 
  else
    {
      Info (_T("Using an array size of %u bytes, which is %u DWORDs."),
	    dwArraySizeBytes, dwArraySizeDwords);
    }

  DWORD dwRunTimeS;

  /* get test run time */
  if (getTestRunTimeS (&dwRunTimeS) == FALSE)
    {
      Error (_T("Could not get the test run time."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  returnVal = runCacheTest (CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING_MIXEDUP, 
			    dwArraySizeDwords, dwRunTimeS, lpFTE->dwUserData);

 cleanAndReturn:
  
  return (returnVal);

}


/*****************************************************************************
 *
 *                      Test Initialization / Run the Test
 *
 *****************************************************************************/

/*
 * Run the test.  This function serves as a common entry point for the
 * testprocs.  It does some initialization and then calls the actual
 * test code.
 *
 * Before a test can be run, memory must be allocated, and the test
 * must determine how long it needs to be run to satisfy the time
 * requirement given by the user.  This is all common code, so it is
 * put in this routine.
 *
 * Each test function takes a different set of arguments, though, so
 * they are special cased at two points in this function.  The first
 * is for initialization and the second is when the test is run.
 *
 * cacheTest is the cache test type (see the enum above).  dwArraySize
 * is the arraySize and must be > 0 (But each test has its own
 * constraints that must be observed.  These are checked in the
 * functions that call this function.)
 *
 * This function return tux return values.
 */
INT
runCacheTest (cacheTestType_t cacheTest, DWORD dwArraySize,
	      DWORD dwCacheTestLengthS, DWORD dwTestParam)
{
  if (dwArraySize == 0)
    {
      IntErr (_T("runCacheTest: dwArraySize == 0"));
      return TPR_FAIL;
    }

  if (dwCacheTestLengthS < 0)
    {
      IntErr (_T("(dwCacheTestLengthS < 0)"));
      return (TPR_FAIL);
    }

  /* main return value (TESTPROC format) */
  INT returnVal = TPR_PASS;

  /* return value for cache test helper functions (boolean format) */
  BOOL bRetVal = FALSE;

  /* make nice times when printing */
  double doRealisticTime;
  _TCHAR * pszRealisticUnits;

  /*
   * they array that we do testing over.  Must be page aligned in mem.
   */
  DWORD * vals;

  /* check for overflow on array size */
  DWORD dwArraySizeBytes = dwArraySize * sizeof (DWORD);

  if (dwArraySizeBytes < MAX(dwArraySize, sizeof (DWORD)))
    {
      Error (_T("Array size is too large.  Memory allocation will overflow ")
	     _T("DWORD"));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  TCHAR * szMemoryAllocationType = NULL;

#if defined USE_VIRTUAL_ALLOC
  /*
   * Force the os to get us this memory.  Memory will be on a page boundary
   * so the boundary conditions will actually hit cache boundaries.
   */
  vals = (DWORD *) VirtualAlloc (NULL, dwArraySizeBytes,
				 MEM_COMMIT, PAGE_READWRITE);

  szMemoryAllocationType = _T("VirtualAlloc");

#elif defined USE_ALLOC_PHYS_MEM

  if (!inKernelMode())
    {
      Info (_T(""));
      Info (_T("Warning!!!  AllocPhysMem will fail if not in kernel mode."));
      Info (_T("Are you sure you are running the test correctly?"));
      Info (_T("Consider using ktux)"));
      Info (_T(""));
    }

  /* physical address of the allocation.  currently unused */
  DWORD dwPhysicalAddress;

  /*
  /* Force the os to get a contiguious chunk of memory. */
  vals = (DWORD *) AllocPhysMem (dwArraySizeBytes, PAGE_READWRITE, 0, 0, 
				 &dwPhysicalAddress);

  szMemoryAllocationType = _T("AllocPhysMem");

#else
#error "No memory allocation scheme specified"
#endif

  if (vals == NULL)
    {
      Error (_T("The test tried to allocate %u bytes of memory using ")
	     _T("AllocPhysMem.  The call failed.  GetLastError returned %u."),
	     dwArraySizeBytes, GetLastError ());
      Error (_T("This probably means that a physically contiguous block of"));
      Error (_T("memory was not available.  Reboot the machine and try the"));
      Error (_T("test again.  You should also be running only on TinyKernel."));

      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }
  
  Info (_T("Allocated %u bytes of memory using %s."),
	dwArraySizeBytes, szMemoryAllocationType);

#ifdef _SHX_
  Info (_T("Currently don't support setting cache into specific modes for SHX."));
  Info (_T("The cache is running in the default mode."));
#else
  /* this will print out what cache is set to */
  if (setCacheMode (vals, dwArraySizeBytes, dwTestParam) == FALSE)
    {
      Error (_T("Could not set cache mode for test."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }
#endif

  /**** Figure out how long one test iteration takes ************************/

  Info (_T("Calibrating..."));

  DWORD dwTestCalibrationRuns = 1;

  /* 
   * these are ulonglong because we are using functions from the timing code,
   * which take ulonglong parameters
   */
  ULONGLONG ullDiff = 0;	      
  ULONGLONG ullTimeBegin, ullTimeEnd;

  while (1)
    {
      ullTimeBegin = (ULONGLONG) GetTickCount ();

      /* run the correct test */
      switch (cacheTest)
	{
	case CACHE_TEST_WRITE_BOUNDARIES_READ_EVERYTHING:
	  bRetVal = cacheTestWriteBoundariesReadEverything(vals, dwArraySize, 
							   dwTestCalibrationRuns, 
							   CACHE_TEST_STEP_SIZE);
	  break;
	case CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING:
	  bRetVal = cacheTestWriteEverythingReadEverything(vals, dwArraySize, 
							   dwTestCalibrationRuns);
	  break;
	case CACHE_TEST_VARY_POWERS_OF_TWO:
	  bRetVal = cacheTestVaryPowersOfTwo(vals, dwArraySize,
					     dwTestCalibrationRuns);
	  break;
	case CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING_MIXEDUP:
	  bRetVal = cacheTestWriteEverythingReadEverythingMixedUp(vals, 
								  dwArraySize, 
								  dwTestCalibrationRuns);
	  break;
	default:
	  /* an internal error, so bail out */
	  Error (_T("No test specified for function runCacheTest."));
	  returnVal = TPR_FAIL;
	  goto cleanAndReturn;
	  break;
	}

      /* check for error */
      if (bRetVal == FALSE)
	{
	  Error (_T("Test failed during calibration."));
	  Error (_T("Calibration runs the given cache test for a short length"));
	  Error (_T("of time to determine how long it takes.  Failure commonly"));
	  Error (_T("means that the cache test failed.  Most likely, something"));
	  Error (_T("is wrong with the cache routines or memory subsystem "));
	  Error (_T("(either hardware or software) on the machine"));
	  
	  returnVal = TPR_FAIL;
	  goto cleanAndReturn;
	}

      ullTimeEnd = (ULONGLONG) GetTickCount ();

      /* subtract, accounting for overflow */
      if (AminusB (ullDiff, ullTimeEnd, ullTimeBegin, MAX_DWORD) == FALSE)
	{
	  /* 
	   * This should never happen.  If something goes wrong, then there is 
	   * a bug in the code.
	   */
	  IntErr (_T("AminusB failed in runCacheTest"));

	  returnVal = TPR_FAIL;
	  goto cleanAndReturn;
	}

      if (ullDiff < TEST_CALIBRATION_THRESHOLD_MS)
	{
	  /* if within threshold double number of runs and try again */
	  DWORD prev = dwTestCalibrationRuns;
	  
	  dwTestCalibrationRuns *= 2;

	  if (prev >= dwTestCalibrationRuns)
	    {
	      /* 
	       * we overflowed.  We could try to recover but it is much easier
	       * to bail at this point.  If this happens the user either has
	       * lightening speed caches or something is wrong with either 
	       * the timers or something else.
	       *
	       * This should be a very rare error.
	       */
	      Error (_T("Could not calibrate.  We overflowed the variable "));
	      Error (_T("storing the number of calibration runs.  Either the "));
	      Error (_T("cache routines are running very fast or the "));
	      Error (_T("calibration threshold is set too high."));
	      Error (_T("Reduce the calibration threshold in the code (see the"));
	      Error (_T("defines at the top of tuxCaches.cpp), recompile, and "));
	      Error (_T("try again."));

	      returnVal = TPR_FAIL;
	      goto cleanAndReturn;
	    }
	}
      else
	{
	  break;
	}
    } /* while */

  /* 
   * we now know how long one test will take.  We calculate how many
   * tests we need to do for the given test length.
   */
  
  /*
   * convert test length into ms.  Cast ullDiff to DWORD.  This won't cause
   * us to loose data because there will be nothing in the upper 32 bits.
   * AminusB prevents this.
   */
  DWORD dwIterations = 
    (dwCacheTestLengthS * 1000) / (DWORD) ullDiff;

  DWORD dwIterationsPrev = dwIterations;

  /* factor in that it took dwTestCalibrationRun loops for each iteration */
  dwIterations *= dwTestCalibrationRuns;
  
  if (dwIterationsPrev > dwIterations)
    {
      Error (_T("Overflow in dwIterations"));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  if (dwIterations == 0)
    {
      Error (_T("Either calibration is incorrect (maybe the timers haven't been"));
      Error (_T("tested?) or the test length constants for the cache tests "));
      Error (_T("are not set correctly.  Exiting..."));

      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }

  Info (_T("We are going to do %u iterations for this cache test."),
	dwIterations);
  realisticTime ((double) dwCacheTestLengthS,
		 doRealisticTime, &pszRealisticUnits);
  Info (_T("It should take about %g %s."), 
	doRealisticTime, pszRealisticUnits);
  Info (_T("Step size is %u."), CACHE_TEST_STEP_SIZE);

  /* run the correct test */
  switch (cacheTest)
    {
    case CACHE_TEST_WRITE_BOUNDARIES_READ_EVERYTHING:
      bRetVal = cacheTestWriteBoundariesReadEverything(vals, dwArraySize, 
						       dwIterations,
						       CACHE_TEST_STEP_SIZE);
      break;
    case CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING:
      bRetVal = cacheTestWriteEverythingReadEverything(vals, dwArraySize, 
						       dwIterations);
      break;
    case CACHE_TEST_VARY_POWERS_OF_TWO:
      bRetVal = cacheTestVaryPowersOfTwo(vals, dwArraySize, dwIterations);
      break;
    case CACHE_TEST_WRITE_EVERYTHING_READ_EVERYTHING_MIXEDUP:
      bRetVal = cacheTestWriteEverythingReadEverythingMixedUp(vals, 
							      dwArraySize, 
							      dwIterations);
      break;
    default:
      /* an internal error, so bail out */
      Error (_T("No test specified for function runCacheTest."));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
      break;
    }

  if (bRetVal == FALSE)
    {
      /* we print out more info in the test function */
      Error (_T("Cache test failed"));
      returnVal = TPR_FAIL;
      goto cleanAndReturn;
    }
  else
    {
      Info (_T("Cache test succeeded"));
    }

 cleanAndReturn:
  
  if (vals != NULL)
    {
      /* clean up if we successfully allocated the memory */

#if defined USE_ALLOC_PHYS_MEM
      if (FreePhysMem (vals) != TRUE)
	{
	  Error (_T("Could not free memory.  GetLastError returned %u."),
		 GetLastError);
	  returnVal = TPR_FAIL;
	}

#elif defined USE_VIRTUAL_ALLOC

      if (VirtualFree (vals, 0, MEM_RELEASE) == 0)
	{
	  Error (_T("Could not free memory.  GetLastError returned %u."),
		 GetLastError);
	  returnVal = TPR_FAIL;
	}

#else
#error "No memory allocation scheme specified"
#endif

    }
  
  return (returnVal);
}

/*****************************************************************************
 *
 *                            Argument Processing
 *
 *****************************************************************************/

/*
 * Figure out what array size to use for the test from given in the
 * function table.
 *
 * See the notes in the header for information on how dwParam is laid
 * out.
 *
 * Returns the size of the array size in the bytes.
 */
BOOL
getArraySizeFromFT (DWORD dwParam, DWORD * pdwCacheSize)
{
  if (pdwCacheSize == NULL)
    {
      IntErr (_T("getArraySizeFromFT: pdwCacheSize == NULL"));
      return FALSE;
    }

  BOOL returnVal = TRUE;
  
  CClParse cmdLine(g_szDllCmdLine);

  /* used for overflow checking in some calculations */
  DWORD prev = BAD_VAL;

  /* grab only the cache size bits */
  dwParam &= CT_ARRAY_SIZE_MASK;

  switch (dwParam)
    {
    case CT_CACHE_SIZE:
      if (cmdArgALisSet () == TRUE)
	{
	  Info (_T("Ignoring command line argument for array length."));
	  Info (_T("Cache size data is taken from CeGetCacheInfo."));
	}

      /* get the cache size from the system */
      if (guessCacheSize (L1DATA, pdwCacheSize) == FALSE)
	{
	  Error (_T("Could not get the cache size."));
	  returnVal = FALSE;
	  goto cleanAndExit;
	}

      Info (_T("The L1 data cache size is %u bytes."), *pdwCacheSize);
      
      break;
    case CT_TWICE_CACHE_SIZE:
      if (cmdArgALisSet () == TRUE)
	{
	  Info (_T("Ignoring command line argument for array length."));
	  Info (_T("Cache size data is taken from CeGetCacheInfo."));
	}

      /* get the cache size from the system */
      if (guessCacheSize (L1DATA, pdwCacheSize) == FALSE)
	{
	  Error (_T("Could not get the cache size."));
	  returnVal = FALSE;
	  goto cleanAndExit;
	}

      Info (_T("The L1 data cache size is %u bytes."), *pdwCacheSize);

      prev = *pdwCacheSize;

      *pdwCacheSize *= 2;

      if (*pdwCacheSize <= prev)
	{
	  Error (_T("Overflow when doubling cache size"));
	  returnVal = FALSE;
	  goto cleanAndExit;
	}
      break;
    case CT_USER_DEFINED:
      /* in this case parse the command line to find the size */
      if (cmdLine.GetOptVal (_T("arrayLen"), pdwCacheSize) == FALSE)
	{
	  Info (_T("Could not get the cache test array length."));
	  Info (_T("Looking for an option in the form ")
		_T("arrayLen <number>."));

	  *pdwCacheSize = DEFAULT_TEST_ARRAY_SIZE;

	  Info (_T("Using default value of %u"), *pdwCacheSize);
	}
      break;
    default:
      Error (_T("Bad paramter."));
      returnVal = FALSE;
      goto cleanAndExit;
    }

 cleanAndExit:
  return (returnVal);
}



/*
 * Guess the cache size.  If the value from the ceGetCacheInfo function
 * doesn't make sense, then use the default value.
 *
 * Currently only L1 data cache queries are supported.  If the L1 data 
 * cache has a size of zero, we use the default cache size.  It will be
 * very rare that we won't have a L1 data cache, and even if we didn't
 * and wanted to test memory we would still want a default sized array.
 *
 * return TRUE on success and FALSE otherwise.  Print out messages in
 * both cases denoting what we are doing.
 */
BOOL
guessCacheSize(cacheType_t cacheType, DWORD * pdwSize)
{
  if (pdwSize == NULL)
    {
      IntErr (_T("guessCacheSize: pdwSize == NULL"));
      return (FALSE);
    }

  BOOL returnVal = TRUE;

  /* struct to store cache information in */
  CacheInfo cacheInfo;
  
  if (cacheType != L1DATA)
    {
      Error (_T("Error:  Currently only support guessing the cache size")
	     _T("for the L1 data cache"));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  returnVal = CeGetCacheInfo (sizeof (cacheInfo), &cacheInfo);

  if (returnVal == FALSE)
    {
      Error (_T("Got 0 as a return value from CeGetCacheInfo."));
      Error (_T("CeGetCacheInfo could not return information on the cache."));
      Error (_T("Most likely, IOCTL_HAL_GET_CACHE_INFO isn't implemented"));
      Error (_T("or is implemented incorrecty.  CeGetCacheInfo calls this"));
      Error (_T("ioctl."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }
  else if (returnVal != TRUE)
    {
      Error (_T("CeGetCacheInfo is not returning the correct error value.  ")
	     _T("It should return TRUE (%u) or FALSE (%u) but instead ")
	     _T("returned %u."),
	     TRUE, FALSE, returnVal);
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  /* at this point cacheInfo has the cache info information */

  Info (_T("The cache size is reported from the OAL.  It currently cannot"));
  Info (_T("be verified.  If it isn't correct then the test might generate"));
  Info (_T("false negatives.  An incorrect cache size should not generate "));
  Info (_T("false positives."));
  
  *pdwSize = cacheInfo.dwL1DCacheSize;

  if (*pdwSize == 0)
    {
      /* 
       * most likely this means that it isn't set.  We currently don't set
       * this information on the stock x86, since the x86 varies so much and
       * it can't be determined on the fly.  
       *
       * For now, we just allocate a standard size in this case and hope for
       * the best.  In the future we will have better checks and will probably
       * use some timing measurements to figure out how large the cache really
       * is.
       */
      *pdwSize = DEFAULT_TEST_ARRAY_SIZE;
      
      Info (_T("The L1 data cache size is zero.  This probably isn't correct."));
      Info (_T("For example, this value often isn't set on the x86 platform"));
      Info (_T("since it varies so much from platform to platform."));
      Info (_T("If you modified the OAL to return a value other than zero "));
      Info (_T("then something is wrong."));
      Info (_T("This test requires a value greater than zero, so we are going"));
      Info (_T("to use a default value of %u."), *pdwSize);
    }

 cleanAndReturn:

  return (returnVal);
}


/*
 * Get the test run time.
 *
 * If it wasn't passed in as an argument use the default value.
 *
 * doRt has the test run time in seconds.
 *
 * return TRUE on success and FALSE otherwise.
 */
BOOL
getTestRunTimeS (DWORD * pdwRt)
{
  BOOL returnVal = FALSE;

  CClParse cmdLine(g_szDllCmdLine);

  /* returned run time from the argument processing function */
  DWORD dwArgRt;

  if (cmdLine.GetOptVal (_T("runTime"), &dwArgRt) == TRUE)
    {
      /* convert from minutes into seconds */
      *pdwRt = dwArgRt * 60;
  
      if (*pdwRt < MAX (dwArgRt, 60))
	{
	  Error (_T("Overflow converting run time to seconds."));
	  goto cleanAndReturn;
	}
    }
  else
    {
      /* no option, use default */
      *pdwRt = DEFAULT_CACHE_TEST_LENGTH_S;
    }

  /* only succeeded if we got to here */
  returnVal = TRUE;

 cleanAndReturn:
  return (returnVal);
}


/*
 * return TRUE if the array size option is on the command line and
 * false if it isn't. 
 *
 * This looks for correct options (in this case flag and associated
 * parameter.  If the command line arg parsing function fails then
 * this function returns false, because this option won't be able to
 * be parsed by this program.
 */
BOOL
cmdArgALisSet()
{
  CClParse cmdLine(g_szDllCmdLine);

  return (cmdLine.GetOpt (_T("arrayLen")));
}

/*
 * return TRUE if the test run time option is on the command line
 * and false if it isn't.
 *
 * This looks for correct options (in this case flag and associated
 * parameter.  If the command line arg parsing function fails then
 * this function returns false, because this option won't be able to
 * be parsed by this program.
 */
BOOL
cmdArgRTisSet()
{
  CClParse cmdLine(g_szDllCmdLine);

  return (cmdLine.GetOpt (_T("runTime")));
}

