/* $Id: tstTSC.cpp $ */
/** @file
 * IPRT Testcase - SMP TSC testcase.
 */

/*
 * Copyright (C) 2006-2007 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <iprt/asm-amd64-x86.h>
#include <iprt/asm.h>
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/mp.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include <iprt/time.h>


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
typedef struct TSCDATA
{
    /** The TSC.  */
    uint64_t volatile   TSC;
    /** The APIC ID. */
    uint8_t volatile    u8ApicId;
    /** Did it succeed? */
    bool volatile       fRead;
    /** Did it fail? */
    bool volatile       fFailed;
    /** The thread handle. */
    RTTHREAD            Thread;
} TSCDATA, *PTSCDATA;


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** The number of CPUs waiting on their user event semaphore. */
static volatile uint32_t g_cWaiting;
/** The number of CPUs ready (in spin) to do the TSC read. */
static volatile uint32_t g_cReady;
/** The variable the CPUs are spinning on.
 * 0: Spin.
 * 1: Go ahead.
 * 2: You're too late, back to square one. */
static volatile uint32_t g_u32Go;
/** The number of CPUs that managed to read the TSC. */
static volatile uint32_t g_cRead;
/** The number of CPUs that failed to read the TSC. */
static volatile uint32_t g_cFailed;

/** Indicator forcing the threads to quit. */
static volatile bool g_fDone;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser);


/**
 * Thread function for catching the other cpus.
 *
 * @returns VINF_SUCCESS (we don't care).
 * @param   Thread  The thread handle.
 * @param   pvUser  PTSCDATA.
 */
static DECLCALLBACK(int) ThreadFunction(RTTHREAD Thread, void *pvUser)
{
    PTSCDATA pTscData = (PTSCDATA)pvUser;

    while (!g_fDone)
    {
        /*
         * Wait.
         */
        ASMAtomicIncU32(&g_cWaiting);
        RTThreadUserWait(Thread, RT_INDEFINITE_WAIT);
        RTThreadUserReset(Thread);
        ASMAtomicDecU32(&g_cWaiting);
        if (g_fDone)
            break;

        /*
         * Spin.
         */
        ASMAtomicIncU32(&g_cReady);
        while (!g_fDone)
        {
            const uint8_t   ApicId1 = ASMGetApicId();
            const uint64_t  TSC1    = ASMReadTSC();
            const uint32_t  u32Go   = g_u32Go;
            if (u32Go == 0)
                continue;

            if (u32Go == 1)
            {
                /* do the reading. */
                const uint8_t   ApicId2 = ASMGetApicId();
                const uint64_t  TSC2    = ASMReadTSC();
                const uint8_t   ApicId3 = ASMGetApicId();
                const uint64_t  TSC3    = ASMReadTSC();
                const uint8_t   ApicId4 = ASMGetApicId();

                if (    ApicId1 == ApicId2
                    &&  ApicId1 == ApicId3
                    &&  ApicId1 == ApicId4
                    &&  TSC3 - TSC1 < 2250 /* WARNING: This is just a guess, increase if it doesn't work for you. */
                    &&  TSC2 - TSC1 < TSC3 - TSC1
                    )
                {
                    /* succeeded. */
                    pTscData->TSC = TSC2;
                    pTscData->u8ApicId = ApicId1;
                    pTscData->fFailed = false;
                    pTscData->fRead = true;
                    ASMAtomicIncU32(&g_cRead);
                    break;
                }
            }

            /* failed */
            pTscData->fFailed = true;
            pTscData->fRead = false;
            ASMAtomicIncU32(&g_cFailed);
            break;
        }
    }

    return VINF_SUCCESS;
}

