/**
 * =========================================================================
 * File        : tsc.cpp
 * Project     : 0 A.D.
 * Description : Timer implementation using RDTSC
 * =========================================================================
 */

// license: GPL; see lib/license.txt

#include "precompiled.h"
#include "tsc.h"

#include "lib/sysdep/win/win.h"
#include "lib/sysdep/win/wcpu.h"
#include "lib/sysdep/ia32/ia32.h"   // ia32_rdtsc
#include "lib/bits.h"


//-----------------------------------------------------------------------------
// detect throttling

enum AmdPowerNowFlags
{
    PN_FREQ_ID_CTRL    = BIT(1),
    PN_SW_THERMAL_CTRL = BIT(5),
    PN_INVARIANT_TSC   = BIT(8)
};

static bool IsThrottlingPossible()
{
    u32 regs[4];

    switch(ia32_Vendor())
    {
    case IA32_VENDOR_INTEL:
        if(ia32_cap(IA32_CAP_TM_SCC) || ia32_cap(IA32_CAP_EST))
            return true;
        break;

    case IA32_VENDOR_AMD:
        if(ia32_asm_cpuid(0x80000007, regs))
        {
            if(regs[EDX] & (PN_FREQ_ID_CTRL|PN_SW_THERMAL_CTRL))
                return true;
        }
        break;
    }

    return false;
}


//-----------------------------------------------------------------------------

LibError CounterTSC::Activate()
{
    ia32_Init();

    if(!ia32_cap(IA32_CAP_TSC))
        return ERR::NO_SYS;     // NOWARN (CPU doesn't support RDTSC)

    return INFO::OK;
}

void CounterTSC::Shutdown()
{
    ia32_Shutdown();
}

bool CounterTSC::IsSafe() const
{
    // use of the TSC for timing is subject to a litany of potential problems:
    // - separate, unsynchronized counters with offset and drift;
    // - frequency changes (P-state transitions and STPCLK throttling);
    // - failure to increment in C3 and C4 deep-sleep states.
    // we will discuss the specifics below.

    // SMP or multi-core => counters are unsynchronized. this could be
    // solved by maintaining separate per-core counter states, but that
    // requires atomic reads of the TSC and the current processor number.
    //
    // (otherwise, we have a subtle race condition: if preempted while
    // reading the time and rescheduled on a different core, incorrect
    // results may be returned, which would be unacceptable.)
    //
    // unfortunately this isn't possible without OS support or the
    // as yet unavailable RDTSCP instruction => unsafe.
    //
    // (note: if the TSC is invariant, drift is no longer a concern.
    // we could synchronize the TSC MSRs during initialization and avoid
    // per-core counter state and the abovementioned race condition.
    // however, we won't bother, since such platforms aren't yet widespread
    // and would surely support the nice and safe HPET, anyway)
    if(ia32_NumPackages() != 1 || ia32_CoresPerPackage() != 1)
        return false;

    // recent CPU:
    if(ia32_Generation() >= 7)
    {
        // note: 8th generation CPUs support C1-clock ramping, which causes
        // drift on multi-core systems, but those were excluded above.

        u32 regs[4];
        if(ia32_asm_cpuid(0x80000007, regs))
        {
            // TSC is invariant WRT P-state, C-state and STPCLK => safe.
            if(regs[EDX] & PN_INVARIANT_TSC)
                return true;
        }

        // in addition to P-state transitions, we're also subject to
        // STPCLK throttling. this happens when the chipset thinks the
        // system is dangerously overheated; the OS isn't even notified.
        // it may be rare, but could cause incorrect results => unsafe.
        return false;

        // newer systems also support the C3 Deep Sleep state, in which
        // the TSC isn't incremented. that's not nice, but irrelevant
        // since STPCLK dooms the TSC on those systems anyway.
    }

    // we're dealing with a single older CPU; the only problem there is
    // throttling, i.e. changes to the TSC frequency. we don't want to
    // disable this because it may be important for cooling. the OS
    // initiates changes but doesn't notify us; jumps are too frequent
    // and drastic to detect and account for => unsafe.
    if(IsThrottlingPossible())
        return false;

    return true;
}

u64 CounterTSC::Counter() const
{
    return ia32_rdtsc();
}

/**
 * WHRT uses this to ensure the counter (running at nominal frequency)
 * doesn't overflow more than once during CALIBRATION_INTERVAL_MS.
 **/
uint CounterTSC::CounterBits() const
{
    return 64;
}

/**
 * initial measurement of the tick rate. not necessarily correct
 * (e.g. when using TSC: wcpu_ClockFrequency isn't exact).
 **/
double CounterTSC::NominalFrequency() const
{
    return wcpu_ClockFrequency();
}