;------------------------------------------------------------------------------
;
;  This is used just to produce 32 bit opcode and compare it with 64 bit.
;
;	in Win SDK prompt:
; 	SetEnv /x86
;	ml /c /Fl TestAsm32.asm
;------------------------------------------------------------------------------
	
XDTR	STRUCT
			Limit	DW	?
			Base	DQ	?
XDTR	ENDS

EXTERN	gKernelEntry: dword

	.model flat
	.data
	
; variables accessed from both 32 and 64 bit code
; need to have this exactly in this order
DataBase	LABEL PTR


; 64 bit state
SavedGDTROff	EQU $-DataBase
SavedGDTR		XDTR	<?>

SavedIDTROff	EQU $-DataBase
SavedIDTR		XDTR	<?>

SavedCR3Off		EQU $-DataBase
SavedCR3		DQ		?

SavedCSOff		EQU $-DataBase
SavedCS			DW		?

SavedDSOff		EQU $-DataBase
SavedDS			DW		?

; 32 bit state
SavedGDTR32Off	EQU $-DataBase
SavedGDTR32		XDTR	<?>

SavedIDTR32Off	EQU	$-DataBase
SavedIDTR32		XDTR	<?>

SavedCS32Off	EQU $-DataBase
SavedCS32		DW			?

SavedDS32Off	EQU $-DataBase
SavedDS32		DW			?

SavedESP32Off	EQU $-DataBase
SavedESP32		DD			?

; kernel entry - 32 bit
AsmKernelEntryOff				EQU $-DataBase
AsmKernelEntry					DD			?


;
; for copying kernel image from reloc block to proper mem place
;

; kernel image start in reloc block (source) - 32 bit
AsmKernelImageStartRelocOff		EQU $-DataBase
AsmKernelImageStartReloc		DD			?

; kernel image start (destination) - 32 bit
AsmKernelImageStartOff			EQU $-DataBase
AsmKernelImageStart				DD			?

; kernel image size - 32 bit
AsmKernelImageSizeOff			EQU $-DataBase
AsmKernelImageSize				DD			?

; address of relocated MyAsmCopyAndJumpToKernel32 - 32 bit
MyAsmCopyAndJumpToKernel32AddrOff		EQU $-DataBase
MyAsmCopyAndJumpToKernel32Addr		DD			?

	.code

MyAsmEntryPatchCodeSample32   PROC
	mov		ecx, 011223344h
	;push	ecx
	;ret
	jmp		ecx
MyAsmEntryPatchCodeSample32   ENDP


;------------------------------------------------------------------------------
; MyAsmJumpFromKernel32
; 
;------------------------------------------------------------------------------
MyAsmJumpFromKernel32   PROC
	
	;hlt	; uncomment to stop here for test
	; save bootArgs pointer to edi
	mov		edi, eax
	
	; load ebx with DataBase - we'll access our saved data with it
	db		0BBh				; mov ebx, OFFSET DataBase
DataBaseAdr	dd	0

	; let's find out kernel entry point - we'll need it to jump back.
	; we are called with
	;   mov ecx, 0x11223344
	;   call ecx
	; and that left return addr on stack. those instructions
	; are 7 bytes long, and if we take address from stack and
	; substitute 7 from it, we will get kernel entry point.
	pop		ecx							; 32 bit: pop ecx
	sub		ecx, 7
	; and save it
	mov		DWORD PTR [ebx + AsmKernelEntryOff], ecx

	; lets save 32 bit state to be able to recover it later
	; rbx is ebx in 32 bit
	sgdt	FWORD PTR [ebx + SavedGDTR32Off]
	sidt	FWORD PTR [ebx + SavedIDTR32Off]
	mov		WORD  PTR [ebx + SavedCS32Off], cs
	mov		WORD  PTR [ebx + SavedDS32Off], ds
	mov		DWORD PTR [ebx + SavedESP32Off], esp
	
	;
	; move to 64 bit mode ...
	;
	
	; load saved UEFI GDT, IDT
	; will become active after code segment is changed in long jump
	; rbx is ebx in 32 bit
	lgdt	FWORD PTR [ebx + SavedGDTROff]
	lidt	FWORD PTR [ebx + SavedIDTROff]

	; enable the 64-bit page-translation-table entries by setting CR4.PAE=1
	mov		eax, cr4
	bts		eax, 5
	mov		cr4, eax
	
	; set the long-mode page tables - reuse saved UEFI tables
	mov		eax, DWORD PTR [ebx +SavedCR3Off]
	mov		cr3, eax
	
	; enable long mode (set EFER.LME=1).
	mov 	ecx, 0c0000080h			; EFER MSR number.
	rdmsr							; Read EFER.
	bts		eax, 8					; Set LME=1.
	wrmsr							; Write EFER.

	; enable paging to activate long mode (set CR0.PG=1)
	mov		eax, cr0				; Read CR0.
	bts		eax, 31					; Set PG=1.
	mov		cr0, eax				; Write CR0.
	
	; jump to the 64-bit code segment
	mov		ax, WORD  PTR [ebx + SavedCSOff]
	push 	eax
	call	_RETF32
	
	;
	; aloha!
	; if there is any luck, we are in 64 bit mode now
	;
	;hlt	; uncomment to stop here for test
	
	; set segmens
	;mov		ax, WORD  PTR [ebx + SavedDSOff]
	;mov		ds, ax
	; set up stack ...
	; not sure if needed, but lets set ss to ds
	;mov		ss, ax
	; lets align the stack
	;mov		rax, rsp
	;and		rax, 0xfffffffffffffff0
	;mov		rsp, rax
	
	; call our C code with bootArgs as first arg (in rcx)
	;mov		rcx, rdi
	;push	rcx
	; KernelEntryPatchJumpBack should be EFIAPI
	; and rbx should not be changed by EFIAPI calling convention
	;call	KernelEntryPatchJumpBack
	;hlt	; uncomment to stop here for test
	; return value in rax is bootArgs pointer
	;mov		rdi, rax
	
	;
	; time to go back to 32 bit
	;
	
	; load saved 32 bit gdtr
	;lgdt	FWORD PTR [ebx + SavedGDTR32Off]
	; push saved cs and rip (with call) to stack and do retf
	;mov		ax, WORD  PTR [ebx + SavedCS32Off]
	;push 	rax
	;call	_RETF64

	;
	; ok, 32 bit opcode again from here
	;

	; disable paging (set CR0.PG=0)
	mov		eax, cr0				; Read CR0.
	btr		eax, 31					; Set PG=0.
	mov		cr0, eax				; Write CR0.
	
	; disable long mode (set EFER.LME=0).
	mov 	ecx, 0c0000080h			; EFER MSR number.
	rdmsr							; Read EFER.
	btr		eax, 8					; Set LME=0.
	wrmsr							; Write EFER.
	jmp		toNext