static int tstTSCCalcDrift(void)
{
    /*
     * This is only relevant to on SMP systems.
     */
    const unsigned cCpus = RTMpGetOnlineCount();
    if (cCpus <= 1)
    {
        RTPrintf("tstTSC: SKIPPED - Only relevant on SMP systems\n");
        return 0;
    }

    /*
     * Create the threads.
     */
    static TSCDATA s_aData[254];
    uint32_t i;
    if (cCpus > RT_ELEMENTS(s_aData))
    {
        RTPrintf("tstTSC: FAILED - too many CPUs (%u)\n", cCpus);
        return 1;
    }

    /* ourselves. */
    s_aData[0].Thread = RTThreadSelf();

    /* the others */
    for (i = 1; i < cCpus; i++)
    {
        int rc = RTThreadCreate(&s_aData[i].Thread, ThreadFunction, &s_aData[i], 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "OTHERCPU");
        if (RT_FAILURE(rc))
        {
            RTPrintf("tstTSC: FAILURE - RTThreatCreate failed when creating thread #%u, rc=%Rrc!\n", i, rc);
            ASMAtomicXchgSize(&g_fDone, true);
            while (i-- > 1)
            {
                RTThreadUserSignal(s_aData[i].Thread);
                RTThreadWait(s_aData[i].Thread, 5000, NULL);
            }
            return 1;
        }
    }

    /*
     * Retry until we get lucky (or give up).
     */
    for (unsigned cTries = 0; ; cTries++)
    {
        if (cTries > 10240)
        {
            RTPrintf("tstTSC: FAILURE - %d attempts, giving.\n", cTries);
            break;
        }

        /*
         * Wait for the other threads to get ready (brute force active wait, I'm lazy).
         */
        i = 0;
        while (g_cWaiting < cCpus - 1)
        {
            if (i++ > _2G32)
                break;
            RTThreadSleep(i & 0xf);
        }
        if (g_cWaiting != cCpus - 1)
        {
            RTPrintf("tstTSC: FAILURE - threads failed to get waiting (%d != %d (i=%d))\n", g_cWaiting + 1, cCpus, i);
            break;
        }

        /*
         * Send them spinning.
         */
        ASMAtomicXchgU32(&g_cReady, 0);
        ASMAtomicXchgU32(&g_u32Go, 0);
        ASMAtomicXchgU32(&g_cRead, 0);
        ASMAtomicXchgU32(&g_cFailed, 0);
        for (i = 1; i < cCpus; i++)
        {
            ASMAtomicXchgSize(&s_aData[i].fFailed, false);
            ASMAtomicXchgSize(&s_aData[i].fRead, false);
            ASMAtomicXchgU8(&s_aData[i].u8ApicId, 0xff);

            int rc = RTThreadUserSignal(s_aData[i].Thread);
            if (RT_FAILURE(rc))
                RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc!\n", i, rc);
        }

        /* wait for them to get ready. */
        i = 0;
        while (g_cReady < cCpus - 1)
        {
            if (i++ > _2G32)
                break;
        }
        if (g_cReady != cCpus - 1)
        {
            RTPrintf("tstTSC: FAILURE - threads failed to get ready (%d != %d, i=%d)\n", g_cWaiting + 1, cCpus, i);
            break;
        }

        /*
         * Flip the "go" switch and do our readings.
         * We give the other threads the slack it takes to two extra TSC and APIC ID reads.
         */
        const uint8_t   ApicId1 = ASMGetApicId();
        const uint64_t  TSC1    = ASMReadTSC();
        ASMAtomicXchgU32(&g_u32Go, 1);
        const uint8_t   ApicId2 = ASMGetApicId();
        const uint64_t  TSC2    = ASMReadTSC();
        const uint8_t   ApicId3 = ASMGetApicId();
        const uint64_t  TSC3    = ASMReadTSC();
        const uint8_t   ApicId4 = ASMGetApicId();
        const uint64_t  TSC4    = ASMReadTSC();
        ASMAtomicXchgU32(&g_u32Go, 2);
        const uint8_t   ApicId5 = ASMGetApicId();
        const uint64_t  TSC5    = ASMReadTSC();
        const uint8_t   ApicId6 = ASMGetApicId();

        /* Compose our own result. */
        if (    ApicId1 == ApicId2
            &&  ApicId1 == ApicId3
            &&  ApicId1 == ApicId4
            &&  ApicId1 == ApicId5
            &&  ApicId1 == ApicId6
            &&  TSC5 - TSC1 < 2750  /* WARNING: This is just a guess, increase if it doesn't work for you. */
            &&  TSC4 - TSC1 < TSC5 - TSC1
            &&  TSC3 - TSC1 < TSC4 - TSC1
            &&  TSC2 - TSC1 < TSC3 - TSC1
            )
        {
            /* succeeded. */
            s_aData[0].TSC = TSC2;
            s_aData[0].u8ApicId = ApicId1;
            s_aData[0].fFailed = false;
            s_aData[0].fRead = true;
            ASMAtomicIncU32(&g_cRead);
        }
        else
        {
            /* failed */
            s_aData[0].fFailed = true;
            s_aData[0].fRead = false;
            ASMAtomicIncU32(&g_cFailed);
        }

        /*
         * Wait a little while to let the other ones to finish.
         */
        i = 0;
        while (g_cRead + g_cFailed < cCpus)
        {
            if (i++ > _2G32)
                break;
            if (i > _1M)
                RTThreadSleep(i & 0xf);
        }
        if (g_cRead + g_cFailed != cCpus)
        {
            RTPrintf("tstTSC: FAILURE - threads failed to complete reading (%d + %d != %d)\n", g_cRead, g_cFailed, cCpus);
            break;
        }

        /*
         * If everone succeeded, print the results.
         */
        if (!g_cFailed)
        {
            /* sort it by apic id first. */
            bool fDone;
            do
            {
                for (i = 1, fDone = true; i < cCpus; i++)
                    if (s_aData[i - 1].u8ApicId > s_aData[i].u8ApicId)
                    {
                        TSCDATA Tmp = s_aData[i - 1];
                        s_aData[i - 1] = s_aData[i];
                        s_aData[i] = Tmp;
                        fDone = false;
                    }
            } while (!fDone);

            RTPrintf(" #  ID  TSC            delta0 (decimal)\n"
                     "-----------------------------------------\n");
            RTPrintf("%2d  %02x  %RX64\n", 0, s_aData[0].u8ApicId, s_aData[0].TSC);
            for (i = 1; i < cCpus; i++)
                RTPrintf("%2d  %02x  %RX64  %s%lld\n", i, s_aData[i].u8ApicId, s_aData[i].TSC,
                         s_aData[i].TSC > s_aData[0].TSC ? "+" : "", s_aData[i].TSC - s_aData[0].TSC);
            RTPrintf("(Needed %u attempt%s.)\n", cTries + 1, cTries ? "s" : "");
            break;
        }
    }

    /*
     * Destroy the threads.
     */
    ASMAtomicXchgSize(&g_fDone, true);
    for (i = 0; i < cCpus; i++)
        if (s_aData[i].Thread != RTThreadSelf())
        {
            int rc = RTThreadUserSignal(s_aData[i].Thread);
            if (RT_FAILURE(rc))
                RTPrintf("tstTSC: WARNING - RTThreadUserSignal(%#u) -> rc=%Rrc! (2)\n", i, rc);
        }
    for (i = 0; i < cCpus; i++)
        if (s_aData[i].Thread != RTThreadSelf())
        {
            int rc = RTThreadWait(s_aData[i].Thread, 5000, NULL);
            if (RT_FAILURE(rc))
                RTPrintf("tstTSC: WARNING - RTThreadWait(%#u) -> rc=%Rrc!\n", i, rc);
        }

    return g_cFailed != 0 || g_cRead != cCpus;
}


