/*
 * 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>

#if defined(USE_ARM_SIMD) && defined(_MSC_VER)
/* Needed for EXCEPTION_ILLEGAL_INSTRUCTION */
#include <windows.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)

#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 (__linux__) || defined(ANDROID) /* linux ELF */

#include <stdlib.h>
#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 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;

#ifdef ANDROID

/* on Android, we can't reliably access /proc/self/auxv,
 * so instead read the text version in /proc/cpuinfo and
 * parse that instead.
 */

static void
pixman_arm_detect_cpu_features (void)
{
    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;
}

#else

static void
pixman_arm_detect_cpu_features (void)
{
    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

#if defined(USE_ARM_SIMD)
pixman_bool_t
pixman_have_arm_simd (void)
{
    if (!arm_tests_initialized)
	pixman_arm_detect_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_detect_cpu_features ();

    return arm_has_neon;
}

#endif /* USE_ARM_NEON */

#else /* linux ELF */

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

#endif

#endif /* USE_ARM_SIMD || USE_ARM_NEON */

#if defined(USE_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;
}

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;
}

#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_MMX
#define pixman_have_mmx() TRUE
#endif
#ifdef USE_SSE2
#define pixman_have_sse2() TRUE
#endif
#endif /* __amd64__ */
#endif

pixman_implementation_t *
_pixman_choose_implementation (void)
{
    pixman_implementation_t *imp;

    imp = _pixman_implementation_create_general();
    imp = _pixman_implementation_create_fast_path (imp);
    
#ifdef USE_MMX
    if (pixman_have_mmx ())
	imp = _pixman_implementation_create_mmx (imp);
#endif

#ifdef USE_SSE2
    if (pixman_have_sse2 ())
	imp = _pixman_implementation_create_sse2 (imp);
#endif

#ifdef USE_ARM_SIMD
    if (pixman_have_arm_simd ())
	imp = _pixman_implementation_create_arm_simd (imp);
#endif

#ifdef USE_ARM_NEON
    if (pixman_have_arm_neon ())
	imp = _pixman_implementation_create_arm_neon (imp);
#endif
    
#ifdef USE_VMX
    if (pixman_have_vmx ())
	imp = _pixman_implementation_create_vmx (imp);
#endif

    return imp;
}

