/*
 * Copyright 2019 The Hafnium Authors.
 *
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/BSD-3-Clause.
 */

#include "feature_id.h"

#include "hf/check.h"
#include "hf/dlog.h"
#include "hf/types.h"
#include "hf/vm.h"

#include "msr.h"
#include "sysregs.h"

/* clang-format off */

/**
 * Definitions of read-only feature ID (group 3) registers' encodings.
 * See Arm Architecture Reference Manual Armv8-A, Table D1-52 and D12-2.
 * NAME, op0, op1, crn, crm, op2
 */
#define FEATURE_ID_REGISTERS_READ              \
	X(ID_PFR0_EL1       , 3, 0,  0,  1, 0) \
	X(ID_PFR1_EL1       , 3, 0,  0,  1, 1) \
	X(ID_DFR0_EL1       , 3, 0,  0,  1, 2) \
	X(ID_AFR0_EL1       , 3, 0,  0,  1, 3) \
	X(ID_MMFR0_EL1      , 3, 0,  0,  1, 4) \
	X(ID_MMFR1_EL1      , 3, 0,  0,  1, 5) \
	X(ID_MMFR2_EL1      , 3, 0,  0,  1, 6) \
	X(ID_MMFR3_EL1      , 3, 0,  0,  1, 7) \
	X(ID_ISAR0_EL1      , 3, 0,  0,  2, 0) \
	X(ID_ISAR1_EL1      , 3, 0,  0,  2, 1) \
	X(ID_ISAR2_EL1      , 3, 0,  0,  2, 2) \
	X(ID_ISAR3_EL1      , 3, 0,  0,  2, 3) \
	X(ID_ISAR4_EL1      , 3, 0,  0,  2, 4) \
	X(ID_ISAR5_EL1      , 3, 0,  0,  2, 5) \
	X(ID_MMFR4_EL1      , 3, 0,  0,  2, 6) \
	\
	X(MVFR0_EL1         , 3, 0,  0,  3, 0) \
	X(MVFR1_EL1         , 3, 0,  0,  3, 1) \
	X(MVFR2_EL1         , 3, 0,  0,  3, 2) \
	\
	X(ID_AA64PFR0_EL1   , 3, 0,  0,  4, 0) \
	X(ID_AA64PFR1_EL1   , 3, 0,  0,  4, 1) \
	\
	X(ID_AA64DFR0_EL1   , 3, 0,  0,  5, 0) \
	X(ID_AA64DFR1_EL1   , 3, 0,  0,  5, 1) \
	\
	X(ID_AA64AFR0_EL1   , 3, 0,  0,  5, 4) \
	X(ID_AA64AFR1_EL1   , 3, 0,  0,  5, 5) \
	\
	X(ID_AA64ISAR0_EL1  , 3, 0,  0,  6, 0) \
	X(ID_AA64ISAR1_EL1  , 3, 0,  0,  6, 1) \
	\
	X(ID_AA64MMFR0_EL1  , 3, 0,  0,  7, 0) \
	X(ID_AA64MMFR1_EL1  , 3, 0,  0,  7, 1) \
	X(ID_AA64MMFR2_EL1  , 3, 0,  0,  7, 2)

/* clang-format on */

enum {
#define X(reg_name, op0, op1, crn, crm, op2) \
	reg_name##_ENC = GET_ISS_ENCODING(op0, op1, crn, crm, op2),
	FEATURE_ID_REGISTERS_READ
#undef X
};

/**
 * Returns true if the ESR register shows an access to a feature ID group 3
 * register.
 */
bool feature_id_is_register_access(uintreg_t esr)
{
	uintreg_t op0 = GET_ISS_OP0(esr);
	uintreg_t op1 = GET_ISS_OP1(esr);
	uintreg_t crn = GET_ISS_CRN(esr);
	uintreg_t crm = GET_ISS_CRM(esr);

	/* From the Arm Architecture Reference Manual Table D12-2. */
	return op0 == 3 && op1 == 0 && crn == 0 && crm >= 1 && crm <= 7;
}

/**
 * RAS-related. RES0 when RAS is not implemented.
 */
#define ID_AA64MMFR1_EL1_SPEC_SEI (UINT64_C(0xf) << 24)

/**
 * Indicates support for LORegions.
 */
#define ID_AA64MMFR1_EL1_LO (UINT64_C(0xf) << 24)

/**
 * RAS Extension version.
 */
#define ID_AA64PFR0_EL1_RAS (UINT64_C(0xf) << 28)

