/*
 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * 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; either version 2 of the
 * License, or any later version.
 *
 * 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.
 *
 */

	.arch i386
	.section ".prefix.lib", "awx", @progbits
	.section ".data16", "aw", @progbits

/**
 * High memory temporary load address
 *
 * Temporary buffer into which to copy (or decompress) our runtime
 * image, prior to calling get_memmap() and relocate().  We don't
 * actually leave anything here once install() has returned.
 *
 * We use the start of an even megabyte so that we don't have to worry
 * about the current state of the A20 line.
 *
 * We use 4MB rather than 2MB because some PXE stack / PMM BIOS
 * combinations are known to place data required by other UNDI ROMs
 * loader around the 2MB mark.
 */
	.globl	HIGHMEM_LOADPOINT
	.equ	HIGHMEM_LOADPOINT, ( 4 << 20 )

/* Image compression enabled */
#define COMPRESS 1

#define CR0_PE 1

/*****************************************************************************
 * Utility function: print character (with LF -> LF,CR translation)
 *
 * Parameters:
 *   %al : character to print
 *   %ds:di : output buffer (or %di=0 to print to console)
 * Returns:
 *   %ds:di : next character in output buffer (if applicable)
 *****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl	print_character
print_character:
	/* Preserve registers */
	pushw	%ax
	pushw	%bx
	pushw	%bp
	/* If %di is non-zero, write character to buffer and exit */
	testw	%di, %di
	jz	1f
	movb	%al, %ds:(%di)
	incw	%di
	jmp	3f
1:	/* Print character */
	movw	$0x0007, %bx		/* page 0, attribute 7 (normal) */
	movb	$0x0e, %ah		/* write char, tty mode */
	cmpb	$0x0a, %al		/* '\n'? */
	jne	2f
	int	$0x10
	movb	$0x0d, %al
2:	int	$0x10
	/* Restore registers and return */
3:	popw	%bp
	popw	%bx
	popw	%ax
	ret
	.size	print_character, . - print_character

/*****************************************************************************
 * Utility function: print a NUL-terminated string
 *
 * Parameters:
 *   %ds:si : string to print
 *   %ds:di : output buffer (or %di=0 to print to console)
 * Returns:
 *   %ds:si : character after terminating NUL
 *   %ds:di : next character in output buffer (if applicable)
 *****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl	print_message
print_message:
	/* Preserve registers */
	pushw	%ax
	/* Print string */
1: 	lodsb
	testb	%al, %al
	je	2f
	call	print_character
	jmp	1b
2:	/* Restore registers and return */
	popw	%ax
	ret
	.size	print_message, . - print_message

/*****************************************************************************
 * Utility functions: print hex digit/byte/word/dword
 *
 * Parameters:
 *   %al (low nibble) : digit to print
 *   %al : byte to print
 *   %ax : word to print
 *   %eax : dword to print
 *   %ds:di : output buffer (or %di=0 to print to console)
 * Returns:
 *   %ds:di : next character in output buffer (if applicable)
 *****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl	print_hex_dword
print_hex_dword:
	rorl	$16, %eax
	call	print_hex_word
	rorl	$16, %eax
	/* Fall through */
	.size	print_hex_dword, . - print_hex_dword
	.globl	print_hex_word
print_hex_word:
	xchgb	%al, %ah
	call	print_hex_byte
	xchgb	%al, %ah
	/* Fall through */
	.size	print_hex_word, . - print_hex_word
	.globl	print_hex_byte
print_hex_byte:
	rorb	$4, %al
	call	print_hex_nibble
	rorb	$4, %al
	/* Fall through */
	.size	print_hex_byte, . - print_hex_byte
	.globl	print_hex_nibble
print_hex_nibble:
	/* Preserve registers */
	pushw	%ax
	/* Print digit (technique by Norbert Juffa <norbert.juffa@amd.com> */
	andb	$0x0f, %al
	cmpb	$10, %al
	sbbb	$0x69, %al
	das
	call	print_character
	/* Restore registers and return */
	popw	%ax
	ret
	.size	print_hex_nibble, . - print_hex_nibble

/*****************************************************************************
 * Utility function: print PCI bus:dev.fn
 *
 * Parameters:
 *   %ax : PCI bus:dev.fn to print
 *   %ds:di : output buffer (or %di=0 to print to console)
 * Returns:
 *   %ds:di : next character in output buffer (if applicable)
 *****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl	print_pci_busdevfn
print_pci_busdevfn:
	/* Preserve registers */
	pushw	%ax
	/* Print bus */
	xchgb	%al, %ah
	call	print_hex_byte
	/* Print ":" */
	movb	$':', %al
	call	print_character
	/* Print device */
	movb	%ah, %al
	shrb	$3, %al
	call	print_hex_byte
	/* Print "." */
	movb	$'.', %al
	call	print_character
	/* Print function */
	movb	%ah, %al
	andb	$0x07, %al
	call	print_hex_nibble
	/* Restore registers and return */
	popw	%ax
	ret
	.size	print_pci_busdevfn, . - print_pci_busdevfn

/****************************************************************************
 * pm_call (real-mode near call)
 *
 * Call routine in 16-bit protected mode for access to extended memory
 *
 * Parameters:
 *   %ax : address of routine to call in 16-bit protected mode
 * Returns:
 *   none
 * Corrupts:
 *   %ax
 *
 * The specified routine is called in 16-bit protected mode, with:
 *
 *   %cs : 16-bit code segment with base matching real-mode %cs
 *   %ss : 16-bit data segment with base matching real-mode %ss
 *   %ds,%es,%fs,%gs : 32-bit data segment with zero base and 4GB limit
 *
 ****************************************************************************
 */

#ifndef KEEP_IT_REAL

	/* GDT for protected-mode calls */
	.section ".prefix.lib"
	.align 16
pm_call_vars:
gdt:
gdt_limit:		.word gdt_length - 1
gdt_base:		.long 0
			.word 0 /* padding */
pm_cs:		/* 16-bit protected-mode code segment */	
	.equ    PM_CS, pm_cs - gdt
	.word   0xffff, 0
	.byte   0, 0x9b, 0x00, 0
pm_ss:		/* 16-bit protected-mode stack segment */
	.equ    PM_SS, pm_ss - gdt
	.word   0xffff, 0
	.byte   0, 0x93, 0x00, 0
pm_ds:		/* 32-bit protected-mode flat data segment */
	.equ    PM_DS, pm_ds - gdt
	.word   0xffff, 0
	.byte   0, 0x93, 0xcf, 0
gdt_end:
	.equ	gdt_length, . - gdt
	.size	gdt, . - gdt

	.section ".prefix.lib"
	.align 16
pm_saved_gdt:	
	.long	0, 0
	.size	pm_saved_gdt, . - pm_saved_gdt

	.equ	pm_call_vars_size, . - pm_call_vars
#define PM_CALL_VAR(x) ( -pm_call_vars_size + ( (x) - pm_call_vars ) )

	.section ".prefix.lib"
	.code16
pm_call:
	/* Preserve registers, flags, and RM return point */
	pushw	%bp
	movw	%sp, %bp
	subw	$pm_call_vars_size, %sp
	andw	$0xfff0, %sp
	pushfl
	pushw	%gs
	pushw	%fs
	pushw	%es
	pushw	%ds
	pushw	%ss
	pushw	%cs
	pushw	$99f

	/* Set up local variable block, and preserve GDT */
	pushw	%cx
	pushw	%si
	pushw	%di
	pushw	%ss
	popw	%es
	movw	$pm_call_vars, %si
	leaw	PM_CALL_VAR(pm_call_vars)(%bp), %di
	movw	$pm_call_vars_size, %cx
	cs rep movsb
	popw	%di
	popw	%si
	popw	%cx
	sgdt	PM_CALL_VAR(pm_saved_gdt)(%bp)

	/* Set up GDT bases */
	pushl	%eax
	pushl	%edi
	xorl	%eax, %eax
	movw	%ss, %ax
	shll	$4, %eax
	movzwl	%bp, %edi
	leal	PM_CALL_VAR(gdt)(%eax, %edi), %eax
	movl	%eax, PM_CALL_VAR(gdt_base)(%bp)
	movw	%cs, %ax
	movw	$PM_CALL_VAR(pm_cs), %di
	call	set_seg_base
	movw	%ss, %ax
	movw	$PM_CALL_VAR(pm_ss), %di
	call	set_seg_base
	popl	%edi
	popl	%eax

	/* Switch CPU to protected mode and load up segment registers */
	pushl	%eax
	cli
	lgdt	PM_CALL_VAR(gdt)(%bp)
	movl	%cr0, %eax
	orb	$CR0_PE, %al
	movl	%eax, %cr0
	ljmp	$PM_CS, $1f
1:	movw	$PM_SS, %ax
	movw	%ax, %ss
	movw	$PM_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs
	popl	%eax

	/* Call PM routine */
	call	*%ax

	/* Set real-mode segment limits on %ds, %es, %fs and %gs */
	movw	%ss, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs

	/* Return CPU to real mode */
	movl	%cr0, %eax
	andb	$0!CR0_PE, %al
	movl	%eax, %cr0

	/* Restore registers and flags */
	lret	/* will ljmp to 99f */
99:	popw	%ss
	popw	%ds
	popw	%es
	popw	%fs
	popw	%gs
	lgdt	PM_CALL_VAR(pm_saved_gdt)(%bp)
	popfl
	movw	%bp, %sp
	popw	%bp
	ret
	.size pm_call, . - pm_call

set_seg_base:
	rolw	$4, %ax
	movw	%ax, 2(%bp,%di)
	andw	$0xfff0, 2(%bp,%di)
	movb	%al, 4(%bp,%di)
	andb	$0x0f, 4(%bp,%di)
	ret
	.size set_seg_base, . - set_seg_base

#endif /* KEEP_IT_REAL */

/****************************************************************************
 * copy_bytes (real-mode or 16-bit protected-mode near call)
 *
 * Copy bytes
 *
 * Parameters:
 *   %ds:esi : source address
 *   %es:edi : destination address
 *   %ecx : length
 * Returns:
 *   %ds:esi : next source address
 *   %es:edi : next destination address
 * Corrupts:
 *   None
 ****************************************************************************
 */
	.section ".prefix.lib"
	.code16
copy_bytes:
	pushl %ecx
	rep addr32 movsb
	popl %ecx
	ret
	.size copy_bytes, . - copy_bytes

/****************************************************************************
 * install_block (real-mode near call)
 *
 * Install block to specified address
 *
 * Parameters:
 *   %esi : source physical address (must be a multiple of 16)
 *   %edi : destination physical address (must be a multiple of 16)
 *   %ecx : length of (decompressed) data
 *   %edx : total length of block (including any uninitialised data portion)
 * Returns:
 *   %esi : next source physical address (will be a multiple of 16)
 * Corrupts:
 *   none
 ****************************************************************************
 */
	.section ".prefix.lib"
	.code16
install_block:
	
#ifdef KEEP_IT_REAL

	/* Preserve registers */
	pushw	%ds
	pushw	%es
	pushl	%ecx
	pushl	%edi
	
	/* Convert %esi and %edi to segment registers */
	shrl	$4, %esi
	movw	%si, %ds
	xorw	%si, %si
	shrl	$4, %edi
	movw	%di, %es
	xorw	%di, %di

#else /* KEEP_IT_REAL */

	/* Call self in protected mode */
	pushw	%ax
	movw	$1f, %ax
	call	pm_call
	popw	%ax
	ret
1:
	/* Preserve registers */
	pushl	%ecx
	pushl	%edi
	
#endif /* KEEP_IT_REAL */

	
#if COMPRESS
	/* Decompress source to destination */
	call	decompress16
#else
	/* Copy source to destination */
	call	copy_bytes
#endif

	/* Zero .bss portion */
	negl	%ecx
	addl	%edx, %ecx
	pushw	%ax
	xorw	%ax, %ax
	rep addr32 stosb
	popw	%ax

	/* Round up %esi to start of next source block */
	addl	$0xf, %esi
	andl	$~0xf, %esi


#ifdef KEEP_IT_REAL

	/* Convert %ds:esi back to a physical address */
	movzwl	%ds, %cx
	shll	$4, %ecx
	addl	%ecx, %esi

	/* Restore registers */
	popl	%edi
	popl	%ecx
	popw	%es
	popw	%ds

#else /* KEEP_IT_REAL */

	/* Restore registers */
	popl	%edi
	popl	%ecx

#endif

	ret
	.size install_block, . - install_block
	
/****************************************************************************
 * alloc_basemem (real-mode near call)
 *
 * Allocate space for .text16 and .data16 from top of base memory.
 * Memory is allocated using the BIOS free base memory counter at
 * 0x40:13.
 *
 * Parameters: 
 *   none
 * Returns:
 *   %ax : .text16 segment address
 *   %bx : .data16 segment address
 * Corrupts:
 *   none
 ****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl	alloc_basemem
alloc_basemem:
	/* FBMS => %ax as segment address */
	movw	$0x40, %ax
	movw	%ax, %fs
	movw	%fs:0x13, %ax
	shlw	$6, %ax

	/* .data16 segment address */
	subw	$_data16_size_pgh, %ax
	pushw	%ax

	/* .text16 segment address */
	subw	$_text16_size_pgh, %ax
	pushw	%ax

	/* Update FBMS */
	shrw	$6, %ax
	movw	%ax, %fs:0x13

	/* Return */
	popw	%ax
	popw	%bx
	ret
	.size alloc_basemem, . - alloc_basemem

