#include "callbacks.h"
	.equ CR0_PE, 1
	
	.text
	.arch i386
	.section ".prefix", "ax", @progbits

#undef CODE16
#if defined(PCBIOS)
#define	CODE16
#endif

/* We have two entry points: "conventional" (at the start of the file)
 * and "callback" (at _entry, 2 bytes in).  The "callback" entry
 * should be used if the caller wishes to provide a specific opcode.
 * It is equivalent to a call to in_call.  Using the "conventional"
 * entry point is equivalent to using the "callback" entry point with
 * an opcode of EB_OPCODE_MAIN.
 *
 * Both entry points can be called in either 16-bit real or 32-bit
 * protected mode with flat physical addresses.  We detect which mode
 * the processor is in and call either in_call or rm_in_call as
 * appropriate.  Note that the mode detection code must therefore be
 * capable of doing the same thing in either mode, even though the
 * machine code instructions will be interpreted differently.
 *
 * The decompressor will be invoked if necessary to decompress
 * Etherboot before attempting to jump to it.
 */

/******************************************************************************
 * Entry points and mode detection code
 ******************************************************************************
 */

	.code32
/* "Conventional" entry point: caller provides no opcode */
	.globl	_start
_start:
	/* Set flag to indicate conventional entry point used */
	pushw	$0			/* "pushw $0" in 16-bit code */
	/* Fall through to "callback" entry point */
	
/* "Callback" entry point */
	.globl	_entry
_entry:
	
#ifdef CODE16	  

	/* CPU mode detection code */
	pushl	%eax			/* "pushw %ax" in 16-bit code */
	pushw	%ax			/* "pushl %eax" in 16-bit code */
	movl	%cr0, %eax		/* Test protected mode bit */
	testb	$CR0_PE, %al
	popw	%ax			/* "popl %eax" in 16-bit code */
	popl	%eax			/* "popw %eax" in 16-bit code */
	jz	rmode
#endif /* CODE16 */

/******************************************************************************
 * Entered in protected mode
 ******************************************************************************
 */
		
	.code32
pmode:
	cmpl	$0, 0(%esp)		/* Conventional entry point used? */
	jne	1f
	/* Entered via conventional entry point: set up stack */
	xchgl	%eax, 4(%esp)		/* %eax = return addr, store %eax */
	movl	%eax, 0(%esp)		/* 0(%esp) = return address */
	movl	$(EB_OPCODE_MAIN|EB_USE_INTERNAL_STACK|EB_SKIP_OPCODE), %eax
	xchgl	%eax, 4(%esp)		/* 4(%esp) = opcode, restore %eax */
1:
	/* Run decompressor if necessary */
	pushl	%eax
	movl	$decompress, %eax
	testl	%eax, %eax
	jz	1f
	call	decompress
1:	popl	%eax

	/* Make in_call to Etherboot */
	jmp	_prefix_in_call

/******************************************************************************
 * Entered in real mode
 ******************************************************************************
 */
	
#ifdef CODE16
	.code16
rmode:

	pushw	%ax			/* Padding */
	pushw	%bp
	movw	%sp, %bp
	cmpw	$0, 6(%bp)		/* Conventional entry point used? */
	jne	1f
	/* Entered via conventional entry point: set up stack */
	
	pushw	%ax
	movw	6(%bp), %ax
	movw	%ax, 2(%bp)		/* Move return address down */
	movl	$(EB_OPCODE_MAIN|EB_USE_INTERNAL_STACK|EB_SKIP_OPCODE), 4(%bp)
	popw	%ax
	popw	%bp
	jmp	2f
1:	/* Entered via callback entry point: do nothing */
	popw	%bp
	popw	%ax
2:
	/* Preserve registers */
	pushw	%ds
	pushl	%eax
	
	/* Run decompressor if necessary.  Decompressor is 32-bit
	 * code, so we must switch to pmode first.  Save and restore
	 * GDT over transition to pmode.
	 */
	movl	$decompress, %eax
	testl	%eax, %eax
	jz	1f
	pushw	%ds
	pushw	%es
	pushw	%fs
	pushw	%gs
	subw	$8, %sp
	pushw	%bp
	movw	%sp, %bp
	sgdt	2(%bp)
	pushw	%ss			/* Store params for _prot_to_real */
	pushw	%cs
	call	_prefix_real_to_prot
	.code32
	call	decompress
	call	_prefix_prot_to_real
	.code16
	popw	%ax			/* skip */
	popw	%ax			/* skip */
	lgdt	2(%bp)
	popw	%bp
	addw	$8, %sp
	popw	%gs
	popw	%fs
	popw	%es
	popw	%ds
1:

	/* Set rm_etherboot_location */
	xorl	%eax, %eax
	movw	%cs, %ax
	movw	%ax, %ds
	shll	$4, %eax
	addl	$_prefix_size, %eax
	movl	%eax, _prefix_rm_etherboot_location

	/* Restore registers */
	popl	%eax
	popw	%ds

	/* Make real-mode in_call to Etherboot */
	jmp	_prefix_rm_in_call
#endif /* CODE16 */

