/*
 * Copyright © 2000 SuSE, Inc.
 * Copyright © 2007 Red Hat, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of SuSE not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  SuSE makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * SuSE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL SuSE
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <stdlib.h>

#if defined(USE_ARM_SIMD) && defined(_MSC_VER)
/* Needed for EXCEPTION_ILLEGAL_INSTRUCTION */
#include <windows.h>
#endif

#if defined(__APPLE__)
#include "TargetConditionals.h"
#endif

#include "pixman-private.h"

#ifdef USE_VMX

/* The CPU detection code needs to be in a file not compiled with
 * "-maltivec -mabi=altivec", as gcc would try to save vector register
 * across function calls causing SIGILL on cpus without Altivec/vmx.
 */
static pixman_bool_t initialized = FALSE;
static volatile pixman_bool_t have_vmx = TRUE;

#ifdef __APPLE__
#include <sys/sysctl.h>

static pixman_bool_t
pixman_have_vmx (void)
{
    if (!initialized)
    {
	size_t length = sizeof(have_vmx);
	int error =
	    sysctlbyname ("hw.optional.altivec", &have_vmx, &length, NULL, 0);

	if (error)
	    have_vmx = FALSE;

	initialized = TRUE;
    }
    return have_vmx;
}

#elif defined (__OpenBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <machine/cpu.h>

static pixman_bool_t
pixman_have_vmx (void)
{
    if (!initialized)
    {
	int mib[2] = { CTL_MACHDEP, CPU_ALTIVEC };
	size_t length = sizeof(have_vmx);
	int error =
	    sysctl (mib, 2, &have_vmx, &length, NULL, 0);

	if (error != 0)
	    have_vmx = FALSE;

	initialized = TRUE;
    }
    return have_vmx;
}

#elif defined (__linux__)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/auxvec.h>
#include <asm/cputable.h>

static pixman_bool_t
pixman_have_vmx (void)
{
    if (!initialized)
    {
	char fname[64];
	unsigned long buf[64];
	ssize_t count = 0;
	pid_t pid;
	int fd, i;

	pid = getpid ();
	snprintf (fname, sizeof(fname) - 1, "/proc/%d/auxv", pid);

	fd = open (fname, O_RDONLY);
	if (fd >= 0)
	{
	    for (i = 0; i <= (count / sizeof(unsigned long)); i += 2)
	    {
		/* Read more if buf is empty... */
		if (i == (count / sizeof(unsigned long)))
		{
		    count = read (fd, buf, sizeof(buf));
		    if (count <= 0)
			break;
		    i = 0;
		}

		if (buf[i] == AT_HWCAP)
		{
		    have_vmx = !!(buf[i + 1] & PPC_FEATURE_HAS_ALTIVEC);
		    initialized = TRUE;
		    break;
		}
		else if (buf[i] == AT_NULL)
		{
		    break;
		}
	    }
	    close (fd);
	}
    }
    if (!initialized)
    {
	/* Something went wrong. Assume 'no' rather than playing
	   fragile tricks with catching SIGILL. */
	have_vmx = FALSE;
	initialized = TRUE;
    }

    return have_vmx;
}

#else /* !__APPLE__ && !__OpenBSD__ && !__linux__ */
#include <signal.h>
#include <setjmp.h>

static jmp_buf jump_env;

static void
vmx_test (int        sig,
	  siginfo_t *si,
	  void *     unused)
{
    longjmp (jump_env, 1);
}

static pixman_bool_t
pixman_have_vmx (void)
{
    struct sigaction sa, osa;
    int jmp_result;

    if (!initialized)
    {
	sa.sa_flags = SA_SIGINFO;
	sigemptyset (&sa.sa_mask);
	sa.sa_sigaction = vmx_test;
	sigaction (SIGILL, &sa, &osa);
	jmp_result = setjmp (jump_env);
	if (jmp_result == 0)
	{
	    asm volatile ( "vor 0, 0, 0" );
	}
	sigaction (SIGILL, &osa, NULL);
	have_vmx = (jmp_result == 0);
	initialized = TRUE;
    }
    return have_vmx;
}

#endif /* __APPLE__ */
#endif /* USE_VMX */

