/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2008 rPath, Inc. - All Rights Reserved
 *
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software and associated documentation
 *   files (the "Software"), to deal in the Software without
 *   restriction, including without limitation the rights to use,
 *   copy, modify, merge, publish, distribute, sublicense, and/or
 *   sell copies of the Software, and to permit persons to whom
 *   the Software is furnished to do so, subject to the following
 *   conditions:
 *
 *   The above copyright notice and this permission notice shall
 *   be included in all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *   OTHER DEALINGS IN THE SOFTWARE.
 *
 * ----------------------------------------------------------------------- */

/*
 * Relocation stub start.  This needs to be position-independent code.
 */

CS32	= 0x10
DS32	= 0x18
CS16	= 0x20
DS16	= 0x28

		/* We will be entered in flat 32-bit protected mode... */
		.section ".start","ax",@progbits
		.globl	_start
		.code32
_start:
		popl	%edx		/* return address */
		pushl	%cs
		pushl	%edx
		pushfl
		cli
		cld
		pushl	%ds
		pushl	%es
		pushl	%fs
		pushl	%gs
		pushl	%ebx
		pushl	%ebp
		pushl	%esi
		pushl	%edi

		calll	1f
1:		popl	%ebx		/* Figure out where we are */
		subl	$1b, %ebx	/* %ebx -> offset */

		movl	%esp, orig_esp(%ebx)
		movw	%ss, orig_ss(%ebx)

		sgdtl	orig_gdt(%ebx)	/* Save current GDT */
		sidtl	orig_idt(%ebx)	/* Save current IDT */

		addl	%ebx, my_gdt+2(%ebx)
		lidtl	rm_idt(%ebx)
		lgdtl	my_gdt(%ebx)

		/* Reinitialize segments */
		movw	$DS32,%dx
		movw	%dx,%ss
		leal	stack_end(%ebx),%esp
		movw	%dx,%ds
		movw	%dx,%es
		movw	%dx,%fs
		movw	%dx,%gs

		leal	2f(%ebx),%edx
		pushl	$CS32
		pushl	%edx
		lretl
2:
		/* Adjust the real-mode segment bases */
		addl	%ebx, my_gdt+CS16+2(%ebx)
		addl	%ebx, my_gdt+DS16+2(%ebx)
		addl	%ebx, .L_intcall_rm_ret_jmp+2(%ebx)

		/* Equivalent real-mode segment */
		movl	%ebx, %eax
		shrl	$4, %eax
		movw	%ax, .L_intcall_rm_jmp+3(%ebx)

		/* Zero the .bss - stack must be empty here! */
		leal	__bss_start(%ebx), %edi
		leal	_end(%ebx), %ecx
		subl	%edi, %ecx
		shrl	$2, %ecx
		xorl	%eax, %eax
		rep; stosl

		calll	main

		/* If main() returns, try to return to the original
		   protected-mode environment.  %eax is live here
		   and contains the exit code. */

		/* Don't make this global without regenerating %ebx! */
_exit:
		lidtl	orig_idt(%ebx)
		lgdtl	orig_gdt(%ebx)
		lssl	orig_esp(%ebx),%esp
		popl	%edi
		popl	%esi
		popl	%ebp
		popl	%ebx
		popl	%gs
		popl	%fs
		popl	%es
		popl	%ds
		popfl
		lretl

		/* Call a real-mode interrupt from 32-bit protected mode
		 *
		 * %eax -> interrupt number,
		 * %edx -> input register image,
		 * %ecx -> output register image
		 *
		 */
		.text
		.globl	intcall
intcall:	pushl	%ebx
		pushl	%esi
		pushl	%edi
		pushl	%ebp
		pushl	%ecx

		calll	1f
1:		popl	%ebx
		subl	$1b,%ebx

		movl	%edx,%esi
		leal	rm_stack_end-(44+12)(%ebx),%edi
		movl	$11,%ecx
		rep; movsl

		movl	(,%eax,4),%eax	/* cs:ip */
		stosl
		movw	$intcall_rm_return,(%edi)
		movl	%ebx,%eax
		shrl	$4,%eax		/* real mode segment */
		movw	%ax,2(%edi)
		movw	$2,4(%edi)	/* iret return flags */

		pushl	%ebx
		leal	intcall_pm_return(%ebx),%ecx
		movl	%esp,pm_stack(%ebx)

		movw	$DS16,%dx
		ljmpw	$CS16, $intcall_rm

		.section ".text16","ax",@progbits
		.code16
