/*
 * Performance Counter Functions.
 * 2022-10-28 (c) Udo Krebelder <udo@krebelder.com>
 */

#include "winutil.h"


/* NtQueryPerformanceCounter */
#pragma comment(lib, "ntdll.lib")


NTSYSCALLAPI NTSTATUS NTAPI
NtQueryPerformanceCounter(PLARGE_INTEGER, PLARGE_INTEGER);


/*
 * Remember performance counter value and frequency of the first
 * NtQueryPerformanceCounter call.
 */
UINT64 g_PerfCountStartStamp;
UINT64 g_PerfCountFrequency;


/* Convert performance counter stamp difference to Milliseconds.
 * Will overflow after 29247 years. */
STATIC INLINE UINT64 PerfCountToMS_Inline(UINT64 stamp, UINT64 freq)
{
    UINT64 n = 0;

    if (freq) {
        /* Round to Milliseconds */
        freq /= 1000;
        n = ((2 * stamp) / freq + 1) / 2;
    }

    return n;
}


/* Convert performance counter stamp difference to 100 Microseconds. */
STATIC INLINE UINT64 PerfCountTo100US_Inline(UINT64 stamp, UINT64 freq)
{
    UINT64 n = 0;

    if (freq) {
        /* Round to Milliseconds */
        freq /= 1000;
        n = ((20 * stamp) / freq + 1) / 2;
    }

    return n;
}


/* Convert performance counter stamp difference to Microseconds. */
STATIC INLINE UINT64 PerfCountToUS_Inline(UINT64 stamp, UINT64 freq)
{
    UINT64 n = 0;

    if (freq) {
        /* Round to Microseconds */
        freq /= 1000;
        n = (1000 * stamp) / freq;
    }

    return n;
}


/* Convert performance counter timestamp to Nanoseconds.
 * Will overflow after 21 days. */
STATIC INLINE UINT64 PerfCountToNS_Inline(UINT64 stamp, UINT64 freq)
{
    UINT64 n = 0;

    if (freq) {
        freq /= 1000;
        n = (1000 * 1000 * stamp) / freq;
    }

    return n;
}


UINT64 PerfCountToMS(UINT64 stamp)
{
    return PerfCountToMS_Inline(stamp, g_PerfCountFrequency);
}


UINT64 PerfCountToUS(UINT64 stamp)
{
    return PerfCountToUS_Inline(stamp, g_PerfCountFrequency);
}


UINT64 PerfCountToNS(UINT64 stamp)
{
    return PerfCountToNS_Inline(stamp, g_PerfCountFrequency);
}


/* Convert the Performance counter timestamp to Picoseconds. */
UINT64 PerfCountToPS(UINT64 stamp)
{
    return 1000 * PerfCountToNS_Inline(stamp, g_PerfCountFrequency);
}


/*
 * Return elapsed time in Milliseconds since the
 * first call of QueryPerformanceCounter in startup code.
 */
UINT64 PerfCountTime(VOID)
{
    return PerfCountToMS_Inline(PerfCountStamp(), g_PerfCountFrequency);
}


/*
 * Return elapsed time in 100 Microseconds units since the
 * first call of QueryPerformanceCounter in startup code.
 */
UINT64 PerfCountTime100US(VOID)
{
    return PerfCountTo100US_Inline(PerfCountStamp(), g_PerfCountFrequency);
}


/*
 * Return elapsed time in Microseconds since the
 * first call of QueryPerformanceCounter in startup code.
 */
UINT64 PerfCountTimeUS(VOID)
{
    return PerfCountToUS_Inline(PerfCountStamp(), g_PerfCountFrequency);
}


/*
 * Return Performance counter time stamp value since first call.
 */
UINT64 PerfCountStamp(VOID)
{
    UINT64 stamp = 0;
    LARGE_INTEGER counter, freq;

    if (NT_SUCCESS(NtQueryPerformanceCounter(&counter, &freq)))
    {
        if (!g_PerfCountStartStamp)
        {
            g_PerfCountStartStamp = counter.QuadPart;
            g_PerfCountFrequency = freq.QuadPart;
        }

        stamp = counter.QuadPart - g_PerfCountStartStamp;
    }

    return stamp;
}


UINT64 OffsetTSC(VOID)
{
    UINT64 n, t0, t1, td, tmin = 127;

    /* Warm up the machine */
    for (n = 0; n < 1024*1; n++)
    {
        t0 = ReadTSC();
        t1 = ReadTSC();
        td = t1 - t0;
        tmin = MIN(tmin, td);
    }

    return tmin;
}
