/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#if defined(__lint)

int silence_lint = 0;

#else

#include <sys/segments.h>
#include <sys/controlregs.h>

/*
 * Do a call into BIOS.  This goes down to 16 bit real mode and back again.
 */

/*
 * instruction prefix to change operand size in instruction
 */
#define DATASZ	.byte 0x66;

#if defined(__amd64)
#define	MOVCR(x, y)	movq  x,%rax; movq  %rax, y
#define LOAD_XAX(sym)	leaq	sym, %rax
#elif defined(__i386)
#define	MOVCR(x, y)	movl  x,%eax; movl  %eax, y
#define LOAD_XAX(sym)	leal	sym, %eax
#endif

	.globl	_start
_start:

#if defined(__i386)

	/*
	 * Save caller registers
	 */
	movl	%ebp, save_ebp
	movl	%esp, save_esp
	movl	%ebx, save_ebx
	movl	%esi, save_esi
	movl	%edi, save_edi

	/* get registers argument into esi */
	movl	8(%esp), %esi

	/* put interrupt number in %bl */
	movl	4(%esp), %ebx

	/* Switch to a low memory stack */
	movl	$_start, %esp

	/* allocate space for args on stack */
	subl	$18, %esp
	movl	%esp, %edi

#elif defined(__amd64)

	/*
	 * Save caller registers
	 */
	movq	%rbp, save_rbp
	movq	%rsp, save_rsp
	movq	%rbx, save_rbx
	movq	%rsi, save_rsi
	movq	%r12, save_r12
	movq	%r13, save_r13
	movq	%r14, save_r14
	movq	%r15, save_r15

	/* Switch to a low memory stack */
	movq	$_start, %rsp

	/* put interrupt number in %bl */
	movq	%rdi, %rbx

	/* allocate space for args on stack */
	subq	$18, %rsp
	movq	%rsp, %rdi

#endif

	/* copy args from high memory to stack in low memory */
	cld
	movl	$18, %ecx
	rep
	movsb

	/*
	 * Save system registers
	 */
	sidt	save_idt
	sgdt	save_gdt
	str	save_tr
	movw	%cs, save_cs
	movw	%ds, save_ds
	movw	%ss, save_ss
	movw	%es, save_es
	movw	%fs, save_fs
	movw	%gs, save_gs
	MOVCR(	%cr4, save_cr4)
	MOVCR(	%cr3, save_cr3)
	MOVCR(	%cr0, save_cr0)

#if defined(__amd64)
	/*
	 * save/clear the extension parts of the fs/gs base registers and cr8
	 */
	movl	$MSR_AMD_FSBASE, %ecx
	rdmsr
	movl	%eax, save_fsbase
	movl	%edx, save_fsbase + 4
	xorl	%eax, %eax
	xorl	%edx, %edx
	wrmsr

	movl	$MSR_AMD_GSBASE, %ecx
	rdmsr
	movl	%eax, save_gsbase
	movl	%edx, save_gsbase + 4
	xorl	%eax, %eax
	xorl	%edx, %edx
	wrmsr

	movl	$MSR_AMD_KGSBASE, %ecx
	rdmsr
	movl	%eax, save_kgsbase
	movl	%edx, save_kgsbase + 4
	xorl	%eax, %eax
	xorl	%edx, %edx
	wrmsr

	movq	%cr8, %rax
	movq	%rax, save_cr8
#endif

	/*
	 * set offsets in 16 bit ljmp instructions below
	 */
	LOAD_XAX(enter_real)
	movw	%ax, enter_real_ljmp

	LOAD_XAX(enter_protected)
	movw	%ax, enter_protected_ljmp

	LOAD_XAX(gdt_info)
	movw	%ax, gdt_info_load

	/*
	 * insert BIOS interrupt number into later instruction
	 */
	movb    %bl, int_instr+1
	jmp     1f
1:

	/*
	 * zero out all the registers to make sure they're 16 bit clean
	 */
#if defined(__amd64)
	xorq	%r8, %r8
	xorq	%r9, %r9
	xorq	%r10, %r10
	xorq	%r11, %r11
	xorq	%r12, %r12
	xorq	%r13, %r13
	xorq	%r14, %r14
	xorq	%r15, %r15
#endif
	xorl	%eax, %eax
	xorl	%ebx, %ebx
	xorl	%ecx, %ecx
	xorl	%edx, %edx
	xorl	%ebp, %ebp
	xorl	%esi, %esi
	xorl	%edi, %edi

	/*
	 * Load our own GDT/IDT
	 */
	lgdt	gdt_info
	lidt	idt_info

