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

#include <cpu/x86/cpu_info.S.inc>
#include <cpu/x86/post_code.h>
#include <arch/ram_segs.h>

/* Place the stack in the bss section. It's not necessary to define it in the
 * the linker script. */
	.section .bss, "aw", @nobits
.global _stack
.global _estack
.global _stack_size

/* Stack alignment is not enforced with rmodule loader, reserve one
 * extra CPU such that alignment can be enforced on entry. */
.align CONFIG_STACK_SIZE
_stack:
.space (CONFIG_MAX_CPUS+1)*CONFIG_STACK_SIZE
_estack:
.set _stack_size, _estack - _stack

	.section ".text._start", "ax", @progbits
#if ENV_X86_64
	.code64
#else
	.code32
#endif
	.globl _start
_start:
	cli
#if ENV_X86_64
	movabs	$gdtaddr, %rax
	lgdt	(%rax)
#else
	lgdt	%cs:gdtaddr
	ljmp	$RAM_CODE_SEG, $1f
#endif
1:	movl	$RAM_DATA_SEG, %eax
	movl	%eax, %ds
	movl	%eax, %es
	movl	%eax, %ss
	xor	%eax, %eax /* zero out the gs and fs segment index */
	movl	%eax, %fs
	movl	%eax, %gs /* Will be used for cpu_info */
#if ENV_X86_64
	mov	$RAM_CODE_SEG64, %ecx
	call	SetCodeSelector
#endif

	post_code(POST_ENTRY_C_START)		/* post 13 */

	cld

#if ENV_X86_64
	mov	%rdi, %rax
	movabs	%rax, _cbmem_top_ptr
	movabs	$_stack, %rdi
#else
	/* The return argument is at 0(%esp), the calling argument at 4(%esp) */
	movl	4(%esp), %eax
	movl	%eax, _cbmem_top_ptr
	leal	_stack, %edi
#endif

	/** poison the stack. Code should not count on the
	 * stack being full of zeros. This stack poisoning
	 * recently uncovered a bug in the broadcast SIPI
	 * code.
	 */
	movl	$_estack, %ecx
	subl	%edi, %ecx
	shrl	$2, %ecx   /* it is 32 bit aligned, right? */
	movl	$0xDEADBEEF, %eax
	rep
	stosl

	/* Set new stack with enforced alignment. */
	movl	$_estack, %esp
	andl	$(~(CONFIG_STACK_SIZE-1)), %esp

	push_cpu_info

#if CONFIG(CPU_INFO_V2)
	/* Allocate the per_cpu_segment_data on the stack */
	push_per_cpu_segment_data

	/*
	 * Update the BSP's per_cpu_segment_descriptor to point to the
	 * per_cpu_segment_data that was allocated on the stack.
	 */
	set_segment_descriptor_base $per_cpu_segment_descriptors, %esp

	mov	$per_cpu_segment_selector, %eax
	movl	(%eax), %eax
	mov	%eax, %gs
#endif

	/*
	 *	Now we are finished. Memory is up, data is copied and
	 *	bss is cleared.   Now we call the main routine and
	 *	let it do the rest.
	 */
	post_code(POST_PRE_HARDWAREMAIN)	/* post 6e */

	andl	$0xFFFFFFF0, %esp

#if CONFIG(ASAN_IN_RAMSTAGE)
	call asan_init
#endif

#if CONFIG(GDB_WAIT)
	call gdb_hw_init
	call gdb_stub_breakpoint
#endif
	call	main
	/* NOTREACHED */
.Lhlt:
	post_code(POST_DEAD_CODE)	/* post ee */
	hlt
	jmp	.Lhlt

#if CONFIG(GDB_WAIT)

	.globl gdb_stub_breakpoint
gdb_stub_breakpoint:
#if ENV_X86_64
	pop	%rax	/* Return address */
	pushfl
	push	%cs
	push	%rax	/* Return address */
	push	$0	/* No error code */
	push	$32	/* vector 32 is user defined */
#else
	popl	%eax	/* Return address */
	pushfl
	pushl	%cs
	pushl	%eax	/* Return address */
	pushl	$0	/* No error code */
	pushl	$32	/* vector 32 is user defined */
#endif
	jmp	int_hand
#endif

	.globl gdt, gdt_end
	.global per_cpu_segment_descriptors, per_cpu_segment_selector