/**
 * Activity Monitor Unit.
 */
#define ID_AA64PFR0_EL1_AMU (UINT64_C(0xf) << 44)

/**
 * Self-hosted Trace Extension Version
 */
#define ID_AA64DFR0_EL1_TRACE_FILT (UINT64_C(0xf) << 40)

/**
 * OS Double Lock implemented.
 */
#define ID_AA64DFR0_EL1_DOUBLE_LOCK (UINT64_C(0xf) << 36)

/**
 * Statistical Profiling Extension version.
 */
#define ID_AA64DFR0_EL1_PMS_VER (UINT64_C(0xf) << 32)

/**
 * Performance Monitors Extension version.
 */
#define ID_AA64DFR0_EL1_PMU_VER (UINT64_C(0xf) << 8)

/**
 * Indicates whether System register interface to trace unit is implemented.
 */
#define ID_AA64DFR0_EL1_TRACE_VER (UINT64_C(0xf) << 4)

/**
 * Debug architecture version.
 */
#define ID_AA64DFR0_EL1_DEBUG_VER (UINT64_C(0xf))

/**
 * PAuth: whether an implementation defined algorithm for generic code
 * authentication is implemented.
 */
#define ID_AA64ISAR1_EL1_GPI (UINT64_C(0xf) << 28)

/**
 * PAuth: whether QARMA or Architected algorithm for generic code authentication
 * is implemented.
 */
#define ID_AA64ISAR1_EL1_GPA (UINT64_C(0xf) << 24)

/**
 * PAuth: whether an implementation defined algorithm for address authentication
 * is implemented.
 */
#define ID_AA64ISAR1_EL1_API (UINT64_C(0xf) << 8)

/**
 * PAuth: whether QARMA or Architected algorithm for address authentication is
 * implemented.
 */
#define ID_AA64ISAR1_EL1_APA (UINT64_C(0xf) << 24)

void feature_set_traps(struct vm *vm, struct arch_regs *regs)
{
	arch_features_t features = vm->arch.trapped_features;

	if (features & ~HF_FEATURE_ALL) {
		panic("features has undefined bits 0x%x", features);
	}

	/* By default do not mask out any features. */
	vm->arch.tid3_masks.id_aa64mmfr1_el1 = ~0ULL;
	vm->arch.tid3_masks.id_aa64pfr0_el1 = ~0ULL;
	vm->arch.tid3_masks.id_aa64pfr1_el1 = ~0ULL;
	vm->arch.tid3_masks.id_aa64dfr0_el1 = ~0ULL;
	vm->arch.tid3_masks.id_aa64isar1_el1 = ~0ULL;

	/*
	 * Always mask VHE feature. No nested virualization support at this
	 * point so there is no need to expose VHE to guests.
	 */
	vm->arch.tid3_masks.id_aa64mmfr1_el1 &=
		~(ID_AA64MMFR1_EL1_VH_MASK << ID_AA64MMFR1_EL1_VH_SHIFT);

	if (features & HF_FEATURE_SVE) {
		vm->arch.tid3_masks.id_aa64pfr0_el1 &= ~(
			ID_AA64PFR0_EL1_SVE_MASK << ID_AA64PFR0_EL1_SVE_SHIFT);
	}

	if (features & HF_FEATURE_SME) {
		vm->arch.tid3_masks.id_aa64pfr1_el1 &= ~(
			ID_AA64PFR1_EL1_SME_MASK << ID_AA64PFR1_EL1_SME_SHIFT);
	}

	if (features & HF_FEATURE_RAS) {
		regs->hyp_state.hcr_el2 |= HCR_EL2_TERR;
		vm->arch.tid3_masks.id_aa64mmfr1_el1 &=
			~ID_AA64MMFR1_EL1_SPEC_SEI;
		vm->arch.tid3_masks.id_aa64pfr0_el1 &= ~ID_AA64PFR0_EL1_RAS;
	}

	if (features & HF_FEATURE_SPE) {
		/*
		 * Trap VM accesses to Statistical Profiling Extension (SPE)
		 * registers.
		 */
		regs->lazy.mdcr_el2 |= MDCR_EL2_TPMS;

		/*
		 * Set E2PB to 0b00. This ensures that accesses to Profiling
		 * Buffer controls at EL1 are trapped to EL2.
		 */
		regs->lazy.mdcr_el2 &= ~MDCR_EL2_E2PB;

		vm->arch.tid3_masks.id_aa64dfr0_el1 &= ~ID_AA64DFR0_EL1_PMS_VER;
	}

	if (features & HF_FEATURE_DEBUG) {
		regs->lazy.mdcr_el2 |=
			MDCR_EL2_TDRA | MDCR_EL2_TDOSA | MDCR_EL2_TDA;

		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
			~ID_AA64DFR0_EL1_DOUBLE_LOCK;
	}

	if (features & HF_FEATURE_TRACE) {
		regs->lazy.mdcr_el2 |= MDCR_EL2_TTRF;

		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
			~ID_AA64DFR0_EL1_TRACE_FILT;
		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
			~ID_AA64DFR0_EL1_TRACE_VER;
	}

	if (features & HF_FEATURE_PERFMON) {
		regs->lazy.mdcr_el2 |= MDCR_EL2_TPM | MDCR_EL2_TPMCR;

		vm->arch.tid3_masks.id_aa64dfr0_el1 &= ~ID_AA64DFR0_EL1_PMU_VER;
	}

	if (features & HF_FEATURE_LOR) {
		regs->hyp_state.hcr_el2 |= HCR_EL2_TLOR;

		vm->arch.tid3_masks.id_aa64mmfr1_el1 &= ~ID_AA64MMFR1_EL1_LO;
	}

	if (features & HF_FEATURE_PAUTH) {
		/* APK and API bits *enable* trapping when cleared. */
		regs->hyp_state.hcr_el2 &= ~(HCR_EL2_APK | HCR_EL2_API);

		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_GPI;
		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_GPA;
		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_API;
		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_APA;
	}

	if (features & HF_FEATURE_AMU) {
		vm->arch.tid3_masks.id_aa64pfr0_el1 &= ~ID_AA64PFR0_EL1_AMU;
	}
}

/**
 * Processes an access (mrs) to a feature ID register.
 * Returns true if the access was allowed and performed, false otherwise.
 */
bool feature_id_process_access(struct vcpu *vcpu, uintreg_t esr)
{
	const struct vm *vm = vcpu->vm;
	uintreg_t sys_register = GET_ISS_SYSREG(esr);
	uintreg_t rt_register = GET_ISS_RT(esr);
	uintreg_t value;

	/* +1 because Rt can access register XZR */
	CHECK(rt_register < NUM_GP_REGS + 1);

	if (!ISS_IS_READ(esr)) {
		dlog_notice(
			"Unsupported feature ID register write: "
			"op0=%lu, op1=%lu, crn=%lu, crm=%lu, op2=%lu, "
			"rt=%lu.\n",
			GET_ISS_OP0(esr), GET_ISS_OP1(esr), GET_ISS_CRN(esr),
			GET_ISS_CRM(esr), GET_ISS_OP2(esr), GET_ISS_RT(esr));
		return true;
	}

	switch (sys_register) {
#define X(reg_name, op0, op1, crn, crm, op2)              \
	case (GET_ISS_ENCODING(op0, op1, crn, crm, op2)): \
		value = read_msr(reg_name);               \
		break;
		FEATURE_ID_REGISTERS_READ
#undef X
	default:
		/* Reserved registers should be read as zero (raz). */
		value = 0;
		dlog_notice(
			"Unsupported feature ID register read: "
			"op0=%lu, op1=%lu, crn=%lu, crm=%lu, op2=%lu, "
			"rt=%lu.\n",
			GET_ISS_OP0(esr), GET_ISS_OP1(esr), GET_ISS_CRN(esr),
			GET_ISS_CRM(esr), GET_ISS_OP2(esr), GET_ISS_RT(esr));
		break;
	}

	/* Mask values for features Hafnium might restrict. */
	switch (sys_register) {
	case ID_AA64MMFR1_EL1_ENC:
		value &= vm->arch.tid3_masks.id_aa64mmfr1_el1;
		break;
	case ID_AA64PFR0_EL1_ENC:
		value &= vm->arch.tid3_masks.id_aa64pfr0_el1;
		break;
	case ID_AA64PFR1_EL1_ENC:
		value &= vm->arch.tid3_masks.id_aa64pfr1_el1;
		break;
	case ID_AA64DFR0_EL1_ENC:
		value &= vm->arch.tid3_masks.id_aa64dfr0_el1;
		break;
	case ID_AA64ISAR1_EL1_ENC:
		value &= vm->arch.tid3_masks.id_aa64isar1_el1;
		break;
	default:
		break;
	}

	if (rt_register != RT_REG_XZR) {
		vcpu->regs.r[rt_register] = value;
	}

	return true;
}