#if defined(__amd64)
	/*
	 * Shut down 64 bit mode. First get into compatiblity mode.
	 */
	movq	%rsp, %rax
	pushq	$B32DATA_SEL
	pushq	%rax
	pushf
	pushq	$B32CODE_SEL
	pushq	$1f
	iretq
1:
	.code32

	/*
	 * disable long mode by:
	 * - shutting down paging (bit 31 of cr0)
	 * - flushing the TLB
	 * - disabling LME (long made enable) in EFER (extended feature reg)
	 */
	movl	%cr0, %eax
	btcl	$31, %eax		/* disable paging */
	movl	%eax, %cr0
	ljmp	$B32CODE_SEL, $1f
1:

	xorl	%eax, %eax
	movl	%eax, %cr3		/* flushes TLB */

	movl	$MSR_AMD_EFER, %ecx	/* Extended Feature Enable */
	rdmsr
	btcl	$8, %eax		/* bit 8 Long Mode Enable bit */
	wrmsr
#endif

	/*
	 * ok.. now enter 16 bit mode, so we can shut down protected mode
	 *
	 * We'll have to act like we're still in a 32 bit section.
	 * So the code from this point has DATASZ in front of it to get 32 bit
	 * operands. If DATASZ is missing the operands will be 16 bit.
	 *
	 * Now shut down paging and protected (ie. segmentation) modes.
	 */
	ljmp	$B16CODE_SEL, $enter_16_bit
enter_16_bit:

	/*
	 * Make sure hidden parts of segment registers are 16 bit clean
	 */
	DATASZ	movl	$B16DATA_SEL, %eax
		movw    %ax, %ss
		movw    %ax, %ds
		movw    %ax, %es
		movw    %ax, %fs
		movw    %ax, %gs


	DATASZ	movl	$0x0, %eax	/* put us in real mode */
	DATASZ	movl	%eax, %cr0
	.byte	0xea			/* ljmp */
enter_real_ljmp:
	.value	0			/* addr (16 bit) */
	.value	0x0			/* value for %cs */
enter_real:

	/*
	 * zero out the remaining segment registers
	 */
	DATASZ	xorl	%eax, %eax
		movw    %ax, %ss
		movw    %ax, %ds
		movw    %ax, %es
		movw    %ax, %fs
		movw    %ax, %gs

	/*
	 * load the arguments to the BIOS call from the stack
	 */
	popl	%eax	/* really executes a 16 bit pop */
	popl	%ebx
	popl	%ecx
	popl	%edx
	popl	%esi
	popl	%edi
	popl	%ebp
	pop	%es
	pop	%ds

	/*
	 * do the actual BIOS call
	 */
	sti
int_instr:
	int	$0x10		/* this int number is overwritten */
	cli			/* ensure interrupts remain disabled */

	/*
	 * save results of the BIOS call
	 */
	pushf
	push	%ds
	push	%es
	pushl	%ebp		/* still executes as 16 bit */
	pushl	%edi
	pushl	%esi
	pushl	%edx
	pushl	%ecx
	pushl	%ebx
	pushl	%eax

	/*
	 * Restore protected mode and 32 bit execution
	 */
	push	$0			/* make sure %ds is zero before lgdt */
	pop	%ds
	.byte	0x0f, 0x01, 0x16	/* lgdt */
gdt_info_load:
	.value	0	/* temp GDT in currently addressible mem */

	DATASZ	movl	$0x1, %eax
	DATASZ	movl	%eax, %cr0

	.byte	0xea			/* ljmp */
enter_protected_ljmp:
	.value	0			/* addr (still in 16 bit) */
	.value	B32CODE_SEL		/* %cs value */
enter_protected:

	/*
	 * We are now back in a 32 bit code section, fix data/stack segments
	 */
	.code32
	movw	$B32DATA_SEL, %ax
	movw	%ax, %ds
	movw	%ax, %ss

	/*
	 * Re-enable paging. Note we only use 32 bit mov's to restore these
	 * control registers. That's OK as the upper 32 bits are always zero.
	 */
	movl	save_cr4, %eax
	movl	%eax, %cr4
	movl	save_cr3, %eax
	movl	%eax, %cr3

#if defined(__amd64)
	/*
	 * re-enable long mode
	 */
	movl	$MSR_AMD_EFER, %ecx
	rdmsr
	btsl	$8, %eax
	wrmsr
