/*
 * SpanDSP - a series of DSP components for telephony
 *
 * testcpuid.c - Check the CPU type, to identify special features, like SSE.
 *
 * Written by Steve Underwood <steveu@coppice.org>
 * Stitched together from bits of testing code found here and there
 *
 * Copyright (C) 2004 Steve Underwood
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: testcpuid.c,v 1.13 2009/01/05 13:48:31 steveu Exp $
 */

/*! \file */

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#include <inttypes.h>

/* Make this file just disappear if we are not on an x86 machine */
#if defined(__i386__) //  ||  defined(__x86_64__)

#define X86_EFLAGS_CF   0x00000001 /* Carry Flag */
#define X86_EFLAGS_PF   0x00000004 /* Parity Flag */
#define X86_EFLAGS_AF   0x00000010 /* Auxillary carry Flag */
#define X86_EFLAGS_ZF   0x00000040 /* Zero Flag */
#define X86_EFLAGS_SF   0x00000080 /* Sign Flag */
#define X86_EFLAGS_TF   0x00000100 /* Trap Flag */
#define X86_EFLAGS_IF   0x00000200 /* Interrupt Flag */
#define X86_EFLAGS_DF   0x00000400 /* Direction Flag */
#define X86_EFLAGS_OF   0x00000800 /* Overflow Flag */
#define X86_EFLAGS_IOPL 0x00003000 /* IOPL mask */
#define X86_EFLAGS_NT   0x00004000 /* Nested Task */
#define X86_EFLAGS_RF   0x00010000 /* Resume Flag */
#define X86_EFLAGS_VM   0x00020000 /* Virtual Mode */
#define X86_EFLAGS_AC   0x00040000 /* Alignment Check */
#define X86_EFLAGS_VIF  0x00080000 /* Virtual Interrupt Flag */
#define X86_EFLAGS_VIP  0x00100000 /* Virtual Interrupt Pending */
#define X86_EFLAGS_ID   0x00200000 /* CPUID detection flag */

/* Standard macro to see if a specific flag is changeable */
static __inline__ int flag_is_changeable_p(uint32_t flag)
{
    uint32_t f1;
    uint32_t f2;

    __asm__ __volatile__ (
        " pushfl\n"
        " pushfl\n"
        " popl %0\n"
        " movl %0,%1\n"
        " xorl %2,%0\n"
        " pushl %0\n"
        " popfl\n"
        " pushfl\n"
        " popl %0\n"
        " popfl\n"
        : "=&r" (f1), "=&r" (f2)
        : "ir" (flag));

    return ((f1^f2) & flag) != 0;
}
/*- End of function --------------------------------------------------------*/

/* Probe for the CPUID instruction */
static int have_cpuid_p(void)
{
    return flag_is_changeable_p(X86_EFLAGS_ID);
}
/*- End of function --------------------------------------------------------*/

int has_MMX(void)
{
    int result;

    if (!have_cpuid_p())
        return  0;
    /*endif*/
    __asm__ __volatile__ (
        " push  %%ebx;\n"
	" mov	$1,%%eax;\n"
	" cpuid;\n"
	" xor   %%eax,%%eax;\n"
	" test	$0x800000,%%edx;\n"
	" jz	1f;\n"	            /* no MMX support */
	" inc   %%eax;\n"	    /* MMX support */
        "1:\n"
        " pop  %%ebx;\n"
	: "=a" (result)
        : 
        : "ecx", "edx");
    return  result;
}
/*- End of function --------------------------------------------------------*/
        
int has_SIMD(void)
{
    int result;

    if (!have_cpuid_p())
        return  0;
    /*endif*/
    __asm__ __volatile__ (
        " push  %%ebx;\n"
	" mov	$1,%%eax;\n"
	" cpuid;\n"
	" xor   %%eax,%%eax;\n"
	" test	$0x02000000,%%edx;\n"
	" jz	1f;\n"		        /* no SIMD support */
	" inc	%%eax;\n"		/* SIMD support */
        "1:\n"
        " pop  %%ebx;\n"
	: "=a" (result)
        : 
        : "ecx", "edx");
    return  result;
}
/*- End of function --------------------------------------------------------*/

int has_SIMD2(void)
{
    int result;

    if (!have_cpuid_p())
        return  0;
    /*endif*/
    __asm__ __volatile__ (
        " push  %%ebx;\n"
	" mov	$1,%%eax;\n"
	" cpuid;\n"
	" xor   %%eax,%%eax;\n"
	" test	$0x04000000,%%edx;\n"
	" jz	1f;\n"		        /* no SIMD2 support */
	" inc	%%eax;\n"		/* SIMD2 support */
        "1:\n"
        " pop  %%ebx;\n"
	: "=a" (result)
        : 
        : "ecx", "edx");
    return  result;
}
/*- End of function --------------------------------------------------------*/
        
int has_3DNow(void)
{
    int result;

    if (!have_cpuid_p())
        return  0;
    /*endif*/
    __asm__ __volatile__ (
        " push  %%ebx;\n"
	" mov	$0x80000000,%%eax;\n"
	" cpuid;\n"
        " xor   %%ecx,%%ecx;\n"
	" cmp	$0x80000000,%%eax;\n"
	" jbe	1f;\n"		        /* no extended MSR(1), so no 3DNow! */
	" mov	$0x80000001,%%eax;\n"
	" cpuid;\n"
        " xor   %%ecx,%%ecx;\n"
	" test	$0x80000000,%%edx;\n"
	" jz	1f;\n"		        /* no 3DNow! support */
	" inc   %%ecx;\n"		/* 3DNow! support */
        "1:\n"
        " pop  %%ebx;\n"
	: "=c" (result)
        : 
        : "eax", "edx");
    return  result;
}
/*- End of function --------------------------------------------------------*/

#if defined(TESTBED)
int main(int argc, char *argv[])
{
    int result;

    result = has_MMX();
    printf("MMX is %x\n", result);
    result = has_SIMD();
    printf("SIMD is %x\n", result);
    result = has_SIMD2();
    printf("SIMD2 is %x\n", result);
    result = has_3DNow();
    printf("3DNow is %x\n", result);
    return  0;
}
/*- End of function --------------------------------------------------------*/
#endif

#endif
/*- End of file ------------------------------------------------------------*/
