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

#include <cpu/x86/cr.h>
#include <cpu/x86/mtrr.h>
#include <cpu/x86/msr.h>

#include "getsec_mtrr_setup.inc"

#define NO_EVICT_MODE	0x2e0

.align 4
.text

/*
 * void getsec_sclean(const uint32_t acm_base, const uint32_t acm_size);
 */
.global getsec_sclean
getsec_sclean:
	/*
	 * At this point, it is certain that the BIOS ACM will be run.
	 * This requires tearing down CAR, which cannot be undone.
	 *
	 * From here onwards, the only way out is to reset the system.
	 */

	/* Enable SMXE, SSE and debug extensions */
	movl	%cr4, %eax
	orl	$(CR4_OSFXSR | CR4_DE | CR4_SMXE), %eax
	movl	%eax, %cr4

	/*
	 * Save arguments into SSE registers. We need to tear down CAR
	 * before launching the BIOS ACM, which will destroy the stack.
	 */
	movd	4(%esp), %xmm2			/* acm_base */
	movd	8(%esp), %xmm3			/* acm_size */

	/* Disable cache */
	movl	%cr0, %eax
	orl	$(CR0_CD | CR0_NE), %eax
	andl	$(~(CR0_NW)), %eax
	movl	%eax, %cr0

	/* Invalidate the cache */
	invd

	/* Disable MTRRs */
	movl	$(MTRR_DEF_TYPE_MSR), %ecx
	xorl	%eax, %eax
	xorl	%edx, %edx
	wrmsr

	/* Disable NEM, needs to be done in two steps */
	movl	$NO_EVICT_MODE, %ecx
	rdmsr
	andl	$~2, %eax			/* Clear NEM Run bit */
	wrmsr
	andl	$~1, %eax			/* Clear NEM Setup bit */
	wrmsr

	/* Invalidate the cache, again */
	invd

	/*
	 * Clear variable MTRRs
	 * Chapter 2.2.5.1
	 * Intel TXT Software Development Guide (Document: 315168-015)
	 */
	movl	$(MTRR_CAP_MSR), %ecx
	rdmsr
	andl	$(0xff), %eax
	movl	%eax, %ebx

	xorl	%eax, %eax
	xorl	%edx, %edx

	jmp	cond_clear_var_mtrrs

body_clear_var_mtrrs:

	decl	%ebx
	movl	%ebx, %ecx
	shll	%ecx
	addl	$(MTRR_PHYS_BASE(0)), %ecx
	wrmsr
	incl	%ecx				/* MTRR_PHYS_MASK */
	wrmsr

cond_clear_var_mtrrs:

	cmpl	$0, %ebx
	jnz	body_clear_var_mtrrs

	/*
	 * Setup BIOS ACM as WB
	 * Chapter A.1.1
	 * Intel TXT Software Development Guide (Document: 315168-015)
	 */

	/* Determine size of AC module */
	movd	%xmm2, %eax			/* acm_base */
	movd	%xmm3, %ebx			/* acm_size */

	/* Round up to page size */
	addl	$(0xfff), %ebx
	andl	$(~0xfff), %ebx			/* Aligned to a page (4 KiB) */

	/* Use SSE registers to store local variables */
	movd	%eax, %xmm0
	movd	%ebx, %xmm1

	/*
	 * Important note: The MTRRs must cache less than a page (4 KiB)
	 * of unused memory after the BIOS ACM. Not doing so on Haswell
	 * will cause a TXT reset with Class Code 5, Major Error Code 2.
	 *
	 * The caller must have checked that there are enough variable
	 * MTRRs to cache the ACM size prior to invoking this routine.
	 */
	SET_UP_MTRRS_FOR_BIOS_ACM

	/* Enable variable MTRRs */
	movl	$MTRR_DEF_TYPE_MSR, %ecx
	rdmsr
	orl	$MTRR_DEF_TYPE_EN, %eax
	wrmsr

	/* Enable cache - CR0_NW is and stays clear */
	movl	%cr0, %eax
	andl	$~(CR0_CD), %eax
	movl	%eax, %cr0

	/*
	 * Get function arguments.
	 * It's important to pass the exact ACM size as it's used by getsec to verify
	 * the integrity of ACM. Unlike the size for MTRR programming, which needs to
	 * be power of two.
	 *
	 * Note: Do not forget that CAR has been torn down, so the stack doesn't exist.
	 */
	movl	$2, %eax			/* GETSEC[ENTERACCS] */
	movd	%xmm2, %ebx			/* acm_base */
	movd	%xmm3, %ecx			/* acm_size */
	movl	$0, %edx			/* reserved, must be zero */
	movl	$0, %edi			/* must be zero */
	movl	$0, %esi			/* SCLEAN */

	getsec

	/*
	 * The platform state after SCLEAN is undefined. The only sane
	 * thing to do afterwards is to reset the platform. Note that
	 * the BIOS ACM should already reset the platform, so this code
	 * may not always be reached, but keep it here just to be sure.
	 */
#if 1
	movw	$0xcf8, %dx
	movl	$0x8000F8AC, %eax
	outl	%eax, %dx

	movw	$0xcfc, %dx
	inl	%dx, %eax
	andl	$~(1 << 20), %eax
	outl	%eax, %dx
#endif

	movw	$0xcf9, %dx
	movb	$0, %al
	outb	%al, %dx

	movw	$0xcf9, %dx
	movb	$0x0e, %al
	outb	%al, %dx

	cli

	hlt

	ret