599 lines
12 KiB
C
599 lines
12 KiB
C
|
/*
|
||
|
* 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 */
|
||
|
|
||
|
#else /* 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;
|
||
|
|
||
|
static void
|
||
|
pixman_arm_read_auxv ()
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
#if defined(USE_ARM_SIMD)
|
||
|
pixman_bool_t
|
||
|
pixman_have_arm_simd (void)
|
||
|
{
|
||
|
if (!arm_tests_initialized)
|
||
|
pixman_arm_read_auxv ();
|
||
|
|
||
|
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 ();
|
||
|
|
||
|
return arm_has_neon;
|
||
|
}
|
||
|
|
||
|
#endif /* USE_ARM_NEON */
|
||
|
|
||
|
#endif /* linux */
|
||
|
|
||
|
#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)
|
||
|
{
|
||
|
#ifdef USE_SSE2
|
||
|
if (pixman_have_sse2 ())
|
||
|
return _pixman_implementation_create_sse2 ();
|
||
|
#endif
|
||
|
#ifdef USE_MMX
|
||
|
if (pixman_have_mmx ())
|
||
|
return _pixman_implementation_create_mmx ();
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_ARM_NEON
|
||
|
if (pixman_have_arm_neon ())
|
||
|
return _pixman_implementation_create_arm_neon ();
|
||
|
#endif
|
||
|
#ifdef USE_ARM_SIMD
|
||
|
if (pixman_have_arm_simd ())
|
||
|
return _pixman_implementation_create_arm_simd ();
|
||
|
#endif
|
||
|
#ifdef USE_VMX
|
||
|
if (pixman_have_vmx ())
|
||
|
return _pixman_implementation_create_vmx ();
|
||
|
#endif
|
||
|
|
||
|
return _pixman_implementation_create_fast_path ();
|
||
|
}
|
||
|
|