#if defined(USE_ARM_SIMD) || defined(USE_ARM_NEON) || defined(USE_ARM_IWMMXT)

#if defined(_MSC_VER)

#if defined(USE_ARM_SIMD)
extern int pixman_msvc_try_arm_simd_op ();

pixman_bool_t
pixman_have_arm_simd (void)
{
    static pixman_bool_t initialized = FALSE;
    static pixman_bool_t have_arm_simd = FALSE;

    if (!initialized)
    {
	__try {
	    pixman_msvc_try_arm_simd_op ();
	    have_arm_simd = TRUE;
	} __except (GetExceptionCode () == EXCEPTION_ILLEGAL_INSTRUCTION) {
	    have_arm_simd = FALSE;
	}
	initialized = TRUE;
    }

    return have_arm_simd;
}

#endif /* USE_ARM_SIMD */

#if defined(USE_ARM_NEON)
extern int pixman_msvc_try_arm_neon_op ();

pixman_bool_t
pixman_have_arm_neon (void)
{
    static pixman_bool_t initialized = FALSE;
    static pixman_bool_t have_arm_neon = FALSE;

    if (!initialized)
    {
	__try
	{
	    pixman_msvc_try_arm_neon_op ();
	    have_arm_neon = TRUE;
	}
	__except (GetExceptionCode () == EXCEPTION_ILLEGAL_INSTRUCTION)
	{
	    have_arm_neon = FALSE;
	}
	initialized = TRUE;
    }

    return have_arm_neon;
}

#endif /* USE_ARM_NEON */

#elif (defined (__APPLE__) && defined(TARGET_OS_IPHONE)) /* iOS (iPhone/iPad/iPod touch) */

/* Detection of ARM NEON on iOS is fairly simple because iOS binaries
 * contain separate executable images for each processor architecture.
 * So all we have to do is detect the armv7 architecture build. The
 * operating system automatically runs the armv7 binary for armv7 devices
 * and the armv6 binary for armv6 devices.
 */

pixman_bool_t
pixman_have_arm_simd (void)
{
#if defined(USE_ARM_SIMD)
    return TRUE;
#else
    return FALSE;
#endif
}

pixman_bool_t
pixman_have_arm_neon (void)
{
#if defined(USE_ARM_NEON) && defined(__ARM_NEON__)
    /* This is an armv7 cpu build */
    return TRUE;
#else
    /* This is an armv6 cpu build */
    return FALSE;
#endif
}

pixman_bool_t
pixman_have_arm_iwmmxt (void)
{
#if defined(USE_ARM_IWMMXT)
    return FALSE;
#else
    return FALSE;
#endif
}

#elif defined (__linux__) || defined(__ANDROID__) || defined(ANDROID) /* linux ELF or ANDROID */

static pixman_bool_t arm_has_v7 = FALSE;
static pixman_bool_t arm_has_v6 = FALSE;
static pixman_bool_t arm_has_vfp = FALSE;
static pixman_bool_t arm_has_neon = FALSE;
static pixman_bool_t arm_has_iwmmxt = FALSE;
static pixman_bool_t arm_tests_initialized = FALSE;

#if defined(__ANDROID__) || defined(ANDROID) /* Android device support */

static void
pixman_arm_read_auxv_or_cpu_features ()
{
    char buf[1024];
    char* pos;
    const char* ver_token = "CPU architecture: ";
    FILE* f = fopen("/proc/cpuinfo", "r");
    if (!f) {
	arm_tests_initialized = TRUE;
	return;
    }

    fread(buf, sizeof(char), sizeof(buf), f);
    fclose(f);
    pos = strstr(buf, ver_token);
    if (pos) {
	char vchar = *(pos + strlen(ver_token));
	if (vchar >= '0' && vchar <= '9') {
	    int ver = vchar - '0';
	    arm_has_v7 = ver >= 7;
	    arm_has_v6 = ver >= 6;
	}
    }
    arm_has_neon = strstr(buf, "neon") != NULL;
    arm_has_vfp = strstr(buf, "vfp") != NULL;
    arm_has_iwmmxt = strstr(buf, "iwmmxt") != NULL;
    arm_tests_initialized = TRUE;
}

#elif defined (__linux__) /* linux ELF */

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <elf.h>

