/*
 * exec_kernel/user_space/head.S
 *
 * Copyright (C) 2000, 2002, 2003 Eric Biederman
 *
 * Parts of this code were take from the linux startup
 * code of linux-2.4.0-test9
 *
 * Other parts were taken from etherboot-5.0.5
 */

#define ASSEMBLY 1

#define RELOC 0x10000
#define PROT_CODE_SEG 0x10
#define PROT_DATA_SEG 0x18
#define REAL_CODE_SEG 0x08
#define REAL_DATA_SEG 0x20

	.equ	CR0_PE,1

.text
.code32


#include "convert.h"

	.globl startup_32
startup_32:
	cld
	cli

	# Save the arguments safely out of the way
	movl	%eax, boot_type
	movl	%ebx, boot_data
	cmp	$0,%esp
	jz	1f
	movl	4(%esp), %eax
	movl	%eax, boot_param
1:

	movl stack_start, %esp

	# Clear eflags
	pushl $0
	popfl

	# Clear BSS
	xorl %eax,%eax
	movl $ _edata,%edi
	movl $ _end,%ecx
	subl %edi,%ecx
	cld
	rep
	stosb

	# Move the gdt where Linux will not smash it during decompression
	movl	$gdt, %esi
	movl	$GDTLOC, %edi
	movl	$(gdt_end - gdt), %ecx
	rep	movsb

	# Linux makes stupid assumptions about the segments
	# that are already setup, so setup a new gdt & ldt
	# and then reload the segment registers.

	lgdt	gdt_48
	lidt	idt_48

	# Load the data segment registers
	movl	$ PROT_DATA_SEG, %eax
	movl	%eax, %ds
	movl	%eax, %es
	movl	%eax, %fs
	movl	%eax, %gs
	movl    %eax, %ss

	pushl	$image_params	# image build time parameters as forth arg
	pushl	boot_param	# boot_param pointer as third arg
	pushl	boot_data	# boot data pointer as second arg
	pushl	boot_type	# boot data type as first argument
	call	convert_params

	movl	%eax, %esi	# put the real mode pointer in a safe place
	addl	$16, %esp	# pop the arguments


	# Setup the registers before jumping to linux


	# clear eflags
	pushl	$0
	popfl

	# Flag to indicate we are the bootstrap processor
	xorl	%ebx, %ebx

	movl    switch_64, %eax
	cmp	$1, %eax
	jz	switch_to_64

	# Clear the unspecified registers for good measure
	xorl	%eax, %eax
	xorl	%ecx, %ecx
	xorl	%edx, %edx
	xorl	%edi, %edi
	xorl	%ebp, %ebp

	# do not clear esp, we still need to use lret later

        pushl $PROT_CODE_SEG
        movl entry, %eax
        pushl %eax

	lret

switch_to_64:

	/* We need to switch to 64bit before use startup_64 entry go to kernel */
 /*
  * Prepare for entering 64 bit mode
  */
        # Move the gdt64 where Linux will not smash it during decompression
	movl	%esi, %eax # save the real mode pointer
	movl    $gdt64, %esi
	movl    $GDT64LOC, %edi
	movl    $(gdt64_end - gdt64), %ecx
	rep     movsb
	movl	%eax, %esi

	/* Load new GDT with the 64bit segments using 32bit descriptor */
	lgdt	gdt64

	/* Enable PAE mode */
	xorl    %eax, %eax
	btsl    $5, %eax
	movl    %eax, %cr4

 /*
  * Build early 4G boot pagetable
  */
       /* Initialize Page tables to 0*/
       movl    $PGTLOC, %edi
       xorl    %eax, %eax
       movl    $((4096*6)/4), %ecx
       rep     stosl

       /* Build Level 4 */
       movl    $(PGTLOC + 0), %edi
       leal    0x1007 (%edi), %eax
       movl    %eax, 0(%edi)

       /* Build Level 3 */
       movl    $(PGTLOC + 0x1000), %edi
       leal    0x1007(%edi), %eax
       movl    $4, %ecx
1:     movl    %eax, 0x00(%edi)
       addl    $0x00001000, %eax
       addl    $8, %edi
       decl    %ecx
       jnz     1b

       /* Build Level 2 */
       movl    $(PGTLOC + 0x2000), %edi
       movl    $0x00000183, %eax
       movl    $2048, %ecx
1:     movl    %eax, 0(%edi)
       addl    $0x00200000, %eax
       addl    $8, %edi
       decl    %ecx
       jnz     1b

       /* Enable the boot page tables */
       movl    $PGTLOC, %eax
       movl    %eax, %cr3

       /* Enable Long mode in EFER (Extended Feature Enable Register) */
       movl    $0xc0000080, %ecx
       rdmsr
       btsl    $8, %eax
       wrmsr

	/* Preparing for 64bit jmp */
        pushl $PROT_CODE_SEG
        movl entry, %eax
        pushl %eax

       /* Enter paged protected Mode, activating Long Mode */
        xorl    %eax, %eax
        btsl    $31, %eax
        btsl    $0, %eax
        movl    %eax, %cr0

        /*
         * At this point we're in long mode but in 32bit compatibility mode
         * with EFER.LME = 1, CS.L = 0, CS.D = 1 (and in turn
         * EFER.LMA = 1). Now we want to jump in 64bit mode, to do that we use
         * the new gdt/idt that has __KERNEL_CS with CS.L = 1.
         */

	lret


	/* Routines to query the BIOS... */