toNext:	
	;
	; we are in 32 bit protected mode, no paging
	;

	; now reload saved 32 bit state data
	lidt	FWORD PTR [ebx + SavedIDTR32Off]
	mov		ax, WORD  PTR [ebx + SavedDS32Off]
	mov		ss, ax
	mov		ds, ax
	mov		es, ax
	mov		fs, ax
	mov		gs, ax
	mov		esp, DWORD PTR [ebx + SavedESP32Off]
	
	;
	; prepare vars for copying kernel to proper mem
	; and jump to kernel: set registers as needed
	; by MyAsmCopyAndJumpToKernel32
	;
	
	; boot args back from edi
	mov		eax, edi
	; kernel entry point
	mov		edx, DWORD PTR [ebx + AsmKernelEntryOff]
	
	; source, destination and size for kernel copy
	mov		esi, DWORD PTR [ebx + AsmKernelImageStartRelocOff]
	mov		edi, DWORD PTR [ebx + AsmKernelImageStartOff]
	mov		ecx, DWORD PTR [ebx + AsmKernelImageSizeOff]
	
	; address of relocated MyAsmCopyAndJumpToKernel32
	mov		ebx, DWORD PTR [ebx + MyAsmCopyAndJumpToKernel32AddrOff]
	; note: ebx not valid as a pointer to DataBase any more
	
	;
	; jump to MyAsmCopyAndJumpToKernel32
	;
	jmp		DWORD PTR ebx			; jmp DWORD PTR ebx in 32 bit
	

_RETF64:
	DB	048h
_RETF32:
	retf

; the following is not used - it's here just for a reference
	; jump to the 64-bit code segment
	; as xnu kernel does it
	; example:
	; 07f8437a4: 68 28 00 00 cb = push 0cb000008h - last word on stack is segment 0x0008, 07f8437a8h contains CB opcode which is retf
	; 07f8437a9: e8 fa ff ff ff = call 07f8437a8h - push EIP on stack and continue with 07f8437a8h which is retf
	; retf does far return to next instr after 'call $-1'
	
	;push	((0cbh SHL 24) OR CODE64_SEL)
	;call	$-1

	
MyAsmJumpFromKernel32   ENDP


;------------------------------------------------------------------------------
; MyAsmCopyAndJumpToKernel32
; 
; This is the last part of the code - it will copy kernel image from reloc
; block to proper mem place and jump to kernel.
; It's 32 bit code and runs after switching back to 32 bit.
; This code will be relocated (copied) to higher mem by PrepareJumpFromKernel().
;
; Expects:
; EAX = address of boot args (proper address, not from reloc block)
; EDX = kernel entry point
; ESI = start of kernel image in reloc block (source)
; EDI = proper start of kernel image (destination)
; ECX = kernel image size in bytes
;------------------------------------------------------------------------------
		align 08h
MyAsmCopyAndJumpToKernel32   PROC
	
	;
	; we will move double words (4 bytes)
	; so ajust ECX to number of double words.
	; just in case ECX is not multiple of 4 - inc by 1
	;
	shr		ecx, 2
	inc		ecx
	
	;
	; copy kernel image from reloc block to proper mem place.
	; all params should be already set:
	; ECX = number of double words
	; DS:ESI = source
	; ES:EDI = destination
	;
	cld								; direction is up
	rep movsd
	
	;
	; and finally jump to kernel:
	; EAX already contains bootArgs pointer,
	; and EDX contains kernel entry point
	;
	;hlt
	jmp		DWORD PTR edx			; jmp DWORD PTR edx in 32 bit
MyAsmCopyAndJumpToKernel32   ENDP
MyAsmCopyAndJumpToKernel32End:


END
