/*
 * Start-up request IPI handler.
 *
 * This code is executed on an application processor in response to receiving
 * a Start-up IPI (SIPI) from another processor.
 * This must be placed on a 4KiB boundary
 * somewhere in the 1st MiB of conventional memory. However,
 * due to some shortcuts below it's restricted further to within the 1st 64KiB.
 * The AP starts in real-mode, with
 *   CS selector set to the startup memory address/16;
 *   CS base set to startup memory address;
 *   CS limit set to 64KiB;
 *   CPL and IP set to 0.
 * Parameters are passed to this code via a vector in low memory
 * indexed by the APIC number of the processor. The layout, size,
 * and location have to be kept in sync with the setup in sipi.s.
 */
#include "mem.h"
#include "amd64.h"
#ifndef __ASSEMBLER__
#define __ASSEMBLER__
#endif

.section .text

/*
 * Real mode. Welcome to 1978.
 * Load a basic GDT, turn on protected mode and make
 * inter-segment jump to the protected mode code.
 */
.code16
.align 4096
.globl sipihandler
sipihandler:
_real:
	ljmp	$0x0, $_endofheader

_startofheader:
	NOP; NOP; NOP
	.quad	0xa5a5a5a5a5a5a5a5

_gdt32p:
	.quad	0x0000000000000000		/* NULL descriptor */
	.quad	0x00cf9a000000ffff		/* CS */
	.quad	0x00cf92000000ffff		/* DS */
	.quad	0x0020980000000000		/* .long mode CS */

_gdtptr32p:
	.word	4*8-1				/* includes .long mode */
	.long	_gdt32p

_gdt64:
	.quad	0x0000000000000000		/* NULL descriptor */
	.quad	0x0020980000000000		/* CS */
	.quad	0x0000800000000000		/* DS */

_gdtptr64v:
	.word	3*8-1
	.quad	_gdt64

_endofheader:
	mov	%cs, %ax
	mov	%ax, %ds

	lgdt	_gdtptr32p			/* load a basic gdt */

	mov	%cr0, %eax
	or	$Pe, %ax
	mov	%eax, %cr0			/* turn on protected mode */

	jmp	1f
1:
	mov	$(SSEL(SiDS, SsTIGDT|SsRPL0)), %ax
	mov	%ax, %ds
	mov	%ax, %es
	mov	%ax, %fs
	mov	%ax, %gs
	mov	%ax, %ss

	ljmpl	$8, $_protected			/* 8 = SSEL(SiCS, SsTIGDT|SsRPL0) */


/*
 * Protected mode. Welcome to 1982.
 * Get the local APIC ID from the memory mapped APIC
 * and use it to locate the index to the parameter vector;
 * load the PDB with the page table address from the
 * information vector;
 * make an identity map for the inter-segment jump below,
 * using the stack space to hold a temporary PDP and PD;
 * enable and activate .long mode;
 * make an inter-segment jump to the .long mode code.
 */
.code32

/*
 * Macros for accessing page table entries; must turn
 * the C-style array-index macros into a page table byte
 * offset.
 */
#define PML4O(v)	((PTLX((v), 3))<<3)
#define PDPO(v)		((PTLX((v), 2))<<3)
#define PDO(v)		((PTLX((v), 1))<<3)
#define PTO(v)		((PTLX((v), 0))<<3)

_protected:
	mov	$0xfee00000, %ebp	/* apicbase */
	mov	0x20(%ebp), %ebp	/* Id */
	shr	$0x18, %ebp		/* becomes RARG later */

	mov	%ebp, %eax		/* apicno */
	imul	$0x20, %eax, %eax	/* [apicno] */
	mov	$_real, %ebx
	add	$0x1000, %ebx		/* sipi */
	add	%eax, %ebx		/* sipi[apicno] */

	mov	(%ebx), %esi		/* sipi[apicno].pml4 */

	mov	%esi, %eax
	mov	%eax, %cr3		/* load the mmu */

	mov	%eax, %edx
	sub	$MACHSTKSZ, %edx	/* PDP for identity map */
	add	$(PteRW|PteP), %edx
	mov	%edx, PML4O(0)(%eax)	/* PML4E for identity map */

	sub	$MACHSTKSZ, %eax	/* PDP for identity map */
	add	$PTSZ, %edx
	mov	%edx, PDPO(0)(%eax)	/* PDPE for identity map */
	mov	$(PtePS|PteRW|PteP), %edx
	add	$PTSZ, %eax		/* PD for identity map */
	mov	%edx, PDO(0)(%eax)	/* PDE for identity 0-[24]MiB */

/*
 * Enable and activate .long Mode. From the manual:
 * 	make sure Page Size Extentions are off, and Page Global
 *	Extensions and Physical Address Extensions are on in CR4;
 *	set .long Mode Enable in the Extended Feature Enable MSR;
 *	set Paging Enable in CR0;
 *	make an inter-segment jump to the .long Mode code.
 * It's all in 32-bit mode until the jump is made.
 */
_lme:
	mov	%cr4, %eax
	and	$~Pse, %eax		/* Page Size */
	or	$(Pge|Pae), %eax	/* Page Global, Phys. Address */
	mov	%eax, %cr4

	mov	$Efer, %ecx		/* Extended Feature Enable */
	rdmsr
	or	$Lme, %eax		/* .long Mode Enable */
	wrmsr

	mov	%cr0, %edx
	and	$~(Cd|Nw|Ts|Mp), %edx
	or	$(Pg|Wp), %edx		/* Paging Enable */
	mov	%edx, %cr0

	ljmp	$0x18, $_identity	/* 0x18 = SSEL(3, SsTIGDT|SsRPL0) */

/*
 * .long mode. Welcome to 2003.
 * Jump out of the identity map space;
 * load a proper .long mode GDT;
 * zap the identity map;
 * initialise the stack, RMACH, RUSER,
 * and call the C startup code.
 */
.code64
_identity:
	mov	$(_start64v+KZERO), %rax
	jmpq	*%rax

_start64v:
	mov	$_gdtptr64v, %rax
	lgdt	(%rax)

	xor	%rdx, %rdx		/* DX is 0 from here on */
	mov	%edx, %ds		/* not used in .long mode */
	mov	%edx, %es		/* not used in .long mode */
	mov	%edx, %fs
	mov	%edx, %gs
	mov	%edx, %ss		/* not used in .long mode */

	mov	%esi, %esi		/* sipi[apicno].pml4 */
	mov	%rsi, %rax
	add	$KZERO, %rax		/* PML4 */

	mov	%rdx, PML4O(0)(%rax)	/* zap identity map */
	mov	%rsi, %cr3		/* flush TLB */

	add	$KZERO, %rbx		/* &sipi[apicno] */

	mov	8(%rbx), %rsp		/* sipi[apicno].stack */

	push	%rdx			/* clear flags */
	popfq
	mov	%ebp, %ebp		/* APIC ID */
	push	%rbp			/* apicno */

	mov	16(%rbx), %r15		/* sipi[apicno].mach */
	mov	%rdx, %r14
	mov	24(%rbx), %rax		/* sipi[apicno].pc */
	callq	*%rax			/* (*sipi[apicno].pc)(apicno) */

_ndnr:
	jmp	_ndnr

.globl sipihandlerend
sipihandlerend:
	jmp	sipihandlerend
