#include "mem.h"
#include "amd64l.h"

MODE $32

#define pFARJMP32(s, o)	BYTE $0xea;		/* far jump to ptr32:16 */\
			LONG $o; WORD $s

/*
 * Enter here in 32-bit protected mode. Welcome to 1982.
 * Make sure the GDT is set as it should be:
 *	disable interrupts;
 *	load the GDT with the table in _gdt32p;
 *	load all the data segments
 *	load the code segment via a far jump.
 */
TEXT _protected<>(SB), 1, $-4
	CLI
	BYTE $0xe9; LONG $0x00000058;		/* JMP _endofheader */

_startofheader:
	BYTE	$0x90				/* NOP */
	BYTE	$0x90				/* NOP */

TEXT _multibootheader<>(SB), 1, $-4		/* must be 4-byte aligned */
	LONG	$0x1badb002			/* magic */
	LONG	$0x00000003			/* flags */
	LONG	$-(0x1badb002 + 0x00000003)	/* checksum */

TEXT _gdt32p<>(SB), 1, $-4
	QUAD	$0x0000000000000000		/* NULL descriptor */
	QUAD	$0x00cf9a000000ffff		/* CS */
	QUAD	$0x00cf92000000ffff		/* DS */
	QUAD	$0x0020980000000000		/* Long mode CS */

TEXT _gdtptr32p<>(SB), 1, $-4
	WORD	$(4*8-1)
	LONG	$_gdt32p<>-KZERO(SB)

TEXT _gdt64<>(SB), 1, $-4
	QUAD	$0x0000000000000000		/* NULL descriptor */
	QUAD	$0x0020980000000000		/* CS */

TEXT _gdtptr64p<>(SB), 1, $-4
	WORD	$(2*8-1)
	QUAD	$_gdt64<>-KZERO(SB)

TEXT _gdtptr64v<>(SB), 1, $-4
	WORD	$(3*8-1)
	QUAD	$_gdt64<>(SB)

_endofheader:
	MOVL	AX, BP				/* possible passed-in magic */

	MOVL	$_gdtptr32p<>-KZERO(SB), AX
	MOVL	(AX), GDTR

	MOVL	$SSEL(SiDS, SsTIGDT|SsRPL0), AX
	MOVW	AX, DS
	MOVW	AX, ES
	MOVW	AX, FS
	MOVW	AX, GS
	MOVW	AX, SS

	pFARJMP32(SSEL(SiCS, SsTIGDT|SsRPL0), _warp64<>-KZERO(SB))

/*
 * Make the basic page tables for CPU0 to map 0-4MiB physical
 * to KZERO, and include an identity map for the switch from protected
 * to paging mode. There's an assumption here that the creation and later
 * removal of the identity map will not interfere with the KZERO mappings;
 * the conditions for clearing the identity map are
 *	clear PML4 entry when (KZER0 & 0x0000ff8000000000) != 0;
 *	clear PDP entry when (KZER0 & 0x0000007fc0000000) != 0;
 *	don't clear PD entry when (KZER0 & 0x000000003fe00000) == 0;
 * the code below assumes these conditions are met.
 *
 * Assume a recent processor with Page Size Extensions
 * and use two 2MiB entries.
 */
/*
 * The layout is decribed in data.h:
 *	_protected:	start of kernel text
 *	- 4*KiB		unused
 *	- 4*KiB		unused
 *	- 4*KiB		ptrpage
 *	- 4*KiB		syspage
 *	- MACHSZ	m
 *	- 4*KiB		vsvmpage for gdt, tss
 *	- PTSZ		PT for PMAPADDR		unused - assumes in KZERO PD
 *	- PTSZ		PD
 *	- PTSZ		PDP
 *	- PTSZ		PML4
 *	- MACHSTKSZ	stack
 */