gdtaddr:
	.word	gdt_end - gdt - 1
#if ENV_X86_64
	.quad	gdt
#else
	.long	gdt		/* we know the offset */
#endif

	.data

	/* This is the gdt for GCC part of coreboot.
	 * It is different from the gdt in ASM part of coreboot
	 * which is defined in gdt_init.S
	 *
	 * When the machine is initially started, we use a very simple
	 * gdt from ROM (that in gdt_init.S) which only contains those
	 * entries we need for protected mode.
	 *
	 * When we're executing code from RAM, we want to do more complex
	 * stuff, like initializing PCI option ROMs in real mode, or doing
	 * a resume from a suspend to RAM.
	 */
gdt:
	/* selgdt 0, unused */
	.word	0x0000, 0x0000		/* dummy */
	.byte	0x00, 0x00, 0x00, 0x00

	/* selgdt 8, unused */
	.word	0x0000, 0x0000		/* dummy */
	.byte	0x00, 0x00, 0x00, 0x00

	/* selgdt 0x10, flat code segment */
	.word	0xffff, 0x0000
	.byte	0x00, 0x9b, 0xcf, 0x00 /* G=1 and 0x0f, So we get 4Gbytes for
					* limit
					*/

	/* selgdt 0x18, flat data segment */
	.word	0xffff, 0x0000
#if ENV_X86_64
	.byte	0x00, 0x92, 0xcf, 0x00
#else
	.byte	0x00, 0x93, 0xcf, 0x00
#endif

	/* selgdt 0x20, unused */
	.word	0x0000, 0x0000		/* dummy */
	.byte	0x00, 0x00, 0x00, 0x00

	/* The next two entries are used for executing VGA option ROMs */

	/* selgdt 0x28 16 bit 64k code at 0x00000000 */
	.word	0xffff, 0x0000
	.byte	0, 0x9a, 0, 0

	/* selgdt 0x30 16 bit 64k data at 0x00000000 */
	.word	0xffff, 0x0000
	.byte	0, 0x92, 0, 0

	/* The next two entries are used for ACPI S3 RESUME */

	/* selgdt 0x38, flat data segment 16 bit */
	.word	0x0000, 0x0000		/* dummy */
	.byte	0x00, 0x93, 0x8f, 0x00 /* G=1 and 0x0f, So we get 4Gbytes for
					* limit
					*/

	/* selgdt 0x40, flat code segment 16 bit */
	.word	0xffff, 0x0000
	.byte	0x00, 0x9b, 0x8f, 0x00 /* G=1 and 0x0f, So we get 4Gbytes for
					* limit
					*/

#if ENV_X86_64
	/* selgdt 0x48, flat x64 code segment */
	.word	0xffff, 0x0000
	.byte	0x00, 0x9b, 0xaf, 0x00
#endif
#if CONFIG(CPU_INFO_V2)
per_cpu_segment_descriptors:
	.rept CONFIG_MAX_CPUS
	/* flat data segment */
	.word	0xffff, 0x0000
#if ENV_X86_64
	.byte	0x00, 0x92, 0xcf, 0x00
#else
	.byte	0x00, 0x93, 0xcf, 0x00
#endif
	.endr
#endif /* CPU_INFO_V2 */
gdt_end:

#if CONFIG(CPU_INFO_V2)
/* Segment selector pointing to the first per_cpu_segment_descriptor. */
per_cpu_segment_selector:
	.long	per_cpu_segment_descriptors - gdt
#endif /* CPU_INFO_V2 */

	.section ".text._start", "ax", @progbits
#if ENV_X86_64
SetCodeSelector:
	# save rsp because iret will align it to a 16 byte boundary
	mov	%rsp, %rdx

	# use iret to jump to a 64-bit offset in a new code segment
	# iret will pop cs:rip, flags, then ss:rsp
	mov	%ss, %ax	# need to push ss..
	push	%rax		# push ss instruction not valid in x64 mode,
				# so use ax
	push	%rsp
	pushfq
	push	%rcx		# cx is code segment selector from caller
	movabs	$setCodeSelectorLongJump, %rax
	push	%rax

	# the iret will continue at next instruction, with the new cs value
	# loaded
	iretq

setCodeSelectorLongJump:
	# restore rsp, it might not have been 16-byte aligned on entry
	mov	%rdx, %rsp
	ret
#endif