/****************************************************************************
 * install (real-mode near call)
 *
 * Install all text and data segments.
 *
 * Parameters:
 *   none
 * Returns:
 *   %ax  : .text16 segment address
 *   %bx  : .data16 segment address
 * Corrupts:
 *   none
 ****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl install
install:
	/* Preserve registers */
	pushl	%esi
	pushl	%edi
	/* Allocate space for .text16 and .data16 */
	call	alloc_basemem
	/* Image source = %cs:0000 */
	xorl	%esi, %esi
	/* Image destination = HIGHMEM_LOADPOINT */
	movl	$HIGHMEM_LOADPOINT, %edi
	/* Install text and data segments */
	call	install_prealloc
	/* Restore registers and return */
	popl	%edi
	popl	%esi
	ret
	.size install, . - install

/****************************************************************************
 * install_prealloc (real-mode near call)
 *
 * Install all text and data segments.
 *
 * Parameters:
 *   %ax  : .text16 segment address
 *   %bx  : .data16 segment address
 *   %esi : Image source physical address (or zero for %cs:0000)
 *   %edi : Decompression temporary area physical address
 * Corrupts:
 *   none
 ****************************************************************************
 */
	.section ".prefix.lib"
	.code16
	.globl install_prealloc
install_prealloc:
	/* Save registers */
	pushal
	pushw	%ds
	pushw	%es

	/* Sanity: clear the direction flag asap */
	cld

	/* Calculate physical address of payload (i.e. first source) */
	testl	%esi, %esi
	jnz	1f
	movw	%cs, %si
	shll	$4, %esi
1:	addl	$_payload_offset, %esi

	/* Install .text16 and .data16 */
	pushl	%edi
	movzwl	%ax, %edi
	shll	$4, %edi
	movl	$_text16_size, %ecx
	movl	%ecx, %edx
	call	install_block		/* .text16 */
	movzwl	%bx, %edi
	shll	$4, %edi
	movl	$_data16_progbits_size, %ecx
	movl	$_data16_size, %edx
	call	install_block		/* .data16 */
	popl	%edi

	/* Set up %ds for access to .data16 */
	movw	%bx, %ds

#ifdef KEEP_IT_REAL
	/* Initialise libkir */
	movw	%ax, (init_libkir_vector+2)
	lcall	*init_libkir_vector
#else
	/* Install .text and .data to temporary area in high memory,
	 * prior to reading the E820 memory map and relocating
	 * properly.
	 */
	movl	$_textdata_progbits_size, %ecx
	movl	$_textdata_size, %edx
	call	install_block

	/* Initialise librm at current location */
	movw	%ax, (init_librm_vector+2)
	lcall	*init_librm_vector

	/* Call relocate() to determine target address for relocation.
	 * relocate() will return with %esi, %edi and %ecx set up
	 * ready for the copy to the new location.
	 */
	movw	%ax, (prot_call_vector+2)
	pushl	$relocate
	lcall	*prot_call_vector
	popl	%edx /* discard */

	/* Copy code to new location */
	pushl	%edi
	pushw	%ax
	movw	$copy_bytes, %ax
	call	pm_call
	popw	%ax
	popl	%edi

	/* Initialise librm at new location */
	lcall	*init_librm_vector

#endif
	/* Restore registers */
	popw	%es
	popw	%ds
	popal
	ret
	.size install_prealloc, . - install_prealloc

	/* Vectors for far calls to .text16 functions */
	.section ".data16"
#ifdef KEEP_IT_REAL
init_libkir_vector:
	.word init_libkir
	.word 0
	.size init_libkir_vector, . - init_libkir_vector
#else
init_librm_vector:
	.word init_librm
	.word 0
	.size init_librm_vector, . - init_librm_vector
prot_call_vector:
	.word prot_call
	.word 0
	.size prot_call_vector, . - prot_call_vector
#endif


	/* File split information for the compressor */
#if COMPRESS
	.section ".zinfo", "a"
	.ascii	"COPY"
	.long	_prefix_load_offset
	.long	_prefix_progbits_size
	.long	_max_align
	.ascii	"PACK"
	.long	_text16_load_offset
	.long	_text16_progbits_size
	.long	_max_align
	.ascii	"PACK"
	.long	_data16_load_offset
	.long	_data16_progbits_size
	.long	_max_align
	.ascii	"PACK"
	.long	_textdata_load_offset
	.long	_textdata_progbits_size
	.long	_max_align
#else /* COMPRESS */
	.section ".zinfo", "a"
	.ascii	"COPY"
	.long	_prefix_load_offset
	.long	_load_size
	.long	_max_align
#endif /* COMPRESS */