/**************************************************************************
E820_MEMSIZE - Get a listing of memory regions
**************************************************************************/
#define SMAP	0x534d4150
	.globl	meme820
meme820:
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%ebx
	pushl	%esi
	pushl	%edi
	movl	8(%ebp), %edi	/* Address to return e820 structures at */
	subl	$RELOC, %edi
	movl	12(%ebp), %esi	/* Maximum number of e820 structurs to return */
	pushl	%esi
	call	_prot_to_real
	.code16
	xorl	%ebx, %ebx
jmpe820:
	movl	$0xe820, %eax
	movl	$SMAP, %edx
	movl	$20, %ecx
	/* %di was setup earlier */
	int	$0x15
	jc	bail820

	cmpl	$SMAP, %eax
	jne	bail820

good820:
	/* If this is useable memory, we save it by simply advancing %di by
	 * sizeof(e820rec)
	 */
	decl	%esi
	testl	%esi,%esi
	jz	bail820

	addw	$20, %di
again820:
	cmpl	$0, %ebx	/* check to see if %ebx is set to EOF */
	jne	jmpe820

bail820:
	data32 call	_real_to_prot
	.code32
	popl	%eax
	subl	%esi, %eax	/* Compute how many structure we read */

	/* Restore everything else */
	popl	%edi
	popl	%esi
	popl	%ebx
	movl	%ebp, %esp
	popl	%ebp
	ret


/**************************************************************************
MEME801 - Determine size of extended memory
**************************************************************************/
	.globl meme801
meme801:
	pushl	%ebx
	pushl	%esi
	pushl	%edi
	call	_prot_to_real
	.code16

	stc					# fix to work around buggy
	xorw	%cx,%cx				# BIOSes which dont clear/set
	xorw	%dx,%dx				# carry on pass/error of
						# e801h memory size call
						# or merely pass cx,dx though
						# without changing them.
	movw	$0xe801,%ax
	int	$0x15
	jc	e801absent

	cmpw	$0x0, %cx			# Kludge to handle BIOSes
	jne	e801usecxdx			# which report their extended
	cmpw	$0x0, %dx			# memory in AX/BX rather than
	jne	e801usecxdx			# CX/DX.  The spec I have read
	movw	%ax, %cx			# seems to indicate AX/BX
	movw	%bx, %dx			# are more reasonable anyway...

e801usecxdx:
	andl	$0xffff, %edx			# clear sign extend
	shll	$6, %edx			# and go from 64k to 1k chunks
	movl	%edx, %eax			# store extended memory size
	andl	$0xffff, %ecx			# clear sign extend
 	addl	%ecx, %eax			# and add lower memory into

	jmp	e801out
e801absent:
	xorl	%eax,%eax

e801out:
	data32 call	_real_to_prot
	.code32
	/* Restore Everything */
	popl	%edi
	popl	%esi
	popl	%ebx
	ret

/**************************************************************************
MEM88 - Determine size of extended memory
**************************************************************************/
	.globl mem88
mem88:
	pushl	%ebx
	pushl	%esi
	pushl	%edi
	call	_prot_to_real
	.code16

	movb	$0x88, %ah
	int	$0x15
	andl	$0xffff, %eax

	data32 call	_real_to_prot
	.code32

	/* Restore Everything */
	popl	%edi
	popl	%esi
	popl	%ebx
	ret


/**************************************************************************
BASEMEMSIZE - Get size of the conventional (base) memory
**************************************************************************/
	.globl	basememsize
basememsize:
	call	_prot_to_real
	.code16
	int	$0x12
	movw	%ax,%cx
	DATA32 call	_real_to_prot
	.code32
	movw	%cx,%ax
	ret

/**************************************************************************
_REAL_TO_PROT - Go from REAL mode to Protected Mode
**************************************************************************/
	.globl	_real_to_prot
_real_to_prot:
	.code16
	cli
	cs
	addr32 lgdt	gdt_48 - RELOC
	movl	%cr0,%eax
	orl	$CR0_PE,%eax
	movl	%eax,%cr0		/* turn on protected mode */

	/* flush prefetch queue, and reload %cs:%eip */
	data32 ljmp	$PROT_CODE_SEG,$1f
1:
	.code32
	/* reload other segment registers */
	movl	$PROT_DATA_SEG,%eax
	movl	%eax,%ds
	movl	%eax,%es
	movl	%eax,%ss
	addl	$RELOC,%esp		/* Fix up stack pointer */
	xorl	%eax,%eax
	movl	%eax,%fs
	movl	%eax,%gs
	popl	%eax			/* Fix up return address */
	addl	$RELOC,%eax
	pushl	%eax

	/* switch to protected mode idt */
	cs
	lidt	idt_48
	ret

/**************************************************************************
_PROT_TO_REAL - Go from Protected Mode to REAL Mode
**************************************************************************/
	.globl	_prot_to_real
