// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#if defined(OPENSSL_AARCH64) && !defined(OPENSSL_STATIC_ARMCAP)

#include "cpu_aarch64.h"

void handle_cpu_env(uint32_t *out, const char *in) {
  const int invert = in[0] == '~';
  const int or = in[0] == '|';
  const int skip_first_byte = invert || or;
  const int hex = in[skip_first_byte] == '0' && in[skip_first_byte+1] == 'x';
  uint32_t armcap = out[0];

  int sscanf_result;
  uint32_t v;
  if (hex) {
    sscanf_result = sscanf(in + skip_first_byte + 2, "%" PRIx32, &v);
  } else {
    sscanf_result = sscanf(in + skip_first_byte, "%" PRIu32, &v);
  }

  if (!sscanf_result) {
    return;
  }

  // Detect if the user is trying to use the environment variable to set
  // a capability that is _not_ available on the CPU:
  // If the runtime capability check (e.g via getauxval() on Linux)
  // returned a non-zero hwcap in `armcap` (out)
  // and a bit set in the requested `v` is not set in `armcap`,
  // abort instead of crashing later.
  // The case of invert cannot enable an unexisting capability;
  // it can only disable an existing one.
  if (!invert && armcap && (~armcap & v))
  {
    fprintf(stderr,
            "Fatal Error: HW capability found: 0x%02X, but HW capability requested: 0x%02X.\n",
            armcap, v);
    abort();
  }

  if (invert) {
    out[0] &= ~v;
  } else if (or) {
    out[0] |= v;
  } else {
    out[0] = v;
  }
}

#if defined(AARCH64_DIT_SUPPORTED)
// "DIT" is not recognised as a register name by clang-10 (at least)
// Register's encoded name is from e.g.
// https://github.com/ashwio/arm64-sysreg-lib/blob/d421e249a026f6f14653cb6f9c4edd8c5d898595/include/sysreg/dit.h#L286
#define DIT_REGISTER s3_3_c4_c2_5
DEFINE_STATIC_MUTEX(OPENSSL_armcap_P_lock)

uint64_t armv8_get_dit(void) {
  if (CRYPTO_is_ARMv8_DIT_capable()) {
    uint64_t val = 0;
    __asm__ volatile("mrs %0, s3_3_c4_c2_5" : "=r" (val));
    return (val >> 24) & 1;
  } else {
    return 0;
  }
}

// See https://github.com/torvalds/linux/blob/53eaeb7fbe2702520125ae7d72742362c071a1f2/arch/arm64/include/asm/sysreg.h#L82
// As per Arm ARM for v8-A, Section "C.5.1.3 op0 == 0b00, architectural hints,
// barriers and CLREX, and PSTATE access", ARM DDI 0487 J.a, system instructions
// for accessing PSTATE fields have the following encoding
// and C5.2.4 DIT, Data Independent Timing:
//	Op0 = 0, CRn = 4
//	Op1 (3 for DIT) , Op2 (5 for DIT) encodes the PSTATE field modified and defines the constraints.
//	CRm = Imm4 (#0 or #1 below)
//	Rt = 0x1f
uint64_t armv8_set_dit(void) {
  if (CRYPTO_is_ARMv8_DIT_capable()) {
    uint64_t original_dit = armv8_get_dit();
    // Encoding of "msr dit, #1"
    __asm__ volatile(".long 0xd503415f");
    return original_dit;
  } else {
    return 0;
  }
}

void armv8_restore_dit(volatile uint64_t *original_dit) {
  if (*original_dit != 1 && CRYPTO_is_ARMv8_DIT_capable()) {
    // Encoding of "msr dit, #0"
    __asm__ volatile(".long 0xd503405f");
  }
}

void armv8_disable_dit(void) {
  CRYPTO_STATIC_MUTEX_lock_write(OPENSSL_armcap_P_lock_bss_get());
  OPENSSL_armcap_P &= ~ARMV8_DIT_ALLOWED;
  CRYPTO_STATIC_MUTEX_unlock_write(OPENSSL_armcap_P_lock_bss_get());
}

void armv8_enable_dit(void) {
  CRYPTO_STATIC_MUTEX_lock_write(OPENSSL_armcap_P_lock_bss_get());
  OPENSSL_armcap_P |= ARMV8_DIT_ALLOWED;
  CRYPTO_STATIC_MUTEX_unlock_write(OPENSSL_armcap_P_lock_bss_get());
}

int CRYPTO_is_ARMv8_DIT_capable_for_testing(void) {
  return CRYPTO_is_ARMv8_DIT_capable();
}

#endif  // AARCH64_DIT_SUPPORTED

#endif // OPENSSL_AARCH64 && !OPENSSL_STATIC_ARMCAP
