diff options
Diffstat (limited to 'chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c')
-rw-r--r-- | chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c | 326 |
1 files changed, 261 insertions, 65 deletions
diff --git a/chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c b/chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c index 4ca730de353..be0ebfde961 100644 --- a/chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c +++ b/chromium/third_party/android_tools/ndk/sources/android/cpufeatures/cpu-features.c @@ -28,6 +28,8 @@ /* ChangeLog for this library: * + * NDK r9?: Support for 64-bit CPUs (Intel, ARM & MIPS). + * * NDK r8d: Add android_setCpu(). * * NDK r8c: Add new ARM CPU features: VFPv2, VFP_D32, VFP_FP16, @@ -56,16 +58,23 @@ * * NDK r4: Initial release */ -#include <sys/system_properties.h> -#ifdef __arm__ -#include <machine/cpu-features.h> -#endif -#include <pthread.h> + +#if defined(__le32__) || defined(__le64__) + +// When users enter this, we should only provide interface and +// libportable will give the implementations. + +#else // !__le32__ && !__le64__ + #include "cpu-features.h" + +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> #include <stdio.h> #include <stdlib.h> -#include <fcntl.h> -#include <errno.h> +#include <sys/system_properties.h> static pthread_once_t g_once; static int g_inited; @@ -73,16 +82,12 @@ static AndroidCpuFamily g_cpuFamily; static uint64_t g_cpuFeatures; static int g_cpuCount; -static const int android_cpufeatures_debug = 0; - #ifdef __arm__ -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_ARM -#elif defined __i386__ -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_X86 -#else -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_UNKNOWN +static uint32_t g_cpuIdArm; #endif +static const int android_cpufeatures_debug = 0; + #define D(...) \ do { \ if (android_cpufeatures_debug) { \ @@ -231,10 +236,6 @@ EXIT: return result; } -/* Like strlen(), but for constant string literals */ -#define STRLEN_CONST(x) ((sizeof(x)-1) - - /* Checks that a space-separated list of items contains one given 'item'. * Returns 1 if found, 0 otherwise. */ @@ -268,7 +269,7 @@ has_list_item(const char* list, const char* item) return 0; } -/* Parse an decimal integer starting from 'input', but not going further +/* Parse a number starting from 'input', but not going further * than 'limit'. Return the value into '*result'. * * NOTE: Does not skip over leading spaces, or deal with sign characters. @@ -279,15 +280,23 @@ has_list_item(const char* list, const char* item) * be <= 'limit'). */ static const char* -parse_decimal(const char* input, const char* limit, int* result) +parse_number(const char* input, const char* limit, int base, int* result) { const char* p = input; int val = 0; while (p < limit) { int d = (*p - '0'); - if ((unsigned)d >= 10U) - break; - val = val*10 + d; + if ((unsigned)d >= 10U) { + d = (*p - 'a'); + if ((unsigned)d >= 6U) + d = (*p - 'A'); + if ((unsigned)d >= 6U) + break; + d += 10; + } + if (d >= base) + break; + val = val*base + d; p++; } if (p == input) @@ -297,6 +306,18 @@ parse_decimal(const char* input, const char* limit, int* result) return p; } +static const char* +parse_decimal(const char* input, const char* limit, int* result) +{ + return parse_number(input, limit, 10, result); +} + +static const char* +parse_hexadecimal(const char* input, const char* limit, int* result) +{ + return parse_number(input, limit, 16, result); +} + /* This small data type is used to represent a CPU list / mask, as read * from sysfs on Linux. See http://www.kernel.org/doc/Documentation/cputopology.txt * @@ -408,6 +429,8 @@ cpulist_read_from(CpuList* list, const char* filename) cpulist_parse(list, file, filelen); } +#if defined(__arm__) + // See <asm/hwcap.h> kernel header. #define HWCAP_VFP (1 << 6) #define HWCAP_IWMMXT (1 << 9) @@ -418,27 +441,65 @@ cpulist_read_from(CpuList* list, const char* filename) #define HWCAP_IDIVA (1 << 17) #define HWCAP_IDIVT (1 << 18) +// This is the list of 32-bit ARMv7 optional features that are _always_ +// supported by ARMv8 CPUs, as mandated by the ARM Architecture Reference +// Manual. +#define HWCAP_SET_FOR_ARMV8 \ + ( HWCAP_VFP | \ + HWCAP_NEON | \ + HWCAP_VFPv3 | \ + HWCAP_VFPv4 | \ + HWCAP_IDIVA | \ + HWCAP_IDIVT ) + #define AT_HWCAP 16 -#if defined(__arm__) -/* Compute the ELF HWCAP flags. - */ +// Probe the system's C library for a 'getauxval' function and call it if +// it exits, or return 0 for failure. This function is available since API +// level 20. +// +// This code does *NOT* check for '__ANDROID_API__ >= 20' to support the +// edge case where some NDK developers use headers for a platform that is +// newer than the one really targetted by their application. +// This is typically done to use newer native APIs only when running on more +// recent Android versions, and requires careful symbol management. +// +// Note that getauxval() can't really be re-implemented here, because +// its implementation does not parse /proc/self/auxv. Instead it depends +// on values that are passed by the kernel at process-init time to the +// C runtime initialization layer. static uint32_t -get_elf_hwcap(const char* cpuinfo, int cpuinfo_len) -{ - /* IMPORTANT: - * Accessing /proc/self/auxv doesn't work anymore on all - * platform versions. More specifically, when running inside - * a regular application process, most of /proc/self/ will be - * non-readable, including /proc/self/auxv. This doesn't - * happen however if the application is debuggable, or when - * running under the "shell" UID, which is why this was not - * detected appropriately. - */ -#if 0 - uint32_t result = 0; +get_elf_hwcap_from_getauxval(void) { + typedef unsigned long getauxval_func_t(unsigned long); + + dlerror(); + void* libc_handle = dlopen("libc.so", RTLD_NOW); + if (!libc_handle) { + D("Could not dlopen() C library: %s\n", dlerror()); + return 0; + } + + uint32_t ret = 0; + getauxval_func_t* func = (getauxval_func_t*) + dlsym(libc_handle, "getauxval"); + if (!func) { + D("Could not find getauxval() in C library\n"); + } else { + // Note: getauxval() returns 0 on failure. Doesn't touch errno. + ret = (uint32_t)(*func)(AT_HWCAP); + } + dlclose(libc_handle); + return ret; +} + +// Parse /proc/self/auxv to extract the ELF HW capabilities bitmap for the +// current CPU. Note that this file is not accessible from regular +// application processes on some Android platform releases. +// On success, return new ELF hwcaps, or 0 on failure. +static uint32_t +get_elf_hwcap_from_proc_self_auxv(void) { const char filepath[] = "/proc/self/auxv"; - int fd = open(filepath, O_RDONLY); + int fd = TEMP_FAILURE_RETRY(open(filepath, O_RDONLY)); if (fd < 0) { D("Could not open %s: %s\n", filepath, strerror(errno)); return 0; @@ -446,11 +507,10 @@ get_elf_hwcap(const char* cpuinfo, int cpuinfo_len) struct { uint32_t tag; uint32_t value; } entry; + uint32_t result = 0; for (;;) { - int ret = read(fd, (char*)&entry, sizeof entry); + int ret = TEMP_FAILURE_RETRY(read(fd, (char*)&entry, sizeof entry)); if (ret < 0) { - if (errno == EINTR) - continue; D("Error while reading %s: %s\n", filepath, strerror(errno)); break; } @@ -464,12 +524,33 @@ get_elf_hwcap(const char* cpuinfo, int cpuinfo_len) } close(fd); return result; -#else - // Recreate ELF hwcaps by parsing /proc/cpuinfo Features tag. +} + +/* Compute the ELF HWCAP flags from the content of /proc/cpuinfo. + * This works by parsing the 'Features' line, which lists which optional + * features the device's CPU supports, on top of its reference + * architecture. + */ +static uint32_t +get_elf_hwcap_from_proc_cpuinfo(const char* cpuinfo, int cpuinfo_len) { uint32_t hwcaps = 0; + long architecture = 0; + char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture"); + if (cpuArch) { + architecture = strtol(cpuArch, NULL, 10); + free(cpuArch); + + if (architecture >= 8L) { + // This is a 32-bit ARM binary running on a 64-bit ARM64 kernel. + // The 'Features' line only lists the optional features that the + // device's CPU supports, compared to its reference architecture + // which are of no use for this process. + D("Faking 32-bit ARM HWCaps on ARMv%ld CPU\n", architecture); + return HWCAP_SET_FOR_ARMV8; + } + } char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features"); - if (cpuFeatures != NULL) { D("Found cpuFeatures = '%s'\n", cpuFeatures); @@ -495,7 +576,6 @@ get_elf_hwcap(const char* cpuinfo, int cpuinfo_len) free(cpuFeatures); } return hwcaps; -#endif } #endif /* __arm__ */ @@ -525,12 +605,19 @@ get_cpu_count(void) static void android_cpuInitFamily(void) { -#if defined(__ARM_ARCH__) +#if defined(__arm__) g_cpuFamily = ANDROID_CPU_FAMILY_ARM; #elif defined(__i386__) g_cpuFamily = ANDROID_CPU_FAMILY_X86; -#elif defined(_MIPS_ARCH) +#elif defined(__mips64) +/* Needs to be before __mips__ since the compiler defines both */ + g_cpuFamily = ANDROID_CPU_FAMILY_MIPS64; +#elif defined(__mips__) g_cpuFamily = ANDROID_CPU_FAMILY_MIPS; +#elif defined(__aarch64__) + g_cpuFamily = ANDROID_CPU_FAMILY_ARM64; +#elif defined(__x86_64__) + g_cpuFamily = ANDROID_CPU_FAMILY_X86_64; #else g_cpuFamily = ANDROID_CPU_FAMILY_UNKNOWN; #endif @@ -575,11 +662,8 @@ android_cpuInit(void) D("found cpuCount = %d\n", g_cpuCount); -#ifdef __ARM_ARCH__ +#ifdef __arm__ { - char* features = NULL; - char* architecture = NULL; - /* Extract architecture from the "CPU Architecture" field. * The list is well-known, unlike the the output of * the 'Processor' field which can vary greatly. @@ -600,10 +684,7 @@ android_cpuInit(void) /* read the initial decimal number, ignore the rest */ archNumber = strtol(cpuArch, &end, 10); - /* Here we assume that ARMv8 will be upwards compatible with v7 - * in the future. Unfortunately, there is no 'Features' field to - * indicate that Thumb-2 is supported. - */ + /* Note that ARMv8 is upwards compatible with ARMv7. */ if (end > cpuArch && archNumber >= 7) { hasARMv7 = 1; } @@ -644,7 +725,19 @@ android_cpuInit(void) } /* Extract the list of CPU features from ELF hwcaps */ - uint32_t hwcaps = get_elf_hwcap(cpuinfo, cpuinfo_len); + uint32_t hwcaps = 0; + hwcaps = get_elf_hwcap_from_getauxval(); + if (!hwcaps) { + D("Parsing /proc/self/auxv to extract ELF hwcaps!\n"); + hwcaps = get_elf_hwcap_from_proc_self_auxv(); + } + if (!hwcaps) { + // Parsing /proc/self/auxv will fail from regular application + // processes on some Android platform versions, when this happens + // parse proc/cpuinfo instead. + D("Parsing /proc/cpuinfo to extract ELF hwcaps!\n"); + hwcaps = get_elf_hwcap_from_proc_cpuinfo(cpuinfo, cpuinfo_len); + } if (hwcaps != 0) { int has_vfp = (hwcaps & HWCAP_VFP); @@ -696,20 +789,102 @@ android_cpuInit(void) g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv2 | ANDROID_CPU_ARM_FEATURE_ARMv7; - // Note that some buggy kernels do not report these even when - // the CPU actually support the division instructions. However, - // assume that if 'vfpv4' is detected, then the CPU supports - // sdiv/udiv properly. - if (has_idiva || has_vfpv4) + if (has_idiva) g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_ARM; - if (has_idivt || has_vfpv4) + if (has_idivt) g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2; if (has_iwmmxt) g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_iWMMXt; } + + /* Extract the cpuid value from various fields */ + // The CPUID value is broken up in several entries in /proc/cpuinfo. + // This table is used to rebuild it from the entries. + static const struct CpuIdEntry { + const char* field; + char format; + char bit_lshift; + char bit_length; + } cpu_id_entries[] = { + { "CPU implementer", 'x', 24, 8 }, + { "CPU variant", 'x', 20, 4 }, + { "CPU part", 'x', 4, 12 }, + { "CPU revision", 'd', 0, 4 }, + }; + size_t i; + D("Parsing /proc/cpuinfo to recover CPUID\n"); + for (i = 0; + i < sizeof(cpu_id_entries)/sizeof(cpu_id_entries[0]); + ++i) { + const struct CpuIdEntry* entry = &cpu_id_entries[i]; + char* value = extract_cpuinfo_field(cpuinfo, + cpuinfo_len, + entry->field); + if (value == NULL) + continue; + + D("field=%s value='%s'\n", entry->field, value); + char* value_end = value + strlen(value); + int val = 0; + const char* start = value; + const char* p; + if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) { + start += 2; + p = parse_hexadecimal(start, value_end, &val); + } else if (entry->format == 'x') + p = parse_hexadecimal(value, value_end, &val); + else + p = parse_decimal(value, value_end, &val); + + if (p > (const char*)start) { + val &= ((1 << entry->bit_length)-1); + val <<= entry->bit_lshift; + g_cpuIdArm |= (uint32_t) val; + } + + free(value); + } + + // Handle kernel configuration bugs that prevent the correct + // reporting of CPU features. + static const struct CpuFix { + uint32_t cpuid; + uint64_t or_flags; + } cpu_fixes[] = { + /* The Nexus 4 (Qualcomm Krait) kernel configuration + * forgets to report IDIV support. */ + { 0x510006f2, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | + ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 }, + { 0x510006f3, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | + ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 }, + }; + size_t n; + for (n = 0; n < sizeof(cpu_fixes)/sizeof(cpu_fixes[0]); ++n) { + const struct CpuFix* entry = &cpu_fixes[n]; + + if (g_cpuIdArm == entry->cpuid) + g_cpuFeatures |= entry->or_flags; + } + + // Special case: The emulator-specific Android 4.2 kernel fails + // to report support for the 32-bit ARM IDIV instruction. + // Technically, this is a feature of the virtual CPU implemented + // by the emulator. Note that it could also support Thumb IDIV + // in the future, and this will have to be slightly updated. + char* hardware = extract_cpuinfo_field(cpuinfo, + cpuinfo_len, + "Hardware"); + if (hardware) { + if (!strcmp(hardware, "Goldfish") && + g_cpuIdArm == 0x4100c080 && + (g_cpuFamily & ANDROID_CPU_ARM_FEATURE_ARMv7) != 0) { + g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_ARM; + } + free(hardware); + } } -#endif /* __ARM_ARCH__ */ +#endif /* __arm__ */ #ifdef __i386__ int regs[4]; @@ -784,6 +959,25 @@ android_setCpu(int cpu_count, uint64_t cpu_features) return 1; } +#ifdef __arm__ +uint32_t +android_getCpuIdArm(void) +{ + pthread_once(&g_once, android_cpuInit); + return g_cpuIdArm; +} + +int +android_setCpuArm(int cpu_count, uint64_t cpu_features, uint32_t cpu_id) +{ + if (!android_setCpu(cpu_count, cpu_features)) + return 0; + + g_cpuIdArm = cpu_id; + return 1; +} +#endif /* __arm__ */ + /* * Technical note: Making sense of ARM's FPU architecture versions. * @@ -968,3 +1162,5 @@ android_setCpu(int cpu_count, uint64_t cpu_features) * ARCH_NEON_FP16 (+EXT_FP16) * */ + +#endif // defined(__le32__) || defined(__le64__) |