static void
pixman_arm_read_auxv_or_cpu_features ()
{
    int fd;
    Elf32_auxv_t aux;

    fd = open ("/proc/self/auxv", O_RDONLY);
    if (fd >= 0)
    {
	while (read (fd, &aux, sizeof(Elf32_auxv_t)) == sizeof(Elf32_auxv_t))
	{
	    if (aux.a_type == AT_HWCAP)
	    {
		uint32_t hwcap = aux.a_un.a_val;
		/* hardcode these values to avoid depending on specific
		 * versions of the hwcap header, e.g. HWCAP_NEON
		 */
		arm_has_vfp = (hwcap & 64) != 0;
		arm_has_iwmmxt = (hwcap & 512) != 0;
		/* this flag is only present on kernel 2.6.29 */
		arm_has_neon = (hwcap & 4096) != 0;
	    }
	    else if (aux.a_type == AT_PLATFORM)
	    {
		const char *plat = (const char*) aux.a_un.a_val;
		if (strncmp (plat, "v7l", 3) == 0)
		{
		    arm_has_v7 = TRUE;
		    arm_has_v6 = TRUE;
		}
		else if (strncmp (plat, "v6l", 3) == 0)
		{
		    arm_has_v6 = TRUE;
		}
	    }
	}
	close (fd);
    }

    arm_tests_initialized = TRUE;
}

#endif /* Linux elf */

#if defined(USE_ARM_SIMD)
pixman_bool_t
pixman_have_arm_simd (void)
{
    if (!arm_tests_initialized)
	pixman_arm_read_auxv_or_cpu_features ();

    return arm_has_v6;
}

#endif /* USE_ARM_SIMD */

#if defined(USE_ARM_NEON)
pixman_bool_t
pixman_have_arm_neon (void)
{
    if (!arm_tests_initialized)
	pixman_arm_read_auxv_or_cpu_features ();

    return arm_has_neon;
}

#endif /* USE_ARM_NEON */

#if defined(USE_ARM_IWMMXT)
pixman_bool_t
pixman_have_arm_iwmmxt (void)
{
    if (!arm_tests_initialized)
	pixman_arm_read_auxv_or_cpu_features ();

    return arm_has_iwmmxt;
}

#endif /* USE_ARM_IWMMXT */

#else /* !_MSC_VER && !Linux elf && !Android */

#define pixman_have_arm_simd() FALSE
#define pixman_have_arm_neon() FALSE
#define pixman_have_arm_iwmmxt() FALSE

#endif

#endif /* USE_ARM_SIMD || USE_ARM_NEON || USE_ARM_IWMMXT */

#if defined(USE_MIPS_DSPR2)

#if defined (__linux__) /* linux ELF */

pixman_bool_t
pixman_have_mips_dspr2 (void)
{
    const char *search_string = "MIPS 74K";
    const char *file_name = "/proc/cpuinfo";
    /* Simple detection of MIPS DSP ASE (revision 2) at runtime for Linux.
     * It is based on /proc/cpuinfo, which reveals hardware configuration
     * to user-space applications.  According to MIPS (early 2010), no similar
     * facility is universally available on the MIPS architectures, so it's up
     * to individual OSes to provide such.
     *
     * Only currently available MIPS core that supports DSPr2 is 74K.
     */

    char cpuinfo_line[256];

    FILE *f = NULL;

    if ((f = fopen (file_name, "r")) == NULL)
        return FALSE;

    while (fgets (cpuinfo_line, sizeof (cpuinfo_line), f) != NULL)
    {
        if (strstr (cpuinfo_line, search_string) != NULL)
        {
            fclose (f);
            return TRUE;
        }
    }

    fclose (f);

    /* Did not find string in the proc file. */
    return FALSE;
}

#else /* linux ELF */

#define pixman_have_mips_dspr2() FALSE

#endif /* linux ELF */

#endif /* USE_MIPS_DSPR2 */

#if defined(USE_X86_MMX) || defined(USE_SSE2)
/* The CPU detection code needs to be in a file not compiled with
 * "-mmmx -msse", as gcc would generate CMOV instructions otherwise
 * that would lead to SIGILL instructions on old CPUs that don't have
 * it.
 */
