/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2009,2010  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/symbol.h>
#include <grub/i386/memory.h>

#ifdef __x86_64__
#define RAX %rax
#define RSI %rsi
#else
#define RAX %eax
#define RSI %esi
#endif

#define CODE32_SEGMENT	0x18
#define CODE64_SEGMENT	0x08

/* The data segment of the protected mode.  */
#define DATA_SEGMENT	0x10

	.p2align	4	/* force 16-byte alignment */

VARIABLE(grub_relocator64_start)
LOCAL(base):
	/* %rax contains now our new 'base'.  */
	mov	RAX, RSI

	add	$(LOCAL(cont0) - LOCAL(base)), RAX
	jmp	*RAX
LOCAL(cont0):
#ifndef __x86_64__
	lea	(LOCAL(cont1) - LOCAL(base)) (RSI, 1), RAX
	mov	RAX, (LOCAL(jump_vector) - LOCAL(base)) (RSI, 1)

	lea	(LOCAL(gdt) - LOCAL(base)) (RSI, 1), RAX
	mov	RAX, (LOCAL(gdt_addr) - LOCAL(base)) (RSI, 1)

	/* Disable paging. */
	movl	%cr0, %eax
	andl	$(~GRUB_MEMORY_CPU_CR0_PAGING_ON), %eax
	movl	%eax, %cr0

	/* Turn on PAE. */
	movl	%cr4, %eax
	orl	$(GRUB_MEMORY_CPU_CR4_PAE_ON | GRUB_MEMORY_CPU_CR4_PSE_ON), %eax
	movl	%eax, %cr4

	/* mov imm32, %eax */
	.byte	0xb8
VARIABLE(grub_relocator64_cr3)
	.long	0
	movl	%eax, %cr3

	/* Turn on amd64. */
	movl	$GRUB_MEMORY_CPU_AMD64_MSR, %ecx
	rdmsr
	orl	$GRUB_MEMORY_CPU_AMD64_MSR_ON, %eax
	wrmsr

	/* Enable paging. */
	movl	%cr0, %eax
	orl	$GRUB_MEMORY_CPU_CR0_PAGING_ON, %eax
	movl	%eax, %cr0

	/* Load GDT. */
	lgdt	(LOCAL(gdtdesc) - LOCAL(base)) (RSI, 1)

	/* Update %cs.  */
	ljmp	*(LOCAL(jump_vector) - LOCAL(base)) (RSI, 1)
#else
	/* mov imm64, %rax */
	.byte 	0x48
	.byte	0xb8
VARIABLE(grub_relocator64_cr3)
	.quad	0
	movq	%rax, %cr3
#endif

LOCAL(cont1):
	.code64

	/* mov imm64, %rax */
	.byte 	0x48
	.byte	0xb8
VARIABLE(grub_relocator64_rsp)
	.quad	0

	movq	%rax, %rsp

	/* mov imm64, %rax */
	.byte 	0x48
	.byte	0xb8
VARIABLE(grub_relocator64_rsi)
	.quad	0

	movq	%rax, %rsi
	
	/* mov imm64, %rax */
	.byte 	0x48
	.byte	0xb8
VARIABLE(grub_relocator64_rax)
	.quad	0

	/* mov imm64, %rbx */
	.byte 	0x48
	.byte	0xbb
VARIABLE(grub_relocator64_rbx)
	.quad	0

	/* mov imm64, %rcx */
	.byte 	0x48
	.byte	0xb9
VARIABLE(grub_relocator64_rcx)
	.quad	0

	/* mov imm64, %rdx */
	.byte 	0x48
	.byte	0xba
VARIABLE(grub_relocator64_rdx)
	.quad	0

	/* Cleared direction flag is of no problem with any current
	   payload and makes this implementation easier.  */
	cld

	jmp *LOCAL(jump_addr) (%rip)

LOCAL(jump_addr):
VARIABLE(grub_relocator64_rip)
	.quad	0

#ifndef __x86_64__
	.p2align	4
LOCAL(gdt):
	/* NULL.  */
	.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

	/* 64-bit segment.  */
	.word 0xffff /* Limit xffff.  */
	.word 0x0000 /* Base xxxx0000.  */
	.byte 0x00 /* Base xx00xxxx.  */
	.byte (0x8 /* Type 8.  */ | (1 << 4) /* Code.  */ \
	       | (0 << 5) /* Ring 0.  */ | (1 << 7) /* Present.  */)
	.byte (0xf /* Limit fxxxx.  */ | (0 << 4) /* AVL flag.  */ \
	       | (1 << 5) /* 64-bit.  */ | (0 << 6) \
	       | (1 << 7) /* 4K granular.  */)
	.byte 0x00 /* Base 00xxxxxx.  */

	/* Data segment*/
	.word 0xffff /* Limit xffff.  */
	.word 0x0000 /* Base xxxx0000.  */
	.byte 0x00 /* Base xx00xxxx.  */
	.byte (0x0 /* Type 0.  */ | (0 << 4) /* Data.  */ \
	       | (0 << 5) /* Ring 0.  */ | (1 << 7) /* Present.  */)
	.byte (0xf /* Limit fxxxx.  */ | (0 << 4) /* AVL flag.  */ \
	       | (0 << 5) /* Data.  */ | (0 << 6) \
	       | (1 << 7) /* 4K granular.  */)
	.byte 0x00 /* Base 00xxxxxx.  */

	/* Compatibility segment.  */
	.word 0xffff /* Limit xffff.  */
	.word 0x0000 /* Base xxxx0000.  */
	.byte 0x00 /* Base xx00xxxx.  */
	.byte (0x8 /* Type 8.  */ | (1 << 4) /* Code.  */  \
	       | (0 << 5) /* Ring 0.  */ | (1 << 7) /* Present.  */)
	.byte (0xf /* Limit fxxxx.  */ | (0 << 4) /* AVL flag.  */ \
	       | (0 << 5) /* 32-bit.  */ | (1 << 6) /* 32-bit.  */ \
	       | (1 << 7) /* 4K granular.  */)
	.byte 0x00 /* Base 00xxxxxx.  */

	.p2align	4
LOCAL(gdtdesc):
	.word	0x20
LOCAL(gdt_addr):
	/* Filled by the code. */
	.long	0

	.p2align	4
LOCAL(jump_vector):
	/* Jump location. Is filled by the code */
	.long	0
	.long	CODE64_SEGMENT
#endif

VARIABLE(grub_relocator64_end)