intcall_rm:
		movw	%dx,%ss
		movl	$rm_stack_end-(44+12),%esp
		movw	%dx,%ds
		movw	%dx,%es
		movw	%dx,%fs
		movw	%dx,%gs

		movl	%cr0,%edx
		andb	$~1,%dl
		movl	%edx,%cr0
.L_intcall_rm_jmp:
		.byte	0xea		/* ljmpw */
		.word	1f		/* offset */
		.word	0		/* segment */
1:
		movw	%ax,%ss
		popw	%gs
		popw	%fs
		popw	%es
		popw	%ds
		popal
		popfl
		lretw

intcall_rm_return:
		movw	$rm_stack_end,%sp
		pushfl
		pushal
		pushw	%ds
		pushw	%es
		pushw	%fs
		pushw	%gs
		cli
		cld
		call	enable_a20	/* The BIOS might have disabled A20 */
		movw	$DS32,%dx
		movl	%cs:pm_stack,%esp
		movl	%cr0,%eax
		orb	$1,%al
		movl	%eax,%cr0
.L_intcall_rm_ret_jmp:
		.byte	0x66, 0xea	/* ljmpl */
		.long	intcall_pm_return
		.short	CS32

		.text
		.code32
intcall_pm_return:
		movw	%dx,%ss
		movw	%dx,%ds
		movw	%dx,%es
		movw	%dx,%fs
		movw	%dx,%gs

		popl	%ebx
		popl	%edi
		andl	%edi,%edi
		jz	1f
		leal	rm_stack_end-44(%ebx),%esi
		movl	$11,%ecx
		rep; movsl
1:
		popl	%ebp
		popl	%edi
		popl	%esi
		popl	%ebx
		retl

/*
 * Code to invoke the Linux kernel
 *
 * %eax: setup segment address
 * %edx: kernel initial (e)sp
 */
		.globl	jump_to_kernel
jump_to_kernel:
		calll	1f
1:		popl	%ebx
		subl	$1b,%ebx
		movw	$DS16, %si
		movw	%si,%ss
		movl	$rm_stack_end, %esp
		movw	%si,%ds
		movw	%si,%es
		movw	%si,%fs
		movw	%si,%gs
		shrl	$4,%ebx		/* %bx <- our real mode segment */
		ljmpw	$CS16, $jump_to_kernel_rm

		.section ".text16","ax",@progbits
		.code16
jump_to_kernel_rm:
		pushw	%bx
		pushw	$1f
		movl	%cr0,%ecx
		andb	$~1,%cl
		movl	%ecx,%cr0
		lretw
1:
		movw	%ax,%ss
		movl	%edx,%esp
		movw	%ax,%ds
		movw	%ax,%es
		movw	%ax,%fs
		movw	%ax,%gs
		addw	$0x20,%ax
		pushw	%ax
		pushw	$0
		lretw


		.section ".rodata","a",@progbits
		.code32
		.balign 4
rm_idt:		.short	256*4-1
		.long	0
		.short	0

		.section ".data","aw",@progbits
		/* Not really initialized, but can't be in .bss */
orig_esp:	.long	0
orig_ss:	.short	0	/* Must follow orig_esp */
orig_gdt:	.short	0, 0, 0
orig_idt:	.short	0, 0, 0

		.balign	16
my_gdt:		.short	my_gdt_end-my_gdt-1
		.long	my_gdt
		.short	0
	/* Unused */
		.long	0, 0

	/* CS32 */
		.long	0x0000ffff
		.long	0x00cf9b00
	/* DS32 */
		.long	0x0000ffff
		.long	0x00cf9300
	/* CS16 */
		.long	0x0000ffff
		.long	0x00009b00
	/* DS16 */
		.long	0x0000ffff
		.long	0x00009300
my_gdt_end:

		.section ".bss","aw",@nobits
		.balign 4
pm_stack:	.space 4
kernel_entry:	.space 4
rm_stack:	.space	1024
rm_stack_end:
stack:		.space	1024
stack_end:
