/*
 * kexec: Linux boots Linux
 *
 * Copyright (C) 2003,2004  Eric Biederman (ebiederm@xmission.com)
 *
 * This program 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 (version 2 of the License).
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

	.equ	PROT_CODE_SEG, pmcs - gdt
	.equ	REAL_CODE_SEG, rmcs - gdt
 	.equ	PROT_DATA_SEG, pmds - gdt
 	.equ	REAL_DATA_SEG, rmds - gdt
	.equ	CR0_PE, 1
	/* Gas thinks the .equs for these are non-absolute so use a define */
#define	PROT_CODE_SEG 0x08
#define REAL_CODE_SEG 0x18
#undef i386

	.text
	.arch	i386
	.globl _start
_start:
	.code32
	# Disable interrupts 
	cli
	
	# Save the initial registers 
	movl	%eax, orig_eax
	movl	%ebx, orig_ebx
	movl	%ecx, orig_ecx
	movl	%edx, orig_edx
	movl	%esi, orig_esi
	movl	%edi, orig_edi
	movl	%esp, orig_esp
	movl	%ebp, orig_ebp

	# Setup a stack 
	movl	$stack_end, %esp

	# Display a message to say everything is working so far 
	pushl	$s_hello
	call	print_string
	addl	$4, %esp

	# Save the idt and gdt 
	sidt	orig_idtp
	sgdt	orig_gdtp

	# Display the initial register contents 
	call	print_orig_regs

	pushl	$s_switching_descriptors
	call	print_string
	addl	$4, %esp

	# Load descriptor pointers 
	lgdt	gdtp
	lidt	idtp
	# Reload the data segments
	movl	$PROT_DATA_SEG, %eax
	movl	%eax, %ds
	movl	%eax, %es
	movl	%eax, %ss
	movl	%eax, %fs
	movl	%eax, %gs

	# Reload %cs 
	ljmp	$PROT_CODE_SEG, $_start.1
_start.1:

	pushl	$s_descriptors_changed
	call	print_string
	addl	$4, %esp

	call	setup_legacy_pic
	pushl	$s_legacy_pic_setup
	call	print_string
	addl	$4, %esp
	
	call	prot_to_real
	.code16

	callw	test16

	/* Return to 32bit mode */
	data32	call	real_to_prot
	.code32
	pushl	$s_in_protected_mode
	call	print_string
	addl	$4, %esp

	pushl	$s_halting
	call	print_string
	addl	$4, %esp
	jmp	halt
	

	/* Go from protected to real mode */
prot_to_real:
	.code32
	/* Load the 16bit idt */
	lidt	idtp_real
	
	popl	%eax
	subl	$RELOC, %eax		/* Adjust return address */
	pushl	%eax
	subl	$RELOC, %esp		/* Adjust stack pointer */
	ljmp	$REAL_CODE_SEG, $1f - RELOC
1:
	.code16
	/* Reload the segment registers to force a 16bit limit */
	movw	$REAL_DATA_SEG, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %ss
	movw	%ax, %fs
	movw	%ax, %gs
	
	/* 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 
	 */
	movw	%cs,%ax
	movw	%ax,%ds
	movw	%ax,%es
	movw	%ax,%ss
	movw	%ax,%fs
	movw	%ax,%gs
	data32	ret

real_to_prot:
	.code16
	pushl	%ebx

	/* Compute the address of gdtp */
	movw	%cs, %ax
	shlw	$4, %ax
	movl	$gdtp, %ebx
	subw	%ax, %bx
	
	data32 lgdt %cs:(%bx)
	movl	%cr0, %eax
	orl	$CR0_PE, %eax
	movl	%eax, %cr0

	/* 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
	movl	%eax, %fs
	movl	%eax, %gs

	popl	%ebx		/* Restore %ebx */
	
	addl	$RELOC, %esp	/* Fix up stack pointer */

	popl	%eax		/* Fix up return address */
	addl	$RELOC, %eax
	pushl	%eax

	lidt	idtp		/* Load a dummy idt */
	ret


halt:
	.code32
	hlt
	jmp	halt

print_orig_regs:
	.code32
	# Display the initial register contents 
	pushl	$s_eax
	call	print_string
	pushl	orig_eax
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	
	pushl	$s_ebx
	call	print_string
	pushl	orig_ebx
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	

	pushl	$s_ecx
	call	print_string
	pushl	orig_ecx
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	

	pushl	$s_edx
	call	print_string
	pushl	orig_edx
	call	print_hex
	pushl	$crlf
	call	print_string
	addl	$12, %esp
	

	pushl	$s_esi
	call	print_string
	pushl	orig_esi
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	
	pushl	$s_edi
	call	print_string
	pushl	orig_edi
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	

	pushl	$s_esp
	call	print_string
	pushl	orig_esp
	call	print_hex
	pushl	$space
	call	print_string
	addl	$12, %esp
	

	pushl	$s_ebp
	call	print_string
	pushl	orig_ebp
	call	print_hex
	pushl	$crlf
	call	print_string
	addl	$12, %esp

	# display the interrupt descritor table pointer 
	pushl	$s_idtp
	call	print_string
	movzwl	orig_idtp, %eax
	pushl	%eax
	call	print_hex
	pushl	$space
	call	print_string
	pushl	orig_idt_base
	call	print_hex
	pushl	$crlf
	call	print_string
	addl	$20, %esp

	# display the global descritor table pointer 
	pushl	$s_gdtp
	call	print_string
	movzwl	orig_gdtp, %eax
	pushl	%eax
	call	print_hex
	pushl	$space
	call	print_string
	pushl	orig_gdt_base
	call	print_hex
	pushl	$crlf
	call	print_string
	addl	$20, %esp

	ret


print_string:
	.code32
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%esi
	movl	8(%ebp), %esi
	xorl	%eax, %eax
print_string.1:	
	lodsb %ds:(%esi), %al
	testb	$0xff, %al
	jz	print_string.2
	call	print_char
	jmp	print_string.1
print_string.2:
	popl	%esi
	popl	%ebp
	ret


print_hex:
	.code32
	pushl	%ebp
	movl	%esp, %ebp
	movb	$32, %cl
print_hex.1:	
	movl	8(%ebp), %eax
	subb	$4, %cl
	shrl	%cl, %eax
	andb	$0x0f, %al
	cmpb	$9, %al
	ja	print_hex.2
	addb	$'0', %al
	jmp	print_hex.3
print_hex.2:	
	addb	$'A' - 10, %al
print_hex.3:
	pushl	%ecx
	call	print_char
	popl	%ecx
	testb	%cl, %cl
	jnz	print_hex.1

	popl	%ebp
	ret

print_char:
	.code32
	# The character to print is in al 
	call serial_print_char
	retl


#define TTYS0_BASE	0x3f8
#define TTYS0_RBR	(TTYS0_BASE + 0x00)
#define TTYS0_TBR	(TTYS0_BASE + 0x00)
#define TTYS0_LSR	(TTYS0_BASE + 0x05)
serial_print_char:
	.code32
	# The character to print is in al 
	pushl	%eax
	
	# Wait until the serial port is ready to receive characters 
serial_print_char.1:
	movl	$TTYS0_LSR, %edx
	inb	%dx, %al
	testb	$0x20, %al
	jz	serial_print_char.1

	# Output the character 
	movl	$TTYS0_TBR, %edx
	movb	0(%esp), %al
	outb	%al, %dx

	# Wait until the serial port has transmitted the character 
serial_print_char.2:
	movl	$TTYS0_LSR, %edx
	inb	%dx, %al
	testb	$0x40, %al
	jz	serial_print_char.2

	# Restore %eax 
	popl	%eax
	# Return to caller 
	ret

	.code32

idtp_real:
	.word	0x400				# idt limit = 256
	.word	0, 0
idtp:
	.word	0				# idt limit = 0
	.word	0, 0				# idt base = 0L

gdt:
gdtp:
	.word	gdt_end - gdt - 1		# gdt limit
	.long	gdt				# gdt base
	.word	0				# dummy

pmcs:
	# the 32 bit protected mode code segment 
	.word	0xffff,0
	.byte	0,0x9f,0xcf,0

pmds:
	# the 32 bit protected mode data segment 
	.word	0xffff,0
	.byte	0,0x93,0xcf,0

rmcs:
	# the 16 bit real mode code segment 
	.word	0xffff,(RELOC&0xffff)
	.byte	(RELOC>>16),0x9b,0x00,(RELOC>>24)

rmds:
	# the 16 bit real mode data segment 
	.word	0xffff,(RELOC&0xffff)
	.byte	(RELOC>>16),0x93,0x00,(RELOC>>24)
gdt_end:	
	
	
s_hello:
	.ascii	"kexec_test "
	.ascii VERSION
	.asciz " starting...\r\n"
s_switching_descriptors:
	.asciz	"Switching descriptors.\r\n"
s_descriptors_changed:
	.asciz	"Descriptors changed.\r\n"
s_legacy_pic_setup:
	.asciz	"Legacy pic setup.\r\n"
s_in_protected_mode:
	.asciz	"In protected mode.\r\n"
s_halting:
	.asciz	"Halting.\r\n"
	

space:	.asciz	" "
crlf:	.asciz	"\r\n"
s_eax:	.asciz	"eax: "
s_ebx:	.asciz	"ebx: "
s_ecx:	.asciz	"ecx: "
s_edx:	.asciz	"edx: "
s_esi:	.asciz	"esi: "
s_edi:	.asciz	"edi: "
s_esp:	.asciz	"esp: "
s_ebp:	.asciz	"ebp: "
	

s_idtp:	.asciz	"idt: "
s_gdtp:	.asciz	"gdt: "

#include "x86-setup-legacy-pic.S"

	.bss
	.balign 4096
stack:
	.skip 4096
stack_end:	
	
	.bss
	.balign 4	
orig_eax:	.long 0
orig_ebx:	.long 0
orig_ecx:	.long 0
orig_edx:	.long 0
orig_esi:	.long 0
orig_edi:	.long 0
orig_esp:	.long 0
orig_ebp:	.long 0

	.balign 4
orig_idtp:	.short 0
orig_idt_base:	.long  0
orig_gdtp:	.short 0
orig_gdt_base:	.long  0