#if !defined(__amd64__) && !defined(__x86_64__) && !defined(_M_AMD64)

#ifdef HAVE_GETISAX
#include <sys/auxv.h>
#endif

typedef enum
{
    NO_FEATURES = 0,
    MMX = 0x1,
    MMX_EXTENSIONS = 0x2,
    SSE = 0x6,
    SSE2 = 0x8,
    CMOV = 0x10
} cpu_features_t;


static unsigned int
detect_cpu_features (void)
{
    unsigned int features = 0;
    unsigned int result = 0;

#ifdef HAVE_GETISAX
    if (getisax (&result, 1))
    {
	if (result & AV_386_CMOV)
	    features |= CMOV;
	if (result & AV_386_MMX)
	    features |= MMX;
	if (result & AV_386_AMD_MMX)
	    features |= MMX_EXTENSIONS;
	if (result & AV_386_SSE)
	    features |= SSE;
	if (result & AV_386_SSE2)
	    features |= SSE2;
    }
#else
    char vendor[13];
#ifdef _MSC_VER
    int vendor0 = 0, vendor1, vendor2;
#endif
    vendor[0] = 0;
    vendor[12] = 0;

#ifdef __GNUC__
    /* see p. 118 of amd64 instruction set manual Vol3 */
    /* We need to be careful about the handling of %ebx and
     * %esp here. We can't declare either one as clobbered
     * since they are special registers (%ebx is the "PIC
     * register" holding an offset to global data, %esp the
     * stack pointer), so we need to make sure they have their
     * original values when we access the output operands.
     */
    __asm__ (
        "pushf\n"
        "pop %%eax\n"
        "mov %%eax, %%ecx\n"
        "xor $0x00200000, %%eax\n"
        "push %%eax\n"
        "popf\n"
        "pushf\n"
        "pop %%eax\n"
        "mov $0x0, %%edx\n"
        "xor %%ecx, %%eax\n"
        "jz 1f\n"

        "mov $0x00000000, %%eax\n"
        "push %%ebx\n"
        "cpuid\n"
        "mov %%ebx, %%eax\n"
        "pop %%ebx\n"
        "mov %%eax, %1\n"
        "mov %%edx, %2\n"
        "mov %%ecx, %3\n"
        "mov $0x00000001, %%eax\n"
        "push %%ebx\n"
        "cpuid\n"
        "pop %%ebx\n"
        "1:\n"
        "mov %%edx, %0\n"
	: "=r" (result),
        "=m" (vendor[0]),
        "=m" (vendor[4]),
        "=m" (vendor[8])
	:
	: "%eax", "%ecx", "%edx"
        );

#elif defined (_MSC_VER)

    _asm {
	pushfd
	pop eax
	mov ecx, eax
	xor eax, 00200000h
	push eax
	popfd
	pushfd
	pop eax
	mov edx, 0
	xor eax, ecx
	jz nocpuid

	mov eax, 0
	push ebx
	cpuid
	mov eax, ebx
	pop ebx
	mov vendor0, eax
	mov vendor1, edx
	mov vendor2, ecx
	mov eax, 1
	push ebx
	cpuid
	pop ebx
    nocpuid:
	mov result, edx
    }
    memmove (vendor + 0, &vendor0, 4);
    memmove (vendor + 4, &vendor1, 4);
    memmove (vendor + 8, &vendor2, 4);

#else
#   error unsupported compiler
#endif

    features = 0;
    if (result)
    {
	/* result now contains the standard feature bits */
	if (result & (1 << 15))
	    features |= CMOV;
	if (result & (1 << 23))
	    features |= MMX;
	if (result & (1 << 25))
	    features |= SSE;
	if (result & (1 << 26))
	    features |= SSE2;
	if ((features & MMX) && !(features & SSE) &&
	    (strcmp (vendor, "AuthenticAMD") == 0 ||
	     strcmp (vendor, "Geode by NSC") == 0))
	{
	    /* check for AMD MMX extensions */
#ifdef __GNUC__
	    __asm__ (
	        "	push %%ebx\n"
	        "	mov $0x80000000, %%eax\n"
	        "	cpuid\n"
	        "	xor %%edx, %%edx\n"
	        "	cmp $0x1, %%eax\n"
	        "	jge 2f\n"
	        "	mov $0x80000001, %%eax\n"
	        "	cpuid\n"
	        "2:\n"
	        "	pop %%ebx\n"
	        "	mov %%edx, %0\n"
		: "=r" (result)
		:
		: "%eax", "%ecx", "%edx"
	        );
#elif defined _MSC_VER
	    _asm {
		push ebx
		mov eax, 80000000h
		cpuid
		xor edx, edx
		cmp eax, 1
		jge notamd
		mov eax, 80000001h
		cpuid
	    notamd:
		pop ebx
		mov result, edx
	    }
#endif
	    if (result & (1 << 22))
		features |= MMX_EXTENSIONS;
	}
    }
#endif /* HAVE_GETISAX */

    return features;
}

