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

/*
 * The stub is a generic wrapper for bootstrapping a C-based SMM handler. Its
 * primary purpose is to put the CPU into protected mode with a stack and call
 * into the C handler.
 *
 * The stub_entry_params structure needs to correspond to the C structure
 * found in smm.h.
 */

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

.code32
.section ".module_parameters", "aw", @progbits
stub_entry_params:
stack_size:
.long 0
stack_top:
.long 0
c_handler:
.long 0
fxsave_area:
.long 0
fxsave_area_size:
.long 0
/* apic_to_cpu_num is a table mapping the default APIC id to CPU num. If the
 * APIC id is found at the given index, the contiguous CPU number is index
 * into the table. */
apic_to_cpu_num:
.fill CONFIG_MAX_CPUS,2,0xffff

.data
/* Provide fallback stack to use when a valid CPU number cannot be found. */
fallback_stack_bottom:
.skip 128
fallback_stack_top:

#define CR0_CLEAR_FLAGS \
	(CR0_CD | CR0_NW | CR0_PG | CR0_AM | CR0_WP | \
	 CR0_NE | CR0_TS | CR0_EM | CR0_MP)

#define SMM_DEFAULT_SIZE 0x10000

.text
.code16
.global _start
_start:
smm_handler_start:
#if CONFIG(SMM_LAPIC_REMAP_MITIGATION)
	/* Check if the LAPIC register block overlaps with the stub.
	 * This block needs to work without data accesses because they
	 * may be routed into the LAPIC register block.
	 * Code accesses, on the other hand, are never routed to LAPIC,
	 * which is what makes this work in the first place.
	 */
	mov	$LAPIC_BASE_MSR, %ecx
	rdmsr
	and	$(~0xfff), %eax
	call	1f
	/* Get the current program counter */
1:
	pop	%ebx
	sub	%ebx, %eax
	cmp	$(SMM_DEFAULT_SIZE), %eax
	ja	untampered_lapic
1:
#if CONFIG(CONSOLE_SERIAL)
	/* emit "Crash" on serial */
	mov	$(CONFIG_TTYS0_BASE), %dx
	mov	$'C', %al
	out	%al, (%dx)
	mov	$'r', %al
	out	%al, (%dx)
	mov	$'a', %al
	out	%al, (%dx)
	mov	$'s', %al
	out	%al, (%dx)
	mov	$'h', %al
	out	%al, (%dx)
#endif /* CONFIG_CONSOLE_SERIAL */
	/* now crash for real */
	ud2
untampered_lapic:
#endif
	movl	$(smm_relocate_gdt), %ebx
	lgdtl	(%ebx)

	movl	%cr0, %eax
	andl	$~CR0_CLEAR_FLAGS, %eax
	orl	$CR0_PE, %eax
	movl	%eax, %cr0

	/* Enable protected mode */
	ljmpl	$0x8, $smm_trampoline32

.align 4
smm_relocate_gdt:
	/* The first GDT entry is used for the lgdt instruction. */
	.word	smm_relocate_gdt_end - smm_relocate_gdt - 1
	.long	smm_relocate_gdt
	.word	0x0000

	/* gdt selector 0x08, flat code segment */
	.word	0xffff, 0x0000
	.byte	0x00, 0x9b, 0xcf, 0x00 /* G=1 and 0x0f, 4GB limit */

	/* gdt selector 0x10, flat data segment */
	.word	0xffff, 0x0000
	.byte	0x00, 0x93, 0xcf, 0x00

	/* gdt selector 0x18, flat code segment (64-bit) */
	.word	0xffff, 0x0000
	.byte	0x00, 0x9b, 0xaf, 0x00

	/* gdt selector 0x20 tss segment */
	.word   0xffff, 0x0000
	.byte   0x00, 0x8b, 0x80, 0x00
smm_relocate_gdt_end:

.align 4
.code32
.global smm_trampoline32
smm_trampoline32:
	/* Use flat data segment */
	movw	$0x10, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %ss
	xor	%ax, %ax /* zero out the gs and fs segment index */
	movw	%ax, %fs
	movw	%ax, %gs /* Used by cpu_info in ramstage */

	/* The CPU number is calculated by reading the initial APIC id. Since
	 * the OS can manipulate the APIC id use the non-changing cpuid result
	 * for APIC id (eax). A table is used to handle a discontiguous
	 * APIC id space.  */
apic_id:
	mov	$LAPIC_BASE_MSR, %ecx
	rdmsr
	and	$LAPIC_BASE_X2APIC_ENABLED, %eax
	cmp	$LAPIC_BASE_X2APIC_ENABLED, %eax
	jne	xapic

x2apic:
	mov	$0xb, %eax
	mov	$0, %ecx
	cpuid
	mov	%edx, %eax
	jmp	apicid_end

xapic:
	mov	$1, %eax
	cpuid
	mov	%ebx, %eax
	shr	$24, %eax

apicid_end:
	mov	$(apic_to_cpu_num), %ebx
	xor	%ecx, %ecx

1:
	cmp	(%ebx, %ecx, 2), %ax
	je	1f
	inc	%ecx
	cmp	$CONFIG_MAX_CPUS, %ecx
	jne	1b
	/* This is bad. One cannot find a stack entry because a CPU num could
	 * not be assigned. Use the fallback stack and check this condition in
	 * C handler. */
	movl	$(fallback_stack_top), %esp
	/* Clear fxsave location as there will be no saving/restoring. */
	xor	%edi, %edi
	jmp	2f
1:
	movl	stack_size, %eax
	mul	%ecx /* %eax(stack_size) * %ecx(cpu) = %eax(offset) */
	movl	stack_top, %ebx
	subl	%eax, %ebx /* global_stack_top - offset = stack_top */
	mov	%ebx, %esp

	/* Write canary to the bottom of the stack */
	movl	stack_size, %eax
	subl	%eax, %ebx /* %ebx(stack_top) - size = %ebx(stack_bottom) */
	movl	%ebx, (%ebx)
#if ENV_X86_64
	movl	$0, 4(%ebx)
#endif

	/* Create stack frame by pushing a NULL stack base pointer */
	pushl	$0x0
	mov	%esp, %ebp

	/* Allocate locals (fxsave, efer_backup) */
	subl	$0xc, %esp

	/* calculate fxsave location */
	mov	fxsave_area, %edi
	test	%edi, %edi
	jz	2f
	movl	fxsave_area_size, %eax
	mul	%ecx
	add	%eax, %edi

2:
	/* Save location of fxsave area. */
	mov	%edi, -4(%ebp)
	test	%edi, %edi
	jz	1f

	/* Enable sse instructions. */
	mov	%cr4, %eax
	orl	$(CR4_OSFXSR | CR4_OSXMMEXCPT), %eax
	mov	%eax, %cr4

	/* Save FP state. */
	fxsave	(%edi)

1:
	/* Align stack to 16 bytes. Another 32 bytes are pushed below. */
	andl	$0xfffffff0, %esp

#if ENV_X86_64
	mov	%ecx, %edi
	/* Backup IA32_EFER. Preserves ebx. */
	movl	$(IA32_EFER), %ecx
	rdmsr
	movl	%eax, -0x8(%ebp)
	movl	%edx, -0xc(%ebp)

	/* entry64.inc preserves ebx, esi, edi */
#include <cpu/x86/64bit/entry64.inc>
	mov	%edi, %ecx

#endif

	/* Call into the c-based SMM relocation function with the platform
	 * parameters. Equivalent to:
	 *   struct arg = { c_handler_params, cpu_num, smm_runtime, canary };
	 *   c_handler(&arg)
	 */
#if ENV_X86_64
	push	%rbx /* uintptr_t *canary */
	push	%rcx /* size_t cpu */

	mov	%rsp, %rdi	/* *arg */

	movabs	c_handler, %eax
	call	*%rax

	/*
	 * The only reason to go back to protected mode is that RSM doesn't restore
	 * MSR registers and MSR IA32_EFER was modified by entering long mode.
	 * Drop to protected mode to safely operate on the IA32_EFER MSR.
	 */

	/* Disable long mode. */
	#include <cpu/x86/64bit/exit32.inc>

	/* Restore IA32_EFER as RSM doesn't restore MSRs. */
	movl	$(IA32_EFER), %ecx
	rdmsr
	movl	-0x8(%ebp), %eax
	movl	-0xc(%ebp), %edx

	wrmsr

#else
	push	$0x0 /* Padding */
	push	%ebx /* uintptr_t *canary */
	push	%ecx /* size_t cpu */
	push	%esp /* smm_module_params *arg (allocated on stack). */
	mov	c_handler, %eax
	call	*%eax
#endif
	/* Retrieve fxsave location. */
	mov	-4(%ebp), %edi
	test	%edi, %edi
	jz	1f

	/* Restore FP state. */
	fxrstor	(%edi)

1:
	/* Exit from SM mode. */
	rsm