/******************************************************************************
 * Utility routines that can be called by the "prefix".
 ******************************************************************************
 */

#ifdef CODE16

/* Prelocate code: either to an area at the top of free base memory.
 * Switch stacks to use the stack within the resulting
 * Etherboot image.
 *
 * On entry, %cs:0000 must be the start of the prefix: this is used to
 * locate the code to be copied.
 *
 * This routine takes a single word parameter: the number of bytes to
 * be transferred from the old stack to the new stack (excluding the
 * return address and this parameter itself, which will always be
 * copied).  If this value is negative, the stacks will not be
 * switched.
 *
 * Control will "return" to the appropriate point in the relocated
 * image.
 */

#define PRELOC_PRESERVE ( 20 )
#define PRELOC_OFFSET_RETADDR ( PRELOC_PRESERVE )
#define PRELOC_OFFSET_RETADDR_E ( PRELOC_OFFSET_RETADDR + 4 )
#define PRELOC_OFFSET_COPY ( PRELOC_OFFSET_RETADDR_E )
#define PRELOC_OFFSET_COPY_E ( PRELOC_OFFSET_COPY + 2 )

#define PRELOC_ALWAYS_COPY ( PRELOC_OFFSET_COPY_E )
	
	.code16
	.globl	prelocate
prelocate:
	/* Pad to allow for expansion of return address */
	pushw	%ax
	
	/* Preserve registers */
	pushaw
	pushw	%ds
	pushw	%es
	
	/* Claim an area of base memory from the BIOS and put the
	 * payload there.
	 */
	movw	$0x40, %bx
	movw	%bx, %es
	movw	%es:(0x13), %bx		/* FBMS in kb to %ax */
	shlw	$6, %bx			/* ... in paragraphs */
	subw	$_image_size_pgh, %bx	/* Subtract space for image */
	shrw	$6, %bx			/* Round down to nearest kb */
	movw	%bx, %es:(0x13)		/* ...and claim memory from BIOS */
	shlw	$6, %bx

	/* At this point %bx contains the segment address for the
	 * start of the image (image = prefix + runtime).
	 */

	/* Switch stacks */
	movw	%ss, %ax
	movw	%ax, %ds
	movw	%sp, %si		/* %ds:si = current %ss:sp */
	movw	%ss:PRELOC_OFFSET_COPY(%si), %cx
	testw	%cx, %cx
	js	1f
	leaw	_stack_offset_pgh(%bx), %ax /* %ax = new %ss */
	movw	%ax, %es
	movw	$_stack_size, %di
	addw	$PRELOC_ALWAYS_COPY, %cx
	subw	%cx, %di		/* %es:di = new %ss:sp */
	movw	%ax, %ss		/* Set new %ss:sp */
	movw	%di, %sp
	cld
	rep movsb			/* Copy stack contents */
1:	

	/* Do the image copy backwards, since if there's overlap with
	 * a forward copy then it means we're going to get trashed
	 * during the copy anyway...
	 */
	pushal				/* Preserve 32-bit registers */
	movw	%bx, %es		/* Destination base for copy */
	pushw	%cs
	popw	%ds			/* Source base for copy */
	movl	$_verbatim_size-1, %ecx	/* Offset to last byte */
	movl	%ecx, %esi
	movl	%ecx, %edi
	incl	%ecx			/* Length */
	std				/* Backwards copy of binary */
	ADDR32 rep movsb
	cld
	popal				/* Restore 32-bit registers */

	/* Store (%bx<<4) as image_basemem to be picked up by
	 * basemem.c.  Also store image_size, since there's no other
	 * way that we can later know how much memory we allocated.
	 * (_zfile_size is unavailable when rt2 is linked).
	 */
	pushl	%eax
	xorl	%eax, %eax
	movw	%bx, %ax
	shll	$4, %eax
	movl	%eax, %es:_prefix_image_basemem
	movl	$_image_size, %es:_prefix_image_basemem_size
	popl	%eax

	/* Expand original near return address into far return to new
	 * code location.
	 */
	movw	%sp, %bp
	xchgw	%bx, (PRELOC_OFFSET_RETADDR+2)(%bp)
	movw	%bx, (PRELOC_OFFSET_RETADDR+0)(%bp)

	/* Restore registers and return */
	popw	%es
	popw	%ds
	popaw
	lret				/* Jump to relocated code */

	/* Utility routine to free base memory allocated by prelocate.
	 * Ensure that said memory is not in use (e.g. for the CPU
	 * stack) before calling this routine.
	 */
	.globl deprelocate
deprelocate:	
	/* Claim an area of base memory from the BIOS and put the
	 * payload there.
	 */
	pushw	%ax
	pushw	%es
	movw	$0x40, %ax
	movw	%ax, %es
	movw	%es:(0x13), %ax		/* FBMS in kb to %ax */
	shlw	$6, %ax			/* ... in paragraphs */
	addw	$_image_size_pgh+0x40-1, %ax /* Add space for image and... */
	shrw	$6, %ax			/* ...round up to nearest kb */
	movw	%ax, %es:(0x13)		/* Give memory back to BIOS */
	popw	%es
	popw	%ax
	ret
	
#endif /* CODE16 */