/*
 * Macros for accessing page table entries; change 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)

TEXT _warp64<>(SB), 1, $-4
	MOVL	$_protected<>-(MACHSTKSZ+4*PTSZ+5*(4*KiB)+MACHSZ+KZERO)(SB), SI

	MOVL	SI, DI
	XORL	AX, AX
	MOVL	$((MACHSTKSZ+4*PTSZ+5*(4*KiB)+MACHSZ)>>2), CX

	CLD
	REP;	STOSL				/* stack, P*, vsvm, m, sys */

	MOVL	SI, AX				/* sys-KZERO */
	ADDL	$(MACHSTKSZ), AX		/* PML4 */
	MOVL	AX, CR3				/* load the mmu */
	MOVL	AX, DX
	ADDL	$(PTSZ|PteRW|PteP), DX		/* PDP at PML4 + PTSZ */
	MOVL	DX, PML4O(0)(AX)		/* PML4E for identity map */
	MOVL	DX, PML4O(KZERO)(AX)		/* PML4E for KZERO, PMAPADDR */

	ADDL	$PTSZ, AX			/* PDP at PML4 + PTSZ */
	ADDL	$PTSZ, DX			/* PD at PML4 + 2*PTSZ */
	MOVL	DX, PDPO(0)(AX)			/* PDPE for identity map */
	MOVL	DX, PDPO(KZERO)(AX)		/* PDPE for KZERO, PMAPADDR */

	ADDL	$PTSZ, AX			/* PD at PML4 + 2*PTSZ */
	MOVL	$(PtePS|PteRW|PteP), DX
	MOVL	DX, PDO(0)(AX)			/* PDE for identity 0-[24]MiB */
	MOVL	DX, PDO(KZERO)(AX)		/* PDE for KZERO 0-[24]MiB */
	ADDL	$PGLSZ(1), DX
	MOVL	DX, PDO(KZERO+PGLSZ(1))(AX)	/* PDE for KZERO [24]-[48]MiB */

	MOVL	AX, DX				/* PD at PML4 + 2*PTSZ */
	ADDL	$(PTSZ|PteRW|PteP), DX		/* PT at PML4 + 3*PTSZ */
	MOVL	DX, PDO(PMAPADDR)(AX)		/* PDE for PMAPADDR */

/*
 * 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.
 */
TEXT _lme<>(SB), 1, $-4
	MOVL	CR4, AX
	ANDL	$~Pse, AX			/* Page Size */
	ORL	$(Pge|Pae), AX			/* Page Global, Phys. Address */
	MOVL	AX, CR4

	MOVL	$Efer, CX			/* Extended Feature Enable */
	RDMSR
	ORL	$Lme, AX			/* Long Mode Enable */
	WRMSR

	MOVL	CR0, DX
	ANDL	$~(Cd|Nw|Ts|Mp), DX
	ORL	$(Pg|Wp), DX			/* Paging Enable */
	MOVL	DX, CR0

	pFARJMP32(SSEL(3, SsTIGDT|SsRPL0), _identity<>-KZERO(SB))

/*
 * Long mode. Welcome to 2003.
 * Jump out of the identity map space;
 * load a proper long mode GDT.
 */
MODE $64

TEXT _identity<>(SB), 1, $-4
	MOVQ	$_start64v<>(SB), AX
	JMP*	AX

TEXT _start64v<>(SB), 1, $-4
	MOVQ	$_gdtptr64v<>(SB), AX
	MOVL	(AX), GDTR

	XORQ	DX, DX
	MOVW	DX, DS				/* not used in long mode */
	MOVW	DX, ES				/* not used in long mode */
	MOVW	DX, FS
	MOVW	DX, GS
	MOVW	DX, SS				/* not used in long mode */

	MOVLQZX	SI, SI				/* sys-KZERO */
	MOVQ	SI, AX
	ADDQ	$KZERO, AX
	MOVQ	AX, sys(SB)			/* sys */

	ADDQ	$(MACHSTKSZ), AX		/* PML4 and top of stack */
	MOVQ	AX, SP				/* set stack */

_zap0pml4:
	CMPQ	DX, $PML4O(KZERO)		/* KZER0 & 0x0000ff8000000000 */
	JEQ	_zap0pdp
	MOVQ	DX, PML4O(0)(AX) 		/* zap identity map PML4E */
_zap0pdp:
	ADDQ	$PTSZ, AX			/* PDP at PML4 + PTSZ */
	CMPQ	DX, $PDPO(KZERO)		/* KZER0 & 0x0000007fc0000000 */
	JEQ	_zap0pd
	MOVQ	DX, PDPO(0)(AX)			/* zap identity map PDPE */
_zap0pd:
	ADDQ	$PTSZ, AX			/* PD at PML4 + 2*PTSZ */
	CMPQ	DX, $PDO(KZERO)			/* KZER0 & 0x000000003fe00000 */
	JEQ	_zap0done
	MOVQ	DX, PDO(0)(AX)			/* zap identity map PDE */
_zap0done:

	ADDQ	$(MACHSTKSZ), SI		/* PML4-KZERO */
	MOVQ	SI, CR3				/* flush TLB */

	ADDQ	$(2*PTSZ+4*KiB), AX		/* PD+PT+vsvm */
	MOVQ	AX, RMACH			/* Mach */
	MOVQ	DX, RUSER

	PUSHQ	DX				/* clear flags */
	POPFQ

	MOVLQZX	BX, BX				/* push multiboot args */
	PUSHQ	BX				/* multiboot info* */
	MOVLQZX	RARG, RARG
	PUSHQ	RARG				/* multiboot magic */

	CALL	main(SB)

TEXT ndnr(SB), 1, $-4				/* no deposit, no return */
_dnr:
	STI
	HLT
	JMP	_dnr				/* do not resuscitate */