_prot_to_real:
	.code32
	popl	%eax
	subl	$RELOC,%eax		/* Adjust return address */
	pushl	%eax
	subl	$RELOC,%esp		/* Adjust stack pointer */
	ljmp	$REAL_CODE_SEG,$1f- RELOC	/* jump to a 16 bit segment */
1:
	.code16
	/* clear the PE bit of CR0 */
	movl	%cr0,%eax
	andl	$0!CR0_PE,%eax
	movl	%eax,%cr0

	/* make intersegment jmp to flush the processor pipeline
	 * and reload %cs:%eip (to clear upper 16 bits of %eip).
	 */
	data32 ljmp	$(RELOC)>>4,$2f- RELOC
2:
	/* we are in real mode now
	 * set up the real mode segment registers : %ds, $ss, %es
	 */
	movw	%cs,%ax
	movw	%ax,%ds
	movw	%ax,%es
	movw	%ax,%ss
	movw	%ax,%fs
	movw	%ax,%gs

	/* Switch to the real mode idt */
	cs
	addr32	lidt	idt_real - RELOC

	sti
	data32 ret	/* There is a 32 bit return address on the stack */
	.code32

boot_type:	.long 0
boot_data:	.long 0
boot_param:	.long 0

idt_real:
	.word	0x400				# idt limit = 256
	.word	0, 0
idt_48:
	.word	0				# idt limit = 0
	.word	0, 0				# idt base = 0L
gdt_48:
	.word	gdt_end - gdt - 1		# gdt limit=40,
						# (5 GDT entries)
	.long   GDTLOC				# gdt base

# Descriptor tables
# These need to be in a seperate section so I can be
# certain later activities dont stomp them
gdt:
	/* 0x00 */
	.word	0, 0, 0, 0			# dummy

	/* 0x08 */
	/* 16 bit real mode code segment */
	.word	0xffff,(RELOC&0xffff)
	.byte	(RELOC>>16),0x9b,0x00,(RELOC>>24)

	/* 0x10 */
	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9A00				# code read/exec
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	/* 0x18 */
	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9200				# data read/write
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	/* 0x20 */
	/* 16 bit real mode data segment */
	.word	0xffff,(RELOC&0xffff)
	.byte	(RELOC>>16),0x93,0x00,(RELOC>>24)

	/* For 2.5.x the kernel segments have moved */

	/* 0x28 dummy */
	.quad	0

	/* 0x30 dummy */
	.quad	0
	/* 0x38 dummy */
	.quad	0
	/* 0x40 dummy */
	.quad	0
	/* 0x48 dummy */
	.quad	0
	/* 0x50 dummy */
	.quad	0
	/* 0x58 dummy */
	.quad	0


	/* 0x60 */
	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9A00				# code read/exec
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	/* 0x68 */
	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9200				# data read/write
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

/*
 * The layout of the per-CPU GDT under Linux:
 *
 *   0 - null
 *   1 - reserved
 *   2 - reserved
 *   3 - reserved
 *
 *   4 - default user CS                <==== new cacheline
 *   5 - default user DS
 *
 *  ------- start of TLS (Thread-Local Storage) segments:
 *
 *   6 - TLS segment #1                 [ glibc's TLS segment ]
 *   7 - TLS segment #2                 [ Wine's %fs Win32 segment ]
 *   8 - TLS segment #3
 *   9 - reserved
 *  10 - reserved
 *  11 - reserved
 *
 *  ------- start of kernel segments:
 *
 *  12 - kernel code segment            <==== new cacheline
 *  13 - kernel data segment
 *  14 - TSS
 *  15 - LDT
 *  16 - PNPBIOS support (16->32 gate)
 *  17 - PNPBIOS support
 *  18 - PNPBIOS support
 *  19 - PNPBIOS support
 *  20 - PNPBIOS support
 *  21 - APM BIOS support
 *  22 - APM BIOS support
 *  23 - APM BIOS support
 */
gdt_end:

gdt64:
	.word	gdt64_end - gdt64
	.long	GDT64LOC
	.word	0
	.quad	0x0000000000000000	/* NULL descriptor */
	.quad	0x00af9a000000ffff	/* __KERNEL_CS */
	.quad	0x00cf92000000ffff	/* __KERNEL_DS */
gdt64_end:

	.section ".trailer", "a"
	/* Constants set at build time, these are at the very end of my image */
	.balign 16
	.global image_params
image_params:

convert_magic:
	.long	CONVERT_MAGIC
gdt_size:
	.long	gdt_end - gdt
gdt64_size:
	.long	gdt64_end - gdt64
pgt_size:
	.long   4096*6
bss_size:
	.long	bss_sizex
ramdisk_flags:
	.word	0
root_dev:
	.word	DEFAULT_ROOT_DEV
entry:
	.long	0
switch_64:
	.long	0
initrd_start:
	.long	0
initrd_size:
	.long	0
cmdline:
	.asciz "BOOT_IMAGE=head.S console=ttyS0 ip=dhcp root=/dev/nfs"
	.org cmdline + CMDLINE_MAX, 0
cmdline_end: