/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2015 Google, Inc
 *
 * Taken from coreboot file of the same name
 */

/*
 * The SIPI vector is responsible for initializing the APs in the sytem. It
 * loads microcode, sets up MSRs, and enables caching before calling into
 * C code
 */

#include <asm/msr-index.h>
#include <asm/processor.h>
#include <asm/processor-flags.h>
#include <asm/sipi.h>

#define CODE_SEG	(X86_GDT_ENTRY_32BIT_CS * X86_GDT_ENTRY_SIZE)
#define DATA_SEG	(X86_GDT_ENTRY_32BIT_DS * X86_GDT_ENTRY_SIZE)

/*
 * First we have the 16-bit section. Every AP process starts here.
 * The simple task is to load U-Boot's Global Descriptor Table (GDT) to allow
 * U-Boot's 32-bit code to become visible, then jump to ap_start.
 *
 * Note that this code is copied to RAM below 1MB in mp_init.c, and runs from
 * there, but the 32-bit code (ap_start and onwards) is part of U-Boot and
 * is therefore relocated to the top of RAM with other U-Boot code. This
 * means that for the 16-bit code we must write relocatable code, but for the
 * rest, we can do what we like.
 */
.text
.code16
.globl ap_start16
ap_start16:
	cli
	xorl	%eax, %eax
	movl	%eax, %cr3		/* Invalidate TLB */

	/* setup the data segment */
	movw	%cs, %ax
	movw	%ax, %ds

	/* Use an address relative to the data segment for the GDT */
	movl	$gdtaddr, %ebx
	subl	$ap_start16, %ebx

	data32 lgdt (%ebx)

	movl	%cr0, %eax
	andl	$(~(X86_CR0_PG | X86_CR0_AM | X86_CR0_WP | X86_CR0_NE | \
		    X86_CR0_TS | X86_CR0_EM | X86_CR0_MP)), %eax
	orl	$(X86_CR0_NW | X86_CR0_CD | X86_CR0_PE), %eax
	movl	%eax, %cr0

	movl	$ap_start_jmp, %eax
	subl	$ap_start16, %eax
	movw	%ax, %bp

	/* Jump to ap_start within U-Boot */
data32 cs	ljmp	*(%bp)

	.align	4
.globl sipi_params_16bit
sipi_params_16bit:
	/* 48-bit far pointer */
ap_start_jmp:
	.long	0		/* offset set to ap_start by U-Boot */
	.word	CODE_SEG	/* segment */

	.word	0		/* padding */
gdtaddr:
	.word	0 /* limit */
	.long	0 /* table */
	.word	0 /* unused */

.globl ap_start16_code_end
ap_start16_code_end:

/*
 * Set up the special 'fs' segment for global_data. Then jump to ap_continue
 * to set up the AP.
 */
.globl ap_start
ap_start:
	.code32
	movw	$DATA_SEG, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %ss
	movw	%ax, %gs

	movw	$(X86_GDT_ENTRY_32BIT_FS * X86_GDT_ENTRY_SIZE), %ax
	movw	%ax, %fs

	/* Load the Interrupt descriptor table */
	mov	idt_ptr, %ebx
	lidt	(%ebx)

	/* Obtain cpu number */
	movl	ap_count, %eax
1:
	movl	%eax, %ecx
	inc	%ecx
	lock cmpxchg %ecx, ap_count
	jnz	1b

	/* Setup stacks for each CPU */
	movl	stack_size, %eax
	mul	%ecx
	movl	stack_top, %edx
	subl	%eax, %edx
	mov	%edx, %esp
	/* Save cpu number */
	mov	%ecx, %esi

	/* Determine if one should check microcode versions */
	mov	microcode_ptr, %edi
	test	%edi, %edi
	jz	microcode_done /* Bypass if no microde exists */

	/* Get the Microcode version */
	mov	$1, %eax
	cpuid
	mov	$MSR_IA32_UCODE_REV, %ecx
	rdmsr
	/* If something already loaded skip loading again */
	test	%edx, %edx
	jnz	microcode_done

	/* Determine if parallel microcode loading is allowed */
	cmpl	$0xffffffff, microcode_lock
	je	load_microcode

	/* Protect microcode loading */
lock_microcode:
	lock btsl $0, microcode_lock
	jc	lock_microcode

load_microcode:
	/* Load new microcode */
	mov	$MSR_IA32_UCODE_WRITE, %ecx
	xor	%edx, %edx
	mov	%edi, %eax
	/*
	 * The microcode pointer is passed in pointing to the header. Adjust
	 * pointer to reflect the payload (header size is 48 bytes)
	 */
	add	$UCODE_HEADER_LEN, %eax
	pusha
	wrmsr
	popa

	/* Unconditionally unlock microcode loading */
	cmpl	$0xffffffff, microcode_lock
	je	microcode_done

	xor	%eax, %eax
	mov	%eax, microcode_lock

microcode_done:
	/*
	 * Load MSRs. Each entry in the table consists of:
	 * 0: index,
	 * 4: value[31:0]
	 * 8: value[63:32]
	 * See struct saved_msr in mp_init.c.
	 */
	mov	msr_table_ptr, %edi
	mov	msr_count, %ebx
	test	%ebx, %ebx
	jz	1f
load_msr:
	mov	(%edi), %ecx
	mov	4(%edi), %eax
	mov	8(%edi), %edx
	wrmsr
	add	$12, %edi
	dec	%ebx
	jnz	load_msr

1:
	/* Enable caching */
	mov	%cr0, %eax
	andl	$(~(X86_CR0_CD | X86_CR0_NW)), %eax
	mov	%eax, %cr0

	/* c_handler(cpu_num) */
	movl	%esi, %eax	/* cpu_num */
	mov	c_handler, %esi
	call	*%esi

	/* This matches struct sipi_param */
	.align	4
.globl	sipi_params
sipi_params:
idt_ptr:
	.long 0
stack_top:
	.long 0
stack_size:
	.long 0
microcode_lock:
	.long 0
microcode_ptr:
	.long 0
msr_table_ptr:
	.long 0
msr_count:
	.long 0
c_handler:
	.long 0
ap_count:
	.long 0
