/**
 * \file
 * ARM hardware feature detection
 *
 * Authors:
 *    Alex Rønne Petersen (alexrp@xamarin.com)
 *    Elijah Taylor (elijahtaylor@google.com)
 *    Miguel de Icaza (miguel@xamarin.com)
 *    Neale Ferguson (Neale.Ferguson@SoftwareAG-usa.com)
 *    Paolo Molaro (lupus@xamarin.com)
 *    Rodrigo Kumpera (kumpera@gmail.com)
 *    Sebastien Pouliot (sebastien@xamarin.com)
 *    Zoltan Varga (vargaz@xamarin.com)
 *
 * Copyright 2003 Ximian, Inc.
 * Copyright 2003-2011 Novell, Inc
 * Copyright 2006 Broadcom
 * Copyright 2007-2008 Andreas Faerber
 * Copyright 2011-2013 Xamarin Inc
 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
 */

#include "mono/utils/mono-hwcap.h"

#if defined(HAVE_SYS_AUXV_H) && !defined(HOST_ANDROID)
#include <sys/auxv.h>
#elif defined(__APPLE__)
#include <mach/machine.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#else
#if defined (HAVE_SYS_UTSNAME_H)
#include <sys/utsname.h>
#endif
#include <stdio.h>
#endif

void
mono_hwcap_arch_init (void)
{
#if defined(HAVE_SYS_AUXV_H) && !defined(HOST_ANDROID)
	unsigned long hwcap;
	unsigned long platform;

	if ((hwcap = getauxval(AT_HWCAP))) {
		/* HWCAP_ARM_THUMB */
		if (hwcap & 0x00000004)
			mono_hwcap_arm_has_thumb = TRUE;

		/* HWCAP_ARM_VFP */
		if (hwcap & 0x00000040)
			mono_hwcap_arm_has_vfp = TRUE;

		/* HWCAP_ARM_VFPv3 */
		if (hwcap & 0x00002000)
			mono_hwcap_arm_has_vfp3 = TRUE;

		/* HWCAP_ARM_VFPv3D16 */
		if (hwcap & 0x00004000)
			mono_hwcap_arm_has_vfp3_d16 = TRUE;

		/* TODO: Find a way to detect Thumb 2. */
	}

	if ((platform = getauxval(AT_PLATFORM))) {
		const char *str = (const char *) platform;

		if (str [1] >= '5')
			mono_hwcap_arm_is_v5 = TRUE;

		if (str [1] >= '6')
			mono_hwcap_arm_is_v6 = TRUE;

		if (str [1] >= '7')
			mono_hwcap_arm_is_v7 = TRUE;

		/* TODO: Find a way to detect v7s. */
	}
#elif defined(__APPLE__)
	cpu_subtype_t sub_type;
	size_t length = sizeof (sub_type);

	sysctlbyname ("hw.cpusubtype", &sub_type, &length, NULL, 0);

	if (sub_type == CPU_SUBTYPE_ARM_V5TEJ || sub_type == CPU_SUBTYPE_ARM_XSCALE) {
		mono_hwcap_arm_is_v5 = TRUE;
	} else if (sub_type == CPU_SUBTYPE_ARM_V6) {
		mono_hwcap_arm_is_v5 = TRUE;
		mono_hwcap_arm_is_v6 = TRUE;
	} else if (sub_type == CPU_SUBTYPE_ARM_V7 || sub_type == CPU_SUBTYPE_ARM_V7F || sub_type == CPU_SUBTYPE_ARM_V7K) {
		mono_hwcap_arm_is_v5 = TRUE;
		mono_hwcap_arm_is_v6 = TRUE;
		mono_hwcap_arm_is_v7 = TRUE;
	}

	/* TODO: Find a way to detect features like Thumb and VFP. */
#elif defined (_WIN32)
	/* From MSDN:	
	 * Windows on ARM presumes that it is running on an ARMv7 architecture at all times. 
	 * Floating-point support in the form of VFPv3-D32 or later must be present in hardware.
	 * The VFP must support both single-precision and double-precision floating-point in hardware. 
	 *
	 * The Windows runtime does not support emulation of floating-point to enable running on non-VFP hardware.
	 * Advanced SIMD Extensions (NEON) support—this includes both integer and floating-point operations—must also be present in hardware. 
	 * No run-time support for emulation is provided.
	 *
	 * Integer divide support (UDIV/SDIV) is strongly recommended but not required.
	 * Platforms that lack integer divide support may incur a performance penalty because
	 * these operations have to be trapped and possibly patched.
	 *
	 * The instruction set for Windows on ARM is strictly limited to Thumb-2. 
	 * All code executed on this platform is expected to start and remain in Thumb mode at all times. 
	 */
	mono_hwcap_arm_is_v5 = TRUE;
	mono_hwcap_arm_is_v6 = TRUE;
	mono_hwcap_arm_is_v7 = TRUE;
	mono_hwcap_arm_has_vfp = TRUE;
	mono_hwcap_arm_has_vfp3 = TRUE;
	mono_hwcap_arm_has_vfp3_d16 = TRUE;
	mono_hwcap_arm_has_thumb = TRUE;
	mono_hwcap_arm_has_thumb2 = TRUE;
#else
	/* We can't use the auxiliary vector on Android due to
	 * permissions, so fall back to /proc/cpuinfo. We also
	 * hit this path if the target doesn't have sys/auxv.h.
	 */

#if defined (HAVE_SYS_UTSNAME_H)
	struct utsname name;

	/* Only fails if `name` is invalid (it isn't). */
	g_assert (!uname (&name));

	if (!strncmp (name.machine, "aarch64", 7) || !strncmp (name.machine, "armv8", 5)) {
		/*
		 * We're a 32-bit program running on an ARMv8 system.
		 * Whether the system is actually 32-bit or 64-bit
		 * doesn't matter to us. The important thing is that
		 * all 3 of ARMv8's execution states (A64, A32, T32)
		 * are guaranteed to have all of the features that
		 * we want to detect and use.
		 *
		 * We do this ARMv8 detection via uname () because
		 * in the early days of ARMv8 on Linux, the
		 * /proc/cpuinfo format was a disaster and there
		 * were multiple (merged into mainline) attempts at
		 * cleaning it up (read: breaking applications that
		 * tried to rely on it). So now multiple ARMv8
		 * systems in the wild have different /proc/cpuinfo
		 * output, some of which are downright useless.
		 *
		 * So, when it comes to detecting ARMv8 in a 32-bit
		 * program, it's better to just avoid /proc/cpuinfo
		 * entirely. Maybe in a decade or two, we won't
		 * have to worry about this mess that the Linux ARM
		 * maintainers created. One can hope.
		 */

		mono_hwcap_arm_is_v5 = TRUE;
		mono_hwcap_arm_is_v6 = TRUE;
		mono_hwcap_arm_is_v7 = TRUE;

		mono_hwcap_arm_has_vfp = TRUE;
		mono_hwcap_arm_has_vfp3 = TRUE;
		mono_hwcap_arm_has_vfp3_d16 = TRUE;

		mono_hwcap_arm_has_thumb = TRUE;
		mono_hwcap_arm_has_thumb2 = TRUE;
	}
#endif

	char buf [512];
	char *line;

	FILE *file = fopen ("/proc/cpuinfo", "r");

	if (file) {
		while ((line = fgets (buf, 512, file))) {
			if (!strncmp (line, "Processor", 9) ||
			    !strncmp (line, "model name", 10)) {
				char *ver = strstr (line, "(v");

				if (ver) {
					if (ver [2] >= '5')
						mono_hwcap_arm_is_v5 = TRUE;

					if (ver [2] >= '6')
						mono_hwcap_arm_is_v6 = TRUE;

					if (ver [2] >= '7')
						mono_hwcap_arm_is_v7 = TRUE;

					/* TODO: Find a way to detect v7s. */
				}

				continue;
			}

			if (!strncmp (line, "Features", 8)) {
				if (strstr (line, "thumb"))
					mono_hwcap_arm_has_thumb = TRUE;

				/* TODO: Find a way to detect Thumb 2. */

				if (strstr (line, "vfp"))
					mono_hwcap_arm_has_vfp = TRUE;

				if (strstr (line, "vfpv3"))
					mono_hwcap_arm_has_vfp3 = TRUE;

				if (strstr (line, "vfpv3-d16"))
					mono_hwcap_arm_has_vfp3_d16 = TRUE;

				continue;
			}
		}

		fclose (file);
	}
#endif
}
