/* SPDX-License-Identifier: GPL-2.0-only */

#include <cf9_reset.h>
#include <console/console.h>
#include <cpu/intel/common/common.h>
#include <cpu/x86/cr.h>
#include <cpu/x86/cache.h>
#include <cpu/x86/mp.h>
#include <cpu/x86/msr.h>
#include <types.h>

#include "txt_register.h"
#include "txt_getsec.h"

/**
 * Check for SMX support and enable it if possible.
 *
 * Returns false on error, true on success.
 */
static bool getsec_enabled(void)
{
	unsigned int ecx = cpuid_ecx(1);
	/*
	 * Check if SMX and VMX is supported by CPU.
	 */
	if (!(ecx & CPUID_SMX) || !(ecx & CPUID_VMX)) {
		printk(BIOS_ERR, "SMX/VMX not supported by CPU\n");
		return false;
	}
	/*
	 * This requirement is not needed for ENTERACCS, but for SENTER (see SDM).
	 * Skip check in romstage because IA32_FEATURE_CONTROL cannot be unlocked
	 * even after a global reset e.g. on Sandy/IvyBridge. However the register
	 * gets set properly in ramstage where all CPUs are already initialized.
	 */
	if (!ENV_ROMSTAGE_OR_BEFORE) {
		/*
		* Check if SMX, VMX and GetSec instructions haven't been disabled.
		*/
		msr_t msr = rdmsr(IA32_FEATURE_CONTROL);
		if ((msr.lo & 0xff06) != 0xff06) {
			printk(BIOS_ERR, "GETSEC not enabled in IA32_FEATURE_CONTROL MSR\n");
			return false;
		}
	}
	/*
	 * Enable SMX. Required to execute GetSec instruction.
	 * Chapter 2.2.4.3
	 * Intel TXT Software Development Guide (Document: 315168-015)
	 */
	write_cr4(read_cr4() | CR4_SMXE);

	return true;
}

void enable_getsec_or_reset(void)
{
	msr_t msr = rdmsr(IA32_FEATURE_CONTROL);

	if (!(msr.lo & FEATURE_CONTROL_LOCK_BIT)) {
		/*
		 * MSR not locked, enable necessary GETSEC and VMX settings.
		 * We do not lock this MSR here, though.
		 */
		msr.lo |= 0xff06;
		wrmsr(IA32_FEATURE_CONTROL, msr);

	} else if ((msr.lo & 0xff06) != 0xff06) {
		/*
		 * MSR is locked without necessary GETSEC and VMX settings.
		 * This can happen after internally reflashing a coreboot
		 * image with different settings, and then doing a warm
		 * reboot. Perform a full reset in order to unlock the MSR.
		 */
		printk(BIOS_NOTICE,
		       "IA32_FEATURE_CONTROL MSR locked with GETSEC and/or VMX disabled.\n"
		       "Will perform a full reset to unlock this MSR.\n");

		full_reset();
	}
}

/**
 * Get information as returned by getsec[PARAMETER].
 * Arguments can be set to NULL if not needed.
 *
 * Returns false on error, true on success.
 */
bool getsec_parameter(uint32_t *version_mask,
		      uint32_t *version_numbers_supported,
		      uint32_t *max_size_acm_area,
		      uint32_t *memory_type_mask,
		      uint32_t *senter_function_disable,
		      uint32_t *txt_feature_flags)
{
	uint32_t i, eax, ebx, ecx;

	if (!getsec_enabled())
		return false;

	/*
	 * SAFER MODE EXTENSIONS REFERENCE.
	 * Intel 64 and IA-32 Architectures Software Developer Manuals Vol 2D
	 */
	for (i = 0; i < 0x1f; i++) {
		/* Getsec[PARAMETERS] */
		asm volatile ("getsec\n"
			: "=a" (eax), "=b" (ebx), "=c" (ecx)
			: "a" (IA32_GETSEC_PARAMETERS), "b" (i) :);
		switch (eax & 0x1f) {
		case 0: /* NULL - Exit marker */
			return true;
		case 1: /* Supported AC module versions */
			if (version_mask)
				*version_mask = ebx;
			if (version_numbers_supported)
				*version_numbers_supported = ecx;
			break;
		case 2: /* Max size of authenticated code execution area */
			if (max_size_acm_area)
				*max_size_acm_area = eax & ~0x1f;
			break;
		case 3: /* External memory types supported during AC mode */
			if (memory_type_mask)
				*memory_type_mask = eax;
			break;
		case 4: /* Selective SENTER functionality control */
			if (senter_function_disable)
				*senter_function_disable = eax & (0x3f00);
			break;
		case 5: /* TXT extensions support */
			if (txt_feature_flags)
				*txt_feature_flags = eax & (0x60);
			break;
		}
	}

	return true;
}

/**
 * Get capabilities as returned by getsec[CAPABILITIES].
 *
 * Returns false on error, true on success.
 */

bool getsec_capabilities(uint32_t *eax)
{
	if (!getsec_enabled())
		return false;

	asm volatile ("getsec\n"
		: "=a" (*eax)
		: "a" (IA32_GETSEC_CAPABILITIES), "b" (0) :);

	return true;
}