// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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

#include "cpu_features_macros.h"
#include "internal/filesystem.h"
#include "internal/hwcaps.h"
#include "internal/string_view.h"

#if defined(NDEBUG)
#define D(...)
#else
#include <stdio.h>
#define D(...)           \
  do {                   \
    printf(__VA_ARGS__); \
    fflush(stdout);      \
  } while (0)
#endif

#if defined(CPU_FEATURES_ARCH_MIPS) || defined(CPU_FEATURES_ARCH_ANY_ARM)
#define HWCAPS_ANDROID_MIPS_OR_ARM
#endif

#if defined(CPU_FEATURES_OS_LINUX_OR_ANDROID) && \
    !defined(HWCAPS_ANDROID_MIPS_OR_ARM)
#define HWCAPS_REGULAR_LINUX
#endif

#if defined(HWCAPS_ANDROID_MIPS_OR_ARM) || defined(HWCAPS_REGULAR_LINUX)
#define HWCAPS_SUPPORTED
#endif

////////////////////////////////////////////////////////////////////////////////
// Implementation of GetElfHwcapFromGetauxval
////////////////////////////////////////////////////////////////////////////////

// On Linux we simply use getauxval.
#if defined(HWCAPS_REGULAR_LINUX)
#include <dlfcn.h>
#include <sys/auxv.h>
static unsigned long GetElfHwcapFromGetauxval(uint32_t hwcap_type) {
  return getauxval(hwcap_type);
}
#endif  // defined(HWCAPS_REGULAR_LINUX)

// On Android we 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.
#if defined(HWCAPS_ANDROID_MIPS_OR_ARM)
#include <dlfcn.h>
#define AT_HWCAP 16
#define AT_HWCAP2 26
#define AT_PLATFORM 15
#define AT_BASE_PLATFORM 24

typedef unsigned long getauxval_func_t(unsigned long);

static uint32_t GetElfHwcapFromGetauxval(uint32_t hwcap_type) {
  uint32_t ret = 0;
  void* libc_handle = NULL;
  getauxval_func_t* func = NULL;

  dlerror();  // Cleaning error state before calling dlopen.
  libc_handle = dlopen("libc.so", RTLD_NOW);
  if (!libc_handle) {
    D("Could not dlopen() C library: %s\n", dlerror());
    return 0;
  }
  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)(hwcap_type);
  }
  dlclose(libc_handle);
  return ret;
}
#endif  // defined(HWCAPS_ANDROID_MIPS_OR_ARM)

#if defined(HWCAPS_SUPPORTED)
////////////////////////////////////////////////////////////////////////////////
// Implementation of GetHardwareCapabilities for Android and Linux
////////////////////////////////////////////////////////////////////////////////

// Fallback when getauxval is not available, retrieves hwcaps from
// "/proc/self/auxv".
static uint32_t GetElfHwcapFromProcSelfAuxv(uint32_t hwcap_type) {
  struct {
    uint32_t tag;
    uint32_t value;
  } entry;
  uint32_t result = 0;
  const char filepath[] = "/proc/self/auxv";
  const int fd = CpuFeatures_OpenFile(filepath);
  if (fd < 0) {
    D("Could not open %s\n", filepath);
    return 0;
  }
  for (;;) {
    const int ret = CpuFeatures_ReadFile(fd, (char*)&entry, sizeof entry);
    if (ret < 0) {
      D("Error while reading %s\n", filepath);
      break;
    }
    // Detect end of list.
    if (ret == 0 || (entry.tag == 0 && entry.value == 0)) {
      break;
    }
    if (entry.tag == hwcap_type) {
      result = entry.value;
      break;
    }
  }
  CpuFeatures_CloseFile(fd);
  return result;
}

// Retrieves hardware capabilities by first trying to call getauxval, if not
// available falls back to reading "/proc/self/auxv".
static unsigned long GetHardwareCapabilitiesFor(uint32_t type) {
  unsigned long hwcaps = GetElfHwcapFromGetauxval(type);
  if (!hwcaps) {
    D("Parsing /proc/self/auxv to extract ELF hwcaps!\n");
    hwcaps = GetElfHwcapFromProcSelfAuxv(type);
  }
  return hwcaps;
}

HardwareCapabilities CpuFeatures_GetHardwareCapabilities(void) {
  HardwareCapabilities capabilities;
  capabilities.hwcaps = GetHardwareCapabilitiesFor(AT_HWCAP);
  capabilities.hwcaps2 = GetHardwareCapabilitiesFor(AT_HWCAP2);
  return capabilities;
}

PlatformType kEmptyPlatformType;

PlatformType CpuFeatures_GetPlatformType(void) {
  PlatformType type = kEmptyPlatformType;
  char *platform = (char *)GetHardwareCapabilitiesFor(AT_PLATFORM);
  char *base_platform = (char *)GetHardwareCapabilitiesFor(AT_BASE_PLATFORM);

  if (platform != NULL)
    CpuFeatures_StringView_CopyString(str(platform), type.platform,
                                      sizeof(type.platform));
  if (base_platform != NULL)
    CpuFeatures_StringView_CopyString(str(base_platform), type.base_platform,
                                      sizeof(type.base_platform));
  return type;
}
#else  // (defined(HWCAPS_SUPPORTED)

////////////////////////////////////////////////////////////////////////////////
// Implementation of GetHardwareCapabilities for unsupported platforms.
////////////////////////////////////////////////////////////////////////////////

const HardwareCapabilities kEmptyHardwareCapabilities;
HardwareCapabilities CpuFeatures_GetHardwareCapabilities(void) {
  return kEmptyHardwareCapabilities;
}
#endif