static int tstTSCCalcFrequency(uint32_t cMsDuration)
{
    /*
     * Sample the TSC and time, sleep the requested time and calc the deltas.
     */
    uint64_t uNanoTS = RTTimeSystemNanoTS();
    uint64_t uTSC    = ASMReadTSC();
    RTThreadSleep(cMsDuration);
    uNanoTS = RTTimeSystemNanoTS() - uNanoTS;
    uTSC    = ASMReadTSC() - uTSC;

    /*
     * Calc the frequency.
     */
    RTPrintf("tstTSC: %RU64 ticks in %RU64 ns\n", uTSC, uNanoTS);
    uint64_t cHz = (uint64_t)(uTSC / ((long double)uNanoTS / (long double)1000000000));
    RTPrintf("tstTSC: Frequency %RU64 Hz", cHz);
    if (cHz > _1G)
    {
        cHz += _1G / 20;
        RTPrintf("  %RU64.%RU64 GHz", cHz / _1G, (cHz % _1G) / (_1G / 10));
    }
    else if (cHz > _1M)
    {
        cHz += _1M / 20;
        RTPrintf("  %RU64.%RU64 MHz", cHz / _1M, (cHz % _1M) / (_1M / 10));
    }
    RTPrintf("\n");
    return 0;
}


int main(int argc, char **argv)
{
    RTR3Init();

    /*
     * Parse arguments.
     */
    bool fCalcFrequency = false;
    uint32_t cMsDuration = 1000; /* 1 sec */
    static const RTGETOPTDEF s_aOptions[] =
    {
        { "--duration",         'd', RTGETOPT_REQ_UINT32 },
        { "--calc-frequency",   'f', RTGETOPT_REQ_NOTHING },
    };
    int iArg = 1;
    int ch;
    RTGETOPTUNION Value;
    RTGETOPTSTATE GetState;
    RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /* fFlags */);
    while ((ch = RTGetOpt(&GetState, &Value)))
        switch (ch)
        {
            case 'd':   cMsDuration = Value.u32;
                break;

            case 'f':   fCalcFrequency = true;
                break;

            case 'h':
                RTPrintf("usage: tstTSC\n"
                         "   or: tstTSC <-f|--calc-frequency> [--duration|-d ms]\n");
                return 1;

            case 'V':
                RTPrintf("$Revision: $\n");
                return 0;

            default:
                return RTGetOptPrintError(ch, &Value);
        }

    if (fCalcFrequency)
        return tstTSCCalcFrequency(cMsDuration);
    return tstTSCCalcDrift();
}