#endif

	movl	save_cr0, %eax
	movl	%eax, %cr0
	jmp	enter_paging
enter_paging:


#if defined(__amd64)
	/*
	 * transition back to 64 bit mode
	 */
	pushl	$B64CODE_SEL
	pushl	$longmode
	lret
longmode:
	.code64
#endif
	/*
	 * restore caller frame pointer and segment registers
	 */
	lgdt	save_gdt
	lidt	save_idt

	/*
	 * Before loading the task register we need to reset the busy bit
	 * in its corresponding GDT selector. The busy bit is the 2nd bit in
	 * the 5th byte of the selector.
	 */
#if defined(__i386)
	movzwl	save_tr, %eax
	addl	save_gdt+2, %eax
	btcl	$1, 5(%eax)
#elif defined(__amd64)
	movzwq	save_tr, %rax
	addq	save_gdt+2, %rax
	btcl	$1, 5(%rax)
#endif
	ltr	save_tr
	movw	save_ds, %ds
	movw	save_ss, %ss
	movw	save_es, %es
	movw	save_fs, %fs
	movw	save_gs, %gs

#if defined(__i386)
	pushl	save_cs
	pushl	$.newcs
	lret
#elif defined(__amd64)
	pushq	save_cs
	pushq	$.newcs
	lretq
#endif
.newcs:

#if defined(__amd64)
	/*
	 * restore the hidden kernel segment base register values
	 */
	movl	save_fsbase, %eax
	movl	save_fsbase + 4, %edx
	movl	$MSR_AMD_FSBASE, %ecx
	wrmsr

	movl	save_gsbase, %eax
	movl	save_gsbase + 4, %edx
	movl	$MSR_AMD_GSBASE, %ecx
	wrmsr

	movl	save_kgsbase, %eax
	movl	save_kgsbase + 4, %edx
	movl	$MSR_AMD_KGSBASE, %ecx
	wrmsr

	movq	save_cr8, %rax
	cmpq	$0, %rax
	je	1f
	movq	%rax, %cr8
1:
#endif

	/*
	 * copy results to caller's location, then restore remaining registers
	 */
#if defined(__i386)
	movl    save_esp, %edi
	movl	8(%edi), %edi
	movl	%esp, %esi
	movl	$18, %ecx
	rep
	movsb
	movw	18(%esp), %ax
	andl	$0xffff, %eax
	movl    save_ebx, %ebx
	movl    save_esi, %esi
	movl    save_edi, %edi
	movl    save_esp, %esp
	movl    save_ebp, %ebp
	movl    save_esp, %esp
	ret

#elif defined(__amd64)
	movq    save_rsi, %rdi
	movq	%rsp, %rsi
	movq	$18, %rcx
	rep
	movsb
	movw	18(%rsp), %ax
	andq	$0xffff, %rax
	movq    save_r12, %r12
	movq    save_r13, %r13
	movq    save_r14, %r14
	movq    save_r15, %r15
	movq    save_rbx, %rbx
	movq    save_rbp, %rbp
	movq    save_rsp, %rsp
	ret

#endif


/*
 * Caller's registers to restore
 */
	.align 4
save_esi:
	.long	0
save_edi:
	.long	0
save_ebx:
	.long	0
save_ebp:
	.long	0
save_esp:
	.long	0

	.align 8
#if defined(__amd64)
save_rsi:
	.quad	0
save_rbx:
	.quad	0
save_rbp:
	.quad	0
save_rsp:
	.quad	0
save_r12:
	.quad	0
save_r13:
	.quad	0
save_r14:
	.quad	0
save_r15:
	.quad	0
save_kgsbase:
	.quad	0
save_gsbase:
	.quad	0
save_fsbase:
	.quad	0
save_cr8:
	.quad	0
#endif	/* __amd64 */

save_idt:
	.quad	0
	.quad	0

save_gdt:
	.quad	0
	.quad	0

save_cr0:
	.quad	0
save_cr3:
	.quad	0
save_cr4:
	.quad	0
save_cs:
	.quad	0
save_ss:
	.value	0
save_ds:
	.value	0
save_es:
	.value	0
save_fs:
	.value	0
save_gs:
	.value	0
save_tr:
	.value	0

idt_info:
	.value 0x3ff
	.quad 0


/*
 * We need to trampoline thru a gdt we have in low memory.
 */
#include "../boot/boot_gdt.s"
#endif /* __lint */
