/* 
 * Querying processor features
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

#include <stdio.h>
#include <stdlib.h>

#include "rpplugin.h"
#include "rpdbgerr.h"
#include "rtintel.h"
#include "cpuid.h"

/*
 * -----------------------------------------------------------------
 * 
 * Subject:      Re: Q: timestamp in inline _asm?
 * From:         Terje Mathisen <Terje.Mathisen@hda.hydro.com>
 * Date:         1997/05/22
 * Message-ID:   <3383F0AF.ACB87A9E@hda.hydro.com>
 * Newsgroups:   comp.lang.asm.x86
 * 
 * [Subscribe to comp.lang.asm.x86] [New!]
 * [More Headers]
 * 
 * Soeren Juelsgaard wrote:
 * >
 * > Hi all,
 * > I want to read the timestamp counter, but my assembly code is inline
 * > assembler in C and the instruction rdtsc (ms vc4.1).
 * > I have seen the opcode defined as "rdtsc db 0Fh, 31h", how do I do
 * > something similar when I am using inline assembler?
 * 
 * MS aren't too happy about supporting inline asm, but it does work.
 * 
 * I wrote a little set of MS-compatible code a couple of days ago to do
 * this properly, including the tests to see if RDTSC is available:
 * 
 * Regards,
 * Terje
 * 
 * --
 * - <Terje.Mathisen@hda.hydro.com>
 * Using self-discipline, see http://www.eiffel.com/discipline
 * "almost all programming can be viewed as an exercise in caching"
 * 
 */

/*
 * See aslo
 * http://www2.dgsys.com/~raymoon/x86faqs.html
 */

#include "rtintel.h"
#include <rpdbgerr.h>
#include "cpuid.h"

static const char __RWUNUSED__   rcsid[] =
    "@@(#)$Id: cpuid.c,v 1.7 2001/06/12 08:55:05 johns Exp $";

/**
 * \ingroup rtintel
 * \ref RtIntelRDTSC read time stamp counter

 * \return Returns lower 32 bits of 64-bit result from ASM RDTSC
 */
RwUInt32
RtIntelRDTSC(void)              /* return lower 32 bits of 64-bit cnt */
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelRDTSC"));

#if (defined(_MSC_VER))
/* *INDENT-OFF* */
   __asm
   {
      _emit 0x0f;
      _emit 0x31;
      mov            [result], eax;
   }
/* *INDENT-ON* */

#endif /* (defined(_MSC_VER)) */

    RWRETURN(result);
}

/**
 * \ingroup rtintel
 * \ref RtIntelToggleEFLAGS checks to see if a flags bit can be toggled.

 * \param  mask   flags bit to be tested.
 *
 * \return Returns one of the following values:
 *      \li mask - On success
 *      \li FALSE - On failure
 */

/* Testing for cpu capabilities:
 * Check to see if a flags bit can be toggled.
 */
RwUInt32
RtIntelToggleEFLAGS(int __RWUNUSED__ mask)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelToggleEFLAGS"));

#if (defined(_MSC_VER))
   /* *INDENT-OFF* */
   __asm
   {
      pushfd;
      pushfd;
      mov            ebx, [mask];
      pop            eax;
      xor            ebx, eax;
      push           ebx;

      popfd;
      pushfd;
      pop            ebx;

      popfd;
      xor            eax, ebx;
      mov            [result], eax;      
   }
/* *INDENT-ON* */

#endif /* (defined(_MSC_VER)) */

    RWRETURN(result);
}

/**
 * \ingroup rtintel
 * \ref RtIntelCPUID CPU identification

 * \param  level   level of indentification to be queried
 * \param  pb      return value for EBX
 * \param  pc      return value for ECX
 * \param  pd      return value for EDX
 *
 * \return Returns value of EAX
 */

RwUInt32
RtIntelCPUID(RwUInt32 __RWUNUSED__ level, void *pb, void *pc, void *pd)
{
    RwUInt32            a = 0;
    RwUInt32            b = 0;
    RwUInt32            c = 0;
    RwUInt32            d = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelCPUID"));

#if (defined(_MSC_VER))
   /* *INDENT-OFF* */
   __asm
   {
      mov            eax, [level];
      _emit 0x0f;
      _emit 0xa2;
      mov[a], eax;
      mov[b], ebx;
      mov[c], ecx;
      mov[d], edx;

   }
/* *INDENT-ON* */

#endif /* (defined(_MSC_VER)) */

    if (pb)
    {
        *(RwUInt32 *) pb = b;
    }

    if (pc)
    {
        *(RwUInt32 *) pc = c;
    }

    if (pd)
    {
        *(RwUInt32 *) pd = d;
    }

    RWRETURN(a);
}

/**
 * \ingroup rtintel
 * \ref RtIntelHaveCPUID checks whether CPU supports CPUID function
 * \return Returns one of the following values:
 *      \li TRUE - On success
 *      \li FALSE - On failure
 */

RwUInt32
RtIntelHaveCPUID(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelHaveCPUID"));

    /* Check first that the ID eflags bit can be toggled.
     * If so, check also that CPUID can retrun a Feature mask.
     * Some very early Pentium samples does not support CPUID(1)
     */

    result = (RtIntelToggleEFLAGS(1 << 21) &&
              RtIntelCPUID(0, NULL, NULL, NULL));

    RWRETURN(result);
}

/**
 * \ingroup rtintel
 * \ref RtIntelHaveRDTSC checks whether CPU supports RDTSC function
 * \return Returns one of the following values:
 *      \li TRUE - On success
 *      \li FALSE - On failure
 */

RwUInt32
RtIntelHaveRDTSC(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelHaveRDTSC"));

    if (RtIntelHaveCPUID())
    {

        RtIntelCPUID(1, NULL, NULL, &result);

        result &= (1 << 4);
    }

    RWRETURN(result);

}

/**
 * \ingroup rtintel
 * \ref RtIntelHaveMMX checks whether CPU supports MMX functions
 * \return Returns one of the following values:
 *      \li Bit23 - On success
 *      \li FALSE - On failure
 */
RwUInt32
RtIntelHaveMMX(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelHaveMMX"));

    if (RtIntelHaveCPUID())
    {
        RtIntelCPUID(1, NULL, NULL, &result);

        result &= (1 << 23);
    }

#if ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) )
    {
        RwInt32             IgnoreMMX = 0;

        RWGETWINREGDWORD(IgnoreMMX, _T("IgnoreMMX"));
        if (IgnoreMMX)
            result = 0;
    }
#endif /* ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) ) */

    RWRETURN(result);

}

/*
 * An application must check that CPUID.SSE, bit 25 of the EDX
 * register, or CPUID.WNI, bit 26 of the EDX register, 
 * is set to one to verify that the processor supports all
 * Katmai New Instructions.
 */

#define CPUID_SSE (1 << 25)
#define CPUID_WNI (1 << 26)

/**
 * \ingroup rtintel
 * \ref RtIntelHaveSSE checks whether CPU supports 
 * Streaming SIMD Extenison/SSE functions
 *
 * \return Returns one of the following values:
 *      \li Bit25 or Bit26 - On success
 *      \li FALSE - On failure
 *
 */
RwUInt32
RtIntelHaveSSE(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelHaveSSE"));

    if (RtIntelHaveCPUID())
    {
        RtIntelCPUID(1, NULL, NULL, &result);

        result &= (CPUID_SSE | CPUID_WNI);
    }

#if ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) )
    {
        RwInt32             IgnoreSSE = 0;

        RWGETWINREGDWORD(IgnoreSSE, _T("IgnoreSSE"));
        if (IgnoreSSE)
            result = 0;
    }
#endif /* ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) ) */

    RWRETURN(result);

}

/*
 * An application must check that CPUID.WNI, bit 26 of the EDX
 * register, is set to one to verify that the processor supports all
 * Willamette New Instructions.
 */

/**
 * \ingroup rtintel
 * \ref RtIntelHaveWNI checks whether CPU supports 
 * Willamtte New Instruction/WNI functions
 *      \li Bit26 - On success
 *      \li FALSE - On failure
 *
 * \return Returns one of the following values:
 */
RwUInt32
RtIntelHaveWNI(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelHaveWNI"));

    if (RtIntelHaveCPUID())
    {
        RtIntelCPUID(1, NULL, NULL, &result);

        result &= CPUID_WNI;
    }

#if ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) )
    {
        RwInt32             IgnoreWNI = 0;

        RWGETWINREGDWORD(IgnoreWNI, _T("IgnoreWNI"));
        if (IgnoreWNI)
            result = 0;
    }
#endif /* ( defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER>=1000) ) */

    RWRETURN(result);

}

/**
 * \ingroup rtintel
 * \ref RtIntelCpuType returns CPUID-compatible type information

 * \return Returns (family, model, stepping) in the three lower nybbles
 */

/*
 * cputype() returns cpuid-compatible type information:
 * (family, model, stepping) in the three lower nybbles!
 */
RwUInt32
RtIntelCpuType(void)
{
    RwUInt32            result = 0;

    RWAPIFUNCTION(RWSTRING("RtIntelCpuType"));

    if (!RtIntelToggleEFLAGS(1 << 18))
    {
        result = 0x311;        /* 386 */
    }
    else if (!RtIntelHaveCPUID())
    {
        result = 0x411;        /* 486 */
    }
    else
    {
        result = RtIntelCPUID(1, NULL, NULL, NULL); /* 486+ to P2 ... */
    }

    RWRETURN(result);
}