#ifdef USE_X86_MMX
static pixman_bool_t
pixman_have_mmx (void)
{
    static pixman_bool_t initialized = FALSE;
    static pixman_bool_t mmx_present;

    if (!initialized)
    {
	unsigned int features = detect_cpu_features ();
	mmx_present = (features & (MMX | MMX_EXTENSIONS)) == (MMX | MMX_EXTENSIONS);
	initialized = TRUE;
    }

    return mmx_present;
}
#endif

#ifdef USE_SSE2
static pixman_bool_t
pixman_have_sse2 (void)
{
    static pixman_bool_t initialized = FALSE;
    static pixman_bool_t sse2_present;

    if (!initialized)
    {
	unsigned int features = detect_cpu_features ();
	sse2_present = (features & (MMX | MMX_EXTENSIONS | SSE | SSE2)) == (MMX | MMX_EXTENSIONS | SSE | SSE2);
	initialized = TRUE;
    }

    return sse2_present;
}

#endif

#else /* __amd64__ */
#ifdef USE_X86_MMX
#define pixman_have_mmx() TRUE
#endif
#ifdef USE_SSE2
#define pixman_have_sse2() TRUE
#endif
#endif /* __amd64__ */
#endif

static pixman_bool_t
disabled (const char *name)
{
    const char *env;

    if ((env = getenv ("PIXMAN_DISABLE")))
    {
	do
	{
	    const char *end;
	    int len;

	    if ((end = strchr (env, ' ')))
		len = end - env;
	    else
		len = strlen (env);

	    if (strlen (name) == len && strncmp (name, env, len) == 0)
	    {
		printf ("pixman: Disabled %s implementation\n", name);
		return TRUE;
	    }

	    env += len;
	}
	while (*env++);
    }

    return FALSE;
}

pixman_implementation_t *
_pixman_choose_implementation (void)
{
    pixman_implementation_t *imp;

    imp = _pixman_implementation_create_general();

    if (!disabled ("fast"))
	imp = _pixman_implementation_create_fast_path (imp);

#ifdef USE_X86_MMX
    if (!disabled ("mmx") && pixman_have_mmx ())
	imp = _pixman_implementation_create_mmx (imp);
#endif

#ifdef USE_SSE2
    if (!disabled ("sse2") && pixman_have_sse2 ())
	imp = _pixman_implementation_create_sse2 (imp);
#endif

#ifdef USE_ARM_SIMD
    if (!disabled ("arm-simd") && pixman_have_arm_simd ())
	imp = _pixman_implementation_create_arm_simd (imp);
#endif

#ifdef USE_ARM_IWMMXT
    if (!disabled ("arm-iwmmxt") && pixman_have_arm_iwmmxt ())
	imp = _pixman_implementation_create_mmx (imp);
#endif

#ifdef USE_ARM_NEON
    if (!disabled ("arm-neon") && pixman_have_arm_neon ())
	imp = _pixman_implementation_create_arm_neon (imp);
#endif

#ifdef USE_MIPS_DSPR2
    if (!disabled ("mips-dspr2") && pixman_have_mips_dspr2 ())
	imp = _pixman_implementation_create_mips_dspr2 (imp);
#endif

#ifdef USE_VMX
    if (!disabled ("vmx") && pixman_have_vmx ())
	imp = _pixman_implementation_create_vmx (imp);
#endif

    imp = _pixman_implementation_create_noop (imp);

    return imp;
}